CI (.gitea/workflows/ci.yaml): lint → typecheck → vitest w/ coverage → OpenNext build → preview smoke → bundle-size budgets; Node 20; npm ci; artifacts; safe env; D1 dry-run scaffold. Budgets: add scripts/budgets.mjs; TOTAL_STATIC_MAX_BYTES and MAX_ASSET_BYTES thresholds; report top offenders; fail on breach; README CI section. Flags: add lib/flags.ts with typed booleans and safe defaults (ADMIN_ENABLED, ARTISTS_MODULE_ENABLED, UPLOADS_ADMIN_ENABLED, BOOKING_ENABLED, PUBLIC_APPOINTMENT_REQUESTS_ENABLED, REFERENCE_UPLOADS_PUBLIC_ENABLED, DEPOSITS_ENABLED, PUBLIC_DB_ARTISTS_ENABLED, ADVANCED_NAV_SCROLL_ANIMATIONS_ENABLED, STRICT_CI_GATES_ENABLED, ISR_CACHE_R2_ENABLED); robust parsing; client provider; unit tests. Wiring: gate Admin shell and admin write APIs (503 JSON on uploads and artists writes); disable booking submit and short-circuit booking mutations when off; render static Hero/Artists when advanced animations off; tests for UI and API guards. Ops: expand docs/prd/rollback-strategy.md with “Feature Flags Operations,” Cloudflare Dashboard and wrangler.toml steps, preview simulation, incident playbook, and post-toggle smoke checklist. Release: add docs/releases/2025-09-19-feature-flags-rollout.md with last-good commit, preview/production flag matrices, rollback notes, and smoke results; link from rollback doc. Chore: fix TS issues (gift-cards boolean handling, Lenis options, tailwind darkMode), remove next-on-pages peer conflict, update package.json scripts, configure Gitea act_runner label, open draft PR to trigger CI. Refs: CI-1, FF-1, FF-2, FF-3, OPS-1 Impact: defaults preserve current behavior; no runtime changes unless flags flipped
United Tattoo — Official Website (Next.js + ShadCN UI)
Hi, I’m Nicholai. I built this site for my friend Christy (aka Ink Mama) and the United Tattoo crew in Fountain, CO. The goal was simple: give the studio a site that actually reflects the art, the people, and the experience — not the stiff, generic stuff you usually see. This is also a thank you for everything Christy has done for Amari (my girlfriend and soulmate), who was her apprentice. So yeah, this is personal — and it shows.
This repo powers the official United Tattoo website, built with:
- Next.js App Router
- TypeScript
- Tailwind CSS
- ShadCN UI components (used across all pages)
- Lenis (smooth scroll)
- Lucide (icons)
Live dev server: http://localhost:3000
Project Structure
-
app/
- page.tsx — homepage (Hero, Artists, Services, Contact sections)
- aftercare/page.tsx — aftercare instructions (ShadCN-driven)
- deposit/page.tsx — deposit policy + payment options (Afterpay, Stripe)
- terms/page.tsx — terms of service
- privacy/page.tsx — privacy policy
- artists/ — artists listing + dynamic routes for profiles (coming from data)
- book/page.tsx — booking flow
- specials/page.tsx — promotions (monthly specials, VIP list)
- contact/page.tsx — contact
- gift-cards/page.tsx — gift card info
-
components/
- hero-section.tsx, artists-section.tsx, services-section.tsx, contact-section.tsx
- aftercare-page.tsx, deposit-page.tsx, terms-page.tsx, privacy-page.tsx
- booking-form.tsx — multi-step form using ShadCN components
- footer.tsx — contains direct links to Aftercare, Deposit Policy, Terms, and Privacy
- ui/ — ShadCN UI primitives
-
data/
- artists.ts — single source of truth for artist metadata and images used across pages
-
public/
- united-logo-*.png/jpg, artists/, and other stable assets
Content & Assets
- All the “real” content, bios, and images are now wired in (not seed placeholders).
- Artist portraits and tattoo samples live under public/artists/.
- Pages like Aftercare, Deposit, Terms, and Privacy use consistent styling patterned after the homepage and portfolio pages — powered by ShadCN components.
If you need to re-copy images from the temp folder into public (on your machine), there’s a helper script:
- ./copy-artist-images.sh Note: This script expects the temp directory structure to exist locally; it silently skips if a source is missing.
Getting Started (Local Dev)
-
Install deps:
- npm install
-
Run dev server:
- npm run dev
- Open http://localhost:3000
-
Lint (optional):
- npm run lint
Build:
- npm run build
- npm start
Continuous Integration (CI)
This repo includes a CI workflow that enforces linting, type safety, unit tests, build/preview, and bundle size budgets.
- Workflow file:
.gitea/workflows/ci.yaml - Triggers: Push and PR against
main/master - Node: 20.x with
npm ci
Stages
- Lint:
npm run ci:lint(ESLint) - Typecheck:
npm run ci:typecheck(TypeScript noEmit) - Unit tests:
npm run ci:test(Vitest with coverage) - Build:
npm run ci:build(OpenNext build to.vercel/output/static) - Preview smoke: start OpenNext preview briefly to ensure no immediate crash
- Budgets:
npm run ci:budgets(analyzes.vercel/output/static)
Bundle Size Budgets
- Defaults are defined in
package.jsonunder thebudgetskey:TOTAL_STATIC_MAX_BYTES: 3,000,000 (≈3 MB)MAX_ASSET_BYTES: 1,500,000 (≈1.5 MB)
- Override via environment variables in CI:
TOTAL_STATIC_MAX_BYTESMAX_ASSET_BYTES
Artifacts
- A budgets report is written to
.vercel/output/static-budgets-report.txtand uploaded as a CI artifact.
Migration Dry-Run (D1)
- The workflow attempts a best‑effort local dry‑run:
wrangler d1 execute united-tattoo --local --file=./sql/schema.sql. - If local bindings are unavailable in CI, the step is skipped with a note; wire it up later when CI bindings are configured.
Rollback Strategy
- This story does not deploy. To disable CI temporarily, remove or rename the workflow file or adjust failing stages. For full rollback strategy see
docs/prd/rollback-strategy.md.
Docker
This repo is docker-ready. We build a standalone Next.js app for a smaller runtime image.
Build image:
- docker build -t united-tattoo:latest .
Run container (port 3000):
- docker run --rm -p 3000:3000 -e PORT=3000 united-tattoo:latest
- Open http://localhost:3000
Notes:
- next.config.mjs sets output: "standalone"
- The Dockerfile copies .next/standalone + .next/static and runs the server with HOSTNAME=0.0.0.0
Pages Overview
- Home — Bold, high-contrast, split imagery, parallax accents. This sets the identity.
- Artists — Grid and profile surfaces wired to data/artists.ts. Each artist shows image, specialties, and sample work.
- Aftercare — Two flows: General Aftercare and Transparent Bandage Aftercare (accurate, readable, ShadCN cards + alerts).
- Deposit — Clear policy, payment options, and compliance notes (LW2 Investments, LLC oversight).
- Terms & Privacy — Straightforward, legally sound, human-readable. Both accessible from the footer.
- Booking — Multi-step form with ShadCN components and validation-friendly structure.
- Specials — Marketing surface for time-bound promotions and membership-like advantages.
Design Language
- ShadCN components everywhere possible
- Monochrome foundation with high contrast and cinematic image splits
- Type scales + spacing match the homepage/portfolio feeling
- Lucide icons for affordances
Tech Notes
- TypeScript errors are ignored during build in CI to allow non-blocking content/design iteration (next.config.mjs).
- Images are unoptimized (no Next image loader), served statically; change if you plan to put this behind a CDN with transforms.
- Smooth scroll and parallax-style offsets are kept subtle to let the work shine.
Deployment
- Standard Next.js deploys work (Vercel, Node server, Docker)
- For self-hosting or VPS, use the Dockerfile in the repo
- The site runs on port 3000 by default
Why This Exists
Because Christy deserved a proper site — and because the previous one was, bluntly, not it. United Tattoo is more than a shop. It’s a community with real people and real art. This site tries to honor that.
— Nicholai