Compare commits

76 Commits

Author SHA1 Message Date
f94e3dc8ac fix: use namespace zod import in house tracker types
All checks were successful
Test and deploy staging / staging (push) Successful in 2m46s
`import { z } from 'zod'` is undefined when vitest runs under Bun (as in the
Docker build), matching the earlier fix in eca43c8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:20:22 +00:00
54924b39f0 feat: upgrade to SvelteKit 3 and adapter-bun
Some checks failed
Test and deploy staging / staging (push) Failing after 43s
Run the sveltekit-3 migration and finish its manual tasks:

- Move kit config into the sveltekit() Vite plugin; drop svelte.config.js
- Replace $lib with #lib subpath imports
- Declare env vars in src/env.ts and import them from $app/env/{private,public}
- Consolidate param matchers into src/params.ts
- Use error(status, message), Response.json, refreshAll and goto reset/shallow
- Fall back to a full page load when goto rejects a login return path

Switch from adapter-node to adapter-bun: the build script runs Vite under
Bun (the adapter requires it) and the image starts with `bun ./build`.
Runtime env names (PORT, BODY_SIZE_LIMIT, ADDRESS_HEADER, ...) are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:17:30 +00:00
b52e9188f4 house worker 2026-10-11 22:05:05 +00:00
ecd49fc410 fix: parse real Pararius search titles correctly 2026-10-11 21:38:47 +00:00
4ea0ba2d48 fix: enforce house worker request spacing and ignore local secrets 2026-10-11 21:14:41 +00:00
0846cf7d77 feat: house tracker with home worker, alerts and listing map
Collects Pararius rent/buy listings into Postgres via a worker that runs on a
residential connection, sends Discord/ntfy alerts for new listings matching
rules, and shows an admin-only map, market stats and worker log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HfF5tnChi8tkCx9svJP6D
2026-10-11 20:57:20 +00:00
0ee9ed0abb feat: roomier BO2 companion with +/- rank stepping
All checks were successful
Test and deploy staging / staging (push) Successful in 2m29s
- Scale the COD page to the screen: larger gun art, text and progress
  ribbon, sized from one viewport unit so it fills a MacBook Pro 16"
  and big monitors and still fits 1280x720 without scrolling.
- Five columns on wide screens and four below, picked by CSS, so the
  server-rendered page already has the final layout.
- Replace the tabs/filters/overview rows with one toolbar of matching
  controls.
- Player and weapon ranks step with + and -: level 55 + 1 is the next
  prestige at level 1, up to max prestige. Presses show at once, batch
  while a save is running, and stepRank does the arithmetic in SQL.
- Clicking a rank still opens exact entry for big jumps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:50:19 +00:00
8eec207f2d fix: create deployment backups with allowed Docker permissions
All checks were successful
Test and deploy staging / staging (push) Successful in 2m26s
2026-10-08 21:10:21 +00:00
c2bc9050c2 feat: build compact BO2 grind companion with cumulative ranks
Some checks failed
Test and deploy staging / staging (push) Failing after 2m50s
2026-10-08 21:06:19 +00:00
c2ba8febb4 Expand website and game trackers
Some checks failed
Test and deploy staging / staging (push) Failing after 5m29s
2026-09-25 22:43:04 +00:00
f6b34150d4 add ai slop warnings
All checks were successful
Test and deploy staging / staging (push) Successful in 12m6s
2026-09-25 16:41:45 +00:00
abac077541 ui changes and cs prices
All checks were successful
Test and deploy staging / staging (push) Successful in 11m52s
2026-09-25 00:44:06 +00:00
c757d0882a add icons to lists and solve money tracker bugs
All checks were successful
Test and deploy staging / staging (push) Successful in 2m30s
2026-09-24 19:24:09 +00:00
a10585a4d2 add game tracker with owned pokemon data and call of duty mastery
All checks were successful
Test and deploy staging / staging (push) Successful in 18s
Fold catchemall into /projects/game-tracker (old URL redirects) and add
Call of Duty tracking for Black Ops through WWII.

- Pokémon catalog is now owned: locations, encounters and per-game
  evolution methods are built from the pinned PokéAPI snapshot and seeded.
- Pokémon you can evolve or breed in a game count as obtainable; mythicals
  without another route are marked event-only.
- Detail dialog with evolution chain and where to find it, location pages
  with admin notes and pictures, and admin corrections that survive reseeds.
- Call of Duty weapons, milestones and Steam achievements per game, with
  derived Diamond / Dark Matter / Solar / Black Sky / Chrome tiers.
- New migration (db:migrate:game-tracker), seeds (db:seed, db:seed:cod) and
  deploy preflight checks for them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 17:36:18 +00:00
