In The Game
  • Home
  • Blog
  • AFL
    • Overview

    • Player Stats
    • Player Ratings
    • Player Game Logs
    • Player Comparison

    • Team Stats
    • Team Game Logs
    • Team Ratings

    • Matches
    • Ladder
    • Definitions
  • Football
    • Overview

    • Player Stats
    • Player Ratings
    • Player Game Logs
    • Player Comparison

    • Team Stats
    • Team Game Logs
    • Team Ratings

    • Leagues
    • Matches
    • Definitions
  • About
Skip to content

AFL Matches

AFL match results and predictions powered by TORP ratings

AFL > Matches

Match-by-match predictions from the TORP model. H% = home win probability. A% = away win probability. TORP = TORP model prediction. xS = shot quality win probability (finished matches). Live = in-game win probability with draw segment (live matches).

Show code
statsEsc = window.statsEsc
statsTable = window.statsTable

fetchParquet = window.fetchParquet
base_url = window.DATA_BASE_URL

predictions = fetchParquet(base_url + "afl/predictions.parquet")

fixtures = {
  const data = await window.fetchFixtures("afl")
  return data ? (data.games || null) : null
}

// Team name mappings (from shared afl/team-maps.js)
predToFull = window.aflTeamMaps?.predToFull || {}
fullToPred = window.aflTeamMaps?.fullToPred || {}
predToAbbr = window.aflTeamMaps?.predToAbbr || {}
squiggleToPred = window.aflTeamMaps?.squiggleToPred || {}
aflTeamColors = window.aflTeamMaps?.aflTeamColors || {}
teamLogo = window.aflTeamMaps?.teamLogo || (() => null)

// Resolve any team name format to pred name or abbreviation
toPred = (name) => fullToPred[name] || (predToFull[name] ? name : null) || name
teamToAbbr = (name) => predToAbbr[toPred(name)] || name

// Build a lookup: "round|home_full|away_full" → fixture object
// Normalize Squiggle names → canonical full names via predToFull
fixtureMap = {
  if (!fixtures) return {}
  const map = {}
  for (const f of fixtures) {
    const h = predToFull[squiggleToPred[f.hteam] || f.hteam] || predToFull[f.hteam] || f.hteam
    const a = predToFull[squiggleToPred[f.ateam] || f.ateam] || predToFull[f.ateam] || f.ateam
    map[`${f.round}|${h}|${a}`] = f
  }
  return map
}

lookupFixture = (round, home, away) => {
  const h = predToFull[home] || home
  const a = predToFull[away] || away
  return fixtureMap[`${round}|${h}|${a}`]
}
Show code
currentYear = new Date().getFullYear()

seasons = {
  if (!predictions) return [currentYear]
  const raw = [...new Set(predictions.map(d => d.season))].filter(s => s != null)
  return raw.length > 0 ? raw.sort((a, b) => b - a) : [currentYear]
}

defaultSeason = {
  if (seasons.includes(currentYear)) return currentYear
  if (seasons.includes(currentYear - 1)) return currentYear - 1
  return seasons[0]
}

viewof selectedSeason = predictions == null
  ? html`<p></p>`
  : Inputs.select(seasons, {
      value: defaultSeason,
      format: d => `${d}`,
      label: "Season"
    })

seasonPreds = {
  if (!predictions || selectedSeason == null) return []
  return predictions.filter(d => d.season === selectedSeason)
}

