Sitio del estudio indie de videojuegos Shadow Games. Fase 1: portafolio + showcase de juegos. Las fases futuras (jobs, education, open-source) se sumarán sin reescribir la arquitectura.
"Donde la penumbra cobra vida." · "Mundos con sombra, hechos a mano."
- Next.js 16 — App Router, Turbopack, React 19.2, TypeScript estricto.
- Tailwind CSS v4 — design tokens en
@theme(paleta dark/light, spacing, radios). - next-intl 4 — i18n (solo
esen fase 1, infra lista paraen). - next-themes — toggle dark/light, respeta
prefers-color-scheme. - Radix UI + primitives propias en
src/components/ui/. - react-hook-form + zod + Resend — formulario de contacto.
- Sonner — toasts (montado solo en
/contacto). - next/og — Open Graph images dinámicas.
Sin librería de animación: las entradas usan CSS puro (hero) e IntersectionObserver + CSS (
<Reveal>). Motion se retiró del bundle por rendimiento.
npm install
cp .env.example .env.local # rellena RESEND_API_KEY si vas a probar el form
npm run dev # http://localhost:3000 (redirige a /es)npm run dev # desarrollo (Turbopack)
npm run build # build de producción
npm run start # sirve el build de producción
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm run format # prettier --write .
npm run press:zip # regenera los ZIPs del press kit (ver abajo)src/
app/
layout.tsx ← layout raíz (pass-through, requerido por Next)
icon.svg ← favicon (convención de archivos)
apple-icon.tsx ← apple-touch-icon (next/og)
manifest.ts ← web app manifest
robots.ts ← robots.txt autogenerado
sitemap.ts ← sitemap.xml autogenerado (con hreflang)
actions/contact.ts ← Server Action del form (Resend + honeypot + rate-limit)
[locale]/
layout.tsx ← layout real: fonts, metadata, JSON-LD Organization, providers
page.tsx ← home
opengraph-image.tsx ← OG genérica
not-found.tsx ← 404
error.tsx ← error boundary
juegos/page.tsx ← catálogo + filtros (estado/género) por URL
juegos/[slug]/page.tsx ← detalle de juego (+ opengraph-image.tsx por juego)
estudio/ press/ contacto/ privacidad/ aviso-legal/
components/
providers.tsx ← ThemeProvider (next-themes)
theme-toggle.tsx
site/ ← header, footer, brand-mark, social-links
home/ ← hero, featured-games, studio, merch, community, game-card
games/ ← filter-bar, screenshots-gallery, game-image
contact/contact-form.tsx
press/press-trailer-button.tsx
legal/legal-page.tsx ← layout compartido /privacidad y /aviso-legal
motion/reveal.tsx ← reveal on-scroll (IntersectionObserver + CSS)
ui/ ← primitives propias (button, input, textarea, badge, dialog, toaster…)
i18n/
routing.ts ← rutas localizadas
request.ts ← carga de mensajes por request
navigation.ts ← Link/redirect/usePathname tipados
messages/es.json ← TODAS las cadenas en español
lib/
games.ts ← catálogo (10 juegos; schema listo para CMS en fase 2)
site.ts ← SITE_URL + helper de canonical/hreflang
social.ts ← URLs sociales (placeholders "#")
filters.ts ← parseo de filtros del catálogo
contact-schema.ts ← schema zod compartido cliente/servidor
cn.ts ← clsx + tailwind-merge
proxy.ts ← middleware de next-intl (renombrado en Next 16)
scripts/
build-press-zip.mjs ← genera los ZIPs del press kit
design-system/shadow-games-studio/
PRINCIPLES.md ← reglas de diseño DURADERAS (minimalismo, antipatrones)
MASTER.md ← design system base (no editar a mano; ver nota interna)
pages/*.md ← overrides por ruta
public/
brand/logo.svg ← logo placeholder
games/<slug>/ ← key art, cards, screenshots (vacío en fase 1)
press/*.zip ← press kit generado
- Añade una entrada al array
GAMESensrc/lib/games.tscon unslugúnico y estable (no lo cambies tras publicar: rompe enlaces y SEO). - Crea
public/games/<slug>/conkey-art.jpg,card.jpgyscreen-1.jpg…cuando tengas los assets. Mientras no existan, la UI degrada a un fallback de gradiente automáticamente. featured: truesolo si quieres que aparezca en el grid del home (máx. 6).- Campos opcionales (
trailer,longDescription,links,accentColor,screenshots) se omiten con elegancia si faltan — no hace falta rellenarlos todos.
La nueva ruta /es/juegos/<slug>, su OG image, el sitemap y el JSON-LD se generan solos (SSG).
Todo el texto vive en src/i18n/messages/es.json, agrupado por namespace (Home, Games, Studio, Press, Contact, Legal, Footer…). Edita el valor y listo; los componentes lo leen con getTranslations/useTranslations.
Para añadir inglés en el futuro: añade "en" a routing.locales en src/i18n/routing.ts, traduce los pathnames, y crea messages/en.json. El código de componentes ya está preparado.
Bloques marcados
// TODO: bio real/bio prensa realen/estudioy/pressson placeholders pendientes de copy definitivo del cliente.
- Paleta: navy
#181534+ crema#f5f3ef+ violeta#8b5cf6(#6d4ad6en claro para contraste AA). - Display: Fraunces (variable). Body: Inter Tight. Tema por defecto: oscuro.
- Tokens (colores, radios, sombras, escala de spacing de sección):
src/app/globals.cssdentro de@theme. Cambia un color/espaciado ahí y se propaga. - Reglas de diseño:
design-system/shadow-games-studio/PRINCIPLES.md(minimalismo, antipatrones prohibidos). Orden de autoridad: PRINCIPLES > pages/<ruta>.md > MASTER.
Los ZIPs descargables de /press se generan con un script idempotente:
npm run press:zipCrea en public/press/: shadow-games-logos.zip, shadow-games-key-arts.zip, shadow-games-screenshots.zip y el maestro shadow-games-press-kit.zip. En fase 1 contienen el logo placeholder + READMEs explicativos; vuelve a ejecutarlo cuando lleguen el logo definitivo, las key arts y las screenshots reales.
Ver .env.example. En local van en .env.local (gitignored). En producción, en Vercel → Settings → Environment Variables.
| Variable | Requerida | Uso |
|---|---|---|
RESEND_API_KEY |
Sí (prod) | Envío del formulario de contacto vía Resend. |
NEXT_PUBLIC_SITE_URL |
Recomendada | URL pública (canonical, sitemap, robots, OG). Sin barra final. |
CONTACT_EMAIL |
No | Inbox que recibe el form. Default shadowgames.devteam@gmail.com. |
RESEND_FROM |
No | Remitente. Default onboarding@resend.dev (solo entrega al email de la cuenta Resend hasta verificar dominio). |
Sin
RESEND_API_KEY, el formulario degrada con elegancia: muestra un toast "servicio de email no configurado" en vez de romperse.
El proyecto despliega en Vercel con configuración por defecto (framework Next.js autodetectado). Pasos resumidos:
- Push del repo a GitHub.
- Vercel → Add New… → Project → Import el repo.
- Añadir las variables de entorno (tabla de arriba) antes del primer deploy.
- Deploy.
robots.txt, sitemap.xml, OG images, favicons y manifest se sirven automáticamente.
Cuando registres el dominio (p. ej. shadowgames.studio):
- Vercel → Project → Settings → Domains → Add e introduce el dominio.
- Vercel te dará los registros DNS:
- Apex (
shadowgames.studio) → registro A a76.76.21.21, o ALIAS/ANAME al destino que indique Vercel. www→ registro CNAME acname.vercel-dns.com.
- Apex (
- Configura esos registros en tu proveedor de DNS (donde compraste el dominio) y espera la propagación (minutos a unas horas). Vercel emite el certificado SSL solo.
- Marca el dominio como Primary en Vercel (redirige el
.vercel.appal dominio propio). - Actualiza la variable
NEXT_PUBLIC_SITE_URLen Vercel al nuevo dominio y vuelve a desplegar, para que canonical/sitemap/OG usen la URL definitiva.
- GSAP + ScrollTrigger (storytelling de scroll más rico).
- Sanity CMS — sustituir
lib/games.tspor una fuente remota sin tocar componentes. - Rutas
/jobs,/learn,/open-source. - Tienda real de merch (la sección "Shadow Stuff" es placeholder).
- Inglés (
en) — infra i18n ya lista.