================ 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.
---
# Counter-Argument
## 1. Project
**Counter-Argument** is a private rehearsal app for high-school and
university debaters who need to argue against the strongest version of
the opposition's case — not the easy strawman — before they walk into a
real round. The user types in a motion, picks a side (proposition or
opposition), and ten seconds later the app loads a voice on the other
end of the line: an opposition that opens warmly, makes a sourced
opening case in under ninety seconds, and then takes the user's rebuttal
seriously. Every claim the model makes against the user is grounded —
every statistic, every legal precedent, every named study, every
historical example comes back with a citation URL the user can open in a
new tab while the round is still running. After each clash the user
gets a calm, specific debrief: which of their rebuttals landed, which
claim the model would weight as the strongest against them, what to read
between now and Saturday morning.
This is the kind of app a Filipino-American sixteen-year-old debater
opens on a Sunday afternoon — three weeks before she flies to Dallas
for the national tournament — because her coach is excellent but
overworked, her parents do not speak the language of policy debate, and
the only way she gets better against the schools that ran circles
around her last year is to argue against them in her bedroom at the
volume her mother lets her use. It is also the kind of app an Indian-
British year-twelve student opens before her Cambridge Union schools
debate; the kind of app a Nigerian-American senior at a Newark public
high school opens because her team has two coaches between sixteen
kids; the kind of app a first-year university debater opens late on a
Tuesday because everyone else in the society has been doing this since
they were thirteen and she is two years behind. Same shape of moment —
the kid with the motion typed up on the kitchen table, the case to
oppose, and nobody around to play the side they will face on Saturday
— different city, different background, different debate format.
The single demo that proves the magic: type in **"This house would ban
private cars from city centres"** → pick **opposition** → the model
says, voice in headphones, "I'll oppose. My strongest case is that
city-centre car bans displace congestion and emissions to working-class
neighbourhoods outside the ring, where the bus network is thinner and
the air-quality monitoring is less dense — there's a 2024 Transport &
Environment study and a peer-reviewed paper out of the LSE that I'll
cite. I'll wait for your first rebuttal." The user makes her first
rebuttal — that the studies cited measured short-run displacement, not
the longer-run modal shift documented in the Madrid central low-
emissions zone — and the model responds with the next anchor of its
case, citing a real source for each claim, and pushing back on her
counter-evidence without hedging and without conceding. The transcript
scrolls. Beside each model claim, a small superscript number opens the
citation. At the end of the round the debrief tells her that the
Madrid counter was her strongest moment, that her warrant on the air-
quality claim was thin and she should look up the specific peer-
reviewed study before Saturday, and that her closing summary went
ninety seconds long.
And in the harder cases — a year-eleven who has just discovered ethics
debate and is preparing to argue the proposition on **"This house
believes a state should never prosecute a teenager as an adult"**
against a model running the strongest deontological opposition; a
university novice arguing **"This house regrets the European Union's
asylum-pact reform"** and needing the model to steel-man the security-
sceptic case without straying into rhetoric the round won't reward; a
parliamentary-style debater preparing for an info-slide motion she will
not see until ten minutes before the round — the app holds the same
core scaffold: pick the strongest published opposition, name every
source as it speaks, never invent a statistic, never invent a study,
never claim a court decision said something the court did not say.
**Tagline:** _Argue against the strongest version — in any motion, any
format, with every counter-claim sourced to a real study, a real
ruling, a real policy paper, a real reporter._
## 2. Target audience
- Competitive high-school debaters preparing for state, regional, and national tournaments — policy, Lincoln-Douglas, public forum, parliamentary, World Schools, British Parliamentary
- University novices doing their first IV (intervarsity tournament) and trying to bridge the two-year gap to teammates who started at thirteen
- Schools debate students at under-resourced public schools — one coach for thirty kids, no varsity partner to practise against, no driving-range equivalent of debate
- First-generation immigrant students whose parents cannot run a practice round in English (or in Tagalog, or in Punjabi, or in Mandarin) and who carry the load of preparing alone
- Heritage-language debaters preparing for tournaments in their family language — Mandarin debate at a Beijing or Singapore tournament, Hindi debate at a Delhi schools competition, Arabic debate at a Doha world schools event, Spanish debate at an Iberoamerican circuit, Portuguese debate at a Brazilian schools final
- Adult learners taking up debate later — Toastmasters members preparing for the World Championship of Public Speaking, working professionals preparing for an Oxford-Cambridge style invitational, lifelong learners who want to argue better with their own family
- Teachers and coaches preparing case files for their team — using the app's grounded counter-arguments as a starting point for a week's research, never the endpoint
- Mock-trial students rehearsing opening and closing arguments against opposing counsel, with the model playing the prosecution or defence depending on side
- Model UN delegates preparing position papers and floor speeches, where the counter-bloc may include a security council member with positions the user has never personally argued against
- Pre-law and pre-policy students testing the strength of their argumentation before they apply to law school or fellowship programmes
## 3. Core value propositions
Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app.
- **The model never invents a statistic.** Every number in the opposition's case comes from a grounded Google Search result with a citation URL. If the model says "a 2024 IEA report shows that residential heating accounts for forty per cent of city-centre PM2.5 emissions in winter", the next thing the user sees is a small superscript that opens the IEA report in a new tab. The user can — and will — open it mid-round. The transcript saves the URL so the debrief can re-cite it. **This is the hard rule of the template.**
- **The opposition is the strongest published version of the case, not a strawman.** The model is briefed to pick the case the user is most likely to lose against, not the case that is easiest to beat. If the motion is the Madrid car ban, the opposition leads with the displacement-to-poorer-neighbourhoods case, not "people like cars". The user practises against the case their best opponent will run.
- **It corrects what the user gets wrong about the user's own side, too.** If the user, on proposition, claims a study said something it did not, the model — even on opposition — flags it: "the Lancet paper you just cited is about ambient PM2.5 generally; it doesn't break out vehicle versus heating contribution. Want to look that up?" The model is your sparring partner, not your hype crew.
- **Multilingual, multinational, multi-format.** Debate runs in English (US, UK, Indian, Filipino, Nigerian, Australian, Singaporean), Mandarin, Cantonese, Hindi, Tamil, Bengali, Urdu, Punjabi, Tagalog, Vietnamese, Korean, Japanese, Arabic (Modern Standard, Egyptian, Levantine, Gulf), Farsi, Swahili, Amharic, French, Spanish (Mexican, Castilian, Argentine), Portuguese (Brazilian, European), German, Polish, Russian, Turkish. Format presets cover World Schools, British Parliamentary, Policy, Lincoln-Douglas, Public Forum, Model UN, Mock Trial, Toastmasters World Championship style. The grounded-search anchors shift to the user's region — UK case law for a UK debater, Indian Supreme Court rulings for a Delhi debater, ICCAT and FAO fisheries data for a Pacific Islands sustainability motion.
- **The user does the talking; the debrief gives them the receipts.** The Live API keeps the conversational pace of a real round — the user can interrupt, the model can be interrupted, both sides can pause to think. After the round, the user sees three blocks: what landed, what to look up, what to rebuild. Every "what to look up" entry includes the URL the model was working from, so the user does not have to find the source again.
- **Private by default.** Transcripts of the round live in the user's account. They are never shared, never used for training. The user can delete a session in sixty seconds.
- **Coach mode for the over-stretched coach.** A teacher running thirty kids on one coach can review their students' transcripts (with student consent), tag the moments the team needs to drill together, and assign specific motions for the week. The coach pays nothing; the team pays nothing; the heavy lifting is the model's, not the teacher's.
## 4. Features to build
- Motion input — a single text field, with format selector (World Schools, BP, Policy, LD, PF, MUN, Mock Trial, Toastmasters) and side selector (proposition / opposition / "let me hear both before I pick"); a small "is this an info-slide motion?" toggle that adds context-paste support
- Round-style picker — "first practice on this motion" (slow opening, model explains its case structure as it goes) vs "tournament pace" (no explanation, real-time rebuttals, hard time limits)
- Opening-case generation — the model writes out the opposition's first speech as text before the live round starts, so the user can read it once if they want to prep; the user can also skip straight to live
- Live API round — voice in / voice out, with the model playing the opposing team. Interruption support, voice-activity detection, pause and resume. Default cap: 12 minutes per round; configurable for full-length tournament rounds (e.g. 8-minute PF crossfires, 15-minute British Parliamentary roundtables)
- Per-claim citations — the model emits a structured event for every external claim ("source name", "URL", "verbatim claim", "year of source"); the client renders them as superscript numbers in the live transcript and opens them in a side panel on tap
- Counter-evidence panel — the user's own case file (uploaded as PDFs, pasted text, or linked URLs) is searchable inside the live round; a single hotkey ("did I have a source on this?") surfaces the user's strongest pre-loaded counter to the claim the model just made
- Steel-man toggle — a hard switch that asks the model "what is the strongest version of the OTHER side's case here?" mid-round; the model spells out the steel-man for thirty seconds, then resumes opposing the user
- POI / cross-examination support — the model accepts a Point of Information when in WS/BP mode; in policy and LD modes the model handles cross-examination questions with the format's rules
- Hedge + warrant detector — the app listens for "I think", "kind of", "sort of", "I just feel", "isn't it obvious that", "everyone knows", "studies show" (without a named study), "experts agree" (without naming experts); the debrief flags these as warrants the user must rebuild
- Time-keeping — visible per-speech timer with the format's bell pattern (e.g. 6:00 / 7:00 / 8:00 protected, then bell-and-bell); user can ask the model to pause the clock to think
- Replay-this-clash — pick the 30-second window where the user got crossed, re-run just that exchange against the same opposition voice, three tries before the debrief lifts
- Coach mode — invite a coach to one archive; coach sees consented transcripts, can tag moments (`good warrant`, `look-up-needed`, `weak link`), and assign next week's motion
- Library of past rounds — searchable by motion, format, side, opposition's strongest claim; the user can mark a round "this is the one I'll watch back the morning of the tournament"
- Brief export — a one-page PDF per round with the motion, the opposition's case, the sources the model cited, the user's strongest moments, and one paragraph of "what to read this week"
- Team mode — a debater can pair with their partner for two-on-two formats (BP, PF), with the model running two opposition voices that pass speeches between themselves; the partner joins via magic link
- Tournament-day mode — the user picks a tournament from a curated list (or enters one); the app surfaces the publicly announced motion topics for that circuit's recent rounds, with the option to drill three motions in an hour
- Reduced-pressure mode — for first-time users, the model opens with "I'll explain how I'm building this case as I go; you don't have to keep up with tournament pace today"
- Etiquette layer — in formats and circuits where adjudication norms are specific (British Parliamentary's "extension" requirement; WS's prepared vs impromptu split; Policy's stock-issues structure), the model debates inside those norms and the debrief reads in those terms
## 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)
- **Gemini Live API (voice in / voice out)** — runs the full live debate round. The opposition persona is loaded as the system instruction, with the motion, format, side, and the user's case file (if uploaded) bundled as session-level context. Voice activity detection handles barge-in and POIs. The user can interrupt, the model can be interrupted, both sides can pause. Use the Live API's tool-use / function-calling support so the model can request a grounded-search call mid-round when it needs a citation, then weave the cited claim back into its next sentence.
- **Search grounding (Google Search)** — for the opening-case generation and for every mid-round claim that references an external source. The model never produces a statistic, a court ruling, a study finding, or a named report without a grounded query producing a citation. Citation URLs come from `response.groundingMetadata.groundingChunks[].web.uri` — never from the JSON body the model emits. **Grounded search prevents fabricated evidence.** This is the hard rule of the template.
- **Structured output / JSON Schema** — every non-grounded call (case-plan synthesis, hedge + warrant detection over transcript, debrief generation, brief export) emits a typed object matching the `CasePlan`, `WarrantMoment`, and `RoundDebrief` schemas below.
- **Multilingual** — the round runs in the user's chosen language. The Live API voice catalogue covers the languages listed in section 3, including Indian and Filipino English voices that match how those students actually debate, and Mandarin with both Mainland and Taiwan registers. The grounded-search call shifts to the right Google market per language and region.
- **Long context (1M tokens)** — the debrief reads the full session transcript (typically 8–25 minutes of dialogue ≈ 4k–15k tokens) plus the case-plan plus the user's uploaded case file plus the user's last five rounds on the same motion. Comfortable headroom at this scale. **Guardrail**: for tournament-day mode where a user runs three motions in an hour, keep each round's debrief scoped to its own transcript + case-plan; do not include the prior rounds' transcripts in the same call to avoid token bloat.
- **Multimodal image input** (Gemini 3.5 Flash) — for users who photograph their case file from a notebook (handwritten case notes), the case-file ingestion call reads the page and extracts the user's structured arguments. Optional; pasted text and uploaded PDFs are the primary path.
- **Thinking levels** — `medium` for the opening case-plan synthesis (it has to reconcile the motion, the user's side, the format's norms, and the grounded sources into a coherent first speech). `low` for the hedge+warrant detection and for individual rebuttal generation during the live round. The Live API call is real-time and does not expose `thinkingConfig` — omit it.
### 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** is required for the coach-mode invitation flow and the partner-mode pairing flow; it requires the sender domain to be authorised in Firebase Auth. School-issued accounts (Google Workspace for Education) work out of the box; the coach-invitation flow is the most common entry point for under-resourced school programmes.
- **Database — Required.** Firestore for `users`, `rounds` (one document per round with transcript + case-plan + debrief), `case_files` (the user's evidence library, sharable with coach + partner), `coach_relationships` (coach ↔ debater consent records), `team_pairings` (debater ↔ partner pairings for BP / PF), `tournaments` (curated list of circuits with motion topics).
- **File storage — Required.** Firebase Storage for the audio recordings of rounds (opt-in per session, default OFF) and for the case-file PDFs the user uploads. **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 audio save or PDF upload. Pre-signed URLs only; case files are private to the debater and any explicitly invited coach or partner.
- **Email — Required (transactional).** Coach-invitation emails, partner-pairing emails, magic-link sign-in, brief export delivery, tournament-day reminder emails ("your prep window for the Dallas final starts in 45 minutes").
- **Payments — Not needed for v1.** Free for personal use, including coach mode and team mode. An optional generosity tier (Stripe, $5/mo) funds infrastructure but adds no gated features. The school-team and under-resourced programmes use the free tier indefinitely.
- **External APIs:** Gemini API for all intelligence (with Live API access); optional Google Calendar API for tournament-day reminders (graceful fallback to email-only).
**Environment variables:** every secret (Gemini API key, Firebase service-account JSON, Stripe key, Google Calendar OAuth client secret) 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 round transcripts and case files 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) · audio recordings are opt-in per session and the default is OFF · the coach-mode consent record is per-debater and revocable in one tap · users under 18 must have a parent/guardian consent flow surfaced at first sign-in.
**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 schemas are 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. **Grounded responses can wrap JSON in ```json fences or add prose preamble.** Server-side, strip fences and brace-extract:
```typescript
function safeExtractJSON(raw: string): T {
const clean = raw.replace(/```json\s*|```/gi, '').trim();
const s = clean.indexOf('{'); const e = clean.lastIndexOf('}');
if (s === -1 || e === -1) throw new Error('No JSON boundaries in grounded response');
return JSON.parse(clean.slice(s, e + 1)) as T;
}
```
8. **Strip unsupported Zod modifiers before passing to `responseSchema`** — Gemini's OpenAPI subset rejects `.regex()` / `pattern`, fixed-length `z.tuple()`, and other custom validators. Use a sanitizer that flattens tuples to length-2 arrays and removes regex patterns before serializing. Validate those constraints in middleware AFTER parsing.
### Per-call model + tools matrix
| Call | Model | thinkingLevel | Tools enabled |
|------|-------|---------------|---------------|
| Opening-case synthesis (motion + side + format → `CasePlan`) | `gemini-3.5-flash` | medium | `google_search` grounding (no `responseSchema` — emit JSON in body) |
| Mid-round claim grounding (one claim at a time, requested by Live API tool-call) | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` — emit JSON in body) |
| Live API round (voice in / voice out) | `gemini-3.5-flash` (Live) | n/a | `request_grounded_claim` function tool — model calls when it needs a citation mid-round |
| Hedge + warrant detection over transcript | `gemini-3.5-flash` | low | (none) — long-context over transcript |
| Round debrief synthesis (`RoundDebrief`) | `gemini-3.5-flash` | medium | (none) — long-context over transcript + case-plan |
| Case-file ingestion (PDF or photograph → `CaseFileEntry`) | `gemini-3.5-flash` | low | (none) — multimodal image / PDF input |
| Steel-man-the-other-side mid-round response | `gemini-3.5-flash` | medium | (none) — receives the case-plan as context |
*Note for builders:* on Live API calls, omit `thinkingConfig` entirely — the field is not supported on that model class. The `n/a` cells in this matrix are documentation only; do not serialise them into the request body. Live API runs over a WebSocket — wire the client to `wss://generativelanguage.googleapis.com/...` and stream audio frames; do NOT try to invoke Live via `generateContent`. The mid-round grounded-claim call is invoked as a function-tool from inside the Live session; the server brokers the grounded call, then injects the cited claim back into the Live session context as a structured tool result. Do NOT try to put `responseSchema` AND `google_search` on the same Gemini call — they are mutually exclusive; the grounded calls emit JSON in the response text body and citation URLs are read server-side from `groundingMetadata.groundingChunks[].web.uri`.
### Primary structured-output schemas (seed these verbatim in the prompts)
```typescript
import { z } from "zod";
const GroundedClaim = z.object({
claim_verbatim: z.string(), // the exact sentence the model will say in the round
source_name: z.string(), // "Transport & Environment", "LSE Cities", "European Court of Human Rights"
source_url: z.string(), // populated from groundingMetadata.groundingChunks[].web.uri server-side
source_publication_year: z.number().nullable(), // 2024
source_kind: z.enum([
"peer-reviewed-study",
"policy-paper",
"government-report",
"ngo-report",
"court-ruling",
"treaty-or-statute",
"news-investigation",
"official-statistics",
"industry-data",
"expert-testimony",
"scholarly-book",
"encyclopedic-reference",
]),
one_sentence_methodology: z.string(), // plain English: how the source produced this number
one_sentence_caveat: z.string().nullable(), // honest read of the source's limits
jurisdiction_or_scope: z.string().nullable(), // "EU", "UK only", "US federal courts", "Madrid only"
});
const CasePlanArgument = z.object({
one_sentence_claim: z.string(), // "Car-centre bans displace congestion to working-class peripheries."
warrant_one_sentence: z.string(), // the causal mechanism
grounded_evidence: z.array(GroundedClaim).min(1).max(3),
expected_user_rebuttal: z.string(), // what the model anticipates the user will say
prepared_response_to_user_rebuttal: z.string(), // the model's prepared comeback
priority: z.enum(["lead", "second", "third", "extension"]),
});
const CasePlan = z.object({
case_plan_id: z.string(),
motion_verbatim: z.string(), // the exact motion the user typed
format: z.enum([
"world-schools",
"british-parliamentary",
"policy-debate",
"lincoln-douglas",
"public-forum",
"parliamentary-uk-uni",
"model-un",
"mock-trial",
"toastmasters",
"informal-rehearsal",
]),
model_side: z.enum(["proposition", "opposition", "either"]),
format_norms_note: z.string(), // one paragraph: how this format scores debaters
opening_speech_text: z.string(), // the model's first speech, fully written out
arguments: z.array(CasePlanArgument).min(2).max(5),
language: z.string(), // BCP-47, "en-PH", "hi-IN", "ar-EG"
jurisdiction_default: z.string().nullable(), // "US federal", "England and Wales", "EU", "India"
generated_at_iso: z.string(),
});
const WarrantMoment = z.object({
transcript_quote_verbatim: z.string(), // what the user actually said
warrant_problem: z.enum([
"missing-warrant", // "studies show" with no study named
"claimed-source-without-naming-it", // "there's a study" — what study?
"named-source-without-warrant", // "the IEA report" — but how does that prove your point?
"hedge", // "I think", "kind of", "sort of"
"appeal-to-emotion-only", // emotional appeal without a logical link
"circular-reasoning",
"ad-hominem",
"straw-man-of-opposition",
"anecdote-as-data",
"overgeneralised-from-one-case",
]),
one_sentence_read: z.string(), // plain English of what weakened the argument
alternative_sentence_verbatim: z.string(), // the suggested replacement
what_to_look_up: z.string().nullable(), // "the specific Lancet PM2.5 study on Madrid 2003"
timestamp_seconds: z.number(),
});
const LandedMoment = z.object({
transcript_quote_verbatim: z.string(),
why_it_landed: z.string(), // "anchored to the IEA 2024 figure, with a clean warrant"
judge_lens_note: z.string().nullable(), // "in WS this kind of comparison wins under 'matter'"
timestamp_seconds: z.number(),
});
const RoundDebrief = z.object({
round_id: z.string(),
case_plan_id: z.string(),
duration_seconds: z.number(),
format: z.string(),
model_side: z.string(),
what_landed: z.array(LandedMoment).min(1).max(5),
what_to_keep: z.array(z.object({
sentence_verbatim: z.string(),
keep_because: z.string(),
})).min(1).max(5),
what_to_rebuild: z.array(z.object({
sentence_verbatim: z.string(),
rebuild_because: z.string(),
rebuilt_alternative_verbatim: z.string(),
})).min(0).max(5),
warrant_moments: z.array(WarrantMoment),
strongest_model_claim_to_prepare_against: z.string(), // the one claim the model would weight as hardest for the user
strongest_model_claim_source_url: z.string().nullable(), // the citation
what_to_read_this_week: z.array(z.object({
one_sentence_topic: z.string(),
suggested_source_url: z.string(), // from grounded search, not invented
why_this_matters_for_the_user_one_sentence: z.string(),
})).min(1).max(5),
next_round_suggestion_one_sentence: z.string(), // "Run this motion again Friday, on the proposition side, against the same opposition voice."
generated_at_iso: z.string(),
});
const CaseFileEntry = z.object({
entry_id: z.string(),
user_claim_verbatim: z.string(), // the line in the user's own case file
user_source_name: z.string().nullable(), // what the user attributes it to
user_source_url: z.string().nullable(),
user_source_kind: z.string().nullable(),
parsed_from: z.enum(["pasted-text", "pdf-upload", "photographed-page"]),
flagged_for_user_review: z.string().nullable(), // "the source you cite here doesn't appear to say this — open the PDF on page 14 to check"
});
type CasePlan = z.infer;
type RoundDebrief = z.infer;
type CaseFileEntry = z.infer;
```
### Common failure modes (and how to avoid them)
- Agent invents a statistic during the live round when no grounded search has fired — the function-tool `request_grounded_claim` MUST be called before any sentence the model emits that contains a number, a named study, a court case, or an attributed quote. If grounding returns nothing usable, the model says aloud "I'd want to cite a source here but I don't have one in front of me — let me make a structural argument instead" and pivots to a logic-only point. NEVER let the model say "studies show" or "experts agree" in voice without a citation having been resolved.
- Agent invents a court ruling — the most common subtle failure. A model that says "in Brown v Board the court held…" with no citation, or worse, misstates what the court held. Pin in the persona: court rulings get the same grounded treatment as statistics; if the model cannot ground a ruling, it does not name it.
- Agent silently downgrades `thinkingLevel` on the the opening-case synthesis call to save quota — pin `gemini-3.5-flash` with the matrix-specified `thinkingLevel` explicitly. Flash will produce a shallower case plan and weaker steel-mans.
- Agent puts `responseSchema` on a grounded call — this will silently disable grounding (the two are mutually exclusive in one Gemini call). Emit JSON in the response body and parse server-side; read citation URLs from `groundingMetadata.groundingChunks[].web.uri`, not from the JSON body.
- Live API session loses persona + motion state mid-conversation when the user pauses for >30 seconds — pin the motion, side, format, and the full `CasePlan` in `systemInstruction` and re-send a one-paragraph case-state summary on resume. Do NOT rely on session memory across pauses.
- Opposition persona drifts into "talk-show pundit" tone — bake specific format norms into the persona's system instruction (e.g. "this is a World Schools debate, scored on Matter / Manner / Method"; "this is a Policy round, scored on stock issues"). A real debater has format constraints; a pundit has none.
- Hedge detector flags every hedge in equal weight — rank by impact on the user's case and only surface the top 3–5 in the debrief. The user is rehearsing, not being scored.
- Citation URL pasted by the model in the JSON body, not pulled from grounding metadata — the model will, when stressed, paste a plausible-looking URL into the JSON. Always overwrite the model's `source_url` with the server-side `groundingMetadata.groundingChunks[].web.uri` matched by source name.
- Mid-round grounded call fires for a non-evidence sentence (e.g. for a logical comparison the model is making) — pin in the function-tool description: "request grounded evidence ONLY for sentences that name a study, a statistic, a court case, a treaty, a named policy, or a quoted authority. Do NOT request grounding for analytical or comparative sentences."
- Coach mode surfacing a transcript the debater did not consent to share — pin: the consent flag on the `coach_relationships` document is checked server-side on every transcript read. The default is NOT shared; the debater explicitly opts in per round.
- The model running too long on its opening speech — pass the format's per-speech time limit into the persona; the model is told to cap its opening to the format's first-speech budget (4 minutes for PF, 8 for WS, 6 for BP, etc.).
- The model staying in formal debate register when the user is clearly informal — the "informal-rehearsal" format setting drops the format-norms pressure and lets the model debate conversationally. Use this for first-time users so they don't bounce.
- User under 18 signing up without parent consent — at first sign-in, detect by self-reported birthdate; if under 18, gate the live-API feature behind a parent-consent flow (parent's email, magic-link confirmation, single signed consent record stored in `users.parental_consent`).
- Tournament-day mode caching motions that are not actually public — the curated motion list MUST come from publicly announced tournament topics (e.g. World Schools' published motion list; the WUDC topic releases). NEVER scrape paywalled or private coach forums; NEVER attribute a motion to a tournament without the tournament's own publication.
### Negative constraints (hard rules)
- Do NOT fabricate a statistic, a study, a court ruling, a treaty article, a policy paper, or any other external claim. Every external claim in the round comes from the function-tool grounded search call; the citation URL is the server-side `groundingMetadata.groundingChunks[].web.uri`, never a URL the model emits in its own JSON. If grounding returns nothing usable for a claim, the model pivots to a logic-only argument in voice and surfaces a one-sentence "I couldn't ground this; treat as a structural argument" note in the transcript.
- Do NOT misattribute a real source. The model never says "the IPCC report says…" if the grounded source is the WMO; "a Lancet study found…" if the grounded source is a BMJ commentary. Source name in voice MUST match the grounded source name, character-for-character on the lead noun.
- Do NOT claim a court held something different from what it held. If the grounded source is a court ruling and the model cannot extract a one-sentence holding, the model does not cite the case; it pivots to a different argument.
- Do NOT generate a steel-man of an extremist position outside the bounds of standard academic debate. The app debates on the side the user picked, within the norms of competitive debate. Motions are filtered against a small block-list (incitement, dehumanisation, illegal-advocacy-of-violence) before the case-plan generates.
- Do NOT translate proper nouns in either the user's case file or the model's evidence. Court case names, statute names, treaty names, study names, author names, named policies, named cities stay verbatim. Translations get parenthetical glosses on first use.
- Do NOT default to US case law for a non-US user. The grounded-search call shifts jurisdiction by user country: UK case law for a UK debater (UK Supreme Court, Court of Appeal, ECtHR), Indian case law for a Delhi debater (Supreme Court of India, High Courts), EU case law for a continental European debater (CJEU, ECtHR), Brazilian case law for a São Paulo debater (STF, STJ).
- Do NOT let the opposition persona become hostile, sarcastic, or personally cutting toward the user. Skeptical, constrained, sharp on substance, even disappointing — yes. Mocking, demeaning, name-calling — never. The pushback profile has a defined ceiling. The model never makes the user feel like they shouldn't have shown up.
- Do NOT include faux scores or "win/loss" calls in the debrief. Confidence percentages on the user's performance are not surfaced; surface qualitative reads with verbatim quotes and concrete next-week reading.
- Do NOT use the user's round transcripts or case files 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-record audio. Audio recording is opt-in per session; the default toggle is OFF. The transcript is stored automatically; the audio is not.
- Do NOT expose any other user's transcript or case file ever, under any flag, including admin or debug flags. Transcripts are between the debater, the model, and any coach or partner the debater has explicitly invited.
- Do NOT serve a debrief that is purely encouraging when the round went poorly. The user is rehearsing; flattering them now wastes the rehearsal. The debrief is honest, specific, and humane — and always names exactly two things to read between now and the tournament.
- Do NOT autoshare a round with a coach. Per-round consent. The debater taps a button; the coach gets access to that one transcript only.
- Do NOT include legal advice or medical advice in any debrief. The app is not a lawyer and not a doctor. When the user is debating a motion that touches medical or legal substance, the model debates the position but never tells the user what to actually do about their own health or legal situation.
### 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: Opening-case synthesis (motion + side + format → `CasePlan`)
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: `google_search` grounding (no `responseSchema`)
```
You build the strongest published opening case for one side of a
competitive debate motion. The case must be (a) the version the user is
most likely to lose against, not the version that is easiest to beat;
(b) inside the norms of the format the user picked; (c) anchored to
real, named, citable sources, retrieved by grounded Google Search.
Inputs you receive:
- motion_verbatim (string)
- format (one of: world-schools, british-parliamentary, policy-debate,
lincoln-douglas, public-forum, parliamentary-uk-uni, model-un,
mock-trial, toastmasters, informal-rehearsal)
- model_side (proposition, opposition, or either — if either, pick the
side that produces the harder round for the user)
- user_jurisdiction (ISO 3166-1 alpha-2)
- user_language (BCP-47)
- user_level (novice, intermediate, advanced, tournament-level)
- prior_user_rounds (optional, the user's last 3 rounds on related
motions — read to avoid repeating the same case against the same
user)
Steel-man rule:
- You are not arguing the easy version. You are arguing the version a
top-ranked schools or university team would lead with at a final.
Pick the structural, evidenced argument over the rhetorical one.
- For ethics motions, choose the deontological vs consequentialist
axis the user is least prepared for, based on prior rounds.
- For policy motions, pick the displacement, second-order, or
political-economy argument over the obvious cost-benefit one.
Grounded sourcing rule:
- For every external claim — a statistic, a named study, a court
ruling, a treaty article, a named policy, a quoted authority —
issue a grounded Google Search query and pull the canonical URL
from groundingMetadata.groundingChunks[].web.uri. Do NOT include
URLs in the JSON body; the server reads them from grounding
metadata.
- Source kinds you trust (in this priority order): peer-reviewed
studies; government reports; court rulings; ngo reports from
reputable bodies; news investigations from outlets with a known
fact-checking process; official statistics; expert testimony at
legislatures; scholarly books; encyclopedic references for general
context only.
- For jurisdiction-bound sources (court rulings, statutes,
regulators), prefer the user's jurisdiction. UK debater → UK
Supreme Court / CA / ECtHR. Indian debater → SCI / HCs. US debater
→ SCOTUS / Circuit / federal agencies. Continental EU → CJEU /
ECtHR. Brazilian → STF / STJ. Match the jurisdiction to the
motion's frame; if motion is about international law, prefer
ICJ / ICC / WTO / UNHRC.
- For each grounded claim, write a one-sentence methodology and a
one-sentence caveat. If the source has a known limit (small sample,
funded by industry, contested by peer review, jurisdiction-bound,
outdated), say so plainly.
Format norms — match these exactly:
- world-schools: three speakers per side, 8-minute speeches, POIs
accepted between minutes 1 and 7; scored on Matter / Manner /
Method. Your opening speech is the first proposition or opposition
speech.
- british-parliamentary: four teams, 7-minute speeches, "extension"
required from the closing teams. Your opening is the opening half
speech.
- policy-debate: stock issues (harms, inherency, solvency,
topicality); 8-minute constructives + 5-minute rebuttals; evidence
cards required. Your opening is the first negative constructive
(or first affirmative if model_side=proposition).
- lincoln-douglas: value + criterion structure; 6-minute affirmative
constructive, 7-minute negative constructive. Your opening is the
format's first speech for your side.
- public-forum: 4-minute constructive + 3-minute crossfire; tight
case + tight summary. Your opening is the format's first speech.
- parliamentary-uk-uni: similar to BP but with whip speeches; pick
the closing or opening half accordingly.
- model-un: position paper voice; one-paragraph case for a state's
bloc position with sourced precedent.
- mock-trial: opening statement for prosecution or defence; no
hedges, narrative voice with cited evidence ("the prosecution will
show…").
- toastmasters: contest-speech voice; one anchor source per claim;
≤ 7 minutes.
- informal-rehearsal: no format pressure; conversational; explain
your case structure as you go for first-time users.
Hard rules:
- Do NOT invent any external claim. If grounding returns nothing
usable for a claim, drop the claim and replace with a logic-only
argument; do NOT name a source you have not grounded.
- Do NOT misattribute. The source_name in the JSON MUST match the
grounded source's canonical name on the lead noun.
- Do NOT translate proper nouns. Court case names, treaty names,
named policies, named cities stay verbatim.
- Do NOT default to US case law for a non-US user. Match the user's
jurisdiction.
- The opening_speech_text is the literal speech the model will read
out in voice. Length must match the format's first-speech budget.
Avoid hedges in the model's own voice ("I think", "kind of") — the
opening is the model's strongest argument, said cleanly.
Output: JSON in the response text body (NOT via responseSchema, which
cannot be combined with google_search grounding). Shape:
{
"case_plan": CasePlan,
"no_strong_case_reason": string | null
}
Server-side: read citation URLs for each grounded_evidence entry from
the response's groundingMetadata.groundingChunks[].web.uri — do NOT
ask the model to include URLs in the JSON body; it will hallucinate
them. The server matches the citation URL back to the grounded_evidence
entry by source_name.
No commentary outside the JSON.
```
---
### Call: Mid-round claim grounding (one claim at a time, requested by Live API tool-call)
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding (no `responseSchema`)
```
You return one grounded claim, on demand, for the Live API debate round.
The Live model has just decided to assert an external claim — a
statistic, a named study, a court ruling, a treaty article, a named
policy, a quoted authority — and has called the request_grounded_claim
function-tool with a short query.
Input you receive:
- short_query (string) — what the Live model wants to assert
- motion_verbatim (string) — for context
- user_jurisdiction (ISO 3166-1) — for case-law / policy grounding
- user_language (BCP-47) — for language-region search
- already_cited_sources (string[]) — to avoid double-citing the same source
You issue a single targeted grounded Google Search query. You return
one GroundedClaim with: claim_verbatim (the exact sentence the Live
model can say in voice), source_name, source_kind, source_publication_year,
one_sentence_methodology, one_sentence_caveat, jurisdiction_or_scope.
Hard rules:
- Do NOT invent. If grounding returns nothing strong, return
{ "grounded_claim": null, "reason": "no high-confidence source for
this claim in this jurisdiction" } and the Live model will pivot to
a logic-only argument in voice.
- Claim_verbatim must be a single sentence, ≤ 25 words, the Live
model can speak fluidly. No source-name-as-clause-beginning ("As
the IEA report shows…"); prefer "The IEA's 2024 report finds X."
- Source_name MUST be the canonical institutional name of the source
(e.g. "International Energy Agency", not "the IEA report"). The
Live model can shorten in voice but the structured field is
canonical.
- Prefer sources from the user's jurisdiction for court rulings,
policy papers, regulators, and official statistics.
- Prefer peer-reviewed studies and government reports over news
summaries; if a news investigation is the strongest source, name
the newspaper and the reporter.
- Do NOT return more than one claim per call. If the Live model
needs two claims, it will call you twice.
Output: JSON in the response text body. Shape:
{
"grounded_claim": GroundedClaim | null,
"reason": string | null
}
Server-side: read the citation URL from groundingMetadata.
groundingChunks[].web.uri — do NOT include URLs in the JSON body; the
server reads them from grounding metadata and injects them back into
the Live session context so the Live model can superscript the source
in the streaming transcript.
No commentary outside the JSON.
```
---
### Call: Live API round (voice in / voice out)
Model: `gemini-3.5-flash` (Live) · n/a · Tools: `request_grounded_claim` function tool
```
You are debating against the user in a competitive debate round.
The user has picked the motion, the format, and their side. You are
playing the opposing team. Your job is to argue the strongest
published version of your case, push back on the user's rebuttals
without conceding, and make every external claim a sourced one.
You receive at session-start:
- The CasePlan (motion, format, model_side, opening_speech_text,
arguments[], language, jurisdiction_default).
- The user's case-file entries (their pre-prepared evidence — what
they will likely cite).
- The user's level (novice / intermediate / advanced / tournament).
Open with the opening_speech_text verbatim, at the format's first-
speech pace. Do not improvise the opening; read what you prepared.
This makes the round predictable enough for a novice to follow and
hard enough for an advanced debater to attack.
After the opening, run the round in real time. Follow the format's
speech order, time budgets, and POI / crossfire rules. You can be
interrupted; you stop when the user starts speaking. The user can
be interrupted; you can offer a Point of Information in WS / BP
mode by saying "On that point" — and stop if the user declines.
Grounded sourcing rule (the hard rule):
- Every external claim you make in voice — a statistic, a named
study, a court ruling, a treaty article, a named policy, a quoted
authority — MUST come from a call to the request_grounded_claim
function-tool. The server will return a GroundedClaim with a
citation URL. You weave the source name into your sentence; the
client renders the citation as a superscript in the transcript.
- If request_grounded_claim returns null, you say aloud "I'd want to
cite a source here but I don't have one in front of me — let me
make a structural argument instead", and pivot to a logic-only
point. Do NOT name a study or statistic you have not grounded.
- Do NOT say "studies show", "experts agree", "everyone knows",
"research has demonstrated" without naming the study, the expert,
the body. These are warrants in plain clothes; the user will be
marked down for them in a real round, and so will you.
- For your already-prepared CasePlan arguments, the grounded
evidence is already attached — speak the claim_verbatim with the
source as already cited. You do NOT re-call grounding for these.
Persona behaviour:
- Stay in your side. If you are opposition, do not concede the
motion. You can acknowledge a strong rebuttal ("that's a fair
point on Madrid — let me sharpen my warrant") without conceding
the overall case. You can never say "you're right, the motion is
correct".
- Match the format's register. World Schools is more conversational
than Policy. Lincoln-Douglas is more philosophical than Public
Forum. Toastmasters is contest-speech voice, not argumentative.
Informal-rehearsal is conversational and patient.
- Match the format's etiquette. WS uses "Madam Speaker" once; BP
uses team-prefixed self-reference ("the Opening Government
argues…"); MUN uses third-person bloc voice; Mock Trial uses
"Your Honour". The opening_speech_text already includes the
etiquette; carry it through the rest of the round.
- Match the user's level. Against a novice, slow down and structure
your sentences cleanly. Against a tournament-level debater, run
full-pace with compound clashes; do not condescend.
- Do NOT be hostile, sarcastic, or personally cutting. Skeptical,
constrained, sharp on substance — yes. Mocking, demeaning,
name-calling — never.
- Do NOT make the user feel they shouldn't have shown up. The user
is sixteen and practising alone; you are the strongest possible
opposition, not their enemy.
Voice activity detection:
- Stop talking when the user starts. Resume when the user pauses
for >2 seconds.
- If the user goes silent for >6 seconds, prompt gently once:
"Take your time" or "Want me to repeat that?". Do not nag.
- The user can pause the clock with "pause"; resume with "go".
Do not lecture them about pausing.
Steel-man toggle:
- When the user activates the steel-man toggle, you take 30 seconds
to spell out the strongest version of the OTHER side's case (i.e.
the user's side). Do not be coy; be honest about what makes their
side strong. Then return to opposing them.
Time-keeping:
- Honour the format's per-speech time budget. You stop talking when
your speech runs out. You count the user's time too (silently);
if the user runs over, you don't interrupt — you let the round
finish and the debrief mentions the overrun.
End-of-round:
- When the user signals close ("that's my summary", "I'm done", "end
round"), stop. Do not add a final speech. The debrief is generated
out-of-band; you do not debrief in voice.
Hard rules for the persona:
- Never invent a statistic or a study in voice.
- Never misattribute a real source.
- Never break the fourth wall.
- Never threaten or imply consequences for the user's argument
("if you said that in a real round, you'd lose") — the debrief
handles that off-line, calmly.
- Stay in the format's norms.
The session is the user's practice. You are the strongest possible
opposition the user can argue against in their bedroom on a Sunday
afternoon.
```
---
### Call: Hedge + warrant detection over transcript
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) — long-context over transcript
```
You receive the full transcript of a debate round — the user's turns
and the model's turns, with timestamps and (where present) citation
markers next to the model's sourced claims.
You extract WarrantMoment[] for the USER's turns only. A WarrantMoment
is a place where the user's argument structure weakened — a missing
warrant, an unnamed source, a hedge, an appeal to emotion without a
logical link, circular reasoning, ad-hominem, straw-man of the
opposition, anecdote as data, overgeneralisation from one case.
Rules:
- Quote verbatim from the user's turns. Do not paraphrase.
- Rank by impact on the round. Surface the top 3-5 warrant moments,
not every minor hedge.
- For each warrant_moment, write an alternative_sentence_verbatim —
the exact sentence the user could say instead. Same length range.
Same register. No corporate-speak.
- For each warrant_moment, if the user named a source that should
exist but didn't fully support the claim, write a what_to_look_up
string — the specific source the user should read this week
(e.g. "the specific Lancet PM2.5 study on Madrid 2003, not the
general WHO ambient air-quality page").
- one_sentence_read is plain English. No coaching jargon. No
percentages.
- Do NOT flag the model's turns. This is only about the user's
argumentation.
- Do NOT mark the user's strongest turns as problems. Those go in
what_landed in the next call.
- timestamp_seconds is the second the turn starts, measured from
session start.
Output: a JSON object with `warrant_moments: WarrantMoment[]`. No
commentary.
```
---
### Call: Round debrief synthesis (`RoundDebrief`)
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) — long-context over transcript + case-plan
```
You produce a debrief of a debate round. The user just ended the
round; they want to know what landed, what to keep, what to rebuild,
and what to read between now and the tournament.
Inputs: the CasePlan; the full transcript; the warrant_moments from
the prior call; the user's case-file entries; the user's last 3
rounds on related motions (if any).
Voice:
- Honest. Specific. Humane. The user is a high-school or university
debater who showed up to practise; flattering them now wastes the
practice.
- Read like a coach at 8pm — direct, warm, no jargon, no
motivational-poster lines, no "you've got this!".
- No win/loss calls. No score percentages. No "confidence: 76%".
Qualitative reads with verbatim quotes, always.
Hard rules:
- what_landed: 1-5 moments where the user said something well.
Quote verbatim. Explain why it landed. If the format scores on
named axes (WS Matter/Manner/Method, Policy stock issues), tie
the moment to the axis in judge_lens_note.
- what_to_keep: 1-5 exact sentences the user should say again in
the next round. Quote verbatim.
- what_to_rebuild: 0-5 exact sentences that didn't carry their own
weight, with a rebuilt alternative. Quote verbatim, then propose
the alternative verbatim.
- warrant_moments come from the prior call; pass them through.
- strongest_model_claim_to_prepare_against: name the ONE claim from
the model's side that the user struggled against most. Use the
exact claim_verbatim from the CasePlan or transcript, and the
citation URL of that claim. This is the claim the user will read
about between now and the tournament.
- what_to_read_this_week: 1-5 entries. Each names a specific
source URL (from grounded search, never invented) and one
sentence of why it matters for the user's next round on this
motion. Prefer sources from the user's jurisdiction.
- next_round_suggestion_one_sentence: ONE sentence. "Run this
motion again Friday, on the proposition side, against the same
opposition voice." or similar.
- If the round went poorly, say so plainly. "Your case fell apart
in the third clash. Friday, you re-run the same motion and you
hold the line on the displacement argument." That is more useful
than a flattering debrief.
- Do NOT include the model's turns in what_to_keep, rebuild.
The debrief is for the user.
- Do NOT recommend the user adopt the model's case. If the user is
on proposition, the debrief helps them argue proposition; the
model's opposition case is the strongest version of what they
will face, not the position they switch to.
- Do NOT include legal advice or medical advice in any debrief.
Output: a single RoundDebrief JSON object. No commentary.
```
---
### Call: Case-file ingestion (PDF or photograph → `CaseFileEntry`)
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) — multimodal image / PDF input
```
You receive one document — a pasted block of text, an uploaded PDF
of a debate case file, or a photograph of a handwritten case notebook
page — and you extract the user's structured arguments into
CaseFileEntry[].
For each entry, capture:
- user_claim_verbatim: the line in the user's own case file, exactly
as written (preserve every word, including their phrasing tics).
- user_source_name: the source the user attributes the claim to
(e.g. "IPCC 2023", "Brown v Board", "Krugman 2008"); null if the
user did not attribute.
- user_source_url: only if the user provided a URL.
- user_source_kind: best guess from the kind enum.
- parsed_from: pasted-text, pdf-upload, or photographed-page.
- flagged_for_user_review: if the user appears to misattribute
("you cite Brown v Board for an equal-protection claim, but Brown
is a desegregation ruling — want to look up the specific cite?"),
or if the user's source URL doesn't appear to support the claim,
write a one-sentence flag. Do NOT fabricate the correction; only
flag what is clearly wrong.
For handwritten notebook photographs:
- Preserve every word the user wrote. Do not autocorrect.
- If a line is illegible, mark it "[illegible]" rather than
inventing.
- Diagrams and arrows in the margin become a one-sentence
description ("an arrow connects the IEA 2024 figure to the
Madrid displacement claim").
Hard rules:
- Do NOT translate proper nouns. Case names, treaty names, study
names, author names stay verbatim.
- Do NOT smooth the user's voice. If the user writes "Brown is the
obvious cite", you do not rewrite it as "Brown is the canonical
precedent". The case file is the user's own voice.
- Do NOT add citations the user didn't write. If the user wrote
"the IEA report" without a year, you do not fill in 2024.
Output: an array of CaseFileEntry objects. No commentary.
```
---
### Call: Steel-man-the-other-side mid-round response
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) — receives the CasePlan and the user's side as context
```
You produce a 30-second spoken steel-man of the OTHER side of the
motion (i.e. the side the user is debating). The user has activated
the steel-man toggle and wants to hear the strongest version of their
own side, so they can check whether their case is the strongest one
available.
Inputs you receive:
- motion_verbatim
- model_side (the side you have been arguing)
- user_side (the side you are now steel-manning, briefly)
- the CasePlan
- the round's transcript so far
Voice:
- Direct, generous, honest. The steel-man names the strongest
argument on the user's side, even if you have been arguing
against it for the last six minutes.
- ≤ 30 seconds spoken (≈ 75 words). Read at conversational pace.
Hard rules:
- Do NOT concede the round. After the steel-man, you return to
opposing the user. The steel-man is a 30-second window, not a
switch of side.
- Do NOT invent statistics in the steel-man. If you name a source,
it must be a source the user has already cited or one already
in the CasePlan's "expected_user_rebuttal" notes.
- Do NOT belittle the user's side in the steel-man. The point is
to give the user the strongest version of their own case to test
themselves against.
- Do NOT use the steel-man to make a strawman of the user's
current line. Argue the strongest available version, not the
one the user is running.
Output: a single string (the spoken steel-man). 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 Filipino-American nationals debater.** A 16-year-old Filipino-American debater on the Bay Area circuit prepares for the Texas national tournament in three weeks. She types in "This house would ban private cars from city centres", picks proposition, picks World Schools, picks intermediate level. The model opens on opposition with a displacement-to-working-class-peripheries case, cites a 2024 Transport & Environment report and an LSE Cities working paper, and pushes back when she argues the Madrid central low-emissions zone counter-evidence. Her debrief flags that she said "studies show" twice without naming a study, and her what-to-read-this-week names the specific Lancet PM2.5 paper she should pull before Friday.
- **The London year-twelve Indian-British debater.** An Indian-British 17-year-old prepares for a Cambridge Union schools invitational. She types in "This house regrets the EU's asylum-pact reform", picks opposition, picks British Parliamentary, picks tournament-level. The model opens on proposition with a security-cooperation case grounded in EU Council 2024 decisions and an ECRE briefing, and pushes back when she runs the Mediterranean displacement counter. Her debrief flags that her opening half didn't differentiate from a likely Opening Government line, and her what-to-read names a specific Frontex annual report passage.
- **The Newark high-school debater.** A 17-year-old Nigerian-American senior at a Newark public school prepares for the city championships. Her team has two coaches for sixteen kids. She types in "This house believes the United States should adopt a federal universal basic income", picks proposition, picks Policy debate, picks intermediate level. The model opens negative on the structural-employment-effects case, cites a 2024 Federal Reserve Bank of San Francisco working paper, a Hamilton Project policy brief, and the Finland UBI pilot evaluation. The model surfaces a citation she can open in a side panel while she's still speaking. Her debrief gives her three sources for the week and one specific page in the Stockton SEED final report.
- **The Delhi schools final preparation.** An Indian 16-year-old in Delhi prepares for an All-India schools tournament. She types in "This house would replace examinations with continuous assessment", picks proposition, picks World Schools, in Hindi. The model opens opposition in Hindi, cites a NCERT 2023 evaluation, a NEP-2020 implementation review, and a Pratham ASER report on assessment validity. Her debrief is in Hindi; the suggested reading is two papers in Hindi and one in English.
- **The first-year university novice.** A 19-year-old first-year university student in Sydney who started debate three months ago types in "This house regrets the rise of cancel culture", picks the "let me hear both before I pick" option, picks World Schools, picks novice level. The model writes both sides' opening speeches before the round so the user can read them, then asks the user which side they want to argue. The model opens slowly, with format-norms explanation interleaved, and pauses to ask "would you like me to explain why this is a third-pillar argument?" once. The user picks proposition and runs the round. The debrief is gentle, names two warrants she missed, and suggests one specific Atlantic essay for Saturday.
- **The Toastmasters World Championship preparation.** A 34-year-old Toastmasters contestant in Lagos prepares his World Championship of Public Speaking entry. He types in "Why we are kinder than we think", picks Toastmasters format, picks the contest-speech voice. The model plays the judge's "what's your anchor source?" role, drilling him on the single peer-reviewed source he leads with. The debrief names two warrants thin enough to lose him a finalist score and suggests he replace one of them with the specific 2023 PNAS paper on dictator-game generosity.
- **The Beijing university debate-society Mandarin round.** A 20-year-old Mandarin-speaking university debater at Tsinghua types in a motion in simplified Chinese, picks British Parliamentary, in Mandarin (Mainland register). The model debates back in Mainland Mandarin. The grounded sources prioritise Mandarin-language policy papers (CASS, NDRC reports) and the user's home circuit's adjudication norms.
- **The Sri Lankan-Canadian debater preparing for Worlds.** A 17-year-old Sri Lankan-Canadian debater in Toronto prepares for the World Schools Debating Championships. She types in a published motion from the tournament's open round, picks proposition, picks tournament-level, in English (Indian register because that is her household English). The model debates back at tournament pace, with no explanation and full-speed clashes. The debrief is dense, names exactly three things to read in the next 48 hours.
- **The mock-trial student.** A 17-year-old in Houston prepares the prosecution opening for a regional mock-trial round. She types in the case packet's summary, picks Mock Trial, picks prosecution. The model opens as defence counsel, cites a real Texas case on hearsay admissibility, and objects mid-round to a leading question. Her debrief names the moments she hedged into "the evidence will show" without specifying what evidence, and what to drill before the regional.
- **The coach mode case.** A teacher at the Newark high school has sixteen kids on her team. Each kid has invited her into their archive. On Monday morning she has 25 minutes; the app shows her a coach dashboard with each kid's last round, a one-sentence summary of what landed and what fell apart, and the option to assign a single motion for everyone to drill at Wednesday practice. She types one motion, taps assign-to-team, and goes back to the rest of her teaching load.
- **The under-eighteen consent case.** A 15-year-old in Atlanta signs up. The app detects she is under 18 from self-reported birthdate, gates the Live API feature, and surfaces a parent-consent flow. Her father gets a magic-link email, reads a one-screen explanation of what the app does, and confirms in two taps. The app saves a signed consent record. She gets full access.
- **The tournament-day case.** It is Saturday morning at the Dallas national tournament. The user has 45 minutes before her octafinal. She opens tournament-day mode, picks the tournament from the curated list, picks the three motions she expects from the publicly announced topic areas, and drills three 8-minute rounds in 30 minutes — opening, partial round, debrief on each. The debriefs are short and tight: one thing to keep, one thing to rebuild, the motion she is most likely to face.
## 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 quiet photographed-looking image of a teenager at a kitchen table at evening — a notebook open with a pen across the page, a glass of water at the edge, a laptop dimmed beside it; the user's hand-written notes on the page in fountain-pen ink. One paragraph: "Counter-Argument is a private rehearsal app for debaters. Pick a motion, pick a side; the strongest version of the opposition argues back, voice in your headphones, every claim sourced." Single Google sign-in button; Apple sign-in next to it; "or sign in with email" link below. A small line: "We never train on your rounds." Below: "Try with a sample motion" → loads the Madrid car-ban motion and a 90-second sample round.
2. **First-visit format picker.** A single focused screen: "What are you debating for?" with a small set of presets — *Tournament prep (high-school)*, *Tournament prep (university)*, *Schools league*, *Toastmasters contest*, *Mock trial*, *Model UN*, *Just practising*. Each preset loads the right format defaults and the right grounded-source prior (UK case law if UK, Indian if India, etc.). A small line at the bottom: "You can change this any time in Settings."
3. **Empty state — "New round".** A single form: motion (text area), format selector (defaults to the preset), side selector (proposition / opposition / let me hear both), level selector (novice / intermediate / advanced / tournament), language selector (defaults to the user's locale), "is this an info-slide motion?" toggle that adds a context-paste field.
4. **Case-file panel.** A side panel the user can open at any time: their evidence library — uploaded PDFs, pasted text, photographed pages. Each entry shows the user's claim verbatim, the source name, and the source URL. The user can add an entry inline ("paste a paragraph", "upload a PDF", "snap a notebook page"). The model uses this library during the live round.
5. **Generate case-plan.** A single primary button: "Build the round". The processing screen shows three honest progress steps: "Picking the strongest published version of the opposition…" → "Pulling grounded sources from peer-reviewed studies, government reports, and court rulings…" → "Writing the opening speech in [format] norms…". Total wall time ~30 seconds. The user sees the opening speech text as it streams in.
6. **Pre-round view.** The case-plan rendered as a document at letter-paper width. At the top: the motion, the model's side, the format, the level. Below: the opening speech text, with each grounded claim's source name and a small citation URL. Below: the model's three or four prepared arguments, each with the expected user rebuttal and the model's prepared response. A primary CTA: **Start the round now**. A secondary CTA: **Skip the read-through — start cold**.
7. **Round view (live).** The user's mic state and the model's voice state are visible — two equal-weight indicators, no theatrics. A live transcript scrolls below, the user's turns left-aligned, the model's turns right-aligned, in a serif body face. Each model claim with a citation gets a superscript number; tapping opens the source URL in a side panel. The format's per-speech timer ticks at the top with the right bell pattern. A small "case-file" hotkey opens the side panel mid-round. A small "steel-man" toggle next to it. A single prominent "End round" button. A secondary "Pause" button. No win/loss meter — the user does not need a score.
8. **Replay-this-clash view.** From the debrief, the user can tap a warrant_moment and re-rehearse just that 30-second window. The model voice replays the line that preceded the warrant; the user gets up to three tries at the new response; the model gives a brief reaction. Loops freely.
9. **Debrief view.** Four blocks: **What landed** (with verbatim quotes and the judge-lens note), **What to keep** (sentence verbatim), **What to rebuild** (sentence verbatim + rebuilt alternative), **What to read this week** (the five sources, each with a URL and a one-sentence why). Below: the warrant_moments as a small annotated mini-transcript. At the bottom: the strongest model claim the user should prepare against, with its source URL pinned, and the next-round suggestion.
10. **Library of past rounds.** The user's rounds, listed by date. Each shows the motion, the format, the user's side, the model's strongest claim, and a 1-line summary of what landed. The user can delete any round in a single tap; they can mark a round "this is the one I'll watch back the morning of the tournament".
11. **Coach mode.** A coach who has been invited sees a dashboard of consented transcripts from their team. Each row: the debater's name (or alias if the debater set one), the date, the motion, the 1-line summary. Tapping opens the full transcript with the warrant moments annotated. The coach can tag a moment ("good warrant", "look-up-needed", "weak link") and assign a motion to the whole team for the next practice. The coach cannot see audio recordings unless the debater explicitly shares them.
12. **Team / partner mode.** For two-on-two formats (BP, PF), a debater can pair with their partner via magic link. The round runs with two opposition voices that pass speeches between themselves; the partner joins from their device. Transcripts go to both partners; the debrief is shared.
13. **Tournament-day mode.** A curated list of tournaments with publicly announced motion topics. The user picks one, picks three motions to drill, and runs a 30-45 minute prep session. Each round is shorter than the format default; the debrief is one screen, not five.
14. **Settings & privacy.** Country, language, format defaults, level default, coach relationships, partner pairings, the under-18 consent record (if applicable). Privacy controls: "Save audio for sessions" (default OFF), "Auto-delete rounds older than" (default 60 days), "Delete all my data" (one-tap, confirmation, gone in 60 seconds), "Pause the model's training-data agreement reminder banner" (the banner that explains we never train on user content).
15. **Footer.** "Argue against the strongest version." Privacy: "Your rounds 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 Counter-Argument."
- Subhead: "Argue against the strongest version — in any motion, any format, with every counter-claim sourced to a real study, a real ruling, a real policy paper, a real reporter."
- One paragraph (≤ 60 words) explaining who this is for: high-school and university debaters, schools-league competitors, mock-trial students, MUN delegates, Toastmasters contestants — anyone who needs to practise against the strongest published opposition when their coach is over-stretched or their teammates are asleep. What makes it different: the opposition is voice-in-your-headphones live, the citations are real, the debrief tells you what to read between now and Saturday.
- Visual: a small illustration of a notebook with a motion written across the top and three handwritten bullets below. Not a robot, not a chat bubble.
**Slide 2 — Try it now.**
- One short prompt: "Try with a sample motion — This house would ban private cars from city centres".
- A live demo round pre-loaded with the Madrid car-ban motion, opposition side, World Schools format, intermediate level.
- 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the Transport & Environment 2024 citation surfaced live from grounded search; the model's voice in real time; the warrant-moment flagged in the debrief).
**Slide 3 — How to remix this.**
- Headline: "Make this yours."
- Three short bullets:
- "Swap the format presets in `/data/formats/` for your circuit's norms."
- "Adjust the persona instructions in `/server/personas/` for your students' level."
- "Wire up your Gemini API key (with Live API access) 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 Live API (voice in / voice out)** — the round runs as a live conversation. You speak; the opposition speaks back. Interruptions, pauses, Points of Information work the way they would in a real round. The session is private and the audio is opt-in to save.
- **Gemini 3.5 Flash + Google Search grounding** — every external claim the opposition makes — every statistic, every named study, every court ruling, every policy paper — comes from a real, named source with a citation URL. The model never invents evidence. The Live model calls a grounded function-tool mid-round to fetch each citation.
- **Gemini 3.5 Flash (multilingual)** — the round runs in your language and your region's debate register. English (US, UK, Indian, Filipino, Nigerian, Australian, Singaporean), Mandarin (Mainland and Taiwan), Cantonese, Hindi, Tamil, Bengali, Urdu, Punjabi, Tagalog, Vietnamese, Korean, Japanese, Arabic (MSA, Egyptian, Levantine, Gulf), Farsi, Swahili, Amharic, French, Spanish (Mexican, Castilian, Argentine), Portuguese (Brazilian, European), German, Polish, Russian, Turkish.
- **Gemini 3.5 Flash (long context)** — the debrief reads the full session transcript, your case file, your case-plan, and your last few rounds on related motions. It finds the patterns across rounds.
- **Gemini 3.5 Flash (multimodal)** — case-file ingestion from PDFs or photographs of your handwritten notebook. Snap a page; the entries land in your library.
- **Structured output / JSON Schema** — every non-conversational call (case-plan, case-file entry, warrant moments, debrief) returns a typed object. The debrief is the same shape whether you're in Newark or Delhi.
- **Firebase Auth** — Google and Apple sign-in, magic-link email for coach + partner invitations.
- **Firestore** — stores your rounds, case files, coach + partner pairings; syncs across devices in real time.
- **Firebase Storage** — keeps optional audio recordings of rounds (opt-in per session, default OFF) and your uploaded case-file PDFs.
- **Cost note** — see the detailed breakdown in 6d. A typical user (case-plan + one 12-minute round + debrief) costs about $0.30 of Gemini API spend.
- **Privacy note** — your rounds and case files are private. 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. Audio recording is opt-in per session. Under-18 users go through a parent-consent flow at first sign-in; the consent record is revocable in one tap.
**Backend services this app depends on:**
- Auth: see section 4b — Google + Apple + magic-link
- Database: see section 4b — Firestore
- Storage: see section 4b — Firebase Storage (opt-in audio + case-file PDFs)
- Email: see section 4b — transactional (coach + partner invites, magic-link, tournament reminders)
- Payments: see section 4b — not used in v1; optional generosity tier funds infra, no gated features
- External APIs: see section 4b — Gemini Live API + Google Search grounding + optional Google Calendar
**Environment variables you'll need to configure:**
- `GEMINI_API_KEY` — your Google AI Studio API key (with Live API access enabled)
- `FIREBASE_PROJECT_ID` — your Firebase project id
- `FIREBASE_SERVICE_ACCOUNT` — service-account JSON (server-side only)
- `GOOGLE_CALENDAR_OAUTH_CLIENT_ID` — optional, only if you want to wire tournament-day reminders into Calendar
- `STRIPE_SECRET_KEY` — optional, only if you enable the generosity tier
**Cost + privacy notes:**
- One short paragraph per cost-sensitive capability: Live API sessions are billed per second of audio plus per token of conversational state; a 12-minute round costs about $0.24 at current rates. The grounded mid-round function-tool calls add ~$0.005 per claim (typically 3-6 per round).
- One short paragraph on privacy: where the data lives (your Firebase project), how to delete it (Settings → "Delete this round forever" or "Delete all my data" — gone in 60 seconds), what is never sent for training (everything you do here), that audio is opt-in, that under-18 users go through parent consent at first sign-in.
**Documentation links:**
- AI Studio Build docs
- Gemini API multilingual, long-context, multimodal, Live API, Google Search grounding, function-calling docs
- Firebase Auth, Firestore, Firebase Storage docs
- A short note on how the format presets map to the major debate circuits (WSDC, WUDC, NSDA, ICCA, etc.)
**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)
- **Opening-case synthesis (Gemini 3.5 Flash, medium thinking, grounded)** — ~2k input tokens (motion + format norms + user level), grounded search overhead, ~3k output tokens (case plan + opening speech text). ~$0.025 per round.
- **Mid-round claim grounding (Gemini 3.5 Flash, low thinking, grounded)** — typically 3-6 calls per round, each ~500 input tokens + ~400 output tokens, plus grounded-search overhead. ~$0.005 per claim. ~$0.020 per round at the midpoint.
- **Manager-rehearsal (Gemini Live API)** — billed per second of audio plus per token of conversational state. A 12-minute round ≈ $0.24 per round at current rates. Per-second audio is the dominant variable cost.
- **Hedge + warrant detection over transcript (Gemini 3.5 Flash, low thinking)** — ~5k input tokens (transcript), ~1.5k output tokens (warrant moments). ~$0.013 per round.
- **Round debrief synthesis (Gemini 3.5 Flash, medium thinking)** — ~8k input tokens (transcript + case-plan + prior debriefs), ~2.5k output tokens (debrief). ~$0.022 per round.
- **Case-file ingestion (Gemini 3.5 Flash, low thinking)** — varies; a 20-page PDF ≈ ~6k input tokens + 1k output tokens. ~$0.015 per file. Photographs of a notebook page: ~$0.01 per page. Run once per upload, cached thereafter.
- **Steel-man-the-other-side response (Gemini 3.5 Flash, medium thinking)** — ~3k input tokens (case-plan + transcript so far), ~500 output tokens (the 30-second steel-man). ~$0.008 per call. Typically 0-2 per round.
- **Expected cost per round (case-plan + 12-min Live round + grounded claims + debrief):** ~$0.30. A typical user runs 3-5 rounds per motion before the tournament; a typical user-week is ~$1.50.
- **Image storage:** Firebase Storage standard tier, ~$0.026/GB/month. Audio is opt-in; a 12-minute Opus-compressed audio file ≈ 5 MB. A user with 20 saved rounds ≈ 100 MB ≈ negligible. Case-file PDFs typically <5 MB each.
- **Calendar integration:** Google Calendar API is free for normal usage; rate limits apply.
- **A note on the cost ceiling:** the Live API is the dominant cost. If the deployer wants to cap, do it by limiting round length (12-minute default cap, with per-format presets) and by offering a 60-second "single-clash drill" mode that costs ~$0.04. Under-resourced school programmes can use the drill mode 20 times for the cost of one full round.
- **A note on coach mode:** transcript reads by a coach do not invoke the Live API; they are Firestore reads only and cost effectively zero.
## 7. Design language
- **Mood:** A quiet teenager's bedroom at evening. Not a coaching app. Not an ed-tech dashboard. Not a gamified streak app. The desk lamp warm, the notebook open, the dog asleep on the rug, the user wearing the headphones their grandmother bought them, practising the sentence they will say at 11am Saturday. It is the seriousness of competitive debate without the chrome of corporate "AI-powered learning".
- **Typography:** Display serif for the case-plan content, the motion title, and the debrief headings (Source Serif Pro or Crimson Pro). A handwriting-styled accent (sparingly) only for the user's own margin annotations and for the verbatim "opening sentence to keep" call-outs — never for the model's voice, which sits in clean serif. Clean grotesque for app chrome (Inter or Geist). Numbers in market or stat callouts use tabular figures.
- **Palette:** Cream-paper background `#F5F0E6` for the case-plan and debrief views, deep ink `#181613` for body text, anchor blue `#2C4868` for citation links and source-name pills, ledger green `#3D5A39` for the user's strongest moments and "what landed" highlights, soft red `#9A382B` only for "what to rebuild" — used sparingly. A muted warm gold `#A78637` for the opening-speech highlights, the way a debater would mark them in their own notebook. Borrowed from a Moleskine notebook, not from a SaaS design system.
- **Imagery:** No stock photography of students raising hands. No glossy headshots of "diverse teens around a laptop". The hero is a photographed-looking notebook page on a kitchen table at evening; the empty state is a line drawing of a motion written across a page with three bullets and a pen. Never a robot. Never a chat-bubble graphic. Never a "you got this" inspirational poster.
- **Hand-feel touches:** A barely-visible paper grain on the case-plan background. The verbatim opening sentence the user should say sits inside a soft gold highlight, the way a debater would underline it in a real notebook. The "live round" button has a small recording-light indicator that pulses gently when the round is live — respects `prefers-reduced-motion` by holding steady. Citation superscripts in the transcript are set in a smaller serif numeral with a soft underline on hover, the way a footnote would feel in a typeset book.
- **Spacing:** consistent 4-px base. Generous whitespace — the user reads the debrief late at night and needs the page to breathe.
- **Radius:** consistent token set (e.g. 6 / 12 / 20 px). Case-plan cards use 6; the format-picker presets use 12; the welcome card uses 20.
- **Shadows:** subtle, layered, warm-tinted. 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 live-round mic indicator is the one place where motion carries meaning (the gentle pulse when the user is being listened to); respect reduced-motion by holding steady and showing a static "listening" label. The citation-superscript "open in side panel" transition is the second; it slides from the right at 280ms, falls back to instant under reduced-motion.
- **States:** every interactive element has hover, focus, active, disabled. Loading uses skeletons not spinners where possible. Empty states have helpful next-action guidance ("Type a motion, pick a side — we'll build the round").
## 8. Content generation rules
- Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'.
- Invent plausible motions, debater names, tournaments, and sample transcripts that fit the domain (use the seed content in section 8a as a starting point). When inventing, lean on real circuits and real format norms — World Schools, British Parliamentary, NSDA Policy, Cambridge Union schools invitational, WSDC, WUDC — but never claim that a fictional transcript is from a real student or that a fictional round happened at a real tournament. Every source-grounded claim the model emits in production must come from the live grounded-search call.
- Tone: warm, direct, free of corporate language. This template is for a high-school or university debater, not a company.
- Headlines: punchy and concrete. No 'Empower your X' filler. No 'Revolutionize'. No 'Seamless'. No "you got this".
- Body copy: short paragraphs (2-4 sentences). Use lists where appropriate.
- Plain language. Avoid jargon — except where the user already speaks the jargon (the policy debater wants to see "stock issues" and "topicality" in the format-norms note; the BP debater wants to see "extension" and "knife"; the Lincoln-Douglas debater wants to see "value premise" and "criterion"). Match the user's circuit's vocabulary.
- 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 grounded source with a small sample shows a "small sample" badge; the source caveat is one sentence below the band; a claim the model could not ground is rendered in voice as "let me make a structural argument here" and in transcript as italics).
## 8a. Seed content (use these specific examples)
Anchor every generated copy + sample data point in the concrete content below. Use these names, motions, formats, and snippets verbatim where helpful, or generate close variants that sit in the same world. **Statistics, study names, and court rulings used in seed content are illustrative and must be replaced by the live grounded-search call in production — never serve seed-content claims as if they were grounded data.**
**Sample motions (sidebar — for the demo's "try a sample" picker):**
- "This house would ban private cars from city centres" — World Schools, intermediate, English (US), proposition default.
- "This house regrets the EU's asylum-pact reform" — British Parliamentary, tournament-level, English (UK), opposition default.
- "This house believes the United States should adopt a federal universal basic income" — Policy debate, intermediate, English (US), proposition default.
- "This house would replace examinations with continuous assessment" — World Schools, intermediate, Hindi (India), proposition default.
- "This house regrets the rise of cancel culture" — World Schools, novice, English (Australia), either side.
- "Why we are kinder than we think" — Toastmasters contest, intermediate, English (Nigeria), single-speaker.
- "This house believes that the International Criminal Court does more harm than good" — World Schools, advanced, English (Singapore), either side.
- "本院反对人工智能在中小学课堂的强制部署" — British Parliamentary, advanced, Mandarin (Mainland), either side.
**Sample user debaters (seed for the library + demo):**
- "Maya — 16, Bay Area circuit, WS" (3 rounds, all on policy motions, intermediate level, English)
- "Aanya — 17, Delhi schools league" (5 rounds, mixed policy + ethics, advanced level, Hindi + English)
- "Adaeze — 17, London Cambridge Union invitational prep" (4 rounds, all BP, tournament-level, English UK)
- "Chinaza — 17, Newark public school" (6 rounds, NSDA Policy, intermediate, English US)
- "Nadia — 19, first-year Sydney uni novice" (2 rounds, WS, novice, English AU)
- "Olu — 34, Lagos Toastmasters" (3 rounds, Toastmasters contest, intermediate, English NG)
- "Yu Han — 20, Tsinghua debate society" (4 rounds, BP, advanced, Mandarin Mainland)
- "Priya — 17, Toronto, Sri Lankan-Canadian, WSDC prep" (5 rounds, WS, tournament-level, English IN-register)
- "Hannah — 17, Houston, mock-trial prosecution" (3 mock-trial opens, prosecution, intermediate)
**Sample case-plan in detail view (this is what the demo should show):**
- **User:** Maya (illustrative)
- **Motion verbatim:** "This house would ban private cars from city centres"
- **Format:** World Schools
- **Model side:** opposition
- **Format norms note:** "World Schools is scored on Matter (the strength of the argument), Manner (delivery and engagement), and Method (structure and clash). Speeches are 8 minutes. Points of Information accepted between minutes 1 and 7. Your opposition opens with the strongest structural case, not the easiest one to beat."
- **Opening speech text (illustrative — replace in production with grounded version):** "Madam Speaker, opposition opposes. We oppose this motion not because we love cars but because city-centre car bans, as drafted in this house, displace congestion and emissions to working-class neighbourhoods outside the ring, where the bus network is thinner and the air-quality monitoring is less dense. We have three arguments. First, displacement is not theoretical — it is the documented pattern. Second, the wealthier ring residents are not the ones who carry the cost of the spillover. Third, the political economy of an in-ring car ban makes the harder reforms — better outer-borough transit, school-run alternatives, freight rerouting — less likely, not more. Let me start with displacement."
- **Arguments (3):**
- **Argument 1 (lead):** "City-centre car bans displace congestion and emissions to working-class peripheries." Warrant: "When you ban cars in the central low-emissions zone, the trips don't evaporate; they reroute through the ring road and through the neighbourhoods the ring road passes." Grounded evidence (illustrative — to be replaced by live grounding): a 2024 Transport & Environment report on Madrid Central, an LSE Cities working paper on London's Ultra Low Emission Zone displacement. Expected user rebuttal: "the long-run modal shift in Madrid Central shows the displacement disappears after 24 months". Prepared response: "the long-run modal shift is real for the wealthier central residents whose alternatives improved; it is not real for the outer-ring residents whose commute patterns are structurally different."
- **Argument 2 (second):** "The ring residents who carry the spillover are not the ones who benefit." Warrant: "Air-quality monitoring is densest inside the ring; outer-ring residents are systematically under-protected." Grounded evidence (illustrative): a 2023 Imperial College London study on London air-quality monitor density. Expected user rebuttal: "we can mandate ring-area monitoring as part of the policy". Prepared response: "the policy as drafted does not; even if it did, the political coalition that passes the central ban is not the coalition that funds ring monitoring."
- **Argument 3 (third):** "Central car bans foreclose the harder reforms." Warrant: "Political capital spent on the central ban is political capital not spent on outer-borough transit, school-run alternatives, and freight rerouting — the reforms that would actually reduce city-wide emissions." Grounded evidence (illustrative): a 2024 ITDP policy brief on Bogotá's TransMilenio expansion vs central car restriction. Expected user rebuttal: "the central ban builds the political appetite for the harder reforms". Prepared response: "in three of the four cities where this was tried, the harder reforms followed; in the others, the central ban became the policy that the next administration repealed."
- **Language:** en-US
- **Jurisdiction default:** US / EU / UK (motion is jurisdiction-neutral; sources will lean to the user's region)
**Sample round transcript excerpt (intermediate level, WS, opposition model):**
- *Model (00:00–01:30, opening speech):* "Madam Speaker, opposition opposes. We oppose this motion not because we love cars but because city-centre car bans, as drafted in this house, displace congestion and emissions to working-class neighbourhoods outside the ring…"
- *Model (01:32):* "…and on this I cite a 2024 Transport & Environment report.¹"
- *Citation panel (auto-opens, side panel):* ¹ Transport & Environment, "Air-quality spillover from low-emissions zones: a 2024 review", https://www.transportenvironment.org/... (illustrative URL — replace with grounded result in production)
- *User (04:48, first rebuttal):* "I want to take that head-on. The Madrid Central low-emissions zone showed exactly the displacement pattern you describe in the first 18 months and then the modal-shift effect kicked in. There's a peer-reviewed Lancet paper on this, sort of, and the displacement disappears."
- *Model (05:16):* "I want to be careful with that — the Lancet paper you're thinking of is, I believe, the 2023 review of ambient PM2.5 in central Madrid; it doesn't disaggregate the displacement effect onto the ring. The displacement question turns on what happens beyond the ring road, not what happens inside it. Do you want me to grant the central modal shift and argue the spillover side?"
- *User (05:48):* "Sure, grant the central modal shift. But the spillover claim has to be measured against the alternative, which is no policy at all."
- *Model (06:01):* "Right — and there's where the political-economy argument bites. The alternative isn't 'no policy'; it's the bus-network expansion the Bogotá TransMilenio model would suggest. I'll cite the ITDP brief on that.² Let me make the third clash."
- *Citation panel (auto-opens):* ² ITDP, "Beyond restrictions: TransMilenio and the political economy of central car policy", https://www.itdp.org/... (illustrative URL — replace with grounded result in production)
- *User (06:42, weak warrant moment):* "I just feel like the structural argument doesn't account for the fact that, like, the political-economy story is different in every city."
- *Model (06:54):* "It is — and that's the strongest version of your case. Walk me through the city you'd model on instead of Madrid or London."
**Sample debrief excerpt (illustrative):**
- *What landed:* "I want to take that head-on. The Madrid Central low-emissions zone showed exactly the displacement pattern you describe in the first 18 months and then the modal-shift effect kicked in." — Named the case study, named the time horizon, did not hedge into "I think". Judge lens (WS): strong on Matter; carry this into your second-speech material. Keep this sentence.
- *What to keep:* "Grant the central modal shift. But the spillover claim has to be measured against the alternative." — Clean concession with a hard pivot. Tomorrow, lead with this on the displacement clash.
- *What to rebuild:* "I just feel like the structural argument doesn't account for the fact that, like, the political-economy story is different in every city." → rebuilt: "The structural argument over-generalises. Madrid is not Bogotá. Let me walk you through how the political-economy story differs in [city] — and why my proposition stands there." Reason: "I just feel" and "like" softened the warrant; the rebuilt sentence opens with a clear concession and names a city.
- *Warrant moments (3):*
- "There's a peer-reviewed Lancet paper on this, sort of" (timestamp 05:00) — claimed-source-without-naming-it + hedge ("sort of"). Look up: the specific Lancet study you were thinking of — is it the 2023 ambient PM2.5 review or the 2022 city-mortality paper? You need the citation by Friday.
- "I just feel like" (timestamp 06:42) — hedge + missing warrant. Rebuilt above.
- "Studies show this kind of thing" (timestamp 07:18) — claimed-source-without-naming-it. Drop the phrasing; if you have a study, name it; if you don't, make a logic argument.
- *Strongest model claim to prepare against:* "Political capital spent on the central ban is political capital not spent on outer-borough transit, school-run alternatives, and freight rerouting." Source URL (illustrative): the 2024 ITDP brief on TransMilenio. This is the argument you struggled against; the third clash is where your warrant got thinnest. Read this brief before Friday and decide whether your proposition concedes the political-economy argument or refutes it.
- *What to read this week:*
- The specific Lancet paper on Madrid Central air quality you were thinking of (illustrative URL — replace with grounded result). Why: you need to name it cleanly or drop the line.
- The ITDP brief on TransMilenio political economy. Why: this is the model's strongest argument and you don't have a refutation yet.
- A peer-reviewed study on London ULEZ displacement (Imperial College or LSE Cities). Why: London is the other case the opposition will cite; you need the displacement-vs-modal-shift answer for that city too.
- *Next round suggestion:* "Re-run this motion Friday, on proposition side, against the same opposition voice — and lead with the rebuilt sentence above."
**Sample voice copy:**
- Onboarding: "Type a motion. Pick a side. The strongest opposition argues back — voice in your headphones, every claim sourced."
- Processing: "Picking the strongest published version of the opposition…" / "Pulling grounded sources from peer-reviewed studies, government reports, and court rulings…" / "Writing the opening speech in [format] norms…"
- Empty rounds: "Your library is waiting for its first round. Type a motion to start."
- Error (no strong opposition found): "We couldn't ground a strong opposition case for this motion in your jurisdiction. Try widening the motion (e.g. drop the country specifier) or switch to the structural-argument mode."
- Save confirmation: "Round saved — Maya, 'private cars from city centres', WS opposition, intermediate."
- Round-start: "When you're ready, tap the mic. I'll open with the case I prepared."
- Warrant note: "A few of your warrants went thin. Open the debrief to see the verbatim moments and the rebuilt alternatives."
- Reduced-pressure mode entry: "We'll go slowly. I'll explain how I'm building the case as I go; you don't have to keep up with tournament pace today."
- Low confidence note on a grounded source: "This source has a small sample. The wider [other source] is shown beside it for context."
- Under-18 parent consent prompt: "Because you're under 18, we'll email a one-paragraph note to a parent or guardian and they confirm in two taps. Then you're set up."
- Coach mode invite: "Your coach has been invited to your archive. They'll see only the rounds you explicitly share. You can change this any time in Settings."
- Citation pop: "Source: Transport & Environment, 2024. Tap to open."
**Sample format presets:**
- **World Schools (WS).** 8-minute speeches, three speakers per side, POIs accepted minutes 1–7. Scored on Matter / Manner / Method. Model opens with the strongest structural case for opposition; expects three substantive clashes; respects POIs.
- **British Parliamentary (BP).** Four teams, 7-minute speeches, opening + closing on each side, "extension" required from the closing teams. Model can play any of the four roles; default is closing opposition (the harder spot for novices to argue against). POIs accepted minutes 1–6.
- **Policy debate (NSDA).** Stock issues: harms, inherency, solvency, topicality. 8-minute constructives, 5-minute rebuttals, evidence cards required. Model opens with the first negative constructive against an affirmative plan; topicality argument is standard.
- **Lincoln-Douglas.** Value premise + criterion structure. 6-minute affirmative constructive, 7-minute negative constructive. Model opens with negative; value clash is the centre of the round.
- **Public Forum.** 4-minute constructive, 3-minute crossfire, 2-minute summary. Lay-judge accessible. Model keeps voice conversational; avoids deep theory.
- **British/UK parliamentary university.** 7-minute speeches, four teams, similar to BP; whip speeches in closing positions. Model defaults to whip opposition.
- **Model UN.** Position-paper voice, bloc representation, two-minute speeches. Model opens as a state delegation in the user's bloc's opposition.
- **Mock Trial.** Opening statement + closing argument; objections during direct/cross. Model opens as defence (if user is prosecution) or prosecution (if user is defence). Real case law from the user's jurisdiction.
- **Toastmasters.** Contest-speech voice, ≤ 7 minutes, single speaker, one anchor source. Model plays the judging panel's "what's your anchor?" role in the debrief, not in a live round.
- **Informal rehearsal.** No format pressure. Conversational. Model explains structure as it goes. Default for first-time users; default for users practising motions that aren't in their main circuit.
**Sample tournament curated list (publicly announced motions only):**
- "World Schools Debating Championships — recent topic areas: climate adaptation policy, international criminal justice, education reform, AI in public life. Motion list released two weeks before each round."
- "World Universities Debating Championship — recent topic areas: trade and development, healthcare access, civil-military relations, technology and democracy."
- "NSDA National Tournament (US high schools) — Policy resolutions are released annually each summer."
- "Cambridge Union Schools — invitational motions released two weeks before."
- "Asian Schools Debating Championships — released motions per round."
## 9. Media & assets
- **Hero image (landing screen):** A photographed-looking shot of a teenager's open notebook on a wooden kitchen table at evening — a motion written across the top in fountain-pen ink, three handwritten bullets below, a pen across the page, a glass of water blurred at the edge, headphones half-in-frame on the table. Generate via Nano Banana 2 with a prompt emphasising "wooden table, warm desk-lamp light, hand of a teenager in soft focus, late evening, open notebook with fountain-pen handwriting across two pages, headphones on the table, no laptop screen visible, no people centred, no logo, no books with brand names visible, real paper grain, soft shadow under the notebook, no AI-glossy render quality".
- **App icon / wordmark:** Set in the display serif. A small ink-underline mark beneath the wordmark — like a debater underlining the motion in their own notebook. No icon — just type.
- **Empty-state illustration:** A simple line drawing of a notebook page with a motion written across the top and three bullets below, a pen across the page. Hand-drawn aesthetic, not a flat icon. Never a robot, never a chat-bubble graphic.
- **Format-preset cards:** Small illustrative scene per format — for WS, a podium with a microphone; for BP, four lecterns in a quadrangle; for Policy, a stack of evidence cards; for Mock Trial, a gavel and a witness stand line drawing. Same illustrative style across all formats. No photoreal stock images.
- **Opposition persona avatars:** None. The opposition is a voice and a transcript, not a character with a face. The Live API session shows the model's voice state as a simple audio waveform and the persona name + format label. We do not anthropomorphise the model into a cartoon judge or coach.
- **Citation panel illustrations:** None. The citation panel is typeset, with the source name in serif, the URL as a clickable link, the publication year, the source kind as a small pill, the methodology and caveat in body. No charts, no graphics — the citation is the source, not a visualisation of it.
- **Period or stat charts:** None in the core flow. The case-plan and debrief are typeset documents. Numbers are inline in prose. If a deployer wants charts (e.g. for tournament-day mode to show a user's last 5 rounds' warrant-moment counts), keep them small, sepia-tinted, and never animated.
- **Stock fallbacks:** If image generation fails, fall back to a hand-drawn line illustration of a notebook page from `/public/samples/notebook.svg`. Never to a generic stock photo of students in a classroom. 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"). The motion text field accepts up to 500 characters; the case-file paste accepts up to 100k characters; the PDF upload accepts up to 20MB per file.
- Loading states use skeletons that match the eventual layout, not spinners. The case-plan generation screen shows the three honest progress steps in sequence, never a generic "loading…" line.
- Empty states explain the next action with a button whose label fits THIS app's domain: "Type your first motion", "Pick a format", "Upload your case file", "Invite your coach" — never a generic "Add your first item".
- Smooth scroll for in-page anchors.
- All AI-generated content streams in token-by-token where supported, with a clear "thinking…" indicator before content starts arriving — the case-plan view streams the opening speech first, then the three arguments, then the citation list, so the user can read top-down as it builds.
- If a Gemini call fails, show a calm, specific error ("The grounded-search call didn't return high-confidence sources for this motion in your jurisdiction. Try widening the jurisdiction, or run the round in structural-argument mode where the opposition makes logic-only arguments without external citations.") and offer retry.
- Mid-round, when the model requests a grounded claim via the function-tool and grounding returns null, the model says aloud "let me make a structural argument here" and the transcript renders the next claim in italics with a small "[ungrounded — logic only]" tag, so the user knows it isn't a sourced claim.
- The Live API connection state is always visible — "connecting", "live", "paused", "reconnecting", "round complete" — never hidden behind a spinner.
- The round mic-state indicator pulses gently when the user is being listened to; `prefers-reduced-motion` falls back to a static "listening" label.
- Citation superscripts in the transcript are tappable; tapping opens the source URL in a side panel (slides in from the right at 280ms, instant under reduced-motion). The side panel shows source name, URL, publication year, source kind, methodology, caveat, and a "open in new tab" button.
- "End round" is always one tap and never asks for confirmation more than once. The user is in a rehearsal and gets out instantly when they ask to.
- The audio-save toggle is OFF by default for every round and visible at the top of the pre-round screen. Users can opt-in per round; the global default in settings is also OFF.
- The under-18 parent-consent flow blocks the Live API feature until consent is confirmed; the rest of the app (case-plan reading, debrief reading) is available without consent so the user is never fully blocked at first sign-in.
- The steel-man toggle, when activated mid-round, dims the rest of the UI and surfaces a small "steel-man — 30 seconds" label. After 30 seconds the UI returns to normal and the model resumes opposing.
- Coach mode access controls are always visible at the top of any transcript a coach is viewing ("you are viewing X's transcript; X can revoke access at any time").
## 11. Tech & responsive requirements
- **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. The Live API runs over a WebSocket; the server brokers the connection so the API key never reaches the client. The mid-round function-tool grounded-claim calls also fire server-side, with the citation URL injected back into the Live session via the server.
- **Model selection:** explicitly pin `gemini-3.5-flash` for case-plan / Live round / hedge+warrant detection / debrief / case-file ingestion / steel-man, and `gemini-3.5-flash` for the mid-round grounded claim. Set `thinkingLevel` explicitly per call. Omit `thinkingConfig` on Live API calls. DO NOT combine `responseSchema` with `google_search` grounding on any call — they are mutually exclusive; grounded calls emit JSON in the response text body and citation URLs are read server-side from `groundingMetadata.groundingChunks[].web.uri`.
- **Database:** Firestore (auto-provisioned by AI Studio Build). Show a sample round on first launch (Maya — Madrid car-ban motion, WS, opposition, illustrative, clearly marked as a sample).
- **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it; magic-link email as fallback. Under-18 users get a parent-consent flow at first sign-in, with the consent record stored in `users.parental_consent` and a one-tap revoke control.
- **Storage:** Firebase Storage for optional audio recordings and case-file PDFs. Pre-signed URLs only; case files are private to the debater and explicitly invited coach or partner.
- **Live API client:** WebSocket connection brokered server-side; client receives audio frames via a relay endpoint. Implement a 200ms jitter buffer on the client; downgrade gracefully to text-only if bandwidth drops below 64kbps for >5 seconds. Function-tool calls (the mid-round grounded claim) fire from the Live session, are brokered server-side to the Flash grounded call, and the result is injected back into the Live session context as a structured tool result so the Live model can speak the cited claim.
- **Multipage / multi-image input:** for case-file ingestion of multi-page PDFs or multiple notebook photographs, use Gemini Files API (`files/*` resource name) or `inlineData` (base64). Do NOT pass Firebase Storage public URLs to `generateContent` — the API does not fetch them server-side. Include explicit "page 1 of N" headers in the accompanying text so the model can sequence reliably.
- **Long-context guardrails:** for tournament-day mode and library views, cap any single Gemini call at ~800k tokens of input. For coach-mode dashboards, do not load full transcripts into a single call; instead summarise per round and pass summaries.
- **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+. The live-round transcript view must remain legible on a 375px screen with one-handed scroll; citation superscripts must remain tap-targets ≥ 44×44px.
- 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 library view and the coach dashboard.
- Optimistic UI on writes; reconcile on response.
- Live rounds use the Web Audio API for the user's mic; fall back to native mic capture if Web Audio is unavailable. The headphones-required check at round start warns the user gently if no headphones are detected (echo from speakers is the most common Live API problem).
- **iOS Safari gotchas (graceful degradation):** the Live API round must survive audio-session interruption (incoming call, Siri, alarm) — listen for `MediaStreamTrack.onmute` and pause the round; resume on `onunmute`. Mic permission does NOT persist across reloads on iOS — re-request on every round and re-confirm the under-18 consent token. Backgrounded Safari tabs throttle WebSocket and kill `getUserMedia` — combine `visibilitychange` with a screen Wake Lock during a round. PCM streaming via `AudioWorklet` (Safari `MediaRecorder` is AAC-only).
- All grounded-search citation URLs open in a new tab with `rel="noopener noreferrer"`.
- The under-18 consent record uses a signed token; the parent cannot be impersonated by the user.
## 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 citation superscripts and the warrant-moment underlines meet the 3:1 minimum against the cream-paper background.
- All images have meaningful `alt` text. The hero notebook image has `alt` describing the artefact ("an open notebook on a wooden kitchen table at evening, a motion written across the top in fountain-pen ink, three handwritten bullets below, headphones on the table").
- Form fields have associated `