Investigated BCP's API for a team-size field (5-man vs 8-man, etc.) —
there isn't one. The only candidate, totalPlayers / totalTeamPlayers,
tracks live registration progress rather than the event's configured
format, so it's unreliable for upcoming events with thin early signups
(verified: "TEO-8 - All in.", an event explicitly branded as an 8-man
format, currently shows 21/8 ≈ 2.6 via that ratio — would round to a
wrong "3-Man Teams"). Sticking to the one field BCP does report
reliably (teamEvent) rather than shipping a specific size that's
sometimes confidently wrong.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
DNS was pointed at gg1-roc-web1, so ran certbot --nginx to issue the
cert (same flow as the other 4 sites), then re-applied the security
headers/CSP/shared-asset alias blocks the same way as before. Site is
now live at https://events.gateway-gamers.net — verified header,
footer, images, and compiled CSS all resolve correctly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
npm ci requires a lockfile; I have no Node available in this sandbox
to generate one, so adapted scouting's (identical devDependencies
across every site in this repo) and renamed the two embedded package
names to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
New 5th site in the monorepo, following the same shared header/footer/
images pattern as the others. Shows upcoming events for a configurable
list of BCP circuits (CONFIG.circuits in sites/events/src/main.js) —
adding a circuit is a one-line edit.
Data comes from the BCP API (`GET /v1/events?leagueId=<circuitId>`,
same auth-free headers the other sites already use) — "circuit" in
BCP's frontend URLs maps to `leagueId` in the API, confirmed via
/v1/leagues/<id> returning circuitLeague: true. Paginates newest-date-
first and stops once a full page has entirely concluded, so it doesn't
walk a circuit's whole history just to find the handful of upcoming
events. Verified against the live API for all 4 configured circuits
before writing this commit.
Nav cross-links with scouting only (per request), not the other 3
sites. nginx conf follows the same shared-asset-alias pattern as the
rest; TLS/DNS for the new subdomain still need to be provisioned
before it can go live (see follow-up).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
Tailwind v4's automatic content detection skips gitignored files, and
public/header.html was gitignored (generated) — so every header-only
utility class (hero-carousel, navbar-center, swap-rotate,
menu-horizontal, drop-shadow-2xl, etc.) got purged from the compiled
CSS on the last deploy. The header markup was correct, just entirely
unstyled.
Fixed two ways for certainty: added an explicit @source in each
site's style.css (Tailwind's documented way to force-include a
gitignored path), and also stopped gitignoring public/header.html
so automatic detection picks it up regardless — matching how the
original per-site repos worked before this generated a real file.
predev/prebuild still regenerate it on every dev/build, so it can't
go stale.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
If refresh_elo.py failed, the trailing '&& cd ../../..' never ran,
leaving the job's shell inside statcheck-elo-scraper/ for the
subsequent git commands — which then tried to add a doubly-nested,
nonexistent path and failed with a confusing pathspec error instead
of the real failure. A subshell scopes the cd regardless of the
script's exit status.
Found by manually triggering the new project's ELO-refresh schedule:
the underlying scraper is currently failing on its own (OneDrive
source URL now 404s) — that's a separate, pre-existing data-source
issue outside this repo, not something this commit addresses.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
The committed .conf files were stale pre-certbot templates that no
longer matched what's actually running on gg1-roc-web1 (certbot --nginx
had rewritten 40k-rankings/aos-rankings/kingmaker's structure, and in
the process 40k-rankings and aos-rankings lost their security headers
entirely). Replaced each with the true live config, plus the new
location /images/ and location = /footer.html alias blocks that were
applied directly on the server.
Also fixed on gg1-roc-web1 while there (not reflected in this repo,
just noting for the record):
- kingmaker's TLS cert had expired 2026-08-26; renewed.
- certbot-renew.timer was enabled but never started (box hasn't
rebooted in 172 days), so nothing was auto-renewing; started it and
ran a full certbot renew, which also caught 40k-rankings/aos-rankings
sitting inside their renewal window.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
Merge the four independent gateway-gamers.net site repos (40k-rankings,
aos-rankings, kingmaker, scouting) into one monorepo under sites/,
preserving each site's full commit history via a manual subtree-style
merge (git-subtree isn't available in this environment).
- shared/: header.template.html (nav swapped in per site), footer.html,
and images/ — previously byte-identical copies duplicated 4x (~100MB).
- Each site's public/footer.html and public/images are now symlinks into
shared/; public/header.html is generated at predev/prebuild time from
shared/header.template.html + the site's own nav.html, so nav bars stay
individual while everything else about the header is shared.
- Single root .gitlab-ci.yml with path-scoped build/deploy jobs per site
plus a deploy:shared job, replacing the 4 separate per-repo pipelines.
- nginx confs (kept per-site as reference, same as before) gain
location /images/ and location = /footer.html alias blocks pointing at
a shared webserver directory, so the deployed site no longer duplicates
the images either — see README.md for the full layout and the runbook
for applying this on the webserver.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
Some sources list two dispositions together (a team template noting a
secondary/alternate, e.g. "Purge the Foe (Reconnaissance)"). Split on the
separators that join them and match segment-by-segment left to right, so
the first one listed wins instead of whichever scored closer across the
combined string.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Levenshtein distance alone scored "Recon" too far from "Reconnaissance"
(9 edits) to pass the typo threshold, since it's a truncation rather than
a misspelling. Add a prefix check against each canonical name (and a
filler-word-stripped "core" form, so "Take Hold" matches "Take and Hold")
gated to 3+ characters to avoid collisions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Hand-typed disposition text (submitted-list free text, BCP's own field)
often has small typos like "Priority Assests". Match it against the fixed
Purge the Foe / Take and Hold / Priority Assets / Disruption / Reconnaissance
list via Levenshtein distance so typo'd variants merge into the correct bar
instead of forking off as duplicates.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
style-src 'self' (without unsafe-inline) silently blocks style="..."
attributes introduced via innerHTML — the CSS parses fine and the
attribute is present in the DOM, but the browser never applies it,
so every bar rendered at its track's full width regardless of its
real percentage. script-src stays locked down; this only loosens
CSS, and no untrusted external content is ever rendered into a
style attribute here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Paste a Best Coast Pairings event link to get roster-only stats:
faction/disposition distribution, per-team ITC rank rollups, and an
optional list-backfill for players missing faction/disposition data
(uses the viewer's own BCP auth token, stored client-side only).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Add refresh-elo job to .gitlab-ci.yml triggered by schedules; runs
the Python scraper and commits updated elo-data.json back to main
- Hide Stat-Check ELO column header on mobile (sm:hidden), fold ELO
score as a subtitle under ITC Points on mobile instead
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scraper (statcheck-elo-scraper/refresh_elo.py) downloads the stat-check
ELO workbook from OneDrive and builds public/elo-data.json
- GitHub Actions workflow (.github/workflows/refresh-elo.yml) runs daily
at 06:00 UTC and commits updated ELO data
- Frontend fetches elo-data.json and matches players by name, using BCP
career event history to disambiguate same-name candidates
- ELO score shown in new sortable Stat-Check ELO column, visible on all
screen sizes; unmatched players show —
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ports the career stats feature from the 40K rankings site: fetches
all-time W-L-T per player from BCP and shows it as a subtitle under
each season stat. Also folds W-L-T, ITC Rank, and Win% into the
Player and ITC Points cells on mobile so all stats are visible without
horizontal scrolling. Skeleton loader updated to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
W-L-T and ITC Rank appear as sub-text under the player name; Win% appears
under ITC Points. Desktop layout is unchanged. Skeleton loader also gets
matching placeholder lines on mobile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the standalone Career W% column. Career totals now appear
as a small secondary line inside the W-L-T and Win% cells, loading
asynchronously with a … placeholder while fetching.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
For each roster player, fire a parallel fetch to
/placings?placingsType=player&userId={id} and aggregate
wins/losses/ties across all events that have those fields.
Renders asynchronously - shows … while loading, — if no
data found. Column is sortable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
stat-check.com's ELO data is behind Microsoft auth with no public
API or download endpoint available. Removing the sync script,
placeholder JSON, CI job, table column, and all related JS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Helps identify what the server is actually returning when the
download URL doesn't serve the xlsx file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>