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>
@@ -0,0 +1,58 @@
|
|||||||
|
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
|
||||||
|
// static/logo → _site/logo, static/games → _site/games
|
||||||
|
eleventyConfig.addPassthroughCopy({ "static/logo": "logo" });
|
||||||
|
eleventyConfig.addPassthroughCopy({ "static/games": "games" });
|
||||||
|
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 en español
|
||||||
|
eleventyConfig.addFilter("dateFormat", function (dateStr) {
|
||||||
|
const d = new Date(dateStr + "T00:00:00");
|
||||||
|
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",
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Build output
|
||||||
|
_site/
|
||||||
|
|
||||||
|
# Node
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# ---------- 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
|
||||||
@@ -0,0 +1,925 @@
|
|||||||
|
# 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) |
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
# JAILGAMES
|
||||||
|
|
||||||
|
Sitio web estático para publicar y descargar juegos indie del estudio JAILGAMES.
|
||||||
|
Generado con **Eleventy (11ty)** y servido con **Nginx en Docker**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requisitos previos
|
||||||
|
|
||||||
|
- **Node.js 18+** (para el build local)
|
||||||
|
- **Docker** + **Docker Compose** (para despliegue)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Instalación
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Desarrollo local
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Abre [http://localhost:8080](http://localhost:8080) en el navegador.
|
||||||
|
Eleventy reconstruye automáticamente al guardar cambios.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build estático
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
El resultado queda en `_site/`. Para limpiar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run clean
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Añadir un juego nuevo
|
||||||
|
|
||||||
|
1. **Crear el archivo YAML** del juego:
|
||||||
|
```
|
||||||
|
src/games/<slug>.yaml
|
||||||
|
```
|
||||||
|
Copiar de un juego existente y modificar los campos. El `slug` es el identificador URL (solo letras minúsculas, números y guiones).
|
||||||
|
|
||||||
|
2. **Crear la carpeta de imágenes:**
|
||||||
|
```
|
||||||
|
static/games/<slug>/
|
||||||
|
```
|
||||||
|
Copiar: `logo.png` (o `.svg`), `screenshot-1.png`, `screenshot-2.png`, ...
|
||||||
|
|
||||||
|
3. **Crear la carpeta de descargas:**
|
||||||
|
```
|
||||||
|
downloads/<slug>/
|
||||||
|
```
|
||||||
|
Copiar los binarios: `<slug>-v<version>-<plataforma>.<ext>`
|
||||||
|
|
||||||
|
4. **Verificar** que las rutas en el YAML coinciden exactamente con los archivos copiados.
|
||||||
|
|
||||||
|
5. **Regenerar el sitio:**
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Verificar localmente:**
|
||||||
|
```bash
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
Abrir [http://localhost:8080](http://localhost:8080) y comprobar el juego nuevo.
|
||||||
|
|
||||||
|
7. **Reconstruir el contenedor Docker:**
|
||||||
|
```bash
|
||||||
|
docker compose up --build -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Despliegue con Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up --build -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Acceder en [http://localhost](http://localhost).
|
||||||
|
|
||||||
|
Para detener:
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Despliegue sin internet (ferias)
|
||||||
|
|
||||||
|
**En la máquina con internet:**
|
||||||
|
```bash
|
||||||
|
docker compose build
|
||||||
|
docker save jailgames-web:latest | gzip > jailgames-image.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
**Copiar** `jailgames-image.tar.gz` al portátil de la feria (USB).
|
||||||
|
|
||||||
|
**En el portátil de la feria (sin internet):**
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Abrir [http://localhost](http://localhost) en cualquier navegador.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estructura de carpetas
|
||||||
|
|
||||||
|
```
|
||||||
|
jailgames/
|
||||||
|
├── src/
|
||||||
|
│ ├── _data/site.json # Datos globales del sitio
|
||||||
|
│ ├── _includes/
|
||||||
|
│ │ ├── base.njk # Layout base HTML
|
||||||
|
│ │ ├── game-card.njk # Tarjeta de juego (home)
|
||||||
|
│ │ └── game-page.njk # Ficha individual de juego
|
||||||
|
│ ├── games/ # Un archivo YAML por juego
|
||||||
|
│ ├── css/style.css # Estilos globales
|
||||||
|
│ ├── js/main.js # JS mínimo (lightbox)
|
||||||
|
│ ├── index.njk # Página principal
|
||||||
|
│ ├── game.njk # Generador de fichas individuales
|
||||||
|
│ └── 404.njk # Página de error 404
|
||||||
|
├── static/
|
||||||
|
│ ├── logo/ # Logo del sitio
|
||||||
|
│ └── games/<slug>/ # Imágenes de cada juego
|
||||||
|
├── downloads/<slug>/ # Binarios descargables
|
||||||
|
├── _site/ # Build generado (no versionar)
|
||||||
|
├── .eleventy.js # Configuración de Eleventy
|
||||||
|
├── package.json
|
||||||
|
├── Dockerfile
|
||||||
|
├── docker-compose.yml
|
||||||
|
└── nginx.conf
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
services:
|
||||||
|
jailgames:
|
||||||
|
build: .
|
||||||
|
container_name: jailgames-web
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
volumes:
|
||||||
|
# Permite actualizar binarios sin reconstruir la imagen completa
|
||||||
|
- ./downloads:/usr/share/nginx/html/downloads:ro
|
||||||
|
# Permite actualizar imágenes de juegos sin reconstruir
|
||||||
|
- ./static/games:/usr/share/nginx/html/games:ro
|
||||||
|
restart: unless-stopped
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
Aquí irá el binario de Cosmic Escape.
|
||||||
|
|
||||||
|
Nombre del juego: Cosmic Escape
|
||||||
|
Versión: 1.2.0
|
||||||
|
Motor: Godot 4.2
|
||||||
|
|
||||||
|
Plataformas disponibles:
|
||||||
|
- Windows: cosmic-escape-v1.2-windows.zip (~85 MB)
|
||||||
|
- Linux: cosmic-escape-v1.2-linux.tar.gz (~78 MB)
|
||||||
|
- macOS: cosmic-escape-v1.2-mac.dmg (~90 MB)
|
||||||
|
|
||||||
|
Instrucciones de instalación:
|
||||||
|
Windows: Descomprimir el ZIP y ejecutar CosmiEscape.exe
|
||||||
|
Linux: Descomprimir el tar.gz y ejecutar ./cosmic-escape
|
||||||
|
macOS: Abrir el DMG y arrastrar la app a Aplicaciones
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
Aquí irá el binario de Pixel Dungeon.
|
||||||
|
|
||||||
|
Nombre del juego: Pixel Dungeon
|
||||||
|
Versión: 0.9.1
|
||||||
|
|
||||||
|
Plataformas disponibles:
|
||||||
|
- Windows: pixel-dungeon-v0.9.1-windows.zip (~22 MB)
|
||||||
|
|
||||||
|
Instrucciones de instalación:
|
||||||
|
Windows: Descomprimir el ZIP y ejecutar PixelDungeon.exe
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rutas limpias
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ $uri.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Página 404 personalizada
|
||||||
|
error_page 404 /404.html;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "web_jailgames",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "npx @11ty/eleventy",
|
||||||
|
"serve": "npx @11ty/eleventy --serve --port=8080",
|
||||||
|
"clean": "rm -rf _site"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://gitea.sustancia.synology.me/jaildesigner-utilitats/web_jailgames.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@11ty/eleventy": "^3.1.5",
|
||||||
|
"js-yaml": "^4.1.1",
|
||||||
|
"markdown-it": "^14.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
layout: base.njk
|
||||||
|
title: "404 — Página no encontrada — JAILGAMES"
|
||||||
|
permalink: /404.html
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="not-found">
|
||||||
|
<h1>404</h1>
|
||||||
|
<h2>Página no encontrada</h2>
|
||||||
|
<p>La página que buscas no existe o ha sido movida.</p>
|
||||||
|
<a href="/" class="download-btn" style="display:inline-flex;">← Volver al catálogo</a>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "JAILGAMES",
|
||||||
|
"tagline": "Juegos caseros, hechos con cariño",
|
||||||
|
"url": "http://localhost",
|
||||||
|
"lang": "es"
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ site.lang | default('es') }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>{{ title if title else site.name }}</title>
|
||||||
|
<link rel="stylesheet" href="/css/style.css" />
|
||||||
|
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="site-header">
|
||||||
|
<a href="/" class="site-header__logo-link">
|
||||||
|
<img src="/logo/jailgames-logo.svg" alt="{{ site.name }}" class="site-logo" />
|
||||||
|
<span class="site-header__name">{{ site.name }}</span>
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="site-main">
|
||||||
|
{{ content | safe }}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="site-footer">
|
||||||
|
<p>{{ site.name }} · Hecho con ♥</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
layout: base.njk
|
||||||
|
---
|
||||||
|
<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 class="game-detail__header-info">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{% if game.screenshots and game.screenshots.length %}
|
||||||
|
<section class="game-detail__screenshots">
|
||||||
|
{% for img in game.screenshots %}
|
||||||
|
<img src="{{ img }}" alt="Captura {{ loop.index }} de {{ game.name }}"
|
||||||
|
loading="lazy" class="game-detail__screenshot" />
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<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>
|
||||||
@@ -0,0 +1,531 @@
|
|||||||
|
/* =============================================
|
||||||
|
VARIABLES — Tema oscuro gaming
|
||||||
|
============================================= */
|
||||||
|
:root {
|
||||||
|
--color-bg: #0f0f0f;
|
||||||
|
--color-surface: #1a1a2e;
|
||||||
|
--color-surface-hover: #22223a;
|
||||||
|
--color-primary: #e94560;
|
||||||
|
--color-primary-hover: #c73650;
|
||||||
|
--color-secondary: #16213e;
|
||||||
|
--color-text: #eaeaea;
|
||||||
|
--color-text-muted: #a0a0a0;
|
||||||
|
--color-accent: #0f3460;
|
||||||
|
--color-success: #4ecca3;
|
||||||
|
--color-border: #2a2a4a;
|
||||||
|
--font-main: "Segoe UI", "Inter", system-ui, sans-serif;
|
||||||
|
--font-heading: "Segoe UI", "Inter", system-ui, sans-serif;
|
||||||
|
--radius: 8px;
|
||||||
|
--radius-lg: 12px;
|
||||||
|
--shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||||
|
--shadow-hover: 0 8px 32px rgba(233, 69, 96, 0.3);
|
||||||
|
--transition: 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
RESET MÍNIMO
|
||||||
|
============================================= */
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
BASE
|
||||||
|
============================================= */
|
||||||
|
body {
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-family: var(--font-main);
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.6;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
margin: 0 0 0.5em;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
HEADER
|
||||||
|
============================================= */
|
||||||
|
.site-header {
|
||||||
|
background-color: var(--color-secondary);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
padding: 0.75rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header__logo-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-logo {
|
||||||
|
max-height: 48px;
|
||||||
|
width: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header__name {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
MAIN
|
||||||
|
============================================= */
|
||||||
|
.site-main {
|
||||||
|
flex: 1;
|
||||||
|
padding: 2rem 1.5rem;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
HERO
|
||||||
|
============================================= */
|
||||||
|
.hero {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 1rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-logo {
|
||||||
|
max-height: 120px;
|
||||||
|
margin: 0 auto 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subtitle {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
GAMES GRID
|
||||||
|
============================================= */
|
||||||
|
.games-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 1.5rem;
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
GAME CARD
|
||||||
|
============================================= */
|
||||||
|
.game-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
overflow: hidden;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color-text);
|
||||||
|
transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card__image {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card__image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card__placeholder {
|
||||||
|
font-size: 4rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card__info {
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card__title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card__tagline {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card__version {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-success);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card__tag {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
color: var(--color-text);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.15em 0.5em;
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
GAME DETAIL
|
||||||
|
============================================= */
|
||||||
|
.game-detail {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__logo {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__header-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__header-info h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__tagline {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__meta span {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.2em 0.6em;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
SCREENSHOTS
|
||||||
|
============================================= */
|
||||||
|
.game-detail__screenshots {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--color-border) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__screenshot {
|
||||||
|
width: 320px;
|
||||||
|
min-width: 280px;
|
||||||
|
height: 180px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform var(--transition), box-shadow var(--transition);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__screenshot:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
DESCRIPCIÓN
|
||||||
|
============================================= */
|
||||||
|
.game-detail__description {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__description p {
|
||||||
|
margin: 0 0 1em;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
DESCARGAS
|
||||||
|
============================================= */
|
||||||
|
.game-detail__downloads {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__downloads h2 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-weight: 600;
|
||||||
|
transition: background-color var(--transition), transform var(--transition);
|
||||||
|
min-width: 120px;
|
||||||
|
gap: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-btn:hover {
|
||||||
|
background-color: var(--color-primary-hover);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-btn__platform {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-btn__size {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
opacity: 0.85;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
TAGS
|
||||||
|
============================================= */
|
||||||
|
.game-detail__tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
color: var(--color-text);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.25em 0.7em;
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
BACK LINK
|
||||||
|
============================================= */
|
||||||
|
.back-link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: color var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
FOOTER
|
||||||
|
============================================= */
|
||||||
|
.site-footer {
|
||||||
|
background-color: var(--color-secondary);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
404
|
||||||
|
============================================= */
|
||||||
|
.not-found {
|
||||||
|
text-align: center;
|
||||||
|
padding: 4rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-found h1 {
|
||||||
|
font-size: 5rem;
|
||||||
|
color: var(--color-primary);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-found p {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
LIGHTBOX
|
||||||
|
============================================= */
|
||||||
|
.lightbox-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.92);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
cursor: zoom-out;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-overlay img {
|
||||||
|
max-width: 95vw;
|
||||||
|
max-height: 90vh;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 1.5rem;
|
||||||
|
right: 1.5rem;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: opacity var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-close:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
RESPONSIVE — Tablet (≤ 1024px)
|
||||||
|
============================================= */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.games-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
RESPONSIVE — Móvil (≤ 768px)
|
||||||
|
============================================= */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.site-main {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.games-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__meta {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-detail__header-info h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-logo {
|
||||||
|
max-height: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
pagination:
|
||||||
|
data: collections.games
|
||||||
|
size: 1
|
||||||
|
alias: game
|
||||||
|
permalink: "/game/{{ game.slug }}/"
|
||||||
|
layout: game-page.njk
|
||||||
|
eleventyComputed:
|
||||||
|
title: "{{ game.name }} — JAILGAMES"
|
||||||
|
---
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
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.
|
||||||
|
Ideal para partidas cortas e intensas.
|
||||||
|
|
||||||
|
version: "1.2.0"
|
||||||
|
release_date: "2025-11-15"
|
||||||
|
tags:
|
||||||
|
- plataformas
|
||||||
|
- 2D
|
||||||
|
- singleplayer
|
||||||
|
|
||||||
|
logo: "/games/cosmic-escape/logo.svg"
|
||||||
|
|
||||||
|
screenshots:
|
||||||
|
- "/games/cosmic-escape/screenshot-1.svg"
|
||||||
|
- "/games/cosmic-escape/screenshot-2.svg"
|
||||||
|
- "/games/cosmic-escape/screenshot-3.svg"
|
||||||
|
|
||||||
|
downloads:
|
||||||
|
- platform: "Windows"
|
||||||
|
file: "/downloads/cosmic-escape/README.txt"
|
||||||
|
size: "85 MB"
|
||||||
|
- platform: "Linux"
|
||||||
|
file: "/downloads/cosmic-escape/README.txt"
|
||||||
|
size: "78 MB"
|
||||||
|
- platform: "macOS"
|
||||||
|
file: "/downloads/cosmic-escape/README.txt"
|
||||||
|
size: "90 MB"
|
||||||
|
|
||||||
|
engine: "Godot 4.2"
|
||||||
|
players: "1 jugador"
|
||||||
|
genre: "Plataformas / Acción"
|
||||||
|
repo: "https://gitea.local/jailgames/cosmic-escape"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: "Pixel Dungeon"
|
||||||
|
slug: "pixel-dungeon"
|
||||||
|
description: |
|
||||||
|
Un roguelike de mazmorras con gráficos pixel art de 16x16. Explora
|
||||||
|
generaciones procedurales de niveles, recoge objetos y derrota al jefe
|
||||||
|
final. Cada partida es única.
|
||||||
|
|
||||||
|
version: "0.9.1"
|
||||||
|
release_date: "2025-08-03"
|
||||||
|
tags:
|
||||||
|
- roguelike
|
||||||
|
- pixel-art
|
||||||
|
- singleplayer
|
||||||
|
|
||||||
|
logo: "/games/pixel-dungeon/logo.svg"
|
||||||
|
|
||||||
|
screenshots:
|
||||||
|
- "/games/pixel-dungeon/screenshot-1.svg"
|
||||||
|
|
||||||
|
downloads:
|
||||||
|
- platform: "Windows"
|
||||||
|
file: "/downloads/pixel-dungeon/README.txt"
|
||||||
|
size: "22 MB"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
layout: base.njk
|
||||||
|
title: "JAILGAMES — Nuestros Juegos"
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<img src="/logo/jailgames-logo.svg" alt="JAILGAMES" class="site-logo hero-logo" />
|
||||||
|
<p class="hero-subtitle">{{ site.tagline }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="games-grid">
|
||||||
|
{% for game in collections.games %}
|
||||||
|
{% include "game-card.njk" %}
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// ── Lightbox para capturas de pantalla ──────────────────────────────────
|
||||||
|
function initLightbox() {
|
||||||
|
const screenshots = document.querySelectorAll(".game-detail__screenshot");
|
||||||
|
if (!screenshots.length) return;
|
||||||
|
|
||||||
|
const overlay = document.createElement("div");
|
||||||
|
overlay.className = "lightbox-overlay";
|
||||||
|
overlay.setAttribute("role", "dialog");
|
||||||
|
overlay.setAttribute("aria-modal", "true");
|
||||||
|
overlay.setAttribute("aria-label", "Vista ampliada de captura");
|
||||||
|
|
||||||
|
const img = document.createElement("img");
|
||||||
|
const closeBtn = document.createElement("button");
|
||||||
|
closeBtn.className = "lightbox-close";
|
||||||
|
closeBtn.textContent = "\u00D7";
|
||||||
|
closeBtn.setAttribute("aria-label", "Cerrar");
|
||||||
|
|
||||||
|
overlay.appendChild(img);
|
||||||
|
overlay.appendChild(closeBtn);
|
||||||
|
|
||||||
|
function open(src, alt) {
|
||||||
|
img.src = src;
|
||||||
|
img.alt = alt;
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
closeBtn.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
if (overlay.parentNode) {
|
||||||
|
document.body.removeChild(overlay);
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screenshots.forEach(function (el) {
|
||||||
|
el.addEventListener("click", function () {
|
||||||
|
open(el.src, el.alt);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
overlay.addEventListener("click", function (e) {
|
||||||
|
if (e.target === overlay) close();
|
||||||
|
});
|
||||||
|
|
||||||
|
closeBtn.addEventListener("click", close);
|
||||||
|
|
||||||
|
document.addEventListener("keydown", function (e) {
|
||||||
|
if (e.key === "Escape") close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
initLightbox();
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
|
||||||
|
<rect width="200" height="200" fill="#0f3460" rx="12"/>
|
||||||
|
<text x="100" y="90" text-anchor="middle" font-family="monospace" font-size="56" fill="#e94560">🚀</text>
|
||||||
|
<text x="100" y="130" text-anchor="middle" font-family="monospace" font-size="13" fill="#eaeaea">COSMIC ESCAPE</text>
|
||||||
|
<text x="100" y="155" text-anchor="middle" font-family="monospace" font-size="11" fill="#4ecca3">v1.2.0</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="450" viewBox="0 0 800 450">
|
||||||
|
<rect width="800" height="450" fill="#0a0a1a"/>
|
||||||
|
<rect x="0" y="380" width="800" height="70" fill="#1a1a2e"/>
|
||||||
|
<circle cx="120" cy="80" r="40" fill="#f4d03f" opacity="0.8"/>
|
||||||
|
<circle cx="300" cy="50" r="8" fill="#eaeaea" opacity="0.4"/>
|
||||||
|
<circle cx="500" cy="120" r="5" fill="#eaeaea" opacity="0.5"/>
|
||||||
|
<circle cx="700" cy="30" r="10" fill="#eaeaea" opacity="0.3"/>
|
||||||
|
<rect x="350" y="250" width="40" height="60" fill="#4ecca3"/>
|
||||||
|
<rect x="360" y="220" width="20" height="30" fill="#a0a0a0"/>
|
||||||
|
<text x="400" y="430" text-anchor="middle" font-family="monospace" font-size="18" fill="#e94560">COSMIC ESCAPE — Nivel 1</text>
|
||||||
|
<text x="400" y="230" text-anchor="middle" font-family="monospace" font-size="12" fill="#a0a0a0">[Captura 1 — Placeholder]</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 861 B |
@@ -0,0 +1,11 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="450" viewBox="0 0 800 450">
|
||||||
|
<rect width="800" height="450" fill="#16213e"/>
|
||||||
|
<rect x="50" y="200" width="700" height="20" fill="#0f3460"/>
|
||||||
|
<rect x="50" y="300" width="300" height="20" fill="#0f3460"/>
|
||||||
|
<rect x="500" y="320" width="250" height="20" fill="#0f3460"/>
|
||||||
|
<circle cx="200" cy="190" r="15" fill="#e94560" opacity="0.9"/>
|
||||||
|
<circle cx="400" cy="290" r="15" fill="#e94560" opacity="0.7"/>
|
||||||
|
<rect x="370" y="160" width="30" height="40" fill="#4ecca3"/>
|
||||||
|
<text x="400" y="430" text-anchor="middle" font-family="monospace" font-size="18" fill="#e94560">COSMIC ESCAPE — Nivel 4</text>
|
||||||
|
<text x="400" y="60" text-anchor="middle" font-family="monospace" font-size="12" fill="#a0a0a0">[Captura 2 — Placeholder]</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 799 B |
@@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="450" viewBox="0 0 800 450">
|
||||||
|
<rect width="800" height="450" fill="#1a0a0a"/>
|
||||||
|
<rect x="0" y="350" width="800" height="100" fill="#2a1a1a"/>
|
||||||
|
<ellipse cx="400" cy="200" rx="200" ry="120" fill="#e94560" opacity="0.15"/>
|
||||||
|
<circle cx="400" cy="200" r="60" fill="#c73650" opacity="0.6"/>
|
||||||
|
<text x="400" y="208" text-anchor="middle" font-family="monospace" font-size="36" fill="#eaeaea">👾</text>
|
||||||
|
<rect x="360" y="330" width="30" height="20" fill="#4ecca3"/>
|
||||||
|
<text x="400" y="430" text-anchor="middle" font-family="monospace" font-size="18" fill="#e94560">COSMIC ESCAPE — Jefe Final</text>
|
||||||
|
<text x="400" y="50" text-anchor="middle" font-family="monospace" font-size="12" fill="#a0a0a0">[Captura 3 — Placeholder]</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 797 B |
@@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
|
||||||
|
<rect width="200" height="200" fill="#16213e" rx="12"/>
|
||||||
|
<rect x="60" y="60" width="20" height="20" fill="#4ecca3"/>
|
||||||
|
<rect x="80" y="60" width="20" height="20" fill="#4ecca3"/>
|
||||||
|
<rect x="100" y="60" width="20" height="20" fill="#4ecca3"/>
|
||||||
|
<rect x="60" y="80" width="20" height="20" fill="#4ecca3"/>
|
||||||
|
<rect x="100" y="80" width="20" height="20" fill="#4ecca3"/>
|
||||||
|
<rect x="60" y="100" width="20" height="20" fill="#4ecca3"/>
|
||||||
|
<rect x="80" y="100" width="20" height="20" fill="#4ecca3"/>
|
||||||
|
<rect x="100" y="100" width="20" height="20" fill="#4ecca3"/>
|
||||||
|
<text x="100" y="150" text-anchor="middle" font-family="monospace" font-size="11" fill="#eaeaea">PIXEL DUNGEON</text>
|
||||||
|
<text x="100" y="170" text-anchor="middle" font-family="monospace" font-size="10" fill="#a0a0a0">v0.9.1</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 888 B |
@@ -0,0 +1,23 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="450" viewBox="0 0 800 450">
|
||||||
|
<rect width="800" height="450" fill="#0a0a12"/>
|
||||||
|
<!-- Cuadrícula de tiles -->
|
||||||
|
<rect x="100" y="100" width="600" height="260" fill="#16213e"/>
|
||||||
|
<!-- Paredes -->
|
||||||
|
<rect x="100" y="100" width="600" height="20" fill="#0f3460"/>
|
||||||
|
<rect x="100" y="340" width="600" height="20" fill="#0f3460"/>
|
||||||
|
<rect x="100" y="100" width="20" height="260" fill="#0f3460"/>
|
||||||
|
<rect x="680" y="100" width="20" height="260" fill="#0f3460"/>
|
||||||
|
<!-- Pasillos -->
|
||||||
|
<rect x="300" y="100" width="20" height="80" fill="#0a0a12"/>
|
||||||
|
<rect x="480" y="280" width="20" height="80" fill="#0a0a12"/>
|
||||||
|
<!-- Jugador -->
|
||||||
|
<rect x="190" y="200" width="16" height="16" fill="#4ecca3"/>
|
||||||
|
<rect x="194" y="196" width="8" height="8" fill="#eaeaea"/>
|
||||||
|
<!-- Enemigo -->
|
||||||
|
<rect x="500" y="230" width="16" height="16" fill="#e94560"/>
|
||||||
|
<rect x="504" y="226" width="8" height="8" fill="#a02040"/>
|
||||||
|
<!-- Objetos -->
|
||||||
|
<rect x="380" y="230" width="10" height="10" fill="#f4d03f"/>
|
||||||
|
<rect x="420" y="210" width="8" height="14" fill="#8e44ad"/>
|
||||||
|
<text x="400" y="430" text-anchor="middle" font-family="monospace" font-size="14" fill="#4ecca3">PIXEL DUNGEON — Nivel B1</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="80" viewBox="0 0 300 80">
|
||||||
|
<rect width="300" height="80" fill="#1a1a2e" rx="6"/>
|
||||||
|
<text x="20" y="52" font-family="monospace" font-size="36" font-weight="bold" fill="#e94560">JAIL</text>
|
||||||
|
<text x="115" y="52" font-family="monospace" font-size="36" font-weight="bold" fill="#4ecca3">GAMES</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 365 B |