================ 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. --- # Park Walk Audit ## 1. Project **Park Walk Audit** is a field tool for the people who walk a public park and notice everything that doesn't work for somebody. The user opens the app, hits record, walks for fifteen or twenty minutes with the camera held at chest height, and at the end receives a numbered list of findings — every kerb without a cut, every broken bench slat, every missing tactile paving panel, every uneven path joint, every unlit underpass section, every drinking fountain mounted out of reach of a wheelchair — each one with a bounding-boxed screenshot pulled from the exact second of video where the model saw it, the GPS point, the timestamp, and a one-sentence factual description. The findings become a PDF the user can hand to a city council, a parks department, a community board, or a local newspaper. This is the kind of app a community accessibility advocate in Toronto opens on a Saturday morning before her ward meeting on Wednesday — she has fifteen minutes to walk one corner of Trinity Bellwoods and wants three concrete defects on paper by Wednesday night. It is also the kind of app a wheelchair user in Quezon City opens before the LGU plans to repave Ninoy Aquino Parks and Wildlife Center, walking the same loop her physiotherapist asked her to walk and tagging every place she has to ask a stranger for a push. And it is the kind of app a parent of a blind seven-year-old in Cairo opens at Al-Azhar Park on a Friday afternoon, walking the route from the east gate to the playground holding her son's hand, tagging every place the tactile trail breaks, every bollard at head height, every kerb edge with no warning strip. Same shape of walk, different city, different body asking the questions. The single demo that proves the magic: hit record at the park gate → walk for twenty minutes at a normal pace → hit stop → in under ninety seconds the user sees a numbered list of findings. Finding #7: "Kerb without a curb cut at the eastern path junction, ~3 cm vertical lip, no tactile warning surface." Below the line, a still frame from the video at 11:42 with a yellow bounding box drawn around the kerb edge, the GPS pin showing the exact metre, and a calm sentence the user can hand to a council clerk on Monday. A second tap exports the full audit as a PDF with the park name, the audit date, the walking route on a map, the findings table, and the bounding-boxed evidence frames — twelve pages, in plain English, council-ready. And in the cases where the park has been audited before — by the same advocate six months ago, by a separate volunteer last spring — the app reads the previous PDF and tells the user which findings from last time appear to have been fixed (no longer in frame), which are still present (now finding #12 in this audit, previously finding #8 in March), and which are new since. **Tagline:** _Walk any park, in any city, for any body — and let the model see every kerb, bench, sign, and ramp that doesn't yet work._ ## 2. Target audience - Community accessibility advocates auditing local parks, plazas, and waterfronts ahead of council meetings, budget hearings, and master-plan consultations - Wheelchair users, blind and low-vision walkers, and parents pushing strollers — people whose daily route is the audit - City accessibility coordinators and ADA / Equality Act / RA 7277 (Philippines) / Disability Discrimination Act (various) compliance staff - Disability-rights NGOs running citywide park surveys — Rick Hansen Foundation chapters, Sightsavers in Lagos, Christoffel-Blindenmission in Nairobi, AccessNow in Toronto, Disability Inclusive Development partners in Manila and Dhaka - Park maintenance teams and grounds-keeping leads who want a structured weekly walk-through with photographic evidence, not a clipboard - Landscape architects and civic design firms inheriting a park renovation brief who need a baseline of what's actually wrong before they design - Local journalists and councillors tracking whether last year's accessibility budget produced anything walkable - Schoolchildren on civics projects, scout troops earning a community-service badge, and university urban-planning students doing fieldwork - Visiting auditors comparing the same park across seasons (snow drifts cover kerb cuts; autumn leaves hide tactile paving; summer crowds reveal bench shortages) ## 3. Core value propositions Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app. - **Walk the park, end up with paper** — the artefact at the end is a PDF a councillor will actually read. Numbered findings, one per page in the long form, evidence frames, a route map, the auditor's name, the date, the weather, and a signed cover letter the council clerk can file. No spreadsheet to fill in afterwards. - **The model sees only what's in frame** — every finding is anchored to a specific second of video and a still frame with the defect bounding-boxed. The model will refuse to list a finding it can't point to in the footage. No "the entrance is probably inaccessible" — only "the eastern entrance kerb at 11:42:03 has a 3 cm vertical lip and no tactile warning, see frame". - **Spatial understanding, not object detection** — Gemini 3.5 Flash reads the scene: a bench is broken because a slat is missing AND the missing slat is in the middle of the seat so a child could fall through. A kerb is non-compliant because the slope exceeds what a manual wheelchair user can climb solo. The judgement is grounded in the visible geometry, not in a pre-trained object label. - **The body asking the question** — the audit lens is the user's. A wheelchair-user lens flags kerbs, slopes, narrow gates, raised manhole covers, bench-without-armrest, and accessible toilets. A blind / low-vision lens flags tactile paving gaps, bollards at head height, missing audio at crossings, unmarked steps. A parent-of-small-child lens flags fall hazards, sharp edges at child height, water features without a barrier. The same walk produces different lists. - **Multi-language** — the spoken findings, the PDF, and the cover letter all render in the language of the city council the auditor will hand them to: English, Spanish, French, Portuguese, Arabic, Tagalog, Vietnamese, Mandarin, Cantonese, Korean, Tamil, Hindi, Urdu, Bengali, Punjabi, Amharic, Swahili, Farsi, Khmer. The findings text follows the local accessibility law's terminology, not US ADA terminology, when the user picks a locale. - **Walk-against-walk** — upload last quarter's audit PDF and the app reads it. The new audit annotates which findings appear resolved, which are still present, and which are new. The council sees progress (or lack of it) on the same loop, photographed twice. - **The original footage is the citation** — every finding line in the PDF is timestamped and the full source video stays on the user's device. If a council member asks "where exactly did you see that broken bench?" the user scrubs to the second and the model points at it. The footage is never sent to model training (paid Gemini API tier). - **Walking pace, not lab pace** — the audit assumes the user walks the park as they would on any day. No tripod, no measurement tape, no perfect lighting. The model handles natural camera shake, overcast light, and dappled tree shadow. If it can't tell, it says so and asks the user to revisit that spot. ## 4. Features to build - Live-video capture session with chest-height framing guidance, audio recorded for the user's spoken notes - "Pick an audit lens" before recording: wheelchair, blind / low-vision, ambulatory mobility-impaired, parent-of-small-child, deaf / hard-of-hearing, neurodivergent, elder, dog-on-leash, baseline-generic (the union of all) - GPS track recorded alongside the video at 1 Hz so every frame has coordinates - Pause / resume mid-walk (a phone call, a conversation with another park-goer, a coffee from the kiosk) - Spoken-note voice tagging — at any moment the user says "flag this" or holds the screen, and the model knows this second is high-signal - Long-form video upload from gallery (some users prefer to walk first with a body-cam and upload later) - Post-walk processing: Gemini 3.5 Flash reads the video chunk-by-chunk and emits structured `Finding` objects with bounding boxes, timestamps, GPS, severity, and a one-sentence factual description - Frame extraction at each finding's anchor second — the still frame is what gets shown in the PDF - Bounding-box rendering on the still frame, with the box colour matched to severity (yellow = note, amber = action, red = urgent / safety) - Findings list view with grouping by category (paths & ramps, seating, signage & wayfinding, tactile & sensory, lighting & safety, toilets & water, play areas, gates & entrances) - Each finding has a "revisit this" button that scrubs the video to the second, a "flag as not a finding" button (so the auditor can dismiss false positives), and a "merge with #" button (two findings of the same defect from two angles) - Manual finding addition — the user can scrub to a moment the model missed, drop a marker, and write the finding by hand - PDF export with cover letter, route map (from the GPS track), findings table grouped by category, evidence frames, and the auditor's signature line - CSV / structured JSON export for compliance staff who roll up many audits - Walk-against-walk comparison: upload a previous audit PDF or its JSON, see resolved / persistent / new findings on the new audit - Local-law preset for the cover letter and the finding terminology (Ontario AODA, US ADA + 2010 Standards, EU EN 17210, UK Equality Act 2010, RA 7277 Magna Carta for PWD in the Philippines, Egyptian Law 10/2018, Indian RPWD 2016, etc.) - Council letter generator — once the PDF is exported, the user picks a council recipient and a short cover letter drafts itself in the council's language with the three highest-severity findings called out - On-device storage of the video by default, with explicit one-tap "send to my dropbox / drive" for the user's own archive - Privacy guardrails: faces and licence plates blurred automatically on every exported frame; the original video stays unredacted on device for the auditor's reference - "Share this audit with another auditor" — invite a co-auditor to add a second walk through the same park; the app aligns the two walks on the map and merges findings - Accessibility of the app itself: full VoiceOver / TalkBack support; the live-video recording mode emits a spoken confirmation when a finding is auto-flagged so a blind auditor walking solo knows the model saw something ## 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) - **Spatial understanding (Gemini 3.5 Flash multimodal, video input)** — the load-bearing capability. The model reads the scene: the geometry of the kerb edge, the slope of the ramp, the height of the drinking fountain spout above the ground, the gap in the tactile strip. Bounding boxes are returned in normalised image coordinates `[ymin, xmin, ymax, xmax]` per the Gemini 3 spatial-understanding API; the app converts to absolute pixel coordinates against the extracted still frame. - **Video understanding (Gemini 3.5 Flash)** — the model accepts the walking video as input via the Files API. Video is chunked into 90-second segments to stay well under the 1M token context and to keep per-call latency manageable; each segment returns a list of `Finding` objects, each anchored to a timestamp in seconds-from-start. Server-side, segment timestamps are added to the GPS-track start time to produce wall-clock times. - **Structured output / JSON Schema** — every Gemini 3.5 Flash vision call returns JSON matching the `Finding` schema. The schema is included verbatim as `responseSchema`. - **Multilingual generation (Gemini 3.5 Flash)** — finding descriptions, severity rationales, and the council cover letter are written in the locale the user picked. Locales include English, Spanish, French, Portuguese, Arabic, Tagalog, Vietnamese, Mandarin (Simplified and Traditional), Cantonese, Korean, Tamil, Hindi, Urdu, Bengali, Punjabi, Amharic, Swahili, Farsi, Khmer. Accessibility terminology is matched to the named local law (AODA wording in Ontario, ADA wording in the US, RPWD 2016 wording in India, RA 7277 wording in the Philippines). - **Long context (1M tokens)** — used for walk-against-walk comparison: the previous audit's full JSON (often 50-200 findings) plus the new walk's full JSON are submitted together for the resolved / persistent / new diff. **Guardrail**: a typical 20-minute audit produces ~30-80 findings ≈ ~20k tokens; comparing four audits at once stays under ~100k tokens. For a citywide rollup across >50 parks chunk by park before the diff call. - **Search grounding (Gemini 3.5 Flash)** — used once, when the user picks a city or a park: the app fetches the relevant local accessibility law and the council's contact address. Findings descriptions use locally-correct terminology rather than US-centric ADA defaults. - **Gemini TTS (`gemini-3.1-flash-tts-preview`)** — used in the live recording mode for spoken auto-flag confirmations ("noted, finding at metre 142"). Used post-walk for an audio summary of the top three findings the user can play while walking back to the car. Language follows the user's chosen locale (`languageCode`); pronunciation follows the voice's native locale. - **Nano Banana 2 (`gemini-3.1-flash-image`)** — used for the route map illustration on the PDF cover page (a hand-drawn-feeling line of the walked route over a desaturated park outline) and for the "before / after" mockup the user can optionally request — a frame from the audit with a sketch overlay of how the defect could be fixed. Mockups are clearly labelled "illustrative suggestion, not a council recommendation". - **Thinking levels** — `medium` for the primary video-parse call (spatial reasoning over moving footage is the hardest task in the app). `low` for the council cover letter, the walk-against-walk diff, and the audio summary. `high` is the default for the "revisit + re-analyse this 30-second segment" call when the auditor asks the model to look more carefully at one spot. ### Backend services - **Auth — Required.** Firebase Auth with Google sign-in (auto-provisioned by AI Studio Build). **Apple sign-in is optional but user-configured**: it requires an Apple Developer account, Service ID, Key ID, and private key wired into the Firebase Auth console. **Magic-link email** (used to invite a co-auditor) also requires the sender domain to be authorised in Firebase Auth. Audits are private to the owner and any co-auditors the owner explicitly invites. - **Database — Required.** Firestore for `users`, `audits`, `findings`, `routes`, `audit_members`, `local_law_presets`. - **File storage — Required.** Firebase Storage for the walking videos (kept on device by default, optionally uploaded for cloud backup) and the extracted still frames used in PDFs. **Storage is NOT auto-provisioned by AI Studio Build today** — enable it in the Firebase console and wire the bucket name into the AIS Build project before the first upload. Videos are stored encrypted; pre-signed URLs only. - **Email — Required (transactional).** Co-auditor invitations via Firebase Auth magic links. Optional council-letter send: the app composes the email locally; the user reviews; the user sends from their own mail client. The app never sends council letters silently on the user's behalf. - **Payments — Not needed for v1.** Free for individual community use. A future "city-wide rollout tier" could charge per park audited per year for parks departments; not in scope here. - **External APIs:** Gemini API for all intelligence. Mapbox or MapTiler optional for the route-map render quality; a static OpenStreetMap tile fallback ships in `/data/tiles/` so the offline path works. **Environment variables:** every secret (Gemini API key, Firebase service-account JSON, Mapbox token if used) 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 this audit" inside the UI (gone in 60 seconds from Firestore and Storage) · explicit opt-in for any analytics · the user's walking video is never sent to Gemini for model 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) · faces and licence plates are auto-blurred on every exported frame before the frame is rendered into the PDF. **Read this first — prompt-craft rules that apply to every call in this template:** 1. **Name the model variant explicitly** in every Gemini API call. Do not let the agent pick the model. See the per-call matrix below. 2. **Pin `thinkingLevel` explicitly** per call. See the matrix. 3. **Seed the JSON Schema as a fenced TypeScript / Zod block** in the system instruction and as `responseSchema`. **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 on bounding-box coordinates are documentation only inside `responseSchema`; clamp on the server after the response arrives.** 4. **Pin the system instruction separately** from user input. Use `systemInstruction` for persona + behavioural rules; use `contents` for user input (video, prior audit JSON). 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. 7. **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; } ``` 8. **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. ### Per-call model + tools matrix | Call | Model | thinkingLevel | Tools enabled | |------|-------|---------------|---------------| | Parse a 90-second video chunk → `Finding[]` with bounding boxes | `gemini-3.5-flash` | medium | (none) | | Re-analyse a single 30-second segment when user asks for a second look | `gemini-3.5-flash` | high | (none) | | Walk-against-walk diff (resolved / persistent / new findings) | `gemini-3.5-flash` | low | (none) — long-context over two or more audit JSON inputs | | Generate council cover letter in the local language and local-law terminology | `gemini-3.5-flash` | low | (none) | | Look up local accessibility law and council contact for the chosen city / park | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) | | Audio summary: read the top three findings aloud for the walk back to the car | `gemini-3.1-flash-tts-preview` | n/a | n/a | | Route-map illustration for the PDF cover page | `gemini-3.1-flash-image` | n/a | n/a | | Optional "before / after" mockup of a fixed defect | `gemini-3.1-flash-image` | n/a | n/a | | Suggest a finding category from a short user description (manual finding add) | `gemini-3.5-flash` | low | (none) | *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"; // Normalised bounding box per Gemini 3 spatial-understanding API, // values in [0, 1] relative to the still-frame width/height. const BoundingBox = z.object({ ymin: z.number().min(0).max(1), xmin: z.number().min(0).max(1), ymax: z.number().min(0).max(1), xmax: z.number().min(0).max(1), label_short: z.string(), // "kerb, no cut" / "broken slat" / "tactile gap" }); const AuditLens = z.enum([ "wheelchair", "blind_or_low_vision", "ambulatory_mobility_impaired", "parent_of_small_child", "deaf_or_hard_of_hearing", "neurodivergent", "elder", "dog_on_leash", "baseline_generic", ]); const FindingCategory = z.enum([ "paths_and_ramps", "seating", "signage_and_wayfinding", "tactile_and_sensory", "lighting_and_safety", "toilets_and_water", "play_areas", "gates_and_entrances", "fall_hazards", "obstructions", "maintenance", "other", ]); const FindingSeverity = z.enum([ "note", // observation, not actionable urgently "action", // should be addressed in next maintenance cycle "urgent_safety", // present-day safety risk ]); const Finding = z.object({ finding_id: z.string(), // server-assigned UUID; model emits empty string // Anchor in the source video video_chunk_index: z.number().int().min(0), timestamp_in_chunk_seconds: z.number().min(0), // seconds from start of this 90s chunk timestamp_in_walk_seconds: z.number().min(0), // server fills this after the call still_frame_uri: z.string().nullable(), // server fills this after extracting the frame gps_lat: z.number().nullable(), // server fills from the GPS track gps_lng: z.number().nullable(), // server fills from the GPS track // The defect itself category: FindingCategory, severity: FindingSeverity, bounding_boxes: z.array(BoundingBox).min(1).max(4), // one finding may need multiple boxes description_factual: z.string(), // ONE sentence, what is visible in frame description_for_audit_lens: z.string(), // ONE sentence, why this matters to the chosen lens measurement_visible: z.string().nullable(), // "appears ~3 cm vertical lip" — leave null if you can't see it local_law_reference: z.string().nullable(), // e.g. "AODA Design of Public Spaces s.80.21" — set by post-process, model leaves null confidence: z.number().min(0).max(1), // Optional metadata is_camera_partial_view: z.boolean(), // true if only part of the defect is in frame needs_revisit: z.boolean(), // true if the camera shake or lighting was bad notes_from_audio_at_this_moment: z.string().nullable(), // verbatim transcript of any user "flag this" at the same moment }); const VideoChunkResult = z.object({ chunk_index: z.number().int().min(0), chunk_start_seconds: z.number(), // seconds from start of the whole walk chunk_duration_seconds: z.number(), findings: z.array(Finding), chunk_overall_notes: z.string().nullable(), // optional: anything the model wants to flag about the whole chunk (e.g. "rain on lens makes the eastern path findings lower confidence") }); type Finding = z.infer; type VideoChunkResult = z.infer; ``` ### Common failure modes (and how to avoid them) - Agent silently downgrades `thinkingLevel` on the the video parse call to save quota — pin `gemini-3.5-flash` with the matrix-specified `thinkingLevel` explicitly. Flash is materially worse at spatial reasoning over video and will silently miss subtle defects (a 3 cm kerb lip, a missing tactile dot strip). - Bounding boxes returned in absolute pixel coordinates against a frame the model never saw — the model returns normalised `[ymin, xmin, ymax, xmax]` in [0, 1] relative to the chunk's video frame. Server-side, extract the still frame at the timestamp first, then convert to absolute pixels. Do NOT ask the model to estimate pixel coordinates. - Model invents a defect not in the video to make the audit "complete" — hard rule in the system instruction: every finding must point to a specific second and a specific visible feature; if you cannot point, do not list. Add a unit test with a video of a freshly-renovated park and assert the audit comes back empty rather than padded. - Model conflates lens types and lists every defect for every lens — pin the chosen `audit_lens` in the system instruction for each call; `description_for_audit_lens` must speak to THAT lens. - Model uses US ADA terminology in a Canadian or Filipino or Egyptian audit — pin the `local_law_preset` in the system instruction. - Findings emitted with timestamps that exceed the chunk duration (model invents "at 03:21" in a 90-second clip) — clamp server-side to chunk bounds; reject and re-run if more than half the findings violate. - Faces or licence plates rendered into the PDF — auto-blur every exported still frame before render; never the original video. - Model writes findings as recommendations instead of observations — hard rule: factual description in `description_factual`; the recommendation lives only in the council cover letter or in the optional Nano Banana 2 "before / after" mockup. - Walk-against-walk diff hallucinates "fixed" findings without evidence — for any "resolved" claim, require the model to also state which second of the new walk it walked past the same spot and saw no defect; without that, mark as "could not confirm". - Video chunking misaligned with GPS track — the GPS track is recorded at 1 Hz from session start; the chunk boundaries must align so `chunk_start_seconds` in the model output adds cleanly to the wall-clock start of the walk. Pass the chunk's wall-clock-start as part of the user content, never as a side-channel. ### Negative constraints (hard rules) - Do NOT invent a finding the model cannot point to in the video. Every `Finding` must be bound to a `video_chunk_index`, a `timestamp_in_chunk_seconds`, and at least one `BoundingBox`. If you cannot point, do not list. - Do NOT estimate measurements you cannot see. `measurement_visible` is null unless a measurable feature is plainly in frame (e.g. a paver against a known kerb height). "Approximate" language is allowed when justified ("appears ~3 cm vertical lip"); precise figures without visible reference are forbidden. - Do NOT make recommendations in `description_factual`. That field is what is visible; recommendations belong in the council cover letter and the optional Nano Banana 2 mockup. - Do NOT translate proper nouns. The park name, the council name, the neighbourhood name, person names stay verbatim. "Trinity Bellwoods" is not translated to French or Mandarin; it is "Trinity Bellwoods" everywhere. - Do NOT speak about a defect outside the chosen `audit_lens`'s scope unless the severity is `urgent_safety` (in which case any auditor would want to know — a fall hazard at child height belongs in any lens's report). - Do NOT apply US-centric ADA terminology when the user picked a non-US locale. Use the local law's preferred wording (Ontario: "barrier-free path of travel"; Philippines: "accessible facilities under RA 7277"; UK: "reasonable adjustment"; EU: "Design for All"). - Do NOT include faces or licence plates in any exported frame. The original video keeps them; the PDF does not. The model never sees the un-blurred frame after server-side blur is applied on export — but server-side blurring happens AFTER the model has analysed the frame, since the model's analysis improves with un-blurred context (a person walking through a frame is useful spatial context). - Do NOT auto-publish or auto-share. Audits are private by default. Sharing is explicit, per-audit, per-co-auditor. - Do NOT use the user's walking video to train or fine-tune 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. - Do NOT auto-send the council cover letter. The user reads it, edits it, and sends it from their own mail client. - Do NOT extrapolate to whole-park claims from a partial walk. If the user walked the eastern half, the report says so; the report does not say "the park's signage is inadequate" — it says "across the eastern half of the park walked on this date, three signage issues were observed". ### 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: Parse a 90-second video chunk → `Finding[]` with bounding boxes Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You are auditing a public park for accessibility defects. The input is a video chunk between 30 and 90 seconds long, recorded by a person walking at normal pace, holding a phone at chest height or wearing a chest-mounted camera. The auditor has chosen an `audit_lens` (one of wheelchair, blind_or_low_vision, ambulatory_mobility_impaired, parent_of_small_child, deaf_or_hard_of_hearing, neurodivergent, elder, dog_on_leash, baseline_generic). The auditor has also chosen a `local_law_preset` (one of AODA-Ontario, ADA-US, EN17210-EU, EqualityAct-UK, RA7277-Philippines, RPWD-India, LDP-Egypt-10-2018, or baseline-international). Your task: identify every defect visible in frame that matters to THIS audit lens, point to it with a bounding box, anchor it to a specific second within the chunk, and write one factual sentence describing what is visible plus one sentence on why it matters to this lens. Hard rules: - Every finding MUST be bound to a `timestamp_in_chunk_seconds` (a number between 0 and the chunk duration) and at least one `BoundingBox`. If you cannot point to a specific second and a specific region in frame, do NOT include the finding. - Bounding boxes are normalised in [0, 1] relative to the video frame at the named second: `[ymin, xmin, ymax, xmax]`. y grows downward, x grows rightward. Do NOT estimate absolute pixel coordinates; the server converts. - Use `is_camera_partial_view: true` when only part of the defect is in frame (e.g. you can see the start of a sloped ramp but not its base). Use `needs_revisit: true` when camera shake or lighting is bad enough that confidence < 0.6. - `description_factual` is ONE sentence stating what is visible: "a concrete kerb on the eastern path edge, approximately 3 cm above the adjoining asphalt, with no curb cut and no tactile warning surface". Do NOT include recommendations ("should be lowered") in this field. - `description_for_audit_lens` is ONE sentence stating why this matters to the chosen lens: for a wheelchair user, "a 3 cm vertical lip prevents independent transition from the path to the lawn area". For a blind walker, "the lack of a tactile warning strip removes the cane-detectable transition". Speak in the language of the chosen `local_law_preset` where applicable. - `measurement_visible` is null unless a reference object in frame makes the estimate defensible. A standard paving slab edge, a visible foot beside the kerb, a known-height bollard count as reference. Do NOT invent dimensions. - `category` and `severity` come from the closed enums. Reserve `urgent_safety` for present-day safety risks: a fall hazard at child height, a hole in the path, an exposed electrical box, a bollard at head height in low light. - Findings should be deduplicated within a chunk. If you see the same defect from two angles five seconds apart, list it ONCE at the second with the clearest view; do not list it twice. - For the `audit_lens = blind_or_low_vision`, prioritise tactile and audio cues: missing tactile paving, audio-less crossings, surface changes without warning, signage relying on colour only, bollards and obstructions that cannot be cane-detected. - For the `audit_lens = parent_of_small_child`, prioritise fall hazards at child height, sharp edges at child height, water features without barriers, play equipment in disrepair, and pinch-points in gates and stroller-incompatible widths. - For the `audit_lens = elder`, prioritise seating gaps (distance between benches), trip hazards, slope, lighting under tree canopy, and toilet proximity. - For the `audit_lens = baseline_generic`, list defects of any category at severity `action` or higher; for `note` severity, only list if multiple lenses would benefit. Output ONLY the VideoChunkResult JSON matching the provided schema. No commentary. JSON only. ``` --- ### Call: Re-analyse a single 30-second segment when user asks for a second look Model: `gemini-3.5-flash` · thinkingLevel: high · Tools: (none) ``` You are looking again at a 30-second segment of a park-walk video that the auditor has flagged for closer attention. The auditor believes the model missed a defect, or wants confirmation of one the model returned with low confidence. Read this segment carefully. Frame by frame if needed. Look for: - subtle geometric defects (a kerb lip under 5 cm; a slope just above 1:12; a tactile paving panel missing one row of dots; a bench slat warped but not yet broken) - temporal defects (a sprinkler running across the only accessible path; a service vehicle parked across the kerb cut; the sun glaring across signage so it is unreadable from a chair) - defects only visible from the chosen audit lens's vantage (a sign mounted at 1.8 m is fine standing; from chair height the bench obstructs the line of sight to the same sign). Hard rules: - The schema is the same VideoChunkResult schema as the primary parse. Anchor every finding to a timestamp within this 30-second segment (`timestamp_in_chunk_seconds` between 0 and 30) and at least one `BoundingBox`. - If after careful re-examination there is no defect, return findings: []. Do not invent a finding to satisfy the auditor's request. - If you find a defect the primary call missed, set `notes_from_audio_at_this_moment` to "found on re-analysis requested by auditor" so the UI can label it. - Use the same `audit_lens` and `local_law_preset` the primary call used. Output ONLY the VideoChunkResult JSON. No commentary. ``` --- ### Call: Walk-against-walk diff (resolved / persistent / new findings) Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none, long context) ``` You receive two audit JSON documents at once: a previous audit (often 3-12 months old) and a new audit of the same park, walked along the same or overlapping route. Your task: classify each previous finding as one of: - "resolved" — the new walk passes through the same GPS area within approximately 15 metres and the model in the new walk did not flag this defect AND the new walk's `chunk_overall_notes` do not include "could not see this corner" or similar - "persistent" — the new walk flags a finding within approximately 15 metres and the same category whose `description_factual` describes the same defect - "could_not_confirm" — the new walk did not pass through the same area, OR the new walk's notes flag visibility issues at the same area And classify each new finding as one of: - "new_since_previous" — no previous finding in the same area and category - "matches_persistent" — already linked above Hard rules: - "Same area" is GPS within ~15 metres. Server-side filtering applies before your call so you only see findings already within range; still, double-check by category match. - "Same defect" is category match plus description_factual match in the same noun phrase ("kerb without cut" matches "kerb, no curb cut"). Do NOT match across categories. - For every "resolved" claim, also include the new walk's `timestamp_in_walk_seconds` where the model passed the same spot with no defect flagged. Without that anchor, demote to "could_not_confirm". - Do NOT speculate why a defect was fixed ("the city repaved this in Q2"). Just record the state. - Output a single JSON object: `{ resolved: [...], persistent: [...], could_not_confirm: [...], new_since_previous: [...] }` where each array element references the relevant `finding_id` from the input audits and includes a one-line `reason` field. No commentary. ``` --- ### Call: Generate council cover letter in the local language and local-law terminology Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You write a cover letter from the auditor to the named council body that accompanies the audit PDF. The auditor's name, the park name, the audit date, the route (e.g. "eastern half from the east gate to the playground and back"), the audit lens, the local-law preset, and the top three findings (by severity) are provided as input. Write the letter in the language named by `locale` and using the terminology of `local_law_preset`. The voice is calm, factual, and collaborative — not adversarial. The letter is one page maximum, formatted as plain text suitable for the user to paste into an email or save as a PDF page. Structure: 1. One short paragraph naming the auditor, the park, the date, the route, and the lens. 2. One short paragraph naming the three highest-severity findings, each in one sentence, each with the finding number from the PDF. 3. One short paragraph naming the local-law reference at issue (e.g. "AODA Design of Public Spaces, s.80.21–80.25" for an Ontario audit) and asking for a response within a reasonable window (30 working days by default). 4. A closing line and a signature placeholder ("Sincerely, [Auditor Name]") — do not invent the auditor's title or affiliation. Hard rules: - Do NOT translate proper nouns. The park name, the council name, the neighbourhood name, the auditor name stay verbatim regardless of the letter's language. - Do NOT propose specific remedies. The letter requests a response, not a fix design. - Do NOT use marketing language. No "we are excited to share"; no "delighted". Plain civic tone. - Match the law citation format the local council expects (US: 28 CFR Part 35; Ontario: O.Reg. 191/11; UK: section + year; Philippines: Republic Act number + section). - Output the letter as a single string. No commentary. ``` --- ### Call: Look up local accessibility law and council contact for the chosen city / park Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: search grounding ``` The user named a city and a park. Resolve: - The name of the local council body responsible for the park (e.g. "City of Toronto Parks, Forestry and Recreation Division"; "Quezon City Parks Development and Administration Department"; "Cairo Governorate, Greater Cairo Park Authority"). - The single best contact address for accessibility submissions (email + postal, if both exist). - The primary local accessibility law that applies to public parks in this jurisdiction, with the section number that most directly governs outdoor public spaces (e.g. AODA Design of Public Spaces Standard, O.Reg. 413/12, s.80.21–80.25). - The official recommended response window for accessibility submissions if published. Hard rules: - Use `google_search` grounding for every field. Do NOT answer from memory. - Preserve official names verbatim in their original language; do NOT translate "Quezon City Parks Development and Administration Department" into English just because the user is in English. - If multiple councils share jurisdiction (a regional park crossing two municipalities), return all candidates. - If the law citation is uncertain, return what you can ground and flag it as `confidence: "low"`. Output the response as JSON in the text body (NOT via `responseSchema` — `responseSchema` and `google_search` cannot be combined in the same Gemini call today). Server-side: parse the JSON, then read citation URLs from the response's `groundingMetadata.groundingChunks[].web.uri` — do NOT ask the model to include URLs in the JSON body; it will hallucinate them. No commentary outside the JSON. ``` --- ### Call: Audio summary — read the top three findings aloud for the walk back to the car Model: `gemini-3.1-flash-tts-preview` · n/a · n/a ``` Voice: calm, conversational, unhurried. Pick the Gemini 2.5 Flash TTS voice whose `languageCode` matches the audit `locale` — pronunciation will follow that locale automatically. Prefer a voice that sounds like a colleague debriefing with the auditor, not a news reader. Pre-process the text before sending to TTS: - Read out the audit lens, the park name, the date, and the route. - For each of the top three findings, read the finding number, then `description_factual`, then `description_for_audit_lens`. - Between findings, insert a blank line plus an em-dash (`—`) so the TTS produces a clear pause. Gemini 2.5 TTS does not support SSML `` — these textual cues are how you signal pacing. - End with one sentence: "Full audit PDF is ready when you are." Style direction: prepend ONE short directive sentence to the input, e.g. "Read calmly, as a colleague summarising a walk's findings to the auditor on the walk back to the car. …". There is no separate `style` API field on Gemini 2.5 TTS; the directive sentence inside the input is how style is conveyed. Target rate: ~120 words per minute — walking pace. Phoneme overrides (Arabic emphatic consonants, Tagalog ng, French liaison) are NOT exposed by Gemini 2.5 TTS — no SSML `` tag. Pronunciation comes from the chosen voice's native locale. ``` --- ### Call: Route-map illustration for the PDF cover page Model: `gemini-3.1-flash-image` · n/a · n/a ``` Generate a single illustration: a hand-drawn-feeling line of the walked route, plotted over a desaturated outline of the park boundary. Numbered pins (1, 2, 3, …) mark the locations of the top ten findings along the route. The image is intended for the cover page of a council audit PDF. Style direction in the prompt: - "thin black pen on cream paper" - "route drawn as a single continuous line in muted forest green" - "park boundary in a thin grey outline" - "numbered circular pins in muted orange, small, with finding numbers in serif type" - "no logos, no satellite imagery, no photographs, no people" - "1:1 aspect ratio" Accept the route GPS track and the park boundary as input via the SVG path the server pre-computes; pass the SVG as accompanying text in the prompt content so the model can position the route correctly relative to the boundary. The output is one PNG. No animation, no variants. ``` --- ### Call: Optional "before / after" mockup of a fixed defect Model: `gemini-3.1-flash-image` · n/a · n/a ``` The auditor has tapped "show what this could look like fixed" on a specific finding. Generate one image: take the still frame the finding is anchored to (server passes it as input) and produce a sketched overlay of how the defect could be addressed, suggested purely visually. Examples: - For a kerb without a cut, sketch a curb cut at standard slope with tactile warning paving in front of it. - For a broken bench slat, sketch the slat replaced. - For a missing tactile strip, sketch the tactile strip drawn in along the path edge. Style direction: - "soft pencil sketch overlaid on the photograph" - "the photograph stays visible behind the sketch at full opacity" - "the proposed change is drawn in dark blue pencil, slightly loose lines, clearly a suggestion not a render" - "a small handwritten label points to the change" The image must always carry a small footer caption added by the server: "Illustrative suggestion — not an engineering recommendation." No people are added. No branding. No glossy 3D render look. ``` --- ### Call: Suggest a finding category from a short user description (manual finding add) Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` The auditor has dropped a manual marker at a moment the model missed, and typed a short free-text description of the defect. Suggest one category from the FindingCategory enum and one severity from the FindingSeverity enum. Hard rules: - Pick from the closed enums only. Do not invent new values. - If the description is ambiguous (e.g. "broken thing on path"), return `category: "other"` and `severity: "note"` and ask the auditor to clarify via the `clarification_needed` field. Output JSON: { "category": "...", "severity": "...", "clarification_needed": "...|null" } No commentary. ``` ## 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 ward-meeting walk.** A community accessibility advocate in Toronto has fifteen minutes on a Saturday morning to walk one corner of Trinity Bellwoods before her ward meeting on Wednesday night. She picks the wheelchair lens, walks the eastern path from Queen West to the dog bowl and back, and at the kitchen table on Sunday morning the app has produced a twelve-page PDF with seventeen findings, the route on the cover page, and a draft cover letter to the City of Toronto Parks, Forestry and Recreation Division ready to email. - **The user is the body.** A wheelchair user in Quezon City walks the same loop in Ninoy Aquino Parks and Wildlife Center her physiotherapist asks her to walk twice a week. Picking the wheelchair lens, she logs every kerb she has to ask a stranger for help with, every tree root in the path, every accessible toilet padlocked at the time of day she is there. After eight weeks of weekly walks the app produces a per-month rollup showing which spots have been fixed (none, so far), which are intermittent (the padlocked toilet — fine in the morning, locked by 2 pm), and which she wants the LGU to prioritise before the planned repaving. - **The parent's lens.** A parent of a blind seven-year-old in Cairo walks Al-Azhar Park on a Friday afternoon holding her son's hand. She picks the blind_or_low_vision lens. The app flags every place the tactile trail breaks (six of them on the route from the east gate to the playground), every bollard at head height the child could walk into (three), every water-feature edge with no audible cue (two), and produces a cover letter in Arabic to the Greater Cairo Park Authority citing Egyptian Law 10/2018. - **The maintenance pre-walk.** A parks-maintenance lead at a small UK town council walks a public garden every Monday morning before opening. The baseline_generic lens flags eleven things this week: a fallen branch across the path, a broken bench slat, a flickering lamp post, two graffiti tags above child height. By Monday lunchtime the works team has a structured list with photos for the next two days' rota. - **The journalist's check.** A local journalist in a US Rust-Belt city is writing a story about a $3 million accessibility budget the city passed last year. She walks four parks before and after the renovation, picks the wheelchair lens, and uses the walk-against-walk diff to show that of last year's 84 findings in the four parks, 31 are resolved, 38 are still present, and 15 are new. The diff PDF goes in the print edition. - **The two-co-auditor walk.** A neighbour in Manila who uses a power chair invites a friend who is blind to add a second walk to the same audit. The two of them walk the same loop the next weekend, half a day apart. The app aligns the two GPS tracks, dedupes findings the two of them both caught, and surfaces the findings only one of them saw — three kerb cuts the wheelchair user noticed that the blind walker did not, four tactile gaps the blind walker noticed that the wheelchair user did not. - **The seasonal compare.** An accessibility coordinator in Montréal walks the same waterfront park in February (snow on the kerb cuts), in May (no snow but mud across the tactile strips after spring melt), in August (full canopy shadow making signage harder to read), and in November (leaf cover hiding the tactile dots). The four seasonal audits stack into a one-page seasonal rollup that goes to the borough alongside the budget request. - **The retrofit study.** A landscape-architecture firm in Lagos has been hired to redesign a city park for accessibility. The lead architect uses the app to baseline every entrance, path, seating, and signage condition before the design begins, exports the JSON, and pulls the findings into her firm's design brief. Six months after the renovation opens she runs the app again and the resulting before-and-after document goes in her firm's portfolio (with the local council's permission). - **The school civics project.** A grade-10 civics class in Mumbai walks Shivaji Park in three small groups, each with a different lens. The three audits combine into one classroom report the teacher submits to the local ward office. The Indian RPWD 2016 preset writes the cover letter in Marathi and English. ## 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 person walking a tree-lined park path at chest height, the phone visible in their hand. One paragraph: "Park Walk Audit turns a fifteen-minute walk into a council-ready accessibility report — every kerb, bench, sign, and ramp that doesn't yet work, bounding-boxed in the video you just recorded." Single Google sign-in button; Apple sign-in next to it. Below: "Try with the sample audit" → loads the demo audit in section 8a (a 19-minute walk through a fictional "Eastside Common" with 23 findings already processed). 2. **Empty state — "Start an audit".** Three big input methods: 🎥 Live walk · 📤 Upload video · 🔍 Open a saved audit. A short explainer below each ("Best for a planned walk you'll do in the next hour", "Best if you already recorded with a body-cam this morning", "Best to keep working on yesterday's audit"). Below: a "Pick the audit lens" panel with the nine lenses as cards, the chosen one highlighted, a one-line description on each ("Wheelchair — kerbs, slopes, gates, narrow paths, accessible toilets"). Below that: a "Pick the local law preset" select with searchable city / region presets. 3. **Live walk flow** (mobile-first). Live viewfinder centred on a chest-height crop guide. A subtle overlay shows the audit lens chosen (small chip in the upper-right). A second chip shows the elapsed walk time and approximate route distance. A "Flag this" button (long-press anywhere on the screen, also a hardware-volume-up shortcut) records the current second as high-signal. A bottom strip shows the spoken-note transcript live for accessibility. Pause / resume in the bottom-left; stop in the bottom-right. On stop, the app uploads the video in the background and walks the user through onboarding to the processing screen. 4. **Processing queue.** A vertical progress view. Each video chunk shows its index (1 of 14, 2 of 14, …), a tiny thumbnail of the first frame, and a step-by-step honest progress bar: "Reading chunk 4 of 14 with Gemini 3.5 Flash…" → "Extracting still frames at finding moments…" → "Resolving local-law references…" → "Drafting council cover letter…". Each step takes 8-30 seconds for a 20-minute walk. The user can close the app and come back. 5. **Findings list view.** The primary results screen. A vertical scrollable list of findings, grouped by category (paths & ramps, seating, signage & wayfinding, tactile & sensory, lighting & safety, toilets & water, play areas, gates & entrances, fall hazards, obstructions, maintenance). Each finding card shows: a colour-coded severity dot (yellow / amber / red), the finding number, the still frame with the bounding box drawn in the same colour, the timestamp in the walk, the GPS pin, the one-sentence factual description, and an arrow into the detail view. Filter chips above: "Show urgent only", "Show only items in eastern half", "Show findings the model wasn't sure about", "Show lens-specific only". 6. **Finding detail view.** Three panels on desktop, stacked on mobile. Top / left panel: the still frame with the bounding box, zoomable. A scrubbable strip below scrubs the video ±5 seconds either side; "play in context" plays the 10-second window. Middle panel: the factual description, the lens-specific description, the measurement (if any), the local-law reference, and the confidence as a thin bar. Right / bottom panel: actions — "Mark as not a finding", "Merge with finding #", "Request a second look (uses high thinking)", "Show a fixed mockup (uses Nano Banana 2, ~$0.03)". 7. **Route map view.** The walking route plotted on a tile map, with numbered pins at each finding, coloured by severity. Click a pin → finding detail panel slides in. Toggle: "Show only urgent". The map also supports a "draw a polygon" mode for filtering findings inside a chosen sub-area of the park. 8. **Walk-against-walk view.** Upload (or pick from your library) a previous audit. The app aligns the GPS tracks, dedupes overlapping findings, and shows four lists side by side: Resolved (green, with the new walk's pass-through timestamp as proof), Persistent (amber), Could-not-confirm (grey), New since previous (red). A single button: "Export diff PDF for council". 9. **Co-auditor view.** Modal: "Invite another auditor to walk this park". Magic-link email; arrival drops the co-auditor straight into the same audit with their own avatar and their own lens choice. Their walk is overlaid on the route map in a second colour. The findings list shows whose walk each finding came from. 10. **PDF preview & export.** A full PDF preview rendered in-app — cover page with the route illustration, the auditor's name and the date, a table-of-findings, then one page per finding with the bounding-boxed frame and the descriptions. Toggle: "Include the cover letter to council", "Include the mockups I requested", "Bilingual side-by-side (e.g. English + Arabic)". Export → PDF; CSV / JSON also available. 11. **Council letter editor.** The drafted cover letter in the locale and the local-law terminology, editable inline. The user reads, edits, then chooses "Save to PDF" or "Copy to clipboard for email" — the app does not send the letter on the user's behalf. 12. **Settings.** Audit lens defaults, locale and local-law presets, GPS sampling rate (1 Hz default), face-blur on/off for exported frames (default on), video retention (default: keep on device until manually deleted), cloud-backup toggle (default off). 13. **Footer.** "Made for the parks nobody else is auditing." Privacy: "Your walks are yours. We never train on them." 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 Park Walk Audit." - Subhead: "Walk any park, in any city, for any body — and let the model see every kerb, bench, sign, and ramp that doesn't yet work." - One paragraph (≤ 60 words) explaining who this is for and what makes it different from a generic photo-tagging app: every finding is anchored to a specific second of video and a bounding box on the still frame, the audit lens shapes what counts as a finding, and the local-law preset shapes the terminology in the cover letter to the council. - Visual: a small annotated illustration of a phone held at chest height while walking, with three labelled callouts (the lens chip, the elapsed-time chip, the "flag this" button). **Slide 2 — Try it now.** - One short prompt: "Try with the sample audit". - A pre-processed sample audit of a fictional "Eastside Common" — a 19-minute walk in the wheelchair lens, 23 findings, the AODA-Ontario preset, with the demo PDF ready to preview. - 1-2 sentences pointing at the specific page elements where the Gemini magic happens: the bounding box drawn on the still frame, the lens-specific description, the resolved / persistent / new diff on the walk-against-walk page. **Slide 3 — How to remix this.** - Headline: "Make this yours." - Three short bullets: - "Swap the sample audit in `/data/sample-audit/` for your own walk." - "Adjust the audit lenses and local-law presets in `/data/lenses.json` and `/data/local-law-presets.json` to fit your jurisdictions." - "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`. All copy meets 4.5:1 contrast; all icons have text labels for users who have icon-only UIs disabled. **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 (spatial understanding over video)** — the load-bearing capability. Reads the walking video chunk-by-chunk, identifies accessibility defects in frame, and returns bounding boxes in normalised image coordinates anchored to a specific second. - **Gemini 3.5 Flash (multilingual generation)** — writes finding descriptions and council cover letters in your locale and using your local accessibility law's terminology. English, Spanish, French, Portuguese, Arabic, Tagalog, Vietnamese, Mandarin, Cantonese, Korean, Tamil, Hindi, Urdu, Bengali, Punjabi, Amharic, Swahili, Farsi, Khmer. - **Gemini 3.5 Flash (long context)** — used for walk-against-walk diff and seasonal rollups; reads two or four audits at once and identifies what's resolved, what's persistent, what's new. - **Gemini 3.5 Flash + grounded search** — looks up the local accessibility law and the council's submission contact for the park you named. - **Gemini TTS** — reads the top three findings aloud at walking pace as you head back to the car. - **Nano Banana 2 (Gemini 3.5 Flash Image)** — generates the hand-drawn route illustration for the cover page and (optionally) sketched "before / after" mockups of how a defect could be addressed. - **Firebase Auth** — Google and Apple sign-in, co-auditor invitations via magic links. - **Firestore** — stores your audits, syncs across devices in real time. Audits are private by default. - **Firebase Storage** — keeps the walking videos and the extracted still frames; video stays on device by default, cloud backup is opt-in per-audit. - **Cost note** — see the detailed breakdown in 6d. A typical 20-minute walk costs about $0.70 of Gemini API spend, processed once. - **Privacy note** — your walking video is private to you and the co-auditors you invite. 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. Faces and licence plates are auto-blurred on every exported PDF frame; the original video on your device stays unredacted. **Backend services this app depends on:** - Auth: see section 4b — Google sign-in auto-provisioned; Apple sign-in and magic-link email require user configuration in the Firebase console. - Database: see section 4b — Firestore, auto-provisioned. - Storage: see section 4b — Firebase Storage, NOT auto-provisioned; enable in Firebase console. - Email: see section 4b — magic-link invitations require sender-domain authorisation in Firebase Auth. - Payments: see section 4b — not used in v1. - External APIs: see section 4b — Gemini for intelligence; Mapbox / MapTiler optional for richer tile rendering. **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 only) - `MAPBOX_TOKEN` — optional, only if you want richer tile rendering than the static OpenStreetMap fallback **Cost + privacy notes:** - One short paragraph per cost-sensitive capability: a 20-minute walk produces 14 chunks at ~$0.045/chunk for the primary parse ≈ $0.63 per walk. The cover-letter call and the route illustration add ~$0.05. A "second look" call on a 30-second segment is ~$0.02 each. Mockups via Nano Banana 2 are $0.03 each and are optional. - One short paragraph on privacy: where the data lives (your Firebase project), how to delete it (Settings → "Delete this audit forever" — gone in 60 seconds from Firestore and Storage), what is never sent for training. Note that the auto-blur of faces and licence plates happens AFTER the model has analysed the frame — the model sees the unblurred frame for better spatial reasoning, then the server blurs before any PDF export. The original video stays on your device unredacted; the blurred frames only exist in the exported PDF. **Documentation links:** - AI Studio Build docs - Gemini API multimodal video, spatial understanding, multilingual, long-context, TTS, image-generation docs - Firebase Auth, Firestore, Firebase Storage docs - A short note on each supported local-law preset (AODA, ADA, EN17210, Equality Act 2010, RA 7277, RPWD 2016, Egyptian Law 10/2018) **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) - **Parse a 90-second video chunk (Gemini 3.5 Flash, medium thinking)** — video input ~50k tokens per chunk, output ~3-8 findings at ~150 tokens each = ~1.2k output tokens. ~$0.045/chunk. A 20-minute walk = 14 chunks = ~$0.63 per walk. - **Re-analyse a 30-second segment (Gemini 3.5 Flash, high thinking)** — only run when the user taps "second look". ~$0.02/call. Typical: 2-3 per audit = ~$0.05. - **Walk-against-walk diff (Gemini 3.5 Flash, low thinking, long-context)** — input ~30k tokens for two audits of ~60 findings each; output ~1k tokens. ~$0.04/diff. Runs on user demand. - **Council cover letter (Gemini 3.5 Flash, low thinking)** — input ~2k tokens (top 3 findings + audit metadata), output ~400 tokens. ~$0.005/letter. - **Look up local law and council (Gemini 3.5 Flash + grounded search)** — ~$0.003/call. Runs once per new city / park combination, cached thereafter. - **TTS audio summary (Gemini 2.5 Flash TTS)** — billed per output token (~$10/M output tokens), effectively ~$0.000003/character. A 300-word debrief ≈ $0.005 per summary. Cached per audit. - **Route-map illustration (Nano Banana 2)** — ~$0.03/audit. Runs once per audit. - **Before/after mockup (Nano Banana 2)** — ~$0.03/mockup. Optional, only if the user requests it. - **Suggest category for manual finding (Gemini 3.5 Flash)** — ~$0.0005/call. - **Expected per-walk cost on first ingest:** ~$0.70 for a 20-minute walk (one route-map illustration + 14 chunk parses + one cover letter + the local-law lookup if a new city). **Ongoing walk-against-walk:** ~$0.05 per comparison. - **Video storage:** Firebase Storage standard tier, ~$0.026/GB/month. A 20-minute 1080p walk video at ~150 MB/minute ≈ 3 GB; users typically delete after PDF export, or back up to their own Drive. ~$0.08/month per stored audit if kept in cloud. - **Still-frame storage:** ~25 frames per audit at ~500 KB each ≈ ~12 MB per audit ≈ negligible. ## 7. Design language - **Mood:** A field tool that looks like it belongs in the pocket of someone who walks a lot. Not a tech product. Not a kiosk. The morning park at 9 am with the dog-walkers and the joggers, the auditor walking at normal pace, the phone in a chest harness, the coffee in the other hand cooling. Not adversarial; collaborative with the council that will read the PDF on Wednesday. - **Typography:** A clean grotesque for app chrome (Inter or Geist). A solid slab or modern serif (Roboto Slab, IBM Plex Serif, or Source Serif Pro) for finding numbers, finding headlines, and the PDF body text — it has to read as civic, official, and trustworthy when printed. A monospace (JetBrains Mono) for timestamps and GPS coordinates so they're unambiguously copyable. - **Palette:** A primary ink `#1F2630` for body text. A muted forest green `#3F6B4A` for the route line, the "resolved" state, and the primary CTA. A warm amber `#C97A3A` for the "action" severity and the route pins. A restrained red `#9E2A2B` for the "urgent safety" severity (used sparingly — never as a hero accent). A bone background `#F6F2EB` for the PDF preview and the finding cards (so they read as paper). A soft slate `#E7EAEE` for chrome surfaces. Borrowed from civic publications (Pelican Books, mid-century field guides), not from SaaS dashboards. - **Imagery:** The walking video frames are the hero. Bounding boxes are drawn cleanly in 3 px strokes with no glow. The route illustration on the PDF cover page is sketchy and warm, not slick. Period of imagery is "today, this morning" — no stock park photography on the marketing pages; real-looking frames from the sample audit only. - **Hand-feel touches:** A barely-visible paper grain on the PDF preview background. The "flag this" button on the live walk view has a tactile-feeling depression on press (a 100 ms shadow-lift on release with `prefers-reduced-motion` skipping it). The video scrubber has subtle haptic feedback (on supported devices) on second boundaries so you can scrub to "10:42" by feel. - **Spacing:** consistent 4-px base. Generous whitespace — findings need room to breathe so the council reader doesn't feel buried. - **Radius:** consistent token set (6 / 12 / 20 px). Finding cards use 12; the PDF preview uses 6 (to read as paper); the CTA buttons use 20. - **Shadows:** subtle, layered, warm-gray-tinted. Avoid heavy drop-shadows. Bounding boxes have no shadow; they sit flat on the frame. - **Motion:** purposeful — entrance fades on cards, hover lifts on action buttons, page transitions. Respect `prefers-reduced-motion`. No bouncing splash animations. No theatrical hero animations. The route-map's "play the walk" mode (the timelapse that traces the route in 8 seconds) is the one place motion carries meaning; respect reduced-motion by jumping to the end frame. - **States:** every interactive element has hover, focus, active, and disabled. Loading uses skeletons that match the eventual layout, not spinners. Empty states have helpful next-action guidance ("Pick a lens to start. Then hit record at the park gate."). ## 8. Content generation rules - Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like "Your tagline here". - Invent plausible park names, councils, findings, GPS points, audit dates, and weather. When inventing park names, vary the continent (a North American city park, a Southeast Asian historic park, a Middle Eastern urban park, a Sub-Saharan African botanical garden, a South American hillside park) so the demo content reflects the audience. - Findings are factual sentences. Never use adjectives like "unfortunately" or "regrettably" — the auditor's emotion is not what's in the report. The report says what is visible. - Tone: calm, civic, free of corporate language. This template is for a person doing community work, not a company. - Headlines: punchy and concrete. No "Empower your accessibility audit" 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 AODA / ADA / RA 7277 / Equality Act preset names; the term "tactile warning surface" is correct and stays). - 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 "the model was less than 70% confident here" badge with a "(i)" that opens a panel explaining the model's notes, and a "tap to revisit" button to scrub the video to the moment. ## 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 audits (sidebar):** - "Eastside Common — wheelchair lens" (the primary demo audit, 19-minute walk, 23 findings, AODA-Ontario preset, contributors: just me, the auditor "Adaeze N.") - "Ninoy Aquino Parks & Wildlife Center — wheelchair lens" (weekly recurring audit, contributors: me, my physiotherapist Mae) — Tagalog, RA 7277 preset, 14 weekly walks stacked. - "Al-Azhar Park east loop — blind / low-vision lens" (a parent's audit, contributors: me, my son's mobility teacher) — Arabic, Egyptian Law 10/2018 preset. - "Shivaji Park civic-class audit — three lenses combined" (grade-10 civics project, three small groups, three lenses) — Marathi and English, RPWD 2016 preset. - "Lagos Lekki Conservation Centre baseline" (landscape-architecture firm baseline pre-renovation, contributors: me, two colleagues from the practice) — English, baseline-international preset. **Sample finding in detail view (this is what the demo should show):** - **Finding number:** 7 - **Category:** paths_and_ramps - **Severity:** action - **Audit lens:** wheelchair - **Local-law preset:** AODA-Ontario - **Timestamp in walk:** 11:42:03 (started recording at 09:14:00 local) - **Timestamp in chunk:** 12.4 seconds into chunk 8 of 14 - **GPS:** 43.6489, −79.4136 (illustrative) - **Bounding box (normalised, in [0,1]):** `{ ymin: 0.62, xmin: 0.18, ymax: 0.94, xmax: 0.71, label_short: "kerb, no cut" }` - **Description (factual):** "A concrete kerb on the eastern path edge at the junction with the lawn area, approximately 3 cm vertical lip above the adjoining asphalt, no curb cut and no tactile warning surface." - **Description (for the wheelchair lens):** "A 3 cm vertical lip prevents independent transition from the path to the lawn area for a manual wheelchair user; assistance or a longer route via the southern junction is required." - **Measurement visible:** "appears ~3 cm vertical lip, referenced against the 6 cm paving slab edge visible to the right of the kerb" - **Local-law reference:** "AODA Design of Public Spaces, O.Reg. 413/12, s.80.21–80.25 (exterior paths of travel)" - **Confidence:** 0.88 - **Camera partial view:** false - **Needs revisit:** false - **Notes from audio at this moment:** "flag this" (the auditor said "flag this" 0.7 s before the model's anchor moment) **Sample input artefacts (for the build to demonstrate):** - A 19-minute walking video of the fictional "Eastside Common" with realistic chest-height framing, occasional dog-walker passes (with auto-blurred faces in the exported frames), and natural overcast lighting. - A pre-existing audit JSON from "Eastside Common — three months ago" with 31 findings, of which 12 are designed to be resolved in the new walk, 17 persistent, and 2 could-not-confirm; the new walk introduces 6 new findings. - A demo cover letter to the City of Toronto Parks, Forestry and Recreation Division in English, citing AODA s.80.21–80.25. - A second demo audit on "Ninoy Aquino Parks & Wildlife Center" — 14-minute walk, 18 findings, with a Tagalog cover letter to the Quezon City Parks Development and Administration Department citing RA 7277. - A third demo audit on "Al-Azhar Park east loop" — 16-minute walk, 21 findings under the blind / low-vision lens, with an Arabic cover letter to the Greater Cairo Park Authority citing Egyptian Law 10/2018. **Sample voice copy:** - Onboarding: "Walk the park as you would on any day. The model will see what doesn't yet work." - Live recording, on "Flag this": "Noted — finding around metre 142, looking again after the walk." - Processing: "Reading chunk 4 of 14…" / "Extracting still frames…" / "Looking up Ontario AODA references…" / "Drafting your council letter in English…" - Empty audit: "This audit is waiting for its first walk. Pick a lens, then hit record at the park gate." - Error (couldn't read a chunk): "We couldn't see clearly between minutes 8 and 9 — likely sun glare on the lens. Want to retry that segment, or accept the lower-confidence findings from that window?" - Save confirmation: "Eastside Common — wheelchair lens — 23 findings — saved." - Auto-flag confirmation (spoken by TTS for blind auditors during the walk): "Possible kerb without a cut, approximately metre 142." - Low confidence note: "Model confidence on this finding was 0.62. Tap 'second look' to re-analyse this 30-second segment with higher reasoning." - Walk-against-walk summary: "31 previous findings: 12 resolved, 17 persistent, 2 could not be confirmed. 6 new findings since." **Sample co-auditor invitation email subject + body:** - Subject: "Mae — I'm doing the wheelchair audit of Ninoy Aquino on Saturday. Will you walk with the elder lens?" - Body: "Hi Mae — I'm walking the east loop on Saturday morning at 8 am. Could you do the same loop with the elder lens — benches, lighting, slopes — and add your walk to the same audit? Tap to join." [Open Audit] **Sample council cover letter (English, AODA-Ontario preset, abbreviated):** - "To the City of Toronto Parks, Forestry and Recreation Division — I conducted an accessibility audit of the eastern half of Trinity Bellwoods Park on 12 May 2026, walking from the Queen Street West entrance to the off-leash dog area and back, recording video and GPS throughout. The audit was conducted with the wheelchair lens. Three findings of action-level severity are summarised below. Finding 7: a concrete kerb on the eastern path edge at the junction with the lawn area, approximately 3 cm vertical lip and no curb cut or tactile warning surface, contrary to AODA O.Reg. 413/12, s.80.21–80.25. Finding 12: a bench at the path junction with no armrests on either side and approximately 380 mm seat height. Finding 19: the accessible toilet at the south washroom block was padlocked at 11:18 on the audit date despite posted opening hours. The full audit PDF — including all 23 findings, bounding-boxed still frames, GPS coordinates, and the original walking route — is attached. I would be grateful for a response within 30 working days. Sincerely, [Auditor Name]" ## 9. Media & assets - **Hero image (landing screen):** A photographed-looking shot of a person walking a tree-lined path in a city park at chest height, the phone visible in their hand, soft morning light, ordinary park-goers in the distance. Generate via Nano Banana 2 with a prompt emphasising "morning park, dappled light, phone held at chest height in a chest harness, hand visible at the edge of frame, ordinary city park (not pristine), real worn paving, soft shadow under trees, no marketing gloss". - **App icon / wordmark:** Set the wordmark in the slab serif. A small route-line glyph in muted forest green beside it. No icon — just type and one line. - **Empty-state illustration:** A simple line drawing of a phone held at chest height with the route extending out from it in a curving line. Hand-drawn aesthetic, not a flat icon. - **Demo finding frames:** Generated per the prompts in section 8a — Nano Banana 2 prompts that specifically request "morning park, overcast light, ordinary kerb edge with a 3 cm lip, ordinary park bench with a missing slat, ordinary signage at adult standing height, no people visible (we will composite a walker separately)". The bounding boxes are drawn by the app in render, not by the image generator. - **Route illustrations on PDF cover:** Hand-drawn-feeling, thin black ink lines on cream, the route in muted forest green, numbered pins in muted amber. - **Stock fallbacks:** If image generation fails, fall back to the photographed sample frames from `/public/samples/sample-frame-{1..6}.jpg`. Never to a generic emoji. - **Generated imagery:** prefer Nano Banana 2 over stock photography. Prompt for warmth, ordinariness, 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. The audit-lens icons (wheelchair, white-cane, stroller, ear, brain, cane, dog) come from a single coherent set; no mixing icon styles. ### 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: "Pick a lens", "Hit record at the park gate", "Drop a previously-recorded video", "Invite a co-auditor" — 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. The processing screen shows the chunk-by-chunk progress honestly: chunk 4 of 14 currently parsing. - If an AI call fails, show a calm, specific error ("We couldn't read chunk 4 — likely sun glare on the lens. Retry just this segment, or accept lower-confidence findings from that window?") and offer retry. - Low-confidence findings are visually marked: the severity dot has a thin dashed ring, and tapping reveals the confidence value plus a "request a second look" button. - Bounding boxes pulse subtly on hover (300 ms, 1.04× scale, fade) with `prefers-reduced-motion` showing a static thicker stroke instead. - The route map's "play the walk" mode (the timelapse trace) takes 8 seconds; `prefers-reduced-motion` falls back to instant. - Live recording has a clear "currently recording" affordance: a small pulsing red dot in the top-left, and a haptic tick every 30 seconds so the auditor knows the recording is still live without checking the screen. The pulsing dot becomes a static red dot when reduced-motion is on. - The "flag this" button is reachable by a hardware volume-up press in addition to the on-screen button, so a one-handed walker can flag without looking. - Auto-save: every 30 seconds during recording the partial video and GPS track flush to local storage. A crash mid-walk recovers the audit on next launch. ## 11. Tech & responsive requirements - **TTS markdown-stripping preprocessor:** before sending any user-authored markdown to `gemini-3.1-flash-tts-preview`, strip non-spoken markdown: `#`/`##`/`###` headings (keep the title text), `**bold**` (keep the inner text), `[label](url)` (keep `label`, drop URL), `` ``` `` fenced code blocks (skip entirely), `>` block-quote markers (keep the text), and `|` table pipes (read row-by-row as sentences). Insert `…` between sentences for a short pause and a blank line plus `—` between paragraphs for a long pause. The model does not understand markdown; raw markdown will be read aloud as literal characters ("asterisk asterisk"). - **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 video parse / re-analyse / walk-diff / cover-letter, `gemini-3.5-flash` for local-law lookup and category suggestion, `gemini-3.1-flash-tts-preview` for TTS, and `gemini-3.1-flash-image` for route illustration and mockups. Set `thinkingLevel` explicitly per call. - **Video upload:** chunk the walking video client-side into 90-second segments; upload each to Firebase Storage via resumable upload; server-side, register each chunk in Firestore with its wall-clock-start derived from the GPS track. Pass `files/*` resource names to Gemini via the Files API; do NOT pass Firebase public URLs to `generateContent`. - **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed audit on first launch. - **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it; magic-link email as fallback for co-auditor invitations. - **Storage:** Firebase Storage for video chunks and extracted still frames. Pre-signed URLs only; client never gets a permanent public URL. - **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+. The live-recording mode is the canonical mobile-first screen — chest-height crop guide centred, controls at thumb-reach. - 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 — the record / stop buttons must clear the home-indicator area. - Zero horizontal overflow at any width. Zero layout shift on load. - Persist user data in Firestore. Use real-time listeners on the findings list during the processing run so chunks appear as they parse. - Optimistic UI on writes; reconcile on response. - Web Camera API with `getUserMedia` for the live capture, with `MediaRecorder` writing chunks. Fall back to native camera capture on browsers that don't expose `MediaRecorder` reliably. - **iOS Safari gotchas (graceful degradation):** Safari `MediaRecorder` only supports `video/mp4` (H.264 + AAC) — feature-detect `MediaRecorder.isTypeSupported('video/webm;codecs=vp9,opus')` and fall back to MP4; iOS may drop `MediaRecorder` chunks on long sessions — flush every 5s and persist to IndexedDB so a 20-minute park walk never sits in volatile RAM; camera permission does NOT persist across reloads on iOS — re-request on every walk; backgrounded Safari tabs pause `getUserMedia` — combine a screen Wake Lock + the documented Firestore reconciliation so a walk is never silently dropped. - Background-tab handling: if the user switches away during processing, finish the chunks server-side and surface them on next focus via a Firestore listener. ## 12. Accessibility (WCAG 2.2 AA) - The app's own UI is held to a higher standard than the parks it audits. If anything in this app violates AA, fix it before shipping. - Semantic HTML — `header`, `nav`, `main`, `section`, `article`, `footer`. - All interactive controls reachable by keyboard with a visible focus ring; the "flag this" button on the live recording view is reachable by Space-bar and by hardware volume-up. - Color contrast ≥ 4.5:1 for body, 3:1 for large text and UI components. Severity dots are NOT colour-only — yellow has a square, amber a triangle, red a circle-cross — so colour-blind users can distinguish. - All images have meaningful `alt` text. The walking-video frames have `alt` describing what's in frame ("park path, concrete kerb on the right edge, no curb cut visible, mid-morning overcast light"). Bounding-boxed images have `alt` describing the box's location ("kerb edge in the lower-left of the frame, boxed in yellow"). - The bounding box is also exposed as an SVG `` with `role="img"` and `aria-label="kerb without curb cut, lower-left of frame"` for screen-reader users. - Form fields have associated `