# SalesLogHQ — full-app redesign mockup (B1 "Navy & Steel")

**Source inventoried:** `agallardo55/saleslog-dashboard` @ `main`, commit `ee491e7` (2026-10-15 08:50 PDT). Read-only shallow clone. Nothing was pushed, committed or opened against the repo.
**Mockup:** static HTML, one shared stylesheet (`app.css`, B1 tokens), one file per screen, `index.html` as the hub. Every person, dealership, deal, phone and email is **fictional example data** (dealer "Example Motors (Demo)", `example.com` addresses, `(555) 010-xxxx` numbers).
**Renders:** `png/<screen>.png` (desktop 1440 wide full page; mobile 390 wide full page) and `contact-sheet.png` (all 32 screens, grouped).
**Regenerate:** `python3 build.py && python3 render.py && python3 contact.py`. The generator is split into `part1_core.py` … `part5_mobile_out.py`.

## Design tokens (B1)
| Token | Value | Use |
|---|---|---|
| navy-950 / navy / navy-700 | `#081629` / `#0B1F3A` / `#173F7A` | Band, headings, primary text |
| App shell | `#F7F8FA` sidebar + topbar + canvas, `1px #E3E8EF` dividers | Single-mode light shell |
| cobalt / -600 / -400 / -200 / -100 / -50 | `#1D5BD6` / `#184CB5` / `#4B7FE0` / `#BCCDEB` / `#E2EAF8` / `#F1F5FC` | Primary actions, focus, progress |
| steel / charcoal / body / muted | `#56657A` / `#2B2F36` / `#3E4A5C` / `#6B7686` | Text scale |
| line / surface | `#E3E8EF` / `#F4F6F9` | Borders, app background |
| success / danger / warn | `#0E9F6E` / `#DF1B41` / `#B7791F` | Status |
| Band | `linear-gradient(115deg,#081629,#0B1F3A 30%,#173F7A 62%,#1D5BD6 82%,#56657A)`, skewed -8° | Public hero, auth, report |
| Type | Inter. Headings weight 800 with tight tracking; tabular numerals for money | |
| Radius | Buttons and inputs 6px; cards 10px; modals 14px | |

## Proposed IA change (not in the current app)
The current app has a **top header**, shown only on `/dashboard` and `/settings`. It holds the logo, dealership name, chart toggle, month/year picker, user name, settings/logout icons, and the Demo pill. Settings is one page with **7 tabs**.
The mockup proposes a **light left sidebar** (Mercury-style single light mode: `#F7F8FA` surface matching the app, 1px right border, navy/charcoal text, active item = cobalt-100 tint + cobalt text + 3px cobalt left indicator; no dark chrome):
- Workspace: Dashboard, Add Deal, Import Sales.
- Settings: the 7 tabs as nav items.
- Resources: Guide.
- Plan/seat box, then the user footer.
- The page topbar keeps the month picker and primary action.
Treat this as a proposal. It isn't a 1:1 copy of the current layout.

---

## Screen list

