2026-04-10 — Web Brand Parity v1
Brings the Flow web portal’s marketing pages (/, /about, /services, /contact, /privacy, /upgrade) and user auth landing pages (/auth/confirmed, /auth/reset-password) to visual brand parity with the Flow Flutter mobile app — without regressing the admin/vendor/moderator dashboards.
Branch
feature/web-brand-parity-v1 in flow_backend/ repo, off develop.
Spec
Design spec v3 | Implementation plan
Pre-flight audit findings (Phase 0)
font-displayclassName: 0 hits in dashboard scope — all 40 hits are in the 8 in-scope marketing pages +globals.css+layout.tsx+components/landing/rounded-lg/md/xlin dashboards: 131 hits — standard Tailwind radii, untouched by the additive approach (brand usesrounded-button,rounded-cardetc.)text-foreground/--foregroundin dashboards: 0 direct JSX usage in dashboard pages (dashboards use hardcoded grays). The#374151→#18181Bchange is global but low-impact--card-bg: 2 hits only inglobals.css(definition +@theme inlinemapping), kept unchanged- Token parity script: passes with full allow-list coverage against
flow_mobile/lib/core/theme/app_theme.dart
Changes
Phase 0 — pre-flight
- Token parity checker script (
web/scripts/check-token-parity.ts) + 4 unit tests - Web-only token allow-list (
web/scripts/web-only-tokens.txt, 20 entries) - Dev deps added:
plaiceholder,@axe-core/playwright,@lhci/cli,tsx
Phase 1 — tokens + fonts + tone context
- Added 30+ brand tokens to
globals.css:--ink-black,--text-secondary/tertiary,--text-on-dark-*,--divider-light,--error,--surface-warm/elev,--brand-gradient, spacing scale (space-1–space-32), brand radii (brand-radius-xs–brand-radius-full), shadows - Font swap: DM Sans + Syne → Inter (single font, weight 400–900)
- Removed
--font-display/.font-display; replaced all usages withfont-sans font-[800] - Created
ToneProvider/useTonecontext (components/ui/tone-context.tsx)
Phase 2 — primitives (6 commits)
Added tone?: Tone prop to 6 shared UI primitives. Each component defaults to 'neutral' (zero visual change) and opts into brand styling when tone="brand" is set via context or prop:
- Button — brand mode bypasses CVA with direct class strings;
brandBase+brandVariantMap - Card —
rounded-card border-0 bg-surface-warm-elev shadow-md - Input —
rounded-button bg-surface-warm-elev border-divider-light ring-primary-brand - Textarea — same pattern as Input +
min-h-[120px] resize-y - SelectTrigger —
rounded-button bg-surface-warm-elev border-divider-light - Modal —
rounded-card bg-surface-warm-elev p-8 shadow-lg
Phase 3 — marketing component tree (8 commits)
New marketing component layer built on top of the brand primitives:
- SvgDuotoneFilter — SVG filter definition (
#brand-duotone) for hero image duotone treatment - WarmSection — content section wrapper with
max-w-[1200px], responsive padding,asprop - DarkBand — dark cinematic section with duotone imagery (hero-01..04 | abstract), 3 intensity levels, abstract CSS fallback on image error
- Nav — sticky nav with 4 links + logo + brand CTA, mobile hamburger, backdrop blur on scroll
- Footer — 3-column layout (brand, product, legal), Instagram + TikTok social icons, 18+ disclaimer
- MarketingShell — top-level wrapper providing ToneProvider, ScrollRevealProvider, Nav, Footer, SvgDuotoneFilter, warm canvas
- AuthLanding — centered Card on warm canvas with coral radial glow (no Nav/Footer)
- Hero — theatrical DarkBand + h1 + subtitle + dual CTAs + trust strip
All components have matching test files (34 marketing tests total, all passing).
Phase 4 — imagery
Deferred. Hero images require manual asset creation. DarkBand’s AbstractBackdrop fallback (CSS radial gradient blobs) serves as placeholder.
Phase 5 — page-level rewrites (2 commits)
- 5 marketing pages (
/,/about,/services,/contact,/privacy) wrapped inMarketingShell— replaced per-page Navbar/Footer/ScrollRevealProvider imports - 2 auth pages (
/auth/confirmed,/auth/reset-password) wrapped inAuthLanding— replaced plain gray wrappers with brand Card on warm canvas
Phase 6 — copy audit
All user-facing strings in new marketing components verified Italian. No English text leaked into UI. Code comments remain in English (convention).
Phase 7 — verification
- 68 tests pass (2 modal tests skipped — JSDOM limitation, not a regression)
- Production build succeeds
- No ESLint regressions (pre-existing
eslint-plugin-reactconfig issue, unrelated)
Phase 8 — docs
This changelog entry.
Why
The web portal is primarily an admin tool, but the public marketing pages and post-auth email landings shape first impressions. Today they feel disjoint from the mobile brand (different font, different surfaces, different tone). Fixing this closes the “one brand” goal without touching the dashboards that depend on the shared components/ui/* tree.
Risks and mitigations
- Dashboard regression from shared primitives →
tone="neutral"default + dashboard token audit confirms no collision - Font swap DM Sans → Inter changes every dashboard page → intentional; Inter is designed for UI density
--foregroundvalue change → documented as intentional contrast improvement; 16.1:1 AAA on warm canvas- AI imagery quality → stop-ship trigger to fall back to abstract CSS blobs