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",
|
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>
|
||||||
|
|||||||
@ -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't reach the API
|
Couldn'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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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" },
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user