================ 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. --- # Vinyl Restorer ## 1. Project **Vinyl Restorer** is a personal record-collection rebuilder for the people who inherit a partner's, a parent's, or a sibling's vinyl library and want to know — sleeve by sleeve — what they actually have. The user photographs each record (front sleeve, back sleeve, inner labels, deadwax matrix etches, and the disc surface) and the app produces an honest catalogue entry: artist and title, the specific pressing edition (country, year, label catalogue number, matrix runout codes), the visual condition of sleeve and vinyl on the Goldmine grading scale, an estimated market value range with citations, and the small biographical notes that made the record matter to the person who bought it. This is the kind of app a widow in Lagos opens at her dining table on a Saturday afternoon, six months after her husband's funeral, when his three-hundred-album highlife collection — Rex Lawson, Bobby Benson, Celestine Ukwu, Oriental Brothers, Ebenezer Obey — has finally been moved from the music room into the living room because the music room is becoming the grandchildren's library. It is also the kind of app a widow in São Paulo builds the year after losing her husband, working through his bossa nova and MPB collection (the Elis Regina pressings he chased for forty years, the Clube da Esquina reissues, the original 1972 Milton Nascimento), and the kind of app a widow in Glasgow builds in the year after her husband dies, sleeve by sleeve through his punk and post-punk wall — the Buzzcocks 7-inches in plain paper sleeves, the Postcard Records run, the early Rough Trade pressings he kept since he was nineteen. Same shape of moment, different scene, different decade. The single demo that proves the magic: photograph one creased sleeve front and the inner label of the disc → in under 30 seconds the user sees a single catalogue card. On the left, the photograph of the sleeve with the artist and title overlaid in small type (in the fictional demo: "Emmanuel Okonkwo and his Coastal Dance Band — Highlife with a Coastal Beat, Coastline West African Records, CWA 1011, Nigeria, 1971"). In the middle, the pressing identification with the matrix runout codes transcribed verbatim ("CWA 1011 A 1G △ 9", "CWA 1011 B 1G △ 9 INSPECT 4") and a confidence note ("first Nigerian pressing — matrix codes consistent with a Lagos plant of the period; not a UK reissue"). On the right, a condition card with two grades (sleeve VG+, vinyl VG+) and a market estimate range with three citations from a grounded discography and recent sales — and the explicit, unmissable line at the bottom: "This is a record for keeping. We do not suggest selling." And in the harder cases — undocumented pressings, regional labels with no master discography, an LP whose deadwax matrix codes contradict the sleeve country, a sleeve someone in the family wrote on in biro forty years ago — the app reads what is there, marks what it cannot prove, cites every source it used, and never invents a number to fill the gap. **Tagline:** _Rebuild the record collection of someone you loved — in any genre, any country, with every label, matrix code, and sleeve crease honoured._ ## 2. Target audience - Widows and widowers cataloguing a late partner's record collection in the year after a death — the primary gallery-browser entry point - Adult children inheriting a parent's vinyl library and trying to understand what is on the shelves before deciding what to keep, what to gift to siblings, and what to play - Diaspora collectors rebuilding lost or destroyed collections — Nigerian highlife, Brazilian MPB and bossa nova, Greek rebetiko, Persian pop pre-1979, Lebanese tarab, Cuban son montuno, Punjabi bhangra, Tamil playback, Korean folk-rock, Filipino kundiman, Vietnamese pre-1975 nhạc vàng, Ethiopian Ethio-jazz - Music estate executors (small estates, not auction-house clients) who need a defensible inventory for a will or a charitable donation receipt - Solo collectors who want a private catalogue of their own collection with proper edition data — but who do not want a Discogs public profile - Independent record-shop owners pricing a recently-arrived collection without sending each sleeve through a database GUI by hand - Genealogists and oral historians documenting a relative's musical life as part of a broader family-history project - Community archivists at small institutions (a Pentecostal church basement collection of Yoruba juju, a Polish-American social club's polka library, a queer community centre's disco 12-inches) who need a structured catalogue without museum software - Adoptees rebuilding the record collection of a birth parent they reconnected with late or after death ## 3. Core value propositions Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app. - **Identifies the specific pressing, not just the album** — Gemini 3.5 Flash reads the sleeve photograph, the label scan, and the matrix runout codes etched into the deadwax in a single multimodal call, and resolves to the pressing edition: country, year, label catalogue number, plant code, mono vs stereo, original vs reissue. A 1971 Nigerian first pressing of Rex Lawson is not the same artefact as the 1991 UK reissue, and the app refuses to flatten the difference. - **Grades sleeve and vinyl separately, against a published scale** — uses the Goldmine grading scale (M, NM, VG+, VG, G+, G, P, F) for both sleeve and disc, with the visual evidence cited inline ("ring wear visible on lower-right of sleeve front" / "two scuff lines across track 3, side A — likely audible as light noise"). Sleeve and disc are graded independently, the way the trade does it. - **Reads matrix runout codes verbatim** — the etched runout codes in the deadwax (e.g. "PWA 1011 A 1G △ 9", "BIEM STEMRA", "A//1 ▽ Porky Prime Cuts") are the canonical pressing fingerprint. The app reads them as carefully as the front sleeve and stores them verbatim, with every visible character including stamper marks, mastering-engineer initials, and the pressing plant glyph. - **Estimates market value as a range with citations** — never a single dollar number. Always low–median–high in the user's local currency, drawn from grounded discography databases and recent completed sales. Every range includes the date the citation was checked. **The app never suggests selling**. It says, every time: "This is your collection. The value range is for your records." The "sell" word is absent from the UI by design. - **Reads any language on any label** — Cyrillic Soviet Melodiya pressings, Amharic on early Amha Records, Tamil on HMV India, Japanese on Toshiba EMI obi strips, Persian on Apollo Records Tehran, Yoruba on Decca West Africa, Bengali on Hindusthan Musical Products. One Gemini 3.5 Flash call handles the script identification, the transliteration, and the translation. - **The originals are sacred** — the photograph of the actual sleeve is always one tap away at full upload resolution. The catalogue entry sits beside it; it never replaces it. - **Audio reasoning where the user can play a track** — the user can record a short phone-mic snippet of side A and the app verifies that the disc actually plays as the label claims, identifies surface noise as light, moderate, or heavy, and flags mis-labelled pressings (label says "side A track 1" but the audio analysis says it is a different track from the album). - **Private to the household** — collections are private by default. Sharing with a named child or sibling is explicit, per-collection, never public. The matrix codes and recent-sales citations stay inside the household's view; they are never broadcast. ## 4. Features to build - Camera capture optimised for vinyl: sleeve front, sleeve back, inner sleeve / lyric sheet, label (side A), label (side B), deadwax matrix (close-up of the runout area), spine, and any insert or OBI strip - Upload from photo library or from a flatbed-scanner workflow (some users have already scanned at 600 dpi) - Automatic artefact-type detection — distinguishes sleeve, label, matrix close-up, inner sleeve, obi strip, hype sticker, original purchase receipt, owner's handwriting on the sleeve - Multimodal parse — artist + title + label + catalogue number + country + year + matrix codes + condition evidence, in a single Gemini 3.5 Flash call per record - Pressing-edition resolution — grounded search against Discogs / MusicBrainz / RYM / specialist regional discographies (Africambo, Bossa & Beyond, Persian Vinyl, Diskology Brazil), with citations preserved - Condition grading on the Goldmine scale, separately for sleeve and disc, with cited visual evidence for each grade - Market value estimate as a range — low, median, high — with at least two citations and a "last checked" date; **never** a single number; **never** a "list to sell" CTA - Audio verification (optional, microphone permission requested explicitly) — user holds the phone over the playing record for 20 seconds, the app confirms the track matches the labelled side/track and reports surface noise as light / moderate / heavy - Owner's notes preservation — biro on the sleeve, a price written in pencil on the back, a date scrawled inside the gatefold, "for Adaeze on her 30th — love, Tunde" inside the inner sleeve - Original purchase artefacts — if the user finds the original receipt, the in-store sticker, or a postal-order stub tucked inside the sleeve, those are photographed and parsed too - Collection view — magazine grid filterable by artist, genre, country, year, pressing edition, condition, market-value range, "has handwritten note from late spouse", "incomplete pressing" - Shelf view — a literal shelves-of-vinyl view of the catalogue, ordered the way the original collector ordered theirs (the user can drag to match the physical shelf) - Genre lineage — for the regional collections, a small contextual sidebar explains the scene a record belongs to (highlife, bossa nova, Ethio-jazz) in a one-paragraph factual summary with sources - Audio-fingerprint matching for tracks the label is missing or illegible — record a 20-second clip of the playing disc and the app matches it to a track in grounded databases - Restoration recommendations that are NOT sales pitches — "the sleeve has 6mm of ring wear and a 12mm split along the lower seam; here are the three archival sleeves a household would use", with one neutral source per recommendation - Sleeve restoration log — if the user replaces an inner sleeve or applies an archival outer sleeve, that gets logged into the record's history alongside the photograph of the original - Family invitations — share a collection with a sibling or child; merge their photographed records into the same catalogue - Printable catalogue export — typeset paperback-style catalogue with one record per page, original sleeve photograph, full pressing identification, condition, and family notes; PDF first, print-on-demand later - Insurance / estate export — a structured CSV or PDF inventory that a household can attach to a will or a homeowner's contents policy, with grading and value-range columns; explicit watermark "household estate inventory — not a sale listing" - Search across the collection — semantic ("show me the records he bought in the year we got married"), structured ("Nigerian first pressings, VG+ or better, 1968-1974") ## 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) - **Multimodal image input** (Gemini 3.5 Flash) — reads sleeve front, sleeve back, label, and deadwax matrix in a single multi-image call. The matrix runout is the canonical pressing fingerprint; pin the matrix close-up as the highest-priority image. The model identifies sleeve condition evidence (ring wear, seam splits, edge wear, water staining, biro annotations) and disc condition evidence (visible scratches, scuffs, paper-residue from a stuck label, warps visible at angle) and assigns Goldmine grades for sleeve and disc separately, citing visual evidence in each case. - **Multilingual text reading** (Gemini 3.5 Flash) — labels are printed in Cyrillic on Soviet Melodiya, Amharic on Amha Records, Tamil on HMV India, Japanese on Toshiba EMI obi strips, Persian on Apollo Records Tehran, Yoruba on Decca West Africa, Bengali on Hindusthan Musical Products, Mandarin on Diamond Records Hong Kong, Korean on Oasis Records, Swahili on Polydor East Africa. The model reads the script, transliterates, and translates in the same call. - **Audio input** (Gemini 3.5 Flash) — a 20-second microphone clip of the playing record is sent to Gemini 3.5 Flash. The model returns: track-match confidence against the labelled track, surface-noise level (light / moderate / heavy), and any audible damage (skip, lock-groove, repeating click). Used for verification only — not for music identification of unknown discs unless the user explicitly enables that mode. - **Structured output / JSON Schema** — every primary call returns a `Record` object matching the schema below. Numeric `min`/`max` constraints inside `responseSchema` are documentation only; clamp on the server. - **Search grounding** — for pressing identification and market-value estimation. Pressing identification uses grounded search against discography databases and preserves the citation URLs from `groundingMetadata.groundingChunks[].web.uri`. Market value ranges cite at least two recent completed sales with the URL and the date checked. Grounding and `responseSchema` are mutually exclusive in a single Gemini call — for these calls the model emits JSON inside the text body and the server parses it; citations come from `groundingMetadata` separately. - **Long context (1M tokens)** — once the collection grows past a few hundred records, the "genre arc of the collection" view and the "what did he buy together" cluster analysis run as a single long-context pass over the parsed records. **Guardrail**: a parsed Record object averages ~1,400 tokens; a 300-record collection ≈ ~420k tokens (comfortable). For collections larger than 600 records, chunk by genre or by decade before the cluster call — the 1M ceiling is real. - **Gemini TTS** (`gemini-3.1-flash-tts-preview`) — reads each record's catalogue card aloud (artist, title, pressing, condition, notes) at a calm pace, in the user's preferred locale. Used by users with low vision or who prefer to listen while shelving the records. - **Gemini 3.5 Flash Image** (`gemini-3.1-flash-image`, Nano Banana 2) — generates the warm hero image for the landing page (a hand sliding an LP from a tall wooden shelf in afternoon light) and per-genre scene illustrations for the genre-lineage sidebar. Used sparingly and only for illustration, never for fabricating sleeve art of a specific record. - **Thinking levels** — `medium` for the primary parse-and-grade call (matrix-code forensics, pressing disambiguation, condition evidence). `low` for label transliteration, owner's-note transcription, audio verification, and TTS narration. `high` is reserved only for the optional "is this a counterfeit?" deep-check the user explicitly requests on a single record. ### 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 for family invitations) also requires the sender domain to be authorised in Firebase Auth. Collections are private to the owner and explicitly-invited family members. No public-by-default. - **Database — Required.** Firestore for `users`, `collections`, `records`, `pressings`, `grades`, `value_estimates`, `notes`, `audio_clips`, `collection_members`, `restoration_log`. - **File storage — Required.** Firebase Storage for original sleeve and label photographs (preserved at upload resolution, forever) + audio clip storage + Nano Banana 2 generated illustrations. **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 first photograph upload. Pre-signed URLs only; the photographs and audio are never publicly addressable. - **Email — Required (transactional).** Family invitations via magic links (Firebase Auth). Insurance / estate inventory export emails (PDF attachments) to the household member the user nominates. - **Payments — Not needed for v1.** Free for personal use. A future "bound printed collection book" tier could pipe to a print-on-demand partner (Blurb, Lulu) and charge for the physical artefact only. **There is no "list to sell" tier and there will not be one.** - **External APIs:** Gemini API for all intelligence; optional Discogs API token for higher-fidelity pressing data (rate-limited, user-configured). The app must work without a Discogs token by falling back to grounded Gemini search across publicly accessible discographies. **Environment variables:** every secret (Gemini API key, Firebase service-account JSON, Discogs API 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 my account' inside the UI · explicit opt-in for any analytics · the user's collection and the audio clips are 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) · market-value citations are stored locally inside the user's collection; the household's value totals are never shared with third-party sites. **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 or `responseSchema` field. The literal schema is below. **Convert the Zod schema to Gemini's `Schema` type via the SDK helper** before passing to `responseSchema` — do NOT pass raw Zod. **Numeric `min`/`max` constraints are documentation only inside `responseSchema`; clamp on the server after the response arrives.** 4. **Pin the system instruction separately** from user input. Use the `systemInstruction` field for persona + behavioural rules; use `contents` for user input. Never concatenate. 5. **Pre-declare tools as an enable/disable list** per call. The matrix below names which tools are enabled per call. Tools NOT listed for a call should be disabled. 6. **State negative constraints explicitly** — they are listed below. They are NOT "be careful" suggestions; they are hard rules the model must follow. 7. **Multi-image and audio inputs** — for matrix-code reads and audio verification, upload to the Gemini Files API (`files/*` resource name) or send as `inlineData` (base64). Do NOT pass Firebase Storage public URLs directly to `generateContent`; the API does not fetch them. 8. **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; } ``` 9. **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 record images → `Record` schema (sleeve front/back + label + matrix close-up) | `gemini-3.5-flash` | medium | (none) | | Resolve pressing edition (country, year, plant) with grounded search | `gemini-3.5-flash` | medium | `google_search` grounding (no `responseSchema` on this call — see note) | | Estimate market value range with citations | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) | | Grade sleeve and disc on Goldmine scale with cited visual evidence | `gemini-3.5-flash` | medium | (none) | | Transliterate + translate non-Latin label text | `gemini-3.5-flash` | low | (none) | | Verify audio matches label, report surface noise | `gemini-3.5-flash` | low | (none, audio input) | | Counterfeit deep-check (user-requested only) | `gemini-3.5-flash` | high | `google_search` grounding (no `responseSchema`) | | Collection-wide genre arc + cluster analysis | `gemini-3.5-flash` | medium | (none, long-context over collection) | | Generate TTS narration of catalogue card | `gemini-3.1-flash-tts-preview` | n/a | n/a | | Generate hero / genre-lineage illustration | `gemini-3.1-flash-image` | n/a | n/a | *Note for builders:* on TTS and image-generation calls, omit `thinkingConfig` entirely — the field is not supported on those models. The `n/a` cells in this matrix are documentation only; do not serialise them into the request body. ### Primary structured-output schema (seed this verbatim in the prompt) ```typescript import { z } from "zod"; const MatrixCode = z.object({ side: z.enum(["A", "B", "C", "D", "unknown"]), text_verbatim: z.string(), // "PWA 1011 A 1G △ 9 INSPECT 4" characters_uncertain: z.array(z.string()), // ["the 9 could be a 0"] glyphs_present: z.array(z.enum([ "triangle", "circle", "square", "porky-prime-cuts-script", "kendun-mastering-stamp", "bilbo-stamp", "biem-stemra", "other-stamper-glyph", "engineer-initials", "pressing-plant-code", ])), position: z.enum(["3-oclock", "6-oclock", "9-oclock", "12-oclock", "around-the-label", "across-multiple-positions"]), }); const SleeveAnnotation = z.object({ text_verbatim: z.string(), // "for Adaeze on her 30th — Tunde, Lagos 1984" language: z.string(), // BCP-47 position_on_sleeve: z.enum([ "front-upper-left", "front-upper-right", "front-lower-left", "front-lower-right", "front-center", "back-upper", "back-lower", "spine", "inner-sleeve", "gatefold-inside", "inside-cover", ]), ink_or_pencil: z.enum(["biro-blue", "biro-black", "ink-fountain", "pencil", "marker", "other"]), appears_to_be_owner_hand: z.boolean(), appears_to_be_shop_sticker: z.boolean(), inferred_decade: z.string().nullable(), // "1980s" }); const ConditionEvidence = z.object({ body: z.enum(["sleeve", "disc-side-a", "disc-side-b", "inner-sleeve", "insert", "obi-strip"]), description: z.string(), // "6mm of ring wear on lower-right of sleeve front" approximate_extent_mm: z.number().nullable(), affects_playback: z.boolean().nullable(), }); const GoldmineGrade = z.object({ body: z.enum(["sleeve", "disc-side-a", "disc-side-b"]), grade: z.enum(["M", "NM", "VG+", "VG", "G+", "G", "P", "F"]), grade_confidence: z.number().min(0).max(1), cited_evidence: z.array(z.string()), // pointers into condition_evidence[].description }); const PressingCandidate = z.object({ label: z.string(), // "Philips West African Records" catalogue_number: z.string(), // "PWA 1011" country: z.string(), // ISO 3166-1 alpha-2 if possible, else verbatim year_known: z.number().nullable(), format_format: z.enum(["LP", "EP", "7-inch", "10-inch", "12-inch", "78-rpm", "boxed-set"]), channels: z.enum(["mono", "stereo", "quad", "unknown"]), pressing_plant_inferred: z.string().nullable(), is_original_pressing: z.boolean().nullable(), // null when not determinable reissue_year: z.number().nullable(), confidence: z.number().min(0).max(1), citation_anchors: z.array(z.string()), // resolved from groundingMetadata after the call }); const ValueEstimateRange = z.object({ currency: z.string(), // ISO 4217, "NGN", "BRL", "GBP", "USD" low: z.number(), median: z.number(), high: z.number(), sample_size: z.number(), // number of completed sales the range is drawn from date_checked_iso: z.string(), // "2026-05-28" citation_anchors: z.array(z.string()), context_note: z.string(), // "based on 4 completed VG+ sleeve / VG+ disc sales in the last 18 months" for_household_records_only: z.literal(true), // documentation field, true always }); const AudioVerification = z.object({ clip_duration_seconds: z.number(), side_recorded: z.enum(["A", "B", "C", "D"]), track_recorded: z.number().nullable(), match_confidence_against_labelled_track: z.number().min(0).max(1), surface_noise: z.enum(["light", "moderate", "heavy", "not-assessable"]), audible_damage: z.array(z.enum([ "skip", "lock-groove", "repeating-click", "warp-rumble", "groove-wear-noise", "stuck-needle", "off-center-pressing", "none", ])), notes: z.string().nullable(), }); const Record = z.object({ record_id: z.string(), sleeve_image_uris: z.array(z.string()), // front, back, inner, spine label_image_uris: z.array(z.string()), // side A, side B matrix_image_uris: z.array(z.string()), // close-ups of deadwax insert_image_uris: z.array(z.string()), artist_verbatim: z.string(), // as printed on the sleeve artist_canonical: z.string().nullable(), // resolved against grounded discography title_verbatim: z.string(), title_canonical: z.string().nullable(), label_printed: z.string(), catalogue_number_printed: z.string(), country_printed_verbatim: z.string().nullable(), // "Nigeria", "ROC", "U.S.A." matrix_codes: z.array(MatrixCode), glyphs_present_overall: z.array(z.string()), pressing_candidates: z.array(PressingCandidate), // multiple if ambiguous; user picks the best best_pressing_pick: z.string().nullable(), // pointer to pressing_candidates[].catalogue_number when confident condition_evidence: z.array(ConditionEvidence), goldmine_grades: z.array(GoldmineGrade), sleeve_annotations: z.array(SleeveAnnotation), inserts_present: z.array(z.enum([ "lyric-sheet", "obi-strip", "hype-sticker", "poster", "receipt", "tour-flyer", "photograph", "letter", "none", ])), value_estimate: ValueEstimateRange.nullable(), audio_verification: AudioVerification.nullable(), genre_inferred: z.array(z.string()), // ["highlife", "Yoruba juju", "Nigerian guitar band"] scene_or_movement_inferred: z.string().nullable(), // "Lagos guitar-band era, late 1960s" household_notes: z.array(z.object({ written_by_member: z.string(), // "the late owner", "Adaeze", "Tunde" text: z.string(), date_added_iso: z.string().nullable(), })), reading_confidence: z.number().min(0).max(1), flagged_for_user_review: z.array(z.object({ field_path: z.string(), // "matrix_codes[0].text_verbatim" reason: z.string(), })), // Hard rule: this field is always exactly this string and never changes. selling_recommendation: z.literal("not-applicable-collection-is-for-keeping"), }); type Record = z.infer; ``` ### Common failure modes (and how to avoid them) - Agent silently downgrades `thinkingLevel` on the the primary parse call to save quota — pin `gemini-3.5-flash` with the matrix-specified `thinkingLevel` explicitly. Flash mis-reads matrix runout codes, confuses triangles with deltas, and silently smooths over biro annotations. - Matrix codes flattened to ASCII — the deadwax often contains triangle (△), circle, square, or specific stamper glyphs (Porky Prime Cuts script, Kendun stamp). Pin in the system instruction: "preserve every glyph; if you cannot type it, name it in `glyphs_present`". - Pressing identified as "the album" instead of "this specific pressing" — the model resolves to a canonical artist+title but returns no year or country. Reject the call result and re-call with explicit instruction "the canonical record only matters if you can identify country, year, and plant; otherwise return multiple pressing_candidates and a confidence below 0.7". - Market value returned as a single number — strictly forbidden. Always low–median–high. Reject any response that returns a single dollar number. - Currency assumed as USD — pin the user's preferred currency in the system instruction for the value-estimate call; if the user's locale is NG, return NGN as primary with USD as a secondary reference only. - Goldmine grade asserted without cited evidence — every grade must point at one or more `condition_evidence[].description` strings. - Owner's handwriting transcribed silently into the artist or title field — biro on the sleeve gets parsed into `sleeve_annotations[]`, never into `artist_verbatim` or `title_verbatim`. - "Sell now for $X" suggestion — strictly forbidden. The schema's `selling_recommendation` field is a literal type. The UI never renders a "sell" CTA. The value range is for the household's records. - Counterfeit detection invoked silently on every record — `high` thinking and grounded-search-heavy. Only run on explicit user request from the record detail view's "(i) Is this an original pressing?" panel. - Audio clip sent as a Firebase Storage public URL — the Gemini API does not fetch public URLs. Upload to Files API or send as `inlineData`. - TTS read in the wrong locale — for a Yoruba record narration, the catalogue card itself is read in the user's locale (English by default for the diaspora user), but the artist name and Yoruba song titles are pronounced as Yoruba via prepended directive. The Yoruba voice is selected via `languageCode` for those segments if the user opts in to per-language narration; otherwise pronunciation falls back to the host voice's locale. ### Negative constraints (hard rules) - Do NOT suggest selling. The word "sell" does not appear in the UI. There is no "list this on a marketplace" CTA. The value range is for the household's records, full stop. - Do NOT translate artist names, song titles, or label names. "Ebenezer Obey", "Elis Regina", "Buzzcocks", "Anjos do Inferno", "Oriental Brothers", "Postcard Records", "Hindusthan Musical Products" stay verbatim. A parenthetical translation may appear on first occurrence of a song title only ("Eyín Yorùbá ò sun (Yoruba do not sleep)") and never thereafter. - Do NOT modernise label names or country names. "ROC" stays "ROC" on a Taiwanese pressing; "U.S.S.R." stays "U.S.S.R." on a Melodiya pressing — preserve `country_printed_verbatim` exactly as printed. - Do NOT invent pressing data. If the matrix codes are ambiguous and grounded search returns no clear match, return multiple `pressing_candidates` with confidence below 0.7 and let the user pick. Never assert a single pressing without evidence. - Do NOT extrapolate market value. If no completed sales exist in the last 24 months for this pressing in this grade, return `value_estimate: null` and explain in `flagged_for_user_review`. Do not invent a price. - Do NOT silently fill in matrix code characters. If a character is uncertain, list it in `characters_uncertain[]` and use a literal placeholder in `text_verbatim` (e.g. `"PWA 1011 A 1G △ ?"`). - Do NOT use the user's collection or audio clips 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-publish or auto-share. Collections are private by default. Sharing is explicit, per-collection, per-family-member. - Do NOT diagnose authenticity or counterfeit without an explicit user request. The user must press "Is this an original?" on a single record to invoke the `high`-thinking counterfeit check. - Do NOT downgrade the grading scale to a "simpler" five-point scale. The Goldmine scale (M, NM, VG+, VG, G+, G, P, F) is the trade standard and the schema enforces it. - Do NOT use any imagery that resembles the actual sleeve art of a specific record in generated illustrations. Nano Banana 2 generates only generic genre scenes (hands sliding LPs from shelves, turntables in late-afternoon light, a record-store wall in soft focus). Never asked to render "the sleeve of Elis Regina's 1972 Elis album" or anything close. ### 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 record images → `Record` schema Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You are reading a single vinyl record artefact set: sleeve front, sleeve back, label side A, label side B, and one or more close-up photographs of the deadwax (the smooth area between the label and the last groove, where the matrix runout codes are etched). Optional additional images include inner sleeve, inserts, obi strip, hype stickers, original purchase receipts, and any annotated paper tucked inside the gatefold. Records can come from anywhere in the world and any decade from 1948 onwards. Languages encountered on labels and sleeves include English, French, German, Italian, Spanish, Portuguese (both Brazilian and European), Yoruba, Igbo, Hausa, Amharic, Swahili, Arabic, Persian (Farsi in Nastaliq), Greek, Russian and Ukrainian (Cyrillic), Hebrew, Yiddish, Tamil, Telugu, Malayalam, Hindi (Devanagari), Urdu (Nastaliq), Bengali, Punjabi (Gurmukhi or Shahmukhi), Sinhala, Burmese, Khmer, Vietnamese (chữ Quốc ngữ), Thai, Mandarin and Cantonese (traditional or simplified Chinese), Japanese (kanji + kana + rōmaji on obi strips), Korean (Hangul mixed with Hanja in older pressings), Tagalog (Filipino), and many regional African languages. Read each script accurately and transliterate as appropriate. Multipage record artefacts (sleeve front + back + label A + label B + matrix close-up + insert) are submitted as a SINGLE call with multiple images, in order. Upload each image via the Gemini Files API (`files/*` resource name) or send as `inlineData` (base64). Do NOT pass Firebase Storage public URLs directly to `generateContent` — the API does not fetch them. Include an explicit "image 1 of 6: sleeve front / image 2 of 6: sleeve back / image 3 of 6: label side A / image 4 of 6: label side B / image 5 of 6: matrix close-up side A / image 6 of 6: matrix close-up side B" header at the start so the model can attribute correctly. Read every visible mark carefully. Distinguish: - printed text on the sleeve (artist, title, label, country) - printed text on the label (catalogue number, side, track listing, copyright notice, BIEM/STEMRA/AFM rights notices) - etched matrix runout codes in the deadwax (these are the canonical pressing fingerprint and must be transcribed verbatim, including every glyph) - stamper glyphs (triangle, circle, square, mastering-house marks like Porky Prime Cuts, Kendun, Bilbo) - biro / pencil / marker annotations by the owner or shop, on the sleeve or label - ring wear, seam splits, edge wear, water staining, hype stickers, shop stickers, library stamps, price stickers Output ONLY the Record JSON matching the provided schema. Hard rules: - Preserve every diacritic exactly. Polish ł, ą, ę; Yoruba subdotted vowels (ẹ, ọ, ṣ) and tone marks; Portuguese ã, õ, ç; French é è ê ç; Vietnamese tone marks; Tamil grantha letters; Arabic shadda and sukoon. Render in the exact Unicode character. - Do NOT translate artist names, song titles, or label names. They go into artist_verbatim, title_verbatim, label_printed exactly as printed. - Matrix codes go into matrix_codes[] with side, verbatim text, any uncertain characters listed, glyphs named (triangle, circle, square, Porky-Prime-Cuts script, Kendun stamp, BIEM/STEMRA, etc.), and position around the deadwax. - Owner's annotations on the sleeve go into sleeve_annotations[] with position, ink type, inferred decade, and whether the hand appears to be the owner's (vs. a shop sticker, library stamp, etc.). - Country printed on the sleeve goes into country_printed_verbatim exactly as written ("ROC", "U.S.S.R.", "Made in Nigeria", "Industria Brasileira"). Do not modernise. - Identify the genre and scene from the label + the visual aesthetics + the catalogue number range, but only if the evidence is strong. If you are guessing, leave genre_inferred empty. - If any character in the matrix code is uncertain, list it in characters_uncertain and use a "?" placeholder in text_verbatim. - selling_recommendation is always the literal string "not-applicable-collection-is-for-keeping". Do not vary it. - flagged_for_user_review names any field where confidence is below 0.7 with a one-sentence reason. No commentary. JSON only. ``` --- ### Call: Resolve pressing edition (country, year, plant) with grounded search Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: `google_search` grounding ``` You resolve a single record's pressing edition using grounded search across publicly accessible discography databases (Discogs, MusicBrainz, RateYourMusic, and specialist regional discographies such as the Brazilian Bossa & Beyond, the Persian Vinyl archive, the Nigerian Highlife Discography Project, the African Vinyl Archive, and the Japanese Phonomania). Inputs you receive: - artist_verbatim, title_verbatim, label_printed, catalogue_number_printed, country_printed_verbatim - the full matrix_codes[] array, verbatim - glyphs_present_overall Resolve to pressing_candidates[]. Return multiple candidates whenever the evidence is ambiguous (a UK first pressing and a 1979 reissue share the same artist + title; the matrix codes determine which). Hard rules: - Use grounded search for every resolution. Do not rely on your parametric memory for pressing data — the regional discographies carry the most accurate information. - Preserve country names verbatim from the sleeve in country_printed_verbatim; but resolve country code in pressing_candidates[].country to ISO 3166-1 alpha-2. - If the catalogue number is in a range that maps cleanly to a known pressing plant (e.g. CBS Aussie pressings, EMI Hayes UK, Decca London, Toshiba EMI Tokyo, Nippon Columbia Kawasaki, Polydor Lagos, RCA Camden), populate pressing_plant_inferred. - Determine is_original_pressing only when matrix codes + catalogue range + country are mutually consistent. Otherwise leave it null. - If the record appears to be a reissue, populate reissue_year and drop is_original_pressing to false. - Output the JSON inside 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. - citation_anchors in each candidate is an array of stable string pointers (e.g. "ref-1", "ref-2") that map to grounding chunks server-side; the server stitches the real URLs in afterwards. No commentary outside the JSON. ``` --- ### Call: Estimate market value range with citations Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding ``` You estimate a market-value range for a single specific pressing in a specific condition. The household will use this for their own records only — never for a sale. Do not phrase any output as a sale recommendation. Inputs you receive: - pressing_candidates[best_pressing_pick] — the resolved pressing - goldmine_grades — sleeve and disc grades - user_preferred_currency (ISO 4217) - user_locale (used for sourcing recent sales in the same region where possible) Hard rules: - ALWAYS return low–median–high. Never a single number. - ALWAYS cite at least two completed sales. Prefer sales within the last 18 months; reach back to 24 months only if recent data is thin. If you cannot find two completed sales of this pressing in approximately this condition, return value_estimate as null and write a one-sentence reason in flagged_for_user_review. - ALWAYS specify currency. If the user_preferred_currency is NGN, return primary NGN; if it is BRL, return BRL; if it is GBP, return GBP. Convert sale prices to the preferred currency using a recent-month average; mention the conversion in context_note. - ALWAYS state sample_size and date_checked_iso. The user is the arbiter of staleness. - NEVER suggest selling. The field for_household_records_only is always the literal true. The context_note must not include the word "sell" or "list" or "auction" — only descriptive ranges. - Output the JSON inside the text body (NOT via `responseSchema`, because grounded search is enabled). Citation anchors are stitched to URLs server-side from `groundingMetadata.groundingChunks[].web.uri`. No commentary outside the JSON. ``` --- ### Call: Grade sleeve and disc on Goldmine scale with cited visual evidence Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You assign Goldmine grades to the sleeve and to each disc side independently, based on visible evidence in the photographs. The Goldmine grading scale, top to bottom: M (Mint — perfect, as manufactured), NM (Near Mint — looks unplayed, perhaps once), VG+ (Very Good Plus — light handling, no flaws visible at arm's length), VG (Very Good — visible flaws, plays through), G+ (Good Plus — heavy handling, plays with noise), G (Good — heavy wear, plays through with significant noise), P (Poor — barely playable), F (Fair — not really playable). Sleeves and discs are graded INDEPENDENTLY. A pristine sleeve can hold a worn disc, and vice versa. Hard rules: - Cite the visual evidence for every grade. Each GoldmineGrade has a cited_evidence[] array; each entry is a verbatim description pulled from condition_evidence[].description. - Be conservative. When evidence is borderline between two grades, choose the lower grade. Buyers and inheritors are better served by the slight downside surprise than by the inflated upside. - Distinguish ring wear (sleeve front, around the disc's outline) from edge wear (sleeve perimeter) from seam splits (sleeve seams separating) from corner dings. - For disc grades, look for visible scratches (light vs. hairline vs. deep), scuffs (paper-residue or stylus-track), warps (visible at an angle), label damage (water stain, residue from old price sticker), and spindle wear (concentric rings around the spindle hole). - Set grade_confidence to reflect lighting quality, photograph angle, and visibility of the relevant surface. Low light + low resolution = low confidence; ask the user to retake in flagged_for_user_review. - Do NOT grade audio quality from the photographs. Disc surface grade does not equal disc playback grade; the audio_verification call handles playback. Output: an array of GoldmineGrade objects (sleeve, disc-side-a, disc-side-b) plus the underlying condition_evidence[]. No commentary outside the structured output. ``` --- ### Call: Transliterate + translate non-Latin label text Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You transliterate and translate text from a record label that is printed in a non-Latin script. The output is for a diaspora reader who may not read the script but reads the user's preferred locale fluently. Hard rules: - Output three forms for each text block: - text_verbatim (the original script, exact) - transliteration (Latin script following the standard for that script: ISO 9 for Cyrillic, ISO 233 for Arabic, BGN/PCGN for Persian, Wade-Giles or pinyin for Chinese as appropriate to the pressing's era, Hepburn for Japanese, Revised Romanization for Korean, ISCII or IAST for Devanagari, ALA-LC for Tamil) - translation (into the user's preferred locale) - Do NOT translate artist names. Transliterate only. - Do NOT translate song titles wholesale — transliterate, and add a parenthetical translation on first occurrence only (e.g. "Eyín Yorùbá ò sun (Yoruba do not sleep)"). - DO translate copyright notices, side and track designations, catalogue numbers, country-of-manufacture statements, and rights-society notices (BIEM, STEMRA, GEMA, JASRAC, KOMCA, ASCAP, SACEM, AKM). - Preserve diacritics and tone marks exactly. No commentary outside the structured output. ``` --- ### Call: Verify audio matches label, report surface noise Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none, audio input) ``` You receive a 20-second microphone clip of a record playing on a turntable, plus the labelled side and track number the user said they were recording. Your task: verify the audio matches what the label claims, and report surface noise. Inputs: - audio clip (uploaded via Files API or inlineData) - artist_canonical, title_canonical, side, track number, and the labelled track title - expected duration of the labelled track (when known) Hard rules: - Return AudioVerification only. Do not transcribe the music. - match_confidence_against_labelled_track is a probability that the audio is the labelled track. Use musical structure, instrumentation, vocal language, and tempo — not lyric content alone. - surface_noise is light, moderate, or heavy. Light = barely audible hiss between phrases; moderate = consistent crackle through the track; heavy = pervasive noise that obscures musical content. - audible_damage names specific defects: skip (the needle jumps forward), lock-groove (the same fragment repeats), repeating-click (single click on every revolution), warp-rumble (low-frequency thump on every revolution), groove-wear-noise (broad shhh throughout), stuck-needle (no progress), off-center-pressing (pitch wobble on every revolution). - If the audio clip is too short, too quiet, or otherwise unevaluable, return surface_noise: "not-assessable" and explain in notes. - Do NOT identify unknown music. This call is for verification only. An unknown-disc identification mode is a separate, explicitly user-invoked call. No commentary outside the structured output. ``` --- ### Call: Counterfeit deep-check (user-requested only) Model: `gemini-3.5-flash` · thinkingLevel: high · Tools: `google_search` grounding ``` You perform a deep authenticity check on a single record, invoked only when the user clicks "Is this an original pressing?" on the record detail view. Inputs: - the full Record object - all photographs at original upload resolution Hard rules: - Compare the label typography, the sleeve printing quality, the matrix runout characters, and the stamper glyphs against grounded references for the claimed original pressing. - Counterfeits often have: matrix codes that are stamped rather than etched (look for tell-tale uniform stroke widths), label fonts slightly off (the spacing, the curve of the ampersand), sleeve cardboard that is too uniform (originals show fibre variation), spelling mistakes on the label, the absence of a rights-society notice that the original always carries. - Return a confidence (0.0 to 1.0) that the pressing is original, with a list of evidence items both for and against. - NEVER assert "this is a counterfeit" without strong evidence. If the evidence is ambiguous, say so explicitly. - Output the JSON inside the text body (NOT via `responseSchema`, because grounded search is enabled). Citations stitch server-side from `groundingMetadata`. No commentary outside the JSON. ``` --- ### Call: Collection-wide genre arc + cluster analysis Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none, long-context over the collection) ``` You receive every Record object in a collection at once (long-context). Your task: propose a genre arc and discover clusters that the original collector seemed to assemble deliberately. Hard rules: - Output a JSON object with: timeline_arc (the chronological storyline of the collection by acquisition or by release year), scene_clusters[] (named clusters with the records they contain and a one-paragraph factual note), and acquisition_patterns (e.g. "the collector seemed to chase first Nigerian pressings of Decca West Africa releases between 1968 and 1972"). - Be conservative. A scene_cluster needs at least three records to be named. - Do NOT extrapolate biographically about the collector. State only what the records themselves show. - Do NOT recommend selling, splitting, or trimming the collection. This is a descriptive analysis of what is there, for the household's understanding. No commentary outside the structured output. ``` --- ### Call: Generate TTS narration of catalogue card Model: `gemini-3.1-flash-tts-preview` · n/a · n/a ``` Voice: calm, unhurried, the pace of a curator reading a museum label aloud to a friend. Pick the Gemini 2.5 Flash TTS voice whose `languageCode` matches the user's preferred locale — pronunciation will follow that locale automatically. Pre-process the catalogue card text before sending it to TTS: - Read in this order: artist (verbatim), title (verbatim), label and catalogue number, country and year of pressing, sleeve grade and disc grade, market value range (low to high with currency) only if the user has explicitly enabled value narration, household notes. - At each section break, insert a single ellipsis (`…`) so the TTS model produces a natural pause. Between artist and title, between title and label, between label and grades — pauses are how a museum-style read sounds calm. Gemini 2.5 TTS does not support SSML `` — these textual cues are how you signal pace. - Skip matrix runout codes when narrating (they are visual fingerprints, not spoken content) — but if the user explicitly requests "read the matrix" in the UI, then read them character by character with em-dash spacing. - Mid-call voice switching is not supported. Yoruba song titles inside an English narration stay in the host voice; the on-screen subtitle italicises the original script so the user sees the switch. - Never narrate the word "sell" or any sale phrasing. The value range, when narrated, reads as "value range for household records, [low] to [high] [currency]". - Target rate: ~120 words per minute — a curator's pace, not a podcast pace. Style direction: prepend ONE short directive sentence to the text input, exactly like: "Read calmly and unhurriedly, the way a curator reads a museum label to a friend. …". There is no separate `style` API field on Gemini 2.5 TTS; the directive sentence inside the input is how style is conveyed. Phoneme overrides (Yoruba subdotted vowels, Polish ł, Tamil retroflex consonants) are NOT exposed by Gemini 2.5 TTS — no SSML `` tag. Pronunciation comes from the chosen voice's native locale. ``` --- ### Call: Generate hero / genre-lineage illustration Model: `gemini-3.1-flash-image` · n/a · n/a ``` You generate a single warm illustration for either the landing-page hero or a genre-lineage sidebar card. Output one image at the requested aspect ratio. Allowed subjects (these are the only allowed subjects — do NOT generate any other): - a hand sliding an LP from a tall wooden shelf in afternoon light - a turntable on a side table with a single LP propped against the wall behind it, late-afternoon golden light, no people in frame - a record-store wall in soft focus, generic sleeves blurred, no legible artist or title text on any sleeve - a hand placing the needle on a spinning LP, close-up of the cartridge, no album art visible - a stack of LPs in their inner sleeves only (no printed art) on a living-room rug Hard rules: - Do NOT render the sleeve art of any specific real record. No recognisable artist names, no recognisable album titles, no recognisable label logos on any sleeve in any image. - Do NOT include text on the sleeves; if text appears, render it as abstract typographic shapes only. - Do NOT include people's faces. Hands and forearms are fine; no faces. - Warm light, slight imperfection, asymmetry, a real-photograph feel — avoid the glossy 'AI render' look. - Avoid stylised or cartoon aesthetics. The intent is photographic. Output: one image at the requested aspect ratio. 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 widow's first Saturday.** A widow in Lagos opens the app at her dining table at 2 p.m. on a Saturday, six months after her husband's funeral. There are 312 LPs in his collection. The app's empty state offers a "Saturday-afternoon catalogue session" flow optimised for 20–40 records per sitting: the camera holds focus, autocrops sleeve and label and matrix, and queues each record as a single artefact. She works through 22 records, has a cup of tea, comes back next Saturday for 25 more. - **The widow's second city.** Two months later, the widow's daughter in Abuja photographs another 28 records from a small second shelf the family had moved to her flat after the funeral. They merge into the same collection; the duplicates the algorithm catches turn out to be the widow's first vinyl from before she married, which her husband had quietly added to his shelf decades earlier. - **The bossa nova rebuild.** A widow in São Paulo is working through her late husband's MPB and bossa nova collection — 480 LPs accumulated over 42 years. The app reads "Industria Brasileira" on the sleeve back, identifies the Polydor and Odeon pressings, distinguishes the original 1972 Clube da Esquina from the 1990 reissue by the matrix codes ("OD-1015 A △ 2" vs "OD-1015 A 1979 ▽ 3"), and catches the Elis Regina 1972 with the white-label promo stamp her husband had circled in biro on the sleeve back forty years ago. - **The punk wall.** A widow in Glasgow works through her late husband's punk and post-punk wall — Buzzcocks 7-inches in plain paper sleeves with no insert, the Postcard Records run (Orange Juice, Aztec Camera, Josef K), early Rough Trade pressings still with the shop's price written in pencil on the back. The matrix codes carry the Porky Prime Cuts script on several originals. - **The Yoruba Sunday.** A daughter cataloguing her father's Yoruba juju collection — King Sunny Ade & His African Beats, Ebenezer Obey & His International Brothers Band — reads "BAS 21" pressed at the Polydor Lagos plant on records her father bought new at Glendora Records on Lagos Island in 1976. The app pulls the genre-lineage sidebar and the daughter learns that "talking drum" is the colloquial English for "dùndún" and that the band names she heard her father say all her life are pressed in tiny type on every sleeve. - **The Persian pre-revolution shelf.** A diaspora family in Los Angeles is rebuilding the grandfather's Persian pop collection from before 1979 — Googoosh, Dariush, Ebi, Hayedeh — pressed on Apollo Records Tehran, Aramfar, Caspian. The Nastaliq script on the labels parses cleanly; the catalogue card carries the transliteration and a one-paragraph note that these pressings are scarce because the masters were destroyed. - **The Vietnamese pre-1975 box.** An adult son in Toronto is cataloguing his late mother's nhạc vàng — Tuấn Vũ, Khánh Ly, Hoàng Oanh — on Sóng Nhạc Saigon and Asia Records Saigon pressings, with the diacritics on Vietnamese song titles preserved exactly and the catalogue card explaining the post-1975 ban that makes original pressings the only ones that exist. - **The biro on the sleeve.** A record sleeve has "for Adaeze on her 30th — Tunde, Lagos 1984" written in biro on the inner gatefold. The app reads it, parses it as a sleeve annotation, and asks the user (Adaeze, the recipient, now in her seventies) whether she wants the note shown on the catalogue card. She does. - **The unknown disc.** A 7-inch in a plain white sleeve with no label has been in the collection for forty years. The user holds the phone over the playing record for 20 seconds; the audio-verification call flips into identification mode (user-confirmed), and Gemini 3.5 Flash proposes three candidate tracks across the user's collection-wide genre, citing each. - **The grandfather's Cuban son.** An adult grandson in Miami is cataloguing his abuelo's Cuban son montuno and danzón collection — Beny Moré, Celia Cruz with La Sonora Matancera, Pérez Prado — including the 1955-58 Panart pressings the abuelo brought from Havana in his suitcase and the post-exile Tico and Alegre pressings made in New York. The app distinguishes the Cuban originals from the New York exiles by the matrix codes and the printed country. - **The mis-labelled side.** A record's side B label says "Track 1: Eyín Yorùbá ò sun" but the audio verification reports moderate-confidence mismatch and the user discovers a side B label was glued over the original by a previous owner. The app logs both labels (verbatim) and flags the artefact for the user to investigate. - **The estate inventory.** A solicitor asks the household for a defensible inventory of the collection for the estate. The app exports the structured catalogue as a PDF and a CSV — both watermarked "household estate inventory — not a sale listing" — with per-record pressing, grades, and value ranges as ranges (low–median–high), citation URLs preserved. ## 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 hand sliding an LP from a tall wooden shelf in late-afternoon light. One paragraph: "Vinyl Restorer rebuilds a record collection sleeve by sleeve — with every label, matrix code, and family note honoured. Photograph the sleeve. We do the rest." Single Google sign-in button; Apple sign-in next to it. Below: "Try with the sample collection" → loads the demo collection in section 8a. 2. **Empty state — "Start a collection".** Three big input methods: 📷 Sleeve + label capture · 🖼 Upload scans · 🎙 Verify a playing record. A short explainer below each ("Best for one Saturday-afternoon catalogue session", "Best if you've already scanned at 600 dpi", "Best when you want to check the audio matches the label"). 3. **Capture flow** (mobile-first). Live viewfinder with sequenced prompts: sleeve front → flip prompt → sleeve back → "open the gatefold?" → "label side A" → flip prompt → "label side B" → "matrix close-up side A" → "matrix close-up side B" → "anything else? inserts, OBI, biro on the sleeve". Each record clusters as a single artefact in the queue. The flow keeps the camera at fixed focus and warmth so the sleeve colours stay consistent. 4. **Processing queue.** A vertical list of the session's batch. Each item shows the sleeve thumbnail, artist + title (when known), country + year (when known), and a step-by-step honest progress bar: "Reading the sleeve…" → "Reading the matrix runout…" → "Resolving the pressing…" → "Grading sleeve and disc…" → "Looking up the value range…". Each step takes 4–14 seconds. The user can close the app and come back. 5. **Record detail view.** A three-column layout on desktop, stacked on mobile. Left column: the photographs of sleeve front, sleeve back, label, and matrix close-up (zoomable, with annotations overlaid — ring wear, seam splits, stamper glyphs, biro from the previous owner). Middle column: pressing identification (artist, title, label, catalogue number, country, year, plant), with confidence chips and citation icons. Right column: condition card (sleeve grade + disc-A grade + disc-B grade, each with its cited evidence), value range card (low–median–high in the user's currency, with citations and a "last checked" date), audio verification card (when the user has recorded), household notes. Sticky header: artist → title → pressing-confidence chip → "(i) Is this an original pressing?". 6. **Collection view.** Magazine-grid of records. Filter by artist, country, year, label, pressing edition, condition, market-value range, "has handwritten note from late spouse", "incomplete pressing", "audio verified". A toggle: "Show me the records he bought in the year we got married", "Show me the records with biro notes", "Show me the Nigerian first pressings". 7. **Shelf view.** A literal shelves-of-vinyl view that mirrors the physical shelf order. Drag-and-drop to match the room. Useful for the user re-shelving after a house move. 8. **Genre arc view.** A timeline of the collection by acquisition decade and by genre, with named scene-clusters discovered by the long-context cluster call. Each cluster opens a small factual sidebar that explains the scene in one paragraph with sources. Never speculative about the collector. 9. **Audio bench.** A focused workspace for the verify-playing-record flow. Big "Record 20 seconds" button. After recording: side/track confirmation, surface-noise level, audible damage list, and a small spectrogram visualisation that helps the user see what the app heard. 10. **Sharing & invitations.** Modal: "Invite a sibling or child to add their photographs to this collection". Magic-link email; arrival drops the family member straight into the same collection with their own avatar. 11. **Catalogue export.** Side-by-side typeset preview. Choose: by artist, by genre, by year, by shelf order. Toggle: include matrix-code runouts, include market-value ranges, include household notes. Export PDF; future tier: print-bound paperback. 12. **Estate / insurance inventory.** A separate, watermarked export: structured CSV + PDF with grading and value-range columns, household total, citation URLs preserved. Watermark on every page: "household estate inventory — not a sale listing". 13. **Footer.** "For the collections nobody else hears." Privacy: "Your collection is yours. We never train on it." Capabilities `(i)` icon in header. ## 6b. First-visit onboarding Show a **first-visit onboarding** the first time a visitor lands on the app (detect via `localStorage` flag; do not show on return visits). Three slides, dismissible at any time. Persistent re-entry: a `?` icon in the header reopens it. **Slide 1 — What this is.** - Headline: "Welcome to Vinyl Restorer." - Subhead: "Rebuild a record collection sleeve by sleeve — in any genre, any country, with every label, matrix code, and sleeve crease honoured." - One paragraph (≤ 60 words) explaining who this is for and what makes it different from a generic Discogs uploader: it reads the matrix runout codes as carefully as the sleeve, it grades sleeve and disc separately, it cites every market-value range, and it never suggests selling. The collection is for keeping. - Visual: a small annotated illustration of an LP and its sleeve with the relevant marks labelled (matrix runout, stamper glyph, ring wear, biro annotation) — not a generic vinyl icon. **Slide 2 — Try it now.** - One short prompt: "Try with the sample collection". - A live demo input pre-loaded with three records from the seed content in section 8a (Lagos highlife, São Paulo bossa, Glasgow post-punk). - 1–2 sentences pointing at *the specific page elements* where the Gemini magic happens (the matrix runout transcribed verbatim with the triangle glyph, the Yoruba label transliterated, the citation links beside the value range). **Slide 3 — How to remix this.** - Headline: "Make this yours." - Three short bullets: - "Swap the sample collection in `/data/seed-collection/` for your own photographs." - "Adjust the prompts in `/server/prompts/` to fit the genres and regions your family collected." - "Wire up your Gemini API key, Firebase project, and optional Discogs token via the env-var list in the capabilities panel." - Primary CTA: "Use this template" → links to AI Studio Build remix entry point. - Secondary: "Just exploring — close" (sets localStorage flag, never auto-shows again). **Accessibility:** focus trap, `Esc` closes, `role="dialog"`, `aria-modal="true"`, `aria-labelledby`, focus restored to trigger on close. Respect `prefers-reduced-motion`. **Don't:** - Don't gate content behind the modal. The page beneath must be fully usable. - Don't auto-reshow on return visits. Use `localStorage['onboarding-seen-v1']`. - Don't include unrelated CTAs (newsletter signup, social follow). Keep it about the template only. ## 6c. Capabilities info button (persistent in header) Add a persistent `(i)` icon in the top-right of the header (next to the primary nav). Click → opens a modal/panel titled **"What powers this app"**. **Panel contents (in this order):** **Gemini capabilities used (the hero list):** - **Gemini 3.5 Flash (multimodal images)** — reads sleeve front, sleeve back, label side A, label side B, and matrix runout close-ups in a single multi-image call. Handles every script — Cyrillic, Amharic, Tamil, Japanese, Persian, Yoruba, Bengali, Mandarin — in the same call. - **Gemini 3.5 Flash (multimodal audio)** — verifies a 20-second microphone clip against the labelled track, reports surface noise (light/moderate/heavy), and detects audible damage (skip, lock-groove, repeating click). - **Gemini 3.5 Flash (long context)** — once your collection grows past a few hundred records, the genre-arc view sees every parsed record at once to find the clusters the original collector seemed to chase deliberately. - **Gemini 3.5 Flash + grounded search** — resolves the specific pressing edition (country, year, plant) against publicly accessible discographies, with citations preserved. - **Gemini 3.5 Flash + grounded search** — looks up market-value ranges as low–median–high with at least two completed-sale citations. Always a range. Never a single number. Never a sell suggestion. - **Gemini 2.5 Flash TTS** — reads each catalogue card aloud at a curator's pace, in the user's preferred locale. - **Gemini 3.5 Flash Image (Nano Banana 2)** — generates the warm landing-page hero and the genre-lineage scene illustrations. Never used to render specific sleeve art. - **Firebase Auth** — Google and Apple sign-in, family invitations via magic links. - **Firestore** — stores your collection, syncs across devices in real time. - **Firebase Storage** — keeps the original sleeve and label photographs at upload resolution, forever. - **Cost note** — see the detailed breakdown in 6d. A typical Saturday-afternoon session of 25 records costs about $0.55 of Gemini API spend, total, processed once. - **Privacy note** — your collection is private to you and the family members 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. The market-value citations stay inside your collection; no totals are broadcast. **Backend services this app depends on:** - Auth: see section 4b - Database: see section 4b - Storage: see section 4b - Email: see section 4b - Payments: see section 4b (not used in v1; and there is no "list to sell" tier ever) - External APIs: see section 4b **Environment variables you'll need to configure:** - `GEMINI_API_KEY` — your Google AI Studio API key - `FIREBASE_PROJECT_ID` — your Firebase project id - `FIREBASE_SERVICE_ACCOUNT` — service-account JSON (server-side only) - `DISCOGS_TOKEN` — optional, only if you want higher-fidelity pressing data **Cost + privacy notes:** - One short paragraph per cost-sensitive capability: long-context calls are billed per token of input — a 300-record genre-arc resolve costs about $0.45 each time it runs (default: monthly). - One short paragraph on privacy: where the data lives (your Firebase project), how to delete it (Settings → "Delete this collection forever" — gone in 60 seconds), what is never sent for training. **Documentation links:** - AI Studio Build docs - Gemini API multimodal-image, multimodal-audio, long-context, grounded-search, TTS, image-generation docs - Firebase Auth, Firestore, Firebase Storage docs - A short note on Goldmine grading **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 record images (Gemini 3.5 Flash, medium thinking)** — typical 6-image set (front, back, label A, label B, matrix A, matrix B), ~1,400 output tokens. ~$0.018/record. - **Resolve pressing edition (Gemini 3.5 Flash + grounded search, medium thinking)** — typical 1–3 grounding chunks. ~$0.014/record. - **Estimate market value range (Gemini 3.5 Flash + grounded search, low thinking)** — typical 2–4 grounding chunks. ~$0.001/record. - **Grade sleeve and disc (Gemini 3.5 Flash, medium thinking)** — typical 250 output tokens. ~$0.004/record. - **Transliterate + translate non-Latin label (Gemini 3.5 Flash, low thinking)** — runs only when the label is non-Latin. ~$0.002/record (on records that need it). - **Audio verification (Gemini 3.5 Flash, low thinking, audio input)** — 20-second clip; billed per audio second + small output. ~$0.005/record (on records the user verifies). - **Counterfeit deep-check (Gemini 3.5 Flash, high thinking + grounded search, user-requested only)** — ~$0.06/record. Only when the user clicks "Is this an original?". - **Genre arc + cluster (Gemini 3.5 Flash, medium thinking, long-context)** — runs monthly. ~$0.45 per collection of 300 records per run. - **TTS narration (Gemini 2.5 Flash TTS)** — billed per output token (~$10/M output tokens), effectively ~$0.000003/character. A 150-word catalogue card ≈ $0.003 per narration. Cached per record; charged once. - **Hero / genre-lineage illustration (Gemini 3.5 Flash Image / Nano Banana 2)** — ~$0.03 per image. Generated once per genre cluster; cached. - **Expected per-record cost on first ingest:** ~$0.038. **Saturday-afternoon session of 25 records:** ~$0.95. **Ongoing monthly genre-arc refresh** (collection of 300): ~$0.45/month. - **Image storage:** Firebase Storage standard tier, ~$0.026/GB/month. A high-res 600 dpi sleeve scan + 4 label/matrix close-ups ≈ ~12 MB per record; a 300-record collection uses ~3.5 GB ≈ ~$0.09/month. ## 7. Design language - **Mood:** A record-collector's listening room that happens to live on your phone. Not a marketplace. Not a Discogs power-user dashboard. The afternoon light through the slats of a wooden venetian blind, the LP half-out of its sleeve on the side table, the kettle on in the kitchen, the user (a widow, an adult child, a sibling) holding the phone over the next record because there are still 240 to go and the collection deserves to be known properly. - **Typography:** Display serif for record-detail headings (the artist, the title) — something warm and editorial, Source Serif Pro or Adobe Caslon Pro. A clean grotesque for app chrome (Inter or Geist). A small-caps treatment for the matrix runout codes themselves, in a monospaced face (JetBrains Mono) so the glyphs are readable. The Goldmine grade chips use a slightly heavier sans-serif weight to read at a glance. - **Palette:** Warm card cream `#F2EBDC` for record cards, deep ink `#1F1A14` for body text, sepia accent `#7B4F2A` for label authentic-pressing chips, muted indigo `#3A4A6B` for the user's household notes (so they cannot be mistaken for printed sleeve text). Faded burgundy `#7A2E2A` only for "needs review" flags. A near-charcoal `#28231D` for the matrix runout block backgrounds — like the deadwax itself. Avoid bright SaaS blues. - **Imagery:** The photographs of the records are the hero. Never replace them; never crop them tighter than the user did. The sleeve corners, the spine, the slight curve of a worn cardboard — all honoured. Generated illustrations from Nano Banana 2 are restricted to the welcome hero and per-genre scene cards, and never resemble a specific real record's sleeve. - **Hand-feel touches:** A barely-visible paper grain on the record-detail background that looks like an inner sleeve. The "show original photograph" expandable panel slides the image in with a thin shadow — like sliding an LP halfway out of its sleeve. Hovering on a matrix code reveals the verbatim characters and the named glyphs; never aggressively glow. - **Spacing:** consistent 4-px base. Generous whitespace — the catalogue cards need air. - **Radius:** consistent token set (e.g. 6 / 12 / 20 px). Record cards use 6; the genre-arc cards use 12; the welcome card uses 20. - **Shadows:** subtle, layered, sepia-tinted. Avoid heavy drop-shadows. - **Motion:** purposeful — entrance fades, hover lifts, page transitions. Respect `prefers-reduced-motion`. No spinning-LP animations on loading. No theatrical hero animations. The shelf-view drag-and-drop reorder is the one place where motion carries meaning; respect reduced-motion by snapping rather than animating. - **States:** every interactive element has hover, focus, active, disabled. Loading uses skeletons not spinners where possible. Empty states have helpful next-action guidance ("Photograph the first sleeve front to start"). ## 8. Content generation rules - Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'. - Invent plausible artists, titles, labels, catalogue numbers, matrix runouts, household notes that fit the domain (use the seed content in section 8a as a starting point). When inventing, lean on real twentieth-century pressing conventions — Polydor Lagos catalogue numbers in the BAS / POL ranges for Nigerian highlife, Odeon and Polydor Brazil for MPB, Rough Trade RT and Postcard 80- catalogue numbers for early-80s UK post-punk — but never claim that a fictional pressing is a real reference release. Invented artist names are fine; named real-world artists may appear only in passing illustrative mentions (the way the use cases above use names like Rex Lawson, Elis Regina, Buzzcocks) and never in fabricated discography records. - Tone: warm, direct, free of corporate language. This template is for a person, not a marketplace. - Headlines: punchy and concrete. No 'Empower your X' filler. No 'Revolutionize'. No 'Seamless'. - Body copy: short paragraphs (2–4 sentences). Use lists where appropriate. - Plain language. Avoid jargon — except where the user already speaks the jargon (the collector user wants to see "matrix runout" and "stamper glyph"; the estate user wants to see "household estate inventory"). - Where the app outputs AI-generated content, never label it as "AI says" — let it speak naturally. Use small uncertainty cues only where epistemic honesty requires them (a low-confidence pressing pick shows as a chip with a faint dashed border; tapping it reveals the alternates the model considered). ## 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 collections (sidebar):** - "Adaeze's Lagos Shelf" (312 records, contributors: Adaeze, her daughter Chiamaka in Abuja) — primarily Nigerian highlife, juju, Afrobeat, and 1970s soul on Polydor Lagos, Decca West Africa, EMI Nigeria, and Tabansi pressings. Inherited from her late husband Tunde, who collected from 1968 to 2024. - "Maria das Graças's Bossa Shelf" (480 records, contributors: Maria das Graças, her son Fábio in Porto Alegre) — bossa nova, MPB, Tropicália, and Clube da Esquina on Polydor Brazil, Odeon, Philips, and Continental pressings. Inherited from her late husband Antônio, who collected 1965–2025. - "Senga's Wall" (208 records, contributor: Senga only) — UK punk and post-punk on Rough Trade, Postcard, Factory, 4AD, Stiff, and Small Wonder pressings, 1976–1986. Senga's late husband Iain played in three bands between 1979 and 1982 and kept everything. - "Roya's Tehran Box" (62 records, contributors: Roya, her brother Babak in Vancouver) — pre-1979 Persian pop on Apollo Records Tehran, Aramfar, Caspian, and Royal Records. Brought out of Iran in 1979 in three suitcases. **Sample record in detail view (this is what the demo should show — fictional artist and title for the demo; do not pass this off as a real release):** - **Artist (verbatim):** "Emmanuel Okonkwo and his Coastal Dance Band" - **Title (verbatim):** "Highlife with a Coastal Beat" - **Label printed:** "Coastline West African Records" - **Catalogue number printed:** "CWA 1011" - **Country printed verbatim:** "Made in Nigeria" - **Matrix codes (2):** - side A — text_verbatim "CWA 1011 A 1G △ 9 INSPECT 4", glyphs ["triangle", "pressing-plant-code"], position "around-the-label" - side B — text_verbatim "CWA 1011 B 1G △ 9", glyphs ["triangle", "pressing-plant-code"], position "around-the-label" - **Glyphs present overall:** ["triangle (△) consistent with a Lagos pressing-plant stamper of the period", "INSPECT 4 quality-control mark"] - **Pressing candidates (1, high confidence):** - label "Coastline West African Records", catalogue "CWA 1011", country "NG", year_known 1971, format "LP", channels "mono", pressing_plant_inferred "Lagos pressing plant", is_original_pressing true, confidence 0.93, citation_anchors ["ref-1", "ref-2"] - **Best pressing pick:** "CWA 1011" - **Condition evidence (4):** - sleeve, "ring wear visible on lower-right of sleeve front, approximately 12mm across", affects_playback null - sleeve, "seam intact, no splits visible along any edge", affects_playback null - disc-side-a, "two hairline scuffs across track 3, side A, approximately 30mm long, no deep scratches", affects_playback true - disc-side-b, "side B label has a 2mm water stain at the lower edge; vinyl surface shows light groove-wear sheen but no visible scratches", affects_playback null - **Goldmine grades (3):** - sleeve grade VG+, grade_confidence 0.86, cited_evidence ["ring wear visible on lower-right of sleeve front, approximately 12mm across", "seam intact, no splits visible along any edge"] - disc-side-a grade VG+, grade_confidence 0.81, cited_evidence ["two hairline scuffs across track 3, side A"] - disc-side-b grade VG+, grade_confidence 0.84, cited_evidence ["light groove-wear sheen but no visible scratches"] - **Sleeve annotations (1):** "Tunde — Glendora Records, 1971" in biro-blue, position front-lower-right, appears_to_be_owner_hand true, inferred_decade "1970s" - **Inserts present:** ["none"] - **Value estimate:** currency "NGN", low 18000, median 28000, high 42000, sample_size 4, date_checked_iso "2026-05-28", citation_anchors ["ref-3", "ref-4"], context_note "based on 4 completed VG+ sleeve / VG+ disc sales in the last 18 months from Lagos, London, and São Paulo collectors", for_household_records_only true - **Audio verification:** clip_duration_seconds 20, side_recorded "A", track_recorded 1, match_confidence_against_labelled_track 0.97, surface_noise "light", audible_damage ["none"], notes "the audio matches the labelled track 1 cleanly; light hiss consistent with the disc grade" - **Genre inferred:** ["highlife", "Nigerian guitar band", "Niger Delta highlife"] - **Scene or movement inferred:** "Lagos and Port Harcourt guitar-band scene, late 1960s and early 1970s" - **Household notes (1):** written_by_member "Adaeze (Tunde's widow)", text "Tunde bought this on a Saturday in Lagos and played it the night he proposed to me. He always played side A track 3 last, after the kids had gone to bed.", date_added_iso "2026-05-15" - **Reading confidence:** 0.91 - **Selling recommendation:** "not-applicable-collection-is-for-keeping" **Sample input artefacts (for the build to demonstrate):** - A Nigerian first-pressing LP from Polydor Lagos, 1971, with the triangle stamper glyph in the matrix and biro on the inner sleeve. - A 1972 Brazilian Polydor pressing of an MPB album, "Industria Brasileira" stamped on the back, with the catalogue number range consistent with São Paulo Polydor. - A UK 7-inch single from Postcard Records, 1981, with "Postcard 80-X" catalogue range and the Porky Prime Cuts script in the matrix. - A Persian Apollo Records LP, Tehran, late 1970s, label in Nastaliq, with the catalogue number printed in both Persian numerals and Western numerals. - A 1968 Vietnamese 7-inch on Sóng Nhạc Saigon, with the diacritics on the song titles fully preserved. **Sample voice copy:** - Onboarding: "Photograph the next sleeve. We'll read it — even the bit she wrote on the back forty years ago." - Processing: "Reading the sleeve…" / "Reading the matrix runout…" / "Resolving the pressing…" / "Grading sleeve and disc…" / "Looking up the value range…" - Empty collection: "This collection is waiting for its first record. Photograph the front of one sleeve to start." - Error (couldn't read the matrix): "We couldn't make out the matrix runout. Want to try a closer photograph of the deadwax — the smooth area between the label and the last groove?" - Save confirmation: "Added to Adaeze's Lagos Shelf — Emmanuel Okonkwo, 'Highlife with a Coastal Beat', 1971 Lagos pressing." - Low-confidence pressing: "Two candidates fit this sleeve. Tap to choose." - Value range note: "Value range: ₦18,000–₦42,000, median ₦28,000. Based on 4 sales in the last 18 months. For your records — we do not suggest selling." - Audio verified: "Side A track 1 matches the label cleanly. Light hiss only." - Counterfeit check available: "(i) Is this an original pressing? — runs a deeper check." **Sample family invitation email subject + body:** - Subject: "Chiamaka — I'm cataloguing your dad's records. Will you add the box at yours?" - Body: "Hi Chia — I've started photographing your dad's collection at home. Could you add the small shelf he gave you for university? Tap to join and add the ones you have. Take your time." [Open Collection] ## 9. Media & assets - **Hero image (landing screen):** A photographed-looking shot of a hand sliding an LP from a tall wooden shelf in late-afternoon light, an inner sleeve half-visible, the spine of the next record just behind. Generate via Nano Banana 2 with a prompt emphasising "wooden shelf, warm afternoon window light, hand of a person in their fifties or sixties, late afternoon, the LP half-out of its inner sleeve, no album art visible on the sleeve, soft shadow under the hand, real worn cardboard texture". No legible text on any sleeve in frame. - **App icon / wordmark:** Set in the display serif. A subtle deadwax-style etched line beneath the wordmark. No icon — just type. - **Empty-state illustration:** A simple line drawing of an inner sleeve with an LP half-out. Hand-drawn aesthetic, not a flat icon. - **Demo record photographs:** Generated per the prompts in section 8a — Nano Banana 2 prompts that specifically request "creased cardboard sleeve, faded label, soft afternoon window light, the close-up of a deadwax with etched matrix runout codes, no recognisable artist or title text". Each demo record should look photographed, not rendered. Never generate sleeve art that resembles any real release. - **Genre-lineage illustrations:** One small warm illustration per scene-cluster (a hand on a turntable for "Lagos guitar-band era", a stack of unmarked inner sleeves on a rug for "Glasgow Postcard run", a single LP propped against a wall for "São Paulo MPB"). Restricted subjects per the system instruction in 4b. - **Stock fallbacks:** If image generation fails, fall back to the photographed sample sleeve from `/public/samples/sample-sleeve.jpg`. Never to a "💿" emoji. - **Generated imagery:** prefer Nano Banana 2 over stock photography. Prompt for warmth, asymmetry, and slight imperfection — avoid the glossy 'AI render' look. - **Optimisation:** WebP/AVIF, `loading="lazy"`, explicit `width`/`height` to prevent layout shift. - **Icons:** `lucide-react` for UI. Use sparingly — never decorative-only. ### Build-time asset manifest (explicit specs) Every image, illustration, and visual reference mentioned above must resolve to ONE of the three buckets below — runtime-generated, seed-shipped, or user-supplied. Do NOT ship `` tags whose `src` is not listed here. Do NOT depend on bare "section 8a prompts" without binding them to explicit paths and model IDs. **Bucket 1 — Runtime-generated (Nano Banana Pro `gemini-3-pro-image` for hero/demo photographs; Nano Banana 2 `gemini-3.1-flash-image` for in-app illustrations and reference-conditioned variants).** Cached to Firebase Storage; served via signed URL. Every reference above to "Nano Banana 2" or "Nano Banana Pro" MUST be wired to one of these specific calls with an explicit model id: - `/public/generated/hero.webp` (2400×1500, WebP) — model `gemini-3-pro-image` — uses the literal prompt described as "Hero image (landing screen)" above. Run once at build; commit a `/public/samples/hero-fallback.webp` (1600×1000) generated from the same prompt with `gemini-3.1-flash-image` so the page renders if quota is exhausted. - `/public/generated/demo/{demo-slug}-{NN}.webp` (1600×1200, WebP) — model `gemini-3.1-flash-image` (reference-conditioned where the prior frame is passed as input) — one path per "Demo X" image referenced above. The slug derives from the seed example in section 8a; the NN index covers each frame in the demo sequence. - `/public/generated/illustrations/{name}.webp` (1024×1024, WebP) — model `gemini-3.1-flash-image` — one path per named illustration above ("Empty-state illustration", "Recipe-card hero illustrations", "Curriculum picker imagery", "Period-style frames", etc.). Each illustration's prompt is the literal description above; ship a deterministic seed in the request so re-runs are reproducible. **Bucket 2 — Seed assets shipped with the deliverable.** Every "Stock fallback" path referenced above (e.g. `/public/samples/sample-X.jpg`) is generated once via Nano Banana 2 (`gemini-3.1-flash-image`) at 1024×1024 WebP using the same prompt as its Bucket-1 counterpart, then committed to the repo so the page renders identically if Gemini quota is exhausted or the user is offline. Replace any `.jpg` extension above with `.webp` to match the optimisation rule. Also commit these empty-state seeds (1024×1024 WebP, single-stroke hand-drawn line, no colour fill): - `/public/samples/empty-state-primary.webp` — line drawing of the app's primary empty surface (the named "Empty-state illustration" above), generated from that exact prompt. - `/public/samples/empty-state-archive.webp` — line drawing of an empty saved/archive view, single-stroke outline. - `/public/samples/empty-state-error.webp` — line drawing of a hand placing a single object aside with care, used when an AI call fails. **Bucket 3 — User-supplied.** Uploads from the user's camera / file picker land at the Firebase Storage path conventional for this template (named in section 4b). The build ships with Bucket-1 + Bucket-2 only; no user-supplied images at first paint. **Hard rules** - Every `` tag MUST have a `src` that resolves to a path listed in Bucket 1, Bucket 2, or a Bucket 3 upload path. Anything else is a build error. - No bare `image.jpg` / `hero.jpg` / `placeholder.png` references anywhere in the code. - Model IDs: `gemini-3-pro-image` for hero-quality photographic generation; `gemini-3.1-flash-image` for in-app illustrations, reference-conditioned variants, empty-state seeds, and stock fallbacks. Never use a legacy model id (no `imagen-*`, no `gemini-1.5-*-image`). - File format: WebP everywhere (AVIF acceptable where the target browsers support it). No `.jpg` / `.jpeg` / `.png` in `/public/samples/`. ## 10. Interactivity & states - Every interactive element has hover, focus, active, and disabled states. - Forms validate inline and show specific error messages (not "Invalid input"). - Loading states use skeletons that match the eventual layout, not spinners. Never a spinning-LP loader. - Empty states explain the next action with a button whose label fits THIS app's domain: "Photograph the front of the first sleeve", "Drop a previously-scanned set of sleeve + label", "Record 20 seconds of the playing record" — never a generic "Add your first item". - Smooth scroll for in-page anchors. - All AI-generated content streams in token-by-token where supported, with a clear "thinking…" indicator before content starts arriving. - If an AI call fails, show a calm, specific error ("We couldn't make out the matrix runout — try a closer photograph of the deadwax, the smooth area between the label and the last groove") and offer retry. - Low-confidence pressing picks are rendered with a faint dashed-border chip; tapping reveals the alternate candidates the model considered with their evidence. - The shelf-view drag-and-drop reorder uses a 350 ms snap on touch and falls back to instant on `prefers-reduced-motion`. - The audio-bench spectrogram is purely decorative; the actual analysis comes from the audio-verification call. Screen-reader users get the AudioVerification fields directly. - The "(i) Is this an original pressing?" button shows a confirmation modal first ("This runs a deeper check using more compute — about $0.06 per record. Continue?") because counterfeit deep-check costs are user-visible. ## 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 parse / pressing-resolution / grading / transliteration / audio verification / counterfeit deep-check / genre-arc; `gemini-3.5-flash` for value-estimate; `gemini-3.1-flash-tts-preview` for TTS; `gemini-3.1-flash-image` for illustration. Set `thinkingLevel` explicitly per call. - **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed collection on first launch. - **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it; magic-link email as fallback. - **Storage:** Firebase Storage for original sleeve and label photographs + audio clips. Pre-signed URLs only. - **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+. - Use `clamp()` for fluid typography. Prefer container queries over media queries for component-level responsiveness. - Use `dvh` / `svh` instead of `vh`. Respect safe-area insets on iOS. - Zero horizontal overflow at any width. Zero layout shift on load. - Persist user data in Firestore. Use real-time listeners on the collection view. - Optimistic UI on writes; reconcile on response. - Capture flow uses the Web Camera API with fixed focus/exposure where supported; falls back to native camera otherwise. - Audio capture uses the Web Audio API with the `MediaStreamTrack` constrained to mono 44.1 kHz; clips are uploaded to Firebase Storage and then submitted to Gemini via the Files API. - **iOS Safari gotchas (graceful degradation):** camera and mic permissions do NOT persist across page reloads on iOS — re-request on every capture; Safari `MediaRecorder` only supports `audio/mp4` (AAC) — feature-detect and persist as AAC mono; an incoming call interrupts the audio session (`MediaStreamTrack.onmute` fires) — auto-pause the listen-clip capture, save what was captured, and prompt resume; backgrounded Safari tabs pause `getUserMedia` — pair `visibilitychange` with a screen Wake Lock during a listen pass; rotation drops the camera track on iOS — re-bind on `orientationchange`; always offer `` as the final fallback when WebRTC is denied. ## 12. Accessibility (WCAG 2.2 AA) - Semantic HTML — `header`, `nav`, `main`, `section`, `article`, `footer`. - All interactive controls reachable by keyboard with a visible focus ring. - Color contrast ≥ 4.5:1 for body, 3:1 for large text and UI components. - All images have meaningful `alt` text. The original sleeve photographs have `alt` describing the artefact ("photograph of a 1971 LP sleeve front, the artist's name printed in red type on a green background, lower-right corner has ring wear approximately 12mm across"). - Form fields have associated `