diff --git a/.eleventy.js b/.eleventy.js new file mode 100644 index 0000000..626cec0 --- /dev/null +++ b/.eleventy.js @@ -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", + }; +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..825c533 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Build output +_site/ + +# Node +node_modules/ + +# OS +.DS_Store +Thumbs.db + +# Editor +.vscode/ +.idea/ +*.swp diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fa4f4d9 --- /dev/null +++ b/Dockerfile @@ -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 diff --git a/JAILGAMES_Plan_Tecnico.md b/JAILGAMES_Plan_Tecnico.md new file mode 100644 index 0000000..058f053 --- /dev/null +++ b/JAILGAMES_Plan_Tecnico.md @@ -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/.yaml` | +| Imágenes de cada juego | `static/games//` | +| Binarios descargables | `downloads//` | +| 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" +--- + +
+ +

Juegos caseros, hechos con cariño

+
+ +
+ {% for game in collections.games %} + {% include "game-card.njk" %} + {% endfor %} +
+``` + +### 4.3 Tarjeta de Juego (`src/_includes/game-card.njk`) + +```html + +
+ {% if game.logo %} + {{ game.name }} logo + {% else %} +
🎮
+ {% endif %} +
+
+

{{ game.name }}

+ {% if game.tagline %} +

{{ game.tagline }}

+ {% endif %} +
+ v{{ game.version }} + {% if game.tags %} + {% for tag in game.tags %} + {{ tag }} + {% endfor %} + {% endif %} +
+
+
+``` + +### 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 %} +
+ +
+ {% if game.logo %} + + {% endif %} +
+

{{ game.name }}

+ {% if game.tagline %}

{{ game.tagline }}

{% endif %} +
+ v{{ game.version }} + {{ game.release_date | dateFormat }} + {% if game.genre %}{{ game.genre }}{% endif %} + {% if game.players %}{{ game.players }}{% endif %} + {% if game.engine %}{{ game.engine }}{% endif %} +
+
+
+ +
+ {% for img in game.screenshots %} + Captura de {{ game.name }} + {% endfor %} +
+ +
+ {{ game.description | markdown | safe }} +
+ +
+

Descargar

+
+ {% for dl in game.downloads %} + + {{ dl.platform }} + {{ dl.size }} + + {% endfor %} +
+
+ + {% if game.tags %} +
+ {% for tag in game.tags %} + {{ tag }} + {% endfor %} +
+ {% endif %} + + ← Volver al catálogo + +
+{% endblock %} +``` + +### 4.6 Descargas Locales + +Las descargas se enlazan directamente a archivos en `/downloads//`. El atributo `download` en el `` 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/.yaml + (copiar de un juego existente y modificar campos) + +Paso 2 Crear carpeta de imágenes + → static/games// + Copiar: logo.png, screenshot-1.png, screenshot-2.png, ... + +Paso 3 Crear carpeta de descargas + → downloads// + Copiar binarios: -v-. + +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/.yaml +Paso 2 Si hay imágenes nuevas, copiarlas a static/games// +Paso 3 Si hay binarios nuevos, copiarlos a downloads// +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 /game/{{ game.slug }}/ + - Imagen del logo del juego (o placeholder si no hay logo) + - Nombre del juego

