import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import Tree, { prewarmTreeCanvas } from '../Tree/Tree'
import FallingLeaves from '../Tree/FallingLeaves'
import ReplayCanvasPanel from './ReplayCanvas'
import { buildBracket, computeBracketLayout, feederParent, layoutKey } from './bracket'

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',
  viewerDelete: '/api/viewers/delete',
  teams: '/api/tss/leaderboard/teams?limit=100',
  players: '/api/tss/leaderboard/players?limit=100',
  tournaments: '/api/tss/tournaments',
  tournament: (id) => `/api/tss/tournaments/${encodeURIComponent(id)}`,
  homeTeams: '/api/tss/leaderboard/teams?limit=4',
  teamsHealth: '/api/tss/leaderboard/teams?limit=1',
  recentGames: '/api/tss/games/recent?limit=50',
  game: (gameId) => `/api/tss/games/${encodeURIComponent(gameId)}`,
  gameLogs: (gameId) => `/api/tss/games/${encodeURIComponent(gameId)}/logs`,
  resolve: (name) => `/api/tss/teams/resolve?name=${encodeURIComponent(name)}`,
  searchTeams: (name) => `/api/tss/teams/search?q=${encodeURIComponent(name)}&limit=10`,
  detail: (name) => `/api/tss/teams/${encodeURIComponent(name)}`,
  games: (name) => `/api/tss/teams/${encodeURIComponent(name)}/games`,
  player: (uid) => `/api/tss/player/${encodeURIComponent(uid)}`,
  searchPlayers: (name) => `/api/tss/players/search?q=${encodeURIComponent(name)}&limit=10`,
}

const primaryNavItems = [
  { path: '/', label: 'Home' },
  { path: '/teams', label: 'Team' },
  { path: '/players', label: 'Players' },
  { path: '/battle-logs', label: 'Battles' },
  { path: '/tournaments', label: 'Tournaments' },
  { path: '/blog', label: 'Blog' },
]

const utilityNavItems = [
  { path: '/viewers', label: 'Viewers' },
  { path: '/docs', label: 'Setup' },
  { path: '/settings', label: 'Settings' },
]

const analyticsConsentKey = 'tssbot.analyticsConsent'
const analyticsPreferencesKey = 'tssbot.analyticsPreferences'
const analyticsPreferencesCookie = 'tssbot_analytics_preferences'
const analyticsVisitorKey = 'tssbot.analyticsVisitor'
const analyticsConsentVersion = 3
const themePreferenceKey = 'tssbot.theme'
const themePreferenceCookie = 'tssbot_theme'
const liveRefreshMs = 15000
const siteVersion = import.meta.env.VITE_SITE_VERSION || '1.0.0'

const turnstileSiteKey = import.meta.env.VITE_TURNSTILE_SITE_KEY || ''
const siteGateEnabled = String(import.meta.env.VITE_SITE_GATE || 'false').toLowerCase() === 'true'
const staticDataBase = (import.meta.env.VITE_STATIC_DATA_BASE || '/data').replace(/\/+$/, '')
const staticDataEnabled = String(import.meta.env.VITE_STATIC_DATA || 'false').toLowerCase() === 'true'
const missingStaticDataPaths = new Set()
const blogPostFiles = import.meta.glob('./blog/posts/*.md', {
  eager: true,
  import: 'default',
  query: '?raw',
})
// Rendered inline (not via <img src="data:...">) so the SVGs live in the page's
// CSS scope and can resolve currentColor and the --sprite-* / --color-* theme
// variables. An external SVG image renders in an isolated document and would
// fall back to black for every themed fill.
function PixelSun(props) {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 16 16"
      shapeRendering="crispEdges"
      {...props}
    >
      <rect width="16" height="16" fill="none" />
      <path fill="currentColor" d="M5 1h6v1h2v2h1v2h1v4h-1v2h-1v2h-2v1H5v-1H3v-2H2v-2H1V6h1V4h1V2h2z" />
      <path fill="var(--sprite-light)" d="M5 3h5v1h2v2h1v3h-1v2h-2v1H5v-1H4V9H3V6h1V4h1z" />
      <path fill="var(--sprite-shine)" d="M5 3h3v1h2v2H8v1H5z" />
      <path fill="var(--sprite-shadow)" d="M11 9h2v2h-2v1H6v-1h3v-1h2z" />
    </svg>
  )
}
function PixelMoon(props) {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 16 16"
      shapeRendering="crispEdges"
      {...props}
    >
      <rect width="16" height="16" fill="none" />
      <path fill="var(--sprite-light)" d="M7 1h5v1h2v2h1v7h-1v2h-2v1H6v-1H4v-2H3V5h1V3h3z" />
      <path fill="var(--sprite-mid)" d="M5 4h2v2H5zm5-1h2v1h1v2h-2V5h-1zm-4 6h2v2H6zm4 2h3v1h-1v1h-2zM4 7h1v3H4z" />
      <path fill="var(--sprite-shadow)" d="M5 10h1v2h2v1H6v-1H5zm7-5h1v2h-2V6h1zM8 3h1v2H7V4h1z" />
      <path fill="currentColor" d="M7 0h5v1h2v1h1v2h1v7h-1v2h-1v1h-2v1H6v-1H4v-1H3v-2H2V5h1V3h1V2h3zm0 1v1H5v1H4v2H3v6h1v2h2v1h6v-1h2v-2h1V4h-1V2h-2V1z" />
      <path fill="var(--sprite-shine)" d="M6 3h2v1H7v2H5V5h1zm4 4h2v1h1v1h-3zM8 11h1v1H7v-1z" />
    </svg>
  )
}

const defaultAnalyticsPreferences = {
  chosen: false,
  analytics: false,
  device: false,
  display: false,
  locale: false,
  referrer: false,
  diagnostics: false,
  version: analyticsConsentVersion,
}

function staticDataPath(...segments) {
  return [staticDataBase, ...segments].join('/')
}

function staticDataKey(value) {
  return encodeURIComponent(String(value || '').trim()).replace(/%/g, '~')
}

function dataSource(apiPath, staticPath = null) {
  return { apiPath, staticPath }
}

const publicDataSources = {
  teams: dataSource(apiEndpoints.teams, staticDataPath('leaderboard-teams.json')),
  players: dataSource(apiEndpoints.players, staticDataPath('leaderboard-players.json')),
  homeTeams: dataSource(apiEndpoints.homeTeams, staticDataPath('home-teams.json')),
  recentGames: dataSource(apiEndpoints.recentGames, staticDataPath('recent-games.json')),
  detail: (name) => dataSource(apiEndpoints.detail(name), staticDataPath('teams', `${staticDataKey(name)}.json`)),
  games: (name) => dataSource(apiEndpoints.games(name), staticDataPath('teams', `${staticDataKey(name)}.games.json`)),
  player: (uid) => dataSource(apiEndpoints.player(uid), staticDataPath('players', `${staticDataKey(uid)}.json`)),
  game: (gameId) => dataSource(apiEndpoints.game(gameId), staticDataPath('games', `${staticDataKey(gameId)}.json`)),
  gameLogs: (gameId) => dataSource(apiEndpoints.gameLogs(gameId), staticDataPath('games', `${staticDataKey(gameId)}.logs.json`)),
}

async function fetchJson(path, signal) {
  const response = await fetch(path, {
    signal,
    headers: { Accept: 'application/json' },
  })
  const contentType = response.headers.get('content-type') || ''
  const body = contentType.includes('application/json')
    ? await response.json().catch(() => null)
    : null

  if (!response.ok) {
    const error = new Error(body?.error || `Request failed with ${response.status}`)
    error.status = response.status
    error.path = path
    throw error
  }

  if (!body) {
    const error = new Error(`Expected JSON from ${path}`)
    error.status = response.status
    error.path = path
    error.contentType = contentType
    throw error
  }

  return body
}

async function fetchPublicJson(source, signal) {
  if (!source?.staticPath || !staticDataEnabled || missingStaticDataPaths.has(source.staticPath)) {
    return fetchJson(source.apiPath, signal)
  }

  try {
    return await fetchJson(source.staticPath, signal)
  } catch (error) {
    if (signal?.aborted || error?.name === 'AbortError') throw error
    if (error?.status === 404 || error?.contentType) {
      missingStaticDataPaths.add(source.staticPath)
      return fetchJson(source.apiPath, signal)
    }
    throw error
  }
}

function parseRoute(pathname = window.location.pathname) {
  if (pathname === '/') return { page: 'home', teamName: '' }
  if (pathname === '/teams') return { page: 'teams', teamName: '' }
  if (pathname === '/players') return { page: 'players', teamName: '' }
  if (pathname === '/blog') return { page: 'blog', teamName: '' }
  if (pathname.startsWith('/blog/')) {
    const slug = decodeURIComponent(pathname.slice('/blog/'.length))
    return { page: 'blog-post', teamName: '', slug }
  }
  if (pathname === '/uptime') return { page: 'uptime', teamName: '' }
  if (pathname === '/viewers') return { page: 'viewers', teamName: '' }
  if (pathname === '/privacy') return { page: 'privacy', teamName: '' }
  if (pathname === '/docs') return { page: 'docs', teamName: '' }
  if (pathname === '/settings') return { page: 'settings', teamName: '' }
  if (pathname === '/tournaments') return { page: 'tournaments-list', teamName: '' }
  if (pathname.startsWith('/tournaments/')) {
    const tournamentId = decodeURIComponent(pathname.slice('/tournaments/'.length))
    return { page: 'tournament', teamName: '', tournamentId }
  }
  if (pathname.startsWith('/players/')) {
    const uid = decodeURIComponent(pathname.slice('/players/'.length))
    return { page: 'player', teamName: '', uid }
  }
  if (pathname.startsWith('/games/')) {
    const gameId = decodeURIComponent(pathname.slice('/games/'.length))
    return { page: 'game', teamName: '', gameId }
  }
  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 gamePath(gameId) {
  return `/games/${encodeURIComponent(gameId)}`
}

function playerPath(uid) {
  return `/players/${encodeURIComponent(uid)}`
}

function tournamentPath(tournamentId) {
  return `/tournaments/${encodeURIComponent(tournamentId)}`
}

function blogPostPath(slug) {
  return `/blog/${encodeURIComponent(slug)}`
}

function formatNumber(value) {
  return numberFormat.format(Number(value || 0))
}

function formatMatchSize(playerCount) {
  const count = formatNumber(playerCount)
  return `${count}v${count}`
}

function formatDate(timestamp) {
  if (!timestamp) return 'Unknown time'
  return dateFormat.format(new Date(Number(timestamp) * 1000))
}

function formatDuration(seconds) {
  const total = Math.round(Number(seconds || 0))
  if (!total) return ''
  const m = Math.floor(total / 60)
  const s = total % 60
  if (!m) return `${s}s`
  return `${m}m ${s}s`
}

function slugify(value) {
  return String(value || '')
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
}

function parseBlogFrontMatter(raw) {
  const text = String(raw || '').replace(/^\uFEFF/, '')
  if (!text.startsWith('---\n') && !text.startsWith('---\r\n')) {
    return { attributes: {}, content: text.trim() }
  }

  const normalized = text.replace(/\r\n/g, '\n')
  const end = normalized.indexOf('\n---', 4)
  if (end === -1) return { attributes: {}, content: normalized.trim() }

  const frontMatter = normalized.slice(4, end).trim()
  const content = normalized.slice(end + 4).trim()
  const attributes = {}

  frontMatter.split('\n').forEach((line) => {
    const separator = line.indexOf(':')
    if (separator === -1) return
    const key = line.slice(0, separator).trim()
    const value = line.slice(separator + 1).trim().replace(/^["']|["']$/g, '')
    if (key) attributes[key] = value
  })

  return { attributes, content }
}

function excerptFromMarkdown(markdown) {
  return String(markdown || '')
    .replace(/```[\s\S]*?```/g, '')
    .split('\n')
    .map((line) => line.replace(/^#{1,6}\s+/, '').trim())
    .find((line) => line && !line.startsWith('![') && !line.startsWith('>')) || ''
}

function createBlogPost(path, raw) {
  const { attributes, content } = parseBlogFrontMatter(raw)
  const filename = path.split('/').pop()?.replace(/\.md$/i, '') || ''
  const slug = slugify(attributes.slug || filename)
  const title = attributes.title || filename.replace(/[-_]+/g, ' ') || 'Untitled post'
  const dateValue = attributes.date || ''
  const timestamp = dateValue ? Date.parse(dateValue) : 0

  return {
    author: attributes.author || '',
    content,
    date: dateValue,
    excerpt: attributes.excerpt || excerptFromMarkdown(content),
    path: blogPostPath(slug),
    slug,
    timestamp: Number.isFinite(timestamp) ? timestamp : 0,
    title,
  }
}

const blogPosts = Object.entries(blogPostFiles)
  .map(([path, raw]) => createBlogPost(path, raw))
  .filter((post) => post.slug && post.title)
  .sort((a, b) => b.timestamp - a.timestamp || a.title.localeCompare(b.title))

const blogPostsBySlug = new Map(blogPosts.map((post) => [post.slug, post]))

function formatBlogDate(dateValue) {
  if (!dateValue) return 'Undated'
  const timestamp = Date.parse(dateValue)
  if (!Number.isFinite(timestamp)) return dateValue
  return new Intl.DateTimeFormat('en-GB', { dateStyle: 'long' }).format(new Date(timestamp))
}

function gameParticipants(game) {
  const winner = displayTeamName(game?.winning_team)
  const loser = displayTeamName(game?.losing_team)

  if (winner || loser) {
    return [
      winner ? { name: winner, result: 'win' } : null,
      loser ? { name: loser, result: 'loss' } : null,
    ].filter(Boolean)
  }

  const fallbackName = game?.team_name || ''
  if (!fallbackName) return []

  return [
    {
      name: fallbackName,
      result: String(game?.result || '').toLowerCase() === 'win' ? 'win' : 'loss',
    },
  ]
}

function displayTeamName(value) {
  return String(value || '').trim()
}

function ParticipantNames({ participants, spread = false }) {
  if (!participants.length) {
    return <p className="truncate text-sm font-semibold text-text-soft">Participants unknown</p>
  }

  // On wide rows (battle logs), spread the two teams to opposite ends with a
  // centered "vs" so each side gets an equal share of the available width.
  if (spread && participants.length === 2) {
    const [first, second] = participants
    return (
      <div className="mx-auto grid w-full max-w-xl min-w-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-x-4">
        <span
          className={`min-w-0 truncate text-right text-sm font-semibold ${first.result === 'win' ? 'text-win' : 'text-loss'}`}
        >
          {first.name}
        </span>
        <span className="shrink-0 text-xs font-semibold uppercase tracking-wide text-text-muted">
          vs
        </span>
        <span
          className={`min-w-0 truncate text-left text-sm font-semibold ${second.result === 'win' ? 'text-win' : 'text-loss'}`}
        >
          {second.name}
        </span>
      </div>
    )
  }

  return (
    <div className={`flex min-w-0 flex-wrap gap-x-3 gap-y-1${spread ? ' justify-between' : ''}`}>
      {participants.map((participant) => (
        <span
          className={`truncate text-sm font-semibold ${participant.result === 'win' ? 'text-win' : 'text-loss'}`}
          key={`${participant.result}-${participant.name}`}
        >
          {participant.name}
        </span>
      ))}
    </div>
  )
}

function bestTeamName(team) {
  return team?.name || ''
}

function searchKey(value) {
  return String(value || '').trim().toLocaleLowerCase()
}

function teamSearchResult(team) {
  const name = bestTeamName(team)
  return {
    kind: 'team',
    name,
    value: name,
    detail: 'Team',
    aliases: [team?.name].filter(Boolean),
  }
}

function playerSearchResult(player) {
  const uid = String(player?.uid || '').trim()
  const nick = String(player?.nick || '').trim()
  return {
    kind: 'player',
    name: nick || (uid ? `Player ${uid}` : ''),
    value: nick || uid,
    detail: uid ? `Player ${uid}` : 'Player',
    uid,
    aliases: [nick, uid].filter(Boolean),
  }
}

function dedupeSearchResults(results) {
  const seen = new Set()
  return results.filter((result) => {
    const key = result.kind === 'player' && result.uid
      ? `player:${result.uid}`
      : `${result.kind}:${searchKey(result.name)}`
    if (!result.name || seen.has(key)) return false
    seen.add(key)
    return true
  })
}

function teamDetailLooksReal(detail) {
  if (!detail || typeof detail !== 'object') return false

  const summary = detail.team_summary || detail.squadron_summary || null
  const players = Array.isArray(detail.players) ? detail.players : []
  const hasStableId = Boolean(detail.clan_id || detail.id || detail.team_id || detail.squadron_id)
  const hasRoster = players.length > 0
  const hasActivity = Boolean(
    Number(summary?.player_count || 0) > 0 ||
    Number(summary?.total_battles || 0) > 0 ||
    Number(summary?.wins || 0) > 0 ||
    Number(summary?.points?.total_points || summary?.total_points || 0) > 0,
  )

  return hasStableId || hasRoster || hasActivity
}

function canonicalTeamName(detail, fallback = '') {
  return detail?.name || fallback
}

function normalizeAnalyticsPreferences(value) {
  if (!value || typeof value !== 'object') return { ...defaultAnalyticsPreferences }

  const analytics = Boolean(value.analytics)

  return {
    chosen: Boolean(value.chosen) && value.version === analyticsConsentVersion,
    analytics,
    device: analytics && Boolean(value.device),
    display: analytics && Boolean(value.display),
    locale: analytics && Boolean(value.locale),
    referrer: analytics && Boolean(value.referrer),
    diagnostics: analytics && Boolean(value.diagnostics),
    version: analyticsConsentVersion,
  }
}

function browserPrivacySignalEnabled() {
  return Boolean(
    navigator.globalPrivacyControl ||
    navigator.doNotTrack === '1' ||
    window.doNotTrack === '1',
  )
}

function browserConnectionDetails() {
  const connection =
    navigator.connection || navigator.mozConnection || navigator.webkitConnection || null

  if (!connection) return {}

  return {
    effective_type: connection.effectiveType || '',
    downlink_mbps: Number.isFinite(connection.downlink) ? connection.downlink : null,
    rtt_ms: Number.isFinite(connection.rtt) ? connection.rtt : null,
    save_data: Boolean(connection.saveData),
  }
}

function browserDiagnosticsMetadata() {
  return {
    cookies_enabled: navigator.cookieEnabled,
    do_not_track: navigator.doNotTrack || window.doNotTrack || '',
    global_privacy_control: Boolean(navigator.globalPrivacyControl),
    online: navigator.onLine,
    platform: navigator.platform || '',
    vendor: navigator.vendor || '',
    max_touch_points: navigator.maxTouchPoints || 0,
    hardware_concurrency: navigator.hardwareConcurrency || null,
    device_memory_gb: navigator.deviceMemory || null,
    webdriver: Boolean(navigator.webdriver),
    history_length: window.history.length,
    visibility_state: document.visibilityState,
    connection: browserConnectionDetails(),
  }
}

function readCookie(name) {
  try {
    const match = document.cookie
      .split('; ')
      .find((item) => item.startsWith(`${encodeURIComponent(name)}=`))
    return match ? decodeURIComponent(match.split('=').slice(1).join('=')) : ''
  } catch {
    return ''
  }
}

function writeCookie(name, value) {
  try {
    const sixMonths = 60 * 60 * 24 * 180
    document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; Max-Age=${sixMonths}; Path=/; SameSite=Lax`
  } catch {
    // Some browsers block cookies; local storage and in-memory state still work.
  }
}

function normalizeThemePreference(value) {
  return value === 'light' ? 'light' : 'dark'
}

function storedThemePreference() {
  const bootTheme = window.__TSS_BOOT_PREFERENCES__?.theme
  if (bootTheme) return normalizeThemePreference(bootTheme)

  const cookieValue = readCookie(themePreferenceCookie)
  if (cookieValue) return normalizeThemePreference(cookieValue)

  try {
    return normalizeThemePreference(window.localStorage.getItem(themePreferenceKey))
  } catch {
    return 'dark'
  }
}

function persistThemePreference(theme) {
  const normalized = normalizeThemePreference(theme)
  writeCookie(themePreferenceCookie, normalized)

  try {
    window.localStorage.setItem(themePreferenceKey, normalized)
  } catch {
    // Cookies still remember the theme when local storage is blocked.
  }

  return normalized
}

const customColorCookie = 'tssbot_custom_color'
const customColorKey = 'tssbot.customColor'

function hexToRgb(hex) {
  try {
    const cleanHex = String(hex || '').trim().replace('#', '')
    const bigint = parseInt(cleanHex, 16)
    const r = (bigint >> 16) & 255
    const g = (bigint >> 8) & 255
    const b = bigint & 255
    return { r, g, b }
  } catch {
    return { r: 232, g: 37, b: 23 }
  }
}

function adjustColorBrightness(hex, percent) {
  try {
    const { r, g, b } = hexToRgb(hex)
    const adjust = (val) => percent > 0
      ? Math.max(0, Math.min(255, Math.round(val + (255 - val) * percent)))
      : Math.max(0, Math.min(255, Math.round(val * (1 + percent))))
    const nr = adjust(r)
    const ng = adjust(g)
    const nb = adjust(b)
    return '#' + ((1 << 24) + (nr << 16) + (ng << 8) + nb).toString(16).slice(1)
  } catch {
    return hex
  }
}

function storedCustomColorPreference() {
  const cookieValue = readCookie(customColorCookie)
  if (cookieValue && /^#[0-9a-fA-F]{6}$/.test(cookieValue)) return cookieValue

  try {
    const local = window.localStorage.getItem(customColorKey)
    if (local && /^#[0-9a-fA-F]{6}$/.test(local)) return local
  } catch { }

  return ''
}

function persistCustomColorPreference(color) {
  if (color && /^#[0-9a-fA-F]{6}$/.test(color)) {
    writeCookie(customColorCookie, color)
    try {
      window.localStorage.setItem(customColorKey, color)
    } catch { }
    return color
  } else {
    writeCookie(customColorCookie, '')
    try {
      window.localStorage.removeItem(customColorKey)
    } catch { }
    return ''
  }
}

const customColorsCookie = 'tssbot_custom_colors'
const customColorsKey = 'tssbot.customColors'

const customColorsFields = ['bg', 'surface', 'text', 'border', 'textSoft', 'textMuted']

function storedCustomColors() {
  const parse = (raw) => {
    try {
      const obj = JSON.parse(raw)
      if (obj && typeof obj === 'object') {
        const result = {}
        for (const field of customColorsFields) {
          if (obj[field] && /^#[0-9a-fA-F]{6}$/.test(obj[field])) result[field] = obj[field]
        }
        return result
      }
    } catch { }
    return {}
  }

  const cookieValue = readCookie(customColorsCookie)
  if (cookieValue) {
    const parsed = parse(cookieValue)
    if (Object.keys(parsed).length) return parsed
  }

  try {
    const local = window.localStorage.getItem(customColorsKey)
    if (local) return parse(local)
  } catch { }

  return {}
}

function persistCustomColors(colors) {
  const filtered = {}
  for (const field of customColorsFields) {
    if (colors[field] && /^#[0-9a-fA-F]{6}$/.test(colors[field])) filtered[field] = colors[field]
  }
  const json = JSON.stringify(filtered)
  writeCookie(customColorsCookie, json)
  try { window.localStorage.setItem(customColorsKey, json) } catch { }
  return filtered
}

function storedAnalyticsPreferences() {
  const bootPreferences = window.__TSS_BOOT_PREFERENCES__?.analyticsPreferences
  if (bootPreferences) return normalizeAnalyticsPreferences(bootPreferences)

  const cookieValue = readCookie(analyticsPreferencesCookie)
  if (cookieValue) {
    try {
      return normalizeAnalyticsPreferences(JSON.parse(cookieValue))
    } catch {
      // Fall back through the older storage formats below.
    }
  }

  try {
    const stored = window.localStorage.getItem(analyticsPreferencesKey)
    if (stored) return normalizeAnalyticsPreferences(JSON.parse(stored))
  } catch {
    // Fall back through the older consent flag below.
  }

  try {
    const legacyConsent = window.localStorage.getItem(analyticsConsentKey) || ''
    if (legacyConsent === 'analytics') {
      return {
        ...defaultAnalyticsPreferences,
        chosen: true,
        analytics: true,
        device: true,
        display: true,
        locale: true,
        referrer: true,
        diagnostics: true,
      }
    }
    if (legacyConsent === 'declined') {
      return { ...defaultAnalyticsPreferences, chosen: true, analytics: false }
    }
  } catch {
    // Use the default prompt state.
  }

  return { ...defaultAnalyticsPreferences }
}

function persistAnalyticsPreferences(preferences) {
  const normalized = normalizeAnalyticsPreferences(preferences)
  const serialized = JSON.stringify(normalized)

  writeCookie(analyticsPreferencesCookie, serialized)

  try {
    window.localStorage.setItem(analyticsPreferencesKey, serialized)
    window.localStorage.setItem(
      analyticsConsentKey,
      normalized.analytics ? 'analytics' : 'declined',
    )
  } catch {
    // Local storage can be blocked; the cookie and in-memory choice still apply.
  }

  return normalized
}

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)}`
  }
}

function storedVisitorId(storageKey) {
  try {
    return window.localStorage.getItem(storageKey) || ''
  } catch {
    return ''
  }
}

function forgetVisitorId(storageKey) {
  try {
    window.localStorage.removeItem(storageKey)
  } catch {
    // Storage may be unavailable; nothing else to clear locally.
  }
}

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 === 'players') return 'Player Leaderboard'
  if (route.page === 'blog') return 'Blog'
  if (route.page === 'blog-post') return blogPostsBySlug.get(route.slug)?.title || 'Blog post'
  if (route.page === 'battle-logs') return 'Battle Logs'
  if (route.page === 'game') return route.gameId ? `Game ${route.gameId}` : 'Game'
  if (route.page === 'tournaments-list') return 'Tournaments'
  if (route.page === 'tournament') return route.tournamentId ? `Tournament ${route.tournamentId}` : 'Tournament'
  if (route.page === 'uptime') return 'Uptime'
  if (route.page === 'viewers') return 'viewers'
  if (route.page === 'privacy') return 'Privacy notice'
  if (route.page === 'docs') return 'Docs'
  if (route.page === 'settings') return 'Settings'
  if (route.page === 'player') return route.uid ? `Player ${route.uid}` : 'Player'
  return 'Home'
}

function currentPublicOrigin() {
  return window.location.origin.replace(/\/$/, '')
}

function canonicalPathForRoute(route) {
  if (route.page === 'team' && route.teamName) return teamPath(route.teamName)
  if (route.page === 'teams') return '/teams'
  if (route.page === 'players') return '/players'
  if (route.page === 'blog') return '/blog'
  if (route.page === 'blog-post' && route.slug) return blogPostPath(route.slug)
  if (route.page === 'battle-logs') return '/battle-logs'
  if (route.page === 'game' && route.gameId) return gamePath(route.gameId)
  if (route.page === 'tournaments-list') return '/tournaments'
  if (route.page === 'tournament' && route.tournamentId) return tournamentPath(route.tournamentId)
  if (route.page === 'uptime') return '/uptime'
  if (route.page === 'viewers') return '/viewers'
  if (route.page === 'privacy') return '/privacy'
  if (route.page === 'docs') return '/docs'
  if (route.page === 'settings') return '/settings'
  if (route.page === 'player' && route.uid) return `/players/${encodeURIComponent(route.uid)}`
  return '/'
}

function seoForRoute(route, profileDetail = null) {
  const teamName =
    route.page === 'team'
      ? canonicalTeamName(profileDetail, route.teamName).trim() || route.teamName
      : ''
  const teamSummary = profileDetail?.team_summary || profileDetail?.squadron_summary || null
  const teamPoints = Number(teamSummary?.points?.total_points || teamSummary?.total_points || 0)
  const teamBattles = Number(teamSummary?.total_battles || 0)

  if (route.page === 'team' && teamName) {
    const stats = [
      teamPoints ? `${formatNumber(teamPoints)} points` : '',
      teamBattles ? `${formatNumber(teamBattles)} battles` : '',
    ].filter(Boolean)
    return {
      title: `${teamName} TSS Team Profile | Toothless' TSS Bot`,
      description: stats.length
        ? `${teamName} TSS team profile with ${stats.join(', ')}, roster details, battle history, and recent War Thunder squadron activity.`
        : `${teamName} TSS team profile with roster details, battle history, and recent War Thunder squadron activity.`,
      robots: 'index, follow',
      path: canonicalPathForRoute({ ...route, teamName }),
    }
  }

  if (route.page === 'player' && route.uid) {
    return {
      title: `Player ${route.uid} | Toothless' TSS Bot`,
      description: `TSS career stats for player ${route.uid} — battles, win rate, kills, and teams seen with.`,
      robots: 'noindex, follow',
      path: canonicalPathForRoute(route),
    }
  }

  if (route.page === 'game' && route.gameId) {
    return {
      title: `Game ${route.gameId} | Toothless' TSS Bot`,
      description: `TSS battle log details for game ${route.gameId}, including participants, map, player counts, and combat stats.`,
      robots: 'noindex, follow',
      path: canonicalPathForRoute(route),
    }
  }

  if (route.page === 'tournament' && route.tournamentId) {
    return {
      title: `Tournament ${route.tournamentId} | Toothless' TSS Bot`,
      description: `TSS tournament ${route.tournamentId} bracket, matches, standings, and games tracked by Toothless' TSS Bot.`,
      robots: 'index, follow',
      path: canonicalPathForRoute(route),
    }
  }

  const byPage = {
    teams: {
      title: "TSS Team Leaderboard | Toothless' TSS Bot",
      description: 'Browse the live TSS team leaderboard, compare War Thunder squadron rankings, points, players, and recent team activity.',
      robots: 'index, follow',
      path: '/teams',
    },
    players: {
      title: "TSS Player Leaderboard | Toothless' TSS Bot",
      description: 'Browse the TSS player leaderboard, compare War Thunder player score, kills, win rate, KDR, and battle activity.',
      robots: 'index, follow',
      path: '/players',
    },
    'battle-logs': {
      title: "TSS Battle Logs | Toothless' TSS Bot",
      description: 'Read recent TSS battle logs with team names, map history, player counts, battle times, and War Thunder match context.',
      robots: 'index, follow',
      path: '/battle-logs',
    },
    'tournaments-list': {
      title: "TSS Tournaments | Toothless' TSS Bot",
      description: 'Browse tracked TSS tournaments with authoritative brackets, standings, and linked replay availability.',
      robots: 'index, follow',
      path: '/tournaments',
    },
    blog: {
      title: "TSS Bot Blog | Toothless' TSS Bot",
      description: 'News, updates, and announcements from Toothless TSS Bot.',
      robots: 'index, follow',
      path: '/blog',
    },
    'blog-post': {
      title: `${blogPostsBySlug.get(route.slug)?.title || 'Blog post'} | Toothless' TSS Bot`,
      description: blogPostsBySlug.get(route.slug)?.excerpt || 'News and updates from Toothless TSS Bot.',
      robots: blogPostsBySlug.has(route.slug) ? 'index, follow' : 'noindex, follow',
      path: route.slug ? blogPostPath(route.slug) : '/blog',
    },
    uptime: {
      title: "TSS Bot Uptime Status | Toothless' TSS Bot",
      description: 'Check Toothless TSS Bot uptime, API health, TSS data proxy status, and recent availability history.',
      robots: 'index, follow',
      path: '/uptime',
    },
    viewers: {
      title: "Viewer Analytics | Toothless' TSS Bot",
      description: 'Opt-in viewer analytics for Toothless TSS Bot, including active pages, broad device signals, and privacy-safe activity trends.',
      robots: 'noindex, follow',
      path: '/viewers',
    },
    privacy: {
      title: "Privacy Notice | Toothless' TSS Bot",
      description: 'How Toothless TSS Bot handles cookies, opt-in analytics, viewer data, retention, deletion, and privacy rights.',
      robots: 'index, follow',
      path: '/privacy',
    },
    docs: {
      title: "Docs | Toothless' TSS Bot",
      description: 'Documentation and command reference for Toothless TSS Bot.',
      robots: 'noindex, follow',
      path: '/docs',
    },
    settings: {
      title: "Settings | Toothless' TSS Bot",
      description: 'Customize layout and appearance preferences for Toothless TSS Bot, including custom theme accent colors.',
      robots: 'noindex, nofollow',
      path: '/settings',
    },
  }

  return byPage[route.page] || {
    title: "Toothless' TSS Bot | Live TSS Leaderboards and Battle Logs",
    description: 'Live War Thunder TSS team leaderboards, battle logs, team profiles, uptime, and privacy-safe viewer analytics from Toothless TSS Bot.',
    robots: 'index, follow',
    path: '/',
  }
}

function upsertMeta(selector, createAttributes, valueAttribute, value) {
  let element = document.head.querySelector(selector)
  if (!element) {
    element = document.createElement('meta')
    Object.entries(createAttributes).forEach(([key, attributeValue]) => {
      element.setAttribute(key, attributeValue)
    })
    document.head.appendChild(element)
  }
  element.setAttribute(valueAttribute, value)
}

function upsertLink(rel, href) {
  let element = document.head.querySelector(`link[rel="${rel}"]`)
  if (!element) {
    element = document.createElement('link')
    element.setAttribute('rel', rel)
    document.head.appendChild(element)
  }
  element.setAttribute('href', href)
}

function structuredDataForSeo(seo, canonicalUrl) {
  const base = currentPublicOrigin()
  const items = [
    {
      '@context': 'https://schema.org',
      '@type': 'WebSite',
      name: "Toothless' TSS Bot",
      url: `${base}/`,
      description: "Live War Thunder TSS leaderboards, battle logs, and team profiles.",
      potentialAction: {
        '@type': 'SearchAction',
        target: `${base}/teams/{search_term_string}`,
        'query-input': 'required name=search_term_string',
      },
    },
    {
      '@context': 'https://schema.org',
      '@type': 'WebPage',
      name: seo.title,
      url: canonicalUrl,
      description: seo.description,
      isPartOf: {
        '@type': 'WebSite',
        name: "Toothless' TSS Bot",
        url: `${base}/`,
      },
    },
  ]

  return JSON.stringify(items)
}

function applySeo(route, profileDetail = null) {
  const seo = seoForRoute(route, profileDetail)
  const canonicalUrl = `${currentPublicOrigin()}${seo.path}`

  document.title = seo.title
  upsertMeta('meta[name="description"]', { name: 'description' }, 'content', seo.description)
  upsertMeta('meta[name="robots"]', { name: 'robots' }, 'content', seo.robots)
  upsertMeta('meta[property="og:title"]', { property: 'og:title' }, 'content', seo.title)
  upsertMeta('meta[property="og:description"]', { property: 'og:description' }, 'content', seo.description)
  upsertMeta('meta[property="og:url"]', { property: 'og:url' }, 'content', canonicalUrl)
  upsertMeta('meta[name="twitter:title"]', { name: 'twitter:title' }, 'content', seo.title)
  upsertMeta('meta[name="twitter:description"]', { name: 'twitter:description' }, 'content', seo.description)
  upsertLink('canonical', canonicalUrl)

  let structuredData = document.getElementById('site-structured-data')
  if (!structuredData) {
    structuredData = document.createElement('script')
    structuredData.id = 'site-structured-data'
    structuredData.type = 'application/ld+json'
    document.head.appendChild(structuredData)
  }
  structuredData.textContent = structuredDataForSeo(seo, canonicalUrl)
}

async function fetchRecentTssGames(signal) {
  return fetchPublicJson(publicDataSources.recentGames, signal)
}

function Stat({ label, value }) {
  return (
    <div className="border-l border-border pl-4">
      <p className="text-xs font-semibold uppercase tracking-wide text-text-soft">
        {label}
      </p>
      <p className="mt-1 text-2xl font-semibold text-text">{value}</p>
    </div>
  )
}

function ThemeToggle({ theme, onThemeChange }) {
  const isDark = theme === 'dark'

  return (
    <button
      aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
      aria-pressed={isDark}
      className="grid h-9 w-9 shrink-0 place-items-center rounded-full border border-border bg-surface text-sm font-semibold text-text-soft transition hover:border-ring hover:text-text"
      onClick={() => onThemeChange(isDark ? 'light' : 'dark')}
      title={isDark ? 'Light mode' : 'Dark mode'}
      type="button"
    >
      <span aria-hidden="true">{isDark ? '☾' : '☼'}</span>
    </button>
  )
}

function defaultThemeTogglePosition() {
  const inset = window.innerWidth >= 640 ? 32 : 20
  return {
    x: window.innerWidth - inset - 36,
    y: inset,
  }
}

function themeToggleDockPosition(navPill) {
  if (!navPill) return defaultThemeTogglePosition()

  const width = navPill.offsetWidth
  const height = navPill.offsetHeight
  const left = (window.innerWidth - width) / 2
  const top = 16

  return {
    x: Math.round(left + width - 42),
    y: Math.round(top + (height - 36) / 2),
  }
}

function ThemeToggleMover({ dockPosition, homePosition, isHome, theme, onThemeChange }) {
  const moverRef = useRef(null)

  useEffect(() => {
    const mover = moverRef.current
    if (!mover) return undefined
    let cancelled = false
    let tween = null

    async function animate() {
      const [{ gsap }, { ScrollTrigger }] = await Promise.all([
        import('gsap'),
        import('gsap/ScrollTrigger'),
      ])
      if (cancelled) return

      gsap.registerPlugin(ScrollTrigger)

      if (!isHome) {
        tween = gsap.to(mover, {
          x: dockPosition.x,
          y: dockPosition.y,
          duration: 0.56,
          ease: 'power3.out',
          overwrite: true,
        })
        return
      }

      tween = gsap.fromTo(
        mover,
        { x: homePosition.x, y: homePosition.y },
        {
          x: dockPosition.x,
          y: dockPosition.y,
          ease: 'none',
          overwrite: true,
          scrollTrigger: {
            end: '+=96',
            scrub: 0.35,
            start: 'top top',
            trigger: document.documentElement,
          },
        },
      )
    }

    animate()

    return () => {
      cancelled = true
      tween?.scrollTrigger?.kill()
      tween?.kill()
    }
  }, [dockPosition.x, dockPosition.y, homePosition.x, homePosition.y, isHome])

  const initialPosition = isHome ? homePosition : dockPosition

  return (
    <div
      className="theme-toggle-mover fixed left-0 top-0 z-[60]"
      ref={moverRef}
      style={{ transform: `translate3d(${initialPosition.x}px, ${initialPosition.y}px, 0)` }}
    >
      <ThemeToggle theme={theme} onThemeChange={onThemeChange} />
    </div>
  )
}

function TurnstileWidget({ siteKey, theme = 'auto', size = 'normal', action, onVerify, onExpire, onError, resetSignal = 0 }) {
  const containerRef = useRef(null)
  const widgetIdRef = useRef(null)
  const callbacksRef = useRef({ onVerify, onExpire, onError })

  useEffect(() => {
    callbacksRef.current = { onVerify, onExpire, onError }
  }, [onVerify, onExpire, onError])

  useEffect(() => {
    if (!siteKey) return undefined
    const container = containerRef.current
    if (!container) return undefined

    let cancelled = false
    let pollTimer
    let script = document.querySelector('script[data-tss-turnstile]')

    if (!script) {
      script = document.createElement('script')
      script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js'
      script.async = true
      script.defer = true
      script.dataset.tssTurnstile = 'true'
      document.head.appendChild(script)
    }

    const renderWidget = () => {
      if (cancelled) return
      if (!window.turnstile || typeof window.turnstile.render !== 'function') {
        pollTimer = window.setTimeout(renderWidget, 150)
        return
      }
      const options = {
        sitekey: siteKey,
        theme,
        size,
        callback: (token) => callbacksRef.current.onVerify?.(token),
        'expired-callback': () => callbacksRef.current.onExpire?.(),
        'error-callback': () => callbacksRef.current.onError?.(),
      }
      if (action) options.action = action
      widgetIdRef.current = window.turnstile.render(container, options)
    }
    renderWidget()

    return () => {
      cancelled = true
      window.clearTimeout(pollTimer)
      const widgetId = widgetIdRef.current
      widgetIdRef.current = null
      if (widgetId != null && window.turnstile?.remove) {
        try { window.turnstile.remove(widgetId) } catch { /* widget already gone */ }
      }
    }
  }, [siteKey, theme, size, action])

  useEffect(() => {
    if (!resetSignal) return
    const widgetId = widgetIdRef.current
    if (widgetId != null && window.turnstile?.reset) {
      try { window.turnstile.reset(widgetId) } catch { /* widget gone or already reset */ }
    }
  }, [resetSignal])

  if (!siteKey) return null

  return <div ref={containerRef} />
}

function SiteGate({ onVerified }) {
  const [status, setStatus] = useState('idle')
  const [error, setError] = useState('')
  const [resetSignal, setResetSignal] = useState(0)
  const submittingRef = useRef(false)

  useEffect(() => {
    const previousOverflow = document.body.style.overflow
    document.body.style.overflow = 'hidden'
    return () => {
      document.body.style.overflow = previousOverflow
    }
  }, [])

  async function handleVerify(token) {
    if (submittingRef.current) return
    submittingRef.current = true
    setStatus('verifying')
    setError('')
    try {
      const response = await fetch('/api/turnstile/verify', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ token }),
      })
      if (!response.ok) {
        setError('Verification failed — please try again.')
        setStatus('idle')
        setResetSignal((value) => value + 1)
        return
      }
      setStatus('verified')
      onVerified()
    } catch {
      setError('Network error — please retry.')
      setStatus('idle')
      setResetSignal((value) => value + 1)
    } finally {
      submittingRef.current = false
    }
  }

  return (
    <div
      aria-labelledby="site-gate-title"
      aria-modal="true"
      className="fixed inset-0 z-[9999] grid place-items-center bg-bg/95 px-4 py-6 backdrop-blur-sm"
      role="dialog"
    >
      <div className="flex w-full max-w-sm flex-col items-center gap-4 rounded-md border border-border bg-fury-white p-6 text-center text-text shadow-[0_24px_70px_rgba(0,0,0,0.24)]">
        <h1 id="site-gate-title" className="text-lg font-semibold">
          Verifying you are human
        </h1>
        <p className="text-sm text-text-soft">
          This quick check protects the site from automated abuse. It usually clears itself.
        </p>
        <TurnstileWidget
          siteKey={turnstileSiteKey}
          action="site-gate"
          onVerify={handleVerify}
          onExpire={() => setError('Challenge expired — please solve it again.')}
          onError={() => setError('Challenge could not load. Refresh to retry.')}
          resetSignal={resetSignal}
        />
        {status === 'verifying' ? (
          <p className="text-xs text-text-soft">Confirming with Cloudflare…</p>
        ) : null}
        {error ? <p className="text-xs font-semibold text-red-600">{error}</p> : null}
      </div>
    </div>
  )
}

