// ── Hand tray (drag-to-build + game launchers) — AFL port of football's tray.
// Shares cards-deck.css (.deck-tray / .tray-card / .deck-ghost / .tray-gap).
aflHandTray = {
if (!ratings) return html``
const KEY = "ig-afl-hand", MAX = 30
let hand = []
try { hand = (JSON.parse(localStorage.getItem(KEY) || "[]") || []).filter(id => deckById.has(id)).slice(0, MAX) } catch (e) {}
const save = () => { try { localStorage.setItem(KEY, JSON.stringify(hand)) } catch (e) {} }
const prior = document.querySelector(".deck-tray"); if (prior) prior.remove()
const tray = document.createElement("div"); tray.className = "deck-tray"
const head = document.createElement("div"); head.className = "deck-tray-head"
const title = document.createElement("span"); title.className = "deck-tray-title"
const actions = document.createElement("span"); actions.className = "deck-tray-actions"
const mk = (label, cls, title, fn) => { const b = document.createElement("button"); b.type = "button"; b.className = "deck-tray-btn " + cls; b.textContent = label; if (title) b.title = title; b.addEventListener("click", fn); return b }
const clearBtn = mk("Clear", "", "", () => { hand = []; save(); render() })
const playBtn = mk("Play", "play", "Duel your hand as Top Trumps (need 2+ cards)", () => { if (hand.length >= 2 && window._aflStartDuelHand) window._aflStartDuelHand([...hand]) })
const dailyBtn = mk("Daily", "daily", "Today's seeded Gaffer's Run — same opponents for everyone; best depth + streak saved", () => { if (window._aflStartDaily) window._aflStartDaily() })
const runBtn = mk("Gaffer's Run", "run", "Roguelike: climb a ladder, draft after each win, lose and it's over", () => { if (window._aflStartRun) window._aflStartRun() })
const hlBtn = mk("Higher / Lower", "hl", "Endless streak: guess if the next card's stat is higher or lower", () => { if (window._aflStartHL) window._aflStartHL() })
const quickBtn = mk("Quick Play", "quick", "Deal a random hand from the current filter and duel the CPU", () => { if (window._aflStartQuick) window._aflStartQuick() })
actions.append(dailyBtn, runBtn, hlBtn, quickBtn, clearBtn, playBtn)
head.append(title, actions)
const strip = document.createElement("div"); strip.className = "deck-tray-strip"
tray.append(head, strip)
function miniCard(id) {
const r = deckById.get(id); if (!r) return null
const name = r.player_name || "?"
const colour = aflColOf(r)
const initials = name.split(" ").map(w => w[0] || "").join("").slice(0, 2).toUpperCase()
const photo = aflHeadshot(r)
const el = document.createElement("div"); el.className = "tray-card"; el.style.setProperty("--c", colour)
el.dataset.id = id
el.innerHTML = `
<div class="tray-thumb">
${photo ? `<img src="${photo}" alt="" draggable="false" onerror="this.style.display='none';this.nextElementSibling.style.display=''">` : ""}
<span class="tray-mono"${photo ? ' style="display:none"' : ''}>${statsEsc(initials)}</span>
</div>
<div class="tray-meta"><span class="tray-name">${statsEsc(name)}</span><span class="tray-panna">${r.torp == null ? "—" : r.torp.toFixed(2)}</span></div>
<button type="button" class="tray-x" title="Remove">×</button>`
const goProfile = () => { if (window._aflDidDrag) return; location.href = "player.html#id=" + encodeURIComponent(id) }
el.querySelector(".tray-thumb").addEventListener("click", goProfile)
el.querySelector(".tray-meta").addEventListener("click", goProfile)
el.querySelector(".tray-x").addEventListener("click", e => { e.stopPropagation(); hand = hand.filter(x => x !== id); save(); render() })
el.style.touchAction = "none"; el.draggable = false
el.addEventListener("dragstart", e => e.preventDefault())
el.addEventListener("pointerdown", e => {
if (e.pointerType === "mouse" && e.button !== 0) return
if (e.target.closest(".tray-x")) return
beginDrag(e, id, name, "reorder", el, el)
})
return el
}
function render() {
title.textContent = hand.length ? `Your Hand · ${hand.length}${hand.length >= MAX ? " (max)" : ""}` : "Your Hand"
clearBtn.disabled = !hand.length
playBtn.disabled = hand.length < 2
playBtn.title = hand.length < 2 ? "Add 2+ cards to duel" : "Duel your hand as Top Trumps"
strip.replaceChildren()
if (!hand.length) {
const hint = document.createElement("span"); hint.className = "deck-tray-hint"
hint.textContent = "Drag cards here (or tap “+ Hand”) to build your deck"
strip.appendChild(hint); return
}
for (const id of hand) { const m = miniCard(id); if (m) strip.appendChild(m) }
}
window._aflAddToHand = (id) => {
if (!deckById.has(id) || hand.includes(id) || hand.length >= MAX) return false
hand.push(id); save(); render(); return true
}
// ── Pointer-drag controller (mouse + touch + pen) — identical to football's ──
const overTray = (x, y) => { const el = document.elementFromPoint(x, y); return !!(el && el.closest(".deck-tray")) }
const liveCards = () => [...strip.querySelectorAll(".tray-card:not(.dragging)")]
const gapIndex = clientX => {
const items = liveCards()
for (let i = 0; i < items.length; i++) { const b = items[i].getBoundingClientRect(); if (clientX < b.left + b.width / 2) return i }
return items.length
}
let gap = null
function showGap(idx) {
if (!gap) { gap = document.createElement("div"); gap.className = "tray-gap" }
const ref = liveCards()[idx] || null
if (gap.nextElementSibling !== ref || gap.parentNode !== strip) strip.insertBefore(gap, ref)
requestAnimationFrame(() => gap && gap.classList.add("open"))
}
const hideGap = () => { if (gap) { gap.remove(); gap = null } }
let drag = null
function makeGhost(d) {
if (d.cloneEl) {
const c = d.cloneEl.cloneNode(true)
c.className = (d.kind === "add" ? "ttc" : "tray-card") + " deck-ghost-card"
c.style.setProperty("--c", d.colour)
c.querySelectorAll(".ttc-add, .tray-x").forEach(b => b.remove())
if (c.removeAttribute) c.removeAttribute("href")
return c
}
const g = document.createElement("div"); g.className = "deck-ghost"; g.style.setProperty("--c", d.colour); g.textContent = d.name
return g
}
function onMove(e) {
if (!drag) return
if (!drag.moved && Math.hypot(e.clientX - drag.x0, e.clientY - drag.y0) < 6) return
if (!drag.moved) {
drag.moved = true; window._aflDidDrag = true
if (drag.sourceEl) drag.sourceEl.classList.add("dragging")
drag.ghost = makeGhost(drag); document.body.appendChild(drag.ghost)
document.body.style.userSelect = "none"
}
drag.ghost.style.left = e.clientX + "px"; drag.ghost.style.top = e.clientY + "px"
const inTray = overTray(e.clientX, e.clientY)
tray.classList.toggle("drop-hot", inTray)
if (inTray && hand.length) showGap(gapIndex(e.clientX)); else hideGap()
e.preventDefault()
}
function onUp(e) {
document.removeEventListener("pointermove", onMove)
tray.classList.remove("drop-hot"); document.body.style.userSelect = ""
const inTray = drag && drag.moved && overTray(e.clientX, e.clientY)
const slot = inTray ? gapIndex(e.clientX) : -1
hideGap()
if (drag) {
if (drag.sourceEl) drag.sourceEl.classList.remove("dragging")
if (drag.ghost) drag.ghost.remove()
if (drag.moved) {
if (drag.kind === "add" && inTray) {
if (window._aflAddToHand(drag.id)) {
const cur = hand.indexOf(drag.id), to = Math.min(slot, hand.length - 1)
if (cur !== to && to >= 0) { hand.splice(cur, 1); hand.splice(to, 0, drag.id); save(); render() }
}
} else if (drag.kind === "reorder") {
const from = hand.indexOf(drag.id)
if (from >= 0 && inTray) { hand.splice(from, 1); hand.splice(Math.max(0, Math.min(hand.length, slot)), 0, drag.id); save(); render() }
else if (from >= 0 && !inTray) { hand = hand.filter(x => x !== drag.id); save(); render() }
}
}
}
setTimeout(() => { window._aflDidDrag = false }, 0)
drag = null
}
function beginDrag(e, id, name, kind, sourceEl, cloneEl) {
drag = { id, name, kind, x0: e.clientX, y0: e.clientY, moved: false, colour: aflColOf(deckById.get(id) || {}), sourceEl: sourceEl || null, cloneEl: cloneEl || null }
window._aflDidDrag = false
document.addEventListener("pointermove", onMove, { passive: false })
document.addEventListener("pointerup", onUp, { once: true })
document.addEventListener("pointercancel", onUp, { once: true })
}
window._aflBeginCardDrag = (e, id, name, cardEl) => beginDrag(e, id, name, "add", null, cardEl)
render()
return tray
}