+ - 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// 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) | diff --git a/README.md b/README.md index e69de29..d44385c 100644 --- a/README.md +++ b/README.md @@ -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/.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// + ``` + Copiar: `logo.png` (o `.svg`), `screenshot-1.png`, `screenshot-2.png`, ... + +3. **Crear la carpeta de descargas:** + ``` + downloads// + ``` + Copiar los binarios: `-v-.` + +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// # Imágenes de cada juego +├── downloads// # Binarios descargables +├── _site/ # Build generado (no versionar) +├── .eleventy.js # Configuración de Eleventy +├── package.json +├── Dockerfile +├── docker-compose.yml +└── nginx.conf +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2877ec0 --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/downloads/cosmic-escape/README.txt b/downloads/cosmic-escape/README.txt new file mode 100644 index 0000000..c4e6307 --- /dev/null +++ b/downloads/cosmic-escape/README.txt @@ -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 diff --git a/downloads/pixel-dungeon/README.txt b/downloads/pixel-dungeon/README.txt new file mode 100644 index 0000000..3033d32 --- /dev/null +++ b/downloads/pixel-dungeon/README.txt @@ -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 diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..eeaf588 --- /dev/null +++ b/nginx.conf @@ -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; +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..230ff94 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1525 @@ +{ + "name": "web_jailgames", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web_jailgames", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@11ty/eleventy": "^3.1.5", + "js-yaml": "^4.1.1", + "markdown-it": "^14.1.1" + } + }, + "node_modules/@11ty/dependency-tree": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-4.0.2.tgz", + "integrity": "sha512-RTF6VTZHatYf7fSZBUN3RKwiUeJh5dhWV61gDPrHhQF2/gzruAkYz8yXuvGLx3w3ZBKreGrR+MfYpSVkdbdbLA==", + "dev": true, + "dependencies": { + "@11ty/eleventy-utils": "^2.0.1" + } + }, + "node_modules/@11ty/dependency-tree-esm": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree-esm/-/dependency-tree-esm-2.0.4.tgz", + "integrity": "sha512-MYKC0Ac77ILr1HnRJalzKDlb9Z8To3kXQCltx299pUXXUFtJ1RIONtULlknknqW8cLe19DLVgmxVCtjEFm7h0A==", + "dev": true, + "dependencies": { + "@11ty/eleventy-utils": "^2.0.7", + "acorn": "^8.15.0", + "dependency-graph": "^1.0.0", + "normalize-path": "^3.0.0" + } + }, + "node_modules/@11ty/eleventy": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-3.1.5.tgz", + "integrity": "sha512-hZ0g6MwZyRxCqXsPm82gIM304LraKbUz3ZmezOSjsqxttZG6cHTib3Qq8QkESJoKwnr+yX1eyfOkPC5/mEgZnQ==", + "dev": true, + "dependencies": { + "@11ty/dependency-tree": "^4.0.2", + "@11ty/dependency-tree-esm": "^2.0.4", + "@11ty/eleventy-dev-server": "^2.0.8", + "@11ty/eleventy-plugin-bundle": "^3.0.7", + "@11ty/eleventy-utils": "^2.0.7", + "@11ty/lodash-custom": "^4.17.21", + "@11ty/posthtml-urls": "^1.0.2", + "@11ty/recursive-copy": "^4.0.4", + "@sindresorhus/slugify": "^2.2.1", + "bcp-47-normalize": "^2.3.0", + "chokidar": "^3.6.0", + "debug": "^4.4.3", + "dependency-graph": "^1.0.0", + "entities": "^6.0.1", + "filesize": "^10.1.6", + "gray-matter": "^4.0.3", + "iso-639-1": "^3.1.5", + "js-yaml": "^4.1.1", + "kleur": "^4.1.5", + "liquidjs": "^10.25.0", + "luxon": "^3.7.2", + "markdown-it": "^14.1.1", + "minimist": "^1.2.8", + "moo": "0.5.2", + "node-retrieve-globals": "^6.0.1", + "nunjucks": "^3.2.4", + "picomatch": "^4.0.3", + "please-upgrade-node": "^3.2.0", + "posthtml": "^0.16.7", + "posthtml-match-helper": "^2.0.3", + "semver": "^7.7.4", + "slugify": "^1.6.8", + "tinyglobby": "^0.2.15" + }, + "bin": { + "eleventy": "cmd.cjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-dev-server": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-2.0.8.tgz", + "integrity": "sha512-15oC5M1DQlCaOMUq4limKRYmWiGecDaGwryr7fTE/oM9Ix8siqMvWi+I8VjsfrGr+iViDvWcH/TVI6D12d93mA==", + "dev": true, + "dependencies": { + "@11ty/eleventy-utils": "^2.0.1", + "chokidar": "^3.6.0", + "debug": "^4.4.0", + "finalhandler": "^1.3.1", + "mime": "^3.0.0", + "minimist": "^1.2.8", + "morphdom": "^2.7.4", + "please-upgrade-node": "^3.2.0", + "send": "^1.1.0", + "ssri": "^11.0.0", + "urlpattern-polyfill": "^10.0.0", + "ws": "^8.18.1" + }, + "bin": { + "eleventy-dev-server": "cmd.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-plugin-bundle": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-bundle/-/eleventy-plugin-bundle-3.0.7.tgz", + "integrity": "sha512-QK1tRFBhQdZASnYU8GMzpTdsMMFLVAkuU0gVVILqNyp09xJJZb81kAS3AFrNrwBCsgLxTdWHJ8N64+OTTsoKkA==", + "dev": true, + "dependencies": { + "@11ty/eleventy-utils": "^2.0.2", + "debug": "^4.4.0", + "posthtml-match-helper": "^2.0.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-2.0.7.tgz", + "integrity": "sha512-6QE+duqSQ0GY9rENXYb4iPR4AYGdrFpqnmi59tFp9VrleOl0QSh8VlBr2yd6dlhkdtj7904poZW5PvGr9cMiJQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/lodash-custom": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@11ty/lodash-custom/-/lodash-custom-4.17.21.tgz", + "integrity": "sha512-Mqt6im1xpb1Ykn3nbcCovWXK3ggywRJa+IXIdoz4wIIK+cvozADH63lexcuPpGS/gJ6/m2JxyyXDyupkMr5DHw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/posthtml-urls": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@11ty/posthtml-urls/-/posthtml-urls-1.0.3.tgz", + "integrity": "sha512-1YvhnkaNlFnnJic1rBMWmTC2adbuy+JQiBfl1Hecr1Wjjik1pQZmGyk/eC9zKX/FQv52s2Nht1Gi/UwhYqrBeg==", + "dev": true, + "dependencies": { + "evaluate-value": "^2.0.0", + "http-equiv-refresh": "^2.0.1", + "list-to-array": "^1.1.0", + "parse-srcset": "^1.0.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@11ty/recursive-copy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.4.tgz", + "integrity": "sha512-oI7m8pa7/IAU/3lqRU9vjBbs20iKFo7x+1K9kT3aVira6scc1X9MjBdgLCHzLJeJ7iB6wydioA+kr9/qPnvmlQ==", + "dev": true, + "dependencies": { + "errno": "^1.0.0", + "junk": "^3.1.0", + "minimatch": "^3.1.5", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sindresorhus/slugify": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", + "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", + "dev": true, + "dependencies": { + "@sindresorhus/transliterate": "^1.0.0", + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", + "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "dev": true, + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-normalize": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-2.3.0.tgz", + "integrity": "sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==", + "dev": true, + "dependencies": { + "bcp-47": "^2.0.0", + "bcp-47-match": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz", + "integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==", + "dev": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esm-import-transformer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/esm-import-transformer/-/esm-import-transformer-3.0.5.tgz", + "integrity": "sha512-1GKLvfuMnnpI75l8c6sHoz0L3Z872xL5akGuBudgqTDPv4Vy6f2Ec7jEMKTxlqWl/3kSvNbHELeimJtnqgYniw==", + "dev": true, + "dependencies": { + "acorn": "^8.15.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/evaluate-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/evaluate-value/-/evaluate-value-2.0.0.tgz", + "integrity": "sha512-VonfiuDJc0z4sOO7W0Pd130VLsXN6vmBWZlrog1mCb/o7o/Nl5Lr25+Kj/nkCCAhG+zqeeGjxhkK9oHpkgTHhQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filesize": { + "version": "10.1.6", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz", + "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==", + "dev": true, + "engines": { + "node": ">= 10.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-equiv-refresh": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-2.0.1.tgz", + "integrity": "sha512-XJpDL/MLkV3dKwLzHwr2dY05dYNfBNlyPu4STQ8WvKCFdc6vC5tPXuq28of663+gHVg03C+16pHHs/+FmmDjcw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/iso-639-1": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.5.tgz", + "integrity": "sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/liquidjs": { + "version": "10.25.2", + "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.25.2.tgz", + "integrity": "sha512-ZbgcjEjGNlAIjqhuMzymO3lCpHgmVMftKfrq4/YLLxmKaFFeQMXRGrJTqKX7OXX1hKVPUDpTIrvL7lxt3X/hmw==", + "dev": true, + "dependencies": { + "commander": "^10.0.0" + }, + "bin": { + "liquid": "bin/liquid.js", + "liquidjs": "bin/liquid.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/liquidjs" + } + }, + "node_modules/list-to-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/list-to-array/-/list-to-array-1.1.0.tgz", + "integrity": "sha512-+dAZZ2mM+/m+vY9ezfoueVvrgnHIGi5FvgSymbIgJOFwiznWyA59mav95L+Mc6xPtL3s9gm5eNTlNtxJLbNM1g==", + "dev": true + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", + "dev": true + }, + "node_modules/morphdom": { + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.8.tgz", + "integrity": "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/node-retrieve-globals": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/node-retrieve-globals/-/node-retrieve-globals-6.0.1.tgz", + "integrity": "sha512-j0DeFuZ/Wg3VlklfbxUgZF/mdHMTEiEipBb3q0SpMMbHaV3AVfoUQF8UGxh1s/yjqO0TgRZd4Pi/x2yRqoQ4Eg==", + "dev": true, + "dependencies": { + "acorn": "^8.14.1", + "acorn-walk": "^8.3.4", + "esm-import-transformer": "^3.0.3" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nunjucks": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", + "dev": true, + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", + "dev": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/posthtml": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.7.tgz", + "integrity": "sha512-7Hc+IvlQ7hlaIfQFZnxlRl0jnpWq2qwibORBhQYIb0QbNtuicc5ZxvKkVT71HJ4Py1wSZ/3VR1r8LfkCtoCzhw==", + "dev": true, + "dependencies": { + "posthtml-parser": "^0.11.0", + "posthtml-render": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/posthtml-match-helper": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-2.0.3.tgz", + "integrity": "sha512-p9oJgTdMF2dyd7WE54QI1LvpBIkNkbSiiECKezNnDVYhGhD1AaOnAkw0Uh0y5TW+OHO8iBdSqnd8Wkpb6iUqmw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "posthtml": "^0.16.6" + } + }, + "node_modules/posthtml-parser": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", + "dev": true, + "dependencies": { + "htmlparser2": "^7.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml-render": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", + "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", + "dev": true, + "dependencies": { + "is-json": "^2.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slugify": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.8.tgz", + "integrity": "sha512-HVk9X1E0gz3mSpoi60h/saazLKXKaZThMLU3u/aNwoYn8/xQyX2MGxL0ui2eaokkD7tF+Zo+cKTHUbe1mmmGzA==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/ssri": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-11.0.0.tgz", + "integrity": "sha512-aZpUoMN/Jj2MqA4vMCeiKGnc/8SuSyHbGSBdgFbZxP8OJGF/lFkIuElzPxsN0q8TQQ+prw3P4EDfB3TBHHgfXw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "dev": true + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0c73b3f --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/src/404.njk b/src/404.njk new file mode 100644 index 0000000..4337cfa --- /dev/null +++ b/src/404.njk @@ -0,0 +1,12 @@ +--- +layout: base.njk +title: "404 — Página no encontrada — JAILGAMES" +permalink: /404.html +--- + + diff --git a/src/_data/site.json b/src/_data/site.json new file mode 100644 index 0000000..c3aa13a --- /dev/null +++ b/src/_data/site.json @@ -0,0 +1,6 @@ +{ + "name": "JAILGAMES", + "tagline": "Juegos caseros, hechos con cariño", + "url": "http://localhost", + "lang": "es" +} diff --git a/src/_includes/base.njk b/src/_includes/base.njk new file mode 100644 index 0000000..11492ad --- /dev/null +++ b/src/_includes/base.njk @@ -0,0 +1,29 @@ + + + + + + {{ title if title else site.name }} + + + + + + + +
+ {{ content | safe }} +
+ +
+