function App() {
  if (!siteGateEnabled) return <AppContent />
  return <GatedAppContent />
}

function GatedAppContent() {
  const embeddedGateState = document
    .querySelector('meta[name="tss-turnstile-session"]')
    ?.getAttribute('content')
  const initialGateState = turnstileSiteKey
    ? ['verified', 'required'].includes(embeddedGateState)
      ? embeddedGateState
      : 'checking'
    : 'verified'
  const [gateState, setGateState] = useState(initialGateState)

  useEffect(() => {
    if (!turnstileSiteKey || gateState !== 'checking') return undefined
    let cancelled = false
    fetch('/api/turnstile/session', { headers: { Accept: 'application/json' } })
      .then((response) => (response.ok ? response.json() : { verified: false }))
      .then((data) => {
        if (cancelled) return
        setGateState(data?.verified ? 'verified' : 'required')
      })
      .catch(() => {
        if (!cancelled) setGateState('required')
      })
    return () => {
      cancelled = true
    }
  }, [gateState])

  if (gateState === 'checking') {
    return <div className="fixed inset-0 bg-bg" aria-hidden="true" />
  }

  if (gateState === 'required') {
    return <SiteGate onVerified={() => setGateState('verified')} />
  }

  return <AppContent />
}

function AppContent() {
  const [route, setRoute] = useState(() => parseRoute())
  const [leaderboard, setLeaderboard] = useState({ status: 'idle', data: null, error: null })
  const [playerLeaderboard, setPlayerLeaderboard] = useState({ status: 'idle', data: null, error: null })
  const [homeTeams, setHomeTeams] = useState({ status: 'idle', data: null, error: null })
  const [live, setLive] = useState({ status: 'idle', data: null, error: null, updatedAt: 0 })
  const [uptime, setUptime] = useState({ status: 'idle', checks: [], history: [], updatedAt: null })
  const [viewers, setViewers] = useState({ status: 'idle', data: null, error: null, updatedAt: null })
  const [analyticsPreferences, setAnalyticsPreferences] = useState(() => storedAnalyticsPreferences())
  const [theme, setTheme] = useState(() => storedThemePreference())
  const [customColor, setCustomColor] = useState(() => storedCustomColorPreference())
  const [customColors, setCustomColors] = useState(() => storedCustomColors())
  const [showFloatingNav, setShowFloatingNav] = useState(() => window.scrollY > 40)
  const [themeTogglePositions, setThemeTogglePositions] = useState(() => {
    const position = defaultThemeTogglePosition()
    return { dock: position, home: position }
  })
  const [teamQuery, setTeamQuery] = useState('')
  const [searchHint, setSearchHint] = useState({ status: 'idle', name: '' })
  const [teamSearchResults, setTeamSearchResults] = useState([])
  const [profile, setProfile] = useState({
    teamName: '',
    detail: { 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 players = useMemo(
    () => playerLeaderboard.data?.players || [],
    [playerLeaderboard.data],
  )
  const teamsToWatch = useMemo(
    () => homeTeams.data?.teams || homeTeams.data?.squadrons || teams.slice(0, 4),
    [homeTeams.data, teams],
  )
  const matches = live.data?.matches || []
  const liveRef = useRef(live)
  const navPillRef = useRef(null)
  const themeRef = useRef(theme)
  const themeTransitionTimerRef = useRef(null)

  useEffect(() => {
    liveRef.current = live
  }, [live])

  useEffect(() => {
    themeRef.current = theme
    document.documentElement.dataset.theme = theme
    document.documentElement.style.colorScheme = theme

    const metaThemeColor = document.querySelector('meta[name="theme-color"]')

    if (customColor && /^#[0-9a-fA-F]{6}$/.test(customColor)) {
      document.documentElement.style.setProperty('--color-fury-cyan', customColor)
      document.documentElement.style.setProperty('--color-ring', customColor)

      const { r, g, b } = hexToRgb(customColor)
      document.documentElement.style.setProperty('--color-shadow', `rgba(${r}, ${g}, ${b}, ${theme === 'dark' ? 0.18 : 0.12})`)

      const aquaColor = adjustColorBrightness(customColor, 0.2)
      const violetColor = adjustColorBrightness(customColor, -0.2)
      document.documentElement.style.setProperty('--color-fury-aqua', aquaColor)
      document.documentElement.style.setProperty('--color-fury-violet', violetColor)

      metaThemeColor?.setAttribute('content', customColor)
    } else {
      document.documentElement.style.removeProperty('--color-fury-cyan')
      document.documentElement.style.removeProperty('--color-ring')
      document.documentElement.style.removeProperty('--color-shadow')
      document.documentElement.style.removeProperty('--color-fury-aqua')
      document.documentElement.style.removeProperty('--color-fury-violet')

      metaThemeColor?.setAttribute(
        'content',
        theme === 'dark' ? '#101211' : '#e82517',
      )
    }

    const cssVarMap = {
      bg: '--color-bg',
      surface: '--color-surface',
      text: '--color-text',
      border: '--color-border',
      textSoft: '--color-text-soft',
      textMuted: '--color-text-muted',
    }
    for (const [field, cssVar] of Object.entries(cssVarMap)) {
      const val = customColors[field]
      if (val && /^#[0-9a-fA-F]{6}$/.test(val)) {
        document.documentElement.style.setProperty(cssVar, val)
      } else {
        document.documentElement.style.removeProperty(cssVar)
      }
    }
  }, [theme, customColor, customColors])

  useEffect(() => () => {
    window.clearTimeout(themeTransitionTimerRef.current)
    document.documentElement.classList.remove('theme-transition')
  }, [])

  function navigate(path, { replace = false } = {}) {
    if (replace) {
      window.history.replaceState({}, '', path)
    } else {
      window.history.pushState({}, '', path)
    }
    setRoute(parseRoute(path))
  }

  useEffect(() => {
    const onPopState = () => setRoute(parseRoute())
    window.addEventListener('popstate', onPopState)
    return () => window.removeEventListener('popstate', onPopState)
  }, [])

  useEffect(() => {
    const onScroll = () => setShowFloatingNav(window.scrollY > 40)
    onScroll()
    window.addEventListener('scroll', onScroll, { passive: true })
    return () => window.removeEventListener('scroll', onScroll)
  }, [])

  useEffect(() => {
    if (route.page === 'home') return

    const prewarm = () => {
      renderPixelMountainsCanvas()
      prewarmTreeCanvas()
    }
    const requestIdle = window.requestIdleCallback || ((callback) => window.setTimeout(callback, 250))
    const cancelIdle = window.cancelIdleCallback || window.clearTimeout
    const id = requestIdle(prewarm)

    return () => cancelIdle(id)
  }, [route.page])

  useEffect(() => {
    applySeo(
      route,
      route.page === 'team' && profile.detail.status === 'ready'
        ? profile.detail.data
        : null,
    )
  }, [profile.detail.data, profile.detail.status, route])

  useEffect(() => {
    if (!analyticsPreferences.analytics) return
    if (
      route.page === 'team' &&
      (profile.teamName !== route.teamName || profile.detail.status !== 'ready')
    ) {
      return
    }

    const visitorId = stableId(analyticsVisitorKey)
    let stopped = false
    const deviceDetails = analyticsPreferences.device
    const displayDetails = analyticsPreferences.display
    const localeDetails = analyticsPreferences.locale
    const referrerDetails = analyticsPreferences.referrer
    const diagnosticsDetails = analyticsPreferences.diagnostics

    function sendViewerEvent(eventType) {
      if (stopped) return
      const metadata = {
        preferences: {
          device: deviceDetails,
          display: displayDetails,
          locale: localeDetails,
          referrer: referrerDetails,
          diagnostics: diagnosticsDetails,
          theme: themeRef.current,
        },
      }

      if (deviceDetails) {
        metadata.device = {
          user_agent_length: navigator.userAgent.length,
          platform: navigator.platform || '',
          vendor: navigator.vendor || '',
          max_touch_points: navigator.maxTouchPoints || 0,
        }
      }

      if (displayDetails) {
        metadata.display = {
          color_depth: window.screen.colorDepth,
          pixel_depth: window.screen.pixelDepth,
          viewport: `${window.innerWidth}x${window.innerHeight}`,
          device_pixel_ratio: window.devicePixelRatio || 1,
          orientation: window.screen.orientation?.type || '',
        }
      }

      if (localeDetails) {
        metadata.locale = {
          languages: Array.isArray(navigator.languages) ? navigator.languages.slice(0, 8) : [],
          timezone_offset_minutes: new Date().getTimezoneOffset(),
        }
      }

      if (diagnosticsDetails) {
        metadata.diagnostics = browserDiagnosticsMetadata()
      }

      const body = {
        consent: 'analytics',
        event_type: eventType,
        visitor_id: visitorId,
        session_id: analyticsSessionId,
        page_path: window.location.pathname,
        page_title: routeLabel(route),
        referrer: referrerDetails ? document.referrer : '',
        user_agent: deviceDetails ? navigator.userAgent : 'Not shared',
        browser: deviceDetails ? browserName() : 'Not shared',
        os: deviceDetails ? operatingSystem() : 'Not shared',
        device: deviceDetails ? deviceType() : 'Not shared',
        screen: displayDetails ? `${window.screen.width}x${window.screen.height}` : '',
        language: localeDetails ? navigator.language : '',
        timezone: localeDetails ? Intl.DateTimeFormat().resolvedOptions().timeZone : '',
        theme: themeRef.current,
        metadata,
      }

      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)
    }
  }, [analyticsPreferences, profile.detail.status, profile.teamName, 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: '' })
      setTeamSearchResults([])
      return
    }

    const controller = new AbortController()
    const timer = window.setTimeout(() => {
      setSearchHint({ status: 'loading', name: '' })
      Promise.allSettled([
        fetchJson(apiEndpoints.searchTeams(query), controller.signal),
        fetchJson(apiEndpoints.searchPlayers(query), controller.signal),
      ]).then(([teamResult, playerResult]) => {
        if (controller.signal.aborted) return

        const teamResults = teamResult.status === 'fulfilled'
          ? (teamResult.value.teams || teamResult.value.results || []).map(teamSearchResult)
          : []
        const playerResults = playerResult.status === 'fulfilled'
          ? (playerResult.value.players || []).map(playerSearchResult)
          : []
        const results = dedupeSearchResults([...teamResults, ...playerResults]).slice(0, 10)

        setTeamSearchResults(results)
        setSearchHint(results.length ? { status: 'ready', name: results[0].name } : { status: 'error', name: '' })
      }).catch(() => {
        if (!controller.signal.aborted) {
          setTeamSearchResults([])
          setSearchHint({ status: 'error', name: '' })
        }
      })
    }, 350)

    return () => {
      window.clearTimeout(timer)
      controller.abort()
    }
  }, [teamQuery])

  useEffect(() => {
    if (!['teams', 'team', 'battle-logs'].includes(route.page)) return

    const controller = new AbortController()
    setLeaderboard({ status: 'loading', data: null, error: null })

    fetchPublicJson(publicDataSources.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()
  }, [route.page])

  useEffect(() => {
    if (route.page !== 'players') return

    const controller = new AbortController()
    setPlayerLeaderboard({ status: 'loading', data: null, error: null })

    fetchPublicJson(publicDataSources.players, controller.signal)
      .then((data) => setPlayerLeaderboard({ status: 'ready', data, error: null }))
      .catch((error) => {
        if (!controller.signal.aborted) {
          setPlayerLeaderboard({ status: 'error', data: null, error: error.message })
        }
      })

    return () => controller.abort()
  }, [route.page])

  useEffect(() => {
    if (route.page !== 'home') return

    const controller = new AbortController()
    setHomeTeams({ status: 'loading', data: null, error: null })

    fetchPublicJson(publicDataSources.homeTeams, controller.signal)
      .then((data) => setHomeTeams({ status: 'ready', data, error: null }))
      .catch((error) => {
        if (!controller.signal.aborted) {
          setHomeTeams({ status: 'error', data: null, error: error.message })
        }
      })

    return () => controller.abort()
  }, [route.page])

  useEffect(() => {
    if (!['teams', 'team', 'battle-logs'].includes(route.page)) return

    const controller = new AbortController()
    const timer = window.setInterval(() => {
      fetchPublicJson(publicDataSources.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
    const currentLive = liveRef.current
    if (currentLive.status === 'ready' && Date.now() - currentLive.updatedAt < liveRefreshMs) return

    const controller = new AbortController()
    setLive((current) =>
      current.status === 'ready'
        ? current
        : { status: 'loading', data: null, error: null, updatedAt: current.updatedAt || 0 },
    )

    fetchRecentTssGames(controller.signal)
      .then((data) => setLive({ status: 'ready', data, error: null, updatedAt: Date.now() }))
      .catch((error) => {
        if (!controller.signal.aborted) {
          setLive({ status: 'error', data: null, error: error.message, updatedAt: Date.now() })
        }
      })

    return () => controller.abort()
  }, [route.page])

  useEffect(() => {
    if (!['home', 'battle-logs'].includes(route.page)) return

    const controller = new AbortController()
    const timer = window.setInterval(() => {
      fetchRecentTssGames(controller.signal)
        .then((data) => setLive({ status: 'ready', data, error: null, updatedAt: Date.now() }))
        .catch((error) => {
          if (!controller.signal.aborted) {
            setLive((current) => ({ ...current, error: error.message }))
          }
        })
    }, liveRefreshMs)

    return () => {
      window.clearInterval(timer)
      controller.abort()
    }
  }, [route.page])

  useEffect(() => {
    if (route.page !== 'team' || !route.teamName) return

    const controller = new AbortController()
    setProfile({
      teamName: route.teamName,
      detail: { status: 'loading', data: null, error: null },
      games: { status: 'loading', data: null, error: null },
    })

    Promise.allSettled([
      fetchPublicJson(publicDataSources.detail(route.teamName), controller.signal),
      fetchPublicJson(publicDataSources.games(route.teamName), controller.signal),
    ])
      .then(([detailResult, gamesResult]) => {
        if (controller.signal.aborted) return
        if (detailResult.status !== 'fulfilled') {
          navigate('/teams', { replace: true })
          return
        }

        const detail = detailResult.value
        if (!teamDetailLooksReal(detail)) {
          navigate('/teams', { replace: true })
          return
        }

        setProfile({
          teamName: route.teamName,
          detail: { status: 'ready', data: detail, error: null },
          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([
        fetchPublicJson(publicDataSources.detail(route.teamName), controller.signal),
        fetchPublicJson(publicDataSources.games(route.teamName), controller.signal),
      ]).then(([detailResult, gamesResult]) => {
        if (controller.signal.aborted) return

        setProfile((current) => ({
          teamName: route.teamName,
          detail:
            detailResult.status === 'fulfilled'
              ? { status: 'ready', data: detailResult.value, error: null }
              : current.detail,
          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 localTeamSuggestions = useMemo(() => {
    const query = searchKey(teamQuery)

    return dedupeSearchResults(teams.map(teamSearchResult))
      .filter(({ name, aliases }) => {
        if (!query) return true
        return aliases.some((alias) => searchKey(alias).includes(query)) || searchKey(name).includes(query)
      })
      .slice(0, 10)
  }, [teamQuery, teams])
  const teamSuggestions = teamSearchResults.length ? teamSearchResults : localTeamSuggestions
  const selectedSearchResult = teamSuggestions.find((result) => {
    const query = searchKey(teamQuery)
    return searchKey(result.value) === query || result.aliases.some((alias) => searchKey(alias) === query)
  })
  const searchPlaceholder =
    searchHint.status === 'ready'
      ? `Found ${searchHint.name}`
      : searchHint.status === 'error'
        ? 'No team or player found'
        : topTeamName ? `${topTeamName}, or search players` : 'Search teams or players'

  async function handleTeamSearch(event) {
    event.preventDefault()
    const name = teamQuery.trim()
    if (!name) return

    try {
      if (selectedSearchResult?.kind === 'player' && selectedSearchResult.uid) {
        navigate(playerPath(selectedSearchResult.uid))
        setTeamQuery('')
        return
      }

      const resolved = await fetchJson(apiEndpoints.resolve(name))
      const resolvedName = resolved.name
      if (!resolvedName) throw new Error('Team not found')

      const detail = await fetchPublicJson(publicDataSources.detail(resolvedName))
      if (!teamDetailLooksReal(detail)) throw new Error('Team not found')

      navigate(teamPath(canonicalTeamName(detail, resolvedName)))
      setTeamQuery('')
    } catch (teamError) {
      try {
        const players = await fetchJson(apiEndpoints.searchPlayers(name))
        const player = (players.players || []).find((candidate) => (
          searchKey(candidate.nick) === searchKey(name) || searchKey(candidate.uid) === searchKey(name)
        )) || players.players?.[0]

        if (!player?.uid) throw teamError
        navigate(playerPath(player.uid))
        setTeamQuery('')
      } catch {
        setSearchHint({ status: 'error', name: '' })
      }
    }
  }

  function chooseAnalyticsConsent(preferences) {
    const previousVisitorId = storedVisitorId(analyticsVisitorKey)
    const nextPreferences = persistAnalyticsPreferences({ ...preferences, chosen: true })

    if (!nextPreferences.analytics) {
      forgetVisitorId(analyticsVisitorKey)
      if (previousVisitorId) {
        fetch(apiEndpoints.viewerDelete, {
          method: 'POST',
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify({
            visitor_id: previousVisitorId,
            session_id: analyticsSessionId,
          }),
          keepalive: true,
        }).catch(() => { })
      }
    }

    setAnalyticsPreferences(nextPreferences)
  }

  function chooseTheme(nextTheme) {
    if (nextTheme === themeRef.current) return
    window.clearTimeout(themeTransitionTimerRef.current)
    document.documentElement.classList.add('theme-transition')
    themeTransitionTimerRef.current = window.setTimeout(() => {
      document.documentElement.classList.remove('theme-transition')
    }, 460)
    setTheme(persistThemePreference(nextTheme))
  }

  function chooseCustomColor(nextColor) {
    if (!nextColor) {
      setCustomColor('')
      persistCustomColorPreference('')
      return
    }

    setCustomColor(nextColor)

    if (/^#[0-9a-fA-F]{6}$/.test(nextColor)) {
      persistCustomColorPreference(nextColor)
    }
  }

  function chooseCustomColors(nextColors) {
    const saved = persistCustomColors(nextColors)
    setCustomColors(saved)
  }

  const activeNavPath =
    route.page === 'team'
      ? '/teams'
      : route.page === 'player' || route.page === 'players'
        ? '/players'
        : route.page === 'battle-logs' || route.page === 'game'
          ? '/battle-logs'
          : route.page === 'tournaments-list' || route.page === 'tournament'
            ? '/tournaments'
            : route.page === 'blog' || route.page === 'blog-post'
              ? '/blog'
              : route.page === 'viewers'
                ? '/viewers'
                : route.page === 'settings'
                  ? '/settings'
                  : window.location.pathname
  const shouldShowFloatingNav = route.page !== 'home' || showFloatingNav

  useEffect(() => {
    let frame = 0

    function updateThemeTogglePositions() {
      window.cancelAnimationFrame(frame)
      frame = window.requestAnimationFrame(() => {
        setThemeTogglePositions({
          dock: themeToggleDockPosition(navPillRef.current),
          home: defaultThemeTogglePosition(),
        })
      })
    }

    updateThemeTogglePositions()
    window.addEventListener('resize', updateThemeTogglePositions)
    return () => {
      window.cancelAnimationFrame(frame)
      window.removeEventListener('resize', updateThemeTogglePositions)
    }
  }, [route.page])

  useEffect(() => {
    if (route.page !== 'players') return

    const controller = new AbortController()
    const timer = window.setInterval(() => {
      fetchPublicJson(publicDataSources.players, controller.signal)
        .then((data) => setPlayerLeaderboard({ status: 'ready', data, error: null }))
        .catch((error) => {
          if (!controller.signal.aborted) {
            setPlayerLeaderboard((current) => ({ ...current, error: error.message }))
          }
        })
    }, 60000)

    return () => {
      window.clearInterval(timer)
      controller.abort()
    }
  }, [route.page])

  return (
    <main className="min-h-screen bg-bg text-text">
      <header
        className={`fixed inset-x-0 top-4 z-50 flex justify-center px-4 transition duration-300 ${shouldShowFloatingNav
          ? 'translate-y-0 opacity-100'
          : 'pointer-events-none -translate-y-8 opacity-0'
          }`}
      >
        <div
          className="flex max-w-[calc(100vw-2rem)] items-center gap-1.5 rounded-full border border-border bg-fury-white/92 py-1.5 pl-1.5 pr-11 shadow-[0_8px_24px_rgba(0,0,0,0.13)] backdrop-blur sm:pl-2"
          ref={navPillRef}
        >
          <button
            aria-label="Go to Toothless' TSS Bot home"
            className="hidden h-8 w-8 shrink-0 place-items-center rounded-full transition hover:bg-surface sm:grid"
            onClick={() => navigate('/')}
            type="button"
          >
            <img
              alt=""
              className="h-7 w-7 rounded-full"
              src="/embed-icon.svg"
            />
          </button>

          <nav className="flex min-w-0 items-center gap-0.5 overflow-x-auto">
            {primaryNavItems.map((item) => (
              <button
                className={`shrink-0 rounded-full px-2.5 py-1.5 text-[13px] font-semibold leading-5 transition sm:px-3 ${activeNavPath === item.path
                  ? 'bg-text text-bg apricot-button-text'
                  : 'text-text-soft hover:bg-surface hover:text-text'
                  }`}
                key={item.path}
                onClick={() => navigate(item.path)}
                type="button"
              >
                {item.label}
              </button>
            ))}
          </nav>

          <div className="h-5 w-px shrink-0 bg-border/80" aria-hidden="true" />

          <nav className="flex shrink-0 items-center gap-0.5">
            {utilityNavItems.map((item) => (
              <button
                className={`shrink-0 rounded-full px-2.5 py-1.5 text-[13px] font-semibold leading-5 transition ${activeNavPath === item.path
                  ? 'bg-text text-bg apricot-button-text'
                  : 'text-text-soft hover:bg-surface hover:text-text'
                  }`}
                key={item.path}
                onClick={() => navigate(item.path)}
                type="button"
              >
                {item.label}
              </button>
            ))}
          </nav>
        </div>
      </header>
      <ThemeToggleMover
        dockPosition={themeTogglePositions.dock}
        homePosition={themeTogglePositions.home}
        isHome={route.page === 'home'}
        theme={theme}
        onThemeChange={chooseTheme}
      />

      <section className="mx-auto w-full max-w-7xl px-5 sm:px-8">
        {route.page === 'home' ? (
          <Landing
            live={live}
            matches={matches}
            navigate={navigate}
            onTeamSearch={handleTeamSearch}
            searchPlaceholder={searchPlaceholder}
            setTeamQuery={setTeamQuery}
            teamSuggestions={teamSuggestions}
            teams={teams}
            teamsToWatch={teamsToWatch}
            teamQuery={teamQuery}
          />
        ) : null}
        {route.page === 'teams' ? (
          <TeamsPage leaderboard={leaderboard} navigate={navigate} teams={teams} />
        ) : null}
        {route.page === 'players' ? (
          <PlayersPage leaderboard={playerLeaderboard} navigate={navigate} players={players} />
        ) : null}
        {route.page === 'team' ? (
          <TeamProfilePage
            navigate={navigate}
            profile={profile}
            requestedTeam={route.teamName}
            teams={teams}
          />
        ) : null}
        {route.page === 'battle-logs' ? (
          <BattleLogsPage live={live} matches={matches} navigate={navigate} />
        ) : null}
        {route.page === 'uptime' ? <UptimePage uptime={uptime} /> : null}
        {route.page === 'viewers' ? <ViewersPage viewers={viewers} /> : null}
        {route.page === 'privacy' ? <PrivacyPage /> : null}
        {route.page === 'docs' ? <DocsPage /> : null}
        {route.page === 'blog' ? <BlogPage navigate={navigate} posts={blogPosts} /> : null}
        {route.page === 'blog-post' ? <BlogPostPage navigate={navigate} post={blogPostsBySlug.get(route.slug)} /> : null}
        {route.page === 'player' ? <PlayerPage uid={route.uid} navigate={navigate} /> : null}
        {route.page === 'game' ? <GamePage gameId={route.gameId} navigate={navigate} /> : null}
        {route.page === 'tournaments-list' ? <TournamentsPage navigate={navigate} /> : null}
        {route.page === 'tournament' ? <TournamentDetailPage tournamentId={route.tournamentId} navigate={navigate} /> : null}
        {route.page === 'settings' ? (
          <SettingsPage
            theme={theme}
            onThemeChange={chooseTheme}
            customColor={customColor}
            onCustomColorChange={chooseCustomColor}
            customColors={customColors}
            onCustomColorsChange={chooseCustomColors}
            analyticsPreferences={analyticsPreferences}
            onAnalyticsChoose={chooseAnalyticsConsent}
          />
        ) : null}
      </section>
      <Footer navigate={navigate} />
      <ConsentBanner preferences={analyticsPreferences} onChoose={chooseAnalyticsConsent} />
    </main>
  )
}

function Footer({ navigate }) {
  return (
    <footer className="mt-14 border-t border-border bg-fury-white">
      <div className="mx-auto flex w-full max-w-7xl flex-col gap-3 px-5 py-6 text-sm text-text-soft sm:flex-row sm:items-center sm:justify-between sm:px-8">
        <p>Toothless&apos; TSS Bot · {siteVersion}</p>
        <div className="flex flex-wrap gap-4">
          <button
            className="w-fit font-semibold text-fury-cyan transition hover:text-text"
            onClick={() => navigate('/uptime')}
            type="button"
          >
            Uptime
          </button>
          <button
            className="w-fit font-semibold text-fury-cyan transition hover:text-text"
            onClick={() => navigate('/viewers')}
            type="button"
          >
            Viewers
          </button>
          <button
            className="w-fit font-semibold text-fury-cyan transition hover:text-text"
            onClick={() => navigate('/privacy')}
            type="button"
          >
            Privacy
          </button>
          <button
            className="w-fit font-semibold text-fury-cyan transition hover:text-text"
            onClick={() => navigate('/settings')}
            type="button"
          >
            Settings
          </button>
          <button
            className="w-fit font-semibold text-fury-cyan transition hover:text-text"
            onClick={() => navigate('/docs')}
            type="button"
          >
            Documentation
          </button>
          <button
            className="w-fit font-semibold text-fury-cyan transition hover:text-text"
            onClick={() => navigate('/blog')}
            type="button"
          >
            Blog
          </button>
          <a
            className="w-fit font-semibold text-fury-cyan transition hover:text-text"
            href="https://github.com/Clippii/tssbot.web"
            target="_blank"
            rel="noopener noreferrer"
          >
            GitHub
          </a>
          <a
            className="w-fit font-semibold text-fury-cyan transition hover:text-text"
            href="https://clippi.dev/"
            target="_blank"
            rel="noopener noreferrer"
          >
            clippi.dev
          </a>
        </div>
      </div>
    </footer>
  )
}

function ConsentBanner({ preferences, onChoose }) {
  const [isOpen, setIsOpen] = useState(() => !preferences.chosen)
  const [isConfiguring, setIsConfiguring] = useState(() => Boolean(preferences.chosen))
  const [draft, setDraft] = useState(() => normalizeAnalyticsPreferences(preferences))
  const privacySignalEnabled = browserPrivacySignalEnabled()

  useEffect(() => {
    setDraft(normalizeAnalyticsPreferences(preferences))
    if (!preferences.chosen) {
      setIsOpen(true)
      setIsConfiguring(false)
    }
  }, [preferences])

  useEffect(() => {
    if (!isOpen) return undefined

    const previousOverflow = document.body.style.overflow
    document.body.style.overflow = 'hidden'

    return () => {
      document.body.style.overflow = previousOverflow
    }
  }, [isOpen])

  function updateDraft(key, value) {
    setDraft((current) => {
      if (key === 'analytics' && !value) {
        return {
          ...current,
          analytics: false,
          device: false,
          display: false,
          locale: false,
          referrer: false,
          diagnostics: false,
        }
      }

      return { ...current, [key]: value }
    })
  }

  function savePreferences(nextPreferences) {
    onChoose(normalizeAnalyticsPreferences(nextPreferences))
    setIsOpen(false)
  }

  function declineAll() {
    savePreferences({ ...defaultAnalyticsPreferences, chosen: true })
  }

  function allowAll() {
    savePreferences({
      chosen: true,
      analytics: true,
      device: true,
      display: true,
      locale: true,
      referrer: true,
      diagnostics: true,
      version: analyticsConsentVersion,
    })
  }

  if (!isOpen) {
    return (
      <button
        className="fixed right-4 bottom-4 z-50 rounded-md border border-border bg-fury-white px-3 py-2 text-xs font-semibold text-text-soft shadow-sm transition hover:border-ring hover:bg-surface hover:text-text"
        onClick={() => {
          setIsConfiguring(true)
          setIsOpen(true)
        }}
        type="button"
      >
        Cookie settings
      </button>
    )
  }

  return (
    <div
      aria-labelledby="consent-title"
      aria-modal="true"
      className="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/45 px-4 py-4 backdrop-blur-[2px] sm:py-6"
      role="dialog"
    >
      <div className="flex max-h-[calc(100dvh-2rem)] w-full max-w-2xl flex-col overflow-hidden rounded-md border border-border bg-fury-white p-5 text-text shadow-[0_24px_70px_rgba(0,0,0,0.24)] sm:max-h-[calc(100dvh-3rem)]">
        <div className="max-w-xl shrink-0">
          <h2 id="consent-title" className="text-xl font-semibold">
            Cookie and analytics settings
          </h2>
          <p className="mt-2 text-sm leading-6 text-text-soft">
            We use a necessary cookie to remember these choices. You can also allow
            analytics for the public viewers page and choose which details are included.
          </p>
          {privacySignalEnabled ? (
            <p className="mt-2 text-sm font-semibold text-text">
              Your browser is signalling a privacy preference, so optional analytics stay
              off unless you explicitly allow them.
            </p>
          ) : null}
        </div>

        {isConfiguring ? (
          <>
            <div className="mt-4 min-h-0 space-y-3 overflow-y-auto pr-1">
              <PreferenceToggle
                checked
                description="Stores this consent choice so the popup does not appear on every page."
                disabled
                label="Necessary cookie"
              />
              <PreferenceToggle
                checked={draft.analytics}
                description="Sends page views, live viewing state, session ID, and pseudonymous visitor ID."
                label="Viewer analytics"
                onChange={(value) => updateDraft('analytics', value)}
              />
              <PreferenceToggle
                checked={draft.device}
                description="Includes browser, operating system, and broad device type."
                disabled={!draft.analytics}
                label="Browser and device details"
                onChange={(value) => updateDraft('device', value)}
              />
              <PreferenceToggle
                checked={draft.display}
                description="Includes screen size, viewport size, and colour depth."
                disabled={!draft.analytics}
                label="Screen details"
                onChange={(value) => updateDraft('display', value)}
              />
              <PreferenceToggle
                checked={draft.locale}
                description="Includes browser language and timezone."
                disabled={!draft.analytics}
                label="Language and timezone"
                onChange={(value) => updateDraft('locale', value)}
              />
              <PreferenceToggle
                checked={draft.referrer}
                description="Includes the page that linked you here when the browser provides it."
                disabled={!draft.analytics}
                label="Referrer"
                onChange={(value) => updateDraft('referrer', value)}
              />
              <PreferenceToggle
                checked={draft.diagnostics}
                description="Includes HTTP version, protocol headers, cache/debug headers, network quality, privacy signals, touch support, CPU/memory hints, and other browser diagnostics."
                disabled={!draft.analytics}
                label="Technical diagnostics"
                onChange={(value) => updateDraft('diagnostics', value)}
              />
            </div>

            <div className="mt-4 flex shrink-0 flex-col-reverse gap-2 sm:flex-row sm:justify-end">
              <button
                className="rounded-md border border-border px-4 py-2 text-sm font-semibold text-text-soft transition hover:bg-surface hover:text-text"
                onClick={declineAll}
                type="button"
              >
                Decline all
              </button>
              <button
                className="rounded-md border border-border px-4 py-2 text-sm font-semibold text-text transition hover:bg-surface"
                onClick={() => savePreferences(draft)}
                type="button"
              >
                Save choices
              </button>
              <button
                className="rounded-md bg-fury-cyan px-4 py-2 text-sm font-semibold text-bg transition hover:bg-fury-aqua"
                onClick={allowAll}
                type="button"
              >
                Allow all
              </button>
            </div>
          </>
        ) : (
          <div className="mt-4 flex flex-col gap-2 sm:flex-row sm:justify-end">
            <button
              className="rounded-md border border-border px-4 py-2 text-sm font-semibold text-text-soft transition hover:bg-surface hover:text-text"
              onClick={declineAll}
              type="button"
            >
              Decline all
            </button>
            <button
              className="rounded-md border border-border px-4 py-2 text-sm font-semibold text-text transition hover:bg-surface"
              onClick={() => setIsConfiguring(true)}
              type="button"
            >
              Configure
            </button>
            <button
              className="rounded-md bg-fury-cyan px-4 py-2 text-sm font-semibold text-bg transition hover:bg-fury-aqua"
              onClick={allowAll}
              type="button"
            >
              Allow all
            </button>
          </div>
        )}
      </div>
    </div>
  )
}

function PreferenceToggle({ checked, description, disabled = false, label, onChange }) {
  return (
    <label
      className={`flex items-start gap-3 rounded-md border border-border bg-surface px-3 py-3 ${disabled ? 'opacity-65' : 'cursor-pointer'
        }`}
    >
      <input
        checked={checked}
        className="mt-1 h-4 w-4 accent-fury-cyan"
        disabled={disabled}
        onChange={(event) => onChange?.(event.target.checked)}
        type="checkbox"
      />
      <span>
        <span className="block text-sm font-semibold text-text">{label}</span>
        <span className="mt-1 block text-xs leading-5 text-text-soft">{description}</span>
      </span>
    </label>
  )
}

function PrivacyPage() {
  return (
    <section className="mx-auto max-w-4xl pb-12 pt-24 sm:pt-28">
      <div className="border-b border-border pb-6">
        <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
          Privacy notice
        </p>
        <h1 className="mt-2 text-4xl font-bold">How we handle your data</h1>
        <p className="mt-3 text-text-soft">
          Controller: Heidi
        </p>
        <p className="mt-3 text-text-soft">
          Contact through Discord: <span className="font-semibold text-text">clippiii</span>
        </p>
      </div>

      <div className="mt-8 grid gap-6 text-sm leading-6 text-text-soft">
        <PrivacySection title="What we collect">
          Necessary cookies store your cookie, theme, and analytics choices. If you opt in to
          viewer analytics, we collect page views, live viewing status, a pseudonymous
          visitor ID, a session ID, and the active light/dark theme. Optional settings control whether browser/device,
          screen, language/timezone, referrer, and technical diagnostics are included.
          Technical diagnostics can include HTTP version, request protocol details,
          content negotiation headers, browser privacy signals, network quality, touch
          support, CPU/memory hints, and similar debugging fields.
        </PrivacySection>

        <PrivacySection title="Why we collect it">
          Necessary cookies are used to remember your privacy and theme choices. Optional viewer
          analytics are used only to power the public viewers page, understand basic site
          activity, and keep abuse low.
        </PrivacySection>

        <PrivacySection title="Legal basis">
          Necessary cookies are used because they are needed to remember your consent
          preferences. Optional analytics only run with your consent, and you can withdraw
          that consent from the Cookie settings button at any time.
        </PrivacySection>

        <PrivacySection title="Retention and deletion">
          Analytics events are automatically deleted after the configured retention period,
          currently documented as 30 days by default. Active viewer sessions expire after a
          short inactivity window. If you decline analytics after previously allowing them,
          the site asks the server to delete records linked to your current visitor and
          session IDs, and removes the local visitor ID from your browser.
        </PrivacySection>

        <PrivacySection title="Sharing and transfers">
          Viewer analytics are stored by this site and are not sold. Public viewer pages show
          only consented analytics fields and never show raw IP addresses. Hosting providers
          may process server logs as part of running the site.
        </PrivacySection>

        <PrivacySection title="Your rights">
          You can ask for access, correction, deletion, restriction, objection, portability,
          or withdrawal of consent by contacting Heidi on Discord at clippiii. If you are in
          the UK, you can also complain to the ICO. If you are in the EU or EEA, you can
          complain to your local data protection authority.
        </PrivacySection>
      </div>
    </section>
  )
}

function PlayerStatCard({ label, value }) {
  return (
    <div className="rounded-xl border border-border bg-fury-white p-4">
      <p className="text-xs font-semibold uppercase tracking-wide text-text-soft">{label}</p>
      <p className="mt-1 text-2xl font-bold">{value}</p>
    </div>
  )
}

function PlayerPage({ uid, navigate }) {
  const [state, setState] = useState({ status: 'loading', data: null, error: '' })

  useEffect(() => {
    if (!uid) {
      setState({ status: 'error', data: null, error: 'No player specified.' })
      return
    }
    let cancelled = false
    setState({ status: 'loading', data: null, error: '' })
    fetchPublicJson(publicDataSources.player(uid))
      .then((data) => {
        if (!cancelled) setState({ status: 'ready', data, error: '' })
      })
      .catch((err) => {
        if (!cancelled) setState({ status: 'error', data: null, error: err?.message || 'Failed to load player.' })
      })
    return () => {
      cancelled = true
    }
  }, [uid])

  const { status, data, error } = state

  return (
    <section className="mx-auto max-w-4xl pb-12 pt-24 sm:pt-28">
      <div className="border-b border-border pb-6">
        <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">Player</p>
        <h1 className="mt-2 text-4xl font-bold">
          {status === 'ready' ? (data.nick || `Player ${uid}`) : `Player ${uid || ''}`}
        </h1>
        <p className="mt-3 text-text-soft">UID {uid} · TSS career</p>
      </div>

      {status === 'loading' ? <p className="mt-8 text-text-soft">Loading…</p> : null}
      {status === 'error' ? <p className="mt-8 text-text-soft">{error}</p> : null}

      {status === 'ready' ? (
        <div className="mt-8 grid gap-8">
          <div className="grid grid-cols-2 gap-4 sm:grid-cols-3">
            <PlayerStatCard label="Battles" value={formatNumber(data.career.total_battles)} />
            <PlayerStatCard label="Win rate" value={`${(data.career.win_rate || 0).toFixed(1)}%`} />
            <PlayerStatCard label="K/D" value={(data.career.kdr || 0).toFixed(2)} />
            <PlayerStatCard label="Total kills" value={formatNumber(data.career.total_kills)} />
            <PlayerStatCard label="Wins / Losses" value={`${formatNumber(data.career.wins)} / ${formatNumber(data.career.losses)}`} />
            <PlayerStatCard label="Deaths" value={formatNumber(data.career.deaths)} />
            <PlayerStatCard label="Ground kills" value={formatNumber(data.career.ground_kills)} />
            <PlayerStatCard label="Air kills" value={formatNumber(data.career.air_kills)} />
            <PlayerStatCard label="Assists" value={formatNumber(data.career.assists)} />
          </div>

          {Array.isArray(data.teams) && data.teams.length ? (
            <div>
              <h2 className="text-lg font-semibold text-text">Teams seen with</h2>
              <div className="mt-3 flex flex-col gap-2">
                {data.teams.map((team) => {
                  const label = team.team_tag || team.team_name || 'Unknown'
                  return (
                    <button
                      key={`${team.team_tag}-${team.team_id ?? ''}`}
                      className="flex items-center justify-between rounded-lg border border-border bg-fury-white px-4 py-3 text-left transition hover:border-fury-cyan"
                      onClick={() => navigate(teamPath(label))}
                      type="button"
                    >
                      <span className="font-semibold text-text">{label}</span>
                      <span className="text-sm text-text-soft">{formatNumber(team.games)} games</span>
                    </button>
                  )
                })}
              </div>
            </div>
          ) : null}
        </div>
      ) : null}
    </section>
  )
}

function DocsPage() {
  return (
    <section className="mx-auto max-w-4xl pb-12 pt-24 sm:pt-28">
      <div className="border-b border-border pb-6">
        <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
          Docs
        </p>
        <h1 className="mt-2 text-4xl font-bold">Documentation</h1>
        <p className="mt-3 text-text-soft">
          Documentation is being written.
        </p>
      </div>
    </section>
  )
}

function safeMarkdownLink(href) {
  const value = String(href || '').trim()
  if (/^(https?:|mailto:|\/)/i.test(value)) return value
  return '#'
}

function safeMediaSource(src) {
  const value = String(src || '').trim()
  if (/^(https?:|\/)/i.test(value)) return value
  return ''
}

function youtubeEmbedUrl(value) {
  try {
    const url = new URL(value)
    const host = url.hostname.replace(/^www\./, '')
    let id = ''

    if (host === 'youtu.be') {
      id = url.pathname.split('/').filter(Boolean)[0] || ''
    } else if (host === 'youtube.com' || host === 'm.youtube.com') {
      if (url.pathname.startsWith('/watch')) id = url.searchParams.get('v') || ''
      if (url.pathname.startsWith('/shorts/') || url.pathname.startsWith('/embed/')) {
        id = url.pathname.split('/').filter(Boolean)[1] || ''
      }
    }

    if (!/^[A-Za-z0-9_-]{6,32}$/.test(id)) return ''
    return `https://www.youtube-nocookie.com/embed/${id}`
  } catch {
    return ''
  }
}

function vimeoEmbedUrl(value) {
  try {
    const url = new URL(value)
    const host = url.hostname.replace(/^www\./, '')
    if (host !== 'vimeo.com' && host !== 'player.vimeo.com') return ''
    const id = url.pathname.split('/').filter(Boolean).find((part) => /^\d+$/.test(part)) || ''
    return id ? `https://player.vimeo.com/video/${id}` : ''
  } catch {
    return ''
  }
}

function mediaEmbedFor(url) {
  const src = safeMediaSource(url)
  if (!src) return { kind: 'invalid', src: '' }

  const youtube = youtubeEmbedUrl(src)
  if (youtube) return { kind: 'iframe', src: youtube, title: 'YouTube video' }

  const vimeo = vimeoEmbedUrl(src)
  if (vimeo) return { kind: 'iframe', src: vimeo, title: 'Vimeo video' }

  if (/\.(mp4|webm|ogg)(?:[?#].*)?$/i.test(src)) return { kind: 'video', src }
  return { kind: 'link', src }
}

function MarkdownInline({ text }) {
  const parts = []
  const pattern = /(\[([^\]]+)\]\(([^)]+)\))|(`([^`]+)`)|(\*\*([^*]+)\*\*)|(\*([^*]+)\*)/g
  let lastIndex = 0
  let match = pattern.exec(text)

  while (match) {
    if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index))

    if (match[2] && match[3]) {
      const href = safeMarkdownLink(match[3])
      parts.push(
        <a
          className="font-semibold text-fury-cyan underline-offset-4 hover:underline"
          href={href}
          key={`link-${match.index}`}
          rel={href.startsWith('http') ? 'noopener noreferrer' : undefined}
          target={href.startsWith('http') ? '_blank' : undefined}
        >
          {match[2]}
        </a>,
      )
    } else if (match[5]) {
      parts.push(
        <code className="rounded bg-surface px-1.5 py-0.5 text-[0.9em] text-text" key={`code-${match.index}`}>
          {match[5]}
        </code>,
      )
    } else if (match[7]) {
      parts.push(<strong key={`strong-${match.index}`}>{match[7]}</strong>)
    } else if (match[9]) {
      parts.push(<em key={`em-${match.index}`}>{match[9]}</em>)
    }

    lastIndex = pattern.lastIndex
    match = pattern.exec(text)
  }

  if (lastIndex < text.length) parts.push(text.slice(lastIndex))
  return parts
}

function MarkdownContent({ markdown }) {
  const lines = String(markdown || '').replace(/\r\n/g, '\n').split('\n')
  const blocks = []
  let index = 0

  while (index < lines.length) {
    const line = lines[index]
    if (!line.trim()) {
      index += 1
      continue
    }

    if (line.startsWith('```')) {
      const language = line.slice(3).trim()
      const code = []
      index += 1
      while (index < lines.length && !lines[index].startsWith('```')) {
        code.push(lines[index])
        index += 1
      }
      index += 1
      blocks.push({ type: 'code', language, text: code.join('\n') })
      continue
    }

    const mediaEmbed = line.match(/^@\[([^\]]*)\]\(([^)]+)\)$/)
    if (mediaEmbed) {
      blocks.push({ type: 'media', label: mediaEmbed[1].trim(), url: mediaEmbed[2].trim() })
      index += 1
      continue
    }

    const image = line.match(/^!\[([^\]]*)\]\(([^)]+)\)$/)
    if (image) {
      blocks.push({ type: 'image', alt: image[1].trim(), src: image[2].trim() })
      index += 1
      continue
    }

    const heading = line.match(/^(#{1,3})\s+(.+)$/)
    if (heading) {
      blocks.push({ type: 'heading', level: heading[1].length, text: heading[2] })
      index += 1
      continue
    }

    if (/^>\s?/.test(line)) {
      const quote = []
      while (index < lines.length && /^>\s?/.test(lines[index])) {
        quote.push(lines[index].replace(/^>\s?/, ''))
        index += 1
      }
      blocks.push({ type: 'quote', text: quote.join(' ') })
      continue
    }

    if (/^[-*]\s+/.test(line)) {
      const items = []
      while (index < lines.length && /^[-*]\s+/.test(lines[index])) {
        items.push(lines[index].replace(/^[-*]\s+/, ''))
        index += 1
      }
      blocks.push({ type: 'list', ordered: false, items })
      continue
    }

    if (/^\d+\.\s+/.test(line)) {
      const items = []
      while (index < lines.length && /^\d+\.\s+/.test(lines[index])) {
        items.push(lines[index].replace(/^\d+\.\s+/, ''))
        index += 1
      }
      blocks.push({ type: 'list', ordered: true, items })
      continue
    }

    const paragraph = [line.trim()]
    index += 1
    while (
      index < lines.length &&
      lines[index].trim() &&
      !lines[index].startsWith('```') &&
      !/^@\[([^\]]*)\]\(([^)]+)\)$/.test(lines[index]) &&
      !/^!\[([^\]]*)\]\(([^)]+)\)$/.test(lines[index]) &&
      !/^(#{1,3})\s+/.test(lines[index]) &&
      !/^>\s?/.test(lines[index]) &&
      !/^[-*]\s+/.test(lines[index]) &&
      !/^\d+\.\s+/.test(lines[index])
    ) {
      paragraph.push(lines[index].trim())
      index += 1
    }
    blocks.push({ type: 'paragraph', text: paragraph.join(' ') })
  }

  return (
    <div className="blog-content mt-8 grid gap-5 text-base leading-8 text-text-soft">
      {blocks.map((block, blockIndex) => {
        if (block.type === 'heading') {
          const className = block.level === 1
            ? 'mt-4 text-3xl font-bold leading-tight text-text'
            : block.level === 2
              ? 'mt-3 text-2xl font-bold leading-tight text-text'
              : 'mt-2 text-xl font-semibold leading-tight text-text'
          const HeadingTag = `h${Math.min(block.level + 1, 4)}`
          return (
            <HeadingTag className={className} key={`heading-${blockIndex}`}>
              <MarkdownInline text={block.text} />
            </HeadingTag>
          )
        }
        if (block.type === 'code') {
          return (
            <pre className="overflow-x-auto rounded-lg border border-border bg-surface p-4 text-sm leading-6 text-text" key={`code-${blockIndex}`}>
              <code>{block.text}</code>
            </pre>
          )
        }
        if (block.type === 'image') {
          const src = safeMediaSource(block.src)
          if (!src) return null
          return (
            <figure className="overflow-hidden rounded-lg border border-border bg-surface" key={`image-${blockIndex}`}>
              <img
                alt={block.alt}
                className="block max-h-[560px] w-full object-contain"
                loading="lazy"
                src={src}
              />
              {block.alt ? (
                <figcaption className="border-t border-border px-4 py-2 text-sm text-text-soft">
                  {block.alt}
                </figcaption>
              ) : null}
            </figure>
          )
        }
        if (block.type === 'media') {
          const embed = mediaEmbedFor(block.url)
          if (embed.kind === 'iframe') {
            return (
              <div className="aspect-video overflow-hidden rounded-lg border border-border bg-surface shadow-sm" key={`media-${blockIndex}`}>
                <iframe
                  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
                  allowFullScreen
                  className="h-full w-full"
                  loading="lazy"
                  referrerPolicy="strict-origin-when-cross-origin"
                  src={embed.src}
                  title={block.label || embed.title}
                />
              </div>
            )
          }
          if (embed.kind === 'video') {
            return (
              <video
                className="aspect-video w-full rounded-lg border border-border bg-surface shadow-sm"
                controls
                key={`media-${blockIndex}`}
                preload="metadata"
                src={embed.src}
              >
                <a href={embed.src}>Open video</a>
              </video>
            )
          }
          if (embed.kind === 'link') {
            return (
              <p className="rounded-lg border border-border bg-surface px-4 py-3 text-sm text-text-soft" key={`media-${blockIndex}`}>
                Media link:{' '}
                <a
                  className="font-semibold text-fury-cyan underline-offset-4 hover:underline"
                  href={embed.src}
                  rel="noopener noreferrer"
                  target="_blank"
                >
                  {block.label || embed.src}
                </a>
              </p>
            )
          }
          return null
        }
        if (block.type === 'quote') {
          return (
            <blockquote className="border-l-4 border-fury-cyan bg-surface px-4 py-3 font-semibold text-text" key={`quote-${blockIndex}`}>
              <MarkdownInline text={block.text} />
            </blockquote>
          )
        }
        if (block.type === 'list') {
          const ListTag = block.ordered ? 'ol' : 'ul'
          return (
            <ListTag className={`${block.ordered ? 'list-decimal' : 'list-disc'} grid gap-2 pl-6`} key={`list-${blockIndex}`}>
              {block.items.map((item, itemIndex) => (
                <li key={`${blockIndex}-${itemIndex}`}>
                  <MarkdownInline text={item} />
                </li>
              ))}
            </ListTag>
          )
        }
        return (
          <p key={`paragraph-${blockIndex}`}>
            <MarkdownInline text={block.text} />
          </p>
        )
      })}
    </div>
  )
}

function BlogPage({ navigate, posts }) {
  return (
    <section className="mx-auto max-w-5xl pb-12 pt-24 sm:pt-28">
      <div className="border-b border-border pb-6">
        <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
          Blog
        </p>
        <h1 className="mt-2 text-4xl font-bold">News and updates</h1>
        <p className="mt-3 max-w-2xl text-text-soft">
          Updates from the TSS bot, posted from Markdown files in the repo.
        </p>
      </div>

      <div className="mt-8 grid gap-4">
        {posts.map((post) => (
          <article className="rounded-lg border border-border bg-fury-white p-5 shadow-sm" key={post.slug}>
            <button
              className="block w-full text-left"
              onClick={() => navigate(post.path)}
              type="button"
            >
              <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
                {formatBlogDate(post.date)}
              </p>
              <h2 className="mt-2 text-2xl font-bold text-text">{post.title}</h2>
              {post.excerpt ? (
                <p className="mt-3 text-sm leading-6 text-text-soft">{post.excerpt}</p>
              ) : null}
              <span className="mt-4 inline-block text-sm font-semibold text-fury-cyan">
                Read post
              </span>
            </button>
          </article>
        ))}

        {!posts.length ? (
          <p className="rounded-lg border border-border bg-fury-white px-5 py-10 text-sm text-text-soft shadow-sm">
            No posts yet. Add a Markdown file to frontend/src/blog and rebuild.
          </p>
        ) : null}
      </div>
    </section>
  )
}

function BlogPostPage({ navigate, post }) {
  if (!post) {
    return (
      <section className="mx-auto max-w-4xl pb-12 pt-24 sm:pt-28">
        <div className="border-b border-border pb-6">
          <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">Blog</p>
          <h1 className="mt-2 text-4xl font-bold">Post not found</h1>
          <p className="mt-3 text-text-soft">That blog post does not exist.</p>
        </div>
        <button
          className="mt-8 rounded-lg bg-text px-4 py-2 text-sm font-semibold text-bg apricot-button-text"
          onClick={() => navigate('/blog')}
          type="button"
        >
          Back to blog
        </button>
      </section>
    )
  }

  return (
    <article className="mx-auto max-w-4xl pb-12 pt-24 sm:pt-28">
      <button
        className="mb-6 text-sm font-semibold text-fury-cyan transition hover:text-text"
        onClick={() => navigate('/blog')}
        type="button"
      >
        Back to blog
      </button>
      <header className="border-b border-border pb-6">
        <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
          {formatBlogDate(post.date)}
        </p>
        <h1 className="mt-2 text-4xl font-bold leading-tight">{post.title}</h1>
        {post.author ? <p className="mt-3 text-text-soft">By {post.author}</p> : null}
        {post.excerpt ? (
          <p className="mt-4 max-w-2xl rounded-md border-l-2 border-fury-cyan bg-surface pl-4 pr-3 py-3 text-base italic leading-7 text-text-soft">
            {post.excerpt}
          </p>
        ) : null}
      </header>
      <MarkdownContent markdown={post.content} />
    </article>
  )
}

const accentPresets = [
  { id: 'default-light', label: 'Classic Light', theme: 'light', color: '' },
  { id: 'default-dark', label: 'Classic Dark', theme: 'dark', color: '' },
  { id: 'preset-sky', label: 'Sky Blue', theme: null, color: '#0ea5e9' },
  { id: 'preset-violet', label: 'Violet', theme: null, color: '#8b5cf6' },
  { id: 'preset-emerald', label: 'Emerald', theme: null, color: '#10b981' },
  { id: 'preset-rose', label: 'Rose', theme: null, color: '#f43f5e' },
  { id: 'preset-amber', label: 'Amber', theme: null, color: '#f59e0b' },
  { id: 'preset-indigo', label: 'Indigo', theme: null, color: '#6366f1' },
]

const advancedColorDefs = [
  {
    field: 'bg',
    label: 'Background',
    desc: 'Page background colour',
    defaults: { light: '#fefde7', dark: '#130d08' },
  },
  {
    field: 'surface',
    label: 'Card / Surface',
    desc: 'Cards, panels, and raised elements',
    defaults: { light: '#fcfbcf', dark: '#24170d' },
  },
  {
    field: 'text',
    label: 'Primary text',
    desc: 'Headings and body text',
    defaults: { light: '#000000', dark: '#fdb068' },
  },
  {
    field: 'textSoft',
    label: 'Soft text',
    desc: 'Secondary labels and descriptions',
    defaults: { light: '#555555', dark: '#fff2e6' },
  },
  {
    field: 'textMuted',
    label: 'Muted text',
    desc: 'Placeholder and hint text',
    defaults: { light: '#888888', dark: '#fee5cd' },
  },
  {
    field: 'border',
    label: 'Border',
    desc: 'Lines, dividers, and outlines',
    defaults: { light: '#fee5cd', dark: '#68401f' },
  },
]

function SettingsPage({ theme, onThemeChange, customColor, onCustomColorChange, customColors, onCustomColorsChange, analyticsPreferences, onAnalyticsChoose }) {
  const [draftColor, setDraftColor] = useState(customColor || '#e82517')
  const [draft, setDraft] = useState(() => normalizeAnalyticsPreferences(analyticsPreferences))

  useEffect(() => {
    setDraft(normalizeAnalyticsPreferences(analyticsPreferences))
  }, [analyticsPreferences])

  function updateDraft(key, value) {
    setDraft((current) => {
      if (key === 'analytics' && !value) {
        return {
          ...current,
          analytics: false,
          device: false,
          display: false,
          locale: false,
          referrer: false,
          diagnostics: false,
        }
      }
      return { ...current, [key]: value }
    })
  }

  function saveAnalytics() {
    onAnalyticsChoose(normalizeAnalyticsPreferences({ ...draft, chosen: true }))
  }

  const activePresetId = (() => {
    if (!customColor) return theme === 'light' ? 'default-light' : 'default-dark'
    const match = accentPresets.find((p) => p.color && p.color.toLowerCase() === customColor.toLowerCase())
    return match ? match.id : 'custom'
  })()

  function handlePreset(preset) {
    if (preset.theme) onThemeChange(preset.theme)
    onCustomColorChange(preset.color)
    if (preset.color) setDraftColor(preset.color)
  }

  function handleColorInput(value) {
    setDraftColor(value)
    if (/^#[0-9a-fA-F]{6}$/.test(value)) {
      onCustomColorChange(value)
    }
  }

  function handleColorPickerChange(value) {
    setDraftColor(value)
    onCustomColorChange(value)
  }

  function resetToDefault() {
    onCustomColorChange('')
    setDraftColor('#e82517')
  }

  const hasAnyCustomColor = customColor || Object.keys(customColors).length > 0

  function resetAllColors() {
    onCustomColorChange('')
    setDraftColor('#e82517')
    onCustomColorsChange({})
  }

  const allColorDefs = [
    {
      id: 'accent',
      label: 'Accent',
      desc: 'Links, highlights, interactive elements',
      getValue: () => draftColor,
      getDisplayVal: () => draftColor,
      isCustom: () => !!customColor,
      onChange: handleColorPickerChange,
      onReset: resetToDefault,
    },
    ...advancedColorDefs.map(({ field, label, desc, defaults }) => ({
      id: field,
      label,
      desc,
      getValue: () => customColors[field] || '',
      getDisplayVal: () => customColors[field] || defaults[theme] || defaults.dark,
      isCustom: () => !!customColors[field],
      onChange: (val) => onCustomColorsChange({ ...customColors, [field]: val }),
      onReset: () => {
        const next = { ...customColors }
        delete next[field]
        onCustomColorsChange(next)
      },
    })),
  ]

  return (
    <section className="mx-auto max-w-2xl pb-16 pt-24 sm:pt-28">
      <div className="border-b border-border pb-6">
        <h1 className="text-3xl font-bold">Settings</h1>
        <p className="mt-2 text-sm text-text-soft">
          Your choices are saved locally and persist across visits.
        </p>
      </div>

      <div className="mt-8 space-y-6">
        {/* Appearance */}
        <div className="rounded-xl border border-border bg-fury-white p-6 shadow-sm">
          <h2 className="text-base font-semibold">Appearance</h2>

          {/* Light / Dark toggle */}
          <div className="mt-4 flex items-center justify-between">
            <span className="text-sm text-text-soft">Base mode</span>
            <div className="flex rounded-full border border-border bg-surface p-0.5">
              <button
                type="button"
                onClick={() => onThemeChange('light')}
                className={`rounded-full px-4 py-1 text-sm font-semibold transition ${theme === 'light' ? 'bg-text text-bg' : 'text-text-soft hover:text-text'}`}
              >
                ☼ Light
              </button>
              <button
                type="button"
                onClick={() => onThemeChange('dark')}
                className={`rounded-full px-4 py-1 text-sm font-semibold transition ${theme === 'dark' ? 'bg-text text-bg' : 'text-text-soft hover:text-text'}`}
              >
                ☾ Dark
              </button>
            </div>
          </div>

          {/* Presets */}
          <div className="mt-5">
            <p className="mb-2.5 text-xs font-semibold uppercase tracking-wide text-text-muted">Presets</p>
            <div className="flex flex-wrap gap-2">
              {accentPresets.map((preset) => {
                const isActive = activePresetId === preset.id
                return (
                  <button
                    key={preset.id}
                    type="button"
                    onClick={() => handlePreset(preset)}
                    className={`flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-semibold transition ${isActive
                        ? 'border-fury-cyan bg-fury-cyan text-bg'
                        : 'border-border bg-surface text-text hover:border-ring hover:bg-surface-alt'
                      }`}
                  >
                    <span
                      className="h-2.5 w-2.5 rounded-full shrink-0"
                      style={{
                        background: preset.color
                          ? preset.color
                          : preset.id === 'default-light'
                          ? 'linear-gradient(135deg, #fefde7 50%, #e82517 50%)'
                          : 'linear-gradient(135deg, #130d08 50%, #ff6a5f 50%)',
                        border: '1px solid rgba(128,128,128,0.3)',
                      }}
                    />
                    {preset.label}
                  </button>
                )
              })}
            </div>
          </div>

          {/* Colour grid */}
          <div className="mt-5">
            <div className="mb-2.5 flex items-center justify-between">
              <p className="text-xs font-semibold uppercase tracking-wide text-text-muted">Colours</p>
              {hasAnyCustomColor && (
                <button
                  type="button"
                  onClick={resetAllColors}
                  className="text-xs text-text-muted transition hover:text-text"
                >
                  Reset all
                </button>
              )}
            </div>
            <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4">
              {allColorDefs.map(({ id, label, desc, getDisplayVal, isCustom, onChange, onReset }) => {
                const displayVal = getDisplayVal()
                const customised = isCustom()
                return (
                  <div
                    key={id}
                    className="group relative flex flex-col gap-2 rounded-lg border border-border bg-surface p-3"
                  >
                    <div className="flex items-center justify-between">
                      <label
                        className="relative h-7 w-7 cursor-pointer overflow-hidden rounded-full border-2 border-border shadow-sm transition hover:scale-105 active:scale-95"
                        style={{ background: displayVal }}
                        title={`Pick ${label}`}
                      >
                        <input
                          type="color"
                          value={displayVal}
                          onChange={(e) => onChange(e.target.value)}
                          className="absolute inset-0 h-full w-full cursor-pointer opacity-0"
                        />
                      </label>
                      {customised && (
                        <button
                          type="button"
                          onClick={onReset}
                          className="text-xs text-text-muted opacity-0 transition group-hover:opacity-100 hover:text-text"
                          title="Reset to default"
                        >
                          ↺
                        </button>
                      )}
                    </div>
                    <div>
                      <p className="text-xs font-semibold text-text leading-none">
                        {label}
                        {customised && <span className="ml-1 text-fury-cyan">•</span>}
                      </p>
                      <p className="mt-0.5 text-xs text-text-muted leading-tight">{desc}</p>
                    </div>
                    <div className="flex items-center gap-0.5 rounded border border-border bg-bg px-1.5 py-1">
                      <span className="text-xs text-text-muted">#</span>
                      <input
                        type="text"
                        value={displayVal.replace('#', '')}
                        maxLength={6}
                        onChange={(e) => {
                          const v = '#' + e.target.value.replace(/[^0-9a-fA-F]/g, '')
                          if (/^#[0-9a-fA-F]{6}$/.test(v)) onChange(v)
                        }}
                        className="w-full bg-transparent text-xs font-mono text-text outline-none"
                        spellCheck={false}
                      />
                    </div>
                  </div>
                )
              })}
            </div>
          </div>
        </div>

        {/* Analytics */}
        <div className="rounded-xl border border-border bg-fury-white p-6 shadow-sm">
          <h2 className="text-base font-semibold">Analytics &amp; cookies</h2>
          <p className="mt-1 text-sm text-text-soft">
            A necessary cookie stores these preferences. You can allow analytics for the viewers page and control what's included.
          </p>

          <div className="mt-4 space-y-2">
            <PreferenceToggle
              checked
              description="Stores your theme and consent choice so preferences persist across visits."
              disabled
              label="Necessary cookies"
            />
            <PreferenceToggle
              checked={draft.analytics}
              description="Sends page views, live viewing state, session ID, and pseudonymous visitor ID."
              label="Viewer analytics"
              onChange={(value) => updateDraft('analytics', value)}
            />
            {draft.analytics && (
              <div className="ml-7 space-y-2">
                <PreferenceToggle
                  checked={draft.device}
                  description="Browser, OS, and broad device type."
                  label="Browser and device"
                  onChange={(value) => updateDraft('device', value)}
                />
                <PreferenceToggle
                  checked={draft.display}
                  description="Screen size, viewport size, and colour depth."
                  label="Screen details"
                  onChange={(value) => updateDraft('display', value)}
                />
                <PreferenceToggle
                  checked={draft.locale}
                  description="Browser language and timezone."
                  label="Language and timezone"
                  onChange={(value) => updateDraft('locale', value)}
                />
                <PreferenceToggle
                  checked={draft.referrer}
                  description="The page that linked you here, when the browser provides it."
                  label="Referrer"
                  onChange={(value) => updateDraft('referrer', value)}
                />
                <PreferenceToggle
                  checked={draft.diagnostics}
                  description="Network quality, privacy signals, touch support, CPU/memory hints."
                  label="Technical diagnostics"
                  onChange={(value) => updateDraft('diagnostics', value)}
                />
              </div>
            )}
          </div>

          <div className="mt-5 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
            <button
              type="button"
              onClick={() => onAnalyticsChoose({ ...defaultAnalyticsPreferences, chosen: true })}
              className="rounded-md border border-border px-4 py-2 text-sm font-semibold text-text-soft transition hover:bg-surface hover:text-text"
            >
              Decline all
            </button>
            <button
              type="button"
              onClick={saveAnalytics}
              className="rounded-md bg-fury-cyan px-4 py-2 text-sm font-semibold text-bg transition hover:bg-fury-aqua"
            >
              Save preferences
            </button>
          </div>
        </div>
      </div>
    </section>
  )
}


function PrivacySection({ children, title }) {
  return (
    <article className="border-l border-border pl-4">
      <h2 className="text-lg font-semibold text-text">{title}</h2>
      <p className="mt-2">{children}</p>
    </article>
  )
}

function Landing({
  live,
  matches,
  navigate,
  onTeamSearch,
  searchPlaceholder,
  setTeamQuery,
  teamSuggestions,
  teams,
  teamsToWatch,
  teamQuery,
}) {
  const treeRef = useRef(null)
  const latestBlogPost = blogPosts[0]

  return (
    <div className="relative left-1/2 w-screen -translate-x-1/2 bg-bg">
      <div className="relative min-h-screen overflow-hidden px-5 sm:px-8">
        <PixelMountains />

        <section className="relative z-10 mx-auto grid min-h-screen w-full max-w-7xl gap-8 pt-16 pb-16 lg:grid-cols-[1.05fr_0.95fr] lg:items-center">
          <div className="max-w-3xl lg:translate-y-[5px]">
            <p className="text-base font-semibold uppercase tracking-wide text-fury-cyan">
              As An Alternative to the TSS Website;
            </p>
            <h1 className="mt-3 text-6xl font-bold tracking-normal sm:text-7xl lg:text-8xl">
              Toothless&apos; TSS Bot
            </h1>
            <p className="mt-6 max-w-2xl text-xl leading-9 text-text-soft">
              A TSS web companion, free forever ♥
            </p>
            <div className="mt-8 w-full max-w-xl">
              <div className="grid gap-4 sm:grid-cols-3">
                <button
                  className="apricot-button-text min-h-15 rounded-lg bg-text px-5 py-4 text-base font-semibold text-bg transition hover:bg-fury-cyan"
                  onClick={() => navigate('/teams')}
                  type="button"
                >
                  Team Leaderboard
                </button>
                <button
                  className="min-h-15 rounded-lg border-2 border-text bg-fury-white px-5 py-4 text-base font-semibold text-text transition hover:bg-surface"
                  onClick={() => navigate('/players')}
                  type="button"
                >
                  Player Leaderboard
                </button>
                <button
                  className="min-h-15 rounded-lg border-2 border-ring px-5 py-4 text-base font-semibold text-fury-cyan transition hover:bg-surface hover:text-text"
                  onClick={() => navigate('/battle-logs')}
                  type="button"
                >
                  Battle Logs
                </button>
              </div>

              <form
                className="mt-5 grid gap-2 rounded-lg border border-border bg-fury-white/88 p-2 shadow-sm backdrop-blur sm:grid-cols-[1fr_auto]"
                onSubmit={onTeamSearch}
              >
                <input
                  className="min-w-0 rounded-md border border-border bg-bg px-4 py-3 text-sm outline-none transition focus:border-ring focus:shadow-[0_0_0_3px_var(--color-shadow)]"
                  list="team-search-suggestions"
                  placeholder={searchPlaceholder}
                  value={teamQuery}
                  onChange={(event) => setTeamQuery(event.target.value)}
                />
                <datalist id="team-search-suggestions">
                  {teamSuggestions.map((result) => (
                    <option
                      key={`${result.kind}-${result.uid || result.name}`}
                      label={result.detail}
                      value={result.value}
                    />
                  ))}
                </datalist>
                <button
                  className="rounded-md bg-fury-cyan px-5 py-3 text-sm font-semibold text-text transition hover:bg-fury-aqua"
                  type="submit"
                >
                  Search
                </button>
              </form>

              <button
                className="mt-4 grid w-full gap-3 border-l-2 border-fury-cyan bg-fury-white/80 px-4 py-3 text-left shadow-sm backdrop-blur transition hover:bg-surface sm:grid-cols-[1fr_auto] sm:items-center"
                onClick={() => navigate(latestBlogPost?.path || '/blog')}
                type="button"
              >
                <span className="min-w-0">
                  <span className="block text-xs font-semibold uppercase tracking-wide text-fury-cyan">
                    Latest news
                  </span>
                  <span className="mt-1 block truncate text-sm font-semibold text-text">
                    {latestBlogPost?.title || 'News and updates'}
                  </span>
                  <span className="mt-1 block truncate text-xs text-text-soft">
                    {latestBlogPost?.excerpt || 'Read the latest notes from Toothless TSS Bot.'}
                  </span>
                </span>
                <span className="text-sm font-semibold text-fury-cyan">
                  {latestBlogPost ? 'Read post' : 'Open blog'}
                </span>
              </button>
            </div>
          </div>

          <div className="relative min-h-[500px] overflow-hidden">
            <FallingLeaves treeRef={treeRef} />
            <div className="absolute inset-0 z-[1] flex items-end justify-center">
              <Tree ref={treeRef} />
            </div>
          </div>

          <div className="absolute bottom-5 left-1/2 z-20 hidden -translate-x-1/2 flex-col items-center gap-2 text-xs font-semibold uppercase tracking-wide text-text-soft sm:flex">
            <span>Scroll</span>
            <span className="h-10 w-[2px] overflow-hidden rounded-full bg-border">
              <span className="block h-4 w-full animate-[scrollPulse_1.5s_ease-in-out_infinite] rounded-full bg-fury-cyan" />
            </span>
          </div>
        </section>
      </div>

      <LandingOverview teams={teams} teamsToWatch={teamsToWatch} matches={matches} navigate={navigate} />
      <RecentGamesSection live={live} matches={matches} navigate={navigate} />
      <LandingTrustSection navigate={navigate} />
    </div>
  )
}

function LandingOverview({ teams, teamsToWatch, matches, navigate }) {
  const activeTeams = teamsToWatch.slice(0, 4)
  const visibleTeamCount = teams.length || teamsToWatch.length
  const totalPlayers = matches.reduce((sum, match) => sum + Number(match.player_count || 0), 0)
  const latestMatch = matches[0]

  return (
    <section className="relative z-20 border-t border-border bg-bg px-5 py-12 sm:px-8">
      <div className="mx-auto grid w-full max-w-7xl gap-10 lg:grid-cols-[0.9fr_1.1fr] lg:items-start">
        <div>
          <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
            What it does
          </p>
          <h2 className="mt-2 text-3xl font-bold">A clean window into TSS activity</h2>
          <p className="mt-4 max-w-2xl text-base leading-7 text-text-soft">
            Track teams, inspect recent battles, and jump from a leaderboard name to a
            profile without touching the god awful TSS website.
          </p>

          <div className="mt-6 grid gap-3 sm:grid-cols-3">
            <LandingMetric label="Teams indexed" value={formatNumber(visibleTeamCount)} />
            <LandingMetric label="Recent games" value={formatNumber(matches.length)} />
            <LandingMetric label="Players seen" value={formatNumber(totalPlayers)} />
          </div>
        </div>

        <div className="grid gap-4 md:grid-cols-2">
          <LandingFeature
            action="Open leaderboard"
            description="We track teams so you don't have to."
            onClick={() => navigate('/teams')}
            title="Team discovery"
          />
          <LandingFeature
            action="Read battle logs"
            description="Recent games, map names, player counts, and combat stat summaries."
            onClick={() => navigate('/battle-logs')}
            title="Battle context"
          />
          <LandingFeature
            action="Check uptime"
            description="Track if TSS data not showing up is our fault or Gaijins, wink wink."
            onClick={() => navigate('/uptime')}
            title="Operational status"
          />
          <LandingFeature
            action="View analytics"
            description="Track what other people are watching utilising opt in data analytics."
            onClick={() => navigate('/viewers')}
            title="Viewer pulse"
          />
        </div>
      </div>

      <div className="mx-auto mt-10 grid w-full max-w-7xl gap-4 lg:grid-cols-[1fr_1fr]">
        <div className="border-l border-border bg-fury-white px-5 py-5">
          <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
            Latest signal
          </p>
          <h3 className="mt-2 text-2xl font-bold">
            {latestMatch?.map_name || 'Waiting for the next battle'}
          </h3>
          <p className="mt-2 text-sm leading-6 text-text-soft">
            {latestMatch
              ? `${latestMatch.team_name || 'A TSS team'} played with ${formatNumber(latestMatch.player_count)} players.`
              : 'Latest match data will appear here once the data proxy returns games.'}
          </p>
        </div>

        <div className="border-l border-border bg-fury-white px-5 py-5">
          <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
            Teams to watch
          </p>
          <div className="mt-3 grid gap-2 sm:grid-cols-2">
            {activeTeams.length ? (
              activeTeams.map((team) => {
                const name = bestTeamName(team)
                return (
                  <button
                    className="truncate rounded-md border border-border bg-bg px-3 py-2 text-left text-sm font-semibold transition hover:border-ring hover:bg-surface"
                    key={name}
                    onClick={() => navigate(teamPath(name))}
                    type="button"
                  >
                    {name}
                  </button>
                )
              })
            ) : (
              <p className="text-sm text-text-soft">Team data is still loading.</p>
            )}
          </div>
        </div>
      </div>
    </section>
  )
}

function LandingMetric({ label, value }) {
  return (
    <div className="border-l border-border pl-4">
      <p className="text-2xl font-bold text-text">{value}</p>
      <p className="mt-1 text-xs font-semibold uppercase tracking-wide text-text-soft">{label}</p>
    </div>
  )
}

function LandingFeature({ action, description, onClick, title }) {
  return (
    <article className="rounded-lg border border-border bg-fury-white p-5 shadow-sm">
      <h3 className="text-lg font-semibold">{title}</h3>
      <p className="mt-2 min-h-12 text-sm leading-6 text-text-soft">{description}</p>
      <button
        className="mt-4 text-sm font-semibold text-fury-cyan transition hover:text-text"
        onClick={onClick}
        type="button"
      >
        {action}
      </button>
    </article>
  )
}

function LandingTrustSection({ navigate }) {
  return (
    <section className="relative z-20 border-t border-border bg-bg px-5 py-10 sm:px-8">
      <div className="mx-auto grid w-full max-w-7xl gap-6 lg:grid-cols-[1.2fr_0.8fr] lg:items-center">
        <div>
          <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
            Brought to you by...
          </p>
          <h2 className="mt-2 text-3xl font-bold">Built by the team behind SREBot</h2>
          <p className="mt-3 max-w-3xl text-base leading-7 text-text-soft">
            TSSBot is built alongside SREBot, both providing different views into different aspects of "competitive" War Thunder.
          </p>
        </div>

        <div className="flex flex-wrap gap-3 lg:justify-end">
          <a
            className="apricot-button-text rounded-lg bg-text px-5 py-3 text-sm font-semibold text-bg transition hover:bg-fury-cyan"
            href="https://sre.pawjob.us/"
          >
            Visit SREBOT
          </a>
          <button
            className="rounded-lg border border-ring px-5 py-3 text-sm font-semibold text-fury-cyan transition hover:bg-surface"
            onClick={() => navigate('/privacy')}
            type="button"
          >
            Privacy notice
          </button>
        </div>
      </div>
    </section>
  )
}

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

  return (
    <section className="relative z-20 border-t border-border bg-fury-white px-5 py-10 sm:px-8">
      <div className="mx-auto w-full max-w-7xl">
        <div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
          <div>
            <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
              Recent activity
            </p>
            <h2 className="mt-1 text-3xl font-bold">Latest games</h2>
          </div>
          <button
            className="w-fit rounded-lg border border-ring px-4 py-2 text-sm font-semibold text-fury-cyan transition hover:bg-surface"
            onClick={() => navigate('/battle-logs')}
            type="button"
          >
            View Battle Logs
          </button>
        </div>

        <div className="mt-6 grid gap-4 lg:grid-cols-3">
          {recentMatches.map((match) => (
            <button
              className="rounded-lg border border-border bg-bg p-4 text-left shadow-sm transition hover:border-ring hover:bg-surface"
              key={match.session_id}
              onClick={() => navigate(gamePath(match.session_id))}
              type="button"
            >
              <div className="flex items-start justify-between gap-3">
                <div className="min-w-0">
                  <h3 className="truncate text-lg font-semibold">
                    {match.map_name || 'Unknown map'}
                  </h3>
                  <p className="mt-1 text-xs text-text-soft">{formatDate(match.timestamp)}</p>
                </div>
              </div>

              <div className="mt-4 grid grid-cols-[1fr_auto] items-center gap-3 text-sm">
                <ParticipantNames participants={gameParticipants(match)} />
                <p className="text-text-soft">
                  {formatNumber(match.player_count)} players
                </p>
              </div>
              <div className="mt-3 flex flex-wrap gap-2 text-xs text-text-soft">
                <span>{formatNumber(match.stats?.deaths)} deaths</span>
              </div>
            </button>
          ))}
        </div>

        {!recentMatches.length ? (
          <p className="mt-6 rounded-lg border border-border bg-bg px-5 py-6 text-sm text-text-soft">
            {live.status === 'loading' ? 'Loading latest games' : live.error || 'No games returned'}
          </p>
        ) : null}
      </div>
    </section>
  )
}

const cachedPixelMountainsCanvases = new Map()

function renderPixelMountainsCanvas(theme = 'light') {
  const isDark = theme === 'dark'
  const palette =
    isDark
      ? ['#332614', '#4c341b', '#68431f', '#8a5824']
      : ['#fcfbcf', '#fff2e6', '#fee5cd', '#fdca9b']
  const cached = cachedPixelMountainsCanvases.get(theme)
  if (cached) return cached

  const canvas = document.createElement('canvas')
  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],
      ],
      palette[0],
      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],
      ],
      palette[1],
      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],
      ],
      palette[2],
      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],
      ],
      palette[3],
      1.4,
    )
  }

  draw()
  cachedPixelMountainsCanvases.set(theme, canvas)
  return canvas
}

