import { useEffect, useMemo, useRef, useState } from 'react' import Tree from '../Tree/Tree' import FallingLeaves from '../Tree/FallingLeaves' const numberFormat = new Intl.NumberFormat('en-GB') const dateFormat = new Intl.DateTimeFormat('en-GB', { dateStyle: 'medium', timeStyle: 'short', }) const apiEndpoints = { health: '/health', uptime: '/api/uptime', viewers: '/api/viewers', viewerEvent: '/api/viewers/event', teams: '/api/tss/leaderboard/teams?limit=100', teamsHealth: '/api/tss/leaderboard/teams?limit=1', resolve: (name) => `/api/tss/teams/resolve?name=${encodeURIComponent(name)}`, detail: (name) => `/api/tss/teams/${encodeURIComponent(name)}`, history: (name) => `/api/tss/teams/${encodeURIComponent(name)}/history`, games: (name) => `/api/tss/teams/${encodeURIComponent(name)}/games`, } const navItems = [ { path: '/', label: 'Home' }, { path: '/teams', label: 'Team leaderboard' }, { path: '/battle-logs', label: 'Battle Logs' }, { path: '/viewers', label: 'Viewers' }, ] const analyticsConsentKey = 'tssbot.analyticsConsent' const analyticsVisitorKey = 'tssbot.analyticsVisitor' async function fetchJson(path, signal) { const response = await fetch(path, { signal, headers: { Accept: 'application/json' }, }) const body = await response.json().catch(() => null) if (!response.ok) { throw new Error(body?.error || `Request failed with ${response.status}`) } return body } function parseRoute(pathname = window.location.pathname) { if (pathname === '/') return { page: 'home', teamName: '' } if (pathname === '/teams') return { page: 'teams', teamName: '' } if (pathname === '/uptime') return { page: 'uptime', teamName: '' } if (pathname === '/viewers') return { page: 'viewers', teamName: '' } if (pathname.startsWith('/teams/')) { const teamName = decodeURIComponent(pathname.slice('/teams/'.length)) return { page: 'team', teamName } } if (pathname === '/battle-logs' || pathname === '/live') return { page: 'battle-logs', teamName: '' } return { page: 'home', teamName: '' } } function teamPath(name) { return `/teams/${encodeURIComponent(name)}` } function formatNumber(value) { return numberFormat.format(Number(value || 0)) } function formatDate(timestamp) { if (!timestamp) return 'Unknown time' return dateFormat.format(new Date(Number(timestamp) * 1000)) } function bestTeamName(team) { return team?.tag_name || team?.short_name || team?.long_name || '' } function storedConsent() { try { return window.localStorage.getItem(analyticsConsentKey) || '' } catch { return '' } } function stableId(storageKey) { try { const existing = window.localStorage.getItem(storageKey) if (existing) return existing const id = window.crypto?.randomUUID?.() || `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}` window.localStorage.setItem(storageKey, id) return id } catch { return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}` } } const analyticsSessionId = window.crypto?.randomUUID?.() || `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}` function browserName() { const ua = navigator.userAgent if (ua.includes('Edg/')) return 'Microsoft Edge' if (ua.includes('OPR/')) return 'Opera' if (ua.includes('Firefox/')) return 'Firefox' if (ua.includes('Chrome/') && !ua.includes('Chromium/')) return 'Chrome' if (ua.includes('Safari/') && ua.includes('Version/')) return 'Safari' return 'Unknown' } function operatingSystem() { const ua = navigator.userAgent if (ua.includes('Windows NT')) return 'Windows' if (ua.includes('Android')) return 'Android' if (/iPhone|iPad|iPod/.test(ua)) return 'iOS' if (ua.includes('Mac OS X')) return 'macOS' if (ua.includes('Linux')) return 'Linux' return 'Unknown' } function deviceType() { const ua = navigator.userAgent if (/Mobi|Android|iPhone|iPod/.test(ua)) return 'Mobile' if (/iPad|Tablet/.test(ua)) return 'Tablet' return 'Desktop' } function routeLabel(route) { if (route.page === 'team' && route.teamName) return `Team: ${route.teamName}` if (route.page === 'teams') return 'Team leaderboard' if (route.page === 'battle-logs') return 'Battle Logs' if (route.page === 'uptime') return 'Uptime' if (route.page === 'viewers') return 'viewers' return 'Home' } async function fetchRecentTssGames(teams, signal) { const teamNames = teams.map(bestTeamName).filter(Boolean).slice(0, 12) if (!teamNames.length) { return { matches: [] } } const responses = await Promise.allSettled( teamNames.map((name) => fetchJson(apiEndpoints.games(name), signal).then((data) => ({ name, data }))), ) const bySession = new Map() responses.forEach((result) => { if (result.status !== 'fulfilled') return const { name, data } = result.value ;(data.games || []).forEach((game) => { if (!game.session_id) return const existing = bySession.get(game.session_id) const currentTimestamp = Number(game.timestamp || 0) if (existing && Number(existing.timestamp || 0) >= currentTimestamp) return bySession.set(game.session_id, { ...game, team_name: data.tag_name || name, long_name: data.long_name || '', }) }) }) return { matches: Array.from(bySession.values()) .sort((a, b) => Number(b.timestamp || 0) - Number(a.timestamp || 0)) .slice(0, 50), } } function Stat({ label, value }) { return (

{label}

{value}

) } function App() { const [route, setRoute] = useState(() => parseRoute()) const [leaderboard, setLeaderboard] = useState({ status: 'idle', data: null, error: null }) const [live, setLive] = useState({ status: 'idle', data: null, error: null }) const [uptime, setUptime] = useState({ status: 'idle', checks: [], history: [], updatedAt: null }) const [viewers, setViewers] = useState({ status: 'idle', data: null, error: null, updatedAt: null }) const [analyticsConsent, setAnalyticsConsent] = useState(() => storedConsent()) const [teamQuery, setTeamQuery] = useState('') const [searchHint, setSearchHint] = useState({ status: 'idle', name: '' }) const [profile, setProfile] = useState({ teamName: '', detail: { status: 'idle', data: null, error: null }, history: { status: 'idle', data: null, error: null }, games: { status: 'idle', data: null, error: null }, }) const teams = useMemo( () => leaderboard.data?.teams || leaderboard.data?.squadrons || [], [leaderboard.data], ) const matches = live.data?.matches || [] function navigate(path) { window.history.pushState({}, '', path) setRoute(parseRoute(path)) } useEffect(() => { const onPopState = () => setRoute(parseRoute()) window.addEventListener('popstate', onPopState) return () => window.removeEventListener('popstate', onPopState) }, []) useEffect(() => { const title = route.page === 'team' && route.teamName ? `${route.teamName} | Toothless' TSS Bot` : route.page === 'teams' ? "Team leaderboard | Toothless' TSS Bot" : route.page === 'battle-logs' ? "Battle Logs | Toothless' TSS Bot" : route.page === 'uptime' ? "Uptime | Toothless' TSS Bot" : route.page === 'viewers' ? "viewers | Toothless' TSS Bot" : "Toothless' TSS Bot" document.title = title }, [route.page, route.teamName]) useEffect(() => { if (analyticsConsent !== 'analytics') return const visitorId = stableId(analyticsVisitorKey) let stopped = false function sendViewerEvent(eventType) { if (stopped) return const body = { consent: 'analytics', event_type: eventType, visitor_id: visitorId, session_id: analyticsSessionId, page_path: window.location.pathname, page_title: routeLabel(route), referrer: document.referrer, browser: browserName(), os: operatingSystem(), device: deviceType(), screen: `${window.screen.width}x${window.screen.height}`, language: navigator.language, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, metadata: { color_depth: window.screen.colorDepth, viewport: `${window.innerWidth}x${window.innerHeight}`, }, } fetch(apiEndpoints.viewerEvent, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), keepalive: true, }).catch(() => {}) } sendViewerEvent('page_view') const timer = window.setInterval(() => sendViewerEvent('heartbeat'), 30000) const onVisibilityChange = () => { if (document.visibilityState === 'visible') sendViewerEvent('heartbeat') } document.addEventListener('visibilitychange', onVisibilityChange) return () => { stopped = true window.clearInterval(timer) document.removeEventListener('visibilitychange', onVisibilityChange) } }, [analyticsConsent, route]) useEffect(() => { const onKeyDown = (event) => { if (event.key === 'Escape') { navigate('/') } } window.addEventListener('keydown', onKeyDown) return () => window.removeEventListener('keydown', onKeyDown) }, []) useEffect(() => { const query = teamQuery.trim() if (query.length < 2) { setSearchHint({ status: 'idle', name: '' }) return } const controller = new AbortController() const timer = window.setTimeout(() => { setSearchHint({ status: 'loading', name: '' }) fetchJson(apiEndpoints.resolve(query), controller.signal) .then((data) => { const name = data.tag_name || data.short_name || data.long_name || query setSearchHint({ status: 'ready', name }) }) .catch(() => { if (!controller.signal.aborted) { setSearchHint({ status: 'error', name: '' }) } }) }, 350) return () => { window.clearTimeout(timer) controller.abort() } }, [teamQuery]) useEffect(() => { if (!['home', 'teams', 'team', 'battle-logs'].includes(route.page)) return if (leaderboard.status === 'ready' || leaderboard.status === 'loading') return const controller = new AbortController() setLeaderboard({ status: 'loading', data: null, error: null }) fetchJson(apiEndpoints.teams, controller.signal) .then((data) => setLeaderboard({ status: 'ready', data, error: null })) .catch((error) => { if (!controller.signal.aborted) { setLeaderboard({ status: 'error', data: null, error: error.message }) } }) return () => controller.abort() }, [leaderboard.status, route.page]) useEffect(() => { if (!['home', 'teams', 'team', 'battle-logs'].includes(route.page)) return const controller = new AbortController() const timer = window.setInterval(() => { fetchJson(apiEndpoints.teams, controller.signal) .then((data) => setLeaderboard({ status: 'ready', data, error: null })) .catch((error) => { if (!controller.signal.aborted) { setLeaderboard((current) => ({ ...current, error: error.message })) } }) }, 60000) return () => { window.clearInterval(timer) controller.abort() } }, [route.page]) useEffect(() => { if (!['home', 'battle-logs'].includes(route.page)) return if (!teams.length) return const controller = new AbortController() setLive((current) => current.status === 'ready' ? current : { status: 'loading', data: null, error: null }, ) fetchRecentTssGames(teams, controller.signal) .then((data) => setLive({ status: 'ready', data, error: null })) .catch((error) => { if (!controller.signal.aborted) { setLive({ status: 'error', data: null, error: error.message }) } }) return () => controller.abort() }, [route.page, teams]) useEffect(() => { if (!['home', 'battle-logs'].includes(route.page)) return if (!teams.length) return const controller = new AbortController() const timer = window.setInterval(() => { fetchRecentTssGames(teams, controller.signal) .then((data) => setLive({ status: 'ready', data, error: null })) .catch((error) => { if (!controller.signal.aborted) { setLive((current) => ({ ...current, error: error.message })) } }) }, 15000) return () => { window.clearInterval(timer) controller.abort() } }, [route.page, teams]) useEffect(() => { if (route.page !== 'team' || !route.teamName) return const controller = new AbortController() setProfile({ teamName: route.teamName, detail: { status: 'loading', data: null, error: null }, history: { status: 'loading', data: null, error: null }, games: { status: 'loading', data: null, error: null }, }) Promise.allSettled([ fetchJson(apiEndpoints.detail(route.teamName), controller.signal), fetchJson(apiEndpoints.history(route.teamName), controller.signal), fetchJson(apiEndpoints.games(route.teamName), controller.signal), ]).then(([detailResult, historyResult, gamesResult]) => { if (controller.signal.aborted) return setProfile({ teamName: route.teamName, detail: detailResult.status === 'fulfilled' ? { status: 'ready', data: detailResult.value, error: null } : { status: 'error', data: null, error: detailResult.reason.message }, history: historyResult.status === 'fulfilled' ? { status: 'ready', data: historyResult.value, error: null } : { status: 'error', data: null, error: historyResult.reason.message }, games: gamesResult.status === 'fulfilled' ? { status: 'ready', data: gamesResult.value, error: null } : { status: 'error', data: null, error: gamesResult.reason.message }, }) }) return () => controller.abort() }, [route.page, route.teamName]) useEffect(() => { if (route.page !== 'uptime') return const controller = new AbortController() async function loadUptime() { setUptime((current) => ({ status: current.status === 'ready' ? 'refreshing' : 'loading', checks: current.checks, history: current.history, updatedAt: current.updatedAt, })) fetchJson(apiEndpoints.uptime, controller.signal) .then((data) => { const latest = data.latest const checks = latest ? [ { name: 'Website', detail: 'App shell and static assets', ok: latest.website_ok, label: latest.details?.website?.label || (latest.website_ok ? 'Online' : 'Issue'), latency: latest.latency_ms, }, { name: 'Health endpoint', detail: apiEndpoints.health, ok: latest.health_ok, label: latest.details?.health?.label || (latest.health_ok ? 'Operational' : 'Issue'), latency: latest.latency_ms, }, { name: 'TSS data proxy', detail: apiEndpoints.teamsHealth, ok: latest.tss_ok, label: latest.details?.tss?.label || (latest.tss_ok ? 'Operational' : 'Issue'), latency: latest.details?.tss?.latency_ms || latest.latency_ms, }, ] : [] setUptime({ status: 'ready', checks, history: (data.history || []).map((sample) => ({ timestamp: new Date(sample.checked_at).getTime(), onlineChecks: [sample.website_ok, sample.health_ok, sample.tss_ok].filter(Boolean).length, totalChecks: 3, ok: sample.ok, })), updatedAt: latest ? new Date(latest.checked_at).getTime() : null, configured: data.configured, }) }) .catch((error) => { if (controller.signal.aborted) return setUptime({ status: 'error', updatedAt: null, configured: false, history: [], checks: [ { name: 'Website', detail: 'App shell and static assets', ok: true, label: 'Online', latency: 0, }, { name: 'Health endpoint', detail: apiEndpoints.health, ok: false, label: error.message, latency: 0, }, { name: 'TSS data proxy', detail: apiEndpoints.teamsHealth, ok: false, label: 'Uptime history unavailable', latency: 0, }, ], }) }) } loadUptime() const timer = window.setInterval(loadUptime, 60000) return () => { window.clearInterval(timer) controller.abort() } }, [route.page]) useEffect(() => { if (route.page !== 'viewers') return const controller = new AbortController() function loadViewers() { setViewers((current) => ({ status: current.status === 'ready' ? 'refreshing' : 'loading', data: current.data, error: null, updatedAt: current.updatedAt, })) fetchJson(apiEndpoints.viewers, controller.signal) .then((data) => { setViewers({ status: 'ready', data, error: null, updatedAt: Date.now(), }) }) .catch((error) => { if (!controller.signal.aborted) { setViewers((current) => ({ ...current, status: 'error', error: error.message })) } }) } loadViewers() const timer = window.setInterval(loadViewers, 5000) return () => { window.clearInterval(timer) controller.abort() } }, [route.page]) useEffect(() => { if (route.page !== 'team' || !route.teamName) return const controller = new AbortController() const timer = window.setInterval(() => { Promise.allSettled([ fetchJson(apiEndpoints.detail(route.teamName), controller.signal), fetchJson(apiEndpoints.history(route.teamName), controller.signal), fetchJson(apiEndpoints.games(route.teamName), controller.signal), ]).then(([detailResult, historyResult, gamesResult]) => { if (controller.signal.aborted) return setProfile((current) => ({ teamName: route.teamName, detail: detailResult.status === 'fulfilled' ? { status: 'ready', data: detailResult.value, error: null } : current.detail, history: historyResult.status === 'fulfilled' ? { status: 'ready', data: historyResult.value, error: null } : current.history, games: gamesResult.status === 'fulfilled' ? { status: 'ready', data: gamesResult.value, error: null } : current.games, })) }) }, 60000) return () => { window.clearInterval(timer) controller.abort() } }, [route.page, route.teamName]) const topTeamName = bestTeamName(teams[0]) const searchPlaceholder = searchHint.status === 'ready' ? `Found ${searchHint.name}` : topTeamName || 'Search teams' async function handleTeamSearch(event) { event.preventDefault() const name = teamQuery.trim() if (!name) return try { const resolved = await fetchJson(apiEndpoints.resolve(name)) navigate(teamPath(resolved.tag_name || resolved.short_name || resolved.long_name || name)) } catch { navigate(teamPath(name)) } } function chooseAnalyticsConsent(value) { try { window.localStorage.setItem(analyticsConsentKey, value) } catch { // Local storage can be blocked; the in-memory choice still controls this session. } setAnalyticsConsent(value) } const activeNavPath = route.page === 'team' ? '/teams' : route.page === 'battle-logs' ? '/battle-logs' : route.page === 'viewers' ? '/viewers' : window.location.pathname return (
setTeamQuery(event.target.value)} />
{route.page === 'home' ? ( ) : null} {route.page === 'teams' ? ( ) : null} {route.page === 'team' ? ( ) : null} {route.page === 'battle-logs' ? : null} {route.page === 'uptime' ? : null} {route.page === 'viewers' ? : null}
) } function Footer({ navigate }) { return ( ) } function ConsentBanner({ consent, onChoose }) { if (consent) { return ( ) } return (

Analytics consent

We can track page views, live viewing state, browser, device, screen size, language, timezone, referrer, and pseudonymous identifiers so the public viewers page works. Raw IP addresses are not shown publicly.

) } function Landing({ live, matches, navigate }) { const treeRef = useRef(null) return (

BorisBot got nothin on THIS

Toothless' TSS Bot

Powered by Spectra. TSS analytics.

) } function RecentGamesSection({ live, matches, navigate }) { const recentMatches = matches.slice(0, 6) return (

Recent activity

Latest games

{recentMatches.map((match) => (

{match.map_name || 'Unknown map'}

{formatDate(match.timestamp)}

{match.result || 'Unknown'}

{match.team_name || 'TSS team'}

{formatNumber(match.player_count)} players

{formatNumber(match.stats?.ground_kills)} ground {formatNumber(match.stats?.air_kills)} air {formatNumber(match.stats?.deaths)} deaths
))}
{!recentMatches.length ? (

{live.status === 'loading' ? 'Loading latest games' : live.error || 'No games returned'}

) : null}
) } function PixelMountains() { const canvasRef = useRef(null) useEffect(() => { const canvas = canvasRef.current const ctx = canvas.getContext('2d') const WORLD_W = 1920 const WORLD_H = 900 function interpolate(points, x) { for (let i = 0; i < points.length - 1; i++) { const [x0, y0] = points[i] const [x1, y1] = points[i + 1] if (x >= x0 && x <= x1) { const t = (x - x0) / Math.max(1, x1 - x0) return y0 + (y1 - y0) * t } } return points.at(-1)[1] } function drawMountain(points, color, jitter = 0) { const width = WORLD_W const height = WORLD_H ctx.fillStyle = color for (let x = 0; x < width; x++) { const wave = jitter ? Math.sin(x * 0.08) * jitter + Math.sin(x * 0.021) * jitter * 1.8 : 0 const y = Math.round(interpolate(points, x) + wave) ctx.fillRect(x, y, 1, height - y) } } function draw() { const width = WORLD_W const height = WORLD_H canvas.width = WORLD_W canvas.height = WORLD_H ctx.imageSmoothingEnabled = false ctx.clearRect(0, 0, WORLD_W, WORLD_H) drawMountain( [ [0, height * 0.82], [width * 0.12, height * 0.73], [width * 0.26, height * 0.66], [width * 0.39, height * 0.76], [width * 0.55, height * 0.58], [width * 0.64, height * 0.46], [width * 0.73, height * 0.62], [width * 0.86, height * 0.56], [width, height * 0.64], ], '#fcfbcf', 1.1, ) drawMountain( [ [0, height * 0.86], [width * 0.15, height * 0.78], [width * 0.31, height * 0.81], [width * 0.43, height * 0.69], [width * 0.52, height * 0.76], [width * 0.62, height * 0.43], [width * 0.69, height * 0.31], [width * 0.78, height * 0.48], [width, height * 0.5], ], '#fff2e6', 1.6, ) drawMountain( [ [0, height * 0.94], [width * 0.17, height * 0.9], [width * 0.32, height * 0.92], [width * 0.47, height * 0.83], [width * 0.58, height * 0.89], [width * 0.66, height * 0.61], [width * 0.71, height * 0.5], [width * 0.84, height * 0.7], [width, height * 0.68], ], '#fee5cd', 2.1, ) drawMountain( [ [0, height * 0.98], [width * 0.17, height * 0.96], [width * 0.34, height * 0.99], [width * 0.48, height * 0.93], [width * 0.62, height * 0.97], [width * 0.72, height * 0.88], [width * 0.86, height * 0.95], [width, height * 0.93], ], '#fdca9b', 1.4, ) } draw() }, []) return