- /pedir/: calendario de fechas pedidas (negro) y confirmadas (verde), findes tranqui y con ruido, solo findes por defecto con opción de ver la semana entera. Se pincha un día libre y, sin sesión, se pide entrar y se vuelve al formulario con la fecha puesta. - Gestor en Go (gestor/): cuentas por colectivo con argon2id, sesiones y CSRF, cupo y ventana de la asamblea, caducidades, registro append-only. - La cuenta de la asamblea confirma, rechaza, cambia turnos de finde y en «Todas las fechas» quita o modifica cualquier evento. - Orden «cuenta» y «clave» con -clave y -definitiva para fijar contraseñas desde el servidor; ninguna contraseña va en el repositorio. - Botón «Pedir evento» arriba de la barra lateral en todas las páginas. - Calendario semanal en móvil: lista de días con nombre y hora de cada cita. - Fuentes servidas desde fuentes/: fuera Google Fonts en todas las páginas.
624 lines
23 KiB
HTML
624 lines
23 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="description" content="Cartelera del Tattoo Circus para proyectar en el local.">
|
|
<!-- Vista previa al compartir el enlace. -->
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:site_name" content="CSO La Enredadera de Tetuán">
|
|
<meta property="og:locale" content="es_ES">
|
|
<meta property="og:title" content="Tattoo Circus · Panel">
|
|
<meta property="og:description" content="El horario del fin de semana de jornadas, para proyectar.">
|
|
<meta property="og:image" content="/INFO/cso_laenre.jpeg">
|
|
<meta property="og:image:alt" content="Fachada del CSO La Enredadera: el cartel «La Enredadera» pintado a mano letra a letra sobre la puerta, y encima el mural «El último tesoro que le queda a la ciudad, es el barrio»">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="robots" content="noindex">
|
|
<meta name="referrer" content="no-referrer">
|
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'none'; object-src 'none'">
|
|
<title>Tattoo Circus · Panel</title>
|
|
<link rel="preload" href="/Code462WPink-Regular.ttf" as="font" type="font/ttf" crossorigin>
|
|
<style>
|
|
/* Este micrositio es oscuro por diseño. */
|
|
:root{ color-scheme: dark; }
|
|
|
|
/* Fuentes servidas desde laenre.net, sin Google Fonts. */
|
|
@font-face {
|
|
font-family: 'Anton';
|
|
src: url('/fuentes/anton-latin.woff2') format('woff2');
|
|
font-weight: 400; font-style: normal; font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: 'Special Elite';
|
|
src: url('/fuentes/specialelite-latin.woff2') format('woff2');
|
|
font-weight: 400; font-style: normal; font-display: swap;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'Code462WPink';
|
|
src: url('/Code462WPink-Regular.ttf') format('truetype');
|
|
font-weight: normal;
|
|
font-style: normal;
|
|
font-display: swap;
|
|
}
|
|
|
|
:root{
|
|
--neon: #E84A1F;
|
|
--cream: #f4ecd8;
|
|
--black: #0a0a0a;
|
|
--divider: rgba(232,74,31,.35);
|
|
}
|
|
|
|
*{ box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
html, body{
|
|
height: 100vh;
|
|
width: 100vw;
|
|
overflow: hidden;
|
|
background: var(--black);
|
|
font-family: 'Special Elite', 'Courier New', monospace;
|
|
color: var(--neon);
|
|
}
|
|
|
|
body{
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* ============================================================
|
|
HEADER FIJO — banner crema con el título principal
|
|
Altura controlada: ~12-14% del viewport
|
|
============================================================ */
|
|
header.title{
|
|
background: var(--cream);
|
|
color: #000;
|
|
text-align: center;
|
|
padding: clamp(8px, 1.6vh, 26px) 2vw;
|
|
flex-shrink: 0;
|
|
border-bottom: 3px solid #000;
|
|
}
|
|
header.title h1{
|
|
font-family: 'Code462WPink', cursive;
|
|
font-size: clamp(2.2rem, 5.5vw, 7rem);
|
|
letter-spacing: clamp(6px, 1.2vw, 24px);
|
|
text-transform: uppercase;
|
|
line-height: 1;
|
|
color: #000;
|
|
}
|
|
header.title .sub{
|
|
margin-top: clamp(4px, .6vh, 12px);
|
|
font-family: 'Anton', Impact, sans-serif;
|
|
font-size: clamp(.75rem, 1.1vw, 1.6rem);
|
|
letter-spacing: clamp(3px, .6vw, 12px);
|
|
text-transform: uppercase;
|
|
color: #000;
|
|
opacity: .6;
|
|
}
|
|
|
|
/* ============================================================
|
|
DAY BANNER — banner negro con el día actual
|
|
============================================================ */
|
|
.day-banner{
|
|
background: var(--black);
|
|
color: var(--neon);
|
|
text-align: center;
|
|
padding: clamp(8px, 1.4vh, 24px) 2vw;
|
|
font-family: 'Anton', Impact, sans-serif;
|
|
font-size: clamp(1.8rem, 4vw, 5rem);
|
|
letter-spacing: clamp(6px, 1.3vw, 22px);
|
|
text-transform: uppercase;
|
|
text-shadow: 0 0 12px rgba(232,74,31,.7);
|
|
border-bottom: 2px solid var(--neon);
|
|
flex-shrink: 0;
|
|
transition: opacity .4s ease;
|
|
}
|
|
|
|
/* ============================================================
|
|
SCHEDULE — contenedor de filas que rellena el espacio restante
|
|
Cada .row distribuye verticalmente con flex:1
|
|
============================================================ */
|
|
.schedule{
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
transition: opacity .4s ease;
|
|
min-height: 0;
|
|
}
|
|
|
|
.row{
|
|
flex: 1 1 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: clamp(100px, 11vw, 220px) 1fr clamp(110px, 11vw, 200px);
|
|
column-gap: clamp(16px, 2.2vw, 50px);
|
|
padding: clamp(8px, 1.2vh, 20px) clamp(20px, 3vw, 60px);
|
|
align-items: center;
|
|
border-bottom: 1px solid var(--divider);
|
|
overflow: hidden;
|
|
}
|
|
.row:last-child{ border-bottom: none; }
|
|
|
|
/* alternancia de fondos */
|
|
.row:nth-child(odd){
|
|
background: var(--cream);
|
|
color: #000;
|
|
}
|
|
.row:nth-child(even){
|
|
background: var(--black);
|
|
color: var(--neon);
|
|
}
|
|
|
|
/* ============================================================
|
|
HORA (columna izquierda)
|
|
============================================================ */
|
|
.row .time{
|
|
font-family: 'Anton', Impact, sans-serif;
|
|
font-size: clamp(1.4rem, 2.4vw, 3rem);
|
|
letter-spacing: 1.5px;
|
|
font-weight: 700;
|
|
white-space: nowrap;
|
|
line-height: 1;
|
|
}
|
|
.row:nth-child(odd) .time{ color: #000; }
|
|
.row:nth-child(even) .time{
|
|
color: var(--neon);
|
|
text-shadow: 0 0 10px rgba(232,74,31,.6);
|
|
}
|
|
|
|
/* ============================================================
|
|
ACTIVIDAD (columna derecha) — title + detail + crew
|
|
Tipografía bien jerarquizada y line-height ajustado
|
|
============================================================ */
|
|
.row .act{
|
|
font-size: clamp(.9rem, 1.3vw, 1.7rem);
|
|
line-height: 1.3;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
.row .act .title{
|
|
display: block;
|
|
font-family: 'Anton', Impact, sans-serif;
|
|
font-weight: 700;
|
|
font-size: 1.15em;
|
|
letter-spacing: clamp(1px, .12vw, 3px);
|
|
text-transform: uppercase;
|
|
line-height: 1.18;
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
}
|
|
.row .act .detail{
|
|
display: block;
|
|
margin-top: .35em;
|
|
font-size: .8em;
|
|
line-height: 1.25;
|
|
/* atenuarlo bajaba el naranja a 3.6:1 sobre el negro */
|
|
font-style: italic;
|
|
}
|
|
.row .act .crew{
|
|
display: block;
|
|
margin-top: .3em;
|
|
font-size: .72em;
|
|
line-height: 1.25;
|
|
opacity: .65;
|
|
}
|
|
|
|
/* ============================================================
|
|
HIGHLIGHT — badge para conciertos / cabaret / freakshow
|
|
============================================================ */
|
|
.row .act .highlight{
|
|
display: inline-block;
|
|
background: var(--neon);
|
|
color: #000;
|
|
padding: 3px 14px;
|
|
border-radius: 4px;
|
|
box-shadow: 0 0 10px rgba(232,74,31,.6);
|
|
letter-spacing: clamp(2px, .25vw, 5px);
|
|
}
|
|
.row:nth-child(odd) .act .highlight{
|
|
background: #000;
|
|
color: var(--cream);
|
|
box-shadow: none;
|
|
}
|
|
|
|
/* ============================================================
|
|
PLANTA (columna derecha) — badge que indica el piso
|
|
============================================================ */
|
|
.row .planta{
|
|
justify-self: end;
|
|
text-align: center;
|
|
font-family: 'Anton', Impact, sans-serif;
|
|
font-size: clamp(.75rem, 1vw, 1.25rem);
|
|
letter-spacing: clamp(1px, .15vw, 3px);
|
|
text-transform: uppercase;
|
|
line-height: 1.1;
|
|
padding: clamp(4px, .6vh, 9px) clamp(8px, 1vw, 16px);
|
|
border: 2px solid currentColor;
|
|
border-radius: 6px;
|
|
white-space: nowrap;
|
|
}
|
|
.row:nth-child(odd) .planta{ color: #000; }
|
|
.row:nth-child(even) .planta{
|
|
color: var(--neon);
|
|
box-shadow: 0 0 8px rgba(232,74,31,.4);
|
|
}
|
|
|
|
/* ============================================================
|
|
PLANTAS SLIDE — 4 rectángulos anchos a pantalla completa
|
|
============================================================ */
|
|
.plantas-slide{
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
.planta-card{
|
|
flex: 1 1 0;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
gap: clamp(6px, 1.2vh, 16px);
|
|
padding: clamp(10px, 1.8vh, 28px) clamp(24px, 4vw, 70px);
|
|
border-bottom: 1px solid var(--divider);
|
|
overflow: hidden;
|
|
}
|
|
.planta-card:last-child{ border-bottom: none; }
|
|
.planta-card:nth-child(odd){ background: var(--cream); color: #000; }
|
|
.planta-card:nth-child(even){ background: var(--black); color: var(--neon); }
|
|
|
|
.planta-head{
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: clamp(10px, 1.5vw, 26px);
|
|
flex-wrap: wrap;
|
|
}
|
|
.planta-name{
|
|
font-family: 'Anton', Impact, sans-serif;
|
|
font-size: clamp(1.6rem, 3.2vw, 4rem);
|
|
letter-spacing: clamp(2px, .5vw, 10px);
|
|
text-transform: uppercase;
|
|
line-height: 1;
|
|
}
|
|
.planta-card:nth-child(even) .planta-name{ text-shadow: 0 0 12px rgba(232,74,31,.6); }
|
|
.planta-tag{
|
|
font-family: 'Anton', Impact, sans-serif;
|
|
font-size: clamp(.85rem, 1.4vw, 1.8rem);
|
|
letter-spacing: clamp(1px, .2vw, 4px);
|
|
text-transform: uppercase;
|
|
opacity: .65;
|
|
}
|
|
.planta-items{
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: clamp(8px, 1.2vw, 20px);
|
|
}
|
|
.planta-item{
|
|
font-size: clamp(.9rem, 1.5vw, 2rem);
|
|
line-height: 1.15;
|
|
letter-spacing: .5px;
|
|
padding: clamp(4px, .7vh, 10px) clamp(10px, 1.3vw, 22px);
|
|
border: 2px solid currentColor;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
/* ============================================================
|
|
BREAKPOINTS por tamaño de pantalla
|
|
============================================================ */
|
|
|
|
/* Laptops 1366x768 — más compacto, hueco para ~10 filas en 768px */
|
|
@media (max-width: 1440px) and (max-height: 900px){
|
|
header.title{ padding: clamp(5px, 1vh, 12px) 2vw; }
|
|
header.title h1{ font-size: clamp(1.7rem, 4vw, 3.2rem); letter-spacing: 5px; }
|
|
header.title .sub{ font-size: .72rem; letter-spacing: 3px; margin-top: 2px; }
|
|
.day-banner{ font-size: clamp(1.3rem, 3vw, 2.2rem); padding: 5px 2vw; letter-spacing: clamp(4px, 1vw, 14px); }
|
|
.row{
|
|
grid-template-columns: clamp(74px, 8vw, 120px) 1fr clamp(96px, 9vw, 150px);
|
|
column-gap: clamp(10px, 1.6vw, 26px);
|
|
padding: 3px clamp(14px, 2.2vw, 36px);
|
|
}
|
|
.row .time{ font-size: clamp(1rem, 1.7vw, 1.6rem); }
|
|
.row .act{ font-size: clamp(.78rem, 1vw, 1rem); line-height: 1.16; }
|
|
.row .act .title{ font-size: 1.02em; line-height: 1.1; letter-spacing: .5px; }
|
|
.row .act .detail{ font-size: .76em; margin-top: .12em; line-height: 1.12; }
|
|
.row .act .crew{ font-size: .68em; margin-top: .1em; line-height: 1.12; }
|
|
.row .act .highlight{ padding: 2px 10px; }
|
|
.row .planta{ font-size: clamp(.6rem, .8vw, .9rem); letter-spacing: 1px; padding: 3px 7px; }
|
|
}
|
|
|
|
/* Portátiles muy cortos (≤720px de alto): apretar aún más para que nada se corte */
|
|
@media (max-width: 1440px) and (max-height: 720px){
|
|
header.title h1{ font-size: clamp(1.4rem, 3.4vw, 2.4rem); letter-spacing: 4px; }
|
|
header.title .sub{ font-size: .62rem; letter-spacing: 2px; }
|
|
.day-banner{ font-size: clamp(1.1rem, 2.6vw, 1.8rem); padding: 3px 2vw; }
|
|
.row{ padding: 2px clamp(12px, 2vw, 30px); }
|
|
.row .act{ font-size: clamp(.72rem, .95vw, .92rem); line-height: 1.12; }
|
|
.row .act .detail{ font-size: .74em; }
|
|
.row .act .crew{ font-size: .66em; }
|
|
}
|
|
|
|
/* QHD (1440p) — escala intermedia agradable */
|
|
@media (min-width: 2000px) and (max-width: 2800px){
|
|
header.title h1{ font-size: clamp(4rem, 5.5vw, 8rem); }
|
|
.day-banner{ font-size: clamp(3rem, 4.2vw, 6rem); }
|
|
.row .time{ font-size: clamp(2.2rem, 2.5vw, 3.6rem); }
|
|
.row .act{ font-size: clamp(1.3rem, 1.4vw, 1.9rem); }
|
|
}
|
|
|
|
/* 4K / TV grande (≥2800px) — escala generosa */
|
|
@media (min-width: 2800px){
|
|
header.title h1{ font-size: clamp(6rem, 5.8vw, 11rem); letter-spacing: 28px; }
|
|
.day-banner{ font-size: clamp(4.5rem, 5vw, 8rem); letter-spacing: 26px; }
|
|
.row .time{ font-size: clamp(3rem, 2.8vw, 4.5rem); }
|
|
.row .act{ font-size: clamp(1.6rem, 1.5vw, 2.3rem); }
|
|
}
|
|
|
|
/* Ventana realmente pequeña (alguien abre el panel en laptop pequeña/tablet) */
|
|
/* En móvil esto deja de ser una proyección. El panel mete todo el
|
|
horario en una pantalla a la fuerza (filas de alto flexible con
|
|
overflow oculto), y a 320px la charla de título más largo perdía 23px
|
|
de texto sin avisar. Aquí las filas crecen lo que necesiten y el
|
|
horario se desliza, que es lo propio de un teléfono. */
|
|
@media (max-width: 480px){
|
|
.schedule{ overflow-y: auto; }
|
|
.row{ flex: 0 0 auto; overflow: visible; }
|
|
}
|
|
|
|
@media (max-width: 900px){
|
|
header.title h1{ font-size: clamp(1.8rem, 6vw, 3rem); letter-spacing: 3px; }
|
|
.day-banner{ font-size: clamp(1.4rem, 5vw, 2.2rem); letter-spacing: 4px; padding: 5px 2vw; }
|
|
.row{ grid-template-columns: 64px 1fr auto; column-gap: 8px; padding: 5px 12px; }
|
|
.row .time{ font-size: .95rem; }
|
|
.row .act{ font-size: .8rem; line-height: 1.2; }
|
|
.row .act .title{ font-size: 1em; line-height: 1.1; }
|
|
.row .act .detail, .row .act .crew{ font-size: .72em; margin-top: .15em; }
|
|
.row .planta{ font-size: .62rem; letter-spacing: 1px; padding: 3px 6px; border-width: 1px; }
|
|
.planta-card{ padding: 8px 14px; gap: 6px; }
|
|
.planta-name{ font-size: 1.4rem; letter-spacing: 2px; }
|
|
.planta-tag{ font-size: .8rem; }
|
|
.planta-item{ font-size: .8rem; padding: 3px 8px; border-width: 1px; }
|
|
}
|
|
|
|
/* ===== IMAGE SLIDE — PANTALLA COMPLETA ===== */
|
|
/* Cuando el body tiene .image-mode, oculta TODO menos la imagen */
|
|
body.image-mode header.title,
|
|
body.image-mode .day-banner,
|
|
body.image-mode .progress{
|
|
display: none;
|
|
}
|
|
body.image-mode .schedule{
|
|
height: 100vh;
|
|
}
|
|
|
|
.image-slide{
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0;
|
|
background: #000;
|
|
overflow: hidden;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.image-slide img{
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
width: auto;
|
|
height: auto;
|
|
object-fit: contain;
|
|
/* hints para renderizar lo más nítido posible al escalar */
|
|
image-rendering: -webkit-optimize-contrast;
|
|
image-rendering: crisp-edges;
|
|
image-rendering: high-quality;
|
|
}
|
|
|
|
/* ===== FADE TRANSITIONS ===== */
|
|
.fading{ opacity: 0; }
|
|
|
|
/* ===== PROGRESS BAR ===== */
|
|
.progress{
|
|
height: 8px;
|
|
background: rgba(232,74,31,.12);
|
|
flex-shrink: 0;
|
|
position: relative;
|
|
border-top: 1px solid rgba(232,74,31,.3);
|
|
}
|
|
.progress-bar{
|
|
height: 100%;
|
|
background: var(--neon);
|
|
width: 0%;
|
|
box-shadow: 0 0 14px var(--neon);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header class="title">
|
|
<h1>TATTOO CIRCUS</h1>
|
|
<div class="sub">★ La Enredadera de Tetuán · Fin de semana de jornadas ★</div>
|
|
</header>
|
|
|
|
<div class="day-banner" id="day-banner">VIERNES</div>
|
|
|
|
<div class="schedule" id="schedule"></div>
|
|
|
|
<div class="progress">
|
|
<div class="progress-bar" id="progress-bar"></div>
|
|
</div>
|
|
|
|
<script>
|
|
const FADE_MS = 400;
|
|
|
|
const SLIDES = [
|
|
{
|
|
type: 'day',
|
|
day: 'VIERNES',
|
|
duration: 10000,
|
|
slots: [
|
|
{ time: '16:00', title: 'Apertura', planta: 'Planta Baja' },
|
|
{ time: '16:30', title: 'Taller de carteo a presxs', detail: 'Agrietando los muros de prisión', planta: 'Planta Baja' },
|
|
{ time: '18:00', title: '¿Se puede regularizar la vida en un sistema colonial?', detail: 'Dinámicas para entender el proceso de documentación desde una perspectiva decolonial y antifascista', crew: 'Articulación Antirracista de Madrid · Sindicato de Manteros', planta: 'Planta Baja' },
|
|
{ time: '20:00', title: 'Cena', planta: 'Planta Primera' },
|
|
{ time: '20:30', title: 'Cabaret de Circo', highlight: true, planta: 'Planta Primera' },
|
|
{ time: '23:00', title: 'Cierre', planta: 'Planta Primera' }
|
|
]
|
|
},
|
|
{
|
|
type: 'day',
|
|
day: 'SÁBADO',
|
|
duration: 10000,
|
|
slots: [
|
|
{ time: '11:30', title: 'Apertura', planta: 'Planta Baja' },
|
|
{ time: '12:00', title: 'Manifestación Orgullo Loco', detail: 'Centro de salud mental Tetuán → Ni terapia, ni castigo, ni explotación', planta: 'Exterior' },
|
|
{ time: '14:00', title: 'Comida', planta: 'Planta Primera' },
|
|
{ time: '16:00', title: 'Contra el psistema productivo', detail: 'Manicomios, orfanatos, asilos y otras instituciones de control → INSANIA', planta: 'Planta Baja' },
|
|
{ time: '18:00', title: 'Salir con vida', detail: 'Autobiografía de un preso en lucha → Toni Chavero', planta: 'Planta Baja' },
|
|
{ time: '20:00', title: 'Cena', planta: 'Planta Primera' },
|
|
{ time: '20:30', title: 'Conciertos', highlight: true, planta: 'Planta Baja' },
|
|
{ time: '23:00', title: 'Cierre', planta: 'Planta Baja' }
|
|
]
|
|
},
|
|
{
|
|
type: 'day',
|
|
day: 'DOMINGO',
|
|
duration: 10000,
|
|
slots: [
|
|
{ time: '11:30', title: 'Apertura', planta: 'Planta Baja' },
|
|
{ time: '12:00', title: 'Las familias y la cárcel', crew: 'Coordinadora de Familias y Presxs · Famílies de Presos de Catalunya', planta: 'Planta Baja' },
|
|
{ time: '14:00', title: 'Comida', planta: 'Planta Primera' },
|
|
{ time: '16:00', title: 'Más fuertes que la muerte y la prisión', detail: 'Represión anti-anarquista a nivel internacional', planta: 'Planta Baja' },
|
|
{ time: '18:00', title: 'Taller de identificación policial', detail: 'Identificar policía y proteger derechos en protestas → LEGAL SOL', planta: 'Planta Baja' },
|
|
{ time: '19:30', title: 'Flamenco', detail: 'Un quejío contra los muros', planta: 'Planta Primera' },
|
|
{ time: '20:00', title: 'Cena', planta: 'Planta Primera' },
|
|
{ time: '20:30', title: 'Freakshow', highlight: true, planta: 'Planta Primera' },
|
|
{ time: '22:00', title: 'Rifa', planta: 'Planta Primera' }
|
|
]
|
|
},
|
|
{
|
|
type: 'plantas',
|
|
day: 'MAPA DE PLANTAS',
|
|
duration: 12000,
|
|
floors: [
|
|
{ name: 'PLANTA BAJA', items: ['Charlas', 'Conciertos/DJ', 'Carteo a presxs', 'Baños'] },
|
|
{ name: 'PLANTA PRIMERA', tag: 'Villapaloma', items: ['Barra', 'Distris', 'Espectáculos', 'Baños'] },
|
|
{ name: 'PLANTA DOS', items: ['Citas de Tattoo', 'Piercing', 'Masaje', 'Gemas dentales', 'Peluquería'] },
|
|
{ name: 'PLANTA TRES', items: ['Tattoo', 'Piercing', 'Gemas dentales'] }
|
|
]
|
|
}
|
|
];
|
|
|
|
const dayEl = document.getElementById('day-banner');
|
|
const scheduleEl = document.getElementById('schedule');
|
|
const progressEl = document.getElementById('progress-bar');
|
|
|
|
// Utilidad: crea un elemento con clase y texto. textContent NO interpreta HTML —
|
|
// garantiza que aunque cambien los datos en el futuro no se pueda inyectar markup.
|
|
function el(tag, className, text){
|
|
const n = document.createElement(tag);
|
|
if (className) n.className = className;
|
|
if (text != null) n.textContent = text;
|
|
return n;
|
|
}
|
|
|
|
function clearChildren(parent){
|
|
while (parent.firstChild) parent.removeChild(parent.firstChild);
|
|
}
|
|
|
|
function renderSlide(slide){
|
|
clearChildren(scheduleEl);
|
|
|
|
if (slide.type === 'image'){
|
|
document.body.classList.add('image-mode');
|
|
const wrap = el('div', 'image-slide');
|
|
const img = document.createElement('img');
|
|
img.src = slide.src;
|
|
img.alt = 'Cartel Tattoo Circus';
|
|
wrap.appendChild(img);
|
|
scheduleEl.appendChild(wrap);
|
|
return;
|
|
}
|
|
|
|
document.body.classList.remove('image-mode');
|
|
dayEl.textContent = slide.day;
|
|
|
|
if (slide.type === 'plantas'){
|
|
const wrap = el('div', 'plantas-slide');
|
|
slide.floors.forEach(f => {
|
|
const card = el('div', 'planta-card');
|
|
|
|
const head = el('div', 'planta-head');
|
|
head.appendChild(el('span', 'planta-name', f.name));
|
|
if (f.tag) head.appendChild(el('span', 'planta-tag', f.tag));
|
|
card.appendChild(head);
|
|
|
|
const list = el('div', 'planta-items');
|
|
f.items.forEach(it => list.appendChild(el('span', 'planta-item', it)));
|
|
card.appendChild(list);
|
|
|
|
wrap.appendChild(card);
|
|
});
|
|
scheduleEl.appendChild(wrap);
|
|
return;
|
|
}
|
|
|
|
slide.slots.forEach(s => {
|
|
const row = el('div', 'row');
|
|
row.appendChild(el('div', 'time', s.time));
|
|
|
|
const act = el('div', 'act');
|
|
const titleSpan = el('span', 'title');
|
|
if (s.highlight){
|
|
titleSpan.appendChild(el('span', 'highlight', s.title));
|
|
} else {
|
|
titleSpan.textContent = s.title;
|
|
}
|
|
act.appendChild(titleSpan);
|
|
|
|
if (s.detail) act.appendChild(el('span', 'detail', s.detail));
|
|
if (s.crew) act.appendChild(el('span', 'crew', s.crew));
|
|
|
|
row.appendChild(act);
|
|
|
|
if (s.planta) row.appendChild(el('div', 'planta', s.planta));
|
|
|
|
scheduleEl.appendChild(row);
|
|
});
|
|
}
|
|
|
|
function startProgress(duration){
|
|
progressEl.style.transition = 'none';
|
|
progressEl.style.width = '0%';
|
|
// forzar reflow
|
|
void progressEl.offsetWidth;
|
|
progressEl.style.transition = `width ${duration}ms linear`;
|
|
progressEl.style.width = '100%';
|
|
}
|
|
|
|
let idx = 0; // empieza con el cartel
|
|
|
|
function tick(){
|
|
scheduleEl.classList.add('fading');
|
|
dayEl.classList.add('fading');
|
|
setTimeout(() => {
|
|
idx = (idx + 1) % SLIDES.length;
|
|
const slide = SLIDES[idx];
|
|
renderSlide(slide);
|
|
scheduleEl.classList.remove('fading');
|
|
dayEl.classList.remove('fading');
|
|
startProgress(slide.duration);
|
|
setTimeout(tick, slide.duration);
|
|
}, FADE_MS);
|
|
}
|
|
|
|
// primera slide (sin fade)
|
|
const first = SLIDES[idx];
|
|
renderSlide(first);
|
|
startProgress(first.duration);
|
|
setTimeout(tick, first.duration);
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|