πŸ—ΊοΈ The Big Picture

EmojiLab is a multi-page web app. Players spin emoji reels, get an AI-generated nickname read aloud, vote on each other's nicknames in a live feed, and compete on a daily leaderboard. A standalone prototype called the Avatar Generator lets players turn a nickname into a personalised PokΓ©mon-style creature card.

All the pages are static HTML files β€” just files your browser downloads and runs. All the server-side logic lives in two Cloudflare Workers.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Cloudflare Pages (static HTML) β”‚ β”‚ index Β· feed Β· leaderboard Β· player Β· avatar β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ emojilab β”‚ β”‚ nickname-avatar β”‚ β”‚ Cloudflare Worker β”‚ β”‚ Cloudflare Worker β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ /api/nickname β”‚ β”‚ /api/madlibs β”‚ β”‚ /api/feed β”‚ β”‚ /api/avatar β”‚ β”‚ /api/vote β”‚ β”‚ β”‚ β”‚ /api/leaderboard β”‚ β”‚ Cloudflare AI: β”‚ β”‚ /api/player/:id β”‚ β”‚ Llama 3.1 + SDXL β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Groq + D1 database β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🧱 The Three Layers of Every Web Page

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ HTML β†’ the SKELETON β”‚ β”‚ "there is a slot machine here" β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ CSS β†’ the SKIN β”‚ β”‚ "the machine is dark purple β”‚ β”‚ with gold glowing reels" β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ JavaScript β†’ the BRAIN β”‚ β”‚ "when the reel is dragged, β”‚ β”‚ spin it and call the AI" β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

All three live inside each HTML file β€” CSS between <style> tags, JavaScript between <script> tags, structure between the <body> tags.

🎰 The Reel System

Each reel is an instance of the Reel class β€” a tall scrolling strip of emojis inside a window that only shows 3 at a time:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 🐢 β”‚ ← top (partially visible, curled away) │══════════│ ← gold selection line β”‚ 🦊 β”‚ ← selected (centre, full size) │══════════│ ← gold selection line β”‚ 🐼 β”‚ ← bottom (partially visible, curled away) β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The strip is actually 16Γ— longer than it appears β€” it repeats the emoji list 16 times so you can scroll endlessly without running out. When you flick fast, a momentum/friction loop keeps it moving and snaps to the nearest slot. The 3D cylinder effect is pure CSS: emojis near the edges are scaled down and rotated on the X axis to look like they're curling around a drum.

Every page load picks 12 random animals from a pool of 35 using a Fisher-Yates shuffle, so the reels feel fresh on every visit.

πŸ‘£ Step-by-Step: Making a Nickname

1 Drag the reels to chosen emojis

The app uses the Pointer Events API (pointerdown, pointermove, pointerup) which works identically on touchscreens and desktop. Dragging slowly gives precise control; flicking fast gives a momentum spin.

2 Tap "Make My Nickname!"
const animal = reels[0].selectedEmoji;   // e.g. 🦊
const color  = reels[1].selectedEmoji;   // e.g. πŸ’™
const vibe   = reels[2].selectedEmoji;   // e.g. ⚑
3 The browser calls the Worker
Browser β†’ POST /api/nickname { animal: "🦊", color: "πŸ’™", vibe: "⚑", creator_id: "uuid-from-localstorage" } ↓ Cloudflare Worker Β· builds AI prompt with ban lists Β· calls Groq API (Llama 3.3 70B) Β· saves nickname to D1 database Β· returns nickname + transfer_code ↓ Browser displays result
4 The result overlay appears

The nickname pops up with a bounce animation and confetti. The Web Speech API reads it aloud β€” each word highlights yellow as it's spoken so early readers can follow along.

5 Play Again navigates to the feed

A coin-drop animation plays, then the app navigates to feed.html where the new nickname appears at the top, encouraging the player to vote on others.

πŸ€– The AI Prompt System

The Worker builds the nickname prompt using three layers that work together to keep results fresh and varied:

Layer 1 β€” Static ban list

Common adjectives like Happy, Silly, Cute, Golden, Brave are permanently banned so the AI never falls back on boring defaults.

Layer 2 β€” Global last-8 avoidance

The Worker queries D1 for the last 8 nicknames generated by anyone and appends them as words to avoid. This prevents the same word appearing twice in a row across all players.

Layer 3 β€” Rolling dynamic ban

The last 100 nicknames are analysed. Any word appearing 5% or more of the time is temporarily banned (max 8 words). Animal names are protected from this ban so the core nickname structure stays intact.

Prompt styles

