> An interactive, responsive, and kiosk-ready photo experience celebrating Canadian autumn folklore and Edmonton's river valley magic. Built with **Next.js**, **React**, **TypeScript**, and **Tailwind CSS**.
Users step into the enchanted portal, capture a photo, pick an iconic Canadian/Edmonton Halloween disguise, and receive a transformed 3D cartoon character portrait.
- Silhouettes of the iconic **High Level Bridge**, **Walterdale Arch**, glowing **Muttart Conservatory Pyramids**, and river valley spruce trees.
- Radiant harvest moon, drifting autumn maple leaves, friendly bats, and a cute hovering ghost.
- 🎨 **Stylized 3D Cartoon Character Disguises**:
- 🌟 **Vibe Matcher**: Keep your exact face, outfit, hair & makeup completely unchanged, but replace the background with a funny, exaggerated scene that matches your vibe!
- 🎸 **Classic Mullet**: Keep your photo portrait intact while rocking an iconic 80s mullet hairstyle.
- 🏛️ **Muttart Pyramid Witch**: Whimsical young witch brewing emerald potions inside the glass conservatory pyramid.
- 🦇 **High Level Vampire**: Chibi vampire in a winter velvet coat hovering above the foggy bridge.
- 🧙♂️ **100% Magical Lore**: Zero robotic AI technical jargon. The entire user experience is written as enchanting spooky spellcraft.
- 🕹️ **Tactile Cartoon Arcade UI**:
- Chunky 3D buttons (`.btn-cartoon-orange`, `.btn-cartoon-purple`, `.btn-cartoon-dark`) with satisfying downward click physics.
- Bold comic-book outlines and playful cartoon wobbles.
- 🧭 **Complete Navigation**:
- **`← Back`** buttons across all intermediate screens (Camera, Confirm, Style Selection, and Loading).
- Dedicated **`Finish 🏁`** button on the final result screen to reset the booth.
- Top navigation bars use a strictly non-overlapping 3-column layout that never covers or blocks buttons.
- 📲 **QR Code & Print Ready**:
- On-screen QR code allows visitors to scan and instantly save their portrait to their smartphone.
- Integrated 1-click photo printing formatted for 4x6 print media via `@media print`.
- Safety rejections immediately return the user to the camera/photo capture screen with an interactive Spooky Safety Warning modal to snap or choose another photo.
- No API cooldown lockouts: input rejections never penalize or disable the booth for subsequent guests.
- ⏱️ **Kiosk Inactivity Auto-Reset**: Automatically resets to the standby welcome screen after $N$ seconds of inactivity.
- 🏷️ **Sokaris Studio Attribution**: Built-in footer attribution and copyright link to [sokaris.com](https://sokaris.com).
---
## 🖥️ Display Modes
### 1. Standard Mode (Default)
By default, the booth renders in a centered, vertical 9:16 kiosk viewport (`max-width: 540px`, `max-height: 960px`) on desktop and automatically expands on mobile screens. Ideal for testing, desktop browsing, and tablets:
```
http://localhost:3000
```
### 2. Fullscreen Kiosk Mode (`?mode=fullscreen`)
For physical event touchscreens, vertical digital signage, or dedicated kiosk hardware, add `?mode=fullscreen` to the URL:
```
http://localhost:3000/?mode=fullscreen
```
**What this does:**
1. Expands the viewport to **100% screen width and 100% screen height** (`100vw` x `100vh`).
2. Removes all desktop borders, rounded corners, and margins.
3. Automatically triggers native browser fullscreen (`document.documentElement.requestFullscreen()`) upon the user's first tap.
---
## 🚀 Quick Start
### 1. Prerequisites
- **Node.js** 18.17+ or 20+
- **npm** or **yarn**
### 2. Install Dependencies
```cmd
cmd /c npm install
```
### 3. Configure Environment Variables
Copy `.env.example` to `.env.local` and add your API credentials:
```env
# Google Gemini API Key (Primary Provider)
GEMINI_API_KEY=your_gemini_api_key_here
# OpenRouter API Key (Automatic Backup Provider)
OPENROUTER_API_KEY=your_openrouter_api_key_here
# Model Selection Override (Optional: "nanobanana 2", "nanobanana", "pro", "lite")
GENERATION_MODEL=nanobanana 2
```
*(If no keys are provided, the app will run gracefully in demo fallback mode using pre-rendered sample portraits).*
### 4. Run Development Server
```cmd
cmd /c npm run dev
```
Open [http://localhost:3000](http://localhost:3000) (or [http://localhost:3000/?mode=fullscreen](http://localhost:3000/?mode=fullscreen)) in your browser.
For production deployment instructions on Linux with **PM2**, **Nginx** (`karate.ph.sokaris.link`), and user **`www-data`**, see [`deploy/PM2_PRODUCTION_SETUP.md`](deploy/PM2_PRODUCTION_SETUP.md).
"cameraInstruction":"Strike your most spooktacular pose!",
"cameraSnap":"Snap Spooky Photo",
"cameraUpload":"Upload Photo",
"confirmTitle":"Review Your Spooky Shot",
"styleSelectionTitle":"Pick Your Halloween Disguise",
"styleSelectionSubtitle":"Choose an iconic local legend to transform into",
"processingTitle":"Brewing Your Transformation Spell...",
"processingSubtitle":"Mixing river valley mist, northern starlight & potion sparks...",
"resultTitle":"Your Spooktacular Portrait! 🎃",
"scanQrLabel":"Scan to take your treat home",
"downloadLabel":"Save Portrait",
"printLabel":"Print Photo",
"startOverLabel":"Haunt Again"
}
}
```
### 2. Styles & Prompts (`config/styles.json`)
To add, remove, or modify character disguise presets, edit `config/styles.json`:
```json
[
{
"id":"yeg_hockey_phantom",
"name":"YEG Hockey Phantom",
"description":"Friendly vintage hockey ghost on river ice under the glowing Northern Lights",
"thumbnail":"/assets/styles/hockey_ghost.jpg",
"prompt":"Vibrant 3D cartoon animated character portrait based on the person in the user's photo... Bubbly cartoon animation style, Hotel Transylvania and Pixar aesthetics...",
"negativePrompt":"scary, bloody, gore, grotesque, deformed hands, bad anatomy, realistic human skin pores, text watermark"