// Fixtures are only relevant for the current (max) season
isCurrentSeason = seasons.length > 0 && selectedSeason === seasons[0]
seasonFixtures = isCurrentSeason ? fixtures : null
Show code
// Round / Results / Fixtures toggle
viewof matchView = {
  const wrap = document.createElement("div")
  wrap.className = "epv-toggle"
  wrap.value = "Round"
  for (const label of ["Round", "Results", "Fixtures"]) {
    const btn = document.createElement("button")
    btn.className = "epv-toggle-btn" + (label === "Round" ? " active" : "")
    btn.textContent = label
    btn.addEventListener("click", () => {
      wrap.querySelectorAll(".epv-toggle-btn").forEach(b => b.classList.remove("active"))
      btn.classList.add("active")
      wrap.value = label
      wrap.dispatchEvent(new Event("input", { bubbles: true }))
    })
    wrap.appendChild(btn)
  }
  return wrap
}
Show code
roundLabels = {
  if (seasonPreds.length === 0 && !seasonFixtures) return []
  const predRounds = seasonPreds.map(d => d.round)
  const fixRounds = seasonFixtures ? seasonFixtures.map(d => d.round) : []
  const rounds = [...new Set([...predRounds, ...fixRounds])].sort((a, b) => a - b)
  return rounds.map(r => ({
    value: r,
    label: r === 0 ? "Opening Round" : `Round ${r}`
  }))
}

// Helper: check if a match is finished (using both predictions AND fixture data)
_isFinished = (m, round) => {
  if (m.actual_margin != null && !isNaN(m.actual_margin)) return true
  const f = lookupFixture(round, m.home_team, m.away_team)
  return f && f.complete === 100
}

defaultRound = {
  if (roundLabels.length === 0) return 0
  const allRounds = roundLabels.map(r => r.value)

  // Results/Fixtures default to "All Rounds"
  if (matchView === "Results" || matchView === "Fixtures") return "all"

  // Round tab: first round with at least one unfinished game
  let foundRound = allRounds[allRounds.length - 1]
  for (const r of allRounds) {
    const preds = seasonPreds.filter(d => d.round === r)
    if (preds.length > 0) {
      if (preds.some(d => !_isFinished(d, r))) { foundRound = r; break }
    } else if (seasonFixtures) {
      const fixMatches = seasonFixtures.filter(d => d.round === r)
      if (fixMatches.some(d => d.complete < 100)) { foundRound = r; break }
    }
  }

  // Tuesday switchover: on Tue/Wed, if current round mostly complete, advance
  const day = new Date().getDay()
  if (day === 2 || day === 3) {
    const preds = seasonPreds.filter(d => d.round === foundRound)
    const finished = preds.filter(d => _isFinished(d, foundRound)).length
    if (preds.length > 0 && finished >= preds.length - 1) {
      const next = allRounds.find(r => r > foundRound)
      if (next != null) foundRound = next
    }
  }

  return foundRound
}

// Round dropdown options: prepend "All Rounds" for Results/Fixtures
roundOptions = {
  if (matchView === "Results" || matchView === "Fixtures") {
    return [{ value: "all", label: "All Rounds" }, ...roundLabels]
  }
  return roundLabels
}

viewof selectedRound = roundOptions.length === 0
  ? html`<p></p>`
  : Inputs.select(roundOptions, {
      value: roundOptions.find(r => r.value === defaultRound),
      format: d => d.label,
      label: "Round"
    })