### Public
| # | Mockup | Route | Repo source | Real elements captured | Notes / gaps |
|---|---|---|---|---|---|
| 1 | `landing.html` | `/` | `components/LandingPage.tsx`; plan data from `supabase/migrations/001_sl_schema.sql`, `010_free_tier.sql`, `services/plans.ts` | **Nav:** Guide, Pricing, Security, Try Demo, Login, Get Started.<br>**Hero:** "Ditch the spreadsheet…"; CTAs "Import your first log" / "Try the demo"; "Supports CSV, XLS and XLSX"; dashboard preview with a "Recent import" chip.<br>**Sections:** Zero-friction import features; SMS nightly recap; Pricing (Free / Dealership $249 / Enterprise Custom); Security; footer (Guide, Support, Privacy, Terms). | Pricing lives inside the landing page; there is **no separate pricing route**. Trust row and security copy are marked as placeholders. Features marked with `*` don't exist in the code (see Gaps). |
| 2 | `modal-contact.html` | landing footer "Support" | `components/ContactModal.tsx` | Email*, Category (Sales / Support / Bugs / Feedback / Product Request), Message*, Send. | |
| 3 | `login.html` | `/login` | `components/AuthPages.tsx` | Email, Password (show/hide), Remember me, Forgot password?, Login, "Join now" link; error banner. | |
| 4 | `signup.html` | `/signup?plan=&error=` | `components/AuthPages.tsx`, RPC `sl_signup`, edge fns `stripe-checkout` / `create-checkout-session`, `cleanup-signup` | **Plan picker:** Free / Dealership only.<br>**Fields:** First, Last, Dealership name, Email (duplicate error), Mobile, Password + Confirm, Terms checkbox.<br>`payment_cancelled` banner. Dealership goes to Stripe Checkout. | Enterprise can't be chosen at signup (it's Contact Sales only). The docs say "choose a plan after signup", which is wrong. |
| 5 | `forgot-password.html` | `/forgot-password` | `components/AuthPages.tsx`; edge fns `send-reset-code`, `verify-reset-code` | Four steps in one frame:<br>1. Email → Send Reset Code.<br>2. 6-digit code, "Code sent to ••••4567", Resend, Back.<br>3. New + Confirm password.<br>4. Success → Return to Login. | The reset depends on a mobile number on the profile. There's no email fallback if the phone is missing. |
| 6 | `docs.html` | `/docs`, `/docs/:section` | `components/docs/DocsPage.tsx`, `DocsSidebar.tsx`, `DocsContent.tsx`, `DocsBlock.tsx`, `services/docsContent.ts` | 10 sections: Getting Started, Dashboard, Sales Log, Adding a Sale, Importing, Reports & Notifications, Team Management, Settings, Billing & Plans, Troubleshooting. Steps, tips, screenshot blocks, prev/next. | The search box and "On this page" rail are **proposed**. For copy mismatches see Gaps. |
| 7 | `legal-privacy.html` | `/privacy` | `components/LegalPages.tsx` | Six sections, "Last updated" date. | |
| 8 | `legal-terms.html` | `/terms` | `components/LegalPages.tsx` | Eight sections. | |
| 9 | `report.html` | `/report?token=` | `components/Report.tsx`; edge fn `view-report`; `supabase/functions/send-nightly-sms` | **Today:** total / New / Used / CPO units, total gross with the Front/F&I split.<br>**Month-to-date:** same layout.<br>Unavailable/invalid-token state. | This is the page linked from the nightly SMS. |
| 10 | `404.html` | `*` | `NotFound`, inline in `App.tsx` | 404, Back to Home. | "Open Guide" is proposed. |

### Dashboard & sales log
| # | Mockup | Route | Repo source | Real elements | Notes / gaps |
|---|---|---|---|---|---|
| 11 | `dashboard.html` | `/dashboard` | `components/Dashboard.tsx`, `StatCard.tsx`, `SalesTable.tsx`, `hooks/useAppData.ts`, header in `App.tsx` | **KPI cards:** Total Gross Profit, Volume Units, Avg Front GP, Avg F&I GP, each with % completed and current/target.<br>**Collapsible panels:** Unit Breakdown (New/Used/CPO vs target); Sales by Consultant (bar chart; split deals count 0.5 each).<br>**Sales Log "Live Transaction Ledger":** search, Import, Add Deal; tabs All/New/Used/CPO with counts; "N Matches Found".<br>**Table columns (14):** Date, Deal #, Stock #, Customer, Consultant(s), F&I Mgr, Model info, Retail, Front GP, F&I GP, Total GP, Source, Trade info, Age.<br>Row edit/delete; pagination 10 per page; month/year picker with "This Month". | The header **chart toggle (`showChart`) isn't used** by Dashboard. |
| 12 | `dashboard-empty-onboarding.html` | `/dashboard` (new account) | `components/OnboardingChecklist.tsx`, `SalesTable.tsx` | **Checklist:** 3 steps (dealership profile, first salesperson, first deal); "Setup Guide (n/3)" button; "I'll do this later".<br>Zeroed KPIs with "Set objectives". Empty table: "No sales logged yet… Add First Deal". | Only shown to admin/owner. Import as a secondary empty-state CTA is proposed. |
| 13 | `dashboard-empty-search.html` | `/dashboard` | `components/SalesTable.tsx` | "No records match your search or filters." | |
| 14 | `states-loading-error.html` | app shell | `App.tsx`, `components/Toast.tsx` | Loading ("Loading SaleslogHQ / Syncing your dealership data..."); error with Try Again; toasts ("Sale saved", "Sale updated", errors); demo-mode pill with Sign Up; demo toast "This action is disabled in demo mode". | |
| 15 | `overlay-feedback.html` | app views | `components/FeedbackFab.tsx`; edge fn `send-feedback` | Floating button opens Bug / Feedback / Request, message, send. | |

