// Navigation cards with football-themed SVG icons
{
const ns = "http://www.w3.org/2000/svg"
const accent = "#5a9a7a"
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", accent)
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}]]),
leagues: () => svgIcon([["circle",{cx:6,cy:6,r:2,fill:accent,stroke:"none",opacity:"0.9"}],["line",{x1:11,y1:6,x2:20,y2:6}],["circle",{cx:6,cy:12,r:2,fill:accent,stroke:"none",opacity:"0.5"}],["line",{x1:11,y1:12,x2:18,y2:12}],["circle",{cx:6,cy:18,r:2,fill:accent,stroke:"none",opacity:"0.25"}],["line",{x1:11,y1:18,x2:16,y2:18}]]),
sims: () => svgIcon([["polyline",{points:"4 18 8 14 12 16 16 10 20 6"}],["circle",{cx:20,cy:6,r:2,fill:accent,stroke:"none"}],["line",{x1:4,y1:4,x2:4,y2:20}],["line",{x1:4,y1:20,x2:20,y2:20}]]),
"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:accent,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}]]),
"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}]]),
defs: () => svgIcon([["path",{d:"M4 4h16v16H4z",rx:2}],["line",{x1:8,y1:8,x2:16,y2:8}],["line",{x1:8,y1:12,x2:14,y2:12}],["line",{x1:8,y1:16,x2:12,y2:16}]])
}
const pages = [
{ title: "Matches", desc: "Results, predictions & win probabilities", href: "matches", icon: "matches" },
{ title: "Leagues", desc: "Standings, form guide, xG & season projections", href: "leagues", icon: "leagues" },
{ title: "Player Ratings", desc: "Predictive Panna ratings across 15 leagues", href: "player-ratings", icon: "player-ratings" },
{ title: "Player Stats", desc: "Per-match box scores & xG values", href: "player-stats", icon: "player-stats" },
{ title: "Team Ratings", desc: "Squad strength relative to league average", href: "team-ratings", icon: "team-ratings" },
{ title: "Team Stats", desc: "Aggregated team match statistics", href: "team-stats", icon: "team-stats" },
{ title: "Definitions", desc: "Glossary of Panna model concepts & stats", href: "definitions", icon: "defs" }
]
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
}Football
Player ratings and match predictions powered by the panna R package.
How Panna works
Traditional football stats tell you what a player did. Panna ratings try to answer a harder question: how much did it actually matter?
The core idea is plus-minus — when a player is on the pitch, does their team create more and concede less than expected? But raw plus-minus is noisy (your numbers look great if you play alongside Messi), so the model controls for the quality of every other player on the field. Think of it as isolating your contribution from everyone else’s.
For players without enough minutes to get a reliable plus-minus estimate, the model falls back on box-score stats — things like key passes, interceptions, and progressive carries — that tend to predict plus-minus well. The final Panna rating blends both signals, leaning more on the box score for bit-part players and more on plus-minus for regulars.
The rating splits into offense and defense. Positive offense means you’re helping create goals. Negative defense means you’re preventing them. Add them together and you get the overall picture.