尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗A shared camera for friend groups: call roll and everyone shoots the same moment, with short challenges, and it all develops the next day.
CLAUDE.md: the build brief. Start here.prototype/index.html: the working prototype (design, rules, photo look, sample event). Serve the folder over HTTP to try it (npx serve prototype); the sample photos won't process from a file:// URL. Outside claude.ai the prototype has no database, so creating groups is disabled; the sample event and "Try the look" still work.docs/challenges.md: every challenge.docs/recreate-challenges.xlsx: recreations with copyright notes.docs/research.md: the research behind the decisions.docs/credits.md: photo and font credits.Next.js (App Router) and Supabase. Node 20 or later.
npm install
cp .env.example .env.local # then fill in the two values
npm run dev
Needs Docker. npx supabase start applies supabase/migrations, uses the auth settings in supabase/config.toml and prints the API URL and publishable key for .env.local. Sign-in emails land in Inbucket at http://127.0.0.1:54324.
npx supabase link --project-ref <ref>, then npx supabase db push. Or paste each file in supabase/migrations into the SQL Editor, oldest first.https://<your-domain>/** (plus Vercel preview URLs if you use them) to the redirect URLs.smtp.resend.com, port 465, user resend, password a Resend API key), raise the email rate limit, then paste supabase/templates/sign-in.html into both "Confirm signup" and "Magic link", and supabase/templates/email-change.html into "Change email address". The default templates work, but only send a link. Ours add a code, because a home-screen app on iPhone doesn't share cookies with Safari, so the link alone signs people in on the wrong side.npx web-push generate-vapid-keys): NEXT_PUBLIC_VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY (secret).supabase/migrations/20261021000000_foundations.sql has every table from the brief, plus a timezone on groups and host_member_id on events. Row-level security is on everywhere: people see only their groups, and other people's photos (rows and Storage objects) stay hidden until the reveal. Groups are made with create_group() and joined with join_group() from an invite link; group_preview() lets the invite page show the group before you sign in. Events are made with create_event(), which saves the event and its guest list together. event_shot_counts() gives the group each person's shot count without unsealing anything. Roll Calls go through call_roll() and answer_roll(), which apply the budget, reserved call, one-per-person rule, cooldown, backed extras and answer windows in one transaction. roll_state() returns the same check for the button, so the app and the server always agree. ask_extra() and back_extra() handle asking for one more. Roll calls, asks, backs and statuses are published to Realtime. Push: devices register with save_push_subscription(); after a call, /api/push/roll-call gets the recipients once through roll_call_push_targets() (caller only, under a minute old, snoozed and home left out) and sends with web-push.
Profiles (20261027000000_profile.sql): one name and one away card per person. set_my_name() and set_my_away_card() update every group you're in; making or joining a group copies them in. Profile cards are stored at away-cards/u/{user_id}/.
Start-day alerts (20261031000000_start_push.sql): "Sam's 30th is today", once, from 09:00 local on the first day, sent the same three ways as reveal alerts.
Reveal alerts (20261030000000_reveal_push.sql): "Lisbon is out" goes to everyone on an event once it reveals. Whichever comes first sends it, once: the host pressing Reveal now, anyone in the group opening the app after the reveal time, or the daily Vercel cron in vercel.json (needs CRON_SECRET in Vercel and the same value in private.secrets).
Letter-day word (20261029000000_hunt_word.sql): events.hunt_word, set by the host when creating a trip; event_details is rebuilt to include it.
Negatives (20261028000000_negatives.sql): at capture the phone also uploads a 45×60 inverted, tinted negative to the negatives bucket. The group can read those before the reveal, and event_negatives() lists which shots exist without saying what they are.
Moderation and cleanup (20261026000000_moderation.sql): report_photo() reports a revealed photo; the event's host and the group's maker see reports (reports table) and can remove any revealed photo, and owners can always delete their own. leave_group() deletes your photos and event places, keeps Roll Calls you started, hands events you host (and the group, if you made it) to whoever joined first, and leaves your name unclaimed so you can rejoin as yourself; the last person out deletes the group. Anyone in the group can join an event until its reveal; late joiners sort last so nobody else's challenge moves. Photo files are cleaned up by their uploader: Storage only deletes what you can read, so my_orphaned_files() lists your files that no row points at any more and the app removes them on load. Until then nobody else can read them, because reading a photo or away card now needs a live row.