Add Generate Matrix: auto-build the team scouting matchup matrix

Duplicates the team's Google Sheets matchup-matrix template once per
opposing team at a loaded event, pre-filled with names, factions,
career win% (all already computed by this page), and cleaned army
lists. New "Which team is yours?" selector + Generate Matrix button,
team-events only.

- Auth: Google Identity Services token client (drive.file +
  spreadsheets scopes) — the viewer signs in with their own Google
  account via a real OAuth popup, no backend, no client secret.
- Template layout is located at runtime by searching each tab for its
  own placeholder text ("Player N - Faction", "Opponent N") rather
  than hardcoded A1 ranges, so it survives the template changing later.
  Distinguishes the real input area from the template's own "mobile
  matrix" mirror and reference sections (which repeat the same
  placeholder text) by checking for literal typed values vs formulas —
  CSV export can't tell these apart, only the real Sheets API response
  can, which is how this was actually verified before writing this.
  Auto-picks between the template's 5-man/8-man tab variants by team
  size.
- List cleaning via a vendored copy of desjani's 40k-compactor
  (src/vendor/40k-compactor/, see NOTICE.md) — it isn't actually
  published to npm despite documenting `npm install 40k-compactor`
  (verified: 404 against the registry under every plausible name), so
  it can't be a normal dependency; vendored instead, MIT per direct
  confirmation from the maintainer. Verified against 40k-compactor's
  own sample list fixtures with real Node before committing.
- Generating a matrix needs the same BCP auth token already used for
  faction/disposition backfill (list text is a subscriber-gated
  endpoint) — reuses the existing token UI/storage as-is; missing
  lists are reported, not a hard failure.
- nginx CSP updated (script/connect/frame-src) for Google's identity
  script and the Drive/Sheets REST APIs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
This commit is contained in:
2026-09-04 18:09:48 -05:00
co-authored by Claude Sonnet 5
parent 345f952970
commit 06b0cf4418
18 changed files with 6974 additions and 1 deletions
+20
View File
@@ -5,6 +5,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">
@@ -133,6 +135,24 @@
<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>