Skip to content

Commit e4ebb5e

Browse files
committed
feat(day-2): Layout completo con HUD, Intro y Firebase
✨ Nuevas funcionalidades: - HUD SpaceComputer (top bar + sidebar desktop) - IntroScreen con narrativa científica animada - ParticleBackground con 60 partículas interactivas - Hooks: useCountdown y useEventConfig - Integración Firebase con configuración mock 🎨 Mejoras visuales: - Progreso gestacional real (Semana 26/40 - 65%) - Countdown dinámico al reveal (26 Oct 2025) - Layout responsive (mobile/tablet/desktop) - Animaciones con Framer Motion 🔧 Configuración: - Firebase conectado (lib/firebase.ts) - Variables de entorno (.env actualizado) - Mock temporal para desarrollo 📁 Archivos día 2: - components/Layout/SpaceComputer.tsx - components/Layout/MainLayout.tsx - components/Intro/IntroScreen.tsx - components/Intro/ScientificNarrative.tsx - components/Intro/ParticleBackground.tsx - components/Shared/Button.tsx - hooks/useCountdown.ts - hooks/useEventConfig.ts - lib/firebase.ts - App.tsx actualizado FPP: 29 Enero 2026 | Lanzamiento: 23 Oct 2025
1 parent f537d57 commit e4ebb5e

10 files changed

Lines changed: 684 additions & 31 deletions

File tree

src/App.tsx

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,73 @@
11
import { useState } from "react";
2-
import "./App.css";
2+
import { MainLayout } from "./components/Layout/MainLayout";
3+
import { IntroScreen } from "./components/Intro/IntroScreen";
4+
import type { GamePhase } from "./types";
35

46
function App() {
5-
const [count, setCount] = useState(0);
7+
const [currentPhase, setCurrentPhase] = useState<GamePhase>("intro");
8+
9+
const handleStart = () => {
10+
setCurrentPhase("hypothesis");
11+
};
612

713
return (
8-
<>
9-
<div className="min-h-screen grid place-items-center">
10-
<h1 className="text-3xl font-bold text-green-400">Tailwind ON ✅</h1>
11-
</div>
12-
<div>
13-
<a
14-
target="_blank"
15-
href="https://calendar.google.com/calendar/event?action=TEMPLATE&amp;tmeid=ZmE5N3VmZzd2YjBxNnZlcjN2MDdrY2ZnbWsgYXJva2VuMTgyQG0&amp;tmsrc=aroken182%40gmail.com"
16-
rel="noopener noreferrer"
17-
>
18-
<img
19-
// style={{ border: "0" }}
20-
src="https://calendar.google.com/calendar/images/ext/gc_button1_es.gif"
21-
alt="Google Calendar"
22-
/>
23-
</a>
24-
</div>
25-
<div className="card">
26-
<button onClick={() => setCount((count) => count + 1)}>
27-
count is {count}
28-
</button>
29-
<p>
30-
Edit <code>src/App.tsx</code> and save to test HMR
31-
</p>
32-
</div>
33-
<p className="read-the-docs">
34-
Click on the Vite and React logos to learn more
35-
</p>
36-
</>
14+
<MainLayout currentPhase={currentPhase}>
15+
{/* Fase Intro */}
16+
{currentPhase === "intro" && <IntroScreen onStart={handleStart} />}
17+
18+
{/* Fase Hypothesis - Placeholder */}
19+
{currentPhase === "hypothesis" && (
20+
<div className="min-h-screen flex items-center justify-center">
21+
<div className="text-center space-y-4">
22+
<h2 className="text-4xl text-cyan-400 font-bold">
23+
FASE: FORMULACIÓN DE HIPÓTESIS
24+
</h2>
25+
<p className="text-gray-400 text-lg">
26+
(Componente en desarrollo - Día 3)
27+
</p>
28+
<div className="mt-8 text-cyan-300 font-mono text-sm">
29+
<p>✅ Sistema operativo</p>
30+
<p>✅ Intro completado</p>
31+
<p>⏳ Esperando componente HypothesisScreen...</p>
32+
</div>
33+
</div>
34+
</div>
35+
)}
36+
37+
{/* Resto de fases - Placeholders */}
38+
{currentPhase === "collider" && (
39+
<div className="min-h-screen flex items-center justify-center">
40+
<div className="text-center space-y-4">
41+
<h2 className="text-4xl text-cyan-400 font-bold">
42+
FASE: COLISIONADOR DE PARTÍCULAS
43+
</h2>
44+
<p className="text-gray-400">(Día 3)</p>
45+
</div>
46+
</div>
47+
)}
48+
49+
{currentPhase === "equation" && (
50+
<div className="min-h-screen flex items-center justify-center">
51+
<div className="text-center space-y-4">
52+
<h2 className="text-4xl text-cyan-400 font-bold">
53+
FASE: ECUACIÓN DE ENLACE
54+
</h2>
55+
<p className="text-gray-400">(Día 3)</p>
56+
</div>
57+
</div>
58+
)}
59+
60+
{currentPhase === "synthesis" && (
61+
<div className="min-h-screen flex items-center justify-center">
62+
<div className="text-center space-y-4">
63+
<h2 className="text-4xl text-cyan-400 font-bold">
64+
FASE: SÍNTESIS FARMACÉUTICA
65+
</h2>
66+
<p className="text-gray-400">(Día 3)</p>
67+
</div>
68+
</div>
69+
)}
70+
</MainLayout>
3771
);
3872
}
3973

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { ScientificNarrative } from "./ScientificNarrative";
2+
import { ParticleBackground } from "./ParticleBackground";
3+
import { Button } from "../Shared/Button";
4+
5+
interface IntroScreenProps {
6+
onStart: () => void;
7+
}
8+
9+
export function IntroScreen({ onStart }: IntroScreenProps) {
10+
return (
11+
<div className="min-h-screen w-full flex flex-col items-center justify-center px-4 py-8 relative overflow-x-hidden">
12+
{/* Fondo de partículas animadas */}
13+
<ParticleBackground />
14+
15+
{/* Contenedor con margen superior para no chocar con el header */}
16+
<div className="w-full max-w-4xl pt-20 pb-12 relative z-10">
17+
{/* Título principal */}
18+
<div className="text-center mb-12 relative z-10">
19+
<h1 className="text-5xl md:text-6xl font-bold text-cyan-400 mb-4 animate-pulse">
20+
BABY-REVEAL v1.0
21+
</h1>
22+
<p className="text-gray-400 text-lg font-mono">
23+
Iniciando protocolo experimental...
24+
</p>
25+
</div>
26+
27+
{/* Narrativa científica */}
28+
<div className="relative z-10">
29+
<ScientificNarrative />
30+
</div>
31+
32+
{/* Botón de inicio */}
33+
<div className="mt-12 text-center relative z-10">
34+
<Button onClick={onStart} variant="primary">
35+
🚀 Unirme al Experimento
36+
</Button>
37+
38+
<p className="mt-4 text-xs text-gray-500 font-mono">
39+
Sistema operativo | Firebase conectado | Fase: INTRO
40+
</p>
41+
</div>
42+
</div>
43+
</div>
44+
);
45+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { useEffect, useRef } from "react";
2+
3+
interface Particle {
4+
x: number;
5+
y: number;
6+
vx: number;
7+
vy: number;
8+
radius: number;
9+
color: string;
10+
alpha: number;
11+
}
12+
13+
export function ParticleBackground() {
14+
const canvasRef = useRef<HTMLCanvasElement>(null);
15+
16+
useEffect(() => {
17+
const canvas = canvasRef.current;
18+
if (!canvas) return;
19+
20+
const ctx = canvas.getContext("2d");
21+
if (!ctx) return;
22+
23+
// Configurar tamaño del canvas
24+
const resizeCanvas = () => {
25+
canvas.width = window.innerWidth;
26+
canvas.height = window.innerHeight;
27+
};
28+
resizeCanvas();
29+
window.addEventListener("resize", resizeCanvas);
30+
31+
// Colores temáticos (física = cyan, química = pink/purple)
32+
const colors = [
33+
"rgba(6, 182, 212, ", // cyan-500 (física)
34+
"rgba(59, 130, 246, ", // blue-500 (física)
35+
"rgba(236, 72, 153, ", // pink-500 (química)
36+
"rgba(168, 85, 247, ", // purple-500 (química)
37+
];
38+
39+
// Crear partículas
40+
const particleCount = 60;
41+
const particles: Particle[] = [];
42+
43+
for (let i = 0; i < particleCount; i++) {
44+
particles.push({
45+
x: Math.random() * canvas.width,
46+
y: Math.random() * canvas.height,
47+
vx: (Math.random() - 0.5) * 0.5,
48+
vy: (Math.random() - 0.5) * 0.5,
49+
radius: Math.random() * 2 + 1,
50+
color: colors[Math.floor(Math.random() * colors.length)],
51+
alpha: Math.random() * 0.5 + 0.3,
52+
});
53+
}
54+
55+
// Función de animación
56+
const animate = () => {
57+
ctx.clearRect(0, 0, canvas.width, canvas.height); // Limpiar completamente en vez de trail
58+
59+
// Actualizar y dibujar partículas
60+
particles.forEach((particle) => {
61+
// Mover partícula
62+
particle.x += particle.vx;
63+
particle.y += particle.vy;
64+
65+
// Rebotar en los bordes
66+
if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -1;
67+
if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -1;
68+
69+
// Dibujar partícula
70+
ctx.beginPath();
71+
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
72+
ctx.fillStyle = particle.color + particle.alpha + ")";
73+
ctx.fill();
74+
75+
// Efecto de glow
76+
ctx.shadowBlur = 10;
77+
ctx.shadowColor = particle.color + "0.8)";
78+
});
79+
80+
// Conectar partículas cercanas
81+
for (let i = 0; i < particles.length; i++) {
82+
for (let j = i + 1; j < particles.length; j++) {
83+
const dx = particles[i].x - particles[j].x;
84+
const dy = particles[i].y - particles[j].y;
85+
const distance = Math.sqrt(dx * dx + dy * dy);
86+
87+
if (distance < 120) {
88+
ctx.beginPath();
89+
ctx.moveTo(particles[i].x, particles[i].y);
90+
ctx.lineTo(particles[j].x, particles[j].y);
91+
ctx.strokeStyle = `rgba(100, 200, 255, ${
92+
0.15 * (1 - distance / 120)
93+
})`;
94+
ctx.lineWidth = 0.5;
95+
ctx.stroke();
96+
}
97+
}
98+
}
99+
100+
ctx.shadowBlur = 0;
101+
requestAnimationFrame(animate);
102+
};
103+
104+
animate();
105+
106+
return () => {
107+
window.removeEventListener("resize", resizeCanvas);
108+
};
109+
}, []);
110+
111+
return (
112+
<canvas
113+
ref={canvasRef}
114+
className="fixed inset-0 z-0 pointer-events-none"
115+
style={{ background: "transparent" }}
116+
/>
117+
);
118+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { motion } from "framer-motion";
2+
import { SCIENTIFIC_INTRO } from "@/utils/constants";
3+
4+
export function ScientificNarrative() {
5+
const lines = SCIENTIFIC_INTRO.trim()
6+
.split("\n")
7+
.filter((line) => line.trim());
8+
9+
return (
10+
<div className="max-w-3xl mx-auto space-y-6">
11+
{lines.map((line, index) => (
12+
<motion.p
13+
key={index}
14+
initial={{ opacity: 0, x: -20 }}
15+
animate={{ opacity: 1, x: 0 }}
16+
transition={{
17+
delay: index * 0.15,
18+
duration: 0.5,
19+
ease: "easeOut",
20+
}}
21+
className={`
22+
font-mono leading-relaxed
23+
${
24+
line.includes("⚛️") || line.includes("🧪")
25+
? "text-lg font-semibold text-gradient-dual"
26+
: line.includes("EXPERIMENTO") ||
27+
line.includes("ESTADO") ||
28+
line.includes("ENERGÍA")
29+
? "text-chemistry-accent font-bold tracking-wider"
30+
: "text-text-light text-opacity-80"
31+
}
32+
`}
33+
>
34+
{line}
35+
</motion.p>
36+
))}
37+
38+
{/* Cursor parpadeante al final */}
39+
<motion.span
40+
className="inline-block w-2 h-5 bg-physics-accent ml-1"
41+
animate={{ opacity: [1, 0] }}
42+
transition={{ repeat: Infinity, duration: 0.8 }}
43+
/>
44+
</div>
45+
);
46+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { ReactNode } from "react";
2+
import { SpaceComputer } from "./SpaceComputer";
3+
import type { GamePhase } from "../../types";
4+
5+
interface MainLayoutProps {
6+
children: ReactNode;
7+
currentPhase: GamePhase;
8+
}
9+
10+
export function MainLayout({ children, currentPhase }: MainLayoutProps) {
11+
return (
12+
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-black to-blue-900">
13+
{/* HUD/SpaceComputer */}
14+
<SpaceComputer currentPhase={currentPhase} />
15+
16+
{/* Contenido Principal */}
17+
<main className="min-h-screen w-full pt-16 lg:pl-80">
18+
<div className="w-full h-full">{children}</div>
19+
</main>
20+
</div>
21+
);
22+
}

0 commit comments

Comments
 (0)