438623d857 add language learner project
Some checks failed
Test and deploy staging / staging (push) Has been cancelled
Dutch and Korean (explained in English) and English (explained in Dutch)
from A1 to C1, with a Hangul level for Korean. Lessons with notes, words
and sentences generate multiple-choice, typing, sentence building, cloze,
listening and matching exercises. Endless challenges: a seeded daily
challenge, sprint, survival, mixed practice, level tests, spaced-repetition
reviews and generated drills (numbers, clock times, de/het, plurals, Dutch
and Korean conjugation, particles, Hangul reading, irregular verbs).
Progress is stored in localStorage with export/import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 17:34:56 +00:00
afb1f191df add world opinions project
All checks were successful
Test and deploy staging / staging (push) Successful in 1m49s
World map with switchable layers (what "liberal" means, what left vs right
is about, traditional/secular and survival/self-expression values), country
cards, a shared-scale party compass, a values scatter and national
narratives of major events.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 16:24:40 +00:00
499dd94cbb update money tracker
All checks were successful
Test and deploy staging / staging (push) Successful in 30s
2026-09-23 18:06:20 +00:00
2466db74cd fix csv import for money tracker
All checks were successful
Test and deploy staging / staging (push) Successful in 1m44s
2026-09-23 17:23:14 +00:00
0638ead87f add money tracker
All checks were successful
Test and deploy staging / staging (push) Successful in 1m46s
2026-09-23 17:02:32 +00:00
753b2e5d6a add animal modal and page
All checks were successful
Test and deploy staging / staging (push) Successful in 1m54s
2026-09-23 16:56:19 +00:00
3691a85fdf add projects dropdown
All checks were successful
Test and deploy staging / staging (push) Successful in 1m48s
2026-09-23 16:44:21 +00:00
4b63e9ca11 remove emojis with real icons
All checks were successful
Test and deploy staging / staging (push) Successful in 1m46s
2026-09-23 16:12:41 +00:00
f1cd0ef849 add catchemall progress bars and hide uncatchable pokemon by default
All checks were successful
Test and deploy staging / staging (push) Successful in 28s
2026-09-23 04:59:22 +00:00
eca43c82d0 fix zod imports in bun test build
Some checks failed
Test and deploy staging / staging (push) Failing after 1m39s
2026-09-23 04:47:27 +00:00
1d0b96e7bc optimize building and deploying
Some checks failed
Test and deploy staging / staging (push) Failing after 13s
2026-09-23 04:36:58 +00:00
d0c7b6548b beeeeg update
Some checks failed
Test and deploy staging / staging (push) Has been cancelled
2026-09-23 04:16:50 +00:00
487eff5bb2 Merge pull request 'update homepage experience min width' (#43) from dev into staging
All checks were successful
build / build (push) Successful in 44s
build / deploy (push) Successful in 3s
Reviewed-on: #43
2026-09-22 14:42:50 +00:00
379b21cb93 update homepage experience min width 2026-09-22 14:42:28 +00:00
8ad422083e Merge pull request 'update home page' (#41) from dev into staging
All checks were successful
build / build (push) Successful in 51s
build / deploy (push) Successful in 3s
Reviewed-on: #41
2026-09-22 13:51:44 +00:00
0991a9585f update home page 2026-09-22 13:51:04 +00:00
e8d6c8de4b Merge pull request 'dev' (#37) from dev into staging
All checks were successful
build / build (push) Successful in 50s
build / deploy (push) Successful in 3s
Reviewed-on: #37
2026-09-20 02:24:50 +00:00
7f04896ff8 update blog index page ui 2026-09-20 02:24:13 +00:00
8ea36dd515 add projects page 2026-09-20 02:23:30 +00:00
6ec596bc24 add derby page 2026-09-20 02:23:12 +00:00
97cbf90c3d remove drizzle and vscode files 2026-09-19 18:23:19 +00:00
953b2744bd add header border 2026-09-19 18:20:05 +00:00
a4d583e241 Merge pull request 'dev' (#33) from dev into staging
All checks were successful
build / build (push) Successful in 49s
build / deploy (push) Successful in 13s
Reviewed-on: #33
2026-09-17 05:46:56 +00:00
e93db1dbe7 remove blog link in homepage 2026-09-17 05:40:01 +00:00
3f9190041f set dev title + setup for future logic 2026-09-17 05:39:44 +00:00
d831afac15 Merge pull request 'dont use postgis for animaldex' (#30) from dev into staging
All checks were successful
build / build (push) Successful in 40s
build / deploy (push) Successful in 13s
Reviewed-on: #30
2026-09-17 05:01:22 +00:00
247e65324e dont use postgis for animaldex 2026-09-17 05:00:59 +00:00
a543ceb4d1 Merge pull request 'update umami url' (#28) from dev into staging
All checks were successful
build / build (push) Successful in 36s
build / deploy (push) Successful in 13s
Reviewed-on: #28
2026-09-17 03:24:24 +00:00
dff5c98e37 update umami url 2026-09-17 03:24:03 +00:00
a58bf74321 Merge pull request 'dev' (#26) from dev into staging
All checks were successful
build / build (push) Successful in 48s
build / deploy (push) Successful in 4s
Reviewed-on: #26
2026-09-17 03:17:31 +00:00
ff5661a635 add umami analytics 2026-09-17 03:16:53 +00:00
78ea93b1f7 underline stan runge top left 2026-09-13 17:00:26 +00:00
1311831fe9 fix redirect after creating new article 2026-09-04 17:05:37 +00:00
ce5c1cc9ee Merge pull request 'add footer with git link' (#20) from dev into staging
All checks were successful
build / build (push) Successful in 35s
build / deploy (push) Successful in 3s
Reviewed-on: #20
2026-09-01 21:58:06 +00:00
739d8486b2 add footer with git link 2026-09-01 21:57:31 +00:00
b11c414130 Merge pull request 'add markdown formatting with code support' (#14) from dev into staging
All checks were successful
build / build (push) Successful in 1m16s
build / deploy (push) Successful in 13s
Reviewed-on: #14
2026-09-01 11:20:27 +00:00
0994f31281 add markdown formatting with code support 2026-09-01 11:19:13 +00:00
4328d81408 Merge pull request 'add blog editor' (#11) from dev into staging
All checks were successful
build / build (push) Successful in 43s
build / deploy (push) Successful in 13s
Reviewed-on: #11
2026-08-31 16:40:31 +00:00
4822125787 add blog editor 2026-08-31 16:39:56 +00:00
62ae672630 Merge pull request 'update api key imprts and change URLs' (#9) from dev into staging
All checks were successful
build / build (push) Successful in 43s
build / deploy (push) Successful in 3s
Reviewed-on: #9
2026-08-31 14:41:25 +00:00
d793400336 update api key imprts and change URLs 2026-08-31 14:40:31 +00:00
f94d68f539 Merge pull request 'dynamically import the maptiler api key' (#8) from dev into staging
All checks were successful
build / build (push) Successful in 46s
build / deploy (push) Successful in 13s
Reviewed-on: #8
2026-08-31 14:38:24 +00:00
20dddaa263 dynamically import the maptiler api key 2026-08-31 14:38:02 +00:00
3c2b626ea2 Merge pull request 'update dockerfile' (#7) from dev into staging
All checks were successful
build / build (push) Successful in 1m17s
build / deploy (push) Successful in 3s
Reviewed-on: #7
2026-08-31 14:24:48 +00:00
07f6a77fc6 update dockerfile 2026-08-31 14:24:03 +00:00
eba89011aa Merge pull request 'smaller docker image' (#6) from dev into staging
All checks were successful
build / build (push) Successful in 38s
build / deploy (push) Successful in 4s
Reviewed-on: #6
2026-08-30 22:25:18 +00:00
fe225f8a3d smaller docker image 2026-08-30 22:24:55 +00:00
c76a137090 Merge pull request 'removed auth dependency at build time' (#5) from dev into staging
Some checks failed
build / build (push) Failing after 1m19s
build / deploy (push) Has been skipped
Reviewed-on: #5
2026-08-30 22:07:07 +00:00
0e8c3c70b8 removed auth dependency at build time 2026-08-30 22:06:42 +00:00
99e97ce750 Merge pull request 'fix dockerfile with docker lockfile' (#4) from dev into staging
Some checks failed
build / build (push) Failing after 38s
build / deploy (push) Has been skipped
Reviewed-on: #4
2026-08-30 21:43:01 +00:00
077e9f54b9 fix dockerfile with docker lockfile 2026-08-30 21:42:33 +00:00
b005a39f7c Merge pull request 'fix: build errors and merging projects and homepage' (#3) from dev into staging
Some checks failed
build / build (push) Failing after 16s
build / deploy (push) Has been skipped
Reviewed-on: #3
2026-08-30 21:39:46 +00:00
c9a7294d13 fix: build errors and merging projects and homepage 2026-08-30 21:38:59 +00:00
95514c8eea Merge pull request 'dev' (#2) from dev into staging
Some checks failed
build / build (push) Failing after 23s
build / deploy (push) Has been skipped
Reviewed-on: #2
2026-08-29 11:39:42 +00:00
97ff9852cd update homepage draft and other pages 2026-08-29 11:38:16 +00:00
6452331ee7 update tailwind to v4 2026-08-27 00:50:55 +00:00
a337879875 chore: add basic readme
All checks were successful
build / build (push) Successful in 40s
build / deploy (push) Successful in 7s
2026-08-26 04:19:11 +00:00
40ff6f795b ci: pick dir by branch for staging/prod deploy
All checks were successful
build / build (push) Successful in 40s
build / deploy (push) Successful in 7s
2026-08-26 04:13:16 +00:00
cb5fc9d095 chore: flatten repo
All checks were successful
build / build (push) Successful in 47s
build / deploy (push) Successful in 17s
2026-08-26 03:57:15 +00:00
b02a07d3bd ci: update deploy command
All checks were successful
build / build (push) Successful in 11s
build / deploy (push) Successful in 3s
2026-08-26 03:22:45 +00:00
36cf013eef ci: add deploy job
Some checks failed
build / build (push) Successful in 11s
build / deploy (push) Failing after 2s
2026-08-26 03:15:41 +00:00
2bfba0d8fe ci: build and push image
All checks were successful
build / build (push) Successful in 2m54s
2026-08-24 20:47:14 +00:00
4584 changed files with 157129 additions and 7002 deletions

10
.dockerignore Normal file
View File

@@ -0,0 +1,10 @@
.git
.env
.env.*
!.env.example
node_modules
.svelte-kit
build
test-results
playwright-report
*.log

View File

@@ -0,0 +1,37 @@
# PostgreSQL connection (server only)
DB_URL=postgresql://postgres:postgres@localhost:5432/personal_dev
# Use the exact public origin, including scheme and port. Set the same value for both.
ORIGIN=http://localhost:5173
BETTER_AUTH_URL=http://localhost:5173
# Generate with: openssl rand -base64 48
BETTER_AUTH_SECRET=
# Optional Google login; callback: <BETTER_AUTH_URL>/api/auth/callback/google
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Optional osu! login; callback: <BETTER_AUTH_URL>/api/auth/callback/osu
# These can also serve the existing beatmap finder.
OSU_CLIENT_ID=
OSU_CLIENT_SECRET=
SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=
# Public browser key for Animaldex map tiles.
PUBLIC_MAPTILER_API_KEY=
# Cloudflare R2, shared by Animaldex and admin-uploaded list pictures.
# R2_PUBLIC_URL is the public bucket/custom-domain base URL (without a trailing slash).
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=
R2_PUBLIC_URL=
# House tracker: shared secret for the home worker (openssl rand -hex 32), and
# where alerts go (Discord webhook and/or ntfy topic URL). See docs/house-tracker.md.
HOUSE_WORKER_TOKEN=
HOUSE_DISCORD_WEBHOOK_URL=
HOUSE_NTFY_URL=
HOUSE_NTFY_TOKEN=

View File

@@ -0,0 +1,44 @@
name: Test and deploy staging
on:
push:
branches: [staging]
jobs:
staging:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Build once. The same SHA-tagged image stays on the deployment host for
# one-click promotion to production; the production workflow never rebuilds it.
- uses: docker/build-push-action@v5
with:
context: .
push: false
load: true
tags: git.stanrunge.dev/stan/personal-website:${{ github.sha }}
build-args: |
GIT_SHA=${{ github.sha }}
- name: Configure deployment SSH key
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
set -euo pipefail
install -d -m 700 ~/.ssh
printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H 172.17.0.1 >> ~/.ssh/known_hosts
- name: Transfer tested image
run: |
set -euo pipefail
image="git.stanrunge.dev/stan/personal-website:${{ github.sha }}"
docker save "$image" | ssh -o BatchMode=yes deploy@172.17.0.1 'sudo docker load'
- name: Deploy staging
run: |
set -euo pipefail
ssh -o BatchMode=yes deploy@172.17.0.1 \
"bash -s -- '${{ github.sha }}' '/opt/personal-website-staging' 'https://staging.stanrunge.dev'" \
< scripts/deploy-remote.sh

View File

@@ -0,0 +1,33 @@
name: Deploy production
on:
workflow_dispatch:
jobs:
promote:
runs-on: ubuntu-latest
steps:
# Always test the staging branch, regardless of which branch the button
# appears on in Gitea's Actions UI.
- uses: actions/checkout@v4
with:
ref: staging
- name: Configure deployment SSH key
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
set -euo pipefail
install -d -m 700 ~/.ssh
printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H 172.17.0.1 >> ~/.ssh/known_hosts
- name: Promote the exact staging image
run: |
set -euo pipefail
commit=$(git rev-parse HEAD)
ssh -o BatchMode=yes deploy@172.17.0.1 "bash -s -- '$commit'" \
< scripts/verify-staging-image.sh
ssh -o BatchMode=yes deploy@172.17.0.1 \
"bash -s -- '$commit' '/opt/personal-website' 'https://stanrunge.dev'" \
< scripts/deploy-remote.sh

32
.gitignore vendored
View File

@@ -1 +1,31 @@
.env
node_modules
# Output
.output
.vercel
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Home worker credentials and browser cookies
/house.env
/.house-worker-profile/
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vscode/
# Browser test output
/test-results/
/playwright-report/

21
.prettierignore Executable file
View File

@@ -0,0 +1,21 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
# Generated pinned catalog
data/pokemon-catalog.json
# Generated Pokémon locations and encounters
data/pokemon-locations.json
# Generated Pokémon research and map coordinates
data/pokemon-research.json
data/pokemon-atlas.json
# Attributed generated challenge snapshot
data/cod-challenges.json
# Generated local image download manifest
data/game-images.json
data/cod-achievement-art.json
src/lib/house-tracker/fixtures/

24
Dockerfile Executable file
View File

@@ -0,0 +1,24 @@
FROM oven/bun:1.4.0 AS builder
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run test:unit -- --run
RUN bun run build
FROM oven/bun:1.4.0
WORKDIR /app
ARG GIT_SHA=dev
ENV APP_VERSION=$GIT_SHA
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/build ./build
COPY --from=builder /app/scripts ./scripts
COPY --from=builder /app/data ./data
COPY --from=builder /app/src/lib/server/db/schema ./src/lib/server/db/schema
COPY --from=builder /app/src/lib/cs-prices ./src/lib/cs-prices
COPY --from=builder /app/src/lib/availability.ts ./src/lib/availability.ts
COPY package.json ./
EXPOSE 3000
ENV NODE_ENV=production
ENV BODY_SIZE_LIMIT=6M
CMD ["bun", "./build"]

478
README.md Normal file
View File

@@ -0,0 +1,478 @@
# Stan Website
This is my personal website, including:
- Contact info / social links
- About me
- Blog posts + comments
- Projects like Animaldex, CatchEmAll
## House tracker
Available at `/projects/house-tracker` (admin only). Listings come from a worker
that runs at home; setup, settings and the ban-avoidance rules are in
[docs/house-tracker.md](docs/house-tracker.md).
## osu! Map Finder
Available at `/projects/spotify-beatmap-finder`. Built with the existing Svelte 5,
SvelteKit 3, Tailwind 4, and adapter-bun setup. Uses server-only direct `fetch`
calls; no Spotify/osu! SDK or additional services.
Set these private variables in your local `.env` or deployment environment:
```text
SPOTIFY_CLIENT_ID
SPOTIFY_CLIENT_SECRET
OSU_CLIENT_ID
OSU_CLIENT_SECRET
```
Never prefix these with `PUBLIC_` or commit `.env`. Providers read
`$app/env/private`; the browser receives only display metadata and results.
The other projects on this personal site retain their own environment requirements.
Supported inputs: song text (`Deftones Change`), Spotify track, playlist, and album
URLs, and `spotify:track:ID`, `spotify:playlist:ID`, `spotify:album:ID` URIs.
URLs allow HTTP/HTTPS, `www`, locale prefixes, query parameters and trailing slashes.
Artist, episode, show and other resources receive a validation error.
Playlists and albums preserve order and process up to **50 usable unique tracks**
(`MAX_PLAYLIST_TRACKS`), scanning at most **10 pages** (`MAX_PLAYLIST_PAGES`).
Null, unavailable, local, malformed and episode entries are skipped. Truncation is
shown explicitly. Album tracks inherit the album artwork/name. Each match has its
own error state; an osu! failure cannot erase other completed tracks. The enhanced
form shows a pending state and renders the complete batch; native form submission
also works without JavaScript. Filters require JavaScript.
Spotify playlist availability depends on application quota/access. Both legacy
`tracks/track` and newer `items/item` payloads are supported. Spotify's
[2026 development-mode changes](https://developer.spotify.com/documentation/web-api/tutorials/february-2026-migration-guide)
restrict playlist access; client credentials cannot unlock private/user-authorized
playlists. Such responses produce an access error. This project intentionally does
not add user authentication.
Matching is pure and testable in `src/lib/map-finder/matching.ts`: Unicode and
punctuation normalization, diacritic removal, common annotation cleanup, title and
artist token overlap, Unicode metadata alternatives, and version penalties. Title
contributes 65 points and artist 35. High confidence starts at 88, medium at 72;
weak artist evidence is capped below medium, and a different version costs 24.
These are heuristics, not probabilities. Candidates below 40 are omitted; up to six
are sorted and deduplicated by beatmapset ID. Low-confidence candidates are collapsed.
Search first uses artist + title, then one cleaned-title fallback if no medium/high
match exists. Duration is omitted because osu! map length may reflect a song cut.
Tokens are reused until 30 seconds before expiry with a shared refresh promise.
Matching results cache for five minutes (maximum 500 entries), including no-match
results, and identical pending searches are coalesced. Three concurrent osu!
matching jobs are allowed per process. The site allows one active search per IP,
up to six active searches globally, and 12 budget units per IP per 10 minutes:
a single track costs one, a playlist/album six. Expired records are cleaned on
requests; the limiter has a 10,000-record ceiling. It uses `getClientAddress()`,
never arbitrary forwarded headers; unsupported adapters share a conservative key.
Configure adapter-bun's trusted proxy/client-address settings (`ADDRESS_HEADER`, `XFF_DEPTH`) correctly at deployment.
Requests have a 12-second timeout and at most two retries for network errors,
429s or server errors. Backoff includes jitter. `Retry-After` is honored; delays
above four seconds return a helpful error and retain a provider cooldown rather
than holding the request open. All caches, cooldowns, concurrency and rate limits
are per process and reset on restart; multiple instances do not share them.
Local commands (npm scripts and existing tooling; no new dependencies):
```sh
npm install
npm run dev
npm run test:unit -- --run
npm run test:integration
npm run check
npm run lint
npm run build
```
Finder unit tests mock upstream APIs. Browser tests cover initial layout and
validation with and without enhancement, and do not call Spotify or osu!.
## Language learner
`/projects/language-learner` teaches Dutch and Korean (explained in English) and
English (explained in Dutch), from A1 to C1. Korean starts with a Hangul level.
It runs entirely in the browser: no database tables, migrations or API keys.
- Course content lives in `src/lib/language-learner/courses/<lang>/<level>.ts` as
compact `target | translation | note` lines, plus markdown notes and
multiple-choice grammar questions (`prompt | answer | wrong; wrong | explanation`).
Exercises (choice, typing, sentence building, fill-in-the-blank, listening,
matching) are generated from that data. `content.test.ts` validates every lesson.
- Endless drills are generated by code in `src/lib/language-learner/drills/`:
number words, Dutch/English clock times, de/het, plurals, Dutch and Korean
conjugation (a rule-based Korean conjugator handling the ㅂ/ㄷ/르/ㅅ/ㅎ
irregulars), Korean particles and counters, Hangul reading, English irregular
verbs, comparatives and a/an.
- Challenges: a seeded daily challenge (first score of the day counts), 60-second
sprint, survival with three lives, mixed practice, level tests that mark a level
as done at 80%, and spaced-repetition reviews (simplified SM-2).
- Audio uses the browser's speech synthesis, so listening exercises appear only
when the device has a voice for the language. Romanization of Korean is
approximate Revised Romanization.
- Progress, streaks and settings are stored in `localStorage`
(`language-learner-v1`), per browser. Export/import on the overview page moves
them between devices.
## Shared accounts and permissions
`/login`, `/register`, and `/account` are the central account pages. Better Auth
handles email/password credentials and database-backed sessions for the entire
site. Google and osu! buttons appear when the corresponding client ID and secret
are configured. The header exposes account access everywhere.
Copy the auth settings from `.env.example`. `BETTER_AUTH_SECRET` must be at least
32 random characters; there is no runtime fallback secret. Set `ORIGIN` and
`BETTER_AUTH_URL` to the same public origin (for example,
`https://dev.stanrunge.dev` behind the local proxy). Register these exact callbacks:
- Google: `<BETTER_AUTH_URL>/api/auth/callback/google`
- osu!: `<BETTER_AUTH_URL>/api/auth/callback/osu`
osu! uses the `identify` scope and a stable numeric account ID. Its API does not
supply an email address, so these accounts use a reserved, unverified
`<id>@osu.invalid` internal identifier, hidden from the account UI. Credential
registration cannot claim that namespace. Provider accounts are linked explicitly
from `/account` while logged in; there is no automatic merging by email. Link a
provider **before** using it to create a separate account if you want shared
progress. Existing separate accounts are not automatically merged.
Every new user has role `user`. The shared server guards enforce administrator
access for blog drafts/writes, list creation/editing, Animaldex moderation, and
Progress edits. Legacy `ADMIN_SECRET`, `AUTH_TOKEN`, `admin_token`, and `token`
logins no longer authorize anything. To grant yourself admin access, register,
copy the account ID shown on `/account`, then run on the server:
```sh
npm run auth:role -- <user-id> admin
# To revoke access:
npm run auth:role -- <user-id> user
```
Role changes take effect on the next request; roles cannot be set through signup
or profile updates. Public registrations are never made admin automatically.
Future projects should reference the shared `user.id`, use `requireUser(event)`,
and scope **both reads and writes** to the session user's ID. Use
`requireAdmin(event)` for site-wide administration; never accept an owner ID from
a form or infer permission from the presence of a cookie.
Email/password registration works without a mail service. Email verification and
password recovery emails are **not configured** yet. Connect a transactional mail
sender and Better Auth's verification/reset hooks before offering those flows.
Better Auth's rate limiting is enabled and currently uses per-process memory;
a multi-instance deployment needs shared rate-limit storage. Account pages and
saved progress require a working database. Authentication forms require JavaScript.
## Game tracker
`/projects/game-tracker` is the hub for completionist progress. Catchemall lives at
`/projects/game-tracker/pokemon` (the old `/projects/catch-em-all` URL redirects),
with location pages under `/projects/game-tracker/pokemon/locations`. Call of Duty
games live at `/projects/game-tracker/call-of-duty/<slug>`. All catalog data is
owned by the site: it is checked in under `data/` and seeded into Postgres, and
nothing calls PokéAPI or Steam at runtime.
### Pokémon catalog
`npm run catalog:build` rebuilds two files from the pinned PokéAPI CSV snapshot
(revision recorded inside them):
- `data/pokemon-catalog.json`: **1,025 species, 53 game versions/expansions and
27,267 species/game entries**, plus each species' evolution chain and the
evolution method that applies **in each game** (Magnezone needs Mt. Coronet in
Platinum but a Thunder Stone in Sword).
- `data/pokemon-locations.json`: 1,104 locations and about 67,000 encounters
(area, method, levels, rarity, form and conditions such as time of day, season
or swarm). Max Raid dens are folded per location.
Each entry has one availability:
- `catchable`: a documented encounter (wild, gift, static, in-game trade).
- `evolve`: a pre-evolution is obtainable in that game and the method exists
there, e.g. Blastoise in Red is “Evolve Wartortle: Level 36+”.
- `breed`: an obtainable relative can breed it (not in Gen 1, Let’s Go,
Colosseum/XD or the Legends games); incense babies name their incense.
- `event_only`: a mythical in the game with no other route.
- `unknown`: in the game's Pokédex data, but no confirmed route.
`catchable`, `evolve` and `breed` can be ticked and count toward progress.
PokéAPI has **no encounter data** for Brilliant Diamond/Shining Pearl, Legends:
Arceus, Scarlet/Violet and their DLC, or Legends: Z-A, so those games stay
`unknown` until another source is added. Missing pairs are not proof of absence.
Administrators can correct any entry from the Pokémon detail dialog and add notes
and a picture (JPEG/PNG/WebP, 5 MB, stored in R2 under `locations/`) on any
location page. Rows carry a `source`: seeding refreshes `pokeapi` and `derived`
rows and never touches `manual` ones. Location notes and pictures always survive
reseeding, and user progress is keyed by entry, so it survives too.
### Call of Duty catalog
`data/call-of-duty.json` covers Black Ops, Black Ops II, Ghosts, Advanced
Warfare, Black Ops III, Infinite Warfare and WWII. Weapon classes, post-launch
(DLC/update) weapons and camo rules are **hand-maintained** from the Call of Duty
Wiki (CC BY-SA). `npm run catalog:cod` refreshes only the achievement lists from
the public Steam Community pages. Users tick per-weapon milestones (bought or
unlocked, max level, Gold, Gold Cheetah); class and overall camos (Diamond, Dark
Matter, Solar, Black Sky, Chrome, Chrome Tiger) are derived in
`src/lib/game-tracker/mastery.ts`, and post-launch weapons never gate them.
Weapon progress is keyed by a slug of the weapon name, so renaming a weapon in
the JSON starts its progress over; the seed keeps removed items that already have
progress and warns about them.
### Database
For an existing database using the current domain schemas:
```sh
npm run db:migrate:auth
npm run db:migrate:foundation
npm run db:migrate:money-tracker
npm run db:migrate:game-tracker
npm run db:seed
npm run db:seed:cod
```
For a fresh database, run `npm run db:push` followed by both seeds.
The migrations are additive and repeatable; they do not reset tables. The
game-tracker migration adds the `evolve`/`breed` availability values, the entry
`source` column (marking pre-existing hand-edited notes as `manual`), and the
location, encounter, evolution and `tracker_*` tables. Unknown legacy OAuth
providers cause the auth migration to fail for explicit review instead of
guessing an issuer. Back up a production database before any schema migration.
The scripts use `DB_URL` from `.env` via Bun.
## Money tracker
`/projects/money-tracker` is restricted to administrator accounts. Its tables
are created by `npm run db:migrate:money-tracker`; apply that migration to each
environment before deploying the new route. The deployment preflight checks for
all nine tracker tables and the projection columns.
Transactions can be entered manually or imported from a CSV or tab-separated
export with `date,account,description,amount` headers. Dates accept
`YYYY-MM-DD`, `DD/MM/YYYY`, or `Fri 1 Aug 2025`; amounts accept signed euros
such as `-€1,234.50`. Quoted fields and comma, semicolon, or tab delimiters
are supported, and empty rows are skipped. Imported entries are stored in date
order. `Start balance` rows are imported as ordinary transactions, so leave
the account opening balance at zero for those accounts. Import creates
missing accounts and adds every row in one database transaction. It does not
deduplicate repeat uploads, so import each file once. Accounts have an editable
opening balance for money held before the first recorded transaction.
The current balance and history stop at today; future dated transactions feed the
wealth projection. The projection invests spendable money above the configured
reserve in a modeled balance, even without an existing investment account.
An existing investment account can optionally be selected as the starting
balance for growth. The projection treats recurring entries as monthly
equivalents from next month and can disable them to avoid double counting entries
already recorded as future transactions. Its reserve, hypothetical return,
contribution rule, and end date are editable. Wish list priority runs from tier 5
(highest) to tier 1 (lowest), with cumulative estimated costs on the overview.
The utility comparison uses meter readings within the configured billing period,
the current per-unit rate, and prorated monthly advances. It excludes fixed
charges, taxes, and past rate changes. Set the billing start to a recorded opening
meter reading for a meaningful annual comparison.
Seeding runs in one transaction and is repeatable. It preserves row IDs, existing
catches, selected games, and hand-maintained availability notes. Pokémon are keyed
by national number, games by PokéAPI version ID, and entries by Pokémon/game pair.
Existing games with exactly matching names are adopted to preserve their IDs.
The old broken reset and auth-schema overwrite commands have been removed.
Availability is deliberately conservative:
- `catchable`: PokéAPI supplies an encounter record. Conditions or a particular
form may be required. These entries contribute to the catch checklist.
- `unknown` (`?`): a Pokédex or game-index record exists, but the acquisition method
has not been confirmed. Membership alone never becomes “catchable”.
- A dash: no source record; this does **not** prove the Pokémon is unavailable.
- Manually maintained trade/transfer/event entries remain supported.
Encounter coverage varies by game and is especially incomplete for newer titles.
The import does not invent evolution, breeding, gift, trade, transfer, or event
rules from missing data. The page explains these limits. Catalog refreshes are
explicit: update `REVISION` in `scripts/build-pokemon-catalog.py`, run
`npm run catalog:build`, review the snapshot, and rerun the seed. Source:
[PokéAPI data](https://github.com/PokeAPI/pokeapi/tree/master/data/v2/csv).
Catches and selected games are stored server-side under the authenticated user's
ID. Repeated catch requests are idempotent, writes validate catalog IDs, and an
explicit empty game selection survives reloads. The UI rolls back failed saves
and prevents overlapping changes to the same catch.
`npm run test:unit -- --run` covers authorization, safe return paths, osu! identity
mapping, and catalog integrity alongside existing tests. `npm run test:auth` runs
browser/database checks against a migrated, seeded **development** database; it
creates temporary test accounts/content and removes them afterward. It checks
registration, login/logout, cookie replay, role escalation attempts, cross-user
progress isolation, admin routes, game selection, and OAuth authorization startup.
Completing Google/osu! consent still requires real provider credentials and the
registered callback URL. The integration follows the installed Better Auth APIs;
see [SvelteKit integration](https://better-auth.com/docs/integrations/svelte-kit)
and [Generic OAuth](https://better-auth.com/docs/plugins/generic-oauth).
## Staging and production rollout
Use `staging` as the working branch and set it as the repository's default branch
in Gitea after the first push. A direct push to `staging` runs unit tests, builds
the site and a SHA-tagged Docker image, and deploys that image to
`https://staging.stanrunge.dev`. After checking staging, use Gitea Actions →
**Deploy production** → **Run workflow**. That action retests the current staging
commit, verifies that staging runs its exact image, and starts the **same image**
at `https://stanrunge.dev`. It does not rebuild the image or need a PR. Pushes to
`main` no longer deploy production; once `staging` is the default branch, its
current manual workflow is also the one shown in Gitea's Actions UI.
The staging Docker build runs `bun install --frozen-lockfile`, unit tests, and
the production build, so a failed test never produces a deployable image. The
production button verifies the exact tested staging image before switching
production. Browser/database tests need a
dedicated test database and are not part of this gate yet. `npm run check` and
full lint are also not gates while the repository has existing unrelated
diagnostics; add them after those are fixed. A failed test, build, or preflight
prevents the switch; a failed post-deploy health check attempts to restore the
previous image tag. Deployment never migrates or seeds either database. The
Gitea runner needs Docker CLI/socket access and the existing `DEPLOY_KEY` secret.
Before either push, configure the existing private environment file on that
host. Staging uses `/etc/personal-website-staging/staging.env`; production uses
`/etc/personal-website/prod.env`. Each file needs `DB_URL`, a unique random
`BETTER_AUTH_SECRET` of at least 32 characters, and both `ORIGIN` and
`BETTER_AUTH_URL` set exactly to its public HTTPS origin. Add the Google and
osu! client ID/secret pairs for whichever sign-in buttons should be enabled.
List and Animaldex picture uploads reuse the existing R2 bucket; set the five
`R2_*` values from `.env.example`. The app server uploads images to R2, so the
bucket does not need browser CORS rules for these forms. Each image is limited
to 5 MB; the container sets `BODY_SIZE_LIMIT=6M` for multipart requests.
Their provider console callback URLs must use that environment's origin:
```text
https://staging.stanrunge.dev/api/auth/callback/google
https://staging.stanrunge.dev/api/auth/callback/osu
https://stanrunge.dev/api/auth/callback/google
https://stanrunge.dev/api/auth/callback/osu
```
For this first rollout, review the SQL and back up each database. The first push
to `staging` may pass **Transfer tested image** but fail **Deploy staging** at
the database preflight. That is expected if the database has not been migrated:
the image is then available on the deployment host for you to run the migrations
yourself. If `animals`, `sightings`, and `photos` still have UUID IDs, first apply
`scripts/animaldex-uuid-to-serial.sql` with `psql` once. If the database has the
legacy singular tables `game`, `pokemon_game_entry`, `user_entry`, and `user_game`,
then apply `scripts/legacy-schema-migration.sql` with `psql` once. That migration
renames those tables without discarding their rows and creates the empty base
`lists` and `list_items` tables. Run both reviewed SQL files before the container
commands below. On the deployment host, use the SHA from the staging Actions run:
```sh
release_image=git.stanrunge.dev/stan/personal-website:<staging-sha>
sudo docker run --rm --network backend --env-file /etc/personal-website-staging/staging.env "$release_image" bun scripts/migrate-auth-catalog.ts
sudo docker run --rm --network backend --env-file /etc/personal-website-staging/staging.env "$release_image" bun scripts/migrate-foundation.ts
sudo docker run --rm --network backend --env-file /etc/personal-website-staging/staging.env "$release_image" bun scripts/migrate-money-tracker.ts
sudo docker run --rm --network backend --env-file /etc/personal-website-staging/staging.env "$release_image" bun scripts/migrate-game-tracker.ts
sudo docker run --rm --network backend --env-file /etc/personal-website-staging/staging.env "$release_image" bun scripts/migrate-tracker-journal.ts
sudo docker run --rm --network backend --env-file /etc/personal-website-staging/staging.env "$release_image" bun scripts/seed-catalog.ts
sudo docker run --rm --network backend --env-file /etc/personal-website-staging/staging.env "$release_image" bun scripts/seed-cod-catalog.ts
sudo docker run --rm --network backend --env-file /etc/personal-website-staging/staging.env "$release_image" bun scripts/seed-pokemon-research.ts
sudo docker run --rm --network backend --env-file /etc/personal-website-staging/staging.env "$release_image" bun scripts/seed-cod-challenges.ts
sudo docker run --rm --network backend --env-file /etc/personal-website-staging/staging.env "$release_image" bun scripts/seed-cod-art.ts
```
These catalog seeds use the reviewed files in `data/` and upsert catalog rows;
they preserve user accounts, catches, ranks, and saved challenge progress. Do not
copy or restore the entire development database over staging or production.
Repeat the same migration and seed commands for staging first, replacing the
environment file with `/etc/personal-website/prod.env` only after staging has
passed its preflight and been reviewed. Back up production before applying its
migrations. Rerun the same staging Actions run after the staging migration and
seed complete. Click the production button only after production migration and
seed complete. Future releases without database changes need only a staging push
and the production button.
The foundation migration gives lists slugs, progress, pictures, and blog tags;
gives site-owned join/settings rows numeric IDs; and adds `created_at`/
`updated_at` with a database update trigger. Blog posts already had numeric IDs
and timestamps. Better Auth's account/session IDs stay as text because they are
provider-managed identities. The deploy script verifies the migrations and
catalog before switching the image, then checks `/login` and Catchemall. A
failed app health check attempts to restore the previous image tag. Database
changes are not rolled back with the app image.
The release image contains `scripts/`, `data/`, and the schema files needed by
the migration tools. After registration in each environment, promote your own
account using the host's matching environment file, for example:
```sh
sudo docker run --rm --network backend \
--env-file /etc/personal-website-staging/staging.env \
git.stanrunge.dev/stan/personal-website:<deployed-sha> \
bun scripts/set-admin.ts <account-id> admin
```
Use `/etc/personal-website/prod.env` and its deployed image SHA for production.
The account ID is shown on `/account`. No public signup grants admin privileges.
# CS prices
`/projects/cs-prices` reads only from Postgres. Each Steam market hash name is an
individual listing. The separate `cs_item_dimensions` table makes it possible to
compare or group by weapon type, weapon name, weapon plus skin, skin, wear, StatTrak, Souvenir, and
additional imported dimensions such as collection or rarity. Group chart points
average the available per-listing prices equally within each time bucket. Raw
observations retain their source, currency, timestamp, kind, price in cents, and
volume. Chart responses use up to roughly 240 time buckets per series and allow
up to 1,000 selected series; actual response time still depends on database size
and hardware.
With `DB_URL` pointed at a running Postgres database:
```sh
npm run db:migrate:cs-prices
npm run cs-prices:ingest -- catalog
STEAM_SESSIONID=... STEAM_LOGIN_SECURE=... npm run cs-prices:ingest -- history
```
The catalog pass pages through Counter-Strike market listings and saves a current
lowest-listing snapshot. The history pass visits each catalog item and stores
Steam's available historical sale-price and volume points. It needs a Steam web
session, may take many hours for the full catalog, and can be resumed with
`--start=<item offset>` and bounded with `--limit=<items>`. Requests pause for two
seconds by default (`--delay-ms=...`, minimum one second), retry temporary
failures, and upsert observations safely. Run catalog regularly for new listings
and snapshots; run history regularly for new sale points. Steam's market web
endpoints are undocumented and may change or limit access. The historical
granularity and completeness are bounded by what Steam returns; running this
collector cannot reconstruct older high-frequency prices that Steam no longer
exposes.
For another provider or a prepared archive, import JSON Lines instead:
```sh
npm run cs-prices:ingest -- import ./prices.jsonl
```
Each line needs `marketHashName`, `source`, `currency` (three-letter ISO code),
`observedAt` (ISO timestamp), `priceCents`, and `kind`. Optional `name`, `volume`,
and `dimensions` add detail. For example:
```json
{
"marketHashName": "AK-47 | Redline (Field-Tested)",
"source": "archive",
"currency": "USD",
"observedAt": "2026-09-24T12:00:00Z",
"priceCents": 1850,
"volume": 42,
"kind": "sale-median",
"dimensions": { "collection": "Phoenix", "rarity": "classified" }
}
```
Imported sources and price kinds appear in the page's source and kind controls.

1108
bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"$schema": "https://shadcn-svelte.com/schema.json",
"tailwind": {
"css": "src/app.css",
"baseColor": "slate"
"baseColor": "neutral"
},
"aliases": {
"components": "$lib/components",
@@ -12,5 +12,9 @@
"lib": "$lib"
},
"typescript": true,
"registry": "https://tw3.shadcn-svelte.com/registry/default"
"registry": "https://shadcn-svelte.com/registry",
"style": "vega",
"iconLibrary": "lucide",
"menuColor": "default",
"menuAccent": "subtle"
}

View File

@@ -6,7 +6,7 @@ services:
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
ports:
- "5432:5432"
- '5432:5432'
volumes:
- db-data:/var/lib/postgresql
@@ -17,7 +17,7 @@ services:
working_dir: /app
command: sh -c "bun install & bun run dev --host"
ports:
- "3000:5173"
- '3000:5173'
env_file: .env
develop:
watch:

5166
data/call-of-duty.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,620 @@
{
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/f84ba26b1844819ea9e52aef0d0570559b57a75a.jpg": "/game-art/call-of-duty/achievements/238b785e1fca8f84bbee.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/e3b18cd1c090368b0386de8cc46f5a169c4038e9.jpg": "/game-art/call-of-duty/achievements/668be15f0347459b94f2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/0a2812d34a7fa98e20285368bc1a763bd792f889.jpg": "/game-art/call-of-duty/achievements/5cd7a661f6d3e7e6e1bb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/584c74c05982e10c4441dd65324f277b7539eae2.jpg": "/game-art/call-of-duty/achievements/3f54bba6d8f7de2e2284.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/b1b1349e22f48972109b743d520183ba70caad3c.jpg": "/game-art/call-of-duty/achievements/a9236535546a8dbc79b5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/aa159223f265ab937d7d5b6690ddcf49020a2cbc.jpg": "/game-art/call-of-duty/achievements/7535dcc4298af27198c1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/f2edbcfaed969439c62f7a4e6f1eb068b413e158.jpg": "/game-art/call-of-duty/achievements/8852bd4a99d6ccc75845.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/e9d07584f66650764670664404079276b4a3406b.jpg": "/game-art/call-of-duty/achievements/d49877c3e2ce9ca95bd9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/b5b0accacfbcfa0396cb41f268b5ad8d9a65c613.jpg": "/game-art/call-of-duty/achievements/1826f646d000287dc1f6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/e8d0caf4122d896eb05024593bb3b66dadf4e88d.jpg": "/game-art/call-of-duty/achievements/652149e0966e1eae6125.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/066c88a94e5a4b27555d5c643a08859ddf915d0f.jpg": "/game-art/call-of-duty/achievements/090152a9c71b3a45e225.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/8557b021165d25955cb8c9fef04bde10d9c73a99.jpg": "/game-art/call-of-duty/achievements/19009908c84ec2cafc6d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/e63f09bb9b40effe11802afce07b6e9a90571724.jpg": "/game-art/call-of-duty/achievements/e20d161a6df6dc45f756.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/77911ac60f573a18fe2b136fe366c6eed30fe108.jpg": "/game-art/call-of-duty/achievements/3805fbfc6e08a1eeab92.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/8fb4508be92820c78ff69f0e69546de0d9b76be7.jpg": "/game-art/call-of-duty/achievements/a647123583f71660a8df.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/052772410645d83c3fce7b03c9467b66ccdf0213.jpg": "/game-art/call-of-duty/achievements/2bf1bc39e772950369fa.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/f6e25d8f4ebbe3166dfc3b58c34867f268f2d033.jpg": "/game-art/call-of-duty/achievements/566ea9ce791bf9abe5a3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/54d9340f91dbdb3e22ff5fc256ce30d0fa6a963c.jpg": "/game-art/call-of-duty/achievements/fdfdce2ff535cf32cd0a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/82c5e869cf17efa57d7c2a6d19903cb2aee7ff0d.jpg": "/game-art/call-of-duty/achievements/c3e15cc571084bd8e032.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/19dcec07495607501ec6b251e2b822be1b60f0fd.jpg": "/game-art/call-of-duty/achievements/3a814d01d4b72ed78b86.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/2af436c800d1b2343c79bb8cfec3517a896b2074.jpg": "/game-art/call-of-duty/achievements/ca1b094aada4c4d3e3bf.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/6e962666da615b8cb3821e60f3f104b270338e04.jpg": "/game-art/call-of-duty/achievements/e09047b22965850447f9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/d013df6d049ec65340b44f297ae5e181d10e4b49.jpg": "/game-art/call-of-duty/achievements/04af8a32026bacfca67c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/5e4644a4b38f3fc47114804ec397ea4700629285.jpg": "/game-art/call-of-duty/achievements/b9c0bf5f5afcc39213c9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/75798a0d173926d2ee97d71bca3ec380927d104d.jpg": "/game-art/call-of-duty/achievements/02bc128c11c94faf1c9b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/b5feb12f310b6feb51db21f92cf62e53ba3f6521.jpg": "/game-art/call-of-duty/achievements/edb0787878d0b6e8e696.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/4a90e8dda5c440eb25d33a6f495edbed0140d7af.jpg": "/game-art/call-of-duty/achievements/a57766605831b6778bf0.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/35e478ea8071694ca9be384f361aea9c2928c420.jpg": "/game-art/call-of-duty/achievements/350f4b45235fdc221371.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/7047eecc9292d4c18462340cb7cbeb417f35b0f7.jpg": "/game-art/call-of-duty/achievements/68dc121e074880e68595.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/c51d6a0a1a54e2b152d000fea518a0e6359dac4b.jpg": "/game-art/call-of-duty/achievements/5297acb4f4da5e454251.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/b6bedf6b9fb9a8dc846b8a0d178bde44ab140f35.jpg": "/game-art/call-of-duty/achievements/40ebfc363b9df8683469.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/7d2b7d0909fed0934b8958181b8e6e3834c31698.jpg": "/game-art/call-of-duty/achievements/5772b839a7ac1b2099a3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/c531aac734c62f81aea8b4a3cd0d393b3da7e4a4.jpg": "/game-art/call-of-duty/achievements/3db466d8d7ea4e0ce404.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/a00d833009c4c2f5390f3f6750a341c3ded2e3f4.jpg": "/game-art/call-of-duty/achievements/8a1dc6e2c75c02ca2d9b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/904509f71a94d27bd02c8c59f735ce9625cc1c49.jpg": "/game-art/call-of-duty/achievements/078f7c246f6c0bdd0193.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/c6bbe0d3d22db00af800f931976e8003be50ee06.jpg": "/game-art/call-of-duty/achievements/80472754bf73ca7d58e6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/21177bc1209e70be184fd480004bb2082c27e428.jpg": "/game-art/call-of-duty/achievements/bec752b67dc9ab1ca236.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/2f1b3a3deb73182554d30c5e6240bf2abb7e9137.jpg": "/game-art/call-of-duty/achievements/4ad516df8a73d5904575.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/414eea359802851cd50bf577d6be3d814988a88a.jpg": "/game-art/call-of-duty/achievements/9989fb508e8136957f1a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/1e1797ea80faffaebab8aac35df9b5f737a3a740.jpg": "/game-art/call-of-duty/achievements/013fa5da7e485ac3242c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/f8d225e74c13b54a4be33d1db176424518cae516.jpg": "/game-art/call-of-duty/achievements/1e08a1136a06591bcd6f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/294382b96591ade0f7a3dd35f69aa64cf8e0d14d.jpg": "/game-art/call-of-duty/achievements/f56176dafd96a78a3c03.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/6cfdcdff1c60ba4087436650c376379668611765.jpg": "/game-art/call-of-duty/achievements/907d50d6eb676aad489f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/db3ebdb04f6d8dc9208cb41d65bf0dde71e48ee6.jpg": "/game-art/call-of-duty/achievements/1af5f1612db6eb49ec8e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/3b4ddc46340c4ee914c43232708e4f0b99d1f433.jpg": "/game-art/call-of-duty/achievements/6ffbbc679ea915877be5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/93c7772577b5cbf3385f4b505017684f0d27a77e.jpg": "/game-art/call-of-duty/achievements/cb00bccd7155c5c79939.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/7d430d7cb0e27f1357148e8c57c1ff53969e9789.jpg": "/game-art/call-of-duty/achievements/5d0dc7bf0c89d2cb7a4e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/0fd21bfea7ae6cea7d19beacda813546b8aa2aac.jpg": "/game-art/call-of-duty/achievements/dbaad790ad126bced253.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/9dad5b5851b3b41c654b5b64bf17289c2bd25e5c.jpg": "/game-art/call-of-duty/achievements/74b743415e4823b9afbd.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/fab6950999928104e43aafbed1b875bd8fa535ae.jpg": "/game-art/call-of-duty/achievements/afdea16426b4c756e4bd.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/b5d3e15aeb110c232b25d35badd27d3723cfc79f.jpg": "/game-art/call-of-duty/achievements/34bd87a05e2d5c87308d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/e3008c9f8bc996aab7ccd1c03252eb489eb2a961.jpg": "/game-art/call-of-duty/achievements/8e7cbdfd2248aa26d16e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/3e370768bce2246e4a34e2b95a63894a8f3ad300.jpg": "/game-art/call-of-duty/achievements/af00a2e00f4d494e1bc7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/221f9b2e1a6562e0f2dc99518bc133b6d9e28845.jpg": "/game-art/call-of-duty/achievements/c45cafdc867922a8fe07.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/0703c58b9cdbc32a607b198dd6df75aebbda4e0f.jpg": "/game-art/call-of-duty/achievements/8878c7ee8bd4313bb88c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/dfffdca7f1eec0f51c4d1ba6c7794a98d234ef36.jpg": "/game-art/call-of-duty/achievements/ef3dc83102c12eda7a11.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/3924b4c5269c1db5e3fe069ef1b14bbdcdd03b7b.jpg": "/game-art/call-of-duty/achievements/e8811318dbb354abc472.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/d959ef75d60450e2eeb53099cd3ac21ed8543d89.jpg": "/game-art/call-of-duty/achievements/a8f55324896c83dac16c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/aaf807e4357992e7926fa8e37d994dea52716f5d.jpg": "/game-art/call-of-duty/achievements/ef83905f63cb07adf3b4.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/d8f4344e51350ddbcf8594090fb5fe716a1cf87e.jpg": "/game-art/call-of-duty/achievements/18074a3c513c6266b0e2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/a2f171ee14933001a4126270eedbfbbe51f41c0e.jpg": "/game-art/call-of-duty/achievements/7767a2b17c42543b9a15.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/945ede3b335f123ae7f556d8125a0f957c04ad30.jpg": "/game-art/call-of-duty/achievements/10f6dc026886bf6d8fed.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/3aa709991192b7df667d76aeade231ac5d5a491e.jpg": "/game-art/call-of-duty/achievements/586ae3efa7b170f43654.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/ae1ea1ba275ee59ba9b86bef5b2d22488f9b5c8a.jpg": "/game-art/call-of-duty/achievements/8623c2c97d51409a3fcf.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/33bf5941093863c085a3cba80d03004258b2bdc8.jpg": "/game-art/call-of-duty/achievements/4bb2e298d674f00435e7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/4b1a75db52ecb1484674cb4c8fd10ae4028d9b0e.jpg": "/game-art/call-of-duty/achievements/4558c32b182fb8e840ef.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/0ad5d6255b43a6d24b4ecc7ac5d4b87289e24ab7.jpg": "/game-art/call-of-duty/achievements/8bc453728da64b88935a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42700/12112302162a4ca970c616a9b4d6c777d0e7e623.jpg": "/game-art/call-of-duty/achievements/12bc6d970ab690b96ee4.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42710/02c69139d83d887e8bbad07330f7c076e4181a28.jpg": "/game-art/call-of-duty/achievements/bc3aa66672504284d00e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42710/f84f81208278703664dbd530e5e3c6c5c71daf13.jpg": "/game-art/call-of-duty/achievements/613c8f97eb5108156e44.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/42710/0dd80a8bb848350d4b8a0a18dafb270652068384.jpg": "/game-art/call-of-duty/achievements/e2769289d1b1467be1b9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/0133fda3a5fa19b1165259600644d25d981bf479.jpg": "/game-art/call-of-duty/achievements/334bacf4dad73d50f310.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/ab8d4ccecae718e5314300a6716d0c3623ce0dc5.jpg": "/game-art/call-of-duty/achievements/2d642533372b4304001b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/0980082fe85ff0d25840700b6ed2e52504956d1c.jpg": "/game-art/call-of-duty/achievements/4470c1371d884636304f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/3bef8dba7289f3eb05a69fcb97e252b13a44a492.jpg": "/game-art/call-of-duty/achievements/dd9e2b94ec07c8fabd4d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/910637c7094876116abbb2e786652120fb4f65f3.jpg": "/game-art/call-of-duty/achievements/a142154c721fce62166a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/df51dbfc2880d1d6053175b6160efb42a71d0a44.jpg": "/game-art/call-of-duty/achievements/5c7e46bb2e598bd62c8f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/f43d08d5e78681254c6d1d83edea8a0a3ed6d50b.jpg": "/game-art/call-of-duty/achievements/00e8802dfb3a6e23b510.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/eb2f8a890a5a1c09082c7f31d6907ad9852b8d6b.jpg": "/game-art/call-of-duty/achievements/a6a776d25ae0dd14f3f7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/1814c8da565a05aeb72c3a260dcbb53722b7349c.jpg": "/game-art/call-of-duty/achievements/0292942faf8b9048ae7b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/69f41d5d43ccfe5b5491392eae1b1ec9e2eec557.jpg": "/game-art/call-of-duty/achievements/18bdee8e74450d4081c5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/7f6c52357f7ea9a087edf50427d002b2544c2321.jpg": "/game-art/call-of-duty/achievements/7344321d980fae59ea9b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/bdb4750ed6445a7fb6e9a588ad7c7f25595bbfe3.jpg": "/game-art/call-of-duty/achievements/64144e08856de27591ec.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/6a8c99571f5eda2d2e43696189f23ce5fea933b3.jpg": "/game-art/call-of-duty/achievements/babce0e82a2404d14780.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/774b2da404ddc3a9056c8dff17cf67aa8f8abf6c.jpg": "/game-art/call-of-duty/achievements/6635d754150bed1f4a52.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/a9e6270ce3479af37f5ace927b7eac23f014ad19.jpg": "/game-art/call-of-duty/achievements/7f8eaceb05beacb38982.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/2958048adcec6bf1ff5ada6285f9525e76edaf03.jpg": "/game-art/call-of-duty/achievements/f2c28703a829c38a7ef7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/d17d6491ab01329bc3585da8b36dc354308983d3.jpg": "/game-art/call-of-duty/achievements/26bdf2f4f35b581412fb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/3bdf965dafb49cb647a31cc21baf86572df84cfa.jpg": "/game-art/call-of-duty/achievements/18e91bf129656ecfa143.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/e27810ec54b75392b3539ddddfc6ada5de42cc36.jpg": "/game-art/call-of-duty/achievements/65b1a4886f0cefe57fcd.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/03ee61e8a78cbd8ec3b9dfa592d20d3433ef8759.jpg": "/game-art/call-of-duty/achievements/f1e208e8d69e884977d3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/5d8f8186eb5516caaad9138a3c7cb9b85e0059e0.jpg": "/game-art/call-of-duty/achievements/536203b9c317cef8f921.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/a064792dbe470c7ebf93c07df2c0125b8199f68f.jpg": "/game-art/call-of-duty/achievements/f43afe71b1b2f4c99be6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/1dbf014f724756b8ea85f94879089ce63344b453.jpg": "/game-art/call-of-duty/achievements/0a02f70e810fd5c73ed1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/c5ca4e900ec711fbbaa15322bcacca74057e92ab.jpg": "/game-art/call-of-duty/achievements/c46c3d53d7617ce2df29.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/2e406d05afd666007c927c29c7cb8df66dcf125a.jpg": "/game-art/call-of-duty/achievements/4c1ee54bbdc0de51a397.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/9dfd28b4500bdc6a148469ad3576fca079768bc8.jpg": "/game-art/call-of-duty/achievements/30101749dcc9e8f00f0e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/53173f0fc6c55779ed242f82ea899194c58e1b6b.jpg": "/game-art/call-of-duty/achievements/cf15357082a813202247.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/f6699b8c585d8e0f74d946382dc9a6b944668e1d.jpg": "/game-art/call-of-duty/achievements/4d80ad746889b6e51b11.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/6952628963694dbdb8c8b2149bd5d0b13d25a512.jpg": "/game-art/call-of-duty/achievements/f5e0217080c8e78a385b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/a452e8f7a3986dfe5ee2dcfd5997db72e36b1908.jpg": "/game-art/call-of-duty/achievements/3ced3d4600e56bd1b8f5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/5ced4efe9b5745469076c965e0fa878c1579b388.jpg": "/game-art/call-of-duty/achievements/68b751f778d9075b3438.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/dc92dcfcab652a8d51315a3e9abc8a2b87db664f.jpg": "/game-art/call-of-duty/achievements/687438517f482f07c3cd.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/c2a4e5c077eca0eb3913fccdf1cd502444859717.jpg": "/game-art/call-of-duty/achievements/9ac358819352bb811725.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/c1683a5aaa4459a4c30a64c7114581b5e5d535db.jpg": "/game-art/call-of-duty/achievements/f579808080eac5263a19.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202970/4c18e7babfce7b1e9a87a91ee8f3d906336f0d66.jpg": "/game-art/call-of-duty/achievements/34fdac009785f70f3a10.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202990/81c1462e80a491628924060da29d6b28fd04eab1.jpg": "/game-art/call-of-duty/achievements/d1df330a62288f93daee.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202990/d17a0f39120c0fe05b991f50c8158ccc3a0519db.jpg": "/game-art/call-of-duty/achievements/35b09deb66bebeaffe24.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202990/cfffa291c82382e008c2b1102864d8efb2bb54b5.jpg": "/game-art/call-of-duty/achievements/9cf4c992ca6581f3188e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202990/f05286d7cbdebfe28a34155a8c3a87158555f940.jpg": "/game-art/call-of-duty/achievements/a6c2e17cef39da2ae9f2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/202990/0124448f02f1564f79b5b0db570b1f736a6e10c0.jpg": "/game-art/call-of-duty/achievements/2ec7b277532d83f8bc00.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/9cb9fed7bd89695d81b2df2a78e54e7496ff7811.jpg": "/game-art/call-of-duty/achievements/414383f0814d172be947.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/5ef50a6c4cb152dd5a599d9edbfcb8b01800b208.jpg": "/game-art/call-of-duty/achievements/9001d16125fc11453433.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/3e1149b43b4a4f9173e3a5bc8b8eb6020745430c.jpg": "/game-art/call-of-duty/achievements/ec4fa15d8342ffa088be.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/b4149289f16f86509f23d4a64d3305c8949b639a.jpg": "/game-art/call-of-duty/achievements/07109eeca8b226f66582.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/4198c3c5a45727f7fbd1b56a0550b39bd2c7be41.jpg": "/game-art/call-of-duty/achievements/5e6d7390b0040d17f382.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/aa3095124501a0269a16d1ca1d43ce7a576dd097.jpg": "/game-art/call-of-duty/achievements/fe71b44a31aa3bffaba1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/b5e4c73252d3bcf7adc023659eef75227d469a4f.jpg": "/game-art/call-of-duty/achievements/ee23c7268ede05849376.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/8688428ee57a31db06a01f617b27fa594b2e1768.jpg": "/game-art/call-of-duty/achievements/e55f4f23ab46a034b0e5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/ffd7006549012bd3446a63cfb6a5ab02ce9c4b23.jpg": "/game-art/call-of-duty/achievements/d0628ddac4b96e0acbff.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/44b401b61ef76da507f4f68ba70529b79813c434.jpg": "/game-art/call-of-duty/achievements/4bdfcb1087e540bf298b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/58b1b329fc69b4575f51890b299a613a66168b9f.jpg": "/game-art/call-of-duty/achievements/ed3aa2c7c084d6206576.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/ba3c0b4a33c13a614aabcf3689a5de688fc2ce8c.jpg": "/game-art/call-of-duty/achievements/b7a6b37b1a42b24dbcc8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/014b27d6529a2962da3b0885a0f381386d315fb9.jpg": "/game-art/call-of-duty/achievements/92fc55d6e72e1a9562af.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/57137bee776f973ac60b5b78f12368a40eb668a1.jpg": "/game-art/call-of-duty/achievements/39917528f97b83a77af7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/402d492afcb268e1c782a2d9b0efaf6a888ac043.jpg": "/game-art/call-of-duty/achievements/d9369ed45d2404a7042e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/7654f81ef6a8904b079d0efc8979bfb70fe84031.jpg": "/game-art/call-of-duty/achievements/21a06303c02c87795486.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/c115776df2f978c2fefeaac726905b4562b1f2bc.jpg": "/game-art/call-of-duty/achievements/aafd004c36a22f406f02.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/20e2eaed04069ab2d120e3ad59ff5962811f873b.jpg": "/game-art/call-of-duty/achievements/5718a95c950469faebf5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/9024a82b5900604aae70224d142aba561b03f32d.jpg": "/game-art/call-of-duty/achievements/94a082f60893d4cac5a3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/b1255b28f31764890bd8e091fbbc8e6cc733b8ff.jpg": "/game-art/call-of-duty/achievements/ecf176d404db072b1191.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/b4fa1dc3da619e91ee1a9451bee10f76fb1f2fff.jpg": "/game-art/call-of-duty/achievements/d1b71242d74e7049b1d9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/88516669f90dbf453e51ef71a80283c816ce8e39.jpg": "/game-art/call-of-duty/achievements/95151875ccbdf29fc0b3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/8870a661c34b3f30442b5b739fe0efe4cf6e7c88.jpg": "/game-art/call-of-duty/achievements/acc3d4a27a712aa33d5e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/ff6956ca387b85abc2f8fd9d556d3171f365bec2.jpg": "/game-art/call-of-duty/achievements/a296ebf4142d08eb8171.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/affa528c8efa5ace55e526fe1056f1c0c880b3a3.jpg": "/game-art/call-of-duty/achievements/34faa2286f859e4ef883.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/e8624092dbc511ce22738709002767687b1318ef.jpg": "/game-art/call-of-duty/achievements/5ab29e7e5d2638bfd973.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/2d084cd19f869118a54e873560b7cc72f5958ecf.jpg": "/game-art/call-of-duty/achievements/68e944579f95a6efbce8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/008cfb3cb67e60096605cca5a345940459dfaef0.jpg": "/game-art/call-of-duty/achievements/d4ffdfc1a9c180cbbec9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/23c6604893406d0c1de71cf902b83036d5195cba.jpg": "/game-art/call-of-duty/achievements/49c5e3803cd325081584.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/78c51356d1ec5d11a5859b571406cc13a0437f81.jpg": "/game-art/call-of-duty/achievements/da67236546bab6d710ad.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/bdb32d53b37dfb7ee8697f33d6d8b562f6d9bb19.jpg": "/game-art/call-of-duty/achievements/a04d785f7384b8ec08de.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/6e23e3219dac8f448d9771f0c85252c4e51b53ea.jpg": "/game-art/call-of-duty/achievements/845f6e30ece4d5830bc7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/c6bd87838a42ad9105417c8206ef03befc063e1c.jpg": "/game-art/call-of-duty/achievements/c07284fe0ce0ae0cf020.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/add7001a22ffe44d0eb1a231ba8b3678bf607cb0.jpg": "/game-art/call-of-duty/achievements/fe8363fccefe3e208c56.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/60a410c18c0961922c0b6184b6cf560f9a104e9c.jpg": "/game-art/call-of-duty/achievements/04536f0a02897beda365.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/8d9968d9592f55f89feae4de7eec43727232a39e.jpg": "/game-art/call-of-duty/achievements/a8de98585c8315e0d03c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/080951c14ab06932c4f5c1bd8d3b3e6c1bf39714.jpg": "/game-art/call-of-duty/achievements/be8130070b07f1d7de8d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/87d54d65d1e289e2067c9c3242826efc9386c284.jpg": "/game-art/call-of-duty/achievements/597da19410c37ba79471.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/bd13209f20a3a91977efed0be33feb57f475b0e6.jpg": "/game-art/call-of-duty/achievements/f52e922e57b3e985f75e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/c512a6fdd4046539d974a46fa800c78bffb386be.jpg": "/game-art/call-of-duty/achievements/08063cb881c78706d4da.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/13dfac69945f5592f310d1c8691ce77efe65cc43.jpg": "/game-art/call-of-duty/achievements/09d0c54a2098a7c62b55.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/d52adc9ffe0a7e4afdf34377bfe630f5974ae46e.jpg": "/game-art/call-of-duty/achievements/1f03769c740daab1c248.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/74d98845ae2afd34468df4636dcfc8e7ae3b01ff.jpg": "/game-art/call-of-duty/achievements/0a97ffde899d5d8a3892.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/b83aab19a451e06dcf837b7c714250d8bdc0f2e1.jpg": "/game-art/call-of-duty/achievements/93f9eff015cb69ddb6d8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/4fde690288c1729e309bb6aa8d699d3ee183efee.jpg": "/game-art/call-of-duty/achievements/6c16d165aeeee4f7f8d5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/608f862c91acbd5070827e16a74acd7e8301dc1b.jpg": "/game-art/call-of-duty/achievements/46401b6250fb1b2d8743.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/0c6091bc69bc52636473f15b8846c3121e4f9692.jpg": "/game-art/call-of-duty/achievements/1c25fab35bd9506e3ec2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/ebfb4b1a4669815e2abde15a1f5bb133b05a9ab3.jpg": "/game-art/call-of-duty/achievements/b9e3bae0f6ed760e6ec2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/25c2af163b6af528fae74383bfd03d9a2a8517ae.jpg": "/game-art/call-of-duty/achievements/fc49cf28f27efa9eb0d7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/212910/8df32a41f76f24ac6e8e7b324ef4e9fa9f827fee.jpg": "/game-art/call-of-duty/achievements/31c5acd1d552c3ada03e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/938412147809b3b8d782a0c6b1354980b7a76466.jpg": "/game-art/call-of-duty/achievements/33c4b3d1c6340964894f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/02389e50db94d8103d71e3e26532659526caf88c.jpg": "/game-art/call-of-duty/achievements/f38810da50f22d46bcb8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/a398ddce474468a1e86dd574eb2ea0d4376c3054.jpg": "/game-art/call-of-duty/achievements/de27cce95c4d870ba405.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/bc18770b2f678ce079eb645b346bc382b1413701.jpg": "/game-art/call-of-duty/achievements/b518642efce084c12125.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/eee8e470d8814bcd7656b725fd69a2befa9b85aa.jpg": "/game-art/call-of-duty/achievements/76bb5e0379a74272a2b8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/bd9bc54978cc4e496bd56b01b9310558b445ee66.jpg": "/game-art/call-of-duty/achievements/aa77b61181dd612fdc28.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/92780cf10aadd423c0220c712bda43d771c28895.jpg": "/game-art/call-of-duty/achievements/211aa8aae0f177b5e529.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/a50d913586131c5361a061484ab1af89a55bf500.jpg": "/game-art/call-of-duty/achievements/55259b5c8a012ee36742.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/43116761fcecd5ee2e779fcf4a44671d059dea36.jpg": "/game-art/call-of-duty/achievements/66cab33804509ca93ec5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/53aa6769167cb791c8937d914eea6187b457206c.jpg": "/game-art/call-of-duty/achievements/774f9f97189ec253aae2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/bfa0592d8e8afd7223ce09077e90eb96f18ef54b.jpg": "/game-art/call-of-duty/achievements/fa8d7a05d505c8a169cb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/a24743f65dd411c593ce4aeea2c0ddca531b2f97.jpg": "/game-art/call-of-duty/achievements/c27becc53b17266aa07a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/c33f19060684bf97b90c7a2b5e4acd45282a4f41.jpg": "/game-art/call-of-duty/achievements/f8f9105abc5a19505251.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/9fae3eaaeb9592ab9abb923853648db26f060727.jpg": "/game-art/call-of-duty/achievements/d8713fdad3cce9149fbc.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/d2309eb8d02d6cba6d5c9066bc290f81507724d6.jpg": "/game-art/call-of-duty/achievements/f78b311c634d024f7212.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/58a5787ec19de789f22118238c219166b5879c2a.jpg": "/game-art/call-of-duty/achievements/fc0511b8a8f451e31b96.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/a4db0a6179ec7e43ab2e9d528dd800193f3a3f2e.jpg": "/game-art/call-of-duty/achievements/a8d8e785ea66bfd50395.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/7df87b2e05cecca9b64b72379999f3ec684c90e4.jpg": "/game-art/call-of-duty/achievements/5a05b2edc9a6cf26c2b7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/847f129434ca57fea22fad195dd050f7ac84b26d.jpg": "/game-art/call-of-duty/achievements/05dc7fab4c1945a38c89.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/3f2ce46fe0875b0e20bcda66dda170cfaa2bd326.jpg": "/game-art/call-of-duty/achievements/a184150a8351d169bea0.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/b1f82e4fdd456c755e74831dbd9656f8513cb02b.jpg": "/game-art/call-of-duty/achievements/ea8506d6d5ecb4d83d41.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/1df05bcc60677d4079a0477f358ed2969bfbc9c5.jpg": "/game-art/call-of-duty/achievements/62a4e28a78e2fbc6620b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/ba24d697d0b40477fbe3c8f910b998709e571c51.jpg": "/game-art/call-of-duty/achievements/518c2e90a89ad4e18f89.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/0273d8ae279400fdfc70a04193f38a5761aa0c6c.jpg": "/game-art/call-of-duty/achievements/6867da51392b2a7cbbcb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/b22e38d57625347eab217d729128eeb4db0b4b42.jpg": "/game-art/call-of-duty/achievements/042c2e99d89b11b11ce2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/4268c8d61c9b7ba0f3e9d30f5c1b8710eeffbe8a.jpg": "/game-art/call-of-duty/achievements/f1cb256ef89049cee39b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/d631ddab876e49701f239d48c3c726b920e4afb1.jpg": "/game-art/call-of-duty/achievements/05ddeeac3170749eabb7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/35978e3bee44c5f47960bf6402d8017f80e621eb.jpg": "/game-art/call-of-duty/achievements/a6a8081342baa4827e01.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/7649a95fc52417c32bb68ba9b6e2b5afbb8ed356.jpg": "/game-art/call-of-duty/achievements/5b8bdad757dcbb5910df.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/350975aca49822ad5333b344c09fe5731ca09a50.jpg": "/game-art/call-of-duty/achievements/88eaa22dcd5346871a17.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/9a1954acd31b60a475590b67bdd5076b1a9031ed.jpg": "/game-art/call-of-duty/achievements/189eba245560bf593d4c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/1366d36a636b584ef7ada54a563ce7d0bfe9ce46.jpg": "/game-art/call-of-duty/achievements/cd69584aaea92839e157.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/7d4e5fbb281349fdfdac177573815bf29b11c0da.jpg": "/game-art/call-of-duty/achievements/e75c0a817729b4502215.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/a3f98881e75528718bb396a93f5eda22e4bf89ce.jpg": "/game-art/call-of-duty/achievements/d465f49420763f09c12b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/527e6f6f7a6985ccd146095c86a95a07972cc5f3.jpg": "/game-art/call-of-duty/achievements/be3c201d42ccf528c417.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/e2f9814f32a4f19b0662cacf9e60c7f050aaf788.jpg": "/game-art/call-of-duty/achievements/982416111ad59fb0ee5c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/166d66a24f482cc50fe730defb60b5b594cd942e.jpg": "/game-art/call-of-duty/achievements/28b83c917f642ebf63bc.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/2dbc27cc5cc21467c3721ca8b8ec7367bdb73626.jpg": "/game-art/call-of-duty/achievements/edef7fdb97fe10818f0a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/f538ea6d78ea1645dc54c04af031ccdee6568984.jpg": "/game-art/call-of-duty/achievements/270e2a421a005b5dddfe.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209160/1f92129f1dd043e20858106244a5fc272f686b86.jpg": "/game-art/call-of-duty/achievements/92e43364427d5e9059b6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/6d7a6e51581af0ffe24116942136f2d81912fd5f.jpg": "/game-art/call-of-duty/achievements/881a6dcdb647a5456dbe.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/bf9871d14aa2080c53c6ad7bd8b44fb698ce9858.jpg": "/game-art/call-of-duty/achievements/cbf2768728500d2aea68.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/be9985fb5b0b3ec7f6090fd6e9bcc6999ff47c39.jpg": "/game-art/call-of-duty/achievements/5e2480d30de157fa3fc9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/2a65ab1924a9aa0973853c89cbf6de1b6a3f1b56.jpg": "/game-art/call-of-duty/achievements/cb01eaaed54970697e75.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/b8d2965a03da8768a690adf601a6125275c65954.jpg": "/game-art/call-of-duty/achievements/5442fbdf2f6974acd90d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/92c128fb406db30bdb68b246b95f73e980bd0007.jpg": "/game-art/call-of-duty/achievements/7c4f4b2fc6b80acf9e3a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/dfffd590f99c950a8f42737602bf5bbcde05ef51.jpg": "/game-art/call-of-duty/achievements/c114604b3ea2245bd5ff.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/c26807e278a807bfa9a9e194132debb994e700d3.jpg": "/game-art/call-of-duty/achievements/c720a215a01a41843917.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/f86efa50cdb567bf7fe1b3d732bfc90f28c2d85c.jpg": "/game-art/call-of-duty/achievements/2e69343cde4ac31be831.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/57083ea13b16f8cddc531bbceb75d6497690eb82.jpg": "/game-art/call-of-duty/achievements/1ee9ce141d49f68403b1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/68cff2f93656ee35858621a27f45df5d637d6897.jpg": "/game-art/call-of-duty/achievements/92dd45baf84e04330fea.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/c5c38a113aa37738d0238d463fa76db953406524.jpg": "/game-art/call-of-duty/achievements/cb6f70177e029d0bb502.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/3d5e62983b187417073ceef554847f9d00ce394a.jpg": "/game-art/call-of-duty/achievements/ba6f3cd833455f3b90bb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/029d30e48640e9e588e7c254d42d735760f2e01f.jpg": "/game-art/call-of-duty/achievements/3a2f2380e76d6d2d4d3c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/40389e331060b203357e57dd61a3f363b9979475.jpg": "/game-art/call-of-duty/achievements/b47151eba05bf44da97f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/7ff96353c673619e2d2842385084d09868959e91.jpg": "/game-art/call-of-duty/achievements/561a880efddcee091658.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/ccb127bdaa673e31efb9d70454119e02ae2ca766.jpg": "/game-art/call-of-duty/achievements/cae6882a0941fa16921a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/087aed50312fb462e05211ad90905406b2409b5f.jpg": "/game-art/call-of-duty/achievements/4804a593f80db9e96f61.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/d72dc9e5f3b89f6a69e288de7f51fadf101ad876.jpg": "/game-art/call-of-duty/achievements/914c34dc12a4487979fa.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/6f52f66ffde12ccab6fd9dbe2a0a6e352b691b46.jpg": "/game-art/call-of-duty/achievements/06e0022dd9d28a55dd89.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/0c41ca34b961eae7baed5cd9fa22206f7196da5a.jpg": "/game-art/call-of-duty/achievements/c723c6e6e732d58c5c93.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/fbf0db3c197a2865ff8a546cd12950491b59eab7.jpg": "/game-art/call-of-duty/achievements/f6881f12ab401ed7ed80.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/91ca2c866c87108ee73388103a4e14c95e9e5d45.jpg": "/game-art/call-of-duty/achievements/2f9a0970dc33a4e8f3e7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/0246b25415a4eb83d6dae41bbefe5800e9c758b9.jpg": "/game-art/call-of-duty/achievements/90438c063f19119ea25d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/a2d770c6dbc5f58106ba15255c1815e2fa6bfb65.jpg": "/game-art/call-of-duty/achievements/c85019f07f9c65e315dd.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/51f85202f9407ac9c89c1a23c62717454aeed483.jpg": "/game-art/call-of-duty/achievements/b99c72128425236b1035.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/d946bb02b2001926c36d1c8b4bcf2e9da2a7df9f.jpg": "/game-art/call-of-duty/achievements/2a5be22b7dc548a2822c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/ca842a5338ed929ed9e82bfa95c3dd371d2197a4.jpg": "/game-art/call-of-duty/achievements/0bcdb9a4c85d98296f13.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/b9cc357bf7dff52609164ffc6ef9357fc29e49b7.jpg": "/game-art/call-of-duty/achievements/14804cc8bd7397a0ff89.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/be864d18461b9998bbbb0a8d3d16a6327c151222.jpg": "/game-art/call-of-duty/achievements/4adda62a9920bf5ed7db.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/eb0573e8cc9f1a3878d7ef7ec4ac80dfa1761b61.jpg": "/game-art/call-of-duty/achievements/9aea21d05fe9092fd238.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/84caa17a06d06d31adc9931bcb6db33e62c47670.jpg": "/game-art/call-of-duty/achievements/a6d5ae65a7085ade450a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/99a4e0e015ae9be3b8ca65867bbab6650e2be4a7.jpg": "/game-art/call-of-duty/achievements/5aebe7390c44348243a4.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/86064965b478de57183d1b43d122f8dcb55bf933.jpg": "/game-art/call-of-duty/achievements/baf8d5dac758f4d4e27d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/647e830027c5674a640ac6e4cd15c2ed182f40ee.jpg": "/game-art/call-of-duty/achievements/917e8bb7b9071f135d07.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/a5c165e92b5b46b90a7bd9e6ef1f0e8142c9a9fe.jpg": "/game-art/call-of-duty/achievements/34e5afd793ffa563ff78.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/c83af569c6421ec90065f01e5f6ca3bbbfed12e3.jpg": "/game-art/call-of-duty/achievements/6504e1d1cf9bd7c5e023.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/298570669846c5b34f22843c132d7ab3282f28ff.jpg": "/game-art/call-of-duty/achievements/aa7ec52c45ac218e3b5e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/522d635afe5643642ff92e4e847cc2134924ea49.jpg": "/game-art/call-of-duty/achievements/891c8afc4c4d72e9052d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/35e201ad8332ddfbcacceec0661498f86ea7920d.jpg": "/game-art/call-of-duty/achievements/58117f46b67e5f8319bd.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/81ba45eba34f972948c411f8fe449149cebdd6a7.jpg": "/game-art/call-of-duty/achievements/ea2c5010e5df6266508c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/698088f494c39ba52fa3901a40ed972197065b12.jpg": "/game-art/call-of-duty/achievements/7e8aff9916e8753b6017.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/a62c206a46f6961b66eaa42b93465b4a9acb90d3.jpg": "/game-art/call-of-duty/achievements/f8e7e9067abe8bd97043.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/63b91d1c9f1057775175185dd7ed32a85d7c4024.jpg": "/game-art/call-of-duty/achievements/eb5e344cbc69c8be20c2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/a445134fc4d8ebcd88c4509a2e7f4c0e4b4cf935.jpg": "/game-art/call-of-duty/achievements/73aaa55d00fc669decb6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/a013f9c37c35c49595a007006016d74058191cc9.jpg": "/game-art/call-of-duty/achievements/68a76bf623fbaaffa1de.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/9b30e1a1fbb4b87626c730dffbabf4b29a12111d.jpg": "/game-art/call-of-duty/achievements/93be31b31e10c70d69da.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/2fb09ee55aa0815adf363172bdd1ecab23f724b2.jpg": "/game-art/call-of-duty/achievements/3e3fa6bfd93d450030f2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/3a2fa1c8efe6e383a891ae866aa8fdaa1841679a.jpg": "/game-art/call-of-duty/achievements/d76d9073568fdb7f1e22.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/5f0d23a405f525db66d5de98217fe178f3085a31.jpg": "/game-art/call-of-duty/achievements/05a8efb5e1e15477d51c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209170/20755ba1aed7213aafb9678daa6718fbd654fefe.jpg": "/game-art/call-of-duty/achievements/3e8f3d7cb413572aee1a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/050bae232fd990eb93163372db049de1e1667220.jpg": "/game-art/call-of-duty/achievements/a3fa1324f9bfa48e7ffd.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/ad68409f7ee2029e76ffc99a24431817e479e15c.jpg": "/game-art/call-of-duty/achievements/0a790da83079d22ff365.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/77c17d48b3bb8f201f1c70106830ac2c93cc2bec.jpg": "/game-art/call-of-duty/achievements/1f569f12ef6bf02e2f60.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/9876a14b1eb1e169200e97ad975ba8a3f30f9dc1.jpg": "/game-art/call-of-duty/achievements/27832d705a5816832e31.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/de9c3a1e8c1e223cc764433967c2f4d6f957e56c.jpg": "/game-art/call-of-duty/achievements/db531ff792ba71a88bd2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/818648ba1376faae9caed0a37b39ee62c4cfdac1.jpg": "/game-art/call-of-duty/achievements/21351a3cca37d9104b8b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/6d61fe4bfae30b393c098e533598dcae13d54e70.jpg": "/game-art/call-of-duty/achievements/e467ca6eec81624f607b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/13e2d09ec3fd46b409918f9e78e08e016028d662.jpg": "/game-art/call-of-duty/achievements/4407c5769f18a5653760.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/a863a128014547ec4ffee3151c84a0f0217a3673.jpg": "/game-art/call-of-duty/achievements/6c1736bbf16d4782813f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/a6103cf19e593a4f72dba733a9587a8b11afdd56.jpg": "/game-art/call-of-duty/achievements/d63505f2c28cd355246c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/3c2cf01172a6ebe981d788ae2a93169befaff5e8.jpg": "/game-art/call-of-duty/achievements/81358b11c5b0cddbed0e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/2e11039befd3a7ce82b8f2a7f082bcbe7fc2bd48.jpg": "/game-art/call-of-duty/achievements/88da698e6c00ecffc4ad.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/fc28df331e97465a79a21e474913c33fd9ad7b36.jpg": "/game-art/call-of-duty/achievements/fd8dea35848fe13e55d4.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/686c5d0f87a272597a0b20d9532375fc9e5b7d12.jpg": "/game-art/call-of-duty/achievements/61884f34fcc524b656f7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/e52fc0cbbc3344a95f9221fdb4cfe83dc3309f8b.jpg": "/game-art/call-of-duty/achievements/6323591518b8852fecf8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/bc113dc8c9b656e3c2d0126ad847a518d8e71f43.jpg": "/game-art/call-of-duty/achievements/8893e5ac9a39df8861ba.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/f58a4c733358e524682fe4bff82e2b1a1bab647b.jpg": "/game-art/call-of-duty/achievements/579c1482e528f86abea0.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/4920ae39511c42ad9a0e07493ef88aa63c60b25f.jpg": "/game-art/call-of-duty/achievements/e50138224f098ff2b0e9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/2edc4b06d9a471fee962c1e88ed15af793182854.jpg": "/game-art/call-of-duty/achievements/c2c0bf56904af06f44af.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/6d36e7c98bc7caa26d3c02d4ded63a2a0b3e5f3a.jpg": "/game-art/call-of-duty/achievements/6a5efa3bd9334d5d867b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/e0f85322f92ba1ff6567bbc23dfbf89dff58f650.jpg": "/game-art/call-of-duty/achievements/290296290c6b8566569a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/ad615c659c19ba7050c7d8b226d6dfdc19cfa062.jpg": "/game-art/call-of-duty/achievements/57922440c12a64977ca8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/28711f22e4d02183fa3b9ddee726798870d3294c.jpg": "/game-art/call-of-duty/achievements/7614725d8df2f9e2deaa.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/1adeb3c1d0616ed493b878064414ba475a3fc970.jpg": "/game-art/call-of-duty/achievements/68a83b461dbf8944ba1b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/5e1ff8cee3a47d7259264b3e0d672e0c7358f6e2.jpg": "/game-art/call-of-duty/achievements/e1a62db91c1bbee7d6bc.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/bfc8834dad242fa7b4e54f965e52e3af764c648f.jpg": "/game-art/call-of-duty/achievements/fb9e970b872359137afb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/adc0de3da240048a1c3ef711e8935298e7048d71.jpg": "/game-art/call-of-duty/achievements/18938e8b05c43850a1d1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/37f79052f673cd66e823b8a2ac0edf46361e9086.jpg": "/game-art/call-of-duty/achievements/de047bf800f352c3ccda.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/90bd08a0ca63a5f797a632c37360fcd7417d670d.jpg": "/game-art/call-of-duty/achievements/cc9a0743649ab4c59526.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/f21d7109756d613aae0c1f5939edd05ff11f497a.jpg": "/game-art/call-of-duty/achievements/f544fe840003238312be.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/c710f5068bd6fcc2e003c9687bf35a4e9eb6ec75.jpg": "/game-art/call-of-duty/achievements/470a0f7cb6ebca39d5a2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/142953876ef39884da73128e38f1e1031da26075.jpg": "/game-art/call-of-duty/achievements/4a5a93323ab645faf0bf.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/44a46d2994af6d8908b1b1a8db56f63e93ebb9d9.jpg": "/game-art/call-of-duty/achievements/bc64976f8e558446785b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/3605bded742f799361ad78d90b6e69a1b2ed4396.jpg": "/game-art/call-of-duty/achievements/9f300fde5a055be0a2f7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/16b7df11cfbc7e897f6b166c2f6af9a572f148bf.jpg": "/game-art/call-of-duty/achievements/c6f06ebc885130e0722e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/4ab8c81fbd79007c23a981e4b44212e3d91ff1d9.jpg": "/game-art/call-of-duty/achievements/9e2b0d60184c6df69f4a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/17fad7e3884b0a6d22d9186521159c8a48f99710.jpg": "/game-art/call-of-duty/achievements/d9668edb4a10ec5afb29.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/439b0eb7928b4b7b7e4e6fb1b9f4e78454f6129c.jpg": "/game-art/call-of-duty/achievements/39a710368dfcc33592fd.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/95855c51876cd96887b8044dac125939defb8756.jpg": "/game-art/call-of-duty/achievements/9c8dab60983dd5f09299.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/339a36f54f554c4bd428b34c6ab57ca302a33148.jpg": "/game-art/call-of-duty/achievements/33c148406570b01ac65e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/e847bc992c94bb85aa7d877cca66d7a304202c38.jpg": "/game-art/call-of-duty/achievements/2b4d4ce264f28bf49169.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/bc7b95aca3dd892e3af7bb8855609e5deffc640c.jpg": "/game-art/call-of-duty/achievements/ddf6fde35a75833626cf.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/bdc263e06fd24394a7b3f556e6383b5cf2a5bd8e.jpg": "/game-art/call-of-duty/achievements/822d7187b609f4026d01.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/57f801ff4e86a62c29137529be436dd0c67e801a.jpg": "/game-art/call-of-duty/achievements/a7e8830d01f2edb54842.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209650/d1d328cd4b41675b205aa41b179f39cdef652162.jpg": "/game-art/call-of-duty/achievements/8e54b7d6307955986b96.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/52da6d72be62dd5a6c4e06ac0a4b6caa8e77109c.jpg": "/game-art/call-of-duty/achievements/4d410e57cd4155a21ff5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/bb758e805f9d2da1053db7db6518605b8b74acfd.jpg": "/game-art/call-of-duty/achievements/9f5847949abb34cbf970.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/5b120f358bd219fa1830106aff9713e3e6888210.jpg": "/game-art/call-of-duty/achievements/ddb5a6e3ba7e9cf06f1a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/b3c22eab9f1e84f9d73392731db39fc30d0396d1.jpg": "/game-art/call-of-duty/achievements/6566800a8a3788ec7255.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/6467e3d1e0d8a819cc4e064b3451e2519c2a8c21.jpg": "/game-art/call-of-duty/achievements/d519143240b213146571.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/96bb5a6ddaed3705e0fb1b04cf889dc484e48348.jpg": "/game-art/call-of-duty/achievements/fe5d49946f714a41def4.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/47cc1f734302c3acd1a15c365de915d8c6bbad73.jpg": "/game-art/call-of-duty/achievements/588e3e026859e6b20c4e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/236c9cf40603e1c35cace7ad3b923005d8313c07.jpg": "/game-art/call-of-duty/achievements/d4b4f64c747f8cce5747.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/861a3a694b818be9072467dd283c028371b2e063.jpg": "/game-art/call-of-duty/achievements/ef0937348329fb2b5b7f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/e90c82ba064b78420e1ed4bad078158bfb708985.jpg": "/game-art/call-of-duty/achievements/e744091006695c8e43f0.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/756b0d4a29fbc2ebbf236daf1e8a030e346041bc.jpg": "/game-art/call-of-duty/achievements/75446a54b94fe2cca1ba.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/7fa120b283d4bdd452167da70156378588dd63d7.jpg": "/game-art/call-of-duty/achievements/7d76c465d77c0c4ed74d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/421be651f3b3faa73f873b1aff31f918b6d33faf.jpg": "/game-art/call-of-duty/achievements/356e1b587d1aa06e229f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/5c09f121abcde97031674a6224f3cc20f50fea2f.jpg": "/game-art/call-of-duty/achievements/6b7a793e48a2806701b1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/2b42df8394c11a52ffd953d12fdae05b9fdc5b83.jpg": "/game-art/call-of-duty/achievements/50eed7e3271e385ba055.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/7e6b3e6c827e209091223bcc22237a2904cf765d.jpg": "/game-art/call-of-duty/achievements/77f0a50f6e76f0895d78.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/4ca7533816c1d6901b77bfee63e5d8b8be38dc45.jpg": "/game-art/call-of-duty/achievements/10b81c1bf5661167f7e9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/ce04968c413bfeca4b66ec2368a65da39a4b6f46.jpg": "/game-art/call-of-duty/achievements/3e1b28577bacd3294557.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/9b16bf7dab254eed651d2a78a41f3e8cd98fd52c.jpg": "/game-art/call-of-duty/achievements/48ec62405747e62c1221.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/9808074d5ee0e3fed290a3871963fc27f702a494.jpg": "/game-art/call-of-duty/achievements/140c3fc9803cc12de9e8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/52f205ce172e2455709b9c56d9a91ac9f83c2cc4.jpg": "/game-art/call-of-duty/achievements/f8471d3a20bca44f2c6d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/6bc4563784f085681652b5cf1938417d092ea225.jpg": "/game-art/call-of-duty/achievements/fbb12b75d252fcf4c473.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/50039d61608edf89dea7a3661a44d20fe2ef5316.jpg": "/game-art/call-of-duty/achievements/a3e82b8ab886ea64042b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/642be79721bbdd465e8cd02abc08e3dfedbf0f16.jpg": "/game-art/call-of-duty/achievements/4844d808b7dcf60ab4ea.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/f47dbd16bfda8265ffed525c4020176a536f03a2.jpg": "/game-art/call-of-duty/achievements/7fd6c2e9a3fc8d1013b7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/7bad50fee59ad5bc0c3ca3bf18b1982199386110.jpg": "/game-art/call-of-duty/achievements/4249be6f37e30ef940ec.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/1c883dc3d1adc6a5ec710f76191277b769ea76e9.jpg": "/game-art/call-of-duty/achievements/f741557d449ea7cde80a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/dfb171eac5a5c121258d4e05475c5d2e24f7cd1c.jpg": "/game-art/call-of-duty/achievements/17e4f46fee28877750f3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/022fb52a500d56c8437262d50fa280c2714bb42f.jpg": "/game-art/call-of-duty/achievements/8132fe0e13988bd24fd1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/5f8e24e7491983f80a7fa0ff69e0493e07d0be0d.jpg": "/game-art/call-of-duty/achievements/e42ff6122323ed9a3395.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/af2e18e12cb9755a12a35732a6f22d4544f5c3cc.jpg": "/game-art/call-of-duty/achievements/127c266e7e0fc974c8b8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/58ec162ca04995a17db5193753a6ab4d72a549fe.jpg": "/game-art/call-of-duty/achievements/90e6a8e1e520abcf4a56.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/7c38971df65317d11d36d0bc83446007b7e82d3e.jpg": "/game-art/call-of-duty/achievements/d57ea860dac87b16eed1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/e3d2196805f3364ccf63b11af82bf78f26fe367f.jpg": "/game-art/call-of-duty/achievements/055fa62b0ec398270f1f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/c055805d0b537d1f5592821c4957a05c56c035e0.jpg": "/game-art/call-of-duty/achievements/c141e7cd14b49ac24e14.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/ff3828ee10a3dd90c39d4ba03e4c80318f41df3a.jpg": "/game-art/call-of-duty/achievements/26ce14a18ff0d3ca4630.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/19725429483357cf9923024eb3b01571cb590964.jpg": "/game-art/call-of-duty/achievements/c222d739e10080d08e71.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/374aef06c38570f275550d5ec112dadbdafe2474.jpg": "/game-art/call-of-duty/achievements/65d6c7d09869e3d44d9a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/4d1a14f525f9eee3732db407b0b00e8776b7da80.jpg": "/game-art/call-of-duty/achievements/ce5174efcfa4cd484408.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/e64fa2ec9f5b716db5aadab888dfbed2897cfb57.jpg": "/game-art/call-of-duty/achievements/3649dd52dcfcbb203aed.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/c74fb5b0b7a56ebc7f963c1d30caa069820d2540.jpg": "/game-art/call-of-duty/achievements/35d06b424797d3dec59e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/35a53e8e4094957bfa43273c524cf45133da03df.jpg": "/game-art/call-of-duty/achievements/8d292c25901c52b93b82.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/ffdbc4312f0f2420a218acd67ad92df364910a6b.jpg": "/game-art/call-of-duty/achievements/d8fad671953c03f693d8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/b544bd18fb12b0b37891c889a51755e59318404a.jpg": "/game-art/call-of-duty/achievements/3140fc6f7f446c716a1b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/209660/67a2a2b17f8736eec7743f0df0c1c96a8f545272.jpg": "/game-art/call-of-duty/achievements/7ef86a67871720d7252c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/be09a9092346a464996c7df633d6f9f953b008d6.jpg": "/game-art/call-of-duty/achievements/197b69315304f7e48443.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/1d4a50459abf5d2364a5d98606f6262a6fab9213.jpg": "/game-art/call-of-duty/achievements/223cbe388d22b5576646.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/d6b5ee282b0916528cb140ccbef2612cf1e21d4b.jpg": "/game-art/call-of-duty/achievements/e735731d7451544af1a6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/db0d1b5f92f92cbd3d80842d57943d8d5be8d5cb.jpg": "/game-art/call-of-duty/achievements/e76bb42ca3475d9b0892.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/bfd15017d6af1bc8d41067ac2c572955c217493d.jpg": "/game-art/call-of-duty/achievements/ae01cdcbada7670a3afa.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/8b244c1fea2b4fdea2623a6b02b8679306b02b56.jpg": "/game-art/call-of-duty/achievements/03b4a6ad35e8d85f2257.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/254dec333adbadd7753e20a030fba050bdd03a78.jpg": "/game-art/call-of-duty/achievements/73502027f0cfafd4b5c0.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/8f9d970c9cfbf6a836e459d3ac8e15f1e38930ba.jpg": "/game-art/call-of-duty/achievements/8856e5ac6156c0c7ab33.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/25ed74dddf450df12f377e5342c81713b3129d2a.jpg": "/game-art/call-of-duty/achievements/4f967cbfd21a42fbaa16.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/f825b53bb58668624201e12ed4eabd033efcd585.jpg": "/game-art/call-of-duty/achievements/b10628c3d94d45f19de9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/a737ecf9f6d5e3953b1bf4c50aae4cec5e9e06bb.jpg": "/game-art/call-of-duty/achievements/2e67b31daa3d79a4df8e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/9e7be918ab894a01f7728744ba615eac1820817c.jpg": "/game-art/call-of-duty/achievements/3a0fba9657b38aa35e43.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/f21271c5b4fe81a8c2dacb3124dd38d5bf8f2a66.jpg": "/game-art/call-of-duty/achievements/be14e6541017097851a9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/fcc3e34ba09068858def201bcdf9ceb789eb61bc.jpg": "/game-art/call-of-duty/achievements/6c614d52d10224d2fa8e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/30de18e9078ef6b63b3299eae374845469d095d5.jpg": "/game-art/call-of-duty/achievements/46af7819bce39547f361.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/c62e9ba3af62855025c2b3b17a91ef02e2528cd2.jpg": "/game-art/call-of-duty/achievements/bc5360cb1b7898b761ea.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/d36b6dfff0667595586a4ddbf1d7c3788acbf38b.jpg": "/game-art/call-of-duty/achievements/acf446664a46e34baee6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/936a1c5bd72edb720616e40eab136d672d607a4a.jpg": "/game-art/call-of-duty/achievements/4c88c48b2a35e2ea664f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/86f552ab60ab431d435dc70606adfc1212a6744b.jpg": "/game-art/call-of-duty/achievements/b175cc13888dd81b6a9e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/381722b8ae8a5f47100470a2d2cb419c872da647.jpg": "/game-art/call-of-duty/achievements/6ad5ee337862d9c49188.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/41169a20ac367bd8a588ace5b5d39ad139c92b78.jpg": "/game-art/call-of-duty/achievements/4895825fc7ac017a9c81.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/6348a58f593d9f44d2cedbdca5f221d0042b679d.jpg": "/game-art/call-of-duty/achievements/cfcac47d7797bdb2a9b7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/26745f61f56cbf73a45b7ef9b1948e492148a5b4.jpg": "/game-art/call-of-duty/achievements/2ce53b47a9e930cd0f78.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/6090b4e792a1023679013b471e2aa71ecd5d4f99.jpg": "/game-art/call-of-duty/achievements/74b955b39ea1ed52fc64.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/def3fc3485640dd6edae975f87139aeef4d4fbe1.jpg": "/game-art/call-of-duty/achievements/f54072dc01e0c107a2bd.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/68f16e67a78e2b087c70ba959b216b49c16c2abf.jpg": "/game-art/call-of-duty/achievements/ca18fcccac729d9d7db5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/92a8876b6145ad17c8f0404f86d01bf26a010334.jpg": "/game-art/call-of-duty/achievements/3cc75903f021d64740bb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/e71deeb01ca53bb6bf6a7e6973efe9b82a0f8665.jpg": "/game-art/call-of-duty/achievements/388fce23aa91b4901180.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/474b8a9fb5b38c894cd1f1957a63b679bb771216.jpg": "/game-art/call-of-duty/achievements/e773ca386395ded5b66f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/c41b5d959900613a8cc39386742f7b22291f7999.jpg": "/game-art/call-of-duty/achievements/60b0825c4351c069e7cb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/d4a3b90d95dc9533703d69aade0ebed45b0b8693.jpg": "/game-art/call-of-duty/achievements/f4973928757861b05492.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/4a856c1791fd65d131bdb4fd173acc78aee409a1.jpg": "/game-art/call-of-duty/achievements/06254bba874e7fd01696.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/e53cf9bba3fac118dda21382e0e22df8eb24194b.jpg": "/game-art/call-of-duty/achievements/ddf914f209f0775a6681.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/e24f7b67ee2e209383ad61e5db207235e5c1b36c.jpg": "/game-art/call-of-duty/achievements/c891f4cb387f443cf069.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/1937a0f8698adcadc42a0ace8b4462977a760d3d.jpg": "/game-art/call-of-duty/achievements/09de527af050eb1ea6db.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/caaa4c120cc31a3b3844078901690924622eefec.jpg": "/game-art/call-of-duty/achievements/100ea250a4b8821ed763.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/8264162bd63715cce724a4c0e43200863e9db401.jpg": "/game-art/call-of-duty/achievements/245b5e76379398c97e60.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/3b7c4ba4b7813850520cb9490ad83b5053674cf3.jpg": "/game-art/call-of-duty/achievements/caf98f2b75941adb9458.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/889a2ef104e08f5c7202344b4002c92360a198d7.jpg": "/game-art/call-of-duty/achievements/47b989f34d821c69c13c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/5be087b25337e1be80fc34fab49516a9bc77b6b6.jpg": "/game-art/call-of-duty/achievements/c4dfbcc832173be93b88.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/8b85c34b1028b1a36a15b1a2a5163c887f127b43.jpg": "/game-art/call-of-duty/achievements/134ff04d96b5e2117659.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/e3c00a88baa550f02cb701bd54a48a75584884ec.jpg": "/game-art/call-of-duty/achievements/691f0231b7479088e835.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/cb1ff523e32e0c9c4d71db0f03ba939783e2c65e.jpg": "/game-art/call-of-duty/achievements/076ad1effb6e1150ee58.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/13a1311666c200fe0d063946a86ff12eccafdacc.jpg": "/game-art/call-of-duty/achievements/b2aad82684e6f9525b74.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/24f298d0cad725a3a2a6d6c157ba8184954bd51d.jpg": "/game-art/call-of-duty/achievements/1f401a4643636b7e09ea.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/db45274296fa6e35c811ad848ae8ad626135d5e4.jpg": "/game-art/call-of-duty/achievements/6513f9d21ce06e1c34a4.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/a0c4ad945aba7cb59e1cd01286a298bc0319048b.jpg": "/game-art/call-of-duty/achievements/e7cf115da299db339ab8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/2391734766760a40fb7938f2f70273c4d3d63525.jpg": "/game-art/call-of-duty/achievements/901df0e5c0d48e171f53.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/ec08f0773c6d0e51642dde798ec750e9aff7d6e9.jpg": "/game-art/call-of-duty/achievements/54ddc40c8fce36f87df2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/de077255c5d4e52e0f6f8f00fb2e9dbca80a64d1.jpg": "/game-art/call-of-duty/achievements/c0ae7942b7a2eb803695.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/e014cca3b1ca0ac9b9252f8a4541a80a07972956.jpg": "/game-art/call-of-duty/achievements/07de5ba81b60ceeb4adb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/668440cb517ae4f0bb5de178bb65482af2f9352c.jpg": "/game-art/call-of-duty/achievements/36735e3cc67ff6aefd7d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/8a237750c9186100f4bf102ce1958334d8c94a5f.jpg": "/game-art/call-of-duty/achievements/394689ecddb3bb3c6ae7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/1087feeae28b52a8691214007e0e4b55193c57b6.jpg": "/game-art/call-of-duty/achievements/64727141ed121cf93c42.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/2ba51a64811bce2e9d987100c8de9057edefddd0.jpg": "/game-art/call-of-duty/achievements/632a1fcb4fedf10658d6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/4290a97f840765a0a8eaad905cdc3944c524b6dd.jpg": "/game-art/call-of-duty/achievements/ca5280a764455c85011e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/c9c5c6ad44fd794cca4f1dc884f38f5d04bdc49c.jpg": "/game-art/call-of-duty/achievements/e0b4aa8da3809ac08f42.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/b518c5c6818a1dd05389dc4d00d68ec6d1c295aa.jpg": "/game-art/call-of-duty/achievements/bd4b2888b439fa2fdb0e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/e70609d0d14761efad36689c6164748b9ab906dc.jpg": "/game-art/call-of-duty/achievements/2a6bea06204ff2131803.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/98a9190243d1e999f28e52cff2f41eacf2bea14a.jpg": "/game-art/call-of-duty/achievements/29d44f556a418702ab3f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/858fa9620a0b3f18af5202262cd01014111fa142.jpg": "/game-art/call-of-duty/achievements/f0c8abc666782ed1b999.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/41e444d3d97c38c8c9745eb9812270071366d034.jpg": "/game-art/call-of-duty/achievements/5dfbb5d9c9e6761f5421.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/33c0c6c42510a5740d92052401cfa3e022041aee.jpg": "/game-art/call-of-duty/achievements/cbbf8d2b5b17d9049b8f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/ad607c6979932af3f628a2f49ab7861768317271.jpg": "/game-art/call-of-duty/achievements/b4664863a39199d14aed.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/b21d4dba771c1b995b823ae472ad2449ede75e96.jpg": "/game-art/call-of-duty/achievements/2595dd69394a004aa9b3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/a8691d946c8df1cbe15a5b5a844ce67afe44edec.jpg": "/game-art/call-of-duty/achievements/a33c85c2eac09eee12d3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/94ec5a16bec33a09a7e6d46c59228dc2bfe39036.jpg": "/game-art/call-of-duty/achievements/6ec3b50ebd02d0735b93.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/f4eecc8ddd7ee7293fa25832335188b015ffb83d.jpg": "/game-art/call-of-duty/achievements/e630036163d8caf0debb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/7f11480479347fcea37cc1cfae939aeba0310c02.jpg": "/game-art/call-of-duty/achievements/4836d8a31e08f9d59a2b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/f92300994fb718a533ca129c8ff923a926ab45a3.jpg": "/game-art/call-of-duty/achievements/acec1c540085bf43a216.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/2064b041848207e9152648f9b04b10c0eb4a5ea6.jpg": "/game-art/call-of-duty/achievements/add0b1b64943198d70f3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/0cd1b2512a34e0cdaccffdedc50a3922b97928f9.jpg": "/game-art/call-of-duty/achievements/57f87a62c441cc00fee3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/016736a5e36aa630ef854098bcf8b46a0ed090ce.jpg": "/game-art/call-of-duty/achievements/5ad988718dfbdf86382d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/32576cd779cc6194a221fa52d0fcd1ab42a3d3c2.jpg": "/game-art/call-of-duty/achievements/6081ccee396297fcf8f0.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/2eb29923c09df13e6d1459d86a81fb0578304ab4.jpg": "/game-art/call-of-duty/achievements/ccecd58a309d11d1175b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/ba1e4900d687debb7029236ccc1faa64db1180eb.jpg": "/game-art/call-of-duty/achievements/061b960e95b7d94f8f43.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/5cadec3374b283cb8f9dc854701bc822bdcce372.jpg": "/game-art/call-of-duty/achievements/ecdf86fbec69e35d181d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/25a6f8d781a75b9c5a2836a6143654af206e7ed2.jpg": "/game-art/call-of-duty/achievements/99ec70d1d7557298d43a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/0c3c4505562edca7e9b5c2f33461bed0d024cd9e.jpg": "/game-art/call-of-duty/achievements/f876467edbcda81649b4.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/8dedc819af9d9899a880116f1c5749157a41331b.jpg": "/game-art/call-of-duty/achievements/31c585a4c4713380fb47.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/880481de0edbb3b6c8106b91619047765c0438a4.jpg": "/game-art/call-of-duty/achievements/fe2f56321b012cbac9a5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/b25f0928717fdc0078a4b8235d4f8090f9347b2a.jpg": "/game-art/call-of-duty/achievements/a7d36705f1683cc0f51f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/33cbc57d8d5d260b2a029f72aa840e6f777a5fcc.jpg": "/game-art/call-of-duty/achievements/5a3e56b651582cd37b0b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/00f4b4482ffe5af8e36fc29ad10184fabb796b2e.jpg": "/game-art/call-of-duty/achievements/f9484a145c44b5d0cbd1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/5b6d4af29251e8c797ea6c8df0b5856a338147f7.jpg": "/game-art/call-of-duty/achievements/a14a5d22282b82e02b4e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/67cc050f25ae01778daa55c135f8935f0745ea5d.jpg": "/game-art/call-of-duty/achievements/2e87d420089b4f2be57f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/559bc3b290bc195d1285c286f1cde1c24335391b.jpg": "/game-art/call-of-duty/achievements/3c6074ea42bb0c376cb8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/782ab1a29161fcea2ee6637a6c0965e074e0bc35.jpg": "/game-art/call-of-duty/achievements/aac4dce9aa2be9c78d9b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/ecf93da66531bf5795410c7401216bc46c96377f.jpg": "/game-art/call-of-duty/achievements/27bc37653fd38aff2e63.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/c25d1ef82dc9ae885afc75fb7db37722039a5cfa.jpg": "/game-art/call-of-duty/achievements/ed46540b107123ccac71.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/61e6b6f00e56022f96fff4ea74393b211d87333c.jpg": "/game-art/call-of-duty/achievements/1189b4ced39f5b73873d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/68cf892bf027d8f5b337c878809441595772c462.jpg": "/game-art/call-of-duty/achievements/42184870c69db3836487.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/1c86b033653580d08f213eda50989d7a7284ae57.jpg": "/game-art/call-of-duty/achievements/28d3a3ca08a7bccc03fc.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/cf50aefce11bd69bc0e7aaa776cc8cfbe32c1843.jpg": "/game-art/call-of-duty/achievements/cabc845fe34956e075a8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/e1cda475ce180a6b18d5707c3e43f791890a8a0a.jpg": "/game-art/call-of-duty/achievements/594e4fdf8acf6f23526d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/7cec40e8b161cad9671f4a0e6a3c7b753312bbae.jpg": "/game-art/call-of-duty/achievements/b73876cee3831c464041.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/fed3d249602647c0793d1054b15a88b61e532fcd.jpg": "/game-art/call-of-duty/achievements/88afe330f75fb3f138a5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/311210/ca4a4b6a35ed886f3a38de19ac74cc1d79cf9b24.jpg": "/game-art/call-of-duty/achievements/73c21ba0cdda98cc9bf6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/a4ea4b980d2ecaf4e2cd586d771d4a8ea67f0fb1.jpg": "/game-art/call-of-duty/achievements/0eea8fd2286728848ad9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/9d54b20f3f810ba5350c026dd5ea10fb5219eb9d.jpg": "/game-art/call-of-duty/achievements/307d65e590b40d9b2ed7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/476ebb35cfa9b4c0b1cfdc764396347cf8a956a1.jpg": "/game-art/call-of-duty/achievements/821090905722714275bc.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/4d7838ed2db245e8dc1efd9c33bfb484d0987f5b.jpg": "/game-art/call-of-duty/achievements/9949c131335becbb7bde.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/6f53e953c713a7cb5ccb5f927324289297424f53.jpg": "/game-art/call-of-duty/achievements/bf2530311d97100ba635.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/0da3fabafb959cabc6450cdf61b380ec186a0742.jpg": "/game-art/call-of-duty/achievements/b6595861a5baa64dd7ca.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/1d1d49d31480100db0d8d3ff4ebe60600c548c33.jpg": "/game-art/call-of-duty/achievements/a993f3d757f6e294b0ce.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/364a213998f2261a3bc4c79ebe3d0b2786fcef6d.jpg": "/game-art/call-of-duty/achievements/b1b2c8cca82ab7fef281.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/e41644864b89f501bb6b4e81f58d34e6f7a692cc.jpg": "/game-art/call-of-duty/achievements/24f087406bd644483bc0.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/7cb49cf9cc0bbbb0d345993b1251fe0ad5115b97.jpg": "/game-art/call-of-duty/achievements/7ea2fbd55068b35f2b52.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/503545f86c912ce5f504001f50f0187dbda22e6f.jpg": "/game-art/call-of-duty/achievements/5e04d92c3fba71824fae.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/68a5bbfd6300ee0f9ab5dcd3f1abbc1158f3a4fa.jpg": "/game-art/call-of-duty/achievements/6ecc81eb586e45d95d80.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/eaa5797d0d8aa6892b2cd89342afb9e20854cf90.jpg": "/game-art/call-of-duty/achievements/d4d590aa6e41f28ba0c9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/914889189cdea483f10e7911c0ec21c743bb7ee7.jpg": "/game-art/call-of-duty/achievements/22dc6c8473596aeb227f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/57b930b1ea0cf6e610139859dc2e6d56226a2633.jpg": "/game-art/call-of-duty/achievements/bcd92f34c188b2c94e19.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/efbe0632197664b2c0228426c8df13097631a37d.jpg": "/game-art/call-of-duty/achievements/4ae83a4491c70c5a7a73.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/f4b3adcedec5e27d71b7af7e9ee0428cd1dcf400.jpg": "/game-art/call-of-duty/achievements/5dd6624c4ec7afaadf27.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/c17c0c50abc6e6d9a9aa85e458f54608b099df9b.jpg": "/game-art/call-of-duty/achievements/1a88c2ae4f55f28fe1cb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/be10a6dca336bcffc3db6e100cc84e2afc39a04e.jpg": "/game-art/call-of-duty/achievements/dda424d5bf63271f6a83.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/d36e209bce7aeee14bcd7421ec5989c3d6766335.jpg": "/game-art/call-of-duty/achievements/ea4c12853a21dcc53f2d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/45373ed485cfa11602295fc21cb619facf1b587a.jpg": "/game-art/call-of-duty/achievements/050347b46f0d8368c7c1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/5fd86313adb4952d991ea88fd25c264b4cf3e4d0.jpg": "/game-art/call-of-duty/achievements/a50db8da0e01557fbd8e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/abfde4e6cf3c3461caf90d5830c2984fbaf4bc68.jpg": "/game-art/call-of-duty/achievements/48753b740d2a2cdc0ba2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/fe8f95d54e14d3186cbcb91c422943ed386f4668.jpg": "/game-art/call-of-duty/achievements/85ab28ed3bd4013da705.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/ed8e02846abd5ed0be162106597261e0323ca74b.jpg": "/game-art/call-of-duty/achievements/4ee5e40d6a92ccf126ce.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/c53a03bdce2b1ca3aa4a7c1c51b2fae47aec2417.jpg": "/game-art/call-of-duty/achievements/15f92324581bf6590bf6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/69206aa25a08794251623f894f522dd974094996.jpg": "/game-art/call-of-duty/achievements/a07c68c98dc2d336adda.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/8fd625f315d38418c48ebc2e525ee9a132562029.jpg": "/game-art/call-of-duty/achievements/38e607282e37825c2269.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/01d0cc9c538cf12c4a2604f31f9d44b0a6b81bc6.jpg": "/game-art/call-of-duty/achievements/ff3a9b02aa377eed93fe.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/7783476c4c94f89e01165466eb82d896e0247c45.jpg": "/game-art/call-of-duty/achievements/8a2840e5b9584aa1546b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/48f63f1800eccb761142db00dab66bb306409774.jpg": "/game-art/call-of-duty/achievements/4b5e9de3363f43ea5dac.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/191459e1e92933065eb06461831ec18c8748aac5.jpg": "/game-art/call-of-duty/achievements/09cbb08ecadeced19848.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/bc5c4bfac95f698f5339a3458aa43b8765802e7f.jpg": "/game-art/call-of-duty/achievements/ffb4db59b87d8527049c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/d3f4ef642be6bc91a1f93bc0f079ef3b18ce8b05.jpg": "/game-art/call-of-duty/achievements/9b4b9d4026c4db6643b2.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/e174be71bda7e57d4c4fe7993ca4a958f3352648.jpg": "/game-art/call-of-duty/achievements/f90d041f942f984f6c15.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/c52901981e755d5553186c5b77f6d59348a50f95.jpg": "/game-art/call-of-duty/achievements/d29610fabecd57672df4.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/d20c0b3c00365f3ec8bc9d24682c20aa487cc8ae.jpg": "/game-art/call-of-duty/achievements/31432873709e76e4e1a3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/583e60eb79cd565e85aa1bd4c59bec3aa32e4434.jpg": "/game-art/call-of-duty/achievements/63dfa9a75717ece9f7fe.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/809188e0d91acbef64b5f99a05bdce0e76a48300.jpg": "/game-art/call-of-duty/achievements/2be108c182fe9595ccb9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/322e0ab6895d3f97a06244de57038145321ba78f.jpg": "/game-art/call-of-duty/achievements/55f264e040cb5f11dd6c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/e0d98de5d187d767c9c29ed3fefa5e94fb5d2a71.jpg": "/game-art/call-of-duty/achievements/da40d7b9be06fd8c6981.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/fad7498a85145e17041e7a42a2ac239c9a836940.jpg": "/game-art/call-of-duty/achievements/3cdbcc76097e36b877ae.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/cca04d8cb905c1a79dd337fb637db65753b1eb74.jpg": "/game-art/call-of-duty/achievements/0316533b014576c6f9d4.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/e4be11b62681fe629ce889e408cd33d7c9ce2d82.jpg": "/game-art/call-of-duty/achievements/99974045c45ca4746967.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/a23d7843879e2533cd5ca05cdbb12768421f5200.jpg": "/game-art/call-of-duty/achievements/d9687e41518b77bf05ba.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/2140d0ad44a74cf73dfa8e5eecff94545dbec800.jpg": "/game-art/call-of-duty/achievements/fb7536a485a7fcd2102e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/41eb949d9e5ebded26f444accfbbf776e423d0c1.jpg": "/game-art/call-of-duty/achievements/5d97318fb384c8043818.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/5e903ad5b14acac2a3ce30b241eca02318efe08c.jpg": "/game-art/call-of-duty/achievements/c7ac6468f1a9dd020ad9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/190cc9b3a7a4738b2c0c0aa5b3581c3d13d0fc67.jpg": "/game-art/call-of-duty/achievements/ee8e525ffbd4aad55d05.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/f26e0102562e48114cfdc47063638c0ff13ae369.jpg": "/game-art/call-of-duty/achievements/2598397bd2d8472b6ffa.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/4779b58576c848e74e133b602c4febb942a486c0.jpg": "/game-art/call-of-duty/achievements/6c22b7ab6d07086e064f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/851be884cab04c069af4b95853b679e5783c0a2d.jpg": "/game-art/call-of-duty/achievements/8e7820b5324e90ea2e19.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/e51e3ab96ef6d0c2a029724865f2db73074f879e.jpg": "/game-art/call-of-duty/achievements/fe0bca15248c5db77368.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/73263340a8746250ab3a8f1186f10c238cb98481.jpg": "/game-art/call-of-duty/achievements/51155702084575df7717.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/0ce7057339b8725b96f9ecca6f9a244456e050c3.jpg": "/game-art/call-of-duty/achievements/eb67cbdf0f2bf1e2e392.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/6091d5200a8aed5d2bf8079cd8e5f331b25a6d6a.jpg": "/game-art/call-of-duty/achievements/c14b826b7ceee473d291.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/565514b78a058199b82113be663edafbfcfaeaa6.jpg": "/game-art/call-of-duty/achievements/0e4f0bf6cff2c371105d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/232be3c5fc55ef830f30fe8e4505373482dba0ed.jpg": "/game-art/call-of-duty/achievements/875fa76fce401a711538.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/abe4a2fcc597ac2f024aeaeac6e4aa6f8a989951.jpg": "/game-art/call-of-duty/achievements/c9eece5fc57482ab737d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/d2fca5a28ce5c89ec913731cf05742e5a8ec5afc.jpg": "/game-art/call-of-duty/achievements/0151aa252dbe2c64fa5b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/ce1781c257f210cf1e816d4704030365abc75058.jpg": "/game-art/call-of-duty/achievements/ab8c01332d03579e6fc0.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/f737b9b719c85a924574939fd4d188c1817b9619.jpg": "/game-art/call-of-duty/achievements/5f0fd6350175e1f60148.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/fa839e5d6e9db883809176ac97e5e1e1fb364e50.jpg": "/game-art/call-of-duty/achievements/98a68729deb6696212f7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/a47053b4181e3d73780e2aa08454f29883e5b4ea.jpg": "/game-art/call-of-duty/achievements/bd970842650295550c88.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/e787578182bf475de654ff869d7a76204d7358c0.jpg": "/game-art/call-of-duty/achievements/5d72276ec7235d317174.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/0174240168f53900f133adf792fe1435ee1b0ea1.jpg": "/game-art/call-of-duty/achievements/7cde51a094d2ba37d596.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/c7abcb4db7eae619b4b94affd18b152fb039fe1f.jpg": "/game-art/call-of-duty/achievements/2776bc87b100fcf92325.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/fdf433700a6079bd2af957abe935150557992f31.jpg": "/game-art/call-of-duty/achievements/59803f6ef69ae9c0b923.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/6a11b9adabb47de14ec6e99d57cd08fd05c7638b.jpg": "/game-art/call-of-duty/achievements/43bf5dd8b3e0c059e922.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/f7c7b9405060e89852c1fdd1b98cd99fd4556ee6.jpg": "/game-art/call-of-duty/achievements/51ad1c409df815178212.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/d1cd031ca9d9664353b45bfea4fd6be7da9c0498.jpg": "/game-art/call-of-duty/achievements/14e430fa2934e973aa36.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/e7e98e8582812646a25139106f785cbd75520f63.jpg": "/game-art/call-of-duty/achievements/2b8350b0eae8221c10f8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/9f05aef05e025f2b3562f02a3879a150188beaa9.jpg": "/game-art/call-of-duty/achievements/9fc62e9028597c00592e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/65010f7eb16e517591dc2b62da2a907475033cdd.jpg": "/game-art/call-of-duty/achievements/326861e9806de8f193a8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/13c39f9153e906d20d6fd4526884deb3b08a13a1.jpg": "/game-art/call-of-duty/achievements/ed1fc961b8cae53a42df.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/e4b6c8ab4e761956616be5b79ec00f3df015162d.jpg": "/game-art/call-of-duty/achievements/1884707001ee9093877b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/df54112c58d577e0c021905122dcd40b2f6e7a98.jpg": "/game-art/call-of-duty/achievements/1afc6d1d8574b1961ca8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/e794b4b7a3adcaf08c50323b1a019fa34eed3632.jpg": "/game-art/call-of-duty/achievements/fae378553e0ee2f50a9d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/eb1ca331fcaaa3e1ddf481375bebff75a3ee8c76.jpg": "/game-art/call-of-duty/achievements/b1771805712e9d7dca3f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/f6db79f0db66c4ff5998b54f392bc5f6671e1e62.jpg": "/game-art/call-of-duty/achievements/033de94bcc4b798ef4c6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/804df5f215f8b28b76e370f636664b803e9cca52.jpg": "/game-art/call-of-duty/achievements/754dbd600cc2e348d91e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/2264003a9af6d55e694d9ebfb8c4be90678c9a18.jpg": "/game-art/call-of-duty/achievements/7a11fd527ab181f42e46.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/afdb278ecf2d8812f2eb4e2ba1916746fac6b715.jpg": "/game-art/call-of-duty/achievements/8a30f1adc1bc6d98f62a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/5aa426a557d386edacc8c53a16f31538643fc52a.jpg": "/game-art/call-of-duty/achievements/ed424ec96f90c53b8412.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/1f55129c4a789aeac4909e1d61618b5ea67a6c95.jpg": "/game-art/call-of-duty/achievements/0d96a1f789220f3ee488.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/e33837825d9424f56fcef9fe51e4f7cd8c7ad661.jpg": "/game-art/call-of-duty/achievements/692b9a85693cd1405c98.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/b829035e663ee531e0603a0506f2247cc13e5990.jpg": "/game-art/call-of-duty/achievements/a4dac4e7d2f8011e0a34.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/292730/6b396a5ecb13f571bac9d485356927f286048727.jpg": "/game-art/call-of-duty/achievements/b601313d7027944c14ff.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/5f5204549ec2b2350fe2abb39f7b31106f0eee9d.jpg": "/game-art/call-of-duty/achievements/5423e6e0220bf8389f93.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/6d40e8236f865b88e62f5b8f81683733b190de10.jpg": "/game-art/call-of-duty/achievements/464c02401563b468757a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/444ca3ad0ffcd78d48869b996ac0c3f28e8a3892.jpg": "/game-art/call-of-duty/achievements/f21af58534176e7cc2ae.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/9de932c337c236172603c5380673c6c02be47e56.jpg": "/game-art/call-of-duty/achievements/87efada8bf8cd21d007e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/81828d6e7db5cd1f3e05061f8cb2c2ff212bf021.jpg": "/game-art/call-of-duty/achievements/00fe9ca1edc2bf6ef2c7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/a3a798f1259d54c816478e6636dfb812faa38cd3.jpg": "/game-art/call-of-duty/achievements/017d310d77f9ce71f34d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/d3fd3f4a6d1160e7fca344c7153a3c1c3790e20d.jpg": "/game-art/call-of-duty/achievements/89bc701b69649a38c36c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/97caaed86660711ef4d92f431057ed5f0ed37ab2.jpg": "/game-art/call-of-duty/achievements/e9040935e077d69a190a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/9a09587009ade2752f099e19c947c7ba6b3edbf4.jpg": "/game-art/call-of-duty/achievements/516471bdf6e23a7c3525.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/d24857c1f2837e608c8dc19bdd780cf11dceb234.jpg": "/game-art/call-of-duty/achievements/f8d28a760c778fb16f3b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/d4cd1139dbfda102e4b2865ca8fdd2d2f46b1e3a.jpg": "/game-art/call-of-duty/achievements/0a0f8b7255d63297ad28.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/5f51760b958da5c541c968359c4904ae65b577eb.jpg": "/game-art/call-of-duty/achievements/304f33ab3c88f3c4bb61.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/1f41f3bdd8749e0f4893ba61eed72f52d5cacb6f.jpg": "/game-art/call-of-duty/achievements/a22e8f846d1302897f3b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/14db187aa9805219b5e7665953e2ff5370eb4bf1.jpg": "/game-art/call-of-duty/achievements/34a87af7e5ed992e5f56.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/fe4b494bd12470168e58611cc5848fad5af4c556.jpg": "/game-art/call-of-duty/achievements/47f3c124d30542618467.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/cd0fd8249e28ab27c9ad41bda381164ebb24bdbe.jpg": "/game-art/call-of-duty/achievements/6e28b230d5c7f0ccfd98.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/b77eee595f7c4d33efbbd156cd05b91f2137d504.jpg": "/game-art/call-of-duty/achievements/8078c43246474ae807e8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/8c0b886c75e4c8aa9c7140f7138b1139625a14d4.jpg": "/game-art/call-of-duty/achievements/1e444cfdba5372cf8d80.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/f0d225a5898fbdbf0320519a2e9269232ee0b2a1.jpg": "/game-art/call-of-duty/achievements/52cb1a51cb4f0b4a579c.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/bd1fa843cba1aeaea05401a2012de9f626cdea56.jpg": "/game-art/call-of-duty/achievements/173b27753257df49245a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/fe251e95bb30bf3e566dba7a9a2764588aca5303.jpg": "/game-art/call-of-duty/achievements/325c34e0be02eb788bce.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/4d0bb5694f2137cfa4bd6b45c50dfde39974e797.jpg": "/game-art/call-of-duty/achievements/91a62cd332f49a9dbcbb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/b11c309f8f71d56743791e9066bf507bce47089d.jpg": "/game-art/call-of-duty/achievements/7bc5d0d30105499b4787.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/c83c48ee231f23865e1564bd9dd609d159e2dc78.jpg": "/game-art/call-of-duty/achievements/786a6fbe1c805696cbeb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/f46558af9e3f8d4c34e6d910ddcd06899d7668dd.jpg": "/game-art/call-of-duty/achievements/46d491e0a9b3ef87b4c3.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/f1bd0dc5211c6469c2eb8914a76376b7c9587da2.jpg": "/game-art/call-of-duty/achievements/35bbd7b64edea5f0dbcc.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/e415053365c5cd2a794422e6e6e82b7447695978.jpg": "/game-art/call-of-duty/achievements/c5b97fe5c00980e17812.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/bf5061bb9b4d88a78819244cc9cb4fee608d8e4c.jpg": "/game-art/call-of-duty/achievements/e38ea5af67911aaa1672.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/ce15e88ab93d982b480395308ff8d3a51f4bfd09.jpg": "/game-art/call-of-duty/achievements/0d6e0f9af9b044891c85.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/53e20712d27d0fa30f6389683703fec90785b9f2.jpg": "/game-art/call-of-duty/achievements/fde64e9607353ca56656.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/3aab6e911ce76a3ae861515b15eab3880fbed914.jpg": "/game-art/call-of-duty/achievements/9fa641633b88a769124a.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/0ceaafe4e56dc6a5be562d6695fd556c896f1a6e.jpg": "/game-art/call-of-duty/achievements/dd4cde7eaa47be497800.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/1eeeda536f7fcca06176e03a8c8d0d673f93db81.jpg": "/game-art/call-of-duty/achievements/5e6c05ca59d43672b9e6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/062937d06c763afe5c917ce8c6ff2a3d8c1a10c7.jpg": "/game-art/call-of-duty/achievements/dcd3cfe2c0164cda451f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476600/ac5a08806508f32eaa1e8ee3b630e28c34fe8a8e.jpg": "/game-art/call-of-duty/achievements/26a745ed804f7c028b9e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/fafcdac24818ab3ea5fb9b062822e315636d7ec1.jpg": "/game-art/call-of-duty/achievements/96ad629fae03dc395189.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/3da38c567f9e948f20dc472fdb59f407b96dc8b2.jpg": "/game-art/call-of-duty/achievements/5324eff1e27445cda337.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/6a78ffd1b01c0db1b7efc5d4be32d98eea5c4e8e.jpg": "/game-art/call-of-duty/achievements/845b6b03c0a95993479f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/c56dac0afdaf369cc301c0067d1988bb74b67618.jpg": "/game-art/call-of-duty/achievements/47d8dd228ad855fcea24.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/5b2a1593fe4240b9c6a6f4dd69965e33d5f0abe1.jpg": "/game-art/call-of-duty/achievements/73a2389b03f7587fc0e5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/d1f38005260fe5598668227f322d4fbfc955a590.jpg": "/game-art/call-of-duty/achievements/aadf5c23e728238bb9c5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/8007c03be77aba99d7b1cc88e02a020118047bde.jpg": "/game-art/call-of-duty/achievements/1c4e1eb7189ca710164b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/3e8727718b0f19b5faec8c42fab0b862b4b63481.jpg": "/game-art/call-of-duty/achievements/413f83285d53ffcae2fd.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/e7405eabbe8111af18d2edf6ff029482b2b700fb.jpg": "/game-art/call-of-duty/achievements/26b06ce878eff10b73bf.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/6930db97368989cd6fd99cc8875511008f80f9ab.jpg": "/game-art/call-of-duty/achievements/6e906dc87a721d88e3bd.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/066c58519c94120a7586b048285be0e03f2eac8f.jpg": "/game-art/call-of-duty/achievements/5b39f17436deb4386859.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/9f473cb021b4e2a466eaabdd43e70b0d4bcc33c6.jpg": "/game-art/call-of-duty/achievements/06adf873abc0e85ec5c0.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/4ea7184e640b125cf66a0b41f50782e20141d149.jpg": "/game-art/call-of-duty/achievements/0f588f009483c1119c89.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/0d7fb5796c7fed3697cf1e005f9cf771d8c54d83.jpg": "/game-art/call-of-duty/achievements/fcb7c46cd797564a698f.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/dd125e0c672897afd49f58d916a6b30508d5cb8f.jpg": "/game-art/call-of-duty/achievements/8bf89d4fa5608647b7fa.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/6215cea00d06af99fc481233e5ae205d2fd4cecd.jpg": "/game-art/call-of-duty/achievements/0923efbeb3f152d328be.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/4ed6a97188c019b9c5ad79ec923756f29ba6e4c4.jpg": "/game-art/call-of-duty/achievements/5155ebec3a8d0596e4db.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/b10776bf82f624ffa3266e371074ac5f990a2f21.jpg": "/game-art/call-of-duty/achievements/ab9164941f8672bc8abb.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/d92a57a9bdb0215bbf025f1ebfeef31b86808b74.jpg": "/game-art/call-of-duty/achievements/741578149080d68ea0cf.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/35abebfdd66507398c87c1fb106bd847f29cd103.jpg": "/game-art/call-of-duty/achievements/60315f55775a3fce1077.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/714ebbc054d4a315f36b603047be752e3f03d6e0.jpg": "/game-art/call-of-duty/achievements/df765b3d7d4bf9e8daf4.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/e001bbd75e459f1a3698594ae4d906e637e85505.jpg": "/game-art/call-of-duty/achievements/6b6d3959856e3f9ca287.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/27b2f66216c7326455b475b4968a371b579ee57c.jpg": "/game-art/call-of-duty/achievements/e61c3692670ed08908c1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/9bd50348aa430df867060e24d3770e48b09672dd.jpg": "/game-art/call-of-duty/achievements/c7b703f4789fdf88f2e8.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/fcf0b94a7b7a9c7bc86fc5b797308a6139b7ef54.jpg": "/game-art/call-of-duty/achievements/d4451dddcf7b12a1e957.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/642bb5bf764fe94d8b8ed64221f81ea418dd67f8.jpg": "/game-art/call-of-duty/achievements/b98dcb94df80f1203e69.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/2830777cd6e2ca860803a580a507927315639bdb.jpg": "/game-art/call-of-duty/achievements/e7684dc386168bb855ce.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/f317858e66eae25497d0038dc802403ca25a6d69.jpg": "/game-art/call-of-duty/achievements/e3d5603aaef9f9181396.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/77d79b47cc9d7a1f75250e9b568e35d6aa9c5b0d.jpg": "/game-art/call-of-duty/achievements/3606dac327d5e7c2f002.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/9a2332cefa0811232da7eb65cce6babdd53e17aa.jpg": "/game-art/call-of-duty/achievements/373fb8fa42b25f2ff2f5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/dfbef312f1d2fcfeb4003204c0318041cf40bbac.jpg": "/game-art/call-of-duty/achievements/d2fa391ff5e1ba047d69.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/8c653f390740da89eea0a708fea7a073f6042dc8.jpg": "/game-art/call-of-duty/achievements/6f0bcb55520c5f4ee6d6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/143591c3bd783549dec12e594ca66eae0a7ab3b9.jpg": "/game-art/call-of-duty/achievements/5bb20f6fc96f66d387da.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/a46c13da4c9b6ed4e1477231c8b36349fc808f0b.jpg": "/game-art/call-of-duty/achievements/ffaffffefe23de4af17e.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/a0f7fc7d8cf212d04fa86a806791f43ee8bb7c10.jpg": "/game-art/call-of-duty/achievements/7b4336d95ad38bdceb14.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/8e0e68c0f8fc9c69b0e5fd7dc50fdd9c6b2335b5.jpg": "/game-art/call-of-duty/achievements/8ebc6e71dbc8afa226c6.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/d2bcd4fcee0e57475afca3dcba7809540521ec20.jpg": "/game-art/call-of-duty/achievements/178528d63e0791dfa11d.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/146e6d1394fea265d9850d6a482d643fa55cf8bd.jpg": "/game-art/call-of-duty/achievements/c0fdcae2d2e23d569eb4.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/725b6f81036fe2e27e105e7f2d5fa569af5a0300.jpg": "/game-art/call-of-duty/achievements/9179e207bd077972ff97.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/8a0ee0fc42268e7308d0cbe121944d95647baa9a.jpg": "/game-art/call-of-duty/achievements/f26378d314c0b4bf71f5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/20e57a0d1b40c952c8af700c8b5cee0cc12b4cd8.jpg": "/game-art/call-of-duty/achievements/807b6d5288ea4e99c540.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/531e5e8aab594ff1e740f96f1063186c3e0f9567.jpg": "/game-art/call-of-duty/achievements/8a6ee3f02bbce9526be7.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/adef655eb9161e85b9b75c5fe4341238df00ed1b.jpg": "/game-art/call-of-duty/achievements/c9e7867e6868bbdbd283.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/5d099ed40cadcfa7996e9dc2bac903bbc5427830.jpg": "/game-art/call-of-duty/achievements/21dd1009bcc8472b26d1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/0874326e2469ef5053d713487d2774adf4a83794.jpg": "/game-art/call-of-duty/achievements/50b2a42d676828bf2407.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/14c155b68e14d342bb1807ac44d7f96cf40d5066.jpg": "/game-art/call-of-duty/achievements/954434d2dbdb5fcc4343.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/31381253ea5db5f151e6b2ed375edbd17031b9de.jpg": "/game-art/call-of-duty/achievements/4738f4e73fdec7acb4c1.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/18e378dd7b85bb39e37a3b2c94b5673b2bc192ab.jpg": "/game-art/call-of-duty/achievements/923bcc83b36645e6ef85.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/15d7872c42fcbf3a829021296824cca0a505c6d2.jpg": "/game-art/call-of-duty/achievements/8f7ce98dd2e8abf78bd9.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/1b97bf2a964c883d01b91d830d953078f731413a.jpg": "/game-art/call-of-duty/achievements/f9fe1161efe00ae8c571.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/70dab8649791478c2d9877465550534656917c0f.jpg": "/game-art/call-of-duty/achievements/048b05607127d4ee5b7b.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/89b9d0d811451df6f7c57c25183ada2ee1ddc402.jpg": "/game-art/call-of-duty/achievements/73a862019bb136eb7a86.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/244a19d76e4c44187b88a0d30f2883631e7c82f5.jpg": "/game-art/call-of-duty/achievements/0f898edbf93d8f89dace.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/5b505784913a7dd3d8fd62174cdd76a0fb49c151.jpg": "/game-art/call-of-duty/achievements/8ca100618c6de814d6d5.webp",
"https://shared.akamai.steamstatic.com/community_assets/images/apps/476620/bc21875d3703b16313741bed719261fbb2d1ffdf.jpg": "/game-art/call-of-duty/achievements/f3d9e7b5a4fd35172686.webp"
}

1874
data/cod-art.json Normal file

File diff suppressed because it is too large Load Diff

36766
data/cod-challenges.json Normal file

File diff suppressed because it is too large Load Diff

90
data/cod-rank-art.json Normal file
View File

@@ -0,0 +1,90 @@
[
{
"game": "black-ops-2",
"prestige": 1,
"path": "/game-art/call-of-duty/ranks/black-ops-2-prestige-1.webp",
"url": "https://images.steamusercontent.com/ugc/2506892135804661214/9FD6B03A8AB4ABE1D52ADEB9A14D8DBB72054910/",
"source": "https://steamcommunity.com/sharedfiles/filedetails/?id=3231054009",
"credit": "Activision / Treyarch; prestige emblem via Steam Community"
},
{
"game": "black-ops-2",
"prestige": 2,
"path": "/game-art/call-of-duty/ranks/black-ops-2-prestige-2.webp",
"url": "https://images.steamusercontent.com/ugc/2506892135804661581/1D2C4EAD9F7EBCE0955B6BBE0FEA78A643B817C7/",
"source": "https://steamcommunity.com/sharedfiles/filedetails/?id=3231054009",
"credit": "Activision / Treyarch; prestige emblem via Steam Community"
},
{
"game": "black-ops-2",
"prestige": 3,
"path": "/game-art/call-of-duty/ranks/black-ops-2-prestige-3.webp",
"url": "https://images.steamusercontent.com/ugc/2506892135804661865/38F7010DD1543AA9A08C584D1B90DD7C9C138BA1/",
"source": "https://steamcommunity.com/sharedfiles/filedetails/?id=3231054009",
"credit": "Activision / Treyarch; prestige emblem via Steam Community"
},
{
"game": "black-ops-2",
"prestige": 4,
"path": "/game-art/call-of-duty/ranks/black-ops-2-prestige-4.webp",
"url": "https://images.steamusercontent.com/ugc/2506892135804662398/E36D618159E6474536E21AFD5873BB652658C010/",
"source": "https://steamcommunity.com/sharedfiles/filedetails/?id=3231054009",
"credit": "Activision / Treyarch; prestige emblem via Steam Community"
},
{
"game": "black-ops-2",
"prestige": 5,
"path": "/game-art/call-of-duty/ranks/black-ops-2-prestige-5.webp",
"url": "https://images.steamusercontent.com/ugc/2506892135804662706/45B72C58EB62C84E20A50AEDF2539069BFBF4531/",
"source": "https://steamcommunity.com/sharedfiles/filedetails/?id=3231054009",
"credit": "Activision / Treyarch; prestige emblem via Steam Community"
},
{
"game": "black-ops-2",
"prestige": 6,
"path": "/game-art/call-of-duty/ranks/black-ops-2-prestige-6.webp",
"url": "https://images.steamusercontent.com/ugc/2506892135804663180/AB4D41F43A0A68C3417F5DBB1ADA54365F217A49/",
"source": "https://steamcommunity.com/sharedfiles/filedetails/?id=3231054009",
"credit": "Activision / Treyarch; prestige emblem via Steam Community"
},
{
"game": "black-ops-2",
"prestige": 7,
"path": "/game-art/call-of-duty/ranks/black-ops-2-prestige-7.webp",
"url": "https://images.steamusercontent.com/ugc/2506892135804668104/BC9E371A435F8CCD160A22ED70AABB4E747E78E4/",
"source": "https://steamcommunity.com/sharedfiles/filedetails/?id=3231054009",
"credit": "Activision / Treyarch; prestige emblem via Steam Community"
},
{
"game": "black-ops-2",
"prestige": 8,
"path": "/game-art/call-of-duty/ranks/black-ops-2-prestige-8.webp",
"url": "https://images.steamusercontent.com/ugc/2506892135804668443/6985C3A4733C6CC5E19EEDC53E95FBE705752C70/",
"source": "https://steamcommunity.com/sharedfiles/filedetails/?id=3231054009",
"credit": "Activision / Treyarch; prestige emblem via Steam Community"
},
{
"game": "black-ops-2",
"prestige": 9,
"path": "/game-art/call-of-duty/ranks/black-ops-2-prestige-9.webp",
"url": "https://images.steamusercontent.com/ugc/2506892135804669343/BCBD2BC8411DA714684CE5E01C24F4E3DA948F5F/",
"source": "https://steamcommunity.com/sharedfiles/filedetails/?id=3231054009",
"credit": "Activision / Treyarch; prestige emblem via Steam Community"
},
{
"game": "black-ops-2",
"prestige": 10,
"path": "/game-art/call-of-duty/ranks/black-ops-2-prestige-10.webp",
"url": "https://images.steamusercontent.com/ugc/2506892135804670653/CD1052286A32FDA566E1849D3B6F02ADB22C45CC/",
"source": "https://steamcommunity.com/sharedfiles/filedetails/?id=3231054009",
"credit": "Activision / Treyarch; prestige emblem via Steam Community"
},
{
"game": "black-ops-2",
"prestige": "master",
"path": "/game-art/call-of-duty/ranks/black-ops-2-prestige-11.webp",
"url": "https://images.steamusercontent.com/ugc/2506892135804671070/305118EBAF387C239C6CBF2D8C808E1E6305D3E7/",
"source": "https://steamcommunity.com/sharedfiles/filedetails/?id=3231054009",
"credit": "Activision / Treyarch; prestige emblem via Steam Community"
}
]

39352
data/game-images.json Normal file

File diff suppressed because it is too large Load Diff

5568
data/pokemon-atlas.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3894
data/pokemon-map-art.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
{
"note": "First release of each title; Red and Blue refer to the international versions. Japan versions are separate catalog entries.",
"source": "https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_games",
"dates": {
"red-japan": "1996-02-27",
"green-japan": "1996-02-27",
"blue-japan": "1996-10-15",
"red": "1998-09-28",
"blue": "1998-09-28",
"yellow": "1998-09-12",
"gold": "1999-11-21",
"silver": "1999-11-21",
"crystal": "2000-12-14",
"ruby": "2002-11-21",
"sapphire": "2002-11-21",
"colosseum": "2003-11-21",
"firered": "2004-01-29",
"leafgreen": "2004-01-29",
"emerald": "2004-09-16",
"xd": "2005-08-04",
"diamond": "2006-09-28",
"pearl": "2006-09-28",
"platinum": "2008-09-13",
"heartgold": "2009-09-12",
"soulsilver": "2009-09-12",
"black": "2010-09-18",
"white": "2010-09-18",
"black-2": "2012-06-23",
"white-2": "2012-06-23",
"x": "2013-10-12",
"y": "2013-10-12",
"omega-ruby": "2014-11-21",
"alpha-sapphire": "2014-11-21",
"sun": "2016-11-18",
"moon": "2016-11-18",
"ultra-sun": "2017-11-17",
"ultra-moon": "2017-11-17",
"lets-go-pikachu": "2018-11-16",
"lets-go-eevee": "2018-11-16",
"sword": "2019-11-15",
"shield": "2019-11-15",
"the-isle-of-armor-sword": "2020-06-17",
"the-isle-of-armor-shield": "2020-06-17",
"the-crown-tundra-sword": "2020-10-22",
"the-crown-tundra-shield": "2020-10-22",
"brilliant-diamond": "2021-11-19",
"shining-pearl": "2021-11-19",
"legends-arceus": "2022-01-28",
"scarlet": "2022-11-18",
"violet": "2022-11-18",
"the-teal-mask-scarlet": "2023-09-13",
"the-teal-mask-violet": "2023-09-13",
"the-indigo-disk-scarlet": "2023-12-14",
"the-indigo-disk-violet": "2023-12-14",
"legends-za": "2025-10-16",
"mega-dimension": "2025-12-10",
"champions": "2026-04-08"
}
}

File diff suppressed because one or more lines are too long

132
docs/game-tracker.md Normal file
View File

@@ -0,0 +1,132 @@
# Game tracker
The development database has the journal migration and catalog imports applied. Production and staging need the same additive migration and seeds before deploying this code:
```sh
bun run db:migrate:tracker-journal
bun run db:seed:research
bun run db:seed:challenges
bun run db:seed:art
```
These commands require `DB_URL`. They build on the existing Pokémon and Call of Duty catalogs. They preserve user catches, teams, ranks and challenge progress. Removed challenge definitions are retired only if nobody has saved progress for them. Deployment preflight checks the new tables and populated research catalogs.
## Local images and library layout
The tracker includes locally stored WebP pictures for **all 53 Pokémon entries, all seven COD entries and osu!**, plus the series logos. Most games use box art; expansions use their promotional artwork. Covers keep their original aspect ratio. No supplied files or remote image hotlinks are required. Replace a WebP at the same path to use your own art.
`data/game-images.json` records download URLs, original sources and credits. `data/pokemon-releases.json` records chronology. Game rights remain with Nintendo/The Pokémon Company, Activision, and ppy; these assets are not claimed as site-owned or openly licensed. The public `/projects/game-tracker/credits` page links the sources.
```sh
# Pillow required; map rebuilding also needs beautifulsoup4.
npm run catalog:images
npm run catalog:map-art
npm run check:images
```
The downloader preserves transparency/aspect ratio, writes WebP atomically and reuses valid existing files. PokéAPI sprite URLs and Libretro box-art URLs are pinned to source commits. The three Scarlet/Violet maps are assembled from the original regional map tiles by `build-pokemon-map-art.py`. Map downloads are cached in `/tmp/game-map-art`.
The library has one column per series on desktop. Pokémon has a main journal link and only the games explicitly chosen in rules, arranged chronologically in exactly two columns; COD is chronological and osu! is a single entry. Large collections can scroll vertically so covers and names remain readable. At mobile sizes or high browser zoom, the series flow vertically. In the Pokémon journal, cover-only buttons stay visible across the header. They use one row for small selections and two rows for larger selections; the active cover is highlighted. Narrow screens can scroll the two-row cover strip horizontally.
## Pokémon
Open `/projects/game-tracker/pokemon?game=platinum`. A playthrough remembers its stage and selected location when **Save my place** is pressed. The adventure view hides encounters requiring radar, swarms, another cartridge or similar special conditions by default; these can be revealed with a checkbox. This is a convenience filter, not a full simulation of story gates or encounter eligibility.
The Pokédex defaults to a regional dex where the source has one. National order remains available. Map encounters show methods, areas, levels, rates and conditions. Species details include evolution information, stats, abilities, biology and searchable game-specific learnsets.
The companion layout keeps the current team and an animated completion ribbon above tabs for the map, Pokédex, PC and milestones. Dex and PC pages show smaller batches appropriate to the device. Desktop shows the map beside a fitted grid containing every species matching the selected area and filters, without an encounter-list scrollbar. On smaller screens, switch between the region map and area Pokémon. **Expand encounters** opens a larger grid; tapping any portrait opens encounter details and catch registration. Broad locations with hundreds of species use smaller portraits; area/floor filters and the expanded view make these easier to explore.
Register each individual catch with a nickname, current level, four moves, party slot, shiny flag, acquisition method, location, date and notes. The PC is sorted in National Pokédex order and shows the **first registered record** per species, even if a later record has an earlier catch date. Open species research to browse all duplicates. Assigning an occupied team slot moves the previous occupant to the PC. The six slots and caught checkmark update in one transaction; catch and checkmark mutations use the same per-user/game lock.
Existing caught checkmarks remain intact. A checkmark without an individual record offers **Add catch details**. Deleting a catch removes its individual details and party slot but retains the historical caught checkmark. Clear that checkmark separately after removing all records for the species. Species are separate records: after evolving a companion, record the evolved species and move it into the relevant team slot.
Badges, Elite Four, champions, selected bosses and collection milestones vary by game. Custom milestones cover personal goals and events outside the default list. Progress is split into story, postgame, and trades/transfers/events.
### Current catalog coverage
| Data | Records |
| ------------------------------------- | ------: |
| Species and species research | 1,025 |
| Game catalog | 53 |
| Regional dex/game/species assignments | 16,044 |
| Moves | 937 |
| Species/version-group learnsets | 11,331 |
| Existing encounter rows | 67,282 |
| Existing locations | 1,104 |
`data/pokemon-research.json` uses the same pinned [PokéAPI dataset](https://github.com/PokeAPI/pokeapi/tree/575291cdb197a7e3a320297be276c9de4ef8401a) as the existing catalog. Regenerate with `bun run catalog:research`, then seed. Learn methods and levels are version-specific; battle stats, move power/accuracy/PP and biology use the snapshot’s default forms and current values. Alternate forms and historical stat changes are not yet a complete catalog. Missing encounters are not evidence of unavailability, and recent titles have thinner source coverage.
### Atlas coverage
All **13 supported regions** now use local map images: Kanto (including the Sevii Islands), Johto, Hoenn, Sinnoh, Unova, Kalos, Alola, Galar (including its expansions), Hisui, Paldea, Orre, Kitakami and Blueberry Academy. The SVG schematic rendering has been removed. Maps preserve their aspect ratios and support zoom, dragging, keyboard scrolling, clickable markers and optional town labels.
`data/pokemon-map-art.json` stores image dimensions and marker positions. Kanto, Johto, Hoenn, Sinnoh, Unova and Kalos use the location coordinates associated with the [Serebii map images](https://www.serebii.net/pokearth/). Orre uses a Pokémon XD game screenshot. Paldea, Kitakami and the Terarium use assembled in-game map tiles. The remaining landmark positions are manually placed and labeled approximate. Images are regional references shared by titles in that region, not an exhaustive collection of every game's map revision or every interior map. The searchable location list continues to provide imported encounters outside the shown markers.
The original `pokemon-atlas.json` retains location and coordinate reference data from [pret](https://github.com/pret). Its build script does not control the new image backgrounds.
Pokémon sprites, shiny sprites and official artwork are stored as **3,075 local WebP images**, covering every one of the catalog's 1,025 species.
## Completion and discovery
The library and individual game pages show shadcn progress bars with two decimal places, so a single new entry remains visible even in a large catalog. Bars animate between values and respect reduced-motion preferences. **All progress** in Pokémon opens the full collection, regional dex, story, badge, league, boss, postgame and transfer/event breakdown.
Pokémon sightings are independent, per-user/per-game records. Use **seen** in the Pokédex or **Mark as seen** in an encounter dialog. Catches automatically record sightings; simply browsing the catalog never does. Historical caught checkmarks are backfilled as seen by the additive journal migration. A caught species cannot be marked unseen while its caught checkmark remains.
National caught/seen bars count each species once across the games selected in rules, against the full species catalog. Per-game collections include known acquisition methods (including trades/events/transfers) and exclude unconfirmed availability unless a real catch establishes it. In-game collection bars cover catching, evolution and breeding. Regional bars count distinct species across the imported regional dexes, without double-counting overlap.
Game completion balances collection, story, postgame and transfer/event progress equally where data exists. The story category includes a story-cleared checkpoint. **Mark story complete** records that checkpoint without inventing individual badge records; completing every default story milestone also infers it. Games completed counts cleared stories, not 100% collections. Personal milestones have a separate bar, so adding a custom goal does not change the main denominator. Changing selected games changes the scope; removing data can lower percentages.
COD completion equally weights cumulative player/weapon levels, weapon mastery, achievements and challenges where a denominator is known. Each recorded level counts immediately: `prestige × max level + current level`, out of `(max prestige + 1) × max level`. For example, M27 P0 L17 is 17/51, P1 L1 is 18/51, and P2 L17 is 51/51. Missing ranks contribute zero; launchers/melee without weapon levels and games without verified caps are excluded from rank denominators. Library and game totals share the same calculation; historical out-of-range records are bounded for display calculations, never rewritten. Each challenge contributes its fraction of a known target, or a completed/not-completed value when no target exists. Derived Diamond/Dark Matter tiers are shown separately and are not counted a second time in overall completion. Weapon cards, achievement groups, individual challenges and the full class breakdown show their own percentages. Black Ops II's catalog records a player cap of level 55 and prestige 10 (Prestige Master), a weapon prestige cap of 2, and attachment-rank caps by weapon class. Assault rifles cap at 17, SMGs and LMGs at 16, shotguns, pistols and most sniper rifles at 10, Ballista at 11 and Crossbow at 6. Launchers and the other melee specials have no attachment-rank progression. These caps are enforced by both the controls and server actions. osu! shows all-goal, pass, FC and accuracy completion from saved goals.
The BO2 cap list follows the [weapon-level unlock table](https://w.atwiki.jp/blackops2_cod/pages/47.html); the player Prestige Master threshold is documented by [Activision Support](https://support.activision.com/call-of-duty--black-ops-ii/articles/achieving-prestige-master-in-call-of-duty-black-ops-ii). The numeric player cap represents the level-55 prestige cycle; prestige 10 at level 55 displays the Master emblem.
Re-run `bun run db:migrate:tracker-journal` before deploying this update: it adds `pokemon_seen` and `pokemon_journeys.completed` without deleting existing data. Deployment preflight checks both.
## Call of Duty
Player and weapon levels/prestiges are separate from camo mastery. The challenges tab provides search, category filters, numeric progress where a source gives a target, completion checkboxes and linked requirements/rewards.
Weapon cards combine artwork, name, level, prestige and camo progress. Level and prestige **− / +** buttons save immediately, display save failures, and use atomic database increments to preserve simultaneous updates from multiple devices. Prestige + starts the next prestige at level 1. Tap the level number for direct editing. Positive prestige infers that maximum level has been reached at least once, and existing max-level checkmarks remain recognized. Black Ops II player and weapon level/prestige caps are enforced in both controls and server actions. The shared COD companion board groups weapons by class. At desktop sizes of 1280×720 and above, all 40 BO2 weapons fit on one screen; other games use 32-weapon pages. Narrow screens use a readable vertical layout. Every gun has a still image. Select a weapon name to edit camos and use full rank controls; the compact + button saves a level immediately. All ten BO2 prestige emblems and the Master emblem are served locally beside a level badge. Hide finished requires every prestige run and applicable camo milestone, not just a first-run max-level checkmark. Overall mastery statistics are available in the **Progress overview** dialog.
The imported snapshot has **3,674 challenges/operations**: Black Ops 1,489; Black Ops II 526; Ghosts 31; Advanced Warfare 75; Black Ops III 860; Infinite Warfare 329; WWII 364. This is a partial, attributed catalog, not an exhaustive inventory. Ghosts source rows with missing tier thresholds are excluded. Rewards without source text are left blank, and many emblem/calling-card pictures remain unavailable.
`scripts/build-cod-challenges.py` imports rendered, line-numbered wiki snapshots with `--snapshots <directory>`. The checked-in JSON is the deployment input; regenerating from newer research requires supplying source snapshots and reviewing ambiguous rows. Stable keys use game/category/name, so review renamed source headings before importing to avoid splitting progress.
208 locally served WebP weapon pictures are registered in `data/cod-art.json`, covering all seven games, with a larger shadcn dialog preview and original image credits. The base weapon galleries for Black Ops III and WWII are complete. Other games remain partial. Images include renders, game screenshots, named variants and real weapon reference photos, identified in the preview credits. All 618 cataloged achievement icons have local WebP copies. Add verified images and manifest entries, then run `db:seed:art`; the regular COD catalog seed also retains manifest images.
| Base weapon picture coverage | Pictures / weapons |
| ---------------------------- | -----------------: |
| Black Ops | 32 / 42 |
| Black Ops II | 38 / 39 |
| Ghosts | 20 / 40 |
| Advanced Warfare | 34 / 35 |
| Black Ops III | 31 / 31 |
| Infinite Warfare | 20 / 35 |
| WWII | 31 / 31 |
Additional pictures include BO2’s DLC Peacekeeper and Ghosts’ DLC Maverick-A2. BO2’s Combat Knife is the remaining entry without artwork; all guns have pictures. These counts do not imply complete DLC, camo, emblem or calling-card artwork.
Challenge text is adapted from **Call of Duty Wiki contributors**, with per-record source links. The adapted challenge dataset retains the source’s [CC BY-SA terms](https://www.fandom.com/licensing), per [the wiki’s copyright policy](https://callofduty.fandom.com/wiki/Call_of_Duty_Wiki:Copyrights). Game artwork retains its original owners’ rights; it is not covered by the challenge-text license. `data/cod-art.json` records each image’s source, download URL and credit. Pokémon artwork/sprites are downloaded from [PokéAPI’s sprite repository](https://github.com/PokeAPI/sprites) and served locally.
## osu!
`/projects/game-tracker/osu` contains beatmap goals, memorable scores and training sessions for osu!, taiko, catch and mania. Track pass/FC/accuracy/custom goals, score links, pp, combo, misses, mods and notes. Practice records have a skill, map BPM, playback rate and duration. The chart compares qualifying sessions, with a minimum duration filter. Effective BPM is map BPM multiplied by playback rate. These are manual journal entries; there is no automatic account/score import.
## Shared components and validation
Pokémon rules/research/catches, COD ranks/artwork, osu entries, Animaldex details, money forms, article creation and list/item creation/editing use shadcn Dialog. Destructive list/article/language reset controls use Alert Dialog. Existing custom selects use Native Select. Money/list modal fields use shared inputs, textareas and checkboxes. Native disclosure sections that explain content are retained.
```sh
npm run check
npm run build
npm run test:unit -- --run
npm run test:tracker
```
Tracker browser tests use the migrated **local development** database, start their own server on port 4174, create temporary users and delete them afterward. They check Platinum routes and dex ordering, duplicate catch policy, team slot reassignment, ownership, rank/challenge persistence, osu training calculations, dialog keyboard behavior, mobile page widths, local image loading, picture-button selection, and a readable two-column 53-game Pokémon library. Companion checks cover immediate weapon adjustments, concurrent updates, failed-save recovery, prestige inference, all-species encounter grid fitting, expanded encounters and catch registration from the map. Progress checks cover persisted sightings, caught/seen deduplication, completed stories, partial COD challenges, osu! goal completion, rules-scoped totals and direct cover switching. Do not point `test:tracker` at a shared or production database.
## Deployment verification
`deploy-remote.sh` takes a PostgreSQL-version-matched custom-format backup and validates its archive before running the repeatable migrations and catalog seeds on each environment. The COD seed retains stable IDs and preserves stale items with either checklist progress **or weapon ranks**. No saved player/weapon levels are reset or clamped in storage. `audit-tracker-data.ts` prints only row counts and fingerprints for saved tracker records; compare before/after each deployment (newly added journal tables and backfilled sightings are expected). BO2 catalog caps, weapon counts and image coverage are checked by deployment preflight.
BO2 data sources: [attachment level unlocks](https://w.atwiki.jp/blackops2_cod/pages/47.html), [Peacekeeper attachments](https://callofduty.fandom.com/wiki/Peacekeeper), and [weapon prestige](https://callofduty.fandom.com/wiki/Prestige_Mode). Artwork URLs and credits are in `data/cod-art.json` and `data/cod-rank-art.json` and included in the public credits page.

178
docs/house-tracker.md Normal file
View File

@@ -0,0 +1,178 @@
# House tracker
Personal tool: collects Dutch housing listings into Postgres, shows them on
`/projects/house-tracker` (admin only) and sends an alert when a new listing
matches a rule. Only Pararius for now; other sources plug in the same way.
## How it fits together
```
home box (residential IP) stanrunge.dev (Hetzner)
┌──────────────────────────┐ POST sync ┌──────────────────────────────┐
│ scripts/house-worker.ts │ ──────────────▶ │ /projects/house-tracker/api/ │
│ polls pararius.nl │ POST details │ worker/{sync,details,report}│
│ parses HTML │ ◀────────────── │ stores, dedups, alerts │
└──────────────────────────┘ needDetails └──────────────┬───────────────┘
▼
Discord webhook / ntfy push
```
The server never talks to the listing sites. Pararius sits behind Cloudflare,
which challenges datacenter IPs and plain HTTP clients, so the worker runs at
home where traffic looks like a normal visitor.
Two lanes:
- **fast**: one filtered search (default `huurwoningen/gouda/1300-1700`), page 1
only. Every 3–5 min on weekdays 07:00–18:00 Amsterdam time, every 30–60 min
otherwise. Listings near Gouda were all published in office hours on weekdays.
Alerts are sent from the search card alone, before the detail page is fetched.
- **slow**: full, paginated sweeps of rent and buy listings in Gouda, Rotterdam,
Den Haag, Utrecht, Amsterdam and Eindhoven every 12 h. Unfiltered city sweeps
also mark listings that disappeared, which gives "days listed" in the stats.
Safety rules in the worker: one request at a time, at least 8 s apart with
jitter, a daily budget (1500 by default, the slow lane may only use 60%), and
any challenge/403/429 stops everything and backs off 15 min → 30 → 60 … 6 h.
The first poll of any search only seeds the database, so you don't get 40
alerts for listings that were already online.
## Server setup
Add to the production env file (`/etc/personal-website/prod.env`):
```text
# openssl rand -hex 32
HOUSE_WORKER_TOKEN=
# one or both
HOUSE_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
HOUSE_NTFY_URL=https://ntfy.sh/some-long-random-topic
HOUSE_NTFY_TOKEN= # only for a protected ntfy topic
PUBLIC_MAPTILER_API_KEY= # already set for Animaldex
```
`scripts/migrate-house-tracker.ts` runs on every deploy (idempotent). Use
"send test alert" on the page to check the alert channel.
If Cloudflare Bot Fight Mode / WAF challenges are on for stanrunge.dev, add a
WAF skip rule for `URI Path starts with /projects/house-tracker/api/worker/`,
otherwise the worker's POSTs get challenged too. The endpoints are protected by
the bearer token.
### Development behind Cloudflare Access
For `https://dev.stanrunge.dev`, keep Access enabled. In Cloudflare Zero Trust,
go to **Access controls → Service credentials → Service Tokens** and create a
token named `house-worker`. Save its Client ID and Client Secret. In the Access
application protecting the dev hostname, add a **Service Auth** policy with
**Include → Service Token → house-worker**. Keep the existing browser login
policy. See [Cloudflare's service token documentation](https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/service-tokens/).
Set `HOUSE_WORKER_TOKEN` in the dev website's `.env` and restart its dev server.
On the home machine, copy `house.env.example` to `house.env`, fill in the same
worker token plus the Cloudflare Client ID and Client Secret, then run:
```sh
chmod 600 house.env
bun --env-file=house.env scripts/house-worker.ts --check-api
```
This makes one authenticated GET to the website's worker health endpoint. It
does not contact Pararius, sync listings or send alerts. The expected output is
`API connection OK`. Access credentials are sent only to the website API; the
client refuses redirects and stops immediately on an authentication failure.
An HTTP 503 can mean the website's worker token is missing; an HTTP 404 can mean
the running website does not yet include the health route.
## Home worker setup
Needs a machine that stays on (old laptop, Raspberry Pi, NAS) with Bun 1.4 or
newer; older versions cannot read this repository's lockfile. Use `bun upgrade`
if needed.
```sh
git clone https://git.stanrunge.dev/stan/personal-website ~/house-worker
cd ~/house-worker && bun install --frozen-lockfile
cat > house.env <<'EOF'
HOUSE_API_URL=https://stanrunge.dev
HOUSE_WORKER_TOKEN=same-value-as-the-server
EOF
chmod 600 house.env
# 1. check parsing without sending anything
bun --env-file=house.env scripts/house-worker.ts --dry-run --once
# 2. one real poll
bun --env-file=house.env scripts/house-worker.ts --once
```
Then run it as a service, `/etc/systemd/system/house-worker.service`:
```ini
[Unit]
Description=House tracker worker
After=network-online.target
Wants=network-online.target
[Service]
User=stan
WorkingDirectory=/home/stan/house-worker
ExecStart=/home/stan/.bun/bin/bun --env-file=house.env scripts/house-worker.ts
Restart=always
RestartSec=60
[Install]
WantedBy=multi-user.target
```
```sh
sudo systemctl enable --now house-worker
journalctl -u house-worker -f
```
Or with Docker: `docker run -d --restart unless-stopped --name house-worker
--env-file house.env -v "$PWD":/app -w /app oven/bun:1.4.0 bun
scripts/house-worker.ts`.
## Worker settings
| variable | default |
| ------------------------ | ------------------------------------------------------------------------ |
| `HOUSE_FAST_SEARCHES` | `https://www.pararius.nl/huurwoningen/gouda/1300-1700` (comma separated) |
| `HOUSE_SLOW_SEARCHES` | rent + buy for the six cities above |
| `HOUSE_SLOW_EVERY_HOURS` | `12` |
| `HOUSE_SLOW_MAX_PAGES` | `80` per search |
| `HOUSE_MIN_GAP_SECONDS` | `8` between any two requests |
| `HOUSE_DAILY_BUDGET` | `1500` requests |
| `HOUSE_FETCH_MODE` | `fetch`, or `browser` (real Chromium) |
Any Pararius search URL works as a fast search: set the filters on the site
(price, radius, bedrooms) and paste the URL. Every extra fast search is one more
request per poll.
## When it gets challenged
You get one "worker was challenged" alert per 6 hours and the page header turns
red. In order:
1. Do nothing for a day. Backoff handles short blocks.
2. Slow down: raise `HOUSE_MIN_GAP_SECONDS`, lower `HOUSE_DAILY_BUDGET`, or drop
slow searches.
3. Save a page manually in your normal browser to investigate parsing offline.
Do not switch clients, use proxies or solve challenges to bypass a block.
The optional `HOUSE_FETCH_MODE=browser` uses headless Chromium with a persistent
profile (`npx playwright install chromium` once on the worker machine). It obeys
the same stop/backoff rules and is not a workaround for a challenged fetch.
## Parsers
`src/lib/house-tracker/pararius.ts`, tested against trimmed real pages in
`src/lib/house-tracker/fixtures/`. Listing pages expose an exact publish
timestamp (`odp.offered_since`), coordinates, deposit, energy label and agent.
`pararius-search-gouda.html` is a trimmed real search page saved on 2026-10-11,
with tracking and account data removed. It covers three listings, nested base
and total rent, action attributes and a lazy photo template. The synthetic
fixture still covers pagination and price on request. Rental `priceEur` is the
base/listed rent used by the search filter; a separately shown total can be
higher (Raam: €1,475 base, €1,750 total). If Pararius changes markup, the worker
reports `parse-error` in the worker log on the page.

View File

@@ -2,12 +2,11 @@ import { defineConfig } from 'drizzle-kit';
export default defineConfig({
dialect: 'postgresql',
schema: './src/lib/db/schema.ts',
schema: './src/lib/server/db/schema/index.ts',
out: './drizzle',
verbose: true,
strict: true,
dbCredentials: {
url: process.env.DB_URL
},
schemaFilter: 'personal_website'
url: process.env.DB_URL!
}
});

View File

@@ -20,18 +20,7 @@ export default [
}
},
{
files: ['**/*.svelte'],
rules: {
'@typescript-eslint/explicit-function-return-type': [
'error',
{
allowExpressions: false,
allowTypedFunctionExpressions: true
}
]
},
files: ['**/*.svelte', '**/*.svelte.ts'],
languageOptions: {
parserOptions: {
parser: ts.parser
@@ -42,15 +31,6 @@ export default [
files: ['**/*.ts'],
languageOptions: {
globals: globals.node
},
rules: {
'@typescript-eslint/explicit-function-return-type': [
'error',
{
allowExpressions: false,
allowTypedFunctionExpressions: true
}
]
}
},
{

7
house.env.example Normal file
View File

@@ -0,0 +1,7 @@
# Copy to house.env on the HOME machine. That filename is ignored by Git.
HOUSE_API_URL=https://dev.stanrunge.dev
# Same value as HOUSE_WORKER_TOKEN in the website's .env.
HOUSE_WORKER_TOKEN=
# Cloudflare Access service token, used only for requests to HOUSE_API_URL.
HOUSE_CF_ACCESS_CLIENT_ID=
HOUSE_CF_ACCESS_CLIENT_SECRET=

8616
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

112
package.json Executable file
View File

@@ -0,0 +1,112 @@
{
"name": "web",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "bun --bun vite build",
"preview": "vite preview",
"test": "npm run test:integration && npm run test:unit",
"check": "svelte-kit sync && svelte-check --tsgo --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsgo --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write .",
"test:integration": "playwright test",
"test:unit": "vitest",
"db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio",
"db:seed": "bun scripts/seed-catalog.ts",
"db:seed:cod": "bun scripts/seed-cod-catalog.ts",
"db:migrate:auth": "bun scripts/migrate-auth-catalog.ts",
"db:migrate:foundation": "bun scripts/migrate-foundation.ts",
"db:migrate:money-tracker": "bun scripts/migrate-money-tracker.ts",
"db:migrate:game-tracker": "bun scripts/migrate-game-tracker.ts",
"db:migrate:cs-prices": "bun scripts/migrate-cs-prices.ts",
"cs-prices:ingest": "bun scripts/ingest-cs-prices.ts",
"db:migrate:house-tracker": "bun scripts/migrate-house-tracker.ts",
"house:worker": "bun scripts/house-worker.ts",
"auth:role": "bun scripts/set-admin.ts",
"catalog:build": "python3 scripts/build-pokemon-catalog.py",
"catalog:cod": "python3 scripts/build-cod-catalog.py && prettier --write data/call-of-duty.json",
"test:auth": "node --env-file=.env node_modules/@playwright/test/cli.js test --config playwright.auth.config.ts",
"db:migrate:tracker-journal": "bun scripts/migrate-tracker-journal.ts",
"catalog:research": "python3 scripts/build-pokemon-research.py",
"db:seed:research": "bun scripts/seed-pokemon-research.ts",
"db:seed:challenges": "bun scripts/seed-cod-challenges.ts",
"test:tracker": "node --env-file=.env node_modules/@playwright/test/cli.js test --config playwright.tracker.config.ts",
"db:seed:art": "bun scripts/seed-cod-art.ts",
"catalog:atlas": "python3 scripts/build-pokemon-atlas.py",
"catalog:images": "python3 scripts/download-game-images.py",
"catalog:map-art": "python3 scripts/build-pokemon-map-art.py",
"check:images": "python3 scripts/check-game-images.py"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@fontsource-variable/montserrat": "^5.3.0",
"@iconify-json/simple-icons": "^1.2.94",
"@internationalized/date": "^3.12.0",
"@lucide/svelte": "^1.48.0",
"@playwright/test": "^1.62.1",
"@sveltejs/adapter-bun": "^1.0.0",
"@sveltejs/enhanced-img": "^0.11.0",
"@sveltejs/kit": "^3.0.0",
"@sveltejs/vite-plugin-svelte": "^7.3.0",
"@tailwindcss/typography": "^0.5.20",
"@tailwindcss/vite": "^4.3.3",
"@types/d3-geo": "^3.1.1",
"@types/eslint": "9.6.1",
"@types/pg": "^8.23.1",
"@types/topojson-client": "^3.1.5",
"@typescript/native": "npm:typescript@7",
"better-auth": "^1.7.2",
"bits-ui": "^2.19.0",
"clsx": "^2.1.1",
"cmdk-sv": "^0.0.19",
"cn": "^0.4.0",
"drizzle-kit": "^1.0.0-rc.4",
"eslint": "^10.9.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.23.0",
"globals": "^17.11.0",
"lucide-svelte": "^1.0.1",
"prettier": "3.9.6",
"prettier-plugin-svelte": "^4.1.1",
"shadcn-svelte": "^1.7.0",
"svelte": "^5.57.1",
"svelte-check": "4.7.6",
"tailwind-merge": "^3.6.0",
"tailwind-variants": "^3.3.1",
"tailwindcss": "^4.3.3",
"tw-animate-css": "^1.4.0",
"typescript": "~6",
"typescript-eslint": "8.68.0",
"unplugin-icons": "^23.0.1",
"vite": "^8.2.2",
"vitest": "^4.1.11"
},
"type": "module",
"dependencies": {
"@ai-sdk/anthropic": "^4.0.46",
"@aws-sdk/client-s3": "^3.1121.0",
"@aws-sdk/s3-request-presigner": "^3.1121.0",
"@maptiler/sdk": "^4.1.0",
"ai": "^7.0.85",
"d3-geo": "^3.1.1",
"drizzle-orm": "^1.0.0-rc.4",
"echarts": "^6.1.0",
"highlight.js": "^11.12.0",
"marked": "^18.0.11",
"marked-highlight": "^2.2.4",
"mode-watcher": "^1.1.0",
"postgres": "^3.4.9",
"topojson-client": "^3.1.0",
"world-atlas": "^2.0.2",
"zod": "^4.5.4"
},
"imports": {
"#lib": "./src/lib/index.js",
"#lib/*": "./src/lib/*"
}
}

View File

@@ -1,21 +0,0 @@
node_modules
# Output
.output
.vercel
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View File

@@ -1,4 +0,0 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock

View File

@@ -1,25 +0,0 @@
FROM oven/bun AS builder
RUN bun
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install
COPY . .
RUN bun run build
FROM oven/bun
WORKDIR /app
COPY --from=builder /app ./
EXPOSE 3000
ENV NODE_ENV=production
CMD ["bun", "build/index.js"]

View File

@@ -1,38 +0,0 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

Binary file not shown.

View File

@@ -1,52 +0,0 @@
CREATE TABLE IF NOT EXISTS "books" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(256),
"progress" integer DEFAULT 0,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "certificates" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(256),
"progress" integer DEFAULT 0,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "courses" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(256),
"university_id" integer,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "publications" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(256),
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "topics" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(256),
"emoji" varchar(256),
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "universities" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(256),
"progress" integer DEFAULT 0,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "courses" ADD CONSTRAINT "courses_university_id_universities_id_fk" FOREIGN KEY ("university_id") REFERENCES "public"."universities"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@@ -1,279 +0,0 @@
{
"id": "95e1c262-0787-412f-bb2f-7db06e20f3a0",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.books": {
"name": "books",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(256)",
"primaryKey": false,
"notNull": false
},
"progress": {
"name": "progress",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.certificates": {
"name": "certificates",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(256)",
"primaryKey": false,
"notNull": false
},
"progress": {
"name": "progress",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.courses": {
"name": "courses",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(256)",
"primaryKey": false,
"notNull": false
},
"university_id": {
"name": "university_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"courses_university_id_universities_id_fk": {
"name": "courses_university_id_universities_id_fk",
"tableFrom": "courses",
"tableTo": "universities",
"columnsFrom": [
"university_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.publications": {
"name": "publications",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(256)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.topics": {
"name": "topics",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(256)",
"primaryKey": false,
"notNull": false
},
"emoji": {
"name": "emoji",
"type": "varchar(256)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.universities": {
"name": "universities",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(256)",
"primaryKey": false,
"notNull": false
},
"progress": {
"name": "progress",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"sequences": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -1,13 +0,0 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1725668865024,
"tag": "0000_famous_random",
"breakpoints": true
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,57 +0,0 @@
{
"name": "web",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"test": "npm run test:integration && npm run test:unit",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write .",
"test:integration": "playwright test",
"test:unit": "vitest",
"generate": "drizzle-kit generate",
"migrate": "drizzle-kit migrate",
"studio": "drizzle-kit studio",
"seed": "bun src/lib/db/seed.ts"
},
"devDependencies": {
"@playwright/test": "1.49.1",
"@sveltejs/kit": "^2.15.2",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@types/eslint": "9.6.1",
"@types/pg": "^8.11.10",
"autoprefixer": "^10.4.20",
"bits-ui": "^1.4.7",
"clsx": "^2.1.1",
"cmdk-sv": "^0.0.18",
"drizzle-kit": "^0.30.1",
"eslint": "9.17.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-svelte": "^2.46.1",
"globals": "15.14.0",
"lucide-svelte": "^0.473.0",
"postcss": "^8.4.49",
"prettier": "3.4.2",
"prettier-plugin-svelte": "3.3.2",
"svelte": "^5.16.5",
"svelte-check": "4.1.1",
"tailwind-merge": "^2.6.0",
"tailwind-variants": "^0.3.1",
"tailwindcss": "^3.4.17",
"typescript": "5.7.2",
"typescript-eslint": "8.19.1",
"vite": "^6.0.7",
"vitest": "2.1.8"
},
"type": "module",
"dependencies": {
"@sveltejs/adapter-node": "^5.2.11",
"drizzle-orm": "^0.38.3",
"mode-watcher": "^0.5.0",
"postgres": "^3.4.5"
}
}

View File

@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -1,78 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 210 40% 98%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -1,13 +0,0 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

View File

@@ -1,29 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-7K10F5HJMQ"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-7K10F5HJMQ');
</script>
<meta charset="utf-8" />
<link rel="apple-touch-icon" sizes="180x180" href="%sveltekit.assets%/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="%sveltekit.assets%/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="%sveltekit.assets%/favicon-16x16.png">
<link rel="manifest" href="%sveltekit.assets%/site.webmanifest">
<title>Stan Runge</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -1,7 +0,0 @@
import { describe, it, expect } from 'vitest';
describe('sum test', () => {
it('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});
});

View File

@@ -1,25 +0,0 @@
<script lang="ts">
import { Button as ButtonPrimitive } from "bits-ui";
import { type Events, type Props, buttonVariants } from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = Props;
type $$Events = Events;
let className: $$Props["class"] = undefined;
export let variant: $$Props["variant"] = "default";
export let size: $$Props["size"] = "default";
export let builders: $$Props["builders"] = [];
export { className as class };
</script>
<ButtonPrimitive.Root
{builders}
class={cn(buttonVariants({ variant, size, className }))}
type="button"
{...$$restProps}
on:click
on:keydown
>
<slot />
</ButtonPrimitive.Root>

View File

@@ -1,49 +0,0 @@
import { type VariantProps, tv } from "tailwind-variants";
import type { Button as ButtonPrimitive } from "bits-ui";
import Root from "./button.svelte";
const buttonVariants = tv({
base: "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border-input bg-background hover:bg-accent hover:text-accent-foreground border",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
type Variant = VariantProps<typeof buttonVariants>["variant"];
type Size = VariantProps<typeof buttonVariants>["size"];
type Props = ButtonPrimitive.Props & {
variant?: Variant;
size?: Size;
};
type Events = ButtonPrimitive.Events;
export {
Root,
type Props,
type Events,
//
Root as Button,
type Props as ButtonProps,
type Events as ButtonEvents,
buttonVariants,
};

View File

@@ -1,23 +0,0 @@
<script lang="ts">
import type { Dialog as DialogPrimitive } from "bits-ui";
import type { Command as CommandPrimitive } from "cmdk-sv";
import Command from "./command.svelte";
import * as Dialog from "$lib/components/ui/dialog/index.js";
type $$Props = DialogPrimitive.Props & CommandPrimitive.CommandProps;
export let open: $$Props["open"] = false;
export let value: $$Props["value"] = undefined;
</script>
<Dialog.Root bind:open {...$$restProps}>
<Dialog.Content class="overflow-hidden p-0 shadow-lg">
<Command
class="[&_[data-cmdk-group-heading]]:text-muted-foreground [&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:font-medium [&_[data-cmdk-group]:not([hidden])_~[data-cmdk-group]]:pt-0 [&_[data-cmdk-group]]:px-2 [&_[data-cmdk-input-wrapper]_svg]:h-5 [&_[data-cmdk-input-wrapper]_svg]:w-5 [&_[data-cmdk-input]]:h-12 [&_[data-cmdk-item]]:px-2 [&_[data-cmdk-item]]:py-3 [&_[data-cmdk-item]_svg]:h-5 [&_[data-cmdk-item]_svg]:w-5"
{...$$restProps}
bind:value
>
<slot />
</Command>
</Dialog.Content>
</Dialog.Root>

View File

@@ -1,12 +0,0 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.EmptyProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.Empty class={cn("py-6 text-center text-sm", className)} {...$$restProps}>
<slot />
</CommandPrimitive.Empty>

View File

@@ -1,18 +0,0 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.GroupProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.Group
class={cn(
"text-foreground [&_[data-cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:py-1.5 [&_[data-cmdk-group-heading]]:text-xs [&_[data-cmdk-group-heading]]:font-medium",
className
)}
{...$$restProps}
>
<slot />
</CommandPrimitive.Group>

View File

@@ -1,23 +0,0 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import Search from "lucide-svelte/icons/search";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.InputProps;
let className: string | undefined | null = undefined;
export { className as class };
export let value: string = "";
</script>
<div class="flex items-center border-b px-2" data-cmdk-input-wrapper="">
<Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
class={cn(
"placeholder:text-muted-foreground flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...$$restProps}
bind:value
/>
</div>

View File

@@ -1,24 +0,0 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.ItemProps;
export let asChild = false;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.Item
{asChild}
class={cn(
"aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...$$restProps}
let:action
let:attrs
>
<slot {action} {attrs} />
</CommandPrimitive.Item>

View File

@@ -1,15 +0,0 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.ListProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.List
class={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...$$restProps}
>
<slot />
</CommandPrimitive.List>

View File

@@ -1,10 +0,0 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.SeparatorProps;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.Separator class={cn("bg-border -mx-1 h-px", className)} {...$$restProps} />

View File

@@ -1,16 +0,0 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLSpanElement>;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<span
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...$$restProps}
>
<slot />
</span>

View File

@@ -1,22 +0,0 @@
<script lang="ts">
import { Command as CommandPrimitive } from "cmdk-sv";
import { cn } from "$lib/utils.js";
type $$Props = CommandPrimitive.CommandProps;
export let value: $$Props["value"] = undefined;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<CommandPrimitive.Root
class={cn(
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
className
)}
bind:value
{...$$restProps}
>
<slot />
</CommandPrimitive.Root>

View File

@@ -1,37 +0,0 @@
import { Command as CommandPrimitive } from "cmdk-sv";
import Root from "./command.svelte";
import Dialog from "./command-dialog.svelte";
import Empty from "./command-empty.svelte";
import Group from "./command-group.svelte";
import Item from "./command-item.svelte";
import Input from "./command-input.svelte";
import List from "./command-list.svelte";
import Separator from "./command-separator.svelte";
import Shortcut from "./command-shortcut.svelte";
const Loading = CommandPrimitive.Loading;
export {
Root,
Dialog,
Empty,
Group,
Item,
Input,
List,
Separator,
Shortcut,
Loading,
//
Root as Command,
Dialog as CommandDialog,
Empty as CommandEmpty,
Group as CommandGroup,
Item as CommandItem,
Input as CommandInput,
List as CommandList,
Separator as CommandSeparator,
Shortcut as CommandShortcut,
Loading as CommandLoading,
};

View File

@@ -1,36 +0,0 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import X from "lucide-svelte/icons/x";
import * as Dialog from "./index.js";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = DialogPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 200,
};
export { className as class };
</script>
<Dialog.Portal>
<Dialog.Overlay />
<DialogPrimitive.Content
{transition}
{transitionConfig}
class={cn(
"bg-background fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg md:w-full",
className
)}
{...$$restProps}
>
<slot />
<DialogPrimitive.Close
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none"
>
<X class="h-4 w-4" />
<span class="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</Dialog.Portal>

View File

@@ -1,16 +0,0 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DialogPrimitive.DescriptionProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DialogPrimitive.Description
class={cn("text-muted-foreground text-sm", className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Description>

View File

@@ -1,16 +0,0 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...$$restProps}
>
<slot />
</div>

View File

@@ -1,13 +0,0 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...$$restProps}>
<slot />
</div>

View File

@@ -1,21 +0,0 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { fade } from "svelte/transition";
import { cn } from "$lib/utils.js";
type $$Props = DialogPrimitive.OverlayProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = fade;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 150,
};
export { className as class };
</script>
<DialogPrimitive.Overlay
{transition}
{transitionConfig}
class={cn("bg-background/80 fixed inset-0 z-50 backdrop-blur-sm", className)}
{...$$restProps}
/>

View File

@@ -1,8 +0,0 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
type $$Props = DialogPrimitive.PortalProps;
</script>
<DialogPrimitive.Portal {...$$restProps}>
<slot />
</DialogPrimitive.Portal>

View File

@@ -1,16 +0,0 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DialogPrimitive.TitleProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DialogPrimitive.Title
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Title>

View File

@@ -1,37 +0,0 @@
import { Dialog as DialogPrimitive } from "bits-ui";
import Title from "./dialog-title.svelte";
import Portal from "./dialog-portal.svelte";
import Footer from "./dialog-footer.svelte";
import Header from "./dialog-header.svelte";
import Overlay from "./dialog-overlay.svelte";
import Content from "./dialog-content.svelte";
import Description from "./dialog-description.svelte";
const Root = DialogPrimitive.Root;
const Trigger = DialogPrimitive.Trigger;
const Close = DialogPrimitive.Close;
export {
Root,
Title,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
Close,
//
Root as Dialog,
Title as DialogTitle,
Portal as DialogPortal,
Footer as DialogFooter,
Header as DialogHeader,
Trigger as DialogTrigger,
Overlay as DialogOverlay,
Content as DialogContent,
Description as DialogDescription,
Close as DialogClose,
};

View File

@@ -1,17 +0,0 @@
import { Popover as PopoverPrimitive } from "bits-ui";
import Content from "./popover-content.svelte";
const Root = PopoverPrimitive.Root;
const Trigger = PopoverPrimitive.Trigger;
const Close = PopoverPrimitive.Close;
export {
Root,
Content,
Trigger,
Close,
//
Root as Popover,
Content as PopoverContent,
Trigger as PopoverTrigger,
Close as PopoverClose,
};

View File

@@ -1,22 +0,0 @@
<script lang="ts">
import { Popover as PopoverPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = PopoverPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
export { className as class };
</script>
<PopoverPrimitive.Content
{transition}
{transitionConfig}
class={cn(
"bg-popover text-popover-foreground z-50 w-72 rounded-md border p-4 shadow-md outline-none",
className
)}
{...$$restProps}
>
<slot />
</PopoverPrimitive.Content>

View File

@@ -1,28 +0,0 @@
import Root from "./table.svelte";
import Body from "./table-body.svelte";
import Caption from "./table-caption.svelte";
import Cell from "./table-cell.svelte";
import Footer from "./table-footer.svelte";
import Head from "./table-head.svelte";
import Header from "./table-header.svelte";
import Row from "./table-row.svelte";
export {
Root,
Body,
Caption,
Cell,
Footer,
Head,
Header,
Row,
//
Root as Table,
Body as TableBody,
Caption as TableCaption,
Cell as TableCell,
Footer as TableFooter,
Head as TableHead,
Header as TableHeader,
Row as TableRow,
};

View File

@@ -1,16 +0,0 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import type { WithElementRef } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
</script>
<tbody bind:this={ref} class={cn("[&_tr:last-child]:border-0", className)} {...restProps}>
{@render children?.()}
</tbody>

View File

@@ -1,16 +0,0 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import type { WithElementRef } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
</script>
<caption bind:this={ref} class={cn("text-muted-foreground mt-4 text-sm", className)} {...restProps}>
{@render children?.()}
</caption>

View File

@@ -1,20 +0,0 @@
<script lang="ts">
import type { HTMLTdAttributes } from "svelte/elements";
import type { WithElementRef } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLTdAttributes> = $props();
</script>
<td
bind:this={ref}
class={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...restProps}
>
{@render children?.()}
</td>

View File

@@ -1,16 +0,0 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import type { WithElementRef } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
</script>
<tfoot bind:this={ref} class={cn("bg-muted/50 font-medium", className)} {...restProps}>
{@render children?.()}
</tfoot>

View File

@@ -1,16 +0,0 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import type { WithElementRef } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
</script>
<thead bind:this={ref} class={cn("[&_tr]:border-b", className)} {...restProps}>
{@render children?.()}
</thead>

View File

@@ -1,18 +0,0 @@
<script lang="ts">
import type { HTMLTableAttributes } from "svelte/elements";
import type { WithElementRef } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLTableAttributes> = $props();
</script>
<div class="relative w-full overflow-auto">
<table bind:this={ref} class={cn("w-full caption-bottom text-sm", className)} {...restProps}>
{@render children?.()}
</table>
</div>

View File

@@ -1,10 +0,0 @@
import { env } from '$env/dynamic/private';
import * as schema from './schema';
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
const client = postgres(env.DB_URL, { prepare: false });
export const db = drizzle({
schema,
client
});

View File

@@ -1,73 +0,0 @@
import { relations } from 'drizzle-orm';
import { integer, pgSchema, serial, timestamp, varchar } from 'drizzle-orm/pg-core';
export const personalWebsiteSchema = pgSchema('personal_website');
export const tasks = personalWebsiteSchema.table('tasks', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 256 }),
currentPoints: integer('current_points').default(0),
totalPoints: integer('total_points').default(1),
notes: varchar('notes', { length: 8192 }),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow()
});
export const tasksRelations = relations(tasks, ({ many }) => ({
tasksToTopics: many(tasksToTopics)
}));
export const topics = personalWebsiteSchema.table('topics', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 256 }),
emoji: varchar('emoji', { length: 256 }),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow()
});
export const topicsRelations = relations(topics, ({ many }) => ({
topicsToTasks: many(tasksToTopics)
}));
export const tasksToTopics = personalWebsiteSchema.table('tasks_to_topics', {
taskId: integer('task_id')
.notNull()
.references(() => tasks.id),
topicId: integer('topic_id')
.notNull()
.references(() => topics.id)
});
export const taskToTopicsRelations = relations(tasksToTopics, ({ one }) => ({
task: one(tasks, {
fields: [tasksToTopics.taskId],
references: [tasks.id]
}),
topic: one(topics, {
fields: [tasksToTopics.topicId],
references: [topics.id]
})
}));
export const universities = personalWebsiteSchema.table('universities', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 256 }),
progress: integer('progress').default(0),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow()
});
export const certificates = personalWebsiteSchema.table('certificates', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 256 }),
progress: integer('progress').default(0),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow()
});
export const publications = personalWebsiteSchema.table('publications', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 256 }),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow()
});

View File

@@ -1,11 +0,0 @@
import { topics } from "./schema"
import * as schema from './schema'
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
const client = postgres('postgresql://postgres:postgres@localhost:5432/db');
const db = drizzle(client, { schema });
await db.insert(topics).values([{ name: 'Math', emoji: '👍' }, { name: 'Programming', emoji: '👍' }])
process.exit(0)

View File

@@ -1,5 +0,0 @@
export const load = async ({ cookies }) => {
return {
editable: cookies.get('token')
};
};

View File

@@ -1,108 +0,0 @@
<script lang="ts">
import { enhance } from '$app/forms';
import { ModeWatcher } from 'mode-watcher';
import '../app.css';
interface Props {
children?: import('svelte').Snippet;
}
const { children, data }: Props = $props();
let editModalVisible = $state(false);
</script>
<div
class="min-h-screen bg-black text-white bg-center bg-[url('/moon.jpg')] bg-auto bg-no-repeat flex flex-col"
>
<nav class="flex justify-between bg-gray-800 items-center px-4 py-2">
<div class="m-2">
<a href="/" class="font-bold text-xl sm:text-2xl">Stan Runge</a>
</div>
<div class="m-2 flex gap-4 items-center">
<a href="/progress" class="hover:underline">Progress</a>
<span>|</span>
<div class="flex gap-2 items-center">
<a href="mailto:stan@stanrunge.dev" class="hover:text-gray-300" aria-label="Mail">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20zm8-7L4 8v10h16V8zm0-2l8-5H4zM4 8V6v12z"
/>
</svg>
</a>
<a href="https://github.com/stanrunge" class="hover:text-gray-300" aria-label="GitHub">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34
c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83
c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64
c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02
c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21
c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2"
/>
</svg>
</a>
<a href="https://x.com/stanrunge" class="hover:text-gray-300" aria-label="X">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path
fill="currentColor"
d="m17.687 3.063l-4.996 5.711l-4.32-5.711H2.112l7.477 9.776l-7.086 8.099h3.034l5.469-6.25l4.78 6.25h6.102
l-7.794-10.304l6.625-7.571zm-1.064 16.06L5.654 4.782h1.803l10.846 14.34z"
/>
</svg>
</a>
<a
href="https://linkedin.com/in/stanrunge"
class="hover:text-gray-300"
aria-label="LinkedIn"
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm-.5 15.5v-5.3
a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.32 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1
1.4 1.4v4.93zM6.88 8.56a1.68 1.68 0 0 0 1.68-1.68c0-.93-.75-1.69-1.68-1.69a1.69 1.69 0 0 0-1.69
1.69c0 .93.76 1.68 1.69 1.68m1.39 9.94v-8.37H5.5v8.37z"
/>
</svg>
</a>
</div>
</div>
</nav>
<!-- page content goes here -->
<main class="mx-auto py-8 px-4 max-w-5xl flex-grow">
<ModeWatcher />
{@render children?.()}
</main>
{#if !data.editable}
<div class="flex justify-end">
<button
class="p-3 rounded border border-white m-4"
onclick={() => (editModalVisible = !editModalVisible)}>Edit</button
>
</div>
{/if}
</div>
{#if editModalVisible}
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"
onclick={() => (editModalVisible = false)}
>
<form
class="bg-white rounded-lg shadow-lg p-6 max-w-md w-full flex flex-col items-center"
onclick={(e) => e.stopPropagation()}
action="/?/authenticate"
method="post"
>
<h2 class="text-lg font-semibold m-4 text-black">WHO ARE YOU?!?!?!??!</h2>
<input type="text" class="border border-black text-center rounded my-4" name="token" />
<button class="px-4 py-2 bg-gray-800 text-white rounded hover:bg-gray-700">
Authenticate
</button>
</form>
</div>
{/if}

View File

@@ -1,31 +0,0 @@
import { env } from '$env/dynamic/private';
export const actions = {
authenticate: async ({ request, cookies }) => {
const data = await request.formData();
const token = data.get('token');
if (!token) {
return {
status: 400,
body: { error: 'Missing token' }
};
}
if (token !== env.AUTH_TOKEN) {
return {
status: 401,
body: { error: 'Invalid token' }
};
}
cookies.set('token', token, {
path: '/',
maxAge: 60 * 60 * 24 * 7
});
return {
success: true
};
}
};

View File

@@ -1,10 +0,0 @@
<h1 class="my-8 text-4xl font-bold text-center">Stan Runge</h1>
<img src="/moon.jpg" alt="Moon" />
<div class="flex flex-col">
<div>Vash Software</div>
<div>Hogeschool Inholland</div>
<div>Junior</div>
</div>

View File

@@ -1,153 +0,0 @@
<script lang="ts">
let { data } = $props();
let searchTerm = $state('');
let filteredTasks = $derived(
data.tasks.filter(
(task) =>
task.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
task.tasksToTopics.some((t) =>
t.topic.name.toLowerCase().includes(searchTerm.toLowerCase())
)
)
);
</script>
<div class="flex flex-col items-center p-4">
<div class="w-full max-w-4xl flex flex-col gap-4 items-center mt-4 mb-8">
<div class="flex justify-center gap-8">
<h1 class="font-bold text-2xl">
Tasks ({data.tasks.length})
</h1>
<a href="/progress/topics">
<button class="px-4 py-2 bg-gray-800 text-white rounded hover:bg-gray-600 transition">
Topics
</button>
</a>
{#if data.editable}
<a href="/progress/create">
<button class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition">
Create
</button>
</a>
{/if}
</div>
<!-- Search Bar -->
<div class="w-full max-w-md">
<input
type="text"
bind:value={searchTerm}
placeholder="Search tasks or topics..."
class="w-full px-4 py-2 rounded bg-gray-800 border border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
Total progress: {data.tasks.reduce((acc, task) => acc + task.currentPoints, 0)} / {data.tasks.reduce(
(acc, task) => acc + task.totalPoints,
0
)} ({(
(data.tasks.reduce((acc, task) => acc + task.currentPoints, 0) /
data.tasks.reduce((acc, task) => acc + task.totalPoints, 0)) *
100
).toFixed(3)}%)
</div>
{#if filteredTasks.length > 0}
<div class="overflow-x-auto w-full">
<!-- Desktop Table View -->
<div class="hidden md:block">
<table class="min-w-full">
<thead>
<tr class="border-b border-white">
<th class="px-6 py-3 text-left text-sm font-semibold">Name</th>
<th class="px-6 py-3 text-left text-sm font-semibold">Topics</th>
<th class="px-6 py-3 text-left text-sm font-semibold min-w-[200px]">Progress</th>
</tr>
</thead>
<tbody>
{#each filteredTasks as task, index}
<tr class="{index % 2 === 0 ? 'bg-black' : 'bg-gray-950'} hover:bg-gray-800">
<td class="px-6 py-2 border-b">
<a href={`/progress/${task.id}`} class="text-blue-500 hover:underline">
{data.tasks.indexOf(task) + 1}. {task.name}
</a>
</td>
<td class="px-6 py-2 border-b">
<a href={`/progress/${task.id}`} class="flex gap-2 flex-wrap">
{#each task.tasksToTopics as topic}
<div
class="relative bg-blue-500 text-white rounded-full px-2 py-1 shadow font-medium hover:bg-blue-600 transition cursor-pointer"
title="Click for more details about {topic.topic.name}"
>
{topic.topic.name}
</div>
{/each}
</a>
</td>
<td class="px-6 py-2 border-b min-w-[200px]">
<div class="relative w-full h-6 bg-gray-700 rounded-full overflow-hidden">
<div
class="absolute h-full bg-green-500"
style="width: {(task.currentPoints / task.totalPoints) * 100}%"
></div>
<span class="absolute inset-0 flex items-center justify-center text-sm">
{task.currentPoints} / {task.totalPoints} ({(
(task.currentPoints / task.totalPoints) *
100
).toFixed(1)}%)
</span>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
<!-- Mobile Card View -->
<div class="md:hidden space-y-4">
{#each filteredTasks as task}
<div class="bg-gray-800 rounded-lg p-2 w-full">
<a
href={`/progress/${task.id}`}
class="text-blue-500 hover:underline text-lg font-bold"
>
{task.name}
</a>
<div class="mt-1">
<div class="mb-1"></div>
<span class="font-medium">Topics:</span>
<div class="flex flex-wrap gap-1 mt-1">
{#each task.tasksToTopics as topic}
<div
class="bg-blue-500 text-white rounded-full px-2 py-1 shadow font-medium hover:bg-blue-600 transition cursor-pointer"
title="Click for more details about {topic.topic.name}"
>
{topic.topic.name}
</div>
{/each}
</div>
</div>
<div class="w-full h-4 bg-gray-700 rounded-full relative overflow-hidden">
<div
class="absolute h-full bg-green-500"
style="width: {(task.currentPoints / task.totalPoints) * 100}%"
></div>
<span class="absolute inset-0 flex items-center justify-center text-xs">
{task.currentPoints} / {task.totalPoints} ({(
(task.currentPoints / task.totalPoints) *
100
).toFixed(1)}%)
</span>
</div>
</div>
{/each}
</div>
</div>
{:else}
<p class="text-gray-500">
No tasks found matching your search. Try a different keyword or clear the search.
</p>
{/if}
</div>

View File

@@ -1,174 +0,0 @@
<script lang="ts">
import Check from 'lucide-svelte/icons/check';
import * as Command from '$lib/components/ui/command';
import * as Popover from '$lib/components/ui/popover';
import { Button } from '$lib/components/ui/button';
import { cn } from '$lib/utils.js';
import { tick } from 'svelte';
let { data } = $props();
let open = $state(false);
let value = $state('');
let search = $state('');
let triggerRef = $state<any>();
let addTopicForms = $state<HTMLFormElement[]>([]);
// We want to refocus the trigger button when the user selects
// an item from the list so users can continue navigating the
// rest of the form with the keyboard.
function closeAndFocusTrigger() {
open = false;
tick().then(() => {
triggerRef.focus();
});
}
function getProgress() {
return (data.task?.currentPoints / data.task?.totalPoints) * 100;
}
</script>
<h1 class="my-4 text-2xl font-bold text-center">{data.task?.name}</h1>
<div class="my-4">
<div class="flex justify-center gap-4 items-center relative">
<h2 class="text-xl font-semibold">Topics ({data.task?.tasksToTopics.length})</h2>
<Popover.Root bind:open>
<Popover.Trigger bind:this={triggerRef}>
{#if data.editable}
<Button variant="outline" class="justify-between" role="combobox" aria-expanded={open}>
+
</Button>
{/if}
</Popover.Trigger>
<Popover.Content class="w-[200px] p-0">
<Command.Root>
<Command.Input bind:value={search} placeholder="Search topic..." />
<Command.List>
<div class="flex flex-col items-center p-2">
<Command.Group>
{#each data.topics as topic}
<form
action="?/addTopic"
method="post"
bind:this={addTopicForms[topic.id]}
onsubmit={() => (value = topic.id.toString())}
>
<input type="hidden" name="topic-id" value={topic.id} />
<Command.Item
value={topic.name!}
onSelect={() => {
addTopicForms[topic.id].submit();
closeAndFocusTrigger();
}}
>
<Check
class={cn(
'mr-2 size-4',
value !== topic.id.toString() && 'text-transparent'
)}
/>
{topic.name}
</Command.Item>
</form>
{/each}
</Command.Group>
<form method="post" action="?/createTopic">
<input type="hidden" name="name" value={search} />
<button class="px-4 py-2 rounded bg-secondary">Add Topic</button>
</form>
</div>
</Command.List>
</Command.Root>
</Popover.Content>
</Popover.Root>
</div>
<div class="flex justify-center flex-wrap gap-2 my-1 relative">
{#if data.task?.tasksToTopics}
{#each data.task?.tasksToTopics as topic}
<a href="/progress/topics/{topic.topic.id}">
<div
class="relative bg-blue-500 text-white rounded-full px-2 py-1 shadow font-medium hover:bg-blue-600 transition cursor-pointer group"
title="Click for more details about {topic.topic.name}"
>
{topic.topic.name}
<form
action="?/removeTopic"
method="post"
class="absolute -top-1 -right-1 opacity-0 group-hover:opacity-100 transition"
>
<input type="hidden" name="topic-id" value={topic.topic.id} />
<button
type="submit"
onclick={(e) => {
e.preventDefault();
e.target.closest('form')?.submit();
}}
class="bg-white text-red-500 hover:text-red-700 hover:bg-red-50 font-bold rounded-full w-5 h-5 flex items-center justify-center shadow-sm"
aria-label="Remove topic"
>
&times;
</button>
</form>
</div>
</a>
{/each}
{/if}
</div>
</div>
<div class="my-8">
<h2 class="text-xl font-semibold text-center mb-4">Progress ({getProgress().toFixed(2)}%)</h2>
<div class="flex items-center gap-4 max-w-xl mx-auto">
<form method="POST" action="?/updateProgress" class="flex items-center gap-4 w-full">
<input
type="number"
name="currentPoints"
value={data.task?.currentPoints}
class="w-20 p-2 border rounded"
min="0"
readonly={!data.editable}
onchange={(e) => e.target.form?.submit()}
/>
<div class="flex-1">
<div class="w-full rounded-full h-4">
<progress max="100" value={getProgress()}>{getProgress()}%</progress>
</div>
</div>
<input
type="number"
name="totalPoints"
value={data.task?.totalPoints}
class="w-20 p-2 border rounded"
readonly={!data.editable}
min="1"
onchange={(e) => e.target.form?.submit()}
/>
</form>
</div>
</div>
<div class="my-8">
<h2 class="text-xl font-semibold text-center mb-4">Notes</h2>
<form method="POST" action="?/updateNotes" class="max-w-xl mx-auto">
<textarea
name="notes"
class="w-full p-3 border rounded min-h-[150px] resize-y"
placeholder="Add notes about this task..."
readonly={!data.editable}
onchange={(e) => data.editable && e.target.form?.submit()}>{data.task?.notes || ''}</textarea
>
{#if data.editable}
<div class="flex justify-end mt-2">
<Button type="submit" size="sm">Save Notes</Button>
</div>
{/if}
</form>
</div>

View File

@@ -1,17 +0,0 @@
import { db } from "$lib/db"
import { tasks } from "$lib/db/schema"
import { redirect } from "@sveltejs/kit"
export const actions = {
default: async ({ cookies, request }) => {
if (!cookies.get('token')) {
return { status: 401 };
}
const data = await request.formData()
const task = await db.insert(tasks).values({ name: data.get('name') }).returning()
redirect(303, '/progress/' + task[0].id)
}
}

View File

@@ -1,10 +0,0 @@
<form method="post" class="flex flex-col items-center gap-4">
<h1 class="font-bold text-xl mt-8 mb-4">Create Task</h1>
<div class="flex gap-4">
<label for="name">Name: </label>
<input type="text" name="name" class="border rounded" />
</div>
<button class="px-4 py-2 rounded bg-green-500 m-4">Submit</button>
</form>

View File

@@ -1,29 +0,0 @@
<script lang="ts">
import { goto } from '$app/navigation';
import * as Table from '$lib/components/ui/table/index.js';
let { data } = $props();
</script>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head class="w-[100px]">Name</Table.Head>
<Table.Head>Amount of Tasks</Table.Head>
<Table.Head class="text-right">Actions</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each data.topics as topic}
<Table.Row
onclick={() => goto(`/progress/topics/${topic.id}`)}
class="cursor-pointer hover:bg-muted/50"
>
<Table.Cell class="font-medium">{topic.name}</Table.Cell>
<Table.Cell>{topic.topicsToTasks.length}</Table.Cell>
<Table.Cell class="text-right"></Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>

View File

@@ -1,14 +0,0 @@
<script lang="ts">
let { data } = $props();
</script>
<h1 class="my-4 text-2xl font-bold text-center">{data.topic.name}</h1>
<h2 class="text-xl font-bold">Tasks ({data.topic?.topicsToTasks.length})</h2>
{#each data.topic?.topicsToTasks as task}
<div class="flex gap-4 items-center relative">
<a href="/progress/{task.task.id}" class="text-lg font-semibold hover:underline"
>{task.task.name}</a
>
</div>
{/each}

View File

@@ -1 +0,0 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

View File

@@ -1,18 +0,0 @@
import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;

View File

@@ -1,64 +0,0 @@
import { fontFamily } from "tailwindcss/defaultTheme";
/** @type {import('tailwindcss').Config} */
const config = {
darkMode: ["class"],
content: ["./src/**/*.{html,js,svelte,ts}"],
safelist: ["dark"],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px"
}
},
extend: {
colors: {
border: "hsl(var(--border) / <alpha-value>)",
input: "hsl(var(--input) / <alpha-value>)",
ring: "hsl(var(--ring) / <alpha-value>)",
background: "hsl(var(--background) / <alpha-value>)",
foreground: "hsl(var(--foreground) / <alpha-value>)",
primary: {
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
foreground: "hsl(var(--primary-foreground) / <alpha-value>)"
},
secondary: {
DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
foreground: "hsl(var(--secondary-foreground) / <alpha-value>)"
},
destructive: {
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)"
},
muted: {
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
foreground: "hsl(var(--muted-foreground) / <alpha-value>)"
},
accent: {
DEFAULT: "hsl(var(--accent) / <alpha-value>)",
foreground: "hsl(var(--accent-foreground) / <alpha-value>)"
},
popover: {
DEFAULT: "hsl(var(--popover) / <alpha-value>)",
foreground: "hsl(var(--popover-foreground) / <alpha-value>)"
},
card: {
DEFAULT: "hsl(var(--card) / <alpha-value>)",
foreground: "hsl(var(--card-foreground) / <alpha-value>)"
}
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)"
},
fontFamily: {
sans: [...fontFamily.sans]
}
}
},
};
export default config;

View File

@@ -1,19 +0,0 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View File

@@ -1,9 +0,0 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
}
});

14
playwright.auth.config.ts Normal file
View File

@@ -0,0 +1,14 @@
import { defineConfig } from '@playwright/test';
process.env.AUTH_E2E = '1';
export default defineConfig({
testDir: 'tests',
testMatch: 'auth.test.ts',
workers: 1,
use: { baseURL: 'http://127.0.0.1:4174' },
webServer: {
command: 'npm run dev -- --host 127.0.0.1 --port 4174 --strictPort',
url: 'http://127.0.0.1:4174/login',
env: { ORIGIN: 'http://127.0.0.1:4174', BETTER_AUTH_URL: 'http://127.0.0.1:4174' },
timeout: 60000
}
});

View File

@@ -0,0 +1,15 @@
import { defineConfig } from '@playwright/test';
process.env.TRACKER_E2E = '1';
export default defineConfig({
testDir: 'tests',
testMatch: 'game-tracker.test.ts',
workers: 1,
use: { baseURL: 'http://127.0.0.1:4174' },
webServer: {
command: 'npm run dev -- --host 127.0.0.1 --port 4174 --strictPort',
url: 'http://127.0.0.1:4174/login',
reuseExistingServer: false,
env: { ORIGIN: 'http://127.0.0.1:4174', BETTER_AUTH_URL: 'http://127.0.0.1:4174' },
timeout: 60000
}
});

View File

@@ -0,0 +1,84 @@
-- One-off: convert animaldex ids from uuid to serial, keeping existing rows.
-- Postgres can't cast uuid -> integer, so db:push can't do this itself.
--
-- Run BEFORE re-running db:push, once per database:
-- psql "$DB_URL" -v ON_ERROR_STOP=1 -f scripts/animaldex-uuid-to-serial.sql
--
-- Safe to run on an empty database too. Everything is in one transaction, so a
-- failure leaves the database untouched.
BEGIN;
-- the failed push created this before erroring out
DROP SEQUENCE IF EXISTS animals_id_seq;
-- 1. new integer ids alongside the uuid ones
ALTER TABLE animals ADD COLUMN new_id serial;
ALTER TABLE sightings ADD COLUMN new_id serial;
ALTER TABLE photos ADD COLUMN new_id serial;
-- 2. translate the foreign keys through the uuid -> integer mapping
ALTER TABLE sightings ADD COLUMN new_animal_id integer;
UPDATE sightings s SET new_animal_id = a.new_id FROM animals a WHERE a.id = s.animal_id;
ALTER TABLE photos ADD COLUMN new_sighting_id integer;
UPDATE photos p SET new_sighting_id = s.new_id FROM sightings s WHERE s.id = p.sighting_id;
-- 3. drop the old foreign keys (names vary by how they were created)
DO $$
DECLARE r record;
BEGIN
FOR r IN
SELECT conrelid::regclass AS tbl, conname
FROM pg_constraint
WHERE contype = 'f' AND conrelid IN ('sightings'::regclass, 'photos'::regclass)
LOOP
EXECUTE format('ALTER TABLE %s DROP CONSTRAINT %I', r.tbl, r.conname);
END LOOP;
END $$;
-- 4. swap the foreign key columns
ALTER TABLE photos DROP COLUMN sighting_id;
ALTER TABLE photos RENAME COLUMN new_sighting_id TO sighting_id;
ALTER TABLE photos ALTER COLUMN sighting_id SET NOT NULL;
ALTER TABLE sightings DROP COLUMN animal_id;
ALTER TABLE sightings RENAME COLUMN new_animal_id TO animal_id;
ALTER TABLE sightings ALTER COLUMN animal_id SET NOT NULL;
-- 5. swap the primary keys
DO $$
DECLARE r record;
BEGIN
FOR r IN
SELECT conrelid::regclass AS tbl, conname
FROM pg_constraint
WHERE contype = 'p'
AND conrelid IN ('animals'::regclass, 'sightings'::regclass, 'photos'::regclass)
LOOP
EXECUTE format('ALTER TABLE %s DROP CONSTRAINT %I', r.tbl, r.conname);
END LOOP;
END $$;
ALTER TABLE animals DROP COLUMN id;
ALTER TABLE animals RENAME COLUMN new_id TO id;
ALTER TABLE animals ADD PRIMARY KEY (id);
ALTER SEQUENCE animals_new_id_seq RENAME TO animals_id_seq;
ALTER TABLE sightings DROP COLUMN id;
ALTER TABLE sightings RENAME COLUMN new_id TO id;
ALTER TABLE sightings ADD PRIMARY KEY (id);
ALTER SEQUENCE sightings_new_id_seq RENAME TO sightings_id_seq;
ALTER TABLE photos DROP COLUMN id;
ALTER TABLE photos RENAME COLUMN new_id TO id;
ALTER TABLE photos ADD PRIMARY KEY (id);
ALTER SEQUENCE photos_new_id_seq RENAME TO photos_id_seq;
-- 6. restore the foreign keys under the names drizzle expects
ALTER TABLE sightings ADD CONSTRAINT sightings_animal_id_animals_id_fkey
FOREIGN KEY (animal_id) REFERENCES animals(id) ON DELETE CASCADE;
ALTER TABLE photos ADD CONSTRAINT photos_sighting_id_sightings_id_fkey
FOREIGN KEY (sighting_id) REFERENCES sightings(id) ON DELETE CASCADE;
COMMIT;

View File

@@ -0,0 +1,33 @@
import postgres from 'postgres';
// Read-only fingerprints: lets a deployment verify saved progress without logging it.
if (!process.env.DB_URL) throw new Error('DB_URL is required');
const client = postgres(process.env.DB_URL, { max: 1 });
const tables = [
'user_entries',
'user_games',
'catch_em_all_settings',
'pokemon_journeys',
'pokemon_catches',
'pokemon_milestones',
'pokemon_seen',
'tracker_progress',
'tracker_ranks',
'tracker_weapon_ranks',
'tracker_challenge_progress',
'osu_entries'
];
try {
const result: Record<string, unknown> = {};
for (const table of tables) {
const [exists] = await client`select to_regclass(${'public.' + table}) is not null as present`;
if (!exists.present) continue;
const [row] = await client.unsafe(`select count(*)::int as rows,
md5(coalesce(string_agg(record, E'\\n' order by record), '')) as fingerprint
from (select row_to_json(t)::text as record from "${table}" t) s`);
result[table] = row;
}
console.log(JSON.stringify(result, null, 2));
} finally {
await client.end();
}

View File

@@ -0,0 +1,18 @@
-- Additive migration for the existing domain schema. No users or progress are removed.
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS role text NOT NULL DEFAULT 'user';
ALTER TABLE account ADD COLUMN IF NOT EXISTS issuer text;
-- Existing credentials use Better Auth's local namespace. Fail on unknown legacy
-- OAuth providers rather than silently changing which remote identity they trust.
UPDATE account SET issuer = 'local:credential' WHERE issuer IS NULL AND provider_id = 'credential';
UPDATE account SET issuer = 'https://accounts.google.com' WHERE issuer IS NULL AND provider_id = 'google';
UPDATE account SET issuer = 'local:oauth:github' WHERE issuer IS NULL AND provider_id = 'github';
UPDATE account SET issuer = 'local:oauth:osu' WHERE issuer IS NULL AND provider_id = 'osu';
ALTER TABLE account ALTER COLUMN issuer SET NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS account_issuer_account_id_unique ON account (issuer, account_id);
ALTER TABLE games ADD COLUMN IF NOT EXISTS source_id integer;
CREATE UNIQUE INDEX IF NOT EXISTS games_source_id_unique ON games (source_id);
CREATE UNIQUE INDEX IF NOT EXISTS pokemon_number_unique ON pokemon (number);
ALTER TYPE availability ADD VALUE IF NOT EXISTS 'unknown';
CREATE TABLE IF NOT EXISTS catch_em_all_settings (
user_id text PRIMARY KEY REFERENCES "user"(id) ON DELETE CASCADE
);

Some files were not shown because too many files have changed in this diff Show More