diff --git a/apps/web/src/app/(authenticated)/layout.tsx b/apps/web/src/app/(authenticated)/layout.tsx
index 2580c03..0be3a61 100644
--- a/apps/web/src/app/(authenticated)/layout.tsx
+++ b/apps/web/src/app/(authenticated)/layout.tsx
@@ -4,8 +4,10 @@ import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/lib/auth/auth-context";
import { IS_MOCK_AUTH_MODE } from "@/lib/config";
-import { Navigation } from "@/components/layout/Navigation";
+import { AppHeader } from "@/components/layout/AppHeader";
+import { AppSidebar } from "@/components/layout/AppSidebar";
import { ChatOverlay } from "@/components/chat";
+import { MosaicSpinner } from "@/components/ui/MosaicSpinner";
import type { ReactNode } from "react";
export default function AuthenticatedLayout({
@@ -23,11 +25,7 @@ export default function AuthenticatedLayout({
}, [isAuthenticated, isLoading, router]);
if (isLoading) {
- return (
-
-
-
+
+ {/* Full-width header — grid-column: 1 / -1 via .app-header CSS class */}
+
+
+ {/* Sidebar — left column, row 2, via .app-sidebar CSS class */}
+
+
+ {/* Main content — right column, row 2, via .app-main CSS class */}
+
{IS_MOCK_AUTH_MODE && (
Mock Auth Mode (Local Only): Real authentication is bypassed for frontend development.
)}
- {children}
-
+
{children}
+
+
{!IS_MOCK_AUTH_MODE &&
}
);
diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css
index a7bdc88..9206789 100644
--- a/apps/web/src/app/globals.css
+++ b/apps/web/src/app/globals.css
@@ -3,147 +3,250 @@
@tailwind utilities;
/* =============================================================================
- DESIGN C: PROFESSIONAL/ENTERPRISE DESIGN SYSTEM
- Philosophy: "Good design is as little design as possible." - Dieter Rams
+ MOSAIC DESIGN SYSTEM — Reference token system from dashboard design
============================================================================= */
/* -----------------------------------------------------------------------------
- CSS Custom Properties - Light Theme (Default)
+ Primitive Tokens (Dark-first — dark is the default theme)
----------------------------------------------------------------------------- */
:root {
- /* Base colors - increased contrast from surfaces */
- --color-background: 245 247 250;
- --color-foreground: 15 23 42;
+ /* Mosaic design tokens — dark palette (default) */
+ --ms-bg-950: #080b12;
+ --ms-bg-900: #0f141d;
+ --ms-bg-850: #151b26;
+ --ms-surface-800: #1b2331;
+ --ms-surface-750: #232d3f;
+ --ms-border-700: #2f3b52;
+ --ms-text-100: #eef3ff;
+ --ms-text-300: #c5d0e6;
+ --ms-text-500: #8f9db7;
+ --ms-blue-500: #2f80ff;
+ --ms-blue-400: #56a0ff;
+ --ms-red-500: #e5484d;
+ --ms-red-400: #f06a6f;
+ --ms-purple-500: #8b5cf6;
+ --ms-purple-400: #a78bfa;
+ --ms-teal-500: #14b8a6;
+ --ms-teal-400: #2dd4bf;
+ --ms-amber-500: #f59e0b;
+ --ms-amber-400: #fbbf24;
+ --ms-pink-500: #ec4899;
+ --ms-emerald-500: #10b981;
+ --ms-orange-500: #f97316;
+ --ms-cyan-500: #06b6d4;
+ --ms-indigo-500: #6366f1;
- /* Surface hierarchy (elevation levels) - improved contrast */
- --surface-0: 255 255 255;
- --surface-1: 250 251 252;
- --surface-2: 241 245 249;
- --surface-3: 226 232 240;
+ /* Semantic aliases — dark theme is default */
+ --bg: var(--ms-bg-900);
+ --bg-deep: var(--ms-bg-950);
+ --bg-mid: var(--ms-bg-850);
+ --surface: var(--ms-surface-800);
+ --surface-2: var(--ms-surface-750);
+ --border: var(--ms-border-700);
+ --text: var(--ms-text-100);
+ --text-2: var(--ms-text-300);
+ --muted: var(--ms-text-500);
+ --primary: var(--ms-blue-500);
+ --primary-l: var(--ms-blue-400);
+ --danger: var(--ms-red-500);
+ --success: var(--ms-teal-500);
+ --warn: var(--ms-amber-500);
+ --purple: var(--ms-purple-500);
- /* Text hierarchy */
- --text-primary: 15 23 42;
- --text-secondary: 51 65 85;
- --text-tertiary: 71 85 105;
- --text-muted: 100 116 139;
+ /* Typography */
+ --font: var(--font-outfit, 'Outfit'), system-ui, sans-serif;
+ --mono: var(--font-fira-code, 'Fira Code'), 'Cascadia Code', monospace;
- /* Border colors - stronger borders for light mode */
- --border-default: 203 213 225;
- --border-subtle: 226 232 240;
- --border-strong: 148 163 184;
+ /* Radius scale */
+ --r: 8px;
+ --r-sm: 5px;
+ --r-lg: 12px;
+ --r-xl: 16px;
- /* Brand accent - Indigo (professional, trustworthy) */
- --accent-primary: 79 70 229;
- --accent-primary-hover: 67 56 202;
- --accent-primary-light: 238 242 255;
- --accent-primary-muted: 199 210 254;
+ /* Layout dimensions */
+ --sidebar-w: 260px;
+ --topbar-h: 56px;
+ --terminal-h: 220px;
- /* Semantic colors - Success (Emerald) */
- --semantic-success: 16 185 129;
- --semantic-success-light: 209 250 229;
- --semantic-success-dark: 6 95 70;
+ /* Easing */
+ --ease: cubic-bezier(0.16, 1, 0.3, 1);
- /* Semantic colors - Warning (Amber) */
- --semantic-warning: 245 158 11;
- --semantic-warning-light: 254 243 199;
- --semantic-warning-dark: 146 64 14;
-
- /* Semantic colors - Error (Rose) */
- --semantic-error: 244 63 94;
- --semantic-error-light: 255 228 230;
- --semantic-error-dark: 159 18 57;
-
- /* Semantic colors - Info (Sky) */
- --semantic-info: 14 165 233;
- --semantic-info-light: 224 242 254;
- --semantic-info-dark: 3 105 161;
-
- /* Focus ring */
- --focus-ring: 99 102 241;
- --focus-ring-offset: 255 255 255;
-
- /* Shadows - visible but subtle */
- --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05), 0 1px 3px 0 rgb(0 0 0 / 0.05);
- --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.08), 0 2px 4px -2px rgb(0 0 0 / 0.06);
- --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.08);
-}
-
-/* -----------------------------------------------------------------------------
- CSS Custom Properties - Dark Theme
- ----------------------------------------------------------------------------- */
-.dark {
- --color-background: 3 7 18;
- --color-foreground: 248 250 252;
-
- /* Surface hierarchy (elevation levels) */
- --surface-0: 15 23 42;
- --surface-1: 30 41 59;
- --surface-2: 51 65 85;
- --surface-3: 71 85 105;
-
- /* Text hierarchy */
- --text-primary: 248 250 252;
- --text-secondary: 203 213 225;
- --text-tertiary: 148 163 184;
- --text-muted: 100 116 139;
-
- /* Border colors */
- --border-default: 51 65 85;
- --border-subtle: 30 41 59;
- --border-strong: 71 85 105;
-
- /* Brand accent adjustments for dark mode */
- --accent-primary: 129 140 248;
- --accent-primary-hover: 165 180 252;
- --accent-primary-light: 30 27 75;
- --accent-primary-muted: 55 48 163;
-
- /* Semantic colors adjustments */
- --semantic-success: 52 211 153;
- --semantic-success-light: 6 78 59;
- --semantic-success-dark: 167 243 208;
-
- --semantic-warning: 251 191 36;
- --semantic-warning-light: 120 53 15;
- --semantic-warning-dark: 253 230 138;
-
- --semantic-error: 251 113 133;
- --semantic-error-light: 136 19 55;
- --semantic-error-dark: 253 164 175;
-
- --semantic-info: 56 189 248;
- --semantic-info-light: 12 74 110;
- --semantic-info-dark: 186 230 253;
-
- /* Focus ring */
- --focus-ring: 129 140 248;
- --focus-ring-offset: 15 23 42;
-
- /* Shadows - subtle glow in dark mode */
+ /* Legacy shadow tokens (retained for component compat) */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5), 0 4px 6px -4px rgb(0 0 0 / 0.4);
}
+/* -----------------------------------------------------------------------------
+ Light Theme Override — applied via data-theme attribute on
+ ----------------------------------------------------------------------------- */
+[data-theme="light"] {
+ --ms-bg-950: #f8faff;
+ --ms-bg-900: #f0f4fc;
+ --ms-bg-850: #e8edf8;
+ --ms-surface-800: #dde4f2;
+ --ms-surface-750: #d0d9ec;
+ --ms-border-700: #b8c4de;
+ --ms-text-100: #0f141d;
+ --ms-text-300: #2f3b52;
+ --ms-text-500: #5a6a87;
+
+ /* Re-alias semantics for light — identical structure, primitive tokens differ */
+ --bg: var(--ms-bg-900);
+ --bg-deep: var(--ms-bg-950);
+ --bg-mid: var(--ms-bg-850);
+ --surface: var(--ms-surface-800);
+ --surface-2: var(--ms-surface-750);
+ --border: var(--ms-border-700);
+ --text: var(--ms-text-100);
+ --text-2: var(--ms-text-300);
+ --muted: var(--ms-text-500);
+
+ /* Lighter shadows for light mode */
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05), 0 1px 3px 0 rgb(0 0 0 / 0.05);
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.08), 0 2px 4px -2px rgb(0 0 0 / 0.06);
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.08);
+}
+
/* -----------------------------------------------------------------------------
Base Styles
----------------------------------------------------------------------------- */
-* {
+*,
+*::before,
+*::after {
box-sizing: border-box;
}
html {
+ font-size: 15px;
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
- color: rgb(var(--text-primary));
- background: rgb(var(--color-background));
- font-size: 14px;
+ font-family: var(--font);
+ background: var(--bg);
+ color: var(--text);
line-height: 1.5;
- transition: background-color 0.15s ease, color 0.15s ease;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+button {
+ font-family: inherit;
+ cursor: pointer;
+ border: none;
+ background: none;
+ color: inherit;
+}
+
+input,
+select,
+textarea {
+ font-family: inherit;
+}
+
+ul {
+ list-style: none;
+}
+
+/* Subtle grain/noise overlay for texture */
+body::before {
+ content: '';
+ position: fixed;
+ inset: 0;
+ pointer-events: none;
+ z-index: 9999;
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
+ opacity: 0.025;
+}
+
+/* -----------------------------------------------------------------------------
+ Focus States - Accessible & Visible
+ ----------------------------------------------------------------------------- */
+@layer base {
+ :focus-visible {
+ outline: 2px solid var(--ms-blue-400);
+ outline-offset: 2px;
+ }
+
+ :focus:not(:focus-visible) {
+ outline: none;
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ Scrollbar Styling - Minimal & Professional
+ ----------------------------------------------------------------------------- */
+::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--border);
+ border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--muted);
+}
+
+/* Firefox */
+* {
+ scrollbar-width: thin;
+ scrollbar-color: var(--border) transparent;
+}
+
+/* -----------------------------------------------------------------------------
+ App Shell Grid Layout
+ ----------------------------------------------------------------------------- */
+.app-shell {
+ display: grid;
+ grid-template-columns: var(--sidebar-w) 1fr;
+ grid-template-rows: var(--topbar-h) 1fr;
+ height: 100vh;
+ overflow: hidden;
+}
+
+.app-header {
+ grid-column: 1 / -1;
+ grid-row: 1;
+ background: var(--bg-deep);
+ border-bottom: 1px solid var(--border);
+ display: flex;
+ align-items: center;
+ padding: 0 20px;
+ gap: 12px;
+ z-index: 100;
+}
+
+.app-sidebar {
+ grid-column: 1;
+ grid-row: 2;
+ background: var(--bg-deep);
+ border-right: 1px solid var(--border);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.app-main {
+ grid-column: 2;
+ grid-row: 2;
+ background: var(--bg);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ position: relative;
}
/* -----------------------------------------------------------------------------
@@ -182,102 +285,10 @@ body {
}
.text-mono {
- font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
+ font-family: var(--mono);
font-size: 0.8125rem;
line-height: 1.25rem;
}
-
- /* Text color utilities */
- .text-primary {
- color: rgb(var(--text-primary));
- }
-
- .text-secondary {
- color: rgb(var(--text-secondary));
- }
-
- .text-tertiary {
- color: rgb(var(--text-tertiary));
- }
-
- .text-muted {
- color: rgb(var(--text-muted));
- }
-}
-
-/* -----------------------------------------------------------------------------
- Surface & Card Utilities
- ----------------------------------------------------------------------------- */
-@layer utilities {
- .surface-0 {
- background-color: rgb(var(--surface-0));
- }
-
- .surface-1 {
- background-color: rgb(var(--surface-1));
- }
-
- .surface-2 {
- background-color: rgb(var(--surface-2));
- }
-
- .surface-3 {
- background-color: rgb(var(--surface-3));
- }
-
- .border-default {
- border-color: rgb(var(--border-default));
- }
-
- .border-subtle {
- border-color: rgb(var(--border-subtle));
- }
-
- .border-strong {
- border-color: rgb(var(--border-strong));
- }
-}
-
-/* -----------------------------------------------------------------------------
- Focus States - Accessible & Visible
- ----------------------------------------------------------------------------- */
-@layer base {
- :focus-visible {
- outline: 2px solid rgb(var(--focus-ring));
- outline-offset: 2px;
- }
-
- /* Remove default focus for mouse users */
- :focus:not(:focus-visible) {
- outline: none;
- }
-}
-
-/* -----------------------------------------------------------------------------
- Scrollbar Styling - Minimal & Professional
- ----------------------------------------------------------------------------- */
-::-webkit-scrollbar {
- width: 6px;
- height: 6px;
-}
-
-::-webkit-scrollbar-track {
- background: transparent;
-}
-
-::-webkit-scrollbar-thumb {
- background: rgb(var(--text-muted) / 0.4);
- border-radius: 3px;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background: rgb(var(--text-muted) / 0.6);
-}
-
-/* Firefox */
-* {
- scrollbar-width: thin;
- scrollbar-color: rgb(var(--text-muted) / 0.4) transparent;
}
/* -----------------------------------------------------------------------------
@@ -292,40 +303,46 @@ body {
.btn-primary {
@apply btn px-4 py-2;
- background-color: rgb(var(--accent-primary));
+ background: linear-gradient(135deg, var(--ms-blue-500), var(--ms-purple-500));
color: white;
+ border-radius: var(--r);
}
.btn-primary:hover:not(:disabled) {
- background-color: rgb(var(--accent-primary-hover));
+ box-shadow: 0 8px 28px rgba(47, 128, 255, 0.38);
+ transform: translateY(-1px);
}
.btn-secondary {
@apply btn px-4 py-2;
- background-color: rgb(var(--surface-2));
- color: rgb(var(--text-primary));
- border: 1px solid rgb(var(--border-default));
+ background-color: var(--surface);
+ color: var(--text-2);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
}
.btn-secondary:hover:not(:disabled) {
- background-color: rgb(var(--surface-3));
+ background-color: var(--surface-2);
+ color: var(--text);
}
.btn-ghost {
@apply btn px-3 py-2;
background-color: transparent;
- color: rgb(var(--text-secondary));
+ color: var(--muted);
+ border-radius: var(--r);
}
.btn-ghost:hover:not(:disabled) {
- background-color: rgb(var(--surface-2));
- color: rgb(var(--text-primary));
+ background-color: var(--surface);
+ color: var(--text);
}
.btn-danger {
@apply btn px-4 py-2;
- background-color: rgb(var(--semantic-error));
+ background-color: var(--danger);
color: white;
+ border-radius: var(--r);
}
.btn-danger:hover:not(:disabled) {
@@ -346,34 +363,36 @@ body {
----------------------------------------------------------------------------- */
@layer components {
.input {
- @apply w-full rounded-md px-3 py-2 text-sm transition-all duration-150;
- @apply focus:outline-none focus:ring-2 focus:ring-offset-0;
- background-color: rgb(var(--surface-0));
- border: 1px solid rgb(var(--border-default));
- color: rgb(var(--text-primary));
+ @apply w-full text-sm transition-all duration-150;
+ @apply focus:outline-none;
+ background-color: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ color: var(--text);
+ padding: 11px 14px;
}
.input::placeholder {
- color: rgb(var(--text-muted));
+ color: var(--muted);
}
.input:focus {
- border-color: rgb(var(--accent-primary));
- box-shadow: 0 0 0 3px rgb(var(--accent-primary) / 0.1);
+ border-color: var(--primary);
+ box-shadow: 0 0 0 3px rgba(47, 128, 255, 0.12);
}
.input:disabled {
@apply opacity-50 cursor-not-allowed;
- background-color: rgb(var(--surface-1));
+ background-color: var(--surface);
}
.input-error {
- border-color: rgb(var(--semantic-error));
+ border-color: var(--danger);
}
.input-error:focus {
- border-color: rgb(var(--semantic-error));
- box-shadow: 0 0 0 3px rgb(var(--semantic-error) / 0.1);
+ border-color: var(--danger);
+ box-shadow: 0 0 0 3px rgba(229, 72, 77, 0.12);
}
}
@@ -383,8 +402,8 @@ body {
@layer components {
.card {
@apply rounded-lg p-4;
- background-color: rgb(var(--surface-0));
- border: 1px solid rgb(var(--border-default));
+ background-color: var(--surface);
+ border: 1px solid var(--border);
box-shadow: var(--shadow-sm);
}
@@ -398,7 +417,7 @@ body {
}
.card-interactive:hover {
- border-color: rgb(var(--border-strong));
+ border-color: var(--muted);
box-shadow: var(--shadow-md);
}
}
@@ -412,33 +431,33 @@ body {
}
.badge-success {
- background-color: rgb(var(--semantic-success-light));
- color: rgb(var(--semantic-success-dark));
+ background-color: rgba(20, 184, 166, 0.15);
+ color: var(--ms-teal-400);
}
.badge-warning {
- background-color: rgb(var(--semantic-warning-light));
- color: rgb(var(--semantic-warning-dark));
+ background-color: rgba(245, 158, 11, 0.15);
+ color: var(--ms-amber-400);
}
.badge-error {
- background-color: rgb(var(--semantic-error-light));
- color: rgb(var(--semantic-error-dark));
+ background-color: rgba(229, 72, 77, 0.15);
+ color: var(--ms-red-400);
}
.badge-info {
- background-color: rgb(var(--semantic-info-light));
- color: rgb(var(--semantic-info-dark));
+ background-color: rgba(47, 128, 255, 0.15);
+ color: var(--ms-blue-400);
}
.badge-neutral {
- background-color: rgb(var(--surface-2));
- color: rgb(var(--text-secondary));
+ background-color: var(--surface-2);
+ color: var(--text-2);
}
.badge-primary {
- background-color: rgb(var(--accent-primary-light));
- color: rgb(var(--accent-primary));
+ background-color: rgba(47, 128, 255, 0.15);
+ color: var(--primary-l);
}
}
@@ -451,26 +470,29 @@ body {
}
.status-dot-success {
- background-color: rgb(var(--semantic-success));
+ background-color: var(--success);
+ box-shadow: 0 0 5px var(--success);
}
.status-dot-warning {
- background-color: rgb(var(--semantic-warning));
+ background-color: var(--warn);
+ box-shadow: 0 0 5px var(--warn);
}
.status-dot-error {
- background-color: rgb(var(--semantic-error));
+ background-color: var(--danger);
+ box-shadow: 0 0 5px var(--danger);
}
.status-dot-info {
- background-color: rgb(var(--semantic-info));
+ background-color: var(--primary);
+ box-shadow: 0 0 5px var(--primary);
}
.status-dot-neutral {
- background-color: rgb(var(--text-muted));
+ background-color: var(--muted);
}
- /* Pulsing indicator for live/active status */
.status-dot-pulse {
@apply relative;
}
@@ -489,12 +511,12 @@ body {
@layer components {
.kbd {
@apply inline-flex items-center justify-center rounded px-1.5 py-0.5 text-xs font-medium;
- background-color: rgb(var(--surface-2));
- border: 1px solid rgb(var(--border-default));
- color: rgb(var(--text-tertiary));
- font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
+ background-color: var(--surface-2);
+ border: 1px solid var(--border);
+ color: var(--muted);
+ font-family: var(--mono);
min-width: 1.5rem;
- box-shadow: 0 1px 0 rgb(var(--border-strong));
+ box-shadow: 0 1px 0 var(--border);
}
.kbd-group {
@@ -512,13 +534,13 @@ body {
.table-pro thead {
@apply sticky top-0;
- background-color: rgb(var(--surface-1));
- border-bottom: 1px solid rgb(var(--border-default));
+ background-color: var(--surface);
+ border-bottom: 1px solid var(--border);
}
.table-pro th {
@apply px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider;
- color: rgb(var(--text-tertiary));
+ color: var(--muted);
}
.table-pro th.sortable {
@@ -526,16 +548,16 @@ body {
}
.table-pro th.sortable:hover {
- color: rgb(var(--text-primary));
+ color: var(--text);
}
.table-pro tbody tr {
- border-bottom: 1px solid rgb(var(--border-subtle));
+ border-bottom: 1px solid var(--border);
transition: background-color 0.1s ease;
}
.table-pro tbody tr:hover {
- background-color: rgb(var(--surface-1));
+ background-color: var(--surface);
}
.table-pro td {
@@ -555,9 +577,9 @@ body {
@apply animate-pulse rounded;
background: linear-gradient(
90deg,
- rgb(var(--surface-2)) 0%,
- rgb(var(--surface-1)) 50%,
- rgb(var(--surface-2)) 100%
+ var(--surface) 0%,
+ var(--surface-2) 50%,
+ var(--surface) 100%
);
background-size: 200% 100%;
}
@@ -590,15 +612,16 @@ body {
}
.modal-content {
- @apply relative max-h-[90vh] w-full max-w-lg overflow-y-auto rounded-lg;
- background-color: rgb(var(--surface-0));
- border: 1px solid rgb(var(--border-default));
+ @apply relative max-h-[90vh] w-full max-w-lg overflow-y-auto;
+ background-color: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--r-lg);
box-shadow: var(--shadow-lg);
}
.modal-header {
@apply flex items-center justify-between p-4 border-b;
- border-color: rgb(var(--border-default));
+ border-color: var(--border);
}
.modal-body {
@@ -607,7 +630,7 @@ body {
.modal-footer {
@apply flex items-center justify-end gap-3 p-4 border-t;
- border-color: rgb(var(--border-default));
+ border-color: var(--border);
}
}
@@ -617,9 +640,10 @@ body {
@layer components {
.tooltip {
@apply absolute z-50 rounded px-2 py-1 text-xs font-medium;
- background-color: rgb(var(--text-primary));
- color: rgb(var(--color-background));
+ background-color: var(--text);
+ color: var(--bg);
box-shadow: var(--shadow-md);
+ border-radius: var(--r-sm);
}
.tooltip::before {
@@ -630,7 +654,7 @@ body {
.tooltip-top::before {
@apply left-1/2 top-full -translate-x-1/2;
- border-top-color: rgb(var(--text-primary));
+ border-top-color: var(--text);
}
}
@@ -680,12 +704,10 @@ body {
animation: scaleIn 0.15s ease-out;
}
-/* Message animation - subtle for chat */
.message-animate {
animation: slideIn 0.2s ease-out;
}
-/* Menu dropdown animation */
.animate-menu-enter {
animation: scaleIn 0.1s ease-out;
}
@@ -710,13 +732,8 @@ body {
----------------------------------------------------------------------------- */
@media (prefers-contrast: high) {
:root {
- --border-default: 100 116 139;
- --border-strong: 71 85 105;
- }
-
- .dark {
- --border-default: 148 163 184;
- --border-strong: 203 213 225;
+ --border: #4a5a78;
+ --muted: #a0b0cc;
}
}
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index bd9a6c4..90c96bc 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -1,5 +1,6 @@
import type { Metadata } from "next";
import type { ReactNode } from "react";
+import { Outfit, Fira_Code } from "next/font/google";
import { AuthProvider } from "@/lib/auth/auth-context";
import { ErrorBoundary } from "@/components/error-boundary";
import { ThemeProvider } from "@/providers/ThemeProvider";
@@ -12,6 +13,18 @@ export const metadata: Metadata = {
description: "Mosaic Stack Web Application",
};
+const outfit = Outfit({
+ subsets: ["latin"],
+ variable: "--font-outfit",
+ display: "swap",
+});
+
+const firaCode = Fira_Code({
+ subsets: ["latin"],
+ variable: "--font-fira-code",
+ display: "swap",
+});
+
/**
* Runtime env vars injected as a synchronous script so client-side modules
* can read them before React hydration. This allows Docker env vars to
@@ -34,7 +47,7 @@ function runtimeEnvScript(): string {
export default function RootLayout({ children }: { children: ReactNode }): React.JSX.Element {
return (
-
+
diff --git a/apps/web/src/components/layout/AppHeader.tsx b/apps/web/src/components/layout/AppHeader.tsx
new file mode 100644
index 0000000..6164bca
--- /dev/null
+++ b/apps/web/src/components/layout/AppHeader.tsx
@@ -0,0 +1,114 @@
+"use client";
+
+import Link from "next/link";
+import { useAuth } from "@/lib/auth/auth-context";
+import { LogoutButton } from "@/components/auth/LogoutButton";
+import { ThemeToggle } from "./ThemeToggle";
+
+/**
+ * Full-width application header (topbar).
+ * Logo/brand MUST live here — not in the sidebar — per MS15 design spec.
+ * Spans grid-column 1 / -1 in the app shell grid layout.
+ */
+export function AppHeader(): React.JSX.Element {
+ const { user } = useAuth();
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/components/layout/AppSidebar.tsx b/apps/web/src/components/layout/AppSidebar.tsx
new file mode 100644
index 0000000..6f27c8e
--- /dev/null
+++ b/apps/web/src/components/layout/AppSidebar.tsx
@@ -0,0 +1,160 @@
+"use client";
+
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+
+interface NavItem {
+ href: string;
+ label: string;
+ icon: React.JSX.Element;
+}
+
+const navItems: NavItem[] = [
+ {
+ href: "/",
+ label: "Dashboard",
+ icon: (
+
+ ),
+ },
+ {
+ href: "/tasks",
+ label: "Tasks",
+ icon: (
+
+ ),
+ },
+ {
+ href: "/calendar",
+ label: "Calendar",
+ icon: (
+
+ ),
+ },
+ {
+ href: "/knowledge",
+ label: "Knowledge",
+ icon: (
+
+ ),
+ },
+ {
+ href: "/usage",
+ label: "Usage",
+ icon: (
+
+ ),
+ },
+];
+
+/**
+ * Application sidebar — navigation only, no brand/logo.
+ * Logo lives in AppHeader per MS15 design spec.
+ */
+export function AppSidebar(): React.JSX.Element {
+ const pathname = usePathname();
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/components/layout/ThemeToggle.tsx b/apps/web/src/components/layout/ThemeToggle.tsx
index a61f148..51281e0 100644
--- a/apps/web/src/components/layout/ThemeToggle.tsx
+++ b/apps/web/src/components/layout/ThemeToggle.tsx
@@ -20,7 +20,7 @@ export function ThemeToggle({ className = "" }: ThemeToggleProps): React.JSX.Ele
// Sun icon for dark mode (click to switch to light)