diff --git a/docs/empty-state-guidelines.md b/docs/empty-state-guidelines.md
new file mode 100644
index 0000000..fb69e2f
--- /dev/null
+++ b/docs/empty-state-guidelines.md
@@ -0,0 +1,27 @@
+# Empty State Illustration Style + Copy Guidelines
+
+## Design goals
+
+- Help users understand slot availability, wallet readiness, and booking progress at a glance.
+- Keep the primary action obvious without crowding the state with competing buttons.
+- Use calm, trust-building language before asking for sensitive actions like wallet connection.
+
+## Illustration rules
+
+- Use abstract product-shaped panels instead of decorative mascots so the empty state still feels product-specific.
+- Reserve the strongest accent for the most important action area.
+- Keep illustrations non-essential for meaning; screen-reader users should get the same guidance from headings and body copy.
+
+## Copy rules
+
+- Headline: say what is missing in plain language.
+- Body copy: explain why the state matters or what remains private.
+- Guidance: use short supporting lines to reduce uncertainty.
+- Action labels: start with a verb and match the next obvious step.
+
+## Reusable implementation notes
+
+- Shared shell: `src/app/components/dashboard-shell.tsx`
+- Empty state card: `src/app/components/empty-state-card.tsx`
+- Status chip: `src/app/components/ui/status-chip.tsx`
+- Route-level states: `src/app/dashboard/loading.tsx`, `src/app/dashboard/error.tsx`
diff --git a/src/app/components/dashboard-shell.tsx b/src/app/components/dashboard-shell.tsx
new file mode 100644
index 0000000..a57d4ab
--- /dev/null
+++ b/src/app/components/dashboard-shell.tsx
@@ -0,0 +1,38 @@
+import Link from "next/link";
+
+type DashboardShellProps = {
+ children: React.ReactNode;
+};
+
+export function DashboardShell({ children }: DashboardShellProps) {
+ return (
+
+
+
+
+
+ ChronoPay
+
+
+ Time economy dashboard
+
+
+
+
+
+
{children}
+
+ );
+}
diff --git a/src/app/components/empty-state-card.tsx b/src/app/components/empty-state-card.tsx
new file mode 100644
index 0000000..8200999
--- /dev/null
+++ b/src/app/components/empty-state-card.tsx
@@ -0,0 +1,55 @@
+import type { ReactNode } from "react";
+import { EmptyStateIllustration } from "./empty-state-illustration";
+import { StatusChip } from "./ui/status-chip";
+
+type EmptyStateCardProps = {
+ eyebrow: string;
+ title: string;
+ description: string;
+ accentLabel: string;
+ status: {
+ label: string;
+ tone?: "info" | "warning" | "success" | "danger" | "neutral";
+ };
+ guidance: string[];
+ actions?: ReactNode;
+};
+
+export function EmptyStateCard({
+ eyebrow,
+ title,
+ description,
+ accentLabel,
+ status,
+ guidance,
+ actions,
+}: EmptyStateCardProps) {
+ return (
+
+
+
+ {eyebrow}
+
+
{status.label}
+
+
+
+
+
+
{title}
+
{description}
+
+ {guidance.map((item) => (
+
+ {item}
+
+ ))}
+
+
+ {actions ? {actions}
: null}
+
+ );
+}
diff --git a/src/app/components/empty-state-illustration.tsx b/src/app/components/empty-state-illustration.tsx
new file mode 100644
index 0000000..2151c41
--- /dev/null
+++ b/src/app/components/empty-state-illustration.tsx
@@ -0,0 +1,43 @@
+type EmptyStateIllustrationProps = {
+ accentLabel: string;
+};
+
+export function EmptyStateIllustration({
+ accentLabel,
+}: EmptyStateIllustrationProps) {
+ return (
+
+ );
+}
diff --git a/src/app/components/ui/button-link.tsx b/src/app/components/ui/button-link.tsx
new file mode 100644
index 0000000..d84395e
--- /dev/null
+++ b/src/app/components/ui/button-link.tsx
@@ -0,0 +1,34 @@
+import Link from "next/link";
+import type { LinkProps } from "next/link";
+import type { ReactNode } from "react";
+
+type ButtonLinkProps = LinkProps & {
+ children: ReactNode;
+ className?: string;
+ variant?: "primary" | "secondary" | "ghost";
+};
+
+const variantClasses = {
+ primary:
+ "bg-cyan-300 text-slate-950 hover:bg-cyan-200 shadow-[0_16px_34px_rgba(34,211,238,0.22)]",
+ secondary:
+ "border border-white/12 bg-white/6 text-slate-100 hover:border-cyan-200/30 hover:bg-white/10",
+ ghost:
+ "text-cyan-200 hover:bg-cyan-300/10 hover:text-cyan-100",
+};
+
+export function ButtonLink({
+ children,
+ className = "",
+ variant = "primary",
+ ...props
+}: ButtonLinkProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/app/components/ui/status-chip.tsx b/src/app/components/ui/status-chip.tsx
new file mode 100644
index 0000000..33ec1bb
--- /dev/null
+++ b/src/app/components/ui/status-chip.tsx
@@ -0,0 +1,22 @@
+type StatusChipProps = {
+ tone?: "info" | "warning" | "success" | "danger" | "neutral";
+ children: React.ReactNode;
+};
+
+const toneClasses = {
+ info: "border-cyan-300/25 bg-cyan-300/12 text-cyan-100",
+ warning: "border-amber-300/25 bg-amber-300/12 text-amber-100",
+ success: "border-emerald-300/25 bg-emerald-300/12 text-emerald-100",
+ danger: "border-rose-300/25 bg-rose-300/12 text-rose-100",
+ neutral: "border-white/10 bg-white/6 text-slate-200",
+};
+
+export function StatusChip({ tone = "neutral", children }: StatusChipProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/app/dashboard/error.tsx b/src/app/dashboard/error.tsx
new file mode 100644
index 0000000..9259cab
--- /dev/null
+++ b/src/app/dashboard/error.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import Link from "next/link";
+import { useEffect } from "react";
+import { DashboardShell } from "../components/dashboard-shell";
+
+export default function DashboardError({
+ error,
+ reset,
+}: {
+ error: Error & { digest?: string };
+ reset: () => void;
+}) {
+ useEffect(() => {
+ console.error(error);
+ }, [error]);
+
+ return (
+
+
+
+ Error state
+
+
+ We could not load your booking workspace
+
+
+ Nothing has been published or charged. Try again to reload the dashboard, or return home if you want to restart from a safe point.
+
+
+ {error.message || "Unexpected dashboard error."}
+
+
+
+ Try again
+
+
+ Go home
+
+
+
+
+ );
+}
diff --git a/src/app/dashboard/loading.tsx b/src/app/dashboard/loading.tsx
new file mode 100644
index 0000000..0de7ec7
--- /dev/null
+++ b/src/app/dashboard/loading.tsx
@@ -0,0 +1,27 @@
+import { DashboardShell } from "../components/dashboard-shell";
+
+export default function DashboardLoading() {
+ return (
+
+
+ {[0, 1].map((item) => (
+
+ ))}
+
+
+ {[0, 1, 2].map((item) => (
+
+ ))}
+
+
+ );
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index f28320c..8b799de 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,22 +1,37 @@
@import "tailwindcss";
:root {
- --background: #ffffff;
- --foreground: #171717;
+ --background: #07111f;
+ --foreground: #f4f7fb;
+ --surface: rgba(11, 23, 40, 0.82);
+ --surface-strong: rgba(10, 20, 36, 0.96);
+ --border-subtle: rgba(148, 163, 184, 0.14);
+ --border-strong: rgba(125, 211, 252, 0.22);
+ --accent: #6ee7f9;
+ --accent-strong: #22d3ee;
+ --accent-warm: #f59e0b;
+ --success: #34d399;
+ --danger: #f87171;
+ --muted: #9fb0c7;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
- --font-sans: var(--font-geist-sans);
- --font-mono: var(--font-geist-mono);
+ --font-sans:
+ "Segoe UI", "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
+ --font-mono:
+ "SFMono-Regular", "Cascadia Code", "Fira Code", Consolas, monospace;
}
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
+* {
+ box-sizing: border-box;
+}
+
+html {
+ background:
+ radial-gradient(circle at top, rgba(34, 211, 238, 0.18), transparent 34%),
+ linear-gradient(180deg, #09111e 0%, #050b14 100%);
}
body {
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 3ed0e9c..7e822ed 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,17 +1,6 @@
import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
-
export const metadata: Metadata = {
title: "ChronoPay - Time Economy",
description: "Tokenize and trade human time on the Stellar network.",
@@ -24,11 +13,7 @@ export default function RootLayout({
}>) {
return (
-
- {children}
-
+ {children}
);
}