// UR2D — Ultimate Racing Esports
// Shared data, tokens, atoms. Forked from MZM Apex direction.
// LEAGUE site (not single team) — red over black, arcade racing.

const URL_LEAGUE = {
  brand: {
    name: 'Ultimate Racing Esports',
    short: 'UR2D',
    wordmarkPrimary: 'ULTIMATE',
    wordmarkSecondary: 'RACING ESPORTS',
    tagline: 'Arcade racing. League rules.',
    estab: '2024',
    discord: 'https://discord.gg/SSfKpQFGn9'
  },
  // The next league round.
  nextRound: {
    r: 'R07',
    name: 'Tenbridge UK Grand Prix',
    track: 'Tenbridge International Circuit',
    trackShort: 'Tenbridge',
    location: 'GBR',
    countryShort: 'GBR',
    date: '2026-05-27T14:00:00Z',
    dateLabel: 'Wed · May 27',
    when: '27 MAY',
    laps: 52,
    length: '5.891 km',
    distance: '306.3 km',
    firstCompeted: '2019',
    corners: 18,
    drsZones: '02',
    lapRecord: '1:21.043',
    lightsOut: '14:00 UTC',
    flag: 'uk',
    blurb: 'Five-point-nine kilometres of flowing British asphalt — eighteen corners through the Tenbridge valley, with a river-side DRS straight where the championship gets decided.',
    sectors: [
    { id: 'S1', label: 'Pit Straight · DRS', x: 48, y: 34 },
    { id: 'S2', label: 'Cyanotype Esses', x: 24, y: 26 },
    { id: 'S3', label: 'Riverside Sweeper', x: 78, y: 70 }],

    schedule: [
    { session: 'Apprentice League', day: 'Sun', time: '15:00' },
    { session: 'Journeyman League', day: 'Sun', time: '16:30' },
    { session: 'Horizon League', day: 'Sun', time: '18:00' },
    { session: 'Ultimate League', day: 'Sun', time: '19:00' }]

  },
  // 8 teams in the league
  teams: [
  { id: 'apex', name: 'Apex Volt', short: 'APX', color: '#E10600', drivers: ['Riven', 'Solace'], pts: 312 },
  { id: 'redline', name: 'Redline GP', short: 'RDL', color: '#FF4D2E', drivers: ['Hex', 'Mara'], pts: 287 },
  { id: 'eclipse', name: 'Eclipse Drift', short: 'ECL', color: '#B91515', drivers: ['Volt', 'Kira'], pts: 264 },
  { id: 'phantom', name: 'Phantom Throttle', short: 'PHN', color: '#8C1010', drivers: ['Drax', 'Yuki'], pts: 198 },
  { id: 'mazzar', name: 'Mazzar', short: 'MZR', color: '#FF1493', drivers: ['Albono', 'Justin'], pts: 176, livery: 'assets/mazzar-livery.png', logo: 'assets/mazzar-logo.png' },
  { id: 'circuit', name: 'Circuit Reapers', short: 'CRR', color: '#FF7A1A', drivers: ['Nova', 'Zane'], pts: 142 },
  { id: 'nitro', name: 'Nitro Halo', short: 'NHL', color: '#FFB300', drivers: ['Pyx', 'Onyx'], pts: 118 },
  { id: 'turbo', name: 'Turbo Syndicate', short: 'TRB', color: '#7C2D12', drivers: ['Ash', 'Wrenn'], pts: 84 },
  { id: 'static', name: 'Static Velocity', short: 'STV', color: '#5b1a1a', drivers: ['Cole', 'Vex'], pts: 47 }],

  // Drivers across teams — 16 total (2 per team). Placeholder data, user will provide real.
  drivers: [
  { id: 'riven', name: 'Riven', team: 'apex', number: '01', country: 'usa', countryShort: 'USA', role: 'Starter' },
  { id: 'solace', name: 'Solace', team: 'apex', number: '12', country: 'canada', countryShort: 'CAN', role: 'Starter' },
  { id: 'hex', name: 'Hex', team: 'redline', number: '07', country: 'uk', countryShort: 'GBR', role: 'Starter' },
  { id: 'mara', name: 'Mara', team: 'redline', number: '23', country: 'spain', countryShort: 'ESP', role: 'Starter' },
  { id: 'volt', name: 'Volt', team: 'eclipse', number: '04', country: 'netherlands', countryShort: 'NED', role: 'Starter' },
  { id: 'kira', name: 'Kira', team: 'eclipse', number: '88', country: 'japan', countryShort: 'JPN', role: 'Starter' },
  { id: 'drax', name: 'Drax', team: 'phantom', number: '17', country: 'australia', countryShort: 'AUS', role: 'Starter' },
  { id: 'yuki', name: 'Yuki', team: 'phantom', number: '21', country: 'japan', countryShort: 'JPN', role: 'Starter' },
  { id: 'albono', name: 'Albono', team: 'mazzar', number: '20', country: 'scotland', countryShort: 'SCO', role: 'Starter' },
  { id: 'justin', name: 'Justin', team: 'mazzar', number: '33', country: 'netherlands', countryShort: 'NED', role: 'Starter' },
  { id: 'nova', name: 'Nova', team: 'circuit', number: '09', country: 'china', countryShort: 'CHN', role: 'Starter' },
  { id: 'zane', name: 'Zane', team: 'circuit', number: '44', country: 'usa', countryShort: 'USA', role: 'Starter' },
  { id: 'pyx', name: 'Pyx', team: 'nitro', number: '02', country: 'austria', countryShort: 'AUT', role: 'Starter' },
  { id: 'onyx', name: 'Onyx', team: 'nitro', number: '66', country: 'monaco', countryShort: 'MCO', role: 'Starter' },
  { id: 'ash', name: 'Ash', team: 'turbo', number: '13', country: 'england', countryShort: 'ENG', role: 'Starter' },
  { id: 'wrenn', name: 'Wrenn', team: 'turbo', number: '31', country: 'scotland', countryShort: 'SCO', role: 'Starter' },
  { id: 'cole', name: 'Cole', team: 'static', number: '55', country: 'usa', countryShort: 'USA', role: 'Starter' },
  { id: 'vex', name: 'Vex', team: 'static', number: '99', country: 'saudi', countryShort: 'KSA', role: 'Starter' }],

  // Drivers' standings — sorted by points
  driverStandings: [
  { d: 'riven', pts: 168, w: 2, pod: 4, dnf: 1 },
  { d: 'hex', pts: 154, w: 2, pod: 4, dnf: 0 },
  { d: 'volt', pts: 149, w: 1, pod: 3, dnf: 1 },
  { d: 'kira', pts: 115, w: 1, pod: 2, dnf: 0 },
  { d: 'solace', pts: 144, w: 0, pod: 3, dnf: 2 },
  { d: 'mara', pts: 133, w: 1, pod: 2, dnf: 1 },
  { d: 'drax', pts: 108, w: 0, pod: 2, dnf: 1 },
  { d: 'yuki', pts: 90, w: 0, pod: 1, dnf: 0 },
  { d: 'albono', pts: 102, w: 0, pod: 2, dnf: 1 },
  { d: 'justin', pts: 74, w: 0, pod: 1, dnf: 2 },
  { d: 'nova', pts: 78, w: 0, pod: 1, dnf: 2 },
  { d: 'zane', pts: 64, w: 0, pod: 1, dnf: 1 },
  { d: 'pyx', pts: 61, w: 0, pod: 0, dnf: 0 },
  { d: 'onyx', pts: 57, w: 0, pod: 0, dnf: 1 },
  { d: 'ash', pts: 48, w: 0, pod: 0, dnf: 2 },
  { d: 'wrenn', pts: 36, w: 0, pod: 0, dnf: 1 },
  { d: 'cole', pts: 27, w: 0, pod: 0, dnf: 3 },
  { d: 'vex', pts: 20, w: 0, pod: 0, dnf: 2 }],

  // 16-round calendar — arcade racing themed tracks
  calendar: [
  { r: 'R01', name: 'Twilight Circuit GP', track: 'Twilight Speedway', flag: 'usa', status: 'done', winner: 'Hex', winTeam: 'redline' },
  { r: 'R02', name: 'Sunset Strip Race', track: 'Sunset Strip', flag: 'usa', status: 'done', winner: 'Riven', winTeam: 'apex' },
  { r: 'R03', name: 'Highland Rally', track: 'Highland Loop', flag: 'uk', status: 'done', winner: 'Volt', winTeam: 'eclipse' },
  { r: 'R04', name: 'Neo Tokyo Bash', track: 'Neo Tokyo Ring', flag: 'japan', status: 'done', winner: 'Kira', winTeam: 'eclipse' },
  { r: 'R05', name: 'Mirage Open', track: 'Mirage Speedway', flag: 'saudi', status: 'done', winner: 'Hex', winTeam: 'redline' },
  { r: 'R06', name: 'Aurora Pass GP', track: 'Aurora Pass', flag: 'canada', status: 'done', winner: 'Riven', winTeam: 'apex' },
  { r: 'R07', name: 'Tenbridge UK Grand Prix', track: 'Tenbridge International Circuit', flag: 'uk', status: 'next' },
  { r: 'R08', name: 'Drift Coast Brawl', track: 'Coast Highway', flag: 'australia', status: 'upcoming' },
  { r: 'R09', name: 'Crater Run', track: 'Crater Circuit', flag: 'monaco', status: 'upcoming' },
  { r: 'R10', name: 'Storm Loop', track: 'Storm Loop', flag: 'netherlands', status: 'upcoming' },
  { r: 'R11', name: 'Volcano Rampage', track: 'Volcano Rampage', flag: 'spain', status: 'upcoming' },
  { r: 'R12', name: 'Glacier Sprint', track: 'Glacier Sprint', flag: 'scotland', status: 'upcoming' },
  { r: 'R13', name: 'Skyline Showdown', track: 'Skyline Bridge', flag: 'china', status: 'upcoming' },
  { r: 'R14', name: 'Bayou Burnout', track: 'Bayou Loop', flag: 'usa', status: 'upcoming' },
  { r: 'R15', name: 'Spire Grand Prix', track: 'Spire Speedway', flag: 'austria', status: 'upcoming' },
  { r: 'R16', name: 'Halo Finale', track: 'Halo Speedway', flag: 'usa', status: 'upcoming' }],

  // Hall of Champions — past season title winners
  champions: [
  { season: 'S19', year: '2025', driver: 'Riven', team: 'apex', pts: 482, wins: 9, country: 'usa', constructor: 'apex' },
  { season: 'S18', year: '2025', driver: 'Hex', team: 'redline', pts: 461, wins: 8, country: 'uk', constructor: 'redline' },
  { season: 'S17', year: '2024', driver: 'Volt', team: 'eclipse', pts: 438, wins: 7, country: 'netherlands', constructor: 'apex' },
  { season: 'S16', year: '2024', driver: 'Kira', team: 'eclipse', pts: 455, wins: 9, country: 'japan', constructor: 'eclipse' },
  { season: 'S15', year: '2023', driver: 'Riven', team: 'apex', pts: 470, wins: 10, country: 'usa', constructor: 'apex' },
  { season: 'S14', year: '2023', driver: 'Drax', team: 'phantom', pts: 419, wins: 6, country: 'australia', constructor: 'phantom' },
  { season: 'S13', year: '2022', driver: 'Mara', team: 'redline', pts: 444, wins: 8, country: 'spain', constructor: 'redline' },
  { season: 'S12', year: '2022', driver: 'Nova', team: 'circuit', pts: 401, wins: 5, country: 'china', constructor: 'eclipse' },
  { season: 'S11', year: '2021', driver: 'Hex', team: 'redline', pts: 428, wins: 7, country: 'uk', constructor: 'apex' }],

  // News ribbon for hero marquee
  ticker: [
  'R07 BRITISH GRAND PRIX · MAY 31 · LIGHTS OUT 19:00 UTC',
  'RIVEN EXTENDS DRIVERS LEAD BY 14 PTS',
  'APEX VOLT P1 IN CONSTRUCTORS · 25 POINTS UP',
  'ROOKIE OF THE YEAR RACE TIGHTENS · KIRA vs MARA',
  'DISCORD HITS 12,000 MEMBERS · JOIN THE GRID',
  'NEW PENALTY RULES IN EFFECT FROM R08'],

  // Race result summaries for results page
  results: [
  { r: 'R06', name: 'Aurora Pass GP', flag: 'canada', date: 'May 24',
    podium: [['Riven', 'apex'], ['Hex', 'redline'], ['Volt', 'eclipse']],
    fastLap: 'Riven · 1:23.114',
    notes: 'Riven lights-to-flag · Hex hunted hard in stint 2.' },
  { r: 'R05', name: 'Mirage Open', flag: 'saudi', date: 'May 10',
    podium: [['Hex', 'redline'], ['Mara', 'redline'], ['Riven', 'apex']],
    fastLap: 'Mara · 1:18.762',
    notes: 'Redline 1-2 · first double podium of the season.' },
  { r: 'R04', name: 'Neo Tokyo Bash', flag: 'japan', date: 'Apr 26',
    podium: [['Kira', 'eclipse'], ['Yuki', 'phantom'], ['Hex', 'redline']],
    fastLap: 'Kira · 1:09.402',
    notes: 'Kira home race · last-lap pass on Yuki for the win.' },
  { r: 'R03', name: 'Highland Rally', flag: 'uk', date: 'Apr 12',
    podium: [['Volt', 'eclipse'], ['Riven', 'apex'], ['Solace', 'apex']],
    fastLap: 'Volt · 1:32.014',
    notes: 'Three safety cars · Volt held the lead through all of them.' },
  { r: 'R02', name: 'Sunset Strip Race', flag: 'usa', date: 'Mar 29',
    podium: [['Riven', 'apex'], ['Hex', 'redline'], ['Drax', 'phantom']],
    fastLap: 'Hex · 1:17.226',
    notes: 'Riven dominates · 6.4s margin · clean race start to finish.' },
  { r: 'R01', name: 'Twilight Circuit GP', flag: 'usa', date: 'Mar 15',
    podium: [['Hex', 'redline'], ['Volt', 'eclipse'], ['Riven', 'apex']],
    fastLap: 'Hex · 1:21.554',
    notes: 'Season opener · Hex pole-to-flag · Riven recovers from P9.' }],

  // Featured news / stories
  news: [
  { tag: 'Race Report', date: 'May 24', title: 'Riven storms Aurora — extends championship lead to 14 points' },
  { tag: 'Driver', date: 'May 18', title: 'Inside Kira\'s sim rig: the rookie out-qualifying the field' },
  { tag: 'League', date: 'May 12', title: 'UR2D announces 2027 calendar — four new tracks, 18 rounds total' },
  { tag: 'Paddock', date: 'May 04', title: 'Redline\'s engineering shake-up pays off with Mirage 1-2 finish' }],

  // YouTube — UR2D eSports main channel (@UR2DeSports)
  youtubeChannel: 'https://www.youtube.com/@UR2DeSports/videos',
  videos: [
  { id: 'QzdQqXzv_1M', title: 'UR2D2 Ultimate League Season 22 | Round 4: Zandvoort', date: 'Oct 29, 2025' },
  { id: 'uqOch87KqZ8', title: 'UR2D2 Ultimate League Season 22 | Round 3: Sachsenring', date: 'Oct 22, 2025' },
  { id: '6JKAwcj2gMU', title: 'UR2D2 Ultimate League Season 21 | Round 6: Mar del Plata', date: 'Oct 05, 2025' },
  { id: 'ZWHMaWc5jHE', title: 'UR2D2 Ultimate League Season 21 | Round 2: Igora Drive', date: 'Jul 18, 2025' },
  { id: 'S-diZlvxQfU', title: 'UR2D2 Ultimate League Season 16 | Round 9: Hungarian Grand Prix', date: 'Jun 11, 2024' }]

};