### Sale entry & import
| # | Mockup | Repo source | Real elements | Notes / gaps |
|---|---|---|---|---|
| 16 | `modal-sale-entry.html` | `components/SaleForm.tsx`, `hooks/useCarApi.ts`, edge fn `carapi-proxy`, `services/sales.ts`, `services/sources.ts` | **"Sales Entry" fields:** Sale Date, Deal #, Customer, Consultants Single/Split toggle, F&I Manager, Stock #, Type New/Used/CPO, Source, Vehicle Year → Make → Model → Trim (CarAPI cascade), Price, Front GP, F&I GP, RDR Date, Trade, Trade ACV, Age (days).<br>Cancel, Save Record. | Default sources: Internet, Walk-In, Phone, Service Dept, Referral. "Total GP (auto)" is a proposed read-only field. |
| 17 | `modal-sale-edit.html` | `SaleForm.tsx` | Edit with a split deal (second consultant), Delete, Save Changes. | |
| 18 | `modal-sale-errors.html` | `SaleForm.tsx` | Required-field errors and a save-failure banner. | Inline per-field errors are proposed. Today the app relies on toasts and HTML `required`. |
| 19 | `modal-delete-confirm.html` | `SaleForm.tsx`, `hooks/useAppData.ts` (`handleDeleteSale`) | Proposed styled confirm that replaces `window.confirm`. | **Bug:** deleting from the form confirms twice. `SaleForm` calls `window.confirm`, then `handleDeleteSale` asks "Delete this record?" again. |
| 20 | `modal-import-flow.html` | `components/ImportSalesModal.tsx` (includes the sheet picker), `ImportUploadStep.tsx`, `ImportMappingStep.tsx`, `ImportPreviewStep.tsx`, `ImportResultStep.tsx`, `services/importParser.ts`, `services/importValidator.ts` | Every step stacked:<br>1. **Upload:** CSV/XLSX/XLS up to 10MB; unsupported-type error; "PDF import coming soon".<br>2. **Sheets:** multi-sheet picker, Select All.<br>3. **Map Columns:** Sale Date, Deal #, Customer required; default consultant and F&I.<br>4. **Preview:** ready/warnings/errors counts, first 20 rows, duplicate deal check.<br>5. **Importing:** progress, batches of 50.<br>6. **Result:** imported/skipped/failed, download failed rows as CSV. | |

