From 908c9ebc9a620d3b056070ce11306d418c9cb53b Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sun, 5 Jul 2026 16:38:41 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Phase=201.3:=20Theme=20&=20Global?= =?UTF-8?q?=20Styles=20Integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MudBlazor Theme Configuration ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✅ AppTheme.cs (Client/Theme/) - Light theme: Professional Material Design colors - Dark theme: Modern dark mode palette - Complete typography system (H1-H6, Body1-2, Button, Caption) - Layout properties (Border radius, Drawer width, AppBar height) - Color variables: Primary, Secondary, Success, Warning, Error, Info ✅ Global Styles (app.css) - Base reset and typography - Utility classes (spacing, flex, gaps, text colors) - MudBlazor component overrides - Skeleton loading animation - Form, table, and button styling - Responsive design (mobile-first) - Accessibility support (prefers-reduced-motion) - Print styles - Smooth transitions and animations ✅ App.razor Integration - MudThemeProvider with theme binding - Default: Light theme on initialization - Ready for theme switching Features: - Consistent Material Design - Custom scrollbar styling - Card elevation effects - Navigation link styling - Input field styling - Table styling with hover effects - Responsive breakpoints - Animation utilities (fade-in, slide-in) Next: Phase 2 - Admin UI Development Co-Authored-By: Claude Sonnet 5 --- .../QuantEngine.Web/Client/Theme/AppTheme.cs | 178 +++++++++++ src/dotnet/QuantEngine.Web/Client/app.css | 297 ++++++++++++++++++ .../QuantEngine.Web/Components/App.razor | 13 +- 3 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 src/dotnet/QuantEngine.Web/Client/Theme/AppTheme.cs create mode 100644 src/dotnet/QuantEngine.Web/Client/app.css diff --git a/src/dotnet/QuantEngine.Web/Client/Theme/AppTheme.cs b/src/dotnet/QuantEngine.Web/Client/Theme/AppTheme.cs new file mode 100644 index 0000000..a2b0e55 --- /dev/null +++ b/src/dotnet/QuantEngine.Web/Client/Theme/AppTheme.cs @@ -0,0 +1,178 @@ +using MudBlazor; + +namespace QuantEngine.Web.Client.Theme; + +public static class AppTheme +{ + public static MudTheme LightTheme => new() + { + Palette = new PaletteLight + { + Primary = "#3f51b5", + Secondary = "#f50057", + Success = "#4caf50", + Warning = "#ff9800", + Error = "#f44336", + Info = "#2196f3", + Dark = "#121212", + Background = "#fafafa", + Surface = "#ffffff", + TextPrimary = "#212121", + TextSecondary = "rgba(0,0,0,0.6)", + DrawerBackground = "#ffffff", + DrawerText = "#212121", + AppbarBackground = "#3f51b5", + AppbarText = "#ffffff", + ActionDefault = "#c0c0c0", + ActionDisabled = "#f5f5f5", + ActionDisabledBackground = "rgba(0,0,0,0.12)", + Divider = "#e0e0e0", + DividerLight = "#f5f5f5", + TableLines = "#e0e0e0", + LinesDefault = "#e0e0e0", + LinesInputBorder = "#bdbdbd", + TextDisabled = "rgba(0,0,0,0.38)", + BorderRadius = "4px", + OverlayShadow = "0 5px 5px -3px rgba(0,0,0,0.2), 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12)", + Elevation = new Dictionary + { + { 0, "none" }, + { 1, "0 2px 1px -1px rgba(0,0,0,0.2),0 1px 1px 0 rgba(0,0,0,0.14),0 1px 3px 0 rgba(0,0,0,0.12)" }, + { 2, "0 3px 1px -2px rgba(0,0,0,0.2),0 2px 2px 0 rgba(0,0,0,0.14),0 1px 5px 0 rgba(0,0,0,0.12)" }, + { 3, "0 3px 3px -2px rgba(0,0,0,0.2),0 3px 4px 0 rgba(0,0,0,0.14),0 1px 8px 0 rgba(0,0,0,0.12)" }, + { 4, "0 2px 4px -1px rgba(0,0,0,0.2),0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12)" }, + } + }, + Typography = new Typography + { + Default = new DefaultTypography + { + FontFamily = "Roboto, sans-serif", + FontSize = "1rem", + FontWeight = 400, + LineHeight = 1.5, + LetterSpacing = "0.5px" + }, + H1 = new H1Typography + { + FontSize = "6rem", + FontWeight = 300, + LineHeight = 1.167, + LetterSpacing = "-0.015625em" + }, + H2 = new H2Typography + { + FontSize = "3.75rem", + FontWeight = 300, + LineHeight = 1.2, + LetterSpacing = "-0.0083333333em" + }, + H3 = new H3Typography + { + FontSize = "3rem", + FontWeight = 400, + LineHeight = 1.167, + LetterSpacing = "0em" + }, + H4 = new H4Typography + { + FontSize = "2.125rem", + FontWeight = 500, + LineHeight = 1.235, + LetterSpacing = "0.0125em" + }, + H5 = new H5Typography + { + FontSize = "1.5rem", + FontWeight = 500, + LineHeight = 1.334, + LetterSpacing = "0em" + }, + H6 = new H6Typography + { + FontSize = "1.25rem", + FontWeight = 600, + LineHeight = 1.6, + LetterSpacing = "0.0125em" + }, + Body1 = new Body1Typography + { + FontSize = "1rem", + FontWeight = 500, + LineHeight = 1.5, + LetterSpacing = "0.03125em" + }, + Body2 = new Body2Typography + { + FontSize = "0.875rem", + FontWeight = 400, + LineHeight = 1.43, + LetterSpacing = "0.0178571429em" + }, + Button = new ButtonTypography + { + FontSize = "0.875rem", + FontWeight = 600, + LineHeight = 1.75, + LetterSpacing = "0.0892857143em" + }, + Caption = new CaptionTypography + { + FontSize = "0.75rem", + FontWeight = 400, + LineHeight = 1.66, + LetterSpacing = "0.0333333333em" + } + }, + LayoutProperties = new LayoutProperties + { + DefaultBorderRadius = "4px", + DrawerWidthLeft = "256px", + DrawerWidthRight = "256px", + AppbarHeight = "64px", + } + }; + + public static MudTheme DarkTheme => new() + { + Palette = new PaletteDark + { + Primary = "#bb86fc", + Secondary = "#03dac6", + Success = "#4caf50", + Warning = "#ff9800", + Error = "#cf6679", + Info = "#2196f3", + Dark = "#121212", + Background = "#121212", + Surface = "#1e1e1e", + TextPrimary = "#ffffff", + TextSecondary = "rgba(255,255,255,0.7)", + DrawerBackground = "#1e1e1e", + DrawerText = "#ffffff", + AppbarBackground = "#1f1f1f", + AppbarText = "#ffffff", + ActionDefault = "#3f3f3f", + ActionDisabled = "#1e1e1e", + ActionDisabledBackground = "rgba(255,255,255,0.12)", + Divider = "#37474f", + DividerLight = "#2c3e50", + TableLines = "#37474f", + LinesDefault = "#37474f", + LinesInputBorder = "#555555", + TextDisabled = "rgba(255,255,255,0.38)", + BorderRadius = "4px", + OverlayShadow = "0 5px 5px -3px rgba(0,0,0,0.2), 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12)", + Elevation = new Dictionary + { + { 0, "none" }, + { 1, "0 2px 1px -1px rgba(0,0,0,0.2),0 1px 1px 0 rgba(0,0,0,0.14),0 1px 3px 0 rgba(0,0,0,0.12)" }, + { 2, "0 3px 1px -2px rgba(0,0,0,0.2),0 2px 2px 0 rgba(0,0,0,0.14),0 1px 5px 0 rgba(0,0,0,0.12)" }, + { 3, "0 3px 3px -2px rgba(0,0,0,0.2),0 3px 4px 0 rgba(0,0,0,0.14),0 1px 8px 0 rgba(0,0,0,0.12)" }, + { 4, "0 2px 4px -1px rgba(0,0,0,0.2),0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12)" }, + } + }, + Typography = LightTheme.Typography, + LayoutProperties = LightTheme.LayoutProperties + }; +} diff --git a/src/dotnet/QuantEngine.Web/Client/app.css b/src/dotnet/QuantEngine.Web/Client/app.css new file mode 100644 index 0000000..a743fcc --- /dev/null +++ b/src/dotnet/QuantEngine.Web/Client/app.css @@ -0,0 +1,297 @@ +/* QuantEngine Global Styles */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + height: 100%; +} + +body { + font-family: 'Roboto', sans-serif; + font-size: 14px; + font-weight: 400; + line-height: 1.5; + color: var(--mud-palette-text-primary, #212121); + background-color: var(--mud-palette-background, #fafafa); + transition: background-color 0.3s ease, color 0.3s ease; +} + +#app { + display: flex; + flex-direction: column; + height: 100%; +} + +/* Scrollbar Styling */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--mud-palette-surface, #ffffff); +} + +::-webkit-scrollbar-thumb { + background: var(--mud-palette-action-default, #c0c0c0); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--mud-palette-primary, #3f51b5); +} + +/* Text Utilities */ +.text-primary { + color: var(--mud-palette-primary, #3f51b5); +} + +.text-secondary { + color: var(--mud-palette-secondary, #f50057); +} + +.text-success { + color: var(--mud-palette-success, #4caf50); +} + +.text-warning { + color: var(--mud-palette-warning, #ff9800); +} + +.text-error { + color: var(--mud-palette-error, #f44336); +} + +.text-muted { + color: var(--mud-palette-text-secondary, rgba(0,0,0,0.6)); +} + +/* Spacing Utilities */ +.mt-1 { margin-top: 0.25rem; } +.mt-2 { margin-top: 0.5rem; } +.mt-3 { margin-top: 1rem; } +.mt-4 { margin-top: 1.5rem; } +.mt-5 { margin-top: 3rem; } + +.mb-1 { margin-bottom: 0.25rem; } +.mb-2 { margin-bottom: 0.5rem; } +.mb-3 { margin-bottom: 1rem; } +.mb-4 { margin-bottom: 1.5rem; } +.mb-5 { margin-bottom: 3rem; } + +.mx-auto { margin-left: auto; margin-right: auto; } +.my-auto { margin-top: auto; margin-bottom: auto; } + +.px-2 { padding-left: 0.5rem; padding-right: 0.5rem; } +.px-4 { padding-left: 1rem; padding-right: 1rem; } +.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; } +.py-4 { padding-top: 1rem; padding-bottom: 1rem; } + +/* Flex Utilities */ +.d-flex { + display: flex; +} + +.flex-column { + flex-direction: column; +} + +.align-items-center { + align-items: center; +} + +.justify-content-center { + justify-content: center; +} + +.justify-content-between { + justify-content: space-between; +} + +/* Gap Utilities */ +.gap-1 { gap: 0.25rem; } +.gap-2 { gap: 0.5rem; } +.gap-3 { gap: 1rem; } +.gap-4 { gap: 1.5rem; } + +/* Loading Skeleton */ +.skeleton { + background: linear-gradient( + 90deg, + var(--mud-palette-surface, #fff) 0%, + var(--mud-palette-divider, #e0e0e0) 50%, + var(--mud-palette-surface, #fff) 100% + ); + background-size: 200% 100%; + animation: loading 1.5s infinite; +} + +@keyframes loading { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +/* MudBlazor Overrides */ +.mud-appbar { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.mud-drawer { + border-right: 1px solid var(--mud-palette-divider, #e0e0e0); +} + +.mud-drawer-content { + padding: 1rem; +} + +.mud-nav-link { + border-radius: 4px; + margin-bottom: 0.25rem; + transition: all 0.2s ease; +} + +.mud-nav-link:hover { + background-color: var(--mud-palette-action-default-hover, rgba(0, 0, 0, 0.04)); +} + +.mud-nav-link.mud-ripple-nav-link-active { + background-color: var(--mud-palette-primary-lighten, rgba(63, 81, 181, 0.1)); + color: var(--mud-palette-primary, #3f51b5); + font-weight: 600; +} + +.mud-card { + border: 1px solid var(--mud-palette-divider, #e0e0e0); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + transition: box-shadow 0.2s ease, transform 0.2s ease; +} + +.mud-card:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); +} + +.mud-button { + text-transform: none; + font-weight: 500; + padding: 0.5rem 1rem; + border-radius: 4px; +} + +.mud-button-root:disabled { + opacity: 0.6; +} + +/* Forms */ +.mud-input-control { + margin-bottom: 1rem; +} + +.mud-input-label { + font-weight: 500; +} + +.mud-input { + border-radius: 4px; +} + +.mud-input.mud-input-text { + background-color: var(--mud-palette-surface, #ffffff); +} + +/* Tables */ +.mud-table { + background-color: var(--mud-palette-surface, #ffffff); +} + +.mud-table-head { + background-color: var(--mud-palette-background, #fafafa); +} + +.mud-table-row:hover { + background-color: var(--mud-palette-action-default-hover, rgba(0, 0, 0, 0.04)); +} + +.mud-table-cell { + padding: 1rem; + border-color: var(--mud-palette-divider, #e0e0e0); +} + +/* Responsive */ +@media (max-width: 600px) { + body { + font-size: 13px; + } + + .mud-drawer { + width: 100% !important; + max-width: 90% !important; + } + + .mud-appbar { + height: 56px; + } + + .mud-table-cell { + padding: 0.75rem 0.5rem; + } +} + +/* Animation Classes */ +.fade-in { + animation: fadeIn 0.3s ease-in; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.slide-in { + animation: slideIn 0.3s ease-in; +} + +@keyframes slideIn { + from { + transform: translateY(10px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* Accessibility */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +/* Print Styles */ +@media print { + .mud-appbar, + .mud-drawer, + .no-print { + display: none !important; + } + + body { + background: white; + } +} diff --git a/src/dotnet/QuantEngine.Web/Components/App.razor b/src/dotnet/QuantEngine.Web/Components/App.razor index c2c8ceb..e5e8635 100644 --- a/src/dotnet/QuantEngine.Web/Components/App.razor +++ b/src/dotnet/QuantEngine.Web/Components/App.razor @@ -17,7 +17,7 @@ - + @@ -27,4 +27,15 @@ +@code { + private MudTheme _theme = AppTheme.LightTheme; + + protected override void OnInitialized() + { + _theme = AppTheme.LightTheme; + } +} + +@using QuantEngine.Web.Client.Theme +