feat(web): reskin to refined SaaS aesthetic (per Dribbble reference)

Drop the brutalist hazard-tape vocabulary in favor of refined modern
SaaS: white cards on zinc-50, soft ring-1 zinc-200 borders (no hard
2px black), rounded-full pills, sans for chrome + mono for tabular
data, emerald replacing yellow as the saturated accent. Theme color
shifts to zinc-900 with an emerald dot on the icon.
This commit is contained in:
yiekheng 2026-05-02 21:15:02 +08:00
parent 3fe33772ce
commit f13e3993e9
6 changed files with 71 additions and 73 deletions

View File

@ -13,20 +13,17 @@ export default function AppleIcon() {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
background: "#facc15", background: "#18181b",
border: "8px solid #000000", color: "white",
boxSizing: "border-box", fontFamily: '"Helvetica", "Arial", sans-serif',
fontFamily:
'"Courier New", "Liberation Mono", "DejaVu Sans Mono", monospace',
position: "relative", position: "relative",
}} }}
> >
<span <span
style={{ style={{
color: "#000000", fontSize: 96,
fontSize: 110, fontWeight: 600,
fontWeight: 900, letterSpacing: "-4px",
letterSpacing: "-5px",
lineHeight: 1, lineHeight: 1,
}} }}
> >
@ -35,11 +32,12 @@ export default function AppleIcon() {
<div <div
style={{ style={{
position: "absolute", position: "absolute",
bottom: 0, top: 22,
right: 0, right: 22,
width: 24, width: 12,
height: 24, height: 12,
background: "#000000", borderRadius: 999,
background: "#10b981",
}} }}
/> />
</div> </div>

View File

@ -14,27 +14,27 @@ export default function Error({
}, [error]); }, [error]);
return ( return (
<div className="mx-auto flex min-h-[60vh] max-w-2xl items-center justify-center px-4 py-12"> <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
<div className="w-full border-2 border-zinc-900 bg-white"> <div className="w-full max-w-lg overflow-hidden rounded-2xl bg-white ring-1 ring-zinc-200/60">
<div className="flex items-center gap-3 border-b-2 border-zinc-900 bg-yellow-300 px-4 py-2"> <div className="flex items-center gap-3 border-b border-zinc-100 px-6 py-4">
<span <span
aria-hidden="true" aria-hidden="true"
className="inline-flex h-5 w-5 items-center justify-center bg-zinc-900 text-xs font-bold text-yellow-300" className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-red-100 text-xs font-semibold text-red-700"
> >
! !
</span> </span>
<span className="font-mono text-[11px] font-bold uppercase tracking-[0.25em] text-zinc-900"> <span className="text-[11px] font-medium uppercase tracking-wider text-zinc-500">
api unreachable API unreachable
</span> </span>
</div> </div>
<div className="px-6 py-6 sm:px-8 sm:py-8"> <div className="px-6 py-6 sm:px-8">
<h1 className="font-mono text-2xl font-bold uppercase tracking-tight text-zinc-900 sm:text-3xl"> <h1 className="text-2xl font-semibold tracking-tight text-zinc-900">
Couldn&apos;t reach the API Couldn&apos;t reach the API
</h1> </h1>
<p className="mt-3 text-sm text-zinc-700 sm:text-base"> <p className="mt-2 text-sm leading-relaxed text-zinc-600">
The dashboard fetches data from{" "} The dashboard fetches data from{" "}
<code className="rounded-sm bg-zinc-900 px-1.5 py-0.5 font-mono text-xs text-yellow-300"> <code className="rounded bg-zinc-100 px-1.5 py-0.5 font-mono text-xs text-zinc-700">
api-server:3000 api-server:3000
</code>{" "} </code>{" "}
on the internal docker network. The container may be down or on the internal docker network. The container may be down or
@ -44,16 +44,17 @@ export default function Error({
<button <button
type="button" type="button"
onClick={reset} onClick={reset}
className="mt-6 inline-flex items-center border-2 border-zinc-900 bg-yellow-300 px-4 py-2 font-mono text-[11px] font-bold uppercase tracking-[0.2em] text-zinc-900 hover:bg-zinc-900 hover:text-yellow-300" className="mt-6 inline-flex items-center gap-2 rounded-full bg-zinc-900 px-5 py-2 text-xs font-medium text-white transition-colors hover:bg-zinc-700"
> >
Retry Retry
<span aria-hidden="true"></span>
</button> </button>
<div className="mt-8 border-t-2 border-zinc-200 pt-4"> <div className="mt-8 border-t border-zinc-100 pt-4">
<div className="font-mono text-[10px] font-bold uppercase tracking-[0.25em] text-zinc-500"> <div className="text-[11px] font-medium uppercase tracking-wider text-zinc-500">
error Error
</div> </div>
<pre className="mt-2 overflow-x-auto rounded-sm bg-zinc-100 p-3 font-mono text-xs text-zinc-800"> <pre className="mt-2 overflow-x-auto rounded-md bg-zinc-50 p-3 font-mono text-xs text-zinc-700 ring-1 ring-zinc-200/60">
{error.message} {error.message}
{error.digest ? `\n\ndigest: ${error.digest}` : ""} {error.digest ? `\n\ndigest: ${error.digest}` : ""}
</pre> </pre>

View File

@ -13,20 +13,18 @@ export default function Icon() {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
background: "#facc15", background: "#18181b",
border: "24px solid #000000", borderRadius: 96,
boxSizing: "border-box", color: "white",
fontFamily: fontFamily: '"Helvetica", "Arial", sans-serif',
'"Courier New", "Liberation Mono", "DejaVu Sans Mono", monospace',
position: "relative", position: "relative",
}} }}
> >
<span <span
style={{ style={{
color: "#000000", fontSize: 280,
fontSize: 320, fontWeight: 600,
fontWeight: 900, letterSpacing: "-12px",
letterSpacing: "-16px",
lineHeight: 1, lineHeight: 1,
}} }}
> >
@ -35,11 +33,12 @@ export default function Icon() {
<div <div
style={{ style={{
position: "absolute", position: "absolute",
bottom: 0, top: 64,
right: 0, right: 64,
width: 72, width: 32,
height: 72, height: 32,
background: "#000000", borderRadius: 999,
background: "#10b981",
}} }}
/> />
</div> </div>

View File

@ -9,13 +9,7 @@ export const metadata: Metadata = {
}; };
export const viewport: Viewport = { export const viewport: Viewport = {
themeColor: "#facc15", themeColor: "#18181b",
};
const workbenchGrid = {
backgroundImage:
"radial-gradient(circle at 1px 1px, rgba(24,24,27,0.07) 1px, transparent 0)",
backgroundSize: "24px 24px",
}; };
export default function RootLayout({ export default function RootLayout({
@ -25,12 +19,11 @@ export default function RootLayout({
}) { }) {
return ( return (
<html lang="en"> <html lang="en">
<body <body className="min-h-screen bg-zinc-50 text-zinc-900 antialiased">
className="min-h-screen bg-zinc-50 text-zinc-900 antialiased"
style={workbenchGrid}
>
<Nav /> <Nav />
<main>{children}</main> <main className="mx-auto max-w-6xl px-4 pb-16 pt-8 sm:px-6 sm:pt-12">
{children}
</main>
<AutoRefresh /> <AutoRefresh />
</body> </body>
</html> </html>

View File

@ -8,8 +8,8 @@ export default function manifest(): MetadataRoute.Manifest {
start_url: "/", start_url: "/",
display: "standalone", display: "standalone",
orientation: "portrait", orientation: "portrait",
background_color: "#ffffff", background_color: "#fafafa",
theme_color: "#facc15", theme_color: "#18181b",
icons: [ icons: [
{ src: "/icon", sizes: "any", type: "image/png" }, { src: "/icon", sizes: "any", type: "image/png" },
{ src: "/apple-icon", sizes: "180x180", type: "image/png" }, { src: "/apple-icon", sizes: "180x180", type: "image/png" },

View File

@ -9,18 +9,26 @@ export default function Nav() {
const isAccounts = !isUsers; const isAccounts = !isUsers;
return ( return (
<header className="sticky top-0 z-10 border-b-2 border-zinc-900 bg-white/95 backdrop-blur"> <header className="sticky top-0 z-10 border-b border-zinc-200/80 bg-white/80 backdrop-blur-md">
<div className="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-3"> <div className="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-4 sm:px-6">
<div className="hidden flex-col leading-none sm:flex"> <Link href="/" className="group flex items-center gap-3">
<span className="font-mono text-sm font-bold uppercase tracking-[0.2em] text-zinc-900"> <span className="flex h-8 w-8 items-center justify-center rounded-lg bg-zinc-900 text-[11px] font-semibold tracking-tight text-white">
CM Bot V2 CM
</span> </span>
<span className="mt-0.5 font-mono text-[10px] uppercase tracking-[0.3em] text-zinc-500"> <span className="hidden flex-col leading-none sm:flex">
// dashboard <span className="text-sm font-semibold tracking-tight text-zinc-900">
CM Bot V2
</span>
<span className="mt-0.5 text-[11px] text-zinc-500">
Account dashboard
</span>
</span> </span>
</div> </Link>
<nav className="flex items-center gap-2" aria-label="Primary"> <nav
aria-label="Primary"
className="flex items-center gap-1 rounded-full bg-zinc-100 p-1"
>
<NavLink href="/" active={isAccounts}> <NavLink href="/" active={isAccounts}>
Accounts Accounts
</NavLink> </NavLink>
@ -42,16 +50,15 @@ function NavLink({
active: boolean; active: boolean;
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const base =
"inline-flex items-center border-2 px-3 py-1.5 font-mono text-[11px] font-bold uppercase tracking-[0.2em] transition-colors";
const activeCls = "border-zinc-900 bg-yellow-300 text-zinc-900";
const inactiveCls =
"border-transparent text-zinc-700 hover:border-zinc-900 hover:bg-yellow-50 hover:text-zinc-900";
return ( return (
<Link <Link
href={href} href={href}
aria-current={active ? "page" : undefined} aria-current={active ? "page" : undefined}
className={`${base} ${active ? activeCls : inactiveCls}`} className={`inline-flex items-center rounded-full px-4 py-1.5 text-xs font-medium transition-colors sm:text-sm ${
active
? "bg-white text-zinc-900 shadow-sm ring-1 ring-zinc-200/60"
: "text-zinc-500 hover:text-zinc-900"
}`}
> >
{children} {children}
</Link> </Link>