2026-08-06 10:25:31 -05:00
<!DOCTYPE html>
< html lang = "en" data-theme = "dark" >
< head >
< meta charset = "UTF-8" />
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" />
< title > Gateway Gamers — Scouting</ title >
< link rel = "stylesheet" href = "/src/style.css" />
2026-09-04 18:09:48 -05:00
<!-- Google Identity Services — powers the "Generate Matrix" Google sign-in popup -->
< script src = "https://accounts.google.com/gsi/client" async defer ></ script >
2026-08-06 10:25:31 -05:00
</ head >
< body class = "min-h-screen flex flex-col" >
< div id = "header" ></ div >
< main class = "flex-1 max-w-5xl w-full mx-auto px-6 py-10" >
<!-- Page heading -->
< div class = "mb-6" >
< h1 class = "text-4xl font-bold uppercase tracking-wide" > BCP Roster Scout</ h1 >
< p class = "text-sm opacity-50 uppercase tracking-widest mt-1" > Paste a Best Coast Pairings event link for instant roster stats</ p >
</ div >
<!-- Input row -->
< form id = "scout-form" class = "flex gap-2 mb-8" >
< input
id = "event-input"
type = "text"
placeholder = "https://www.bestcoastpairings.com/event/… or just the event ID"
class = "input input-bordered flex-1 font-mono text-sm"
autocomplete = "off"
/>
< button id = "scout-btn" type = "submit" class = "btn btn-primary uppercase tracking-wide" > Scout</ button >
</ form >
<!-- Optional: BCP auth token for backfilling missing faction/disposition from submitted lists -->
< details class = "mb-6 bg-base-200 rounded-box" >
2026-09-04 19:35:24 -05:00
< summary class = "cursor-pointer px-4 py-2 text-sm opacity-70 select-none flex items-center gap-2" >
< span > Have a BCP subscription? Add your auth token to backfill missing faction/disposition data</ span >
< button
id = "bcp-token-help-btn"
type = "button"
class = "btn btn-circle btn-ghost btn-xs shrink-0"
aria-label = "How do I get my BCP auth token?"
title = "How do I get my BCP auth token?"
onclick = "event.preventDefault()"
> ?</ button >
2026-08-06 10:25:31 -05:00
</ summary >
< div class = "px-4 pb-2 flex gap-2 items-center flex-wrap" >
< input
id = "bcp-token-input"
type = "password"
placeholder = "Paste your BCP auth token…"
class = "input input-bordered input-sm flex-1 font-mono"
autocomplete = "off"
/>
< button id = "bcp-token-save" type = "button" class = "btn btn-sm" > Save</ button >
< button id = "bcp-token-clear" type = "button" class = "btn btn-sm btn-ghost" > Clear</ button >
</ div >
< p class = "px-4 pb-1 text-xs opacity-40" >
Stored only in your browser (localStorage) — sent only to BCP's own API, never committed or deployed.
Tokens expire after about an hour, so you may need to paste a fresh one occasionally.
</ p >
< p class = "px-4 pb-3 text-xs" id = "bcp-token-status" ></ p >
</ details >
2026-09-04 19:35:24 -05:00
<!-- How-to-get-a-BCP-token dialog, opened by the "?" button above -->
< dialog id = "bcp-token-help-modal" class = "modal" >
< div class = "modal-box" >
< h3 class = "font-bold text-lg" > Getting your BCP auth token</ h3 >
< ol class = "list-decimal list-inside text-sm space-y-2 mt-3" >
< li > Log in at < span class = "font-mono" > bestcoastpairings.com</ span > in this browser (must have an active subscription).</ li >
< li > Open your browser's developer tools (< kbd class = "kbd kbd-sm" > F12</ kbd > , or right-click → Inspect) and switch to the < span class = "font-semibold" > Network</ span > tab.</ li >
< li > Back on BCP, open any event or your profile so the page makes a new request — refreshing the page works.</ li >
< li > In the Network tab, click any request to < span class = "font-mono" > newprod-api.bestcoastpairings.com</ span > .</ li >
2026-09-04 19:38:12 -05:00
< li > Find the < span class = "font-mono" > authorization</ span > request header and copy its value (pasting the whole thing, including the leading "Bearer ", is fine).</ li >
2026-09-04 19:35:24 -05:00
< li > Paste it into the field above and click < span class = "font-semibold" > Save</ span > .</ li >
</ ol >
< p class = "text-xs opacity-50 mt-3" >
Tokens are short-lived (about an hour), so you'll need to repeat this occasionally.
The token never leaves your browser except in requests it makes directly to BCP's own API.
</ p >
< div class = "modal-action" >
< form method = "dialog" >
< button class = "btn btn-sm" > Got it</ button >
</ form >
</ div >
</ div >
< form method = "dialog" class = "modal-backdrop" >
< button > close</ button >
</ form >
</ dialog >
2026-08-06 10:25:31 -05:00
<!-- Banner (warnings / errors) -->
< div class = "alert mb-6 hidden" id = "banner" ></ div >
<!-- Loading skeleton -->
< div class = "hidden space-y-4" id = "loading" >
< div class = "skeleton h-8 w-2/3" ></ div >
< div class = "skeleton h-4 w-1/3" ></ div >
< div class = "skeleton h-24 w-full" ></ div >
< div class = "skeleton h-48 w-full" ></ div >
</ div >
<!-- Empty state -->
< div class = "text-center text-base-content/50 py-16" id = "empty" >
Paste a BCP event link above to see roster stats — faction spread, dispositions, checked-in rate, and team breakdowns.
</ div >
<!-- Results -->
< div class = "hidden" id = "results" >
<!-- Event header -->
< div class = "card card-border bg-base-200 mb-6" >
< div class = "card-body py-5" >
< div class = "flex items-start justify-between gap-4 flex-wrap" >
< div >
< h2 class = "text-2xl font-bold" id = "ev-name" > —</ h2 >
< p class = "text-sm opacity-60 mt-1" id = "ev-sub" > —</ p >
</ div >
< div class = "badge badge-lg" id = "ev-status" > —</ div >
</ div >
</ div >
</ div >
<!-- Stats strip -->
< div class = "stats shadow w-full mb-6 bg-base-200 stats-vertical sm:stats-horizontal" >
< div class = "stat" >
< div class = "stat-title" > Players</ div >
< div class = "stat-value text-2xl" id = "s-players" > —</ div >
</ div >
< div class = "stat" id = "s-teams-wrap" >
< div class = "stat-title" > Teams</ div >
< div class = "stat-value text-2xl" id = "s-teams" > —</ div >
</ div >
< div class = "stat" >
< div class = "stat-title" > Checked In</ div >
< div class = "stat-value text-2xl" id = "s-checkin" > —</ div >
</ div >
< div class = "stat" >
< div class = "stat-title" > Lists Submitted</ div >
< div class = "stat-value text-2xl" id = "s-lists" > —</ div >
</ div >
</ div >
<!-- Insights -->
< div class = "mb-8" >
< h3 class = "text-lg font-bold uppercase tracking-wide mb-3" > Insights</ h3 >
< ul class = "space-y-2" id = "insights" ></ ul >
</ div >
<!-- Faction distribution -->
< div class = "mb-8" >
< h3 class = "text-lg font-bold uppercase tracking-wide mb-3" > Faction Distribution</ h3 >
< div id = "faction-bars" ></ div >
</ div >
<!-- Disposition distribution (across all factions) -->
< div class = "mb-8" >
< div class = "flex items-center justify-between mb-3" >
< h3 class = "text-lg font-bold uppercase tracking-wide" > Top Dispositions</ h3 >
< button id = "disposition-toggle" class = "btn btn-ghost btn-xs" > Show all</ button >
</ div >
< div id = "disposition-bars" ></ div >
</ div >
<!-- Roster: per-team dropdowns for team events, flat table for individual events -->
< div class = "mb-8" >
< div class = "flex items-center justify-between mb-3 flex-wrap gap-2" >
< h3 class = "text-lg font-bold uppercase tracking-wide" > Roster</ h3 >
< input id = "player-filter" type = "text" placeholder = "Filter by name, team, or faction…" class = "input input-bordered input-sm w-full sm:w-72" />
</ div >
2026-09-04 18:09:48 -05:00
<!-- Generate Matrix (team events only) -->
< div class = "card card-border bg-base-200 mb-4 hidden" id = "matrix-card" >
< div class = "card-body py-4" >
< div class = "flex items-center gap-2 flex-wrap" >
< label class = "text-sm opacity-70" for = "matrix-team-select" > Which team is yours?</ label >
< select id = "matrix-team-select" class = "select select-bordered select-sm max-w-xs" ></ select >
< button id = "matrix-generate-btn" type = "button" class = "btn btn-sm btn-warning uppercase tracking-wide" disabled >
Generate Matrix
</ button >
</ div >
< p class = "text-xs opacity-50 mt-1" >
Duplicates the team matrix template, once per opposing team, pre-filled with names, factions, career win%, and cleaned lists.
List formatting by < a href = "https://desjani.github.io/40kCompactor/" target = "_blank" rel = "noopener" class = "link" > 40k Compactor</ a > (Desjani), MIT licensed.
</ p >
< p class = "text-sm mt-2 hidden" id = "matrix-status" ></ p >
</ div >
</ div >
2026-08-06 10:25:31 -05:00
<!-- Team accordion (team events) -->
< div class = "space-y-2 hidden" id = "team-list" ></ div >
<!-- Flat table (individual events) -->
< div class = "overflow-x-auto hidden" id = "player-table-wrap" >
< table class = "table table-zebra w-full" >
< thead >
< tr >
< th > Player</ th >
< th > Faction</ th >
< th class = "hidden sm:table-cell" > Disposition</ th >
< th class = "text-right" > ITC Rank</ th >
2026-08-06 18:23:57 -05:00
< th class = "text-right" > Career W%</ th >
2026-08-06 10:25:31 -05:00
< th class = "text-right" > List</ th >
</ tr >
</ thead >
< tbody id = "player-rows" ></ tbody >
</ table >
</ div >
< p class = "text-xs text-base-content/40 mt-2" id = "player-count" ></ p >
</ div >
</ div >
< div class = "divider mt-8" ></ div >
< p class = "text-xs text-base-content/40 text-center" >
Data from Best Coast Pairings · roster-only (this tool doesn't yet read pairings/placings)
</ p >
</ main >
< div id = "footer" ></ div >
< script type = "module" src = "/src/main.js" ></ script >
</ body >
</ html >