// ─────────────────────────────────────────────────────────────────────────────
// COLOR TOKENS — Red over black. Bold arcade-racing palette.
const COLORS = {
  red: '#E10600',
  redDeep: '#B30500',
  redBright: '#FF1F1F',
  redLight: '#FF4D4D',
  black: '#0a0a0a',
  ink: '#0e0e10',
  surface: '#16161a',
  surface2: '#1e1e23',
  hairline: '#2a2a30',
  text: '#fafafa',
  textMute: '#bdbdc4',
  textDim: '#9a9aa3',
  yellow: '#FFD300',
  green: '#22c55e',
  // ── Page chrome & broadcast neutrals — folded in from inline literals (2026-06-30)
  base: '#050507',      // deepest page background — sits a step under `black`
  panel: '#0a0a0c',     // league-switcher / control-strip background
  slate: '#101218',     // broadcast standings slate field
  plate: '#F2F2F4',     // broadcast P1 white plate
  plateInk: '#0b0b0d',  // ink / text shown on the white plate
  plateMute: '#9a9aa2'  // muted text on the white plate
};
// Keep magenta alias to ease porting from MZM — they are now equal to red.
COLORS.magenta = COLORS.red;
COLORS.magentaLight = COLORS.redLight;
COLORS.magentaDark = COLORS.redDeep;

