Aspectgg Cursor commited on
Commit
21031f3
·
1 Parent(s): d1b7226

Finalize hackathon README field notes and UI

Browse files

Co-authored-by: Cursor <cursoragent@cursor.com>

Files changed (6) hide show
  1. .env.example +23 -0
  2. .gitignore +27 -9
  3. FIELD_NOTES.md +131 -0
  4. README.md +260 -33
  5. frontend/script.js +23 -2
  6. frontend/styles.css +167 -59
.env.example ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copy to .env locally; use HF Space Secrets in deployment.
2
+ # Frontend never reads these — backend model_router only.
3
+ APP_ENV=development
4
+ MAX_ROUNDS=6
5
+
6
+ DEFAULT_MODEL_MODE=premium_nvidia
7
+
8
+ # NVIDIA Nemotron 3 Nano Omni 30B-A3B (primary premium model — backend-only API)
9
+ NVIDIA_API_KEY=
10
+ NVIDIA_BASE_URL=https://integrate.api.nvidia.com/v1
11
+ NVIDIA_OMNI_MODEL=nvidia/nemotron-3-nano-omni-30b-a3b-reasoning
12
+
13
+
14
+ # Hugging Face (model download / Space)
15
+ HF_TOKEN=
16
+
17
+
18
+
19
+ # Feature flags
20
+ ENABLE_DECK_CRITIQUE=true
21
+ ENABLE_DEAL_BATTLE=true
22
+ ENABLE_VOICE_MODE=true
23
+
.gitignore CHANGED
@@ -7,47 +7,65 @@
7
  __pycache__/
8
  *.py[cod]
9
  *$py.class
 
10
  *.egg-info/
11
  .eggs/
12
  dist/
13
  build/
 
14
  .pytest_cache/
15
  .mypy_cache/
16
  .ruff_cache/
17
  .coverage
18
  htmlcov/
 
 
19
 
20
  # Virtual environments
21
  venv/
22
  .venv/
23
  env/
 
24
 
25
  # OS / editor
26
  .DS_Store
27
  Thumbs.db
 
28
  .idea/
29
  .vscode/
30
  *.swp
31
  *.swo
 
32
 
33
  # Node (if added later)
34
  node_modules/
 
35
 
36
- # Gradio runtime / uploads
37
  .gradio/
 
38
 
39
  # Local model weights & generated media (large / machine-specific)
40
  models/
41
  *.gguf
 
 
42
  *.wav
43
  *.mp3
 
 
 
 
44
 
45
- # Local-only folders
46
- docs/
47
- scripts/
48
- tests/
49
 
50
- # Environment files
51
- .env
52
- .env.*
53
- .env.example
 
 
 
 
7
  __pycache__/
8
  *.py[cod]
9
  *$py.class
10
+ *.egg
11
  *.egg-info/
12
  .eggs/
13
  dist/
14
  build/
15
+ pip-wheel-metadata/
16
  .pytest_cache/
17
  .mypy_cache/
18
  .ruff_cache/
19
  .coverage
20
  htmlcov/
21
+ *.cover
22
+ .hypothesis/
23
 
24
  # Virtual environments
25
  venv/
26
  .venv/
27
  env/
28
+ ENV/
29
 
30
  # OS / editor
31
  .DS_Store
32
  Thumbs.db
33
+ Desktop.ini
34
  .idea/
35
  .vscode/
36
  *.swp
37
  *.swo
38
+ *~
39
 
40
  # Node (if added later)
41
  node_modules/
42
+ npm-debug.log*
43
 
44
+ # Gradio / Hugging Face runtime
45
  .gradio/
46
+ flagged/
47
 
48
  # Local model weights & generated media (large / machine-specific)
49
  models/
50
  *.gguf
51
+ *.bin
52
+ *.safetensors
53
  *.wav
54
  *.mp3
55
+ *.webm
56
+ uploads/
57
+ tmp/
58
+ temp/
59
 
60
+ # Logs & local databases
61
+ *.log
62
+ *.sqlite
63
+ *.sqlite3
64
 
