Your humor. Our AI. Upload a photo, let AI write captions, add your own, and see whose caption wins.
SocialTypeScript · PLpgSQLGitHub @aa5558OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Next.js app deployed to Vercel
Translation pending
Loading the full README.
Read the full README ↗This is a Next.js project bootstrapped with create-next-app.
Live: https://hello-world-nextjs-eight-blue.vercel.app
First, run the development server:
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
Open http://localhost:3000 with your browser to see the result.
You can start editing the page by modifying app/page.tsx. The page auto-updates as you edit the file.
This project uses next/font to automatically optimize and load Geist, a new font family for Vercel.
To learn more about Next.js, take a look at the following resources:
You can check out the Next.js GitHub repository - your feedback and contributions are welcome!
This app gates /profile behind Supabase Auth using Google as the OAuth provider. To wire it up on a fresh Supabase project:
supabase/migrations/20260928000000_profiles.sql. It creates the profiles table, a trigger that inserts a row for every new auth.users signup, and an avatars storage bucket with policies scoping uploads to each user's own folder.https://<your-project-ref>.supabase.co/auth/v1/callback
http://localhost:3000/auth/callback
https://<your-vercel-app>/auth/callback
SUPABASE_URL, SUPABASE_ANON_KEY) are already server-side only — no changes needed there.New users are redirected to /profile after their first sign-in and prompted to fill in their name if it's missing. Signed-out visitors hitting /profile are redirected to /login.
Can Columbia students be funnier than AI? Signed-in users upload a photo, pick a caption voice, and Gemini writes four captions. Anyone signed in can then add their own caption to any photo. Human and AI captions are shown together with the author hidden; vote on one and it reveals whether a classmate or Gemini wrote it. A running scoreboard tracks which side wins more photos.
/): an editorial hero with the latest posts fanned out like polaroids, the Humans vs. Gemini scoreboard, today's challenge, and the feed as a masonry of battle cards or a photo wall (newest or top this week)./create): photo (upload with drag-and-drop, or pick a library photo such as today's drop), voice, and context, with a live preview and a step-by-step "the AI is cooking" sequence. Lands on the new post with a prompt to beat Gemini./challenge): a new prompt every midnight New York time, plus a featured photo when an admin has scheduled one, live photo and caption counts, the leaderboard, and yesterday's winner with its author revealed./me): your "era" (from the voice you use most), achievements computed only from what you've actually done, your photos with every caption on them, and your own captions with score and rank.battle_result(), never while voting is open)./surprise): two modes. Fresh photo rolls an unclaimed photo from the admin-curated library and sends it to the Studio. Random battle rolls a random photo already posted in the app and opens its battle, so you can jump in and caption without a photo of your own. Nothing is copied or generated; the photo stays its original owner's, and your caption is attributed to you. "Try another" re-rolls, avoiding the last few photos you've seen and your own.ENABLE_AI_PHOTOS=true to show a "Generate with AI" option on Post, where Gemini draws a photo from a description and then captions it (5 a day per user). Needs a Gemini API project with billing enabled; the free tier has no image quota./g/<id> link with a link-preview image.Setup:
supabase/migrations/20261005000000_captions_votes_rls.sql in the Supabase SQL Editor. It creates generations (photo + the exact prompt and model used), captions, and caption_votes (one row per user per caption, with a trigger that keeps the tallies on captions), the generations storage bucket, and turns on RLS for every table in public.supabase/migrations/20261012000000_human_vs_ai_battles.sql. It adds captions.author_type (existing captions become ai) and generations.challenge_date, hides caption authorship from the API, and adds the functions below.GEMINI_API_KEY in .env.local and in Vercel (Settings → Environment Variables). Optionally set GEMINI_MODEL (defaults to gemini-flash-latest) and GEMINI_IMAGE_MODEL (defaults to gemini-2.5-flash-image).RLS summary:
| Table | Read | Write |
|---|---|---|
profiles | own row | update own row (insert via signup trigger) |
movies | everyone | none |
generations | everyone | insert as yourself, into today's challenge only; update only context on your own posts (column grant); delete your own posts |
captions | everyone, but only id, generation_id, text, upvotes, downvotes, score (column grants) | AI captions: owner of a photo posted in the last 10 minutes. Human captions: only through submit_human_caption(). Tallies only via trigger |
caption_votes | own votes | insert / update / delete own votes; never on your own human caption |
Who wrote a caption (author_type, user_id) is never readable directly. These security definer functions decide what each caller may see:
| Function | Who | What |
|---|---|---|
caption_reveals(ids) | signed in | authors of captions you voted on, wrote, or that are on your photo |
submit_human_caption(id, text) | signed in | adds a human caption: 1–200 chars, max 3 per photo, 30 per day, no duplicates |
daily_winner(day) | everyone | top caption of a finished day, author revealed |
battle_scoreboard() | everyone | Humans vs. Gemini totals only |
my_captions() | signed in | your own human captions with rank |
edit_human_caption(id, text) | signed in | edit your own human caption, only while it has no votes |
delete_human_caption(id) | signed in | delete your own human caption |
guess_round(exclude) / check_guess(id, guess) | everyone | the guessing game; only uses captions from finished challenge days |
Captions come from the AI Studio pipeline (src/lib/pipeline.ts, shared with the admin app): the database picks a prompt chain by the admin-set mix, the app runs each step against Gemini with a per-call timeout and an overall time budget, and every call is logged (generation_requests, llm_responses). The tables and functions are in the admin repo's 20261026000000_ai_studio_access_images.sql. Before that migration is applied, the app uses the original single prompt.
Signups can be limited to approved email domains and whitelisted emails from the admin console. The login page explains the rule when it's on, and a blocked Google signup comes back with a clear message instead of a generic error.
Light-only brand system in src/app/globals.css: icy baby blue #B8DCFF, powder #E3F2FF, sky #69B5FF, navy ink #16263D on #F8FCFF. Headlines use Bricolage Grotesque with one Instrument Serif italic word; body text is Geist. Shared classes: .btn (glossy pills), .card, .glass, .chip, .field, .orb (the 3D bubble logo and Post button). Humans are blue #3D8FF0 and Gemini is bubblegum #E5539A; both were checked for color-blind separation and contrast. Motion respects prefers-reduced-motion.
npm test runs Playwright against a production build (npm run build first) and a local Supabase stand-in from the admin repo (morningside-memes-admin/tests/mock). The stand-in runs this repo's migrations with RLS on PGlite. The tests cover browsing signed out, the Human or AI? game, voting and the author reveal, adding/editing/deleting your own caption, deleting your own post, the profile form, the Daily Drop (and that future drops stay hidden), Surprise Me's fresh-photo mode, final battle results, the photo wall, Your Meme Era achievements, signup-gate messages, a pipeline failure being logged with no half-made post, posting with Gemini from an upload and from the Daily Drop (when GEMINI_API_KEY is set and has quota), and no horizontal scrolling on phones. They expect the admin repo checked out next to this one (or MOCK_DIR).
The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.
Check out our Next.js deployment documentation for more details.