function PixelMountains() {
  const [activeTheme, setActiveTheme] = useState(() =>
    document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light',
  )
  const [previousTheme, setPreviousTheme] = useState(null)
  const [skyTransition, setSkyTransition] = useState('')
  const [skyPaths, setSkyPaths] = useState({})
  const activeCanvasRef = useRef(null)
  const previousCanvasRef = useRef(null)
  const skyRef = useRef(null)
  const fadeTimerRef = useRef(null)
  const skyTimerRef = useRef(null)
  const themeSyncedRef = useRef(false)

  function drawCanvas(canvas, theme) {
    if (!canvas || !theme) return
    const source = renderPixelMountainsCanvas(theme)
    const ctx = canvas.getContext('2d')

    canvas.width = source.width
    canvas.height = source.height
    ctx.imageSmoothingEnabled = false
    ctx.drawImage(source, 0, 0)
  }

  useEffect(() => {
    drawCanvas(activeCanvasRef.current, activeTheme)
  }, [activeTheme])

  useEffect(() => {
    drawCanvas(previousCanvasRef.current, previousTheme)
  }, [previousTheme])

  useEffect(() => {
    function syncTheme() {
      const theme = document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light'
      setActiveTheme((current) => {
        if (theme === current) return current
        if (!themeSyncedRef.current) return theme
        window.clearTimeout(fadeTimerRef.current)
        window.clearTimeout(skyTimerRef.current)
        setPreviousTheme(current)
        setSkyTransition(`${current}-to-${theme}`)
        fadeTimerRef.current = window.setTimeout(() => setPreviousTheme(null), 440)
        skyTimerRef.current = window.setTimeout(() => setSkyTransition(''), 900)
        return theme
      })
    }

    syncTheme()
    themeSyncedRef.current = true
    const observer = new MutationObserver(syncTheme)
    observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] })
    return () => {
      observer.disconnect()
      window.clearTimeout(fadeTimerRef.current)
      window.clearTimeout(skyTimerRef.current)
    }
  }, [])

  useEffect(() => {
    function quadraticPath(start, control, end) {
      return `path("M ${start.x.toFixed(1)} ${start.y.toFixed(1)} Q ${control.x.toFixed(1)} ${control.y.toFixed(1)} ${end.x.toFixed(1)} ${end.y.toFixed(1)}")`
    }

    function updateSkyPaths() {
      const rect = skyRef.current?.getBoundingClientRect()
      const width = rect?.width || window.innerWidth || 1920
      const height = rect?.height || window.innerHeight || 900
      const display = { x: width * 0.78, y: height * 0.18 }
      const westSet = { x: width * 0.08, y: height * 0.67 }
      const eastRise = { x: width * 0.94, y: height * 0.67 }
      const westControl = { x: width * 0.46, y: height * -0.05 }
      const eastControl = { x: width * 0.98, y: height * -0.03 }

      setSkyPaths({
        '--celestial-exit-path': quadraticPath(display, westControl, westSet),
        '--celestial-enter-path': quadraticPath(eastRise, eastControl, display),
      })
    }

    updateSkyPaths()
    window.addEventListener('resize', updateSkyPaths)
    return () => window.removeEventListener('resize', updateSkyPaths)
  }, [])

  return (
    <div className="pixel-mountains" aria-hidden="true">
      <div
        className={`pixel-sky pixel-sky-${skyTransition || activeTheme}`}
        ref={skyRef}
        style={skyPaths}
      >
        <PixelSun className="pixel-sun" aria-hidden="true" />
        <span className="pixel-moon-group">
          <PixelMoon className="pixel-moon" aria-hidden="true" />
          <span className="pixel-star pixel-star-a" />
          <span className="pixel-star pixel-star-b" />
          <span className="pixel-star pixel-star-c" />
        </span>
      </div>
      {previousTheme ? (
        <canvas
          className="pixel-mountains-previous"
          ref={previousCanvasRef}
        />
      ) : null}
      <canvas className="pixel-mountains-active" ref={activeCanvasRef} />
    </div>
  )
}

