Consolidate gateway-gamers sites, dedupe shared header/footer/images

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
This commit is contained in:
2026-09-04 14:59:06 -05:00
co-authored by Claude Sonnet 5
parent 60076ac885
commit 0ab611b6fc
60 changed files with 350 additions and 323 deletions
+178
View File
@@ -0,0 +1,178 @@
stages:
- build
- deploy
- refresh
.build_script: &build_script
- cd sites/${SITE}
- npm ci
- npm run build
# ---------------------------------------------------------------------------
# shared assets (images + footer.html) — deployed once to a shared dir that
# every site's nginx server block points at via `alias`, so a photo/logo
# update only has to land in one place instead of four.
# ---------------------------------------------------------------------------
deploy:shared:
stage: deploy
script:
- rsync -avz --delete shared/images/ /var/www/domains/gateway-gamers.net/shared/images/
- rsync -avz shared/footer.html /var/www/domains/gateway-gamers.net/shared/footer.html
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
changes:
- shared/**/*
environment:
name: production/shared
# ---------------------------------------------------------------------------
# 40k-rankings
# ---------------------------------------------------------------------------
build:40k-rankings:
stage: build
variables:
SITE: 40k-rankings
script: *build_script
artifacts:
paths:
- sites/40k-rankings/dist/
expire_in: 1 hour
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
changes:
- sites/40k-rankings/**/*
- shared/**/*
deploy:40k-rankings:
stage: deploy
needs: ["build:40k-rankings"]
script:
- rsync -avz --delete --exclude images --exclude footer.html sites/40k-rankings/dist/ /var/www/domains/gateway-gamers.net/40k-rankings/
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
changes:
- sites/40k-rankings/**/*
- shared/**/*
environment:
name: production
url: https://40k-rankings.gateway-gamers.net
refresh-elo:
stage: refresh
image: python:3.12-slim
variables:
GIT_STRATEGY: clone
script:
- cd sites/40k-rankings/statcheck-elo-scraper && python refresh_elo.py && cd ../../..
- git config user.email "gitlab-ci@gateway-gamers.net"
- git config user.name "GitLab CI"
- git remote set-url origin "https://oauth2:${GITLAB_ACCESS_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
- git add sites/40k-rankings/public/elo-data.json
- |
if ! git diff --cached --quiet; then
git commit -m "data: refresh stat-check ELO rankings"
git push origin HEAD:main
fi
only:
- schedules
# ---------------------------------------------------------------------------
# aos-rankings
# ---------------------------------------------------------------------------
build:aos-rankings:
stage: build
variables:
SITE: aos-rankings
script: *build_script
artifacts:
paths:
- sites/aos-rankings/dist/
expire_in: 1 hour
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
changes:
- sites/aos-rankings/**/*
- shared/**/*
deploy:aos-rankings:
stage: deploy
needs: ["build:aos-rankings"]
script:
- rsync -avz --delete --exclude images --exclude footer.html sites/aos-rankings/dist/ /var/www/domains/gateway-gamers.net/aos-rankings/
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
changes:
- sites/aos-rankings/**/*
- shared/**/*
environment:
name: production
url: https://aos-rankings.gateway-gamers.net
# ---------------------------------------------------------------------------
# kingmaker
# ---------------------------------------------------------------------------
build:kingmaker:
stage: build
variables:
SITE: kingmaker
script: *build_script
artifacts:
paths:
- sites/kingmaker/dist/
expire_in: 1 hour
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
changes:
- sites/kingmaker/**/*
- shared/**/*
deploy:kingmaker:
stage: deploy
needs: ["build:kingmaker"]
script:
- rsync -avz --delete --exclude images --exclude footer.html sites/kingmaker/dist/ /var/www/domains/gateway-gamers.net/kingmaker/
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
changes:
- sites/kingmaker/**/*
- shared/**/*
environment:
name: production
url: https://kingmaker.gateway-gamers.net
# ---------------------------------------------------------------------------
# scouting
# ---------------------------------------------------------------------------
build:scouting:
stage: build
variables:
SITE: scouting
script: *build_script
artifacts:
paths:
- sites/scouting/dist/
expire_in: 1 hour
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
changes:
- sites/scouting/**/*
- shared/**/*
deploy:scouting:
stage: deploy
needs: ["build:scouting"]
script:
- rsync -avz --delete --exclude images --exclude footer.html sites/scouting/dist/ /var/www/domains/gateway-gamers.net/scouting/
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
changes:
- sites/scouting/**/*
- shared/**/*
environment:
name: production
url: https://scouting.gateway-gamers.net
+57
View File
@@ -0,0 +1,57 @@
# gateway-gamers.net
Monorepo for the `*.gateway-gamers.net` static sites. Each site is an independent
Vite + Tailwind v4 + daisyUI project, deployed to its own subdomain, but they
share a header shell, footer, and hero-carousel images so a logo/photo swap
doesn't need to happen four times.
## Layout
```
shared/
header.template.html # full header markup, nav swapped in at build time
footer.html # byte-identical across every site
images/ # logo + carousel photos, one copy
scripts/
render-header.mjs # shared/header.template.html + sites/<site>/nav.html -> sites/<site>/public/header.html
sites/
40k-rankings/
aos-rankings/
kingmaker/
scouting/
```
Each `sites/<name>/` is a full Vite project (own `package.json`, `vite.config.js`,
`src/`, `index.html`). Two things are *not* duplicated per site:
- `public/footer.html` and `public/images` are symlinks into `../../../shared/`.
- `public/header.html` is generated (gitignored) from `shared/header.template.html`
plus that site's own `nav.html` (just its `<li>` nav entries) — run automatically
via the `predev`/`prebuild` npm scripts, so `npm run dev` / `npm run build` always
regenerate it fresh. Nothing else about the header (carousel, logo, theme toggle)
varies per site.
To add a new site's nav, edit `sites/<name>/nav.html`. To change the shared photos,
logo, footer copy, or header markup/structure, edit under `shared/` once.
## Commands (per site)
```bash
cd sites/<name>
npm install
npm run dev # http://localhost:5173, regenerates header.html first
npm run build # -> dist/
npm run preview
```
## Deploy
`.gitlab-ci.yml` at the repo root builds/deploys each site independently
(path-scoped via `rules: changes:`), plus a `deploy:shared` job that syncs
`shared/images/` and `shared/footer.html` to
`/var/www/domains/gateway-gamers.net/shared/` on the webserver — the box
the GitLab Runner executes on. Every site's nginx server block points
`/images/` and `/footer.html` at that shared directory via `alias`, so the
webserver only ever holds one copy of the ~25MB of images. See each
`sites/<name>/<name>.gateway-gamers.net.conf` for the live nginx reference
config (applied manually on the box — CI doesn't touch nginx).
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env node
// Assembles sites/<site>/public/header.html from shared/header.template.html
// + sites/<site>/nav.html. Run automatically via each site's predev/prebuild.
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const site = process.argv[2];
if (!site) {
console.error('usage: render-header.mjs <site>');
process.exit(1);
}
const root = path.resolve(fileURLToPath(import.meta.url), '../..');
const templatePath = path.join(root, 'shared/header.template.html');
const navPath = path.join(root, 'sites', site, 'nav.html');
const outPath = path.join(root, 'sites', site, 'public/header.html');
const template = readFileSync(templatePath, 'utf8');
const nav = readFileSync(navPath, 'utf8').trimEnd();
const placeholder = '<!--NAV_ITEMS-->';
const parts = template.split(placeholder);
if (parts.length !== 3) {
console.error(`expected exactly 2 occurrences of ${placeholder} in ${templatePath}, found ${parts.length - 1}`);
process.exit(1);
}
const indent = (block, spaces) =>
block.split('\n').map(line => ' '.repeat(spaces) + line).join('\n');
const rendered = parts[0] + indent(nav, 8) + parts[1] + indent(nav, 10) + parts[2];
writeFileSync(outPath, rendered);
+3
View File
@@ -0,0 +1,3 @@
<footer class="footer footer-center bg-base-300 text-base-content p-4">
<p>&copy; 2026 Gateway Gamers. All rights reserved.</p>
</footer>
@@ -28,7 +28,7 @@
<div class="navbar bg-base-200 px-4 min-h-0 py-2">
<div class="navbar-center hidden lg:flex flex-1 justify-center">
<ul class="menu menu-horizontal px-1 gap-1">
<li><a href="/index.html">Scouting</a></li>
<!--NAV_ITEMS-->
</ul>
</div>
<div class="navbar-end gap-2 ml-auto">
@@ -48,7 +48,7 @@
</svg>
</label>
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-200 rounded-box w-52">
<li><a href="/index.html">Scouting</a></li>
<!--NAV_ITEMS-->
</ul>
</div>
</div>

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Before

Width:  |  Height:  |  Size: 4.3 MiB

After

Width:  |  Height:  |  Size: 4.3 MiB

Before

Width:  |  Height:  |  Size: 4.6 MiB

After

Width:  |  Height:  |  Size: 4.6 MiB

Before

Width:  |  Height:  |  Size: 4.1 MiB

After

Width:  |  Height:  |  Size: 4.1 MiB

Before

Width:  |  Height:  |  Size: 4.9 MiB

After

Width:  |  Height:  |  Size: 4.9 MiB

Before

Width:  |  Height:  |  Size: 4.4 MiB

After

Width:  |  Height:  |  Size: 4.4 MiB

+3
View File
@@ -2,3 +2,6 @@ node_modules/
dist/
.env
.env.local
# generated from shared/header.template.html — see scripts/render-header.mjs
public/header.html
-49
View File
@@ -1,49 +0,0 @@
stages:
- build
- deploy
- refresh
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
only:
- main
except:
- schedules
deploy:
stage: deploy
script:
- rsync -avz --delete dist/ /var/www/domains/gateway-gamers.net/40k-rankings/
only:
- main
except:
- schedules
environment:
name: production
url: https://40k-rankings.gateway-gamers.net
refresh-elo:
stage: refresh
image: python:3.12-slim
variables:
GIT_STRATEGY: clone
script:
- cd statcheck-elo-scraper && python refresh_elo.py && cd ..
- git config user.email "gitlab-ci@gateway-gamers.net"
- git config user.name "GitLab CI"
- git remote set-url origin "https://oauth2:${GITLAB_ACCESS_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
- git add public/elo-data.json
- |
if ! git diff --cached --quiet; then
git commit -m "data: refresh stat-check ELO rankings"
git push origin HEAD:main
fi
only:
- schedules
@@ -30,6 +30,16 @@ server {
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self' https://newprod-api.bestcoastpairings.com; img-src 'self' data:;" always;
# Shared assets (images + footer) — single on-disk copy under shared/,
# deployed once and aliased by every gateway-gamers.net site.
location /images/ {
alias /var/www/domains/gateway-gamers.net/shared/images/;
}
location = /footer.html {
alias /var/www/domains/gateway-gamers.net/shared/footer.html;
}
location / {
try_files $uri $uri/ =404;
}
+2
View File
@@ -0,0 +1,2 @@
<li><a href="/index.html">40K Rankings</a></li>
<li><a href="https://aos-rankings.gateway-gamers.net">AOS Rankings</a></li>
+2
View File
@@ -4,7 +4,9 @@
"private": true,
"type": "module",
"scripts": {
"predev": "node ../../scripts/render-header.mjs 40k-rankings",
"dev": "vite",
"prebuild": "node ../../scripts/render-header.mjs 40k-rankings",
"build": "vite build",
"preview": "vite preview"
},
-3
View File
@@ -1,3 +0,0 @@
<footer class="footer footer-center bg-base-300 text-base-content p-4">
<p>&copy; 2026 Gateway Gamers. All rights reserved.</p>
</footer>
+1
View File
@@ -0,0 +1 @@
../../../shared/footer.html
-59
View File
@@ -1,59 +0,0 @@
<header class="bg-base-300 shadow-lg">
<!-- Hero: carousel background + centered logo -->
<div class="relative overflow-hidden min-h-64 py-10 flex items-center justify-center">
<!-- Carousel images -->
<div id="hero-carousel" class="absolute inset-0">
<img src="/images/photo1.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo2.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo3.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo4.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo5.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
</div>
<!-- Overlay so logo stays readable over busy images -->
<div class="absolute inset-0 bg-black/40"></div>
<!-- Logo -->
<div class="relative z-10">
<a href="/index.html">
<img src="/images/logo.png" alt="Gateway Gamers" class="h-48 w-auto drop-shadow-2xl" />
</a>
</div>
</div>
<!-- Nav row -->
<div class="navbar bg-base-200 px-4 min-h-0 py-2">
<div class="navbar-center hidden lg:flex flex-1 justify-center">
<ul class="menu menu-horizontal px-1 gap-1">
<li><a href="/index.html">40K Rankings</a></li>
<li><a href="https://aos-rankings.gateway-gamers.net">AOS Rankings</a></li>
</ul>
</div>
<div class="navbar-end gap-2 ml-auto">
<label class="swap swap-rotate btn btn-ghost btn-circle" title="Toggle light/dark">
<input type="checkbox" class="theme-controller" value="light" />
<svg class="swap-off h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M21.64 13a1 1 0 0 0-1.27-.66 8.5 8.5 0 0 1-10.71-10.71 1 1 0 0 0-1.32-1.27A10 10 0 1 0 22 14.29a1 1 0 0 0-.36-1.29z"/>
</svg>
<svg class="swap-on h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M5.64 17l-.71.71a1 1 0 0 0 1.41 1.41l.71-.71A1 1 0 0 0 5.64 17zM5 12a1 1 0 0 0-1-1H3a1 1 0 0 0 0 2h1a1 1 0 0 0 1-1zm7-7a1 1 0 0 0 1-1V3a1 1 0 0 0-2 0v1a1 1 0 0 0 1 1zM5.64 7.05a1 1 0 0 0 .7.29 1 1 0 0 0 .71-.29 1 1 0 0 0 0-1.41l-.71-.71a1 1 0 0 0-1.41 1.41zm12 .29a1 1 0 0 0 .7-.29l.71-.71a1 1 0 0 0-1.41-1.41l-.71.71a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29zM21 11h-1a1 1 0 0 0 0 2h1a1 1 0 0 0 0-2zm-9 8a1 1 0 0 0-1 1v1a1 1 0 0 0 2 0v-1a1 1 0 0 0-1-1zm6.36-2a1 1 0 0 0-1.41 1.41l.71.71a1 1 0 0 0 1.41-1.41zM12 6.5a5.5 5.5 0 1 0 5.5 5.5A5.51 5.51 0 0 0 12 6.5z"/>
</svg>
</label>
<div class="dropdown dropdown-end lg:hidden">
<label tabindex="0" class="btn btn-ghost btn-circle">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</label>
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-200 rounded-box w-52">
<li><a href="/index.html">40K Rankings</a></li>
<li><a href="https://aos-rankings.gateway-gamers.net">AOS Rankings</a></li>
</ul>
</div>
</div>
</div>
</header>
+1
View File
@@ -0,0 +1 @@
../../../shared/images
+3
View File
@@ -2,3 +2,6 @@ node_modules/
dist/
.env
.env.local
# generated from shared/header.template.html — see scripts/render-header.mjs
public/header.html
-25
View File
@@ -1,25 +0,0 @@
stages:
- build
- deploy
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
only:
- main
deploy:
stage: deploy
script:
- rsync -avz --delete dist/ /var/www/domains/gateway-gamers.net/aos-rankings/
only:
- main
environment:
name: production
url: https://aos-rankings.gateway-gamers.net
@@ -30,6 +30,16 @@ server {
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self' https://newprod-api.bestcoastpairings.com; img-src 'self' data:;" always;
# Shared assets (images + footer) — single on-disk copy under shared/,
# deployed once and aliased by every gateway-gamers.net site.
location /images/ {
alias /var/www/domains/gateway-gamers.net/shared/images/;
}
location = /footer.html {
alias /var/www/domains/gateway-gamers.net/shared/footer.html;
}
location / {
try_files $uri $uri/ =404;
}
+2
View File
@@ -0,0 +1,2 @@
<li><a href="https://40k-rankings.gateway-gamers.net">40K Rankings</a></li>
<li><a href="/index.html">AOS Rankings</a></li>
+2
View File
@@ -4,7 +4,9 @@
"private": true,
"type": "module",
"scripts": {
"predev": "node ../../scripts/render-header.mjs aos-rankings",
"dev": "vite",
"prebuild": "node ../../scripts/render-header.mjs aos-rankings",
"build": "vite build",
"preview": "vite preview"
},
-3
View File
@@ -1,3 +0,0 @@
<footer class="footer footer-center bg-base-300 text-base-content p-4">
<p>&copy; 2026 Gateway Gamers. All rights reserved.</p>
</footer>
+1
View File
@@ -0,0 +1 @@
../../../shared/footer.html
-59
View File
@@ -1,59 +0,0 @@
<header class="bg-base-300 shadow-lg">
<!-- Hero: carousel background + centered logo -->
<div class="relative overflow-hidden min-h-64 py-10 flex items-center justify-center">
<!-- Carousel images -->
<div id="hero-carousel" class="absolute inset-0">
<img src="/images/photo1.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo2.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo3.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo4.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo5.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
</div>
<!-- Overlay so logo stays readable over busy images -->
<div class="absolute inset-0 bg-black/40"></div>
<!-- Logo -->
<div class="relative z-10">
<a href="/index.html">
<img src="/images/logo.png" alt="Gateway Gamers" class="h-48 w-auto drop-shadow-2xl" />
</a>
</div>
</div>
<!-- Nav row -->
<div class="navbar bg-base-200 px-4 min-h-0 py-2">
<div class="navbar-center hidden lg:flex flex-1 justify-center">
<ul class="menu menu-horizontal px-1 gap-1">
<li><a href="https://40k-rankings.gateway-gamers.net">40K Rankings</a></li>
<li><a href="/index.html">AOS Rankings</a></li>
</ul>
</div>
<div class="navbar-end gap-2 ml-auto">
<label class="swap swap-rotate btn btn-ghost btn-circle" title="Toggle light/dark">
<input type="checkbox" class="theme-controller" value="light" />
<svg class="swap-off h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M21.64 13a1 1 0 0 0-1.27-.66 8.5 8.5 0 0 1-10.71-10.71 1 1 0 0 0-1.32-1.27A10 10 0 1 0 22 14.29a1 1 0 0 0-.36-1.29z"/>
</svg>
<svg class="swap-on h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M5.64 17l-.71.71a1 1 0 0 0 1.41 1.41l.71-.71A1 1 0 0 0 5.64 17zM5 12a1 1 0 0 0-1-1H3a1 1 0 0 0 0 2h1a1 1 0 0 0 1-1zm7-7a1 1 0 0 0 1-1V3a1 1 0 0 0-2 0v1a1 1 0 0 0 1 1zM5.64 7.05a1 1 0 0 0 .7.29 1 1 0 0 0 .71-.29 1 1 0 0 0 0-1.41l-.71-.71a1 1 0 0 0-1.41 1.41zm12 .29a1 1 0 0 0 .7-.29l.71-.71a1 1 0 0 0-1.41-1.41l-.71.71a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29zM21 11h-1a1 1 0 0 0 0 2h1a1 1 0 0 0 0-2zm-9 8a1 1 0 0 0-1 1v1a1 1 0 0 0 2 0v-1a1 1 0 0 0-1-1zm6.36-2a1 1 0 0 0-1.41 1.41l.71.71a1 1 0 0 0 1.41-1.41zM12 6.5a5.5 5.5 0 1 0 5.5 5.5A5.51 5.51 0 0 0 12 6.5z"/>
</svg>
</label>
<div class="dropdown dropdown-end lg:hidden">
<label tabindex="0" class="btn btn-ghost btn-circle">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</label>
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-200 rounded-box w-52">
<li><a href="https://40k-rankings.gateway-gamers.net">40K Rankings</a></li>
<li><a href="/index.html">AOS Rankings</a></li>
</ul>
</div>
</div>
</div>
</header>
+1
View File
@@ -0,0 +1 @@
../../../shared/images
Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

+3
View File
@@ -2,3 +2,6 @@ node_modules/
dist/
.env
.env.local
# generated from shared/header.template.html — see scripts/render-header.mjs
public/header.html
-25
View File
@@ -1,25 +0,0 @@
stages:
- build
- deploy
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
only:
- main
deploy:
stage: deploy
script:
- rsync -avz --delete dist/ /var/www/domains/gateway-gamers.net/kingmaker/
only:
- main
environment:
name: production
url: https://kingmaker.gateway-gamers.net
@@ -30,6 +30,16 @@ server {
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; frame-src https://docs.google.com; img-src 'self' data:;" always;
# Shared assets (images + footer) — single on-disk copy under shared/,
# deployed once and aliased by every gateway-gamers.net site.
location /images/ {
alias /var/www/domains/gateway-gamers.net/shared/images/;
}
location = /footer.html {
alias /var/www/domains/gateway-gamers.net/shared/footer.html;
}
location / {
try_files $uri $uri/ =404;
}
+4
View File
@@ -0,0 +1,4 @@
<li><a href="/index.html">Home</a></li>
<li><a href="/signups.html">Sign-ups</a></li>
<li><a href="/reporting.html">Game Reporting</a></li>
<li><a href="/standings.html">Standings</a></li>
+2
View File
@@ -4,7 +4,9 @@
"private": true,
"type": "module",
"scripts": {
"predev": "node ../../scripts/render-header.mjs kingmaker",
"dev": "vite",
"prebuild": "node ../../scripts/render-header.mjs kingmaker",
"build": "vite build",
"preview": "vite preview"
},
-3
View File
@@ -1,3 +0,0 @@
<footer class="footer footer-center bg-base-300 text-base-content p-4">
<p>&copy; 2026 Gateway Gamers. All rights reserved.</p>
</footer>
+1
View File
@@ -0,0 +1 @@
../../../shared/footer.html
-63
View File
@@ -1,63 +0,0 @@
<header class="bg-base-300 shadow-lg">
<!-- Hero: carousel background + centered logo -->
<div class="relative overflow-hidden min-h-64 py-10 flex items-center justify-center">
<!-- Carousel images -->
<div id="hero-carousel" class="absolute inset-0">
<img src="/images/photo1.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo2.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo3.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo4.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
<img src="/images/photo5.jpg" class="carousel-slide absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-1000" />
</div>
<!-- Overlay so logo stays readable over busy images -->
<div class="absolute inset-0 bg-black/40"></div>
<!-- Logo -->
<div class="relative z-10">
<a href="/index.html">
<img src="/images/logo.png" alt="Gateway Gamers" class="h-48 w-auto drop-shadow-2xl" />
</a>
</div>
</div>
<!-- Nav row -->
<div class="navbar bg-base-200 px-4 min-h-0 py-2">
<div class="navbar-center hidden lg:flex flex-1 justify-center">
<ul class="menu menu-horizontal px-1 gap-1">
<li><a href="/index.html">Home</a></li>
<li><a href="/signups.html">Sign-ups</a></li>
<li><a href="/reporting.html">Game Reporting</a></li>
<li><a href="/standings.html">Standings</a></li>
</ul>
</div>
<div class="navbar-end gap-2 ml-auto">
<label class="swap swap-rotate btn btn-ghost btn-circle" title="Toggle light/dark">
<input type="checkbox" class="theme-controller" value="light" />
<svg class="swap-off h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M21.64 13a1 1 0 0 0-1.27-.66 8.5 8.5 0 0 1-10.71-10.71 1 1 0 0 0-1.32-1.27A10 10 0 1 0 22 14.29a1 1 0 0 0-.36-1.29z"/>
</svg>
<svg class="swap-on h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M5.64 17l-.71.71a1 1 0 0 0 1.41 1.41l.71-.71A1 1 0 0 0 5.64 17zM5 12a1 1 0 0 0-1-1H3a1 1 0 0 0 0 2h1a1 1 0 0 0 1-1zm7-7a1 1 0 0 0 1-1V3a1 1 0 0 0-2 0v1a1 1 0 0 0 1 1zM5.64 7.05a1 1 0 0 0 .7.29 1 1 0 0 0 .71-.29 1 1 0 0 0 0-1.41l-.71-.71a1 1 0 0 0-1.41 1.41zm12 .29a1 1 0 0 0 .7-.29l.71-.71a1 1 0 0 0-1.41-1.41l-.71.71a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29zM21 11h-1a1 1 0 0 0 0 2h1a1 1 0 0 0 0-2zm-9 8a1 1 0 0 0-1 1v1a1 1 0 0 0 2 0v-1a1 1 0 0 0-1-1zm6.36-2a1 1 0 0 0-1.41 1.41l.71.71a1 1 0 0 0 1.41-1.41zM12 6.5a5.5 5.5 0 1 0 5.5 5.5A5.51 5.51 0 0 0 12 6.5z"/>
</svg>
</label>
<div class="dropdown dropdown-end lg:hidden">
<label tabindex="0" class="btn btn-ghost btn-circle">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</label>
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-200 rounded-box w-52">
<li><a href="/index.html">Home</a></li>
<li><a href="/signups.html">Sign-ups</a></li>
<li><a href="/reporting.html">Game Reporting</a></li>
<li><a href="/standings.html">Standings</a></li>
</ul>
</div>
</div>
</div>
</header>
+1
View File
@@ -0,0 +1 @@
../../../shared/images
Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

+3
View File
@@ -3,3 +3,6 @@ dist/
.env
.env.local
.claude/settings.local.json
# generated from shared/header.template.html — see scripts/render-header.mjs
public/header.html
-29
View File
@@ -1,29 +0,0 @@
stages:
- build
- deploy
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
only:
- main
except:
- schedules
deploy:
stage: deploy
script:
- rsync -avz --delete dist/ /var/www/domains/gateway-gamers.net/scouting/
only:
- main
except:
- schedules
environment:
name: production
url: https://scouting.gateway-gamers.net
+1
View File
@@ -0,0 +1 @@
<li><a href="/index.html">Scouting</a></li>
+2
View File
@@ -4,7 +4,9 @@
"private": true,
"type": "module",
"scripts": {
"predev": "node ../../scripts/render-header.mjs scouting",
"dev": "vite",
"prebuild": "node ../../scripts/render-header.mjs scouting",
"build": "vite build",
"preview": "vite preview"
},
-3
View File
@@ -1,3 +0,0 @@
<footer class="footer footer-center bg-base-300 text-base-content p-4">
<p>&copy; 2026 Gateway Gamers. All rights reserved.</p>
</footer>
+1
View File
@@ -0,0 +1 @@
../../../shared/footer.html
+1
View File
@@ -0,0 +1 @@
../../../shared/images
Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

@@ -35,6 +35,16 @@ server {
# style attribute).
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://newprod-api.bestcoastpairings.com; img-src 'self' data:;" always;
# Shared assets (images + footer) — single on-disk copy under shared/,
# deployed once and aliased by every gateway-gamers.net site.
location /images/ {
alias /var/www/domains/gateway-gamers.net/shared/images/;
}
location = /footer.html {
alias /var/www/domains/gateway-gamers.net/shared/footer.html;
}
location / {
try_files $uri $uri/ =404;
}