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

View File

@ -14,27 +14,27 @@ export default function Error({
}, [error]);
return (
<div className="mx-auto flex min-h-[60vh] max-w-2xl items-center justify-center px-4 py-12">
<div className="w-full border-2 border-zinc-900 bg-white">
<div className="flex items-center gap-3 border-b-2 border-zinc-900 bg-yellow-300 px-4 py-2">
<div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
<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 border-zinc-100 px-6 py-4">
<span
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 className="font-mono text-[11px] font-bold uppercase tracking-[0.25em] text-zinc-900">
api unreachable
<span className="text-[11px] font-medium uppercase tracking-wider text-zinc-500">
API unreachable
</span>
</div>
<div className="px-6 py-6 sm:px-8 sm:py-8">
<h1 className="font-mono text-2xl font-bold uppercase tracking-tight text-zinc-900 sm:text-3xl">
<div className="px-6 py-6 sm:px-8">
<h1 className="text-2xl font-semibold tracking-tight text-zinc-900">
Couldn&apos;t reach the API
</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{" "}
<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
</code>{" "}
on the internal docker network. The container may be down or
@ -44,16 +44,17 @@ export default function Error({
<button
type="button"
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>
<div className="mt-8 border-t-2 border-zinc-200 pt-4">
<div className="font-mono text-[10px] font-bold uppercase tracking-[0.25em] text-zinc-500">
error
<div className="mt-8 border-t border-zinc-100 pt-4">
<div className="text-[11px] font-medium uppercase tracking-wider text-zinc-500">
Error
</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.digest ? `\n\ndigest: ${error.digest}` : ""}
</pre>

View File

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

View File

@ -9,13 +9,7 @@ export const metadata: Metadata = {
};
export const viewport: Viewport = {
themeColor: "#facc15",
};
const workbenchGrid = {
backgroundImage:
"radial-gradient(circle at 1px 1px, rgba(24,24,27,0.07) 1px, transparent 0)",
backgroundSize: "24px 24px",
themeColor: "#18181b",
};
export default function RootLayout({
@ -25,12 +19,11 @@ export default function RootLayout({
}) {
return (
<html lang="en">
<body
className="min-h-screen bg-zinc-50 text-zinc-900 antialiased"
style={workbenchGrid}
>
<body className="min-h-screen bg-zinc-50 text-zinc-900 antialiased">
<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 />
</body>
</html>

View File

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

View File

@ -9,18 +9,26 @@ export default function Nav() {
const isAccounts = !isUsers;
return (
<header className="sticky top-0 z-10 border-b-2 border-zinc-900 bg-white/95 backdrop-blur">
<div className="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-3">
<div className="hidden flex-col leading-none sm:flex">
<span className="font-mono text-sm font-bold uppercase tracking-[0.2em] text-zinc-900">
<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-4 sm:px-6">
<Link href="/" className="group flex items-center gap-3">
<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
</span>
<span className="hidden flex-col leading-none sm:flex">
<span className="text-sm font-semibold tracking-tight text-zinc-900">
CM Bot V2
</span>
<span className="mt-0.5 font-mono text-[10px] uppercase tracking-[0.3em] text-zinc-500">
// dashboard
<span className="mt-0.5 text-[11px] text-zinc-500">
Account dashboard
</span>
</div>
</span>
</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}>
Accounts
</NavLink>
@ -42,16 +50,15 @@ function NavLink({
active: boolean;
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 (
<Link
href={href}
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}
</Link>