function TeamsPage({ leaderboard, navigate, teams }) {
  return (
    <section className="space-y-6 pt-24 sm:pt-28">
      <div>
        <h1 className="text-3xl font-bold">Team Leaderboard</h1>
        <p className="mt-2 text-sm text-text-soft">
          {leaderboard.status === 'loading'
            ? 'Loading leaderboard'
            : leaderboard.error || `${teams.length} teams returned`}
        </p>
      </div>

      <div className="overflow-hidden rounded-lg border border-border bg-fury-white shadow-sm">
        {teams.map((team, index) => {
          const name = bestTeamName(team)
          return (
            <button
              className="grid w-full gap-4 border-b border-surface px-5 py-4 text-left transition hover:bg-surface md:grid-cols-[4rem_1fr_repeat(4,auto)] md:items-center"
              key={`${name}-${team.clan_id || index}`}
              onClick={() => navigate(teamPath(name))}
              type="button"
            >
              <span className="text-sm font-semibold text-fury-cyan">#{index + 1}</span>
              <span className="min-w-0">
                <span className="block truncate text-lg font-semibold">{name}</span>
              </span>
              <span className="text-sm">{formatNumber(team.player_count)} players</span>
              <span className="text-sm">{formatNumber(team.total_battles)} battles</span>
              <span className="text-sm">{Number(team.win_rate || 0).toFixed(1)}% WR</span>
              <span className="text-sm font-semibold">
                {formatNumber(team.points?.total_points || team.total_kills)}
              </span>
            </button>
          )
        })}

        {!teams.length ? (
          <p className="px-5 py-10 text-sm text-text-soft">
            {leaderboard.status === 'loading' ? 'Loading leaderboard' : 'No teams returned'}
          </p>
        ) : null}
      </div>
    </section>
  )
}

