pstzx

Vortex Game online

Component

About

VORTEX is a gaming landing page built with React and TypeScript. It opens on a full-screen hero with an animated perspective grid, a “Coming Soon” badge, a clear call to action, and live-counting stats for games, genres, and server uptime.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Vortex Game online to your project and fits it to your theme.

Code

How Vortex Game online is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

import React, { useEffect, useRef, useState } from "react";

/* ===== Styles (design tokens → components), scoped under .vx ===== */
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:#07060d;
  --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,.1); --color-surface:rgba(18,15,32,.82);
  --radius-card:20px; --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.25rem; --btn-radius:999px; --dur:.2s;
  --btn-primary-top:#6d4aff; --btn-primary-bottom:#4a30d4; --btn-primary-glow:rgba(109,74,255,.75);
  --btn-highlight:rgba(255,255,255,.38); --btn-border:rgba(255,255,255,.26); --btn-border-hover:rgba(255,255,255,.55);
  --btn-ghost-top:rgba(255,255,255,.14); --btn-ghost-bottom:rgba(255,255,255,.04);
  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: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:.6rem;padding:.5rem 1rem;border-radius:999px;background:var(--color-surface);border:1px solid var(--color-border);color:var(--color-text);letter-spacing:.14em;font-size:.8rem;font-weight:600;margin-bottom:1.75rem;animation:vx-rise .6s var(--ease) both}
.vx-dot{width:8px;height:8px;border-radius:50%;background:var(--green-400);box-shadow:0 0 10px 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:-.01em;margin:0 0 1.25rem;font-size:clamp(2.5rem,8.2vw,5.4rem);animation:vx-rise .6s .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:36rem;margin:0 0 2.25rem;color:var(--color-muted);font-size:clamp(1rem,2.2vw,1.2rem);line-height:1.65;animation:vx-rise .6s .16s var(--ease) both}
.vx-actions{display:flex;gap:.9rem;flex-wrap:wrap;justify-content:center;margin-bottom:3.5rem;animation:vx-rise .6s .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:600 .95rem var(--font-body);letter-spacing:.04em;color:var(--color-text);text-decoration:none;cursor:pointer;border:1px solid var(--btn-border);backdrop-filter:blur(14px) saturate(1.5);-webkit-backdrop-filter:blur(14px) saturate(1.5);transition:transform var(--dur) var(--ease),box-shadow .3s var(--ease),border-color 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,.25),transparent);transform:skewX(-20deg);transition:left .8s var(--ease);z-index:-1;pointer-events:none}
.vx-btn:hover::after{left:150%}
.vx-btn:active{transform:scale(.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 -10px 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 16px 36px -10px 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,.28),0 10px 26px -12px rgba(0,0,0,.8)}
.vx-btn-ghost:hover{transform:translateY(-1px);border-color:var(--btn-border-hover);background:linear-gradient(180deg,rgba(255,255,255,.2),rgba(255,255,255,.07))}
.vx :focus-visible{outline:2px solid var(--color-accent);outline-offset:3px}
.vx-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:.8rem;width:100%;max-width:42rem;margin:0;animation:vx-rise .6s .32s var(--ease) both}
.vx-stat{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-card);padding:1.1rem .5rem;transition:transform .26s var(--ease),border-color .26s}
.vx-stat:hover{transform:translateY(-3px);border-color:rgba(139,92,246,.5)}
.vx-stat dd{margin:0;font:700 clamp(1.6rem,4vw,2.1rem)/1 var(--font-display)}
.vx-stat dt{margin-top:.45rem;font-size:clamp(.68rem,1.6vw,.82rem);color:var(--color-muted)}
@keyframes vx-rise{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
@keyframes vx-pulse{50%{opacity:.4}}
@media (prefers-reduced-motion:reduce){.vx *,.vx *::before,.vx *::after{animation-duration:.01ms!important;transition-duration:.01ms!important}}
`;

/* ===== Hooks ===== */
const prefersReducedMotion = (): boolean =>
  typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

/** Perspective grid background with soft fade at the horizon. */
function useGridBackground(canvasRef: React.RefObject<HTMLCanvasElement>): 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 = window.devicePixelRatio || 1;
      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.08, 0, w / 2, h * 0.08, h * 0.85);
      glow.addColorStop(0, "rgba(139,92,246,.30)");
      glow.addColorStop(0.55, "rgba(34,211,238,.05)");
      glow.addColorStop(1, "rgba(7,6,13,0)");
      cx.fillStyle = glow; cx.fillRect(0, 0, w, h);

      const hor = h * 0.66;
      cx.lineWidth = 1;
      const fade = cx.createLinearGradient(0, hor, 0, h);
      fade.addColorStop(0, "rgba(139,92,246,0)");
      fade.addColorStop(0.35, "rgba(139,92,246,.16)");
      fade.addColorStop(1, "rgba(139,92,246,.34)");
      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(34,211,238,${Math.pow(p, 1.4) * 0.26})`;
        cx.beginPath(); cx.moveTo(0, y); cx.lineTo(w, y); cx.stroke();
      }
      const v = cx.createRadialGradient(w / 2, h / 2, h * 0.35, w / 2, h / 2, h * 0.95);
      v.addColorStop(0, "rgba(7,6,13,0)"); v.addColorStop(1, "rgba(7,6,13,.75)");
      cx.fillStyle = v; cx.fillRect(0, 0, w, h);

      if (!still) { t += 0.018; raf = requestAnimationFrame(draw); }
    };

    const onResize = () => { fit(); if (still) draw(); };
    fit(); draw();
    window.addEventListener("resize", onResize);
    return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", onResize); };
  }, [canvasRef]);
}

/** Eased count-up from 0 to target. */
function useCountUp(target: number, frames = 40): number {
  const [value, setValue] = useState(0);
  useEffect(() => {
    if (prefersReducedMotion()) { setValue(target); return; }
    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;
}

/* ===== Components ===== */
const GamepadIcon: React.FC<{ size?: number }> = ({ size = 18 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
    strokeWidth={1.9} 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>
);

interface StatProps { value: number; suffix: string; label: string; }

const Stat: React.FC<StatProps> = ({ value, suffix, label }) => {
  const n = useCountUp(value);
  return (
    <div className="vx-stat">
      <dd className="vx-grad">{n}{suffix}</dd>
      <dt>{label}</dt>
    </div>
  );
};

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">
        <div className="vx-badge">
          <span className="vx-dot" />
          <GamepadIcon />
          COMING SOON
        </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. 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>

        <dl className="vx-stats">
          <Stat value={6} suffix="+" label="Games Available" />
          <Stat value={4} suffix="+" label="Genres" />
          <Stat value={24} suffix="/7" label="Servers Online" />
        </dl>
      </section>
    </div>
  );
};

export default VortexHero;

More by pstzx

View all
  • Contur Land Map

    Contur Land Map

    8
  • Button New & Search

    Button New & Search

  • Arena Games Glass

    Arena Games Glass