Show code
roundMatches = {
  if (!selectedRound) return null
  const roundValue = selectedRound.value

  // "All Rounds" mode for Results/Fixtures
  if (roundValue === "all") {
    let filtered = seasonPreds
      .filter(m => {
        const finished = _isFinished(m, m.round)
        return matchView === "Results" ? finished : !finished
      })

    // Include fixture-only rounds (no predictions yet)
    if (seasonFixtures) {
      const predRounds = new Set(seasonPreds.map(d => `${d.round}|${predToFull[d.home_team] || d.home_team}|${predToFull[d.away_team] || d.away_team}`))
      const fixtureOnly = seasonFixtures
        .filter(f => {
          const h = predToFull[squiggleToPred[f.hteam] || f.hteam] || predToFull[f.hteam] || f.hteam
          const a = predToFull[squiggleToPred[f.ateam] || f.ateam] || predToFull[f.ateam] || f.ateam
          if (predRounds.has(`${f.round}|${h}|${a}`)) return false
          return matchView === "Results" ? f.complete === 100 : f.complete < 100
        })
        .map(f => ({
          home_team: predToFull[squiggleToPred[f.hteam] || f.hteam] || predToFull[f.hteam] || f.hteam,
          away_team: predToFull[squiggleToPred[f.ateam] || f.ateam] || predToFull[f.ateam] || f.ateam,
          round: f.round,
          fixture: f,
          fixtureOnly: true
        }))
      filtered = [...filtered, ...fixtureOnly]
    }

    // Results: most recent round first; Fixtures: next round first
    const dir = matchView === "Results" ? -1 : 1
    filtered.sort((a, b) => {
      if (a.round !== b.round) return (a.round - b.round) * dir
      const fa = a.fixture || lookupFixture(a.round, a.home_team, a.away_team)
      const fb = b.fixture || lookupFixture(b.round, b.home_team, b.away_team)
      if (fa && fb) return (fa.unixtime || 0) - (fb.unixtime || 0)
      return 0
    })
    return filtered
  }

  // Single round mode
  const round = roundValue
  const predMatches = seasonPreds.filter(d => d.round === round)

  // For fixture-only rounds, build cards from Squiggle data
  if (predMatches.length === 0 && seasonFixtures) {
    const fixMatches = seasonFixtures.filter(d => d.round === round)
      .sort((a, b) => (a.unixtime || 0) - (b.unixtime || 0))
    if (fixMatches.length > 0) return fixMatches.map(f => ({
      home_team: predToFull[squiggleToPred[f.hteam] || f.hteam] || predToFull[f.hteam] || f.hteam,
      away_team: predToFull[squiggleToPred[f.ateam] || f.ateam] || predToFull[f.ateam] || f.ateam,
      round: f.round,
      fixture: f,
      fixtureOnly: true
    }))
  }

  // Round view shows all; Results/Fixtures filter by status
  const filtered = matchView === "Round"
    ? predMatches
    : predMatches.filter(m => {
        const finished = _isFinished(m, round)
        return matchView === "Results" ? finished : !finished
      })

  // Sort by fixture kickoff time when available
  return filtered.sort((a, b) => {
    const fa = lookupFixture(round, a.home_team, a.away_team)
    const fb = lookupFixture(round, b.home_team, b.away_team)
    if (fa && fb) return (fa.unixtime || 0) - (fb.unixtime || 0)
    return Math.abs(b.pred_margin) - Math.abs(a.pred_margin)
  })
}

formatMatchDate = (dateStr) => window.formatMatchDate(dateStr, "short")

// Team form from played matches this season (stores result + match link)
aflTeamForm = {
  if (!seasonPreds) return {}
  const form = {}
  const played = seasonPreds
    .filter(d => _isFinished(d, d.round))
    .sort((a, b) => (a.round || 0) - (b.round || 0))
  for (const m of played) {
    const hKey = m.home_team
    const aKey = m.away_team
    if (!form[hKey]) form[hKey] = []
    if (!form[aKey]) form[aKey] = []
    const url = `match#season=${selectedSeason}&round=${m.round}&home=${teamToAbbr(m.home_team)}&away=${teamToAbbr(m.away_team)}`
    // Check actual_margin first, fall back to fixture scores
    let hResult, aResult, hScore, aScore
    const homeFull = predToFull[m.home_team] || m.home_team
    const awayFull = predToFull[m.away_team] || m.away_team
    const f = lookupFixture(m.round, m.home_team, m.away_team)
    if (m.actual_margin != null && !isNaN(m.actual_margin)) {
      hResult = m.actual_margin > 0 ? "W" : m.actual_margin < 0 ? "L" : "D"
      aResult = m.actual_margin > 0 ? "L" : m.actual_margin < 0 ? "W" : "D"
      hScore = f?.hscore ?? (m.pred_total != null ? Math.round((m.pred_total + m.actual_margin) / 2) : null)
      aScore = f?.ascore ?? (m.pred_total != null ? Math.round((m.pred_total - m.actual_margin) / 2) : null)
    } else if (f && f.hscore != null) {
      hResult = f.hscore > f.ascore ? "W" : f.hscore < f.ascore ? "L" : "D"
      aResult = f.hscore > f.ascore ? "L" : f.hscore < f.ascore ? "W" : "D"
      hScore = f.hscore; aScore = f.ascore
    }
    if (hResult) {
      const hAbbr = teamToAbbr(m.home_team), aAbbr = teamToAbbr(m.away_team)
      const scoreStr = hScore != null ? ` ${hScore}–${aScore}` : ""
      form[hKey].push({ result: hResult, url, tip: `${hAbbr} v ${aAbbr}${scoreStr} ${hResult}` })
      form[aKey].push({ result: aResult, url, tip: `${hAbbr} v ${aAbbr}${scoreStr} ${aResult}` })
    }
  }
  for (const k of Object.keys(form)) form[k] = form[k].slice(-5)
  return form
}

