diff --git a/backend/src/index.ts b/backend/src/index.ts
index 3e90bc7..98a789b 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -3,14 +3,15 @@ import { buildApp } from './app';
dotenv.config();
-const port = Number(process.env.PORT ?? 3000);
+const port = Number(process.env.PORT ?? 3003);
const app = buildApp();
const start = async () => {
+ const host = '0.0.0.0';
+
try {
- await app.listen({ port, host: '0.0.0.0' });
- // eslint-disable-next-line no-console
- console.log(`Server listening on http://0.0.0.0:${port}`);
+ await app.listen({ port, host });
+ console.log(`馃殌 Server ready and listening on http://${host}:${port}`);
} catch (err) {
app.log.error(err);
process.exit(1);
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
index 112ea16..3db86c9 100644
--- a/frontend/src/app/globals.css
+++ b/frontend/src/app/globals.css
@@ -7,8 +7,8 @@
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
- --font-sans: var(--font-geist-sans);
- --font-mono: var(--font-geist-mono);
+ --font-display: var(--font-display);
+ --font-mono: var(--font-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@@ -48,123 +48,242 @@
}
:root {
- --radius: 0.625rem;
- --background: #0a0a12;
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.11 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.11 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: #00d0e0;
- --primary-foreground: #0a0a12;
- --secondary: oklch(0.18 0 0);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.18 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: #f0c040;
- --accent-foreground: #0a0a12;
- --destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
+ /* Rich dark backgrounds */
+ --background: #0a0a0a;
+ --foreground: #e5e5e5;
+ --card: #0f0f0f;
+ --card-foreground: #e5e5e5;
+ --popover: #0f0f0f;
+ --popover-foreground: #e5e5e5;
+
+ /* Neon accents */
+ --primary: #00f0ff;
+ --primary-foreground: #0a0a0a;
+ --secondary: #151515;
+ --secondary-foreground: #e5e5e5;
+ --muted: #151515;
+ --muted-foreground: #737373;
+ --accent: #b026ff;
+ --accent-foreground: #e5e5e5;
+ --destructive: #ff6b6b;
+ --border: #262626;
+ --input: #262626;
--ring: #00f0ff;
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.11 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
+
+ /* Chart colors */
+ --chart-1: #00f0ff;
+ --chart-2: #b026ff;
+ --chart-3: #39ff14;
+ --chart-4: #ffd700;
+ --chart-5: #ff6b6b;
+
+ /* Sidebar */
+ --sidebar: #0f0f0f;
+ --sidebar-foreground: #e5e5e5;
--sidebar-primary: #00f0ff;
- --sidebar-primary-foreground: #0a0a12;
- --sidebar-accent: oklch(0.18 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-primary-foreground: #0a0a0a;
+ --sidebar-accent: #151515;
+ --sidebar-accent-foreground: #e5e5e5;
+ --sidebar-border: #262626;
--sidebar-ring: #00f0ff;
+
+ /* Radius */
+ --radius: 0.5rem;
}
.dark {
- --background: #0a0a12;
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.11 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.11 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: #00d0e0;
- --primary-foreground: #0a0a12;
- --secondary: oklch(0.18 0 0);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.18 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: #f0c040;
- --accent-foreground: #0a0a12;
- --destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
+ --background: #0a0a0a;
+ --foreground: #e5e5e5;
+ --card: #0f0f0f;
+ --card-foreground: #e5e5e5;
+ --popover: #0f0f0f;
+ --popover-foreground: #e5e5e5;
+ --primary: #00f0ff;
+ --primary-foreground: #0a0a0a;
+ --secondary: #151515;
+ --secondary-foreground: #e5e5e5;
+ --muted: #151515;
+ --muted-foreground: #737373;
+ --accent: #b026ff;
+ --accent-foreground: #e5e5e5;
+ --destructive: #ff6b6b;
+ --border: #262626;
+ --input: #262626;
--ring: #00f0ff;
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.11 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
+ --chart-1: #00f0ff;
+ --chart-2: #b026ff;
+ --chart-3: #39ff14;
+ --chart-4: #ffd700;
+ --chart-5: #ff6b6b;
+ --sidebar: #0f0f0f;
+ --sidebar-foreground: #e5e5e5;
--sidebar-primary: #00f0ff;
- --sidebar-primary-foreground: #0a0a12;
- --sidebar-accent: oklch(0.18 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-primary-foreground: #0a0a0a;
+ --sidebar-accent: #151515;
+ --sidebar-accent-foreground: #e5e5e5;
+ --sidebar-border: #262626;
--sidebar-ring: #00f0ff;
}
-/* Mass Effect-inspired theme customizations */
+/* Custom neon accent colors */
:root {
- /* Custom colors for Mass Effect theme */
- --mass-effect-dark: #0a0a12;
- --mass-effect-cyan: #00d0e0;
- --mass-effect-gold: #f0c040;
- --mass-effect-cyan-glow: rgba(0, 208, 224, 0.5);
- --mass-effect-gold-glow: rgba(240, 192, 64, 0.5);
- --glass-bg: rgba(10, 10, 18, 0.7);
- --glass-border: rgba(0, 208, 224, 0.2);
+ --neon-cyan: #00f0ff;
+ --neon-purple: #b026ff;
+ --neon-lime: #39ff14;
+ --neon-gold: #ffd700;
+ --neon-coral: #ff6b6b;
+
+ /* Glow effects */
+ --glow-cyan: rgba(0, 240, 255, 0.4);
+ --glow-purple: rgba(176, 38, 255, 0.4);
+ --glow-lime: rgba(57, 255, 20, 0.4);
+ --glow-gold: rgba(255, 215, 0, 0.4);
+ --glow-coral: rgba(255, 107, 107, 0.4);
+
+ /* Transitions */
+ --transition-fast: 0.15s ease;
+ --transition-normal: 0.3s ease;
+
+ /* Glassmorphism */
+ --glass-bg: rgba(15, 15, 15, 0.8);
+ --glass-border: rgba(255, 255, 255, 0.1);
}
/* Glassmorphism effect */
.glass {
background: var(--glass-bg);
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
}
/* Glow effects */
.glow-cyan {
- box-shadow: 0 0 10px var(--mass-effect-cyan-glow);
+ box-shadow: 0 0 20px var(--glow-cyan), 0 0 40px rgba(0, 240, 255, 0.2);
}
-.glow-cyan-intense {
- box-shadow: 0 0 20px var(--mass-effect-cyan-glow), 0 0 40px var(--mass-effect-cyan);
+.glow-purple {
+ box-shadow: 0 0 20px var(--glow-purple), 0 0 40px rgba(176, 38, 255, 0.2);
+}
+
+.glow-lime {
+ box-shadow: 0 0 20px var(--glow-lime), 0 0 40px rgba(57, 255, 20, 0.2);
}
.glow-gold {
- box-shadow: 0 0 10px var(--mass-effect-gold-glow);
+ box-shadow: 0 0 20px var(--glow-gold), 0 0 40px rgba(255, 215, 0, 0.2);
}
-/* Text effects */
+.glow-coral {
+ box-shadow: 0 0 20px var(--glow-coral), 0 0 40px rgba(255, 107, 107, 0.2);
+}
+
+/* Text glow effects */
.text-glow-cyan {
- text-shadow: 0 0 10px var(--mass-effect-cyan-glow);
+ text-shadow: 0 0 10px var(--glow-cyan), 0 0 20px rgba(0, 240, 255, 0.3);
+}
+
+.text-glow-purple {
+ text-shadow: 0 0 10px var(--glow-purple), 0 0 20px rgba(176, 38, 255, 0.3);
+}
+
+.text-glow-lime {
+ text-shadow: 0 0 10px var(--glow-lime), 0 0 20px rgba(57, 255, 20, 0.3);
}
.text-glow-gold {
- text-shadow: 0 0 10px var(--mass-effect-gold-glow);
+ text-shadow: 0 0 10px var(--glow-gold), 0 0 20px rgba(255, 215, 0, 0.3);
}
-/* Holographic effect */
-.holographic {
- position: relative;
- overflow: hidden;
+/* Gradient text */
+.gradient-text {
+ background: linear-gradient(135deg, var(--neon-cyan), var(--neon-purple));
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
}
-.holographic::before {
+.gradient-text-gold {
+ background: linear-gradient(135deg, var(--neon-gold), var(--neon-coral));
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+/* Card hover effects */
+.card-hover {
+ transition: transform var(--transition-fast), box-shadow var(--transition-normal);
+}
+
+.card-hover:hover {
+ transform: translateY(-4px) scale(1.01);
+}
+
+/* Thick bottom border for cards */
+.card-border-cyan {
+ border-bottom: 3px solid var(--neon-cyan);
+}
+
+.card-border-purple {
+ border-bottom: 3px solid var(--neon-purple);
+}
+
+.card-border-lime {
+ border-bottom: 3px solid var(--neon-lime);
+}
+
+.card-border-gold {
+ border-bottom: 3px solid var(--neon-gold);
+}
+
+.card-border-coral {
+ border-bottom: 3px solid var(--neon-coral);
+}
+
+/* Category badges */
+.category-badge {
+ font-family: var(--font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ font-size: 0.75rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: 0.25rem;
+}
+
+/* Staggered animation */
+@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.animate-fade-in-up {
+ animation: fadeInUp 0.5s ease forwards;
+}
+
+/* Pulse animation for system status */
+@keyframes pulse-glow {
+ 0%, 100% {
+ opacity: 1;
+ box-shadow: 0 0 10px var(--glow-cyan);
+ }
+ 50% {
+ opacity: 0.7;
+ box-shadow: 0 0 20px var(--glow-cyan), 0 0 30px rgba(0, 240, 255, 0.4);
+ }
+}
+
+.pulse-glow {
+ animation: pulse-glow 2s infinite;
+}
+
+/* Scanline effect */
+.scanline::before {
content: "";
position: absolute;
top: 0;
@@ -174,13 +293,13 @@
background: linear-gradient(
90deg,
transparent,
- rgba(0, 240, 255, 0.2),
+ rgba(0, 240, 255, 0.1),
transparent
);
- animation: holographic-scan 3s infinite;
+ animation: scanline 3s infinite;
}
-@keyframes holographic-scan {
+@keyframes scanline {
0% {
left: -100%;
}
@@ -189,27 +308,6 @@
}
}
-/* Pulse animation for system status */
-@keyframes pulse {
- 0%, 100% {
- opacity: 1;
- }
- 50% {
- opacity: 0.5;
- }
-}
-
-.pulse {
- animation: pulse 2s infinite;
-}
-
-/* Hover glow effect */
-.hover-glow:hover {
- box-shadow: 0 0 15px var(--mass-effect-cyan-glow);
- transform: translateY(-2px);
- transition: all 0.3s ease;
-}
-
/* Starfield background */
.starfield {
position: fixed;
@@ -219,14 +317,14 @@
height: 100%;
z-index: -1;
background-image:
- radial-gradient(2px 2px at 20px 30px, #eee, transparent),
- radial-gradient(2px 2px at 40px 70px, #eee, transparent),
- radial-gradient(1px 1px at 50px 50px, #eee, transparent),
- radial-gradient(1px 1px at 80px 10px, #eee, transparent),
- radial-gradient(2px 2px at 130px 80px, #eee, transparent);
+ radial-gradient(1px 1px at 20px 30px, rgba(255,255,255,0.3), transparent),
+ radial-gradient(1px 1px at 40px 70px, rgba(255,255,255,0.2), transparent),
+ radial-gradient(1px 1px at 50px 50px, rgba(255,255,255,0.4), transparent),
+ radial-gradient(1px 1px at 80px 10px, rgba(255,255,255,0.2), transparent),
+ radial-gradient(1px 1px at 130px 80px, rgba(255,255,255,0.3), transparent);
background-repeat: repeat;
background-size: 200px 200px;
- opacity: 0.3;
+ opacity: 0.5;
animation: starfield-move 120s linear infinite;
}
@@ -239,27 +337,21 @@
}
}
-/* Custom button styles */
-.btn-mission {
- background: linear-gradient(45deg, var(--mass-effect-cyan), var(--mass-effect-gold));
- border: none;
- color: var(--mass-effect-dark);
- font-weight: bold;
- text-transform: uppercase;
- letter-spacing: 1px;
- padding: 12px 24px;
- border-radius: 4px;
+/* Hover glow effect */
+.hover-glow:hover {
+ box-shadow: 0 0 20px var(--glow-cyan);
+ transform: translateY(-2px);
+ transition: all var(--transition-normal);
+}
+
+/* Button hover effects */
+.btn-neon {
position: relative;
overflow: hidden;
- transition: all 0.3s ease;
+ transition: all var(--transition-normal);
}
-.btn-mission:hover {
- transform: scale(1.05);
- box-shadow: 0 0 20px var(--mass-effect-cyan-glow), 0 0 40px var(--mass-effect-gold-glow);
-}
-
-.btn-mission::before {
+.btn-neon::before {
content: "";
position: absolute;
top: 0;
@@ -275,23 +367,180 @@
transition: left 0.5s;
}
-.btn-mission:hover::before {
+.btn-neon:hover::before {
left: 100%;
}
-/* Search bar glow effect */
-.search-glow:focus {
- box-shadow: 0 0 0 1px var(--mass-effect-cyan), 0 0 15px var(--mass-effect-cyan-glow);
- border-color: var(--mass-effect-cyan);
+.btn-neon:hover {
+ transform: scale(1.02);
+ box-shadow: 0 0 20px var(--glow-cyan);
}
+/* Icon glow on hover */
+.icon-glow:hover {
+ filter: drop-shadow(0 0 8px var(--glow-cyan));
+ transition: filter var(--transition-fast);
+}
+
+/* Dense grid layout */
+.grid-dense {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 1rem;
+}
+
+/* Bento grid layout */
+.bento-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 1rem;
+}
+
+.bento-item-large {
+ grid-column: span 2;
+}
+
+@media (max-width: 768px) {
+ .bento-item-large {
+ grid-column: span 1;
+ }
+}
+
+/* Responsive typography */
+.text-responsive-xl {
+ font-size: clamp(1.5rem, 4vw, 2rem);
+}
+
+.text-responsive-2xl {
+ font-size: clamp(2rem, 5vw, 3rem);
+}
+
+.text-responsive-3xl {
+ font-size: clamp(2.5rem, 6vw, 4rem);
+}
+
+/* Number display for stats */
+.stat-number {
+ font-family: var(--font-mono);
+ font-weight: 700;
+ letter-spacing: -0.02em;
+}
+
+/* Gradient border */
+.gradient-border {
+ position: relative;
+}
+
+.gradient-border::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ padding: 1px;
+ background: linear-gradient(135deg, var(--neon-cyan), var(--neon-purple));
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
+ -webkit-mask-composite: xor;
+ mask-composite: exclude;
+ pointer-events: none;
+}
+
+/* Base styles */
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
- font-family: 'Inter', sans-serif;
+ font-family: var(--font-display), sans-serif;
overflow-x: hidden;
}
-}
\ No newline at end of file
+
+ h1, h2, h3, h4, h5, h6 {
+ font-family: var(--font-display), sans-serif;
+ }
+
+ code, pre, .mono {
+ font-family: var(--font-mono), monospace;
+ }
+
+ /* Game Cover Styles */
+ .game-cover-wrapper {
+ animation: fadeInUp 0.5s ease forwards;
+ opacity: 0;
+ }
+
+ .game-cover-wrapper:hover {
+ transform: translateY(-4px) scale(1.02);
+ transition: transform 0.2s ease-out;
+ z-index: 10;
+ }
+
+ .game-cover-image img {
+ transition: transform 0.3s ease-out, filter 0.3s ease-out;
+ }
+
+ /* Discovery Wall Grid - Dense Masonry Layout */
+ .discovery-wall {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 0;
+ margin: 0 -1px;
+ }
+
+ @media (min-width: 640px) {
+ .discovery-wall {
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+ }
+ }
+
+ @media (min-width: 1024px) {
+ .discovery-wall {
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+ }
+ }
+
+ /* Empty State Animation */
+ @keyframes float {
+ 0%, 100% {
+ transform: translateY(0px);
+ }
+ 50% {
+ transform: translateY(-10px);
+ }
+ }
+
+ .empty-icon {
+ animation: float 3s ease-in-out infinite;
+ }
+
+ /* Pulse animation for empty state button */
+ @keyframes neon-pulse {
+ 0%, 100% {
+ box-shadow: 0 0 20px var(--glow-cyan), 0 0 40px rgba(0, 240, 255, 0.2);
+ }
+ 50% {
+ box-shadow: 0 0 30px var(--glow-cyan), 0 0 60px rgba(0, 240, 255, 0.4);
+ }
+ }
+
+ .btn-neon-pulse:hover {
+ animation: neon-pulse 1.5s infinite;
+ }
+
+ /* Section header with gradient line */
+ .section-header {
+ position: relative;
+ padding-bottom: 0.5rem;
+ }
+
+ .section-header::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 60px;
+ height: 3px;
+ background: linear-gradient(90deg, var(--neon-cyan), var(--neon-purple));
+ border-radius: 2px;
+ }
+}
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
index 3ff9cfd..9dee455 100644
--- a/frontend/src/app/layout.tsx
+++ b/frontend/src/app/layout.tsx
@@ -1,15 +1,17 @@
import type { Metadata } from 'next';
-import { Geist, Geist_Mono } from 'next/font/google';
+import { Space_Grotesk, JetBrains_Mono } from 'next/font/google';
import './globals.css';
-const geistSans = Geist({
- variable: '--font-geist-sans',
+const spaceGrotesk = Space_Grotesk({
+ variable: '--font-display',
subsets: ['latin'],
+ display: 'swap',
});
-const geistMono = Geist_Mono({
- variable: '--font-geist-mono',
+const jetBrainsMono = JetBrains_Mono({
+ variable: '--font-mono',
subsets: ['latin'],
+ display: 'swap',
});
export const metadata: Metadata = {
@@ -32,7 +34,9 @@ export default function RootLayout({
}>) {
return (
-
{children}
+
+ {children}
+
);
}
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
index de7de3c..f11c62e 100644
--- a/frontend/src/app/page.tsx
+++ b/frontend/src/app/page.tsx
@@ -1,145 +1,156 @@
-import Link from 'next/link';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
-import { Button } from '@/components/ui/button';
+'use client';
+
+import { useEffect, useState } from 'react';
+import { Game } from '@/lib/api';
+import { gamesApi } from '@/lib/api';
+import { Gamepad2 } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
-import { Gamepad2, HardDrive, Import, Clock, Activity, Database } from 'lucide-react';
+import { Button } from '@/components/ui/button';
+import Link from 'next/link';
+import { GameCover } from '@/components/landing/GameCover';
+import { EmptyState } from '@/components/landing/EmptyState';
+import { ArrowRight, Grid3X3 } from 'lucide-react';
export default function Home() {
+ const [games, setGames] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ async function fetchGames() {
+ try {
+ const allGames = await gamesApi.getAll();
+ // Ordenar por fecha de creaci贸n (m谩s recientes primero)
+ const sortedGames = allGames.sort(
+ (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
+ );
+ // Limitar a los 煤ltimos 16 juegos para la p谩gina principal
+ setGames(sortedGames.slice(0, 16));
+ } catch (err) {
+ const errorMessage = err instanceof Error ? err.message : 'Error desconocido';
+ setError(`Error al cargar juegos: ${errorMessage}`);
+ console.error('Error fetching games:', err);
+ } finally {
+ setIsLoading(false);
+ }
+ }
+
+ fetchGames();
+ }, []);
+
return (
+ {/* Starfield background */}
+
+
{/* Header */}
-
+
-
-
-
Quasar
+
+
+
+
+
+
QUASAR
+
+ GAME LIBRARY SYSTEM
+
+
+
+
+
+
+
+
+ v1.0.0
+
-
v1.0.0
{/* Main Content */}
-
-
- {/* Statistics Cards */}
-
-
-
- Estad铆sticas
-
-
-
-
- Total de Juegos
- 0
-
-
-
-
- Base de datos
-
-
-
-
-
-
- Juegos Importados
- 0
-
-
-
-
- Desde archivos ROM
-
-
-
-
-
-
- 脷ltima Importaci贸n
- -
-
-
-
-
- Sin registros
-
-
-
-
-
-
- Estado del Sistema
-
- Activo
-
-
-
-
-
-
+
+ {isLoading ? (
+
+
-
-
- {/* Quick Actions */}
-
-
-
- Acciones R谩pidas
-
-
-
-
- Gesti贸n de Juegos
- Ver y administrar tu biblioteca de videojuegos
-
-
-
-
-
-
-
-
-
-
- Importar Juegos
- Importa juegos desde archivos ROM
-
-
-
-
-
-
-
+
+ ) : error ? (
+
+
+
{error}
+
-
-
- {/* Recent Activity */}
-
-
-
- Actividad Reciente
-
-
-
-
-
-
No hay actividad reciente
-
Las importaciones y cambios aparecer谩n aqu铆
+
+ ) : games.length === 0 ? (
+
+ ) : (
+
+ {/* Section Header */}
+
+
+
+
+ 脷LTIMOS JUEGOS
+
+
+ {games.length} JUEGOS EN TU BIBLIOTECA
+
-
-
-
-
+
+
+
+
+
+
+ {/* Discovery Wall - Dense Grid */}
+
+ {games.map((game, index) => (
+
+ ))}
+
+
+ {/* Load More Button */}
+ {games.length >= 16 && (
+
+
+
+
+
+ )}
+
+ )}
);
diff --git a/frontend/src/components/landing/EmptyState.tsx b/frontend/src/components/landing/EmptyState.tsx
new file mode 100644
index 0000000..720a372
--- /dev/null
+++ b/frontend/src/components/landing/EmptyState.tsx
@@ -0,0 +1,66 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import { Gamepad2, Sparkles, ArrowRight } from 'lucide-react';
+import Link from 'next/link';
+
+export function EmptyState() {
+ return (
+
+ {/* Icono animado */}
+
+
+
+
+
+ {/* Sparkles decorativos */}
+
+
+
+
+ {/* T铆tulo principal */}
+
+ TU BIBLIOTECA
+
+ EST脕 VAC脥A
+
+
+ {/* Descripci贸n motivadora */}
+
+ Es hora de comenzar tu colecci贸n de videojuegos.
+
+
+ Importa tus ROMs o a帽ade juegos manualmente
+
+
+
+ {/* Botones de acci贸n */}
+
+
+
+
+
+
+
+
+
+
+ {/* Info adicional */}
+
+
+ Compatible con archivos ROM, IGDB, RAWG y TheGamesDB
+
+
+
+ );
+}
diff --git a/frontend/src/components/landing/GameCover.tsx b/frontend/src/components/landing/GameCover.tsx
new file mode 100644
index 0000000..f50ba15
--- /dev/null
+++ b/frontend/src/components/landing/GameCover.tsx
@@ -0,0 +1,121 @@
+'use client';
+
+import { Game } from '@/lib/api';
+import { Badge } from '@/components/ui/badge';
+import { ExternalLink } from 'lucide-react';
+import Link from 'next/link';
+import { useState } from 'react';
+
+interface GameCoverProps {
+ game: Game;
+ index: number;
+}
+
+export function GameCover({ game, index }: GameCoverProps) {
+ const [isHovered, setIsHovered] = useState(false);
+
+ // Colores de acento por fuente
+ const sourceColors: Record = {
+ igdb: { bg: 'rgba(0, 240, 255, 0.15)', text: '#00f0ff', border: '#00f0ff' },
+ rawg: { bg: 'rgba(176, 38, 255, 0.15)', text: '#b026ff', border: '#b026ff' },
+ thegamesdb: { bg: 'rgba(57, 255, 20, 0.15)', text: '#39ff14', border: '#39ff14' },
+ rom: { bg: 'rgba(255, 215, 0, 0.15)', text: '#ffd700', border: '#ffd700' },
+ manual: { bg: 'rgba(255, 107, 107, 0.15)', text: '#ff6b6b', border: '#ff6b6b' },
+ };
+
+ const colors = sourceColors[game.source] || sourceColors.manual;
+
+ // URL de la portada o placeholder
+ const coverUrl = game.cover || '/placeholder-game-cover.png';
+
+ return (
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+ {/* Imagen de portada */}
+
+

+
+
+ {/* Overlay en hover */}
+
+ {/* Badge de fuente */}
+
+
+ {game.source}
+
+
+
+ {/* Contenido del overlay */}
+
+ {/* T铆tulo del juego */}
+
+ {game.title}
+
+
+ {/* Metadatos */}
+
+ {game.year && (
+
+ {game.year}
+
+ )}
+ {game.platform && (
+
+ {game.platform}
+
+ )}
+ {game.genre && (
+
+ {game.genre}
+
+ )}
+
+
+ {/* Indicador de enlace */}
+
+
+ VER DETALLES
+
+
+
+
+ {/* Glow effect en hover */}
+
+
+ {/* Borde brillante en hover */}
+
+
+ );
+}
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index 1f37b94..2882abd 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -1,4 +1,4 @@
-const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api';
+const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3003/api';
// Tipos para relaciones del juego
export interface Artwork {