// ─────────────────────────────────────────────────────────────────────────────
// FLAGS — real country images via flagcdn.com (works over http/https, same as
// the gviz fetch). 'pacific' stays as a custom UR2D SVG for fictional tracks.
const FLAG_CDN_CODE = {
  scotland: 'gb-sct', netherlands: 'nl', england: 'gb-eng',
  canada: 'ca', saudi: 'sa', australia: 'au', japan: 'jp',
  china: 'cn', usa: 'us', monaco: 'mc', spain: 'es', austria: 'at',
  uk: 'gb', qatar: 'qa', southafrica: 'za', ukraine: 'ua',
  germany: 'de', indonesia: 'id', malaysia: 'my', wales: 'gb-wls',
  slovakia: 'sk', belgium: 'be', sweden: 'se', italy: 'it', newzealand: 'nz',
  russia: 'ru', romania: 'ro', canary: 'ic', france: 'fr',
};
function isoToEmoji(code) {
  // Compute Unicode regional-indicator emoji from a 2-letter ISO code.
  // For subdivision codes like 'gb-sct', fall back to the parent country.
  const base = (code || '').split('-')[0].toUpperCase();
  if (base.length !== 2) return null;
  return base.split('').map(c => String.fromCodePoint(0x1F1E6 + c.charCodeAt(0) - 65)).join('');
}
// Twemoji (Twitter) flag assets. Convert an ISO code to Twemoji's hyphen-joined
// hex codepoint filename. Subdivision flags (Scotland/Wales/England) are tag
// sequences with their own dedicated codepoints.
const TWEMOJI_SUBDIVISION = {
  'gb-sct': '1f3f4-e0067-e0062-e0073-e0063-e0074-e007f',
  'gb-wls': '1f3f4-e0067-e0062-e0077-e006c-e0073-e007f',
  'gb-eng': '1f3f4-e0067-e0062-e0065-e006e-e0067-e007f',
};
function isoToTwemojiCode(code) {
  if (!code) return null;
  const lc = code.toLowerCase();
  if (TWEMOJI_SUBDIVISION[lc]) return TWEMOJI_SUBDIVISION[lc];
  const base = lc.split('-')[0].toUpperCase();
  if (base.length !== 2) return null;
  return base.split('')
    .map(c => (0x1F1E6 + c.charCodeAt(0) - 65).toString(16))
    .join('-');
}
// Flags flagcdn/twemoji don't carry (or carry badly) → local assets. Canary Islands = civil
// tricolour (white/blue/yellow). Regional flags (ISO 3166-2-style codes, 960px PNGs from Wikimedia Commons) are used
// when one schedule has two GPs in the same country (user rule, 2026-10-05) or the GP is named after the region.
// Use urFlagImg(iso, w?) anywhere a flagcdn URL is built.
const UR_LOCAL_FLAGS = {
  ic: 'assets/flags/ic.svg',
  'de-by': 'assets/flags/de-by.png', 'de-sn': 'assets/flags/de-sn.png', 'de-bw': 'assets/flags/de-bw.png', 'de-rp': 'assets/flags/de-rp.png',
  'jp-08': 'assets/flags/jp-08.png', 'jp-01': 'assets/flags/jp-01.png', 'it-82': 'assets/flags/it-82.png', 'id-jk': 'assets/flags/id-jk.png',
  'fr-pdl': 'assets/flags/fr-pdl.png', 'nl-dr': 'assets/flags/nl-dr.png', 'au-qld': 'assets/flags/au-qld.png', 'be-vov': 'assets/flags/be-vov.png',
  'ru-cu': 'assets/flags/ru-cu.png', 'es-ct': 'assets/flags/es-ct.png',
  'jp-lam': 'assets/flags/jp-lam.png', // Saema · Lamunation Region (fictional, flag supplied by user)
};
function urFlagImg(iso, w) {
  if (UR_LOCAL_FLAGS[iso]) return UR_LOCAL_FLAGS[iso];
  return w ? `https://flagcdn.com/w${w}/${iso}.png` : `https://flagcdn.com/${iso}.svg`;
}
function FlagSvg({ kind, w = 24, h = 16, radius = 2 }) {
  // Unknown country (null/empty) → United Nations flag (user rule, 2026-10-05). 'pacific' stays the custom track SVG.
  if (!kind) kind = 'un';
  // Accept raw flagcdn ISO codes ('lv', 'gb-sct') as well as named kinds.
  const code = FLAG_CDN_CODE[kind] || (/^[a-z]{2}(-[a-z]{3})?$/.test(kind || '') ? kind : null);
  const [imgFailed, setImgFailed] = React.useState(false);
  if (!code) {
    const common = { width: w, height: h, viewBox: '0 0 24 16', style: { display: 'inline-block', borderRadius: radius, overflow: 'hidden', flexShrink: 0 } };
    return (
      <svg {...common}>
        <rect width="24" height="16" fill={COLORS.black} />
        <path d="M0 12 L24 4" stroke={COLORS.red} strokeWidth="2" />
        <path d="M0 8 L24 0" stroke="#fff" strokeWidth="1" />
        <circle cx="6" cy="11" r="1.5" fill={COLORS.red} />
      </svg>
    );
  }
  if (imgFailed) {
    const emoji = isoToEmoji(code);
    return (
      <span style={{ display: 'inline-block', width: w, height: h, lineHeight: h + 'px', textAlign: 'center', fontSize: Math.round(h * 1.1), flexShrink: 0, verticalAlign: 'middle' }}>
        {emoji || '🏳'}
      </span>
    );
  }
  return (
    <img
      src={UR_LOCAL_FLAGS[code] || `https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/${isoToTwemojiCode(code)}.png`}
      width={w}
      height={h}
      alt={kind}
      onError={() => setImgFailed(true)}
      style={{ display: 'inline-block', borderRadius: radius, flexShrink: 0, objectFit: 'cover', verticalAlign: 'middle' }}
    />
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// IMAGE SLOT wrapper. On site pages (window.UR_LOCK_IMAGE_SLOTS = true, set in the entry HTML) slots are display-only:
// pointer-events off, so clicking never opens the file picker / reframe tools and clicks reach the card underneath.
function URImageSlot({ id, placeholder, shape = 'rect', radius, mask, style = {}, children }) {
  const locked = typeof window !== 'undefined' && !!window.UR_LOCK_IMAGE_SLOTS;
  return (
    <image-slot
      id={id}
      placeholder={placeholder}
      shape={shape}
      {...radius != null ? { radius } : {}}
      {...mask ? { mask } : {}}
      style={locked ? { ...style, pointerEvents: 'none' } : style}>
      {children}
    </image-slot>);

}

// ─────────────────────────────────────────────────────────────────────────────
// GRAPHIC PLACEHOLDER for non-photo art slots
function GraphicPlaceholder({ label = 'IMAGE', tone = 'dark', accent, style = {}, height }) {
  accent = accent || COLORS.red;
  const bg = tone === 'dark' ? COLORS.ink : '#1a1a1e';
  return (
    <div style={{
      position: 'relative', width: '100%', height: height || '100%',
      background: `repeating-linear-gradient(135deg, ${bg} 0 14px, ${tone === 'dark' ? '#141418' : '#202024'} 14px 15px)`,
      border: `1px dashed ${accent}55`,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      color: accent, fontFamily: "'JetBrains Mono', monospace", fontSize: 10, letterSpacing: 2, textTransform: 'uppercase',
      overflow: 'hidden', ...style
    }}>
      <div style={{ position: 'absolute', inset: 0, background: `radial-gradient(circle at 30% 30%, ${accent}22, transparent 60%)` }} />
      <span style={{ position: 'relative', zIndex: 1, opacity: .9 }}>◇ {label}</span>
    </div>);

}

// ─────────────────────────────────────────────────────────────────────────────
// COUNTDOWN
function getCountdown(toIso, nowMs) {
  const t = new Date(toIso).getTime() - (nowMs ?? Date.now());
  const clamp = Math.max(t, 0);
  return {
    d: Math.floor(clamp / 86400000),
    h: Math.floor(clamp % 86400000 / 3600000),
    m: Math.floor(clamp % 3600000 / 60000),
    s: Math.floor(clamp % 60000 / 1000)
  };
}

// ─────────────────────────────────────────────────────────────────────────────
// PAGE HOOK
function usePage(initial = 'home') {
  const [page, setPage] = React.useState(initial);
  return [page, setPage];
}

// ─────────────────────────────────────────────────────────────────────────────
// HELPERS — team lookup
const TEAM_BY_ID = Object.fromEntries(URL_LEAGUE.teams.map((t) => [t.id, t]));
const DRIVER_BY_ID = Object.fromEntries(URL_LEAGUE.drivers.map((d) => [d.id, d]));

// ─────────────────────────────────────────────────────────────────────────────
// LEAGUES — the four UR2D leagues. Each tab can switch between them.
// LEAGUES — the four UR2D leagues, ordered by tier (top → bottom).
// Changing this array reorders every switcher / leader-card row across the site.
const LEAGUES = [
{ id: 'ul', short: 'UL', name: 'Ultimate League', tier: 'Tier 1 · Pro', logo: 'assets/ul-logo-cropped.png', accent: '#E10600' },
{ id: 'hl', short: 'CL', name: 'Challenger League', tier: 'Tier 2 · Advanced', logo: 'assets/cl-logo-cropped.png', accent: '#B4237A' },
{ id: 'jl', short: 'JL', name: 'Journeyman League', tier: 'Tier 3 · Intermediate', logo: 'assets/jl-logo-gradient-solid.png', accent: '#2684FF' }, // full-logo blue→orange gradient, no white lift (user, 2026-10-04); red-parts-only: jl-logo-blue-orange.png; solid blue: jl-logo-blue.png; original red: jl-logo-cropped.png
{ id: 'al', short: 'AL', name: 'Apprentice League', tier: 'Tier 4 · Beginner', logo: 'assets/al-logo-cropped.png', accent: '#1F8A5B' }];

const LEAGUE_BY_ID = Object.fromEntries(LEAGUES.map((l) => [l.id, l]));

// Shared global league state across pages (persists when user navigates)
function useLeagueState(initial = 'ul') {
  const [league, setLeague] = React.useState(initial);
  return [league, setLeague];
}

// League logo + name display
function LeagueBadge({ league, height = 56, showName = true }) {
  const L = LEAGUE_BY_ID[league];
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 14 }}>
      <div style={{
        height, width: height * 1.5,
        background: 'transparent',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        padding: 0
      }}>
        <img src={L.logo} alt={L.short} style={{ maxHeight: '100%', maxWidth: '100%', objectFit: 'contain', display: 'block' }} />
      </div>
      {showName &&
      <div style={{ fontFamily: 'Archivo', fontWeight: 900, fontSize: 20, color: COLORS.text, letterSpacing: '-0.01em', lineHeight: 1, textTransform: 'uppercase' }}>{L.name}</div>
      }
    </div>);

}

