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 ( -
-
-
- ); + return ; } if (!isAuthenticated) { @@ -35,19 +33,31 @@ export default function AuthenticatedLayout({ } 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 ( - +