feat(ui): apply approved A3 shared design foundation

This commit is contained in:
cesnimda
2026-11-03 03:08:13 +01:00
parent d3d3383764
commit bdd1de0dd3
43 changed files with 1074 additions and 43 deletions
+36 -43
View File
@@ -4,17 +4,16 @@ type PaletteLike = Record<string, any>;
export type ContentDensity = "comfortable" | "compact";
// Existing provisional brand hue. Dark mode uses a lighter tone for readable text;
// this does not settle the Phase 3 brand/identity decision.
const ACCENT = "#5B5BD6";
const DARK_ACCENT = lighten(ACCENT, 0.45);
// Approved A3/D1 application palette; document renderers own their own colours.
const ACCENT = "#694394";
const DARK_ACCENT = "#D7B6F7";
// The existing inverse sidebar remains inverse in both modes. These are MUI palette
// roles so feature code does not have to invent colors for this surface.
const navigation = {
background: "#0F172A", border: "rgba(255,255,255,0.08)",
text: "#E2E8F0", muted: "#94A3B8", hover: "rgba(255,255,255,0.06)",
selected: "rgba(99,102,241,0.18)", selectedText: "#FFFFFF", selectedIcon: "#A5B4FC",
background: "#302744", border: "#5E526E",
text: "#EDF0F6", muted: "#CEC2DA", hover: "rgba(215,182,247,0.10)",
selected: "rgba(215,182,247,0.18)", selectedText: "#FFFFFF", selectedIcon: DARK_ACCENT,
};
function buildPrimary(main: string) {
@@ -28,11 +27,11 @@ function buildPrimary(main: string) {
}
function buildLightPalette(): PaletteLike {
const textPrimary = "#1B1B1F";
const textSecondary = "#46464F";
const textPrimary = "#302744";
const textSecondary = "#695E78";
const secondaryMain = "#5A5C78";
const divider = "#EFEDF4";
const divider = "#DDD6E7";
const background = "#FFFFFF";
const disabled = "#777680";
@@ -42,7 +41,7 @@ function buildLightPalette(): PaletteLike {
primary: { ...buildPrimary(ACCENT), contrastText: "#FFFFFF" },
contrastThreshold: 4.5,
navigation,
focus: { main: ACCENT, inverse: "#A5B4FC" },
focus: { main: ACCENT, inverse: DARK_ACCENT },
border: { subtle: divider, default: "#777680", strong: textSecondary },
secondary: {
lighter: "#E0E0FF",
@@ -97,9 +96,7 @@ function buildLightPalette(): PaletteLike {
disabled,
},
divider,
// Soft grey app background with white paper gives the layered dashboard look
// from the product mockups; cards/inputs (paper) sit above it.
background: { default: "#F4F6FB", paper: background, elevated: background },
background: { default: background, paper: background, elevated: "#F5F4F9" },
action: {
hover: alpha(ACCENT, 0.05),
selected: alpha(ACCENT, 0.12),
@@ -111,11 +108,11 @@ function buildLightPalette(): PaletteLike {
}
function buildDarkPalette(): PaletteLike {
const bg = "#0B0B0E";
const paper = "#111116";
const divider = alpha("#FFFFFF", 0.10);
const textPrimary = "#EAEAF2";
const textSecondary = alpha(textPrimary, 0.72);
const bg = "#282334";
const paper = "#282334";
const divider = "#5E526E";
const textPrimary = "#EDF0F6";
const textSecondary = "#CEC2DA";
const secondaryMain = "#A4A6C8";
const disabled = alpha(textPrimary, 0.5);
@@ -125,8 +122,8 @@ function buildDarkPalette(): PaletteLike {
primary: { ...buildPrimary(DARK_ACCENT), contrastText: paper },
contrastThreshold: 4.5,
navigation,
focus: { main: DARK_ACCENT, inverse: "#A5B4FC" },
border: { subtle: divider, default: "#777680", strong: "#B8B8C6" },
focus: { main: DARK_ACCENT, inverse: DARK_ACCENT },
border: { subtle: divider, default: "#9B8EAA", strong: "#CEC2DA" },
secondary: {
lighter: alpha(secondaryMain, 0.22),
light: alpha(secondaryMain, 0.14),
@@ -137,8 +134,9 @@ function buildDarkPalette(): PaletteLike {
error: {
lighter: alpha("#DE3730", 0.18),
light: alpha("#DE3730", 0.12),
main: "#FF5A52",
dark: "#FF3B33",
main: "#FF8A80",
dark: "#FF7067",
contrastText: paper,
darker: "#FFC2BE",
},
warning: {
@@ -180,7 +178,7 @@ function buildDarkPalette(): PaletteLike {
disabled,
},
divider,
background: { default: bg, paper, elevated: "#1C1C24" },
background: { default: bg, paper, elevated: "#352D43" },
action: {
hover: alpha(DARK_ACCENT, 0.10),
selected: alpha(DARK_ACCENT, 0.18),
@@ -196,10 +194,7 @@ function buildCustomShadows(palette: any) {
const primaryColor = palette.primary.main;
return {
button: `0px 0.711px 1.422px 0px ${alpha(shadowColor, 0.05)}`,
// Mockup cards float on the grey page background with no visible border, just a soft,
// fairly generous shadow (dashboard stat tiles, pipeline columns' cards, job-workspace
// panels) -- the old 1px "section" shadow read as flat/bordered rather than elevated.
section: `0px 1px 2px 0px ${alpha(shadowColor, 0.04)}, 0px 8px 24px -12px ${alpha(shadowColor, 0.12)}`,
section: "none",
tooltip: `0px 12px 16px -4px ${alpha(shadowColor, 0.08)}, 0px 4px 6px -2px ${alpha(shadowColor, 0.03)}`,
focus: `0px 0px 0px 3px ${palette.focus.main}`,
// Gradient CTA (mockup's "Tailor my CV" / "See the interface tour" buttons) gets its own
@@ -218,10 +213,10 @@ export const GRADIENT_CTA = "linear-gradient(90deg, #6366F1 0%, #22D3EE 100%)";
function buildTypography() {
return {
fontFamily: "'Archivo', system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif",
fontFamily: "'Trebuchet MS', system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif",
letterSpacing: 0,
// Opt-in application hierarchy; legacy page/marketing variants stay unchanged.
pageTitle: { fontWeight: 700, fontSize: "1.5rem", lineHeight: 1.25, letterSpacing: "-0.01em", "@media (min-width: 768px)": { fontSize: "1.75rem", lineHeight: 34 / 28 } },
pageTitle: { fontWeight: 700, fontSize: "1.75rem", lineHeight: 1.25, letterSpacing: "-0.01em", "@media (min-width: 768px)": { fontSize: "2rem", lineHeight: 1.25 } },
sectionTitle: { fontWeight: 600, fontSize: "1.125rem", lineHeight: 24 / 18 },
pageDescription: { fontWeight: 400, fontSize: "0.875rem", lineHeight: 20 / 14 },
// Mockup headings (F:\Pictures\website\jobtracker\new) are bold/black-weight display
@@ -237,12 +232,12 @@ function buildTypography() {
h6: { fontWeight: 600, fontSize: 16, lineHeight: "20px" },
subtitle1: { fontWeight: 600, fontSize: 14, lineHeight: "18px" },
subtitle2: { fontWeight: 600, fontSize: 13, lineHeight: "17px" },
body1: { fontWeight: 400, fontSize: 14, lineHeight: "18px" },
body2: { fontWeight: 400, fontSize: 13, lineHeight: "17px" },
body1: { fontWeight: 400, fontSize: 15, lineHeight: 1.55 },
body2: { fontWeight: 400, fontSize: 14, lineHeight: 1.5 },
caption: { fontWeight: 400, fontSize: 12, lineHeight: "16px", letterSpacing: 0 },
overline: { fontWeight: 700, fontSize: 11, lineHeight: "14px", letterSpacing: "0.08em", textTransform: "uppercase" as const },
caption1: { fontWeight: 500, fontSize: 12, lineHeight: "16px", letterSpacing: 0 },
button: { textTransform: "capitalize" as const, fontWeight: 600 },
button: { textTransform: "none" as const, fontWeight: 600 },
};
}
@@ -269,9 +264,7 @@ export const getTheme = (_mode: "light" | "dark") => {
light: { palette: lightPalette, customShadows: buildCustomShadows(lightPalette) },
dark: { palette: darkPalette, customShadows: buildCustomShadows(darkPalette) },
},
// Mockup radius reads noticeably rounder than the previous 10/8px defaults across cards,
// stat tiles, kanban cards and buttons.
shape: { borderRadius: 14 },
shape: { borderRadius: 8 },
typography: buildTypography() as any,
} as any) as any;
@@ -376,9 +369,8 @@ export const getTheme = (_mode: "light" | "dark") => {
defaultProps: { elevation: 0 },
styleOverrides: {
root: ({ theme }: any) => ({
// No border -- mockup cards float on the grey page background purely via shadow.
border: "none",
borderRadius: 16,
border: `1px solid ${theme.vars.palette.divider}`,
borderRadius: 8,
backgroundImage: "none",
boxShadow: theme.vars.customShadows.section,
}),
@@ -395,8 +387,8 @@ export const getTheme = (_mode: "light" | "dark") => {
styleOverrides: {
paper: ({ theme }: any) => ({
backgroundColor: theme.vars.palette.background.elevated,
border: "none",
borderRadius: 20,
border: `1px solid ${theme.vars.palette.divider}`,
borderRadius: 12,
boxShadow: theme.vars.customShadows.tooltip,
}),
},
@@ -438,7 +430,8 @@ export const getTheme = (_mode: "light" | "dark") => {
defaultProps: { disableFocusRipple: true },
styleOverrides: {
root: ({ theme }: any) => ({
borderRadius: 10,
borderRadius: 7,
minHeight: 44,
boxShadow: theme.vars.customShadows.button,
"&:hover": { boxShadow: theme.vars.customShadows.button },
}),
@@ -459,7 +452,7 @@ export const getTheme = (_mode: "light" | "dark") => {
background: theme.vars.palette.background.default,
paddingLeft: 10,
paddingRight: 10,
minHeight: 42,
minHeight: 44,
display: "flex",
alignItems: "center",
// Use the existing label notch: an outer outline crosses floating labels.