function PlayersPage({ leaderboard, navigate, players }) {
  return (
    <section className="space-y-6 pt-24 sm:pt-28">
      <div>
        <h1 className="text-3xl font-bold">Player Leaderboard</h1>
        <p className="mt-2 text-sm text-text-soft">
          {leaderboard.status === 'loading'
            ? 'Loading player leaderboard'
            : leaderboard.error || `${players.length} players returned`}
        </p>
      </div>

      <div className="overflow-hidden rounded-lg border border-border bg-fury-white shadow-sm">
        <div className="overflow-x-auto">
          <div className="min-w-[900px]">
            {players.length ? (
              <div className="grid grid-cols-[4rem_minmax(220px,1fr)_repeat(7,92px)] gap-3 border-b border-surface px-5 py-3 text-xs font-semibold uppercase tracking-wide text-text-soft">
                <p>Rank</p>
                <p>Player</p>
                <p className="text-right">Score</p>
                <p className="text-right">Battles</p>
                <p className="text-right">Kills</p>
                <p className="text-right">Assists</p>
                <p className="text-right">WR</p>
                <p className="text-right">KDR</p>
                <p className="text-right">Teams</p>
              </div>
            ) : null}

            {players.map((player, index) => (
              <button
                className="grid w-full grid-cols-[4rem_minmax(220px,1fr)_repeat(7,92px)] gap-3 border-b border-surface px-5 py-4 text-left text-sm transition hover:bg-surface"
                key={player.uid}
                onClick={() => navigate(playerPath(player.uid))}
                type="button"
              >
                <p className="font-semibold text-fury-cyan">#{index + 1}</p>
                <div className="min-w-0">
                  <p className="truncate text-base font-semibold">{player.nick || player.uid}</p>
                  <p className="truncate text-xs text-text-soft">
                    {player.uid} · last seen {formatDate(player.last_seen)}
                  </p>
                </div>
                <p className="text-right font-semibold">{formatNumber(player.score)}</p>
                <p className="text-right">{formatNumber(player.total_battles)}</p>
                <p className="text-right">{formatNumber(player.total_kills)}</p>
                <p className="text-right">{formatNumber(player.assists)}</p>
                <p className="text-right">{Number(player.win_rate || 0).toFixed(1)}%</p>
                <p className="text-right">{Number(player.kdr || 0).toFixed(2)}</p>
                <p className="text-right">{formatNumber(player.teams_seen)}</p>
              </button>
            ))}
          </div>
        </div>

        {!players.length ? (
          <p className="px-5 py-10 text-sm text-text-soft">
            {leaderboard.status === 'loading'
              ? 'Loading player leaderboard'
              : leaderboard.error || 'No players returned'}
          </p>
        ) : null}
      </div>
    </section>
  )
}

function TeamProfilePage({ navigate, profile, requestedTeam, teams }) {
  const detail = profile.detail.data
  const summary = detail?.team_summary || detail?.squadron_summary
  const players = detail?.players || []
  const games = profile.games.data?.games || []
  const leaderboardTeam = teams.find((team) => bestTeamName(team) === requestedTeam)
  const displayName = detail?.name || bestTeamName(leaderboardTeam) || requestedTeam

  return (
    <section className="space-y-6 pt-24 sm:pt-28">
      <button
        className="text-sm font-semibold text-fury-cyan transition hover:text-text"
        onClick={() => navigate('/teams')}
        type="button"
      >
        Back to leaderboard
      </button>

      <div className="rounded-lg border border-border bg-fury-white p-6 shadow-sm">
        <div>
          <div>
            <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
              Team profile
            </p>
            <h1 className="mt-1 text-4xl font-bold">{displayName}</h1>
            <p className="mt-2 text-sm text-text-soft">
              {profile.detail.error || ''}
            </p>
          </div>
        </div>

        <div className="mt-6 grid gap-5 sm:grid-cols-2 xl:grid-cols-5">
          <Stat label="Roster" value={formatNumber(summary?.player_count)} />
          <Stat label="Battles" value={formatNumber(summary?.total_battles)} />
          <Stat label="Wins" value={formatNumber(summary?.wins)} />
          <Stat label="Win rate" value={`${Number(summary?.win_rate || 0).toFixed(1)}%`} />
          <Stat label="KDR" value={Number(summary?.kdr || 0).toFixed(1)} />
        </div>
      </div>

      <RosterTable players={players} status={profile.detail.status} />

      <BattleResults games={games} navigate={navigate} status={profile.games.status} />
    </section>
  )
}

const SCOREBOARD_GRID = 'grid-cols-[minmax(220px,1fr)_repeat(6,minmax(52px,72px))]'

// Battle-log lines are prefixed +/-/<space> for winner/loser/neither (matching
// the Discord diff format), so colour each line by its acting team.
function battleLineColor(line) {
  if (line.startsWith('+')) return 'text-win'
  if (line.startsWith('-')) return 'text-loss'
  return 'text-text-soft'
}

function formatLogTime(ms) {
  const totalSeconds = Math.floor(Number(ms || 0) / 1000)
  const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0')
  const seconds = String(totalSeconds % 60).padStart(2, '0')
  return `${minutes}:${seconds}`
}

function deadVehicleKey(uid, cdk) {
  return `${String(uid || '').trim()}:${String(cdk || '').trim()}`
}

function deadVehicleKeysFromEventLog(eventLog) {
  const keys = new Set()
  const kills = Array.isArray(eventLog?.kills) ? eventLog.kills : []
  kills.forEach((kill) => {
    const uid = kill?.offended_uid
    const cdk = kill?.offended_unit
    if (uid !== undefined && uid !== null && cdk) {
      keys.add(deadVehicleKey(uid, cdk))
    }
  })
  return keys
}