65
+ # Jupyter
66
+ .ipynb_checkpoints/
67
+
68
+ # README screenshots (hosted via GitHub raw URLs on HF Space)
69
+ assets/*.png
70
+ frontend/assets/battle_fight.png
71
+ frontend/assets/landing_page.png
FIELD_NOTES.md ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Field Notes: PitchFight AI
2
+
3
+ Build log for the Hugging Face **Build Small Hackathon** (Backyard AI track).
4
+ PitchFight AI is a practice arena for student founders — not a replacement for real mentors, investors, or hackathon judges.
5
+
6
+ ---
7
+
8
+ ## What we built
9
+
10
+ PitchFight AI is an AI founder pressure arena where student builders:
11
+
12
+ 1. Enter a startup idea (quick pitch, advanced form, or voice pitch)
13
+ 2. Get an AI-structured founder briefing
14
+ 3. Choose a judge persona and difficulty mode
15
+ 4. Survive multi-round pitch Q&A in a battle-style UI
16
+ 5. Enter an optional deal-style pressure round
17
+ 6. Receive a scorecard with coaching and a retry path for the weakest answer
18
+
19
+ **Stack:** Hugging Face Spaces · Gradio Server (`app.py`) · custom HTML/CSS/JS frontend · NVIDIA Nemotron API (backend-only) · `ffmpeg` for audio · optional MongoDB persistence.
20
+
21
+ The browser talks only to `/api/*`. Model calls never run in the frontend.
22
+
23
+ ---
24
+
25
+ ## Why this exists
26
+
27
+ Most student founders rehearse slides alone or with friends who are too nice. The first hard questions usually come from a real panel — and that is when answers fall apart.
28
+
29
+ We wanted a low-friction place to practice **under pressure** before that moment. The goal is coaching and repetition, not pretending an AI judge is a substitute for human feedback.
30
+
31
+ ---
32
+
33
+ ## Product decisions
34
+
35
+ - **Arena, not chatbot.** Flow is staged: landing → briefing → battle → deal → scorecard. It should feel like stepping into a fight, not opening a generic assistant.
36
+ - **Structured briefing first.** Raw ideas get structured before the judge attacks, so users practice defending a clear narrative.
37
+ - **Personas + difficulty.** Skeptical VC, technical judge, and hackathon judge modes change tone and pressure without swapping the whole product.
38
+ - **Scorecard over vibes.** Output is actionable: what landed, what was weak, what to rewrite — not just “good job.”
39
+ - **Voice as an entry path, not the whole app.** Voice pitch and voice answers are supported where enabled, but text mode stays fully usable.
40
+
41
+ ---
42
+
43
+ ## AI judge design
44
+
45
+ Judges are prompt-driven personas routed through a single backend model layer. Design choices:
46
+
47
+ - **Follow-up questions, not monologues.** Each round pushes on claims, gaps, and vague traction.
48
+ - **Persona-specific pressure.** VC mode leans market and defensibility; technical mode probes AI necessity and feasibility; hackathon mode focuses on demo clarity and MVP.
49
+ - **Attack tags / rubric hooks** (in config) keep scoring and feedback aligned with what was actually said, not generic startup advice.
50
+ - **Retry weakest answer.** After the scorecard, users can rework one weak response — closer to coaching than a one-shot grade.
51
+
52
+ This is simulated judgment for practice. Real panels bring context, chemistry, and standards no API can fully replicate.
53
+
54
+ ---
55
+
56
+ ## NVIDIA Nemotron usage
57
+
58
+ - **API:** `https://integrate.api.nvidia.com/v1`
59
+ - **Primary model:** `nvidia/nemotron-3-nano-omni-30b-a3b-reasoning`
60
+ - **Key handling:** `NVIDIA_API_KEY` lives in `.env` locally and in **HF Space Secrets** in deployment — never in the frontend or git.
61
+ - **Routing:** `core/model_router.py` → `core/nvidia_client.py` for judge questions, structuring, scoring, rewrites, and voice-related paths where enabled.
62
+
63
+ **Practical note:** This is a reasoning model. It can spend tokens on internal reasoning before the visible reply. We tuned `max_tokens` per task so the user-facing answer stays in character and concise.
64
+
65
+ We use Nemotron as the main inference backend for the demo build. We are **not** claiming fine-tuning or custom model training.
66
+
67
+ ---
68
+
69
+ ## Custom UI / Off Brand decisions
70
+
71
+ We deliberately avoided default Gradio widgets for the product surface.
72
+
73
+ - **Custom frontend** (`frontend/`) — cinematic landing, briefing layout, battle stage, deal screen, scorecard.
74
+ - **Game-like presentation** — spotlights, arena framing, persona cards, pressure-meter styling. Pitch practice should feel memorable in a demo, not like a form filler.
75
+ - **Gradio as host, not as UI.** `app.py` serves the static frontend and REST API; Gradio is the deployment shell on Spaces.
76
+
77
+ This aligns with an **Off-Brand** spirit (custom experience on HF infrastructure) without claiming we built a different framework.
78
+
79
+ ---
80
+
81
+ ## Hugging Face deployment notes
82
+
83
+ - **Space type:** Gradio Space, `app_file: app.py`, custom `/` homepage serving `frontend/index.html`.
84
+ - **Secrets:** `NVIDIA_API_KEY` required; optional flags for voice, deal battle, deck critique, MongoDB.
85
+ - **System deps:** `packages.txt` includes `ffmpeg` for audio pipelines.
86
+ - **Layout tuning:** Landing and briefing screens were adjusted for the Spaces desktop viewport — including the white HF navbar above the app iframe. We use `100svh` / `100dvh` with safe padding and a small JS viewport sync so the hero fills the **visible app area** without a dead band at the bottom or content hidden under chrome.
87
+
88
+ Local dev at `127.0.0.1:7860` does not show the HF navbar; we tested both locally and in the live Space while iterating on layout.
89
+
90
+ ---
91
+
92
+ ## What worked well
93
+
94
+ - **Backend-only inference** kept secrets out of the browser and simplified deployment.
95
+ - **Custom UI + `/api/*` contract** made it easy to iterate on flow without fighting Gradio components.
96
+ - **Phased product shape** (brief → battle → deal → scorecard) reads clearly in a short demo.
97
+ - **Persona and difficulty toggles** give variety without multiplying model integrations.
98
+ - **Honest coaching tone** in prompts and scorecard copy — useful feedback without overclaiming “investor approval.”
99
+
100
+ ---
101
+
102
+ ## Tradeoffs and limitations
103
+
104
+ - **Coaching tool, not truth.** AI judges can be sharp but wrong, repetitive, or too harsh. Users still need humans for real validation.
105
+ - **Reasoning model latency and token budget.** Longer waits and token tuning are part of the tradeoff for quality follow-ups.
106
+ - **Voice depends on environment.** `ffmpeg`, browser permissions, and API availability affect voice mode reliability on Spaces.
107
+ - **Deck critique / MongoDB** are optional paths — not every deployment enables them.
108
+ - **No production-scale eval suite.** We did not run formal benchmarks or user studies for this hackathon build; quality is judged by manual demo runs and iterative prompt tuning.
109
+ - **Layout is viewport-sensitive.** Short or embedded viewports may still need scroll on dense screens (e.g. advanced briefing); landing is tuned for HF desktop first.
110
+
111
+ ---
112
+
113
+ ## Submission evidence
114
+
115
+ | Item | Link |
116
+ |------|------|
117
+ | **Live Space** | `[Add your HF Space URL]` |
118
+ | **Demo video** | `[Add demo video URL]` |
119
+ | **Hugging Face blog / write-up** | `[Add blog or post URL]` |
120
+
121
+ **Repo highlights for judges:** `app.py` (API + frontend host) · `core/api_handlers.py` · `core/model_router.py` · `frontend/` (custom UI) · `docs/DOCUMENTATION.md` (technical depth)
122
+
123
+ ---
124
+
125
+ ### Not claiming
126
+
127
+ - Not claiming **OpenBMB / MiniCPM**
128
+ - Not claiming **Modal**
129
+ - Not claiming **Tiny Titan**
130
+ - Not claiming **Off the Grid**
131
+ - Not claiming **fine-tuning**
README.md CHANGED
@@ -4,63 +4,290 @@ emoji: ⚔️
4
  colorFrom: red
5
  colorTo: yellow
6
  sdk: gradio
 
 
7
  app_file: app.py
8
  pinned: true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  ---
10
 
11
- # PitchFight AI
12
 
13
- **Your first tough pitch should not be in front of a real judge.**
14
 
15
- PitchFight AI is a voice-and-text sparring arena for student founders. Practice a startup pitch, get grilled by realistic AI judges, negotiate a deal round, and walk away with a scorecard that shows what landed and what to fix next.
16
 
17
- Built for the Hugging Face **Build Small Hackathon** (Backyard AI track). Runs as a **Gradio Space** with a custom frontend.
18
 
19
- ## Features
20
 
21
- - Multi-round pitch battles against different judge personas
22
- - Voice pitch and voice answers (Nemotron Omni)
23
- - Deal negotiation round after the pitch
24
- - Scorecard with coaching and retry on your weakest answer
25
 
26
- ## How It Works
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  ```
29
- Browser (custom frontend)
30
- │ fetch → /api/*
31
-
32
- Gradio Server (app.py)
33
- │ model_router → nvidia_client
34
-
35
- NVIDIA Nemotron API (integrate.api.nvidia.com)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  ```
37
 
38
- ## Deploy on Hugging Face
39
 
40
- 1. Push this repo to a Gradio Space (the YAML frontmatter above configures it automatically).
41
- 2. Add **`NVIDIA_API_KEY`** under **Settings → Repository secrets**.
42
- 3. Build should pick up `packages.txt` (`ffmpeg`) for voice support.
43
 
44
- Optional secrets: `MAX_ROUNDS`, `ENABLE_VOICE_MODE`, `ENABLE_DEAL_BATTLE`, `MONGODB_URI` (only if you enable MongoDB).
45
 
46
- Never put API keys in the frontend or in git.
 
 
 
 
47
 
48
- ## Run Locally
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  ```bash
51
  python -m venv venv
52
- .\venv\Scripts\Activate.ps1 # Windows
53
  pip install -r requirements.txt
54
- cp .env.example .env # add NVIDIA_API_KEY
55
  python app.py
56
  ```
57
 
58
- Open `http://127.0.0.1:7860`. Install `ffmpeg` locally if you use voice mode.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- ## Stack
61
 
62
- | Piece | Detail |
63
- |-------|--------|
64
- | Host | Gradio Server (`app.py`) on Hugging Face Spaces |
65
- | Model | NVIDIA Nemotron 3 Nano Omni 30B-A3B (backend API) |
66
- | UI | Custom HTML/CSS/JS — not default Gradio widgets |
 
4
  colorFrom: red
5
  colorTo: yellow
6
  sdk: gradio
7
+ sdk_version: 6.17.3
8
+ python_version: 3.12
9
  app_file: app.py
10
  pinned: true
11
+ license: mit
12
+ short_description: An AI founder pressure arena for practicing startup pitches.
13
+ tags:
14
+ - gradio
15
+ - build-small-hackathon
16
+ - backyard-ai
17
+ - startup
18
+ - pitch-practice
19
+ - founder-tooling
20
+ - voice-ai
21
+ - nvidia
22
+ - nemotron
23
+ - track:backyard
24
+ - sponsor:nvidia
25
+ - achievement:offbrand
26
+ - achievement:fieldnotes
27
+ - achievement:bestdemo
28
+ - best-demo
29
+ - community-choice
30
  ---
31
 
32
+ <div align="center">
33
 
34
+ # ⚔️ PitchFight AI
35
 
36
+ ### Your first tough pitch should not be in front of a real judge.
37
 
38
+ **An AI founder pressure arena where student builders practice startup pitches, survive judge-style questions, enter a deal round, and leave with a scorecard that shows exactly what to fix.**
39
 
40
+ **Try it:** [Live Space](YOUR_SPACE_LINK_HERE) · [Demo Video](YOUR_DEMO_VIDEO_LINK_HERE) · [Hugging Face Blog](YOUR_HF_BLOG_LINK_HERE) · [GitHub](YOUR_GITHUB_LINK_HERE)
41
 
42
+ <a href="YOUR_SPACE_LINK_HERE">
43
+ <img src="https://raw.githubusercontent.com/prakhar811/PitchFight/main/assets/pitchfight-landing.png" alt="PitchFight AI landing page" width="900"/>
44
+ </a>
 
45
 
46
+ **⚔️ Click the arena above to try PitchFight AI.**
47
 
48
+ </div>
49
+
50
+ ---
51
+
52
+ ## TL;DR for Judges
53
+
54
+ - **Backyard AI:** PitchFight AI helps student founders practice before hackathons, demo days, mentor reviews, and pitch rooms.
55
+ - **NVIDIA Nemotron Quest:** The judge reasoning is powered by NVIDIA Nemotron through the backend API.
56
+ - **Off Brand:** The app uses a custom cinematic frontend instead of default Gradio components.
57
+ - **Best Demo:** The submission includes a live Space, demo video, and Hugging Face Blog write-up.
58
+ - **Field Notes:** `FIELD_NOTES.md` and the Hugging Face Blog explain the product idea, build decisions, and technical approach.
59
+ - **Community Choice:** The public Space is available for users to try and upvote.
60
+ - **Judges' Wildcard:** PitchFight AI combines founder tooling, coaching, voice, and pressure simulation in one focused demo.
61
+
62
+ ---
63
+
64
+ ## Why this exists
65
+
66
+ Most student founders do not lose because their idea is bad.
67
+
68
+ They lose because the first hard question comes too late.
69
+
70
+ > "What is your moat?"
71
+ > "Who exactly pays?"
72
+ > "Why now?"
73
+ > "What happens if a bigger player copies this?"
74
+
75
+ PitchFight AI gives student builders a private practice room before the real room. It turns pitch practice into a pressure battle so founders can sharpen their story, defend their idea, and build confidence before facing real judges.
76
+
77
+ ---
78
+
79
+ ## What you can do
80
+
81
+ - **Enter your startup idea.** Start with a raw pitch or load a demo founder.
82
+ - **Get a founder briefing.** The system structures your idea into problem, solution, users, traction, competitors, and ask.
83
+ - **Choose your opponent.** Face a Skeptical VC, Technical Judge, or Hackathon Judge.
84
+ - **Pick the pressure level.** Practice Mode, Judge Mode, and Investor Mode change the intensity.
85
+ - **Survive pitch rounds.** Answer realistic follow-up questions based on your pitch.
86
+ - **Use voice mode.** Practice pitching and answering out loud.
87
+ - **Enter the deal phase.** Defend your ask in a negotiation-style pressure round.
88
+ - **Get a scorecard.** See what landed, what broke, and what to improve next.
89
+
90
+ ---
91
+
92
+ ## Demo
93
+
94
+ **Watch the demo:** [YOUR_DEMO_VIDEO_LINK_HERE](YOUR_DEMO_VIDEO_LINK_HERE)
95
+
96
+ <a href="YOUR_DEMO_VIDEO_LINK_HERE">
97
+ <img src="https://raw.githubusercontent.com/prakhar811/PitchFight/main/assets/pitchfight-battle.png" alt="PitchFight AI battle mode" width="900"/>
98
+ </a>
99
+
100
+ **Live Space:** [YOUR_SPACE_LINK_HERE](YOUR_SPACE_LINK_HERE)
101
+ **Build write-up:** [YOUR_HF_BLOG_LINK_HERE](YOUR_HF_BLOG_LINK_HERE)
102
+
103
+ ---
104
+
105
+ ## How it is built
106
+
107
+ PitchFight AI runs as a Hugging Face Gradio Space, but the user experience is built as a custom frontend rather than a default Gradio interface.
108
+
109
+ The frontend talks to backend routes in `app.py`. The backend handles pitch structuring, judge personas, battle state, voice mode, deal rounds, and scorecard generation. NVIDIA Nemotron is used as the core reasoning model for the AI judge.
110
+
111
+ ```mermaid
112
+ flowchart LR
113
+ founder[Student Founder] --> ui[Custom PitchFight Frontend]
114
+ ui --> api[Gradio Backend / app.py]
115
+ api --> router[Model Router]
116
+ router --> nemotron[NVIDIA Nemotron API]
117
+ nemotron --> judge[Judge Questions + Feedback]
118
+ judge --> ui
119
+ api --> scorecard[Scorecard + Coaching]
120
+ scorecard --> ui
121
  ```
122
+
123
+ ---
124
+
125
+ ## System flow
126
+
127
+ 1. The founder submits or records a startup pitch.
128
+ 2. The backend structures the pitch into a founder briefing.
129
+ 3. The founder chooses an opponent and difficulty mode.
130
+ 4. The AI judge asks multi-round follow-up questions.
131
+ 5. The founder answers under pressure.
132
+ 6. The system evaluates clarity, confidence, defensibility, and traction.
133
+ 7. The founder enters a deal-style round.
134
+ 8. The final scorecard highlights strengths, weak points, and retry suggestions.
135
+
136
+ ---
137
+
138
+ ## Tech stack
139
+
140
+ | Layer | Technology |
141
+ | -------------------- | ----------------------------------- |
142
+ | Hosting | Hugging Face Spaces |
143
+ | Runtime | Gradio |
144
+ | Backend | Python, `app.py`, custom API routes |
145
+ | Frontend | Custom HTML, CSS, JavaScript |
146
+ | AI Judge | NVIDIA Nemotron |
147
+ | Voice Support | Nemotron Omni / voice pipeline |
148
+ | Audio Support | ffmpeg |
149
+ | Optional Persistence | MongoDB |
150
+ | Deployment Secrets | Hugging Face Space Secrets |
151
+
152
+ ---
153
+
154
+ ## NVIDIA usage
155
+
156
+ PitchFight AI uses NVIDIA Nemotron as the core judge and reasoning model.
157
+
158
+ The backend calls the NVIDIA API through:
159
+
160
+ ```text
161
+ https://integrate.api.nvidia.com/v1
162
  ```
163
 
164
+ Primary model:
165
 
166
+ ```text
167
+ nvidia/nemotron-3-nano-omni-30b-a3b-reasoning
168
+ ```
169
 
170
+ The model is used for:
171
 
172
+ - founder brief structuring
173
+ - judge persona reasoning
174
+ - pitch battle follow-up questions
175
+ - deal round pressure
176
+ - final scorecard feedback
177
 
178
+ `NVIDIA_API_KEY` is stored only as a backend secret and is never exposed to frontend JavaScript.
179
+
180
+ ---
181
+
182
+ ## Built for the Build Small Hackathon
183
+
184
+ PitchFight AI was built for the Hugging Face **Build Small Hackathon**.
185
+
186
+ The goal was to create a focused AI product that feels genuinely useful through structured reasoning, judge personas, voice interaction, and a polished custom interface.
187
+
188
+ | Prize / Badge | Status | Why PitchFight AI qualifies |
189
+ | ------------------------- | --------- | --------------------------------------------------------------------------------- |
190
+ | **Backyard AI** | Submitted | Helps student founders practice for real-world pitch situations. |
191
+ | **NVIDIA Nemotron Quest** | Submitted | Uses NVIDIA Nemotron as the core reasoning model. |
192
+ | **Off Brand** | Submitted | Fully custom cinematic frontend, not default Gradio UI. |
193
+ | **Best Demo** | Submitted | Live Space, demo video, and blog write-up are linked. |
194
+ | **Field Notes** | Submitted | Hugging Face Blog and `FIELD_NOTES.md` explain the idea, build, and tech. |
195
+ | **Community Choice** | Eligible | Public Space is available for users to try and upvote. |
196
+ | **Judges' Wildcard** | Eligible | PitchFight AI combines founder tooling, coaching, voice, and pressure simulation. |
197
+
198
+ ### Not claiming
199
+
200
+ - **OpenBMB / MiniCPM:** PitchFight AI uses NVIDIA Nemotron, not MiniCPM.
201
+ - **Modal:** Not claimed unless Modal is used in development or runtime.
202
+ - **Tiny Titan:** Not claimed because the primary model is not ≤4B.
203
+ - **Off the Grid:** Not claimed because the app uses the NVIDIA hosted API.
204
+ - **Fine-tuning:** Not claimed. Inference uses the hosted Nemotron API only.
205
+
206
+ ---
207
+
208
+ ## Submission links
209
+
210
+ | Item | Link |
211
+ | ----------------- | ---------------------------------- |
212
+ | Live Space | YOUR_SPACE_LINK_HERE |
213
+ | Demo Video | YOUR_DEMO_VIDEO_LINK_HERE |
214
+ | Hugging Face Blog | YOUR_HF_BLOG_LINK_HERE |
215
+ | GitHub Repository | YOUR_GITHUB_LINK_HERE |
216
+ | Field Notes | [`FIELD_NOTES.md`](FIELD_NOTES.md) |
217
+
218
+ ---
219
+
220
+ ## Hugging Face deployment
221
+
222
+ Required Space secret:
223
+
224
+ ```bash
225
+ NVIDIA_API_KEY=
226
+ ```
227
+
228
+ Optional variables:
229
+
230
+ ```bash
231
+ APP_ENV=production
232
+ MAX_ROUNDS=6
233
+ DEFAULT_MODEL_MODE=premium_nvidia
234
+ NVIDIA_BASE_URL=https://integrate.api.nvidia.com/v1
235
+ NVIDIA_OMNI_MODEL=nvidia/nemotron-3-nano-omni-30b-a3b-reasoning
236
+ ENABLE_DECK_CRITIQUE=true
237
+ ENABLE_DEAL_BATTLE=true
238
+ ENABLE_VOICE_MODE=true
239
+ MONGODB_URI=
240
+ ```
241
+
242
+ Never put API keys in frontend code or commit real secrets to GitHub.
243
+
244
+ ---
245
+
246
+ ## Run locally
247
 
248
  ```bash
249
  python -m venv venv
250
+ .\venv\Scripts\Activate.ps1
251
  pip install -r requirements.txt
252
+ cp .env.example .env
253
  python app.py
254
  ```
255
 
256
+ Open:
257
+
258
+ ```bash
259
+ http://127.0.0.1:7860
260
+ ```
261
+
262
+ Install `ffmpeg` locally if using voice mode.
263
+
264
+ ---
265
+
266
+ ## Project structure
267
+
268
+ ```text
269
+ .
270
+ ├── app.py Gradio app entrypoint and backend routes
271
+ ├── requirements.txt Python dependencies
272
+ ├── packages.txt System packages such as ffmpeg
273
+ ├── .env.example Local environment variable template
274
+ ├── assets/ Screenshots for README and submission
275
+ ├── frontend/ Custom HTML, CSS, JS, and UI assets
276
+ ├── core/ Backend handlers, model router, clients
277
+ ├── FIELD_NOTES.md Build notes for the Field Notes badge
278
+ └── README.md Space card and hackathon submission write-up
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Safety note
284
+
285
+ PitchFight AI is a practice and coaching tool. It does not replace real mentors, investors, or legal/financial advisors. It is designed to help founders sharpen their pitch before entering the real room.
286
+
287
+ ---
288
+
289
+ <div align="center">
290
 
291
+ **If your pitch can survive the arena, it has a better chance in the room.**
292
 
293
+ </div>
 
 
 
 
frontend/script.js CHANGED
@@ -78,8 +78,28 @@ function showScreen(name) {
78
  const app = document.getElementById("app");
79
  app?.classList.toggle("app-arena-fullwidth", name === "battle" || name === "deal" || name === "scorecard" || name === "dealScorecard");
80
  app?.classList.toggle("app-scorecard-fullwidth", name === "scorecard");
81
- if (name === "landing" && landingIntroComplete) {
82
- finalizeLandingIntroStatic();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  }
84
  }
85
 
@@ -3013,6 +3033,7 @@ function boot() {
3013
  hideErrorBanner();
3014
  setBriefingMode("quick");
3015
  initBriefPreviewEditors();
 
3016
  initLandingIntro();
3017
  initScorecardTabs();
3018
 
 
78
  const app = document.getElementById("app");
79
  app?.classList.toggle("app-arena-fullwidth", name === "battle" || name === "deal" || name === "scorecard" || name === "dealScorecard");
80
  app?.classList.toggle("app-scorecard-fullwidth", name === "scorecard");
81
+ if (name === "landing") {
82
+ syncLandingViewport();
83
+ if (landingIntroComplete) {
84
+ finalizeLandingIntroStatic();
85
+ }
86
+ }
87
+ }
88
+
89
+ /* ---- Landing viewport sync (HF Spaces iframe-safe) ---- */
90
+
91
+ function syncLandingViewport() {
92
+ const vh = window.innerHeight;
93
+ if (!vh) return;
94
+ document.documentElement.style.setProperty("--landing-vh", `${vh}px`);
95
+ }
96
+
97
+ function initLandingViewport() {
98
+ syncLandingViewport();
99
+ window.addEventListener("resize", syncLandingViewport, { passive: true });
100
+ window.addEventListener("orientationchange", syncLandingViewport, { passive: true });
101
+ if (window.visualViewport) {
102
+ window.visualViewport.addEventListener("resize", syncLandingViewport, { passive: true });
103
  }
104
  }
