// VEXEL — Home, Tournaments, Tournament detail, Live
const { useState: _u, useEffect: _e, useMemo: _m } = React;
// ─── Tournament card ─────────────────────────────────────────────────────────
function TournamentCard({ t, go, variant = "default", currency = "AED" }) {
const g = gameById(t.game);
const price = currency === "AED" ? fmtAED(t.prize) : fmtUSD(t.prize);
return (
go("tournament", { id: t.id })} style={{ cursor: "pointer", position: "relative" }}>
{t.status === "live" && }
{t.status === "upcoming" && Upcoming }
{t.status === "past" && Past }
{g.name.toUpperCase()} · {t.format.toUpperCase()}
{t.name}
{t.teams} TEAMS · {t.region}
{t.status === "live" ? (
Watch
) : t.status === "upcoming" ? (
Register
) : (
Results
)}
);
}
// ─── HOME ────────────────────────────────────────────────────────────────────
function HomePage({ go, currency }) {
const featured = TOURNAMENTS.find(t => t.featured) || TOURNAMENTS[0];
const liveT = TOURNAMENTS.filter(t => t.status === "live");
const thisWeek = TOURNAMENTS.slice(0, 8);
const topTeam = TEAMS[1]; // Falcons
const topPlayer = PLAYERS[0];
return (
{/* ─── HERO ──────────────────────────── */}
{/* bg layers */}
{/* Side numbers */}
VXL // DUBAI · GULF · 2026
LIVE · {liveT.length} EVENTS
SEASON 03 · OPEN BRACKET
SIGN-UPS OPEN
Vexel · Season 03 · Spring–Summer 2026
Where the Gulf plays.
The competitive home for the region's strongest rosters. Tournaments, rankings, broadcasts, and a player pipeline built out of Dubai — not flown in for it.
go("live")}>
Watch live
go("tournaments")}>Browse tournaments →
{/* Stat strip */}
{[
["12,400", "Active players"],
[currency === "AED" ? "AED 4.2M" : "$1.14M", "Prize pool '26"],
["38", "Tournaments running"],
["7", "Partner publishers"],
].map(([n, l], i) => (
))}
{/* ─── 3-TILE FEATURED ───────────────── */}
The Spotlight.
Updated 11 minutes ago
{/* Next tournament */}
go("tournament", { id: featured.id })} className="card lift" style={{ cursor: "pointer", position: "relative", overflow: "hidden", aspectRatio: "1/1.05" }}>
Next on the calendar
{featured.status === "live" &&
}
{featured.name}
Prize Pool
{currency === "AED" ? fmtAED(featured.prize) : fmtUSD(featured.prize)}
{/* Top team */}
go("team", { id: topTeam.id })} className="card lift" style={{ cursor: "pointer", position: "relative", overflow: "hidden", aspectRatio: "1/1.05" }}>
Top team · this week
{topTeam.name}
{topTeam.region} · {topTeam.members} active players
Win rate
{topTeam.winRate}%
{/* Player of the week */}
go("player", { id: topPlayer.id })} className="card lift" style={{ cursor: "pointer", position: "relative", overflow: "hidden", aspectRatio: "1/1.05" }}>
Player of the week
#{topPlayer.rank} · {gameById(topPlayer.game).name.toUpperCase()}
{topPlayer.handle}
{topPlayer.realName} · {teamById(topPlayer.team).tag}
Win %
{topPlayer.winRate}%
{/* ─── THIS WEEK GRID ─────────────────── */}
Live + Upcoming
Tournaments running this week
go("tournaments")}>View all 38 →
{thisWeek.map(t => )}
{/* ─── TEAM SPOTLIGHT ─────────────────── */}
Team spotlight · May
ApexEsports
"{TEAMS[0].motto}" — Six titles in eight months, the deepest bench in the GCC, and a Valorant roster currently sitting at world #14 on the official VCT ladder.
{[["6", "Titles '26"], ["AED 1.4M", "Earnings YTD"], ["78%", "Win rate"], ["#14", "VCT World"]].map(([v, l]) => (
))}
go("team", { id: TEAMS[0].id })} className="btn btn-primary btn-lg" style={{ marginTop: 32 }}>Full team profile →
{/* ─── NEWS PREVIEW ───────────────────── */}
The Drop · Journal
News & long-form
go("news")}>All stories →
{NEWS.slice(0, 6).map(n => (
go("article", { id: n.id })} className="card lift" style={{ cursor: "pointer" }}>
{n.category} · {n.read}
{n.title}
{n.author.toUpperCase()} · {n.date}
))}
{/* ─── DUBAI PROMO STRIP ──────────────── */}
Built in Dubai
The Gulf gaming scene is about to explode .
Vexel is the operating system for what comes next — a region-first platform, owned and operated out of the UAE.
go("about")} className="btn btn-ghost btn-lg" style={{ marginTop: 24 }}>Our story →
);
}
// ─── TOURNAMENTS LIST ────────────────────────────────────────────────────────
function TournamentsPage({ go, currency }) {
const [game, setGame] = _u("all");
const [status, setStatus] = _u("all");
const [format, setFormat] = _u("all");
const [region, setRegion] = _u("all");
const [view, setView] = _u("grid");
const [sort, setSort] = _u("prize");
const [page, setPage] = _u(1);
let list = TOURNAMENTS.filter(t => (game === "all" || t.game === game) && (status === "all" || t.status === status) && (format === "all" || t.format.toLowerCase().includes(format.toLowerCase())) && (region === "all" || t.region === region));
if (sort === "prize") list = [...list].sort((a, b) => b.prize - a.prize);
if (sort === "soon") list = [...list].sort((a, b) => (a.status === "live" ? -1 : 1));
const featured = TOURNAMENTS.find(t => t.featured);
return (
{/* Featured banner */}
LIVE NOW · DAY 6
{gameById(featured.game).name.toUpperCase()} · BEST-OF-FIVE BRACKET
{featured.name}
Prize Pool
{currency === "AED" ? fmtAED(featured.prize) : fmtUSD(featured.prize)}
go("tournament", { id: featured.id })} className="btn btn-primary btn-lg">View tournament →
Watch stream
{/* Sidebar filters */}
Filters
[g.id, g.name])]} value={game} onChange={setGame} />
{/* Results */}
SHOWING {list.length} OF {TOURNAMENTS.length} TOURNAMENTS
Sort
setSort(e.target.value)}>
Prize pool (high → low)
Live first
Start date
{[["grid", "▦"], ["list", "≡"]].map(([k, ic]) => (
setView(k)} style={{ padding: "6px 10px", fontSize: 14, background: view === k ? "var(--cyan)" : "transparent", color: view === k ? "var(--ink)" : "var(--text-dim)" }}>{ic}
))}
{view === "grid" ? (
{list.map(t => )}
) : (
{list.map((t, i) => (
go("tournament", { id: t.id })} className="sweep" style={{ display: "grid", gridTemplateColumns: "60px 1fr 1fr 1fr 1fr auto", padding: "16px 20px", borderBottom: i < list.length - 1 ? "1px solid var(--line)" : "none", gap: 16, alignItems: "center", cursor: "pointer", position: "relative" }}>
{t.name}
{gameById(t.game).name.toUpperCase()} · {t.format}
{currency === "AED" ? fmtAED(t.prize) : fmtUSD(t.prize)}
{t.date}
{t.teams} teams · {t.region}
{t.status === "live" && }
{t.status === "upcoming" && Upcoming }
{t.status === "past" && Past }
))}
)}
{/* Pagination */}
{[1, 2, 3, "…", 8].map((p, i) => (
typeof p === "number" && setPage(p)} className="btn btn-dark btn-sm" style={{ minWidth: 36, justifyContent: "center", ...(p === page ? { background: "var(--cyan)", color: "var(--ink)", borderColor: "var(--cyan)" } : {}) }}>{p}
))}
);
}
function FilterGroup({ label, options, value, onChange }) {
return (
{label}
{options.map(([k, l]) => (
onChange(k)} style={{ textAlign: "left", padding: "6px 8px", fontSize: 13, color: value === k ? "var(--cyan)" : "var(--text-dim)", background: value === k ? "rgba(0,229,255,0.08)" : "transparent", borderLeft: value === k ? "2px solid var(--cyan)" : "2px solid transparent", transition: "all 0.1s" }}>{l}
))}
);
}
// ─── TOURNAMENT DETAIL ───────────────────────────────────────────────────────
function TournamentDetailPage({ params, go, currency }) {
const t = TOURNAMENTS.find(x => x.id === params.id) || TOURNAMENTS[0];
const g = gameById(t.game);
const [tab, setTab] = _u("overview");
return (
{/* Cinematic hero */}
{t.status === "live" && LIVE · DAY 6 OF 11 }
{t.status === "upcoming" && UPCOMING }
{t.status === "past" && CONCLUDED · WINNER: {t.winner} }
{t.date.toUpperCase()} · {g.name.toUpperCase()}
{t.name}
Prize Pool
{currency === "AED" ? fmtAED(t.prize) : fmtUSD(t.prize)}
{t.status === "live" &&
}
{t.status === "live" && setTab("stream")}> Watch the broadcast }
{t.status === "upcoming" && Register your team → }
setTab("bracket")}>Open bracket
{/* Tabs */}
{["overview", "bracket", "schedule", "teams", "stats", "stream"].map(k => (
setTab(k)}>{k}
))}
{tab === "overview" && }
{tab === "bracket" && }
{tab === "schedule" && }
{tab === "teams" && }
{tab === "stats" && }
{tab === "stream" && }
{/* Sponsors strip */}
Tournament Sponsors
{["Emirates", "DP World", "Logitech G", "Red Bull", "HyperX", "ASUS ROG", "Pepsi", "Lenovo"].map(s => (
{ e.currentTarget.style.color = "var(--cyan)"; e.currentTarget.style.borderColor = "var(--cyan)"; }} onMouseLeave={e => { e.currentTarget.style.color = "var(--text-dim)"; e.currentTarget.style.borderColor = "var(--line)"; }}>{s.toUpperCase()}
))}
);
}
function TournamentOverview({ t, go, setTab }) {
return (
About the event
{t.name} is a {t.format.toLowerCase()} {gameById(t.game).name} event hosting {t.teams} teams across an eleven-day double-elimination bracket. The grand final will be played live in front of a sold-out crowd at the Coca-Cola Arena in Dubai, broadcast in English, Arabic and Turkish.
The winner secures the trophy, a guaranteed seat in Vexel's Summer Invitational, and the lion's share of an AED {(t.prize/1000).toFixed(0)}k prize pool. Sign-ups closed twelve hours after they opened.
{/* Live match widget */}
{t.status === "live" && (
LIVE · MAP 2 · ASCENT SEMI-FINAL · BO5
13:24 ELAPSED · ROUND 19
{TEAMS[0].name}
{TEAMS[0].tag} · WORLD #14 · MAPS 1-0
{TEAMS[1].name}
{TEAMS[1].tag} · WORLD #08 · MAPS 0-1
{["Apex", "ZeroCool", "alZayed", "Mehari", "Astra"].map((p, i) => (
{p}
{14 - i} / {6 + i} / {4 + i}
))}
{["FalconX", "k1rito", "DUNE", "Sphinx", "Mirage"].map((p, i) => (
{p}
{12 - i} / {8 + i} / {3 + i}
))}
)}
{/* Mini schedule preview */}
Coming up
{SCHEDULE[0].matches.map((m, i) => (
{m.time}
{m.a} vs {m.b}
{m.round.toUpperCase()}
{m.live ?
:
Set reminder }
))}
setTab("schedule")}>Full schedule →
{/* Side */}
Prize distribution
{[["1st", "60%", t.prize * 0.6], ["2nd", "22%", t.prize * 0.22], ["3rd–4th", "12%", t.prize * 0.12], ["5th–8th", "6%", t.prize * 0.06]].map(([p, pct, v]) => (
{p}
{pct}
AED {Math.round(v).toLocaleString()}
))}
Broadcast
{[["Twitch · vexel_clutch (EN)", "var(--magenta)"], ["YouTube · Vexel Esports (EN)", "var(--red)"], ["Vexel Native · vexel.com/live", "var(--cyan)"], ["Twitch · vexel_arabic (AR)", "var(--orange)"]].map(([n, c]) => (
{n}
))}
);
}
function BracketView() {
return (
Upper bracket
16-TEAM · DOUBLE ELIMINATION · UPDATED LIVE
{BRACKET.map((round, ri) => (
{round.round}
{round.matches.map((m, mi) => {
const isLive = m.live;
const isUpcoming = m.upcoming;
const winnerA = m.sa !== null && m.sa > m.sb;
const winnerB = m.sb !== null && m.sb > m.sa;
return (
{isLive &&
LIVE NOW · MAP {(m.sets && m.sets.length) || 1}
}
);
})}
))}
);
}
function Slot({ name, score, winner, live }) {
return (
{name}
{score ?? "—"}
);
}
function ScheduleView() {
return (
Schedule
{SCHEDULE.map((day, i) => (
{day.day}
{day.matches.map((m, mi) => (
{m.time}
{m.round.toUpperCase()}
{m.a} vs {m.b}
{m.live ?
:
Reminder }
))}
))}
);
}
function TeamsGrid({ go }) {
return (
Participating teams · 16
{TEAMS.map(t => (
go("team", { id: t.id })} className="card lift" style={{ padding: 16, textAlign: "center", cursor: "pointer", aspectRatio: "1" }}>
{t.name}
{t.region}
))}
);
}
function StatsLeaderboard() {
return (
Top performers
# PLAYER TEAM K/D ACS HS% RATING
{PLAYERS.slice(0, 12).map(p => (
{String(p.rank).padStart(2, "0")}
{p.handle}
{teamById(p.team).tag}
{p.kd}
{260 - p.rank * 4}
{(34 - p.rank * 0.3).toFixed(1)}%
{(1.4 - p.rank * 0.018).toFixed(2)}
))}
);
}
function StreamView({ t }) {
return (
{/* Player overlay */}
LIVE · 48,200
VEXEL · EN
SETTINGS · 1080p60
{/* Scoreboard overlay */}
{/* Bottom controls */}
{t.name} · Semi-final Apex vs Falcons
VEXEL OFFICIAL · ENGLISH · 48,200 WATCHING
♥ Follow
⤴ Share
);
}
// Reusable chat panel
function ChatPanel({ compact }) {
const msgs = [
["sandstorm", "var(--cyan)", "let's go apex!!"],
["k1rito_fan", "var(--magenta)", "kirito clutch incoming"],
["DubaiGamer", "var(--lime)", "this is so good"],
["pulse_", "var(--orange)", "the wallbang though"],
["AyaSL_official", "var(--magenta)", "from the team — gg wp"],
["faroe", "var(--gold)", "13-11 incoming"],
["DUNE", "var(--cyan)", "watching from doha"],
["Astra", "var(--lime)", "VEX FOR THE WIN"],
["Cobra", "var(--magenta)", "they fumbled b site so bad"],
["BurnerX", "var(--cyan)", "the new vyse rework is wild"],
["alZayed", "var(--gold)", "1 round from match point"],
["pixel", "var(--orange)", "huge w for apex"],
["Storm99", "var(--lime)", "GG GG GG"],
];
return (
{msgs.map((m, i) => (
{m[0]}
:
{m[2]}
))}
);
}
// ─── LIVE ────────────────────────────────────────────────────────────────────
function LivePage({ go }) {
const live = TOURNAMENTS.filter(t => t.status === "live");
const [active, setActive] = _u(0);
const [gameFilter, setGameFilter] = _u("all");
const filtered = gameFilter === "all" ? live : live.filter(t => t.game === gameFilter);
const main = filtered[active] || filtered[0] || live[0];
return (
LIVE NOW · {live.length} TOURNAMENTS · 248K WATCHING
UPDATED EVERY 12 SECONDS · NEXT REFRESH 00:08
Live broadcasts
Every Vexel match, live in one place. Pick a stream, drop into chat.
{/* Game chips */}
setGameFilter("all")} className={"pill " + (gameFilter === "all" ? "pill-up" : "pill-past")} style={{ padding: "6px 14px" }}>All games
{GAMES.map(g => (
setGameFilter(g.id)} className={"pill " + (gameFilter === g.id ? "pill-up" : "pill-past")} style={{ padding: "6px 14px", whiteSpace: "nowrap" }}>{g.name}
))}
{/* 4-up grid */}
{filtered.slice(0, 4).map((t, i) => (
setActive(i)} style={{ position: "relative", cursor: "pointer", aspectRatio: "16/9", overflow: "hidden", border: i === active ? "2px solid var(--cyan)" : "1px solid var(--line)", borderRadius: 4 }}>
LIVE
{gameById(t.game).name.toUpperCase()}
{t.name}
{/* Mini scoreboard */}
{TEAMS[i*2 % TEAMS.length].tag} {13 - i}
● {String(20 + i)}:{String(14 + i*3).padStart(2,"0")}
{11 - i} {TEAMS[(i*2+1) % TEAMS.length].tag}
))}
{/* Featured stream */}
LIVE · 48,200 WATCHING
▶
1:24:08 / LIVE
HD · 1080p60
{main.name}
{gameById(main.game).name.toUpperCase()} · SEMI-FINAL · BO5
go("tournament", { id: main.id })} className="btn btn-ghost btn-sm" style={{ marginLeft: "auto" }}>Full match page →
);
}
Object.assign(window, { HomePage, TournamentsPage, TournamentDetailPage, LivePage });