Inkai learns the tattoos you love, then surfaces inspiration and real AI-generated design ideas that actually fit your taste — so your next piece feels inevitable, not accidental.
Next.js 16 · React 19 · TypeScript · Tailwind CSS v4 · Supabase
| Personalised dashboard | AI generation studio |
|---|---|
![]() |
![]() |
| Your feed, tuned to the styles and motifs you picked. | Describe an idea → get real tattoo art in your style. |
| Discover gallery | Inkdex — your tattoo "Pokédex" |
|---|---|
![]() |
![]() |
| Browse and filter the inspiration library. | Collect tattoos you spot in the wild — with rarity, streaks & badges. |
Real AI-generated designs (made by the built-in generator, no paid API needed):
- 🎨 Build a style profile — a quick quiz captures the styles, placements, sizes, colours and motifs you gravitate toward. Finish it and Inkai instantly generates a personalised starter board tuned to your answers.
- 🔮 Generate real tattoo art — describe an idea and get concept variations in your style. Works out of the box with no API key (real raster images, not just templates), and gets even better with a free key.
- 🧭 Discover inspiration that fits — a filterable gallery with picks weighted to your taste, plus a "surprise me" button.
- 💾 Save & organise — heart designs and sort them into public or private collections.
- 🖋️ My Ink — a private journal of the tattoos you already wear, with photos kept in a private, owner-only storage bucket.
- 📸 Inkdex — spot great ink on someone in the real world? Snap it (mobile camera or gallery), log who / where / when with a note, and build a gamified, collectible catalog with rarity tiers, streaks, and milestone badges. Private by default.
Every table is protected by Row-Level Security, private photos are served via short-lived signed URLs, and all AI calls happen server-side so your keys never reach the browser.
Inkai also includes The Canvas (a private front/back body map of your My Ink entries), an attributable Artist & Shop Directory, permission-gated nearby-studio search, and tentative appointment requests with a direct booking fallback and calendar export. Directory listings never copy artist portfolio images: approved AI concepts are labelled as such and external work links back to its attributed source.
You'll need Node.js 20.9+.
npm install
npm run dev # http://localhost:3000Copy .env.example to .env.local and add your Supabase details (below) to
unlock accounts, the gallery, saving, and persistence. Generation works with
no key at all — the built-in engine produces real images immediately.
Just want a look with zero setup?
INKAI_PREVIEW=1 npm run devrenders the whole UI with sample data (run it without the Supabase values set). This is local-preview mode only; never configureINKAI_PREVIEWin a deployed environment.
-
Create a project at supabase.com — the free tier is plenty.
-
Run the migrations. Paste the files in
supabase/migrations/into the Supabase SQL editor in order, or use the CLI (supabase db push):Migration What it does 0001_init.sqlTables, enums, indexes, RLS policies, new-user trigger. 0002_storage.sqlavatars,designs(public) andtattoos(private) buckets + policies.0003_hardening.sqlSecurity hardening (pins search_path, locks down the bootstrap function).0004_inkdex.sqlThe inkdex_entriestable + the privateinkdexbucket, all owner-scoped.0005_security_hardening.sqlOwner-only profile/storage policies and atomic generation quota reservation. 0006_inkdex_explicit_sighting.sqlExplicit photo-and-story guard for Inkdex inserts. 0007_artist_shop_directory.sqlPublic-read, service-role-write directory tables and copyright-safe style pieces. 0008_appointment_requests.sqlOwner-scoped appointment requests and lifecycle indexes. -
Fill in
.env.local:NEXT_PUBLIC_SUPABASE_URL=https://<ref>.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon / publishable key> SUPABASE_SERVICE_ROLE_KEY=<service role key> # server-only; needed for seeding
-
Harden Storage after applying the migrations. This makes private media buckets private, applies the allowed image MIME types/size limit, and checks their owner-scoped policies:
npm run harden:storage
-
Seed the gallery (curated designs, made by the built-in engine — no image API needed):
npm run seed
-
Optionally seed the small, attributable directory starter catalog. It contains no scraped portfolio images:
npm run seed:directory
-
Restart
npm run dev, create an account, complete onboarding, and you're in.
OAuth (Google / GitHub) buttons appear automatically once Supabase is configured. Enable the providers in Supabase → Authentication → Providers and add
<site>/auth/callbackas a redirect URL.
The generator is provider-agnostic. With AI_PROVIDER unset (or local), Inkai
resolves a default chain automatically and tries each in order until one
works — so generation never hard-fails:
- Gemini ("Nano Banana",
gemini-2.5-flash-image) — used if aGEMINI_API_KEYis set. Best quality. (Note: Gemini image generation now requires a billing-enabled Google Cloud project.) - Pollinations — keyless, zero setup. This is what produces real raster tattoo art out of the box, before you touch any config.
- Local — a built-in procedural vector "flash" engine. The unconditional last resort, so a result always comes back.
The UI shows which provider actually produced each result. To use a specific
paid provider instead, set AI_PROVIDER=openai (OPENAI_API_KEY) or
AI_PROVIDER=replicate (REPLICATE_API_TOKEN).
The Canvas can optionally ask Gemini for one fixed body-zone suggestion from a
photo. It uses the same GEMINI_API_KEY, defaults to
GEMINI_VISION_MODEL=gemini-2.5-flash, and always requires the member to
confirm or correct the suggested placement before saving.
Cost & abuse control: each user is capped at DAILY_GENERATION_LIMIT
(default 30) real generations per day, rapid-fire requests are throttled, and
identical recent requests are served from cache instead of paying for a new
call. Remote images are re-hosted in Supabase Storage so links never expire.
-
Push to GitHub and import the repo at vercel.com/new — Next.js is auto-detected, no build config needed.
-
Add environment variables (Project → Settings → Environment Variables):
Variable Value NEXT_PUBLIC_SUPABASE_URLhttps://<ref>.supabase.coNEXT_PUBLIC_SUPABASE_ANON_KEYyour anon / publishable key SUPABASE_SERVICE_ROLE_KEYservice-role key (server-only; for seeding) GEMINI_API_KEY(optional) for best-quality generation GOOGLE_MAPS_API_KEY(optional, server-only) Geocoding + Places API (New) nearby search RESEND_API_KEY(optional, server-only) appointment request email hand-off RESEND_FROM_EMAIL(optional) verified Resend sender, e.g. Inkai <bookings@example.com>NEXT_PUBLIC_SITE_URLcanonical production URL for auth redirects Generation works with no key, so you can ship first and add a key later.
The directory still works without Maps; it returns only curated records. Appointment requests still save without Resend and show the target's direct booking contact instead. Never set
INKAI_PREVIEW=1in Vercel or any other deployed environment. -
Deploy once, then set
NEXT_PUBLIC_SITE_URLto your production URL and redeploy — this is what auth redirects point at. -
Point Supabase auth at production: in Authentication → URL Configuration, add
https://<your-domain>/auth/callbackas a redirect URL. -
Run migrations
0001through0008in order, then runnpm run harden:storage,npm run seed, and optionallynpm run seed:directory. The migrations are idempotent; keep the service role key server-only and never add it to a browser variable.
src/
app/
(marketing)/ # public landing
(auth)/ # login / signup
(app)/ # authenticated shell: dashboard, gallery, generate,
# collections, my-ink, canvas, directory, appointments,
# inkdex, settings
onboarding/ # style-profile quiz
components/ # ui/ primitives, features/, app shell, marketing…
lib/
ai/ # provider chain (gemini, pollinations, openai, replicate)
# + procedural SVG engine fallback
supabase/ # browser / server / admin clients + typed schema
constants.ts # the tattoo taxonomy (styles, placements, motifs…)
server/
queries/ # read-side data access (server-only)
actions/ # 'use server' mutations
proxy.ts # Next 16 middleware (session refresh + route protection)
supabase/migrations/ # SQL schema, RLS, storage
| Script | Purpose |
|---|---|
npm run dev |
Development server (Turbopack). |
npm run build |
Production build. |
npm run seed |
Seed curated public designs (needs SUPABASE_SERVICE_ROLE_KEY). |
npm run seed:directory |
Seed the attributable Artist & Shop Directory starter catalog. |
npm run harden:storage |
Apply private-bucket configuration and storage policy checks. |
npm run typecheck |
tsc --noEmit. |
npm run lint |
ESLint. |
Built with care. Ink that feels like you.