{{ site.name }} · Hecho con ♥

+
+ + + + diff --git a/src/_includes/game-card.njk b/src/_includes/game-card.njk new file mode 100644 index 0000000..c6894bb --- /dev/null +++ b/src/_includes/game-card.njk @@ -0,0 +1,23 @@ + +
+ {% if game.logo %} + {{ game.name }} logo + {% else %} +
🎮
+ {% endif %} +
+
+

{{ game.name }}

+ {% if game.tagline %} +

{{ game.tagline }}

+ {% endif %} +
+ v{{ game.version }} + {% if game.tags %} + {% for tag in game.tags %} + {{ tag }} + {% endfor %} + {% endif %} +
+
+
diff --git a/src/_includes/game-page.njk b/src/_includes/game-page.njk new file mode 100644 index 0000000..9faea11 --- /dev/null +++ b/src/_includes/game-page.njk @@ -0,0 +1,60 @@ +--- +layout: base.njk +--- +
+ +
+ {% if game.logo %} + + {% endif %} +
+

{{ game.name }}

+ {% if game.tagline %} +

{{ game.tagline }}

+ {% endif %} +
+ v{{ game.version }} + {{ game.release_date | dateFormat }} + {% if game.genre %}{{ game.genre }}{% endif %} + {% if game.players %}{{ game.players }}{% endif %} + {% if game.engine %}{{ game.engine }}{% endif %} +
+
+
+ + {% if game.screenshots and game.screenshots.length %} +
+ {% for img in game.screenshots %} + Captura {{ loop.index }} de {{ game.name }} + {% endfor %} +
+ {% endif %} + +
+ {{ game.description | markdown | safe }} +
+ +
+

