Miniaturas HD, formulario de patrocinadores y sección Lo más visto
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TSyR5Zw5eGj6oPTRVjvP1b
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
e8b1f765c8
commit
c8afac0f6d
@@ -1,5 +1,6 @@
|
|||||||
"""Genera index.html a partir del feed RSS del canal (se ejecuta cada 15 min en el contenedor)."""
|
"""Genera index.html a partir del feed RSS del canal (se ejecuta cada 15 min en el contenedor)."""
|
||||||
import html
|
import html
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import unicodedata
|
import unicodedata
|
||||||
@@ -9,6 +10,8 @@ import sys
|
|||||||
import urllib.request
|
import urllib.request
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
OUT = os.environ.get('SITE_DIR', 'public')
|
OUT = os.environ.get('SITE_DIR', 'public')
|
||||||
CHANNEL = 'UC8gVtzB3Lqm7nnfMhMvXGxQ'
|
CHANNEL = 'UC8gVtzB3Lqm7nnfMhMvXGxQ'
|
||||||
# YouTube separa cada tipo en su propia lista: UULF = solo vídeos, UUSH = solo Shorts
|
# YouTube separa cada tipo en su propia lista: UULF = solo vídeos, UUSH = solo Shorts
|
||||||
@@ -62,28 +65,60 @@ pop_videos = sorted(pop_videos, key=lambda v: -v['views'])[:POP_VIDEOS_SHOWN]
|
|||||||
pop_shorts = sorted(pop_shorts, key=lambda v: -v['views'])[:POP_SHORTS_SHOWN]
|
pop_shorts = sorted(pop_shorts, key=lambda v: -v['views'])[:POP_SHORTS_SHOWN]
|
||||||
|
|
||||||
|
|
||||||
def sync_thumbs(vids):
|
KEEP = {'avatar.jpg', 'banner.jpg'}
|
||||||
|
# (nombre en YouTube, ancho máx. al guardar) por orden de preferencia
|
||||||
|
SOURCES = {
|
||||||
|
'video': [('maxresdefault', 1280), ('sddefault', 960), ('hqdefault', 960)],
|
||||||
|
'short': [('oar2', 540), ('hqdefault', 480)], # oar2 = miniatura vertical 1080x1920
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def grab(vid, kind):
|
||||||
|
"""Devuelve (bytes JPEG recomprimido, es_alta_calidad) con la mejor miniatura disponible."""
|
||||||
|
for i, (name, width) in enumerate(SOURCES[kind]):
|
||||||
|
try:
|
||||||
|
im = Image.open(io.BytesIO(fetch(f'https://i.ytimg.com/vi/{vid}/{name}.jpg'))).convert('RGB')
|
||||||
|
except Exception:
|
||||||
|
continue # 404 u otro fallo: se prueba la siguiente
|
||||||
|
if im.width < 200: # YouTube devuelve un gif de 120x90 cuando no existe
|
||||||
|
continue
|
||||||
|
if im.width > width:
|
||||||
|
im = im.resize((width, round(im.height * width / im.width)), Image.LANCZOS)
|
||||||
|
buf = io.BytesIO()
|
||||||
|
im.save(buf, 'JPEG', quality=82, optimize=True, progressive=True)
|
||||||
|
return buf.getvalue(), i == 0
|
||||||
|
return None, False
|
||||||
|
|
||||||
|
|
||||||
|
def sync_thumbs(vids, kind):
|
||||||
|
"""Descarga la miniatura HD de cada vídeo (…-hd.jpg). Si aún no existe la HD, usa una
|
||||||
|
de respaldo (…-lo.jpg) y en la siguiente ejecución vuelve a intentar la HD."""
|
||||||
img_dir = os.path.join(OUT, 'img')
|
img_dir = os.path.join(OUT, 'img')
|
||||||
os.makedirs(img_dir, exist_ok=True)
|
os.makedirs(img_dir, exist_ok=True)
|
||||||
keep = {'avatar.jpg', 'banner.jpg'}
|
|
||||||
ok = []
|
ok = []
|
||||||
for v in vids:
|
for v in vids:
|
||||||
path = os.path.join(img_dir, v['id'] + '.jpg')
|
hd, lo = f"{v['id']}-hd.jpg", f"{v['id']}-lo.jpg"
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(os.path.join(img_dir, hd)):
|
||||||
try:
|
data, is_hd = grab(v['id'], kind)
|
||||||
open(path, 'wb').write(fetch(f"https://i.ytimg.com/vi/{v['id']}/hqdefault.jpg"))
|
if data is None:
|
||||||
except Exception as exc:
|
print('miniatura no descargada', v['id'], file=sys.stderr)
|
||||||
print('miniatura no descargada', v['id'], exc, file=sys.stderr)
|
|
||||||
continue
|
continue
|
||||||
keep.add(v['id'] + '.jpg')
|
open(os.path.join(img_dir, hd if is_hd else lo), 'wb').write(data)
|
||||||
|
v['img'] = hd if os.path.exists(os.path.join(img_dir, hd)) else lo
|
||||||
|
KEEP.add(v['img'])
|
||||||
ok.append(v)
|
ok.append(v)
|
||||||
for f in os.listdir(img_dir):
|
|
||||||
if f not in keep:
|
|
||||||
os.remove(os.path.join(img_dir, f))
|
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
|
|
||||||
ok = sync_thumbs(videos + shorts + pop_videos + pop_shorts)
|
def prune_thumbs():
|
||||||
|
img_dir = os.path.join(OUT, 'img')
|
||||||
|
for f in os.listdir(img_dir):
|
||||||
|
if f not in KEEP:
|
||||||
|
os.remove(os.path.join(img_dir, f))
|
||||||
|
|
||||||
|
|
||||||
|
ok = sync_thumbs(videos + pop_videos, 'video') + sync_thumbs(shorts + pop_shorts, 'short')
|
||||||
|
prune_thumbs()
|
||||||
okids = {v['id'] for v in ok}
|
okids = {v['id'] for v in ok}
|
||||||
videos = [v for v in videos if v['id'] in okids]
|
videos = [v for v in videos if v['id'] in okids]
|
||||||
shorts = [v for v in shorts if v['id'] in okids]
|
shorts = [v for v in shorts if v['id'] in okids]
|
||||||
@@ -147,7 +182,7 @@ def vistas(v):
|
|||||||
def card(v, n, show_views=False):
|
def card(v, n, show_views=False):
|
||||||
return f'''
|
return f'''
|
||||||
<a class="card reveal" style="--d:{(n % 3) * 70}ms" href="{url(v)}" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:{(n % 3) * 70}ms" href="{url(v)}" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/{v['id']}.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/{v['img']}" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>{esc(v['date'])}{' · ' + vistas(v) if show_views and v['views'] else ''}</time><strong>{esc(v['title'])}</strong></span>
|
<span class="meta"><time>{esc(v['date'])}{' · ' + vistas(v) if show_views and v['views'] else ''}</time><strong>{esc(v['title'])}</strong></span>
|
||||||
</a>'''
|
</a>'''
|
||||||
|
|
||||||
@@ -155,7 +190,7 @@ def card(v, n, show_views=False):
|
|||||||
def short_card(v, n, show_views=False):
|
def short_card(v, n, show_views=False):
|
||||||
return f'''
|
return f'''
|
||||||
<a class="scard reveal" style="--d:{(n % 6) * 50}ms" href="https://www.youtube.com/shorts/{v['id']}" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:{(n % 6) * 50}ms" href="https://www.youtube.com/shorts/{v['id']}" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/{v['id']}.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/{v['img']}" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>{esc(v['title'])}</strong>{'<time>' + vistas(v) + '</time>' if show_views and v['views'] else ''}</span>
|
<span class="meta"><strong>{esc(v['title'])}</strong>{'<time>' + vistas(v) + '</time>' if show_views and v['views'] else ''}</span>
|
||||||
</a>'''
|
</a>'''
|
||||||
|
|
||||||
@@ -213,7 +248,7 @@ page = f'''<!doctype html>
|
|||||||
|
|
||||||
<section id="destacado" class="featured wrap reveal">
|
<section id="destacado" class="featured wrap reveal">
|
||||||
<a class="feat" href="{url(featured)}" target="_blank" rel="noopener">
|
<a class="feat" href="{url(featured)}" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/{featured['id']}.jpg" alt="" width="480" height="360"><span class="play big" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/{featured['img']}" alt="" width="1280" height="720"><span class="play big" aria-hidden="true"></span></span>
|
||||||
<span class="feat-text">
|
<span class="feat-text">
|
||||||
<span class="tag">Último vídeo</span>
|
<span class="tag">Último vídeo</span>
|
||||||
<strong>{esc(featured['title'])}</strong>
|
<strong>{esc(featured['title'])}</strong>
|
||||||
@@ -272,7 +307,7 @@ page = f'''<!doctype html>
|
|||||||
<section class="wrap reveal" aria-labelledby="venir">
|
<section class="wrap reveal" aria-labelledby="venir">
|
||||||
<div class="invite">
|
<div class="invite">
|
||||||
<div><h2 id="venir">¿Quieres venir al programa?</h2><p>Cuéntanos quién eres y de qué te gustaría hablar.</p></div>
|
<div><h2 id="venir">¿Quieres venir al programa?</h2><p>Cuéntanos quién eres y de qué te gustaría hablar.</p></div>
|
||||||
<a class="btn primary" href="contacto.html">Escríbenos</a>
|
<div class="cta"><a class="btn primary" href="contacto.html">Escríbenos</a><a class="btn" href="patrocinadores.html">Quiero anunciarme</a></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -289,7 +324,7 @@ page = f'''<!doctype html>
|
|||||||
<footer>
|
<footer>
|
||||||
<div class="wrap foot">
|
<div class="wrap foot">
|
||||||
<p>© 2026 Anda Que No Sabes Ná · Granada</p>
|
<p>© 2026 Anda Que No Sabes Ná · Granada</p>
|
||||||
<p class="links"><a href="privacidad.html">Política de privacidad</a><a href="terminos.html">Condiciones del servicio</a><a href="contacto.html">Contacto</a></p>
|
<p class="links"><a href="privacidad.html">Política de privacidad</a><a href="terminos.html">Condiciones del servicio</a><a href="contacto.html">Contacto</a><a href="patrocinadores.html">Patrocinadores</a></p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
+3
-31
@@ -12,14 +12,14 @@
|
|||||||
<body>
|
<body>
|
||||||
<header class="top">
|
<header class="top">
|
||||||
<a class="brand" href="/" aria-label="Anda Que No Sabes Ná, inicio"><img src="img/avatar.jpg" alt="" width="40" height="40"><span>Anda que no sabes ná</span></a>
|
<a class="brand" href="/" aria-label="Anda Que No Sabes Ná, inicio"><img src="img/avatar.jpg" alt="" width="40" height="40"><span>Anda que no sabes ná</span></a>
|
||||||
<nav aria-label="Principal"><a href="/">Inicio</a><a href="/#quienes">Quiénes somos</a></nav>
|
<nav aria-label="Principal"><a href="/">Inicio</a><a href="/#quienes">Quiénes somos</a><a href="patrocinadores.html">Patrocinadores</a></nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="legal contact">
|
<main class="legal contact">
|
||||||
<h1>¿Quieres venir al <em>programa</em>?</h1>
|
<h1>¿Quieres venir al <em>programa</em>?</h1>
|
||||||
<p class="lead-c">Cuéntanos quién eres y de qué te gustaría hablar. También puedes proponer un tema o simplemente escribirnos. Te respondemos por correo.</p>
|
<p class="lead-c">Cuéntanos quién eres y de qué te gustaría hablar. También puedes proponer un tema o simplemente escribirnos. Te respondemos por correo.</p>
|
||||||
|
|
||||||
<form id="f" class="form" novalidate>
|
<form id="f" class="form" novalidate data-endpoint="/api/contacto" data-mail="andaquenosabesnada@gmail.com" data-ok="¡Gracias! Hemos recibido tu mensaje y te responderemos pronto.">
|
||||||
<label>Nombre
|
<label>Nombre
|
||||||
<input name="nombre" type="text" autocomplete="name" maxlength="100" required>
|
<input name="nombre" type="text" autocomplete="name" maxlength="100" required>
|
||||||
</label>
|
</label>
|
||||||
@@ -47,34 +47,6 @@
|
|||||||
<noscript><p>Necesitas JavaScript para usar el formulario. Mientras tanto, escríbenos a <a href="mailto:andaquenosabesnada@gmail.com">andaquenosabesnada@gmail.com</a>.</p></noscript>
|
<noscript><p>Necesitas JavaScript para usar el formulario. Mientras tanto, escríbenos a <a href="mailto:andaquenosabesnada@gmail.com">andaquenosabesnada@gmail.com</a>.</p></noscript>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script src="form.js"></script>
|
||||||
(function () {
|
|
||||||
var f = document.getElementById('f'), st = document.getElementById('st'), t0 = Date.now(), busy = false;
|
|
||||||
function say(msg, ok) { st.textContent = msg; st.className = 'status ' + (ok ? 'ok' : 'err'); }
|
|
||||||
f.addEventListener('submit', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (busy) return;
|
|
||||||
var d = new FormData(f);
|
|
||||||
var body = {
|
|
||||||
nombre: (d.get('nombre') || '').trim(), email: (d.get('email') || '').trim(),
|
|
||||||
motivo: d.get('motivo'), mensaje: (d.get('mensaje') || '').trim(),
|
|
||||||
consent: f.consent.checked, web: d.get('web') || '', t: Date.now() - t0
|
|
||||||
};
|
|
||||||
if (!body.nombre || !/^[^@\s]+@[^@\s]+\.[^@\s]{2,}$/.test(body.email) || body.mensaje.length < 10 || !body.consent) {
|
|
||||||
say('Revisa los campos: nombre, correo válido, un mensaje de al menos 10 caracteres y la casilla de privacidad.', false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
busy = true; say('Enviando…', true);
|
|
||||||
fetch('/api/contacto', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
|
|
||||||
.then(function (r) { return r.json().catch(function () { return {}; }).then(function (j) { return { ok: r.ok && j.ok, j: j }; }); })
|
|
||||||
.then(function (x) {
|
|
||||||
if (x.ok) { f.reset(); say('¡Gracias! Hemos recibido tu mensaje y te responderemos pronto.', true); }
|
|
||||||
else { say((x.j && x.j.error) || 'No se pudo enviar. Escríbenos a andaquenosabesnada@gmail.com.', false); }
|
|
||||||
})
|
|
||||||
.catch(function () { say('No se pudo enviar. Escríbenos a andaquenosabesnada@gmail.com.', false); })
|
|
||||||
.then(function () { busy = false; });
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
(function () {
|
||||||
|
var f = document.querySelector('form[data-endpoint]');
|
||||||
|
if (!f) return;
|
||||||
|
var st = document.getElementById('st'), t0 = Date.now(), busy = false, mail = f.getAttribute('data-mail');
|
||||||
|
function say(msg, ok) { st.textContent = msg; st.className = 'status ' + (ok ? 'ok' : 'err'); }
|
||||||
|
f.addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (busy) return;
|
||||||
|
var body = { consent: f.consent.checked, t: Date.now() - t0 };
|
||||||
|
Array.prototype.forEach.call(f.elements, function (el) {
|
||||||
|
if (el.name && el.name !== 'consent') body[el.name] = (el.value || '').trim();
|
||||||
|
});
|
||||||
|
if (!body.nombre || !/^[^@\s]+@[^@\s]+\.[^@\s]{2,}$/.test(body.email || '') || (body.mensaje || '').length < 10 ||
|
||||||
|
!body.consent || (f.empresa && !body.empresa)) {
|
||||||
|
say('Revisa los campos obligatorios, el correo y que el mensaje tenga al menos 10 caracteres, y marca la casilla de privacidad.', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (body.sitio && !/^https?:\/\//.test(body.sitio)) { say('La web debe empezar por http:// o https://', false); return; }
|
||||||
|
busy = true; say('Enviando…', true);
|
||||||
|
fetch(f.getAttribute('data-endpoint'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
|
||||||
|
.then(function (r) { return r.json().catch(function () { return {}; }).then(function (j) { return { ok: r.ok && j.ok, j: j }; }); })
|
||||||
|
.then(function (x) {
|
||||||
|
if (x.ok) { f.reset(); say(f.getAttribute('data-ok'), true); }
|
||||||
|
else { say((x.j && x.j.error) || 'No se pudo enviar. Escríbenos a ' + mail + '.', false); }
|
||||||
|
})
|
||||||
|
.catch(function () { say('No se pudo enviar. Escríbenos a ' + mail + '.', false); })
|
||||||
|
.then(function () { busy = false; });
|
||||||
|
});
|
||||||
|
})();
|
||||||
+33
-33
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
<section id="destacado" class="featured wrap reveal">
|
<section id="destacado" class="featured wrap reveal">
|
||||||
<a class="feat" href="https://www.youtube.com/watch?v=_-VaTy2fazY" target="_blank" rel="noopener">
|
<a class="feat" href="https://www.youtube.com/watch?v=_-VaTy2fazY" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/_-VaTy2fazY.jpg" alt="" width="480" height="360"><span class="play big" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/_-VaTy2fazY-hd.jpg" alt="" width="1280" height="720"><span class="play big" aria-hidden="true"></span></span>
|
||||||
<span class="feat-text">
|
<span class="feat-text">
|
||||||
<span class="tag">Último vídeo</span>
|
<span class="tag">Último vídeo</span>
|
||||||
<strong>DOMESTICAR ADOLESCENTES, PLANTAR TOMATES Y CONSTRUIR MINIATURAS | AQNSN Programa 3 - Galaxoide</strong>
|
<strong>DOMESTICAR ADOLESCENTES, PLANTAR TOMATES Y CONSTRUIR MINIATURAS | AQNSN Programa 3 - Galaxoide</strong>
|
||||||
@@ -63,35 +63,35 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<a class="card reveal" style="--d:0ms" href="https://www.youtube.com/watch?v=7zY2ey_ERlQ" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:0ms" href="https://www.youtube.com/watch?v=7zY2ey_ERlQ" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/7zY2ey_ERlQ.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/7zY2ey_ERlQ-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>10 oct 2026</time><strong>Un Simple Teletransporte de Cartas...</strong></span>
|
<span class="meta"><time>10 oct 2026</time><strong>Un Simple Teletransporte de Cartas...</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="card reveal" style="--d:70ms" href="https://www.youtube.com/watch?v=Q7qYQbvjMxQ" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:70ms" href="https://www.youtube.com/watch?v=Q7qYQbvjMxQ" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/Q7qYQbvjMxQ.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/Q7qYQbvjMxQ-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>9 oct 2026</time><strong>El Mahshi y otras RECETAS EXÓTICAS de Egipto que PODRÍAN PERDERSE</strong></span>
|
<span class="meta"><time>9 oct 2026</time><strong>El Mahshi y otras RECETAS EXÓTICAS de Egipto que PODRÍAN PERDERSE</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="card reveal" style="--d:140ms" href="https://www.youtube.com/watch?v=mxP0dvn2HD4" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:140ms" href="https://www.youtube.com/watch?v=mxP0dvn2HD4" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/mxP0dvn2HD4.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/mxP0dvn2HD4-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>6 oct 2026</time><strong>Árabe formal VS Árabe de la calle, ¿qué diferencias hay?</strong></span>
|
<span class="meta"><time>6 oct 2026</time><strong>Árabe formal VS Árabe de la calle, ¿qué diferencias hay?</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="card reveal" style="--d:0ms" href="https://www.youtube.com/watch?v=f6UO6jtUgrU" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:0ms" href="https://www.youtube.com/watch?v=f6UO6jtUgrU" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/f6UO6jtUgrU.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/f6UO6jtUgrU-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>5 oct 2026</time><strong>"Así robaron el busto de Nefertiti..." - El problema del EXPOLIO DE TESOROS de Egipto</strong></span>
|
<span class="meta"><time>5 oct 2026</time><strong>"Así robaron el busto de Nefertiti..." - El problema del EXPOLIO DE TESOROS de Egipto</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="card reveal" style="--d:70ms" href="https://www.youtube.com/watch?v=A2v_T2CCupo" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:70ms" href="https://www.youtube.com/watch?v=A2v_T2CCupo" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/A2v_T2CCupo.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/A2v_T2CCupo-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>4 oct 2026</time><strong>¿Cómo es la vida nocturna y callejera en Egipto?</strong></span>
|
<span class="meta"><time>4 oct 2026</time><strong>¿Cómo es la vida nocturna y callejera en Egipto?</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="card reveal" style="--d:140ms" href="https://www.youtube.com/watch?v=H_yHEe6skW4" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:140ms" href="https://www.youtube.com/watch?v=H_yHEe6skW4" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/H_yHEe6skW4.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/H_yHEe6skW4-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>3 oct 2026</time><strong>EGIPTO NO ES COMO TE LO HAN CONTADO | AQNSN Programa 2 con Sara Yasser</strong></span>
|
<span class="meta"><time>3 oct 2026</time><strong>EGIPTO NO ES COMO TE LO HAN CONTADO | AQNSN Programa 2 con Sara Yasser</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="card reveal" style="--d:0ms" href="https://www.youtube.com/watch?v=Ei_VnfM-uLY" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:0ms" href="https://www.youtube.com/watch?v=Ei_VnfM-uLY" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/Ei_VnfM-uLY.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/Ei_VnfM-uLY-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>30 sep 2026</time><strong>TANATOPRACTOR DE DÍA Y ANIMADOR TUPPER SEX DE NOCHE | AQNSN Programa 1 con Javi (Parte 2)</strong></span>
|
<span class="meta"><time>30 sep 2026</time><strong>TANATOPRACTOR DE DÍA Y ANIMADOR TUPPER SEX DE NOCHE | AQNSN Programa 1 con Javi (Parte 2)</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="card reveal" style="--d:70ms" href="https://www.youtube.com/watch?v=g614maJ0998" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:70ms" href="https://www.youtube.com/watch?v=g614maJ0998" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/g614maJ0998.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/g614maJ0998-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>30 sep 2026</time><strong>TANATOPRACTOR DE DÍA Y ANIMADOR TUPPER SEX DE NOCHE | AQNSN Programa 1 con Javi (Parte 1)</strong></span>
|
<span class="meta"><time>30 sep 2026</time><strong>TANATOPRACTOR DE DÍA Y ANIMADOR TUPPER SEX DE NOCHE | AQNSN Programa 1 con Javi (Parte 1)</strong></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,51 +104,51 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="shorts">
|
<div class="shorts">
|
||||||
<a class="scard reveal" style="--d:0ms" href="https://www.youtube.com/shorts/PxQHPd5YDLs" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:0ms" href="https://www.youtube.com/shorts/PxQHPd5YDLs" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/PxQHPd5YDLs.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/PxQHPd5YDLs-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>La Tediosa Burocracia en la Educación</strong></span>
|
<span class="meta"><strong>La Tediosa Burocracia en la Educación</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:50ms" href="https://www.youtube.com/shorts/rLFSEqe77OY" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:50ms" href="https://www.youtube.com/shorts/rLFSEqe77OY" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/rLFSEqe77OY.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/rLFSEqe77OY-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>¿Cómo dejas de comerte las uñas? El trauma útil de Palín</strong></span>
|
<span class="meta"><strong>¿Cómo dejas de comerte las uñas? El trauma útil de Palín</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:100ms" href="https://www.youtube.com/shorts/jmV_Ao85RvI" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:100ms" href="https://www.youtube.com/shorts/jmV_Ao85RvI" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/jmV_Ao85RvI.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/jmV_Ao85RvI-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>¿Cómo de antiguo es el Árabe?</strong></span>
|
<span class="meta"><strong>¿Cómo de antiguo es el Árabe?</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:150ms" href="https://www.youtube.com/shorts/DLcn8i9lV6Q" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:150ms" href="https://www.youtube.com/shorts/DLcn8i9lV6Q" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/DLcn8i9lV6Q.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/DLcn8i9lV6Q-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>Todas las religiones son similares...</strong></span>
|
<span class="meta"><strong>Todas las religiones son similares...</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:200ms" href="https://www.youtube.com/shorts/E_eULZSlWJs" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:200ms" href="https://www.youtube.com/shorts/E_eULZSlWJs" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/E_eULZSlWJs.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/E_eULZSlWJs-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>La Vida de las Mujeres Egipcias en las zonas costeras</strong></span>
|
<span class="meta"><strong>La Vida de las Mujeres Egipcias en las zonas costeras</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:250ms" href="https://www.youtube.com/shorts/600_XwAHkv0" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:250ms" href="https://www.youtube.com/shorts/600_XwAHkv0" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/600_XwAHkv0.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/600_XwAHkv0-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>Clases religiosas separadas, la política de las escuelas egipcias</strong></span>
|
<span class="meta"><strong>Clases religiosas separadas, la política de las escuelas egipcias</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:0ms" href="https://www.youtube.com/shorts/XKSVhuk_lQI" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:0ms" href="https://www.youtube.com/shorts/XKSVhuk_lQI" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/XKSVhuk_lQI.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/XKSVhuk_lQI-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>Poligamia en Egipto, de la antigüedad a la actualidad</strong></span>
|
<span class="meta"><strong>Poligamia en Egipto, de la antigüedad a la actualidad</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:50ms" href="https://www.youtube.com/shorts/voK4g7lIv4E" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:50ms" href="https://www.youtube.com/shorts/voK4g7lIv4E" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/voK4g7lIv4E.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/voK4g7lIv4E-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>El Barrio Pijo de Egipto, Masr El Gedida, y sus tiendas de Oro</strong></span>
|
<span class="meta"><strong>El Barrio Pijo de Egipto, Masr El Gedida, y sus tiendas de Oro</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:100ms" href="https://www.youtube.com/shorts/yzTMyAKQnDQ" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:100ms" href="https://www.youtube.com/shorts/yzTMyAKQnDQ" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/yzTMyAKQnDQ.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/yzTMyAKQnDQ-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>Las Franquicias se extienden en EGIPTO...</strong></span>
|
<span class="meta"><strong>Las Franquicias se extienden en EGIPTO...</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:150ms" href="https://www.youtube.com/shorts/DgTa9qX4xH0" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:150ms" href="https://www.youtube.com/shorts/DgTa9qX4xH0" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/DgTa9qX4xH0.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/DgTa9qX4xH0-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>El barrio cristiano del Cairo que RECICLA BASURA</strong></span>
|
<span class="meta"><strong>El barrio cristiano del Cairo que RECICLA BASURA</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:200ms" href="https://www.youtube.com/shorts/oAoEz2Kwmk8" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:200ms" href="https://www.youtube.com/shorts/oAoEz2Kwmk8" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/oAoEz2Kwmk8.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/oAoEz2Kwmk8-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>Tiendas QUE NO CIERRAN en Egipto 🌇</strong></span>
|
<span class="meta"><strong>Tiendas QUE NO CIERRAN en Egipto 🌇</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:250ms" href="https://www.youtube.com/shorts/05m1CMyGyUA" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:250ms" href="https://www.youtube.com/shorts/05m1CMyGyUA" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/05m1CMyGyUA.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/05m1CMyGyUA-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>¿Se parece la comida EGIPCIA a la ESPAÑOLA?</strong></span>
|
<span class="meta"><strong>¿Se parece la comida EGIPCIA a la ESPAÑOLA?</strong></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,46 +162,46 @@
|
|||||||
<h3 class="sub reveal">Vídeos</h3>
|
<h3 class="sub reveal">Vídeos</h3>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<a class="card reveal" style="--d:0ms" href="https://www.youtube.com/watch?v=_-VaTy2fazY" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:0ms" href="https://www.youtube.com/watch?v=_-VaTy2fazY" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/_-VaTy2fazY.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/_-VaTy2fazY-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>18 oct 2026 · 385 vistas</time><strong>DOMESTICAR ADOLESCENTES, PLANTAR TOMATES Y CONSTRUIR MINIATURAS | AQNSN Programa 3 - Galaxoide</strong></span>
|
<span class="meta"><time>18 oct 2026 · 385 vistas</time><strong>DOMESTICAR ADOLESCENTES, PLANTAR TOMATES Y CONSTRUIR MINIATURAS | AQNSN Programa 3 - Galaxoide</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="card reveal" style="--d:70ms" href="https://www.youtube.com/watch?v=A2v_T2CCupo" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:70ms" href="https://www.youtube.com/watch?v=A2v_T2CCupo" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/A2v_T2CCupo.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/A2v_T2CCupo-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>4 oct 2026 · 252 vistas</time><strong>¿Cómo es la vida nocturna y callejera en Egipto?</strong></span>
|
<span class="meta"><time>4 oct 2026 · 252 vistas</time><strong>¿Cómo es la vida nocturna y callejera en Egipto?</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="card reveal" style="--d:140ms" href="https://www.youtube.com/watch?v=rp9j_Ph9XvI" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:140ms" href="https://www.youtube.com/watch?v=rp9j_Ph9XvI" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/rp9j_Ph9XvI.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/rp9j_Ph9XvI-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>16 sep 2026 · 219 vistas</time><strong>ISA BECERRIL NOS PRESENTA SU NOVELA "HIALURÓNICO" | AQNSN Episodio Piloto 0</strong></span>
|
<span class="meta"><time>16 sep 2026 · 219 vistas</time><strong>ISA BECERRIL NOS PRESENTA SU NOVELA "HIALURÓNICO" | AQNSN Episodio Piloto 0</strong></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="card reveal" style="--d:0ms" href="https://www.youtube.com/watch?v=mxP0dvn2HD4" target="_blank" rel="noopener">
|
<a class="card reveal" style="--d:0ms" href="https://www.youtube.com/watch?v=mxP0dvn2HD4" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/mxP0dvn2HD4.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/mxP0dvn2HD4-hd.jpg" alt="" loading="lazy" width="640" height="360"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><time>6 oct 2026 · 195 vistas</time><strong>Árabe formal VS Árabe de la calle, ¿qué diferencias hay?</strong></span>
|
<span class="meta"><time>6 oct 2026 · 195 vistas</time><strong>Árabe formal VS Árabe de la calle, ¿qué diferencias hay?</strong></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="sub reveal">Shorts</h3>
|
<h3 class="sub reveal">Shorts</h3>
|
||||||
<div class="shorts">
|
<div class="shorts">
|
||||||
<a class="scard reveal" style="--d:0ms" href="https://www.youtube.com/shorts/zJ_x4iA8OSs" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:0ms" href="https://www.youtube.com/shorts/zJ_x4iA8OSs" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/zJ_x4iA8OSs.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/zJ_x4iA8OSs-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>El árabe que se estudia y el que se habla en las calles son diferentes</strong><time>1.547 vistas</time></span>
|
<span class="meta"><strong>El árabe que se estudia y el que se habla en las calles son diferentes</strong><time>1.547 vistas</time></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:50ms" href="https://www.youtube.com/shorts/nvF7P_S081o" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:50ms" href="https://www.youtube.com/shorts/nvF7P_S081o" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/nvF7P_S081o.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/nvF7P_S081o-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>😌 "Solo vi a una persona morir sonriendo..."</strong><time>1.545 vistas</time></span>
|
<span class="meta"><strong>😌 "Solo vi a una persona morir sonriendo..."</strong><time>1.545 vistas</time></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:100ms" href="https://www.youtube.com/shorts/Wfq5vjJ6sQU" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:100ms" href="https://www.youtube.com/shorts/Wfq5vjJ6sQU" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/Wfq5vjJ6sQU.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/Wfq5vjJ6sQU-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>"Recoger a un niño ahogado de dos años, eso..."</strong><time>1.257 vistas</time></span>
|
<span class="meta"><strong>"Recoger a un niño ahogado de dos años, eso..."</strong><time>1.257 vistas</time></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:150ms" href="https://www.youtube.com/shorts/XKSVhuk_lQI" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:150ms" href="https://www.youtube.com/shorts/XKSVhuk_lQI" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/XKSVhuk_lQI.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/XKSVhuk_lQI-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>Poligamia en Egipto, de la antigüedad a la actualidad</strong><time>1.207 vistas</time></span>
|
<span class="meta"><strong>Poligamia en Egipto, de la antigüedad a la actualidad</strong><time>1.207 vistas</time></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:200ms" href="https://www.youtube.com/shorts/p5trUANcq_c" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:200ms" href="https://www.youtube.com/shorts/p5trUANcq_c" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/p5trUANcq_c.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/p5trUANcq_c-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>El número 13 y su relación con la muerte #podcast #granada #misterio #latenight #andaquenosabesna</strong><time>1.043 vistas</time></span>
|
<span class="meta"><strong>El número 13 y su relación con la muerte #podcast #granada #misterio #latenight #andaquenosabesna</strong><time>1.043 vistas</time></span>
|
||||||
</a>
|
</a>
|
||||||
<a class="scard reveal" style="--d:250ms" href="https://www.youtube.com/shorts/7PRSukQTCwI" target="_blank" rel="noopener">
|
<a class="scard reveal" style="--d:250ms" href="https://www.youtube.com/shorts/7PRSukQTCwI" target="_blank" rel="noopener">
|
||||||
<span class="thumb"><img src="img/7PRSukQTCwI.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
|
<span class="thumb"><img src="img/7PRSukQTCwI-hd.jpg" alt="" loading="lazy" width="540" height="960"><span class="play" aria-hidden="true"></span></span>
|
||||||
<span class="meta"><strong>¿Cómo puede meterte miedo un Tanatopractor? 😖</strong><time>886 vistas</time></span>
|
<span class="meta"><strong>¿Cómo puede meterte miedo un Tanatopractor? 😖</strong><time>886 vistas</time></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,7 +225,7 @@
|
|||||||
<section class="wrap reveal" aria-labelledby="venir">
|
<section class="wrap reveal" aria-labelledby="venir">
|
||||||
<div class="invite">
|
<div class="invite">
|
||||||
<div><h2 id="venir">¿Quieres venir al programa?</h2><p>Cuéntanos quién eres y de qué te gustaría hablar.</p></div>
|
<div><h2 id="venir">¿Quieres venir al programa?</h2><p>Cuéntanos quién eres y de qué te gustaría hablar.</p></div>
|
||||||
<a class="btn primary" href="contacto.html">Escríbenos</a>
|
<div class="cta"><a class="btn primary" href="contacto.html">Escríbenos</a><a class="btn" href="patrocinadores.html">Quiero anunciarme</a></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@
|
|||||||
<footer>
|
<footer>
|
||||||
<div class="wrap foot">
|
<div class="wrap foot">
|
||||||
<p>© 2026 Anda Que No Sabes Ná · Granada</p>
|
<p>© 2026 Anda Que No Sabes Ná · Granada</p>
|
||||||
<p class="links"><a href="privacidad.html">Política de privacidad</a><a href="terminos.html">Condiciones del servicio</a><a href="contacto.html">Contacto</a></p>
|
<p class="links"><a href="privacidad.html">Política de privacidad</a><a href="terminos.html">Condiciones del servicio</a><a href="contacto.html">Contacto</a><a href="patrocinadores.html">Patrocinadores</a></p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Patrocinadores · Anda Que No Sabes Ná</title>
|
||||||
|
<meta name="description" content="¿Quieres que tu marca suene en Anda Que No Sabes Ná? Cuéntanos qué tienes en mente.">
|
||||||
|
<meta name="theme-color" content="#06080d">
|
||||||
|
<link rel="icon" href="img/avatar.jpg">
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="top">
|
||||||
|
<a class="brand" href="/" aria-label="Anda Que No Sabes Ná, inicio"><img src="img/avatar.jpg" alt="" width="40" height="40"><span>Anda que no sabes ná</span></a>
|
||||||
|
<nav aria-label="Principal"><a href="/">Inicio</a><a href="contacto.html">Contacto</a></nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="legal contact">
|
||||||
|
<h1>¿Quieres <em>anunciarte</em> en el programa?</h1>
|
||||||
|
<p class="lead-c">Si tu marca quiere sonar en Anda Que No Sabes Ná, escríbenos. Cuéntanos quién eres, qué tienes en mente y qué te interesa: una mención, patrocinar episodios, una colaboración… Lo hablamos con calma.</p>
|
||||||
|
|
||||||
|
<form id="f" class="form" novalidate data-endpoint="/api/patrocinio" data-mail="andaquenosabesnada@gmail.com" data-ok="¡Gracias! Hemos recibido tu propuesta y te escribiremos pronto.">
|
||||||
|
<label>Empresa o marca
|
||||||
|
<input name="empresa" type="text" autocomplete="organization" maxlength="120" required>
|
||||||
|
</label>
|
||||||
|
<label>Tu nombre
|
||||||
|
<input name="nombre" type="text" autocomplete="name" maxlength="100" required>
|
||||||
|
</label>
|
||||||
|
<label>Correo electrónico
|
||||||
|
<input name="email" type="email" autocomplete="email" maxlength="200" required>
|
||||||
|
</label>
|
||||||
|
<label>Web (opcional)
|
||||||
|
<input name="sitio" type="text" inputmode="url" maxlength="200" placeholder="https://" autocomplete="url">
|
||||||
|
</label>
|
||||||
|
<label>¿Qué te interesa?
|
||||||
|
<select name="tipo">
|
||||||
|
<option value="mencion">Anunciarme / que me mencionéis en el programa</option>
|
||||||
|
<option value="patrocinio">Patrocinar episodios o una temporada</option>
|
||||||
|
<option value="colab">Colaboración o canje</option>
|
||||||
|
<option value="otro">Otra propuesta</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Cuéntanos tu propuesta
|
||||||
|
<textarea name="mensaje" rows="6" maxlength="3000" required placeholder="Qué hacéis, a quién queréis llegar, qué tenéis en mente…"></textarea>
|
||||||
|
</label>
|
||||||
|
<div class="trap" aria-hidden="true"><label>No rellenes este campo <input name="web" type="text" tabindex="-1" autocomplete="off"></label></div>
|
||||||
|
<label class="check">
|
||||||
|
<input name="consent" type="checkbox" required>
|
||||||
|
<span>He leído la <a href="privacidad.html" target="_blank" rel="noopener">política de privacidad</a> y acepto que usen mis datos para responderme.</span>
|
||||||
|
</label>
|
||||||
|
<button class="btn primary" type="submit">Enviar propuesta</button>
|
||||||
|
<p id="st" class="status" role="status" aria-live="polite"></p>
|
||||||
|
</form>
|
||||||
|
<noscript><p>Necesitas JavaScript para usar el formulario. Mientras tanto, escríbenos a <a href="mailto:andaquenosabesnada@gmail.com">andaquenosabesnada@gmail.com</a>.</p></noscript>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="form.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -28,9 +28,9 @@
|
|||||||
<p>Como cualquier servidor web, nuestro servidor puede registrar de forma temporal la dirección IP, la fecha y la página solicitada, únicamente por seguridad y para detectar abusos. No usamos esos registros para identificar a nadie ni los compartimos.</p>
|
<p>Como cualquier servidor web, nuestro servidor puede registrar de forma temporal la dirección IP, la fecha y la página solicitada, únicamente por seguridad y para detectar abusos. No usamos esos registros para identificar a nadie ni los compartimos.</p>
|
||||||
<p>Los enlaces a YouTube, TikTok e Instagram llevan a servicios de terceros, que tienen sus propias políticas de privacidad.</p>
|
<p>Los enlaces a YouTube, TikTok e Instagram llevan a servicios de terceros, que tienen sus propias políticas de privacidad.</p>
|
||||||
|
|
||||||
<h2>3. Formulario de contacto</h2>
|
<h2>3. Formularios de contacto y de patrocinio</h2>
|
||||||
<p>Si nos escribes desde la página de <a href="contacto.html">contacto</a>, recogemos el nombre, el correo electrónico, el motivo y el mensaje que tú mismo introduces. Los usamos <strong>únicamente para responderte</strong>. La base legal es tu consentimiento, que das al marcar la casilla del formulario.</p>
|
<p>Si nos escribes desde la página de <a href="contacto.html">contacto</a> o de <a href="patrocinadores.html">patrocinadores</a>, recogemos los datos que tú mismo introduces: nombre, correo electrónico, mensaje y motivo o tipo de colaboración y, en el formulario de patrocinio, también el nombre de la empresa y su web si la indicas. Los usamos <strong>únicamente para responderte</strong>. La base legal es tu consentimiento, que das al marcar la casilla del formulario.</p>
|
||||||
<p>La web no guarda los mensajes: el formulario los reenvía por correo electrónico, a través de nuestro propio servidor de correo, a la cuenta del programa. Los conservamos solo el tiempo necesario para atender tu consulta y no los cedemos a terceros ni los usamos para otra cosa. El formulario incluye medidas antispam que no identifican a nadie. Puedes pedirnos que borremos tus datos escribiendo al correo de contacto.</p>
|
<p>La web no guarda los mensajes: el formulario los reenvía por correo electrónico, a través de nuestro propio servidor de correo, a la cuenta del programa. Los conservamos solo el tiempo necesario para atender tu consulta y no los cedemos a terceros ni los usamos para otra cosa. Los formularios incluyen medidas antispam que no identifican a nadie. Puedes pedirnos que borremos tus datos escribiendo al correo de contacto.</p>
|
||||||
|
|
||||||
<h2>4. Nuestra aplicación de publicación</h2>
|
<h2>4. Nuestra aplicación de publicación</h2>
|
||||||
<p>Usamos una aplicación de gestión de redes sociales de código abierto (Postiz), instalada en un servidor propio, para programar y publicar el contenido del programa. La aplicación está pensada para uso interno: <strong>solo la utiliza el propietario de las cuentas</strong> y no está abierta al público.</p>
|
<p>Usamos una aplicación de gestión de redes sociales de código abierto (Postiz), instalada en un servidor propio, para programar y publicar el contenido del programa. La aplicación está pensada para uso interno: <strong>solo la utiliza el propietario de las cuentas</strong> y no está abierta al público.</p>
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ MOTIVOS = {
|
|||||||
'tema': 'Propongo un tema',
|
'tema': 'Propongo un tema',
|
||||||
'otro': 'Otro asunto',
|
'otro': 'Otro asunto',
|
||||||
}
|
}
|
||||||
|
TIPOS = {
|
||||||
|
'mencion': 'Anunciarme / que me mencionéis en el programa',
|
||||||
|
'patrocinio': 'Patrocinar episodios o una temporada',
|
||||||
|
'colab': 'Colaboración o canje',
|
||||||
|
'otro': 'Otra propuesta',
|
||||||
|
}
|
||||||
EMAIL_RE = re.compile(r'^[^@\s]{1,64}@[^@\s]{1,190}\.[^@\s]{2,}$')
|
EMAIL_RE = re.compile(r'^[^@\s]{1,64}@[^@\s]{1,190}\.[^@\s]{2,}$')
|
||||||
MAX_BODY = 16 * 1024
|
MAX_BODY = 16 * 1024
|
||||||
RATE_MAX, RATE_WINDOW = 5, 3600 # 5 mensajes por IP y hora
|
RATE_MAX, RATE_WINDOW = 5, 3600 # 5 mensajes por IP y hora
|
||||||
@@ -42,16 +48,16 @@ def rate_ok(ip):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def send(nombre, email, motivo, mensaje):
|
def send(subject, reply_to, body):
|
||||||
host, user, pwd = os.environ.get('SMTP_HOST'), os.environ.get('SMTP_USER'), os.environ.get('SMTP_PASS')
|
host, user, pwd = os.environ.get('SMTP_HOST'), os.environ.get('SMTP_USER'), os.environ.get('SMTP_PASS')
|
||||||
if not (host and user and pwd and os.environ.get('MAIL_TO')):
|
if not (host and user and pwd and os.environ.get('MAIL_TO')):
|
||||||
raise RuntimeError('SMTP no configurado')
|
raise RuntimeError('SMTP no configurado')
|
||||||
msg = EmailMessage()
|
msg = EmailMessage()
|
||||||
msg['Subject'] = f'[Web AQNSN] {MOTIVOS[motivo]} · {nombre}'
|
msg['Subject'] = subject
|
||||||
msg['From'] = os.environ.get('MAIL_FROM', user)
|
msg['From'] = os.environ.get('MAIL_FROM', user)
|
||||||
msg['To'] = os.environ['MAIL_TO']
|
msg['To'] = os.environ['MAIL_TO']
|
||||||
msg['Reply-To'] = email
|
msg['Reply-To'] = reply_to
|
||||||
msg.set_content(f'Motivo: {MOTIVOS[motivo]}\nNombre: {nombre}\nEmail: {email}\n\n{mensaje}\n')
|
msg.set_content(body)
|
||||||
s = smtplib.SMTP(host, int(os.environ.get('SMTP_PORT', '587')), timeout=20)
|
s = smtplib.SMTP(host, int(os.environ.get('SMTP_PORT', '587')), timeout=20)
|
||||||
try:
|
try:
|
||||||
s.ehlo()
|
s.ehlo()
|
||||||
@@ -83,8 +89,9 @@ class H(BaseHTTPRequestHandler):
|
|||||||
self.wfile.write(body)
|
self.wfile.write(body)
|
||||||
|
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
if self.path != '/api/contacto':
|
if self.path not in ('/api/contacto', '/api/patrocinio'):
|
||||||
return self.reply(404, {'ok': False})
|
return self.reply(404, {'ok': False})
|
||||||
|
patro = self.path == '/api/patrocinio'
|
||||||
try:
|
try:
|
||||||
n = int(self.headers.get('Content-Length', '0'))
|
n = int(self.headers.get('Content-Length', '0'))
|
||||||
if n <= 0 or n > MAX_BODY:
|
if n <= 0 or n > MAX_BODY:
|
||||||
@@ -99,12 +106,24 @@ class H(BaseHTTPRequestHandler):
|
|||||||
if not rate_ok(ip):
|
if not rate_ok(ip):
|
||||||
return self.reply(429, {'ok': False, 'error': 'Demasiados mensajes. Inténtalo más tarde.'})
|
return self.reply(429, {'ok': False, 'error': 'Demasiados mensajes. Inténtalo más tarde.'})
|
||||||
nombre, email = clean(d.get('nombre'), 100), clean(d.get('email'), 200)
|
nombre, email = clean(d.get('nombre'), 100), clean(d.get('email'), 200)
|
||||||
motivo = d.get('motivo') if d.get('motivo') in MOTIVOS else 'otro'
|
|
||||||
mensaje = str(d.get('mensaje') or '').replace('\r', '').strip()[:3000]
|
mensaje = str(d.get('mensaje') or '').replace('\r', '').strip()[:3000]
|
||||||
|
bad = {'ok': False, 'error': 'Revisa los campos del formulario.'}
|
||||||
if not nombre or not EMAIL_RE.match(email) or len(mensaje) < 10 or d.get('consent') is not True:
|
if not nombre or not EMAIL_RE.match(email) or len(mensaje) < 10 or d.get('consent') is not True:
|
||||||
return self.reply(400, {'ok': False, 'error': 'Revisa los campos del formulario.'})
|
return self.reply(400, bad)
|
||||||
|
if patro:
|
||||||
|
empresa, sitio = clean(d.get('empresa'), 120), clean(d.get('sitio'), 200)
|
||||||
|
tipo = d.get('tipo') if d.get('tipo') in TIPOS else 'otro'
|
||||||
|
if not empresa or (sitio and not re.match(r'^https?://', sitio)):
|
||||||
|
return self.reply(400, bad)
|
||||||
|
subject = f'[Web AQNSN · Patrocinio] {empresa} · {nombre}'
|
||||||
|
body = (f'Empresa: {empresa}\nContacto: {nombre}\nEmail: {email}\nWeb: {sitio or "—"}\n'
|
||||||
|
f'Interés: {TIPOS[tipo]}\n\n{mensaje}\n')
|
||||||
|
else:
|
||||||
|
motivo = d.get('motivo') if d.get('motivo') in MOTIVOS else 'otro'
|
||||||
|
subject = f'[Web AQNSN] {MOTIVOS[motivo]} · {nombre}'
|
||||||
|
body = f'Motivo: {MOTIVOS[motivo]}\nNombre: {nombre}\nEmail: {email}\n\n{mensaje}\n'
|
||||||
try:
|
try:
|
||||||
send(nombre, email, motivo, mensaje)
|
send(subject, email, body)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
return self.reply(503, {'ok': False, 'error': 'El formulario no está disponible ahora mismo.'})
|
return self.reply(503, {'ok': False, 'error': 'El formulario no está disponible ahora mismo.'})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|||||||
Reference in New Issue
Block a user