About
VORTEX is a gaming platform that brings the best competitive, RPG, and racing titles together in one place. Instead of jumping between launchers and websites, players can search, pick a game, and start playing in just a few clicks. With a sleek monochrome design and a fast
Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Arena Games Glass to your project and fits it to your theme.
Code
How Arena Games Glass is used: the demo's code. The component's own source comes with a free account.
Usage.tsx
import React, { useEffect, useRef } from "react";
/* ===== Styles (design tokens → components), scoped under .vx ===== */
const CSS = `
@import url('https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@500;600&family=Inter:wght@400;500;600&display=swap');
.vx{
--ink-950:#060607; --paper:#f5f5f4; --silver:#c8c8cf; --steel:#8b8b94;
--color-bg:var(--ink-950); --color-text:var(--paper); --color-muted:#9a9aa3;
--color-accent:var(--paper);
--color-border:rgba(255,255,255,.09); --color-surface:rgba(255,255,255,.035);
--ease:cubic-bezier(.2,.8,.2,1);
--font-display:'Chakra Petch',system-ui,sans-serif; --font-body:'Inter',system-ui,sans-serif;
--btn-height:54px; --btn-pad-x:2.4rem; --btn-radius:999px; --dur:.25s;
--btn-primary-top:#fafafa; --btn-primary-bottom:#bdbdc4; --btn-primary-glow:rgba(255,255,255,.22);
--btn-highlight:rgba(255,255,255,.95); --btn-border:rgba(255,255,255,.22); --btn-border-hover:rgba(255,255,255,.5);
--btn-ghost-top:rgba(255,255,255,.07); --btn-ghost-bottom:rgba(255,255,255,.015);
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;
-webkit-font-smoothing:antialiased;
}
.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:calc(env(safe-area-inset-top,0px) + 40px) 20px calc(env(safe-area-inset-bottom,0px) + 40px)}
.vx-badge{display:inline-flex;align-items:center;gap:.65rem;padding:.55rem 1.15rem;border-radius:999px;background:var(--color-surface);border:1px solid var(--color-border);color:var(--silver);letter-spacing:.28em;font-size:.7rem;font-weight:500;margin-bottom:2.1rem;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);animation:vx-rise .7s var(--ease) both}
.vx-dot{width:6px;height:6px;border-radius:50%;background:var(--paper);box-shadow:0 0 12px rgba(255,255,255,.7);animation:vx-pulse 2.4s infinite}
.vx h1{position:relative;isolation:isolate;font-family:var(--font-display);font-weight:600;line-height:1.06;letter-spacing:-.02em;margin:0 0 1.5rem;font-size:clamp(2.5rem,8vw,5.2rem);color:var(--paper);animation:vx-rise .7s .08s var(--ease) both}
.vx-sil{position:absolute;display:block;color:var(--c);width:var(--w);left:var(--x);top:var(--y);z-index:-1;pointer-events:none;opacity:.5;transform:rotate(var(--r));filter:drop-shadow(0 0 .22em currentColor);will-change:transform;animation:vx-float var(--d,7s) ease-in-out var(--dl,0s) infinite}
.vx-sil svg{display:block;width:100%;height:auto;fill:currentColor}
@keyframes vx-float{0%,100%{transform:translateY(0) rotate(var(--r))}50%{transform:translateY(-.2em) rotate(calc(var(--r) + 4deg))}}
.vx-grad{background:linear-gradient(180deg,#ffffff 10%,#b4b4bc 100%);-webkit-background-clip:text;background-clip:text;color:transparent}
.vx-legend{position:relative;display:inline-block;isolation:isolate;padding:0 .1em}
.vx-legend::before{content:"";position:absolute;inset:14% -2% 6% -2%;z-index:-1;border-radius:999px;background:linear-gradient(90deg,#27407a,#4338a0,#6a3fa6,#8a3f9a,#6a3fa6,#4338a0,#27407a);background-size:300% 100%;filter:blur(38px);opacity:.5;animation:vx-flow 8s linear infinite}
@keyframes vx-flow{from{background-position:0% 50%}to{background-position:300% 50%}}
.vx-rule{width:56px;height:1px;margin:0 auto 1.6rem;background:linear-gradient(90deg,transparent,rgba(255,255,255,.55),transparent);animation:vx-rise .7s .12s var(--ease) both}
.vx-sub{max-width:34rem;margin:0 0 2.6rem;color:var(--color-muted);font-size:clamp(.98rem,2.1vw,1.125rem);line-height:1.75;font-weight:400;letter-spacing:.01em;animation:vx-rise .7s .16s var(--ease) both}
.vx-actions{display:flex;gap:.9rem;flex-wrap:wrap;justify-content:center;animation:vx-rise .7s .24s var(--ease) both}
.vx-btn{position:relative;overflow:hidden;isolation:isolate;display:inline-flex;align-items:center;justify-content:center;min-height:var(--btn-height);padding:0 var(--btn-pad-x);border-radius:var(--btn-radius);font:500 .92rem var(--font-body);letter-spacing:.06em;color:var(--color-text);text-decoration:none;cursor:pointer;border:1px solid var(--btn-border);backdrop-filter:blur(14px) saturate(1.4);-webkit-backdrop-filter:blur(14px) saturate(1.4);transition:transform var(--dur) var(--ease),box-shadow .3s var(--ease),border-color var(--dur),background var(--dur)}
.vx-btn::before{content:"";position:absolute;inset:1px 1px 52% 1px;border-radius:var(--btn-radius) var(--btn-radius) 50% 50%/var(--btn-radius) var(--btn-radius) 100% 100%;background:linear-gradient(180deg,rgba(255,255,255,.2),rgba(255,255,255,0));z-index:-1;pointer-events:none}
.vx-btn::after{content:"";position:absolute;top:0;left:-70%;width:34%;height:100%;background:linear-gradient(105deg,transparent,rgba(255,255,255,.3),transparent);transform:skewX(-20deg);transition:left .9s var(--ease);z-index:-1;pointer-events:none}
.vx-btn:hover::after{left:150%}
.vx-btn:active{transform:scale(.98)}
.vx-btn-primary{color:#0a0a0b;font-weight:600;background:linear-gradient(180deg,var(--btn-primary-top),var(--btn-primary-bottom));box-shadow:inset 0 1px 0 var(--btn-highlight),0 14px 34px -14px var(--btn-primary-glow)}
.vx-btn-primary:hover{transform:translateY(-1px);border-color:var(--btn-border-hover);box-shadow:inset 0 1px 0 var(--btn-highlight),0 18px 40px -14px rgba(255,255,255,.32)}
.vx-btn-ghost{color:var(--silver);background:linear-gradient(180deg,var(--btn-ghost-top),var(--btn-ghost-bottom));border-color:rgba(255,255,255,.14);box-shadow:inset 0 1px 0 rgba(255,255,255,.12)}
.vx-btn-ghost:hover{transform:translateY(-1px);color:var(--paper);border-color:var(--btn-border-hover);background:linear-gradient(180deg,rgba(255,255,255,.12),rgba(255,255,255,.03))}
.vx :focus-visible{outline:2px solid var(--color-accent);outline-offset:3px}
@media (max-width:560px){.vx-sil{opacity:.4;filter:drop-shadow(0 0 .15em currentColor)}.vx-actions{width:100%}.vx-btn{flex:1 1 100%}}
@keyframes vx-rise{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
@keyframes vx-pulse{50%{opacity:.35}}
@media (prefers-reduced-motion:reduce){.vx *,.vx *::before,.vx *::after{animation-duration:.01ms!important;transition-duration:.01ms!important}.vx-legend::before,.vx-sil{animation:none}}
`;
/* ===== Hooks ===== */
const prefersReducedMotion = (): boolean =>
typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
/** Monochrome perspective grid background with soft fade at the horizon. */
function useGridBackground(canvasRef: React.RefObject<HTMLCanvasElement | null>): void {
useEffect(() => {
const cv = canvasRef.current;
const cx = cv?.getContext("2d");
if (!cv || !cx) return;
const still = prefersReducedMotion();
let w = 0, h = 0, t = 0, raf = 0;
const fit = () => {
const d = Math.min(window.devicePixelRatio || 1, 2);
w = window.innerWidth; h = window.innerHeight;
cv.width = w * d; cv.height = h * d;
cx.setTransform(d, 0, 0, d, 0, 0);
};
const draw = () => {
cx.clearRect(0, 0, w, h);
const glow = cx.createRadialGradient(w / 2, h * 0.06, 0, w / 2, h * 0.06, h * 0.9);
glow.addColorStop(0, "rgba(235,235,245,.16)");
glow.addColorStop(0.5, "rgba(235,235,245,.035)");
glow.addColorStop(1, "rgba(6,6,7,0)");
cx.fillStyle = glow; cx.fillRect(0, 0, w, h);
const hor = h * 0.68;
cx.lineWidth = 1;
// hairline horizon
const hl = cx.createLinearGradient(0, 0, w, 0);
hl.addColorStop(0, "rgba(255,255,255,0)");
hl.addColorStop(0.5, "rgba(255,255,255,.22)");
hl.addColorStop(1, "rgba(255,255,255,0)");
cx.strokeStyle = hl;
cx.beginPath(); cx.moveTo(0, hor); cx.lineTo(w, hor); cx.stroke();
const fade = cx.createLinearGradient(0, hor, 0, h);
fade.addColorStop(0, "rgba(255,255,255,0)");
fade.addColorStop(0.4, "rgba(255,255,255,.07)");
fade.addColorStop(1, "rgba(255,255,255,.16)");
cx.strokeStyle = fade;
for (let i = -16; i <= 16; i++) {
cx.beginPath(); cx.moveTo(w / 2 + i * 22, hor); cx.lineTo(w / 2 + i * w * 0.12, h); cx.stroke();
}
for (let i = 0; i < 14; i++) {
const p = ((i + t) % 14) / 14;
const y = hor + Math.pow(p, 2.4) * (h - hor);
cx.strokeStyle = `rgba(255,255,255,${Math.pow(p, 1.4) * 0.12})`;
cx.beginPath(); cx.moveTo(0, y); cx.lineTo(w, y); cx.stroke();
}
const v = cx.createRadialGradient(w / 2, h / 2, h * 0.3, w / 2, h / 2, h * 0.95);
v.addColorStop(0, "rgba(6,6,7,0)"); v.addColorStop(1, "rgba(6,6,7,.85)");
cx.fillStyle = v; cx.fillRect(0, 0, w, h);
if (!still) { t += 0.014; raf = requestAnimationFrame(draw); }
};
const onResize = () => { fit(); if (still) draw(); };
fit(); draw();
window.addEventListener("resize", onResize);
return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", onResize); };
}, [canvasRef]);
}
/* ===== Components ===== */
const GamepadIcon: React.FC<{ size?: number }> = ({ size = 16 }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
strokeWidth={1.6} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M6 12h4M8 10v4M15 13h.01M18 11h.01M17.3 5H6.7a4 4 0 0 0-3.9 3.1L1.6 14a3 3 0 0 0 5.2 2.5L8 15h8l1.2 1.5a3 3 0 0 0 5.2-2.5l-1.2-5.9A4 4 0 0 0 17.3 5z" />
</svg>
);
/** Colored game silhouettes floating behind the headline. */
interface SilItem { d: React.ReactNode; c: string; x: string; y: string; w: string; r: string; dur: string; dl: string; }
const SIL: SilItem[] = [
{ // gamepad
c: "#3b82f6", x: "-7%", y: "-26%", w: "1.5em", r: "-16deg", dur: "7s", dl: "0s",
d: <path d="M17.3 5H6.7a4 4 0 0 0-3.9 3.1L1.6 14a3 3 0 0 0 5.2 2.5L8 15h8l1.2 1.5a3 3 0 0 0 5.2-2.5l-1.2-5.9A4 4 0 0 0 17.3 5zM7 9.2h1v1.3h1.3v1H8v1.3H7v-1.3H5.7v-1H7zM15.6 11.4a.9.9 0 1 1 0 1.8.9.9 0 0 1 0-1.8zm2.6-1.6a.9.9 0 1 1 0 1.8.9.9 0 0 1 0-1.8z" fillRule="evenodd" />,
},
{ // sword
c: "#22d3ee", x: "24%", y: "-40%", w: "1.1em", r: "28deg", dur: "8s", dl: "-2s",
d: <><path d="M12 1l2.3 3.2V14H9.7V4.2z" /><rect x="6" y="14" width="12" height="2.2" rx="1.1" /><rect x="11" y="16.2" width="2" height="5" /><circle cx="12" cy="22.2" r="1.5" /></>,
},
{ // crown
c: "#d946ef", x: "72%", y: "-34%", w: "1.3em", r: "10deg", dur: "9s", dl: "-1s",
d: <path d="M2 8l5 4 5-8 5 8 5-4-2 11H4z" />,
},
{ // shield
c: "#8b5cf6", x: "94%", y: "28%", w: "1.3em", r: "12deg", dur: "7.5s", dl: "-3s",
d: <path d="M12 2l8 3v6c0 5-3.4 9.3-8 11-4.6-1.7-8-6-8-11V5z" />,
},
{ // race car
c: "#ec4899", x: "-9%", y: "72%", w: "1.9em", r: "-6deg", dur: "8.5s", dl: "-4s",
d: <><path d="M1.5 15.5l1.6-4.6C3.5 9.6 4.5 8.8 5.8 8.8H14c1 0 1.8.4 2.5 1.2l2.4 2.6 3 .7c.8.2 1.3.8 1.3 1.6v1.4c0 .6-.4 1-1 1H3a1.5 1.5 0 0 1-1.5-1.5z" /><circle cx="6.2" cy="17" r="2.6" /><circle cx="18.2" cy="17" r="2.6" /></>,
},
{ // d-pad
c: "#6366f1", x: "52%", y: "88%", w: "1.2em", r: "18deg", dur: "6.5s", dl: "-2.5s",
d: <path d="M9 2h6v7h7v6h-7v7H9v-7H2V9h7z" />,
},
];
const Silhouettes: React.FC = () => (
<>
{SIL.map((s, i) => (
<span
key={i}
className="vx-sil"
aria-hidden="true"
style={{ "--c": s.c, "--x": s.x, "--y": s.y, "--w": s.w, "--r": s.r, "--d": s.dur, "--dl": s.dl } as React.CSSProperties}
>
<svg viewBox="0 0 24 24">{s.d}</svg>
</span>
))}
</>
);
export interface VortexHeroProps {
onExplore?: () => void;
onLearnMore?: () => void;
}
const VortexHero: React.FC<VortexHeroProps> = ({ onExplore, onLearnMore }) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
useGridBackground(canvasRef);
return (
<div className="vx">
<style>{CSS}</style>
<canvas ref={canvasRef} className="vx-bg" aria-hidden="true" />
<section className="vx-hero" aria-label="VORTEX hero">
<div className="vx-badge">
<span className="vx-dot" />
<GamepadIcon />
COMING SOON
</div>
<h1>
<Silhouettes />
Enter the Arena,<br />
<span className="vx-legend"><span className="vx-grad">Become a Legend</span></span>
</h1>
<div className="vx-rule" aria-hidden="true" />
<p className="vx-sub">
One gateway to the best competitive, RPG, and racing games. Search, pick, and start playing.
</p>
<div className="vx-actions">
<button type="button" className="vx-btn vx-btn-primary" onClick={onExplore}>Explore Games</button>
<button type="button" className="vx-btn vx-btn-ghost" onClick={onLearnMore}>Why VORTEX?</button>
</div>
</section>
</div>
);
};
export default VortexHero;


