The token field's purpose was clear but not how to obtain one; add a "?" button that opens a modal with the DevTools Network-tab steps. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRS5Lvcig4TNpPyviUooGa
228 lines
10 KiB
HTML
228 lines
10 KiB
HTML
<!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" />
|
|
<!-- Google Identity Services — powers the "Generate Matrix" Google sign-in popup -->
|
|
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
|
</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">
|
|
<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>
|
|
</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>
|
|
|
|
<!-- 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>
|
|
<li>Find the <span class="font-mono">authorization</span> request header. Copy everything after <span class="font-mono">Bearer </span> — that's your token.</li>
|
|
<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>
|
|
|
|
<!-- 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>
|
|
|
|
<!-- 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>
|
|
|
|
<!-- 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>
|
|
<th class="text-right">Career W%</th>
|
|
<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>
|