Agency portfolio site. Cinematic 3D galaxy hero presenting seven case studies (RSR · Inside-Out · Cognitive Rise · Warble · Homie · iGaming CRM · Fintech Dashboard). Eight locales (EN/UK/DE/ES/FR/NL/PL/PT). Self-hosted infrastructure, zero-cookie analytics.
Live at air.spoko.dev. Source open for inspection.
- Framework: Next.js 16.2 (App Router, RSC, TS strict, Turbopack), React 19.2
- 3D: React Three Fiber 9 + drei 10
- Styling: Tailwind CSS 4 (CSS-first, OKLCH tokens via
@theme) - i18n: next-intl 4 (8 locales,
localePrefix: "as-needed") - Forms: Server Actions + Zod
- Email: nodemailer → self-hosted Postal SMTP
- Errors: Sentry (
spokodev-lh/spoko-studio) - Smooth scroll: Lenis
- Motion: Framer Motion 12 (
motion) - Analytics: Cloudflare Web Analytics (cookieless)
- Package manager: pnpm 10.x
- Node: 22+
- Cinematic modal extracts from clicked-planet origin: CSS
@keyframesdriven bytransform-originderived from Three.jsVector3.project(camera)→ screen NDC. No Framer-Motion runtime cost on the modal itself. - OKLCH design tokens via Tailwind 4's
@theme— perceptually uniform palette, WCAG AAA contrast verified. Seeapp/globals.css. - Per-case dynamic Open Graph cards generated at request time via
next/og+ Satori. Seeapp/[locale]/work/[slug]/opengraph-image.tsx. - Auto-translation pipeline preserves manual polish — see Locale workflow.
- Pattern A deploy: GitHub Actions self-hosted runner → GHCR → Traefik on a single VPS, with health-checked rollout + automatic rollback on probe failure.
pnpm install
pnpm dev # http://localhost:3000
pnpm typecheck # tsc --noEmit
pnpm lint # eslint
pnpm build # production build (Next standalone output)
pnpm translate # fill missing keys in non-EN locales (needs ANTHROPIC_API_KEY)Production CSP is strict; dev allows 'unsafe-eval' for React HMR only.
EN is the source of truth. UK is hand-polished. The other six (DE, ES, FR, NL, PL, PT) are bootstrapped automatically from EN — manual edits in any locale are preserved across runs (the script only adds missing keys).
Add a key:
- Edit
messages/en/<namespace>.json - Run
pnpm translate - Optionally hand-polish target locales
A new namespace must be added to NAMESPACES in i18n/request.ts.
Pattern A — GitHub Actions self-hosted runner on the same VPS that serves traffic:
- Push to
main→ runner builds Docker image → pushes toghcr.io/spokodev/spoko-studio:latest(and:sha) - Trivy security scan (non-blocking)
- SSH-deploy:
docker compose pull web && docker compose up -d webat/opt/spoko-studio/ - Container healthcheck + external probe through
/api/healthwith rollback to previous SHA on failure - Telegram notification on success/failure
Branch protection on main requires CI (lint + typecheck + build) to pass before merge. See .github/workflows/.
app/
[locale]/ — localized routes (page, about, contact, work/[slug], privacy, error)
layout.tsx — root layout (metadata, fonts, header/footer, providers)
opengraph-image.tsx — generated brand OG card
work/[slug]/
page.tsx
opengraph-image.tsx — per-case dynamic OG card
api/health/ — liveness probe
not-found.tsx, global-error.tsx
sitemap.ts, robots.ts
components/
galaxy/ — R3F 3D scene (planets, camera, particles)
hero/, project/, case/, contact/, nav/, seo/, cursor/, scroll/
i18n/
routing.ts, request.ts, navigation.ts, locales.ts
lib/
projects.ts — 7 case studies (data + content)
contact/ — schema, rate-limit, notify (email + Telegram)
seo/alternates.ts — hreflang helper
messages/
{en,uk,de,es,fr,nl,pl,pt}/{common,home,about,contact,case,privacy,errors}.json
scripts/
translate.ts — locale bootstrap pipeline (Anthropic SDK)
capture-screenshots.ts — Playwright + sharp screenshot capture
See LICENSE.md. Source-available for inspection and learning. Brand assets, copy, and case-study content remain Spoko Studio property.