Skip to content
Brand guide

Conformance

App brand audit

Where sincelabs/app diverges from this guide, why each divergence matters, and what to do about it. Audited at commit df8eb7e on 2026-08-28.

Summary

The app is substantially on-brand. The palette, the type system and the component vocabulary all match the marketing site — the two properties genuinely share one visual identity, which is more than most companies at this stage can say.

The problems are the ones a fast-moving codebase always accumulates: a token layer that stops at the edge of the newest feature, a few accessibility gaps that no one would catch by eye, and one colour decision that is wrong by a margin too small to notice in review.

Counted at df8eb7e:

MeasureValue
Hard-coded hex literals in src/183 occurrences, 52 distinct values
text-terra used as a text colour62 occurrences — all below AA
Pages hand-rolling a title instead of PageHeader11
Pages whose title is an h2 with no h1 on the page11
<table> elements / <caption> elements16 / 0
prefers-reduced-motion handling0
Dark theme supportnone (the marketing site has one)

Nothing here is urgent in the sense of being broken. Three items are accessibility defects and should be scheduled.


P1 — Accessibility defects

A1 · Terracotta text and buttons fail WCAG AA

62 occurrences of text-terra and 16 of bg-terra.

#C4602A on white is 4.16:1; AA for normal text is 4.5:1. That covers every link in the product ("Forgot password?", "View all", "Browse all apps"), the active sidebar row, and every primary button label at 14px.

The fix is not to abandon the brand orange — it is to use the darker terracotta that sincelabs.com already defines as --accent-ink.

UseNowShould beThen
Link / text#C4602A (4.16:1)#A34A1F5.90:1 ✅
Primary button fill#C4602A (4.16:1)#A34A1F5.90:1 ✅
Icons, dots, rings, charts, the // mark#C4602Aunchangednon-text, 3:1 ✅

Fix: add --color-terraink to the theme, then replace text-terratext-terraink for text and bg-terrabg-terraink on Button variant primary and Avatar. Leave every non-text use alone.

Reference: tokens/tailwind-theme.css, components/ui/Button.tsx.


A2 · Eleven pages have no h1

Every admin index and detail page renders its title as <h2 className="font-lora text-[32px] …">, and no h1 exists anywhere on the page — not in app/admin/layout.tsx either.

A screen-reader user landing on /admin/users is told the page has no heading.

Affected: admin/users, admin/users/[id], admin/organizations, admin/organizations/[id], admin/products, admin/products/[id], admin/requests/[id], admin/deletions, admin/audit-log, admin/staff, admin/dashboard.

Fix: replace each with <PageHeader title=… />. This closes A2 and A3 at once.


A3 · Sixteen tables, zero captions

No <table> in the app has a <caption>. A screen-reader user arrowing into a table gets no announcement of what it contains.

Fix: adopt components/ui/DataTable.tsx, which requires a caption prop and renders it sr-only.


A4 · InfoTooltip removes the focus ring and is keyboard-inaccessible

components/ui/InfoTooltip.tsx:9 is the one place in the app that sets focus:outline-none without an equivalent replacement — it substitutes a colour change, which is not a focus indicator.

Separately, the tooltip content is delivered by the native title attribute, which never appears for keyboard or touch users, and the visible tooltip is pointer-events-none and hover-driven.

Fix: restore the focus ring; move the text into an aria-describedby target that appears on :focus-visible as well as :hover.


A5 · No reduced-motion handling

prefers-reduced-motion appears zero times. There are ~100 transitions in the codebase.

Fix: one block, already written in tokens/since-brand.css.


P2 — Token drift

D1 · 183 hard-coded hex literals across 52 distinct values

The token layer covers the platform shell well. It stops at the Sora surfaces and the operator console built on top of them.

Warm-palette drift — right family, wrong value:

LiteralWhereShould be
#A95022 ×9dashboard charts, Sora admin#A34A1F (accent-ink)
#FBFAF8 ×9Sora customer surfaces#F7F5F2 (paper)
#FFF8F2, #FFF8EE, #FBF7EE, #F0DCC0, #E5DCC8scatteredpaper / surface-cream

Off-brand drift — the Tailwind default palette, entirely inside the four Sora operator files (ClientReview, ClientReviewQueue, ClientIngestPanel, runs/page):

LiteralTailwind nameShould be
#B91C1C ×15, #FEF2F2 ×11, #FCA5A5red-700 / red-50--sl-error-*
#FAFAFA ×8, #F4F4F5 ×5, #52525B ×4zinc-50 / zinc-100 / zinc-600paper / mist / ink-muted
#EEF2FF ×5, #3730A3 ×5indigo-50 / indigo-800--sl-info-*
#F0FDF4 ×4, #15803D ×4green-50 / green-700--sl-success-*

