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 insrc/assets/main.cssinside 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(defaulthttp://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. Add new colors there as --color-<name>: #hex; and they become available as Tailwind utilities (bg-<name>, text-<name>) and as var(--color-<name>). Don't hardcode hex values in components.
Current palette comes from the user's reference image:
| Token | Hex | Role |
|---|---|---|
--color-clothes |
#8324DE |
Primary purple (CTAs) |
--color-smash / --color-tongue |
#B9D532 |
Smash green |
--color-pass / --color-iris |
#FF453B |
Pass red |
--color-bg |
#16141A |
Dark grey background |
--color-surface |
#1F1C24 |
Card surface |
--color-fur |
#FBF9FD |
Off-white text |
Fonts: Bebas Neue (display) + Inter (body), loaded via Google Fonts in main.css.
Production deployment
Docker (recommended)
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:
- Set
VITE_API_BASE_URLto the public backend URL at build time (build arg, not runtime env). Rebuild the image whenever it changes. - Front it with HTTPS — Caddy / Traefik / Cloudflare in front of port 80.
- The bundled
nginx.confalready does SPA fallback (try_files $uri /index.html). - Make sure the backend CORS
ALLOWED_ORIGINSincludes 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_URLis 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 callingfetchdirectly from components. - The admin panel renders only when
GET /admin/statusreturnsadmin_enabled: true. That flag lives in the backend's.env.