================ ONE-SHOT BUILD CONTRACT (read first) ================ Build this in Google AI Studio "Build" in ONE shot — a complete, working app, no follow-up turns. These are hard rules, not suggestions: 1. TARGET = Full-Stack Web (Node server runtime, secrets, Firebase allowed). If you target Native Android instead, you MUST drop all server/DB/Workspace/ secrets and persist locally (Room / SharedPreferences) only. 2. PIN EVERY MODEL CALL — never let the agent auto-select (it downgrades on quota): - Reasoning / text -> gemini-3.5-flash (thinkingLevel: minimal|low|medium|high) - 4K image + legible text -> gemini-3-pro-image (image_size "4K", up to 14 refs) - High-volume image -> gemini-3.1-flash-image - Expressive TTS -> gemini-3.1-flash-tts-preview (inline tags e.g. [whispers]) - Realtime audio/video (WebSocket) -> gemini-3.1-flash-live-preview - Sandboxed agent -> antigravity-preview-05-2026 3. DIVISION OF LABOR — the model ONLY parses/extracts to a strict responseSchema. ALL math, money (store currency as integer minor units / cents), sorting, balancing and graph logic run in deterministic TypeScript/Python. The model must never compute totals, splits or balances itself. 4. responseSchema sanitation — no regex patterns, no fixed-length tuples, no format validators in the schema (they crash the OpenAPI engine). Enforce those in server-side code AFTER parsing the JSON. 5. responseSchema and google_search grounding are MUTUALLY EXCLUSIVE in one call. 6. CODEGEN — split large output into modular, single-responsibility files so no file is truncated by the output-token cap. 7. Every external call gets a graceful fallback (e.g. manual paste if a Workspace read fails). Never a silent dead end. 8. ROBUST STORAGE & CANVAS — Wrap all `localStorage`/`sessionStorage` operations (especially JSON parsing and writes) in `try-catch` blocks to prevent crashes in private windows or quota overflows. Canvas drawing elements must dynamically handle window resize and scale pixel density (`window.devicePixelRatio`) to avoid blurry graphics on retina displays. ===================================================================== # MUST OBEY — Mobile-first build requirements This app's PRIMARY surface is a mobile phone. Build it impeccably on mobile FIRST, then verify on tablet and desktop. Treat the rules below as non-negotiable hard constraints, not suggestions. ## Viewports to verify (every screen, every state) - 320 px, 360 px, 375 px, 390 px, 414 px, 480 px - 768 px, 834 px (iPad portrait / Pro 11) - 1024 px, 1280 px, 1440 px, 1920 px, 2560 px - Plus: 200% browser zoom, landscape orientation on every mobile width, iPhone with safe-area insets visible ## Hard layout rules - Mobile-first CSS. Default styles target mobile; `@media (min-width: ...)` for larger viewports. - Use `dvh` and `svh` instead of `vh` for full-height surfaces (iOS Safari URL-bar bug). - Use `clamp()` for fluid typography across all viewports. - Prefer container queries (`@container`) over media queries for component-level responsiveness. - Use `min(100%, ...)` widths so content never overflows. Zero horizontal overflow at any viewport. - Add `` to every page. - Apply `padding: max(safe-area-inset-X, fallback)` on every edge-bleeding container so notched iPhones in landscape never clip content. - Wide tables and code blocks scroll INSIDE their container (`overflow-x: auto`), never push the body. - Use `background-attachment: scroll` on mobile, not `fixed` (iOS Safari repaint bug). - Avoid `backdrop-filter` on animated elements. Use it sparingly on static surfaces only. - **Canvas Scaling**: Canvases must dynamically scale with window resize events and properly handle high-DPI screens (`window.devicePixelRatio`). Set physical dimensions (`canvas.width`/`canvas.height`) using pixel ratio and render relative to this grid, using CSS to control responsive viewport scaling. - **Robust Storage**: Every access to `localStorage`/`sessionStorage` (especially `JSON.parse` of loaded state or writes) MUST be wrapped in a `try-catch` block to handle disabled storage, private browsing mode, quota limits, or corrupted JSON gracefully. Fall back to a robust in-memory object store. ## Touch & accessibility - Tap targets ≥ 44 × 44 px on touch (Apple HIG). Increase to 48 px under `@media (hover: none) and (pointer: coarse)`. - All interactive controls reachable by keyboard with a visible focus ring; respect `:focus-visible`. - Color contrast ≥ 4.5:1 for body text, 3:1 for UI components. - All images have meaningful `alt`. Decorative images use `alt=""`. - Respect `prefers-reduced-motion: reduce` — zero animation durations under that query. - Forms validate inline; error messages are specific, not "Invalid input". - Modals: focus trap, `Esc` closes, `role="dialog"`, `aria-modal="true"`, focus restored on close. ## Performance bar (Lighthouse mobile, throttled 3G/4G) - LCP < 2.5 s · INP < 200 ms · CLS < 0.1 - JS bundle gzip < 200 KB mobile-first; lazy-load non-critical screens via `React.lazy` / dynamic imports. - No render-blocking resources above the fold. - Images: WebP/AVIF preferred, `loading="lazy"`, explicit `width`/`height` attributes (zero CLS), `srcset` for retina. - Videos: `preload="metadata"`, low-resolution poster, max 720p mobile fallback. Never autoplay with audio. - Fonts: `font-display: swap`; preload only the one used above the fold. - Smooth scroll honoured via CSS `scroll-behavior: smooth` with reduced-motion fallback. ## Pre-ship mobile checklist (the deployer MUST verify before declaring done) 1. Open at 375 px in DevTools — every screen scrolls vertically only; zero horizontal scroll. 2. Browser zoom 200% — layout reflows without overlap. 3. iPhone Safari with the URL bar visible AND landscape — no content under the home indicator; no notch clipping. 4. iPad portrait (768 px) and landscape (1024 px) — no awkward gaps; tablet-specific breakpoints land cleanly. 5. Tap every interactive element with a thumb at real-device size — every target is easy to hit. 6. `prefers-reduced-motion: reduce` — every transition / animation skips cleanly, scroll-behavior becomes instant. 7. Lighthouse mobile score ≥ 90 across all 4 categories. 8. Zero `console.error` and zero CLS shift in real-device testing on a mid-tier Android (e.g. Pixel 6a) and an iPhone SE. --- The original template starts below. All rules above apply on TOP of whatever this template specifies. --- # Shot List ## 1. Project **Shot List** is a pre-shoot planning app for the wedding photographer at breakfast on a wedding day. The user opens it over coffee and a plate of eggs, feeds it the couple's pre-shoot brief (the email threads, the questionnaire PDF, the spreadsheet of who-is-related-to- whom, the Pinterest links) and the venue's published photographs and floor plan, and the app produces a structured shot list with real timings — clock-time start and end for every group, every detail shot, every couple portrait window — the family-portrait permutations resolved so nobody important gets missed in the chaos between the ceremony and the canapés, and the golden-hour window adjusted for that day's forecast cloud cover. Every line on the list ties back to the brief: who asked for it, on which date, in which message, in which words. The list refuses to invent shots the couple did not ask for. This is the kind of app a wedding photographer builds for himself on a quiet Tuesday after a Saturday where he forgot the bride's ninety-two-year-old grandmother in the formal group portraits and had to apologise by email on Monday morning. It is the kind of app a Filipino wedding photographer in Quezon City uses on a Saturday in Manila when there are four sets of ninongs and ninangs in the ceremony and the family-portrait permutations alone fill two pages. It is the kind of app a Nigerian-British wedding photographer in Peckham, London uses for a Yoruba-Igbo wedding in Lewisham where the two families' formal groups have different conventions, the engagement ceremony took place the day before, and the aso ebi colour-grouped group shots need to happen before the hall changes light. It is the kind of app a Mexican wedding photographer in Tulum uses on a beach- ceremony morning when the brief is in English, the family is half from Guadalajara and half from Brooklyn, the abuela is in a wheelchair and needs an early portrait window before the heat, and the sunset over the cenote is at 19:47 and there are 320 seconds of usable light. Same shape of morning, different language, different family structure, different sun. The single demo that proves the magic: paste the couple's brief (the forwarded planner email, the questionnaire PDF, the family-relations spreadsheet) into the left pane → drop the venue's photographs and floor plan into the right pane → tap **Build the day**. Ninety seconds later the user sees a vertical timeline of the day from 06:30 prep to 23:30 last-dance, with every shot the couple asked for placed against a clock time, the family-portrait block expanded into the minimum set of group compositions that cover every named relative exactly once (the bride's late father's sister appears in the maternal-side group AND in the bride-with-aunts-only group, but the algorithm does not photograph her twice in the same arrangement), the golden-hour couple-portrait window starting at 19:08 because the day's forecast says broken cumulus and 19:47 sunset, the ceremony aisle-walk position marked on the floor-plan thumbnail with an arrow drawn over the venue's own published photograph of the aisle, and a small panel labelled **Asked for, not in the list** that calls out the three things the couple mentioned in the brief that the photographer should double-check before leaving: the surprise letter the groom is reading in the suite at 14:20, the choreographed first-dance dip the couple mentioned in a Tuesday email, and the nephew's solo violin piece during the cocktail hour that the photographer would otherwise miss because he would be eating his own dinner. And in the harder cases — the venue with a 15:00 ceremony and a 20-minute drive to the reception, the wedding with two ceremonies (civil at the registrar's office in the morning, religious at the gurdwara in the afternoon), the wedding with a flower-girl who has already been told she has fifteen minutes maximum on the formal- portrait line because she is six, the wedding where the groom's father is divorced from the groom's mother and they cannot be in the same group portrait, the wedding where the brief explicitly says **no posed couple portraits, all reportage** — the app keeps up. It respects every constraint the couple wrote down. It never invents a shot they did not ask for. It marks every clock time as a planning suggestion the photographer can override; it never claims to know the photographer's job better than they do. **Tagline:** _A wedding day, planned by breakfast — in any culture, any venue, any sun, with every group portrait permutation resolved and nobody important missed._ ## 2. Target audience - Solo wedding photographers shooting 15-40 weddings a year — the ones who run the whole business out of one head and lose Tuesday evenings to planning each weekend's schedule - Wedding-photography duos and small two-shooter studios — primary shooter + second shooter dividing coverage; need a shared list they can both annotate before the day - Cross-cultural wedding specialists — Indian, Filipino, Nigerian, Vietnamese, Mexican, Persian, Korean, Greek, Lebanese, Italian- American, Polish, Jewish, Sikh, Hindu, Muslim, Catholic, Orthodox Christian, Jain, civil-only, interfaith — each tradition adds its own shot conventions - Destination wedding photographers — different time zone the night before, unfamiliar venue, light direction known only from the venue's marketing photographs and Google Earth - Documentary / reportage photographers who refuse to pose anything but family portraits — the app must respect "no posed shots except group formals" as a hard constraint - Wedding videographers running the same workflow on the same brief — the structured list works for video too, with slight timing tweaks (5 seconds before the kiss, hold 5 after) - Junior associate shooters at larger studios receiving the brief from a studio coordinator and arriving on the day with a list someone else built - Wedding planners who run the photographer's day for them — for studios that outsource scheduling, the planner builds the shot list in the app and shares it with the contracted shooter - Couples themselves, on the Sunday before the wedding, using the app's read-only export to confirm the family-portrait list with the parents before sending the final brief to the photographer ## 3. Core value propositions Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app. - **The brief is the source of truth — the app never invents shots** — every line on the shot list traces back to a specific sentence in the couple's brief, the planner's email, the questionnaire, or the spreadsheet, with a citation chip the photographer can tap to see the original message. Gemini 3 Pro reads the long-context brief and the venue photographs in one call; the structured shot list comes out as JSON; the citation field is mandatory on every shot. If the brief is silent on a shot, it does not appear on the list. Period. - **Family-portrait permutations resolved into the minimum set** — the bride's parents, the groom's parents, both sets of grandparents, all siblings, the chosen aunts and uncles, the named godparents, the divorced parents who cannot be photographed together, the late relative whose photograph is being held in-frame — the relationships are parsed from the brief into a graph, and the formal-portrait block is laid out as the smallest list of groupings that covers every named relative the couple requested, in an order that walks the wheelchair-using grandmother on first and lets her sit down. - **Real clock times, not "1 hour before sunset"** — the app pulls the day's forecast for the venue location, computes the actual golden-hour start time for the weather (overcast pushes it earlier; clear sky stretches it later), and writes every shot against a real clock time the photographer can read at a glance: 19:08, not "sometime around sunset". The photographer can drag any line to a different time; everything downstream re-flows around it. - **The venue is a floor plan with arrows, not a stock photograph** — the user drops the venue's published images and floor plan in; the app generates a one-page **venue briefing** sheet with the aisle- walk position marked, the suggested couple-portrait spots circled on the venue's own photographs, the natural light direction at ceremony time arrowed onto the floor plan, and the reception-table layout annotated for where to stand during the speeches. - **"Asked for, not in the list" panel — the catch-net** — the app also surfaces a panel of every concrete thing the couple mentioned in the brief that did NOT make it into a structured shot line, so the photographer can decide whether to add it. The surprise letter in the suite, the solo violin piece at cocktails, the late father's pocket watch the groom is wearing — things easy to miss because they were mentioned in passing. - **One-tap reduction to a printable card** — the photographer can collapse the full day-plan into a wallet-card view, two sides A6, designed to be photographed onto the back of a phone or printed at 06:00 on the way to the venue. Family groups on side A. Timeline on side B. Couple's no-photo requests in red on the bottom of side B. - **Two-shooter split mode** — when a second shooter is configured, every shot line is assigned to primary or secondary based on location and time. The app produces two cards, one per shooter, cross-referenced so each knows where the other is at every moment. - **Respects refusal explicitly** — the brief can include a **no-photograph** list (the bride's brother who does not want his face online, the relative who is in witness protection, the ex-partner who is attending as a guest and asked not to be in any formal group). These are surfaced as a red banner on the top of every view and stay there until the photographer dismisses them. - **Works in the venue's language and the photographer's language** — brief documents may be in any of the wedding cultures the app serves (Tagalog, Yoruba, Igbo, Mandarin, Cantonese, Korean, Tamil, Hindi, Urdu, Bengali, Punjabi, Amharic, Swahili, Farsi, Khmer, alongside the European set); the output card is in the photographer's preferred working language. Family relationship vocabulary in the original (kuya, ate, ninong, ninang, oga, anti, mamá, papá, abuela, dadi, nani, halmoni, sobo) is preserved verbatim, with a gloss only on first occurrence. ## 4. Features to build - Brief intake — paste long text, drop email exports (`.eml`, `.mbox`, Google Takeout JSON), upload PDF questionnaires, drop CSV / XLSX family-relations spreadsheets, link a public Pinterest board (or drop a folder of saved inspiration JPEGs) - Venue intake — upload published venue photographs (5-30 typical), drop a floor-plan image or PDF, paste a venue address, optionally paste the venue's webpage URL for the AI to also read - Long-context multimodal parse — the brief plus the venue images plus the floor plan parsed in one Gemini 3.5 Flash call into the `DayPlan` schema below; thinking level medium; no other tools - Family-graph resolution — relatives mentioned in the brief are extracted into a graph (each person, their relationship to the bride and to the groom, their named role in the day, their accessibility needs, their refusal flags); the graph is the source of truth for the formal-portrait permutation solver - Formal-portrait permutation solver — a deterministic algorithm (not the LLM) that takes the family graph and the couple's requested groupings (parents-with-couple, full-family-shot, bride-with-her-side-only, groom-with-grandparents, etc.) and emits the smallest ordered list of group compositions that covers every named relative the couple asked for at least once, respects no-photograph-together constraints (divorced parents), and orders the elderly and the very young first - Sun position + weather-aware golden-hour computation — the app uses the venue coordinates and the day's NOAA / OpenWeather forecast to compute civil twilight, golden-hour-start, golden-hour-end; pushes golden-hour earlier on overcast days; widens it on clear days - Timing reflow — every shot line has an estimated duration; dragging a line later or earlier reflows the rest of the timeline around it while honouring fixed-time anchors (the ceremony starts when the ceremony starts) - "Asked for, not in the list" surfacing — the parse keeps an `unstructured_mentions[]` array of every concrete request in the brief that did NOT end up as a structured shot, with the verbatim quote and the email date; the photographer reviews them before committing the day plan - Venue briefing sheet — a generated one-page PDF showing the floor plan with annotations, the ceremony-time light direction arrowed on, the recommended couple-portrait spots circled on the venue's own photographs, and the reception-table speech positions noted - Two-shooter split — when configured, every shot is assigned to primary or secondary; outputs two cards, cross-referenced - Wallet-card export — A6 printable double-sided card optimised for reading on a strap or on the back of a phone in bright daylight; high-contrast, large type, no decoration - Couple-confirmation share — a read-only HTML link the photographer sends the couple the day before so they can confirm the formal- portrait list and the no-photograph list; couple cannot edit - Pre-shoot voice memo capture — at breakfast, the photographer can also record themselves talking through the day; the app uses Gemini 2.5 native audio understanding to merge the voice memo's additions and corrections into the shot list (the photographer remembers the couple mentioned a sparkler exit at midnight and they did not write it down explicitly) - Refusal banner — the no-photograph list is rendered as a red banner on every view; the photographer must acknowledge it once before the banner collapses - Cultural-tradition library — the app ships with a curated set of ceremony-tradition shot conventions (Filipino veil-and-cord, Yoruba iyawo's veil-lift, Sikh anand karaj phere, Hindu seven steps, Jewish chuppah-walk and ring-exchange, Catholic communion, Persian sofreh aghd, Greek crowning, Mexican lazo) — each is a candidate list the photographer can selectively enable when the brief mentions the tradition; never auto-enabled without confirmation - Cultural-vocabulary preservation — Tagalog ninong/ninang, Yoruba iya/baba, Igbo nne/nna, Hindi dadi/nani, Korean halmoni/harabeoji, Spanish madrina/padrino, Italian zia/zio are preserved verbatim in the card with the gloss on first occurrence only - Re-plan after change — the planner emails on Friday night that the ceremony has moved fifteen minutes later; the photographer pastes the new email into the change-log input and the entire timeline re-flows in the same Gemini 3.5 Flash long-context call - Print + share — wallet card PDF, full day-plan PDF, plain-text card for the second shooter's notes app, public read-only HTML link for the couple - History — every wedding the user has planned, with date, couple names, venue, link to the archived shot list; never auto-deleted ## 4b. Required Gemini capabilities + backend services **This template's intelligence comes from the Gemini capabilities below. Wire them up explicitly — don't substitute generic LLM calls.** ### Gemini capabilities (the load-bearing intelligence) - **Long context (1M tokens) — Gemini 3.5 Flash** — the brief is months of planner emails, the questionnaire, the family-relations spreadsheet, the change-log threads, all read in one call. A typical brief runs 30-80 thousand tokens. **Guardrail**: cap input at 800k tokens; if larger, chunk by email-thread date first, then retry. Image inputs (venue photographs + floor plan) count toward the same context window. - **Multimodal image input — Gemini 3.5 Flash** — reads the venue's published photographs (to suggest portrait spots) and the floor plan (to mark aisle-walk position, ceremony orientation, reception flow); generates the venue briefing sheet's annotations from what the model sees. - **Audio understanding — Gemini 3.5 Flash** — the photographer's breakfast voice memo (typically 60-180 seconds) is parsed as audio in the same multimodal call; transcript and inferred additions to the shot list flow into the structured output. - **Structured output / JSON Schema — Gemini 3.5 Flash** — the response matches the `DayPlan` schema below. Every shot is typed; the schema is provided as `responseSchema` (converted from Zod via the SDK helper); numeric clamping happens server-side. - **Search grounding — Gemini 3.5 Flash** — the weather-and-sun computation is a separate grounded call (`google_search` against NOAA, the country's met service, or OpenWeather summary pages) so that golden-hour start is real for the day, not invented. **Hard rule: grounding and `responseSchema` cannot be combined in one Gemini call — the grounded weather call emits JSON in the text body and the server parses it; citations come from `response.groundingMetadata.groundingChunks[].web.uri`.** - **Thinking levels** — `high` for the primary long-context brief parse (the highest-stakes call in this app — get a family member wrong and someone is missing from the wedding album); `medium` for the re-plan-after-change call; `low` for the weather resolution and the wallet-card layout call; omit `thinkingConfig` entirely on the TTS-readout call if it is enabled. - **Multilingual reading** — briefs may be in Tagalog, Yoruba, Igbo, Mandarin, Cantonese, Korean, Tamil, Hindi, Urdu, Bengali, Punjabi, Amharic, Swahili, Farsi, Khmer, and the European set; Gemini 3.5 Flash reads them and outputs the shot card in the photographer's chosen working language with cultural vocabulary preserved verbatim. ### Backend services - **Auth — Required.** Firebase Auth with Google sign-in (auto-provisioned by AI Studio Build). **Apple sign-in is optional but user-configured**: requires an Apple Developer account, Service ID, Key ID, and private key wired into the Firebase Auth console. **Magic-link email** (used for the read-only couple- confirmation share) also requires the sender domain to be authorised in Firebase Auth. Photographer accounts are private; shared read-only links are scoped to a single wedding. - **Database — Required.** Firestore for `users`, `weddings`, `briefs`, `venues`, `family_graphs`, `day_plans`, `change_logs`, `share_links`. - **File storage — Required.** Firebase Storage for uploaded venue photographs, floor-plan PDFs, breakfast voice memos, generated briefing-sheet PDFs, generated wallet-card PDFs. **Storage is NOT auto-provisioned by AI Studio Build today** — enable it in the Firebase console and wire the bucket name into the project before first upload. Pre-signed URLs only. - **Email — Required (transactional).** Couple-confirmation share emails (Firebase Auth magic links). Optional: shot-card auto-send to the second shooter the night before. - **Payments — Not needed for v1.** The app is built for the photographer's own use. A future studio tier (more than five shooters per account) could pipe to Stripe. - **External APIs:** Gemini API for all intelligence; the weather resolution call uses Gemini 3.5 Flash with `google_search` grounding (no separate weather-API key required, though a paid OpenWeather key may be wired in later for higher rate limits). **Environment variables:** every secret (Gemini API key, Firebase service-account JSON, optional OpenWeather key) lives in environment variables, never in client bundle. Include a `.env.example`. **Auth + data privacy reminders:** never log secrets · never store passwords in plain text · use HTTPS everywhere · honour 'delete my account' inside the UI · explicit opt-in for analytics · briefs often contain personal information about non-users (the couple's family) — this content is never sent to Gemini for training; use the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms. The no-photograph list and the family-graph are private to the photographer and any explicitly-named co-shooter. **Read this first — prompt-craft rules that apply to every call:** 1. **Name the model variant explicitly** in every Gemini API call. Do not 2. **Grounded responses can wrap JSON in ```json fences or add prose preamble.** Server-side, strip fences and brace-extract: ```typescript function safeExtractJSON(raw: string): T { const clean = raw.replace(/```json\s*|```/gi, '').trim(); const s = clean.indexOf('{'); const e = clean.lastIndexOf('}'); if (s === -1 || e === -1) throw new Error('No JSON boundaries in grounded response'); return JSON.parse(clean.slice(s, e + 1)) as T; } ``` 3. **Strip unsupported Zod modifiers before passing to `responseSchema`** — Gemini's OpenAPI subset rejects `.regex()` / `pattern`, fixed-length `z.tuple()`, and other custom validators. Use a sanitizer that flattens tuples to length-2 arrays and removes regex patterns before serializing. Validate those constraints in middleware AFTER parsing. let the agent pick the model. See the per-call matrix. 2. **Pin `thinkingLevel` explicitly** per call. See the matrix. 3. **Seed the JSON Schema as a fenced TypeScript / Zod block** in the system instruction or `responseSchema` field. The literal schema is below. **Convert the Zod schema to Gemini's `Schema` type via the SDK helper** before passing to `responseSchema` — do NOT pass raw Zod. **Numeric `min`/`max` constraints are documentation only inside `responseSchema`; clamp server-side.** 4. **Pin the system instruction separately** from user input. Use `systemInstruction` for persona + behavioural rules; use `contents` for user input. Never concatenate. 5. **Pre-declare tools as an enable/disable list** per call. The matrix below names which tools are enabled per call. Tools NOT listed for a call should be disabled. 6. **State negative constraints explicitly** — they are listed below. They are NOT "be careful" suggestions; they are hard rules the model must follow. ### Per-call model + tools matrix | Call | Model | thinkingLevel | Tools enabled | |------|-------|---------------|---------------| | Brief + venue images + (optional) voice memo → `DayPlan` schema | `gemini-3.5-flash` | high | (none) | | Re-plan after change (new email, time shift) | `gemini-3.5-flash` | medium | (none) | | Resolve weather + golden-hour for venue + date | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` — see note) | | Annotate floor plan / venue photographs | `gemini-3.5-flash` | medium | (none) | | Wallet-card layout copy + abbreviation | `gemini-3.5-flash` | low | (none) | | Couple-confirmation HTML page render | `gemini-3.5-flash` | low | (none) | *Note for builders:* the family-portrait permutation solver is NOT a Gemini call — it is a deterministic algorithm on the server-side (combinatorial set cover over the family graph). Putting it in the LLM is wrong: the LLM occasionally drops a relative or doubles up, and dropping a relative is the failure this app exists to prevent. Use Gemini to extract the family graph; use code to solve it. *Note for builders:* on TTS and image-generation calls, omit `thinkingConfig` entirely — the field is not supported on those models. The `n/a` cells in this matrix are documentation only; do not serialise them into the request body. ### Primary structured-output schema (seed this verbatim in the prompt) ```typescript import { z } from "zod"; const BriefCitation = z.object({ source_type: z.enum([ "planner_email", "questionnaire_pdf", "spreadsheet_row", "pinterest_caption", "change_log_email", "voice_memo", "other_message", ]), source_label: z.string(), // "email from Maya, 4 May, subject: family list" quote_verbatim: z.string(), // the exact sentence the shot traces to message_date_iso: z.string().nullable(), }); const PersonInFamily = z.object({ person_id: z.string(), // stable id used in groupings name_verbatim: z.string(), // "Tita Lily", "Lola Nena", "Wujek Janek" relationship_to_bride: z.string().nullable(), // "bride's maternal aunt" relationship_to_groom: z.string().nullable(), role_in_day: z.string().nullable(), // "principal sponsor", "ring bearer", "reader" accessibility_notes: z.string().nullable(), // "uses wheelchair", "stays seated" no_photograph_request: z.boolean(), // hard refusal cannot_be_photographed_with: z.array(z.string()),// person_ids — e.g. divorced parents appears_in_groupings: z.array(z.string()), // populated by the solver }); const FamilyGraph = z.object({ people: z.array(PersonInFamily), refusal_banner_text: z.string().nullable(), // null if no refusals }); const ShotTiming = z.object({ scheduled_clock_time_iso: z.string(), // "2026-08-15T19:08:00+08:00" estimated_duration_seconds: z.number(), // 180 = 3 min anchor_type: z.enum([ "fixed", // ceremony start: cannot move "soft", // shot windows that flex "sun_dependent", // golden-hour, sunset ]), flex_window_before_seconds: z.number(), // how early it can move flex_window_after_seconds: z.number(), }); const ShotLocation = z.object({ venue_area: z.string(), // "bridal suite", "ceremony aisle", "lawn" floor_plan_marker: z.object({ // null if not on a plan x_pct: z.number().min(0).max(100), y_pct: z.number().min(0).max(100), facing_degrees_from_north: z.number().nullable(), }).nullable(), natural_light_note: z.string().nullable(), }); const Shot = z.object({ shot_id: z.string(), category: z.enum([ "getting_ready", "details", "first_look", "ceremony_processional", "ceremony_key_moment", "ceremony_recessional", "formal_group", "couple_portrait", "venue_detail", "reception_detail", "speech", "first_dance", "cultural_tradition", "exit", "candid_reportage", "other_explicit_request", ]), shot_label_short: z.string(), // wallet-card line: "Lola Nena + couple" shot_label_long: z.string(), // full briefing-sheet line notes_for_photographer: z.string().nullable(), timing: ShotTiming, location: ShotLocation, assigned_shooter: z.enum(["primary", "secondary", "either"]), people_required_ids: z.array(z.string()), // for formal_group must_capture_before_id: z.string().nullable(), // ordering constraint must_capture_after_id: z.string().nullable(), brief_citations: z.array(BriefCitation), // why this shot is on the list flagged_for_review: z.array(z.string()), // e.g. "duration assumed 5 min" }); const FormalGroupComposition = z.object({ group_id: z.string(), group_label: z.string(), // "Couple + bride's parents + grandparents" ordered_position: z.number(), // first = wheelchair grandmother goes early person_ids_in_frame: z.array(z.string()), reason_this_group_exists: z.string(), // tie back to brief duration_seconds: z.number(), // typical 90 cultural_tradition_id: z.string().nullable(), }); const SunAndWeather = z.object({ venue_coordinates: z.object({ lat: z.number(), lng: z.number() }), date_iso: z.string(), forecast_summary: z.string(), // "broken cumulus, 24C" forecast_source: z.string(), // populated server-side from grounding sunrise_iso: z.string(), sunset_iso: z.string(), golden_hour_start_iso: z.string(), // adjusted for cloud golden_hour_end_iso: z.string(), civil_twilight_end_iso: z.string(), }); const UnstructuredMention = z.object({ quote_verbatim: z.string(), source_label: z.string(), message_date_iso: z.string().nullable(), photographer_action: z.enum([ "review", "added", "dismissed", ]), reason_not_in_structured_list: z.string(), }); const DayPlan = z.object({ wedding_id: z.string(), couple_label: z.string(), // "Anya & Kit" date_iso: z.string(), primary_language: z.string(), // working language of the card cultural_traditions_active: z.array(z.string()),// tradition_ids enabled family_graph: FamilyGraph, sun_and_weather: SunAndWeather, formal_group_compositions: z.array(FormalGroupComposition), shots: z.array(Shot), unstructured_mentions: z.array(UnstructuredMention), refusal_summary_for_banner: z.string().nullable(), reading_confidence: z.number().min(0).max(1), flagged_for_user_review: z.array(z.object({ field_path: z.string(), reason: z.string(), })), }); type DayPlan = z.infer; ``` ### Common failure modes (and how to avoid them) - Agent invents shots that the couple did not ask for ("sunset silhouette of the rings on the lawn"). The brief is silent on this shot; it does not belong on the list. Pin in the system instruction: every shot needs a `brief_citations[]` entry with a verbatim quote. If you cannot cite, do not include. - Family-portrait permutations missing a named relative. The solver is deterministic, but the family graph that feeds it is LLM- extracted. Add a unit test that every `person_id` appears in at least one `formal_group_compositions[].person_ids_in_frame` after the solve. If anyone is missing, surface a flag — never silently drop. - Family-portrait permutations doubling a relative across two groups unnecessarily. Some doubling is correct (the bride's grandmother appears in the bride's-side group AND in the great-grandchildren group); other doubling is wasted time on the day. The solver must prefer the smallest set cover that satisfies all couple-requested groupings, not the maximum. - Divorced parents photographed together. The `cannot_be_photographed _with[]` array is a hard constraint on the solver. Add a unit test: for every person_id pair in any composition, neither appears in the other's exclusion list. - Wheelchair-using or elderly relatives scheduled last. Order `formal_group_compositions[].ordered_position` to put accessibility-noted people first. Add a unit test. - Sunset time used as golden-hour start. Golden hour starts ~60 min before sunset on a clear day, earlier on overcast. Compute, don't approximate. - Weather call combined with structured output. `responseSchema` and `google_search` grounding cannot coexist in the same Gemini call. Emit JSON in the text body for the weather call; parse server-side. Citations come from `groundingMetadata.groundingChunks[].web.uri`. - Cultural-tradition shots silently auto-enabled. If the brief mentions "Catholic ceremony", the app surfaces the Catholic candidate list as a panel the photographer can accept. Do NOT auto-include unctioned tradition shots without confirmation. - Cultural vocabulary translated. "Ninong" rendered as "godfather"; "lola" rendered as "grandmother". Hard rule: cultural family terms preserved verbatim. Gloss on first occurrence in parenthesis only. - Refusal banner ignored after first acknowledgement. The no- photograph list re-surfaces on the day-of card view; the photographer cannot suppress it permanently. - Voice memo additions overriding the brief. The breakfast voice memo can ADD shots and ADJUST timings, but it cannot delete a shot the brief explicitly requested. If the photographer wants to delete a brief-cited shot, they must do it in the UI with a written reason. - Long-context input >1M tokens. Cap at 800k. Above that, chunk by email-thread date and run sequentially. - Re-plan after change reorders everything chaotically. The re-plan call must respect existing fixed anchors and preserve shot_ids from the prior plan wherever possible. ### Negative constraints (hard rules) - Do NOT invent shots not requested in the brief. Every `Shot` needs at least one `BriefCitation` with a verbatim quote. - Do NOT translate family-relationship vocabulary. Preserve "kuya", "ate", "ninong", "ninang", "iya", "baba", "nne", "nna", "dadi", "nani", "halmoni", "harabeoji", "madrina", "padrino", "zia", "zio", "abuela", "abuelo", "babcia", "dziadek" verbatim. Gloss only on first occurrence. - Do NOT extrapolate cultural ceremony shots without the brief mentioning the tradition. The app surfaces tradition candidates as a panel; the photographer confirms. - Do NOT photograph divorced parents together unless the brief explicitly allows it. The exclusion graph is enforced by the solver. - Do NOT schedule elderly or accessibility-noted relatives last. They go first in the formal-portrait block. - Do NOT silently drop a named relative from the family graph. If the solver cannot fit someone, surface a `flagged_for_user_review` entry naming the person. - Do NOT use the photographer's brief or family graph to train any model. Use the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms. The capabilities-info panel says this in plain English. - Do NOT make the no-photograph banner dismissable permanently. It re-surfaces on the day-of card view. - Do NOT auto-publish or auto-share. The couple-confirmation share link is generated only on the photographer's explicit action, scoped to one wedding, expires 14 days after the wedding date. - Do NOT compute golden-hour from sunset alone. Pull the day's forecast cloud cover and adjust. - Do NOT use a Gemini call to solve the family-portrait permutations. That is a deterministic algorithm on the server. ### Per-call `systemInstruction` strings Use these as the literal `systemInstruction` field for each Gemini API call the built app makes. They complement the series-wide rules already uploaded as the global instructions file (`00-series-instructions.txt`). ### Call: Brief + venue images + (optional) voice memo → `DayPlan` schema Model: `gemini-3.5-flash` · thinkingLevel: high · Tools: (none) ``` You are reading a wedding photographer's pre-shoot brief and the venue materials at breakfast on the day of the wedding. Your job is to produce a structured day plan. The plan will drive what the photographer points the camera at for the next twelve hours. Inputs may include any of: - Forwarded planner emails (long threads, multiple senders) - The couple's filled-in questionnaire PDF - A spreadsheet of family members and their relationships - A Pinterest board exported as captions or as images with captions - The venue's published marketing photographs - A floor plan (image or PDF) - A breakfast voice memo (the photographer talking through the day to themselves, 60-180 seconds) - The venue's address, the wedding date, the photographer's preferred working language Languages encountered include Tagalog, Yoruba, Igbo, Mandarin, Cantonese, Korean, Tamil, Hindi, Urdu, Bengali, Punjabi, Amharic, Swahili, Farsi, Khmer, alongside the European set (English, Spanish, Portuguese, French, German, Italian, Polish, Russian). Read each input in its native language. Preserve cultural family vocabulary verbatim. Output the DayPlan JSON exactly matching the provided schema. Hard rules: - EVERY `Shot` must carry at least one `BriefCitation` with a verbatim quote. If the brief is silent on a shot, do not put it on the list. The photographer wants the brief to be the source of truth. - Extract the family graph into `family_graph.people[]`. For every named relative in the brief, create a `PersonInFamily` record with verbatim name (Tita Lily, Lola Nena, Wujek Janek — never translated). Note accessibility needs ("uses wheelchair", "stays seated", "ninety-two years old"). Note refusal flags ("does not want to be photographed", "should not be in the same frame as his ex-wife"). Populate `cannot_be_photographed_with[]` whenever the brief names an exclusion. - Do NOT solve the family-portrait permutations yourself. Extract the family graph and the couple's REQUESTED groupings (e.g. "couple with both sets of parents", "bride with all her aunts", "groom with his three brothers") into the structured output. The set-cover solver runs on the server side. Leave `formal_group_compositions[]` empty in your output unless the brief explicitly enumerates compositions (e.g. the couple wrote out the exact list themselves). - For every other concrete request in the brief that you parse into a Shot, include the verbatim quote. For requests you read but do NOT include as a structured Shot (because they are ambiguous, or because they are details the photographer should decide about), put them in `unstructured_mentions[]` with a reason. The "asked for, not in the list" panel is the catch-net for things the photographer must double-check. - Read the venue's published photographs to populate `Shot.location.venue_area` and `Shot.location.natural_light_note`. When you can see where the light comes from in the venue's own photographs (e.g. a window wall on the east side of the ceremony room), say so. - Read the floor plan to populate `Shot.location.floor_plan_marker` with rough x/y coordinates (0-100 percent of the plan's bounding box) for ceremony positions and reception-table positions. Mark the aisle-walk position with the bride entering from the marked entrance. - For shots that depend on the sun (golden-hour couple portrait, ceremony at sunset, exit at blue hour), set `anchor_type: "sun_dependent"`. Do not assign a clock time — the server overlays the weather call's golden-hour windows. - For shots that are tied to a fixed-time event (ceremony start at 16:00 because the registrar will not start earlier), set `anchor_type: "fixed"` and copy the time from the brief. - For everything else, set `anchor_type: "soft"` with a reasonable initial clock time and generous flex windows. - Cultural traditions are NOT auto-enabled. If the brief mentions a Filipino veil-and-cord ceremony, a Yoruba iyawo's veil-lift, a Sikh anand karaj phere, etc., add the tradition's `tradition_id` to `cultural_traditions_active[]` AND add the candidate shots as Shots with category `cultural_tradition` and a citation to the brief sentence that names the tradition. Do NOT add tradition shots not mentioned in the brief. - The "no-photograph" refusal list is a hard constraint. Surface it in `refusal_summary_for_banner` as a one-sentence summary ("Eli (bride's brother) has asked not to be in any photographs; the couple's ex-partner Jamie is attending as a guest and has asked not to be in formal groups"). The banner shows verbatim on every view. - `reading_confidence` < 0.7 on any inferred field requires a `flagged_for_user_review[]` entry with the field path and a one-sentence reason. - If the breakfast voice memo is present, treat it as additive + corrective: it can add shots, adjust timings, and add family members. It cannot delete a shot the brief explicitly requested — the photographer must delete those in the UI. - Do NOT translate names. Do NOT translate family-relationship vocabulary. Do NOT translate cultural ceremony names. - Do NOT extrapolate the venue from the photographs in ways the photographs do not show. If the floor plan shows a "lawn" but no photograph of the lawn, do not invent the lawn's light direction. No commentary outside the JSON. Output JSON only. ``` --- ### Call: Re-plan after change (new email, time shift) Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You receive an existing DayPlan and one or more new messages — typically a planner email sent Thursday or Friday evening — that modify the day. Your job is to produce an updated DayPlan that respects all existing shots, preserves their `shot_id`s, and re-flows timings only where the change requires. Common changes: - "Ceremony moved from 16:00 to 16:15" — shift the ceremony anchor and re-flow downstream soft anchors. - "Add a sparkler exit at midnight" — add a new Shot with category `exit`, anchor `fixed`, with the new message as its `BriefCitation`. - "Grandma is now in a wheelchair" — update the `PersonInFamily.accessibility_notes` and re-trigger the formal-portrait solver server-side. - "Cousin Jonas is no longer attending" — mark the person as inactive in the family graph (do not delete the record). - "Reception venue changed from the hall to the marquee on the lawn" — invalidate the venue annotations and request the photographer re-upload venue materials. Hard rules: - Preserve `shot_id`s where the same shot persists. - Preserve `BriefCitation`s for unchanged shots; add new citations to new shots citing the change-log message. - Do not delete shots the photographer has confirmed unless the change message explicitly removes them. - Add a top-level `change_summary` field to the output with a one-paragraph plain-English summary of what changed and what re-flowed. - Re-running the server-side solver is the responsibility of the server, not this call. Leave `formal_group_compositions[]` empty unless the change message explicitly enumerates new compositions. No commentary outside the JSON. ``` --- ### Call: Resolve weather + golden-hour for venue + date Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding ``` You receive a venue location (lat, lng, country, city) and a wedding date in ISO-8601. Your job is to produce a SunAndWeather JSON object with the forecast and the computed golden-hour windows. Hard rules: - Use `google_search` grounding to read the country's met service forecast (or NOAA for the US, the UK Met Office for the UK, PAGASA for the Philippines, NIMET for Nigeria, CONAGUA for Mexico, etc.) for the venue's locality on the given date. Cite the source URL. - Compute sunrise, sunset, civil twilight start and end, and the nominal golden-hour-start (60 min before sunset) and golden-hour-end (sunset). - ADJUST golden-hour-start earlier if the forecast says overcast, thick clouds, or rain in the late afternoon. The directional light fades earlier on cloudy days. A reasonable rule: if cloud cover ≥ 60%, push golden-hour-start 15-25 minutes earlier and shorten golden-hour-end to 10 minutes before sunset. If cloud cover is total (>90%), there is no usable golden hour — set both windows to the same time as sunset and note "no usable golden hour, overcast" in `forecast_summary`. - If the forecast is for clear skies and low humidity, leave the standard windows. Output the SunAndWeather JSON in the text body of your response. Do NOT use `responseSchema` — it is incompatible with `google_search` grounding in the same Gemini call. The server will parse the JSON from your text body and read the citation URLs from `groundingMetadata.groundingChunks[].web.uri`. Hard rule: do NOT include URLs in the JSON body. The model hallucinates URLs. Citations live in groundingMetadata. No commentary outside the JSON. ``` --- ### Call: Annotate floor plan / venue photographs Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You receive the floor plan image and the venue's published photographs (front-of-house, ceremony room, garden, reception hall). Your job is to produce a one-page venue briefing sheet data structure that the server will render as a PDF. For the floor plan: - Identify the ceremony orientation (where does the bride enter, where does the couple stand, where is the audience seated). Mark the aisle-walk path as a series of x/y points (0-100 percent of the plan's bounding box). - Identify the natural-light direction at the ceremony's start time, based on the venue's stated orientation in the published photographs (e.g. "the ceremony room has a large window wall on the east side, the ceremony is at 16:00, so the light will be coming from camera-left at a low angle"). - Identify two or three recommended couple-portrait spots and mark them on the floor plan with circles. Each spot has a natural-light note. - Identify the speech / first-dance / reception-table layout with rough positions. For the venue's published photographs: - For each photograph, label which part of the venue it shows. - On the photographs that contain the ceremony area, mark the recommended couple-portrait spot with a thin red circle. - On the photographs that contain reception areas, mark the recommended speech position. Hard rules: - Do NOT invent details the venue's own photographs do not show. If the floor plan shows a garden but no photograph shows the garden, do not annotate the garden. - Do NOT modify the venue's published photographs visually (the annotations are SVG overlays the server adds; you only return coordinates and labels in JSON). Output: the floor-plan annotation JSON + the venue-photograph annotation JSON. No commentary outside the JSON. ``` --- ### Call: Wallet-card layout copy + abbreviation Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You receive a full DayPlan and the photographer's preferred working language. Your job is to produce shortened, wallet-card- suitable copy for each shot line. The wallet card is A6 double-sided, designed to be read in bright daylight while wearing the camera strap. Each line is ≤ 60 characters. Group labels are ≤ 40 characters. Family- vocabulary terms (Tita, Tito, Ninong, Lola, Abuela, Halmoni, etc.) are preserved verbatim — do not replace them with target-language words to save characters. Hard rules: - Preserve every cultural family-vocabulary term verbatim. - Compress only verbose descriptions; never drop a named relative. - Times go in HH:MM (no seconds). Locations go as ≤ 12-character area labels ("ceremony aisle" → "aisle"; "bridal suite" → "suite"). - The "no-photograph" banner text is reproduced verbatim from the refusal_summary_for_banner field. Output an array of `{shot_id, short_line, time_hhmm, area_label}` per shot. No commentary outside the JSON. ``` --- ### Call: Couple-confirmation HTML page render Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You receive a DayPlan and produce a clean, read-only HTML page the couple opens the day before the wedding to confirm the formal-portrait list and the no-photograph list. The HTML is plain semantic markup the server then wraps with the project's styles. You produce: - A short intro paragraph in the couple's preferred language ("Anya and Kit — here is the family-portrait plan we discussed. Please tell me if anyone is missing or anyone is named twice.") - The list of formal_group_compositions[], each as a card with the group label and the list of named relatives (cultural vocabulary preserved verbatim). - The no-photograph list as a final card if non-empty. - A note that this is a confirmation page only — the couple cannot edit; they reply by message. Hard rules: - Do NOT include shot timings, location floor plans, or unstructured mentions. The couple sees only the family- portrait list and the refusal list. - Preserve every cultural family-vocabulary term verbatim. - The output is a fragment of body HTML; do not include , , or tags. No commentary outside the HTML. ``` ## 5. Use cases & content to include Build dedicated UI sections or flows for each of these — they tell you what content the app must support. - **The Manila first anchor.** A Filipino wedding photographer at breakfast in Quezon City on a Saturday in August. The brief enumerates four sets of principal sponsors (the ninongs and ninangs of the bride and the groom, plus the secondary sponsors who carry the veil, the cord, the arras, the candles). The family-portrait permutations are: couple + parents, couple + parents + grandparents, couple + bride's principal sponsors, couple + groom's principal sponsors, couple + all sponsors, couple + cousins, couple + ninongs only (the godfathers as a group), couple + ninangs only (the godmothers). The app lays out the smallest set cover, puts Lola Nena (95, in a wheelchair, the bride's great-grandmother) first, and writes "Lola Nena + couple" at 11:48 on the card before the ceremony heat. The cultural- traditions panel has surfaced the veil-and-cord and the arras candidates from the brief mentioning a "traditional Filipino Catholic ceremony"; the photographer confirms both. - **The London Yoruba-Igbo wedding.** A Nigerian-British photographer in Peckham, planning a wedding at the Civic Suite in Lewisham. The engagement ceremony happened on Friday at the bride's family home in Mitcham; Saturday is the white wedding. The brief includes the aso ebi colour-coding (bride's side in burgundy, groom's side in gold) and asks for one group shot per colour group inside the reception hall before the lighting changes at 18:00. The family- portrait permutations include three Igbo terms (nne, nna, dim) and four Yoruba terms (iya, baba, anti, egbon) the brief uses verbatim; the card preserves them with a gloss on first occurrence. Eli, the bride's brother, has asked not to be photographed at all — the refusal banner names him and is impossible to dismiss permanently. - **The Tulum beach wedding.** A Mexican wedding photographer planning a destination wedding at a cenote-side venue south of Tulum. The brief is in English; the family is half from Guadalajara and half from Brooklyn. The bride's abuela uses a wheelchair and the heat at 14:00 will be brutal — the app schedules her group shot at 11:20 in the morning, before the ceremony, in the shade of the property's coconut grove. The Catholic tradition adds lazo and arras candidate shots; the photographer accepts lazo, declines arras (the couple did not mention it). Sunset is at 19:47; the forecast says broken cumulus and 67% cloud cover; the golden-hour window starts at 19:08 (25 minutes earlier than the clear-sky default) and ends at 19:37, giving 29 minutes of usable cenote portraits. The day finishes with a sparkler exit at midnight that the brief mentioned in a Pinterest pin caption — surfaced in the "asked for, not in the list" panel until the photographer confirms it as a fixed shot at 23:55. - **The two-shooter day.** A primary + second-shooter team covering a 350-guest wedding in Lagos. The primary covers the bride's prep and the ceremony; the second covers the groom's prep and the reception entrance. The app produces two cross-referenced wallet cards and a shared timeline; at every moment, each shooter knows where the other is. The formal-group block is assigned to the primary; the candid coverage of the cocktail hour is assigned to the secondary. - **The civil + religious double ceremony.** A British-Indian couple in Birmingham: registrar's office at 11:00, gurdwara at 15:00, reception at 19:30. The brief enumerates both ceremonies in detail; the app produces a single day-plan with two ceremony blocks, two processionals, two key-moment captures, and one joint formal-portrait block in the gurdwara's courtyard between 17:00 and 18:00. The anand karaj phere candidate shots surface for the gurdwara ceremony; the registrar-office ceremony has no tradition candidates and uses category `ceremony_key_moment` for ring-exchange and signing. - **The reportage-only couple.** A documentary photographer shooting a small wedding in the Hudson Valley where the brief explicitly says "no posed couple portraits, all reportage". The app respects the constraint: the formal-group block is the only set of posed shots; everything else is `candid_reportage` with flex windows. The golden-hour window still appears, marked as "candid coverage during golden hour" rather than "couple portraits". - **The grieving relative.** The brief mentions that the bride's father passed away two years ago and the bride will be wearing a locket with his photograph. The app surfaces the locket as an `other_explicit_request` detail shot during the prep window with a verbatim quote citation; the formal-portrait block includes a "bride + mother + brother" composition the brief asked for, with a note in `notes_for_photographer` that the bride wants a moment alone with the locket between the family group and the ceremony. - **The change at 21:00 the night before.** The planner emails the photographer on Friday night that the gurdwara ceremony has moved from 15:00 to 15:15. The photographer pastes the email into the change-log input; the re-plan call shifts the ceremony anchor and re-flows everything downstream while preserving the prep timings and the formal-portrait timings. A `change_summary` paragraph at the top of the card reads: "Ceremony moved 15 minutes later (Maya, 8:42pm Friday). Formal portraits now begin 15 min later at 17:15. Reception unchanged." - **The breakfast voice memo.** The photographer eats eggs in Quezon City and records a 90-second voice memo at the table: "Right, one more thing — bride mentioned in her last text the surprise letter the groom is going to read in the suite at half-past two, and the dad-daughter dance has been replaced with a brother-sister dance because the dad has passed. Also she wants a shot of the rosary her mother is going to lend her for the ceremony." The voice memo is uploaded with the brief; the app adds three new shots, each with a citation pointing to the voice memo timestamps. ## 6. Page structure Build the following screens / sections in this order. Adjust copy to fit the voice, but keep the structural intent. 1. **Welcome / sign-in.** A photographed-looking image of a photographer's breakfast on a wedding morning: a half-eaten plate of eggs and tomato, a coffee cup mid-pour, a laptop with the couple's emails open, a camera body on the table beside a hand- written notebook. One paragraph: "Shot List turns a wedding brief and a venue's photographs into a planned day — by breakfast." A single Google sign-in button; Apple sign-in next to it. Below: "Try with the sample wedding" → loads the Anya & Kit Manila brief from section 8a. 2. **Empty state — "Plan a wedding".** Three big input methods: 📥 Paste the brief · 📁 Drop the brief files · 🎙 Record the breakfast memo. A short explainer below each ("Best for a quick thread paste", "Best for the full PDF + spreadsheet workflow", "Best for adding what you remembered while waiting for coffee"). 3. **Brief input view.** A two-pane layout. Left pane: brief intake (text paste box, file drop zone for `.eml`/`.mbox`/`.pdf`/ `.xlsx`/`.csv`, Pinterest URL or folder drop, voice-memo recorder with waveform). Right pane: venue intake (drop zone for venue photographs and floor plan, address field, optional venue-URL field, the wedding date and time-zone). Bottom: a big primary button labelled "Build the day". 4. **Processing view.** A vertical list of the build's stages, each with an honest progress label: "Reading the brief…" → "Mapping the family…" → "Reading the venue…" → "Resolving golden hour…" → "Resolving family-portrait groupings…" → "Laying out the day…". Each step takes 6-15 seconds. The user can leave the tab and return; progress persists. 5. **Day plan view.** A vertical timeline of the wedding day from prep to last dance, with each shot rendered as a card. The card shows the clock time, the short label (with cultural vocabulary preserved), the location area, the assigned shooter (primary or secondary), and a small chip showing the brief citation. The chip is tappable — tapping opens a side panel with the verbatim quote and the original message metadata. Sticky header: couple name → date → venue → the refusal banner (red, if non-empty). 6. **Family portraits view.** A dedicated panel showing the formal-group block. Each composition is a card with the group label, the ordered list of named relatives (cultural vocabulary preserved), the duration, the assigned position in the order, and the cited brief sentence. Wheelchair-using and elderly relatives are clearly flagged so the photographer knows they are scheduled first. The photographer can drag compositions to reorder, but the solver enforces the exclusion graph (divorced parents cannot be moved into the same composition). 7. **"Asked for, not in the list" panel.** A vertical list of the unstructured mentions: each item shows the verbatim quote, the source message label, the reason it was not auto-included, and three buttons: **Add as a shot** · **Dismiss** · **Ask the couple**. Add opens a quick shot-editor; Ask the couple composes a short message the photographer can copy-paste. 8. **Venue briefing view.** A one-page sheet: the floor plan with aisle-walk, light direction, and recommended portrait spots annotated; the venue's published photographs with thin red circles marking portrait spots and speech positions; a small weather summary in the corner ("Broken cumulus, 24°C, sunset 19:47, golden hour 19:08-19:37"). 9. **Wallet card view.** A double-sided A6 preview, designed for the photographer to read at a glance. Side A: the family-portrait block, ordered, with cultural vocabulary preserved. Side B: the day timeline by hour, the refusal text in red across the bottom. Print button → A6 PDF. Share button → copies to clipboard, AirDrop, photo-roll save. 10. **Two-shooter split view.** When a second shooter is configured, this view shows the two parallel timelines side by side. Each shot is colour-coded by assigned shooter. Drag a shot from one column to the other to reassign; the change is cited as a manual override. 11. **Change-log view.** A vertical list of every change the photographer has applied to the day plan, with the change summary, the timestamp, the source (Friday-night email, voice memo, manual edit). The user can undo the last change with one tap; older changes are non-undoable to prevent accidental rollbacks of the day-of state. 12. **Couple-confirmation share.** A modal: "Send the couple a read-only confirmation page". Generates a magic-link HTML page with the family-portrait list and the refusal list only; expires 14 days after the wedding date; never exposes timings, locations, or the photographer's notes. 13. **History.** A vertical list of every wedding planned, with couple names, date, venue, and a link to the archived plan. Plans are kept indefinitely; the photographer can delete a plan in 60 seconds via Settings. 14. **Footer.** "Built for photographers, not by them." Privacy: "The brief is yours. We never train on it." Capabilities `(i)` icon in header. ## 6b. First-visit onboarding Show a **first-visit onboarding** the first time a visitor lands on the app (detect via `localStorage` flag; do not show on return visits). Three slides, dismissible at any time. Persistent re-entry: a `?` icon in the header reopens it. **Slide 1 — What this is.** - Headline: "Welcome to Shot List." - Subhead: "A wedding day, planned by breakfast — in any culture, any venue, any sun, with every group-portrait permutation resolved and nobody important missed." - One paragraph (≤ 60 words) explaining who this is for and what makes it different from a generic schedule app: it reads the couple's brief as the source of truth, it solves the family- portrait permutations deterministically, it respects no-photograph refusals, and it never invents shots the couple did not ask for. - Visual: a small annotated illustration of a wedding day on a vertical timeline, with the family-portrait block highlighted and the golden-hour window marked. **Slide 2 — Try it now.** - One short prompt: "Try with the sample wedding". - A live demo input pre-loaded with the Anya & Kit Manila brief from section 8a. - 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the family-portrait permutation solver, the golden-hour adjustment for cloud cover, the "asked for, not in the list" panel). **Slide 3 — How to remix this.** - Headline: "Make this yours." - Three short bullets: - "Swap the sample brief in `/data/seed-wedding/` for your own." - "Adjust the cultural-tradition library in `/data/traditions/` for the ceremonies you cover most." - "Wire up your Gemini API key and Firebase project via the env-var list in the capabilities panel." - Primary CTA: "Use this template" → links to AI Studio Build remix entry point. - Secondary: "Just exploring — close" (sets localStorage flag, never auto-shows again). **Accessibility:** focus trap, `Esc` closes, `role="dialog"`, `aria-modal="true"`, `aria-labelledby`, focus restored to trigger on close. Respect `prefers-reduced-motion`. **Don't:** - Don't gate content behind the modal. The page beneath must be fully usable. - Don't auto-reshow on return visits. Use `localStorage['onboarding-seen-v1']`. - Don't include unrelated CTAs (newsletter signup, social follow). Keep it about the template only. ## 6c. Capabilities info button (persistent in header) Add a persistent `(i)` icon in the top-right of the header (next to the primary nav). Click → opens a modal/panel titled **"What powers this app"**. **Panel contents (in this order):** **Gemini capabilities used (the hero list):** - **Gemini 3.5 Flash (long context, 1M tokens)** — reads the whole brief in one call: the planner email thread, the questionnaire PDF, the family-relations spreadsheet, the breakfast voice memo, the change-log threads. - **Gemini 3.5 Flash (multimodal)** — reads the venue's published photographs and the floor plan to mark the aisle-walk position and recommend couple-portrait spots, and reads the breakfast voice memo as audio in the same call. - **Gemini 3.5 Flash (multilingual)** — brief documents in Tagalog, Yoruba, Igbo, Mandarin, Cantonese, Korean, Tamil, Hindi, Urdu, Bengali, Punjabi, Amharic, Swahili, Farsi, Khmer, alongside the European set; the card preserves cultural family vocabulary verbatim. - **Gemini 3.5 Flash + grounded search** — resolves the day's forecast and sun times against the country's met service so the golden-hour window is real for the day, not approximated. - **Server-side permutation solver** — the family-portrait permutations are NOT solved by the LLM. A deterministic set-cover algorithm on the server takes the family graph and the couple's requested groupings and emits the smallest ordered list that covers every named relative — because dropping a relative is the failure this app exists to prevent. - **Firebase Auth** — Google and Apple sign-in, magic links for the couple-confirmation share. - **Firestore** — stores your weddings, briefs, family graphs, day plans, change logs; syncs across devices in real time. - **Firebase Storage** — keeps the uploaded venue photographs, floor plans, voice memos, generated PDFs. - **Cost note** — see the detailed breakdown in 6d. A typical wedding plan costs about $0.18 of Gemini API spend; weekly studio use rarely exceeds $5/month. - **Privacy note** — your briefs and family graphs are private to you and any second shooters you explicitly add. This app uses the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms. The no-photograph list is treated as a hard constraint and never logged. **Backend services this app depends on:** - Auth: see section 4b - Database: see section 4b - Storage: see section 4b - Email: see section 4b - Payments: see section 4b (not used in v1) - External APIs: see section 4b **Environment variables you'll need to configure:** - `GEMINI_API_KEY` — your Google AI Studio API key - `FIREBASE_PROJECT_ID` — your Firebase project id - `FIREBASE_SERVICE_ACCOUNT` — service-account JSON (server-side) - `OPENWEATHER_API_KEY` — optional, only if you want higher rate limits than the grounded-search forecast call **Cost + privacy notes:** - One short paragraph per cost-sensitive capability: long-context calls are billed per token of input — a typical 80k-token brief parse costs about $0.10. The re-plan-after-change call is smaller (~$0.03) because it re-uses the existing plan. The weather call is $0.001 per wedding. - One short paragraph on privacy: where the data lives (your Firebase project), how to delete it (Settings → "Delete this wedding forever" — gone in 60 seconds), what is never sent for training. Couple-confirmation share links auto-expire 14 days after the wedding date. **Documentation links:** - AI Studio Build docs - Gemini API multimodal, multilingual, long-context, grounded- search docs - Firebase Auth, Firestore, Firebase Storage docs - A short note on the set-cover algorithm used for family-portrait permutations **Accessibility:** same standards as the onboarding modal — focus trap, `Esc`, ARIA, restored focus. **Behaviour:** - Always available — single click from anywhere in the app. - Tooltip on the `(i)` icon: "How this app is built". - Mobile: opens as a full-screen sheet that slides up. - Should be the most honest part of the app — never hand-wave service requirements; never say "AI" without naming the specific Gemini model and capability. ## 6d. Detailed cost breakdown (deployer reads this BEFORE shipping) - **Brief + venue + voice-memo parse (Gemini 3.5 Flash, high thinking, long context)** — typical input: 60-100 KB of email text + 8 venue images + a 90-second voice memo ≈ 80k tokens input, ~6k tokens output. ~$0.10 per wedding. - **Re-plan after change (Gemini 3.5 Flash, medium thinking)** — typical input: prior DayPlan (~10k tokens) + change message (~1k tokens), ~3k tokens output. ~$0.03 per re-plan. - **Weather + golden-hour (Gemini 3.5 Flash + grounded search)** — ~$0.001 per wedding. - **Floor-plan and venue-photo annotation (Gemini 3.5 Flash, medium thinking)** — ~$0.04 per wedding. - **Wallet-card abbreviation (Gemini 3.5 Flash, low thinking)** — ~$0.002 per wedding. - **Couple-confirmation HTML (Gemini 3.5 Flash, low thinking)** — ~$0.001 per wedding. - **Expected per-wedding cost end to end:** ~$0.18. **Solo photographer doing 30 weddings a year:** ~$5.50 / year of Gemini API spend. **A studio with 8 shooters running 200 weddings a year:** ~$36 / year. - **Image and audio storage:** Firebase Storage standard tier, ~$0.026/GB/month. A wedding's venue photographs + floor plan + voice memo + generated PDFs total ~30 MB; a year of 30 weddings uses ~1 GB ≈ ~$0.03/month. ## 7. Design language - **Mood:** A photographer's notebook on a kitchen table at 06:45. Not a SaaS product. Not a wedding-industry brochure. The hush before a long shoot, the second coffee, the open laptop, the camera body on the placemat next to a saucer. The user is about to spend twelve hours holding a camera; the app's job is to make the next eight minutes worth the time. - **Typography:** Clean grotesque for app chrome (Inter or Geist). A reading serif for the wallet-card preview (Source Serif Pro or Adobe Caslon Pro) so the card looks like a printed shot list the photographer wrote by hand, not a digital UI screenshot. A monospaced accent (JetBrains Mono or Geist Mono) for clock times on the timeline, because numerals need to align. - **Palette:** Warm paper background `#F6F1E8` for the day-plan view, deep ink `#1A1A1A` for body text, accent terracotta `#A35E3F` for the brief-citation chips and the cultural-vocabulary highlights, muted forest `#3A5A47` for the formal-portrait card borders, a faded blue `#3A5773` for the photographer's own manual overrides, and a deep red `#9A2A2A` reserved for the no-photograph refusal banner and nothing else. Borrowed from a Moleskine sketchbook on a breakfast table, not from a wedding blog. - **Imagery:** The venue's own photographs are the hero. Never cropped, never filtered, only annotated with thin SVG overlays. Sample weddings in the gallery use generated photographs in the same palette — paper textures, breakfast tables, camera bodies, notebook pages. No couples in frame. The wedding is the couple's; the app is the photographer's. - **Hand-feel touches:** A barely-visible paper grain on the day- plan background. The "view brief citation" expandable panel slides the verbatim quote in with a thin shadow — like a slip of paper sliding from under a notebook page. Drag-and-drop a shot on the timeline gives a soft paper-rustle micro-feedback (audio optional, disabled by default). - **Spacing:** consistent 4-px base. Generous whitespace — the timeline needs air between shots so the eye can scan at speed. - **Radius:** consistent token set (6 / 12 / 20 px). Shot cards use 6; the wallet-card preview uses 12; the welcome card uses 20. - **Shadows:** subtle, layered, paper-tinted. Avoid heavy drop- shadows. - **Motion:** purposeful — entrance fades, hover lifts, page transitions. Respect `prefers-reduced-motion`. No bouncing splash animations. The wallet-card flip from side A to side B is the one place where motion carries meaning; respect reduced-motion by jumping rather than animating. - **States:** every interactive element has hover, focus, active, disabled. Loading uses skeletons not spinners where possible. Empty states have helpful next-action guidance ("Paste the brief, or drop the planner's email thread"). ## 8. Content generation rules - Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'. - Invent plausible names, dates, venues, family relationships, brief excerpts. Lean on twentieth- and twenty-first-century wedding patterns across cultures — never claim a fictional wedding is a real one. - Tone: warm, direct, free of corporate language. This template is for a working photographer, not a wedding-planning company. - Headlines: punchy and concrete. No 'Empower your X' filler. No 'Revolutionize'. No 'Seamless'. - Body copy: short paragraphs (2-4 sentences). Use lists where appropriate. - Plain language. Avoid jargon — except where the user already speaks the jargon (the photographer wants to see "anchor: fixed" on the timeline; the wedding planner wants to see "aso ebi groupings" in the formal-portrait panel). - Where the app outputs AI-generated content, never label it as "AI says" — let it speak naturally. Use small uncertainty cues only where epistemic honesty requires them (a low-confidence family-graph inference shows as a dashed border on the PersonInFamily card; tapping it reveals the source quote). - The "asked for, not in the list" panel is always visible when non-empty; never collapsed behind a toggle. ## 8a. Seed content (use these specific examples) Anchor every generated copy + sample data point in the concrete content below. Use these names, numbers, dates, and snippets verbatim where helpful, or generate close variants that sit in the same world. **Sample weddings (sidebar):** - **"Anya & Kit, Manila"** — Filipino Catholic wedding, San Agustin Church, 16:00 ceremony, reception at the Manila Hotel. Four sets of principal sponsors, veil-and-cord-and-arras, Lola Nena (95, wheelchair) is the bride's great-grandmother. Brief is in English with Tagalog family vocabulary; 47 pages total across the planner email thread, the questionnaire PDF, and the family-relations spreadsheet. - **"Folake & Adaeze, Lewisham"** — Yoruba-Igbo wedding at the Civic Suite. Engagement ceremony Friday at the bride's family home in Mitcham; white wedding Saturday. Aso ebi colour-coding (burgundy for bride's side, gold for groom's side). Eli (the bride's brother) has asked not to be photographed; this is the refusal-banner test case. - **"Sofía & Marco, Tulum"** — destination wedding at a cenote- side venue south of Tulum. Family half from Guadalajara, half from Brooklyn. Abuela (89, uses a wheelchair) is the test case for early-morning portrait scheduling. Sunset 19:47, broken cumulus forecast, golden hour adjusted to 19:08-19:37. - **"Priya & Arjun, Birmingham"** — civil + religious double ceremony. Registrar at 11:00, gurdwara at 15:00, reception at 19:30. The anand karaj phere candidate shots surface for the gurdwara ceremony. - **"Hannah & Daniel, Hudson Valley"** — documentary-only brief. No posed couple portraits. The formal-group block is the only set of posed shots; everything else is `candid_reportage`. The bride's father passed away two years ago; bride is wearing a locket with his photograph. - **"Tien & Bao, San Jose"** — Vietnamese Buddhist wedding at a pagoda in the morning, reception at a Vietnamese seafood restaurant in the evening. Brief is bilingual Vietnamese- English; the tea ceremony adds candidate shots for the bowing to elders sequence. **Sample brief in detail view (the demo loads this):** - **Couple:** Anya & Kit - **Date:** 2026-08-15 - **Venue (ceremony):** San Agustin Church, Intramuros, Manila - **Venue (reception):** Manila Hotel, Roxas Boulevard - **Time-zone:** Asia/Manila (PHT, UTC+8) - **Working language:** English - **Brief documents:** - Planner email thread, 23 messages, May 4 to August 12 - Questionnaire PDF (filled in by Anya, May 18) - Family-relations spreadsheet (filled in by Kit's mother, May 22) — 47 named relatives - Pinterest board (28 pins), exported as captions - **Voice memo (90 sec, breakfast 06:42 on the day):** "Right, the surprise letter at half-past two in the suite. The brother-sister dance instead of dad-daughter. Lola Nena needs to be first because the wheelchair plus the heat. And the rosary — bride's mother is lending the bride her own rosary for the ceremony, want a detail shot of it in the prep window." - **Family graph extracted (excerpt):** - Lola Nena (95, uses wheelchair) — bride's maternal great- grandmother — first in the formal-portrait order - Tita Lily — bride's maternal aunt — appears in the bride-side group AND in the godmothers group (she is also a ninang) - Ninong Ramon — groom's principal sponsor (the groom's father's college roommate) — appears in the groom-side sponsors group and in the all-sponsors group - 47 named relatives total - **Formal-portrait groupings the couple requested (excerpt):** - Couple + Anya's parents - Couple + Kit's parents - Couple + Anya's family (bride-side, full) - Couple + Kit's family (groom-side, full) - Couple + all four sets of principal sponsors together - Couple + Anya's ninangs only - Couple + Kit's ninongs only - Couple + Lola Nena and the great-grandchildren - Couple + Kit's three brothers - **Sun and weather (resolved):** - Sunrise 05:47 PHT - Sunset 18:23 PHT - Civil twilight end 18:46 PHT - Forecast: scattered thunderstorms in the late afternoon, moving south from Bulacan, 74% cloud cover at 17:30 - Golden hour adjusted: 17:08-17:38 (25 min earlier than standard, 25 min instead of 60 min) - **"Asked for, not in the list" (excerpt):** - "Surprise letter the groom is reading in the suite" — voice memo, 06:42 — photographer action pending - "Brother-sister dance" — voice memo, 06:42 — photographer action pending - "Rosary detail shot" — voice memo, 06:42 — photographer action pending - "The bride's mother's lace handkerchief, family heirloom" — questionnaire PDF, page 4, May 18 — photographer action pending - **Refusal summary for banner:** null (none requested) - **Reading confidence:** 0.94 **Sample input artefacts (for the build to demonstrate):** - A 23-message planner email thread (May to August) in English with embedded Tagalog family vocabulary. - A questionnaire PDF the bride filled in with answers to 18 prompts (favourite moment from prep, what they want to remember most, family member who travelled furthest, etc.). - A family-relations spreadsheet with 47 rows — name, relation to bride, relation to groom, accessibility notes, role in the day, contact email, no-photo flag. - A folder of 12 venue photographs (San Agustin Church interior, exterior, Manila Hotel ballroom, the hotel's pool- side area for cocktail hour) plus a floor plan PDF of both venues. - A 90-second breakfast voice memo recorded by the photographer at the table. **Sample voice copy:** - Onboarding: "Paste the brief. We'll plan the day. Eat your eggs." - Processing: "Reading the brief…" / "Mapping the family…" / "Reading the venue…" / "Resolving golden hour…" / "Resolving family-portrait groupings…" / "Laying out the day…" - Empty plan: "This plan is waiting for the brief. Paste a thread, drop a PDF, or start with the sample wedding." - Error (couldn't read venue photos): "We couldn't make out the venue from these photos. Want to add a clearer set, or describe the orientation in a sentence?" - Save confirmation: "Day plan saved — Anya & Kit, 15 August 2026." - Refusal banner copy: "Eli (bride's brother) has asked not to be in any photographs. Acknowledge before continuing." - Cultural-tradition prompt: "Brief mentions Filipino veil-and- cord-and-arras. Add the candidate shots? — Yes · No · Show details." - Low-confidence family-graph note: "We're not certain about Tito Eddie's relationship to the bride. Tap to see the source quote." **Sample couple-confirmation email subject + body:** - Subject: "Anya & Kit — quick confirmation of the formal-portrait list" - Body: "Hi Anya, Kit — I've put together a plan for Saturday based on everything we've talked about. Could you take a look at the formal-portrait list (especially the relatives I've named) and flag anyone I've missed or named twice? It's a read-only page, no log-in needed. Reply by Friday evening if you can — see you Saturday at 06:30." [Open Confirmation Page] ## 9. Media & assets - **Hero image (landing screen):** A photographed-looking shot of a photographer's breakfast on a wedding morning — a half-eaten plate of eggs and tomato, a coffee cup mid-pour, a laptop with the couple's emails open in muted view, a camera body on the table beside a hand-written notebook. Generate via Nano Banana 2 with a prompt emphasising "wooden breakfast table, early morning window light from the left, slight blur on the coffee cup, sharp focus on a notebook with hand-written shot-list scribbles, no people in frame, soft shadow under the camera body, warm-paper notebook page". - **App icon / wordmark:** Set in the clean grotesque. Slightly worn-paper texture behind it. No icon — just type. - **Empty-state illustration:** A simple line drawing of a vertical timeline with two shot cards on it. Hand-drawn aesthetic, not a flat icon. - **Demo venue photographs:** Generated per the prompts in section 8a — Nano Banana 2 prompts that specifically request "interior of a Spanish-colonial church in Manila, afternoon light through the side windows, no people in frame", "ballroom of a 1920s grand hotel in Manila, table layout for a wedding reception, no people in frame", "cenote at golden hour with natural rock surroundings, no people in frame". Each demo photograph should look photographed, not rendered. - **Floor plan demo:** A vector floor plan PDF showing San Agustin Church's nave with seating, aisle, and altar marked. - **Stock fallbacks:** If image generation fails, fall back to the photographed sample venue from `/public/samples/sample- venue.jpg`. Never to a "💒" emoji. - **Generated imagery:** prefer Nano Banana 2 over stock photography. Prompt for warmth, asymmetry, and slight imperfection — avoid the glossy 'AI render' look. - **Optimisation:** WebP/AVIF, `loading="lazy"`, explicit `width`/`height` to prevent layout shift. - **Icons:** `lucide-react` for UI. Use sparingly — never decorative-only. ### Build-time asset manifest (explicit specs) Every image, illustration, and visual reference mentioned above must resolve to ONE of the three buckets below — runtime-generated, seed-shipped, or user-supplied. Do NOT ship `` tags whose `src` is not listed here. Do NOT depend on bare "section 8a prompts" without binding them to explicit paths and model IDs. **Bucket 1 — Runtime-generated (Nano Banana Pro `gemini-3-pro-image` for hero/demo photographs; Nano Banana 2 `gemini-3.1-flash-image` for in-app illustrations and reference-conditioned variants).** Cached to Firebase Storage; served via signed URL. Every reference above to "Nano Banana 2" or "Nano Banana Pro" MUST be wired to one of these specific calls with an explicit model id: - `/public/generated/hero.webp` (2400×1500, WebP) — model `gemini-3-pro-image` — uses the literal prompt described as "Hero image (landing screen)" above. Run once at build; commit a `/public/samples/hero-fallback.webp` (1600×1000) generated from the same prompt with `gemini-3.1-flash-image` so the page renders if quota is exhausted. - `/public/generated/demo/{demo-slug}-{NN}.webp` (1600×1200, WebP) — model `gemini-3.1-flash-image` (reference-conditioned where the prior frame is passed as input) — one path per "Demo X" image referenced above. The slug derives from the seed example in section 8a; the NN index covers each frame in the demo sequence. - `/public/generated/illustrations/{name}.webp` (1024×1024, WebP) — model `gemini-3.1-flash-image` — one path per named illustration above ("Empty-state illustration", "Recipe-card hero illustrations", "Curriculum picker imagery", "Period-style frames", etc.). Each illustration's prompt is the literal description above; ship a deterministic seed in the request so re-runs are reproducible. **Bucket 2 — Seed assets shipped with the deliverable.** Every "Stock fallback" path referenced above (e.g. `/public/samples/sample-X.jpg`) is generated once via Nano Banana 2 (`gemini-3.1-flash-image`) at 1024×1024 WebP using the same prompt as its Bucket-1 counterpart, then committed to the repo so the page renders identically if Gemini quota is exhausted or the user is offline. Replace any `.jpg` extension above with `.webp` to match the optimisation rule. Also commit these empty-state seeds (1024×1024 WebP, single-stroke hand-drawn line, no colour fill): - `/public/samples/empty-state-primary.webp` — line drawing of the app's primary empty surface (the named "Empty-state illustration" above), generated from that exact prompt. - `/public/samples/empty-state-archive.webp` — line drawing of an empty saved/archive view, single-stroke outline. - `/public/samples/empty-state-error.webp` — line drawing of a hand placing a single object aside with care, used when an AI call fails. **Bucket 3 — User-supplied.** Uploads from the user's camera / file picker land at the Firebase Storage path conventional for this template (named in section 4b). The build ships with Bucket-1 + Bucket-2 only; no user-supplied images at first paint. **Hard rules** - Every `` tag MUST have a `src` that resolves to a path listed in Bucket 1, Bucket 2, or a Bucket 3 upload path. Anything else is a build error. - No bare `image.jpg` / `hero.jpg` / `placeholder.png` references anywhere in the code. - Model IDs: `gemini-3-pro-image` for hero-quality photographic generation; `gemini-3.1-flash-image` for in-app illustrations, reference-conditioned variants, empty-state seeds, and stock fallbacks. Never use a legacy model id (no `imagen-*`, no `gemini-1.5-*-image`). - File format: WebP everywhere (AVIF acceptable where the target browsers support it). No `.jpg` / `.jpeg` / `.png` in `/public/samples/`. ## 10. Interactivity & states - Every interactive element has hover, focus, active, and disabled states. - Forms validate inline and show specific error messages (not "Invalid input"). - Loading states use skeletons that match the eventual layout, not spinners. - Empty states explain the next action with a button whose label fits THIS app's domain: "Paste the brief", "Drop the planner's email thread", "Record the breakfast memo" — never a generic "Add your first item". - Smooth scroll for in-page anchors. - All AI-generated content streams in token-by-token where supported, with a clear "thinking…" indicator before content starts arriving. - If an AI call fails, show a calm, specific error ("We couldn't read the venue from these photos — try a clearer set, or describe the orientation in a sentence?") and offer retry. - Low-confidence family-graph entries are rendered with a dashed border on the PersonInFamily card; tapping reveals the source quote. - The day-plan timeline supports drag-to-reorder for soft anchors; fixed and sun-dependent anchors snap back if dragged outside their constraints. - The refusal banner is sticky on every view; the photographer must acknowledge once per session before it collapses. - The "asked for, not in the list" panel is always visible when non-empty; never collapsed behind a toggle. ## 11. Tech & responsive requirements - **Code-generation calls — pin `maxOutputTokens: 8192`** on any Gemini call whose `systemInstruction` asks the model to emit code (HTML, TypeScript, Python, Kotlin, Swift, SQL, etc.). Without an explicit cap the generation can truncate mid-function on long files. 8192 is the current Gemini 3.5 Flash output ceiling. - **File downloads on Safari / Firefox:** when offering local-disk save of any export (PDF, CSV, MP3, ZIP, JSON, image), fall back to `` with a blob URL — the File System Access API (`showSaveFilePicker()`) is Chromium-only. Detect with `'showSaveFilePicker' in window`; otherwise use the anchor-download path. - **Stack:** React + TypeScript + Tailwind CSS. Functional components + hooks. Use Shadcn UI primitives where appropriate. - **Build runtime:** AI Studio Build — full-stack with Cloud Run server-side functions. All Gemini API calls happen server-side; API key lives in Secrets Manager, never in client bundle. - **Model selection:** explicitly pin `gemini-3.5-flash` for the brief parse, the re-plan, and the venue annotation; `gemini-3.5-flash` for the weather resolution, the wallet-card abbreviation, and the couple-confirmation HTML. Set `thinkingLevel` explicitly per call. - **Family-portrait permutation solver:** a deterministic TypeScript function on Cloud Run. Implements a greedy minimum-set-cover with constraints (exclusion graph, accessibility-first ordering). Unit-tested with the seed weddings. - **Database:** Firestore (auto-provisioned by AI Studio Build). Show the sample weddings on first launch. - **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it; magic-link email for couple-confirmation shares. - **Storage:** Firebase Storage for venue photographs, floor plans, voice memos, generated PDFs. Pre-signed URLs only. - **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+. - Use `clamp()` for fluid typography. Prefer container queries over media queries for component-level responsiveness. - Use `dvh` / `svh` instead of `vh`. Respect safe-area insets on iOS. - Zero horizontal overflow at any width. Zero layout shift on load. - Persist user data in Firestore. Use real-time listeners on the day-plan view. - Optimistic UI on writes; reconcile on response. - Voice-memo capture uses the Web Audio API; falls back to native recorder on iOS Safari. - **iOS Safari gotchas (graceful degradation):** Safari `MediaRecorder` only supports `audio/mp4` (AAC) — persist as AAC mono; mic permission does NOT persist across reloads on iOS — re-request on every memo; an incoming call interrupts the audio session (`MediaStreamTrack.onmute` fires) — auto-pause and prompt resume; backgrounded Safari tabs pause `getUserMedia` — combine `visibilitychange` with a screen Wake Lock during memos. ## 12. Accessibility (WCAG 2.2 AA) - Semantic HTML — `header`, `nav`, `main`, `section`, `article`, `footer`. - All interactive controls reachable by keyboard with a visible focus ring. - Color contrast ≥ 4.5:1 for body, 3:1 for large text and UI components. The refusal banner's red is verified at 4.5:1 against the warm-paper background. - All images have meaningful `alt` text. The venue's photographs have `alt` describing the venue area ("interior of San Agustin Church showing the central aisle facing the altar, afternoon light from the south windows"). - Form fields have associated `