function logLookups(participants) {
  const players = new Map()
    ; (participants || []).forEach((participant) => {
      const result = String(participant.result || '').toLowerCase() === 'win' ? 'win' : 'loss'
        ; (participant.players || []).forEach((player) => {
          const vehicles = new Map()
            ; (player.vehicles || []).forEach((vehicle) => {
              vehicles.set(String(vehicle.cdk || ''), vehicle.name || vehicle.cdk || 'Unknown')
            })
          players.set(String(player.uid), {
            name: player.nick || player.uid,
            team: participant.team_name || '',
            result,
            className: result === 'win' ? 'text-win' : 'text-loss',
            vehicles,
          })
        })
    })
  return players
}

function logNameLookups(participants) {
  const players = new Map()
    ; (participants || []).forEach((participant) => {
      const result = String(participant.result || '').toLowerCase() === 'win' ? 'win' : 'loss'
        ; (participant.players || []).forEach((player) => {
          const name = String(player.nick || '').trim()
          if (!name) return
          players.set(name.toLowerCase(), {
            name,
            team: participant.team_name || '',
            result,
            className: result === 'win' ? 'text-win' : 'text-loss',
          })
        })
    })
  return players
}

function logPlayer(players, uid) {
  return players.get(String(uid)) || {
    name: uid === undefined || uid === null ? 'Unknown' : `Player#${uid}`,
    team: '',
    result: '',
    className: 'text-text-soft',
    vehicles: new Map(),
  }
}

function logVehicle(player, cdk) {
  if (!cdk) return 'Unknown'
  return player.vehicles.get(String(cdk)) || String(cdk)
}

function structuredBattleEvents(eventLog) {
  const kills = Array.isArray(eventLog?.kills) ? eventLog.kills : []
  const damage = Array.isArray(eventLog?.damage) ? eventLog.damage : []
  return [
    ...kills.map((event) => ({ ...event, kind: 'kill' })),
    ...damage.map((event) => ({ ...event, kind: 'damage' })),
  ].sort((a, b) => Number(a.time || 0) - Number(b.time || 0))
}

function chatTypeClass(type, senderClassName) {
  return String(type || 'ALL').toUpperCase() === 'ALL' ? 'text-warning' : senderClassName
}

