Files
web-jailgames/JAILGAMES_Plan_Tecnico.md
JailDesigner e337d7bc45 Implementación completa del sitio JAILGAMES (bloques A–J)
Sitio estático generado con Eleventy (11ty) + Nginx en Docker:
- Plantillas Nunjucks con layout base, tarjetas y fichas individuales
- Datos de juegos en YAML, colección ordenada por fecha
- CSS con tema oscuro gaming y diseño responsive (3/2/1 columnas)
- Lightbox vanilla JS para capturas de pantalla
- Build multi-stage Docker (node:20-alpine → nginx:alpine)
- 2 juegos de ejemplo con imágenes SVG placeholder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 12:58:55 +02:00

29 KiB
Raw Permalink Blame History

JAILGAMES — Plan Técnico Completo


1. Arquitectura General

Decisión: Eleventy (11ty) como generador de sitio estático

Se descartan tres alternativas y se elige una:

Opción Ventajas Inconvenientes
HTML/CSS/JS puro Sin dependencias Hay que duplicar HTML por cada juego; cualquier cambio en el layout obliga a tocar todos los archivos
Astro / Vite Moderno, componentes Pesado para <10 juegos; requiere Node + build complejo
Microframework (Flask, Express) Dinámico Necesita servidor con runtime activo; innecesario para contenido estático en ferias
Eleventy (11ty) Plantillas Nunjucks, lee JSON/YAML nativamente, genera HTML puro, cero JS en el navegador Requiere Node para el build (no en producción)

Justificación concreta:

  • Mantenimiento sencillo: añadir un juego = crear un archivo YAML + copiar imágenes. Nada más.
  • Cero dependencias en producción: el resultado es HTML/CSS/JS estático servido por Nginx.
  • Funciona sin internet: perfecto para ferias presenciales. Se sirve desde un portátil con Docker.
  • Nginx en lugar de Apache: más ligero, configuración mínima para servir estáticos. Si se prefiere Apache, el cambio es trivial (se indica en la sección Docker).

2. Estructura de Carpetas

jailgames/
├── src/                          # ← Fuentes (nunca se sirven directamente)
│   ├── _data/
│   │   └── site.json             # Datos globales (nombre del sitio, redes, etc.)
│   ├── _includes/
│   │   ├── base.njk              # Layout base HTML (head, header, footer)
│   │   ├── game-card.njk         # Componente: tarjeta de juego para la home
│   │   └── game-page.njk        # Layout: ficha individual de juego
│   ├── games/                    # Un archivo YAML por juego
│   │   ├── cosmic-escape.yaml
│   │   ├── pixel-dungeon.yaml
│   │   └── ...
│   ├── css/
│   │   └── style.css             # Estilos globales
│   ├── js/
│   │   └── main.js               # JS mínimo (lightbox, filtros, etc.)
│   ├── index.njk                 # Página principal (home)
│   └── game.njk                  # Plantilla que genera cada /game/slug/
│
├── static/                       # ← Archivos copiados tal cual al build
│   ├── logo/
│   │   └── jailgames-logo.png    # Logo general del sitio
│   ├── games/                    # Carpeta por juego (nombre = slug)
│   │   ├── cosmic-escape/
│   │   │   ├── logo.png
│   │   │   ├── screenshot-1.png
│   │   │   ├── screenshot-2.png
│   │   │   └── screenshot-3.png
│   │   └── pixel-dungeon/
│   │       ├── logo.png
│   │       └── ...
│   └── favicon.ico
│
├── downloads/                    # ← Binarios descargables
│   ├── cosmic-escape/
│   │   ├── cosmic-escape-v1.2-windows.zip
│   │   ├── cosmic-escape-v1.2-linux.tar.gz
│   │   └── cosmic-escape-v1.2-mac.dmg
│   └── pixel-dungeon/
│       └── ...
│
├── _site/                        # ← Salida del build (se sirve con Nginx)
│
├── .eleventy.js                  # Configuración de Eleventy
├── package.json
├── Dockerfile
├── docker-compose.yml
├── nginx.conf                    # Configuración de Nginx
└── README.md

Resumen de ubicaciones

Contenido Ruta
Datos de cada juego src/games/<slug>.yaml
Imágenes de cada juego static/games/<slug>/
Binarios descargables downloads/<slug>/
Plantillas HTML src/_includes/
Página principal src/index.njk
Logo del sitio static/logo/jailgames-logo.png
CSS src/css/style.css
Salida final _site/