### Settings (`/settings`, one page with tabs, `components/Settings.tsx`)
| # | Mockup | Tab (code key) | Repo source | Real elements | Notes / gaps |
|---|---|---|---|---|---|
| 21 | `settings-dealership.html` | Dealership (`ACCOUNT`) | `Settings.tsx`, `services/dealerships.ts` | Dealership name, business phone, address, city, state, zip; Apply Changes. | |
| 22 | `settings-user.html` | User Info (`USER_INFO`) | `Settings.tsx`, `services/profiles.ts`, `services/auth.ts` | First, Last, Email, Role (read-only), Mobile; Update Profile; New + Confirm password. | |
| 23 | `settings-team.html` | Team Admin (`ADMIN`) | `components/AdminPanel.tsx`, `services/users.ts`, edge fn `create-team-auth` | **"Team Administration":** Management Team ("Master Access", can sign in) and Sales Team tables.<br>Columns: Name, Role, Email, Mobile, Status, Action. Search; Add User. | Consultants are roster-only (no login). |
| 24 | `modal-team-user.html` | Team Admin | `AdminPanel.tsx` | **Add/Edit User:** First, Last, Role, Email (required for roles that sign in; sends an invite), Phone; Remove when editing.<br>Roles: consultant, fi_manager, gm, gsm, manager, admin, owner. | |
| 25 | `settings-team-limit.html` | Team Admin | `AdminPanel.tsx`, `services/plans.ts` | Seat limit: "{n}/{max} users — Upgrade to Dealership for up to 20". | |
| 26 | `settings-objectives.html` | Objectives (`OBJECTIVES`) | `Settings.tsx`, `services/targets.ts` | New/Used/CPO unit targets, front and back gross targets, totals; Save Objectives. | **Bug:** see the Objectives item under Gaps. |
| 27 | `settings-notifications.html` | Notifications (`PREFERENCES`) | `Settings.tsx`, `services/notifications.ts`, edge fn `send-nightly-sms` | **Schedule:** Frequency (Daily/Weekly/Monthly), Day, Time (6 AM–10 PM), Timezone (7 US zones).<br>**Recipients:** table with per-user mobile and SMS toggle; warning when SMS is on but there's no phone.<br>SMS preview using the real template. | |
| 28 | `settings-notifications-locked.html` | Notifications on Free | `Settings.tsx` | Upgrade prompt (Free plan has no SMS). | |
| 29 | `settings-subscription.html` | Subscription (`SUBSCRIPTION`) | `Settings.tsx`, `services/plans.ts`, edge fns `create-checkout-session`, `create-stripe-portal` | Current plan, status, billing cycle, trial end, seat usage. Plan cards with Current/Downgrade/Upgrade/Contact Sales. Manage Subscription opens the Stripe portal. | The current plan card shows the **slug** as its heading; the mockup uses the display name. |
| 30 | `settings-support.html` | Support (`SUPPORT`) | `Settings.tsx`, `services/support.ts` | Subject (Bug Report / Feedback / Billing / Feature Request), Message, Submit; success state. | |

### Mobile (390px)
| # | Mockup | Repo source | Notes |
|---|---|---|---|
| 31 | `mobile-dashboard.html` | `SalesTable.tsx` (mobile card view), `Dashboard.tsx`, `StatCard.tsx` | 2×2 KPI grid, collapsed Unit Breakdown, search, type tabs, deal cards, pagination, Add Deal button, bottom tab bar (proposed). **The current app hides the month filter on mobile** (`hidden lg:block`); the mockup shows it, as a proposal. |
| 32 | `mobile-sale-entry.html` | `SaleForm.tsx` | Full-screen sheet with all real fields grouped into Deal / Vehicle / Gross / Trade, plus a sticky Save. |

There are also `index.html` (hub with thumbnails) and `contact-sheet.png`.

---

## Gaps, mismatches and bugs found (for the redesign issue)

### Copy claims features that don't exist in code
1. **"Team Leaderboard"** (Dealership plan feature): there's no leaderboard page or component. The closest thing is the "Sales by Consultant" bar chart on the dashboard.
2. **"Custom Integrations"** and **"Multi-Location Support"** (Enterprise): no code for either. Every account is scoped to a single dealership.
3. **"SOC 2 Type II compliant infrastructure"** and **"works with major DMS providers"** on the landing page aren't verified. Import is generic CSV/XLSX, with no DMS connectors.
4. **"Per-user notification preferences"**: the schedule (frequency, day, time, timezone) is shared by the whole dealership. Only the SMS on/off and phone number are per user. `notifyNewSale` and `notifyDailySummary` exist in the types but aren't exposed in the UI.
5. **"Salespeople log sales"** (positioning): consultants are roster-only and can't sign in. Managers and F&I staff do the logging.
6. **Lead sources**: there's no management page. Source is just a field on the sale, and the app seeds/resyncs a fixed list.
7. **Enterprise price**: the UI shows "Custom", but the code falls back to `499`.

