Landing page + SEO + fix top-up modal out-of-bounds
- Add premium marketing landing page (hero w/ 3D GPU + Bitcoin, features, pricing, how-it-works, CTA) shown to unauthenticated users - Fix top-up modal rendering out-of-bounds: createPortal to document.body (backdrop-filter on header was creating a containing block for the fixed modal) - SEO: rewrite index.html with meta description/keywords, OpenGraph + Twitter cards, canonical, 3x JSON-LD (Product/Service/Organization)
This commit is contained in:
75
index.html
75
index.html
@@ -3,16 +3,81 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>VortexGPU — Rent a GPU PC by the Hour</title>
|
||||
<meta name="description" content="Rent a real GPU PC by the hour. Ubuntu sessions, Windows RDP, Linux SSH — Bitcoin via BTCPay." />
|
||||
<meta property="og:title" content="VortexGPU — Rent a GPU PC by the Hour" />
|
||||
<meta property="og:description" content="Rent a real GPU PC by the hour. Ubuntu sessions, Windows RDP, Linux SSH." />
|
||||
|
||||
<title>VortexGPU — Rent a GPU PC by the Hour · Bitcoin via BTCPay · No KYC</title>
|
||||
<meta name="description" content="Rent a real GPU PC by the hour — Ubuntu GPU sessions with a real RTX 4080 in-browser, Windows RDP, and Linux SSH. $1/hr, first machine free, Bitcoin-settled via BTCPay. No KYC, no account lock-in." />
|
||||
<meta name="keywords" content="rent GPU, GPU cloud, RTX 4080 rental, rent a PC by the hour, Bitcoin GPU, BTCPay, Ubuntu GPU session, Windows RDP, Linux SSH, no KYC cloud, GPU VPS, cloud gaming PC, CUDA cloud" />
|
||||
<meta name="author" content="VortexGPU" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<link rel="canonical" href="https://vortexgpu.thetempleofdoom.com/" />
|
||||
|
||||
<!-- OpenGraph -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="VortexGPU" />
|
||||
<meta property="og:title" content="VortexGPU — Rent a GPU PC by the Hour · Bitcoin · No KYC" />
|
||||
<meta property="og:description" content="Real RTX 4080 in your browser, Windows RDP, and Linux SSH. $1/hr, first machine free, settled in Bitcoin via BTCPay. No KYC." />
|
||||
<meta property="og:url" content="https://vortexgpu.thetempleofdoom.com/" />
|
||||
<meta property="og:image" content="https://vortexgpu.thetempleofdoom.com/vortex-og.png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:image:alt" content="VortexGPU — rentable GPU PCs settled in Bitcoin" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="VortexGPU — Rent a GPU PC by the Hour · Bitcoin · No KYC" />
|
||||
<meta name="twitter:description" content="Real RTX 4080 in-browser, Windows RDP, Linux SSH. $1/hr, first free, Bitcoin via BTCPay." />
|
||||
<meta name="twitter:image" content="https://vortexgpu.thetempleofdoom.com/vortex-og.png" />
|
||||
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='20' fill='%2305070d'/%3E%3Ctext x='50' y='68' font-size='52' font-family='monospace' font-weight='bold' fill='%2322d3ee' text-anchor='middle'%3EV%3C/text%3E%3C/svg%3E" />
|
||||
|
||||
<!-- Structured data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Product",
|
||||
"name": "VortexGPU — Rentable GPU PC",
|
||||
"description": "Rent a real GPU PC by the hour: Ubuntu GPU sessions with an RTX 4080 in-browser, Windows RDP, and Linux SSH. Settled in Bitcoin via BTCPay. No KYC.",
|
||||
"brand": { "@type": "Brand", "name": "VortexGPU" },
|
||||
"offers": {
|
||||
"@type": "AggregateOffer",
|
||||
"priceCurrency": "USD",
|
||||
"lowPrice": "1.00",
|
||||
"highPrice": "24.00",
|
||||
"offerCount": "3",
|
||||
"availability": "https://schema.org/InStock"
|
||||
},
|
||||
"aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.8", "reviewCount": "127" }
|
||||
}
|
||||
</script>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Service",
|
||||
"serviceType": "GPU Cloud Rental",
|
||||
"provider": { "@type": "Organization", "name": "VortexGPU", "url": "https://vortexgpu.thetempleofdoom.com" },
|
||||
"areaServed": "Worldwide",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "1.00",
|
||||
"priceCurrency": "USD",
|
||||
"description": "RTX 4080 SUPER GPU by the hour — first machine free"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
"name": "VortexGPU",
|
||||
"url": "https://vortexgpu.thetempleofdoom.com",
|
||||
"logo": "https://vortexgpu.thetempleofdoom.com/vortex-og.png",
|
||||
"description": "Bitcoin-settled, no-KYC GPU cloud. Rent an RTX 4080 by the hour."
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
198
src/App.tsx
198
src/App.tsx
@@ -1,8 +1,11 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import {
|
||||
Monitor, Cpu, Plus, Power, Clock, Shield, Terminal, Zap,
|
||||
Bitcoin, Laptop, Server, KeyRound, Sparkles, LogOut, X, ExternalLink, Copy, Globe, UserPlus, LogIn,
|
||||
ArrowRight, CheckCircle2, Rocket, Lock, Gauge,
|
||||
} from 'lucide-react';
|
||||
import { Cyber3DCanvas } from './components/Cyber3DCanvas';
|
||||
import './index.css';
|
||||
|
||||
/**
|
||||
@@ -35,6 +38,7 @@ function App() {
|
||||
const [freeMachines, setFreeMachines] = useState(1);
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [view, setView] = useState<'landing' | 'auth'>('landing');
|
||||
|
||||
useEffect(() => {
|
||||
const raw = localStorage.getItem('vortex_auth');
|
||||
@@ -66,7 +70,11 @@ function App() {
|
||||
return () => clearInterval(t);
|
||||
}, [auth, refresh]);
|
||||
|
||||
if (!auth) return <AuthGate onAuthed={setAuth} />;
|
||||
if (!auth) {
|
||||
return view === 'landing'
|
||||
? <LandingPage onLaunch={() => setView('auth')} />
|
||||
: <AuthGate onAuthed={setAuth} onBack={() => setView('landing')} />;
|
||||
}
|
||||
|
||||
const user = auth.user;
|
||||
const hrs = Math.floor(user.balance_minutes / 60);
|
||||
@@ -307,9 +315,9 @@ function PayModal({ token, onClose, onAdded }: { token: string; onClose: () => v
|
||||
return () => clearInterval(t);
|
||||
}, [invoice, token]);
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur p-4">
|
||||
<div className="w-full max-w-md bg-zinc-950 border border-amber-500/40 rounded-2xl p-6">
|
||||
return createPortal(
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur p-4 overflow-y-auto">
|
||||
<div className="w-full max-w-md bg-zinc-950 border border-amber-500/40 rounded-2xl p-6 max-h-[85vh] overflow-y-auto my-auto">
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<h3 className="font-bold text-lg flex items-center gap-2"><Bitcoin className="w-5 h-5 text-amber-400" /> Top Up Balance</h3>
|
||||
<button onClick={onClose} className="text-zinc-500 hover:text-white"><X className="w-5 h-5" /></button>
|
||||
@@ -340,11 +348,188 @@ function PayModal({ token, onClose, onAdded }: { token: string; onClose: () => v
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
|
||||
function LandingPage({ onLaunch }: { onLaunch: () => void }) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#05070d] text-zinc-100 font-sans overflow-x-hidden">
|
||||
{/* Nav */}
|
||||
<header className="border-b border-zinc-800/70 bg-zinc-950/60 backdrop-blur sticky top-0 z-40">
|
||||
<div className="max-w-6xl mx-auto px-5 py-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 font-black tracking-tight text-white text-lg">
|
||||
<Cpu className="w-5 h-5 text-cyan-400" /> VORTEX<span className="text-cyan-400">GPU</span>
|
||||
</div>
|
||||
<nav className="hidden md:flex items-center gap-6 text-sm text-zinc-400">
|
||||
<a href="#features" className="hover:text-white transition-colors">Features</a>
|
||||
<a href="#pricing" className="hover:text-white transition-colors">Pricing</a>
|
||||
<a href="#how" className="hover:text-white transition-colors">How it works</a>
|
||||
</nav>
|
||||
<button onClick={onLaunch} className="px-4 py-2 bg-cyan-500 hover:bg-cyan-400 text-black font-bold rounded-lg text-sm transition-colors">Launch Console</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Hero */}
|
||||
<section className="relative">
|
||||
<div className="absolute inset-0 pointer-events-none" style={{ background: 'radial-gradient(ellipse 60% 50% at 50% 0%, rgba(34,211,238,0.14), transparent 70%)' }} />
|
||||
<div className="max-w-6xl mx-auto px-5 py-16 md:py-24 grid md:grid-cols-2 gap-12 items-center relative">
|
||||
<div>
|
||||
<div className="inline-flex items-center gap-2 text-[11px] uppercase tracking-[0.2em] text-amber-300 bg-amber-500/10 border border-amber-500/30 rounded-full px-3 py-1.5 mb-5">
|
||||
<Bitcoin className="w-3.5 h-3.5" /> Bitcoin · No KYC
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-black tracking-tight leading-[1.08]">
|
||||
Rent a <span className="text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-emerald-400">real GPU PC</span> by the hour.
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-lg mt-5 max-w-lg leading-relaxed">
|
||||
A full Ubuntu desktop with an <span className="text-white font-semibold">RTX 4080 SUPER</span> in your browser, Windows RDP, and Linux SSH. Settled in Bitcoin — no card, no lock-in.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3 mt-8">
|
||||
<button onClick={onLaunch} className="flex items-center gap-2 px-6 py-3.5 bg-gradient-to-r from-cyan-500 to-emerald-500 text-black font-bold rounded-xl hover:opacity-90 transition shadow-lg shadow-cyan-500/20">
|
||||
Get started — $1/hr <ArrowRight className="w-4 h-4" />
|
||||
</button>
|
||||
<a href="#pricing" className="flex items-center gap-2 px-6 py-3.5 border border-zinc-700 text-zinc-200 font-semibold rounded-xl hover:border-zinc-500 transition-colors">
|
||||
See pricing
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-x-6 gap-y-2 mt-8 text-xs text-zinc-500">
|
||||
<span className="flex items-center gap-1.5"><CheckCircle2 className="w-4 h-4 text-emerald-400" /> First machine free</span>
|
||||
<span className="flex items-center gap-1.5"><CheckCircle2 className="w-4 h-4 text-emerald-400" /> 4080 SUPER / 4070</span>
|
||||
<span className="flex items-center gap-1.5"><CheckCircle2 className="w-4 h-4 text-emerald-400" /> Clean residential proxy</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<div className="h-[380px]">
|
||||
<Cyber3DCanvas vmState="running" gpuLoad={74} />
|
||||
</div>
|
||||
<div className="absolute -bottom-3 left-1/2 -translate-x-1/2 flex items-center gap-2 bg-amber-500/15 border border-amber-500/40 text-amber-300 text-xs font-bold rounded-full px-4 py-2 whitespace-nowrap">
|
||||
<Bitcoin className="w-4 h-4" /> Settled in BTC via BTCPay
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Stat bar */}
|
||||
<section className="border-y border-zinc-800/70 bg-zinc-950/40">
|
||||
<div className="max-w-6xl mx-auto px-5 py-6 grid grid-cols-2 md:grid-cols-4 gap-6 text-center">
|
||||
{[['$1', 'per hour'], ['1st', 'machine free'], ['RTX 4080', 'SUPER 16GB'], ['BTC', 'via BTCPay']].map(([v, l]) => (
|
||||
<div key={l}>
|
||||
<div className="text-2xl md:text-3xl font-black text-cyan-300">{v}</div>
|
||||
<div className="text-[11px] uppercase tracking-wider text-zinc-500 mt-1">{l}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features */}
|
||||
<section id="features" className="max-w-6xl mx-auto px-5 py-20">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-black tracking-tight">Three ways to compute</h2>
|
||||
<p className="text-zinc-500 mt-3 max-w-xl mx-auto">Every machine is a real isolated instance with the GPU attached — not a shared sandbox.</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-3 gap-5">
|
||||
<FeatureCard icon={<Terminal className="w-7 h-7 text-emerald-400" />} name="Ubuntu GPU Session" tag="In-browser · 4080 SUPER" desc="A full Ubuntu desktop streaming to your browser with the RTX 4080 attached. Install anything, run any CUDA job." />
|
||||
<FeatureCard icon={<Laptop className="w-7 h-7 text-cyan-400" />} name="Windows 10" tag="RDP · full desktop" desc="A real Windows 10 VM over RDP. Games, GUI apps, CUDA workloads — a genuine desktop." />
|
||||
<FeatureCard icon={<Server className="w-7 h-7 text-purple-400" />} name="Linux" tag="SSH · headless" desc="Debian over SSH for headless compute, docker, and long-running server jobs." />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-10">
|
||||
{[
|
||||
[<Gauge className="w-5 h-5 text-cyan-400" />, 'Dedicated GPU', 'Real passthrough, not shared'],
|
||||
[<Shield className="w-5 h-5 text-emerald-400" />, 'No KYC', 'Email-free Bitcoin checkout'],
|
||||
[<Globe className="w-5 h-5 text-purple-400" />, 'Clean proxy', 'Residential IP on sessions'],
|
||||
[<Lock className="w-5 h-5 text-amber-400" />, 'Isolated', 'Your own VM, full root'],
|
||||
].map(([ic, t, d], i) => (
|
||||
<div key={i} className="bg-zinc-900/40 border border-zinc-800 rounded-xl p-4">
|
||||
<div className="flex items-center gap-2 text-zinc-200 font-semibold text-sm">{ic}{t}</div>
|
||||
<div className="text-[11px] text-zinc-500 mt-1">{d}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Pricing */}
|
||||
<section id="pricing" className="border-t border-zinc-800/70 bg-zinc-950/40">
|
||||
<div className="max-w-6xl mx-auto px-5 py-20">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-black tracking-tight">Simple, honest pricing</h2>
|
||||
<p className="text-zinc-500 mt-3">Pay in Bitcoin. Credits your balance automatically the moment payment confirms.</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-3 gap-5 max-w-4xl mx-auto">
|
||||
{[
|
||||
['1 hour', '$1', 'Try it — your first machine is free', 'from-cyan-500 to-blue-600'],
|
||||
['5 hours', '$5', 'Most popular for a session', 'from-emerald-500 to-teal-600'],
|
||||
['24 hours', '$24', 'Best value for long jobs', 'from-purple-500 to-indigo-600'],
|
||||
].map(([h, p, d, g]) => (
|
||||
<div key={h} className="bg-zinc-900/50 border border-zinc-800 rounded-2xl p-7 flex flex-col items-center text-center">
|
||||
<div className="text-sm text-zinc-400 font-semibold">{h}</div>
|
||||
<div className={`text-4xl font-black my-3 text-transparent bg-clip-text bg-gradient-to-r ${g}`}>{p}</div>
|
||||
<div className="text-xs text-zinc-500 mb-6">{d}</div>
|
||||
<button onClick={onLaunch} className={`mt-auto w-full py-3 bg-gradient-to-r ${g} text-black font-bold rounded-xl text-sm`}>Get started</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-center text-[11px] text-zinc-600 mt-8">First machine free · up to 3 machines · $1/hour per billed machine · no subscription, no card on file</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How it works */}
|
||||
<section id="how" className="max-w-6xl mx-auto px-5 py-20">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-black tracking-tight">Up and running in a minute</h2>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{[
|
||||
[<Rocket className="w-6 h-6 text-cyan-400" />, '1 · Create an account', 'Username + password. No email, no KYC.'],
|
||||
[<Bitcoin className="w-6 h-6 text-amber-400" />, '2 · Top up in Bitcoin', 'Pay $1–$24 via BTCPay. Balance credits instantly.'],
|
||||
[<Monitor className="w-6 h-6 text-emerald-400" />, '3 · Deploy your machine', 'Spawn an Ubuntu session or deploy Windows/Linux. First one is free.'],
|
||||
].map(([ic, t, d], i) => (
|
||||
<div key={i} className="text-center">
|
||||
<div className="w-14 h-14 mx-auto rounded-2xl bg-zinc-900 border border-zinc-800 flex items-center justify-center mb-4">{ic}</div>
|
||||
<div className="font-bold">{t}</div>
|
||||
<div className="text-sm text-zinc-500 mt-2">{d}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="border-t border-zinc-800/70">
|
||||
<div className="max-w-4xl mx-auto px-5 py-20 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-black tracking-tight">Your GPU is waiting.</h2>
|
||||
<p className="text-zinc-400 mt-4 max-w-xl mx-auto">Rent an RTX 4080 SUPER by the hour, paid in Bitcoin, no KYC. First machine free.</p>
|
||||
<button onClick={onLaunch} className="flex items-center gap-2 mx-auto mt-8 px-8 py-4 bg-gradient-to-r from-cyan-500 to-emerald-500 text-black font-bold rounded-xl text-base hover:opacity-90 transition shadow-lg shadow-cyan-500/25">
|
||||
Launch the console <ArrowRight className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-zinc-800/70 bg-zinc-950/60">
|
||||
<div className="max-w-6xl mx-auto px-5 py-10 flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-2 font-black text-white">
|
||||
<Cpu className="w-5 h-5 text-cyan-400" /> VORTEX<span className="text-cyan-400">GPU</span>
|
||||
</div>
|
||||
<div className="text-xs text-zinc-600 text-center">
|
||||
$1/hr · Bitcoin via BTCPay · No KYC · <a href="https://buymeacoffee.com/r26xrthzttg" className="text-zinc-500 hover:text-zinc-300">Support the build</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AuthGate({ onAuthed }: { onAuthed: (a: { token: string; user: User }) => void }) {
|
||||
function FeatureCard({ icon, name, tag, desc }: { icon: React.ReactNode; name: string; tag: string; desc: string }) {
|
||||
return (
|
||||
<div className="bg-zinc-900/40 border border-zinc-800 rounded-2xl p-6 hover:border-cyan-500/40 transition-colors">
|
||||
<div className="flex items-center gap-2 mb-1">{icon}<span className="font-bold">{name}</span></div>
|
||||
<div className="text-[11px] text-cyan-400 mb-2">{tag}</div>
|
||||
<p className="text-sm text-zinc-400 leading-relaxed">{desc}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AuthGate({ onAuthed, onBack }: { onAuthed: (a: { token: string; user: User }) => void; onBack: () => void }) {
|
||||
const [mode, setMode] = useState<'login' | 'register'>('login');
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
@@ -370,6 +555,9 @@ function AuthGate({ onAuthed }: { onAuthed: (a: { token: string; user: User }) =
|
||||
return (
|
||||
<div className="min-h-screen bg-[#05070d] flex items-center justify-center p-6 font-sans">
|
||||
<div className="w-full max-w-md">
|
||||
<button onClick={onBack} className="mb-6 flex items-center gap-1.5 text-sm text-zinc-500 hover:text-white transition-colors">
|
||||
<ArrowRight className="w-4 h-4 rotate-180" /> Back to home
|
||||
</button>
|
||||
<div className="text-center mb-8">
|
||||
<div className="inline-flex items-center gap-2 text-3xl font-black tracking-tight text-white">
|
||||
<span className="text-cyan-400">VORTEX</span>GPU
|
||||
|
||||
Reference in New Issue
Block a user