Skip to content

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.

lib/skins.ts
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;
}
FieldWhat it drives
idURL routing (?skin=<id>), data-skin attribute on <html> for CSS selectors, key into SKINS and fixture sets.
legalNameFooter ”© Heritage Mortgage Network Ltd …” line, FCA register footnote, audit log “tenant” rendering.
shortNameTop chrome logo wordmark, breadcrumb “Heritage Mortgage Network · AR Register”, page title.
verticalSelects the AR fixture pool, the breach-category bias, the rubric.
rubricSelects the file-review rubric (MCOB, ICOBS, CONC) for every review under this tenant.
frnFooter “FRN 412803” line and audit-event tenant footprint. Fictional in the demo; production reads the real FRN from the Tenant row.
cityThe principal firm’s head-office city, shown in tenant settings. Distinct from each AR’s city.
arCountMarketing-landing tagline (“supervising 124 brokers”). Demo only; production reads count(*) from appointed_reps.
taglineMarketing landing skin-selector copy.
swatchHexSkin-switcher chip colour and the OKLCH brand-primary the global stylesheet derives from it. See Architecture overview.
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";
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.

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]

To add a fourth principal firm (say, a wealth-management network using COBS):

  1. Extend Vertical and RubricCode in lib/types.ts if the new vertical or rubric is not present:

    export type Vertical = "mortgage" | "general-insurance" | "credit-broking" | "wealth";
    export type RubricCode = "MCOB" | "ICOBS" | "CONC" | "COBS";
  2. Add the rubric to lib/rubrics.ts. Items grouped by section, with handbook codes in the code field.

  3. Extend SkinId in lib/skins.ts:

    export type SkinId = "heritage" | "crown" | "pinpoint" | "wealthwise";
  4. 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",
    },
  5. Update ALL_SKIN_IDS and isValidSkinId to include the new id.

  6. Add fixtures for the new vertical in lib/fixtures.ts. The generator branches on vertical for trading names, permission codes, and breach-category bias. Add a WEALTH permission set, a list of plausible firm names, and a category bias.

  7. 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 --background clears 7:1 (AA-large; aim for AA-normal where possible).

  8. 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.

A Tenant row carries the skin’s persisted fields plus production-only fields:

FieldSource in demoSource in production
idSkin idULID, generated on tenant onboarding
legalNameskins.tsCompanies House lookup at onboarding
tradingNameskins.ts shortNameEditable in tenant settings
frnskins.tsFCA Register lookup at onboarding
verticalskins.tsSelected at onboarding
rubricskins.tsSelected at onboarding (default by vertical)
brandHexskins.ts swatchHexEditable in tenant settings
registeredOfficenot in demoCompanies House lookup
onboardedAtn/aSet 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.