Descargar

+
+ {% for dl in game.downloads %} + + {{ dl.platform }} + {{ dl.size }} + + {% endfor %} +
+
+ + {% if game.tags %} +
+ {% for tag in game.tags %} + {{ tag }} + {% endfor %} +
+ {% endif %} + + ← Volver al catálogo + +
diff --git a/src/css/style.css b/src/css/style.css new file mode 100644 index 0000000..3d6d23e --- /dev/null +++ b/src/css/style.css @@ -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; + } +} diff --git a/src/game.njk b/src/game.njk new file mode 100644 index 0000000..5e4381e --- /dev/null +++ b/src/game.njk @@ -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" +--- diff --git a/src/games/cosmic-escape.yaml b/src/games/cosmic-escape.yaml new file mode 100644 index 0000000..1df0304 --- /dev/null +++ b/src/games/cosmic-escape.yaml @@ -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" diff --git a/src/games/pixel-dungeon.yaml b/src/games/pixel-dungeon.yaml new file mode 100644 index 0000000..2298617 --- /dev/null +++ b/src/games/pixel-dungeon.yaml @@ -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" diff --git a/src/index.njk b/src/index.njk new file mode 100644 index 0000000..df39d1e --- /dev/null +++ b/src/index.njk @@ -0,0 +1,15 @@ +--- +layout: base.njk +title: "JAILGAMES — Nuestros Juegos" +--- + +
+ +

