supabase = {
const { createClient } = await import("https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm")
return createClient(
"https://ccapldpwlqeaknodfhdp.supabase.co",
"sb_publishable_769k5NjCdtqIpajidtDbZg_zNB_h6Vd"
)
}
seasons = ["20252026","20242025","20232024","20222023","20212022","20202021",
"20192020","20182019","20172018","20162017","20152016"]Loading schedule…
Players
viewof season = Inputs.select(seasons, {
label: "Season",
format: s => s.slice(0,4) + "–" + s.slice(4)
})// ── Tab bar — DOM-based viewof (works in Quarto OJS) ─────────────────────
viewof activeTab = {
const tabs = [
{ id: "g_over_xg", label: "Goals over xG" },
{ id: "goals", label: "Goals" },
{ id: "points", label: "Points" },
{ id: "toi", label: "TOI" },
{ id: "g_under_xg", label: "Goals under xG" }
]
const bar = html`<div class="hdc-tab-bar"></div>`
bar.value = "g_over_xg"
tabs.forEach(t => {
const btn = html`<button class="hdc-tab-btn${t.id === bar.value ? " active" : ""}">${t.label}</button>`
btn.onclick = () => {
bar.querySelectorAll(".hdc-tab-btn").forEach(b => b.classList.remove("active"))
btn.classList.add("active")
bar.value = t.id
bar.dispatchEvent(new Event("input"))
}
bar.appendChild(btn)
})
return bar
}rawPlayers = {
season
const { data, error } = await supabase
.from("player_season_xg")
.select(`
goals, xg_for, assists, points, toi_seconds, shots,
players(full_name, position, shoots, nhl_player_id),
teams(name, abbreviation)
`)
.eq("season", season)
.eq("game_type", "R")
.eq("model_name", "xgboost_iso")
.not("goals", "is", null)
.limit(200)
return error ? [] : (data ?? [])
}top10 = {
activeTab
const rows = rawPlayers.map(r => ({
...r,
goals_over_xg: (r.goals ?? 0) - (r.xg_for ?? 0),
toi_min: ((r.toi_seconds ?? 0) / 60).toFixed(1)
}))
let sorted
if (activeTab === "g_over_xg") sorted = [...rows].sort((a,b) => b.goals_over_xg - a.goals_over_xg)
else if (activeTab === "g_under_xg") sorted = [...rows].sort((a,b) => a.goals_over_xg - b.goals_over_xg)
else if (activeTab === "goals") sorted = [...rows].sort((a,b) => (b.goals??0) - (a.goals??0))
else if (activeTab === "points") sorted = [...rows].sort((a,b) => (b.points??0) - (a.points??0))
else if (activeTab === "toi") sorted = [...rows].sort((a,b) => (b.toi_seconds??0) - (a.toi_seconds??0))
else sorted = rows
return sorted.slice(0, 10)
}headshot = {
top10
const leader = top10[0]
if (!leader) return null
const pid = leader.players?.nhl_player_id
if (!pid) return null
const { data } = await supabase.functions.invoke("get-player-headshot", {
body: { player_id: pid }
})
return data?.url ?? null
}statLabel = ({
g_over_xg: { key: "goals_over_xg", fmt: v => (v >= 0 ? "+" : "") + v.toFixed(1), label: "G−xG" },
g_under_xg: { key: "goals_over_xg", fmt: v => (v >= 0 ? "+" : "") + v.toFixed(1), label: "G−xG" },
goals: { key: "goals", fmt: v => v, label: "G" },
points: { key: "points", fmt: v => v, label: "Pts" },
toi: { key: "toi_min", fmt: v => v, label: "TOI (min)" }
})[activeTab]{
if (!top10.length) return html`<p class="text-secondary">No data available for this season.</p>`
const leader = top10[0]
const lp = leader.players ?? {}
const lt = leader.teams ?? {}
const lStat = statLabel.fmt(leader[statLabel.key] ?? 0)
const featuredCard = html`
<div class="hdc-leader-box" style="min-width:170px;max-width:220px;flex-shrink:0">
${headshot
? html`<img src="${headshot}" class="hdc-leader-headshot" alt="${lp.full_name}">`
: html`<div style="width:80px;height:80px;border-radius:50%;background:var(--bs-border-color);margin-bottom:.5rem"></div>`}
<div class="hdc-leader-name">${lp.full_name ?? "—"}</div>
<div class="hdc-leader-meta">${lp.position ?? ""} · ${lt.abbreviation ?? ""} · ${lp.shoots ?? ""}</div>
<div class="hdc-leader-stat-row">
<div><div class="hdc-stat-val">${lStat}</div><div class="hdc-stat-lbl">${statLabel.label}</div></div>
<div><div class="hdc-stat-val">${leader.goals ?? "—"}</div><div class="hdc-stat-lbl">G</div></div>
<div><div class="hdc-stat-val">${(leader.xg_for ?? 0).toFixed(1)}</div><div class="hdc-stat-lbl">xG</div></div>
</div>
</div>`
const listItems = top10.map((row, i) => {
const p = row.players ?? {}
const t = row.teams ?? {}
const val = statLabel.fmt(row[statLabel.key] ?? 0)
return html`<li>
<span class="hdc-rank">${i + 1}.</span>
<span class="hdc-name">${p.full_name ?? "—"}</span>
<span class="hdc-team-abbrev">${t.abbreviation ?? ""}</span>
<span class="hdc-key-stat">${val}</span>
</li>`
})
return html`
<div style="display:flex;gap:1rem;flex-wrap:wrap;align-items:flex-start" class="hdc-leader-layout">
${featuredCard}
<div class="hdc-card" style="flex:1">
<div style="font-size:.7rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--bs-secondary-color);margin-bottom:.5rem">
Top 10 — ${statLabel.label}
</div>
<ul class="hdc-top10">${listItems}</ul>
</div>
</div>`
}