diff --git a/README.md b/README.md
index b0369ac..f683788 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1 @@
# Jefflix website
-
-*Automatically synced with your [v0.app](https://v0.app) deployments*
-
-[](https://vercel.com/jeff-emmetts-projects/v0-jefflix-website)
-[](https://v0.app/chat/rSGm1BAgi15)
-
-## Overview
-
-This repository will stay in sync with your deployed chats on [v0.app](https://v0.app).
-Any changes you make to your deployed app will be automatically pushed to this repository from [v0.app](https://v0.app).
-
-## Deployment
-
-Your project is live at:
-
-**[https://vercel.com/jeff-emmetts-projects/v0-jefflix-website](https://vercel.com/jeff-emmetts-projects/v0-jefflix-website)**
-
-## Build your app
-
-Continue building your app on:
-
-**[https://v0.app/chat/rSGm1BAgi15](https://v0.app/chat/rSGm1BAgi15)**
-
-## How It Works
-
-1. Create and modify your project using [v0.app](https://v0.app)
-2. Deploy your chats from the v0 interface
-3. Changes are automatically pushed to this repository
-4. Vercel deploys the latest version from this repository
\ No newline at end of file
diff --git a/app/page.tsx b/app/page.tsx
index 3daf808..ef2e8f4 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,6 +1,6 @@
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
-import { Film, Music, Server, Users, Heart, Rocket, Tv, Home, FolderGitIcon as SolidarityFistIcon, HandHeart, Landmark, Palette, Radio } from "lucide-react"
+import { Film, Music, Server, Users, Heart, Rocket, Tv, Home, FolderGitIcon as SolidarityFistIcon, HandHeart, Landmark, Palette, Radio, ListPlus, Play, Upload } from "lucide-react"
import { JefflixLogo } from "@/components/jefflix-logo"
export default function JefflixPage() {
@@ -48,54 +48,36 @@ export default function JefflixPage() {
-
-
- Movies
+
+
+ Request a Show or Movie
-
- Shows
+
+ Watch a Show or Movie
-
-
- Music
+
+
+ Upload Shows or Movies
-
@@ -222,20 +204,20 @@ export default function JefflixPage() {
Or learn how to set up your own Jellyfin server and join the movement
diff --git a/app/request-access/page.tsx b/app/request-access/page.tsx
index dad32a7..2f3c673 100644
--- a/app/request-access/page.tsx
+++ b/app/request-access/page.tsx
@@ -57,6 +57,14 @@ export default function RequestAccessPage() {
This usually happens within 24-48 hours.
+
+
Once you're approved:
+
+ Request movies & shows at requests.jefflix.lol
+ Watch them at movies.jefflix.lol
+ Both sites can be installed as apps on your phone's home screen, or use the Jellyfin app on a smart TV
+
+
@@ -159,14 +167,17 @@ export default function RequestAccessPage() {
-
+
What happens next?
Your request is sent to the admin for review
Once approved, you'll get an email with your login details
- Log in at movies.jefflix.lol to access all content
- Live Sports requires an active Sportsnet subscription
+ Request movies & shows at requests.jefflix.lol
+ Watch them at movies.jefflix.lol
+
+ Both sites can be installed as apps on your phone's home screen. On a smart TV, use the Jellyfin app and connect to movies.jefflix.lol.
+
diff --git a/backlog/config.yml b/backlog/config.yml
index 263b8a2..5f50f6a 100644
--- a/backlog/config.yml
+++ b/backlog/config.yml
@@ -13,3 +13,4 @@ auto_commit: false
bypass_git_hooks: false
check_active_branches: true
active_branch_days: 30
+task_prefix: "task"
diff --git a/backlog/tasks/task-3 - Set-up-Navidrome-mobile-access-Android-iOS.md b/backlog/tasks/task-3 - Set-up-Navidrome-mobile-access-Android-iOS.md
new file mode 100644
index 0000000..eb8ea0f
--- /dev/null
+++ b/backlog/tasks/task-3 - Set-up-Navidrome-mobile-access-Android-iOS.md
@@ -0,0 +1,47 @@
+---
+id: TASK-3
+title: Set up Navidrome mobile access (Android + iOS)
+status: To Do
+assignee: []
+created_date: '2026-02-24 07:13'
+labels:
+ - soulsync
+ - mobile
+ - navidrome
+dependencies: []
+references:
+ - 'https://soulsync.jefflix.lol'
+ - 'https://music.jefflix.lol'
+ - 'https://soulseek.jefflix.lol'
+ - /home/jeffe/Github/jefflix-website/soulsync-docker-compose.yml
+priority: high
+---
+
+## Description
+
+
+Configure Navidrome (music.jefflix.lol) for mobile streaming on both Android and Apple devices using Subsonic-compatible apps.
+
+Navidrome exposes the Subsonic API, so no custom app development is needed - just configure a native mobile client on each platform.
+
+**Recommended Apps:**
+- **Android:** Symfonium (paid, best UX), Subtracks (free/open-source), DSub, Ultrasonic
+- **iOS/Apple:** play:Sub, Amperfy (free/open-source), SubStreamer, iSub
+
+**Server URL:** https://music.jefflix.lol
+
+**Prerequisites:**
+- Navidrome must be accessible externally (verify Cloudflare tunnel routing)
+- User account(s) created in Navidrome
+- Subsonic API enabled in Navidrome settings (usually on by default)
+
+
+## Acceptance Criteria
+
+- [ ] #1 Navidrome is accessible externally at music.jefflix.lol
+- [ ] #2 Subsonic API endpoint responds (music.jefflix.lol/rest/ping)
+- [ ] #3 Android app installed and streaming music successfully
+- [ ] #4 iOS app installed and streaming music successfully
+- [ ] #5 Offline download/caching tested on at least one platform
+- [ ] #6 Document the setup (app name, settings) for future reference
+
diff --git a/backlog/tasks/task-4 - Verify-SoulSync-playlist-sync-pipeline-end-to-end.md b/backlog/tasks/task-4 - Verify-SoulSync-playlist-sync-pipeline-end-to-end.md
new file mode 100644
index 0000000..a1dbb2b
--- /dev/null
+++ b/backlog/tasks/task-4 - Verify-SoulSync-playlist-sync-pipeline-end-to-end.md
@@ -0,0 +1,40 @@
+---
+id: TASK-4
+title: Verify SoulSync playlist sync pipeline end-to-end
+status: To Do
+assignee: []
+created_date: '2026-02-24 07:13'
+labels:
+ - soulsync
+ - maintenance
+dependencies: []
+priority: medium
+---
+
+## Description
+
+
+Ensure the full SoulSync pipeline is working: Spotify playlist → SoulSync orchestration → Soulseek download → Navidrome library update.
+
+Spotify API was configured (task-1), but we should verify the full flow works reliably before relying on mobile access.
+
+**Services to check:**
+- SoulSync web UI: https://soulsync.jefflix.lol
+- slskd (Soulseek): https://soulseek.jefflix.lol
+- Navidrome: https://music.jefflix.lol
+
+**Key checks:**
+- Are Spotify playlists syncing to SoulSync?
+- Are downloads completing via Soulseek?
+- Is Navidrome picking up new files from the music directory?
+- Are there any stale/failed downloads to clean up?
+
+
+## Acceptance Criteria
+
+- [ ] #1 SoulSync shows Spotify playlists synced
+- [ ] #2 At least one playlist successfully downloads tracks via Soulseek
+- [ ] #3 Downloaded tracks appear in Navidrome library
+- [ ] #4 No stuck/failed jobs in the queue
+- [ ] #5 slskd Soulseek connection is healthy and sharing
+
diff --git a/backlog/tasks/task-5 - Audit-games-platform-deployment-on-Netcup.md b/backlog/tasks/task-5 - Audit-games-platform-deployment-on-Netcup.md
new file mode 100644
index 0000000..ba384b8
--- /dev/null
+++ b/backlog/tasks/task-5 - Audit-games-platform-deployment-on-Netcup.md
@@ -0,0 +1,44 @@
+---
+id: TASK-5
+title: Audit games platform deployment on Netcup
+status: To Do
+assignee: []
+created_date: '2026-02-24 07:13'
+labels:
+ - games
+ - infrastructure
+ - audit
+dependencies: []
+references:
+ - 'https://games.jeffemmett.com'
+ - /home/jeffe/Github/games-platform/docker-compose.yml
+ - /home/jeffe/Github/games-platform/DEPLOYMENT.md
+priority: high
+---
+
+## Description
+
+
+Check the current state of the games platform deployment at games.jeffemmett.com on Netcup RS 8000.
+
+**What to check:**
+- Are all Docker containers running? (postgres, redis, backend, worker, frontend, nginx)
+- Is the site accessible at https://games.jeffemmett.com?
+- What games (if any) are currently in the library?
+- Check /data/games/ directories for existing ROMs
+- Review database for any game entries
+- Check if the auto-deploy webhook is working
+- Verify EmulatorJS loads correctly in browser
+
+**Location on server:** /opt/apps/games-platform
+
+
+## Acceptance Criteria
+
+- [ ] #1 All 6 Docker containers verified running (or restarted)
+- [ ] #2 games.jeffemmett.com loads in browser
+- [ ] #3 Inventory of existing games documented
+- [ ] #4 Database health confirmed
+- [ ] #5 Gitea webhook auto-deploy verified
+- [ ] #6 EmulatorJS emulator loads on a game page
+
diff --git a/backlog/tasks/task-6 - Catalog-desired-retro-games-for-games-platform.md b/backlog/tasks/task-6 - Catalog-desired-retro-games-for-games-platform.md
new file mode 100644
index 0000000..f61faf3
--- /dev/null
+++ b/backlog/tasks/task-6 - Catalog-desired-retro-games-for-games-platform.md
@@ -0,0 +1,45 @@
+---
+id: TASK-6
+title: Catalog desired retro games for games platform
+status: To Do
+assignee: []
+created_date: '2026-02-24 07:14'
+labels:
+ - games
+ - content
+dependencies: []
+priority: medium
+---
+
+## Description
+
+
+Create a wishlist of old/retro games to add to the games platform. Go through memories of classic games across all supported platforms and build a catalog of what to source.
+
+**Supported platforms:**
+- PlayStation 1 (.iso, .bin, .cue, .pbp)
+- Nintendo 64 (.z64, .n64, .v64)
+- Super Nintendo (.smc, .sfc)
+- Game Boy Advance (.gba)
+- Game Boy Color (.gbc, .gb)
+- NES (.nes)
+- Sega Genesis (.md, .bin, .gen)
+- Sega Dreamcast (.cdi, .gdi, .chd)
+- PSP (.iso, .cso, .pbp)
+
+**Process:**
+1. List out all desired games by platform
+2. Note which ones are personal favorites / must-haves
+3. Research ROM availability and file sizes
+4. Prioritize which to add first
+5. Source ROM files (user handles this manually)
+6. Upload via the games platform API or direct file copy
+
+
+## Acceptance Criteria
+
+- [ ] #1 Game wishlist created with at least 10 games across multiple platforms
+- [ ] #2 Games prioritized by platform and personal preference
+- [ ] #3 File size estimates noted for storage planning
+- [ ] #4 Top 5 must-have games identified for first batch
+
diff --git a/backlog/tasks/task-7 - Add-ROM-files-and-populate-games-library.md b/backlog/tasks/task-7 - Add-ROM-files-and-populate-games-library.md
new file mode 100644
index 0000000..06d3aa9
--- /dev/null
+++ b/backlog/tasks/task-7 - Add-ROM-files-and-populate-games-library.md
@@ -0,0 +1,43 @@
+---
+id: TASK-7
+title: Add ROM files and populate games library
+status: To Do
+assignee: []
+created_date: '2026-02-24 07:14'
+labels:
+ - games
+ - content
+dependencies:
+ - TASK-5
+ - TASK-4
+priority: medium
+---
+
+## Description
+
+
+Once games are sourced (from task: Catalog desired retro games), upload ROMs to the games platform and register them in the database.
+
+**Methods to add games:**
+1. **Direct file copy:** SCP ROMs to /data/games/{platform}/ on Netcup, then register via API
+2. **Upload API:** POST to /api/upload with ROM file
+3. **Web UI:** Upload through the games platform interface
+
+**For each game added:**
+- Copy ROM to correct platform directory
+- Add cover art if available
+- Register in database with metadata (title, year, description, genre)
+- Test that it loads in EmulatorJS
+- Verify save states work
+
+**Storage location on Netcup:** /data/games/{ps1,n64,snes,gba,gbc,nes,genesis,dreamcast,psp}/
+
+
+## Acceptance Criteria
+
+- [ ] #1 At least 5 games uploaded and playable
+- [ ] #2 Each game has cover art and metadata
+- [ ] #3 EmulatorJS loads and runs each game
+- [ ] #4 Save states work for at least one game per platform
+- [ ] #5 Games appear correctly in the library browse page
+
diff --git a/package.json b/package.json
index a83fad5..2347872 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,6 @@
"@radix-ui/react-toggle": "1.1.1",
"@radix-ui/react-toggle-group": "1.1.1",
"@radix-ui/react-tooltip": "1.1.6",
- "@vercel/analytics": "latest",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
diff --git a/vpn-setup/README.md b/vpn-setup/README.md
new file mode 100644
index 0000000..bcbbd9b
--- /dev/null
+++ b/vpn-setup/README.md
@@ -0,0 +1,37 @@
+# Jefflix VPN Setup — Headscale + Tailscale
+
+Protects all `*.jefflix.lol` services behind the existing Headscale VPN at `vpn.jeffemmett.com`.
+
+## How It Works
+
+```
+Before (public):
+ Browser → Cloudflare → Tunnel → Traefik → Jellyfin/etc
+
+After (VPN-only):
+ Browser → Tailscale (WireGuard) → Traefik → Jellyfin/etc
+ (Only works if connected to the tailnet)
+```
+
+Traefik still routes by Host header — the only change is how traffic reaches it.
+
+## Quick Start
+
+SSH into the server and follow the phases in order:
+
+```bash
+ssh netcup
+```
+
+Then run `setup.sh` (or follow the manual steps below).
+
+## Files
+
+| File | Purpose |
+|------|---------|
+| `setup.sh` | Full setup script (run on Netcup) |
+| `coredns/Corefile` | CoreDNS config — resolves *.jefflix.lol to Tailscale IP |
+| `coredns/docker-compose.yml` | CoreDNS container definition |
+| `headscale-config-patch.yaml` | Split DNS addition for Headscale config |
+| `cloudflared-config-clean.yml` | Cloudflare tunnel config with jefflix entries removed |
+| `rollback.sh` | Emergency rollback script |
diff --git a/vpn-setup/cutover.sh b/vpn-setup/cutover.sh
new file mode 100755
index 0000000..35c44ca
--- /dev/null
+++ b/vpn-setup/cutover.sh
@@ -0,0 +1,99 @@
+#!/bin/bash
+set -euo pipefail
+
+# ============================================================
+# Jefflix VPN Cutover Script
+# Removes public access to *.jefflix.lol
+# Run ONLY after setup.sh and testing VPN access works
+# ============================================================
+
+echo "========================================"
+echo " Jefflix Public Access Cutover"
+echo "========================================"
+echo ""
+
+# Pre-flight check: verify VPN access works
+TAILSCALE_IP=$(tailscale ip -4 2>/dev/null || echo "")
+if [ -z "$TAILSCALE_IP" ]; then
+ echo "ERROR: Tailscale not running on this server. Run setup.sh first."
+ exit 1
+fi
+
+echo "Server Tailscale IP: $TAILSCALE_IP"
+echo ""
+
+# Check DNS works
+DIG_RESULT=$(dig +short @${TAILSCALE_IP} movies.jefflix.lol 2>/dev/null || echo "FAILED")
+if [ "$DIG_RESULT" != "$TAILSCALE_IP" ]; then
+ echo "ERROR: CoreDNS not resolving correctly. Got: $DIG_RESULT (expected $TAILSCALE_IP)"
+ echo "Fix CoreDNS before proceeding."
+ exit 1
+fi
+echo "✓ CoreDNS resolving correctly"
+
+echo ""
+echo "This will REMOVE public access to all *.jefflix.lol services."
+echo "Users will need Tailscale connected to vpn.jeffemmett.com to access Jefflix."
+echo ""
+read -p "Continue? [y/N] " -n 1 -r
+echo ""
+
+if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+ echo "Aborted."
+ exit 0
+fi
+
+# --- Remove jefflix entries from Cloudflare tunnel ---
+echo ""
+echo "[Cutover] Removing *.jefflix.lol from Cloudflare tunnel..."
+
+TUNNEL_CONFIG="/root/cloudflared/config.yml"
+
+# Backup current config (timestamped)
+cp "$TUNNEL_CONFIG" "${TUNNEL_CONFIG}.pre-cutover-$(date +%Y%m%d-%H%M%S)"
+
+# Remove all jefflix.lol hostname entries (hostname + service lines)
+# This removes the "- hostname: *.jefflix.lol" and its " service:" line
+python3 -c "
+import yaml, sys
+
+with open('$TUNNEL_CONFIG', 'r') as f:
+ config = yaml.safe_load(f)
+
+original_count = len(config.get('ingress', []))
+
+# Filter out jefflix.lol entries
+config['ingress'] = [
+ entry for entry in config.get('ingress', [])
+ if not (isinstance(entry.get('hostname', ''), str) and 'jefflix.lol' in entry.get('hostname', ''))
+]
+
+removed = original_count - len(config['ingress'])
+
+with open('$TUNNEL_CONFIG', 'w') as f:
+ yaml.dump(config, f, default_flow_style=False, sort_keys=False)
+
+print(f' Removed {removed} jefflix.lol entries from tunnel config')
+"
+
+# Restart cloudflared
+echo " Restarting cloudflared..."
+docker restart cloudflared
+echo " ✓ Cloudflared restarted"
+
+# Wait and verify
+sleep 5
+echo ""
+echo "========================================"
+echo " Cutover Complete"
+echo "========================================"
+echo ""
+echo "Public access to *.jefflix.lol is now REMOVED."
+echo ""
+echo "Verify from a non-VPN device:"
+echo " curl -I https://movies.jefflix.lol (should fail/404)"
+echo ""
+echo "Verify from a VPN device:"
+echo " curl http://movies.jefflix.lol (should work)"
+echo ""
+echo "To ROLLBACK: run rollback.sh"
diff --git a/vpn-setup/onboard-user.sh b/vpn-setup/onboard-user.sh
new file mode 100755
index 0000000..bfcf026
--- /dev/null
+++ b/vpn-setup/onboard-user.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+set -euo pipefail
+
+# ============================================================
+# Generate a Jefflix VPN invite for a new user
+# Usage: ./onboard-user.sh [username]
+# ============================================================
+
+USERNAME="${1:-}"
+
+if [ -z "$USERNAME" ]; then
+ echo "Usage: ./onboard-user.sh "
+ echo " Creates a pre-auth key for the user and prints setup instructions."
+ exit 1
+fi
+
+# Generate a single-use pre-auth key (7 day expiry)
+echo "Generating pre-auth key for user: $USERNAME"
+PREAUTH_KEY=$(docker exec headscale headscale preauthkeys create \
+ --user jefflix \
+ --reusable=false \
+ --expiration 168h 2>&1 | tail -1)
+
+echo ""
+echo "============================================================"
+echo " Send the following to $USERNAME:"
+echo "============================================================"
+echo ""
+cat </dev/null || true
+echo " ✓ Backups created"
+
+# --- Phase 1b: Create Headscale users ---
+echo ""
+echo "[Phase 1b] Creating Headscale users..."
+docker exec headscale headscale users create server 2>/dev/null || echo " (user 'server' already exists)"
+docker exec headscale headscale users create jefflix 2>/dev/null || echo " (user 'jefflix' already exists)"
+echo " ✓ Users ready"
+
+# --- Phase 2: Install Tailscale ---
+echo ""
+echo "[Phase 2] Installing Tailscale..."
+if command -v tailscale &>/dev/null; then
+ echo " Tailscale already installed: $(tailscale version)"
+else
+ curl -fsSL https://tailscale.com/install.sh | sh
+ echo " ✓ Tailscale installed"
+fi
+
+# Generate pre-auth key and join tailnet
+echo ""
+echo " Generating pre-auth key..."
+PREAUTH_KEY=$(docker exec headscale headscale preauthkeys create --user server --reusable=false --expiration 1h 2>&1 | tail -1)
+echo " Pre-auth key: $PREAUTH_KEY"
+
+echo ""
+echo " Joining tailnet..."
+tailscale up \
+ --login-server=https://vpn.jeffemmett.com \
+ --authkey="$PREAUTH_KEY" \
+ --hostname=netcup-rs8000 \
+ --accept-dns=false
+
+# Get the Tailscale IP
+TAILSCALE_IP=$(tailscale ip -4)
+echo " ✓ Joined tailnet with IP: $TAILSCALE_IP"
+
+# --- Phase 3: Deploy CoreDNS ---
+echo ""
+echo "[Phase 3] Deploying CoreDNS for *.jefflix.lol..."
+mkdir -p /opt/apps/jefflix-dns
+
+# Write Corefile with the actual Tailscale IP
+cat > /opt/apps/jefflix-dns/Corefile < /opt/apps/jefflix-dns/docker-compose.yml </dev/null || echo "FAILED")
+if [ "$DIG_RESULT" = "$TAILSCALE_IP" ]; then
+ echo " ✓ DNS test passed: movies.jefflix.lol -> $TAILSCALE_IP"
+else
+ echo " ⚠ DNS test returned: $DIG_RESULT (expected $TAILSCALE_IP)"
+ echo " Check CoreDNS logs: docker logs jefflix-dns"
+fi
+
+# --- Phase 4: Configure Headscale split DNS ---
+echo ""
+echo "[Phase 4] Configuring Headscale split DNS..."
+
+HEADSCALE_CONFIG="/opt/apps/headscale-deploy/config/config.yaml"
+
+# Check if split DNS is already configured
+if grep -q "jefflix.lol" "$HEADSCALE_CONFIG"; then
+ echo " Split DNS for jefflix.lol already in config, skipping"
+else
+ # Add split DNS section to the nameservers block
+ # This uses sed to add the split DNS config after the global nameservers
+ sed -i '/nameservers:/,/^[^ ]/ {
+ /global:/,/^ [^ ]/ {
+ /- 1.0.0.1/a\ split:\n jefflix.lol:\n - '"${TAILSCALE_IP}"'
+ }
+ }' "$HEADSCALE_CONFIG"
+ echo " ✓ Split DNS added to Headscale config"
+fi
+
+# Restart Headscale
+cd /opt/apps/headscale-deploy && docker compose restart headscale
+echo " ✓ Headscale restarted"
+sleep 3
+
+# Verify Headscale is healthy
+docker exec headscale headscale nodes list >/dev/null 2>&1 && echo " ✓ Headscale healthy" || echo " ⚠ Headscale may need attention"
+
+echo ""
+echo "========================================"
+echo " Setup Complete (Phases 1-4)"
+echo "========================================"
+echo ""
+echo "Server Tailscale IP: $TAILSCALE_IP"
+echo "CoreDNS: running on $TAILSCALE_IP:53"
+echo "Split DNS: jefflix.lol -> $TAILSCALE_IP"
+echo ""
+echo "NEXT STEPS:"
+echo " 1. Connect YOUR device to the tailnet and test:"
+echo " tailscale up --login-server=https://vpn.jeffemmett.com"
+echo " dig movies.jefflix.lol (should return $TAILSCALE_IP)"
+echo " curl http://movies.jefflix.lol (should return Jellyfin)"
+echo ""
+echo " 2. Once confirmed working, run cutover.sh to remove public access"
+echo " 3. Then onboard users with pre-auth keys"
+echo ""