feat(ui): establish premium design foundation from mockups
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:
@@ -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
@@ -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 },
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user