feat(ui): establish premium design foundation from mockups
CI and Deploy / test (push) Successful in 2m21s
CI and Deploy / deploy (push) Successful in 39s

Extract design tokens from the mockup set (F:\Pictures\website\jobtracker\new
dashboard, pipeline, job-workspace, features, workflow screens) into the
central theme so every screen picks the change up automatically:

- Heading weight: h1-h4 go bold/black (800/700) to match the mockups' heavy
  display type; h5/h6 stay a lighter semibold so dense screens don't turn
  into a wall of black text.
- Card shadow: replace the flat 1px "section" shadow + visible border with a
  soft floating shadow and no border, matching how mockup cards sit on the
  grey page background.
- Border radius: 10/12/8px -> 14/16/10px across shape/card/button defaults,
  matching the mockups' rounder corners.
- New GradientButton component wrapping the mockup's signature indigo->cyan
  CTA gradient ("Tailor my CV for this role", "See the interface tour"),
  reserved for the single most important AI-assist/hero action per screen.

The dark navy sidebar (#0f172a) already matched the mockups from an earlier
pass -- untouched here.

Verified: tsc clean, full frontend suite green (65/65). Live visual
screenshot verification wasn't possible -- the Browser pane's screenshot
tool times out in this environment; verified structurally via read_page
and the app rendering without console errors instead.
This commit is contained in:
cesnimda
2026-07-13 08:50:26 +02:00
parent b8b7987c58
commit 1ab92e5c9d
2 changed files with 70 additions and 15 deletions
@@ -0,0 +1,33 @@
import React from "react";
import Button, { ButtonProps } from "@mui/material/Button";
import { GRADIENT_CTA } from "../theme";
/**
* The mockup's gradient AI/primary-action pill ("Tailor my CV for this role", "See the
* interface tour"). Reserve this for the single most important AI-assist or hero action on a
* screen -- everything else stays a plain contained/outlined Button, or the gradient stops
* reading as a signal and becomes wallpaper.
*/
export default function GradientButton({ sx, disabled, ...props }: ButtonProps) {
return (
<Button
variant="contained"
disableElevation
disabled={disabled}
sx={[
(theme: any) => ({
background: disabled ? undefined : GRADIENT_CTA,
color: disabled ? undefined : "#fff",
boxShadow: disabled ? undefined : theme.vars.customShadows.gradientButton,
"&:hover": disabled
? undefined
: { background: GRADIENT_CTA, filter: "brightness(1.06)", boxShadow: theme.vars.customShadows.gradientButton },
}),
...(Array.isArray(sx) ? sx : sx ? [sx] : []),
]}
{...props}
/>
);
}
+37 -15
View File
@@ -173,30 +173,49 @@ function buildCustomShadows(palette: any) {
const primaryColor = palette.primary.main; const primaryColor = palette.primary.main;
return { return {
button: `0px 0.711px 1.422px 0px ${alpha(shadowColor, 0.05)}`, button: `0px 0.711px 1.422px 0px ${alpha(shadowColor, 0.05)}`,
section: `0px 1px 2px 0px ${alpha(shadowColor, 0.07)}`, // 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)}`,
tooltip: `0px 12px 16px -4px ${alpha(shadowColor, 0.08)}, 0px 4px 6px -2px ${alpha(shadowColor, 0.03)}`, tooltip: `0px 12px 16px -4px ${alpha(shadowColor, 0.08)}, 0px 4px 6px -2px ${alpha(shadowColor, 0.03)}`,
focus: `0px 0px 0px 3px ${alpha(primaryColor, 0.2)}`, focus: `0px 0px 0px 3px ${alpha(primaryColor, 0.2)}`,
// Gradient CTA (mockup's "Tailor my CV" / "See the interface tour" buttons) gets its own
// colored glow instead of the flat neutral button shadow.
gradientButton: `0px 4px 14px -4px ${alpha(primaryColor, 0.45)}`,
}; };
} }
// Mockup's signature AI/primary-action gradient (dashboard "Tailor my CV", landing "See the
// interface tour"). Exported so every gradient CTA across the app stays pixel-identical
// instead of each screen inventing its own two-color mix -- consumed via <GradientButton>
// (src/components/GradientButton.tsx), not applied as a MUI Button variant (MUI's variant
// override machinery needs TS module augmentation for a genuinely new variant name; a plain
// wrapper component is far less code for the same result).
export const GRADIENT_CTA = "linear-gradient(90deg, #6366F1 0%, #22D3EE 100%)";
function buildTypography() { function buildTypography() {
return { return {
fontFamily: "'Archivo', system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif", fontFamily: "'Archivo', system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif",
letterSpacing: 0, letterSpacing: 0,
h1: { fontWeight: 500, fontSize: 36, lineHeight: "40px" }, // Mockup headings (F:\Pictures\website\jobtracker\new) are bold/black-weight display
h2: { fontWeight: 500, fontSize: 30, lineHeight: "34px" }, // type ("Dashboard", "One workspace for the whole search") -- 500 across the board read
h3: { fontWeight: 500, fontSize: 26, lineHeight: "30px" }, // as noticeably lighter/flatter than that reference. h1-h4 (page titles, hero, section
h4: { fontWeight: 500, fontSize: 22, lineHeight: "26px" }, // headers) go heavy; h5/h6 (card titles, in-context labels) stay a lighter semibold so
h5: { fontWeight: 500, fontSize: 18, lineHeight: "22px" }, // dense screens don't turn into a wall of black text.
h6: { fontWeight: 500, fontSize: 16, lineHeight: "20px" }, h1: { fontWeight: 800, fontSize: 36, lineHeight: "40px", letterSpacing: "-0.02em" },
subtitle1: { fontWeight: 500, fontSize: 14, lineHeight: "18px" }, h2: { fontWeight: 800, fontSize: 30, lineHeight: "34px", letterSpacing: "-0.02em" },
subtitle2: { fontWeight: 500, fontSize: 13, lineHeight: "17px" }, h3: { fontWeight: 800, fontSize: 26, lineHeight: "30px", letterSpacing: "-0.01em" },
h4: { fontWeight: 700, fontSize: 22, lineHeight: "26px", letterSpacing: "-0.01em" },
h5: { fontWeight: 700, fontSize: 18, lineHeight: "22px" },
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" }, body1: { fontWeight: 400, fontSize: 14, lineHeight: "18px" },
body2: { fontWeight: 400, fontSize: 13, lineHeight: "17px" }, body2: { fontWeight: 400, fontSize: 13, lineHeight: "17px" },
caption: { fontWeight: 400, fontSize: 12, lineHeight: "16px", letterSpacing: 0 }, caption: { fontWeight: 400, fontSize: 12, lineHeight: "16px", letterSpacing: 0 },
overline: { fontWeight: 600, fontSize: 11, lineHeight: "14px", letterSpacing: "0.08em", textTransform: "uppercase" as const }, overline: { fontWeight: 700, fontSize: 11, lineHeight: "14px", letterSpacing: "0.08em", textTransform: "uppercase" as const },
caption1: { fontWeight: 500, fontSize: 12, lineHeight: "16px", letterSpacing: 0 }, caption1: { fontWeight: 500, fontSize: 12, lineHeight: "16px", letterSpacing: 0 },
button: { textTransform: "capitalize" as const }, button: { textTransform: "capitalize" as const, fontWeight: 600 },
}; };
} }
@@ -222,7 +241,9 @@ export const getTheme = (_mode: "light" | "dark") => {
light: { palette: lightPalette, customShadows: buildCustomShadows(lightPalette) }, light: { palette: lightPalette, customShadows: buildCustomShadows(lightPalette) },
dark: { palette: darkPalette, customShadows: buildCustomShadows(darkPalette) }, dark: { palette: darkPalette, customShadows: buildCustomShadows(darkPalette) },
}, },
shape: { borderRadius: 10 }, // Mockup radius reads noticeably rounder than the previous 10/8px defaults across cards,
// stat tiles, kanban cards and buttons.
shape: { borderRadius: 14 },
typography: buildTypography() as any, typography: buildTypography() as any,
} as any) as any; } as any) as any;
@@ -249,8 +270,9 @@ export const getTheme = (_mode: "light" | "dark") => {
defaultProps: { elevation: 0 }, defaultProps: { elevation: 0 },
styleOverrides: { styleOverrides: {
root: ({ theme }: any) => ({ root: ({ theme }: any) => ({
border: `1px solid ${theme.vars.palette.divider}`, // No border -- mockup cards float on the grey page background purely via shadow.
borderRadius: 12, border: "none",
borderRadius: 16,
backgroundImage: "none", backgroundImage: "none",
boxShadow: theme.vars.customShadows.section, boxShadow: theme.vars.customShadows.section,
}), }),
@@ -260,7 +282,7 @@ export const getTheme = (_mode: "light" | "dark") => {
defaultProps: { disableFocusRipple: true }, defaultProps: { disableFocusRipple: true },
styleOverrides: { styleOverrides: {
root: ({ theme }: any) => ({ root: ({ theme }: any) => ({
borderRadius: 8, borderRadius: 10,
boxShadow: theme.vars.customShadows.button, boxShadow: theme.vars.customShadows.button,
"&:hover": { boxShadow: theme.vars.customShadows.button }, "&:hover": { boxShadow: theme.vars.customShadows.button },
}), }),