aflFormDots = (team) => {
  const results = aflTeamForm[team]
  if (!results || results.length === 0) return ""
  return `<span class="form-dots">${results.map(r =>
    `<span class="form-dot form-${r.result.toLowerCase()}" data-tip="${statsEsc(r.tip || "")}" role="link" tabindex="0" onclick="event.preventDefault();event.stopPropagation();window.location.href='${r.url}'"></span>`
  ).join("")}</span>`
}

matchCards = {
  if (!roundMatches || roundMatches.length === 0) {
    const msg = matchView === "Results"
      ? "No results available."
      : matchView === "Fixtures"
      ? "No upcoming fixtures."
      : "No matches available for this round."
    return html`<p class="text-muted">${msg}</p>`
  }

  const showForm = matchView !== "Results"
  const allRoundsMode = selectedRound.value === "all"

  const badge = (name) => {
    const src = teamLogo(name)
    return src ? `<img src="${src}" alt="" class="team-badge" role="link" tabindex="0" onclick="event.preventDefault();event.stopPropagation();window.location.href='team#team=${encodeURIComponent(name)}'">` : ""
  }

  const buildCard = (m, round) => {
    const homeName = predToFull[m.home_team] || m.home_team
    const awayName = predToFull[m.away_team] || m.away_team
    const matchUrl = `match#season=${selectedSeason}&round=${round}&home=${teamToAbbr(m.home_team)}&away=${teamToAbbr(m.away_team)}`

    // Fixture-only card (no TORP predictions yet)
    if (m.fixtureOnly) {
      const f = m.fixture
      const played = f.complete === 100
      const isLive = f.complete > 0 && f.complete < 100
      const liveClass = isLive ? " is-live" : ""
      const liveBadge = isLive ? `<span class="live-badge">LIVE</span>` : ""
      const liveQuarter = isLive && f.timestr ? `<div class="match-quarter">${statsEsc(f.timestr)}</div>` : ""
      const scoreHtml = (played || isLive)
        ? `<div class="match-score">${f.hscore ?? 0} – ${f.ascore ?? 0}${liveQuarter}</div>`
        : ""
      return `
        <a href="${matchUrl}" class="match-card-link">
        <div class="match-card fixture-only${liveClass}" data-match-id="${f.id}">
          <div class="match-info">${liveBadge}${statsEsc(formatMatchDate(f.date))}${f.venue ? ` · ${statsEsc(f.venue)}` : ""}</div>
          <div class="match-teams">
            <div class="team">${badge(homeName)}<span class="team-link" role="link" tabindex="0" onclick="event.preventDefault();event.stopPropagation();window.location.href='team#team=${encodeURIComponent(homeName)}'">${statsEsc(homeName)}</span></div>
            <div class="match-vs">${(played || isLive) ? scoreHtml : "vs"}</div>
            <div class="team">${badge(awayName)}<span class="team-link" role="link" tabindex="0" onclick="event.preventDefault();event.stopPropagation();window.location.href='team#team=${encodeURIComponent(awayName)}'">${statsEsc(awayName)}</span></div>
          </div>
        </div>
        </a>`
    }

    // Standard prediction card — enrich with fixture data
    const f = lookupFixture(m.round, m.home_team, m.away_team)
    const homeWin = m.pred_margin > 0
    const played = m.actual_margin != null && !isNaN(m.actual_margin)
    const isLive = f && f.complete > 0 && f.complete < 100
    const liveClass = isLive ? " is-live" : ""
    const liveBadge = isLive ? `<span class="live-badge">LIVE</span>` : ""
    const matchIdAttr = f ? ` data-match-id="${f.id}"` : ""
    const infoHtml = f
      ? `<div class="match-info">${liveBadge}${statsEsc(formatMatchDate(f.date))}${f.venue ? ` · ${statsEsc(f.venue)}` : ""}</div>`
      : ""
    const squigglePlayed = f && f.complete === 100
    const isFinished = played || squigglePlayed
    const hasXs = m.xscore_home != null && m.xscore_away != null

    const predHome = m.pred_total != null && m.pred_margin != null ? Math.round((m.pred_total + m.pred_margin) / 2) : null
    const predAway = m.pred_total != null && m.pred_margin != null ? Math.round((m.pred_total - m.pred_margin) / 2) : null

    let scoreHtml = "vs"
    if (isLive) {
      const liveQuarter = f.timestr ? `<div class="match-quarter">${statsEsc(f.timestr)}</div>` : ""
      scoreHtml = `<div class="match-score">${f.hscore ?? 0} – ${f.ascore ?? 0}${liveQuarter}</div>`
    } else if (squigglePlayed) {
      const xsLine = hasXs ? `<div class="match-xg">xS: ${m.xscore_home.toFixed(0)} – ${m.xscore_away.toFixed(0)}</div>` : ""
      scoreHtml = `<div class="match-score">${f.hscore} – ${f.ascore}${xsLine}</div>`
    } else if (played) {
      const homeScore = m.pred_total != null ? Math.round((m.pred_total + m.actual_margin) / 2) : null
      const awayScore = m.pred_total != null ? Math.round((m.pred_total - m.actual_margin) / 2) : null
      const xsLine = hasXs ? `<div class="match-xg">xS: ${m.xscore_home.toFixed(0)} – ${m.xscore_away.toFixed(0)}</div>` : ""
      if (homeScore != null && awayScore != null) {
        scoreHtml = `<div class="match-score">${homeScore} – ${awayScore}${xsLine}</div>`
      }
    }

    const dualBar = isFinished && hasXs
    let xsBarHtml = ""
    if (dualBar) {
      const xsMargin = m.xscore_home - m.xscore_away
      const xsHomeProb = 1 / (1 + Math.exp(-xsMargin / 20))
      const xsHomePct = (xsHomeProb * 100).toFixed(0)
      const xsAwayPct = ((1 - xsHomeProb) * 100).toFixed(0)
      xsBarHtml = `
        <div class="prob-bar-row">
          <span class="prob-bar-label">xS</span>
          <div class="prob-bar-container">
            <div class="prob-bar prob-home" style="width: ${xsHomeProb * 100}%">
              ${xsHomeProb >= 0.15 ? xsHomePct + '%' : ''}
            </div>
            <div class="prob-bar prob-away" style="width: ${(1 - xsHomeProb) * 100}%">
              ${(1 - xsHomeProb) >= 0.15 ? xsAwayPct + '%' : ''}
            </div>
          </div>
        </div>`
    }

    let liveWpBarHtml = ""
    if (isLive && window.aflMatchProbs) {
      const probs = window.aflMatchProbs(f.timestr, f.complete, f.hscore, f.ascore)
      const hPct = (probs.home * 100).toFixed(0)
      const dPct = (probs.draw * 100).toFixed(0)
      const aPct = (probs.away * 100).toFixed(0)
      const showDraw = probs.draw >= 0.02
      liveWpBarHtml = `
        <div class="prob-bar-row" data-wp-bar>
          <span class="prob-bar-label">Live</span>
          <div class="prob-bar-container">
            <div class="prob-bar prob-home" style="width: ${probs.home * 100}%">
              ${probs.home >= 0.15 ? hPct + '%' : ''}
            </div>
            ${showDraw ? `<div class="prob-bar prob-draw" style="width: ${probs.draw * 100}%">
              ${probs.draw >= 0.05 ? dPct + '%' : ''}
            </div>` : ''}
            <div class="prob-bar prob-away" style="width: ${(showDraw ? probs.away : 1 - probs.home) * 100}%">
              ${probs.away >= 0.15 ? aPct + '%' : ''}
            </div>
          </div>
        </div>`
    }

    const homeRating = m.pred_total != null ? `<span class="rating">${predHome}</span>` : ""
    const awayRating = m.pred_total != null ? `<span class="rating">${predAway}</span>` : ""

    return `
      <a href="${matchUrl}" class="match-card-link">
      <div class="match-card${liveClass}"${matchIdAttr}>
        ${infoHtml}
        <div class="match-teams">
          <div class="team ${homeWin ? 'favoured' : ''}">${badge(homeName)}<span class="team-link" role="link" tabindex="0" onclick="event.preventDefault();event.stopPropagation();window.location.href='team#team=${encodeURIComponent(homeName)}'">${statsEsc(homeName)}</span>
            ${homeRating}
            ${showForm ? aflFormDots(m.home_team) : ""}
          </div>
          <div class="match-vs">${scoreHtml}</div>
          <div class="team ${!homeWin ? 'favoured' : ''}">${badge(awayName)}<span class="team-link" role="link" tabindex="0" onclick="event.preventDefault();event.stopPropagation();window.location.href='team#team=${encodeURIComponent(awayName)}'">${statsEsc(awayName)}</span>
            ${awayRating}
            ${showForm ? aflFormDots(m.away_team) : ""}
          </div>
        </div>
        <div class="match-prediction">
          <div class="prob-bars-group">
            <div class="prob-bar-row">
              ${(dualBar || isLive) ? '<span class="prob-bar-label">TORP</span>' : ''}
              <div class="prob-bar-container">
                <div class="prob-bar prob-home" style="width: ${m.home_win_prob * 100}%">
                  ${m.home_win_prob >= 0.15 ? (m.home_win_prob * 100).toFixed(0) + '%' : ''}
                </div>
                <div class="prob-bar prob-away" style="width: ${(1 - m.home_win_prob) * 100}%">
                  ${(1 - m.home_win_prob) >= 0.15 ? ((1 - m.home_win_prob) * 100).toFixed(0) + '%' : ''}
                </div>
              </div>
            </div>
            ${xsBarHtml}
            ${liveWpBarHtml}
          </div>
        </div>
        <div class="match-card-footer">
          ${!isFinished && !isLive && predHome != null ? `<div class="pred-summary">Prediction: ${predHome === predAway ? "Draw" : `${homeWin ? statsEsc(homeName) : statsEsc(awayName)} by ${Math.abs(predHome - predAway)} pts`}</div>` : ""}
          <div class="match-chain-link"><span class="chain-nav" role="link" tabindex="0" onclick="event.preventDefault();event.stopPropagation();window.location.href='match-chains.html#season=${selectedSeason}&round=${round}&home=${teamToAbbr(m.home_team)}&away=${teamToAbbr(m.away_team)}'">Chains →</span></div>
        </div>
      </div>
      </a>`
  }

  // Build cards with round group separators in all-rounds mode
  const parts = []
  let lastRound = null
  for (const m of roundMatches) {
    const round = m.round ?? selectedRound.value
    if (allRoundsMode && round !== lastRound) {
      const label = round === 0 ? "Opening Round" : `Round ${round}`
      parts.push(`<div class="round-group-header">${label}</div>`)
      lastRound = round
    }
    parts.push(buildCard(m, round))
  }

  return html`<div class="match-cards-container">${parts.join('')}</div>`
}
Show code
// Live score polling — updates match card scores via DOM manipulation
_livePoller = {
  if (!isCurrentSeason || !window.aflLivePoller) return null

  window.aflLivePoller.start({
    onScoreUpdate: (games) => {
      for (const g of games) {
        const card = document.querySelector(`[data-match-id="${g.id}"]`)
        if (!card) continue

        const isLive = g.complete > 0 && g.complete < 100
        const isFinished = g.complete === 100

        // Update live class
        card.classList.toggle("is-live", isLive)

        // Update LIVE badge in match-info
        const info = card.querySelector(".match-info")
        if (info) {
          const existingBadge = info.querySelector(".live-badge")
          if (isLive && !existingBadge) {
            const badge = document.createElement("span")
            badge.className = "live-badge"
            badge.textContent = "LIVE"
            info.prepend(badge)
          } else if (!isLive && existingBadge) {
            existingBadge.remove()
          }
        }

        // Update score in match-vs
        const vs = card.querySelector(".match-vs")
        if (vs && (isLive || isFinished)) {
          // Clear existing content safely
          while (vs.firstChild) vs.removeChild(vs.firstChild)
          const scoreDiv = document.createElement("div")
          scoreDiv.className = "match-score"
          scoreDiv.textContent = `${g.hscore ?? 0} – ${g.ascore ?? 0}`
          if (isLive && g.timestr) {
            const qtr = document.createElement("div")
            qtr.className = "match-quarter"
            qtr.textContent = g.timestr
            scoreDiv.appendChild(qtr)
          }
          vs.appendChild(scoreDiv)
        }

        // Update live win probability bar (3-segment: home|draw|away)
        const wpBar = card.querySelector("[data-wp-bar]")
        if (isLive && window.aflMatchProbs) {
          const probs = window.aflMatchProbs(g.timestr, g.complete, g.hscore, g.ascore)
          if (wpBar) {
            const container = wpBar.querySelector(".prob-bar-container")
            if (container) {
              const hBar = container.querySelector(".prob-home")
              const dBar = container.querySelector(".prob-draw")
              const aBar = container.querySelector(".prob-away")
              if (hBar) { hBar.style.width = (probs.home * 100) + "%"; hBar.textContent = probs.home >= 0.15 ? (probs.home * 100).toFixed(0) + "%" : "" }
              if (aBar) { aBar.style.width = (probs.away * 100) + "%"; aBar.textContent = probs.away >= 0.15 ? (probs.away * 100).toFixed(0) + "%" : "" }
              if (probs.draw >= 0.02) {
                if (dBar) {
                  dBar.style.width = (probs.draw * 100) + "%"
                  dBar.textContent = probs.draw >= 0.05 ? (probs.draw * 100).toFixed(0) + "%" : ""
                } else {
                  // Insert draw segment between home and away
                  const newDraw = document.createElement("div")
                  newDraw.className = "prob-bar prob-draw"
                  newDraw.style.width = (probs.draw * 100) + "%"
                  if (probs.draw >= 0.05) newDraw.textContent = (probs.draw * 100).toFixed(0) + "%"
                  container.insertBefore(newDraw, aBar)
                }
              } else if (dBar) {
                dBar.remove()
              }
            }
          }
        } else if (!isLive && wpBar) {
          wpBar.remove()
        }
      }
    }
  })

  // Cleanup when cell is invalidated (round/season change)
  invalidation.then(() => window.aflLivePoller.stop())
  return "polling"
}
 

Pete Owen · Sydney · © 2026 · Source

Privacy | Disclaimer