feat: rewrite all 12 MMORPG lore books with novel-quality chapters (178 total)

Complete rewrite of all game lore as detailed Spanish literary prose.
Each chapter is 3000-5000 words of epic fantasy-novel narrative based
on real game lore. Replaced old 3-chapter summaries with full books:

- FFXIV: 20 chapters | WoW: 20 chapters
- FFXI: 15 chapters | EverQuest: 15 chapters | Guild Wars: 15 chapters
- Ragnarok Online: 15 chapters | MapleStory: 15 chapters | Tibia: 15 chapters
- MU Online: 12 chapters | TERA Online: 12 chapters
- Tales of Pirates: 12 chapters | Phantasy Star Online: 12 chapters

Also includes updated BookCover, BookSpine, BookShelf components
and new Emblem SVG system with 12 emblem types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
consultoria-as
2026-02-19 05:28:06 +00:00
parent f83dce31c7
commit a7af71ea2f
207 changed files with 5132 additions and 396 deletions

View File

@@ -1,92 +1,96 @@
import Link from "next/link";
import { GameWithChapters } from "@/types";
import Emblem from "./Emblem";
export default function BookCover({ game }: { game: GameWithChapters }) {
return (
<div className="min-h-screen bg-parchment text-stone-800">
{/* Back to shelf */}
<div className="p-4 md:p-6 lg:px-12">
<Link
href="/"
className="inline-flex items-center text-stone-500 hover:text-stone-700 font-lora text-sm transition-colors"
>
<span className="mr-2">&larr;</span> Volver a la estanteria
</Link>
</div>
{/* Hero banner */}
<div className="relative overflow-hidden"
style={{
background: `
radial-gradient(ellipse at 30% 30%, ${game.color}40 0%, transparent 60%),
radial-gradient(ellipse at 70% 70%, ${game.colorDark}50 0%, transparent 60%),
linear-gradient(to bottom, ${game.colorDark} 0%, ${game.color} 40%, ${game.colorDark} 100%)
`,
}}
>
<div className="relative z-10 p-4 md:p-6 lg:px-12">
<Link href="/"
className="inline-flex items-center text-white/60 hover:text-white/90 font-lora text-sm transition-colors"
>
<span className="mr-2">&larr;</span> Volver a la estanteria
</Link>
</div>
{/* Cover - two column layout on large screens */}
<div className="max-w-6xl mx-auto px-6 py-8 md:py-12 lg:grid lg:grid-cols-2 lg:gap-16 lg:items-start">
{/* Left: Title and description */}
<div className="text-center lg:text-left lg:sticky lg:top-8">
{/* Decorative top */}
<div
className="w-24 h-1 mx-auto lg:mx-0 mb-8 rounded"
style={{ backgroundColor: game.accentColor }}
/>
<div className="relative z-10 flex flex-col items-center text-center px-6 pt-4 pb-16 md:pt-8 md:pb-24">
<div className="mb-6" style={{ filter: "drop-shadow(0 4px 8px rgba(0,0,0,0.4))" }}>
<Emblem type={game.emblem} color={game.accentColor} size={72} />
</div>
<h1
className="font-playfair text-4xl md:text-5xl lg:text-6xl font-bold mb-3"
style={{ color: game.colorDark }}
<h1 className="font-playfair text-4xl md:text-6xl lg:text-7xl font-bold mb-3 tracking-wide"
style={{ color: game.accentColor, textShadow: `0 2px 4px rgba(0,0,0,0.5)` }}
>
{game.title}
</h1>
<p
className="font-lora italic text-lg md:text-xl mb-8"
style={{ color: game.color }}
<p className="font-lora italic text-lg md:text-xl tracking-[0.15em] uppercase mb-8"
style={{ color: `${game.accentColor}bb` }}
>
{game.subtitle}
</p>
{/* Decorative divider */}
<div className="flex items-center justify-center lg:justify-start gap-4 mb-8">
<div className="w-16 h-px bg-stone-300" />
<div
className="w-2 h-2 rotate-45"
style={{ backgroundColor: game.accentColor }}
/>
<div className="w-16 h-px bg-stone-300" />
<div className="flex items-center gap-3 mb-8">
<div className="w-12 h-[1px]" style={{ background: `linear-gradient(to right, transparent, ${game.accentColor}60)` }} />
<div className="w-1.5 h-1.5 rotate-45" style={{ backgroundColor: game.accentColor, opacity: 0.7 }} />
<div className="w-12 h-[1px]" style={{ background: `linear-gradient(to left, transparent, ${game.accentColor}60)` }} />
</div>
<p className="font-lora text-stone-600 text-base md:text-lg leading-relaxed max-w-lg mx-auto lg:mx-0 mb-12 lg:mb-0">
<p className="font-lora text-white/70 text-base md:text-lg leading-relaxed max-w-2xl">
{game.description}
</p>
</div>
{/* Right: Table of Contents */}
<div>
<h2 className="font-playfair text-xl md:text-2xl font-semibold text-stone-700 mb-6 text-center lg:text-left tracking-wide uppercase">
Indice
<div className="absolute bottom-0 left-0 right-0 h-8 md:h-12"
style={{ background: "linear-gradient(to bottom, transparent, #f5f0e8)" }}
/>
</div>
{/* Table of Contents */}
<div className="max-w-2xl mx-auto px-6 py-12 md:py-16">
<div className="text-center mb-10">
<h2 className="font-playfair text-2xl md:text-3xl font-bold text-stone-800 mb-4">
Indice de Capitulos
</h2>
<div className="space-y-1">
{game.chapters.map((chapter) => (
<Link
key={chapter.slug}
href={`/${game.slug}/${chapter.slug}`}
className="flex items-baseline gap-4 py-4 px-4 rounded-lg hover:bg-parchment-dark transition-colors group"
>
<span
className="font-playfair text-base md:text-lg font-bold min-w-[2.5rem]"
style={{ color: game.color }}
>
{String(chapter.number).padStart(2, "0")}
</span>
<span className="font-lora text-stone-700 group-hover:text-stone-900 flex-1 text-base md:text-lg">
{chapter.title}
</span>
<span className="text-stone-300 group-hover:text-stone-500 text-base">
&rarr;
</span>
</Link>
))}
<div className="flex items-center justify-center gap-3">
<div className="w-12 h-[1px] bg-stone-300" />
<div className="w-2 h-2 rotate-45" style={{ backgroundColor: game.accentColor }} />
<div className="w-12 h-[1px] bg-stone-300" />
</div>
</div>
{/* Decorative bottom */}
<div
className="w-24 h-1 mx-auto lg:mx-0 mt-12 rounded"
style={{ backgroundColor: game.accentColor }}
/>
<div className="space-y-1">
{game.chapters.map((chapter) => (
<Link key={chapter.slug} href={`/${game.slug}/${chapter.slug}`}
className="flex items-baseline gap-4 py-4 px-4 rounded-lg hover:bg-parchment-dark transition-colors group"
>
<span className="font-playfair text-2xl md:text-3xl font-bold min-w-[3rem]"
style={{ color: game.color }}
>
{String(chapter.number).padStart(2, "0")}
</span>
<span className="font-lora text-stone-700 group-hover:text-stone-900 flex-1 text-base md:text-lg">
{chapter.title}
</span>
<span className="text-stone-300 group-hover:text-stone-500 text-base">&rarr;</span>
</Link>
))}
</div>
<div className="flex items-center justify-center gap-3 mt-12">
<div className="w-16 h-[1px] bg-stone-300" />
<Emblem type={game.emblem} color={`${game.color}60`} size={24} />
<div className="w-16 h-[1px] bg-stone-300" />
</div>
</div>
</div>

View File

@@ -17,8 +17,7 @@ export default function BookShelf({ games }: { games: GameMeta[] }) {
{/* Shelf */}
<div className="relative w-full max-w-6xl mx-auto">
{/* Books row */}
<div className="flex gap-6 md:gap-10 lg:gap-14 items-end justify-center flex-wrap px-4 md:px-8 pb-4">
<div className="flex gap-5 md:gap-8 lg:gap-10 items-end justify-center flex-wrap px-4 md:px-8 pb-4">
{games.map((game) => (
<BookSpine key={game.slug} game={game} />
))}

View File

@@ -1,45 +1,105 @@
import Link from "next/link";
import { GameMeta } from "@/types";
import Emblem from "./Emblem";
export default function BookSpine({ game }: { game: GameMeta }) {
return (
<Link href={`/${game.slug}`} className="group block">
<div
className="relative w-44 h-64 md:w-56 md:h-80 lg:w-64 lg:h-96 rounded-r-md shadow-lg cursor-pointer transition-all duration-300 group-hover:-translate-y-3 group-hover:rotate-[-2deg] group-hover:shadow-2xl"
className="relative w-48 h-72 md:w-56 md:h-[340px] lg:w-64 lg:h-96 rounded-r-md shadow-lg cursor-pointer transition-all duration-300 group-hover:-translate-y-3 group-hover:rotate-[-2deg] group-hover:shadow-2xl overflow-hidden"
style={{
background: `linear-gradient(135deg, ${game.color} 0%, ${game.colorDark} 100%)`,
background: `
radial-gradient(ellipse at 25% 15%, rgba(255,255,255,0.12) 0%, transparent 50%),
radial-gradient(ellipse at 75% 85%, rgba(0,0,0,0.2) 0%, transparent 50%),
linear-gradient(135deg, ${game.color} 0%, ${game.colorDark} 70%)
`,
boxShadow: `
inset 0 0 30px rgba(0,0,0,0.3),
inset 3px 0 8px rgba(255,255,255,0.05),
6px 8px 20px rgba(0,0,0,0.4)
`,
}}
>
{/* Book spine edge */}
<div
className="absolute left-0 top-0 bottom-0 w-3 rounded-l-sm"
style={{ backgroundColor: game.colorDark }}
{/* Leather texture */}
<div className="absolute inset-0 opacity-[0.05]"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='8' height='8' viewBox='0 0 8 8' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23000' fill-opacity='1'%3E%3Cpath d='M0 0h1v1H0zm2 2h1v1H2zm4-2h1v1H6zm2 2h1v1H8zM1 3h1v1H1zm2 2h1v1H3zm4-2h1v1H5zm2 2h1v1H7zM0 6h1v1H0zm2 2h1v1H2zm4-2h1v1H6z'/%3E%3C/g%3E%3C/svg%3E")`,
}}
/>
{/* Book cover content */}
<div className="flex flex-col items-center justify-center h-full px-4 text-center">
<div
className="w-12 h-0.5 mb-3"
style={{ backgroundColor: game.accentColor }}
/>
<h2
className="font-playfair text-lg md:text-2xl lg:text-3xl font-bold leading-tight text-white"
{/* Spine edge with ridges */}
<div className="absolute left-0 top-0 bottom-0 w-5 md:w-6"
style={{
background: `linear-gradient(to right, ${game.colorDark}, ${game.color}99 40%, transparent)`,
boxShadow: "inset -1px 0 3px rgba(0,0,0,0.3)",
}}
>
{[18, 28, 72, 82].map((top) => (
<div key={top} className="absolute left-1 right-1 h-[2px]"
style={{
top: `${top}%`,
background: `linear-gradient(to right, ${game.accentColor}40, ${game.accentColor}80, ${game.accentColor}40)`,
boxShadow: `0 1px 0 rgba(0,0,0,0.3)`,
}}
/>
))}
</div>
{/* Embossed border */}
<div className="absolute top-3 left-7 right-3 bottom-3 md:top-4 md:left-8 md:right-4 md:bottom-4 rounded-sm"
style={{
border: `1px solid ${game.accentColor}45`,
boxShadow: `inset 0 0 0 1px ${game.accentColor}15`,
}}
/>
{/* Cover content */}
<div className="flex flex-col items-center justify-center h-full px-5 md:px-7 text-center relative z-10">
{/* Top ornament */}
<div className="flex items-center gap-2 mb-3">
<div className="w-6 md:w-8 h-[1px]" style={{ background: `linear-gradient(to right, transparent, ${game.accentColor}aa)` }} />
<div className="w-1.5 h-1.5 rotate-45" style={{ backgroundColor: game.accentColor, opacity: 0.8 }} />
<div className="w-6 md:w-8 h-[1px]" style={{ background: `linear-gradient(to left, transparent, ${game.accentColor}aa)` }} />
</div>
{/* Emblem */}
<div className="mb-3 opacity-80 group-hover:opacity-100 transition-opacity duration-300"
style={{ filter: `drop-shadow(0 2px 4px rgba(0,0,0,0.4))` }}
>
<Emblem type={game.emblem} color={game.accentColor} size={52} />
</div>
{/* Title */}
<h2 className="font-playfair text-lg md:text-2xl lg:text-3xl font-bold leading-tight"
style={{
color: game.accentColor,
textShadow: `0 1px 3px rgba(0,0,0,0.5), 0 0 20px ${game.accentColor}20`,
}}
>
{game.title}
</h2>
<p
className="text-xs md:text-sm mt-2 opacity-75 font-lora italic"
style={{ color: game.accentColor }}
{/* Subtitle */}
<p className="text-xs md:text-sm mt-2 font-lora italic tracking-wider"
style={{ color: game.accentColor, opacity: 0.75 }}
>
{game.subtitle}
</p>
<div
className="w-12 h-0.5 mt-3"
style={{ backgroundColor: game.accentColor }}
/>
{/* Bottom ornament */}
<div className="flex items-center gap-2 mt-3">
<div className="w-6 md:w-8 h-[1px]" style={{ background: `linear-gradient(to right, transparent, ${game.accentColor}aa)` }} />
<div className="w-1.5 h-1.5 rotate-45" style={{ backgroundColor: game.accentColor, opacity: 0.8 }} />
<div className="w-6 md:w-8 h-[1px]" style={{ background: `linear-gradient(to left, transparent, ${game.accentColor}aa)` }} />
</div>
</div>
{/* Shine effect on hover */}
{/* Vignette */}
<div className="absolute inset-0 pointer-events-none rounded-r-md"
style={{ background: "radial-gradient(ellipse at center, transparent 50%, rgba(0,0,0,0.2) 100%)" }}
/>
{/* Shine on hover */}
<div className="absolute inset-0 bg-gradient-to-r from-white/0 via-white/10 to-white/0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-r-md" />
</div>
</Link>

138
src/components/Emblem.tsx Normal file
View File

@@ -0,0 +1,138 @@
export default function Emblem({ type, color, size = 56 }: { type?: string; color: string; size?: number }) {
const style = { width: size, height: size };
switch (type) {
case "shield":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<path d="M24 4L6 12v12c0 11.1 7.7 21.5 18 24 10.3-2.5 18-12.9 18-24V12L24 4z"
stroke={color} strokeWidth="1.5" fill={`${color}15`} />
<path d="M24 9l-14 6.5v10c0 9 6 17.5 14 19.5 8-2 14-10.5 14-19.5v-10L24 9z"
stroke={color} strokeWidth="0.75" opacity="0.4" fill="none" />
<path d="M24 17v14M17 24h14" stroke={color} strokeWidth="2" strokeLinecap="round" />
</svg>
);
case "crossed-swords":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<path d="M14 6l18 18M34 6L16 24" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
<path d="M12 22l3 3-3 3M36 22l-3 3 3 3" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<circle cx="24" cy="31" r="7" stroke={color} strokeWidth="1.5" fill={`${color}10`} />
<path d="M24 38v6M21 42h6" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
case "leaf":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<path d="M24 5c-9 5-17 15-17 26 0 0 8-2 17-11 9 9 17 11 17 11 0-11-8-21-17-26z"
stroke={color} strokeWidth="1.5" fill={`${color}12`} />
<path d="M24 15v24" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
<path d="M17 21c3.5 2.5 7 5 7 7M31 21c-3.5 2.5-7 5-7 7" stroke={color} strokeWidth="1" opacity="0.6" strokeLinecap="round" />
</svg>
);
case "anchor":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<circle cx="24" cy="11" r="5" stroke={color} strokeWidth="1.5" fill={`${color}12`} />
<path d="M24 16v24" stroke={color} strokeWidth="2" strokeLinecap="round" />
<path d="M12 40c0-9 5.5-16 12-16s12 7 12 16" stroke={color} strokeWidth="1.5" fill="none" strokeLinecap="round" />
<path d="M15 30h-5M33 30h5" stroke={color} strokeWidth="2" strokeLinecap="round" />
</svg>
);
case "wing":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<path d="M24 40V20" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
<path d="M24 20c-4-8-14-14-20-14 4 6 8 16 8 22 0 4 4 8 12 12" stroke={color} strokeWidth="1.5" fill={`${color}12`} />
<path d="M24 20c4-8 14-14 20-14-4 6-8 16-8 22 0 4-4 8-12 12" stroke={color} strokeWidth="1.5" fill={`${color}12`} />
<path d="M12 14c3 2 7 6 9 10M36 14c-3 2-7 6-9 10" stroke={color} strokeWidth="0.75" opacity="0.5" strokeLinecap="round" />
<circle cx="24" cy="18" r="2.5" stroke={color} strokeWidth="1.5" fill={`${color}20`} />
</svg>
);
case "flame":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<path d="M24 4c-3 8-12 14-12 24 0 8 5.5 14 12 16 6.5-2 12-8 12-16 0-10-9-16-12-24z"
stroke={color} strokeWidth="1.5" fill={`${color}12`} />
<path d="M24 18c-2 4-6 7-6 13 0 4 2.5 7 6 8 3.5-1 6-4 6-8 0-6-4-9-6-13z"
stroke={color} strokeWidth="1" fill={`${color}20`} />
<path d="M24 28c-1 2-3 3.5-3 6.5 0 2 1.2 3.5 3 4 1.8-.5 3-2 3-4 0-3-2-4.5-3-6.5z"
fill={color} opacity="0.4" />
</svg>
);
case "crystal":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<path d="M24 4L14 18l10 26 10-26L24 4z" stroke={color} strokeWidth="1.5" fill={`${color}12`} />
<path d="M14 18h20" stroke={color} strokeWidth="1" opacity="0.5" />
<path d="M24 4L14 18M24 4l10 14" stroke={color} strokeWidth="1.5" />
<path d="M24 44L14 18M24 44l10-26" stroke={color} strokeWidth="1.5" />
<path d="M19 11l5 7 5-7M17 22l7 14 7-14" stroke={color} strokeWidth="0.75" opacity="0.3" />
<circle cx="24" cy="20" r="3" stroke={color} strokeWidth="1" fill={`${color}20`} />
</svg>
);
case "meteor":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<circle cx="24" cy="26" r="14" stroke={color} strokeWidth="1.5" fill={`${color}10`} />
<circle cx="24" cy="26" r="8" stroke={color} strokeWidth="1" fill={`${color}15`} />
<path d="M24 6l-2 8h4l-2-8z" fill={color} opacity="0.7" />
<path d="M34 10l-5 7 3 1 2-8z" fill={color} opacity="0.5" />
<path d="M14 10l5 7-3 1-2-8z" fill={color} opacity="0.5" />
<path d="M38 18l-7 5 2 2 5-7z" fill={color} opacity="0.3" />
<path d="M10 18l7 5-2 2-5-7z" fill={color} opacity="0.3" />
<circle cx="24" cy="26" r="3" stroke={color} strokeWidth="1.5" fill={`${color}25`} />
</svg>
);
case "skull":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<path d="M24 4C16 4 10 11 10 20c0 5 2 9 5 12v8h18v-8c3-3 5-7 5-12 0-9-6-16-14-16z"
stroke={color} strokeWidth="1.5" fill={`${color}12`} />
<circle cx="18" cy="20" r="4" stroke={color} strokeWidth="1.5" fill={`${color}20`} />
<circle cx="30" cy="20" r="4" stroke={color} strokeWidth="1.5" fill={`${color}20`} />
<path d="M21 28h6l-3 5-3-5z" fill={color} opacity="0.5" />
<path d="M19 36v4M24 36v4M29 36v4" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
case "dragon":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<path d="M24 4c-2 4-8 8-10 16-2 8 2 16 10 20 8-4 12-12 10-20C32 12 26 8 24 4z"
stroke={color} strokeWidth="1.5" fill={`${color}12`} />
<path d="M14 20c-4-2-8-1-10 2 3 1 6 3 8 5" stroke={color} strokeWidth="1" fill={`${color}15`} />
<path d="M34 20c4-2 8-1 10 2-3 1-6 3-8 5" stroke={color} strokeWidth="1" fill={`${color}15`} />
<circle cx="20" cy="18" r="2" stroke={color} strokeWidth="1.5" fill={`${color}25`} />
<circle cx="28" cy="18" r="2" stroke={color} strokeWidth="1.5" fill={`${color}25`} />
<path d="M20 26c2 1 4 2 4 4 0-2 2-3 4-4" stroke={color} strokeWidth="1" opacity="0.6" strokeLinecap="round" />
<path d="M24 34v8M20 38l4 4 4-4" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
case "star":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<path d="M24 4l5 14h15l-12 9 5 15-13-9-13 9 5-15L4 18h15z"
stroke={color} strokeWidth="1.5" fill={`${color}12`} />
<path d="M24 12l3 8h8l-6.5 5 2.5 8L24 28l-7 5 2.5-8L13 20h8z"
stroke={color} strokeWidth="0.75" opacity="0.4" fill={`${color}20`} />
<circle cx="24" cy="22" r="4" stroke={color} strokeWidth="1" fill={`${color}25`} />
</svg>
);
case "crown":
return (
<svg viewBox="0 0 48 48" style={style} fill="none">
<path d="M8 32l4-16 8 8 4-12 4 12 8-8 4 16z"
stroke={color} strokeWidth="1.5" fill={`${color}12`} />
<path d="M8 32h32v6H8z" stroke={color} strokeWidth="1.5" fill={`${color}20`} />
<circle cx="12" cy="16" r="2" fill={color} opacity="0.5" />
<circle cx="24" cy="12" r="2.5" fill={color} opacity="0.6" />
<circle cx="36" cy="16" r="2" fill={color} opacity="0.5" />
<circle cx="16" cy="35" r="1.5" fill={color} opacity="0.4" />
<circle cx="24" cy="35" r="1.5" fill={color} opacity="0.4" />
<circle cx="32" cy="35" r="1.5" fill={color} opacity="0.4" />
</svg>
);
default:
return null;
}
}

View File

@@ -6,6 +6,7 @@ export interface GameMeta {
color: string;
colorDark: string;
accentColor: string;
emblem?: string;
coverImage?: string;
}