Audio Player siempre visible. Guarda por donde vas

This commit is contained in:
Tatiana Villa Ema 2026-05-17 23:44:53 +02:00
parent 7efaa919b6
commit 7211b678fd
4 changed files with 134 additions and 19 deletions

View File

@ -17,6 +17,7 @@
--warning: #d7ba7d;
--sidebar-w: 290px;
--topbar-h: 52px;
--audio-bar-h: 64px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
@ -589,6 +590,63 @@ a:hover { text-decoration: underline; }
accent-color: var(--accent);
}
/* ============================================================
AUDIO BAR barra fija inferior
============================================================ */
.audio-bar-fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
min-height: var(--audio-bar-h);
background: var(--bg-alt);
border-top: 3px solid var(--accent);
box-shadow: 0 -2px 12px rgba(0,0,0,.4);
z-index: 100;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: .6rem 1rem;
padding: .6rem 1.5rem;
transition: transform .3s ease;
}
.audio-bar-fixed.audio-bar-hidden {
transform: translateY(110%);
pointer-events: none;
}
.audio-bar-icon {
color: var(--accent);
font-size: 1rem;
flex-shrink: 0;
}
.audio-bar-title {
font-size: .75rem;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: .06em;
flex-shrink: 0;
}
.audio-bar-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .5rem;
flex: 1;
}
.audio-bar-item {
display: flex;
align-items: center;
gap: .5rem;
}
.audio-bar-fixed audio {
height: 32px;
accent-color: var(--accent);
}
body.has-audio-bar {
padding-bottom: var(--audio-bar-h);
}
/* ============================================================
CUESTIONARIOS
============================================================ */

View File

@ -115,20 +115,6 @@ function renderMarkdown(md, tema, bloque, bloqueId, temaNum) {
const html = marked.parse(md);
const audioHtml = (tema.audios && tema.audios.length)
? `<div class="tema-audio-bar">
<i class="fas fa-headphones"></i>
<span class="tema-audio-label">Escuchar este tema</span>
${tema.audios.map(a => `
<div class="tema-audio-item">
${tema.audios.length > 1 ? `<span class="tema-audio-tag">${a.label}</span>` : ''}
<audio controls preload="none">
<source src="${a.src}" type="audio/mpeg">
</audio>
</div>`).join('')}
</div>`
: '';
const leyesHtml = (tema.leyes && tema.leyes.length)
? `<div class="tema-leyes-bar">
<i class="fas fa-landmark"></i>
@ -142,7 +128,6 @@ function renderMarkdown(md, tema, bloque, bloqueId, temaNum) {
: '';
document.getElementById('lesson-content').innerHTML = `
${audioHtml}
${leyesHtml}
<div class="md-body">${html}</div>
<nav class="lesson-nav" aria-label="Navegación entre temas">
@ -159,6 +144,65 @@ function renderMarkdown(md, tema, bloque, bloqueId, temaNum) {
// Actualizar progress en topbar
updateTopbarProgress(pos, total);
// Barra de audio fija
updateAudioBar(tema, bloqueId, temaNum);
}
// ── Audio bar fija (bottom) ───────────────────────────────────
function updateAudioBar(tema, bloqueId, temaNum) {
const bar = document.getElementById('audio-bar');
const titleEl = document.getElementById('audio-bar-title');
const controlsEl = document.getElementById('audio-bar-controls');
if (!bar || !titleEl || !controlsEl) return;
// Parar cualquier audio en curso
bar.querySelectorAll('audio').forEach(a => a.pause());
if (!tema.audios || !tema.audios.length) {
bar.classList.add('audio-bar-hidden');
document.body.classList.remove('has-audio-bar');
return;
}
titleEl.textContent = `B${toRoman(bloqueId)} · T${temaNum}`;
controlsEl.innerHTML = tema.audios.map(a => `
<div class="audio-bar-item">
${tema.audios.length > 1 ? `<span class="tema-audio-tag">${a.label}</span>` : ''}
<audio controls preload="none" data-key="audioPos:${a.src}">
<source src="${a.src}" type="audio/mpeg">
</audio>
</div>`).join('');
// Restaurar y guardar posición por audio
controlsEl.querySelectorAll('audio').forEach(audioEl => {
const key = audioEl.dataset.key;
audioEl.addEventListener('loadedmetadata', () => {
const saved = parseFloat(localStorage.getItem(key) || '0');
if (saved > 1 && saved < audioEl.duration - 2) {
audioEl.currentTime = saved;
}
});
let _saveTimer = null;
audioEl.addEventListener('play', () => {
clearInterval(_saveTimer);
_saveTimer = setInterval(() => {
localStorage.setItem(key, audioEl.currentTime.toString());
}, 5000);
});
audioEl.addEventListener('pause', () => {
clearInterval(_saveTimer);
localStorage.setItem(key, audioEl.currentTime.toString());
});
audioEl.addEventListener('ended', () => {
clearInterval(_saveTimer);
localStorage.removeItem(key);
});
});
bar.classList.remove('audio-bar-hidden');
document.body.classList.add('has-audio-bar');
}
function showError(msg) {

View File

@ -322,9 +322,7 @@ Mayor seguridad frente a ataques.
---
## 8. Gestión de usuarios
### 8.1. Introducción
# 8. Gestión de usuarios
La gestión de usuarios consiste en administrar:
@ -388,7 +386,6 @@ Cada usuario debe tener:
- Solo los permisos necesarios.
---
## Tipos de permisos
@ -408,6 +405,15 @@ Ejecutar programas.
Administración completa.
## Linux/Unix
- rwx (lectura, escritura, ejecución)
- comandos: chmod, chown, chgrp
## Windows
- R (lectura)
- W (escritura)
- X (ejecución)
- gestión mediante ACLs (Access Control Lists)
---
# 11. Políticas de seguridad

View File

@ -56,6 +56,13 @@
</div>
<!-- ── Audio bar (fija, inferior) ──────────────────────── -->
<div id="audio-bar" class="audio-bar-fixed audio-bar-hidden" aria-label="Reproductor de audio">
<i class="fas fa-headphones audio-bar-icon"></i>
<span class="audio-bar-title" id="audio-bar-title"></span>
<div class="audio-bar-controls" id="audio-bar-controls"></div>
</div>
<!-- ── Scripts ────────────────────────────────────────── -->
<!-- marked.js para renderizar Markdown en el cliente -->
<script src="https://cdn.jsdelivr.net/npm/marked@12/marked.min.js"></script>