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:
parent
3fe33772ce
commit
f13e3993e9
@ -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>
|
||||
|
||||
@ -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'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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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" },
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user