3. Formato de Datos por Juego

Cada juego se define en un archivo YAML independiente dentro de src/games/. El nombre del archivo es el slug (identificador URL).

Ejemplo: src/games/cosmic-escape.yaml

name: "Cosmic Escape"
slug: "cosmic-escape"
tagline: "Escapa de la estación antes de que colapse"
description: |
  Un juego de plataformas 2D donde controlas a un astronauta atrapado en una
  estación espacial que se desmorona. Recoge oxígeno, esquiva meteoritos y
  encuentra la cápsula de escape antes de que se agote el tiempo.

  Desarrollado en Godot Engine. Incluye 12 niveles y modo contrarreloj.

version: "1.2.0"
release_date: "2025-11-15"
tags:
  - plataformas
  - 2D
  - singleplayer

logo: "/games/cosmic-escape/logo.png"

screenshots:
  - "/games/cosmic-escape/screenshot-1.png"
  - "/games/cosmic-escape/screenshot-2.png"
  - "/games/cosmic-escape/screenshot-3.png"

downloads:
  - platform: "Windows"
    file: "/downloads/cosmic-escape/cosmic-escape-v1.2-windows.zip"
    size: "85 MB"
  - platform: "Linux"
    file: "/downloads/cosmic-escape/cosmic-escape-v1.2-linux.tar.gz"
    size: "78 MB"
  - platform: "macOS"
    file: "/downloads/cosmic-escape/cosmic-escape-v1.2-mac.dmg"
    size: "90 MB"

# Campos opcionales
engine: "Godot 4.2"
players: "1 jugador"
genre: "Plataformas / Acción"
repo: "https://gitea.local/jailgames/cosmic-escape"  # Solo referencia interna

Campos obligatorios vs opcionales

Campo Obligatorio Descripción
name Nombre visible del juego
slug Identificador para URLs y carpetas
description Texto descriptivo (soporta párrafos)
version Versión del juego
release_date Fecha de publicación (YYYY-MM-DD)
downloads Al menos una entrada con platform/file/size
screenshots Al menos una captura
logo No Si no existe, se usa una imagen placeholder
tagline No Frase corta para la tarjeta de la home
tags No Etiquetas para filtrar
engine No Motor de desarrollo
players No Número de jugadores
genre No Género del juego
repo No Enlace al repositorio (solo referencia)

4. Generación de Páginas

4.1 Configuración de Eleventy (.eleventy.js)

const yaml = require("js-yaml");
const fs = require("fs");
const path = require("path");

module.exports = function (eleventyConfig) {
  // Parsear archivos YAML
  eleventyConfig.addDataExtension("yaml,yml", (contents) =>
    yaml.load(contents)
  );

  // Copiar archivos estáticos al build
  eleventyConfig.addPassthroughCopy("static");
  eleventyConfig.addPassthroughCopy("downloads");
  eleventyConfig.addPassthroughCopy("src/css");
  eleventyConfig.addPassthroughCopy("src/js");

  // Colección: todos los juegos ordenados por fecha
  eleventyConfig.addCollection("games", function () {
    const gamesDir = path.join(__dirname, "src", "games");
    const files = fs.readdirSync(gamesDir).filter((f) => f.endsWith(".yaml"));
    return files
      .map((f) => {
        const data = yaml.load(
          fs.readFileSync(path.join(gamesDir, f), "utf-8")
        );
        return data;
      })
      .sort((a, b) => new Date(b.release_date) - new Date(a.release_date));
  });

  // Filtro para formatear fechas
  eleventyConfig.addFilter("dateFormat", function (dateStr) {
    const d = new Date(dateStr);
    return d.toLocaleDateString("es-ES", {
      year: "numeric",
      month: "long",
      day: "numeric",
    });
  });

  // Filtro Markdown para descripciones
  const markdownIt = require("markdown-it");
  const md = markdownIt({ html: true });
  eleventyConfig.addFilter("markdown", (content) => md.render(content || ""));

  return {
    dir: {
      input: "src",
      includes: "_includes",
      data: "_data",
      output: "_site",
    },
    templateFormats: ["njk", "md"],
    htmlTemplateEngine: "njk",
  };
};

4.2 Página Principal (src/index.njk)

La home itera sobre la colección games y renderiza una tarjeta por cada juego.

