# Project export: SIM: Sales Incentive Machine

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: SIM turns your sales data into a contest your team actually plays, AI designs the goals, prints the bingo cards, and spins the prize wheel.
- Devpost: https://devpost.com/software/sim-sales-incentive-machine
- GitHub: https://github.com/estevanhernandez-stack-ed/sales-incentive-machine
- Video: https://www.youtube.com/embed/XFfKLfKBDHE?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Estevan Hernandez (27 commits), Claude Fable 5 (19 commits)

## Devpost submission (written by the team)

### Inspiration

I had been meaning to scaffold this one for a while. The idea started at my day job, where sales contests are a real thing that runs on whiteboards, spreadsheets, and whoever remembers what changed. I had also started sketching a version of it for a gaming group, as a scoreboard for friendly competition as I always have trouble imagining fake data. Neither version got far, but the ideas were all resting. Build Week was the forcing function. It let me restart from zero, aim the idea at restaurant sales where the mechanics are sharpest, and use the whole thing as a real test of what Codex and GPT-5.6 can carry. A hackathon deadline is a better project manager than good intentions.

### What it does

SIM runs a weekly sales contest for a restaurant floor team, end to end. Managers set a goal and a prize, then describe the contest they want in plain words. GPT-5.6 turns that into a validated contest configuration. Servers compete on a live dashboard that ranks them across sales metrics, contest goals, and daily Bingo wins. Shift leaders log contest quantities and returned Bingo cards during service. Everything funnels into one auditable prize drawing at the end of the week. The pieces: Live dashboard. Every server ranked against the active contest, with switchable performance lenses, current prize, target, last winner, and deadline always visible. AI Contest Designer. A manager's plain-words goal becomes a validated contest config: GPT-5.6 structured output under a strict JSON schema, then local validation of every menu ID and contest rule. Printable Server Bingo. Randomized five-by-five cards drawn from the active contest's menu pool, one clean card per page, legible at arm's length. Sales games. The same live totals drive a visible floor race and a shared goal board. Final awards convert into wheel entries. Auditable prize wheel. The draw resolves and saves an immutable snapshot first. Only then does the wheel animate toward the already-recorded winner. The show stays exciting without letting the animation decide anything. Sales data entry and import. Shift leaders add contest-only quantities without inventing check data, and managers import their own sales through a shared spreadsheet schema. Corrections are preserved in an audit trail. Every screen works on a fresh clone with no API key and no account. All data shown is fictional. How I built it Next.js 15 App Router with TypeScript and Tailwind, React 19, SQLite through better-sqlite3 with plain readable SQL, Vitest for the metric formulas. Local-first by design: npm install && npm run seed && npm run dev and you have a working app. No auth, no cloud, no deployment step, no POS integration. I wrote a specification first, then built it in Codex sessions against that spec. The spec defined the data model, the exact metric formulas, the contest config shape, and a cut list of things explicitly not being built. Having the cut list in writing mattered more than I expected; it was the thing I pointed at whenever a session started drifting toward scope that would not ship by Tuesday. Two rules shaped most of the architecture. Metrics are always computed in queries and never stored, so no number on screen can drift from the sales records behind it. And the no-key fallback is sacred: if OPENAI_API_KEY is missing, the Contest Designer serves a versioned sample config instead of throwing, so the whole product stays demoable for anyone who just cloned it. The MCP angle. This is the part I did not expect going in. I built two local MCP servers so an agent could operate the app the way a manager or shift leader actually would: sim_boh_pos records fully itemized fictional closed checks as a synthetic point-of-sale feed. sim_ops reads runbooks and current state, previews irreversible operations before committing them, executes role-aware idempotent operations, and returns receipts plus screenshot paths. On top of those, the repo carries canonical JSON runbooks that are both human documentation and agent-executable scripts. An agent can run the Contest Manager or Shift Manager workflow end to end against a disposable copy of the database, produce reconciled receipts and screenshot evidence, and hand back a run package that a verifier script checks for structural completeness. It is a way of testing an app through its actual operator workflows instead of through unit tests alone. Both MCP servers are local STDIO processes, and sim_ops refuses to talk to anything that is not localhost. The demo video is built the same way: demo/manifest.json is the scene plan, a capture script drives Playwright against the running app, and an FFmpeg renderer assembles the master. The prompt that specified that pipeline is versioned in the repo next to the code it produced. Challenges I ran into Keeping the demo honest. Early on it was tempting to record the Contest Designer with a key configured and quietly skip what happens without one. Instead the video shows a live API response and states the fallback behavior plainly. The fallback is a feature, not an embarrassment, and pretending otherwise would have made the whole demo less trustworthy. Animation cannot decide the outcome. The first prize wheel resolved the winner from wherever the animation happened to stop. That is a fun spinner and a terrible system of record. Rewriting it so the draw persists first and the wheel animates toward a recorded result was a small change with a large consequence: the drawing became auditable, and a manager can prove after the fact who won and why. Agent rehearsals found less than I hoped. I invested real time in agent-executable runbooks expecting them to surface product defects. They mostly proved the app already worked and surfaced gaps in my own evidence contract instead. Useful, but not the payoff I had planned. Knowing which techniques do not pay off is worth something, and it is the honest result. Working across two AI tools. Core functionality was built in Codex. I also kept a reviewer in the loop to audit completion claims against the written spec line by line. That division caught real things and kept "it runs" from being mistaken for "it is done." Accomplishments that I'm proud of The zero-config promise holds. Clone it, seed it, run it, and every feature is demoable in under a minute with no key and no account. The prize drawing is genuinely auditable. Immutable entry snapshot, recorded winner, visible drawing history, and an animation that reports the result instead of producing it. The demo video is reproducible from the repository rather than hand-edited once and lost. The scene plan is a versioned artifact, and so is the prompt that generated the pipeline. 45 tests green, production build clean, everything committed. What I learned Writing the specification before opening a Codex session changed the quality of what came out of it. So did having a completion definition that meant something specific: acceptance criteria met, tests green, build passing, work committed, docs current. "The code runs" is not a finish line. The other lesson is that prompts are code. I lost a theme-generator prompt to a chat window mid-build and had to reconstruct it. Any prompt that generates part of the product now lives in the repo, versioned next to the schema it produces.