// League switcher — four tabbed buttons with mini-logos. Place at the top of each subpage.
function LeagueSwitcher({ league, setLeague, dense = false }) {
  return (
    <div style={{
      display: 'inline-flex',
      border: `1px solid ${COLORS.hairline}`,
      background: '#0a0a0c'
    }}>
      {LEAGUES.map((L, i) => {
        const active = league === L.id;
        return (
          <button key={L.id} onClick={() => setLeague(L.id)} style={{
            all: 'unset', cursor: 'pointer',
            display: 'flex', alignItems: 'center', gap: 12,
            padding: dense ? '10px 14px' : '14px 20px',
            background: active ? COLORS.surface : 'transparent',
            borderRight: i < LEAGUES.length - 1 ? `1px solid ${COLORS.hairline}` : 'none',
            position: 'relative',
            transition: 'background .12s ease'
          }}>
            {active && <span style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: 2, background: L.accent }} />}
            <span style={{
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
              width: dense ? 28 : 36, height: dense ? 28 : 36,
              background: COLORS.ink,
              padding: 4
            }}>
              <img src={L.logo} alt={L.short} style={{ maxHeight: '100%', maxWidth: '100%', objectFit: 'contain', display: 'block', opacity: active ? 1 : 0.55, filter: active ? 'none' : 'grayscale(.4)' }} />
            </span>
            <span style={{
              fontFamily: 'Archivo', fontWeight: 800, fontSize: dense ? 12 : 13,
              letterSpacing: '0.2em', textTransform: 'uppercase',
              color: active ? COLORS.text : COLORS.textMute
            }}>{L.short}</span>
          </button>);

      })}
    </div>);

}