---
layout: base.njk
title: "JAILGAMES — Nuestros Juegos"
---

<section class="hero">
  <img src="/logo/jailgames-logo.png" alt="JAILGAMES" class="site-logo" />
  <p class="hero-subtitle">Juegos caseros, hechos con cariño</p>
</section>

<section class="games-grid">
  {% for game in collections.games %}
    {% include "game-card.njk" %}
  {% endfor %}
</section>

4.3 Tarjeta de Juego (src/_includes/game-card.njk)

<a href="/game/{{ game.slug }}/" class="game-card">
  <div class="game-card__image">
    {% if game.logo %}
      <img src="{{ game.logo }}" alt="{{ game.name }} logo" loading="lazy" />
    {% else %}
      <div class="game-card__placeholder">🎮</div>
    {% endif %}
  </div>
  <div class="game-card__info">
    <h2 class="game-card__title">{{ game.name }}</h2>
    {% if game.tagline %}
      <p class="game-card__tagline">{{ game.tagline }}</p>
    {% endif %}
    <div class="game-card__meta">
      <span class="game-card__version">v{{ game.version }}</span>
      {% if game.tags %}
        {% for tag in game.tags %}
          <span class="game-card__tag">{{ tag }}</span>
        {% endfor %}
      {% endif %}
    </div>
  </div>
</a>

4.4 Generación de Fichas Individuales (src/game.njk)

Eleventy genera una página por cada archivo YAML mediante paginación:

---
pagination:
  data: collections.games
  size: 1
  alias: game
permalink: "/game/{{ game.slug }}/"
layout: game-page.njk
eleventyComputed:
  title: "{{ game.name }} — JAILGAMES"
---

4.5 Layout de Ficha (src/_includes/game-page.njk)

{% extends "base.njk" %}

{% block content %}
<article class="game-detail">

  <header class="game-detail__header">
    {% if game.logo %}
      <img src="{{ game.logo }}" alt="{{ game.name }}" class="game-detail__logo" />
    {% endif %}
    <div>
      <h1>{{ game.name }}</h1>
      {% if game.tagline %}<p class="game-detail__tagline">{{ game.tagline }}</p>{% endif %}
      <div class="game-detail__meta">
        <span>v{{ game.version }}</span>
        <span>{{ game.release_date | dateFormat }}</span>
        {% if game.genre %}<span>{{ game.genre }}</span>{% endif %}
        {% if game.players %}<span>{{ game.players }}</span>{% endif %}
        {% if game.engine %}<span>{{ game.engine }}</span>{% endif %}
      </div>
    </div>
  </header>

  <section class="game-detail__screenshots">
    {% for img in game.screenshots %}
      <img src="{{ img }}" alt="Captura de {{ game.name }}" loading="lazy"
           class="game-detail__screenshot" />
    {% endfor %}
  </section>

  <section class="game-detail__description">
    {{ game.description | markdown | safe }}
  </section>

  <section class="game-detail__downloads">
    <h2>Descargar</h2>
    <div class="download-buttons">
      {% for dl in game.downloads %}
        <a href="{{ dl.file }}" class="download-btn" download>
          <span class="download-btn__platform">{{ dl.platform }}</span>
          <span class="download-btn__size">{{ dl.size }}</span>
        </a>
      {% endfor %}
    </div>
  </section>

  {% if game.tags %}
  <section class="game-detail__tags">
    {% for tag in game.tags %}
      <span class="tag">{{ tag }}</span>
    {% endfor %}
  </section>
  {% endif %}

  <a href="/" class="back-link">← Volver al catálogo</a>

</article>
{% endblock %}

4.6 Descargas Locales

Las descargas se enlazan directamente a archivos en /downloads/<slug>/. El atributo download en el <a> fuerza la descarga en lugar de abrir el archivo en el navegador. Nginx sirve estos archivos como estáticos. No hay enlaces a Gitea ni servicios externos.


5. Diseño y Estilo

5.1 Framework CSS: PicoCSS

Se recomienda PicoCSS (@picocss/pico) por:

  • Estilo limpio sin necesidad de clases (aplica estilos directamente a elementos HTML semánticos).
  • Muy ligero (~10 KB).
  • Soporte nativo de tema oscuro/claro.
  • Responsive por defecto.
  • Sin dependencias de build (se puede usar como archivo CSS estático).

Si se prefiere más control visual, la alternativa es Tailwind CSS (requiere build step).

