From d2d1809c2451bc492a9527cd776dd81a419893ad Mon Sep 17 00:00:00 2001 From: consultoria-as Date: Tue, 17 Feb 2026 07:28:12 +0000 Subject: [PATCH] feat: add content system with MDX support, types, and meta files for 4 games Co-Authored-By: Claude Opus 4.6 --- content/maplestory/meta.json | 8 +++ content/ragnarok/meta.json | 8 +++ content/tales-of-pirates/meta.json | 8 +++ content/wow/meta.json | 8 +++ src/lib/content.ts | 93 ++++++++++++++++++++++++++++++ src/types/index.ts | 21 +++++++ 6 files changed, 146 insertions(+) create mode 100644 content/maplestory/meta.json create mode 100644 content/ragnarok/meta.json create mode 100644 content/tales-of-pirates/meta.json create mode 100644 content/wow/meta.json create mode 100644 src/lib/content.ts create mode 100644 src/types/index.ts diff --git a/content/maplestory/meta.json b/content/maplestory/meta.json new file mode 100644 index 0000000..9068c28 --- /dev/null +++ b/content/maplestory/meta.json @@ -0,0 +1,8 @@ +{ + "title": "MapleStory", + "subtitle": "El Mundo del Arce", + "description": "Bajo la sombra del Arbol del Mundo, heroes de todas las eras se alzan contra la oscuridad del Mago Negro en un mundo vibrante lleno de aventura y misterio.", + "color": "#2d5a1e", + "colorDark": "#1a3a12", + "accentColor": "#ff9b2e" +} diff --git a/content/ragnarok/meta.json b/content/ragnarok/meta.json new file mode 100644 index 0000000..fdcbb0a --- /dev/null +++ b/content/ragnarok/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Ragnarok Online", + "subtitle": "La Saga de Midgard", + "description": "En un mundo donde dioses y mortales coexisten, la tierra de Midgard se enfrenta a amenazas que podrian desatar el Ragnarok, el fin de todas las cosas.", + "color": "#8b2500", + "colorDark": "#5c1a00", + "accentColor": "#ff8c42" +} diff --git a/content/tales-of-pirates/meta.json b/content/tales-of-pirates/meta.json new file mode 100644 index 0000000..99a25fc --- /dev/null +++ b/content/tales-of-pirates/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Tales of Pirates", + "subtitle": "Leyendas del Mar", + "description": "En un mundo de islas infinitas y mares inexplorados, piratas, aventureros y cazadores de tesoros escriben su legenda en las aguas del destino.", + "color": "#1a3050", + "colorDark": "#0f1e35", + "accentColor": "#d4a843" +} diff --git a/content/wow/meta.json b/content/wow/meta.json new file mode 100644 index 0000000..c013bb3 --- /dev/null +++ b/content/wow/meta.json @@ -0,0 +1,8 @@ +{ + "title": "World of Warcraft", + "subtitle": "Cronicas de Azeroth", + "description": "Desde la creacion del universo por los Titanes hasta las guerras que definieron el destino de Azeroth, esta es la historia epica del mundo mas grande jamas creado en un MMORPG.", + "color": "#1a3a5c", + "colorDark": "#0f2440", + "accentColor": "#d4a843" +} diff --git a/src/lib/content.ts b/src/lib/content.ts new file mode 100644 index 0000000..a925c3d --- /dev/null +++ b/src/lib/content.ts @@ -0,0 +1,93 @@ +import fs from "fs"; +import path from "path"; +import matter from "gray-matter"; +import { GameMeta, Chapter, GameWithChapters } from "@/types"; + +const contentDir = path.join(process.cwd(), "content"); + +export function getAllGames(): GameMeta[] { + const gameDirs = fs.readdirSync(contentDir).filter((dir) => { + const fullPath = path.join(contentDir, dir); + return fs.statSync(fullPath).isDirectory(); + }); + + return gameDirs.map((slug) => { + const metaPath = path.join(contentDir, slug, "meta.json"); + const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8")); + return { slug, ...meta }; + }); +} + +export function getGame(slug: string): GameWithChapters { + const metaPath = path.join(contentDir, slug, "meta.json"); + const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8")); + + const gameDir = path.join(contentDir, slug); + const chapterFiles = fs + .readdirSync(gameDir) + .filter((f) => f.endsWith(".mdx")) + .sort(); + + const chapters = chapterFiles.map((file, index) => { + const filePath = path.join(gameDir, file); + const raw = fs.readFileSync(filePath, "utf-8"); + const { data } = matter(raw); + const chapterSlug = file.replace(".mdx", ""); + + return { + slug: chapterSlug, + number: index + 1, + title: (data.title as string) || chapterSlug, + }; + }); + + return { slug, ...meta, chapters }; +} + +export function getChapter(gameSlug: string, chapterSlug: string): Chapter { + const gameDir = path.join(contentDir, gameSlug); + const chapterFiles = fs + .readdirSync(gameDir) + .filter((f) => f.endsWith(".mdx")) + .sort(); + + const chapterIndex = chapterFiles.findIndex( + (f) => f.replace(".mdx", "") === chapterSlug + ); + const filePath = path.join(gameDir, `${chapterSlug}.mdx`); + const raw = fs.readFileSync(filePath, "utf-8"); + const { data, content } = matter(raw); + + return { + slug: chapterSlug, + number: chapterIndex + 1, + title: (data.title as string) || chapterSlug, + content, + }; +} + +export function getAdjacentChapters( + gameSlug: string, + chapterSlug: string +): { prev: string | null; next: string | null } { + const gameDir = path.join(contentDir, gameSlug); + const chapterFiles = fs + .readdirSync(gameDir) + .filter((f) => f.endsWith(".mdx")) + .sort(); + + const currentIndex = chapterFiles.findIndex( + (f) => f.replace(".mdx", "") === chapterSlug + ); + + return { + prev: + currentIndex > 0 + ? chapterFiles[currentIndex - 1].replace(".mdx", "") + : null, + next: + currentIndex < chapterFiles.length - 1 + ? chapterFiles[currentIndex + 1].replace(".mdx", "") + : null, + }; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..dcbcda5 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,21 @@ +export interface GameMeta { + slug: string; + title: string; + subtitle: string; + description: string; + color: string; + colorDark: string; + accentColor: string; + coverImage?: string; +} + +export interface Chapter { + slug: string; + number: number; + title: string; + content: string; +} + +export interface GameWithChapters extends GameMeta { + chapters: Omit[]; +}