# Foureyes sales leaderboard: build kit

A full-screen sales leaderboard for a dealership, built on the Foureyes App Builder and the Foureyes data API. It's designed for a TV in the sales office and is aimed at one number: appointment set rate.

This kit contains the complete source. Your Claude writes it into a new app in your own workspace, deploys it, and you then change whatever you like by asking for it.

## How to use this kit

1. Upload this file to a Claude chat that has the Foureyes App Builder tools enabled.
2. Paste the prompt from the section "Prompt to paste" below.
3. Wait for the URL (about 10 minutes). Open it and log in. It opens on the week screen. Pick your store from the dropdown at the top.
4. Ask Claude for changes. See "Changing it" below.

## What you get

- **Yesterday:** who got on the board, a streak for every rep, and one number to aim for today.
- **This week:** the week's set rate beside the month's set rate, with a day-by-day grid for every rep.
- **This month:** set rate by rep, with a bar for leads received and the part that became appointments.
- A store dropdown, so one app covers every store in your group.
- A Log out button in the top right, which signs you out of the app.

## The ideas behind it

These are the decisions worth understanding before you change anything.

- **Set rate** = appointments set in a period divided by leads received in that period. An appointment counts on the day it was booked, and only a lead's first appointment counts, so rescheduling isn't a second set. This one definition is used for the store, every rep, every day, week and month.
- **Goal, pace, target** are three different words.
  - Goal is the set rate we're aiming for, a percentage.
  - Pace is a period's set rate so far, compared with the goal. It looks back, in rates.
  - Target is how many appointments to set from here, spread over the days left. It looks forward, in counts.
  - A count from the past is never compared with a target.
- **The goal is computed, not typed in.** It starts from the store's own last 26 weeks. Below the benchmark's top quarter (18.5%), the goal moves halfway toward it. At or above it, the goal is 5% above the store's own rate. The benchmark table is in `lib/benchmarks.ts`.
- **Ranking is plain set rate.** Ties go to more appointments set, then fewer leads, then name.
- **Streak** is days in a row with at least one appointment set. A day with no leads and no appointments counts as a day off and doesn't break it. More than two such days in a row ends it.
- **Colors mean one thing each.** Teal is on pace, coral is behind, gold is for achievements only (first place, streak flames, hat tricks).
- **Data is through yesterday.** The Foureyes data refreshes daily, so the board shows yesterday's results and a fresh start for today.

## Changing it

Ask your Claude in plain language, for example: "Change the goal formula so it's always 3 points above our trailing rate." Where the common changes live:

| To change | File |
|---|---|
| The goal formula or the benchmark numbers | `src/modules/leaderboard/lib/benchmarks.ts` |
| How rates, streaks, ranking, pace and the daily target are calculated | `src/modules/leaderboard/lib/compute.ts` |
| What data is pulled | `src/modules/leaderboard/lib/queries.ts` and `baseline.ts` |
| Words and layout of the top strip, header and tooltip | `src/modules/leaderboard/components/parts.tsx` |
| The rep tables (week grid, month bar) | `src/modules/leaderboard/components/rep-table.tsx` |
| The side cards and screens | `side-cards.tsx` and `slides.tsx` in the same folder |
| Colors and sizes | the `board-*.css` files in `src/modules/leaderboard/` |

After any change, your Claude should write the changed files and run `apps_deploy` again.

You can also add settings to the web address: `?store=` to pick a store, `?view=week`, `month` or `yesterday` to open a particular screen, and `?personal=3` to change the per-rep daily circles on the Yesterday screen.

## Good to know

- You need to be able to log in to the app and see your stores in the Foureyes data. If a store has under about 500 leads in 26 weeks, the goal falls back to the benchmark median (15.5%).
- The first load can take 20 to 40 seconds. The app asks the data service for several queries and waits its turn when the service is busy.
- Reps, stores and numbers on screen are your real ones. Nothing in this kit is sample data.
- The starter app has some unused sidebar files. This kit replaces the router so the board opens full screen, and leaves the rest alone.

## If something goes wrong

- **A file write fails:** write fewer files per call and retry. The kit already avoids relative `../` imports, which the write tool rejects.
- **The logo gives trouble:** it's a long single line, and the app doesn't need it. Skip `public/foureyes-logo-white.svg` and the header shows the name "Foureyes" instead. You can add the logo later.
- **"The data service is busy":** wait a minute and reload.
- **Blank or login screen:** log in with the account that owns your dealer group.

---

## Prompt to paste

Paste this into your Claude chat after attaching this file.

````
I've attached the Foureyes sales leaderboard build kit. Use my Foureyes App Builder tools to build it exactly as written.

1. Call apps_create to make a new app named "Sales Leaderboard".
2. Write every file in the section "Files" of the kit into the app, using apps_write_files. Keep each file's contents exactly as given, with no changes, additions or reformatting. Use the file's path exactly as shown. Write about 2 or 3 files per call. If a write fails, retry with fewer files in the call.
3. When all 28 files are written, call apps_deploy and check apps_deploy_status until it says READY.
4. Tell me the app's URL, and tell me if anything failed or was skipped.

The two image files in the public folder (the logo and the Lens) are optional. If either one gives you trouble, skip it, tell me, and carry on. The app works without them and shows the name "Foureyes" in place of the logo.

Don't add features and don't change any other file. I'll ask for changes afterwards.
````

---

## Files

There are 28 files. Write each one to the path shown, exactly as given. The block under each path is the full contents of the file. The two image files at the top are each a single long line. Copy them character for character, or skip them: the app works without them.

| # | Path | Lines |
|---|---|---|
| 1 | `public/foureyes-logo-white.svg` | 1 |
| 2 | `public/lens-white.svg` | 2 |
| 3 | `src/router/routes.tsx` | 32 |
| 4 | `src/modules/leaderboard/board-bar.css` | 5 |
| 5 | `src/modules/leaderboard/board-base.css` | 90 |
| 6 | `src/modules/leaderboard/board-controls.css` | 7 |
| 7 | `src/modules/leaderboard/board-pace.css` | 5 |
| 8 | `src/modules/leaderboard/board-pill.css` | 42 |
| 9 | `src/modules/leaderboard/board-slides.css` | 57 |
| 10 | `src/modules/leaderboard/board-streak.css` | 23 |
| 11 | `src/modules/leaderboard/board-tip.css` | 10 |
| 12 | `src/modules/leaderboard/components/parts.tsx` | 255 |
| 13 | `src/modules/leaderboard/components/rep-table.tsx` | 149 |
| 14 | `src/modules/leaderboard/components/side-cards.tsx` | 141 |
| 15 | `src/modules/leaderboard/components/slides.tsx` | 100 |
| 16 | `src/modules/leaderboard/components/streak-badge.tsx` | 22 |
| 17 | `src/modules/leaderboard/lib/baseline.ts` | 59 |
| 18 | `src/modules/leaderboard/lib/benchmarks.ts` | 44 |
| 19 | `src/modules/leaderboard/lib/compute.ts` | 439 |
| 20 | `src/modules/leaderboard/lib/constants.ts` | 2 |
| 21 | `src/modules/leaderboard/lib/dates.ts` | 47 |
| 22 | `src/modules/leaderboard/lib/goal-context.ts` | 5 |
| 23 | `src/modules/leaderboard/lib/queries.ts` | 239 |
| 24 | `src/modules/leaderboard/lib/use-board-data.ts` | 65 |
| 25 | `src/modules/leaderboard/lib/use-goal.ts` | 35 |
| 26 | `src/modules/leaderboard/lib/use-stores.ts` | 37 |
| 27 | `src/modules/leaderboard/pages/board-page.tsx` | 196 |
| 28 | `src/modules/leaderboard/paths.ts` | 4 |

### File 1 of 28: `public/foureyes-logo-white.svg`

````xml
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 1080 430"><path d="M216 133a82 82 0 1 1-66 33q-16-13-17-33zm0-19H114v19q0 20 11 35-11 23-11 47a102 102 0 1 0 102-101m192 69v3h18l-3 20h-15v59h-29v-59h-11v-20h11v-4c0-17 11-30 33-30q14 1 23 6l-7 18-12-3q-8 0-8 10m61 0c-25 0-41 17-41 42q2 43 41 43c39 0 40-17 40-42q-1-42-40-43m0 64q-11 1-11-22c0-23 4-21 11-21s10 6 10 22-3 21-10 21m95 18-1-9q-9 12-24 12-22-1-22-25v-57h30v53q0 9 5 8 3 0 8-7v-54h29v79zm93-80-4 28-7-1q-13 1-15 17v36h-29v-79h25l3 15q6-16 20-17zm78 40q-2-41-39-42c-26 0-39 20-39 43 0 24 14 42 43 42q20-1 32-12l-12-16q-10 7-18 7-13 2-15-13h47zm-49-8q2-16 11-15 10 1 9 14v1z"/><path d="m743 299-3-20c15-2 21-5 26-14h-11l-22-79h31l10 61 12-61h30l-24 79q-10 33-49 34m149-75q-2-40-38-41c-26 0-40 20-40 43 0 24 14 42 43 42q21-1 32-12l-11-16q-10 7-18 7-14 2-16-13h48zm-48-7q1-16 10-15 10 1 9 14l1 1zm118-24-10 16q-10-5-18-5-9 0-8 3c0 3 1 4 16 8q23 6 23 26c0 16-15 27-37 27q-22 0-35-13l14-15q9 8 20 8 8 0 9-5c0-4-2-4-16-9q-21-6-22-25c0-14 12-26 34-26q18 1 30 10m-746-40a62 62 0 1 0 0 125 62 62 0 0 0 0-125m0 94a32 32 0 1 1 6-63l3 1q3 2 1 4l-3 7q1 9 10 10 8-1 8-3 2-3 4 0l2 12c0 18-14 32-31 32"/></svg>
````

### File 2 of 28: `public/lens-white.svg`

````xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 858.5 858.5"><path fill="#FFFFFF" d="M429.3,0H0c0,70.1,33.6,132.7,85.8,171.7C31.8,243.6,0,332.7,0,429.3c0,237.2,192.1,429.3,429.3,429.3s429.3-192.1,429.3-429.3S666.4,0,429.3,0ZM429.3,756.6c-181,0-327.7-146.7-327.7-327.7S248.3,101.2,429.3,101.2s327.7,146.7,327.7,327.7-146.7,327.7-327.7,327.7h0Z"/></svg>
````

### File 3 of 28: `src/router/routes.tsx`

````tsx
import { QueryPreservingRedirect } from '@/router/query-preserving-redirect';
import { NotFoundPage } from '@foureyes-io/foureyes-components';
import { leaderboardPaths } from '@modules/leaderboard/paths';
import { BoardPage } from '@modules/leaderboard/pages/board-page';
import { ApiAuthGate } from '@shared/components/api-auth-gate';
import { AuthGuard } from '@shared/components/auth-guard';
import { OrganizationProvider } from '@shared/contexts/organization-context';
import { LoginPage } from '@shared/pages/login-page';
import { shellPaths } from '@/router/paths';
import type { RouteObject } from 'react-router';

export const DEFAULT_LANDING_PAGE = leaderboardPaths.root;

// The leaderboard is a full-screen display, so it sits outside the sidebar layout.
export const Routes: RouteObject[] = [
  { path: '/', element: <QueryPreservingRedirect to={DEFAULT_LANDING_PAGE} /> },
  {
    path: leaderboardPaths.root,
    element: (
      <AuthGuard>
        <ApiAuthGate>
          <OrganizationProvider>
            <BoardPage />
          </OrganizationProvider>
        </ApiAuthGate>
      </AuthGuard>
    ),
  },
  { path: shellPaths.login, element: <LoginPage /> },
  { path: '*', element: <NotFoundPage /> },
];
````

### File 4 of 28: `src/modules/leaderboard/board-bar.css`

````css
/* Month table bar: the whole bar is leads received (the busiest rep's bar is full width),
   the filled part is appointments set, and the white tick is the goal. */
.lb-rate .lb-bar { background: transparent; }
.lb-bar-len { position: relative; height: 14px; min-width: 10px; border-radius: 999px; background: rgba(255, 255, 255, 0.16); }
````

### File 5 of 28: `src/modules/leaderboard/board-base.css`

