Files
web-jailgames/JAILGAMES_Plan_Tecnico.md
T
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

926 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`
```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` | Sí | Nombre visible del juego |
| `slug` | Sí | Identificador para URLs y carpetas |
| `description` | Sí | Texto descriptivo (soporta párrafos) |
| `version` | Sí | Versión del juego |
| `release_date` | Sí | Fecha de publicación (YYYY-MM-DD) |
| `downloads` | Sí | Al menos una entrada con platform/file/size |
| `screenshots` | Sí | 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`)
```javascript
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.
```html
---
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`)
```html
<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**:
```html
---
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`)
```html
{% 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
```css
: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
```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
```yaml
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
```nginx
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:
```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) |