// ─────────────────────────────────────────────────────────────────────────────
// UR2D MARK — Stylised "U2" monogram. Bold + arcade-y.
// "U" shape with the "2" curve nested into its right arm.
function UR2DMark({ size = 40, color, stroke = COLORS.black }) {
  color = color || COLORS.red;
  return (
    <svg width={size} height={size} viewBox="0 0 100 100" style={{ display: 'block', flexShrink: 0 }}>
      {/* Outer shield-ish hexagon plate */}
      <path d="M50 4 L88 24 L88 76 L50 96 L12 76 L12 24 Z" fill={stroke} stroke={color} strokeWidth="3" />
      {/* "U" stroke — left arm */}
      <path d="M28 26 L28 60 Q28 74 42 74 L46 74" stroke={color} strokeWidth="9" fill="none" strokeLinecap="square" />
      {/* "2" — top arc + diagonal + base */}
      <path d="M52 32 Q66 22 72 36 Q72 46 56 56 L72 56 L72 64 L52 64" stroke={color} strokeWidth="9" fill="none" strokeLinecap="square" strokeLinejoin="miter" />
      {/* Speed slashes */}
      <path d="M16 86 L24 86" stroke={color} strokeWidth="2" />
      <path d="M16 82 L20 82" stroke={color} strokeWidth="2" opacity="0.6" />
    </svg>);

}

