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
Show code
statsEsc = window.statsEsc
base_url = window.DATA_BASE_URL

simData = window.fetchParquet(base_url + "afl/simulations.parquet")

aflFixtures = {
  const data = await window.fetchFixtures("afl")
  return data ? (data.games || []) : []
}

predToFull = window.aflTeamMaps?.predToFull || {}
predToAbbr = window.aflTeamMaps?.predToAbbr || {}
squiggleToPred = window.aflTeamMaps?.squiggleToPred || {}
aflTeamColors = window.aflTeamMaps?.aflTeamColors || {}
teamLogo = window.aflTeamMaps?.teamLogo
Show code
// Hero header
{
  const el = document.createElement("div")
  el.className = "afl-dash-hero"

  const title = document.createElement("h1")
  title.className = "afl-dash-title"
  title.textContent = "AFL"

  el.appendChild(title)
  return el
}
Show code
// Navigation cards
{
  // SVG icon builder — all paths are hardcoded constants
  const ns = "http://www.w3.org/2000/svg"
  function svgIcon(children) {
    const s = document.createElementNS(ns, "svg")
    s.setAttribute("viewBox", "0 0 24 24")
    s.setAttribute("width", "24")
    s.setAttribute("height", "24")
    s.setAttribute("fill", "none")
    s.setAttribute("stroke", "#c89a3e")
    s.setAttribute("stroke-width", "1.5")
    s.setAttribute("stroke-linecap", "round")
    s.setAttribute("stroke-linejoin", "round")
    for (const c of children) {
      const el = document.createElementNS(ns, c[0])
      for (const [k, v] of Object.entries(c[1])) el.setAttribute(k, v)
      s.appendChild(el)
    }
    return s
  }

  const icons = {
    matches: () => svgIcon([
      ["rect", {x:2,y:5,width:20,height:14,rx:2}],
      ["line", {x1:12,y1:5,x2:12,y2:19}],
      ["line", {x1:7,y1:10,x2:7,y2:14}],
      ["line", {x1:17,y1:10,x2:17,y2:14}]
    ]),
    "player-ratings": () => svgIcon([
      ["path", {d:"M12 20a8 8 0 1 1 8-8"}],
      ["path", {d:"M12 20a8 8 0 0 1-8-8"}],
      ["line", {x1:12,y1:12,x2:16,y2:8}],
      ["circle", {cx:12,cy:12,r:1.5,fill:"#c89a3e",stroke:"none"}]
    ]),
    "player-stats": () => svgIcon([
      ["line", {x1:4,y1:20,x2:20,y2:20}],
      ["rect", {x:5,y:13,width:3,height:7,rx:0.5}],
      ["rect", {x:10.5,y:9,width:3,height:11,rx:0.5}],
      ["rect", {x:16,y:4,width:3,height:16,rx:0.5}]
    ]),
    ladder: () => svgIcon([
      ["circle", {cx:6,cy:6,r:2,fill:"#c89a3e",stroke:"none",opacity:0.9}],
      ["line", {x1:11,y1:6,x2:20,y2:6}],
      ["circle", {cx:6,cy:12,r:2,fill:"#c89a3e",stroke:"none",opacity:0.5}],
      ["line", {x1:11,y1:12,x2:18,y2:12}],
      ["circle", {cx:6,cy:18,r:2,fill:"#c89a3e",stroke:"none",opacity:0.25}],
      ["line", {x1:11,y1:18,x2:16,y2:18}]
    ]),
    "team-ratings": () => svgIcon([
      ["path", {d:"M12 2L4 6v5c0 5.25 3.4 10.2 8 12 4.6-1.8 8-6.75 8-12V6l-8-4z"}],
      ["polyline", {points:"9 12 11 14 15 10"}]
    ]),
    "team-stats": () => svgIcon([
      ["rect", {x:3,y:3,width:18,height:18,rx:2}],
      ["line", {x1:3,y1:9,x2:21,y2:9}],
      ["line", {x1:3,y1:15,x2:21,y2:15}],
      ["line", {x1:9,y1:3,x2:9,y2:21}],
      ["line", {x1:15,y1:3,x2:15,y2:21}]
    ])
  }

  const pages = [
    { title: "Matches", desc: "Results, predictions & win probabilities", href: "matches", icon: "matches" },
    { title: "Player Ratings", desc: "Predictive TORP, EPR & PSR ratings", href: "player-ratings", icon: "player-ratings" },
    { title: "Player Stats", desc: "Per-game box scores & EPV values", href: "player-stats", icon: "player-stats" },
    { title: "Ladder", desc: "Current standings & season projections", href: "ladder", icon: "ladder" },
    { title: "Team Ratings", desc: "Squad strength & component ratings", href: "team-ratings", icon: "team-ratings" },
    { title: "Team Stats", desc: "Aggregated team match statistics", href: "team-stats", icon: "team-stats" }
  ]

  const grid = document.createElement("div")
  grid.className = "afl-dash-nav-grid"

  for (const p of pages) {
    const card = document.createElement("a")
    card.href = p.href
    card.className = "afl-dash-nav-card"

    const iconSpan = document.createElement("span")
    iconSpan.className = "afl-dash-nav-icon"
    iconSpan.appendChild(icons[p.icon]())

    const body = document.createElement("div")
    const title = document.createElement("div")
    title.className = "afl-dash-nav-title"
    title.textContent = p.title
    const desc = document.createElement("div")
    desc.className = "afl-dash-nav-desc"
    desc.textContent = p.desc

    body.appendChild(title)
    body.appendChild(desc)
    card.appendChild(iconSpan)
    card.appendChild(body)
    grid.appendChild(card)
  }

  return grid
}
Show code
// Dashboard grid: ladder + recent matches
{
  const el = document.createElement("div")
  el.className = "afl-dash-grid"

  // ── LEFT: Mini ladder ──
  const ladderPanel = document.createElement("div")
  ladderPanel.className = "afl-dash-panel afl-dash-ladder"

  const ladderH = document.createElement("h2")
  ladderH.className = "afl-dash-panel-title"
  ladderH.textContent = "Ladder"
  const ladderLink = document.createElement("a")
  ladderLink.href = "ladder"
  ladderLink.className = "afl-dash-panel-link"
  ladderLink.textContent = "Full ladder →"
  ladderH.appendChild(ladderLink)
  ladderPanel.appendChild(ladderH)

  if (simData && simData.length > 0) {
    const sorted = [...simData].sort((a, b) => {
      const ptsA = (a.current_wins || 0) * 4
      const ptsB = (b.current_wins || 0) * 4
      if (ptsB !== ptsA) return ptsB - ptsA
      return (b.current_pct || 0) - (a.current_pct || 0)
    })

    const table = document.createElement("table")
    table.className = "afl-dash-ladder-table"

    const thead = document.createElement("thead")
    const headRow = document.createElement("tr")
    for (const h of ["Team", "W", "L", "Pts", "%"]) {
      const th = document.createElement("th")
      th.textContent = h
      headRow.appendChild(th)
    }
    thead.appendChild(headRow)
    table.appendChild(thead)

    const tbody = document.createElement("tbody")
    sorted.forEach((t, i) => {
      const tr = document.createElement("tr")
      const fn = predToFull[t.team] || t.team
      const abbr = predToAbbr[fn] || predToAbbr[t.team] || t.team.substring(0, 3).toUpperCase()
      const teamColor = aflTeamColors[fn] || aflTeamColors[t.team] || "#c89a3e"

      const teamTd = document.createElement("td")
      const teamLink = document.createElement("a")
      teamLink.href = `team#team=${encodeURIComponent(fn)}`
      teamLink.className = "afl-dash-team-link"
      teamLink.style.paddingLeft = "2px"
      const logoSrc = teamLogo(fn)
      if (logoSrc) {
        const img = document.createElement("img")
        img.src = logoSrc
        img.className = "afl-dash-team-badge"
        teamLink.appendChild(img)
      }
      const nameSpan = document.createElement("span")
      nameSpan.textContent = abbr
      teamLink.appendChild(nameSpan)
      teamTd.appendChild(teamLink)

      const w = document.createElement("td"); w.textContent = t.current_wins ?? 0
      const l = document.createElement("td"); l.textContent = t.current_losses ?? 0
      const pts = document.createElement("td"); pts.className = "ladder-pts"; pts.textContent = (t.current_wins || 0) * 4
      const pct = document.createElement("td"); pct.textContent = t.current_pct?.toFixed(1) ?? "—"

      tr.appendChild(teamTd)
      tr.appendChild(w)
      tr.appendChild(l)
      tr.appendChild(pts)
      tr.appendChild(pct)
      tbody.appendChild(tr)
    })
    table.appendChild(tbody)
    ladderPanel.appendChild(table)
  }

  // ── RIGHT: Recent matches ──
  const matchesPanel = document.createElement("div")
  matchesPanel.className = "afl-dash-panel afl-dash-matches"

  const matchesH = document.createElement("h2")
  matchesH.className = "afl-dash-panel-title"
  matchesH.textContent = "Recent Matches"
  const matchesLink = document.createElement("a")
  matchesLink.href = "matches"
  matchesLink.className = "afl-dash-panel-link"
  matchesLink.textContent = "All matches →"
  matchesH.appendChild(matchesLink)
  matchesPanel.appendChild(matchesH)

  const finished = aflFixtures
    .filter(g => g.complete === 100 && g.hscore != null)
    .sort((a, b) => (b.date || "").localeCompare(a.date || ""))
    .slice(0, 12)

  if (finished.length > 0) {
    const season = simData?.[0]?.season || new Date().getFullYear()

    // Sort by round descending, then by date within round
    finished.sort((a, b) => (b.round || 0) - (a.round || 0) || (b.date || "").localeCompare(a.date || ""))

    let lastRound = null
    for (const g of finished) {
      // Round separator
      if (g.round !== lastRound) {
        if (lastRound !== null) {
          const sep = document.createElement("div")
          sep.className = "afl-dash-round-sep"
          matchesPanel.appendChild(sep)
        }
        const roundLabel = document.createElement("div")
        roundLabel.className = "afl-dash-round-label"
        roundLabel.textContent = "Round " + g.round
        matchesPanel.appendChild(roundLabel)
        lastRound = g.round
      }
      const hPred = predToFull[squiggleToPred[g.hteam] || g.hteam] || predToFull[g.hteam] || g.hteam
      const aPred = predToFull[squiggleToPred[g.ateam] || g.ateam] || predToFull[g.ateam] || g.ateam
      const hFull = predToFull[hPred] || g.hteam
      const aFull = predToFull[aPred] || g.ateam
      const hAbbr = predToAbbr[hPred] || hPred
      const aAbbr = predToAbbr[aPred] || aPred
      const homeWin = g.hscore > g.ascore
      const hColor = aflTeamColors[hFull] || "#c89a3e"
      const aColor = aflTeamColors[aFull] || "#c89a3e"

      const card = document.createElement("a")
      card.href = `match#season=${season}&round=${g.round}&home=${hAbbr}&away=${aAbbr}`
      card.className = "afl-dash-match-card"

      const homeDiv = document.createElement("div")
      homeDiv.className = "afl-dash-match-team" + (homeWin ? " winner" : "")
      const hLogo = teamLogo(hFull)
      if (hLogo) {
        const hImg = document.createElement("img")
        hImg.src = hLogo
        hImg.className = "afl-dash-team-badge"
        homeDiv.appendChild(hImg)
      }
      const hName = document.createElement("span")
      hName.textContent = hFull
      homeDiv.appendChild(hName)

      const scoreDiv = document.createElement("div")
      scoreDiv.className = "afl-dash-match-score"
      scoreDiv.textContent = g.hscore + " – " + g.ascore

      const awayDiv = document.createElement("div")
      awayDiv.className = "afl-dash-match-team" + (!homeWin && g.hscore !== g.ascore ? " winner" : "")
      const aLogo = teamLogo(aFull)
      if (aLogo) {
        const aImg = document.createElement("img")
        aImg.src = aLogo
        aImg.className = "afl-dash-team-badge"
        awayDiv.appendChild(aImg)
      }
      const aName = document.createElement("span")
      aName.textContent = aFull
      awayDiv.appendChild(aName)

      const venueDiv = document.createElement("div")
      venueDiv.className = "afl-dash-match-venue"
      venueDiv.textContent = [g.venue, `R${g.round}`].filter(Boolean).join(" · ")

      card.appendChild(homeDiv)
      card.appendChild(scoreDiv)
      card.appendChild(awayDiv)
      card.appendChild(venueDiv)
      matchesPanel.appendChild(card)
    }
  } else {
    const msg = document.createElement("p")
    msg.className = "text-muted"
    msg.textContent = "No recent results available."
    matchesPanel.appendChild(msg)
  }

  el.appendChild(matchesPanel)
  el.appendChild(ladderPanel)
  return el
}
 

Pete Owen · Sydney · © 2026 · Source

Privacy | Disclaimer