Audio Player siempre visible. Guarda por donde vas
This commit is contained in:
parent
7efaa919b6
commit
7211b678fd
|
|
@ -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
|
||||
============================================================ */
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue