import React, { useEffect, useRef, useState } from "react";
import {
Gamepad2,
Sparkles,
Zap,
Shield,
Trophy,
ChevronRight,
Play,
X,
Server,
Globe,
Cpu,
LogOut,
Radio,
CheckCircle2,
Sliders,
} from "lucide-react";
import { cn } from "@/lib/utils";
/* ===== Scoped Styles & Design Tokens ===== */
const CSS = `
@import url('https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@600;700&family=Inter:wght@400;500;600&display=swap');
.vx {
--violet-500: #8b5cf6;
--cyan-400: #22d3ee;
--green-400: #4ade80;
--ink-950: #05040a;
--color-bg: var(--ink-950);
--color-text: #f4f3ff;
--color-muted: #a5a1c2;
--color-primary: var(--violet-500);
--color-accent: var(--cyan-400);
--color-border: rgba(255, 255, 255, 0.12);
--color-surface: rgba(14, 11, 26, 0.72);
--radius-card: 20px;
--ease: cubic-bezier(0.2, 0.8, 0.2, 1);
--font-display: 'Chakra Petch', system-ui, sans-serif;
--font-body: 'Inter', system-ui, sans-serif;
--btn-height: 54px;
--btn-pad-x: 2.1rem;
--btn-radius: 999px;
--dur: 0.2s;
--btn-primary-top: #6d4aff;
--btn-primary-bottom: #4a30d4;
--btn-primary-glow: rgba(109, 74, 255, 0.75);
--btn-highlight: rgba(255, 255, 255, 0.38);
--btn-border: rgba(255, 255, 255, 0.26);
--btn-border-hover: rgba(34, 211, 238, 0.75);
--btn-ghost-top: rgba(255, 255, 255, 0.12);
--btn-ghost-bottom: rgba(255, 255, 255, 0.03);
position: relative;
min-height: 100svh;
background: var(--color-bg);
color: var(--color-text);
font-family: var(--font-body);
overflow-x: hidden;
box-sizing: border-box;
color-scheme: dark;
}
.vx * { box-sizing: border-box; }
.vx-bg {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
.vx-hero {
position: relative;
z-index: 1;
min-height: 100svh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 5rem 20px 4rem;
}
.vx-badge {
display: inline-flex;
align-items: center;
gap: 0.6rem;
padding: 0.5rem 1.1rem;
border-radius: 999px;
background: var(--color-surface);
border: 1px solid rgba(34, 211, 238, 0.35);
box-shadow: 0 0 25px -5px rgba(139, 92, 246, 0.5);
backdrop-filter: blur(12px);
color: var(--color-text);
letter-spacing: 0.14em;
font-size: 0.76rem;
font-weight: 600;
margin-bottom: 1.5rem;
animation: vx-rise 0.6s var(--ease) both;
}
.vx-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--green-400);
box-shadow: 0 0 12px var(--green-400);
animation: vx-pulse 1.8s infinite;
}
.vx h1 {
font-family: var(--font-display);
font-weight: 700;
line-height: 1.04;
letter-spacing: -0.01em;
margin: 0 0 1.15rem;
font-size: clamp(2.4rem, 7vw, 4.9rem);
text-shadow: 0 10px 40px rgba(0, 0, 0, 0.85);
animation: vx-rise 0.6s 0.08s var(--ease) both;
}
.vx-grad {
background: linear-gradient(100deg, var(--color-accent), var(--color-primary) 55%, #f0abfc);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.vx-sub {
max-width: 38rem;
margin: 0 0 2rem;
color: var(--color-muted);
font-size: clamp(0.96rem, 2vw, 1.14rem);
line-height: 1.65;
animation: vx-rise 0.6s 0.16s var(--ease) both;
}
.vx-actions {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 2.5rem;
animation: vx-rise 0.6s 0.24s var(--ease) both;
}
.vx-btn {
position: relative;
overflow: hidden;
isolation: isolate;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.55rem;
min-height: var(--btn-height);
padding: 0 var(--btn-pad-x);
border-radius: var(--btn-radius);
font: 600 0.95rem var(--font-body);
letter-spacing: 0.04em;
color: var(--color-text);
text-decoration: none;
cursor: pointer;
border: 1px solid var(--btn-border);
backdrop-filter: blur(14px) saturate(1.5);
transition: transform var(--dur) var(--ease), box-shadow 0.3s var(--ease), border-color var(--dur);
}
.vx-btn::after {
content: "";
position: absolute;
top: 0;
left: -70%;
width: 34%;
height: 100%;
background: linear-gradient(105deg, transparent, rgba(255, 255, 255, 0.28), transparent);
transform: skewX(-20deg);
transition: left 0.8s var(--ease);
z-index: -1;
pointer-events: none;
}
.vx-btn:hover::after { left: 150%; }
.vx-btn:active { transform: scale(0.98); }
.vx-btn-primary {
background: linear-gradient(180deg, var(--btn-primary-top), var(--btn-primary-bottom));
box-shadow: inset 0 1px 0 var(--btn-highlight), 0 12px 30px -8px var(--btn-primary-glow);
}
.vx-btn-primary:hover {
transform: translateY(-2px);
border-color: var(--btn-border-hover);
box-shadow: inset 0 1px 0 var(--btn-highlight), 0 16px 40px -6px var(--btn-primary-glow);
}
.vx-btn-ghost {
background: linear-gradient(180deg, var(--btn-ghost-top), var(--btn-ghost-bottom));
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 10px 26px -12px rgba(0, 0, 0, 0.8);
}
.vx-btn-ghost:hover {
transform: translateY(-2px);
border-color: var(--btn-border-hover);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.06));
}
.vx-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.9rem;
width: 100%;
max-width: 44rem;
margin: 0 0 2.25rem;
animation: vx-rise 0.6s 0.32s var(--ease) both;
}
.vx-stat {
background: var(--color-surface);
backdrop-filter: blur(16px);
border: 1px solid var(--color-border);
border-radius: var(--radius-card);
padding: 1.1rem 0.6rem;
transition: transform 0.26s var(--ease), border-color 0.26s, box-shadow 0.26s;
}
.vx-stat:hover {
transform: translateY(-4px);
border-color: rgba(34, 211, 238, 0.55);
box-shadow: 0 12px 30px -10px rgba(34, 211, 238, 0.25);
}
.vx-stat dd {
margin: 0;
font: 700 clamp(1.5rem, 3.8vw, 2.15rem)/1 var(--font-display);
}
.vx-stat dt {
margin-top: 0.45rem;
font-size: clamp(0.7rem, 1.5vw, 0.82rem);
color: var(--color-muted);
}
.font-display { font-family: var(--font-display); }
@keyframes vx-rise {
from { opacity: 0; transform: translateY(18px); }
to { opacity: 1; transform: none; }
}
@keyframes vx-pulse { 50% { opacity: 0.35; } }
`;
/* ===== Arena Visual Themes ===== */
const ARENA_THEMES: Record<string, any> = {
violet: {
id: "violet",
name: "Cyber Violet",
primary: "#8b5cf6",
accent: "#22d3ee",
secondary: "#f0abfc",
coreGlow1: "rgba(139, 92, 246, 0.30)",
coreGlow2: "rgba(34, 211, 238, 0.11)",
floorRay1: "rgba(139, 92, 246, 0.45)",
floorRay2: "rgba(34, 211, 238, 0.20)",
transverseRgb: "34, 211, 238",
crystalColors: ["#22d3ee", "#8b5cf6", "#f0abfc", "#4ade80"],
},
cyan: {
id: "cyan",
name: "Matrix Cyan",
primary: "#06b6d4",
accent: "#4ade80",
secondary: "#38bdf8",
coreGlow1: "rgba(6, 182, 212, 0.28)",
coreGlow2: "rgba(74, 222, 128, 0.12)",
floorRay1: "rgba(6, 182, 212, 0.45)",
floorRay2: "rgba(74, 222, 128, 0.22)",
transverseRgb: "74, 222, 128",
crystalColors: ["#4ade80", "#22d3ee", "#38bdf8", "#a7f3d0"],
},
crimson: {
id: "crimson",
name: "Crimson Overdrive",
primary: "#f43f5e",
accent: "#f59e0b",
secondary: "#fb7185",
coreGlow1: "rgba(244, 63, 94, 0.28)",
coreGlow2: "rgba(245, 158, 11, 0.11)",
floorRay1: "rgba(244, 63, 94, 0.45)",
floorRay2: "rgba(245, 158, 11, 0.22)",
transverseRgb: "245, 158, 11",
crystalColors: ["#f43f5e", "#f59e0b", "#fb7185", "#fde047"],
},
};
/* ===== Game Catalog Data ===== */
const GAMES_CATALOG = [
{
id: "cyber-strike",
title: "Cyber Strike: Zero",
genre: "Competitive FPS",
players: "14.2k Active",
ping: "9 ms",
tickrate: "128-Tick",
rating: "4.9",
description:
"Tactical 5v5 neural-link shooter set in Neo-Veridia. Precision gunplay meets zero-gravity grapple arenas.",
image: "https://cdn.21st.dev/assets/mirror/49/49a0de4db205b680a0e7d8e8bdae52101cf72f7163eaaf18163daf9243868da6.jpg",
},
{
id: "aether-chronicles",
title: "Aether Chronicles",
genre: "Action RPG",
players: "9.8k Active",
ping: "12 ms",
tickrate: "60-Tick",
rating: "4.8",
description:
"Embark on a seamless open-world raid across shattered astral citadels and conquer world bosses.",
image: "https://cdn.21st.dev/assets/mirror/35/352ff8c0b91115d795f0b25ce3e0b98b524787c929cc500002bfdd950fd7a63e.jpg",
},
{
id: "neon-velocity",
title: "Neon Velocity X",
genre: "Synth Racing",
players: "6.4k Active",
ping: "8 ms",
tickrate: "120-Tick",
rating: "4.9",
description:
"Break the sound barrier in anti-gravity plasma interceptors along vertical megastructure highways.",
image: "https://cdn.21st.dev/assets/mirror/26/26a89e9acad139951a12fe1fc81bc030f1e8d620ed676c7965c2ad1fc7037a9b.jpg",
},
{
id: "mecha-titan",
title: "Titanfall Protocol",
genre: "Mecha Warfare",
players: "11.1k Active",
ping: "11 ms",
tickrate: "128-Tick",
rating: "4.7",
description:
"Pilot customizable 40-ton orbital mechs in planetary siege battles with railgun strikes.",
image: "https://cdn.21st.dev/assets/mirror/79/79311d3bba3e043a607c0ea60721307c918eadf374f3c5a6d6d1529113313bac.jpg",
},
];
/* ===== Custom 3D Gaming Arena Hook ===== */
function use3DGamingArena(
canvasRef: React.RefObject<HTMLCanvasElement | null>,
warpSpeed: number,
themeKey: string
) {
const configRef = useRef({ warpSpeed, themeKey });
useEffect(() => {
configRef.current = { warpSpeed, themeKey };
}, [warpSpeed, themeKey]);
useEffect(() => {
const cv = canvasRef.current;
const cx = cv?.getContext("2d");
if (!cv || !cx) return;
let w = 0,
h = 0,
raf = 0,
time = 0;
const mouse = { x: 0, y: 0, targetX: 0, targetY: 0 };
const PARTICLE_COUNT = 115;
const particles = Array.from({ length: PARTICLE_COUNT }, (_, i) => ({
x: (Math.random() - 0.5) * 2400,
y: (Math.random() - 0.5) * 1400,
z: Math.random() * 2000 + 100,
speed: Math.random() * 6 + 4,
colorIndex: i % 4,
size: Math.random() * 2.2 + 0.8,
}));
const crystals = [
{ x: -520, y: -140, z: 750, radius: 110, rotX: 0.4, rotY: 0.8, speedX: 0.012, speedY: 0.018, colorIdx: 0 },
{ x: 560, y: -110, z: 820, radius: 130, rotX: 1.1, rotY: 0.2, speedX: -0.015, speedY: 0.014, colorIdx: 1 },
{ x: -340, y: 180, z: 1150, radius: 85, rotX: 0.2, rotY: 1.5, speedX: 0.018, speedY: -0.012, colorIdx: 2 },
{ x: 420, y: 190, z: 1050, radius: 95, rotX: 0.9, rotY: 0.6, speedX: -0.01, speedY: -0.02, colorIdx: 3 },
];
const octaVertices = [
[0, 1, 0], [0, -1, 0], [1, 0, 0], [-1, 0, 0], [0, 0, 1], [0, 0, -1],
];
const octaEdges = [
[0, 2], [0, 3], [0, 4], [0, 5],
[1, 2], [1, 3], [1, 4], [1, 5],
[2, 4], [4, 3], [3, 5], [5, 2],
];
const fit = () => {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
w = window.innerWidth;
h = window.innerHeight;
cv.width = w * dpr;
cv.height = h * dpr;
cx.setTransform(dpr, 0, 0, dpr, 0, 0);
};
const FOV = 520;
const project3D = (x: number, y: number, z: number) => {
const camX = x - mouse.x * 170;
const camY = y - mouse.y * 105;
const scale = FOV / Math.max(z, 10);
return { x: w / 2 + camX * scale, y: h / 2 + camY * scale, scale };
};
const draw = () => {
const { warpSpeed: speedMult, themeKey: currentThemeKey } = configRef.current;
const theme = ARENA_THEMES[currentThemeKey] || ARENA_THEMES.violet;
time += 0.025 * speedMult;
mouse.x += (mouse.targetX - mouse.x) * 0.06;
mouse.y += (mouse.targetY - mouse.y) * 0.06;
cx.fillStyle = "#05040a";
cx.fillRect(0, 0, w, h);
const centerX = w / 2 - mouse.x * 45;
const centerY = h * 0.46 - mouse.y * 30;
const coreGlow = cx.createRadialGradient(centerX, centerY, 10, centerX, centerY, Math.max(w, h) * 0.7);
coreGlow.addColorStop(0, theme.coreGlow1);
coreGlow.addColorStop(0.35, theme.coreGlow2);
coreGlow.addColorStop(0.7, "rgba(15, 10, 35, 0.04)");
coreGlow.addColorStop(1, "rgba(5, 4, 10, 0)");
cx.fillStyle = coreGlow;
cx.fillRect(0, 0, w, h);
const gridZNear = 80;
const gridZFar = 1800;
const floorY = 260;
const ceilingY = -320;
const gridCols = 20;
const colSpacing = 140;
cx.lineWidth = 1;
for (let i = -gridCols; i <= gridCols; i++) {
const xVal = i * colSpacing;
const p1 = project3D(xVal, floorY, gridZNear);
const p2 = project3D(xVal, floorY, gridZFar);
const gradFloor = cx.createLinearGradient(p1.x, p1.y, p2.x, p2.y);
gradFloor.addColorStop(0, theme.floorRay1);
gradFloor.addColorStop(0.6, theme.floorRay2);
gradFloor.addColorStop(1, "rgba(34, 211, 238, 0)");
cx.strokeStyle = gradFloor;
cx.beginPath();
cx.moveTo(p1.x, p1.y);
cx.lineTo(p2.x, p2.y);
cx.stroke();
if (i % 2 === 0) {
const c1 = project3D(xVal, ceilingY, gridZNear);
const c2 = project3D(xVal, ceilingY, gridZFar);
const gradCeil = cx.createLinearGradient(c1.x, c1.y, c2.x, c2.y);
gradCeil.addColorStop(0, theme.floorRay2);
gradCeil.addColorStop(1, "rgba(139, 92, 246, 0)");
cx.strokeStyle = gradCeil;
cx.beginPath();
cx.moveTo(c1.x, c1.y);
cx.lineTo(c2.x, c2.y);
cx.stroke();
}
}
const zSteps = 18;
const zSpacing = (gridZFar - gridZNear) / zSteps;
const zOffset = (time * 220) % zSpacing;
for (let j = 0; j < zSteps; j++) {
const z = gridZFar - j * zSpacing - zOffset;
if (z <= gridZNear) continue;
const alpha = Math.pow(1 - (z - gridZNear) / (gridZFar - gridZNear), 1.6) * 0.45;
const leftPt = project3D(-gridCols * colSpacing, floorY, z);
const rightPt = project3D(gridCols * colSpacing, floorY, z);
cx.strokeStyle = `rgba(${theme.transverseRgb}, ${alpha.toFixed(3)})`;
cx.beginPath();
cx.moveTo(leftPt.x, leftPt.y);
cx.lineTo(rightPt.x, rightPt.y);
cx.stroke();
}
crystals.forEach((cr, idx) => {
cr.rotX += cr.speedX * (0.6 + speedMult * 0.4);
cr.rotY += cr.speedY * (0.6 + speedMult * 0.4);
const floatY = cr.y + Math.sin(time * 1.4 + idx) * 22;
const crystalColor = theme.crystalColors[cr.colorIdx];
const projectedVerts = octaVertices.map(([vx, vy, vz]) => {
let x = vx * cr.radius;
let y = vy * cr.radius * 1.35;
let z = vz * cr.radius;
const cosX = Math.cos(cr.rotX), sinX = Math.sin(cr.rotX);
const y1 = y * cosX - z * sinX;
const z1 = y * sinX + z * cosX;
const cosY = Math.cos(cr.rotY), sinY = Math.sin(cr.rotY);
const x2 = x * cosY + z1 * sinY;
const z2 = -x * sinY + z1 * cosY;
return project3D(cr.x + x2, floatY + y1, cr.z + z2);
});
cx.save();
cx.strokeStyle = crystalColor;
cx.lineWidth = 1.4;
cx.shadowColor = crystalColor;
cx.shadowBlur = 12;
cx.globalAlpha = 0.7;
octaEdges.forEach(([a, b]) => {
const pA = projectedVerts[a];
const pB = projectedVerts[b];
cx.beginPath();
cx.moveTo(pA.x, pA.y);
cx.lineTo(pB.x, pB.y);
cx.stroke();
});
projectedVerts.forEach((pt) => {
cx.fillStyle = "#ffffff";
cx.beginPath();
cx.arc(pt.x, pt.y, Math.max(1.5, 3 * pt.scale), 0, Math.PI * 2);
cx.fill();
});
cx.restore();
});
particles.forEach((p) => {
p.z -= p.speed * 3.2 * speedMult;
if (p.z <= 40) {
p.z = 1900;
p.x = (Math.random() - 0.5) * 2400;
p.y = (Math.random() - 0.5) * 1400;
}
const curr = project3D(p.x, p.y, p.z);
const prev = project3D(p.x, p.y, p.z + p.speed * 9 * Math.max(0.6, speedMult));
const alpha = Math.min(1, (1900 - p.z) / 900);
cx.strokeStyle = theme.crystalColors[p.colorIndex];
cx.globalAlpha = alpha * 0.85;
cx.lineWidth = Math.max(1, p.size * curr.scale * 1.8);
cx.beginPath();
cx.moveTo(prev.x, prev.y);
cx.lineTo(curr.x, curr.y);
cx.stroke();
});
cx.globalAlpha = 1;
const horizonPt = project3D(0, floorY, gridZFar);
const horizonGlow = cx.createRadialGradient(centerX, horizonPt.y, 5, centerX, horizonPt.y, w * 0.55);
horizonGlow.addColorStop(0, theme.coreGlow1);
horizonGlow.addColorStop(0.35, theme.coreGlow2);
horizonGlow.addColorStop(1, "rgba(5, 4, 10, 0)");
cx.fillStyle = horizonGlow;
cx.fillRect(0, 0, w, h);
const vignette = cx.createRadialGradient(w / 2, h / 2, h * 0.32, w / 2, h / 2, h * 0.98);
vignette.addColorStop(0, "rgba(5, 4, 10, 0)");
vignette.addColorStop(1, "rgba(5, 4, 10, 0.84)");
cx.fillStyle = vignette;
cx.fillRect(0, 0, w, h);
raf = requestAnimationFrame(draw);
};
const onMouseMove = (e: MouseEvent) => {
mouse.targetX = (e.clientX / window.innerWidth - 0.5) * 2;
mouse.targetY = (e.clientY / window.innerHeight - 0.5) * 2;
};
fit();
draw();
window.addEventListener("resize", fit);
window.addEventListener("mousemove", onMouseMove, { passive: true });
return () => {
cancelAnimationFrame(raf);
window.removeEventListener("resize", fit);
window.removeEventListener("mousemove", onMouseMove);
};
}, [canvasRef]);
}
/** Eased count-up from 0 to target. */
function useCountUp(target: number, frames = 45): number {
const [value, setValue] = useState(0);
useEffect(() => {
let f = 0, id = 0;
const step = () => {
f++;
setValue(Math.round(target * (1 - Math.pow(1 - f / frames, 3))));
if (f < frames) id = requestAnimationFrame(step);
};
step();
return () => cancelAnimationFrame(id);
}, [target, frames]);
return value;
}
const Stat: React.FC<{ value: number; suffix: string; label: string; icon?: React.ReactNode }> = ({
value,
suffix,
label,
icon,
}) => {
const n = useCountUp(value);
return (
<div className="vx-stat">
{icon && <div className="flex justify-center mb-2 text-cyan-400 opacity-90">{icon}</div>}
<dd className="vx-grad">
{n}
{suffix}
</dd>
<dt>{label}</dt>
</div>
);
};
export interface VortexHeroProps {
onExplore?: () => void;
onLearnMore?: () => void;
className?: string;
}
export const VortexHero: React.FC<VortexHeroProps> = ({ onExplore, onLearnMore, className }) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [warpSpeed, setWarpSpeed] = useState(1);
const [themeKey, setThemeKey] = useState("violet");
const [showExploreModal, setShowExploreModal] = useState(false);
const [showWhyModal, setShowWhyModal] = useState(false);
const [showControls, setShowControls] = useState(false);
const [matchmakingGame, setMatchmakingGame] = useState<any>(null);
const [queueSeconds, setQueueSeconds] = useState(0);
const [matchReady, setMatchReady] = useState(false);
const [inLiveArena, setInLiveArena] = useState(false);
use3DGamingArena(canvasRef, warpSpeed, themeKey);
useEffect(() => {
if (!matchmakingGame) {
setQueueSeconds(0);
setMatchReady(false);
setInLiveArena(false);
return;
}
const timer = setInterval(() => {
setQueueSeconds((prev) => {
if (prev >= 2) {
setMatchReady(true);
return prev;
}
return prev + 1;
});
}, 850);
return () => clearInterval(timer);
}, [matchmakingGame]);
const handleCloseLiveArena = () => {
setMatchmakingGame(null);
setInLiveArena(false);
setMatchReady(false);
setWarpSpeed(1);
};
const currentTheme = ARENA_THEMES[themeKey];
return (
<div
className={cn("vx", className)}
style={{
"--color-primary": currentTheme.primary,
"--color-accent": currentTheme.accent,
} as React.CSSProperties}
>
<style>{CSS}</style>
<canvas ref={canvasRef} className="vx-bg" aria-hidden="true" />
{/* Floating Interactive Transition & Animation Demo Control Pill in Top-Right */}
<div className="fixed top-4 right-4 z-40 flex items-center gap-2">
{showControls && (
<div className="flex items-center gap-3 bg-[#0c0918]/90 backdrop-blur-xl px-4 py-2.5 rounded-2xl border border-white/15 shadow-2xl animate-in fade-in zoom-in-95 duration-200">
{/* Theme Selector */}
<div className="flex items-center gap-1 bg-black/60 p-1 rounded-xl border border-white/10">
{Object.values(ARENA_THEMES).map((t: any) => (
<button
key={t.id}
type="button"
onClick={() => setThemeKey(t.id)}
className={`px-2.5 py-1 rounded-lg text-xs font-medium transition-all cursor-pointer ${
themeKey === t.id
? "bg-white/20 text-white shadow border border-white/25"
: "text-slate-400 hover:text-white"
}`}
>
{t.name}
</button>
))}
</div>
{/* Warp Speed Slider */}
<div className="hidden sm:flex items-center gap-2 bg-black/60 px-3 py-1.5 rounded-xl border border-white/10 text-xs">
<Zap className="w-3.5 h-3.5 text-cyan-400" />
<span className="text-slate-300 font-medium">Warp:</span>
<input
type="range"
min="0.2"
max="3"
step="0.2"
value={warpSpeed}
onChange={(e) => setWarpSpeed(parseFloat(e.target.value))}
className="w-16 accent-cyan-400 cursor-pointer"
aria-label="3D Warp Speed"
/>
<span className="text-cyan-300 font-mono w-7 text-right">{warpSpeed.toFixed(1)}x</span>
</div>
</div>
)}
<button
type="button"
onClick={() => setShowControls(!showControls)}
className="flex items-center gap-2 bg-[#0c0918]/90 hover:bg-[#141026] text-white px-3.5 py-2.5 rounded-2xl border border-white/20 backdrop-blur-xl shadow-lg transition-all cursor-pointer group"
title="Demo Transition & 3D Settings"
>
<Sliders className="w-4 h-4 text-cyan-400 group-hover:rotate-90 transition-transform duration-300" />
<span className="text-xs font-semibold hidden md:inline">3D Demo Settings</span>
</button>
</div>
<section className="vx-hero">
<div className="vx-badge">
<span className="vx-dot" />
<Gamepad2 className="w-4 h-4 text-cyan-400" />
<span>NEXT-GEN 3D ARENA ONLINE</span>
</div>
<h1>
Enter the Arena,
<br />
<span className="vx-grad">Become a Legend</span>
</h1>
<p className="vx-sub">
One gateway to the best competitive, RPG, and racing games. Ultra-low latency servers,
cross-platform progression, and instant matchmaking.
</p>
<div className="vx-actions">
<button
type="button"
className="vx-btn vx-btn-primary"
onClick={() => {
onExplore?.();
setShowExploreModal(true);
}}
>
<Play className="w-4 h-4 fill-current" />
Explore Games
</button>
<button
type="button"
className="vx-btn vx-btn-ghost"
onClick={() => {
onLearnMore?.();
setShowWhyModal(true);
}}
>
<Sparkles className="w-4 h-4 text-cyan-400" />
Why VORTEX?
<ChevronRight className="w-4 h-4 opacity-70" />
</button>
</div>
<dl className="vx-stats">
<Stat value={6} suffix="+" label="Games Available" icon={<Gamepad2 className="w-5 h-5" />} />
<Stat value={4} suffix="+" label="Genres" icon={<Trophy className="w-5 h-5" />} />
<Stat value={24} suffix="/7" label="Servers Online" icon={<Zap className="w-5 h-5" />} />
</dl>
{/* Live Featured Arena Cards */}
<div className="w-full max-w-[46rem]">
<div className="flex items-center justify-between mb-3 px-1">
<span className="text-xs font-semibold uppercase tracking-widest text-slate-300 flex items-center gap-2">
<span className="h-2 w-2 rounded-full bg-cyan-400 animate-ping" />
Live Featured Arenas — Click to Enter Arena
</span>
<button
type="button"
onClick={() => setShowExploreModal(true)}
className="text-xs text-cyan-400 hover:text-cyan-300 font-medium flex items-center gap-1 cursor-pointer"
>
View All Arenas <ChevronRight className="w-3.5 h-3.5" />
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{GAMES_CATALOG.slice(0, 3).map((game) => (
<div
key={game.id}
onClick={() => setMatchmakingGame(game)}
className="group flex flex-col justify-between overflow-hidden rounded-2xl border border-white/12 bg-[#0e0b1a]/85 backdrop-blur-md p-3 text-left transition-all duration-300 hover:-translate-y-1.5 hover:border-cyan-400/60 hover:shadow-xl hover:shadow-cyan-500/10 cursor-pointer"
>
<div>
<div className="relative h-32 w-full overflow-hidden rounded-xl mb-3">
<img
src={game.image}
alt={game.title}
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-110"
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-t from-[#05040a] via-black/25 to-transparent pointer-events-none" />
<span className="absolute bottom-2 left-2 inline-flex items-center gap-1.5 rounded-full bg-black/80 px-2.5 py-0.5 text-[10px] font-medium text-emerald-400 border border-emerald-400/30">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />
{game.players}
</span>
<span className="absolute top-2 right-2 rounded-full bg-violet-600/90 backdrop-blur-md px-2 py-0.5 text-[10px] font-semibold text-white">
{game.ping}
</span>
</div>
</div>
<div className="flex items-center justify-between gap-2 pt-1">
<div className="min-w-0">
<h3 className="text-sm font-bold text-white truncate group-hover:text-cyan-300 transition-colors">
{game.title}
</h3>
<p className="text-xs text-slate-400 truncate">{game.genre}</p>
</div>
<div className="shrink-0 h-8 w-8 rounded-full bg-white/5 group-hover:bg-cyan-400/20 border border-white/10 group-hover:border-cyan-400/40 flex items-center justify-center transition-colors">
<Play className="w-3.5 h-3.5 text-cyan-400 fill-current" />
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* MODAL 1: Explore Games Catalog */}
{showExploreModal && (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-md"
onClick={() => setShowExploreModal(false)}
>
<div
className="relative w-full max-w-4xl max-h-[88vh] flex flex-col overflow-hidden rounded-3xl border border-white/15 bg-[#0c0918] text-left shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="sticky top-0 z-20 flex items-center justify-between gap-4 border-b border-white/10 bg-[#0c0918]/95 backdrop-blur-xl px-6 py-4">
<div>
<span className="text-xs font-semibold uppercase tracking-widest text-cyan-400">VORTEX CLOUD CATALOG</span>
<h2 className="font-display text-xl sm:text-2xl font-bold text-white mt-0.5">Select Your Combat Arena</h2>
</div>
<button
type="button"
onClick={() => setShowExploreModal(false)}
className="shrink-0 inline-flex items-center gap-1.5 px-3.5 py-2 rounded-full bg-rose-500/15 hover:bg-rose-500/25 border border-rose-500/30 text-xs font-semibold text-rose-300 hover:text-white transition-colors cursor-pointer"
>
<X className="w-4 h-4" />
<span>Tutup</span>
</button>
</div>
<div className="p-6 overflow-y-auto">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 gap-4">
{GAMES_CATALOG.map((game) => (
<div
key={game.id}
className="flex flex-col justify-between rounded-2xl border border-white/10 bg-white/[0.03] p-3.5 hover:border-cyan-400/50 transition-all"
>
<div>
<div className="relative h-36 w-full overflow-hidden rounded-xl mb-3">
<img src={game.image} alt={game.title} className="h-full w-full object-cover" />
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-black/30 pointer-events-none" />
<span className="absolute top-2 left-2 rounded-full bg-black/80 px-2.5 py-0.5 text-[10px] font-semibold text-cyan-300 border border-cyan-400/30">
{game.tickrate}
</span>
<span className="absolute bottom-2 right-2 rounded-full bg-black/80 px-2.5 py-0.5 text-[10px] font-semibold text-emerald-400">
{game.players}
</span>
</div>
<h3 className="font-display font-bold text-base text-white">{game.title}</h3>
<p className="text-xs text-violet-300 font-medium mb-2">
{game.genre} • Ping {game.ping}
</p>
<p className="text-xs text-slate-400 leading-relaxed mb-4">{game.description}</p>
</div>
<button
type="button"
onClick={() => {
setShowExploreModal(false);
setMatchmakingGame(game);
}}
className="w-full py-2.5 rounded-xl bg-gradient-to-r from-violet-600 to-indigo-600 hover:from-violet-500 hover:to-cyan-500 text-white font-semibold text-xs flex items-center justify-center gap-2 shadow-lg transition-all cursor-pointer"
>
<Play className="w-3.5 h-3.5 fill-current" />
Enter Live Arena
</button>
</div>
))}
</div>
</div>
</div>
</div>
)}
{/* MODAL 2: Why VORTEX? */}
{showWhyModal && (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-md"
onClick={() => setShowWhyModal(false)}
>
<div
className="relative w-full max-w-xl overflow-hidden rounded-3xl border border-white/15 bg-[#0c0918] text-left shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between gap-4 border-b border-white/10 bg-[#0c0918] px-6 py-4">
<div className="flex items-center gap-2.5">
<Sparkles className="w-5 h-5 text-cyan-400" />
<h2 className="font-display text-xl sm:text-2xl font-bold text-white">Why VORTEX Engine?</h2>
</div>
<button
type="button"
onClick={() => setShowWhyModal(false)}
className="shrink-0 inline-flex items-center gap-1.5 px-3.5 py-2 rounded-full bg-rose-500/15 hover:bg-rose-500/25 border border-rose-500/30 text-xs font-semibold text-rose-300 hover:text-white transition-colors cursor-pointer"
>
<X className="w-4 h-4" />
<span>Tutup</span>
</button>
</div>
<div className="p-6 sm:p-8 space-y-4">
<div className="p-4 rounded-2xl bg-white/[0.04] border border-white/10">
<Server className="w-6 h-6 text-cyan-400 mb-2" />
<h3 className="font-display font-bold text-white text-base mb-1">128-Tick Bare-Metal Mesh</h3>
<p className="text-xs text-slate-400 leading-relaxed">
Direct fiber routing across 38 global edge locations ensures sub-15ms latency and zero packet loss.
</p>
</div>
<div className="p-4 rounded-2xl bg-white/[0.04] border border-white/10">
<Shield className="w-6 h-6 text-violet-400 mb-2" />
<h3 className="font-display font-bold text-white text-base mb-1">VortexGuard Kernel Shield</h3>
<p className="text-xs text-slate-400 leading-relaxed">
Real-time AI behavioral telemetry detects anomalies before a single round is compromised.
</p>
</div>
<div className="flex justify-end pt-2">
<button
type="button"
onClick={() => setShowWhyModal(false)}
className="px-5 py-2.5 rounded-full bg-gradient-to-r from-violet-600 to-cyan-500 text-white text-xs font-semibold cursor-pointer"
>
Got It
</button>
</div>
</div>
</div>
</div>
)}
{/* MODAL 3: Matchmaking & Live Arena Session */}
{matchmakingGame && (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/85 backdrop-blur-md"
onClick={handleCloseLiveArena}
>
<div
className="relative w-full max-w-lg overflow-hidden rounded-3xl border border-cyan-400/40 bg-[#0b0816] text-left shadow-2xl shadow-cyan-500/20 flex flex-col"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between gap-3 px-5 py-3.5 bg-[#120e24] border-b border-white/10 z-20">
<div className="flex items-center gap-2 min-w-0">
<span className="h-2.5 w-2.5 rounded-full bg-emerald-400 animate-pulse shrink-0" />
<span className="text-xs font-bold uppercase tracking-wider text-cyan-300 truncate">
{inLiveArena ? "LIVE ARENA TELEMETRY" : "LIVE ARENA MATCHMAKING"}
</span>
</div>
<button
type="button"
onClick={handleCloseLiveArena}
className="shrink-0 inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-rose-500/20 hover:bg-rose-500/35 border border-rose-500/40 text-xs font-semibold text-rose-200 hover:text-white transition-all cursor-pointer"
>
<LogOut className="w-3.5 h-3.5" />
<span>Keluar Arena</span>
</button>
</div>
<div className="p-5 sm:p-6">
<div className="relative h-36 w-full overflow-hidden rounded-2xl mb-5 border border-white/10">
<img src={matchmakingGame.image} alt={matchmakingGame.title} className="h-full w-full object-cover" />
<div className="absolute inset-0 bg-gradient-to-t from-[#0b0816] via-black/40 to-transparent pointer-events-none" />
<div className="absolute top-3 left-3 flex flex-wrap gap-1.5">
<span className="rounded-full bg-black/80 px-2.5 py-1 text-[10px] font-semibold text-cyan-300 border border-cyan-400/30">
{matchmakingGame.genre}
</span>
</div>
<div className="absolute bottom-3 left-3 right-3">
<h3 className="font-display text-xl font-bold text-white drop-shadow">{matchmakingGame.title}</h3>
</div>
</div>
{!matchReady && !inLiveArena && (
<div className="py-3 text-center">
<div className="mx-auto mb-3 h-11 w-11 rounded-full border-2 border-cyan-400 border-t-transparent animate-spin" />
<p className="text-sm font-semibold text-white">Searching for High-Tick Arena Server...</p>
<p className="text-xs text-slate-400 mt-1">Elapsed: 00:0{queueSeconds}</p>
</div>
)}
{matchReady && !inLiveArena && (
<div className="py-2 text-center">
<div className="mx-auto mb-2.5 h-11 w-11 rounded-full bg-emerald-500/20 border border-emerald-400 flex items-center justify-center text-emerald-400">
<CheckCircle2 className="w-6 h-6" />
</div>
<p className="font-display text-lg font-bold text-emerald-400 tracking-wide">MATCH FOUND — SERVER READY!</p>
<div className="flex gap-2.5 mt-4">
<button
type="button"
onClick={() => {
setWarpSpeed(2.6);
setInLiveArena(true);
}}
className="flex-1 py-3 px-4 rounded-xl bg-gradient-to-r from-emerald-500 to-cyan-500 text-slate-950 font-bold text-xs sm:text-sm shadow-lg cursor-pointer"
>
MASUK LIVE ARENA (HYPER-WARP)
</button>
<button
type="button"
onClick={handleCloseLiveArena}
className="py-3 px-4 rounded-xl bg-white/5 border border-white/15 text-slate-300 font-semibold text-xs cursor-pointer"
>
Batal
</button>
</div>
</div>
)}
{inLiveArena && (
<div className="space-y-4">
<div className="rounded-2xl bg-emerald-500/10 border border-emerald-500/30 p-3.5 flex items-center justify-between">
<div className="flex items-center gap-2.5">
<Radio className="w-5 h-5 text-emerald-400 animate-pulse" />
<div>
<p className="text-xs font-bold text-emerald-300 uppercase">Connected to Server #VX-8842</p>
<p className="text-[11px] text-slate-300">3D Hyper-Warp Engine Active (2.6x Speed)</p>
</div>
</div>
</div>
<button
type="button"
onClick={handleCloseLiveArena}
className="w-full py-3 rounded-xl bg-rose-600 hover:bg-rose-500 text-white font-bold text-xs tracking-wide shadow-lg cursor-pointer"
>
KELUAR DARI LIVE ARENA
</button>
</div>
)}
</div>
</div>
</div>
)}
</div>
);
};
export const Component = VortexHero;
export default VortexHero;