### What's next

for SIM Remote sales entry first: a simple phone panel for shift managers that feeds the live dashboard during service, so entry happens on the floor instead of at the back office computer. After that, the list the rehearsals actually earned. Pre-finalization reconciliation so every tally is checked before a contest closes. Wheel provenance with visible odds, so servers can see exactly why they had the entries they had. Role-guarded lock and award controls, so a shift leader cannot trip a manager-only action.

## README (from the GitHub repository)

# Sales Incentive Machine

SIM is a local-first restaurant sales-contest workspace. Managers configure goals and prizes, shift leaders enter live contest quantities and returned Bingo cards, and the team can present gameboards and one auditable prize drawing.

Built for OpenAI Build Week in Codex with GPT-5.6. [Watch the demo](https://www.youtube.com/watch?v=XFfKLfKBDHE).

**Judging this?** Start with [JUDGES.md](./JUDGES.md) for a five-minute guided tour.

All included restaurant, server, menu, check, and contest data is fictional.

## How Codex and GPT-5.6 built this

**Codex wrote the product.** Every feature in this repository was built in Codex sessions against a written specification (`docs/superpowers/specs/2026-07-13-sim-design.md`), which defined the data model, exact metric formulas, contest config shape, acceptance criteria, and an explicit cut list. Session IDs for each build thread are logged in [SUBMISSION.md](./SUBMISSION.md). The model was GPT-5.6 at high reasoning effort throughout.

**GPT-5.6 is also a runtime dependency.** The Contest Designer (`app/api/contest-designer/route.ts`) is the product's AI surface: a manager describes the contest they want in plain words, and GPT-5.6 returns a complete contest configuration through the Responses API using strict `json_schema` structured output. The response is then validated locally against real menu IDs and contest rules. A validation failure feeds the specific error back into a second attempt; if that also fails, or if no key is present, the app loads a versioned sample config instead of erroring. The AI writes the contest, but the app decides what is valid.

**Codex agents operated the app through MCP.** The repo ships two local Model Context Protocol servers so a Codex agent could drive the product the way a real operator would:

- `sim_boh_pos` records fully itemized fictional closed checks as a synthetic point-of-sale feed.
- `sim_ops` reads runbooks and current state, previews irreversible operations before committing them, executes role-aware idempotent operations, and returns receipts plus screenshot paths.

Paired with the agent-executable runbooks in `runbooks/`, this let Codex rehearse the full Contest Manager and Shift Manager workflows against a disposable copy of the database, then hand back a run package of reconciled receipts and screenshot evidence that `npm run runbook:verify` checks for completeness. Testing the app through its actual operator workflows, not only through unit tests.

**Codex built the demo pipeline too.** The submission video is reproducible from this repository: `demo/manifest.json` is the scene plan, `scripts/demo-video/capture.mjs` drives Playwright against the running app, and `scripts/demo-video/build.mjs` renders the master with FFmpeg. The prompt that specified that pipeline is versioned at `docs/prompts/demo-video.md`, next to the code it produced.

## Run it without installing anything

[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/estevanhernandez-stack-ed/sales-incentive-machine)

Click the badge. The container installs dependencies, seeds the deterministic fictional database, starts the dev server, and opens SIM on the forwarded port 3000. No key, no account, no local setup.

If the preview does not open on its own, run `npm run dev` in the Codespace terminal and open the forwarded port. Setup is already done by then; the database is seeded during container creation.

There is no hosted public instance by design: SIM is local-first, writes to a SQLite file, and ships with a deterministic seed so every run starts from the same known state.

## Start locally

```powershell
npm install
npm run seed
npm run dev
```

Open `http://127.0.0.1:3000`. No environment variable or OpenAI key is required. When no key is configured, Contest setup uses the versioned fallback contest.

## Enable the live Contest Designer (optional)

Create `.env.local` in the repo root:

```
OPENAI_API_KEY=sk-...
```

With a key set, Contest setup sends the manager's plain-words goal to GPT-5.6 (override with `OPENAI_MODEL`) under a strict JSON schema, retries once on validation failure, and still falls back to the versioned sample config if both attempts miss. Without a key, every feature stays fully demoable.

## Demo video pipeline

The submission video is reproducible from the repo: `demo/manifest.json` is the scene plan, `node scripts/demo-video/capture.mjs demo/manifest.json` captures footage from the running app, and `node scripts/demo-video/build.mjs demo/manifest.json` renders the master (ffmpeg required on PATH). The prompt that specified the pipeline is versioned at `docs/prompts/demo-video.md`.

## Operating guides

- [Contest Manager runbook](./docs/runbooks/contest-manager.md)
- [Shift Manager runbook](./docs/runbooks/shift-manager.md)
- [Agent-executable runbook specification](./docs/superpowers/specs/2026-07-17-agent-executable-runbooks.md)
- [Local operations API and MCP guide](./docs/runbooks/operations-api.md)

The human guides, agent prompts, screenshot checklist, and verifier all use the canonical JSON manifests in `runbooks/`.

## Disposable discovery runs

```powershell
npm run runbook:scaffold -- --scenario shift-live-entry
npm run runbook:serve -- --run <run-id> --port 3100
npm run runbook:verify -- --run <run-id>
```

Each run receives a copy of `data/sim.db`; operating the run never changes the source database. Run artifacts live under the ignored `artifacts/runbook-runs/` directory.

Available scenarios are `manager-item-contest`, `manager-late-information`, `shift-live-entry`, and `shift-error-recovery`.

## Local agent tools

The project registers two separate optional MCP servers:

- `sim_boh_pos` records fully itemized fictional closed checks as a synthetic POS feed.
- `sim_ops` reads runbooks and current state, previews irreversible work, executes role-aware idempotent operations, and returns receipts plus screenshot paths.

Both are local STDIO processes. `sim_ops` only connects to an HTTP origin on `localhost`, `127.0.0.1`, or `::1`. Restart Codex after changing `.codex/config.toml` so the project MCP registrations reload.

## Verification

```powershell
npm test
npm run build
```


## Detected evidence (automated analysis)

Indexed codebase: 95 recognized source files, 466 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- TypeScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- Tailwind CSS (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (120 of 127)

```
.codex/config.toml
.devcontainer/devcontainer.json
.gitignore
AGENTS.md
agents/runbooks/base-operator.md
agents/runbooks/blind-reviewer.md
agents/runbooks/contest-manager-operator.md
agents/runbooks/shift-manager-operator.md
app/api/bingo/cards/[serverId]/route.ts
app/api/bingo/submissions/route.ts
app/api/contest-designer/activate/route.ts
app/api/contest-designer/route.ts
app/api/games/mission/award/route.ts
app/api/games/race/lock/route.ts
app/api/ops/commands/route.ts
app/api/ops/operations/[operationId]/route.ts
app/api/ops/operations/route.ts
app/api/ops/preview/contest/route.ts
app/api/ops/preview/game/route.ts
app/api/ops/preview/wheel/route.ts
app/api/ops/runbooks/[role]/route.ts
app/api/ops/snapshot/route.ts
app/api/sales-data/checks/[checkId]/route.ts
app/api/sales-data/contest-score/route.ts
app/api/sales-data/export/route.ts
app/api/sales-data/import/route.ts
app/api/sales-data/manual/route.ts
app/api/sales-data/servers/[serverId]/checks/route.ts
app/api/sales-data/template/route.ts
app/api/wheel/draw/route.ts
app/bingo/page.tsx
app/contest/page.tsx
app/data/page.tsx
app/games/page.tsx
app/globals.css
app/layout.tsx
app/page.tsx
app/wheel/page.tsx
app/workspace.css
components/app-nav.tsx
components/bingo-manager.tsx
components/contest-designer.tsx
components/game-hub.tsx
components/leaderboard.tsx
components/page-header.tsx
components/prize-wheel.tsx
components/sales-data-manager.tsx
components/theme-manager.tsx
demo/demo-script.md
demo/manifest.json
docs/AGENT_THEME_PROMPT.md
docs/buildweek-hq.html
docs/devpost-submission.md
docs/MCP_BOH_POS.md
docs/prompts/demo-video.md
docs/runbooks/contest-manager.md
docs/runbooks/operations-api.md
docs/runbooks/README.md
docs/runbooks/shift-manager.md
docs/superpowers/plans/2026-07-13-sim.md
docs/superpowers/specs/2026-07-13-sim-design.md
docs/superpowers/specs/2026-07-14-sales-data-workbench.md
docs/superpowers/specs/2026-07-17-agent-executable-runbooks.md
JUDGES.md
lib/contest-designer.test.ts
lib/contest-designer.ts
lib/db/bingo.test.ts
lib/db/bingo.ts
lib/db/client.ts
lib/db/contest.ts
lib/db/dashboard.ts
lib/db/games.test.ts
lib/db/games.ts
lib/db/index.ts
lib/db/metrics.test.ts
lib/db/metrics.ts
lib/db/sales-data.test.ts
lib/db/sales-data.ts
lib/db/schema.sql
lib/db/wheel.test.ts
lib/db/wheel.ts
lib/ops/errors.ts
lib/ops/http.ts
lib/ops/service.test.ts
lib/ops/service.ts
lib/ops/types.ts
lib/runbooks/catalog.test.ts
lib/runbooks/catalog.ts
lib/runbooks/types.ts
lib/smoke.test.ts
lib/theme.test.ts
lib/theme.ts
LICENSE
mcp/boh-pos-core.mjs
mcp/boh-pos-server.mjs
mcp/boh-pos.test.mjs
mcp/sim-ops-core.mjs
mcp/sim-ops-protocol.test.mjs
mcp/sim-ops-server.mjs
mcp/sim-ops.test.mjs
next-env.d.ts
next.config.ts
package.json
postcss.config.mjs
README.md
runbooks/contest-manager.json
runbooks/scenario-schema.json
runbooks/scenarios/manager-item-contest.json
runbooks/scenarios/manager-late-information.json
runbooks/scenarios/shift-error-recovery.json
runbooks/scenarios/shift-live-entry.json
runbooks/schema.json
runbooks/shift-manager.json
scripts/demo-video/build.mjs
scripts/demo-video/capture.mjs
scripts/generate-runbook-docs.mjs
scripts/mark-run-blocked.mjs
scripts/runbook-harness.test.ts
scripts/runbook-support.mjs
scripts/scaffold-run.mjs
[7 more files omitted for size]
```

### Dependencies

- package.json: @tailwindcss/postcss@^4.1.12, @types/better-sqlite3@^7.6.12, @types/node@^22.15.30, @types/react@^19.1.8, @types/react-dom@^19.1.6, better-sqlite3@^12.4.1, next@^15.5.0, playwright@^1.61.1, react@^19.1.0, react-dom@^19.1.0, typescript@^5.8.3, vitest@^3.2.4

### Recent commits (newest first)

- docs: add judging guide
- docs: note the manual dev-server fallback for Codespaces
- chore: add sshd feature for container verification
- fix: allow the Codespaces proxy origin in dev
- docs: note Codespaces link and Codex usage in submission copy
- feat: add Codespaces config and document Codex and GPT-5.6 usage
- fix: use a relative cwd for the sim_ops MCP server
- docs: add Devpost submission copy
- docs: link the published demo video
- feat: add roadmap scene and import-forward narration to demo
- feat: add repository URL to demo end card and script
- chore: update submission checklist with public repo URL
- docs: add MIT license, contest-designer key setup, and demo pipeline notes
- feat: add demo video pipeline
- chore: finalize runbook rehearsals
- docs: update rehearsal findings
- docs: log blocked UI rehearsals
- feat: add agent-executable manager runbooks
- feat: add direct contest sales tallies
- feat: keep sales tools inline

## Key source files (fetched from GitHub, selected and truncated for size)

### SUBMISSION.md

```markdown
# Build Week submission log

Hackathon: OpenAI Build Week (openai.devpost.com) · Category: Work and productivity · Deadline: Tue July 21, 2026, 5:00 PM PT

## Codex session IDs

Run `/feedback` in Codex at the end of each significant session and paste the Session ID here with a one-line note. The submission form requires the session ID for the thread where the majority of core functionality was built — mark that one with ⭐ when you know which it is.

| Date | Session ID | What was built |
|---|---|---|
| 2026-07-13–14 | ⭐ 019f5e37-0446-70b2-bbe3-a7f63379f3a6 | Core SIM build: dashboard, bingo, prize wheel, contest designer, sales games, and editable/importable sales data. |
| 2026-07-13–14 | 019f5e10-6744-7fb3-8288-30416c41252e | Initial SIM scaffold, deterministic SQLite seed, schema, and metric-query foundation. |
| 2026-07-14–17 | 019f634d-4c75-72d3-8c10-d1f2df214677 | Restaurant-operations polish plus canonical manager runbooks, safe local operations API/MCP, disposable agent rehearsals, screenshot evidence, and product-discovery scaffolds. |
| 2026-07-18 | 019f7545-8b2e-77a2-b364-856246a1d941 | Fresh isolated manager (13 checkpoints, 4 receipts) and shift (7 checkpoints, 4 receipts) UI-first rehearsals completed with `sim_ops` active; both packages structurally verified, true-PNG evidence was captured, and blind reviews documented remaining evidence-contract and runbook gaps. |

## Checklist

- [ ] Devpost registration
- [ ] Free credits requested (gate: Fri 7/17, 12:00 PM PT — window passed)
- [x] Codex installed + signed in
- [x] Public GitHub repo + license — https://github.com/estevanhernandez-stack-ed/sales-incentive-machine (MIT)
- [x] README with setup + sample data
- [x] Demo video (<3 min, public YouTube) — https://www.youtube.com/watch?v=XFfKLfKBDHE (2:45 master, reproducible from demo/manifest.json)
- [x] ⭐ core-functionality session ID confirmed
- [ ] Submitted (target: by noon PT Tue 7/21)

```

### AGENTS.md

```markdown
# SIM: Sales Incentive Machine — agent instructions

A sales-contest platform for restaurant managers: server leaderboards against sales goals, printable Server Bingo cards on menu items, and a weekly prize-wheel drawing. Hackathon project for OpenAI Build Week; deadline July 21, 2026.

**The full spec is `docs/superpowers/specs/2026-07-13-sim-design.md`. Read it before building anything. It defines the data model, exact metric formulas, contest config shape, feature acceptance criteria, and the cut list. When this file and the spec disagree, the spec wins.**

## Stack

- Next.js (App Router) + TypeScript + Tailwind. No component libraries.
- SQLite via `better-sqlite3`, direct SQL. DB at `data/sim.db` (gitignored).
- Vitest for unit tests.

## Commands

- `npm run dev` — start the app
- `npm run seed` — reset + regenerate the database (deterministic, fixed RNG seed)
- `npm test` — run unit tests

## Hard rules

- **Local-first, zero config.** `npm install && npm run seed && npm run dev` must always work on a fresh clone with no environment variables. Judges will do exactly this.
- **The no-key fallback is sacred.** `OPENAI_API_KEY` is optional. Without it, the Contest Designer serves `seed/fallback-contest.json` and every feature stays fully demoable. Never let a missing key throw.
- **No secrets in the repo.** Keys live in `.env.local` only.
- **Metrics are computed in queries, never stored.** Formulas are in the spec — implement them exactly and keep them unit-tested.
- **Fake data only.** No real restaurant, brand, menu, or person names anywhere.
- **Respect the non-goals.** No auth, multi-restaurant, POS integration, deployment, or notifications. Don't scaffold for them "just in case."

## Conventions

- Small focused components; server components by default, client components only where interaction demands.
- Plain, readable SQL in a `lib/db/` module — one function per query, typed returns.
- Print stylesheet for bingo cards renders one clean card per page: no nav, no buttons, legible at arm's length.
- Commit style: conventional commits (`feat:`, `fix:`, `chore:`).

## Definition of done (per feature)

Feature acceptance criteria in the spec pass, `npm test` is green, `npm run seed && npm run dev` on a clean tree shows the feature working with seed data, and no console errors on the touched pages.

```

### package.json

```
{
  "name": "sales-incentive-machine",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "seed": "node seed/index.js",
    "mcp:boh-pos": "node mcp/boh-pos-server.mjs",
    "mcp:ops": "node mcp/sim-ops-server.mjs",
    "runbook:docs": "node scripts/generate-runbook-docs.mjs",
    "runbook:scaffold": "node scripts/scaffold-run.mjs",
    "runbook:serve": "node scripts/serve-run.mjs",
    "runbook:block": "node scripts/mark-run-blocked.mjs",
    "runbook:verify": "node scripts/verify-run.mjs",
    "test": "vitest run"
  },
  "dependencies": {
    "@types/better-sqlite3": "^7.6.12",
    "better-sqlite3": "^12.4.1",
    "next": "^15.5.0",
    "react": "^19.1.0",
    "react-dom": "^19.1.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4.1.12",
    "@types/node": "^22.15.30",
    "@types/react": "^19.1.8",
    "@types/react-dom": "^19.1.6",
    "playwright": "^1.61.1",
    "typescript": "^5.8.3",
    "vitest": "^3.2.4"
  }
}

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import "./globals.css";
import "./workspace.css";

export const metadata: Metadata = {
  title: "SIM — Sales Incentive Machine",
  description: "A local-first sales contest tool for restaurant managers.",
};

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head><script dangerouslySetInnerHTML={{ __html: `try{const t=JSON.parse(localStorage.getItem('sim-theme'));if(t){const r=document.documentElement,x=h=>{const n=parseInt(h.slice(1),16);return[n>>16&255,n>>8&255,n&255]},a=(h,o)=>'rgba('+x(h).join(', ')+', '+o+')',l=(h,o)=>'rgb('+x(h).map(c=>Math.round(c+(255-c)*o)).join(', ')+')',v={'--bg':t.bg,'--card':a(t.glass_on_mica,t.glass_alpha??.8),'--chrome':a(t.glass_on_mica,.92),'--glass-hover':l(t.glass,.1),'--border':a(t.border_tint??t.border,t.border_alpha??.4),'--text':t.text,'--text-secondary':t.text_secondary,'--text-dim':t.text_dim,'--text-muted':t.text_muted,'--accent':t.accent,'--positive':t.positive,'--negative':t.negative,'--bingo-marked':t.bingo_marked,'--pace-marker':t.pace_marker,'--bar-bg':t.bar_bg,'--card-radius':(t.card_corner_radius??10)+'px'};[t.accent,t.positive,t.negative,t.pace_marker,t.text_dim,t.bingo_marked].forEach((c,i)=>v['--wheel-'+(i+1)]=c);Object.entries(v).forEach(([k,v])=>r.style.setProperty(k,v));if(t.opts_out_of_mica)r.classList.add('no-glass')}}catch{}` }} /></head>
      <body>{children}</body>
    </html>
  );
}

```

### app/page.tsx

```typescript
import { Leaderboard } from "../components/leaderboard";
import { PageHeader } from "../components/page-header";
import { openDatabase } from "../lib/db/client";
import { getDashboardData, type ContestGoal } from "../lib/db/dashboard";

export const dynamic = "force-dynamic";

function goalSummary(goal: ContestGoal) {
  const target = goal.vs_house ? "beat house average" : goal.threshold === undefined ? "qualify" : goal.metric === "alcohol_pct" || goal.metric === "attach_rate" ? `${(goal.threshold * 100).toFixed(0)}%` : goal.metric === "item_count" ? `${goal.threshold} sold` : `$${goal.threshold.toFixed(2)}`;
  if (goal.metric === "ppa") return `PPA · ${target}`;
  if (goal.metric === "alcohol_pct") return `Alcohol sales · ${target}`;
  if (goal.metric === "avg_check") return `Average check · ${target}`;
  if (goal.metric === "large_party_ppa") return `Large-party PPA · ${target}`;
  if (goal.metric === "item_count") return `Item sales · ${target}`;
  return `${goal.category ?? "Item"} attachment · ${target}`;
}

function daysRemaining(weekStart: string) {
  const end = new Date(`${weekStart}T00:00:00Z`);
  end.setUTCDate(end.getUTCDate() + 7);
  return Math.max(0, Math.ceil((end.getTime() - Date.now()) / 86_400_000));
}

export default function Home() {
  const db = openDatabase();
  const dashboard = getDashboardData(db);
  db.close();

  if (!dashboard) return <main className="shell"><h1>No active contest</h1><p className="lede">Seed the local database to start a contest.</p></main>;

  const remaining = daysRemaining(dashboard.contest.weekStart);
  return (
    <main className="shell">
      <PageHeader current="/" section="Manager dashboard" title="Weekly performance" description="Live sales, contest goals, and daily wins for the current team." meta={<><span>Sales view</span><strong>Four weeks</strong></>} />
      <section className="contest-banner" aria-labelledby="contest-name">
        <div><p className="eyebrow">Active contest</p><h2 id="contest-name">{dashboard.contest.name}</h2><p className="prize">Prize: <strong>{dashboard.contest.prize}</strong></p>{dashboard.lastWinner && <p className="last-winner">Last winner: <strong>{dashboard.lastWinner.name}</strong> · {new Date(dashboard.lastWinner.drawnAt).toLocaleDateString()}</p>}</div>
        <div className="time-left"><strong>{remaining}</strong><span>days remaining</span></div>
        <ul className="goal-list">{dashboard.contest.goals.map((goal, index) => <li key={index}>{goalSummary(goal)}</li>)}</ul>
      </section>
      <Leaderboard data={dashboard} />
    </main>
  );
}

```

### seed/index.js

```javascript
const Database = require("better-sqlite3");
const fs = require("node:fs");
const path = require("node:path");

const dataDir = path.join(process.cwd(), "data");
const dbPath = path.join(dataDir, "sim.db");
const schemaPath = path.join(process.cwd(), "lib", "db", "schema.sql");
const fallbackPath = path.join(process.cwd(), "seed", "fallback-contest.json");

function rng(seed = 20260713) {
  let state = seed >>> 0;
  return () => ((state = (state * 1664525 + 1013904223) >>> 0) / 4294967296);
}

function pick(random, values) { return values[Math.floor(random() * values.length)]; }
function shuffle(random, values) {
  const copy = [...values];
  for (let i = copy.length - 1; i > 0; i -= 1) {
    const j = Math.floor(random() * (i + 1));
    [copy[i], copy[j]] = [copy[j], copy[i]];
  }
  return copy;
}

const menu = [
  ["Ember Corn Cups", "app", 8, 0], ["Citrus Bean Dip", "app", 9, 0], ["Crisp Plantain Stack", "app", 10, 0], ["Charred Pepper Bites", "app", 11, 0], ["Golden Winglets", "app", 12, 0], ["Garden Skewers", "app", 9, 0], ["Smoky Queso Bowl", "app", 10, 0], ["Hearth Flatbread", "app", 11, 0],
  ["Hearth Burger", "entree", 16, 0], ["River Grain Bowl", "entree", 15, 0], ["Saffron Chicken", "entree", 19, 0], ["Cedar Salmon", "entree", 22, 0], ["Roasted Pepper Pasta", "entree", 17, 0], ["Market Steak Plate", "entree", 28, 0], ["Crisp Tofu Wrap", "entree", 14, 0], ["Lemon Herb Plate", "entree", 18, 0],
  ["Honey Cloud Cake", "dessert", 8, 0], ["Cocoa Pot", "dessert", 9, 0], ["Citrus Ice", "dessert", 7, 0], ["Salted Maple Tart", "dessert", 9, 0], ["Berry Crumble", "dessert", 8, 0], ["Warm Orchard Crisp", "dessert", 8, 0],
  ["Sunset Fizz", "cocktail", 11, 1], ["Garden Spark", "cocktail", 12, 1], ["Copper Mule", "cocktail", 13, 1], ["Hearth Old Fashioned", "cocktail", 14, 1], ["Juniper Cooler", "cocktail", 12, 1], ["Lime Lantern", "cocktail", 11, 1],
  ["Reserve Agave Pour", "top_shelf", 18, 1], ["North Star Rye", "top_shelf", 19, 1], ["Velvet Citrus Martini", "top_shelf", 20, 1], ["Oakline Pour", "top_shelf", 21, 1], ["Moonlit Spritz", "top_shelf", 18, 1],
  ["Hibiscus Soda", "na_bev", 5, 0], ["Cucumber Cooler", "na_bev", 5, 0], ["Sparkling Citrus", "na_bev", 4, 0], ["Ginger Orchard Tea", "na_bev", 4, 0], ["Cold Brew Tonic", "na_bev", 6, 0], ["Cloudy Lemonade", "na_bev", 4, 0], ["Minted Mineral", "na_bev", 4, 0]
];

const servers = [
  ["Avery Moss", "#d97706"], ["Blair Rowan", "#be123c"], ["Cameron Vale", "#0f766e"], ["Devon Sky", "#2563eb"], ["Ellis Reed", "#7c3aed"], ["Finley Hart", "#b45309"],
  ["Gray Lane", "#047857"], ["Harper Quinn", "#c026d3"], ["Indigo Park", "#0891b2"], ["Jordan Wren", "#4f46e5"], ["Kai Sol", "#ca8a04"], ["Logan Briar", "#dc2626"]
];

function partySize(random) { return pick(random, [1, 2, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 10]); }

fs.mkdirSync(dataDir, { recursive: true });
fs.rmSync(dbPath, { force: true });
const db = new Database(dbPath);
db.exec(fs.readFileSync(schemaPath, "utf8"));
const random = rng();
const insertServer = db.prepare("INSERT INTO servers (id, name, color, active) VALUES (?, ?, ?, 1)");
const insertMenu = db.prepare("INSERT INTO menu_items (id, name, category, price, is_alcohol) VALUES (?, ?, ?, ?, ?)");
const insertCheck = db.prepare("INSERT INTO checks (id, server_id, opened_at, party_size, subtotal) VALUES (?, ?, ?, ?, ?)");
const insertItem = db.prepare("INSERT INTO check_items (id, check_id, menu_item_id, qty, price_each) VALUES (?, ?, ?, ?, ?)");

servers.forEach(([name, color], index) => insertServer.run(index + 1, name, color));
menu.forEach(([name, category, price, isAlcohol], index) => insertMenu.run(index + 1, name, category, price, isAlcohol));

let checkId = 1;
let itemId = 1;
const insertSales = db.transaction(() => {
  for (let week = 0; week < 4; week += 1) {
    for (let serverId = 1; serverId <= servers.length; serverId += 1) {
      const shifts = 4 + (random() > 0.55 ? 1 : 0);
      for (let shift = 0; shift < shifts; shift += 1) {
        const checks = 8 + Math.floor(random() * 8);
        for (let i = 0; i < checks; i += 1) {
          const party = partySize(random);
          const selected = [pick(random, menu.filter((item) => item[1] === "entree"))];
          if (random() < 0.32 + (serverId === 1 ? 0.24 : 0)) selected.push(pick(random, menu.filter((item) => item[1] === "cocktail" || item[1] === "top_shelf")));
          if (random() < 0.34) selected.push(pick(random, menu.filter((item) => item[1] === "app")));
          if (random() < 0.22 + (serverId === 5 ? 0.2 : serverId === 2 ? -0.16 : 0)) selected.push(pick(random, menu.filter((item) => item[1] === "dessert")));
          if (random() < 0.28) selected.push(pick(random, menu.filter((item) => item[1] === "na_bev")));
          const items = selected.map((item) => ({ item, qty: party >= 6 && random() < 0.4 ? 2 : 1 }));
          const subtotal = items.reduce((total, { item, qty }) => total + item[2] * qty, 0);
          const openedAt = new Date(Date.UTC(2026, 5, 15 + week * 7 + shift, 17 + (i % 5), 0, 0)).toISOString();
          insertCheck.run(checkId, serverId, openedAt, party, subtotal);
          items.forEach(({ item, qty }) => { insertItem.run(itemId++, checkId, menu.indexOf(item) + 1, qty, item[2]); });
          checkId += 1;
        }
      }
    }
  }
});
insertSales();

const fallback = fs.readFileSync(fallbackPath, "utf8");
db.prepare("INSERT INTO contests (id, name, week_start, config_json, status, created_via) VALUES (1, ?, ?, ?, 'active', 'manual')").run("Summer Signal Sprint", "2026-07-13", fallback);
db.prepare("INSERT INTO contests (id, name, week_start, config_json, status, created_via) VALUES (2, ?, ?, ?, 'closed', 'manual')").run("Harbor Hour Push", "2026-07-06", fallback);

const insertCard = db.prepare("INSERT INTO bingo_cards (id, contest_id, server_id, grid_json, created_at) VALUES (?, 1, ?, ?, ?)");
for (let serverId = 1; serverId <= servers.length; serverId += 1) {
  const cells = shuffle(random, Array.from({ length: 28 }, (_, index) 
[truncated — 974 more characters]
```

### lib/db/index.ts

```typescript
/**
 * Database queries will live here. Metrics must be computed in SQL, never stored.
 */
export {};

```

### app/games/page.tsx

```typescript
import { PageHeader } from "../../components/page-header";
import { GameHub } from "../../components/game-hub";
import { openDatabase } from "../../lib/db/client";
import { getGamesData } from "../../lib/db/games";
export const dynamic = "force-dynamic";
export default async function GamesPage({ searchParams }: { searchParams: Promise<{ game?: string }> }) { const params = await searchParams; const db = openDatabase(); const data = getGamesData(db); db.close(); return <main className="shell games-shell"><PageHeader current="/games" section="Sales games" title="Gameboards" description="Live standings and goal progress from the sales recorded for this contest." />{data?.games.length ? <GameHub data={data} focusGameId={params.game} /> : <p className="lede">Add games in Contest setup to start a board.</p>}</main>; }

```

### app/contest/page.tsx

```typescript
import { ContestBuilder } from "../../components/contest-designer";
import { PageHeader } from "../../components/page-header";
import { openDatabase } from "../../lib/db/client";
import { getContestSetupData } from "../../lib/db/contest";

export const dynamic = "force-dynamic";

export default function ContestPage() {
  const db = openDatabase();
  const data = getContestSetupData(db);
  db.close();
  if (!data) return <main className="shell"><h1>No active contest</h1><p className="lede">Seed the local database to create a contest.</p></main>;
  return <main className="shell contest-shell"><PageHeader current="/contest" section="Contest setup" title="Build the next sales contest" description="Set the goals, prize, scoring, and live gameboards your team will use." /><ContestBuilder initialContestId={data.id} initialName={data.name} initialConfig={data.config} menuItems={data.menuItems} /></main>;
}

```

### app/bingo/page.tsx

```typescript
import { BingoManager } from "../../components/bingo-manager";
import { PageHeader } from "../../components/page-header";
import { openDatabase } from "../../lib/db/client";
import { getBingoPageData } from "../../lib/db/bingo";

export const dynamic = "force-dynamic";

export default async function BingoPage({ searchParams }: { searchParams: Promise<{ server?: string }> }) {
  const params = await searchParams;
  const db = openDatabase();
  const data = getBingoPageData(db);
  db.close();
  if (!data) return <main className="shell"><h1>No active contest</h1><p className="lede">Seed the local database to make bingo cards.</p></main>;
  const requestedServerId = Number(params.server);
  return <main className="shell bingo-shell"><PageHeader current="/bingo" section="Server bingo" title="Bingo cards" description="Print cards, record completed lines, and track daily wins." /><BingoManager data={data} initialServerId={Number.isInteger(requestedServerId) ? requestedServerId : undefined} /></main>;
}

```

[84 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]