e337d7bc45
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>
926 lines
29 KiB
Markdown
926 lines
29 KiB
Markdown
# 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 (768–1024px)**: 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) |
|