{{ site.tagline }}

+
+ +
+ {% for game in collections.games %} + {% include "game-card.njk" %} + {% endfor %} +
diff --git a/src/js/main.js b/src/js/main.js new file mode 100644 index 0000000..fe040e0 --- /dev/null +++ b/src/js/main.js @@ -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(); + }); +})(); diff --git a/static/games/cosmic-escape/logo.svg b/static/games/cosmic-escape/logo.svg new file mode 100644 index 0000000..6d2ecbe --- /dev/null +++ b/static/games/cosmic-escape/logo.svg @@ -0,0 +1,6 @@ + + + 🚀 + COSMIC ESCAPE + v1.2.0 + diff --git a/static/games/cosmic-escape/screenshot-1.svg b/static/games/cosmic-escape/screenshot-1.svg new file mode 100644 index 0000000..00c1f59 --- /dev/null +++ b/static/games/cosmic-escape/screenshot-1.svg @@ -0,0 +1,12 @@ + + + + + + + + + + COSMIC ESCAPE — Nivel 1 + [Captura 1 — Placeholder] + diff --git a/static/games/cosmic-escape/screenshot-2.svg b/static/games/cosmic-escape/screenshot-2.svg new file mode 100644 index 0000000..4020421 --- /dev/null +++ b/static/games/cosmic-escape/screenshot-2.svg @@ -0,0 +1,11 @@ + + + + + + + + + COSMIC ESCAPE — Nivel 4 + [Captura 2 — Placeholder] + diff --git a/static/games/cosmic-escape/screenshot-3.svg b/static/games/cosmic-escape/screenshot-3.svg new file mode 100644 index 0000000..4067294 --- /dev/null +++ b/static/games/cosmic-escape/screenshot-3.svg @@ -0,0 +1,10 @@ + + + + + + 👾 + + COSMIC ESCAPE — Jefe Final + [Captura 3 — Placeholder] + diff --git a/static/games/pixel-dungeon/logo.svg b/static/games/pixel-dungeon/logo.svg new file mode 100644 index 0000000..fa84dc2 --- /dev/null +++ b/static/games/pixel-dungeon/logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + PIXEL DUNGEON + v0.9.1 + diff --git a/static/games/pixel-dungeon/screenshot-1.svg b/static/games/pixel-dungeon/screenshot-1.svg new file mode 100644 index 0000000..6f2cc4e --- /dev/null +++ b/static/games/pixel-dungeon/screenshot-1.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + PIXEL DUNGEON — Nivel B1 + diff --git a/static/logo/jailgames-logo.svg b/static/logo/jailgames-logo.svg new file mode 100644 index 0000000..10788b5 --- /dev/null +++ b/static/logo/jailgames-logo.svg @@ -0,0 +1,5 @@ + + + JAIL + GAMES +