````css
.lb-stage { position: fixed; inset: 0; background: #00205b; overflow: hidden; color: #fff; font-family: Montserrat, system-ui, sans-serif; }
.lb-canvas { position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; transform-origin: 0 0; overflow: hidden; background: #00205b; }
.lb-lens { position: absolute; right: -420px; bottom: -520px; width: 1200px; height: 1200px; opacity: 0.05; pointer-events: none; }
.lb-shell { position: relative; box-sizing: border-box; width: 100%; height: 100%; padding: 32px 48px; display: flex; flex-direction: column; gap: 20px; }
.lb-fade { animation: lb-fade 420ms cubic-bezier(0.22, 1, 0.36, 1); }
@keyframes lb-fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.lb-header { position: relative; z-index: 30; display: flex; align-items: center; justify-content: space-between; height: 64px; flex: 0 0 auto; }
.lb-brand { display: flex; align-items: center; gap: 24px; }
.lb-brand img { height: 36px; width: auto; display: block; }
.lb-divider { width: 1px; height: 32px; background: rgba(255, 255, 255, 0.14); }
.lb-eyebrow-brand { font-size: 20px; font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase; color: #00cec8; }
.lb-store { position: relative; display: flex; align-items: center; }
.lb-store select { appearance: none; -webkit-appearance: none; height: 56px; padding: 0 64px 0 28px; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.28); background: rgba(255, 255, 255, 0.08); color: #fff; font-family: inherit; font-size: 26px; font-weight: 700; cursor: pointer; }
.lb-store select:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(0, 206, 200, 0.45); }
.lb-store select option { color: #00205b; background: #fff; font-size: 18px; }
.lb-store svg { position: absolute; right: 22px; pointer-events: none; }
.lb-header-right { display: flex; align-items: center; gap: 20px; }
.lb-freshness { font-size: 18px; color: #c2deff; }
.lb-tabs { display: flex; gap: 6px; padding: 6px; border-radius: 999px; background: rgba(255, 255, 255, 0.08); }
.lb-tab { padding: 8px 20px; border-radius: 999px; font-size: 18px; font-weight: 600; color: #c2deff; border: 0; background: transparent; font-family: inherit; cursor: pointer; }
.lb-tab[aria-current='true'] { font-weight: 700; background: #00cec8; color: #00205b; }

.lb-strip { flex: 0 0 auto; height: 150px; box-sizing: border-box; display: flex; align-items: center; gap: 48px; padding: 20px 36px; background: #002b7a; border: 1px solid rgba(255, 255, 255, 0.14); border-radius: 24px; }
.lb-strip-rate { display: flex; flex-direction: column; gap: 6px; width: 340px; }
.lb-eyebrow { font-size: 15px; font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase; color: #c2deff; }
.lb-hero { font-size: 88px; font-weight: 800; line-height: 1; color: #00cec8; text-shadow: 0 0 12px rgba(0, 206, 200, 0.65), 0 0 32px rgba(0, 206, 200, 0.35); }
.lb-strip-mid { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 14px; }
.lb-goalline { font-size: 32px; font-weight: 700; }
.lb-goalbar { position: relative; height: 56px; }
.lb-goalbar-track { position: absolute; top: 0; left: 0; right: 0; height: 28px; border-radius: 999px; background: rgba(255, 255, 255, 0.12); }
.lb-goalbar-fill { position: absolute; top: 0; left: 0; height: 28px; border-radius: 999px; background: #00cec8; }
.lb-goalbar-mark { position: absolute; top: -6px; width: 4px; height: 40px; border-radius: 2px; background: #ffbe0b; }
.lb-goalbar-label { position: absolute; top: 36px; transform: translateX(-50%); font-size: 18px; font-weight: 700; color: #ffd35c; white-space: nowrap; }
.lb-strip-stats { display: flex; gap: 40px; }
.lb-stat { display: flex; flex-direction: column; gap: 4px; }
.lb-stat-value { font-size: 44px; font-weight: 800; line-height: 1; }
.lb-stat-label { font-size: 14px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: #c2deff; }

.lb-body { flex: 1; min-height: 0; display: flex; gap: 24px; }
.lb-card { box-sizing: border-box; background: #002b7a; border: 1px solid rgba(255, 255, 255, 0.14); border-radius: 24px; }
.lb-board { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; padding: 24px 28px; }
.lb-board-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 8px; gap: 24px; }
.lb-board-title { font-size: 32px; font-weight: 700; letter-spacing: -0.015em; }
.lb-board-note { font-size: 16px; color: #c2deff; text-align: right; }
.lb-cols, .lb-row { display: grid; column-gap: 16px; align-items: center; padding: 0 16px; }
.lb-cols { font-size: 14px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: #99c9ff; align-items: end; }
.lb-cols--month, .lb-row--month { grid-template-columns: 100px minmax(0, 1fr) 300px 150px 110px; }
.lb-cols--week, .lb-row--week { grid-template-columns: 100px minmax(0, 1fr) 400px 130px 140px; }
.lb-rows { display: flex; flex-direction: column; gap: 4px; }
.lb-row { height: 66px; box-sizing: border-box; border-radius: 16px; background: rgba(255, 255, 255, 0.04); border: 1px solid transparent; }
.lb-row--first { background: rgba(0, 206, 200, 0.14); border-color: rgba(0, 206, 200, 0.55); }
.lb-row--building { opacity: 0.75; }
.lb-rank { display: flex; align-items: center; gap: 10px; }
.lb-rank-badge { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: 800; }
.lb-rank-badge--1 { background: #ffbe0b; color: #00205b; }
.lb-rank-badge--top { background: rgba(255, 255, 255, 0.16); }
.lb-move { display: flex; align-items: center; gap: 2px; font-size: 18px; font-weight: 700; }
.lb-rep { display: flex; align-items: center; gap: 14px; min-width: 0; }
.lb-avatar { flex: 0 0 auto; width: 46px; height: 46px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 17px; font-weight: 700; background: #32539f; color: #fff; }
.lb-avatar--lg { width: 56px; height: 56px; font-size: 20px; }
.lb-rep-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.lb-rep-name { font-size: 24px; font-weight: 600; line-height: 1.1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-chip { align-self: flex-start; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; border: 1px solid transparent; white-space: nowrap; }
.lb-chip--gold { background: #ffbe0b; color: #00205b; border-color: #ffbe0b; }
.lb-chip--silver { background: #c2deff; color: #00205b; border-color: #c2deff; }
.lb-chip--bronze { color: #ffd35c; border-color: #e0a500; }
.lb-chip--rising { color: #c2deff; border-color: rgba(255, 255, 255, 0.4); }
.lb-chip--building { color: #c2deff; border: 1px dashed rgba(194, 222, 255, 0.6); }
.lb-chip--first { color: #00cec8; border-color: #00cec8; }
.lb-chip--on { color: #c2deff; border-color: rgba(255, 255, 255, 0.25); }
.lb-rate { display: flex; align-items: center; gap: 16px; }
.lb-rate-num { width: 84px; font-size: 38px; font-weight: 800; line-height: 1; }
.lb-bar { position: relative; flex: 1; height: 14px; border-radius: 999px; background: rgba(255, 255, 255, 0.12); }
.lb-bar-fill { position: absolute; top: 0; left: 0; height: 14px; border-radius: 999px; }
.lb-bar-goal { position: absolute; top: -4px; width: 3px; height: 22px; border-radius: 2px; background: #ffbe0b; }
.lb-leads { display: flex; align-items: baseline; gap: 8px; }
.lb-leads b { font-size: 26px; font-weight: 700; }
.lb-leads span { font-size: 20px; color: #c2deff; }
.lb-plain { font-size: 26px; font-weight: 600; }
.lb-streak { display: flex; align-items: center; gap: 8px; font-size: 26px; font-weight: 700; }
.lb-days { display: grid; grid-template-columns: repeat(7, 52px); column-gap: 6px; }
.lb-cell { height: 48px; border-radius: 12px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 800; }
.lb-cols-days { display: grid; grid-template-columns: repeat(7, 52px); column-gap: 6px; text-align: center; }
.lb-board-foot { margin-top: auto; display: flex; align-items: center; gap: 14px; padding: 16px 16px 0; border-top: 1px solid rgba(255, 255, 255, 0.14); font-size: 16px; color: #c2deff; }
.lb-spacer { margin-left: auto; }
.lb-pages { display: flex; gap: 6px; margin-left: 12px; }
.lb-pages i { width: 8px; height: 8px; border-radius: 50%; background: rgba(255, 255, 255, 0.25); }
.lb-pages i[aria-current='true'] { background: #00cec8; }
````

### File 6 of 28: `src/modules/leaderboard/board-controls.css`

````css
.lb-logout { padding: 8px 18px; border-radius: 999px; font-family: inherit; font-size: 16px; font-weight: 600; color: #c2deff; border: 1px solid rgba(255, 255, 255, 0.28); background: transparent; cursor: pointer; }
.lb-logout:hover { color: #ffffff; border-color: rgba(255, 255, 255, 0.5); }
.lb-pages button { width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%; background: rgba(255, 255, 255, 0.25); cursor: pointer; }
.lb-pages button[aria-current='true'] { background: #00cec8; }
.lb-pages button:focus-visible, .lb-tab:focus-visible, .lb-logout:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(0, 206, 200, 0.45); }
.lb-wordmark { font-size: 30px; font-weight: 800; letter-spacing: -0.01em; color: #ffffff; }
````

### File 7 of 28: `src/modules/leaderboard/board-pace.css`

````css
.lb-stat-sub { margin-top: 2px; font-size: 15px; color: #c2deff; white-space: nowrap; }
.lb-ring-note { font-size: 18px; line-height: 1.35; color: #c2deff; text-align: center; }
.lb-ring-note b { color: #ffffff; font-weight: 700; }
.lb-ring-notes { display: flex; flex-direction: column; gap: 8px; width: 100%; padding-top: 12px; border-top: 1px solid rgba(255, 255, 255, 0.14); }
````

### File 8 of 28: `src/modules/leaderboard/board-pill.css`

````css
.lb-store-group { display: flex; align-items: center; gap: 14px; }

/* COLOR STORY
   white  the numbers
   teal   on pace, progress, done
   coral  behind pace
   gold   achievement only: tiers, streak flames, hat trick, rank 1
   muted  context */

/* Strip: evenly spaced blocks, each a label, one big value and a few words of context.
   Rates sit side by side, then the two appointment counts. */
.lb-shell .lb-strip { gap: 36px; padding: 16px 36px; position: relative; height: 158px; }
.lb-block { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; padding-left: 36px; border-left: 1px solid rgba(255, 255, 255, 0.14); }
.lb-strip > .lb-block:first-child { padding-left: 0; border-left: 0; }
.lb-block-label { font-size: 15px; font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase; color: #c2deff; }
.lb-block-value { font-size: 52px; font-weight: 800; line-height: 1.05; white-space: nowrap; color: #ffffff; }
.lb-block-value--rate { font-size: 64px; line-height: 1; }
.lb-block-value span { font-size: 26px; font-weight: 600; margin-left: 8px; }
.lb-block-cap { font-size: 22px; color: #c2deff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-st { font-weight: 700; }
.lb-st--on { color: #00cec8; }
.lb-st--behind { color: #ff9d80; }
.lb-strip-tip { position: absolute; top: 14px; right: 20px; }
.lb-strip-tip .lb-tip-body { left: auto; right: 0; top: 44px; }

/* Status pill, used on the Yesterday card */
.lb-statuschip { padding: 3px 14px; border-radius: 999px; font-size: 16px; font-weight: 700; border: 1px solid; white-space: nowrap; }
.lb-statuschip--behind { color: #ff9d80; border-color: #ff8a65; background: rgba(255, 138, 101, 0.14); }
.lb-statuschip--on { color: #00cec8; border-color: #00cec8; background: rgba(0, 206, 200, 0.16); }

/* Yesterday screen cards */
.lb-rowline { display: flex; align-items: center; gap: 14px; font-size: 22px; font-weight: 600; }
.lb-aim { display: flex; align-items: baseline; gap: 14px; }
.lb-aim b { font-size: 72px; font-weight: 800; line-height: 1; color: #ffffff; }
.lb-aim span { font-size: 22px; color: #ffffff; }

/* Week chart: days that happened are solid. Days still to come are dashed outlines at the target. */
.lb-chart .lb-chart-bar--aim { background: rgba(255, 255, 255, 0.04); border: 2px dashed rgba(255, 255, 255, 0.55); border-bottom: 0; box-sizing: border-box; }

/* The goal marker on a rep's bar is a reference line, not an achievement, so it is white. */
.lb-board .lb-bar-goal { background: #ffffff; }
````

### File 9 of 28: `src/modules/leaderboard/board-slides.css`

````css
.lb-side { flex: 0 0 520px; width: 520px; display: flex; flex-direction: column; gap: 16px; }
.lb-side .lb-card { display: flex; flex-direction: column; gap: 10px; padding: 20px 28px; }
.lb-side-head { display: flex; align-items: center; gap: 12px; }
.lb-icon { width: 40px; height: 40px; border-radius: 50%; border: 1.5px solid #00cec8; display: flex; align-items: center; justify-content: center; color: #00cec8; flex: 0 0 auto; }
.lb-icon--gold { border-color: #ffbe0b; color: #ffbe0b; }
.lb-big { display: flex; align-items: baseline; gap: 14px; }
.lb-big b { font-size: 72px; font-weight: 800; line-height: 1; }
.lb-big b.lb-big--gold { color: #ffbe0b; }
.lb-big span { font-size: 22px; color: #c2deff; }
.lb-prog { position: relative; height: 12px; border-radius: 999px; background: rgba(255, 255, 255, 0.12); }
.lb-prog i { position: absolute; top: 0; left: 0; height: 12px; border-radius: 999px; background: #00cec8; }
.lb-top { display: flex; align-items: center; justify-content: space-between; height: 38px; }
.lb-top-name { font-size: 22px; font-weight: 600; }
.lb-top-right { display: flex; align-items: center; gap: 12px; }
.lb-dots { height: 14px; background-image: radial-gradient(circle at 11px 7px, #00cec8 6px, transparent 7px); background-size: 22px 14px; background-repeat: repeat-x; }
.lb-top-count { width: 28px; text-align: right; font-size: 24px; font-weight: 700; }
.lb-improved { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.lb-improved-who { display: flex; align-items: center; gap: 14px; min-width: 0; }
.lb-improved-name { font-size: 26px; font-weight: 700; line-height: 1.1; }
.lb-improved-sub { font-size: 18px; color: #c2deff; }
.lb-improved-delta { font-size: 40px; font-weight: 800; color: #00cec8; white-space: nowrap; }
.lb-muted { font-size: 18px; color: #c2deff; }
.lb-grow { flex: 1; min-height: 0; }

.lb-chart { position: relative; display: flex; align-items: flex-end; justify-content: space-between; height: 208px; padding: 0 8px; }
.lb-chart-target { position: absolute; left: 0; right: 0; border-top: 2px dashed #ffbe0b; }
.lb-chart-target-label { position: absolute; right: 0; font-size: 14px; font-weight: 700; color: #ffd35c; }
.lb-chart-col { width: 46px; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; }
.lb-chart-count { font-size: 20px; font-weight: 700; height: 30px; }
.lb-chart-bar { width: 34px; border-radius: 8px 8px 0 0; background: #00cec8; }
.lb-chart-label { height: 28px; display: flex; align-items: center; font-size: 14px; font-weight: 600; }

.lb-yrows { display: flex; flex-direction: column; gap: 6px; }
.lb-yrow { display: flex; align-items: center; gap: 20px; height: 82px; box-sizing: border-box; padding: 0 20px; border-radius: 16px; background: rgba(255, 255, 255, 0.04); border: 1px solid transparent; }
.lb-yrow--first { background: rgba(0, 206, 200, 0.14); border-color: rgba(0, 206, 200, 0.55); }
.lb-yname { flex: 0 0 360px; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.lb-yname .lb-rep-name { font-size: 28px; }
.lb-circles { flex: 1; display: flex; align-items: center; min-width: 0; }
.lb-circ-full, .lb-circ-empty { height: 44px; background-repeat: repeat-x; background-size: 52px 44px; }
.lb-circ-full { background-image: radial-gradient(circle at 22px 22px, #00cec8 20px, transparent 21px); }
.lb-circ-empty { background-image: radial-gradient(circle at 22px 22px, transparent 18px, rgba(255, 255, 255, 0.4) 19px, rgba(255, 255, 255, 0.4) 21px, transparent 22px); }
.lb-ycount { flex: 0 0 auto; width: 64px; text-align: right; font-size: 48px; font-weight: 800; line-height: 1; }
.lb-ring { position: relative; width: 260px; height: 260px; align-self: center; }
.lb-ring svg { position: absolute; top: 0; left: 0; }
.lb-ring-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.lb-ring-num { font-size: 96px; font-weight: 800; line-height: 1; color: #00cec8; text-shadow: 0 0 12px rgba(0, 206, 200, 0.65), 0 0 32px rgba(0, 206, 200, 0.35); }
.lb-ring-of { font-size: 20px; color: #c2deff; }
.lb-ring-msg { font-size: 28px; font-weight: 700; text-align: center; }
.lb-plan { display: flex; align-items: center; gap: 20px; height: 72px; box-sizing: border-box; padding: 0 16px; border-radius: 12px; background: rgba(255, 255, 255, 0.04); }
.lb-plan b { flex: 0 0 72px; font-size: 44px; font-weight: 800; line-height: 1; color: #00cec8; }
.lb-plan b.lb-plan--gold { color: #ffbe0b; }
.lb-plan span { font-size: 22px; font-weight: 600; }

.lb-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; text-align: center; padding: 0 160px; }
.lb-center h1 { font-size: 48px; font-weight: 700; margin: 0; letter-spacing: -0.015em; }
.lb-center p { font-size: 24px; color: #c2deff; margin: 0; max-width: 1100px; }
````

### File 10 of 28: `src/modules/leaderboard/board-streak.css`

````css
.lb-flame { display: inline-flex; align-items: center; gap: 6px; font-size: 18px; font-weight: 700; line-height: 1; }
.lb-rep-meta { display: flex; align-items: center; gap: 12px; }
.lb-streak-row { display: flex; align-items: center; justify-content: space-between; height: 44px; }
.lb-streak-who { display: flex; align-items: center; gap: 12px; min-width: 0; }
.lb-streak-name { font-size: 22px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-streak-days { display: flex; align-items: center; gap: 8px; font-size: 26px; font-weight: 800; }
.lb-streak-days small { font-size: 16px; font-weight: 600; color: #c2deff; }
.lb-streak-foot { margin-top: auto; padding-top: 10px; border-top: 1px solid rgba(255, 255, 255, 0.14); font-size: 18px; color: #c2deff; }
.lb-avatar--sm { width: 36px; height: 36px; font-size: 14px; }

/* Week table: a streak column, so the grid is a little tighter than the month table. */
.lb-board .lb-cols--week, .lb-board .lb-row--week { grid-template-columns: 100px minmax(0, 1fr) 372px 110px 140px 100px; }
.lb-board .lb-days, .lb-board .lb-cols-days { grid-template-columns: repeat(7, 48px); }

/* Yesterday rows: a header line and a streak column. */
.lb-ycols { display: flex; align-items: flex-end; gap: 20px; padding: 0 20px; font-size: 14px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: #99c9ff; }
.lb-ycols > :nth-child(1) { flex: 0 0 56px; }
.lb-ycols > :nth-child(2) { flex: 0 0 360px; }
.lb-ycols > :nth-child(3) { flex: 1; }
.lb-ycols > :nth-child(4) { flex: 0 0 110px; }
.lb-ycols > :nth-child(5) { flex: 0 0 64px; text-align: right; }
.lb-ystreak { flex: 0 0 110px; display: flex; align-items: center; }
````

### File 11 of 28: `src/modules/leaderboard/board-tip.css`

````css
.lb-goalline-row { position: relative; display: flex; align-items: center; gap: 14px; }
.lb-tip-btn { flex: 0 0 auto; width: 30px; height: 30px; padding: 0; border-radius: 50%; border: 1.5px solid #00cec8; background: transparent; color: #00cec8; font-family: inherit; font-size: 18px; font-weight: 700; line-height: 1; cursor: pointer; }
.lb-tip-btn:hover, .lb-tip-btn[aria-expanded='true'] { background: #00cec8; color: #00205b; }
.lb-tip-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(0, 206, 200, 0.45); }
.lb-tip-body { position: absolute; top: 48px; left: 0; z-index: 50; width: 540px; box-sizing: border-box; padding: 22px 26px; border-radius: 16px; background: #001844; border: 1px solid rgba(255, 255, 255, 0.28); box-shadow: 0 16px 40px rgba(0, 0, 0, 0.45); }
.lb-tip-title { font-size: 14px; font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase; color: #00cec8; margin-bottom: 12px; }
.lb-tip-body p { margin: 0 0 12px; font-size: 20px; line-height: 1.4; color: #ffffff; }
.lb-tip-body p b { color: #ffbe0b; font-weight: 700; }
.lb-tip-foot { margin: 4px 0 0; padding-top: 12px; border-top: 1px solid rgba(255, 255, 255, 0.14); font-size: 16px; line-height: 1.4; color: #c2deff; }
````

### File 12 of 28: `src/modules/leaderboard/components/parts.tsx`

````tsx
import { useContext, useState, type ReactNode } from 'react';
import { useAuth } from '@foureyes-io/foureyes-components';
import type { MonthLink, PeriodView } from '@modules/leaderboard/lib/compute';
import type { StoreInfo } from '@modules/leaderboard/lib/queries';
import { SET_RATE_BENCHMARKS } from '@modules/leaderboard/lib/benchmarks';
import { GoalContext } from '@modules/leaderboard/lib/goal-context';
import '@modules/leaderboard/board-tip.css';
import '@modules/leaderboard/board-pace.css';
import '@modules/leaderboard/board-pill.css';

export type SlideKey = 'yesterday' | 'week' | 'month';

export const barPct = (n: number, scale: number): string =>
  `${Math.max(0, Math.min(100, (n / scale) * 100))}%`;

type IconProps = { size?: number; color?: string };

const Svg = ({ size = 22, color = 'currentColor', sw = 2, children }: IconProps & { sw?: number; children: ReactNode }) => (
  <svg width={size} height={size} viewBox='0 0 24 24' fill='none' stroke={color} strokeWidth={sw} strokeLinecap='round' strokeLinejoin='round' aria-hidden='true'>
    {children}
  </svg>
);

export const ChevronIcon = (p: IconProps) => (
  <Svg {...p} sw={2.5}><path d='M6 9l6 6l6 -6' /></Svg>
);
export const ArrowUpIcon = (p: IconProps) => (
  <Svg {...p} sw={2.5}><path d='M12 5l0 14' /><path d='M18 11l-6 -6l-6 6' /></Svg>
);
export const ArrowDownIcon = (p: IconProps) => (
  <Svg {...p} sw={2.5}><path d='M12 5l0 14' /><path d='M18 13l-6 6l-6 -6' /></Svg>
);
export const FlatIcon = (p: IconProps) => (
  <Svg {...p} sw={2.5}><path d='M5 12l14 0' /></Svg>
);
export const FlameIcon = (p: IconProps) => (
  <Svg {...p}><path d='M12 12c2 -2.96 0 -7 -1 -8c0 3.038 -1.773 4.741 -3 6c-1.226 1.26 -2 3.24 -2 5a6 6 0 1 0 12 0c0 -1.532 -1.056 -3.94 -2 -5c-1.786 3 -2.791 3 -4 2z' /></Svg>
);
export const CalendarCheckIcon = (p: IconProps) => (
  <Svg {...p}>
    <path d='M11.5 21h-5.5a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v6' />
    <path d='M16 3v4' /><path d='M8 3v4' /><path d='M4 11h16' /><path d='M15 19l2 2l4 -4' />
  </Svg>
);
export const TrendIcon = (p: IconProps) => (
  <Svg {...p}><path d='M3 17l6 -6l4 4l8 -8' /><path d='M14 7l7 0l0 7' /></Svg>
);
export const BellIcon = (p: IconProps) => (
  <Svg {...p}>
    <path d='M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6' />
    <path d='M9 17v1a3 3 0 0 0 6 0v-1' />
  </Svg>
);

export function SideHead({ icon, children, gold }: { icon: ReactNode; children: ReactNode; gold?: boolean }) {
  return (
    <div className='lb-side-head'>
      <div className={gold ? 'lb-icon lb-icon--gold' : 'lb-icon'}>{icon}</div>
      <div className='lb-eyebrow'>{children}</div>
    </div>
  );
}

interface HeaderProps {
  stores: StoreInfo[];
  storeId: string;
  onStore: (id: string) => void;
  freshness: string;
  active: SlideKey;
  onTab: (key: SlideKey) => void;
}

const TABS: { key: SlideKey; label: string }[] = [
  { key: 'yesterday', label: 'Yesterday' },
  { key: 'week', label: 'This week' },
  { key: 'month', label: 'This month' },
];

// The logo is a nice-to-have: if the file is missing or does not load, show the name instead.
function BrandLogo() {
  const [failed, setFailed] = useState(false);
  if (failed) return <div className='lb-wordmark'>Foureyes</div>;
  return <img src='/foureyes-logo-white.svg' alt='Foureyes' onError={() => setFailed(true)} />;
}

export function Header({ stores, storeId, onStore, freshness, active, onTab }: HeaderProps) {
  const { logout } = useAuth();
  return (
    <div className='lb-header'>
      <div className='lb-brand'>
        <BrandLogo />
        <div className='lb-divider' />
        <div className='lb-eyebrow-brand'>Sales leaderboard</div>
      </div>
      <div className='lb-store-group'>
        <div className='lb-store'>
          <select value={storeId} onChange={(e) => onStore(e.target.value)} aria-label='Choose store'>
            {stores.map((s) => (
              <option key={s.id} value={s.id}>{s.name}</option>
            ))}
          </select>
          <ChevronIcon size={24} color='#00cec8' />
        </div>
      </div>
      <div className='lb-header-right'>
        <div className='lb-freshness'>{freshness}</div>
        <div className='lb-tabs'>
          {TABS.map((t) => (
            <button key={t.key} type='button' className='lb-tab' aria-current={active === t.key} onClick={() => onTab(t.key)}>
              {t.label}
            </button>
          ))}
        </div>
        <button type='button' className='lb-logout' onClick={() => void logout()}>
          Log out
        </button>
      </div>
    </div>
  );
}

// Plain-language explanation of goal, set rate and target, using this store's own numbers.
function GoalTip() {
  const info = useContext(GoalContext);
  const [open, setOpen] = useState(false);
  if (!info) return null;
  const top = SET_RATE_BENCHMARKS.percentiles.p75;
  const own = info.baselineSource === 'store';
  const belowTop = info.baseline < top;
  return (
    <>
      <button
        type='button'
        className='lb-tip-btn'
        aria-label='How the goal and the numbers work'
        aria-expanded={open}
        onMouseEnter={() => setOpen(true)}
        onMouseLeave={() => setOpen(false)}
        onFocus={() => setOpen(true)}
        onBlur={() => setOpen(false)}
        onClick={() => setOpen(true)}
        onKeyDown={(e) => {
          if (e.key === 'Escape') setOpen(false);
        }}
      >
        i
      </button>
      {open && (
        <div role='tooltip' className='lb-tip-body'>
          <div className='lb-tip-title'>Goal, pace and target</div>
          <p>
            <b>Set rate</b> is appointments set divided by leads received. The <b>goal</b> is the set rate we are aiming for: <b>{info.goal}%</b>.{' '}
            {own
              ? <>It starts from this store&apos;s last 26 weeks ({info.baseline}%)</>
              : <>This store does not have enough lead history yet, so it starts from the typical Foureyes store ({info.baseline}%)</>}
            {belowTop
              ? <> and moves halfway toward the top quarter of Foureyes stores ({top}% and up).</>
              : <>, which is already in the top quarter of Foureyes stores ({top}% and up), so the goal is 5% above its own average.</>}
          </p>
          <p>
            <b>Pace</b> looks back: the set rate so far, compared with the goal. Teal is on pace, coral is behind.
          </p>
          <p>
            <b>Target</b> looks forward: how many appointments to set a day from here to close the month&apos;s gap, if leads come in at the usual volume. If leads run lower, fewer appointments can still be on pace.
          </p>
          <div className='lb-tip-foot'>Gold is saved for achievements: streaks, hat tricks and first place.</div>
        </div>
      )}
    </>
  );
}

interface Status {
  behind: boolean;
  text: string;
}

function periodStatus(period: PeriodView): Status {
  const { need, early } = period;
  return need > 0
    ? { behind: true, text: `${need} appts behind` }
    : { behind: false, text: early ? 'On pace so far' : 'On pace' };
}

// The month as seen from the week: how far behind it is, or on pace.
function monthStatus(link: MonthLink): Status {
  return link.gap > 0
    ? { behind: true, text: `${link.gap} appts behind` }
    : { behind: false, text: 'On pace' };
}

// "34% fewer than usual" becomes "34% fewer leads than usual", so it is clear what it counts.
function leadsPhrase(note: string): string {
  return note.replace(/^(\d+% (?:fewer|more)) than usual$/, '$1 leads than usual');
}

function RateBlock({ label, rate, status, goal }: { label: string; rate: number; status: Status; goal?: number }) {
  return (
    <div className='lb-block'>
      <div className='lb-block-label'>{label}</div>
      <div className='lb-block-value lb-block-value--rate'>{rate}%</div>
      <div className='lb-block-cap'>
        <span className={status.behind ? 'lb-st lb-st--behind' : 'lb-st lb-st--on'}>{status.text}</span>
        {goal !== undefined ? ` \u00b7 Goal ${goal}%` : ''}
      </div>
    </div>
  );
}

// Left to right: the rates side by side, then the two appointment counts.
export function Strip({
  period,
  eyebrow,
  link,
  monthRate,
}: {
  period: PeriodView;
  eyebrow: string;
  link: MonthLink;
  monthRate: number;
}) {
  const { teamRate, set, opps, leadNote } = period;
  const isWeek = period.key === 'week';
  const hasTarget = link.perDay !== null && link.perDay > 0 && link.endLabel !== null;
  return (
    <div className='lb-strip'>
      <RateBlock label={eyebrow} rate={teamRate} status={periodStatus(period)} goal={period.goal} />
      {isWeek && <RateBlock label='Month set rate' rate={monthRate} status={monthStatus(link)} />}

      <div className='lb-block'>
        <div className='lb-block-label'>Appointments set</div>
        <div className='lb-block-value'>{set}</div>
        <div className='lb-block-cap'>{opps} leads{leadNote ? ` \u00b7 ${leadsPhrase(leadNote)}` : ''}</div>
      </div>

      {hasTarget && (
        <div className='lb-block'>
          <div className='lb-block-label'>Target at usual lead volume</div>
          <div className='lb-block-value'>
            {link.perDay}
            <span>a day</span>
          </div>
          <div className='lb-block-cap'>
            {link.gap > 0 ? `closes the gap by ${link.endLabel}` : `stays on pace through ${link.endLabel}`}
          </div>
        </div>
      )}

      <div className='lb-strip-tip'>
        <GoalTip />
      </div>
    </div>
  );
}
````

### File 13 of 28: `src/modules/leaderboard/components/rep-table.tsx`

````tsx
import { createContext, useContext, type CSSProperties } from 'react';
import type { DayCell, PeriodView, RepRow } from '@modules/leaderboard/lib/compute';
import { DAY_LABELS } from '@modules/leaderboard/lib/dates';
import {
  ArrowDownIcon,
  ArrowUpIcon,
  FlatIcon,
} from '@modules/leaderboard/components/parts';
import { StreakBadge } from '@modules/leaderboard/components/streak-badge';
import '@modules/leaderboard/board-bar.css';

export const PAGE_SIZE = 8;

// When provided, the page dots become buttons so a viewer can flip pages by hand.
export const PageContext = createContext<((page: number) => void) | undefined>(undefined);

function cellStyle(cell: DayCell): CSSProperties {
  if (cell.state === 'future') return { background: 'rgba(255,255,255,0.04)' };
  if (cell.state === 'today') {
    return { background: 'rgba(0,206,200,0.08)', border: '1px dashed rgba(0,206,200,0.8)' };
  }
  if (cell.count === 0) {
    return { color: 'rgba(194,222,255,0.6)', border: '1px solid rgba(255,255,255,0.25)' };
  }
  if (cell.count === 1) return { background: '#007a76', color: '#ffffff' };
  if (cell.count === 2) return { background: '#00a39e', color: '#00205b' };
  return { background: '#00cec8', color: '#00205b' };
}

function Move({ row }: { row: RepRow }) {
  const dir = Math.sign(row.move);
  const color = dir > 0 ? '#00cec8' : dir < 0 ? 'rgba(255,255,255,0.75)' : 'rgba(194,222,255,0.55)';
  return (
    <div className='lb-move' style={{ color }}>
      {dir > 0 ? <ArrowUpIcon size={18} color={color} /> : dir < 0 ? <ArrowDownIcon size={18} color={color} /> : <FlatIcon size={18} color={color} />}
      <span>{dir === 0 ? '' : Math.abs(row.move)}</span>
    </div>
  );
}

function Row({ row, period, maxOpps }: { row: RepRow; period: PeriodView; maxOpps: number }) {
  const isWeek = period.key === 'week';
  const rankClass = row.rank === 1 ? 'lb-rank-badge lb-rank-badge--1' : row.rank <= 3 ? 'lb-rank-badge lb-rank-badge--top' : 'lb-rank-badge';
  const rowClass = ['lb-row', isWeek ? 'lb-row--week' : 'lb-row--month', row.rank === 1 ? 'lb-row--first' : ''].join(' ');
  const fillColor = row.rate >= period.goal ? '#00cec8' : 'rgba(255,255,255,0.6)';
  const fillPct = Math.min(100, (row.set / Math.max(1, row.opps)) * 100);

  return (
    <div className={rowClass}>
      <div className='lb-rank'>
        <div className={rankClass}>{row.rank}</div>
        <Move row={row} />
      </div>
      <div className='lb-rep'>
        <div className='lb-avatar'>{row.initials}</div>
        <div className='lb-rep-text'>
          <div className='lb-rep-name' style={{ fontSize: 26 }}>{row.name}</div>
        </div>
      </div>
      {isWeek ? (
        <div className='lb-days'>
          {row.days.map((cell) => (
            <div key={cell.date} className='lb-cell' style={cellStyle(cell)}>
              {cell.state === 'done' ? cell.count : ''}
            </div>
          ))}
        </div>
      ) : (
        <div className='lb-rate'>
          <div className='lb-rate-num'>{row.rate}%</div>
          <div className='lb-bar'>
            <div className='lb-bar-len' style={{ width: `${(row.opps / maxOpps) * 100}%` }}>
              <div className='lb-bar-fill' style={{ width: `${fillPct}%`, background: fillColor }} />
              <div className='lb-bar-goal' style={{ left: `${Math.min(100, period.goal)}%` }} />
            </div>
          </div>
        </div>
      )}
      {isWeek && <div className='lb-rate-num' style={{ width: 'auto' }}>{row.rate}%</div>}
      <div className='lb-leads'>
        <b>{row.set}</b>
        <span>on {row.opps} leads</span>
      </div>
      <StreakBadge days={row.streak} size={26} />
    </div>
  );
}

interface RepTableProps {
  period: PeriodView;
  title: string;
  page: number;
}

export function RepTable({ period, title, page }: RepTableProps) {
  const onPage = useContext(PageContext);
  const isWeek = period.key === 'week';
  const pages = Math.max(1, Math.ceil(period.rows.length / PAGE_SIZE));
  const shown = period.rows.slice(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE);
  // The busiest rep's bar is full width, across every page, so bars stay comparable.
  const maxOpps = Math.max(1, ...period.rows.map((r) => r.opps));

  return (
    <div className='lb-card lb-board'>
      <div className='lb-board-head'>
        <div className='lb-board-title'>{title}</div>
        <div className='lb-board-note'>
          Set rate is appointments set divided by leads received. Ranked by set rate.
        </div>
      </div>
      <div className={isWeek ? 'lb-cols lb-cols--week' : 'lb-cols lb-cols--month'}>
        <div>Rank</div>
        <div>Rep</div>
        {isWeek ? (
          <div className='lb-cols-days'>
            {DAY_LABELS.map((d) => (
              <div key={d}>{d}</div>
            ))}
          </div>
        ) : (
          <div>Set rate</div>
        )}
        {isWeek && <div>Set rate</div>}
        <div>Appts set</div>
        <div>Streak</div>
      </div>
      <div className='lb-rows'>
        {shown.map((row) => (
          <Row key={row.id} row={row} period={period} maxOpps={maxOpps} />
        ))}
      </div>
      <div className='lb-board-foot'>
        <span className='lb-spacer'>{isWeek ? 'Each box is appointments set that day. Flame is days in a row with an appointment set.' : 'Bar: leads received, filled part appointments set, tick is the goal. Flame: days in a row.'}</span>
        {pages > 1 && (
          <div className='lb-pages'>
            {Array.from({ length: pages }, (_, i) =>
              onPage ? (
                <button key={i} type='button' aria-label={`Show reps ${i * PAGE_SIZE + 1} to ${Math.min(period.rows.length, (i + 1) * PAGE_SIZE)}`} aria-current={i === page} onClick={() => onPage(i)} />
              ) : (
                <i key={i} aria-current={i === page} />
              )
            )}
          </div>
        )}
      </div>
    </div>
  );
}
````

### File 14 of 28: `src/modules/leaderboard/components/side-cards.tsx`

````tsx
import type { MonthLink, PeriodView, StreakRep, YesterdayView } from '@modules/leaderboard/lib/compute';
import {
  CalendarCheckIcon,
  FlameIcon,
  SideHead,
  TrendIcon,
} from '@modules/leaderboard/components/parts';
import { streakColor } from '@modules/leaderboard/components/streak-badge';

// Month screen: yesterday's count, with no target beside it. A past count is not compared with a target.
export function YesterdayCard({ y }: { y: YesterdayView }) {
  const top = y.rows.filter((r) => r.count > 0).slice(0, 3);
  return (
    <div className='lb-card'>
      <SideHead icon={<CalendarCheckIcon />}>Appointments set yesterday</SideHead>
      <div className='lb-big'>
        <b>{y.total}</b>
        <span>on {y.leads} leads, {y.rate}% set rate</span>
      </div>
      {top.map((r) => (
        <div key={r.id} className='lb-top'>
          <div className='lb-top-name'>{r.name}</div>
          <div className='lb-top-right'>
            <div className='lb-dots' style={{ width: Math.min(r.count, 8) * 22 }} />
            <div className='lb-top-count'>{r.count}</div>
          </div>
        </div>
      ))}
      {y.leadNote && <div className='lb-muted'>Leads yesterday: {y.leadNote}</div>}
    </div>
  );
}

// Yesterday screen: how yesterday went, as pace.
export function YesterdayPaceCard({ y }: { y: YesterdayView }) {
  return (
    <div className='lb-card'>
      <SideHead icon={<CalendarCheckIcon />}>Yesterday</SideHead>
      <div className='lb-big'>
        <b>{y.total}</b>
        <span>appointments on {y.leads} leads</span>
      </div>
      <div className='lb-rowline'>
        <span className={y.onPace ? 'lb-statuschip lb-statuschip--on' : 'lb-statuschip lb-statuschip--behind'}>
          {y.onPace ? 'On pace' : 'Behind'}
        </span>
        <span>{y.rate}% set rate</span>
      </div>
      {y.leadNote && <div className='lb-muted'>Leads were {y.leadNote}</div>}
    </div>
  );
}

// Yesterday screen: the one forward-looking number, a count for today.
export function TodayTargetCard({ link }: { link: MonthLink }) {
  if (link.perDay === null || link.perDay <= 0 || !link.endLabel) return null;
  return (
    <div className='lb-card'>
      <SideHead icon={<TrendIcon />}>Target: today</SideHead>
      <div className='lb-aim'>
        <b>{link.perDay}</b>
        <span>appointments</span>
      </div>
      <div className='lb-muted'>
        {link.gap > 0
          ? `At usual lead volume, closes the month's gap by ${link.endLabel}`
          : `At usual lead volume, keeps the month on pace through ${link.endLabel}`}
      </div>
    </div>
  );
}

interface StreakCardProps {
  title: string;
  reps: StreakRep[];
  limit: number;
  /** Reps with no streak, shown as a nudge to start one */
  noStreak?: number;
  grow?: boolean;
}

// Longest active streaks. Used for the leaders on Week and Month, and for the streaks on the line
// today on the Yesterday screen.
export function StreakCard({ title, reps, limit, noStreak, grow }: StreakCardProps) {
  const shown = reps.slice(0, limit);
  return (
    <div className={grow ? 'lb-card lb-grow' : 'lb-card'}>
      <SideHead icon={<FlameIcon />} gold>{title}</SideHead>
      {shown.length === 0 ? (
        <div className='lb-muted'>Nobody has a streak yet. Set an appointment to start one.</div>
      ) : (
        shown.map((r) => (
          <div key={r.id} className='lb-streak-row'>
            <div className='lb-streak-who'>
              <div className='lb-avatar lb-avatar--sm'>{r.initials}</div>
              <div className='lb-streak-name'>{r.name}</div>
            </div>
            <div className='lb-streak-days' style={{ color: streakColor(r.days) }}>
              <FlameIcon size={24} color={streakColor(r.days)} />
              <span>{r.days}</span>
              <small>day{r.days === 1 ? '' : 's'}</small>
            </div>
          </div>
        ))
      )}
      {noStreak !== undefined && noStreak > 0 && (
        <div className='lb-streak-foot'>
          {noStreak} rep{noStreak === 1 ? '' : 's'} can start a streak today
        </div>
      )}
    </div>
  );
}

// Past days are plain counts. Days still to come show a dashed bar at the target.
export function DayChartCard({ period, aim }: { period: PeriodView; aim: number | null }) {
  const days = period.dayTotals;
  const showAim = aim !== null && aim > 0 && !period.isFinal;
  const maxCount = days.reduce((m, d) => Math.max(m, d.count), 0);
  const unit = 140 / Math.max(showAim ? (aim as number) : 0, maxCount, 1);
  return (
    <div className='lb-card'>
      <SideHead icon={<CalendarCheckIcon />}>Appointments by day</SideHead>
      <div className='lb-muted'>{showAim ? `Target: ${aim} a day at usual lead volume` : 'Appointments set each day'}</div>
      <div className='lb-chart'>
        {days.map((d) => {
          const done = d.state === 'done';
          const height = done ? d.count * unit : showAim ? (aim as number) * unit : 0;
          return (
            <div key={d.label} className='lb-chart-col'>
              <div className='lb-chart-count'>{done ? d.count : ''}</div>
              <div className={done ? 'lb-chart-bar' : 'lb-chart-bar lb-chart-bar--aim'} style={{ height }} />
              <div className='lb-chart-label' style={{ color: d.state === 'today' ? '#00cec8' : d.state === 'future' ? 'rgba(194,222,255,0.5)' : '#c2deff' }}>{d.label}</div>
            </div>
          );
        })}
      </div>
    </div>
  );
}
````

### File 15 of 28: `src/modules/leaderboard/components/slides.tsx`

````tsx
import type { BoardView, PeriodView, YesterdayRow } from '@modules/leaderboard/lib/compute';
import { Strip } from '@modules/leaderboard/components/parts';
import { RepTable } from '@modules/leaderboard/components/rep-table';
import {
  DayChartCard,
  StreakCard,
  TodayTargetCard,
  YesterdayCard,
  YesterdayPaceCard,
} from '@modules/leaderboard/components/side-cards';
import { StreakBadge } from '@modules/leaderboard/components/streak-badge';

const SLIDE_STYLE = { display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, gap: 20 } as const;

export function PeriodSlide({ view, period, page }: { view: BoardView; period: PeriodView; page: number }) {
  const isWeek = period.key === 'week';
  const name = isWeek
    ? period.isFinal ? 'Last week, final' : 'This week, day by day'
    : period.isFinal ? 'Last month, final' : 'This month';
  const eyebrow = isWeek
    ? period.isFinal ? 'Last week set rate' : 'Week set rate'
    : period.isFinal ? 'Last month set rate' : 'Month set rate';
  return (
    <div className='lb-fade' style={SLIDE_STYLE}>
      <Strip period={period} eyebrow={eyebrow} link={view.monthLink} monthRate={view.month.teamRate} />
      <div className='lb-body'>
        <RepTable period={period} title={name} page={page} />
        <div className='lb-side'>
          {isWeek ? (
            <DayChartCard period={period} aim={view.monthLink.perDay} />
          ) : (
            <YesterdayCard y={view.yesterdayView} />
          )}
          <StreakCard title='Longest streaks' reps={view.streaks} limit={5} grow />
        </div>
      </div>
    </div>
  );
}

function chipClass(chip: YesterdayRow['chip']): string {
  if (chip === 'Hat trick') return 'lb-chip lb-chip--gold';
  if (chip === 'First on the board') return 'lb-chip lb-chip--first';
  if (chip === 'On the board') return 'lb-chip lb-chip--on';
  return 'lb-chip lb-chip--building';
}

export function YesterdaySlide({ view }: { view: BoardView }) {
  const y = view.yesterdayView;
  const rows = y.rows.slice(0, 8);
  return (
    <div className='lb-fade' style={{ ...SLIDE_STYLE, flexDirection: 'row', gap: 24 }}>
      <div className='lb-card lb-board'>
        <div className='lb-board-head'>
          <div className='lb-board-title' style={{ fontSize: 40 }}>Who got on the board yesterday.</div>
          <div className='lb-board-note'>Every filled circle is an appointment set.</div>
        </div>
        <div className='lb-ycols'>
          <div />
          <div>Rep</div>
          <div>Appointments set</div>
          <div>Streak</div>
          <div>Total</div>
        </div>
        <div className='lb-yrows'>
          {rows.map((r, i) => (
            <div key={r.id} className={i === 0 && r.count > 0 ? 'lb-yrow lb-yrow--first' : 'lb-yrow'}>
              <div className='lb-avatar lb-avatar--lg'>{r.initials}</div>
              <div className='lb-yname'>
                <div className='lb-rep-name'>{r.name}</div>
                <div className={chipClass(r.chip)}>{r.chip}</div>
              </div>
              <div className='lb-circles'>
                <div className='lb-circ-full' style={{ width: r.count * 52 }} />
                <div className='lb-circ-empty' style={{ width: Math.max(0, view.personalTarget - r.count) * 52 }} />
              </div>
              <div className='lb-ystreak'>
                <StreakBadge days={r.streak} size={26} />
              </div>
              <div className='lb-ycount' style={{ color: r.count >= 3 ? '#ffbe0b' : r.count === 0 ? 'rgba(194,222,255,0.5)' : '#ffffff' }}>{r.count}</div>
            </div>
          ))}
        </div>
        <div className='lb-board-foot'>
          <span className='lb-chip lb-chip--gold'>Hat trick</span>
          <span>3 or more in a day</span>
          <span className='lb-chip lb-chip--first'>First on the board</span>
          <span>first appointment of the day</span>
          <span className='lb-spacer'>Empty circles show the personal target of {view.personalTarget}. Flame is days in a row.</span>
        </div>
      </div>
      <div className='lb-side'>
        <YesterdayPaceCard y={y} />
        <TodayTargetCard link={view.monthLink} />
        <StreakCard title='Streaks on the line today' reps={view.streaks} limit={4} noStreak={view.noStreak} grow />
      </div>
    </div>
  );
}
````

### File 16 of 28: `src/modules/leaderboard/components/streak-badge.tsx`

````tsx
import { FlameIcon } from '@modules/leaderboard/components/parts';
import '@modules/leaderboard/board-streak.css';

export const streakColor = (n: number): string =>
  n >= 5 ? '#ffbe0b' : n > 0 ? '#ffffff' : 'rgba(194,222,255,0.5)';

// Days in a row with at least one appointment set. Shown from day one, and a dim flame at 0
// so a rep without a streak can see there is one to start.
export function StreakBadge({ days, size = 18 }: { days: number; size?: number }) {
  const color = streakColor(days);
  return (
    <span
      className='lb-flame'
      style={{ color, fontSize: size }}
      title={days > 0 ? `${days} day${days === 1 ? '' : 's'} in a row with an appointment set` : 'No streak yet'}
    >
      <FlameIcon size={size} color={color} />
      <span>{days}</span>
    </span>
  );
}
````

### File 17 of 28: `src/modules/leaderboard/lib/baseline.ts`

````ts
import { runQuery } from '@modules/leaderboard/lib/queries';

export interface Baseline {
  leads: number;
  sets: number;
}

// The store's trailing 26 weeks (182 days ending yesterday, store-local), on the same definition
// the board uses everywhere: appointments set (a lead's first appointment, by booking day) and
// leads received, both for leads owned by an active salesperson.
export async function fetchBaseline(organizationId: string, storeId: string): Promise<Baseline> {
  if (!/^[A-Za-z0-9]+$/.test(storeId)) throw new Error('Unexpected store id');
  const id = storeId;
  const rows = await runQuery(
    organizationId,
    `WITH loc AS (
  SELECT ID, TIMEZONE, CAST(CONVERT_TIMEZONE(TIMEZONE, CURRENT_TIMESTAMP()) AS DATE) AS TODAY
  FROM FOUREYES.PUBLIC.LOCATIONS WHERE ID = '${id}'
),
owner AS (
  SELECT st.CUSTOMER_OPPORTUNITY_ID AS OPP_ID,
         COALESCE(MAX(IFF(st."PRIMARY", st.SALESPERSON_ID, NULL)), MAX(st.SALESPERSON_ID)) AS SP_ID
  FROM FOUREYES.PUBLIC.CUSTOMER_OPPORTUNITY_SALES_TEAM st
  WHERE st.ACTIVE AND st.ROLE_TYPE = 'SALESPERSON' AND st.LOCATION_ID = '${id}'
  GROUP BY st.CUSTOMER_OPPORTUNITY_ID
),
firsts AS (
  SELECT ID AS FIRST_ID FROM (
    SELECT a.ID, ROW_NUMBER() OVER (PARTITION BY a.CUSTOMER_OPPORTUNITY_ID ORDER BY a.APPOINTMENT_CREATED_AT, a.ID) AS RN
    FROM FOUREYES.PUBLIC.CUSTOMER_OPPORTUNITY_APPOINTMENTS a
    WHERE a.LOCATION_ID = '${id}' AND a.APPOINTMENT_CREATED_AT IS NOT NULL
  ) WHERE RN = 1
),
leads AS (
  SELECT COUNT(DISTINCT co.ID) AS LEADS
  FROM FOUREYES.PUBLIC.CUSTOMER_OPPORTUNITIES co
  JOIN loc ON loc.ID = co.LOCATION_ID
  JOIN owner o ON o.OPP_ID = co.ID
  JOIN FOUREYES.PUBLIC.CRM_SALESPEOPLE sp ON sp.ID = o.SP_ID AND sp.LOCATION_ID = co.LOCATION_ID
  WHERE co.LOCATION_ID = '${id}' AND sp.ACTIVE
    AND co.OPPORTUNITY_AT >= DATEADD(day, -190, CURRENT_DATE())
    AND CAST(CONVERT_TIMEZONE('UTC', loc.TIMEZONE, co.OPPORTUNITY_AT) AS DATE) BETWEEN DATEADD(day, -182, loc.TODAY) AND DATEADD(day, -1, loc.TODAY)
),
sets AS (
  SELECT COUNT(DISTINCT a.ID) AS SETS
  FROM FOUREYES.PUBLIC.CUSTOMER_OPPORTUNITY_APPOINTMENTS a
  JOIN firsts f ON f.FIRST_ID = a.ID
  JOIN loc ON loc.ID = a.LOCATION_ID
  JOIN owner o ON o.OPP_ID = a.CUSTOMER_OPPORTUNITY_ID
  JOIN FOUREYES.PUBLIC.CRM_SALESPEOPLE sp ON sp.ID = o.SP_ID AND sp.LOCATION_ID = a.LOCATION_ID
  WHERE a.LOCATION_ID = '${id}' AND sp.ACTIVE
    AND a.APPOINTMENT_CREATED_AT >= DATEADD(day, -190, CURRENT_DATE())
    AND CAST(CONVERT_TIMEZONE('UTC', loc.TIMEZONE, a.APPOINTMENT_CREATED_AT) AS DATE) BETWEEN DATEADD(day, -182, loc.TODAY) AND DATEADD(day, -1, loc.TODAY)
)
SELECT leads.LEADS AS leads, sets.SETS AS sets FROM leads, sets`
  );
  return { leads: Number(rows[0]?.leads ?? 0), sets: Number(rows[0]?.sets ?? 0) };
}
````

### File 18 of 28: `src/modules/leaderboard/lib/benchmarks.ts`

````ts
// Static benchmark table: Foureyes appointment set rate, aggregated across 12 dealer groups and
// 434 stores, 26 weeks ending 2026-10-05. No store or group identifiers. Replace this table to
// update the benchmark; nothing else in the app needs to change.
export const SET_RATE_BENCHMARKS = {
  version: '2026-10-06',
  windowWeeks: 26,
  minLeads: 500,
  percentiles: { p10: 9.5, p25: 11.5, p50: 15.5, p75: 18.5, p90: 22.5 },
} as const;

// Vocabulary used everywhere on the board:
//   GOAL    the set rate we are aiming for, a percentage
//   PACE    a window's set rate so far, compared with the goal (looks back, in rates)
//   TARGET  appointments to set from here, spread over the days left (looks forward, in counts)
export interface GoalInfo {
  /** Whole-number set-rate goal, in percent */
  goal: number;
  /** The store's own trailing 26-week set rate, or the benchmark median when it has too few leads */
  baseline: number;
  baselineSource: 'store' | 'benchmark-median';
  /** Leads the store typically gets in a day, from the same 26 weeks */
  typicalLeadsPerDay: number;
}

// Goal rule, from the benchmark file:
//   baseline = store's own set rate over the trailing 26 weeks (benchmark p50 if under 500 leads)
//   below p75:           goal = baseline + 0.5 x (p75 - baseline)
//   at or above p75:     goal = baseline x 1.05
export function computeGoal(sets: number, leads: number): GoalInfo {
  const b = SET_RATE_BENCHMARKS;
  const enough = leads >= b.minLeads;
  const baseline = enough ? (sets / leads) * 100 : b.percentiles.p50;
  const goal =
    baseline < b.percentiles.p75
      ? baseline + 0.5 * (b.percentiles.p75 - baseline)
      : baseline * 1.05;
  return {
    goal: Math.max(1, Math.round(goal)),
    baseline: Math.round(baseline * 10) / 10,
    baselineSource: enough ? 'store' : 'benchmark-median',
    typicalLeadsPerDay: leads / (b.windowWeeks * 7),
  };
}
````

### File 19 of 28: `src/modules/leaderboard/lib/compute.ts`

````ts
import { addDays, daysBetween, formatWeekday, monthStart, mondayOf } from './dates';
import type { ApptRow, OppRow, StoreInfo } from './queries';

// VOCABULARY, used the same way everywhere on the board:
//   GOAL    the set rate we are aiming for, a percentage
//   PACE    a window's set rate so far, compared with the goal. It looks back, in rates.
//   TARGET  appointments to set from here, spread over the days left. It looks forward, in counts.
// A past count is never compared with a count to aim for.
//
// SET RATE, defined once and used at every level (store, rep, day, week, month):
//   set rate = appointments set in the period / leads received in the period
// An appointment is "set" on the day it is booked, and only a lead's first appointment counts,
// so a reschedule is not a second set. The lead itself can have arrived in any earlier period.
//
// RANKING: reps are ranked by set rate, plain and simple. Ties go to the rep with more
// appointments set, then to the rep with fewer leads, then by name.
//
// STREAK = consecutive store open days, ending yesterday, with at least one appointment set.
// A day when the rep received no leads and set nothing is treated as a day off: it neither adds
// to the streak nor breaks it. More than two such days in a row ends the streak.

export interface RawBoard {
  store: StoreInfo;
  opp: OppRow[];
  appt: ApptRow[];
}

export interface BoardConfig {
  /** Set-rate goal in percent. Computed from the store's own history, see benchmarks.ts. */
  goal?: number;
  /** Leads the store typically gets in a day */
  typicalLeadsPerDay?: number;
  /** Personal per-rep daily target shown as empty circles. */
  personalTarget: number;
}

export interface DayCell {
  date: string;
  state: 'done' | 'today' | 'future';
  count: number;
}

export interface RepRow {
  id: string;
  name: string;
  initials: string;
  rank: number;
  /** Positive = moved up since the day before */
  move: number;
  /** Leads received in the period */
  opps: number;
  /** Appointments set in the period */
  set: number;
  /** Set rate, in percent */
  rate: number;
  streak: number;
  days: DayCell[];
}

export interface PeriodView {
  key: 'week' | 'month';
  start: string;
  end: string;
  isFinal: boolean;
  goal: number;
  /** Axis scale for the rate bars in the rep table */
  scale: number;
  /** The window's pace: set rate so far */
  teamRate: number;
  /** Appointments set so far */
  set: number;
  /** Leads received so far */
  opps: number;
  /** Appointments still needed to be on pace at the leads received so far (never negative) */
  need: number;
  /** True while the sample is small */
  early: boolean;
  /** How lead volume compares with the usual, in words */
  leadNote: string | null;
  rows: RepRow[];
  dayTotals: { label: string; count: number; state: DayCell['state'] }[];
}

export interface YesterdayRow {
  id: string;
  name: string;
  initials: string;
  count: number;
  streak: number;
  chip: 'Hat trick' | 'First on the board' | 'On the board' | 'On deck';
}

export interface YesterdayView {
  date: string;
  total: number;
  /** Leads received yesterday */
  leads: number;
  /** Yesterday's pace: set rate */
  rate: number;
  onPace: boolean;
  leadNote: string | null;
  rows: YesterdayRow[];
}

export interface StreakRep {
  id: string;
  name: string;
  initials: string;
  days: number;
}

/**
 * Where the month stands, and what to do next about it.
 * gap is how many appointments the month is behind pace (negative when ahead).
 */
export interface MonthLink {
  gap: number;
  /** Appointments a day, from today to month end, that close the gap (or keep the month on pace) */
  perDay: number | null;
  endLabel: string | null;
}

export interface BoardView {
  storeName: string;
  yesterday: string;
  today: string;
  goal: number;
  personalTarget: number;
  week: PeriodView;
  month: PeriodView;
  monthLink: MonthLink;
  yesterdayView: YesterdayView;
  /** Active streaks, longest first */
  streaks: StreakRep[];
  /** Reps on the roster with no streak right now */
  noStreak: number;
}

/** Under this many leads, a window's pace is a small sample. */
const EARLY_LEADS = 100;

const initialsOf = (name: string): string =>
  name
    .split(/\s+/)
    .filter(Boolean)
    .map((p) => p[0])
    .slice(0, 2)
    .join('')
    .toUpperCase();

interface Agg {
  id: string;
  name: string;
  opps: number;
  set: number;
}

function aggregate(opp: OppRow[], appt: ApptRow[], start: string, end: string): Map<string, Agg> {
  const reps = new Map<string, Agg>();
  const get = (id: string, name: string): Agg => {
    let a = reps.get(id);
    if (!a) {
      a = { id, name, opps: 0, set: 0 };
      reps.set(id, a);
    }
    return a;
  };
  for (const o of opp) {
    if (o.d < start || o.d > end) continue;
    get(o.spId, o.name).opps += o.opps;
  }
  for (const p of appt) {
    if (p.d < start || p.d > end) continue;
    get(p.spId, p.name).set += p.sets;
  }
  return reps;
}

const pct = (num: number, den: number): number => (den > 0 ? Math.round((num / den) * 100) : 0);

/** Lead volume against the usual, in words. Null when there is nothing to compare with. */
function leadNoteFor(leads: number, expected: number): string | null {
  if (expected <= 0) return null;
  const delta = Math.round((leads / expected - 1) * 100);
  if (delta <= -10) return `${Math.abs(delta)}% fewer than usual`;
  if (delta >= 10) return `${delta}% more than usual`;
  return 'about the usual';
}

/** Every rep with leads, best first, by set rate. */
function rankAgg(reps: Map<string, Agg>): Agg[] {
  return [...reps.values()]
    .filter((a) => a.opps > 0)
    .sort(
      (a, b) =>
        b.set / b.opps - a.set / a.opps || b.set - a.set || a.opps - b.opps || a.name.localeCompare(b.name)
    );
}

const MAX_OFF_RUN = 2;

function streakFor(
  repId: string,
  setByRepDay: Map<string, number>,
  leadsByRepDay: Map<string, number>,
  openDays: Set<string>,
  yday: string
): number {
  let streak = 0;
  let offRun = 0;
  let d = yday;
  for (let i = 0; i < 50; i++) {
    if (openDays.has(d)) {
      const key = `${repId}|${d}`;
      if ((setByRepDay.get(key) ?? 0) > 0) {
        streak += 1;
        offRun = 0;
      } else if ((leadsByRepDay.get(key) ?? 0) > 0) {
        break; // had leads and set nothing: the streak ends
      } else {
        offRun += 1; // a day off: neither extends nor breaks, up to a short run
        if (offRun > MAX_OFF_RUN) break;
      }
    }
    d = addDays(d, -1);
  }
  return streak;
}

function daysInMonthOf(iso: string): number {
  const [y, m] = iso.split('-').map(Number);
  return new Date(Date.UTC(y, m, 0)).getUTCDate();
}

function shortDate(iso: string): string {
  return new Date(`${iso}T00:00:00Z`).toLocaleDateString('en-US', {
    month: 'short',
    day: 'numeric',
    timeZone: 'UTC',
  });
}

export function buildBoard(raw: RawBoard, cfg: BoardConfig): BoardView {
  const { store, opp, appt } = raw;
  const today = store.today;
  const yday = addDays(today, -1);

  // Fallback goal from the last 30 days if the caller does not supply a computed one.
  const baseStart = addDays(yday, -29);
  let baseOpps = 0;
  let baseSet = 0;
  for (const o of opp) {
    if (o.d >= baseStart && o.d <= yday) baseOpps += o.opps;
  }
  for (const p of appt) {
    if (p.d >= baseStart && p.d <= yday) baseSet += p.sets;
  }
  const goal = Math.max(1, cfg.goal ?? pct(baseSet, baseOpps));
  const typical = cfg.typicalLeadsPerDay ?? 0;

  const setByRepDay = new Map<string, number>();
  const storeDay = new Map<string, number>();
  for (const p of appt) {
    setByRepDay.set(`${p.spId}|${p.d}`, (setByRepDay.get(`${p.spId}|${p.d}`) ?? 0) + p.sets);
    storeDay.set(p.d, (storeDay.get(p.d) ?? 0) + p.sets);
  }
  const leadsByRepDay = new Map<string, number>();
  const leadsByDay = new Map<string, number>();
  for (const o of opp) {
    leadsByRepDay.set(`${o.spId}|${o.d}`, (leadsByRepDay.get(`${o.spId}|${o.d}`) ?? 0) + o.opps);
    leadsByDay.set(o.d, (leadsByDay.get(o.d) ?? 0) + o.opps);
  }
  const openDays = new Set<string>([...storeDay.entries()].filter(([, n]) => n > 0).map(([d]) => d));

  const streaks = new Map<string, number>();
  const streakOf = (id: string): number => {
    if (!streaks.has(id)) streaks.set(id, streakFor(id, setByRepDay, leadsByRepDay, openDays, yday));
    return streaks.get(id) ?? 0;
  };

  function period(key: 'week' | 'month'): PeriodView {
    const start = key === 'week' ? mondayOf(yday) : monthStart(yday);
    const end = yday;
    const isFinal = key === 'week' ? mondayOf(today) !== start : monthStart(today) !== start;
    const length = daysBetween(start, end);

    const current = aggregate(opp, appt, start, end);
    const ranked = rankAgg(current);

    // Rank as of the day before, for movement arrows.
    const prevRank = new Map<string, number>();
    if (end > start) {
      const prev = aggregate(opp, appt, start, addDays(end, -1));
      rankAgg(prev).forEach((a, i) => prevRank.set(a.id, i + 1));
    }

    const dayCells = (id: string): DayCell[] => {
      if (key !== 'week') return [];
      return Array.from({ length: 7 }, (_, i) => {
        const date = addDays(start, i);
        if (date <= end) return { date, state: 'done' as const, count: setByRepDay.get(`${id}|${date}`) ?? 0 };
        if (date === today) return { date, state: 'today' as const, count: 0 };
        return { date, state: 'future' as const, count: 0 };
      });
    };

    const rows: RepRow[] = ranked.map((a, i) => ({
      id: a.id,
      name: a.name,
      initials: initialsOf(a.name),
      rank: i + 1,
      move: prevRank.has(a.id) ? (prevRank.get(a.id) as number) - (i + 1) : 0,
      opps: a.opps,
      set: a.set,
      rate: pct(a.set, a.opps),
      streak: streakOf(a.id),
      days: dayCells(a.id),
    }));

    let teamOpps = 0;
    let teamSet = 0;
    for (const a of current.values()) {
      teamOpps += a.opps;
      teamSet += a.set;
    }
    const teamRate = pct(teamSet, teamOpps);
    const need = Math.max(0, Math.ceil((goal / 100) * teamOpps) - teamSet);
    const maxRate = rows.reduce((m, r) => Math.max(m, r.rate), 0);
    const scale = Math.max(60, Math.ceil((Math.max(goal, teamRate, maxRate) + 10) / 10) * 10);

    const dayTotals =
      key === 'week'
        ? Array.from({ length: 7 }, (_, i) => {
            const date = addDays(start, i);
            const state: DayCell['state'] = date <= end ? 'done' : date === today ? 'today' : 'future';
            return { label: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][i], count: storeDay.get(date) ?? 0, state };
          })
        : [];

    return {
      key,
      start,
      end,
      isFinal,
      goal,
      scale,
      teamRate,
      set: teamSet,
      opps: teamOpps,
      need,
      early: teamOpps < EARLY_LEADS,
      leadNote: leadNoteFor(teamOpps, typical * length),
      rows,
      dayTotals,
    };
  }

  const month = period('month');
  const week = period('week');

  // The month's gap, and the daily count that closes it by month end.
  const gapAt = (opps: number, set: number): number => Math.ceil((goal / 100) * opps) - set;
  const monthGap = gapAt(month.opps, month.set);
  let perDay: number | null = null;
  let endLabel: string | null = null;
  if (!month.isFinal && typical > 0) {
    const last = addDays(monthStart(today), daysInMonthOf(monthStart(today)) - 1);
    const remaining = daysBetween(today, last); // today through month end
    if (remaining > 0) {
      const finalNeeded = Math.ceil((goal / 100) * (month.opps + typical * remaining));
      perDay = Math.max(0, Math.ceil(Math.max(0, finalNeeded - month.set) / remaining));
      endLabel = shortDate(last);
    }
  }
  const monthLink: MonthLink = { gap: monthGap, perDay, endLabel };

  // Roster = reps with leads in the last 30 days, plus anyone who set an appointment yesterday.
  const roster = new Map<string, { id: string; name: string; opps: number }>();
  for (const o of opp) {
    if (o.d < addDays(yday, -29) || o.d > yday) continue;
    const r = roster.get(o.spId) ?? { id: o.spId, name: o.name, opps: 0 };
    r.opps += o.opps;
    roster.set(o.spId, r);
  }
  const firstAt = new Map<string, string>();
  for (const p of appt) {
    if (p.d === yday && p.sets > 0) {
      firstAt.set(p.spId, p.firstAt);
      if (!roster.has(p.spId)) roster.set(p.spId, { id: p.spId, name: p.name, opps: 0 });
    }
  }
  const countY = (id: string): number => setByRepDay.get(`${id}|${yday}`) ?? 0;
  const ordered = [...roster.values()].sort(
    (a, b) => countY(b.id) - countY(a.id) || (firstAt.get(a.id) ?? '').localeCompare(firstAt.get(b.id) ?? '') || b.opps - a.opps
  );
  const earliest = [...firstAt.entries()].sort((a, b) => a[1].localeCompare(b[1]))[0]?.[0];
  const yRows: YesterdayRow[] = ordered.map((r) => {
    const count = countY(r.id);
    let chip: YesterdayRow['chip'] = 'On deck';
    if (count >= 3) chip = 'Hat trick';
    else if (count > 0 && r.id === earliest) chip = 'First on the board';
    else if (count > 0) chip = 'On the board';
    return { id: r.id, name: r.name, initials: initialsOf(r.name), count, streak: streakOf(r.id), chip };
  });
  const yTotal = yRows.reduce((s, r) => s + r.count, 0);
  const yLeads = leadsByDay.get(yday) ?? 0;
  const yRate = pct(yTotal, yLeads);
  const yOnPace = yTotal >= Math.ceil((goal / 100) * yLeads);

  const streakList: StreakRep[] = [...roster.values()]
    .map((r) => ({ id: r.id, name: r.name, initials: initialsOf(r.name), days: streakOf(r.id) }))
    .filter((r) => r.days > 0)
    .sort((a, b) => b.days - a.days || a.name.localeCompare(b.name));

  return {
    storeName: store.name,
    yesterday: yday,
    today,
    goal,
    personalTarget: cfg.personalTarget,
    week,
    month,
    monthLink,
    yesterdayView: {
      date: yday,
      total: yTotal,
      leads: yLeads,
      rate: yRate,
      onPace: yOnPace,
      leadNote: leadNoteFor(yLeads, typical),
      rows: yRows,
    },
    streaks: streakList,
    noStreak: roster.size - streakList.length,
  };
}

export { formatWeekday };
````

### File 20 of 28: `src/modules/leaderboard/lib/constants.ts`

````ts
export const BOARD_REFRESH_MS = 15 * 60 * 1000;
````

### File 21 of 28: `src/modules/leaderboard/lib/dates.ts`

````ts
// All dates in this module are plain 'YYYY-MM-DD' strings in the store's own timezone.
// They compare correctly as strings.

export const DAY_LABELS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] as const;

export function addDays(iso: string, n: number): string {
  const [y, m, d] = iso.split('-').map(Number);
  const dt = new Date(Date.UTC(y, m - 1, d + n));
  return dt.toISOString().slice(0, 10);
}

/** 0 = Sunday ... 6 = Saturday */
export function weekday(iso: string): number {
  return new Date(`${iso}T00:00:00Z`).getUTCDay();
}

export function mondayOf(iso: string): string {
  const back = (weekday(iso) + 6) % 7;
  return addDays(iso, -back);
}

export function monthStart(iso: string): string {
  return `${iso.slice(0, 8)}01`;
}

export function daysBetween(start: string, end: string): number {
  const a = Date.parse(`${start}T00:00:00Z`);
  const b = Date.parse(`${end}T00:00:00Z`);
  return Math.round((b - a) / 86_400_000) + 1;
}

export function formatLong(iso: string): string {
  return new Date(`${iso}T00:00:00Z`).toLocaleDateString('en-US', {
    weekday: 'long',
    month: 'short',
    day: 'numeric',
    timeZone: 'UTC',
  });
}

export function formatWeekday(iso: string): string {
  return new Date(`${iso}T00:00:00Z`).toLocaleDateString('en-US', {
    weekday: 'long',
    timeZone: 'UTC',
  });
}
````

### File 22 of 28: `src/modules/leaderboard/lib/goal-context.ts`

````ts
import { createContext } from 'react';
import type { GoalInfo } from '@modules/leaderboard/lib/benchmarks';

export const GoalContext = createContext<GoalInfo | undefined>(undefined);
````

### File 23 of 28: `src/modules/leaderboard/lib/queries.ts`

````ts
import { authenticatedFetch } from '@shared/lib/api-client';

// Foureyes public API: POST /v2/intelligence-queries?organizationId=...
// Body: { statement, timeoutSeconds? }
// Response: { intelligenceQuery: { columns: [{ name, type }], rows: unknown[][], rowCount, isTruncated } }
interface IntelligenceQueryResponse {
  intelligenceQuery: {
    columns: { name: string; type: string }[];
    rows: unknown[][];
    rowCount: number;
    isTruncated: boolean;
  };
}

type Row = Record<string, unknown>;

// The data service limits how many queries one account can run at once and answers 429 when the
// limit is hit. Every query in the app goes through one shared queue: at most MAX_CONCURRENT run
// together, and a 429 is retried with a growing pause before giving up.
const MAX_CONCURRENT = 2;
const MAX_RETRIES = 5;

let active = 0;
const queue: (() => void)[] = [];

function acquire(): Promise<void> {
  return new Promise((resolve) => {
    if (active < MAX_CONCURRENT) {
      active += 1;
      resolve();
    } else {
      queue.push(() => {
        active += 1;
        resolve();
      });
    }
  });
}

function release(): void {
  active -= 1;
  const next = queue.shift();
  if (next) next();
}

const sleep = (ms: number): Promise<void> => new Promise((resolve) => window.setTimeout(resolve, ms));

class RateLimitedError extends Error {}

async function attempt(organizationId: string, statement: string): Promise<Row[]> {
  const res = await authenticatedFetch(
    `/v2/intelligence-queries?organizationId=${encodeURIComponent(organizationId)}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ statement, timeoutSeconds: 25 }),
    }
  );
  if (res.status === 429) throw new RateLimitedError('The data service is busy');
  if (!res.ok) {
    const text = await res.text().catch(() => '');
    throw new Error(`Data request failed (${res.status}). ${text.slice(0, 240)}`);
  }
  const body = (await res.json()) as IntelligenceQueryResponse;
  const { columns, rows, isTruncated } = body.intelligenceQuery;
  if (isTruncated) {
    // Surfaced in the console so a truncated result is never silent.
    console.warn('Leaderboard query result was truncated');
  }
  return rows.map((r) => {
    const out: Row = {};
    columns.forEach((c, i) => {
      out[c.name.toLowerCase()] = r[i];
    });
    return out;
  });
}

export async function runQuery(organizationId: string, statement: string): Promise<Row[]> {
  await acquire();
  try {
    for (let i = 0; ; i += 1) {
      try {
        return await attempt(organizationId, statement);
      } catch (e) {
        if (e instanceof RateLimitedError) {
          if (i >= MAX_RETRIES) {
            throw new Error('The data service is busy right now. It will try again automatically.');
          }
          // Keep the slot while waiting so a busy service is not hit harder.
          await sleep(1000 * 2 ** i + Math.random() * 500);
          continue;
        }
        throw e;
      }
    }
  } finally {
    release();
  }
}

const num = (v: unknown): number => Number(v ?? 0);
const str = (v: unknown): string => String(v ?? '');

// Location ids are opaque hex strings. Only allow that shape into SQL.
function safeId(id: string): string {
  if (!/^[A-Za-z0-9]+$/.test(id)) throw new Error('Unexpected store id');
  return id;
}

export interface StoreInfo {
  id: string;
  name: string;
  timezone: string;
  /** Today's date in the store's own timezone, YYYY-MM-DD */
  today: string;
}

/** Leads received by a rep on a local day. */
export interface OppRow {
  spId: string;
  name: string;
  d: string;
  opps: number;
}

/**
 * Appointments set by a rep on a local day, by the day the appointment was BOOKED.
 * sets = leads whose first appointment was booked that day (the "appointments set" number).
 */
export interface ApptRow {
  spId: string;
  name: string;
  d: string;
  sets: number;
  firstAt: string;
}

export async function fetchStores(organizationId: string): Promise<StoreInfo[]> {
  const rows = await runQuery(
    organizationId,
    `SELECT ID AS id, NAME AS name, TIMEZONE AS tz,
            CAST(CONVERT_TIMEZONE(TIMEZONE, CURRENT_TIMESTAMP()) AS DATE)::VARCHAR AS today
     FROM FOUREYES.PUBLIC.LOCATIONS
     ORDER BY NAME`
  );
  return rows.map((r) => ({
    id: str(r.id),
    name: str(r.name),
    timezone: str(r.tz),
    today: str(r.today),
  }));
}

// One owner per opportunity: the primary active SALESPERSON when the CRM supplies one,
// otherwise any active SALESPERSON. Collapsed before joining so rows are never multiplied.
const ownerCte = (id: string) => `owner AS (
  SELECT st.CUSTOMER_OPPORTUNITY_ID AS OPP_ID,
         COALESCE(MAX(IFF(st."PRIMARY", st.SALESPERSON_ID, NULL)), MAX(st.SALESPERSON_ID)) AS SP_ID
  FROM FOUREYES.PUBLIC.CUSTOMER_OPPORTUNITY_SALES_TEAM st
  WHERE st.ACTIVE AND st.ROLE_TYPE = 'SALESPERSON' AND st.LOCATION_ID = '${id}'
  GROUP BY st.CUSTOMER_OPPORTUNITY_ID
)`;

/** Leads received per rep per local day (the denominator of set rate). */
export async function fetchOppRows(organizationId: string, storeId: string): Promise<OppRow[]> {
  const id = safeId(storeId);
  const rows = await runQuery(
    organizationId,
    `WITH loc AS (
  SELECT ID, TIMEZONE FROM FOUREYES.PUBLIC.LOCATIONS WHERE ID = '${id}'
),
${ownerCte(id)},
opps AS (
  SELECT co.ID AS OPP_ID, o.SP_ID,
         CAST(CONVERT_TIMEZONE('UTC', loc.TIMEZONE, co.OPPORTUNITY_AT) AS DATE) AS D
  FROM FOUREYES.PUBLIC.CUSTOMER_OPPORTUNITIES co
  JOIN loc ON loc.ID = co.LOCATION_ID
  JOIN owner o ON o.OPP_ID = co.ID
  WHERE co.LOCATION_ID = '${id}'
    AND co.OPPORTUNITY_AT >= DATEADD(day, -50, CURRENT_DATE())
)
SELECT CAST(sp.ID AS VARCHAR) AS sp_id, sp.FULL_NAME AS name, CAST(op.D AS VARCHAR) AS d,
       COUNT(DISTINCT op.OPP_ID) AS opps
FROM opps op
JOIN FOUREYES.PUBLIC.CRM_SALESPEOPLE sp ON sp.ID = op.SP_ID AND sp.LOCATION_ID = '${id}'
WHERE sp.ACTIVE
GROUP BY 1, 2, 3`
  );
  return rows.map((r) => ({
    spId: str(r.sp_id),
    name: str(r.name),
    d: str(r.d),
    opps: num(r.opps),
  }));
}

/**
 * Appointments by the day they were set, credited to the rep who owns the lead today.
 * A lead's first appointment ever is what counts as "set", so a reschedule is not a second set.
 */
export async function fetchApptRows(organizationId: string, storeId: string): Promise<ApptRow[]> {
  const id = safeId(storeId);
  const rows = await runQuery(
    organizationId,
    `WITH loc AS (
  SELECT ID, TIMEZONE FROM FOUREYES.PUBLIC.LOCATIONS WHERE ID = '${id}'
),
${ownerCte(id)},
firsts AS (
  SELECT ID AS FIRST_ID FROM (
    SELECT a.ID, ROW_NUMBER() OVER (PARTITION BY a.CUSTOMER_OPPORTUNITY_ID ORDER BY a.APPOINTMENT_CREATED_AT, a.ID) AS RN
    FROM FOUREYES.PUBLIC.CUSTOMER_OPPORTUNITY_APPOINTMENTS a
    WHERE a.LOCATION_ID = '${id}' AND a.APPOINTMENT_CREATED_AT IS NOT NULL
  ) WHERE RN = 1
)
SELECT CAST(sp.ID AS VARCHAR) AS sp_id, sp.FULL_NAME AS name,
       CAST(CONVERT_TIMEZONE('UTC', loc.TIMEZONE, a.APPOINTMENT_CREATED_AT) AS DATE)::VARCHAR AS d,
       COUNT(DISTINCT IFF(f.FIRST_ID IS NOT NULL, a.ID, NULL)) AS sets,
       MIN(IFF(f.FIRST_ID IS NOT NULL, CONVERT_TIMEZONE('UTC', loc.TIMEZONE, a.APPOINTMENT_CREATED_AT), NULL))::VARCHAR AS first_at
FROM FOUREYES.PUBLIC.CUSTOMER_OPPORTUNITY_APPOINTMENTS a
JOIN loc ON loc.ID = a.LOCATION_ID
JOIN owner o ON o.OPP_ID = a.CUSTOMER_OPPORTUNITY_ID
JOIN FOUREYES.PUBLIC.CRM_SALESPEOPLE sp ON sp.ID = o.SP_ID AND sp.LOCATION_ID = a.LOCATION_ID
LEFT JOIN firsts f ON f.FIRST_ID = a.ID
WHERE a.LOCATION_ID = '${id}'
  AND a.APPOINTMENT_CREATED_AT >= DATEADD(day, -50, CURRENT_DATE())
  AND sp.ACTIVE
GROUP BY 1, 2, 3`
  );
  return rows.map((r) => ({
    spId: str(r.sp_id),
    name: str(r.name),
    d: str(r.d),
    sets: num(r.sets),
    firstAt: str(r.first_at),
  }));
}
````

### File 24 of 28: `src/modules/leaderboard/lib/use-board-data.ts`

````ts
import { useEffect, useState } from 'react';
import { fetchApptRows, fetchOppRows, fetchStores, type StoreInfo } from './queries';
import type { RawBoard } from './compute';
import { BOARD_REFRESH_MS } from './constants';

interface Async<T> {
  data: T | undefined;
  error: string | undefined;
  loading: boolean;
}

const errorText = (e: unknown): string => (e instanceof Error ? e.message : String(e));

// Loads one store's numbers and refreshes every 15 minutes. The data itself updates once a day,
// so the refresh mostly makes sure a screen left on overnight rolls over to the new day.
export function useBoardData(
  organizationId: string | undefined,
  store: StoreInfo | undefined
): Async<RawBoard> {
  const [state, setState] = useState<Async<RawBoard>>({
    data: undefined,
    error: undefined,
    loading: true,
  });
  const storeId = store?.id;

  useEffect(() => {
    if (!organizationId || !storeId) return;
    let cancelled = false;
    setState((s) => ({
      data: s.data?.store.id === storeId ? s.data : undefined,
      error: undefined,
      loading: true,
    }));

    const load = async () => {
      try {
        const [stores, opp, appt] = await Promise.all([
          fetchStores(organizationId),
          fetchOppRows(organizationId, storeId),
          fetchApptRows(organizationId, storeId),
        ]);
        const fresh = stores.find((s) => s.id === storeId);
        if (!fresh) throw new Error('That store is no longer available');
        if (!cancelled) {
          setState({ data: { store: fresh, opp, appt }, error: undefined, loading: false });
        }
      } catch (e: unknown) {
        if (!cancelled) {
          setState((s) => ({ data: s.data, error: errorText(e), loading: false }));
        }
      }
    };

    void load();
    const timer = window.setInterval(() => void load(), BOARD_REFRESH_MS);
    return () => {
      cancelled = true;
      window.clearInterval(timer);
    };
  }, [organizationId, storeId]);

  return state;
}
````

### File 25 of 28: `src/modules/leaderboard/lib/use-goal.ts`

````ts
import { useEffect, useState } from 'react';
import { fetchBaseline } from '@modules/leaderboard/lib/baseline';
import { computeGoal, type GoalInfo } from '@modules/leaderboard/lib/benchmarks';
import type { StoreInfo } from '@modules/leaderboard/lib/queries';

interface GoalState {
  data: GoalInfo | undefined;
  error: string | undefined;
}

// The goal is computed from the store's own trailing 26 weeks plus the static benchmark table.
// It moves slowly, so it is loaded once per store.
export function useGoal(organizationId: string | undefined, store: StoreInfo | undefined): GoalState {
  const [state, setState] = useState<GoalState>({ data: undefined, error: undefined });
  const storeId = store?.id;

  useEffect(() => {
    if (!organizationId || !storeId) return;
    let cancelled = false;
    setState({ data: undefined, error: undefined });
    fetchBaseline(organizationId, storeId)
      .then((b) => {
        if (!cancelled) setState({ data: computeGoal(b.sets, b.leads), error: undefined });
      })
      .catch((e: unknown) => {
        if (!cancelled) setState({ data: undefined, error: e instanceof Error ? e.message : String(e) });
      });
    return () => {
      cancelled = true;
    };
  }, [organizationId, storeId]);

  return state;
}
````

### File 26 of 28: `src/modules/leaderboard/lib/use-stores.ts`

````ts
import { useEffect, useState } from 'react';
import { fetchStores, type StoreInfo } from './queries';

interface Async<T> {
  data: T | undefined;
  error: string | undefined;
  loading: boolean;
}

const errorText = (e: unknown): string => (e instanceof Error ? e.message : String(e));

export function useStores(organizationId: string | undefined): Async<StoreInfo[]> {
  const [state, setState] = useState<Async<StoreInfo[]>>({
    data: undefined,
    error: undefined,
    loading: true,
  });

  useEffect(() => {
    if (!organizationId) return;
    let cancelled = false;
    setState({ data: undefined, error: undefined, loading: true });
    fetchStores(organizationId)
      .then((data) => {
        if (!cancelled) setState({ data, error: undefined, loading: false });
      })
      .catch((e: unknown) => {
        if (!cancelled) setState({ data: undefined, error: errorText(e), loading: false });
      });
    return () => {
      cancelled = true;
    };
  }, [organizationId]);

  return state;
}
````

### File 27 of 28: `src/modules/leaderboard/pages/board-page.tsx`

````tsx
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router';
import { useOrganization } from '@shared/hooks/use-organization';
import { useStores } from '@modules/leaderboard/lib/use-stores';
import { useBoardData } from '@modules/leaderboard/lib/use-board-data';
import { useGoal } from '@modules/leaderboard/lib/use-goal';
import { GoalContext } from '@modules/leaderboard/lib/goal-context';
import { buildBoard, type BoardView } from '@modules/leaderboard/lib/compute';
import { Header, type SlideKey } from '@modules/leaderboard/components/parts';
import { PAGE_SIZE, PageContext } from '@modules/leaderboard/components/rep-table';
import { PeriodSlide, YesterdaySlide } from '@modules/leaderboard/components/slides';
import '@modules/leaderboard/board-base.css';
import '@modules/leaderboard/board-slides.css';
import '@modules/leaderboard/board-controls.css';

const DESIGN_W = 1920;
const DESIGN_H = 1080;
const STORE_KEY = 'leaderboard.store';
const MANUAL_VIEWS: SlideKey[] = ['yesterday', 'week', 'month'];

function useFit() {
  const calc = () => {
    const scale = Math.min(window.innerWidth / DESIGN_W, window.innerHeight / DESIGN_H);
    return {
      scale,
      left: (window.innerWidth - DESIGN_W * scale) / 2,
      top: (window.innerHeight - DESIGN_H * scale) / 2,
    };
  };
  const [fit, setFit] = useState(calc);
  useEffect(() => {
    const onResize = () => setFit(calc());
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  return fit;
}

function readStore(): string | null {
  try {
    return window.localStorage.getItem(STORE_KEY);
  } catch {
    return null;
  }
}

function pagesFor(rows: number): number {
  return Math.max(1, Math.ceil(rows / PAGE_SIZE));
}

function rowsFor(key: SlideKey, view: BoardView): number {
  if (key === 'week') return view.week.rows.length;
  if (key === 'month') return view.month.rows.length;
  return 0;
}

// "Through Oct 5": the last day the data covers.
function throughLabel(iso: string): string {
  const d = new Date(`${iso}T00:00:00Z`).toLocaleDateString('en-US', {
    month: 'short',
    day: 'numeric',
    timeZone: 'UTC',
  });
  return `Through ${d}`;
}

function Message({ title, body }: { title: string; body: string }) {
  return (
    <div className='lb-center'>
      <h1>{title}</h1>
      <p>{body}</p>
    </div>
  );
}

export function BoardPage() {
  const fit = useFit();
  const { organizationId, isLoading: orgLoading } = useOrganization();
  const [params, setParams] = useSearchParams();
  const stores = useStores(organizationId);

  const storeList = stores.data ?? [];
  const wanted = params.get('store') ?? readStore();
  const store = storeList.find((s) => s.id === wanted) ?? storeList[0];
  const board = useBoardData(organizationId, store);
  const goalState = useGoal(organizationId, store);
  const goalInfo = goalState.data;

  // Goal is computed, not set by hand: see lib/benchmarks.ts. Pace and target follow from it.
  const personalParam = Number(params.get('personal'));
  const view = useMemo(
    () =>
      board.data && goalInfo
        ? buildBoard(board.data, {
            goal: goalInfo.goal,
            typicalLeadsPerDay: goalInfo.typicalLeadsPerDay,
            personalTarget: personalParam > 0 ? personalParam : 2,
          })
        : undefined,
    [board.data, goalInfo, personalParam]
  );

  // The viewer picks the screen. It starts on the week, or on the screen named in the address.
  const viewParam = params.get('view') as SlideKey | null;
  const [current, setCurrent] = useState<SlideKey>(viewParam && MANUAL_VIEWS.includes(viewParam) ? viewParam : 'week');
  const [manualPage, setManualPage] = useState(0);

  const storeId = store?.id;
  useEffect(() => {
    setManualPage(0);
  }, [storeId]);

  const updateParams = useCallback(
    (changes: Record<string, string | null>) => {
      const next = new URLSearchParams(params);
      for (const [k, v] of Object.entries(changes)) {
        if (v === null) next.delete(k);
        else next.set(k, v);
      }
      setParams(next, { replace: true });
    },
    [params, setParams]
  );

  const onStore = useCallback(
    (id: string) => {
      try {
        window.localStorage.setItem(STORE_KEY, id);
      } catch {
        // Remembering the store is a convenience only.
      }
      updateParams({ store: id });
    },
    [updateParams]
  );

  const onTab = useCallback(
    (key: SlideKey) => {
      setCurrent(key);
      setManualPage(0);
      updateParams({ view: key });
    },
    [updateParams]
  );

  const pages = view ? pagesFor(rowsFor(current, view)) : 1;
  const page = Math.min(manualPage, pages - 1);

  let body;
  if (orgLoading || stores.loading) {
    body = <Message title='Loading the leaderboard' body='Getting the latest numbers.' />;
  } else if (!organizationId) {
    body = <Message title='No organization selected' body='Sign in with the dealer group that owns these stores.' />;
  } else if (stores.error) {
    body = <Message title='The leaderboard could not load' body={stores.error} />;
  } else if (!store) {
    body = <Message title='No stores found' body='This organization has no stores with sales data yet.' />;
  } else if (!view && (board.error || goalState.error)) {
    body = <Message title='The numbers could not load' body={board.error ?? goalState.error ?? ''} />;
  } else if (!view) {
    body = <Message title='Loading the leaderboard' body={`Getting ${store.name}.`} />;
  } else if (current === 'yesterday') {
    body = <YesterdaySlide key='yesterday' view={view} />;
  } else if (current === 'week') {
    body = <PeriodSlide key={`week-${page}`} view={view} period={view.week} page={page} />;
  } else {
    body = <PeriodSlide key={`month-${page}`} view={view} period={view.month} page={page} />;
  }

  return (
    <div className='lb-stage'>
      <div
        className='lb-canvas'
        style={{ transform: `translate(${fit.left}px, ${fit.top}px) scale(${fit.scale})` }}
      >
        <img className='lb-lens' src='/lens-white.svg' alt='' />
        <div className='lb-shell'>
          {store && (
            <Header
              stores={storeList}
              storeId={store.id}
              onStore={onStore}
              freshness={view ? throughLabel(view.yesterday) : 'Loading'}
              active={current}
              onTab={onTab}
            />
          )}
          <GoalContext.Provider value={goalInfo}>
            <PageContext.Provider value={setManualPage}>{body}</PageContext.Provider>
          </GoalContext.Provider>
        </div>
      </div>
    </div>
  );
}
````

### File 28 of 28: `src/modules/leaderboard/paths.ts`

````ts
export const leaderboardPaths = {
  root: '/board',
} as const;
````