5.2 Esquema de Layout

┌──────────────────────────────────────────────┐
│              HEADER                          │
│   [Logo JAILGAMES]     Juegos caseros...     │
├──────────────────────────────────────────────┤
│                                              │
│   ┌──────┐  ┌──────┐  ┌──────┐              │
│   │ Logo │  │ Logo │  │ Logo │              │
│   │      │  │      │  │      │              │
│   │ Name │  │ Name │  │ Name │   ← Grid    │
│   │ Tag  │  │ Tag  │  │ Tag  │     3 cols   │
│   └──────┘  └──────┘  └──────┘              │
│                                              │
│   ┌──────┐  ┌──────┐                        │
│   │ ...  │  │ ...  │                        │
│   └──────┘  └──────┘                        │
│                                              │
├──────────────────────────────────────────────┤
│              FOOTER                          │
│   JAILGAMES · Hecho con ♥                   │
└──────────────────────────────────────────────┘

Ficha individual:

┌──────────────────────────────────────────────┐
│   ← Volver     HEADER                       │
├──────────────────────────────────────────────┤
│                                              │
│   [Logo]  Nombre del Juego                   │
│           v1.2.0 · 15 nov 2025 · Godot       │
│                                              │
│   ┌──────────────────────────────────────┐   │
│   │      Galería de capturas             │   │
│   │  [img1]    [img2]    [img3]          │   │
│   └──────────────────────────────────────┘   │
│                                              │
│   Descripción del juego en prosa...          │
│   Párrafo 1...                               │
│   Párrafo 2...                               │
│                                              │
│   ┌──────────┐ ┌──────────┐ ┌──────────┐    │
│   │ Windows  │ │  Linux   │ │  macOS   │    │
│   │  85 MB   │ │  78 MB   │ │  90 MB   │    │
│   └──────────┘ └──────────┘ └──────────┘    │
│                                              │
│   #plataformas  #2D  #singleplayer           │
│                                              │
├──────────────────────────────────────────────┤
│              FOOTER                          │
└──────────────────────────────────────────────┘

5.3 Diseño Responsive

  • Desktop (>1024px): Grid de 3 columnas para tarjetas.
  • Tablet (7681024px): Grid de 2 columnas.
  • Móvil (<768px): 1 columna, capturas en scroll horizontal.

5.4 Paleta de Colores Sugerida

:root {
  --color-bg: #0f0f0f;
  --color-surface: #1a1a2e;
  --color-primary: #e94560;
  --color-secondary: #16213e;
  --color-text: #eaeaea;
  --color-text-muted: #a0a0a0;
  --color-accent: #0f3460;
  --color-success: #4ecca3;
  --font-main: "Inter", "Segoe UI", sans-serif;
  --font-heading: "Space Grotesk", "Inter", sans-serif;
}

Tema oscuro por defecto (estética gaming), con contraste suficiente para legibilidad.


6. Pipeline de Actualización

Añadir un juego nuevo

Paso 1  Crear el archivo YAML
        → src/games/<slug>.yaml
        (copiar de un juego existente y modificar campos)

Paso 2  Crear carpeta de imágenes
        → static/games/<slug>/
        Copiar: logo.png, screenshot-1.png, screenshot-2.png, ...

Paso 3  Crear carpeta de descargas
        → downloads/<slug>/
        Copiar binarios: <slug>-v<version>-<platform>.<ext>

Paso 4  Verificar que las rutas en el YAML coinciden con los archivos copiados.

Paso 5  Regenerar el sitio:
        $ npm run build
        (Esto ejecuta: npx @11ty/eleventy)

Paso 6  Verificar localmente:
        $ npm run serve
        (Esto ejecuta: npx @11ty/eleventy --serve)
        Abrir http://localhost:8080 y comprobar.

Paso 7  Reconstruir el contenedor Docker:
        $ docker compose up --build -d

Modificar un juego existente

Paso 1  Editar src/games/<slug>.yaml
Paso 2  Si hay imágenes nuevas, copiarlas a static/games/<slug>/
Paso 3  Si hay binarios nuevos, copiarlos a downloads/<slug>/
Paso 4  Regenerar: npm run build
Paso 5  Reconstruir Docker: docker compose up --build -d

Diagrama del flujo