The AI is given a randomly selected style instruction on each call. Honorific style (Dr. / Professor / Captain / Chef) is picked 3Γ— more often than others because it produces the most interesting results. The style used is saved to D1 for analysis.

The model: Llama 3.3 70B on Groq's API β€” fast, free (14,400 requests/day), no credit card required. Get a key at console.groq.com.

πŸ—„οΈ The Database

Cloudflare D1 is a SQLite database built into Cloudflare Workers β€” no separate service to set up. The schema has five tables:

🎭 Creator Identity

Every visitor gets a UUID generated in their browser and stored in localStorage β€” no account or password required. This UUID is the creator_id sent with every API call.

On first nickname generation, the Worker creates a human-readable transfer code (e.g. SUNNY-FOX-4829) stored in both D1 and localStorage. Players use this code to restore their profile on a new device by visiting player.html#SUNNY-FOX-4829.

The player profile page shows stats, all past nicknames, and a QR code for easy device transfer.

πŸ“‘ The Live Feed

feed.html polls GET /api/feed every 10 seconds. When new cards arrive they animate in with a pivot-and-slide entrance:

When multiple cards arrive at once, the current player's card is sorted to enter first, the first three animate with 200ms stagger, and any additional cards appear instantly below.

πŸ† Leaderboards

The daily leaderboard shows the top cards created today in EST, ordered by net score (likes minus dislikes). It refreshes live β€” scores update in real time as people vote.

A cron job runs at midnight EST to snapshot the day's #1 card into daily_leaderboard. This becomes the "Card to Beat" shown at the top of the daily tab the next day β€” a permanent record of past champions.

The all-time leaderboard shows the top 25 cards by net score across all time, with a badge showing how many days each card has appeared on the board.

🎴 The Avatar Generator

The avatar generator (avatar.html) is a standalone prototype that creates PokΓ©mon-style creature cards. It uses a three-step AI pipeline:

1 Mad lib questions

Llama 3.1 8B generates 3 child-friendly sentences with blanks tailored to the creature β€” e.g. "My fox's most powerful ability is ___". Each blank has 4 emoji suggestions. The player picks one emoji per blank.

2 Image prompt chain

Llama writes a rich, detailed image generation prompt incorporating both the original emojis and the mad lib answers. The answers must visibly appear in the illustration β€” Llama is explicitly instructed to reflect them in the creature's design.

3 Image generation

The prompt is sent to @cf/bytedance/stable-diffusion-xl-lightning on Cloudflare's free tier. Three images are generated with different random seeds so the player can pick their favourite. Each is rendered inside a holofoil PokΓ©mon-style card with type badges, HP, and AI-generated flavour text.

Getting there: From the player profile page, tap 🎴 Make a Card on any of your nicknames. The avatar page opens with all the emojis and nickname pre-filled, ready to generate.

☁️ What Is a Cloudflare Worker?

A Cloudflare Worker is a tiny piece of code that runs on Cloudflare's global network β€” only when called. No server to manage; it scales automatically and runs on demand. The free tier includes 100,000 requests per day.

This app uses two Workers:

Workers also handle CORS β€” a browser security rule requiring servers to explicitly allow requests from different domains. Since the frontend and Workers are on different domains, every Worker response includes the necessary headers.

πŸ”Š Text-to-Speech

After a nickname appears, the app reads it aloud using the browser's built-in Web Speech API β€” no external service needed. It speaks the intro phrase "Your nickname is" then each word individually, slowly and clearly. As each word is spoken, it highlights yellow on screen so early readers can follow along.

TTS also works on the feed, leaderboard, and player profile pages β€” tap any nickname card to hear it read aloud. The app prefers Samantha on iOS or Google US English in Chrome.

πŸš€ How Deployment Works

Every push to main triggers three CI jobs in parallel:

git push β†’ CI pipeline β”‚ β”œβ”€β”€ pages β”‚ copies HTML files β†’ Cloudflare Pages β”‚ β†’ https://emojilab.pages.dev β”‚ β”œβ”€β”€ deploy-emojilab β”‚ deploys src/worker.js β†’ Cloudflare β”‚ (wrangler.emojilab.toml) β”‚ └── deploy-nickname-avatar deploys avatar-worker/src/worker.js β†’ Cloudflare (avatar-worker/wrangler.nickname-avatar.toml)

Each Worker job has an isolated environment and an explicit --name flag to prevent the two Workers from being accidentally deployed to each other's slot.

πŸ“š Ready to Go Deeper?

Each guide is written for curious beginners β€” no experience needed.