P7 token audit (8330e93f §10) found two off-brand color bugs:
- utils/v2/gradient.ts GRADIENT_PALETTE used Tailwind blue-green
anchors (teal-500/600 #0d9488, cyan, emerald, sky) clustered in a
single hue family. Two problems: the brand-anchor slot used Tailwind
teal #0d9488, NOT Crewli's #0D9394, AND orgs in multi-workspace
views all rendered as similar teal/green squares (poor
distinguishability). Replaced with the crewli-starter SoT palette:
[0] #0D9394 → #075F60 (Crewli teal — brand anchor)
[1] #7C3AED → #4C1D95 (purple)
[2] #EA580C → #9A3412 (orange)
[3] #16A34A → #14532D (green)
[4] #F59E0B → #92400E (amber)
[5] #EC4899 → #9D174D (pink)
[6] #4F46E5 → #312E81 (indigo — added for org-distinguishability)
[7] #E11D48 → #881337 (rose — added for org-distinguishability)
Palette stays 8-entry; only the values change. Indexing logic
(djb2 hash % 8) unchanged. Per-org avatar colors are not persisted
pre-launch, so the slot reshuffle is safe.
- AppTopbar.vue user-avatar gradient (two sites: the trigger Avatar +
the user-menu header Avatar). Fallback in the CSS var was #0d9488
(Tailwind teal-600), NOT Crewli #0D9394 — if the var ever fails to
resolve, the chrome would render off-brand. Fixed to #0D9394.
The hardcoded pink #f472b6 in the gradient's from-color was kept
intentionally: it matches the crewli-starter SoT (user avatars are
a pink/purple gradient distinct from workspace chrome's teal — the
visual contrast between "your account" and "your workspace" is
by design).
Regression locks:
- gradient.spec.ts +2 specs: brand-anchor slot is #0D9394 (and
defensively, #0d9488 must not appear anywhere in the palette);
palette spans diverse hue families (purple + orange present
beyond the teal anchor).
Suite delta: 564 → 566 (+2). vue-tsc clean. Scoped ESLint clean
(0 errors, pre-existing warnings only).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
45 lines
1.8 KiB
TypeScript
45 lines
1.8 KiB
TypeScript
/**
|
|
* computeOrgGradient — deterministic gradient pair for an organisation.
|
|
*
|
|
* Maps an org `id` string to one of 8 colour pairs via a simple djb2-style
|
|
* character-code hash. Same id always returns the same pair; no external
|
|
* dependencies.
|
|
*
|
|
* Palette rationale (P7-followup-gradient-brand): the diverse crewli-starter
|
|
* SoT palette. Slot 1 is Crewli's brand teal (`#0D9394` → `#075F60`) so the
|
|
* brand-anchor org renders in true Crewli teal — NOT Tailwind teal-600
|
|
* (`#0d9488`), which had drifted in. Remaining slots span hue families
|
|
* (purple / orange / green / amber / pink / indigo / rose) so multi-org
|
|
* users see clearly distinct avatars rather than a wall of similar teals.
|
|
* Each tuple is `[from, to]` for a 135° linear gradient (darker "to"
|
|
* keeps depth on the logo square).
|
|
*/
|
|
|
|
/** Exported so tests can assert membership without hard-coding values. */
|
|
export const GRADIENT_PALETTE: [string, string][] = [
|
|
['#0D9394', '#075F60'], // Crewli teal (brand anchor)
|
|
['#7C3AED', '#4C1D95'], // purple
|
|
['#EA580C', '#9A3412'], // orange
|
|
['#16A34A', '#14532D'], // green
|
|
['#F59E0B', '#92400E'], // amber
|
|
['#EC4899', '#9D174D'], // pink
|
|
['#4F46E5', '#312E81'], // indigo (added for org-distinguishability)
|
|
['#E11D48', '#881337'], // rose (added for org-distinguishability)
|
|
]
|
|
|
|
/**
|
|
* Returns a `[fromHex, toHex]` colour pair deterministically derived
|
|
* from `id`. Handles empty string (hash stays 0; maps to palette[0]).
|
|
*/
|
|
export function computeOrgGradient(id: string): [string, string] {
|
|
// djb2-style hash: accumulate across char codes
|
|
let hash = 5381
|
|
for (let i = 0; i < id.length; i++) {
|
|
// Equivalent to hash * 33 ^ charCode, kept 32-bit safe via >>> 0
|
|
hash = ((hash << 5) + hash + id.charCodeAt(i)) >>> 0
|
|
}
|
|
const index = hash % GRADIENT_PALETTE.length
|
|
|
|
return GRADIENT_PALETTE[index]
|
|
}
|