Skin definition
A skin is one fictional FCA-authorised principal firm. Three skins ship in the demo: Heritage Mortgage Network (mortgage broking, MCOB), Crown GI Collective (general insurance, ICOBS), and Pinpoint Credit Network (consumer credit, CONC). Each skin determines brand colour, FCA register number, file-review rubric, and the AR fixtures rendered.
In production, each skin maps to a Tenant row. The shape is a subset of the Tenant row; the demo’s three skins seed three QA tenants on first run.
import type { Vertical, RubricCode } from "./types";
export type SkinId = "heritage" | "crown" | "pinpoint";
export interface PrincipalFirmSkin { id: SkinId; legalName: string; shortName: string; vertical: Vertical; rubric: RubricCode; /** Fictional FCA Firm Reference Number, footer-only. */ frn: string; /** Headquarters city for the demo. */ city: string; /** Number of ARs in the demo's fixture set. */ arCount: number; /** Tagline for the marketing landing skin selector. */ tagline: string; /** Brand swatch hex for the skin switcher chip. */ swatchHex: string;}Field semantics
Section titled “Field semantics”| Field | What it drives |
|---|---|
id | URL routing (?skin=<id>), data-skin attribute on <html> for CSS selectors, key into SKINS and fixture sets. |
legalName | Footer ”© Heritage Mortgage Network Ltd …” line, FCA register footnote, audit log “tenant” rendering. |
shortName | Top chrome logo wordmark, breadcrumb “Heritage Mortgage Network · AR Register”, page title. |
vertical | Selects the AR fixture pool, the breach-category bias, the rubric. |
rubric | Selects the file-review rubric (MCOB, ICOBS, CONC) for every review under this tenant. |
frn | Footer “FRN 412803” line and audit-event tenant footprint. Fictional in the demo; production reads the real FRN from the Tenant row. |
city | The principal firm’s head-office city, shown in tenant settings. Distinct from each AR’s city. |
arCount | Marketing-landing tagline (“supervising 124 brokers”). Demo only; production reads count(*) from appointed_reps. |
tagline | Marketing landing skin-selector copy. |
swatchHex | Skin-switcher chip colour and the OKLCH brand-primary the global stylesheet derives from it. See Architecture overview. |
Shipped skins
Section titled “Shipped skins”export const SKINS: Record<SkinId, PrincipalFirmSkin> = { heritage: { id: "heritage", legalName: "Heritage Mortgage Network Ltd", shortName: "Heritage Mortgage Network", vertical: "mortgage", rubric: "MCOB", frn: "412803", city: "Manchester", arCount: 124, tagline: "Mortgage AR network supervising 124 brokers across England, Scotland, and Wales.", swatchHex: "#312E81", }, crown: { id: "crown", legalName: "Crown GI Collective Ltd", shortName: "Crown GI Collective", vertical: "general-insurance", rubric: "ICOBS", frn: "528471", city: "Bristol", arCount: 87, tagline: "General insurance AR network covering commercial, personal, and specialist lines.", swatchHex: "#064E3B", }, pinpoint: { id: "pinpoint", legalName: "Pinpoint Credit Network Ltd", shortName: "Pinpoint Credit Network", vertical: "credit-broking", rubric: "CONC", frn: "631920", city: "Edinburgh", arCount: 198, tagline: "Consumer credit AR network supervising retail finance, motor finance, and home improvement brokers.", swatchHex: "#581C87", },};
export const DEFAULT_SKIN_ID: SkinId = "heritage";Helpers
Section titled “Helpers”export function getSkin(id: SkinId): PrincipalFirmSkin;export function isValidSkinId(id: string): id is SkinId;export const ALL_SKIN_IDS: SkinId[];isValidSkinId is the URL-param guard. The marketing landing’s skin selector and the in-app skin switcher (free-explore mode only) call it before persisting skin to useDemoStore.
How a skin reaches the UI
Section titled “How a skin reaches the UI”flowchart LR url[URL ?skin=heritage] --> guard[isValidSkinId] guard -->|valid| store[useDemoStore.setSkin] guard -->|invalid| def[DEFAULT_SKIN_ID] def --> store store --> attr["data-skin attribute on html"] attr --> css[CSS selectors<br/>resolve --brand-primary<br/>per skin] store --> fix[fixtures.ts<br/>filters AR pool by skin] store --> rub[rubrics.ts<br/>getRubric(SKINS[skin].rubric)] store --> chrome[Top chrome<br/>logo, FRN, tagline]Adding a new skin
Section titled “Adding a new skin”To add a fourth principal firm (say, a wealth-management network using COBS):
-
Extend
VerticalandRubricCodeinlib/types.tsif the new vertical or rubric is not present:export type Vertical = "mortgage" | "general-insurance" | "credit-broking" | "wealth";export type RubricCode = "MCOB" | "ICOBS" | "CONC" | "COBS"; -
Add the rubric to
lib/rubrics.ts. Items grouped by section, with handbook codes in thecodefield. -
Extend
SkinIdinlib/skins.ts:export type SkinId = "heritage" | "crown" | "pinpoint" | "wealthwise"; -
Add the skin row to
SKINS:wealthwise: {id: "wealthwise",legalName: "WealthWise Advice Network Ltd",shortName: "WealthWise Advice Network",vertical: "wealth",rubric: "COBS",frn: "742158",city: "Leeds",arCount: 56,tagline: "Independent wealth advisers supervised under COBS.",swatchHex: "#1F2937",}, -
Update
ALL_SKIN_IDSandisValidSkinIdto include the new id. -
Add fixtures for the new vertical in
lib/fixtures.ts. The generator branches onverticalfor trading names, permission codes, and breach-category bias. Add aWEALTHpermission set, a list of plausible firm names, and a category bias. -
Add the brand colour to
globals.css:[data-skin="wealthwise"] {--brand-primary: oklch(0.385 0.085 245);--brand-primary-foreground: oklch(0.984 0.004 247.86);}Verify the contrast ratio against
--backgroundclears 7:1 (AA-large; aim for AA-normal where possible). -
Verify the marketing landing’s skin-selector chip layout still fits at the design’s max width; the chip row wraps gracefully but the hero balance can shift.
Production: the Tenant row
Section titled “Production: the Tenant row”A Tenant row carries the skin’s persisted fields plus production-only fields:
| Field | Source in demo | Source in production |
|---|---|---|
id | Skin id | ULID, generated on tenant onboarding |
legalName | skins.ts | Companies House lookup at onboarding |
tradingName | skins.ts shortName | Editable in tenant settings |
frn | skins.ts | FCA Register lookup at onboarding |
vertical | skins.ts | Selected at onboarding |
rubric | skins.ts | Selected at onboarding (default by vertical) |
brandHex | skins.ts swatchHex | Editable in tenant settings |
registeredOffice | not in demo | Companies House lookup |
onboardedAt | n/a | Set at row insert |
The marketing demo’s three skins are seeded as three QA tenants in non-prod environments only. Production never carries the demo skins.