These are cool-toned. Next to the warm palette they read as a different product — which, visually, is what the Sora back office currently is.

Fix: these four files are the highest-value cleanup in the repo. One pass converts them and closes most of the count.

Legitimate literals to promote, not remove: #B25C00 / #FFF3E0 (the warning triple) and #3A4A60 / #EBF0F7 (the info triple) are correct brand values that were never given token names. They now have them.


D2 · Button is 48px everywhere

Button.tsx hard-codes h-12 with no size prop, so a button in a table row is as tall as one in a sign-in form. Call sites work around it by passing className="h-9", which then also has to re-specify the text size.

Fix: the size prop in components/ui/Button.tsxsm 32 / md 38 / lg 48.


D3 · Three shadow definitions for one concept

shadow-sm (×16), shadow-[0_1px_2px_rgba(26,25,23,0.04)] (×6), shadow-[0_2px_8px_rgba(0,0,0,0.04)] (×4). The third is neutral black on a warm ground, which goes grey.

Fix: shadow-raised / shadow-lifted / shadow-overlay from the theme.


D4 · StatusChip duplicates the status palette

StatusChip.tsx re-declares the six status colours as literals in an if-chain, independently of the custom properties in globals.css. Two sources for one palette.

Fix: components/ui/StatusChip.tsx, which reads the tokens and maps status strings in one place.


P3 — Consistency

C1 · PageHeader adoption

The platform surfaces use PageHeader; the eleven admin pages in A2 do not, and render their titles at 32px against the component's 28px. Two page-title sizes in one product.

C2 · Type scale has drifted wide

Twenty-four distinct arbitrary font sizes, including 10.5px, 14.5px, 19px and one 8px — below the 11px floor. The top five sizes cover 87% of uses; the tail is noise.

Fix: adopt the nine named steps in typography § 5.3. The 8px is a defect regardless.

C3 · transition-all in four places

InfoTooltip, admin/products, ClientIngestPanel ×2. Animates properties nobody intended.

C4 · Email template colours

src/lib/email/templates.ts uses #C4602A for the CTA fill — same AA failure as A1, and email has no fallback — and #666666 for helper text, which is not a brand grey.

Fix: #A34A1F and #6B6762.

C5 · Stale marketing copy

AuthLayout.tsx reads "We partner with Nordic SMBs to build and deploy custom AI agents that drive real operational leverage."

Two problems. It predates the current positioning (the site now leads with the command center and the infrastructure beachhead), and "leverage" as a verb is on the banned-words list in verbal identity § 2.3.

Fix: align with the live site. Suggested:

"We test every AI tool on our own company first. Only the ones that work reach yours."


P4 — Gaps

G1 · No dark theme

The marketing site ships one; the app does not. A customer moving from sincelabs.com to the portal at night gets a full-brightness page.

Not a defect — a deliberate scope decision worth recording. The dark palette is specified in colour § 4.5 and shipped in tokens/since-brand.css, so adopting it is a matter of using the tokens rather than designing anything.

G2 · No visual regression testing

Playwright is configured but there are no screenshot tests, so drift like D1 is invisible until someone reads the diff.

G3 · public/ is empty

No favicon, no app icons, no OG image. src/app/icon.svg carries the mark alone.

Fix: copy from assets/logo/. Note the app's icon.svg is drawn on an off-centre grid — 6.5 units of left margin against 10.5 on the right. The assets in this repo correct that.


Suggested order

#ItemEffortValue
1A1 — terracotta text and buttonsSFixes 78 AA failures
2A2 + C1 — PageHeader on 11 admin pagesSFixes the h1 gap and title sizes together
3A5 — reduced motionXSOne CSS block
4A3 — table captions via DataTableM16 tables
5A4 — InfoTooltipXS
6D1 — the four Sora operator filesMRemoves most of the 183 literals
7C4 — email coloursXS
8D2, D3, D4 — adopt reference componentsM
9C5 — auth copyXSNeeds Talha's sign-off
10G3 — favicons and OG imageS
11C2 — type scaleLMechanical but wide
12G1 — dark themeLTokens are ready

Target end state: src/app/globals.css contains only the two imports from @sincelabs/brand, and src/ contains no hex literals at all.


Method: static analysis of sincelabs/app at df8eb7e; contrast computed with lib/color.ts against WCAG 2.1. Counts are reproducible with grep over src/.