[YAML nuevo] + [Imágenes] + [Binarios]
        │
        ▼
   npm run build      ← Eleventy lee YAML, genera HTML
        │
        ▼
    _site/             ← HTML/CSS/JS estático listo
        │
        ▼
   docker compose up   ← Nginx sirve _site/ + downloads/
        │
        ▼
   http://localhost     ← Web lista para la feria

7. Plan de Docker

7.1 Dockerfile

# ---------- ETAPA 1: Build ----------
FROM node:20-alpine AS builder

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

COPY src/ src/
COPY static/ static/
COPY .eleventy.js ./

RUN npx @11ty/eleventy

# ---------- ETAPA 2: Servir ----------
FROM nginx:alpine

# Configuración personalizada de Nginx
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Copiar el sitio generado
COPY --from=builder /app/_site /usr/share/nginx/html

# Copiar descargas (no pasan por Eleventy)
COPY downloads/ /usr/share/nginx/html/downloads/

EXPOSE 80

7.2 docker-compose.yml

version: "3.8"

services:
  jailgames:
    build: .
    container_name: jailgames-web
    ports:
      - "80:80"
    volumes:
      # Volumen para descargas: permite actualizar binarios
      # sin reconstruir la imagen completa
      - ./downloads:/usr/share/nginx/html/downloads:ro
      # Volumen para imágenes de juegos (opcional, misma idea)
      - ./static/games:/usr/share/nginx/html/games:ro
    restart: unless-stopped

7.3 nginx.conf

server {
    listen 80;
    server_name localhost;

    root /usr/share/nginx/html;
    index index.html;

    # Compresión para HTML/CSS/JS
    gzip on;
    gzip_types text/html text/css application/javascript application/json image/svg+xml;
    gzip_min_length 256;

    # Cacheo agresivo para assets estáticos (feria local)
    location ~* \.(png|jpg|jpeg|gif|svg|ico|webp)$ {
        expires 7d;
        add_header Cache-Control "public, immutable";
    }

    # Descargas: forzar descarga en lugar de abrir en navegador
    location /downloads/ {
        add_header Content-Disposition "attachment";
        sendfile on;
        tcp_nopush on;
    }

    # SPA fallback (por si se accede directamente a /game/slug/)
    location / {
        try_files $uri $uri/ $uri.html =404;
    }

    # Página 404 personalizada
    error_page 404 /404.html;
}

7.4 Uso con Apache (alternativa)

Si se prefiere Apache, reemplazar la etapa 2 del Dockerfile:

FROM httpd:alpine
COPY --from=builder /app/_site /usr/local/apache2/htdocs/
COPY downloads/ /usr/local/apache2/htdocs/downloads/
COPY apache.conf /usr/local/apache2/conf/httpd.conf
EXPOSE 80

7.5 Despliegue en Ferias sin Internet

1. En la máquina de desarrollo (con internet):
   $ docker compose build
   $ docker save jailgames-web:latest | gzip > jailgames-image.tar.gz

2. Copiar jailgames-image.tar.gz al portátil de la feria (USB).

3. En el portátil de la feria (sin internet):
   $ gunzip -c jailgames-image.tar.gz | docker load
   $ docker run -d -p 80:80 \
       -v /ruta/local/downloads:/usr/share/nginx/html/downloads:ro \
       --name jailgames jailgames-web:latest

4. Abrir http://localhost en cualquier navegador.
   (O configurar un punto de acceso WiFi para que otros dispositivos
    se conecten a la IP local del portátil)

8. Lista de Tareas para Claude Code

Copiar esta lista tal cual y dársela a Claude Code como instrucciones paso a paso.


BLOQUE A — Inicialización del Proyecto

TAREA A1: Crear la estructura de carpetas del proyecto "jailgames"

Crear los siguientes directorios:
  - src/_data/
  - src/_includes/
  - src/games/
  - src/css/
  - src/js/
  - static/logo/
  - static/games/
  - downloads/

TAREA A2: Inicializar package.json

Ejecutar: npm init -y
Instalar dependencias:
  npm install --save-dev @11ty/eleventy js-yaml markdown-it

Añadir scripts a package.json:
  "build": "npx @11ty/eleventy",
  "serve": "npx @11ty/eleventy --serve --port=8080",
  "clean": "rm -rf _site"

BLOQUE B — Configuración de Eleventy

TAREA B1: Crear .eleventy.js en la raíz del proyecto

