diff --git a/.gitignore b/.gitignore index c18dd8d..0de8865 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ __pycache__/ +pics/ +.DS_Store diff --git a/README.md b/README.md index 6f328ec..804f0a4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,11 @@ Web de la acampada del 26S en Madrid. HTML y CSS, sin JavaScript. 1. `site/` va a `/var/www/acampada26s.net`. `deploy/nginx.conf` y `deploy/hub.conf` a nginx. 2. `/c` es el HUB del pub OASIS, en `127.0.0.1:3000` de la misma máquina. -3. Directo: `/c`. Comunicados: `/c?type=posts` (los blogposts públicos del HUB). -4. Fotos subidas a mano: `tools/limpia-img.sh` antes, siempre. +3. Cuadros: Directo `/c/feed/CUENTA_ACAMPADA` y Comunicados `/c/blogs/CUENTA_ACAMPADA`. Botón OASIS del pie: `/c/wiki/WIKI_DIDAC`. +4. Cambiar `CUENTA_ACAMPADA` en `site/index.html` por la id de la cuenta de la acampada codificada (`@` → `%40`, `/` → `%2F`, `=` → `%3D`). Esa cuenta tiene que tener activada la visibilidad clearnet en OASIS y el pub tiene que seguirla. Cambiar `WIKI_DIDAC` por el slug de la página wiki de Didac (el de su URL dentro de `/c`); Didac tiene que tener activada la visibilidad clearnet de wikis. En la rama master de OASIS la página por cuenta es `/c/inhabitant/?type=feed` y `?type=posts`; si el pub corre esa versión, cambiar ahí las dos rutas. +5. Banner: fotos en `pics/` (fuera de git), `python3 tools/collage.py` regenera `site/img/banner.webp`, `banner-movil.webp` y `og.jpg` (la imagen de 1200x630 que usan las redes al compartir el enlace). Necesita Pillow. +6. Indexado: `site/robots.txt` y `site/sitemap.xml` llevan el dominio escrito a mano; cambiarlo si cambia el dominio. En la cabecera de `site/index.html` están el canonical, las etiquetas Open Graph y el descubrimiento de RSS. +7. RSS: los sirve el HUB en `/c/rss/posts` (comunicados) y `/c/rss/feed` (directo), con los nombres de módulo de OASIS. Su sitemap propio es `/c/sitemap.xml`. +8. Fotos subidas a mano: `tools/limpia-img.sh` antes, siempre. Código AGPL-3.0. Textos CC BY-SA 4.0. Tipografía Anton, OFL. diff --git a/site/css/main.css b/site/css/main.css index ed532cc..b4d31ae 100644 --- a/site/css/main.css +++ b/site/css/main.css @@ -29,7 +29,7 @@ img { max-width: 100%; height: auto; display: block; } a { color: inherit; } -.w { max-width: 1100px; margin: 0 auto; padding: 0 16px; } +.w { max-width: 1100px; margin: 0 auto; padding-inline: 16px; } h1, h2, h3, .cuadro, .bt { font-family: var(--tit); @@ -72,6 +72,8 @@ h3 { font-size: 26px; margin: 0 0 6px; } outline: none; } +.collage img { width: 100%; } + .botonera { grid-template-columns: repeat(3, 1fr); gap: 16px; @@ -105,7 +107,8 @@ h3 { font-size: 26px; margin: 0 0 6px; } box-shadow: 0 2px 0 var(--sombra); } -footer { margin: 64px 0 24px; border-top: var(--bd); padding-top: 12px; font-size: 14px; } +footer { margin: 64px 0 24px; border-top: var(--bd); padding-top: 16px; } +footer p { margin: 0; font-size: 14px; } @media (max-width: 640px) { .cuadros { grid-template-columns: 1fr; } diff --git a/site/img/banner-movil.webp b/site/img/banner-movil.webp new file mode 100644 index 0000000..3d77bd6 Binary files /dev/null and b/site/img/banner-movil.webp differ diff --git a/site/img/banner.webp b/site/img/banner.webp new file mode 100644 index 0000000..2f0f762 Binary files /dev/null and b/site/img/banner.webp differ diff --git a/site/img/colectivos/krakenslab.webp b/site/img/colectivos/krakenslab.webp new file mode 100644 index 0000000..b387ecd Binary files /dev/null and b/site/img/colectivos/krakenslab.webp differ diff --git a/site/img/og.jpg b/site/img/og.jpg new file mode 100644 index 0000000..0b087fa Binary files /dev/null and b/site/img/og.jpg differ diff --git a/site/index.html b/site/index.html index 6873b1e..e7af22b 100644 --- a/site/index.html +++ b/site/index.html @@ -6,7 +6,23 @@ Acampada 26S - + + + + + + + + + + + + + + + + + @@ -14,19 +30,21 @@

Acampada 26S

+ + + Fotos de la acampada en la Puerta del Sol +
@@ -64,13 +82,14 @@
  • CJCColectivos de Jóvenes Comunistas
  • Frente de EstudiantesSindicato estudiantil
  • Acampada x PalestinaSolidaridad con Palestina
  • +
  • Kräkens.LAbHacktivistas
  • -

    ¿Falta tu colectivo? Escribe a la comisión de comunicación en OASIS.

    +

    ¿Falta tu colectivo? Escribe a la comisión de comunicación en OASIS o a acampada26s@riseup.net.

    diff --git a/site/robots.txt b/site/robots.txt new file mode 100644 index 0000000..738bc40 --- /dev/null +++ b/site/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / + +Sitemap: https://acampada26s.net/sitemap.xml +Sitemap: https://acampada26s.net/c/sitemap.xml diff --git a/site/sitemap.xml b/site/sitemap.xml new file mode 100644 index 0000000..62ad792 --- /dev/null +++ b/site/sitemap.xml @@ -0,0 +1,12 @@ + + + + https://acampada26s.net/ + 2026-09-30 + daily + + + https://acampada26s.net/c + hourly + + diff --git a/tools/collage.py b/tools/collage.py new file mode 100644 index 0000000..bc1ef30 --- /dev/null +++ b/tools/collage.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +"""Collage del banner: pics/ -> site/img/banner.webp, banner-movil.webp y og.jpg. + +Cada celda es (archivo, x, y, ancho, alto[, centro]) en píxeles de la salida. La +foto se recorta para cubrir la celda (como object-fit: cover); `centro` es (cx, cy) +entre 0 y 1, por defecto (0.5, 0.5). Entre celdas hay una línea negra a juego con +el borde de la web. La salida no lleva metadatos. +""" +import sys +from pathlib import Path + +from PIL import Image, ImageOps + +RAIZ = Path(__file__).resolve().parent.parent +PICS = RAIZ / "pics" +IMG = RAIZ / "site" / "img" +NEGRO = (0, 0, 0) +CALIDAD = 80 + +# Las cuatro fotos son apaisadas (ratios 1.02 a 1.53), así que las celdas también. + +# Escritorio: 2200x660 (se muestra a 1100x330). Una fila, línea de 6px (3px a 2x). +ESCRITORIO = { + "archivo": "banner.webp", + "tamano": (2200, 660), + "linea": 6, + "celdas": [ + ("banner4", 0, 0, 620, 660, (0.45, 0.55)), + ("banner1", 620, 0, 600, 660), + ("banner3", 1220, 0, 550, 660), + ("banner5", 1770, 0, 430, 660, (0.5, 0.4)), + ], +} + +# Móvil: 800x640. Dos filas de 320, línea de 4px. +MOVIL = { + "archivo": "banner-movil.webp", + "tamano": (800, 640), + "linea": 4, + "celdas": [ + ("banner4", 0, 0, 400, 320, (0.45, 0.55)), + ("banner1", 400, 0, 400, 320), + ("banner5", 0, 320, 400, 320, (0.5, 0.35)), + ("banner3", 400, 320, 400, 320), + ], +} + +# Para compartir en redes (og:image): 1200x630, el tamaño que esperan las plataformas. +OG = { + "archivo": "og.jpg", + "tamano": (1200, 630), + "linea": 4, + "celdas": [ + ("banner4", 0, 0, 600, 315, (0.45, 0.55)), + ("banner1", 600, 0, 600, 315), + ("banner5", 0, 315, 600, 315, (0.5, 0.35)), + ("banner3", 600, 315, 600, 315), + ], +} + +SALIDAS = (ESCRITORIO, MOVIL, OG) + +def foto(nombre): + for ext in ("png", "jpg", "jpeg", "webp"): + p = PICS / f"{nombre}.{ext}" + if p.exists(): + im = Image.open(p) + im = ImageOps.exif_transpose(im) + return im.convert("RGB") + sys.exit(f"falta pics/{nombre}.(png|jpg|jpeg|webp)") + + +def compone(spec): + ancho, alto = spec["tamano"] + lienzo = Image.new("RGB", (ancho, alto), NEGRO) + linea = spec["linea"] + for nombre, x, y, w, h, *resto in spec["celdas"]: + centro = resto[0] if resto else (0.5, 0.5) + # Línea negra: media a cada lado de la celda, y en los bordes exteriores nada. + x0 = x + (linea // 2 if x > 0 else 0) + y0 = y + (linea // 2 if y > 0 else 0) + x1 = x + w - (linea - linea // 2 if x + w < ancho else 0) + y1 = y + h - (linea - linea // 2 if y + h < alto else 0) + trozo = ImageOps.fit(foto(nombre), (x1 - x0, y1 - y0), Image.LANCZOS, centering=centro) + lienzo.paste(trozo, (x0, y0)) + salida = IMG / spec["archivo"] + if salida.suffix == ".jpg": + lienzo.save(salida, "JPEG", quality=85, optimize=True, progressive=True) + else: + lienzo.save(salida, "WEBP", quality=CALIDAD, method=6) + return salida + + +def limpio(ruta): + im = Image.open(ruta) + if im.getexif(): + return False + return not any(k in im.info for k in ("exif", "xmp", "icc_profile")) + + +def main(): + IMG.mkdir(parents=True, exist_ok=True) + for spec in SALIDAS: + salida = compone(spec) + estado = "sin metadatos" if limpio(salida) else "OJO: con metadatos" + print(f"{salida.relative_to(RAIZ)} {salida.stat().st_size // 1024} KB {estado}") + + +if __name__ == "__main__": + main()