// M2M MARK — footer credit mark; same hex plate + stroke language as UR2DMark, "M 2 M" monogram.
function M2MMark({ size = 40, color, stroke = COLORS.black }) {
  color = color || COLORS.red;
  return (
    <svg width={size} height={size} viewBox="0 0 100 100" style={{ display: 'block', flexShrink: 0 }}>
      <path d="M50 4 L88 24 L88 76 L50 96 L12 76 L12 24 Z" fill={stroke} stroke={color} strokeWidth="3" />
      <path d="M20 64 L20 38 L27.5 52 L35 38 L35 64" stroke={color} strokeWidth="5.5" fill="none" strokeLinejoin="miter" strokeMiterlimit="2" />
      <path d="M43.5 42 Q50 33 56 41 Q57.5 47 51 53.5 L44.5 60.5 L57 60.5" stroke={color} strokeWidth="5.5" fill="none" strokeLinecap="square" strokeLinejoin="miter" strokeMiterlimit="2" />
      <path d="M65 64 L65 38 L72.5 52 L80 38 L80 64" stroke={color} strokeWidth="5.5" fill="none" strokeLinejoin="miter" strokeMiterlimit="2" />
      <path d="M16 86 L24 86" stroke={color} strokeWidth="2" />
      <path d="M16 82 L20 82" stroke={color} strokeWidth="2" opacity="0.6" />
    </svg>);
}