Contenido: Configuración de Eleventy que:
  - Parsea archivos YAML con js-yaml
  - Copia "static" y "downloads" como passthrough
  - Copia "src/css" y "src/js" como passthrough
  - Crea una colección "games" leyendo todos los .yaml de src/games/
  - Ordena los juegos por release_date (más recientes primero)
  - Añade filtro "dateFormat" que formatea fechas en español
  - Añade filtro "markdown" que convierte texto a HTML con markdown-it
  - Input: src/, Output: _site/, Includes: _includes/, Data: _data/

TAREA B2: Crear src/_data/site.json

Contenido:
{
  "name": "JAILGAMES",
  "tagline": "Juegos caseros, hechos con cariño",
  "url": "http://localhost",
  "lang": "es"
}

BLOQUE C — Plantillas HTML (Nunjucks)

TAREA C1: Crear src/_includes/base.njk

Layout base HTML5 que incluya:
  - DOCTYPE html, lang="es"
  - Meta charset, viewport
  - Title dinámico: {{ title }} o fallback a site.name
  - Enlace a /css/style.css
  - Header con logo de JAILGAMES (enlazado a /)
  - Bloque {% block content %}{% endblock %}
  - Footer con "JAILGAMES · Hecho con ♥"
  - Enlace a /js/main.js antes de cerrar body

TAREA C2: Crear src/_includes/game-card.njk

Componente tarjeta de juego:
  - Enlace <a> a /game/{{ game.slug }}/
  - Imagen del logo del juego (o placeholder si no hay logo)
  - Nombre del juego <h2>
  - Tagline si existe
  - Versión y tags en un div de metadatos

TAREA C3: Crear src/_includes/game-page.njk

Layout para ficha individual. Extiende base.njk. Incluye:
  - Header con logo del juego + nombre + tagline + metadatos
    (versión, fecha formateada, género, players, engine)
  - Sección de galería de capturas (iterar screenshots)
  - Sección de descripción (usar filtro markdown)
  - Sección de descargas: un botón por cada entrada en downloads
    (con atributo download, mostrar plataforma y tamaño)
  - Sección de tags
  - Enlace "← Volver al catálogo" a /

TAREA C4: Crear src/index.njk

Página principal:
  - Layout: base.njk
  - Sección hero con logo y subtítulo
  - Sección games-grid que itera collections.games
    e incluye game-card.njk para cada juego

TAREA C5: Crear src/game.njk

Plantilla de paginación que genera /game/<slug>/ para cada juego:
  - Front matter con pagination (data: collections.games, size: 1, alias: game)
  - Permalink: /game/{{ game.slug }}/
  - Layout: game-page.njk
  - eleventyComputed title

BLOQUE D — Estilos CSS

TAREA D1: Crear src/css/style.css

Archivo CSS completo que incluya:
  - Variables CSS (colores tema oscuro gaming, fuentes)
  - Reset mínimo (box-sizing, margin 0)
  - Body: fondo oscuro, color claro, fuente sans-serif
  - .site-header: flex, centrado, con logo a la izquierda
  - .site-logo: max-height 60px
  - .hero: centrado, padding generoso
  - .hero-subtitle: color muted
  - .games-grid: CSS Grid, 3 columnas en desktop, 2 en tablet, 1 en móvil
  - .game-card: bloque con fondo surface, borde redondeado, hover con sombra,
    transición suave. Sin text-decoration.
  - .game-card__image img: object-fit contain, altura fija
  - .game-card__title: color blanco
  - .game-card__tagline: color muted, font-size pequeño
  - .game-card__tag: badge pequeño con color accent
  - .game-detail: max-width 900px, centrado, padding
  - .game-detail__header: flex, logo a la izquierda, info a la derecha
  - .game-detail__screenshots: grid o flex con scroll horizontal en móvil
  - .game-detail__screenshot: borde redondeado, max-width 100%
  - .download-buttons: flex, wrap
  - .download-btn: botón grande con color primary, hover,
    icono de plataforma si es posible
  - .tag: badge igual que game-card__tag
  - .back-link: estilo enlace simple
  - .site-footer: centrado, padding, color muted
  - Media queries para responsive (768px, 1024px)

BLOQUE E — JavaScript Mínimo

TAREA E1: Crear src/js/main.js

Funcionalidad mínima:
  - Lightbox simple para capturas: al hacer clic en una screenshot,
    mostrarla en overlay a pantalla completa. Clic en overlay = cerrar.
  - (Opcional) Filtrado de juegos por tags en la home.
  - No usar frameworks, vanilla JS puro.