### Functional bugs / inconsistencies
8. **Objectives:** the saved `unitTarget` is New + Used only (CPO left out), but the Objectives screen total includes CPO. Dashboard % completed is therefore wrong whenever the CPO target is above 0.
9. **Double delete confirmation** when deleting from the sale form (`window.confirm` in SaleForm, then another confirm in `handleDeleteSale`). Deleting from a table row confirms once.
10. The header **chart toggle (`showChart`)** is wired up but Dashboard ignores it.
11. The **month filter is hidden on mobile**, so mobile users can't change the month.
12. **Settings tabs aren't role-gated** in the UI. Any signed-in role can open Team Admin and Subscription (server/RLS rules may still block writes).
13. **Brand casing** is inconsistent: "SaleslogHQ" in some places, "SalesLogHQ" in others.
14. The Subscription plan card heading shows the **plan slug** instead of the display name.
15. Report-page error copy is generic ("No report token provided"); a clearer expired/invalid-link state is proposed.

### Docs vs. app mismatches (`services/docsContent.ts`)
16. The docs say "Record Sale"; the button is "Add Deal".
17. The docs say "Settings > Account / Admin"; the tabs are Dealership / Team Admin.
18. The docs mention a month **"All"** option, which doesn't exist.
19. The docs describe a "Used/CPO" tab; the tabs are separate (Used, CPO).
20. The docs describe the chart as "gross profit over time, stacked". It's actually **units by consultant**.
21. The docs say to "choose a plan after signup"; the plan is chosen **on** the signup form.

### Project status
22. `STATUS.md` in the repo is **AMBER**: launch is blocked pending staging and security validation. That file also notes a committed test credential. It was **not used or reproduced** here, and should be rotated/removed in the repo.

## Mockup conventions
- Every screen carries a bottom-right tag naming the screen, plus "fictional example data". App screens also show an "Example data · fictional" chip.
- Proposed additions that aren't in the code are labeled in place: sidebar IA, docs search, inline field errors, styled delete confirm, mobile month picker, Total GP auto field, mobile tab bar.

## Responsive behaviour (added Oct 18, 2026)
`app.css` lays out mobile-first, with breakpoints at **640px** and **1024px**:
- **Grids:** 1 column on phones, 2 columns from 640px, and the full column count from 1024px. KPI cards go 4 → 2 → 1. Custom splits (`.cols` with `--cols`) stack below 1024px.
- **Public nav:** under 1024px the links collapse into a hamburger that opens a full-screen menu (CSS `:target` on `#pmenu`, no JS).
- **Landing:**
  - Hero stacks and the headline is sized with `clamp()`.
  - Trust row and CTAs wrap.
  - Pricing and feature cards stack; the footer stacks.
- **Auth pages:** cards are fluid (max 440/560px). The 6-digit code boxes shrink to fit. The forgot-password steps go 4 → 2 → 1 columns.
- **Docs and legal:**
  - Docs sidebar becomes a horizontally scrolling chip row.
  - The "On this page" rail and header search are hidden below 1024px.
  - Legal cards use less padding on phones.
- **App shell under 1024px:**
  - The sidebar becomes an off-canvas drawer (hamburger → `#side`, with a scrim and close button).
  - A fixed bottom tab bar appears: Dashboard, Add Deal, Import, Settings, More (More opens the drawer).
  - The topbar wraps, and the example chip and primary button move out of it.
- **Tables:**
  - Sales Log, Team Admin and Notification recipients (`.table.stack`) become stacked label/value cards under 640px.
  - All other tables scroll horizontally inside their card.
- **Modals:** full-screen sheets under 640px, with a sticky footer. The page behind the sheet is hidden.

**Overflow check:** headless Chrome at 390px requires `document.documentElement.scrollWidth <= 390` for every page. Results are in `overflow-check.txt` / `overflow-check.json`, made by `check.py`. A second pass, `clipcheck.py`, flags any element that runs past 390px and is clipped (rather than scrolled), so a page can't pass just because `overflow:hidden` hides its overflow.

**390px renders:** `png/mobile/<screen>.png`, plus `png/mobile/drawer-open-dashboard.png` and `png/mobile/menu-open-landing.png`. All 390px renders together: `contact-sheet-mobile.png`. In the static full-page captures the bottom tab bar is drawn at the bottom of the page; live, it is `position:fixed`.
