Files
2026-05-06 12:22:17 +02:00
..
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-06 12:22:17 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-06 12:22:17 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00
2026-05-05 16:52:40 +02:00

Smash or Pass — Frontend

Vue 3 + TypeScript + Vite + Pinia + Tailwind CSS v4. Talks to the sop-back FastAPI server.

See ../CLAUDE.md for full architecture notes.


Stack

  • Vue 3 with <script setup lang="ts"> (TypeScript everywhere)
  • Vite 8 (dev server + bundler)
  • Vue Router (SPA: /, /game/:id, /summary)
  • Pinia (game state: queue / smashed / passed)
  • Tailwind CSS v4 via @tailwindcss/vite. Custom palette + fonts in src/assets/main.css inside an @theme { ... } block.

Project layout

sop-front/
├── index.html
├── vite.config.ts              # Tailwind v4 plugin + alias '@'
├── nginx.conf                  # SPA fallback for production image
├── Dockerfile                  # multi-stage: vite build → nginx
└── src/
    ├── main.ts                 # imports ./assets/main.css
    ├── App.vue                 # only <RouterView />
    ├── router/index.ts
    ├── api/client.ts           # typed fetch wrapper, reads VITE_API_BASE_URL
    ├── stores/game.ts          # Pinia store
    ├── assets/main.css         # Tailwind + @theme tokens (palette, fonts)
    ├── components/
    │   ├── SwipeCard.vue       # pointer-event drag, exposes triggerSmash/Pass
    │   └── AdminPanel.vue
    └── views/
        ├── HomeView.vue
        ├── GameView.vue
        └── SummaryView.vue

Configuration

One environment variable, build-time (Vite inlines it):

Var Default Notes
VITE_API_BASE_URL http://localhost:8000 Backend origin

For dev, set in a .env.local:

VITE_API_BASE_URL=http://localhost:8000

For Docker builds, pass it as a build arg (already wired in the repo-root docker-compose.yml):

frontend:
  build:
    context: ./sop-front
    args:
      VITE_API_BASE_URL: "https://api.example.com"

Local development

cd sop-front
npm install
npm run dev
  • App: http://localhost:5173
  • Make sure the backend is running at VITE_API_BASE_URL (default http://localhost:8000).

Other scripts

Command What
npm run dev Vite dev server with HMR
npm run build Type-check + production build into dist/
npm run type-check vue-tsc --build only
npm run preview Serve the built dist/ locally
npm run lint oxlint + eslint
npm run format Prettier
npm run test:unit Vitest

Customizing colors / fonts

All design tokens live in src/assets/main.css under @theme. The palette is graded (Tailwind-style): each hue has a 50900 scale, with 500 as the canonical brand value. Add new shades there; don't hardcode hex values in components.

Scales (50 lightest → 900/950 darkest)

Family 500 (brand) Use
--color-purple-{50…900} #8324DE Primary brand
--color-red-{50…900} #FF453B Pass / errors / destructive
--color-lime-{50…900} #B9D532 Smash / success
--color-neutral-{50…950} #63586E Backgrounds, surfaces, text, borders

Components reference shades like var(--color-purple-300), var(--color-neutral-800), etc. The neutral scale handles dark-mode surfaces (50 = off-white text, 950 = #16141A background).

Semantic aliases (prefer these in components)

These point at scale values — change them once in main.css to retheme:

Alias → Scale Role
--color-primary purple-500 CTAs / brand
--color-primary-hover purple-400 CTA hover
--color-smash lime-500 Smash action
--color-pass red-500 Pass action
--color-bg neutral-950 App background
--color-surface neutral-900 Card surface
--color-surface-2 neutral-800 Elevated surface
--color-border neutral-700 Borders
--color-text neutral-50 Primary text
--color-text-muted neutral-300 Secondary text

Fonts: Bebas Neue (display) + Roboto (body), loaded via Google Fonts in main.css.


Production deployment

A multi-stage Dockerfile builds the SPA with Vite and serves the static bundle with nginx. From the repo root:

docker compose up -d --build frontend

The image listens on port 80 inside the container; the compose file maps it to host :8080.

For a real deployment:

  1. Set VITE_API_BASE_URL to the public backend URL at build time (build arg, not runtime env). Rebuild the image whenever it changes.
  2. Front it with HTTPS — Caddy / Traefik / Cloudflare in front of port 80.
  3. The bundled nginx.conf already does SPA fallback (try_files $uri /index.html).
  4. Make sure the backend CORS ALLOWED_ORIGINS includes the frontend's public origin.

Static hosting (no Docker)

npm install
VITE_API_BASE_URL=https://api.example.com npm run build

Upload the contents of dist/ to any static host (S3+CloudFront, Netlify, Vercel, GitHub Pages, etc.). Configure SPA fallback so any unknown path serves index.html.


Notes

  • VITE_API_BASE_URL is baked into the JS bundle at build time. Changing it requires a rebuild.
  • All API calls go through src/api/client.ts — extend that wrapper rather than calling fetch directly from components.
  • The admin panel renders only when GET /admin/status returns admin_enabled: true. That flag lives in the backend's .env.