BLOQUE F — Datos de Ejemplo

TAREA F1: Crear 2 archivos YAML de ejemplo en src/games/

Archivo 1: src/games/cosmic-escape.yaml
  - Rellenar con datos ficticios pero realistas
  - Incluir al menos 3 screenshots, 2 plataformas de descarga
  - Incluir todos los campos opcionales como ejemplo

Archivo 2: src/games/pixel-dungeon.yaml
  - Rellenar con datos ficticios diferentes
  - Incluir solo campos obligatorios (ejemplo minimalista)

TAREA F2: Crear imágenes placeholder

  - static/logo/jailgames-logo.png → placeholder SVG o imagen genérica
  - static/games/cosmic-escape/logo.png → placeholder
  - static/games/cosmic-escape/screenshot-1.png → placeholder 800x450
  - static/games/cosmic-escape/screenshot-2.png → placeholder 800x450
  - static/games/pixel-dungeon/logo.png → placeholder

  (Pueden ser SVGs generados con texto identificativo)

TAREA F3: Crear archivos de descarga de ejemplo

  - downloads/cosmic-escape/README.txt
    (archivo de texto que diga "Aquí irá el binario de Cosmic Escape")
  - downloads/pixel-dungeon/README.txt
    (archivo de texto que diga "Aquí irá el binario de Pixel Dungeon")

BLOQUE G — Página 404

TAREA G1: Crear src/404.njk

Página de error 404:
  - Layout: base.njk
  - Permalink: /404.html
  - Mensaje amigable: "Página no encontrada"
  - Enlace de vuelta a la home

BLOQUE H — Docker

TAREA H1: Crear Dockerfile en la raíz

Multi-stage build:
  - Etapa 1 (builder): node:20-alpine, npm ci, npx @11ty/eleventy
  - Etapa 2 (serve): nginx:alpine, copiar _site y downloads, copiar nginx.conf

TAREA H2: Crear nginx.conf en la raíz

Configuración Nginx:
  - Puerto 80, root /usr/share/nginx/html
  - Gzip activado para text/html, text/css, application/javascript
  - Cache de imágenes 7 días
  - Descargas con Content-Disposition: attachment
  - try_files para rutas limpias
  - error_page 404 → /404.html

TAREA H3: Crear docker-compose.yml en la raíz

Servicio jailgames:
  - Build desde el directorio actual
  - Puerto 80:80
  - Volumen downloads como read-only
  - restart: unless-stopped

BLOQUE I — Documentación

TAREA I1: Crear README.md en la raíz

Incluir:
  - Nombre del proyecto y descripción breve
  - Requisitos previos (Node.js 18+, Docker)
  - Cómo instalar dependencias (npm ci)
  - Cómo ejecutar en desarrollo (npm run serve)
  - Cómo hacer build (npm run build)
  - Cómo añadir un juego nuevo (los 7 pasos del pipeline)
  - Cómo desplegar con Docker (docker compose up --build)
  - Cómo desplegar sin internet (docker save/load)
  - Estructura de carpetas resumida

BLOQUE J — Verificación Final

TAREA J1: Ejecutar npm run build y verificar que _site/ se genera correctamente.

TAREA J2: Ejecutar npm run serve y verificar en http://localhost:8080:
  - La home muestra las tarjetas de los 2 juegos de ejemplo
  - Hacer clic en una tarjeta lleva a la ficha individual
  - La ficha muestra logo, capturas, descripción y botones de descarga
  - Los botones de descarga funcionan (descargan el archivo)
  - El enlace "Volver" funciona
  - La página 404 funciona al visitar una ruta inexistente
  - El diseño es responsive (probar en móvil con DevTools)

TAREA J3: Ejecutar docker compose up --build y verificar en http://localhost:80
  que todo funciona igual que en J2.

Resumen Ejecutivo

Aspecto Decisión
Generador Eleventy (11ty)
Formato de datos YAML (1 archivo por juego)
Plantillas Nunjucks
CSS CSS custom con tema oscuro (opcionalmente PicoCSS)
Servidor Nginx en Docker
Build Multi-stage Docker (Node → Nginx)
Tiempo para añadir un juego ~5 minutos (crear YAML + copiar archivos + rebuild)
Funciona sin internet Sí (docker save/load)