105
 
 
3033
  hideErrorBanner();
3034
  setBriefingMode("quick");
3035
  initBriefPreviewEditors();
3036
+ initLandingViewport();
3037
  initLandingIntro();
3038
  initScorecardTabs();
3039
 
frontend/styles.css CHANGED
@@ -20,6 +20,11 @@
20
  --gold: var(--accent-gold);
21
  --border: var(--border-soft);
22
  --shadow: 0 12px 40px rgba(0, 0, 0, 0.55);
 
 
 
 
 
23
  }
24
 
25
  * {
@@ -29,22 +34,44 @@
29
  html {
30
  width: 100%;
31
  max-width: 100%;
 
 
 
32
  overflow-x: hidden;
33
  }
34
 
35
  body {
36
  margin: 0;
37
- min-height: 100vh;
 
38
  width: 100%;
39
  max-width: 100%;
40
  overflow-x: hidden;
41
  font-family: "Segoe UI", system-ui, sans-serif;
42
  color: var(--text);
43
- background: #09090f;
44
  font-size: 14px;
45
  line-height: 1.5;
46
  }
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  .bg-glow {
49
  position: fixed;
50
  inset: 0;
@@ -64,10 +91,29 @@ body {
64
 
65
  .app:has(#screen-battle.active),
66
  .app:has(#screen-deal.active),
 
 
67
  .app.app-arena-fullwidth:not(.app-scorecard-fullwidth) {
68
  max-width: none !important;
69
  width: 100% !important;
70
  padding: 0 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  }
72
 
73
  .screen {
@@ -127,20 +173,27 @@ h1 {
127
  ================================ */
128
 
129
  #screen-landing.active {
130
- display: block;
 
 
 
 
 
131
  }
132
 
133
  .arena-landing {
134
  position: relative;
135
- width: 100vw;
136
- min-height: auto;
137
- padding: clamp(0.5rem, 1.5vh, 1rem) 0;
138
- margin-left: calc(50% - 50vw);
139
- margin-right: calc(50% - 50vw);
 
140
  overflow: hidden;
141
  display: flex;
142
  align-items: center;
143
  justify-content: center;
 
144
  }
145
 
146
  /* ---- Landing Scene Layers (Pass 1.5) ---- */
@@ -747,9 +800,12 @@ h1 {
747
  position: relative;
748
  z-index: 2;
749
  text-align: center;
750
- padding: clamp(1rem, 3vw, 1.75rem) clamp(0.85rem, 2.5vw, 1.25rem) clamp(1rem, 2.5vw, 1.5rem);
751
- max-width: 820px;
752
  width: 100%;
 
 
 
 
 
753
  }
754
 
755
  .arena-eyebrow {
@@ -995,11 +1051,13 @@ h1 {
995
  50% { background-position: 0 100%; }
996
  }
997
 
998
- @media (max-width: 640px) {
999
- .arena-landing {
1000
- min-height: calc(100vh - 1rem);
1001
  }
 
1002
 
 
1003
  .founder-silhouette,
1004
  .judge-silhouette {
1005
  opacity: 0.65;
@@ -1135,6 +1193,10 @@ h1 {
1135
  .arena-feature-chips.visible {
1136
  opacity: 1 !important;
1137
  }
 
 
 
 
1138
  }
1139
 
1140
  .btn {
@@ -2861,30 +2923,48 @@ body.rounds-drawer-open {
2861
  margin: 0 auto;
2862
  }
2863
 
2864
- .app:has(#screen-scorecard.active),
2865
- .app.app-scorecard-fullwidth {
2866
- max-width: none !important;
2867
- width: 100% !important;
2868
- padding: clamp(0.5rem, 1.2vw, 0.85rem) !important;
 
 
 
 
 
 
 
 
 
 
2869
  }
2870
 
2871
  #screen-setup.active .briefing-shell-wide {
2872
- width: min(1180px, 92vw);
2873
- max-width: 100%;
2874
  margin: 0 auto;
2875
- padding-bottom: 0.5rem;
 
2876
  }
2877
 
2878
  .briefing-shell-wide {
2879
- max-width: min(1180px, 92vw);
2880
  width: 100%;
2881
  margin: 0 auto;
2882
  }
2883
 
 
 
 
 
 
 
 
2884
  .briefing-mode-tabs {
2885
  display: flex;
2886
  gap: 0.45rem;
2887
- margin-bottom: 0.75rem;
2888
  flex-wrap: wrap;
2889
  }
2890
 
@@ -2916,15 +2996,15 @@ body.rounds-drawer-open {
2916
  }
2917
 
2918
  .briefing-grid-wide {
2919
- grid-template-columns: minmax(0, 1.12fr) minmax(280px, 0.88fr);
2920
- gap: 0.85rem;
2921
  align-items: stretch;
2922
  }
2923
 
2924
  .briefing-left-col {
2925
- display: grid;
2926
- grid-template-columns: minmax(0, 1fr);
2927
- align-items: stretch;
2928
  min-width: 0;
2929
  overflow: visible;
2930
  }
@@ -2933,6 +3013,13 @@ body.rounds-drawer-open {
2933
  min-height: 0;
2934
  }
2935
 
 
 
 
 
 
 
 
2936
  .briefing-left-col.mode-advanced .quick-pitch-panel,
2937
  .briefing-left-col.mode-advanced .brief-preview-panel {
2938
  display: none !important;
@@ -2948,7 +3035,10 @@ body.rounds-drawer-open {
2948
  }
2949
 
2950
  .quick-pitch-panel {
 
 
2951
  padding: clamp(0.75rem, 1.8vw, 0.95rem) clamp(0.85rem, 1.8vw, 1rem);
 
2952
  }
2953
 
2954
  .quick-pitch-label {
@@ -2962,8 +3052,9 @@ body.rounds-drawer-open {
2962
 
2963
  .quick-pitch-textarea {
2964
  width: 100%;
 
2965
  min-height: clamp(96px, 14vh, 128px);
2966
- max-height: clamp(120px, 18vh, 150px);
2967
  padding: 0.7rem 0.85rem;
2968
  border-radius: 12px;
2969
  border: 1px solid rgba(255, 255, 255, 0.12);
@@ -2991,7 +3082,8 @@ body.rounds-drawer-open {
2991
  display: flex;
2992
  flex-direction: column;
2993
  gap: 0.55rem;
2994
- margin-top: 0.75rem;
 
2995
  }
2996
 
2997
  .quick-pitch-actions .btn-wide {
@@ -3313,64 +3405,66 @@ body.rounds-drawer-open {
3313
 
3314
  .briefing-opponent-panel .briefing-section-title,
3315
  .briefing-opponent-panel .difficulty-selector h3 {
3316
- margin: 0 0 0.4rem;
3317
  font-size: 0.82rem;
3318
  letter-spacing: 0.08em;
3319
  }
3320
 
3321
  .briefing-opponent-panel .persona-grid {
3322
  grid-template-columns: repeat(3, minmax(0, 1fr));
3323
- gap: 0.45rem;
3324
- margin-top: 0.35rem;
3325
  }
3326
 
3327
  .briefing-opponent-panel .difficulty-grid {
3328
  grid-template-columns: repeat(3, minmax(0, 1fr));
3329
- gap: 0.45rem;
3330
  }
3331
 
3332
- .briefing-opponent-panel .persona-card {
3333
- padding: 0.5rem 0.55rem;
 
 
 
 
 
3334
  border-radius: 10px;
3335
  }
3336
 
3337
  .briefing-opponent-panel .persona-card .persona-icon {
3338
- font-size: 1rem;
3339
  line-height: 1;
 
 
3340
  }
3341
 
3342
  .briefing-opponent-panel .persona-card h3 {
3343
- margin: 0.25rem 0 0.15rem;
3344
  font-size: 0.8rem;
3345
  }
3346
 
3347
  .briefing-opponent-panel .persona-card p {
3348
  font-size: 0.68rem;
3349
- line-height: 1.25;
3350
  }
3351
 
3352
  .briefing-opponent-panel .difficulty-selector {
3353
- margin-top: 0.65rem;
3354
- }
3355
-
3356
- .briefing-opponent-panel .difficulty-card {
3357
- padding: 0.5rem 0.55rem;
3358
- border-radius: 10px;
3359
  }
3360
 
3361
  .briefing-opponent-panel .difficulty-card .difficulty-icon {
3362
- font-size: 1rem;
3363
- margin-bottom: 0.15rem;
3364
  }
3365
 
3366
  .briefing-opponent-panel .difficulty-card h4 {
3367
- margin: 0 0 0.1rem;
3368
  font-size: 0.78rem;
3369
  }
3370
 
3371
  .briefing-opponent-panel .difficulty-card p {
3372
  font-size: 0.66rem;
3373
- line-height: 1.2;
3374
  }
3375
 
3376
  .briefing-opponent-note {
@@ -3387,13 +3481,13 @@ body.rounds-drawer-open {
3387
  align-self: stretch;
3388
  min-height: 100%;
3389
  height: 100%;
3390
- gap: 0;
3391
  padding: clamp(0.7rem, 1.6vw, 0.85rem) clamp(0.8rem, 1.8vw, 0.95rem);
3392
  }
3393
 
3394
  .briefing-opponent-footer {
3395
  margin-top: auto;
3396
- padding-top: 1rem;
3397
  display: flex;
3398
  flex-direction: column;
3399
  gap: 0.55rem;
@@ -3412,7 +3506,14 @@ body.rounds-drawer-open {
3412
  justify-content: space-between;
3413
  align-items: flex-start;
3414
  gap: 1rem;
3415
- margin-bottom: 0.75rem;
 
 
 
 
 
 
 
3416
  }
3417
 
3418
  .briefing-title {
@@ -8672,14 +8773,20 @@ blockquote.sc-answer-text {
8672
  }
8673
 
8674
  @media (max-height: 760px) {
8675
- .arena-landing {
8676
- min-height: auto;
8677
- padding: 0.5rem 0;
 
8678
  }
8679
 
8680
- .arena-landing-content {
8681
- padding-top: 0.75rem;
8682
- padding-bottom: 0.75rem;
 
 
 
 
 
8683
  }
8684
 
8685
  .founder-silhouette,
@@ -8717,7 +8824,8 @@ blockquote.sc-answer-text {
8717
 
8718
  .briefing-opponent-panel .persona-card,
8719
  .briefing-opponent-panel .difficulty-card {
8720
- padding: 0.45rem 0.5rem;
 
8721
  }
8722
 
8723
  .persona-card,
 
20
  --gold: var(--accent-gold);
21
  --border: var(--border-soft);
22
  --shadow: 0 12px 40px rgba(0, 0, 0, 0.55);
23
+
24
+ /* Landing — HF-safe viewport (JS sets --landing-vh from innerHeight) */
25
+ --landing-vh: 100svh;
26
+ --landing-safe-pad-y: clamp(12px, 2vh, 28px);
27
+ --landing-safe-pad-x: clamp(12px, 2vw, 24px);
28
  }
29
 
30
  * {
 
34
  html {
35
  width: 100%;
36
  max-width: 100%;
37
+ height: 100%;
38
+ margin: 0;
39
+ padding: 0;
40
  overflow-x: hidden;
41
  }
42
 
43
  body {
44
  margin: 0;
45
+ padding: 0;
46
+ min-height: 100%;
47
  width: 100%;
48
  max-width: 100%;
49
  overflow-x: hidden;
50
  font-family: "Segoe UI", system-ui, sans-serif;
51
  color: var(--text);
52
+ background: var(--bg-deep);
53
  font-size: 14px;
54
  line-height: 1.5;
55
  }
56
 
57
+ html:has(#screen-landing.active) {
58
+ height: var(--landing-vh, 100svh);
59
+ height: var(--landing-vh, 100dvh);
60
+ overflow: hidden;
61
+ }
62
+
63
+ body:has(#screen-landing.active) {
64
+ overflow: hidden;
65
+ min-height: var(--landing-vh, 100svh);
66
+ min-height: var(--landing-vh, 100dvh);
67
+ height: var(--landing-vh, 100svh);
68
+ height: var(--landing-vh, 100dvh);
69
+ }
70
+
71
+ body:has(#screen-setup.active) {
72
+ overflow: hidden;
73
+ }
74
+
75
  .bg-glow {
76
  position: fixed;
77
  inset: 0;
 
91
 
92
  .app:has(#screen-battle.active),
93
  .app:has(#screen-deal.active),
94
+ .app:has(#screen-landing.active),
95
+ .app:has(#screen-setup.active),
96
  .app.app-arena-fullwidth:not(.app-scorecard-fullwidth) {
97
  max-width: none !important;
98
  width: 100% !important;
99
  padding: 0 !important;
100
+ margin: 0 !important;
101
+ }
102
+
103
+ .app:has(#screen-landing.active) {
104
+ display: flex;
105
+ flex-direction: column;
106
+ min-height: var(--landing-vh, 100svh);
107
+ min-height: var(--landing-vh, 100dvh);
108
+ height: var(--landing-vh, 100svh);
109
+ height: var(--landing-vh, 100dvh);
110
+ max-height: var(--landing-vh, 100svh);
111
+ max-height: var(--landing-vh, 100dvh);
112
+ overflow: hidden;
113
+ }
114
+
115
+ .app:has(#screen-setup.active) {
116
+ min-height: calc(100dvh - 56px);
117
  }
118
 
119
  .screen {
 
173
  ================================ */
174
 
175
  #screen-landing.active {
176
+ display: flex;
177
+ flex-direction: column;
178
+ flex: 1 1 auto;
179
+ width: 100%;
180
+ min-height: 100%;
181
+ overflow: hidden;
182
  }
183
 
184
  .arena-landing {
185
  position: relative;
186
+ width: 100%;
187
+ flex: 1 1 auto;
188
+ min-height: 100%;
189
+ box-sizing: border-box;
190
+ padding: var(--landing-safe-pad-y) var(--landing-safe-pad-x);
191
+ margin: 0;
192
  overflow: hidden;
193
  display: flex;
194
  align-items: center;
195
  justify-content: center;
196
+ background: var(--bg-deep);
197
  }
198
 
199
  /* ---- Landing Scene Layers (Pass 1.5) ---- */
 
800
  position: relative;
801
  z-index: 2;
802
  text-align: center;
 
 
803
  width: 100%;
804
+ max-width: 820px;
805
+ max-height: 100%;
806
+ padding: clamp(0.5rem, 1.5vh, 1rem) clamp(0.85rem, 2.5vw, 1.25rem);
807
+ transform-origin: center center;
808
+ overflow: hidden;
809
  }
810
 
811
  .arena-eyebrow {
 
1051
  50% { background-position: 0 100%; }
1052
  }
1053
 
1054
+ @media (max-height: 860px) {
1055
+ .arena-landing-content {
1056
+ transform: scale(0.96);
1057
  }
1058
+ }
1059
 
1060
+ @media (max-width: 640px) {
1061
  .founder-silhouette,
1062
  .judge-silhouette {
1063
  opacity: 0.65;
 
1193
  .arena-feature-chips.visible {
1194
  opacity: 1 !important;
1195
  }
1196
+
1197
+ .arena-landing-content {
1198
+ transform: none !important;
1199
+ }
1200
  }
1201
 
1202
  .btn {
 
2923
  margin: 0 auto;
2924
  }
2925
 
2926
+ #screen-setup.active {
2927
+ display: flex;
2928
+ flex-direction: column;
2929
+ align-items: center;
2930
+ justify-content: center;
2931
+ box-sizing: border-box;
2932
+ width: 100%;
2933
+ min-height: calc(100dvh - 56px);
2934
+ max-height: calc(100dvh - 56px);
2935
+ overflow-x: hidden;
2936
+ overflow-y: auto;
2937
+ padding:
2938
+ clamp(1rem, 2vh, 1.75rem)
2939
+ clamp(1rem, 2.5vw, 1.75rem)
2940
+ clamp(2.5rem, 6vh, 4.5rem);
2941
  }
2942
 
2943
  #screen-setup.active .briefing-shell-wide {
2944
+ width: 100%;
2945
+ max-width: min(1400px, calc(100vw - 2.5rem));
2946
  margin: 0 auto;
2947
+ padding: 0;
2948
+ transform: translateY(clamp(-4px, -0.75vh, -10px));
2949
  }
2950
 
2951
  .briefing-shell-wide {
2952
+ max-width: min(1400px, calc(100vw - 2.5rem));
2953
  width: 100%;
2954
  margin: 0 auto;
2955
  }
2956
 
2957
+ .app:has(#screen-scorecard.active),
2958
+ .app.app-scorecard-fullwidth {
2959
+ max-width: none !important;
2960
+ width: 100% !important;
2961
+ padding: clamp(0.5rem, 1.2vw, 0.85rem) !important;
2962
+ }
2963
+
2964
  .briefing-mode-tabs {
2965
  display: flex;
2966
  gap: 0.45rem;
2967
+ margin-bottom: clamp(0.9rem, 1.8vh, 1.2rem);
2968
  flex-wrap: wrap;
2969
  }
2970
 
 
2996
  }
2997
 
2998
  .briefing-grid-wide {
2999
+ grid-template-columns: minmax(0, 1.12fr) minmax(300px, 0.88fr);
3000
+ gap: clamp(0.85rem, 1.6vw, 1.1rem);
3001
  align-items: stretch;
3002
  }
3003
 
3004
  .briefing-left-col {
3005
+ display: flex;
3006
+ flex-direction: column;
3007
+ min-height: 100%;
3008
  min-width: 0;
3009
  overflow: visible;
3010
  }
 
3013
  min-height: 0;
3014
  }
3015
 
3016
+ .briefing-left-col > .panel:not([hidden]) {
3017
+ flex: 1 1 auto;
3018
+ display: flex;
3019
+ flex-direction: column;
3020
+ min-height: 100%;
3021
+ }
3022
+
3023
  .briefing-left-col.mode-advanced .quick-pitch-panel,
3024
  .briefing-left-col.mode-advanced .brief-preview-panel {
3025
  display: none !important;
 
3035
  }
3036
 
3037
  .quick-pitch-panel {
3038
+ display: flex;
3039
+ flex-direction: column;
3040
  padding: clamp(0.75rem, 1.8vw, 0.95rem) clamp(0.85rem, 1.8vw, 1rem);
3041
+ min-height: 100%;
3042
  }
3043
 
3044
  .quick-pitch-label {
 
3052
 
3053
  .quick-pitch-textarea {
3054
  width: 100%;
3055
+ flex: 1 1 auto;
3056
  min-height: clamp(96px, 14vh, 128px);
3057
+ max-height: clamp(140px, 22vh, 190px);
3058
  padding: 0.7rem 0.85rem;
3059
  border-radius: 12px;
3060
  border: 1px solid rgba(255, 255, 255, 0.12);
 
3082
  display: flex;
3083
  flex-direction: column;
3084
  gap: 0.55rem;
3085
+ margin-top: auto;
3086
+ padding-top: 0.85rem;
3087
  }
3088
 
3089
  .quick-pitch-actions .btn-wide {
 
3405
 
3406
  .briefing-opponent-panel .briefing-section-title,
3407
  .briefing-opponent-panel .difficulty-selector h3 {
3408
+ margin: 0 0 0.5rem;
3409
  font-size: 0.82rem;
3410
  letter-spacing: 0.08em;
3411
  }
3412
 
3413
  .briefing-opponent-panel .persona-grid {
3414
  grid-template-columns: repeat(3, minmax(0, 1fr));
3415
+ gap: 0.55rem;
3416
+ margin-top: 0.45rem;
3417
  }
3418
 
3419
  .briefing-opponent-panel .difficulty-grid {
3420
  grid-template-columns: repeat(3, minmax(0, 1fr));
3421
+ gap: 0.55rem;
3422
  }
3423
 
3424
+ .briefing-opponent-panel .persona-card,
3425
+ .briefing-opponent-panel .difficulty-card {
3426
+ display: flex;
3427
+ flex-direction: column;
3428
+ justify-content: flex-start;
3429
+ min-height: clamp(78px, 10.5vh, 94px);
3430
+ padding: clamp(0.62rem, 1.1vh, 0.74rem) clamp(0.58rem, 1vw, 0.68rem);
3431
  border-radius: 10px;
3432
  }
3433
 
3434
  .briefing-opponent-panel .persona-card .persona-icon {
3435
+ font-size: 1.15rem;
3436
  line-height: 1;
3437
+ display: block;
3438
+ margin-bottom: 0.24rem;
3439
  }
3440
 
3441
  .briefing-opponent-panel .persona-card h3 {
3442
+ margin: 0.32rem 0 0.2rem;
3443
  font-size: 0.8rem;
3444
  }
3445
 
3446
  .briefing-opponent-panel .persona-card p {
3447
  font-size: 0.68rem;
3448
+ line-height: 1.3;
3449
  }
3450
 
3451
  .briefing-opponent-panel .difficulty-selector {
3452
+ margin-top: clamp(0.85rem, 1.8vh, 1.15rem);
 
 
 
 
 
3453
  }
3454
 
3455
  .briefing-opponent-panel .difficulty-card .difficulty-icon {
3456
+ font-size: 1.15rem;
3457
+ margin-bottom: 0.24rem;
3458
  }
3459
 
3460
  .briefing-opponent-panel .difficulty-card h4 {
3461
+ margin: 0 0 0.18rem;
3462
  font-size: 0.78rem;
3463
  }
3464
 
3465
  .briefing-opponent-panel .difficulty-card p {
3466
  font-size: 0.66rem;
3467
+ line-height: 1.28;
3468
  }
3469
 
3470
  .briefing-opponent-note {
 
3481
  align-self: stretch;
3482
  min-height: 100%;
3483
  height: 100%;
3484
+ gap: clamp(0.45rem, 0.9vh, 0.65rem);
3485
  padding: clamp(0.7rem, 1.6vw, 0.85rem) clamp(0.8rem, 1.8vw, 0.95rem);
3486
  }
3487
 
3488
  .briefing-opponent-footer {
3489
  margin-top: auto;
3490
+ padding-top: clamp(0.65rem, 1.2vh, 0.85rem);
3491
  display: flex;
3492
  flex-direction: column;
3493
  gap: 0.55rem;
 
3506
  justify-content: space-between;
3507
  align-items: flex-start;
3508
  gap: 1rem;
3509
+ width: 100%;
3510
+ margin-bottom: clamp(1rem, 2vh, 1.35rem);
3511
+ }
3512
+
3513
+ .briefing-header .btn-ghost {
3514
+ flex-shrink: 0;
3515
+ align-self: flex-start;
3516
+ margin-top: 0.15rem;
3517
  }
3518
 
3519
  .briefing-title {
 
8773
  }
8774
 
8775
  @media (max-height: 760px) {
8776
+ .arena-landing-content {
8777
+ padding-top: 0.5rem;
8778
+ padding-bottom: 0.5rem;
8779
+ transform: scale(0.9);
8780
  }
8781
 
8782
+ #screen-setup.active {
8783
+ justify-content: flex-start;
8784
+ padding-top: 0.85rem;
8785
+ padding-bottom: 1.25rem;
8786
+ }
8787
+
8788
+ #screen-setup.active .briefing-shell-wide {
8789
+ transform: none;
8790
  }
8791
 
8792
  .founder-silhouette,
 
8824
 
8825
  .briefing-opponent-panel .persona-card,
8826
  .briefing-opponent-panel .difficulty-card {
8827
+ padding: clamp(0.52rem, 0.95vh, 0.62rem) clamp(0.5rem, 0.9vw, 0.58rem);
8828
+ min-height: clamp(68px, 9vh, 78px);
8829
  }
8830
 
8831
  .persona-card,