function parseFormattedChatLine(line) {
  const match = String(line || '').match(/^([+-]?)(\[\d{2}:\d{2}\])\s+\[([^\]]+)\]\s+\[[^\]]*\]\s+`([^`]*)`:\s?(.*)$/)
  if (!match) return null
  return {
    prefix: match[1],
    time: match[2],
    type: match[3],
    name: match[4],
    message: match[5],
  }
}

function GamePage({ gameId, navigate }) {
  const [gameState, setGameState] = useState({ status: 'loading', data: null, error: null })
  const [logs, setLogs] = useState({ chat_log: [], battle_log: [], event_log: { kills: [], damage: [], chat: [] } })

  useEffect(() => {
    if (!gameId) return

    const controller = new AbortController()
    setGameState({ status: 'loading', data: null, error: null })
    setLogs({ chat_log: [], battle_log: [], event_log: { kills: [], damage: [], chat: [] } })

    fetchPublicJson(publicDataSources.game(gameId), controller.signal)
      .then((data) => {
        if (!controller.signal.aborted) {
          setGameState({ status: 'ready', data, error: null })
        }
      })
      .catch((error) => {
        if (!controller.signal.aborted) {
          setGameState({ status: 'error', data: null, error: error.message })
        }
      })

    fetchPublicJson(publicDataSources.gameLogs(gameId), controller.signal)
      .then((data) => {
        if (!controller.signal.aborted) {
          setLogs({
            chat_log: Array.isArray(data?.chat_log) ? data.chat_log : [],
            battle_log: Array.isArray(data?.battle_log) ? data.battle_log : [],
            event_log: data?.event_log || { kills: [], damage: [], chat: [] },
          })
        }
      })
      .catch(() => {
        // Logs are non-critical; leave them empty on failure.
      })

    return () => controller.abort()
  }, [gameId])

  const game = gameState.data?.game
  const participants = useMemo(() => gameState.data?.participants || [], [gameState.data?.participants])
  const deadVehicleKeys = useMemo(() => deadVehicleKeysFromEventLog(logs.event_log), [logs.event_log])
  const playersByUid = useMemo(() => logLookups(participants), [participants])
  const playersByName = useMemo(() => logNameLookups(participants), [participants])
  const battleEvents = useMemo(() => structuredBattleEvents(logs.event_log), [logs.event_log])
  const chatEvents = Array.isArray(logs.event_log?.chat) ? logs.event_log.chat : []
  const participantNames = participants.length
    ? participants.map((participant) => ({
      name: participant.team_name,
      result: String(participant.result || '').toLowerCase() === 'win' ? 'win' : 'loss',
    }))
    : gameParticipants(game)

  const subtitle = [game?.mission_mode, game?.tournament_name].filter(Boolean).join(' · ')
  const duration = formatDuration(game?.duration)

  return (
    <section className="space-y-6 pt-24 sm:pt-28">
      <button
        className="text-sm font-semibold text-fury-cyan transition hover:text-text"
        onClick={() => navigate('/battle-logs')}
        type="button"
      >
        Back to battle logs
      </button>

      <div className="rounded-lg border border-border bg-fury-white p-6 shadow-sm">
        <div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
          <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
            Game — <span className="break-all text-text-muted opacity-70">{game?.session_id || gameId}</span>
          </p>
          {game?.tournament_id ? (
            <button
              className="text-sm font-semibold uppercase tracking-wide text-fury-violet transition hover:text-text"
              onClick={() => navigate(tournamentPath(game.tournament_id))}
              type="button"
            >
              Tournament — {game.tournament_id}
            </button>
          ) : null}
        </div>
        <div className="mt-1 flex flex-wrap items-center gap-3">
          <h1 className="text-4xl font-bold">{game?.map_name || 'Battle log'}</h1>
          {game?.draw ? (
            <span className="rounded bg-surface px-2 py-1 text-xs font-semibold uppercase tracking-wide text-text-soft">
              Draw
            </span>
          ) : null}
        </div>
        {subtitle ? (
          game?.tournament_id ? (
            <button
              className="mt-1 block text-left text-sm font-medium text-fury-violet underline-offset-2 transition hover:text-text hover:underline"
              onClick={() => navigate(tournamentPath(game.tournament_id))}
              type="button"
            >
              {subtitle}
            </button>
          ) : (
            <p className="mt-1 text-sm font-medium text-fury-violet">{subtitle}</p>
          )
        ) : null}
        <p className="mt-2 text-sm text-text-soft">
          {game ? formatDate(game.timestamp) : ''}
          {duration ? ` · ${duration}` : ''}
        </p>
        <div className="mt-3">
          <ParticipantNames participants={participantNames} />
        </div>

        {gameState.status === 'error' ? (
          <p className="mt-4 text-sm text-danger">{gameState.error}</p>
        ) : null}
      </div>

      <div className="rounded-lg border border-border bg-fury-white shadow-sm">
        <div className="overflow-x-auto">
          <div className="min-w-[720px]">
            {participants.length ? (
              <div className={`grid ${SCOREBOARD_GRID} gap-3 border-b border-border px-5 py-3 text-xs font-semibold uppercase tracking-wide text-text-soft`}>
                <p>Team / player</p>
                <p className="text-center">Air</p>
                <p className="text-center">Ground</p>
                <p className="text-center">Assists</p>
                <p className="text-center">Deaths</p>
                <p className="text-center">Caps</p>
                <p className="text-center">Score</p>
              </div>
            ) : null}

            {participants.map((participant) => {
              const won = String(participant.result || '').toLowerCase() === 'win'
              const accent = won ? 'border-l-win' : 'border-l-loss'
              const nameColor = won ? 'text-win' : 'text-loss'
              return (
                <div className={`border-b-4 border-border border-l-4 ${accent}`} key={participant.team_name}>
                  <button
                    className={`grid w-full ${SCOREBOARD_GRID} items-center gap-3 border-b border-border bg-surface/60 px-5 py-3 text-left transition hover:bg-surface`}
                    onClick={() => navigate(teamPath(participant.team_name))}
                    type="button"
                  >
                    <div className="min-w-0">
                      <p className={`truncate font-bold ${nameColor}`}>
                        {participant.team_name}
                      </p>
                      <p className={`text-xs font-semibold uppercase tracking-wide ${nameColor}`}>
                        {won ? 'Win' : 'Loss'} · {formatNumber(participant.player_count)} players
                      </p>
                    </div>
                    <p className="text-center text-sm">{formatNumber(participant.stats?.air_kills)}</p>
                    <p className="text-center text-sm">{formatNumber(participant.stats?.ground_kills)}</p>
                    <p className="text-center text-sm">{formatNumber(participant.stats?.assists)}</p>
                    <p className="text-center text-sm">{formatNumber(participant.stats?.deaths)}</p>
                    <p className="text-center text-sm">{formatNumber(participant.stats?.captures)}</p>
                    <p className="text-center text-sm font-semibold">{formatNumber(participant.stats?.score)}</p>
                  </button>

                  <div className="divide-y divide-surface py-1">
                    {(participant.players || []).map((player) => (
                      <button
                        className={`grid w-full ${SCOREBOARD_GRID} items-center gap-3 px-5 py-2 text-left text-sm transition hover:bg-surface`}
                        key={player.uid}
                        onClick={() => navigate(playerPath(player.uid))}
                        type="button"
                      >
                        <div className="min-w-0 pl-4 sm:pl-8">
                          <p className="truncate font-semibold text-text">{player.nick || player.uid}</p>
                          <div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5">
                            {(player.vehicles || []).length ? (
                              player.vehicles.map((vehicle) => {
                                const isDead = deadVehicleKeys.has(deadVehicleKey(player.uid, vehicle.cdk))
                                return (
                                  <span
                                    className={`vehicle-name inline-flex items-center gap-1 text-xs text-text-soft transition-opacity ${isDead ? 'vehicle-dead' : ''}`}
                                    key={vehicle.cdk}
                                  >
                                    <img
                                      alt=""
                                      className="h-4 w-4 object-contain"
                                      loading="lazy"
                                      onError={(event) => { event.currentTarget.style.display = 'none' }}
                                      src={`/vehicle-icons/${vehicle.icon}`}
                                    />
                                    {vehicle.name}
                                  </span>
                                )
                              })
                            ) : (
                              <span className="text-xs text-text-muted">{player.uid}</span>
                            )}
                          </div>
                        </div>
                        <p className="text-center">{formatNumber(player.stats?.air_kills)}</p>
                        <p className="text-center">{formatNumber(player.stats?.ground_kills)}</p>
                        <p className="text-center">{formatNumber(player.stats?.assists)}</p>
                        <p className="text-center">{formatNumber(player.stats?.deaths)}</p>
                        <p className="text-center">{formatNumber(player.stats?.captures)}</p>
                        <p className="text-center font-semibold">{formatNumber(player.stats?.score)}</p>
                      </button>
                    ))}
                  </div>
                </div>
              )
            })}
          </div>

          {!participants.length ? (
            <p className="px-5 py-10 text-sm text-text-soft">
              {gameState.status === 'loading' ? 'Loading game' : 'No participants returned'}
            </p>
          ) : null}
        </div>
      </div>

      <ReplayCanvasPanel gameId={gameId} />

      {battleEvents.length || logs.battle_log.length ? (
        <details className="rounded-lg border border-border bg-fury-white shadow-sm">
          <summary className="cursor-pointer px-5 py-4 font-semibold">Battle Log</summary>
          <div className="log-mono overflow-x-auto px-5 py-3 text-xs leading-relaxed">
            {battleEvents.length ? (
              battleEvents.map((event, i) => (
                <BattleEventLine event={event} key={`${event.kind}-${event.time}-${i}`} players={playersByUid} />
              ))
            ) : (
              logs.battle_log.map((line, i) => (
                <div className={`whitespace-pre ${battleLineColor(line)}`} key={i}>{line}</div>
              ))
            )}
          </div>
        </details>
      ) : null}

      {chatEvents.length || logs.chat_log.length ? (
        <details className="rounded-lg border border-border bg-fury-white shadow-sm">
          <summary className="cursor-pointer px-5 py-4 font-semibold">Chat Log</summary>
          <div className="log-mono overflow-x-auto px-5 py-3 text-xs leading-relaxed">
            {chatEvents.length ? (
              chatEvents.map((event, i) => (
                <ChatEventLine event={event} key={`${event.time}-${event.uid}-${i}`} players={playersByUid} />
              ))
            ) : (
              logs.chat_log.map((line, i) => (
                <FormattedChatLine key={i} line={line} players={playersByName} />
              ))
            )}
          </div>
        </details>
      ) : null}
    </section>
  )
}

function FormattedChatLine({ line, players }) {
  const parsed = parseFormattedChatLine(line)
  if (!parsed) {
    return <div className={`whitespace-pre-wrap ${battleLineColor(line)}`}>{line}</div>
  }

  const player = players.get(parsed.name.toLowerCase())
  const className = player?.className || battleLineColor(parsed.prefix)
  const team = player?.team || '??'
  const name = player?.name || parsed.name
  const typeClassName = chatTypeClass(parsed.type, className)

  return (
    <div className="whitespace-pre-wrap">
      <span className="text-text-soft">{parsed.time} </span>
      <span className={typeClassName}>[{parsed.type}]</span>
      <span className={className}> [{team}] `{name}`: {parsed.message}</span>
    </div>
  )
}

function BattleEventLine({ event, players }) {
  const offender = logPlayer(players, event.offender_uid)
  const victim = logPlayer(players, event.offended_uid)
  const ts = formatLogTime(event.time)
  const victimLabel = `[${victim.team}] ${victim.name} (${logVehicle(victim, event.offended_unit)})`

  if (event.kind === 'kill' && (event.crashed || event.offender_uid === undefined || event.offender_uid === null)) {
    return (
      <div className="whitespace-pre">
        <span className="text-text-soft">[{ts}] </span>
        <span className={victim.className}>[{victim.team}] {victimLabel}</span>
        <span className="text-text-soft"> crashed</span>
      </div>
    )
  }

  const offenderLabel = `${offender.name} (${logVehicle(offender, event.offender_unit)})`
  const action = event.kind === 'kill' ? 'destroyed' : `damaged ${event.afire ? '(FIRE) ' : ''}`

  return (
    <div className="whitespace-pre">
      <span className="text-text-soft">[{ts}] </span>
      <span className={offender.className}>[{offender.team}] {offenderLabel}</span>
      <span className="text-text-soft"> {action} </span>
      <span className={victim.className}>{victimLabel}</span>
    </div>
  )
}

function ChatEventLine({ event, players }) {
  const player = logPlayer(players, event.uid)
  const type = event.type || 'ALL'
  const typeClassName = chatTypeClass(type, player.className)
  return (
    <div className="whitespace-pre-wrap">
      <span className="text-text-soft">[{formatLogTime(event.time)}] </span>
      <span className={typeClassName}>[{type}]</span>
      <span className={player.className}> [{player.team}] `{player.name}`: {event.message || ''}</span>
    </div>
  )
}

function RosterTable({ players, status }) {
  const sortedPlayers = [...players].sort((a, b) => {
    return (b.total_kills || 0) - (a.total_kills || 0) || String(a.nick || '').localeCompare(b.nick || '')
  })

  return (
    <div className="rounded-lg border border-border bg-fury-white shadow-sm">
      <div className="border-b border-surface px-5 py-4">
        <h2 className="text-lg font-semibold">Roster</h2>
        <p className="mt-1 text-sm text-text-soft">{formatNumber(sortedPlayers.length)} players</p>
      </div>
      <div className="max-h-[520px] overflow-auto">
        {sortedPlayers.map((player) => (
          <div
            className="grid gap-3 border-b border-surface px-5 py-3 text-sm md:grid-cols-[1fr_repeat(5,auto)] md:items-center"
            key={player.uid}
          >
            <div className="min-w-0">
              <p className="truncate font-semibold">{player.nick || player.uid}</p>
              <p className="text-xs text-text-soft">
                {player.uid} · {formatNumber(player.points || player.sqb_points)} pts
              </p>
            </div>
            <p>{formatNumber(player.total_battles)} battles</p>
            <p>{formatNumber(player.total_kills)} kills</p>
            <p>{Number(player.win_rate || 0).toFixed(1)}% WR</p>
            <p>{Number(player.kdr || 0).toFixed(1)} KDR</p>
            <p>{formatNumber(player.assists)} assists</p>
          </div>
        ))}
        {!sortedPlayers.length ? (
          <p className="px-5 py-10 text-sm text-text-soft">
            {status === 'loading' ? 'Loading roster' : 'No roster rows returned'}
          </p>
        ) : null}
      </div>
    </div>
  )
}

function BattleResults({ games, navigate, status }) {
  return (
    <div className="rounded-lg border border-border bg-fury-white shadow-sm">
      <div className="border-b border-surface px-5 py-4">
        <h2 className="text-lg font-semibold">Battle results</h2>
        <p className="mt-1 text-sm text-text-soft">{formatNumber(games.length)} battles returned</p>
      </div>
      <div className="max-h-[560px] overflow-auto">
        {games.map((game) => (
          <button
            className="grid w-full gap-4 border-b border-surface px-5 py-4 text-left transition hover:bg-surface md:grid-cols-[1fr_minmax(10rem,0.8fr)_repeat(3,auto)] md:items-center"
            key={game.session_id}
            onClick={() => navigate(gamePath(game.session_id))}
            type="button"
          >
            <div className="min-w-0">
              <p className="truncate font-semibold">{game.map_name || 'Unknown map'}</p>
              <p className="text-xs text-text-soft">
                {formatDate(game.timestamp)} · {game.session_id}
              </p>
            </div>
            <ParticipantNames participants={gameParticipants(game)} />
            <p className="text-sm">{formatNumber(game.player_count)} players</p>
            <p className="text-sm">{formatNumber(game.stats?.assists)} assists</p>
            <p className="text-sm">{formatNumber(game.stats?.deaths)} deaths</p>
          </button>
        ))}
        {!games.length ? (
          <p className="px-5 py-10 text-sm text-text-soft">
            {status === 'loading' ? 'Loading battle results' : 'No battle results returned'}
          </p>
        ) : null}
      </div>
    </div>
  )
}

function BattleLogsPage({ live, matches, navigate }) {
  return (
    <section className="space-y-6 pt-24 sm:pt-28">
      <div>
        <h1 className="text-3xl font-bold">Battle Logs</h1>
        <p className="mt-2 text-sm text-text-soft">
          {live.status === 'loading' ? 'Loading battles' : live.error || `${matches.length} battles returned`}
        </p>
      </div>

      <div className="overflow-hidden rounded-lg border border-border bg-fury-white shadow-sm">
        {matches.map((match) => (
          <button
            className="grid w-full gap-x-6 gap-y-2 border-b border-surface px-5 py-4 text-left transition hover:bg-surface md:grid-cols-[minmax(0,1fr)_minmax(18rem,0.9fr)_auto] md:items-center"
            key={match.session_id}
            onClick={() => navigate(gamePath(match.session_id))}
            type="button"
          >
            <div className="min-w-0">
              <p className="truncate font-semibold">{match.map_name || 'Unknown map'}</p>
              <p className="text-xs text-text-soft">
                {formatDate(match.timestamp)} · {match.session_id}
              </p>
            </div>
            <ParticipantNames participants={gameParticipants(match)} spread />
            <p className="text-sm">{formatMatchSize(match.player_count)}</p>
          </button>
        ))}

        {!matches.length ? (
          <p className="px-5 py-10 text-sm text-text-soft">
            {live.status === 'loading' ? 'Loading battles' : 'No battles returned'}
          </p>
        ) : null}
      </div>
    </section>
  )
}

function formatUnixDate(seconds) {
  return seconds ? formatDate(seconds) : 'Unknown'
}

function tournamentDateRange(first, last) {
  if (!first && !last) return 'No dates'
  const a = formatUnixDate(first)
  const b = formatUnixDate(last)
  return a === b ? a : `${a} – ${b}`
}

function tournamentFormatMeta(format, matches = []) {
  const raw = String(format || '').toLowerCase()
  const sides = matches.map((match) => String(match.side || match.type_bracket || '').toLowerCase())
  const hasSide = (needle) => sides.some((side) => side.includes(needle))
  const hasGroup = raw === 'group' || hasSide('group')
  const hasSwiss = raw === 'swiss' || hasSide('swiss')
  const hasLoser = raw === 'double-elim' || hasSide('loser') || hasSide('looser')
  const hasElim = hasLoser || raw === 'single-elim' || hasSide('winner') || hasSide('final')

  if ((hasGroup || hasSwiss) && hasElim) {
    return { label: hasSwiss ? 'Swiss + playoffs' : 'Group stage + playoffs', mode: 'mixed' }
  }
  if (hasSwiss) return { label: 'Swiss', mode: 'standings' }
  if (hasGroup) return { label: 'Group stage', mode: 'standings' }
  if (hasLoser) return { label: 'Double elimination', mode: 'bracket' }
  if (raw === 'single-elim' || hasElim) return { label: 'Single elimination', mode: 'bracket' }
  return { label: 'Matches', mode: 'matches' }
}

function tournamentStatusLabel(status) {
  const raw = String(status || '').trim()
  if (!raw) return 'Unknown'
  return raw.charAt(0).toUpperCase() + raw.slice(1)
}

function TournamentsPage({ navigate }) {
  const [state, setState] = useState({ status: 'loading', data: null, error: null })

  useEffect(() => {
    const controller = new AbortController()
    setState({ status: 'loading', data: null, error: null })
    fetchJson(apiEndpoints.tournaments, controller.signal)
      .then((data) => setState({ status: 'ready', data, error: null }))
      .catch((error) => {
        if (!controller.signal.aborted) {
          setState({ status: 'error', data: null, error: error.message })
        }
      })
    return () => controller.abort()
  }, [])

  const tournaments = state.data?.tournaments || []

  return (
    <section className="space-y-6 pt-24 sm:pt-28">
      <div>
        <h1 className="text-3xl font-bold">Tournaments</h1>
        <p className="mt-2 text-sm text-text-soft">
          {state.status === 'loading'
            ? 'Loading tournaments'
            : state.error || `${tournaments.length} tournaments returned`}
        </p>
      </div>

      <div className="overflow-hidden rounded-lg border border-border bg-fury-white shadow-sm">
        {tournaments.map((tournament) => (
          <button
            className="grid w-full gap-x-4 gap-y-1 border-b border-surface px-5 py-4 text-left transition hover:bg-surface md:grid-cols-[1fr_repeat(3,auto)] md:items-center"
            key={tournament.tournament_id}
            onClick={() => navigate(tournamentPath(tournament.tournament_id))}
            type="button"
          >
            <div className="min-w-0">
              <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
                <p className="truncate text-lg font-semibold">
                  {tournament.name || `Tournament ${tournament.tournament_id}`}
                </p>
                <span className="shrink-0 rounded bg-surface px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-fury-violet">
                  {tournamentFormatMeta(tournament.format).label}
                </span>
              </div>
              <p className="text-xs text-text-soft">
                TID {tournament.tournament_id} · {tournamentDateRange(tournament.date_start, tournament.date_end)}
              </p>
            </div>
            <span className="text-sm">{formatNumber(tournament.match_count)} matches</span>
            <span className="text-sm">{formatNumber(tournament.team_count)} teams</span>
            <span className="text-sm font-semibold text-fury-cyan">View</span>
          </button>
        ))}

        {!tournaments.length ? (
          <p className="px-5 py-10 text-sm text-text-soft">
            {state.status === 'loading' ? 'Loading tournaments' : 'No tournaments returned'}
          </p>
        ) : null}
      </div>
    </section>
  )
}

function TournamentMatchCard({ match, navigate, onHover }) {
  const winner = displayTeamName(match.winner_name).toLowerCase()
  const teamA = displayTeamName(match.team_a_name)
  const teamB = displayTeamName(match.team_b_name)
  const aWon = winner && teamA && winner === teamA.toLowerCase()
  const bWon = winner && teamB && winner === teamB.toLowerCase()
  const decided = Boolean(winner)
  const battles = Array.isArray(match.battles) ? match.battles : []
  const fire = onHover || (() => { })
  const lastKey = useRef('')

  // Hover a team name → light just that team's run (green). Hover anywhere else on
  // the card → light both teams' runs (winner green, loser red). A registered team
  // that forfeited (technical walkover) shows gold rather than red.
  const matchHighlight = () => {
    const map = {}
    if (teamA) map[teamA.toLowerCase()] = !decided || aWon ? 'win' : 'loss'
    if (teamB) map[teamB.toLowerCase()] = !decided || bWon ? 'win' : 'loss'
    return Object.keys(map).length ? map : null
  }
  const handleOver = (event) => {
    const teamEl = event.target.closest('[data-team]')
    const map = teamEl ? { [teamEl.dataset.team.toLowerCase()]: 'win' } : matchHighlight()
    const key = map ? Object.entries(map).map(([k, v]) => `${k}:${v}`).sort().join('|') : ''
    if (key === lastKey.current) return
    lastKey.current = key
    fire(map)
  }
  const handleLeave = () => {
    lastKey.current = ''
    fire(null)
  }

  const teamRow = (name, score, won, emptyLabel = 'TBD') => {
    const lost = decided && name && !won
    const noShow = lost && match.status === 'technical'
    const nameColor = won ? 'text-win' : noShow ? 'text-fury-violet' : lost ? 'text-loss' : 'text-text'
    const scoreColor = won ? 'text-win' : noShow ? 'text-fury-violet' : lost ? 'text-loss' : 'text-text-soft'
    return (
      <div className="flex items-center justify-between gap-2">
        {name ? (
          <button
            className={`min-w-0 truncate text-left font-semibold transition hover:underline ${nameColor}`}
            data-team={name}
            onClick={(event) => {
              event.stopPropagation()
              if (bracketPan.dragged) return
              navigate(teamPath(name))
            }}
            type="button"
          >
            {name}
          </button>
        ) : (
          <span className="min-w-0 truncate font-semibold text-text-muted">{emptyLabel}</span>
        )}
        <span className={`shrink-0 tabular-nums ${scoreColor}`}>{formatNumber(score)}</span>
      </div>
    )
  }
  const emptyLabel = match.status === 'bye' ? 'BYE' : 'TBD'

  return (
    <div
      className="rounded-md border border-border bg-bg p-2.5 text-sm shadow-sm"
      onMouseLeave={handleLeave}
      onMouseOver={handleOver}
    >
      {teamRow(teamA, match.score_a, aWon, emptyLabel)}
      <div className="mt-1">{teamRow(teamB, match.score_b, bWon, emptyLabel)}</div>
      <div className="mt-2 flex items-center justify-between gap-2 text-[10px] font-semibold uppercase tracking-wide text-text-muted">
        <span>{tournamentStatusLabel(match.status)}</span>
        {match.position !== null && match.position !== undefined ? <span>Slot {Number(match.position) + 1}</span> : null}
      </div>
      {battles.length ? (
        <div className="mt-2 flex flex-wrap gap-1">
          {battles.map((battle, index) => (
            battle.have_replay ? (
              <button
                className="rounded bg-surface px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-text-soft transition hover:text-text"
                key={battle.session_hex}
                onClick={() => {
                  if (bracketPan.dragged) return
                  navigate(gamePath(battle.session_hex))
                }}
                title={`Game ${index + 1} — view replay`}
                type="button"
              >
                G{index + 1}
              </button>
            ) : (
              <span
                className="rounded bg-surface px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-text-muted opacity-70"
                key={battle.session_hex}
                title={`Game ${index + 1} — no replay held`}
              >
                G{index + 1}
              </span>
            )
          ))}
        </div>
      ) : null}
    </div>
  )
}

// Shared across the bracket: set true while a pan-drag is in progress so the
// team/replay buttons inside the canvas don't fire a navigation on release.
const bracketPan = { dragged: false }

function teamsOf(match) {
  return [match?.team_a_name, match?.team_b_name]
    .map((n) => displayTeamName(n).toLowerCase())
    .filter(Boolean)
}

function byeAdvanceNames(match) {
  return [match?.team_a_name, match?.team_b_name]
    .map(displayTeamName)
    .filter(Boolean)
}

function noOpponentTooltip(match, roundLabel) {
  const names = byeAdvanceNames(match)
  const teamText = names.length > 1
    ? `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`
    : names[0] || 'this team'
  const opener = names.length > 1
    ? 'One or more teams did not confirm in time'
    : 'A team did not confirm in time'
  return `${opener}, so Gaijin automatically advanced ${teamText} to ${roundLabel}.`
}

// Drag-anywhere pan surface. A tap still clicks; only motion past a small
// threshold pans (and suppresses the click). Move/up live on the window so a fast
// drag keeps panning even when the cursor leaves the canvas.
function BracketViewport({ children }) {
  const containerRef = useRef(null)
  const ref = useRef(null)
  const [fsActive, setFsActive] = useState(false)

  useEffect(() => {
    const handler = () => setFsActive(Boolean(document.fullscreenElement))
    document.addEventListener('fullscreenchange', handler)
    return () => document.removeEventListener('fullscreenchange', handler)
  }, [])

  const toggleFs = (event) => {
    event.stopPropagation()
    if (document.fullscreenElement) document.exitFullscreen()
    else containerRef.current?.requestFullscreen()
  }

  const onPointerDown = (event) => {
    const el = ref.current
    if (!el || event.button !== 0) return
    if (event.target.closest('[data-fs-btn]')) return
    const start = { x: event.clientX, y: event.clientY, sl: el.scrollLeft, st: el.scrollTop, moved: false }
    bracketPan.dragged = false

    const onMove = (e) => {
      const dx = e.clientX - start.x
      const dy = e.clientY - start.y
      if (!start.moved && Math.hypot(dx, dy) > 5) {
        start.moved = true
        bracketPan.dragged = true
        el.classList.add('is-grabbing')
        // Drop any text selection the initial press may have started.
        window.getSelection()?.removeAllRanges()
      }
      if (start.moved) {
        el.scrollLeft = start.sl - dx
        el.scrollTop = start.st - dy
        e.preventDefault()
      }
    }
    const onUp = () => {
      el.classList.remove('is-grabbing')
      window.removeEventListener('pointermove', onMove)
      window.removeEventListener('pointerup', onUp)
      // Keep `dragged` true through the click that fires right after pointerup so
      // that click is suppressed, then clear it.
      window.setTimeout(() => { bracketPan.dragged = false }, 0)
    }
    window.addEventListener('pointermove', onMove)
    window.addEventListener('pointerup', onUp)
  }

  return (
    <div className="bracket-fs-container" ref={containerRef}>
      <div className="bracket-viewport" onPointerDown={onPointerDown} ref={ref}>
        {children}
      </div>
      <button
        className="bracket-fs-btn"
        data-fs-btn=""
        onClick={toggleFs}
        title={fsActive ? 'Exit fullscreen' : 'Fullscreen'}
        type="button"
      >
        {fsActive ? '✕' : '⛶'}
      </button>
    </div>
  )
}

// What colour, if any, a team should glow: the highlight map is { teamLower:
// 'win' | 'loss' }. Winner-side runs glow green, loser runs red.
function traceClass(teams, highlight) {
  if (!highlight) return ''
  let result = ''
  for (const team of teams) {
    const tone = highlight[team]
    if (tone === 'win') return ' is-traced'
    if (tone === 'loss') result = ' is-traced-loss'
  }
  return result
}

function TournamentBracketSide({ side, navigate, highlight, onHover }) {
  const gridRef = useRef(null)
  const nodeRefs = useRef(new Map())
  // `tops` places each match; once placed we read back the DOM to draw connectors.
  const [layout, setLayout] = useState({ tops: new Map(), height: 0 })
  const [connectors, setConnectors] = useState({ width: 0, height: 0, lines: [], byes: [] })
  const [collapsed, setCollapsed] = useState(false)

  // Pass 1: measure card heights, then position every match centred on its feeders.
  useLayoutEffect(() => {
    const grid = gridRef.current
    if (!grid) return undefined
    const relayout = () => {
      const heights = new Map()
      for (const [key, el] of nodeRefs.current) heights.set(key, el.offsetHeight)
      const next = computeBracketLayout(side.columns, heights)
      setLayout({ tops: next.tops, height: next.height })
    }
    relayout()
    const observer = new ResizeObserver(relayout)
    observer.observe(grid)
    return () => observer.disconnect()
  }, [side, collapsed])

  // Pass 2: with matches positioned, read real edges and draw elbow connectors.
  // Each connector carries the teams on either end so a hovered team's whole run
  // can be lit. Matches with no incoming connector (all feeders were byes) get a
  // bye stub so they don't read as appearing from nowhere.
  useLayoutEffect(() => {
    const grid = gridRef.current
    if (!grid) return
    const gridBox = grid.getBoundingClientRect()
    const boxOf = (key) => {
      const el = nodeRefs.current.get(key)
      if (!el) return null
      const b = el.getBoundingClientRect()
      return {
        left: b.left - gridBox.left,
        right: b.right - gridBox.left,
        mid: b.top - gridBox.top + b.height / 2,
      }
    }
    const lines = []
    const fed = new Set()
    for (let c = 0; c < side.columns.length - 1; c += 1) {
      for (const match of side.columns[c].matches) {
        const parent = feederParent(match, c, side.columns)
        if (!parent) continue
        const from = boxOf(layoutKey(c, match))
        const to = boxOf(layoutKey(c + 1, parent))
        if (!from || !to) continue
        fed.add(parent.match_id)
        const midX = (from.right + to.left) / 2
        lines.push({
          d: `M ${from.right} ${from.mid} H ${midX} V ${to.mid} H ${to.left}`,
          teams: teamsOf(match).filter((t) => teamsOf(parent).includes(t)),
        })
      }
    }
    const byes = []
    for (let c = 1; c < side.columns.length; c += 1) {
      for (const match of side.columns[c].matches) {
        if (fed.has(match.match_id)) continue
        const box = boxOf(layoutKey(c, match))
        if (box) {
          byes.push({
            x: box.left,
            y: box.mid,
            title: noOpponentTooltip(match, side.columns[c].label),
          })
        }
      }
    }
    setConnectors({ width: grid.scrollWidth, height: grid.scrollHeight, lines, byes })
  }, [side, layout, collapsed])

  const active = Boolean(highlight)

  return (
    <div className="bracket-side">
      <button
        aria-expanded={!collapsed}
        className="mb-3 flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-fury-violet transition hover:text-text"
        onClick={() => setCollapsed((value) => !value)}
        type="button"
      >
        {side.label}
        <span aria-hidden="true" className={`bracket-caret${collapsed ? ' is-collapsed' : ''}`}>▾</span>
      </button>
      {collapsed ? null : (
        <BracketViewport>
          <div className={`tournament-bracket-grid${active ? ' has-trace' : ''}`} ref={gridRef}>
            <svg
              aria-hidden="true"
              className="tournament-bracket-lines"
              height={connectors.height}
              width={connectors.width}
            >
              {connectors.lines.map((line, index) => {
                const lit = traceClass(line.teams, highlight)
                return (
                  <g key={index}>
                    <path className={`bracket-line-base${lit}`} d={line.d} />
                    <path className={`bracket-line-flow${lit}`} d={line.d} />
                  </g>
                )
              })}
              {connectors.byes.map((bye, index) => (
                <g className="bracket-bye-hover" key={`bye-${index}`}>
                  <title>{bye.title}</title>
                  <path className="bracket-bye-hit" d={`M ${bye.x} ${bye.y} h -18`} />
                  <path className="bracket-bye-stub" d={`M ${bye.x} ${bye.y} h -18`} />
                  <text className="bracket-bye-tag" textAnchor="end" x={bye.x - 24} y={bye.y + 3}>
                    no opponent
                  </text>
                </g>
              ))}
            </svg>
            {side.columns.map((column, columnIndex) => (
              <div className="tournament-round-column" key={column.id}>
                <p className="mb-3 text-center text-xs font-semibold uppercase tracking-wide text-text-muted">
                  {column.label}
                </p>
                <div className="tournament-round-track" style={{ height: layout.height || undefined }}>
                  {column.matches.map((match) => {
                    const key = layoutKey(columnIndex, match)
                    const traced = traceClass(teamsOf(match), highlight)
                    return (
                      <div
                        className={`tournament-match-node${traced}`}
                        key={`${match.type_bracket}-${match.match_id}`}
                        ref={(el) => {
                          if (el) nodeRefs.current.set(key, el)
                          else nodeRefs.current.delete(key)
                        }}
                        style={{ top: layout.tops.get(key) ?? 0 }}
                      >
                        <TournamentMatchCard match={match} navigate={navigate} onHover={onHover} />
                      </div>
                    )
                  })}
                </div>
              </div>
            ))}
          </div>
        </BracketViewport>
      )}
    </div>
  )
}

function TournamentStandings({ standings }) {
  const rows = Array.isArray(standings) ? standings : []
  if (!rows.length) return null
  const grouped = new Map()
  rows.forEach((row) => {
    const group = row.group_index ?? 0
    if (!grouped.has(group)) grouped.set(group, [])
    grouped.get(group).push(row)
  })
  return (
    <div className="space-y-4">
      {[...grouped.entries()].map(([group, groupRows]) => (
        <div className="overflow-hidden rounded-lg border border-border bg-fury-white shadow-sm" key={group}>
          {grouped.size > 1 ? (
            <p className="border-b border-border px-5 py-3 text-xs font-semibold uppercase tracking-wide text-fury-cyan">
              Group {Number(group) + 1}
            </p>
          ) : null}
          <div className="grid grid-cols-[3rem_1fr_4rem_3rem_3rem_3rem_5rem] gap-2 border-b border-border px-5 py-3 text-xs font-semibold uppercase tracking-wide text-text-soft">
            <p>#</p>
            <p>Team</p>
            <p className="text-center">Pts</p>
            <p className="text-center">W</p>
            <p className="text-center">D</p>
            <p className="text-center">L</p>
            <p className="text-center">Buch.</p>
          </div>
          {groupRows.map((row, index) => (
            <div
              className="grid grid-cols-[3rem_1fr_4rem_3rem_3rem_3rem_5rem] items-center gap-2 border-b border-surface px-5 py-2.5 text-sm"
              key={`${group}-${row.team_name || index}`}
            >
              <span className="font-semibold text-fury-cyan">#{row.rank || index + 1}</span>
              <span className="min-w-0 truncate font-semibold">{row.team_name || 'Unknown team'}</span>
              <span className="text-center">{formatNumber(row.points)}</span>
              <span className="text-center">{formatNumber(row.wins)}</span>
              <span className="text-center">{formatNumber(row.draws)}</span>
              <span className="text-center">{formatNumber(row.losses)}</span>
              <span className="text-center">{formatNumber(row.buchholz)}</span>
            </div>
          ))}
        </div>
      ))}
    </div>
  )
}

function TournamentListColumn({ column, navigate }) {
  const [collapsed, setCollapsed] = useState(false)
  return (
    <div className="tournament-list-column">
      <button
        aria-expanded={!collapsed}
        className="mb-3 flex items-center justify-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-text-muted transition hover:text-text"
        onClick={() => setCollapsed((v) => !v)}
        type="button"
      >
        {column.label}
        <span aria-hidden="true" className={`bracket-caret${collapsed ? ' is-collapsed' : ''}`}>▾</span>
      </button>
      {collapsed ? null : (
        <div className="grid grid-cols-2 gap-3">
          {column.matches.map((match) => (
            <TournamentMatchCard
              key={`${match.type_bracket}-${match.match_id}`}
              match={match}
              navigate={navigate}
            />
          ))}
        </div>
      )}
    </div>
  )
}

function TournamentListSide({ side, navigate }) {
  const [collapsed, setCollapsed] = useState(false)
  return (
    <div className="bracket-side">
      <button
        aria-expanded={!collapsed}
        className="mb-3 flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-fury-violet transition hover:text-text"
        onClick={() => setCollapsed((v) => !v)}
        type="button"
      >
        {side.label}
        <span aria-hidden="true" className={`bracket-caret${collapsed ? ' is-collapsed' : ''}`}>▾</span>
      </button>
      {collapsed ? null : (
        <BracketViewport>
          <div className="tournament-bracket-grid">
            {side.columns.map((column) => (
              <TournamentListColumn column={column} key={column.id} navigate={navigate} />
            ))}
          </div>
        </BracketViewport>
      )}
    </div>
  )
}

function TournamentMatchList({ sides, navigate }) {
  return (
    <div className="space-y-6">
      {sides.map((side) => (
        <TournamentListSide key={side.key} side={side} navigate={navigate} />
      ))}
    </div>
  )
}

// Swiss tournaments often have round: null on every match because the bot doesn't
// populate that field. Matches ARE returned in round order (sorted by time_start /
// match_id), so we can infer round boundaries: floor(uniqueTeams / 2) matches per
// round. Only kicks in when a list side has exactly one column (all-null rounds).
function inferSwissRounds(sides) {
  return sides.map((side) => {
    if (side.kind !== 'list' || side.columns.length !== 1) return side
    const allMatches = side.columns[0].matches
    if (!allMatches.length) return side
    const teams = new Set()
    allMatches.forEach((m) => {
      if (m.team_a_name) teams.add(m.team_a_name)
      if (m.team_b_name) teams.add(m.team_b_name)
    })
    const matchesPerRound = Math.floor(teams.size / 2)
    if (matchesPerRound <= 0 || allMatches.length <= matchesPerRound) return side
    const columns = []
    for (let i = 0; i < allMatches.length; i += matchesPerRound) {
      const roundNum = columns.length + 1
      columns.push({
        id: `${side.key}:inferred:${roundNum}`,
        label: `Round ${roundNum}`,
        matches: allMatches.slice(i, i + matchesPerRound),
      })
    }
    return { ...side, columns }
  })
}

function TournamentDetailPage({ tournamentId, navigate }) {
  const [state, setState] = useState({ status: 'loading', data: null, error: null })

  useEffect(() => {
    if (!tournamentId) return undefined
    const controller = new AbortController()
    setState({ status: 'loading', data: null, error: null })
    fetchJson(apiEndpoints.tournament(tournamentId), controller.signal)
      .then((data) => {
        if (!controller.signal.aborted) setState({ status: 'ready', data, error: null })
      })
      .catch((error) => {
        if (!controller.signal.aborted) {
          setState({ status: 'error', data: null, error: error.message })
        }
      })
    return () => controller.abort()
  }, [tournamentId])

  const data = state.data
  const matches = useMemo(() => data?.matches || [], [data])
  const format = useMemo(() => tournamentFormatMeta(data?.format, matches), [data?.format, matches])
  const { sides } = useMemo(() => {
    const result = buildBracket(matches)
    return { ...result, sides: inferSwissRounds(result.sides) }
  }, [matches])
  const bracketSides = sides.filter((side) => side.kind === 'bracket')
  const listSides = sides.filter((side) => side.kind === 'list')
  const standings = data?.standings || []
  const hasStandings = standings.length > 0
  // Lit-up teams shared across both brackets, so a run lights in the winner
  // bracket and wherever it dropped into the loser bracket at once. Shape:
  // { teamLower: 'win' | 'loss' } or null.
  const [highlight, setHighlight] = useState(null)
  const onHover = useCallback((map) => setHighlight(map), [])

  return (
    <section className="space-y-6 pt-24 sm:pt-28">
      <button
        className="text-sm font-semibold text-fury-cyan transition hover:text-text"
        onClick={() => navigate('/tournaments')}
        type="button"
      >
        Back to tournaments
      </button>

      <div className="rounded-lg border border-border bg-fury-white p-6 shadow-sm">
        <div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
          <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
            Tournament — <span className="text-text-muted opacity-70">{tournamentId}</span>
          </p>
          <span className="rounded bg-surface px-2 py-0.5 text-xs font-semibold uppercase tracking-wide text-fury-violet">
            {format.label}
          </span>
        </div>
        <h1 className="mt-1 text-4xl font-bold">
          {data?.name || `Tournament ${tournamentId}`}
        </h1>
        {data ? (
          <p className="mt-2 text-sm text-text-soft">
            {formatNumber(data.match_count)} matches · {formatNumber(data.team_count)} teams ·{' '}
            {tournamentDateRange(data.date_start, data.date_end)}
            {data.status ? ` · ${tournamentStatusLabel(data.status)}` : ''}
          </p>
        ) : null}
        {state.status === 'error' ? (
          <p className="mt-4 text-sm text-danger">{state.error}</p>
        ) : null}
        {state.status === 'loading' ? (
          <p className="mt-4 text-sm text-text-soft">Loading tournament</p>
        ) : null}
      </div>

      {state.status === 'ready' && !matches.length ? (
        <p className="rounded-lg border border-border bg-fury-white px-5 py-10 text-sm text-text-soft shadow-sm">
          No games linked to this tournament yet.
        </p>
      ) : null}

      {matches.length ? (
        <div className="space-y-8">
          {hasStandings || listSides.length ? (
            <div className="space-y-4">
              {bracketSides.length ? <h2 className="text-lg font-semibold">Group stage</h2> : null}
              {hasStandings ? <TournamentStandings standings={standings} /> : null}
              {listSides.length ? <TournamentMatchList sides={listSides} navigate={navigate} /> : null}
            </div>
          ) : null}

          {bracketSides.length ? (
            <div className="space-y-12">
              {hasStandings || listSides.length ? (
                <h2 className="text-lg font-semibold">Playoffs</h2>
              ) : null}
              {bracketSides.map((side) => (
                <TournamentBracketSide
                  highlight={highlight}
                  key={side.key}
                  navigate={navigate}
                  onHover={onHover}
                  side={side}
                />
              ))}
            </div>
          ) : null}
        </div>
      ) : null}
    </section>
  )
}

function relativeSeconds(timestamp) {
  if (!timestamp) return 'unknown'
  const seconds = Math.max(0, Math.round((Date.now() - new Date(timestamp).getTime()) / 1000))
  if (seconds < 60) return `${seconds}s ago`
  return `${Math.round(seconds / 60)}m ago`
}

const shortDateFormat = new Intl.DateTimeFormat('en-GB', {
  day: '2-digit',
  month: 'short',
})

const shortTimeFormat = new Intl.DateTimeFormat('en-GB', {
  hour: '2-digit',
  minute: '2-digit',
})

const countryNames = {
  AR: 'Argentina',
  AU: 'Australia',
  BR: 'Brazil',
  CA: 'Canada',
  CL: 'Chile',
  CN: 'China',
  DE: 'Germany',
  ES: 'Spain',
  FR: 'France',
  GB: 'United Kingdom',
  ID: 'Indonesia',
  IN: 'India',
  IT: 'Italy',
  JP: 'Japan',
  KR: 'South Korea',
  MX: 'Mexico',
  NL: 'Netherlands',
  PL: 'Poland',
  RU: 'Russia',
  SE: 'Sweden',
  SG: 'Singapore',
  US: 'United States',
  ZA: 'South Africa',
}

function repairMojibake(value) {
  const text = String(value || '')
  if (!/[ÃÂâ]/.test(text) || typeof TextDecoder === 'undefined') return text

  try {
    const bytes = Uint8Array.from(text, (character) => character.charCodeAt(0))
    return new TextDecoder('utf-8', { fatal: true }).decode(bytes)
  } catch {
    return text
  }
}

function filledLast30Days(rows) {
  const byDate = new Map(rows.map((row) => [row.date, row]))
  return Array.from({ length: 30 }, (_, index) => {
    const date = new Date()
    date.setDate(date.getDate() - (29 - index))
    const key = date.toISOString().slice(0, 10)
    return byDate.get(key) || {
      date: key,
      events: 0,
      visitors: 0,
      page_views: 0,
      clients: 0,
      locations: 0,
    }
  })
}

function filledLast24Hours(rows) {
  const byDate = new Map(rows.map((row) => [row.date, row]))
  return Array.from({ length: 24 }, (_, index) => {
    const date = new Date()
    date.setMinutes(0, 0, 0)
    date.setHours(date.getHours() - (23 - index))
    const key = date.toISOString().slice(0, 13) + ':00:00.000Z'
    return byDate.get(key) || {
      date: key,
      events: 0,
      visitors: 0,
      page_views: 0,
      clients: 0,
      locations: 0,
      client_labels: [],
      location_labels: [],
    }
  })
}

function MiniLineChart({
  accent = 'text-fury-cyan',
  bucketLabel = 'Daily count',
  data,
  label,
  metric,
  pointFormat = shortDateFormat,
  stroke = '#e82517',
}) {
  const [hoveredPoint, setHoveredPoint] = useState(null)
  const width = 320
  const height = 112
  const padding = 12
  const maxValue = Math.max(1, ...data.map((item) => Number(item[metric] || 0)))
  const midValue = Math.round(maxValue / 2)
  const points = data.map((item, index) => {
    const x = padding + (index / Math.max(1, data.length - 1)) * (width - padding * 2)
    const y = height - padding - (Number(item[metric] || 0) / maxValue) * (height - padding * 2)
    return { ...item, x, y, value: Number(item[metric] || 0) }
  })
  const pathData = points
    .map((point, index) => `${index === 0 ? 'M' : 'L'} ${point.x.toFixed(2)} ${point.y.toFixed(2)}`)
    .join(' ')
  const latest = points.at(-1)?.value || 0

  return (
    <div className="relative rounded-md border border-border bg-bg p-3">
      <div className="flex items-start justify-between gap-3">
        <div>
          <p className="text-sm font-semibold">{label}</p>
          <p className="mt-1 text-xs text-text-soft">{bucketLabel}</p>
        </div>
        <p className={`text-sm font-semibold ${accent}`}>{formatNumber(latest)}</p>
      </div>

      <div className="relative mt-3">
        <div className="absolute left-0 top-0 flex h-28 w-8 flex-col justify-between text-[10px] font-semibold text-text-muted">
          <span>{formatNumber(maxValue)}</span>
          <span>{formatNumber(midValue)}</span>
          <span>0</span>
        </div>
        <svg className="ml-8 h-28 w-[calc(100%-2rem)] overflow-visible" viewBox={`0 0 ${width} ${height}`} role="img" aria-label={`${label} line chart`}>
          <path d={`M ${padding} ${height - padding} H ${width - padding}`} fill="none" stroke="var(--color-border)" strokeWidth="1" />
          <path d={`M ${padding} ${padding} H ${width - padding}`} fill="none" stroke="var(--color-border)" strokeDasharray="4 5" strokeWidth="1" />
          <path d={`M ${padding} ${height / 2} H ${width - padding}`} fill="none" stroke="var(--color-border)" strokeDasharray="4 5" strokeWidth="1" />
          <path d={pathData} fill="none" stroke={stroke} strokeLinecap="round" strokeLinejoin="round" strokeWidth="3" />
          {points.map((point) => (
            <circle
              cx={point.x}
              cy={point.y}
              fill={stroke}
              key={`${metric}-${point.date}`}
              onBlur={() => setHoveredPoint(null)}
              onFocus={() => setHoveredPoint(point)}
              onMouseEnter={() => setHoveredPoint(point)}
              onMouseLeave={() => setHoveredPoint(null)}
              r="4"
              tabIndex="0"
            />
          ))}
        </svg>
      </div>

      {hoveredPoint ? (
        <div
          className="pointer-events-none absolute z-20 w-max max-w-52 rounded-md bg-text px-2 py-1 text-xs font-semibold text-bg shadow-lg"
          style={{
            left: `${(hoveredPoint.x / width) * 100}%`,
            top: `${52 + (hoveredPoint.y / height) * 112}px`,
            transform: 'translate(-50%, -115%)',
          }}
        >
          {pointFormat.format(new Date(hoveredPoint.date))}: {formatNumber(hoveredPoint.value)} {label.toLowerCase()}
          <span className="block font-normal text-bg/80">
            {formatNumber(hoveredPoint.visitors || 0)} visitors
          </span>
          {metric === 'clients' && hoveredPoint.client_labels?.length ? (
            <span className="mt-1 block max-w-56 whitespace-normal font-normal text-bg/80">
              {hoveredPoint.client_labels
                .map((client) => `${client.label}: ${formatNumber(client.visitors)} visitors`)
                .join(', ')}
            </span>
          ) : null}
          {metric === 'locations' ? (
            <span className="mt-1 block max-w-64 whitespace-normal font-normal text-bg/80">
              {hoveredPoint.location_labels?.length
                ? hoveredPoint.location_labels
                  .map((location) => `${location.label} (${formatNumber(location.visitors)})`)
                  .join(', ')
                : 'No location labels stored for this day'}
            </span>
          ) : null}
        </div>
      ) : null}
    </div>
  )
}

function AnalyticsPeriodSection({
  activity,
  bucketLabel,
  description,
  pointFormat,
  title,
  totals,
}) {
  return (
    <div className="rounded-lg border border-border bg-fury-white p-5 shadow-sm">
      <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
        <div>
          <h2 className="text-lg font-semibold">{title}</h2>
          <p className="mt-1 text-sm text-text-soft">{description}</p>
        </div>
        <div className="grid gap-3 text-sm sm:grid-cols-4">
          <span className="rounded-md bg-surface px-3 py-2 font-semibold">
            {formatNumber(totals.visitors)} visitors
          </span>
          <span className="rounded-md bg-surface px-3 py-2 font-semibold">
            {formatNumber(totals.sessions)} sessions
          </span>
          <span className="rounded-md bg-surface px-3 py-2 font-semibold">
            {formatNumber(totals.pageViews)} page views
          </span>
          <span className="rounded-md bg-surface px-3 py-2 font-semibold">
            {formatNumber(totals.events)} events
          </span>
        </div>
      </div>

      <div className="mt-5 grid gap-4 lg:grid-cols-2 xl:grid-cols-5">
        <MiniLineChart
          bucketLabel={bucketLabel}
          data={activity}
          label="Events"
          metric="events"
          pointFormat={pointFormat}
          stroke="#e82517"
        />
        <MiniLineChart
          accent="text-fury-violet"
          bucketLabel={bucketLabel}
          data={activity}
          label="Visitors"
          metric="visitors"
          pointFormat={pointFormat}
          stroke="#fb7b04"
        />
        <MiniLineChart
          accent="text-fury-cyan"
          bucketLabel={bucketLabel}
          data={activity}
          label="Page views"
          metric="page_views"
          pointFormat={pointFormat}
          stroke="#ed5145"
        />
        <MiniLineChart
          accent="text-text"
          bucketLabel={bucketLabel}
          data={activity}
          label="Clients"
          metric="clients"
          pointFormat={pointFormat}
          stroke="var(--color-text)"
        />
        <MiniLineChart
          accent="text-fury-cyan"
          bucketLabel={bucketLabel}
          data={activity}
          label="Locations"
          metric="locations"
          pointFormat={pointFormat}
          stroke="#009ccc"
        />
      </div>
    </div>
  )
}

function ViewersPage({ viewers }) {
  const [analyticsWindow, setAnalyticsWindow] = useState('24h')
  const data = viewers.data || {}
  const active = data.active || []
  const activePages = data.active_pages || []
  const activity24h = filledLast24Hours(data.activity_24h || [])
  const activity30d = filledLast30Days(data.activity_30d || [])
  const totals = data.totals || {}
  const generatedAt = data.generated_at ? dateFormat.format(new Date(data.generated_at)) : 'Waiting for data'
  const periods = {
    '24h': {
      label: '24hr',
      title: 'Last 24 hours',
      description: 'Consented analytics grouped by hour',
      bucketLabel: 'Hourly count',
      pointFormat: shortTimeFormat,
      activity: activity24h,
      topPages: data.top_pages || [],
      clients: data.clients || [],
      themes: data.themes || [],
      countries: data.countries_24h || [],
      locations: data.locations_24h || [],
      totals: {
        events: totals.events_24h,
        visitors: totals.visitors_24h,
        sessions: totals.sessions_24h,
        pageViews: totals.page_views_24h,
      },
    },
    '30d': {
      label: '30d',
      title: 'Last 30 days',
      description: 'Retained consented analytics over the current privacy window',
      bucketLabel: 'Daily count',
      pointFormat: shortDateFormat,
      activity: activity30d,
      topPages: data.top_pages_30d || data.top_pages || [],
      clients: data.clients_30d || [],
      themes: data.themes_30d || data.themes || [],
      countries: data.countries || [],
      locations: data.locations || [],
      totals: {
        events: totals.events_30d,
        visitors: totals.visitors_30d,
        sessions: totals.sessions_30d,
        pageViews: totals.page_views_30d,
      },
    },
  }
  const periodData = periods[analyticsWindow]

  return (
    <section className="space-y-6 pt-10 sm:pt-14">
      <div className="rounded-lg border border-border bg-fury-white p-6 shadow-sm">
        <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
          <div>
            <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
              Public analytics
            </p>
            <h1 className="mt-1 text-4xl font-bold">viewers</h1>
            <p className="mt-2 text-sm text-text-soft">
              Live consented browser sessions and page activity. Last refreshed {generatedAt}.
            </p>
          </div>
          <div className="flex flex-wrap items-center gap-3">
            <div className="flex rounded-md border border-border bg-surface p-1 text-sm font-semibold" aria-label="Analytics time window">
              {Object.entries(periods).map(([key, period]) => (
                <button
                  className={`rounded px-3 py-1.5 transition ${analyticsWindow === key ? 'bg-text text-bg apricot-button-text shadow-sm' : 'text-text-soft hover:text-text'
                    }`}
                  key={key}
                  onClick={() => setAnalyticsWindow(key)}
                  type="button"
                >
                  {period.label}
                </button>
              ))}
            </div>
            <span className="w-fit rounded-md bg-surface px-3 py-2 text-sm font-semibold text-fury-cyan">
              {viewers.status === 'loading' ? 'Loading' : `${formatNumber(totals.active_now)} active now`}
            </span>
          </div>
        </div>
      </div>

      <div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
        <Stat label="Active now" value={formatNumber(totals.active_now)} />
        <Stat label={`Visitors ${periodData.label}`} value={formatNumber(periodData.totals.visitors)} />
        <Stat label={`Page views ${periodData.label}`} value={formatNumber(periodData.totals.pageViews)} />
        <Stat label={`Events ${periodData.label}`} value={formatNumber(periodData.totals.events)} />
      </div>

      <AnalyticsPeriodSection
        activity={periodData.activity}
        bucketLabel={periodData.bucketLabel}
        description={periodData.description}
        pointFormat={periodData.pointFormat}
        title={periodData.title}
        totals={periodData.totals}
      />

      <div className="overflow-hidden rounded-lg border border-border bg-fury-white shadow-sm">
        <div className="border-b border-surface px-5 py-4">
          <div className="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
            <div>
              <h2 className="text-lg font-semibold">Currently viewing</h2>
              <p className="mt-1 text-sm text-text-soft">
                Pages with active heartbeats within {formatNumber(data.active_window_seconds || 75)} seconds
              </p>
            </div>
            <p className="text-sm font-semibold text-fury-cyan">
              {formatNumber(active.length)} active visitors
            </p>
          </div>
        </div>
        {activePages.map((page) => (
          <div
            className="grid gap-4 border-b border-surface px-5 py-4 text-sm lg:grid-cols-[1fr_auto_auto]"
            key={`${page.page_path}-${page.page_title}`}
          >
            <div className="min-w-0">
              <p className="truncate text-lg font-semibold">{page.page_title || page.page_path}</p>
              <p className="truncate text-xs text-text-soft">{page.page_path}</p>
              <div className="mt-2 flex flex-wrap gap-2 text-xs text-text-soft">
                {(page.clients || []).map((client) => (
                  <span className="rounded-md bg-surface px-2 py-1" key={client.label}>
                    {client.label} ({formatNumber(client.count)})
                  </span>
                ))}
                {(page.countries || []).map((country) => (
                  <span className="rounded-md bg-surface px-2 py-1" key={country}>
                    {country}
                  </span>
                ))}
                {(page.themes || []).map((theme) => (
                  <span className="rounded-md bg-surface px-2 py-1" key={theme.theme}>
                    {theme.theme} theme ({formatNumber(theme.count)})
                  </span>
                ))}
              </div>
            </div>
            <div className="grid grid-cols-2 gap-2 text-right sm:grid-cols-1">
              <p className="font-semibold text-fury-cyan">{formatNumber(page.viewers)} viewing</p>
              <p className="text-text-soft">{formatNumber(page.visitors)} visitors</p>
            </div>
            <p className="text-right text-text-soft">Seen {relativeSeconds(page.last_seen_at)}</p>
          </div>
        ))}
        {!activePages.length ? (
          <p className="px-5 py-10 text-sm text-text-soft">
            {viewers.error || 'No pages are actively being viewed right now'}
          </p>
        ) : null}
      </div>

      <div className="overflow-hidden rounded-lg border border-border bg-fury-white shadow-sm">
        <div className="border-b border-surface px-5 py-4">
          <h2 className="text-lg font-semibold">Active visitors</h2>
          <p className="mt-1 text-sm text-text-soft">
            Visitors currently contributing to the active page counts
          </p>
        </div>
        {active.map((viewer) => (
          <div
            className="grid gap-3 border-b border-surface px-5 py-4 text-sm lg:grid-cols-[1fr_1fr_auto_auto_auto]"
            key={viewer.session}
          >
            <div className="min-w-0">
              <p className="truncate font-semibold">{viewer.page_title || viewer.page_path}</p>
              <p className="truncate text-xs text-text-soft">{viewer.page_path}</p>
            </div>
            <div className="min-w-0">
              <p className="truncate font-semibold">
                {viewer.browser} on {viewer.os}
              </p>
              <p className="truncate text-xs text-text-soft">
                {viewer.device} · {viewer.screen || 'unknown screen'} · {viewer.theme || 'light'} theme · {viewer.country || viewer.timezone || 'unknown location'}
              </p>
            </div>
            <p className="text-text-soft">{viewer.language || 'unknown language'}</p>
            <p className="text-text-soft">Seen {relativeSeconds(viewer.last_seen_at)}</p>
            <p className="font-mono text-xs text-text-muted">
              {formatNumber(viewer.sessions || 1)} {(viewer.sessions || 1) === 1 ? 'tab' : 'tabs'} · #{viewer.visitor}
            </p>
          </div>
        ))}
        {!active.length ? (
          <p className="px-5 py-10 text-sm text-text-soft">
            {viewers.error || 'No consented viewers are active right now'}
          </p>
        ) : null}
      </div>

      <div className="grid gap-6 xl:grid-cols-2">
        <div className="rounded-lg border border-border bg-fury-white shadow-sm">
          <div className="border-b border-surface px-5 py-4">
            <h2 className="text-lg font-semibold">Top pages</h2>
            <p className="mt-1 text-sm text-text-soft">Page views over {periodData.title.toLowerCase()}</p>
          </div>
          {periodData.topPages.map((page) => (
            <div className="grid grid-cols-[1fr_auto] gap-3 border-b border-surface px-5 py-3 text-sm" key={page.page_path}>
              <div className="min-w-0">
                <p className="truncate font-semibold">{page.page_title || page.page_path}</p>
                <p className="truncate text-xs text-text-soft">{page.page_path}</p>
              </div>
              <p className="font-semibold text-fury-cyan">{formatNumber(page.views)}</p>
            </div>
          ))}
          {!periodData.topPages.length ? <p className="px-5 py-10 text-sm text-text-soft">No page views recorded yet</p> : null}
        </div>

        <div className="rounded-lg border border-border bg-fury-white shadow-sm">
          <div className="border-b border-surface px-5 py-4">
            <h2 className="text-lg font-semibold">Clients</h2>
            <p className="mt-1 text-sm text-text-soft">Browsers, devices, and operating systems seen over {periodData.title.toLowerCase()}</p>
          </div>
          {periodData.clients.map((client) => (
            <div
              className="grid grid-cols-[1fr_auto_auto] gap-3 border-b border-surface px-5 py-3 text-sm"
              key={`${client.browser}-${client.os}-${client.device}`}
            >
              <div className="min-w-0">
                <p className="truncate font-semibold">{client.browser} on {client.os}</p>
                <p className="truncate text-xs text-text-soft">{client.device}</p>
              </div>
              <p className="text-text-soft">{formatNumber(client.visitors || 0)} visitors</p>
              <p className="font-semibold text-fury-cyan">{formatNumber(client.events)} events</p>
            </div>
          ))}
          {!periodData.clients.length ? <p className="px-5 py-10 text-sm text-text-soft">No client data recorded yet</p> : null}
        </div>
      </div>

      <div className="rounded-lg border border-border bg-fury-white shadow-sm">
        <div className="border-b border-surface px-5 py-4">
          <h2 className="text-lg font-semibold">Themes</h2>
          <p className="mt-1 text-sm text-text-soft">Light and dark mode usage over {periodData.title.toLowerCase()}</p>
        </div>
        <div className="grid gap-3 p-5 sm:grid-cols-2">
          {periodData.themes.map((theme) => {
            const totalEvents = periodData.themes.reduce((sum, item) => sum + Number(item.events || 0), 0) || 1
            const percent = Math.round((Number(theme.events || 0) / totalEvents) * 100)
            return (
              <div className="rounded-md border border-border bg-surface p-4" key={theme.theme}>
                <div className="flex items-center justify-between gap-3">
                  <p className="text-sm font-semibold capitalize">{theme.theme} mode</p>
                  <p className="text-sm font-semibold text-fury-cyan">{percent}%</p>
                </div>
                <div className="mt-3 h-2 overflow-hidden rounded-full bg-bg">
                  <div className="h-full rounded-full bg-fury-cyan" style={{ width: `${percent}%` }} />
                </div>
                <p className="mt-3 text-xs text-text-soft">
                  {formatNumber(theme.visitors || 0)} visitors · {formatNumber(theme.events || 0)} events
                </p>
              </div>
            )
          })}
          {!periodData.themes.length ? <p className="text-sm text-text-soft">No theme data recorded yet</p> : null}
        </div>
      </div>

      <div className="grid gap-6 xl:grid-cols-[0.9fr_1.1fr]">
        <div className="rounded-lg border border-border bg-fury-white shadow-sm">
          <div className="border-b border-surface px-5 py-4">
            <h2 className="text-lg font-semibold">Locations</h2>
            <p className="mt-1 text-sm text-text-soft">Cities and locations seen over {periodData.title.toLowerCase()}</p>
          </div>
          <LocationSignalTable countries={periodData.countries} locations={periodData.locations} />
        </div>

        <div className="rounded-lg border border-border bg-fury-white shadow-sm">
          <div className="border-b border-surface px-5 py-4">
            <h2 className="text-lg font-semibold">Location signals</h2>
            <p className="mt-1 text-sm text-text-soft">
              Cloudflare edge geolocation from {periodData.title.toLowerCase()}
            </p>
          </div>
          <LocationSignalMap countries={periodData.countries} locations={periodData.locations} />
        </div>
      </div>

      <p className="text-xs text-text-soft">
        Analytics are opt-in, retained for {formatNumber(data.privacy?.retention_days || 30)} days,
        and public output excludes raw IP addresses and precise location.
      </p>
    </section>
  )
}

function LocationSignalTable({ countries, locations }) {
  const rows = locations.length
    ? locations.map((location) => {
      const city = repairMojibake(location.city)
      const region = repairMojibake(location.region)
      const timezone = repairMojibake(location.timezone)
      return {
        key: `${location.country}-${location.region}-${location.city}-${location.latitude}-${location.longitude}-${location.timezone}`,
        place: city || region || timezone || 'Unknown city',
        region: region || 'Not shared',
        country: countryNames[location.country] || location.country || 'Not shared',
        visitors: location.visitors,
        events: location.events,
      }
    })
    : countries.map((country) => ({
      key: country.country,
      place: countryNames[country.country] || country.country || 'Unknown country',
      region: 'Country signal',
      country: countryNames[country.country] || country.country || 'Not shared',
      visitors: country.visitors,
      events: country.events,
    }))

  return (
    <div className="overflow-x-auto">
      <table className="w-full min-w-[520px] text-left text-sm">
        <thead className="border-b border-surface text-xs uppercase tracking-wide text-text-soft">
          <tr>
            <th className="px-5 py-3 font-semibold">City/location</th>
            <th className="px-5 py-3 font-semibold">Region</th>
            <th className="px-5 py-3 font-semibold">Country</th>
            <th className="px-5 py-3 text-right font-semibold">Visitors</th>
            <th className="px-5 py-3 text-right font-semibold">Events</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((row) => (
            <tr className="border-b border-surface" key={row.key}>
              <td className="px-5 py-3 font-semibold">{row.place}</td>
              <td className="px-5 py-3 text-text-soft">{row.region}</td>
              <td className="px-5 py-3 text-text-soft">{row.country}</td>
              <td className="px-5 py-3 text-right font-semibold text-fury-cyan">{formatNumber(row.visitors)}</td>
              <td className="px-5 py-3 text-right text-text-soft">{formatNumber(row.events)}</td>
            </tr>
          ))}
        </tbody>
      </table>
      {!rows.length ? <p className="px-5 py-10 text-sm text-text-soft">No location data recorded yet</p> : null}
    </div>
  )
}

function LocationSignalMap({ countries, locations }) {
  const mapRef = useRef(null)
  const markersRef = useRef(null)
  const leafletRef = useRef(null)
  const [mapReady, setMapReady] = useState(false)
  const countryMarkerColor = '#e82517'
  const maxMarkerVisitors = Math.max(
    1,
    ...countries.map((country) => Number(country.visitors || 0)),
    ...locations.map((location) => Number(location.visitors || 0)),
  )
  const countryMarkers = countries
    .map((country) => {
      const lat = Number(country.latitude)
      const lon = Number(country.longitude)
      if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null
      return {
        ...country,
        lat,
        lon,
        label: countryNames[country.country] || country.country,
      }
    })
    .filter(Boolean)
  const cityMarkers = locations
    .map((location) => {
      const lat = Number(location.latitude)
      const lon = Number(location.longitude)
      if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null
      const place = [repairMojibake(location.city), repairMojibake(location.region), countryNames[location.country] || location.country]
        .filter(Boolean)
        .join(', ')
      return {
        ...location,
        lat,
        lon,
        label: place || location.country || repairMojibake(location.timezone) || 'Unknown location',
      }
    })
    .filter(Boolean)
  const topLocations = cityMarkers.length
    ? cityMarkers.slice(0, 8).map((location) => ({
      key: `${location.country}-${location.region}-${location.city}-${location.lat}-${location.lon}`,
      label: location.label,
      detail: 'Cloudflare edge location',
      visitors: location.visitors,
      events: location.events,
    }))
    : countries.slice(0, 8).map((country) => ({
      key: country.country,
      label: countryNames[country.country] || country.country,
      detail: 'country signal',
      visitors: country.visitors,
      events: country.events,
    }))

  useEffect(() => {
    if (!mapRef.current || markersRef.current) return undefined
    let cancelled = false
    let map = null

    async function initializeMap() {
      const [{ default: L }] = await Promise.all([
        import('leaflet'),
        import('leaflet/dist/leaflet.css'),
      ])
      if (cancelled || !mapRef.current) return

      leafletRef.current = L
      map = L.map(mapRef.current, {
        center: [25, 0],
        maxBounds: [[-85, -220], [85, 220]],
        minZoom: 1,
        scrollWheelZoom: true,
        worldCopyJump: true,
        zoom: 1,
      })

      L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
        attribution: '&copy; OpenStreetMap contributors &copy; CARTO',
        maxZoom: 8,
      }).addTo(map)

      markersRef.current = {
        layer: L.layerGroup().addTo(map),
        map,
      }
      setMapReady(true)
    }
    initializeMap()

    return () => {
      cancelled = true
      markersRef.current = null
      leafletRef.current = null
      map?.remove()
    }
  }, [])

  useEffect(() => {
    const L = leafletRef.current
    if (!markersRef.current || !L) return

    const { layer } = markersRef.current
    layer.clearLayers()
    const scaleMarkerRadius = (visitors, minRadius, maxRadius) => {
      const count = Math.max(0, Number(visitors || 0))
      const normalized = Math.log1p(count) / Math.log1p(maxMarkerVisitors)
      return minRadius + normalized * (maxRadius - minRadius)
    }

    const mapMarkers = cityMarkers.length ? cityMarkers : countryMarkers
    mapMarkers.forEach((location) => {
      const isCityMarker = cityMarkers.length > 0
      const radius = isCityMarker
        ? scaleMarkerRadius(location.visitors, 5, 14)
        : scaleMarkerRadius(location.visitors, 8, 24)
      L.circleMarker([location.lat, location.lon], {
        color: countryMarkerColor,
        fillColor: countryMarkerColor,
        fillOpacity: 0.4,
        opacity: 0,
        radius,
        stroke: false,
      })
        .bindTooltip(`${location.label}: ${formatNumber(location.visitors)} visitors`, {
          direction: 'top',
          opacity: 0.95,
        })
        .addTo(layer)
    })
  }, [cityMarkers, countryMarkerColor, countryMarkers, mapReady, maxMarkerVisitors])

  return (
    <div className="p-5">
      <div className="location-signal-map relative z-0 h-[340px] overflow-hidden rounded-md border border-border bg-surface">
        <div ref={mapRef} className="h-full w-full" />
      </div>

      <div className="mt-4 grid gap-2 sm:grid-cols-2">
        {topLocations.map((location) => (
          <div className="grid grid-cols-[1fr_auto] gap-3 rounded-md bg-surface px-3 py-2 text-sm" key={`${location.key}-row`}>
            <div className="min-w-0">
              <p className="truncate font-semibold">{location.label}</p>
              <p className="truncate text-xs text-text-soft">{location.detail}</p>
            </div>
            <p className="font-semibold text-fury-cyan">{formatNumber(location.visitors)}</p>
          </div>
        ))}
      </div>

      {!countries.length && !locations.length ? (
        <p className="mt-4 text-sm text-text-soft">
          No Cloudflare location signals have been shared yet.
        </p>
      ) : null}
    </div>
  )
}

function UptimePage({ uptime }) {
  const [hoveredSnapshot, setHoveredSnapshot] = useState(null)
  const checks = uptime.checks
  const history = uptime.history
  const operationalCount = checks.filter((check) => check.ok).length
  const allOperational = checks.length > 0 && operationalCount === checks.length
  const updatedAt = uptime.updatedAt ? dateFormat.format(new Date(uptime.updatedAt)) : 'Not checked yet'
  const onlineSamples = history.filter((sample) => sample.ok).length
  const availability = history.length ? (onlineSamples / history.length) * 100 : 0

  return (
    <section className="space-y-6 pt-24 sm:pt-28">
      <div className="rounded-lg border border-border bg-fury-white p-6 shadow-sm">
        <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
          <div>
            <p className="text-sm font-semibold uppercase tracking-wide text-fury-cyan">
              Website uptime
            </p>
            <h1 className="mt-1 text-4xl font-bold">
              {allOperational ? 'All systems operational' : 'Status check'}
            </h1>
            <p className="mt-2 text-sm text-text-soft">
              Last server snapshot {updatedAt}. The page refreshes once a minute.
            </p>
          </div>
          <span
            className={`w-fit rounded-md px-3 py-2 text-sm font-semibold ${allOperational
              ? 'bg-surface text-fury-cyan'
              : 'bg-fury-ice text-fury-violet'
              }`}
          >
            {uptime.status === 'loading' ? 'Checking' : `${operationalCount}/${checks.length || 3} online`}
          </span>
        </div>
      </div>

      <div className="rounded-lg border border-border bg-fury-white p-5 shadow-sm">
        <div className="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
          <div>
            <h2 className="text-lg font-semibold">Availability timeline</h2>
            <p className="mt-1 text-sm text-text-soft">
              Last {formatNumber(history.length)} persisted server snapshots
            </p>
          </div>
          <p className="text-sm font-semibold text-fury-cyan">
            {history.length ? `${availability.toFixed(1)}% uptime` : 'Waiting for first sample'}
          </p>
        </div>

        <div
          className="relative mt-5 grid h-28 items-end gap-px rounded-md border border-border bg-bg p-3"
          style={{
            gridTemplateColumns: history.length
              ? `repeat(${history.length}, minmax(0, 1fr))`
              : '1fr',
          }}
        >
          {history.map((sample, index) => {
            const height = `${Math.max(12, (sample.onlineChecks / sample.totalChecks) * 100)}%`
            const left = history.length > 1 ? (index / (history.length - 1)) * 100 : 50

            return (
              <div
                aria-label={`${sample.ok ? 'Uptime' : 'Downtime'} at ${dateFormat.format(new Date(sample.timestamp))}`}
                className={`w-full min-w-0 rounded-sm ${sample.ok ? 'bg-success' : 'bg-danger'}`}
                key={sample.timestamp}
                onBlur={() => setHoveredSnapshot(null)}
                onFocus={() => setHoveredSnapshot({ ...sample, left })}
                onMouseEnter={() => setHoveredSnapshot({ ...sample, left })}
                onMouseLeave={() => setHoveredSnapshot(null)}
                style={{ height }}
                tabIndex="0"
              />
            )
          })}

          {hoveredSnapshot ? (
            <div
              className="pointer-events-none absolute bottom-full z-20 mb-2 w-max max-w-64 rounded-md bg-text px-2 py-1 text-xs font-semibold text-bg shadow-lg"
              style={{
                left: `${hoveredSnapshot.left}%`,
                transform: 'translateX(-50%)',
              }}
            >
              {dateFormat.format(new Date(hoveredSnapshot.timestamp))}
              <span className="block font-normal text-bg/80">
                {hoveredSnapshot.ok ? 'Uptime' : 'Downtime or degraded'} · {formatNumber(hoveredSnapshot.onlineChecks)}/{formatNumber(hoveredSnapshot.totalChecks)} online
              </span>
            </div>
          ) : null}

          {!history.length ? (
            <div className="flex h-full w-full items-center justify-center text-sm text-text-soft">
              Checking website status
            </div>
          ) : null}
        </div>

        <div className="mt-3 flex flex-wrap gap-4 text-xs text-text-soft">
          <span className="flex items-center gap-2">
            <span className="h-2.5 w-2.5 rounded-sm bg-success" />
            Uptime
          </span>
          <span className="flex items-center gap-2">
            <span className="h-2.5 w-2.5 rounded-sm bg-danger" />
            Downtime or degraded
          </span>
        </div>
      </div>

      <div className="grid gap-4 lg:grid-cols-3">
        {checks.map((check) => (
          <article className="rounded-lg border border-border bg-fury-white p-5 shadow-sm" key={check.name}>
            <div className="flex items-start justify-between gap-3">
              <div>
                <h2 className="text-lg font-semibold">{check.name}</h2>
                <p className="mt-1 text-sm text-text-soft">{check.detail}</p>
              </div>
              <span
                className={`shrink-0 rounded-md px-2 py-1 text-xs font-semibold ${check.ok
                  ? 'bg-surface text-fury-cyan'
                  : 'bg-fury-ice text-fury-violet'
                  }`}
              >
                {check.ok ? 'Online' : 'Issue'}
              </span>
            </div>
            <p className="mt-5 text-sm font-semibold">{check.label}</p>
            <p className="mt-1 text-xs text-text-soft">{formatNumber(check.latency)} ms response window</p>
          </article>
        ))}

        {!checks.length ? (
          <p className="rounded-lg border border-border bg-fury-white px-5 py-10 text-sm text-text-soft shadow-sm lg:col-span-3">
            Checking website status
          </p>
        ) : null}
      </div>
    </section>
  )
}

export default App
