A single source of truth for GameZav.
Today the codebase carries two parallel theme systems — theme/index.ts and hooks/useTheme.js — with different gold/red values, mismatched neutrals, and one-off colors hardcoded across components. This document unifies them into one palette, type scale, and component vocabulary, and lists every rename needed to land it.
01Brand color
Two ramps drive the brand: warm gold for primary actions and confidence, deep red as the secondary accent. Both are extended into 7 steps so we can tint, tone, and surface without inventing one-offs.
02Semantic colors
Status colors with both a solid (text/icon) and a soft (background) value. Replaces the four success greens (#10B981, #34C759, #22C55E, #4ADE80) and three reds floating in the codebase.
red-400 brand accent.#FF4500 in MyPickCard.jsx. This is a deliberate brand moment — do not use for anything else.03Dark mode surfaces
The app defines dark surfaces inside useTheme.js while the landing hardcodes its own. These five tokens are the canonical dark surfaces.
04Logo & wordmark
The wordmark is the brand. Always render the two halves with exactly the brand gold and red — no shadows, no strokes, no italics. Track tightly (letter-spacing: -0.025em) and keep the two halves on the same line.
Don't · stretch, recolor halves, add effects, place on busy photography without a scrim.
05Typography
Inter for everything. JetBrains Mono for stat readouts, odds, and confidence percentages where columnar alignment matters. Adds display and h1 sizes that the current scale is missing.
−0.025em
800
−0.02em
800
−0.015em
700
−0.01em
700
0
600
0
400
0
400
0
600
0
400
+0.08em
600
JetBrains Mono
500
06Spacing, radius, elevation
The current scale stops at xl: 32. Extends to 64px so screens don't reach for hardcoded margins. Numeric tokens (s-1 through s-16) are the new format — easier to scan than t-shirt sizes once you pass xl.
Spacing
Radius
07Components
Reference renders for the components in app/src/components. Anything not shown should compose from these primitives — no new colors, no inline shadows.
Buttons
Primary uses gold on dark text — strongest CTA. Accent (red) is reserved for destructive or "place pick" moments. Avoid stacking primary buttons on the same screen.
Badges
Use soft variants on cards, solid variants in headers and chips. Confidence badges always live on the card body, never the bar.
Confidence ring
Color follows confidence: gold-400 baseline, fire above 75%, neutral n-400 below 40%. Replaces the hardcoded #FF415B default in ConfidenceRing.jsx.
Pick card · light + dark
The 4px top bar matches the confidence band. Avatar tint is red-50 in light, red-900 @ 18% in dark.
Form field
Focus ring is gold-400 at 18% opacity. Labels use the label type token. Errors swap the border to danger and add helper text below.
Tab selector
The current TabSelector fills the active pill with colors.accent (red). For the Predictions tab specifically, swap to gold-400 on dark text — it reads as "AI-suggested" instead of "destructive."
Progress bar
Fill is gold-400, track is n-200 (light) / n-700 (dark). The current default of gray700 as a track in light mode is too dark — fix below.
Building step
Pending → current → completed. Currently interpolates pending → accent (red) → success. Recommend pending → gold-400 → success so the brand color reads in the most-seen onboarding moment.
08Audit · what's broken today
Issues I found while reading the code. Tagged by severity. Severity reflects user-visible inconsistency, not engineering risk.
Two different brand golds in production
theme/index.ts uses #E3B23C (warm, deeper). useTheme.js uses #F5A623 (oranger). The wordmark renders with the useTheme values everywhere, while the rest of the system uses theme. They visibly mismatch when both render on the same screen.
Two different brand reds, too
#C62828 (theme) vs #FF415B (useTheme). The latter is closer to coral and is what the wordmark, confidence ring default, and accent fills currently render with. Recommend collapsing to red-400 (#C62828) as the canonical brand red and reserving the coral as a one-off if needed.
Two parallel theme systems
Static theme object (no dark mode) and dynamic useTheme() hook (with dark mode) both export overlapping shapes. Components import from whichever was nearest. Recommend merging: one useTheme() hook that returns light or dark variants of the unified token set.
Hardcoded predictions accent
const PREDICTIONS_ACCENT = "#F5A623" in predictions.jsx. Should be tokens.gold-400 — and once it is, the predictions tab and the wordmark will agree.
Hardcoded fire color
const FIRE_COLOR = "#FF4500" in MyPickCard.jsx. Promote to semantic.fire (#FF6A1A) so the on-fire moment is documented brand territory.
Four different success greens
#10B981 (useTheme), #34C759 (palette.green), #22C55E (palette.greenDark), #4ADE80 (palette.greenLight). Pick one: #16A34A, the WCAG-AA-on-white winner.
Spacing scale stops at xl (32px)
Screen-level layouts reach for 40 / 48 / 64 hardcoded — search the app and you'll find marginBottom: 40 in 18 places. Extend the scale to s-16 (64px).
Type scale missing display + h1
AppText caps at title (24px). Onboarding hero text duplicates a fontSize: 32+ definition inline in 6+ screens. Add display and h1 variants.
gray100 breaks the Tailwind ramp
Token uses #F4F4F4 while the rest of the ramp tracks Tailwind's gray. Snap to #F3F4F6 for a clean ramp.
No elevation tokens
Shadows are inlined or absent. Add shadow-sm/md/lg/xl/gold so cards and modals share the same depth language.
Landing dark surface ≠ app dark surface
Landing uses #111827 as page bg; app dark mode uses #121212. Pick n-950 (#0B1220) for app shell base; n-900 (#111827) for cards. Now they match.
backgroundSecondary is a beige (#D9D6CD) on light
Reads as a different brand temperature from the rest. Replace with n-100 (#F3F4F6) unless the warm beige is intentional — and if it is, make it a documented token.
09Migration map
Drop-in replacements. Keep both exports during one release for safe rollout, then remove the legacy aliases.
| Today | Tomorrow | Note | |
|---|---|---|---|
| BRAND_GOLD #F5A623 | → | tokens.brand.gold-400 #E3B23C | Wordmark, predictions accent |
| BRAND_RED #FF415B | → | tokens.brand.red-400 #C62828 | Confidence ring, accent fills |
| PREDICTIONS_ACCENT #F5A623 | → | tokens.brand.gold-400 | Delete the local const |
| FIRE_COLOR #FF4500 | → | tokens.semantic.fire #FF6A1A | Promote to documented token |
| colors.success #10B981 | → | tokens.semantic.success #16A34A | WCAG-AA on white |
| palette.green #34C759 | → | tokens.semantic.success #16A34A | — |
| palette.greenDark #22C55E | → | tokens.semantic.success #16A34A | Remove |
| palette.greenLight #4ADE80 | → | tokens.semantic.success-soft #DCFCE7 | Backgrounds only |
| colors.error #EF4444 | → | tokens.semantic.danger #DC2626 | Distinct from brand red |
| gray100 #F4F4F4 | → | tokens.neutral.100 #F3F4F6 | Snap to Tailwind ramp |
| SCREEN_BG_DARK #121212 | → | tokens.surface.base #0B1220 | Match landing |
| backgroundSecondary #D9D6CD | → | tokens.neutral.100 #F3F4F6 | Drop the beige |
| spacing.xl 32 (max) | → | space-{1..16} → 64px | Numeric scale |
| AppText variant: title (24) | → | + display (56), h1 (40) | Hero + onboarding |
10Recommendations
What I'd do next, in order. Each step is independently shippable.
Collapse to one token file
Move all tokens into a single app/src/theme/tokens.ts with the structure shown here. Have useTheme() return either tokens.light or tokens.dark based on color scheme. Delete BRAND_GOLD / BRAND_RED exports; everything imports from tokens.
Pick one gold and one red
Recommend gold-400 #E3B23C and red-400 #C62828. They're the deeper, more print-stable values and they read as "professional sports media" — which is the right register for a paid prediction app. The orange-leaning #F5A623 reads as alert/warning when paired with red.
Codify the on-fire moment
The 75%+ confidence band is a brand moment — make it deliberate. Lift FIRE_COLOR to semantic.fire, document the threshold, and consider a subtle fire-soft gradient on the card top bar.
Share tokens with the landing
The landing CSS variables and the app theme drift independently. Generate globals.css from the same tokens file (Style Dictionary, or a hand-rolled build script) so the site and app can never disagree on the brand again.
Adopt numeric scales
Switch spacing.xs/sm/md/lg/xl → space-1/2/3/4/5/6/8/10/12/16 and keep aliases for one release. T-shirt sizes break down past xl; numbers don't.
Add type variants for hero copy
Onboarding screens (ai-powered, building-system, unlock-full-potential) all redefine 32–40px headlines inline. Add display and h1 to AppText and replace those inline styles.
Document the dark surface stack
App, landing, and cards each pick their own dark backgrounds. Lock down n-950 / n-900 / n-800 as the three surfaces — base, card, raised — and refactor.
Ship a Storybook (or Expo route) of this
Render every component on this page from the actual app components, not HTML. That's the only way to keep the design system honest as the codebase moves. Could live at /design-system behind a dev flag.