================ 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.
---
# Pottery Glaze Vault
## 1. Project
**Pottery Glaze Vault** is a reproducible recipe ledger for ceramicists
who keep test tiles in a drawer. The user photographs each tile and
voices the recipe — base glaze percentages, oxide additions, application
method, kiln cycle, atmosphere, the cone it was fired to, the year —
and the app produces a structured archive that next firing can search.
Every tile keeps its photograph at full resolution, its recipe in exact
percentages, its kiln cycle in degrees and hours, and the metadata of
the firing: oxidation or reduction, salt or soda or wood or electric,
which kiln, which shelf, which atmosphere stage at which temperature.
When the user loads the next bisque batch, the app surfaces every
other tile in their drawer that was fired in the same cone with
overlapping oxides, so a result that worked once can be repeated.
This is the kind of app a fifth-generation Talavera potter in Puebla
keeps on her phone next to the wheel because her mother-in-law's
lead-bisilicate recipe lives in three notebooks and one of them got
wet, and she has sixty test tiles in a wooden drawer that each took
half a day to make and she is tired of guessing which one was the
3% copper-carbonate in oxidation versus the 3% in light reduction.
It is also the kind of app a Korean buncheong potter in Icheon keeps
because his teacher's celadon recipe has eleven variations across
forty years and the differences are 0.3% iron and three minutes at
1240°C, and he wants the next firing to land where the 2018 tile
did. Same shape of moment — a tile that worked, a recipe that
must be recoverable — different oxide system, different cone.
The single demo that proves the magic: photograph one test tile and
voice the recipe in under twenty seconds — "Tile 47, base is Leach
A, copper carb 2 point 5 percent, rutile 1 percent, brushed two
coats over bisque, cone 6 oxidation in the test kiln, fired
yesterday, January 14 2026" — and the app produces a structured
entry. The tile is in the vault with its photo, recipe in
percentages, kiln cycle in degrees and hours, year, atmosphere, cone.
Below it a panel appears: "Other tiles you fired in cone 6 with
copper and rutile" — and there are four. Tile 12 from 2024, copper
3% rutile 0.5%, same atmosphere; Tile 28 from 2025, copper 2%
rutile 1.5%; Tile 33 from 2025, copper carb 2.5% rutile 1% (the
near-match — the user can compare side by side). The next bisque
batch is loaded. The user picks Tile 47 again. The app prints the
recipe card for the studio, in grams for a 200-gram test batch.
And in the harder cases — atmospheric variation, long firing
schedules, mixed-clay-body tests, multi-glaze layering — the app
reads what the user voiced and keeps what the user said in their
own words. A buncheong tile sprayed with shino over copper-red
slip on stoneware fired in reduction with a four-stage cooling
hold gets every stage preserved. Cone 10 reduction with a 30-minute
soak at 1280°C and a slow cool through 950°C reads as exactly that
in the kiln_cycle, not flattened into "cone 10 reduction".
**Tagline:** _Turn a drawer of test tiles into a reproducible glaze
vault — in any cone, any atmosphere, any oxide system, with the
chemistry kept exactly as you mixed it._
## 2. Target audience
- Production potters with a drawer of test tiles whose recipes live in three notebooks and a phone-notes app
- Talavera potters and Mexican mayolica makers reproducing inherited lead-bisilicate and tin-opacified recipes, generation to generation
- Korean buncheong, celadon, and onggi makers tracking iron and ash percentages across years of kiln work
- Filipino studio ceramicists working with locally-sourced volcanic ash and Negros red clays, where the raw materials shift between batches
- University ceramics MFA students and TAs running 60-tile thesis test grids who will defend the results in May
- Production studios with two or three throwers and one glaze mixer who need a shared vault the wheel-room can search
- Hobby potters with a community-studio locker who want their recipes private but their kiln log shareable with the firing crew
- Salt, soda, wood, and anagama firers whose atmosphere is the variable and whose cone is one of several markers along the kiln
- Glaze chemists and Insight / glazy.org users who want a photographic record bolted onto a UMF analysis they ran elsewhere
- Conservation and historic-pottery reproducers working off museum catalogues and shard analyses
## 3. Core value propositions
Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app.
- **The chemistry is yours, exactly as you mixed it.** The app records every percentage to one decimal place, never rounds, never substitutes one oxide for another. If your recipe calls for copper carbonate, the app stores copper carbonate — not "copper compound". If you used 2.5% rutile, the app stores 2.5%, not "approximately 2-3% rutile". The percentages add to 100% (or to whatever they add to, including over-100 oxide additions stated as additions). The vault never silently corrects.
- **Reads any glaze recipe, any cone, any atmosphere.** Voice in a recipe in English, Spanish, Korean, Tagalog, Mandarin, Japanese, Portuguese, Italian, French, German, Vietnamese, Hindi — the parse keeps your terminology (Leach A, GA6, Albany slip, ash 木灰, ceniza, buncheong slip 분청, Tenmoku 天目). The percentages and the firing schedule are extracted as numbers; the names of the recipes and the materials are kept verbatim in your language.
- **The kiln cycle is half the recipe.** Ramp rates in degrees per hour, holds in minutes at temperature, atmosphere change points by cone, reduction start and end, body reduction versus glaze reduction, cooling holds at 950°C and 700°C — all preserved as structured stages, not flattened to "cone 6 oxidation".
- **Reproducibility check.** When you load your next bisque batch and pick a tile, the app reprints the recipe in grams for the batch size you specify, the kiln cycle as a printable card, and the firing notes — including the exact atmosphere change points and any cone-watcher observations you logged.
- **Cross-tile search by chemistry, not just name.** "Show me every tile I fired in cone 6 oxidation with copper between 2% and 4% and any titanium" returns the four tiles where it landed. The next firing builds on the last.
- **The photograph is the truth.** The tile photo at upload resolution sits beside the recipe forever. Colour, crawl, pinhole, settle, crystal — what the glaze did is what your eye saw, not a JPEG-compressed thumbnail.
- **Shared studio vault, private recipes.** Invite a studio mate; they can see the kiln logs and shared base glazes you mark public, and nothing else. The recipes you mark private stay yours.
- **Printable for the wheel-room.** Every recipe exports as a 4×6 index-card PDF for the studio wall, including the recipe in percentages, the recipe in grams at a chosen batch size, the kiln cycle, the date last fired, and a thumbnail of the tile.
- **Never substitutes oxides. Ever.** This is a hard rule. Even when the app suggests near-matches by chemistry, it never proposes "you could use chrome instead of copper". The user owns the chemistry; the app is a ledger, not a chemist.
## 4. Features to build
- Camera capture for a single test tile (mobile-first), with auto-crop to the tile edge and consistent colour-card support for users who keep a reference card in frame
- Bulk capture for a tray of tiles — drop the tray, camera segments individual tiles by edge, queues each for naming
- Voice recipe entry — speak the recipe in your own words; the parse extracts base glaze percentages, oxide additions, application method, kiln cycle, atmosphere, cone, year
- Manual recipe entry form — for users who type faster than they talk, with a percentages-must-sum-or-be-explicit validator
- Kiln cycle editor — drag-and-drop stages (ramp rate, hold time, atmosphere change point, reduction start/end, cooling hold), tied to temperature markers
- Application notes — brushed, sprayed, dipped, poured, layered (specify glaze A under glaze B), number of coats, thickness in mm if measured
- Atmosphere fields — electric, gas, propane, wood, salt, soda, anagama, raku, pit; oxidation, neutral, light reduction, heavy reduction; reduction start cone, body reduction yes/no
- Cone marker — cone 04, 06, 6, 8, 10, 13 (Orton standard, with the Seger / Bullers / Witt alternatives noted in the schema for European and historic users)
- Photo gallery per tile — multiple shots (front, side, glaze break, base) with the primary shot used in grid view
- Cross-tile chemistry search — "tiles fired in cone X to cone Y with oxide Z between A% and B% in atmosphere W"
- Side-by-side compare view — pick 2-4 tiles, see them at scale with their recipes aligned and the differences highlighted (without substituting anything)
- "Next firing" view — the user names tomorrow's bisque load and queues recipes to mix; the app prints a mix sheet in grams at the chosen batch size
- Studio sharing — invite a studio mate by email; per-recipe and per-base-glaze public/private toggles
- Index-card export — 4×6 printable card per recipe, glossy-paper friendly, with QR code linking back to the vault entry
- Base-glaze library — Leach A, Leach B, GA6, Reitz Yellow, Tenmoku base, Shino base, Buncheong slip, Talavera transparente — the user's own canonical bases sit at the top so adding a new tile only needs the additions
- Materials lot tracking — when a 25 kg bag of EPK runs out and a new bag comes in, the user logs the lot change; the next tile fired with the new lot inherits the lot stamp so future variation can be traced
- Cone-watcher / firing log — voice notes during a firing ("cone 6 down at 12:47, body reduction off") attach to the kiln cycle
- Glaze-defect tagging — pinhole, crawl, crackle, crystal, settle, flux burn, blistering — tagged on the photo with bounding boxes so a future search can find "tiles with crystal in cone 10 reduction"
- Bisque-batch tracker — every tile has the bisque cone (typically 04 or 06) recorded; the app warns if a tile's bisque doesn't match the studio's standard
- Export to Glazy.org / Insight format — the user can push a recipe to their existing UMF calculator workflow
## 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 the tile photograph, identifies the glaze surface qualities (matt, satin, gloss, dry, crystalline, crawled, pinholed, broken-edge variegation), and notes visible defects with positions on the tile. Does NOT identify the recipe from the photograph — colour is not a recipe; copper at 2% in oxidation looks different on porcelain versus stoneware. The model's job is to describe what the eye sees on the tile, not to guess chemistry.
- **Audio reasoning + structured output** (Gemini 3.5 Flash) — the user voices the recipe. The parse extracts the structured `GlazeRecipe` schema below: base name, oxide additions in percentages, application method, kiln cycle, atmosphere, cone, year. Percentages are extracted to one decimal place; if the user said "two and a half" the parse stores 2.5, not "2-3". If the user said "approximately three" the parse stores 3.0 with a `confidence: low` flag for that field, never silently bumps to 3.0 with full confidence.
- **Structured output / JSON Schema** — the response matches the `GlazeRecipe` schema. Every numeric field is typed. Percentages are floats, kiln temperatures are integers in Celsius (with a Fahrenheit display toggle in the UI), atmosphere is a closed enum, cone is a closed enum (Orton).
- **Multilingual transcription** (built into Gemini 3.5 Flash audio) — handles English, Spanish (Mexican / Castilian / Andean / Caribbean), Korean, Tagalog, Cebuano, Mandarin, Cantonese, Japanese, Vietnamese, Portuguese (Brazilian / European), Italian, French, German, Hindi, Tamil, Bengali, Arabic, Farsi, Turkish, Swahili, Amharic. Recipe and material names stay verbatim in the user's language; numbers and kiln cycle stages are extracted to the structured fields regardless of language.
- **Long context (1M tokens)** — the cross-tile chemistry search and "next firing" surface call reads the whole vault at once. A glaze recipe entry averages ~600 tokens; a 60-tile vault is ~36k tokens (comfortable); a 1,000-tile production studio vault is ~600k tokens (still comfortable). For studios past ~1,400 tiles, pre-filter by cone before the call to stay under 1M.
- **Thinking levels** — `medium` for the primary voice-recipe parse (numeric extraction with strict percentage discipline). `low` for tile photograph description, defect tagging, and cross-tile search. Surface `thoughtSummary` only when the user clicks the small "(i) show how the AI read this" icon next to a low-confidence field.
### 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 studio invitations) also requires the sender domain to be authorised in Firebase Auth. Vaults are private to the owner; studio mates have explicit invitation-based access.
- **Database — Required.** Firestore for `users`, `vaults`, `tiles`, `base_glazes`, `kiln_cycles`, `firings`, `materials_lots`, `vault_members`.
- **File storage — Required.** Firebase Storage for tile photographs (at upload resolution, preserved). **Storage is NOT auto-provisioned by AI Studio Build today** — enable it in the Firebase console and wire the bucket name into the AIS Build project before the first tile upload. Pre-signed URLs only; tile photographs are never publicly addressable.
- **Email — Required (transactional).** Studio mate invitations via magic-link email (Firebase Auth). Index-card export confirmations.
- **Payments — Not needed for v1.** Free for individual potters. A future "studio pro" tier could add multi-vault management or paid glaze-library integrations (Glazy.org pro export, etc.) but is not part of v1.
- **External APIs:** Gemini API for all intelligence. Optional: Glazy.org export (no auth needed — they accept JSON drop-in); optional Orton Ceramic cone-temperature reference (we ship the table as a static JSON file, so the optional API is only for live updates).
**Environment variables:** every secret (Gemini API key, Firebase service-account JSON, optional Stripe key for studio-pro tier) 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 vault' inside the UI · explicit opt-in for any analytics · the user's recipes 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) · base-glaze libraries marked public are visible to invited studio mates only — never to the wider internet.
**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. **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 voice recipe → `GlazeRecipe` schema | `gemini-3.5-flash` | medium | (none) |
| Describe tile photograph (surface, defects, position) | `gemini-3.5-flash` | low | (none) |
| Cross-tile chemistry search (full vault, long-context) | `gemini-3.5-flash` | low | (none) — long-context over the vault |
| "Next firing" recommendation surface (long-context) | `gemini-3.5-flash` | medium | (none) — long-context over the vault |
| Generate index-card recipe summary line (one sentence) | `gemini-3.5-flash` | low | (none) |
| TTS read-back of recipe for the studio (optional) | `gemini-3.1-flash-tts-preview` | n/a | n/a |
| Generate empty-state hero image | `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 Atmosphere = z.enum([
"electric_oxidation",
"gas_oxidation",
"gas_neutral",
"gas_light_reduction",
"gas_heavy_reduction",
"wood_fired",
"wood_with_salt",
"wood_with_soda",
"salt_fired",
"soda_fired",
"anagama",
"raku_oxidation",
"raku_reduction",
"pit_fired",
"other",
]);
const Cone = z.enum([
"022", "020", "018", "016", "014", "012", "010",
"08", "06", "04", "02",
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13",
]);
const ApplicationMethod = z.enum([
"brushed",
"sprayed",
"dipped",
"poured",
"trailed",
"wax_resist_then_glazed",
"layered",
"other",
]);
const ClayBody = z.object({
name_verbatim: z.string(), // "B-Mix 5", "porcelain Limoges", "Negros red"
type: z.enum(["earthenware", "stoneware", "porcelain", "raku", "paper_clay", "other"]),
bisque_cone: Cone.nullable(),
notes: z.string().nullable(),
});
const OxideAddition = z.object({
material_name_verbatim: z.string(), // "copper carbonate", "rutile", "red iron oxide", "cobalt carb"
// Common materials: iron oxides (red, yellow, black, spangle), copper (carb, oxide), cobalt (carb, oxide),
// manganese (carb, dioxide), chrome oxide, nickel oxide, rutile (light/dark), titanium dioxide,
// zinc oxide, tin oxide, zirconium, vanadium, bone ash, bentonite, ilmenite, ochre.
percent: z.number().min(0).max(50), // doc only; clamp server-side
percent_is_addition_over_100: z.boolean(), // true for the standard "add 2% copper to base"; false if it's part of the 100%
confidence: z.enum(["high", "medium", "low"]), // low if the user hedged ("approximately three")
note: z.string().nullable(), // "user said 'a pinch' — interpreted as 0.5%; please confirm"
});
const BaseGlazeComponent = z.object({
material_name_verbatim: z.string(), // "potash feldspar (Custer)", "EPK", "silica", "whiting", "wollastonite", "talc"
percent: z.number().min(0).max(100), // sums to 100 across base components
material_lot_id: z.string().nullable(), // links to materials_lots if the user is tracking lots
});
const KilnStage = z.object({
stage_index: z.number().int().min(0), // 0 = start, then sequential
description_verbatim: z.string(), // what the user said
starts_at_temp_c: z.number().int().nullable(), // 20 if start, else previous stage's end
ends_at_temp_c: z.number().int().nullable(),
ramp_c_per_hour: z.number().nullable(), // null if it's a hold
hold_minutes_at_end: z.number().int().nullable(), // for soaks and holds
atmosphere_change: z.enum([
"none",
"reduction_start",
"reduction_end",
"body_reduction_start",
"body_reduction_end",
"salt_introduction",
"soda_introduction",
"wood_stoke_burst",
"damper_change",
"other",
]),
atmosphere_change_at_cone: z.string().nullable(), // "cone 012 down", "cone 6 going down"
notes: z.string().nullable(),
});
const KilnCycle = z.object({
kiln_name: z.string(), // "test kiln", "Skutt 1027", "studio gas kiln", "Bourry box wood kiln"
kiln_type: z.enum(["electric", "gas", "propane", "wood", "anagama", "raku", "pit", "other"]),
atmosphere: Atmosphere,
target_cone: Cone,
cone_witnessed_down: Cone.nullable(), // what the cone actually did
stages: z.array(KilnStage),
total_firing_hours: z.number().nullable(),
total_cooling_hours: z.number().nullable(),
cooling_holds: z.array(z.object({ // explicit re-firing-style cooling holds for crystalline / matt work
at_temp_c: z.number().int(),
duration_minutes: z.number().int(),
})),
});
const SurfaceObservation = z.object({
quality: z.enum([
"gloss", "satin", "matt", "dry", "crawled",
"pinholed", "crystalline", "broken_edge", "fluxed_burn",
"crackle", "settled", "blistered", "underfired", "overfired",
"shino_carbon_trap", "wood_ash_drip", "salt_orange_peel",
]),
position_on_tile: z.enum([
"upper-left", "upper-right", "lower-left", "lower-right",
"centre", "edge-top", "edge-bottom", "edge-left", "edge-right",
"back-of-tile", "break-edge",
]),
notes: z.string().nullable(),
});
const Defect = z.object({
type: z.enum([
"pinhole", "crawl", "settle", "blister",
"crackle_unwanted", "shivering", "scumming",
"underfired_dry", "overfired_runs",
"kiln_grit_contamination", "other",
]),
position_on_tile: z.string(), // "lower-right", "across-middle"
severity: z.enum(["light", "moderate", "heavy"]),
notes: z.string().nullable(),
});
const GlazeRecipe = z.object({
tile_id: z.string(),
tile_image_uris: z.array(z.string()), // primary first; multiple shots ok
recipe_name_verbatim: z.string(), // "Tile 47", "Buncheong slip variant 3", "Talavera transparente con 3% cobre"
recipe_name_canonical: z.string().nullable(), // user can canonicalise across variants
clay_body: ClayBody,
base_glaze_name_verbatim: z.string().nullable(), // "Leach A", "GA6", "Reitz Yellow", null if all-from-scratch
base_glaze_components: z.array(BaseGlazeComponent),
oxide_additions: z.array(OxideAddition),
application: z.object({
method: ApplicationMethod,
coats: z.number().int().min(1).max(10).nullable(),
thickness_mm: z.number().nullable(),
notes_verbatim: z.string().nullable(), // "brushed two coats, second coat thinned with 10% water"
layered_under: z.string().nullable(), // name of glaze underneath if layered
layered_over: z.string().nullable(),
}),
kiln_cycle: KilnCycle,
date_fired_iso: z.string().nullable(), // "2026-01-14"
date_fired_verbatim: z.string().nullable(), // "yesterday", "last Tuesday" — preserved alongside ISO
year_fired: z.number().int().nullable(),
source_language: z.string(), // BCP-47, "es-MX", "ko-KR", "tl-PH"
voice_input_transcript: z.string().nullable(), // verbatim if voice-entered
voice_input_uri: z.string().nullable(), // original audio kept
surface_observations: z.array(SurfaceObservation),
defects: z.array(Defect),
visual_summary_for_card: z.string(), // one sentence, neutral, for the index card
reading_confidence: z.number().min(0).max(1),
flagged_for_user_review: z.array(z.object({
field_path: z.string(), // "oxide_additions[0].percent"
reason: z.string(),
})),
});
type GlazeRecipe = z.infer;
```
### Common failure modes (and how to avoid them)
- Agent silently downgrades `thinkingLevel` on the the voice-recipe parse call to save quota — pin `gemini-3.5-flash` with the matrix-specified `thinkingLevel` explicitly. Flash will round "two and a half percent" to "two to three percent" which destroys the recipe. Numeric precision is the entire point of this app.
- Model substitutes "copper carbonate" for "copper compound" or "copper" — never. The material name is stored verbatim. Copper carbonate and copper oxide give different results at the same percentage. Treat material names as proper nouns.
- "Approximately three" silently bumped to 3.0 with full confidence — pin in the system instruction: hedged numbers store the value AND set `confidence: low` with a `note` quoting the user's hedge.
- Percentages renormalised to 100% — never. The user knows what they mixed. Some glaze conventions report base components summing to 100% with additions stated as "+2% copper" (over-100); some report a single recipe summing to 100% inclusive. The schema captures both via `percent_is_addition_over_100`. Do not silently convert one to the other.
- "Cone 6" parsed as "cone 06" or vice versa — these are different temperatures by ~700°C. Cone 6 is mid-fire stoneware (~1222°C). Cone 06 is low-fire (~999°C). Pin the disambiguation in the system instruction: if the user says "cone six" assume cone 6; if "cone oh six" or "cone zero six", cone 06. If unclear, flag for review.
- Fahrenheit vs Celsius confused — the schema stores Celsius. If the user says "fired to 2300 degrees" parse as Fahrenheit (~1260°C) and convert; if "fired to 1240 degrees" parse as Celsius. Flag any conversion in `flagged_for_user_review`.
- Atmosphere flattened — "cone 10 reduction" becomes `gas_heavy_reduction` with all stages collapsed. Hard rule: every named atmosphere change point in the voice transcript becomes a `KilnStage` with the right `atmosphere_change` enum value.
- Kiln cycle smoothed — "I ramped 100 degrees an hour to a thousand, then held thirty minutes, then went to 1240" must produce three stages, not two.
- Recipe name translated — "buncheong slip" comes back "white slip", "Talavera transparente" comes back "Mexican clear glaze". Hard rule: recipe and material names stay verbatim.
- Tile photo confused with recipe — the photograph describes what the eye sees on the tile (gloss, crawl, breaking edge); it does NOT extract a recipe from the photo. Copper at 2% and copper at 3% can look identical at thumbnail size; chemistry is never inferred from the photo.
- The app suggests "you could substitute X for Y" — never. The cross-tile search surfaces tiles with similar chemistry the user already made; it never proposes new chemistry.
### Negative constraints (hard rules)
- Do NOT substitute oxides. If the user mixed copper carbonate, the app stores copper carbonate. The app never suggests "you could use chrome oxide instead" or "this is similar to a copper recipe". The user owns the chemistry.
- Do NOT round percentages. 2.5% is 2.5%, not "2 to 3%". 0.3% iron is 0.3%, not "trace iron". Store to one decimal place exactly.
- Do NOT renormalise. The user's recipe sums to whatever it sums to. Base components conventionally sum to 100; oxide additions are conventionally stated as additions over 100. The schema captures both via `percent_is_addition_over_100`; the app never silently converts.
- Do NOT translate recipe names or material names. "Leach A" stays "Leach A". "Buncheong slip" stays "buncheong slip". "Ceniza de plátano" stays "ceniza de plátano". "Tenmoku" stays "Tenmoku". Material common names: "copper carbonate", "iron oxide", "rutile" stay in the language the user spoke them.
- Do NOT confuse cone 6 with cone 06. These differ by ~700°F. If unclear from voice, flag for review with a quote of what the user said.
- Do NOT collapse the kiln cycle. Every named ramp, hold, atmosphere-change point, and cooling hold the user spoke becomes its own stage.
- Do NOT extrapolate from the tile photograph to chemistry. The photo describes surface; the chemistry comes only from the voice or typed recipe.
- Do NOT use the user's recipes 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. The capabilities-info panel says this in plain English.
- Do NOT auto-publish or auto-share. Vaults are private by default. Sharing is explicit, per-vault, per-studio-mate, per-recipe.
- Do NOT propose new recipes. The app is a ledger. It will surface tiles already in the vault that match a chemistry filter; it will never compose a recipe the user has not already mixed.
- Do NOT claim a recipe is "food safe" or "leaching safe" or "dinnerware safe". Surface the chemistry as stored; food-safety determination is on the potter and a leach test.
### 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 voice recipe → `GlazeRecipe` schema
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none)
```
You parse a ceramicist's voiced glaze recipe into the GlazeRecipe
JSON schema. The user may speak in English, Spanish (Mexican,
Castilian, Andean, Caribbean), Korean, Tagalog, Cebuano, Mandarin,
Cantonese, Japanese, Vietnamese, Portuguese, Italian, French,
German, Hindi, Tamil, Bengali, Arabic, Farsi, Turkish, Swahili,
or Amharic. Recipe names and material names stay verbatim in the
language the user spoke. Numeric percentages, kiln temperatures,
hold times, and ramp rates are extracted as numbers.
The input is an audio recording with optional companion images
(the tile photograph, a written recipe slip). Audio is submitted
via Gemini Files API (`files/*` resource name) or as `inlineData` (base64).
Do NOT pass Firebase Storage public URLs directly to
`generateContent` — the API does not fetch them server-side.
Output ONLY the GlazeRecipe JSON matching the provided schema.
Hard rules:
NUMERIC PRECISION
- Percentages are stored to one decimal place exactly. "Two and a
half percent" → 2.5. "Half a percent" → 0.5. "A pinch" → store
0.5 with confidence "low" and a note quoting the user.
- Hedged numbers ("about three", "approximately two", "maybe
one") store the value AND set confidence "low" AND record a
note with the verbatim hedge.
- Percentages are NEVER renormalised to 100. The recipe sums to
whatever it sums to. Base components conventionally sum to 100;
oxide additions are conventionally additions over 100. The
schema captures both via `percent_is_addition_over_100`. Set
this boolean explicitly per oxide addition. If the user did not
make it clear and the percentage is small (under 10%), default
to `true` (additions over 100) and flag for review.
- Do NOT round. If the user said 2.5 store 2.5; do not store
"2 to 3".
CONE DISAMBIGUATION
- "Cone six" → cone 6 (mid-fire stoneware, ~1222°C). "Cone oh
six" or "cone zero six" → cone 06 (low-fire, ~999°C). If the
user said "cone six" but the firing temperature implies low-fire,
flag for review.
- Cone witnessed down may differ from target. Record both if the
user mentioned both.
TEMPERATURE UNITS
- Schema stores Celsius. If the user said "2300 degrees" assume
Fahrenheit (≈1260°C) and convert. If "1240 degrees" assume
Celsius. If "fired to 700" assume Celsius (low-fire bisque).
Flag any conversion in flagged_for_user_review with the
original spoken value.
KILN CYCLE FIDELITY
- Every named ramp rate, hold time, atmosphere-change point, and
cooling hold becomes its own KilnStage with sequential
stage_index.
- "Ramped a hundred an hour to a thousand, held thirty minutes,
then full speed to twelve forty, body reduction at cone 012,
reduction off at cone 6 going down, slow cool through nine
fifty" → seven stages with the right atmosphere_change values.
- Do not collapse stages to save tokens.
NAMES AND MATERIALS — VERBATIM
- Recipe names stay verbatim: "Leach A", "GA6", "Tenmoku base",
"Reitz Yellow", "Buncheong slip", "Talavera transparente",
"Ceniza de plátano", "shino carbon trap", "Hồ Đào" — exactly as
spoken, in the language of speech, including diacritics.
- Material names stay verbatim: "copper carbonate" stays "copper
carbonate", not "copper compound". "Rutile (dark)" stays
"rutile (dark)". "Albany slip" stays "Albany slip". If the user
spoke a Korean material name "도석" (do-seok / pottery stone),
store verbatim with a parenthetical romanisation only if the
user did not specify one.
- Do NOT substitute or normalise material names. Copper carbonate
(CuCO₃·Cu(OH)₂) and copper oxide (CuO) are different materials
with different active percentages. Store what the user said.
CLAY BODY AND BISQUE
- Clay body name verbatim ("B-Mix 5", "porcelain Limoges", "Negros
red", "Icheon stoneware").
- Bisque cone matters: 04 and 06 are the common bisque cones.
Record if the user mentioned it.
APPLICATION
- Method, coats, thickness, layering. "Brushed two coats" → method
brushed, coats 2. "Sprayed one coat then dipped in shino" →
layered, layered_over shino, primary method sprayed.
DATE
- "Yesterday" — store the verbatim string in date_fired_verbatim
AND the ISO date in date_fired_iso (the server provides "today"
via system context).
- "Last Tuesday" — same pattern.
- "January 2024" without a day — store ISO as 2024-01-01 with a
flag for review.
- Year alone (the user just wants the year on the entry) — store
in year_fired.
CONFIDENCE AND REVIEW
- reading_confidence is the overall confidence across all fields.
- flagged_for_user_review lists any single field where confidence
is low, with a quote of the user's words.
DO NOT
- Do NOT propose substitutions ("you could use chrome instead").
- Do NOT round percentages.
- Do NOT renormalise to 100%.
- Do NOT translate recipe or material names.
- Do NOT collapse the kiln cycle.
- Do NOT mark a recipe "food safe" or "leaching safe" — the
schema does not have such a field for a reason.
- Do NOT infer chemistry from the photograph if one is provided —
the photograph is only for surface_observations and defects.
No commentary. JSON only.
```
---
### Call: Describe tile photograph (surface, defects, position)
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You describe a glaze test tile photograph. Your output is a
SurfaceObservation array and a Defect array, scoped to what is
visibly on the tile — the surface qualities, the breaking edges,
the defect positions.
The photograph is the tile front. It may include a colour
reference card in frame, the tile may be on a kiln shelf or a
white background, lighting may be warm desk-lamp or daylight. The
glaze surface is the subject.
Hard rules:
- Describe ONLY what is on the tile. Do not infer chemistry. Do
not infer recipe. Do not infer percentages. Do not name oxides.
Copper at 2% and copper at 3% can look identical at thumbnail
size; chemistry is the recipe's job, not the photo's.
- surface_observations uses the closed enum: gloss, satin, matt,
dry, crawled, pinholed, crystalline, broken_edge, fluxed_burn,
crackle, settled, blistered, underfired, overfired,
shino_carbon_trap, wood_ash_drip, salt_orange_peel.
- Each observation has a position_on_tile from the closed enum.
- defects use the closed enum: pinhole, crawl, settle, blister,
crackle_unwanted, shivering, scumming, underfired_dry,
overfired_runs, kiln_grit_contamination, other.
- Severity is light, moderate, or heavy.
- visual_summary_for_card is one neutral sentence for the index
card, e.g. "satin surface with a soft crystalline break at the
lower-right edge; a single pinhole upper-centre". No
adjectives like "beautiful", "lovely", "interesting" — neutral
description only. No chemistry.
- If the tile shows multiple surface qualities (a glossy run at
the bottom edge over a satin upper half), record each
separately.
- If the photo is unclear (out of focus, low light), set
reading_confidence below 0.8 and flag the specific fields.
Do NOT:
- Do NOT name the glaze, the oxide, or the recipe.
- Do NOT comment on the maker's skill or aesthetic.
- Do NOT use art-history vocabulary ("painterly", "lyrical",
"expressive"). Plain ceramicist's vocabulary only.
- Do NOT use "AI" language ("this appears to be", "likely a").
Describe what is on the tile.
No commentary. JSON only.
```
---
### Call: Cross-tile chemistry search (full vault, long-context)
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none, long context)
```
You receive the full GlazeRecipe vault (every tile the user has
logged) and a search query. The query may be natural language
("show me my cone 6 oxidation tiles with copper and rutile") or
structured (cone range, atmosphere set, oxide present, oxide
percentage range).
Your task: return a ranked list of tile_id values that match the
query, with a one-sentence match reason quoting the relevant
fields from each tile.
Hard rules:
- Surface ONLY tiles that exist in the input. Do NOT propose
hypothetical recipes.
- For percentage ranges, the match is inclusive at both ends.
- For "with copper" the match is on material_name_verbatim
containing "copper" (covers copper carbonate, copper oxide,
copper sulphate, copper carb, copper sulfide). Likewise iron,
rutile, titanium, cobalt, manganese, chrome, nickel.
- Atmosphere matches the closed enum directly OR a broader bucket
the user specified ("any reduction" → gas_light_reduction,
gas_heavy_reduction, wood_fired, anagama, raku_reduction).
- Cone range is inclusive. Cone 6 to 10 includes 6, 7, 8, 9, 10.
- Rank by closeness of match: exact percentage match first, then
percentage within 0.5% of query, then within 1%, etc.
- Do NOT substitute oxides. "With copper" never returns "with
chrome" with a "you could try" note.
- Match reason is one sentence per tile, quoting the relevant
fields ("Tile 28 — 2.0% copper carbonate, 1.5% rutile, cone 6
gas oxidation, July 2025").
Output: a JSON object with `matches: [{ tile_id, match_reason,
match_score }]`, sorted by score descending. No commentary.
```
---
### Call: "Next firing" recommendation surface (long-context)
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none, long-context)
```
You receive (a) the full GlazeRecipe vault and (b) a draft kiln
load the user is planning — a list of recipe names they intend
to fire tomorrow, plus the kiln, atmosphere, and target cone.
Your task: for each recipe in the draft load, surface every other
tile in the vault that was fired in matching cone and atmosphere
with overlapping oxides, so the user can confirm the next firing
extends what they already know.
Hard rules:
- Match on cone (exact) and atmosphere bucket (electric, gas
oxidation, gas reduction, wood, salt/soda, raku, etc.).
- Match on at least one shared oxide between the planned recipe
and the candidate tile.
- For each candidate, include: tile_id, recipe_name_verbatim, the
shared oxides with their percentages from the candidate, the
candidate's date, and the candidate's
visual_summary_for_card.
- Do NOT propose modifications to the planned recipe.
- Do NOT suggest oxide substitutions.
- Do NOT predict what tomorrow's firing will produce. The vault
surfaces history; the user makes the call.
- If no tiles match for a given recipe (a first-of-its-kind
experiment), say so explicitly: "no prior tiles in cone X
atmosphere Y with any of [oxides]; this is a new direction".
Output: a JSON object keyed by the planned recipe name, with the
matching candidate tiles as arrays. No commentary.
```
---
### Call: Generate index-card recipe summary line (one sentence)
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You write a one-sentence summary line for a printed 4×6 index
card. The input is one GlazeRecipe JSON. The output is a single
sentence the potter can read at a glance to recognise the tile.
Examples of good summary lines:
- "Leach A base, +2.5% copper carbonate, +1% rutile, brushed two
coats on B-Mix, cone 6 oxidation, January 2026."
- "Buncheong slip variant, sprayed on Icheon stoneware, cone 10
light reduction, March 2025; satin with carbon trap upper-left."
- "Talavera transparente con 3% cobre, esmalte de bisilicato de
plomo, cono 04 oxidación eléctrica, octubre 2025."
Hard rules:
- Preserve the recipe name verbatim, in the language of the
original entry.
- Include the base glaze name, the oxide additions (each material
name verbatim, each percentage to one decimal place), the
application method, the clay body if relevant, the cone, the
atmosphere, and the month/year fired.
- Do NOT translate recipe or material names.
- Do NOT propose substitutions.
- Do NOT comment on the result ("beautiful", "successful").
- Single sentence. No bullet list.
Output: a single string. No commentary.
```
---
### Call: TTS read-back of recipe for the studio (optional)
Model: `gemini-3.1-flash-tts-preview` · n/a · n/a
```
Voice: warm, steady, studio-floor pace. Pick the Gemini 2.5
Flash TTS voice whose `languageCode` matches the recipe's
source_language — pronunciation follows that locale.
Pronunciation of material names (potash feldspar, copper
carbonate, EPK, Custer, Tenmoku, buncheong) comes from the
chosen voice's native locale. Prefer a voice the user marked
as their studio default; fall back to whichever native-locale
voice is published.
Pre-process the text before sending it to TTS:
- Read the recipe in the order: recipe name, clay body, base
glaze name, then each base component as "[material name],
[percent] percent", then each oxide addition the same way,
then the application method and coats, then the kiln cycle
stages one by one, then the date.
- Insert a single ellipsis (`…`) between sections so the TTS
produces natural pauses. Gemini 2.5 TTS does not support
SSML ``.
- For numbers with decimals, say "two point five percent"
rather than "2.5 percent" — TTS reads "2.5" reliably in most
locales but the dot-disambiguation cue is safer.
- Skip the tile_id and any internal identifiers (the potter
doesn't need to hear "tile dash four seven").
- Target rate: ~110 words per minute — studio-floor reading
pace, not podcast pace.
Style direction: prepend ONE short directive sentence to the
text input, exactly like: "Read steadily and clearly, as a
recipe spoken aloud to a studio mate mixing the batch. …".
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 (Korean ㄲ, Spanish rolled rr, Tagalog ng,
Vietnamese tonal marks) are NOT exposed by Gemini 2.5 TTS —
no SSML `` tag. Pronunciation comes from the chosen
voice's native locale. Mid-call voice switching is not
supported; render the whole recipe in one voice.
```
---
### Call: Generate empty-state hero image
Model: `gemini-3.1-flash-image` · n/a · n/a
```
Generate a photograph-style image for the welcome / empty-state
hero. Subject: a hand placing one glaze test tile into a shallow
wooden drawer that already holds twelve tiles in a 4x3 grid.
Each tile is a 5cm × 5cm slab of unglazed white stoneware on
the back; the front shows a single glaze test in a range of
colours (sage celadon, milky shino, copper-red, dry satin
matt, iron tenmoku, buncheong slip). Lighting: warm afternoon
window light from the left, soft shadow under the drawer. The
hand is the potter's, clay-marked nails. The drawer is on a
slightly worn pine workbench. No people in frame above the
forearm. No text overlays. Photographic, not rendered.
Style: documentary, warm, asymmetric. Avoid the glossy 'AI
render' look. Slight imperfection — one tile a little crooked,
a fingerprint on the drawer rim. Real worn paper label on the
drawer's edge.
Negative prompt: no glowing UI overlays, no holographic
projections, no robot hands, no perfect symmetry, no pure
studio-lighting white-cyc background, no emoji.
```
## 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 Talavera workshop.** A Puebla potter is reproducing her mother-in-law's transparent-lead-bisilicate glaze with three copper-carbonate variants (2%, 2.5%, 3%) and a tin-opacified white. She voices each tile in Spanish while photographing the drawer. The vault stores "Talavera transparente con 2.5% cobre" verbatim — never translated, never substituted.
- **The buncheong test grid.** A Korean potter in Icheon is dialling in a buncheong slip on stoneware over six tiles, sprayed at different thicknesses, in cone 10 light reduction. She voices the test in Korean. The thicknesses (in mm) and the spray-distance notes are extracted; the recipe name "분청 슬립 변형 3 (buncheong slip variant 3)" is preserved verbatim.
- **The Filipino volcanic-ash glaze.** A studio ceramicist in Antipolo is testing a glaze using locally-collected volcanic ash from Mount Pinatubo washings. The ash percentage shifts batch-to-batch; she logs material lots so future variation is traceable. She voices entries in Tagalog with English material terms.
- **The MFA thesis grid.** A graduate student has a 60-tile thesis grid testing iron percentages from 0.5% to 12% in 0.5% increments across cone 6 oxidation, cone 6 reduction, and cone 10 reduction. The cross-tile search surfaces the inflection point where saturated iron breaks to crystalline.
- **The wood-firing crew.** Three potters share a Bourry-box wood kiln. Each tile records which side of the kiln (front-stoke, middle, back-stack), the position on the shelf (top, side, bottom), and the duration each was visible to flame. The kiln cycle has eight stages including stoke bursts.
- **The lot-change mystery.** A potter's reliable Leach A recipe shifts in colour after a new bag of Custer feldspar arrives. The vault's lot tracking surfaces: the new lot's tiles read 0.3 cone hotter than the old lot's. The user logs the lot change; the next mix marks the new lot.
- **The studio-floor handoff.** A production potter is mixing six glazes for tomorrow's firing while her studio mate is throwing in the next room. The mate calls "what's the copper carb percentage on tile 41 again?" — the potter taps tile 41, hits TTS read-back, the studio mate hears the recipe in her own voice without leaving the wheel.
- **The cone 6 to cone 10 conversion test.** A potter is moving from a community electric studio (cone 6 oxidation) to her own gas studio (cone 10 reduction). She wants to know which of her cone-6 oxide systems she has cone-10-reduction data for. The cross-tile search by oxide and atmosphere bucket gives her a starting list.
- **The food-safety leach test.** A potter sends three of her cone 10 glazes to a lab for leach testing. The vault stores the lab report PDF as an attachment to each tile but does NOT mark anything "food safe". The leach-result field is a structured note ("Pb < 0.1 ppm; Cd < 0.01 ppm; lab report attached"), surfaced clearly, never simplified into a green check mark.
- **The historic shino recreation.** A potter is working from a museum catalogue's published Mino-shino recipe with a four-stage cooling hold (a slow cool through 950°C, 850°C, 700°C, 500°C). Each cooling hold is its own kiln stage. The cone-witnessed-down field is recorded separately from the target cone because the heavy reduction caused the cone to drop one cone early.
## 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 placing one test tile into a wooden drawer already holding twelve tiles. One paragraph: "Pottery Glaze Vault turns a drawer of test tiles into a reproducible recipe ledger — every recipe in your own words, every percentage exactly as you mixed it." Single Google sign-in button; Apple sign-in next to it. Below: "Try with the sample vault" → loads the demo vault in section 8a.
2. **Empty state — "Start your vault".** Three input methods: 📷 Photograph a tile · 🎙 Voice a recipe · 📝 Type a recipe. A short explainer below each ("Best when the tile is in your hand", "Best when your hands are wet at the wheel", "Best for typing in a recipe from a slip").
3. **Tile capture flow** (mobile-first). Live viewfinder with a tile-shaped crop guide. Tile alone or in a tray. Auto-detects the tile edge; user confirms the crop. Then prompts: "Now voice the recipe — base, additions, application, kiln, cone, year — in any order, in any language." A 30-second recording with a visible level meter. After recording, the parse runs; the user lands on the tile-detail view with every field populated and any low-confidence fields highlighted for review.
4. **Tile detail view.** A three-column layout on desktop, stacked on mobile. Left column: the tile photograph at full resolution (zoomable), with surface observations and defects pinned to their positions on the tile. Middle column: the recipe in structured form — base glaze with components and percentages, oxide additions with material names verbatim and percentages, application method, clay body, date. Right column: the kiln cycle as a vertical timeline of stages with temperature on the y-axis (Celsius default, Fahrenheit toggle in settings) and atmosphere-change points marked. A sticky top bar: recipe name → date → cone + atmosphere chip → "(i) show how the AI read this" → "Print index card" → "Read recipe aloud" (TTS).
5. **Vault grid view.** The drawer view — a grid of tile photographs at 5cm equivalents (responsive). Filter by cone, atmosphere, clay body, year, oxide present, oxide percentage range. Sort: chronological by date fired, by cone, by base glaze. Default sort: chronological newest first.
6. **Chemistry search view.** A dedicated search modal where the user picks oxide chips (copper, iron, rutile, cobalt, manganese, chrome, nickel, tin, zinc, zirconium, titanium, vanadium, bone ash) and percentage ranges, plus cone range and atmosphere bucket. Results list ranks by closeness. Each result links to its tile detail view.
7. **Next firing view.** "Tomorrow's kiln load." The user names the firing (kiln, atmosphere, target cone) and drags in recipes from the vault. For each, the app surfaces "other tiles you fired in matching conditions with overlapping oxides" — so the user sees prior context before committing. Hit "Generate mix sheet" → a printable PDF with each recipe in grams at the chosen batch size, plus the kiln cycle.
8. **Base-glaze library view.** The user's canonical bases (Leach A, GA6, Reitz Yellow, Tenmoku base, Shino base, Buncheong slip, Talavera transparente, plus the user's own custom bases). Each base lists its component percentages and the tiles in the vault that use it. Adding a new tile starts by picking a base, then only the additions need to be voiced.
9. **Kiln library view.** The user's kilns (test kiln, studio gas, community electric, wood kiln). Each kiln has a default kiln-cycle template the user can apply when entering a new tile. Editing the template propagates only to new entries, never retroactively to historic tiles.
10. **Materials lot tracking.** A view listing material lots (e.g. "Custer feldspar — lot 2024-A", "Custer feldspar — lot 2025-B"). Each tile inherits the lot of the material the day it was mixed. If a glaze defect or surprise shows up, the user can search across the vault by lot.
11. **Studio sharing & members.** Modal: "Invite a studio mate". Per-vault role: viewer, editor. Per-recipe public/private toggle. Per-base-glaze public/private toggle. Per-kiln-cycle template public/private toggle. Magic-link email invites.
12. **Settings.** Units (Celsius / Fahrenheit, percentage display style — additions over 100 vs inclusive 100), default kiln, default clay body, default cone, default atmosphere, default voice for TTS read-back. Export full vault as JSON. Delete vault forever (60-second hold-to-confirm).
13. **Footer.** "Made for the drawer of tiles." Privacy: "Your recipes are yours. We never train on them." Capabilities `(i)` icon in header.
## 6b. First-visit onboarding
Show a **first-visit onboarding** the first time a visitor lands on the app (detect via `localStorage` flag; do not show on return visits). Three slides, dismissible at any time. Persistent re-entry: a `?` icon in the header reopens it.
**Slide 1 — What this is.**
- Headline: "Welcome to Pottery Glaze Vault."
- Subhead: "Turn a drawer of test tiles into a reproducible recipe ledger — in any cone, any atmosphere, any oxide system, with the chemistry kept exactly as you mixed it."
- One paragraph (≤ 60 words) explaining who this is for and what makes it different from a generic recipe app: the chemistry stays exactly as the potter mixed it, percentages never round, recipe names never translate, and the app never substitutes one oxide for another.
- Visual: a small annotated illustration of a single test tile with the relevant fields labelled (recipe name, base glaze, oxide additions in percentages, kiln cycle, cone, atmosphere) — not a generic pot icon.
**Slide 2 — Try it now.**
- One short prompt: "Try with the sample vault".
- A live demo input pre-loaded with five tiles from the seed content in section 8a (the Talavera transparente, the buncheong slip, the volcanic-ash celadon, the cone 6 copper-rutile, the cone 10 reduction Tenmoku).
- 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the verbatim recipe-name preservation, the cone-disambiguation, the kiln cycle parsed into stages, the chemistry search across the vault).
**Slide 3 — How to remix this.**
- Headline: "Make this yours."
- Three short bullets:
- "Swap the sample vault in `/data/seed-vault/` for your own tiles."
- "Adjust the prompts in `/server/prompts/` to fit your studio's language(s) and material naming conventions."
- "Wire up your Gemini API key and Firebase project via the env-var list in the capabilities panel."
- Primary CTA: "Use this template" → links to AI Studio Build remix entry point.
- Secondary: "Just exploring — close" (sets localStorage flag, never auto-shows again).
**Accessibility:** focus trap, `Esc` closes, `role="dialog"`, `aria-modal="true"`, `aria-labelledby`, focus restored to trigger on close. Respect `prefers-reduced-motion`.
**Don't:**
- Don't gate content behind the modal. The page beneath must be fully usable.
- Don't auto-reshow on return visits. Use `localStorage['onboarding-seen-v1']`.
- Don't include unrelated CTAs (newsletter signup, social follow). Keep it about the template only.
## 6c. Capabilities info button (persistent in header)
Add a persistent `(i)` icon in the top-right of the header (next to the primary nav). Click → opens a modal/panel titled **"What powers this app"**.
**Panel contents (in this order):**
**Gemini capabilities used (the hero list):**
- **Gemini 3.5 Flash (audio + multimodal)** — parses the voiced recipe into the GlazeRecipe schema with one-decimal-place precision on every percentage. One call per tile; audio is submitted as a Gemini Files API `files/*` resource name or as `inlineData`.
- **Gemini 3.5 Flash (multilingual)** — transcribes recipes in English, Spanish (multiple regional variants), Korean, Tagalog, Mandarin, Cantonese, Japanese, Vietnamese, Portuguese, Italian, French, German, Hindi, Tamil, Bengali, Arabic, Farsi, Turkish, Swahili, Amharic. Recipe and material names stay verbatim in the language spoken.
- **Gemini 3.5 Flash (multimodal, photo)** — describes the tile's surface qualities and defect positions; does NOT infer chemistry from the photograph.
- **Gemini 3.5 Flash (long context)** — once your vault grows, the chemistry search and "next firing" surface read the whole vault at once. A 1,000-tile production vault is ~600k tokens (comfortable inside 1M).
- **Gemini 3.5 Flash** — generates the one-sentence index-card summary and TTS read-back routing.
- **Gemini TTS** — reads recipes aloud in the language they were voiced, so a studio mate can hear the recipe with wet hands at the wheel.
- **Nano Banana 2** — generates the welcome / empty-state hero image (one-off, cached).
- **Firebase Auth** — Google and Apple sign-in, studio invitations via magic links.
- **Firestore** — stores your vault, syncs across devices in real time.
- **Firebase Storage** — keeps the original tile photographs at upload resolution, forever.
- **Cost note** — see the detailed breakdown in 6d. A typical 60-tile drawer costs about $0.90 of Gemini API spend, total, processed once.
- **Privacy note** — your recipes are private to you and the studio mates 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 chemistry is yours; the app never substitutes oxides or claims a recipe is food safe.
**Backend services this app depends on:**
- Auth: see section 4b
- Database: see section 4b
- Storage: see section 4b
- Email: see section 4b
- Payments: see section 4b (not used in v1)
- External APIs: see section 4b
**Environment variables you'll need to configure:**
- `GEMINI_API_KEY` — your Google AI Studio API key
- `FIREBASE_PROJECT_ID` — your Firebase project id
- `FIREBASE_SERVICE_ACCOUNT` — service-account JSON (server-side only)
- `STRIPE_SECRET_KEY` — optional, only for a future studio-pro tier
**Cost + privacy notes:**
- One short paragraph per cost-sensitive capability: long-context calls are billed per token of input — a 200-tile vault long-context search costs about $0.18 each time it runs (default: per search query, not scheduled).
- One short paragraph on privacy: where the data lives (your Firebase project), how to delete it (Settings → "Delete this vault forever" — gone in 60 seconds), what is never sent for training.
**Documentation links:**
- AI Studio Build docs
- Gemini API audio, multimodal, multilingual, long-context, TTS docs
- Firebase Auth, Firestore, Firebase Storage docs
- A short note on Orton cone references and the standard cone-temperature chart shipped with the app
**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 voice recipe (Gemini 3.5 Flash, medium thinking, audio input + optional tile photo)** — typical 30-second voice recording + one image ≈ ~$0.013/tile. Re-runs after user edits are free if the audio hasn't changed (cache key = audio URI hash).
- **Describe tile photograph (Gemini 3.5 Flash, low thinking)** — typical 1-image input ≈ ~$0.004/tile.
- **Cross-tile chemistry search (Gemini 3.5 Flash, low thinking, long-context)** — billed per query. A 60-tile vault ≈ ~36k input tokens ≈ ~$0.05/query. A 600-tile vault ≈ ~360k input tokens ≈ ~$0.45/query. Most users run 1-3 searches a week.
- **Next firing recommendation (Gemini 3.5 Flash, medium thinking, long-context)** — runs once per planned firing. A 200-tile vault with 8 recipes in the load ≈ ~$0.22/firing.
- **Index-card summary (Gemini 3.5 Flash, low thinking)** — ~$0.0008/tile.
- **TTS read-back (Gemini 2.5 Flash TTS)** — billed per output token (~$10/M output tokens), effectively ~$0.000003/character. A typical recipe read-back ≈ $0.002. Cached per recipe; charged once.
- **Empty-state hero image (Nano Banana 2)** — one-off, ~$0.03; cached forever as a static asset.
- **Expected per-tile cost on first ingest:** ~$0.015. **60-tile drawer total:** ~$0.90. **Ongoing weekly search + planning** (200-tile vault): ~$0.30/week.
- **Image storage:** Firebase Storage standard tier, ~$0.026/GB/month. A high-res tile photo at 2400×2400 ≈ ~2 MB; a 200-tile vault uses ~400 MB ≈ ~$0.01/month.
## 7. Design language
- **Mood:** A potter's workbench at the end of a firing day. Not a tech product. Not a chemistry lab. The shop with the wheel still warm and the kiln cooling, the test drawer pulled open under the lamp, the tile in the palm.
- **Typography:** A workmanlike sans-serif for app chrome (Inter or Geist). A monospaced numeric tabular figure for the recipe percentages — the eye reads "2.5" and "3.0" aligned without effort. A subdued serif (Source Serif Pro) for the recipe name and tile title — recipes are named things, not data. The index-card export uses a slightly heavier serif so the studio wall reads it across the room.
- **Palette:** Bisque-paper background `#F0E9DD` for the recipe view, ash-gloss black `#1A1817` for body text, kiln-iron rust `#8B3A1F` for atmosphere chips and cone markers, copper-green `#3F6C58` for oxide additions, celadon `#A9C7B5` for surface-quality chips. A muted sepia `#6E5232` for material lot labels. A reduction-flame orange `#D86B3F` only for the low-confidence flag — never elsewhere. The palette is borrowed from a fired tile drawer, not from SaaS design systems.
- **Imagery:** The photographs of the tiles are the hero. Never replace them; never crop them past the edges the potter cropped. The kiln cycle visualisation is a thin two-line chart (temperature, atmosphere) — restrained, not infographic-loud. No 3D rendered pots, no clay-being-thrown stock photography in the empty states.
- **Hand-feel touches:** A barely-visible paper grain on the recipe-detail background. The "(i) show how the AI read this" panel slides the parse tree in with a thin shadow — like opening a notebook. Hover on a kiln-cycle stage reveals its full text from the voice transcript; never aggressively glow.
- **Spacing:** consistent 4-px base. Generous whitespace around the percentages — the numbers need air to be read accurately.
- **Radius:** consistent token set (e.g. 6 / 12 / 20 px). Tile cards use 6; the index-card preview uses 12; the welcome card uses 20.
- **Shadows:** subtle, layered, warm-toned. Avoid heavy drop-shadows.
- **Motion:** purposeful — entrance fades, hover lifts, page transitions. Respect `prefers-reduced-motion`. No bouncing splash animations. No theatrical hero animations. The kiln-cycle chart's progressive draw (left-to-right as the user scrolls into the stage) is the one place where motion carries meaning; respect reduced-motion by drawing it all at once.
- **States:** every interactive element has hover, focus, active, disabled. Loading uses skeletons that match the eventual layout. Empty states have helpful next-action guidance ("Photograph the first tile to start the vault").
## 8. Content generation rules
- Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'.
- Invent plausible recipe names, percentages, kiln cycles, and tile descriptions that fit the domain (use the seed content in section 8a as a starting point). When inventing, lean on real ceramic-glaze conventions — Leach A bases, GA6, Reitz Yellow, Tenmoku, buncheong slip, Talavera transparente — but never claim a fictional recipe is a published one. The sample recipes in this template are taught structures, not reproductions of any particular potter's intellectual property.
- Tone: warm, direct, free of corporate language. This template is for a person, not a company.
- Headlines: punchy and concrete. No 'Empower your X' filler. No 'Revolutionize'. No 'Seamless'.
- Body copy: short paragraphs (2-4 sentences). Use lists where appropriate.
- Plain language. Avoid jargon — except where the user already speaks the jargon (the production potter wants to see "cone 6 oxidation", "EPK", "Custer feldspar"; the buncheong potter wants to see "분청 슬립"; the Talavera potter wants to see "bisilicato de plomo").
- 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 percentage shows as a faintly underlined number; tapping it reveals the verbatim hedge the user voiced).
## 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 vaults (sidebar):**
- "Marisol's Talavera Drawer" (47 tiles, contributors: me) — Mexican Talavera in Puebla, lead-bisilicate transparent base with copper, manganese, and tin additions, cone 04 electric oxidation, 2024–2026.
- "Hyun-woo's Buncheong Tests" (32 tiles, contributors: me, my teacher Park-sŏnsaengnim) — Korean buncheong slip variants on Icheon stoneware, cone 10 light reduction, 2023–2026.
- "Rosario's Volcanic Ash Glazes" (28 tiles, contributors: me) — Filipino studio ceramicist in Antipolo, locally-collected Mount Pinatubo ash washings, cone 6 to cone 10 gas reduction, 2025–2026.
- "MFA Thesis Iron Grid" (60 tiles, contributors: just me) — graduate iron-percentage grid 0.5%–12% across three atmospheres, defended May 2026.
**Sample tile in detail view (this is what the demo should show):**
- **Tile ID:** "tile_047_marisol_2026_01_14"
- **Recipe name verbatim:** "Talavera transparente con 2.5% cobre carbonato"
- **Clay body:** "earthenware white, bisque cone 04, Puebla studio body"
- **Base glaze name verbatim:** "Talavera transparente (bisilicato de plomo)"
- **Base glaze components:**
- Lead bisilicate frit (Ferro Pb-313 equivalent) — 70.0%
- Silica — 18.0%
- Whiting — 7.0%
- EPK — 5.0%
- **Oxide additions:**
- Copper carbonate — 2.5%, addition over 100, confidence high
- Tin oxide — 5.0%, addition over 100, confidence high, note "for slight opacity"
- **Application:** brushed two coats, second coat thinned with ~10% water, total thickness ~0.8 mm
- **Kiln cycle:**
- Kiln name: "Skutt 1027 electric"
- Kiln type: electric
- Atmosphere: electric_oxidation
- Target cone: 04
- Cone witnessed down: 04
- Stages:
- Stage 0: 20°C → 600°C, ramp 200°C/h, atmosphere_change none
- Stage 1: 600°C → 1000°C, ramp 150°C/h, atmosphere_change none
- Stage 2: hold 15 min at 1000°C, atmosphere_change none, notes "soak"
- Stage 3: cool natural to room
- Total firing hours: ~7 h
- **Date fired ISO:** "2026-01-14"
- **Date fired verbatim:** "yesterday"
- **Source language:** "es-MX"
- **Voice input transcript:** "Tile 47, Talavera transparente con 2.5 por ciento de cobre carbonato y 5 por ciento de óxido de estaño, base de bisilicato de plomo, pincelado dos manos sobre bizcocho, cono 04 en eléctrica, ayer, 14 de enero 2026."
- **Surface observations:** gloss (centre), broken_edge (lower-right, with a subtle copper-green pool)
- **Defects:** one pinhole, upper-left, severity light
- **Visual summary for card:** "gloss transparent over white earthenware with a soft copper-green pool at the lower-right edge; one light pinhole upper-left."
- **Reading confidence:** 0.96
**Sample input artefacts (for the build to demonstrate):**
- A 30-second voice recording in Mexican Spanish describing the Talavera transparente tile above.
- A 25-second voice recording in Korean describing a buncheong slip variant on Icheon stoneware.
- A 35-second voice recording in Tagalog describing a volcanic-ash celadon test.
- A 20-second voice recording in English describing a cone 6 oxidation copper-rutile tile.
- A 28-second voice recording in English describing a cone 10 reduction Tenmoku.
**Sample voice copy:**
- Onboarding: "Photograph one of your test tiles. Voice the recipe. We'll keep every percentage exactly as you said it."
- Processing: "Listening to the recipe…" / "Pulling out the percentages…" / "Reading the kiln cycle…" / "Looking for your other tiles in this cone…"
- Empty vault: "This vault is waiting for its first tile. Photograph the tile, voice the recipe, and the rest follows."
- Error (couldn't parse): "We couldn't pull out a percentage here. Want to re-record this bit, or type the value in directly?"
- Save confirmation: "Added to Marisol's Talavera Drawer — Talavera transparente con 2.5% cobre, 14 January 2026."
- Low confidence note: "Some percentages were hedged ('approximately'). Tap any underlined number to see what you said."
- Next-firing surface: "Four other tiles in your vault are cone 6 oxidation with copper between 2% and 4%."
**Sample studio invitation email subject + body:**
- Subject: "Hyun-woo — would you add your buncheong tests to my vault?"
- Body: "Hi Hyun-woo — I'm rebuilding my buncheong test drawer in a vault app. Could you add the variants you fired in March? It would help me find the inflection point we were chasing. Tap to join." [Open Vault]
## 9. Media & assets
- **Hero image (landing screen):** A photographed-looking shot of a hand placing a glaze test tile into a shallow wooden drawer that already holds twelve tiles. Generate via Nano Banana 2 with a prompt emphasising "wooden workbench, soft afternoon window light from the left, hand of a potter with clay-marked nails, real worn paper label on the drawer's edge, no people in frame above the forearm".
- **App icon / wordmark:** Set in the display serif, with a subtle bisque-paper texture behind it. No icon — just type.
- **Empty-state illustration:** A simple line drawing of a single test tile resting in an empty drawer. Hand-drawn aesthetic, not a flat icon.
- **Demo tile photographs:** Generated per the prompts in section 8a — Nano Banana 2 prompts specifically requesting "5cm by 5cm white stoneware test tile, single glaze fired in [atmosphere], soft afternoon window light, the tile resting on a pine workbench, slight shadow underneath, real surface texture (gloss / matt / crystalline / pinhole), no kiln furniture in frame". Each demo tile should look photographed on a workbench, not rendered.
- **Kiln cycle chart:** A static thin-line SVG chart per kiln cycle, temperature on the y-axis (Celsius default with Fahrenheit toggle), time on the x-axis, atmosphere-change markers as small triangles. No 3D pottery imagery.
- **Stock fallbacks:** If image generation fails, fall back to the photographed sample tile from `/public/samples/sample-tile.jpg`. Never to a generic "🏺" 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"). Percentages over 50% in oxide additions trigger a "Did you mean to type a base component instead?" warning rather than a silent block.
- Loading states use skeletons that match the eventual layout, not spinners.
- Empty states explain the next action with a button whose label fits THIS app's domain: "Photograph the first tile", "Voice a recipe from memory", "Type a recipe from a slip" — 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. For the voice-recipe parse, the structured fields populate progressively as the parse completes (recipe name first, then base, then additions, then kiln cycle).
- If an AI call fails, show a calm, specific error ("We couldn't pull out a percentage here — want to re-record, or type the value in?") and offer retry.
- Low-confidence percentages in the recipe are faintly underlined; tapping reveals the verbatim hedge the user voiced.
- The kiln cycle chart's progressive draw takes 600 ms with `prefers-reduced-motion` falling back to instant.
- Cone 6 versus cone 06 confusion always opens an inline disambiguation widget — never silently picks one.
## 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 voice-recipe parse / tile description / chemistry search / next-firing surface; `gemini-3.5-flash` for the index-card summary; `gemini-3.1-flash-tts-preview` for read-back; `gemini-3.1-flash-image` for the hero image. Set `thinkingLevel` explicitly per call (omit `thinkingConfig` entirely on TTS and image calls).
- **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed vault 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 tile photographs and voice audio. Pre-signed URLs only.
- **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+. The voice-record button must be reachable with one hand at the wheel.
- 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 vault grid view.
- Optimistic UI on writes; reconcile on response. A tile photographed and voiced shows up in the grid immediately with a "processing" badge until the parse completes.
- Voice recording uses the Web Audio API (MediaRecorder) with a hard cap at 90 seconds per entry; the cap is announced visually as a countdown ring around the record button.
- **iOS Safari gotchas (graceful degradation):** Safari `MediaRecorder` only supports `audio/mp4` (AAC) — feature-detect and persist as AAC; mic permission does NOT persist across page reloads on iOS — re-request on every glaze-vault entry; an incoming call interrupts the audio session (`MediaStreamTrack.onmute` fires) — auto-pause and prompt the potter to resume or restart (entries are short, so restart is usually fine); backgrounded Safari tabs pause `getUserMedia` — combine `visibilitychange` with a screen Wake Lock during entry capture.
- Tabular figures (`font-variant-numeric: tabular-nums`) on every percentage display so 2.5 and 3.0 align vertically.
## 12. Accessibility (WCAG 2.2 AA)
- Semantic HTML — `header`, `nav`, `main`, `section`, `article`, `footer`.
- All interactive controls reachable by keyboard with a visible focus ring.
- Color contrast ≥ 4.5:1 for body, 3:1 for large text and UI components. The copper-green and reduction-orange chips have been tested against the bisque-paper background and meet AA at the chip text size.
- All images have meaningful `alt` text. Tile photographs have `alt` describing the artefact ("photograph of a glaze test tile, gloss transparent surface with copper-green pool at lower-right edge, fired cone 04 oxidation, 14 January 2026").
- Form fields have associated `