// LOGO LOCKUP — mark + wordmark
function LogoLockup({ size = 40, color, accent, dense = false, mark }) {
  color = color || COLORS.text;
  accent = accent || COLORS.red;
  const Mark = mark === 'm2m' ? M2MMark : UR2DMark;
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: dense ? 10 : 14 }}>
      <Mark size={size} color={accent} stroke={COLORS.black} />
      <div style={{ display: 'flex', flexDirection: 'column', lineHeight: 1 }}>
        <span style={{ fontFamily: 'Archivo', fontWeight: 900, fontSize: size * 0.5, letterSpacing: '0.08em', color }}>UR2D</span>
        <span style={{ fontFamily: 'Archivo', fontWeight: 600, fontSize: size * 0.26, letterSpacing: '0.3em', color: accent, marginTop: 4 }}>RACING ESPORTS</span>
      </div>
    </div>);

}

// ─────────────────────────────────────────────────────────────────────────────
// Export everything to window
Object.assign(window, {
  URL_LEAGUE, COLORS, FlagSvg, urFlagImg, UR_LOCAL_FLAGS, URImageSlot, GraphicPlaceholder,
  getCountdown, usePage, UR2DMark, M2MMark, LogoLockup,
  TEAM_BY_ID, DRIVER_BY_ID,
  LEAGUES, LEAGUE_BY_ID, useLeagueState, LeagueBadge, LeagueSwitcher
});