From 01bc8e37b9a72b5d62400792825e0ba2326ad94e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Jasi=C5=84ski?= Date: Tue, 2 Jun 2026 19:53:50 +0200 Subject: [PATCH] feat: add CalloutBox component, update ReaderLayout with theme support, and refactor UI styles --- .../Components/Molecules/CalloutBox.razor | 47 ++++++++ .../Components/Molecules/CalloutBox.razor.css | 94 +++++++++++++++ .../Organisms/BookIngestionModal.razor | 2 +- .../Organisms/BookIngestionModal.razor.css | 92 ++++++-------- .../Organisms/ReaderCanvas.razor.css | 47 +++++++- .../Organisms/ReaderFooter.razor.css | 114 ++++++++++++------ .../Layout/ReaderLayout.razor | 7 +- .../Layout/ReaderLayout.razor.css | 67 +++++++++- src/NexusReader.UI.Shared/wwwroot/app.css | 14 ++- 9 files changed, 385 insertions(+), 99 deletions(-) create mode 100644 src/NexusReader.UI.Shared/Components/Molecules/CalloutBox.razor create mode 100644 src/NexusReader.UI.Shared/Components/Molecules/CalloutBox.razor.css diff --git a/src/NexusReader.UI.Shared/Components/Molecules/CalloutBox.razor b/src/NexusReader.UI.Shared/Components/Molecules/CalloutBox.razor new file mode 100644 index 0000000..81492eb --- /dev/null +++ b/src/NexusReader.UI.Shared/Components/Molecules/CalloutBox.razor @@ -0,0 +1,47 @@ +@namespace NexusReader.UI.Shared.Components.Molecules + +
+ @if (!string.IsNullOrEmpty(Title)) + { +
+ @if (Type == CalloutType.Warning || Type == CalloutType.Error) + { + + } + else if (Type == CalloutType.Success) + { + + } + else + { + + } + @Title +
+ } +
+ @ChildContent +
+
+ +@code { + public enum CalloutType + { + Info, + Warning, + Success, + Error + } + + [Parameter] + public CalloutType Type { get; set; } = CalloutType.Info; + + [Parameter] + public string? Title { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [Parameter] + public string Class { get; set; } = string.Empty; +} diff --git a/src/NexusReader.UI.Shared/Components/Molecules/CalloutBox.razor.css b/src/NexusReader.UI.Shared/Components/Molecules/CalloutBox.razor.css new file mode 100644 index 0000000..79c604d --- /dev/null +++ b/src/NexusReader.UI.Shared/Components/Molecules/CalloutBox.razor.css @@ -0,0 +1,94 @@ +.nexus-callout-box { + padding: 1.25rem 1.5rem; + margin: 1.5rem 0; + border-radius: 0 8px 8px 0; + font-family: var(--nexus-font-sans, sans-serif); + font-size: 0.95rem; + line-height: 1.5; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transition: all 0.2s ease; + border: 1px solid transparent; + border-left-width: 4px; +} + +/* Light / Dark default support via variables or custom colors */ +.nexus-callout-box { + background-color: rgba(255, 255, 255, 0.02); + color: #e2e8f0; +} + +/* Info style */ +.nexus-callout-info { + border-left-color: var(--nexus-neon, #00ff99); +} + +/* Warning style */ +.nexus-callout-warning { + border-left-color: #eab308; /* warning yellow */ + background-color: rgba(234, 179, 8, 0.03); +} + +/* Success style */ +.nexus-callout-success { + border-left-color: #10b981; /* success green */ + background-color: rgba(16, 185, 129, 0.03); +} + +/* Error style */ +.nexus-callout-error { + border-left-color: #f43f5e; /* error red */ + background-color: rgba(244, 63, 94, 0.03); +} + +.nexus-callout-header { + display: flex; + align-items: center; + gap: 0.5rem; + font-weight: 600; + margin-bottom: 0.5rem; + text-transform: uppercase; + font-size: 0.8rem; + letter-spacing: 0.05em; +} + +.nexus-callout-info .nexus-callout-header { + color: var(--nexus-neon, #00ff99); +} + +.nexus-callout-warning .nexus-callout-header { + color: #eab308; +} + +.nexus-callout-success .nexus-callout-header { + color: #10b981; +} + +.nexus-callout-error .nexus-callout-header { + color: #f43f5e; +} + +.nexus-callout-icon { + flex-shrink: 0; +} + +.nexus-callout-body { + opacity: 0.9; +} + +/* Light theme support */ +:global(.theme-light) .nexus-callout-box { + background-color: rgba(0, 0, 0, 0.02); + color: #333333; +} + +:global(.theme-light) .nexus-callout-warning { + background-color: rgba(234, 179, 8, 0.05); +} + +:global(.theme-light) .nexus-callout-success { + background-color: rgba(16, 185, 129, 0.05); +} + +:global(.theme-light) .nexus-callout-error { + background-color: rgba(244, 63, 94, 0.05); +} diff --git a/src/NexusReader.UI.Shared/Components/Organisms/BookIngestionModal.razor b/src/NexusReader.UI.Shared/Components/Organisms/BookIngestionModal.razor index d2abe30..d749f75 100644 --- a/src/NexusReader.UI.Shared/Components/Organisms/BookIngestionModal.razor +++ b/src/NexusReader.UI.Shared/Components/Organisms/BookIngestionModal.razor @@ -41,7 +41,7 @@ -
+
@if (Metadata != null) {
diff --git a/src/NexusReader.UI.Shared/Components/Organisms/BookIngestionModal.razor.css b/src/NexusReader.UI.Shared/Components/Organisms/BookIngestionModal.razor.css index d92d068..0eebfbf 100644 --- a/src/NexusReader.UI.Shared/Components/Organisms/BookIngestionModal.razor.css +++ b/src/NexusReader.UI.Shared/Components/Organisms/BookIngestionModal.razor.css @@ -196,52 +196,37 @@ margin-top: 1rem; } -.btn { - font-family: var(--nexus-font-sans); - font-weight: 600; - padding: 0.75rem 1.5rem; - border-radius: 8px; - border: 1px solid transparent; - cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - font-size: 0.85rem; - letter-spacing: 0.5px; - display: inline-flex; - align-items: center; - justify-content: center; - text-transform: uppercase; +::deep .nexus-btn.btn-primary { + background: var(--nexus-neon, #00ffaa) !important; + color: #050505 !important; + border-color: transparent !important; + box-shadow: 0 4px 12px rgba(var(--nexus-accent-rgb, 0, 255, 170), 0.2) !important; } -.btn-primary { - background: var(--nexus-neon, #00ffaa); - color: #050505; - box-shadow: 0 4px 12px rgba(var(--nexus-accent-rgb, 0, 255, 170), 0.2); +::deep .nexus-btn.btn-primary:hover:not(:disabled) { + background: #00e699 !important; + transform: translateY(-2px) !important; + box-shadow: 0 6px 20px rgba(var(--nexus-accent-rgb, 0, 255, 170), 0.4) !important; } -.btn-primary:hover { - background: #00e699; - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(var(--nexus-accent-rgb, 0, 255, 170), 0.4); +::deep .nexus-btn.btn-primary:active:not(:disabled) { + transform: translateY(0) !important; } -.btn-primary:active { - transform: translateY(0); +::deep .nexus-btn.btn-secondary { + background: rgba(255, 255, 255, 0.03) !important; + color: var(--nexus-text) !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; } -.btn-secondary { - background: rgba(255, 255, 255, 0.03); - color: var(--nexus-text); - border: 1px solid rgba(255, 255, 255, 0.1); +::deep .nexus-btn.btn-secondary:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.08) !important; + border-color: rgba(255, 255, 255, 0.3) !important; + transform: translateY(-2px) !important; } -.btn-secondary:hover { - background: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.3); - transform: translateY(-2px); -} - -.btn-secondary:active { - transform: translateY(0); +::deep .nexus-btn.btn-secondary:active:not(:disabled) { + transform: translateY(0) !important; } /* Verification State */ @@ -357,27 +342,30 @@ to { transform: scale(1.2); opacity: 0.8; } } -.btn:disabled { - opacity: 0.5; - cursor: not-allowed; - filter: grayscale(1); +::deep .nexus-btn:disabled:not(.btn-loading) { + opacity: 0.4 !important; + cursor: not-allowed !important; + filter: grayscale(1) !important; } -.btn-loading { - position: relative; +::deep .nexus-btn.btn-loading { + position: relative !important; color: transparent !important; + opacity: 1 !important; + cursor: wait !important; + filter: none !important; } -.btn-loading::after { - content: ""; - position: absolute; - width: 20px; - height: 20px; - border: 2px solid rgba(255, 255, 255, 0.2); - border-top-color: var(--nexus-neon, #00ffaa); - border-radius: 50%; - animation: spin 0.8s linear infinite; - filter: drop-shadow(0 0 4px var(--nexus-neon, #00ffaa)); +::deep .nexus-btn.btn-loading::after { + content: "" !important; + position: absolute !important; + width: 20px !important; + height: 20px !important; + border: 2px solid rgba(255, 255, 255, 0.2) !important; + border-top-color: var(--nexus-neon, #00ffaa) !important; + border-radius: 50% !important; + animation: spin 0.8s linear infinite !important; + filter: drop-shadow(0 0 4px var(--nexus-neon, #00ffaa)) !important; } /* Indexing State */ diff --git a/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor.css b/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor.css index fac7f04..cfe152f 100644 --- a/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor.css +++ b/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor.css @@ -30,18 +30,37 @@ background-color: rgba(0, 255, 153, 0.5); } +.reader-canvas.theme-dark { + background-color: #121214; +} + .reader-canvas.theme-light { background-color: #F9F9F9; /* Paper-white requirement */ } .reader-flow-container { - max-width: 800px; - margin: 0 auto; + max-width: 680px; + margin: 2rem auto; display: flex; flex-direction: column; gap: 1.5rem; position: relative; - padding: 0 1.5rem 15rem 1.5rem; /* Large padding-bottom for reachability */ + padding: 3rem 4rem 15rem 4rem; /* Large padding-bottom for reachability, plus comfortable side margins */ + border-radius: 12px; + box-sizing: border-box; + transition: background-color 0.3s, box-shadow 0.3s, border-color 0.3s; +} + +.theme-dark .reader-flow-container { + background-color: #1a1a1e; + border: 1px solid rgba(255, 255, 255, 0.03); + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2); +} + +.theme-light .reader-flow-container { + background-color: #ffffff; + border: 1px solid rgba(0, 0, 0, 0.04); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); } .block-wrapper { @@ -57,13 +76,33 @@ line-height: 1.65 !important; letter-spacing: -0.01em !important; font-size: 1.15rem; - font-weight: 300; + font-weight: 400; + text-align: left !important; + color: #e4e4e7; /* Off-white with light gray tint */ } .theme-light ::deep .nexus-ebook { color: #1a1a1a; } +/* Callout Box styling for legacy blockquote segments */ +::deep .nexus-ebook blockquote { + background-color: rgba(255, 255, 255, 0.02); + border-left: 4px solid var(--nexus-neon); + padding: 1.25rem 1.5rem; + margin: 1.5rem 0; + border-radius: 0 8px 8px 0; + font-size: 1.05rem; + color: #e2e8f0; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.theme-light ::deep .nexus-ebook blockquote { + background-color: rgba(0, 0, 0, 0.02); + color: #333333; +} + + /* Technical Code Block Container */ ::deep .nexus-ebook pre { background-color: #2d2d2d; /* Dark theme for code for better contrast */ diff --git a/src/NexusReader.UI.Shared/Components/Organisms/ReaderFooter.razor.css b/src/NexusReader.UI.Shared/Components/Organisms/ReaderFooter.razor.css index edfa764..a97f723 100644 --- a/src/NexusReader.UI.Shared/Components/Organisms/ReaderFooter.razor.css +++ b/src/NexusReader.UI.Shared/Components/Organisms/ReaderFooter.razor.css @@ -1,35 +1,50 @@ .reader-footer { - position: relative; - height: 50px; - background: #F9F9F9; - border-top: 1px solid rgba(0, 0, 0, 0.08); + position: absolute; + bottom: 24px; + left: 50%; + transform: translateX(-50%); + width: min(600px, 90%); + height: 54px; + background: rgba(24, 24, 27, 0.6); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 9999px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); display: flex; align-items: center; padding: 0 1.5rem; - z-index: 10; - flex-shrink: 0; + z-index: 100; + transition: background 0.3s, border-color 0.3s, box-shadow 0.3s; +} + +/* Light mode override for .reader-footer */ +:global(.theme-light) .reader-footer { + background: rgba(255, 255, 255, 0.7); + border: 1px solid rgba(0, 0, 0, 0.06); + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05); } .footer-content { display: flex; align-items: center; width: 100%; - gap: 1.5rem; + gap: 1rem; + justify-content: space-between; } .navigation-controls { - display: grid; - grid-template-columns: 32px 1fr 32px; + display: flex; align-items: center; gap: 0.75rem; - width: 260px; - flex-shrink: 0; + flex: 1; + min-width: 0; } .nav-btn { - background: white; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 6px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 50%; width: 32px; height: 32px; display: flex; @@ -37,28 +52,43 @@ justify-content: center; cursor: pointer; transition: all 0.2s ease; - color: #333; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + color: #e4e4e7; } .nav-btn:hover:not(:disabled) { - background: #f0f0f0; - transform: translateY(-1px); + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.2); + transform: scale(1.05); } .nav-btn:disabled { - opacity: 0.3; + opacity: 0.25; cursor: not-allowed; } +:global(.theme-light) .nav-btn { + background: rgba(0, 0, 0, 0.02); + border-color: rgba(0, 0, 0, 0.08); + color: #333333; +} + +:global(.theme-light) .nav-btn:hover:not(:disabled) { + background: rgba(0, 0, 0, 0.05); + border-color: rgba(0, 0, 0, 0.15); +} + .chapter-info { display: flex; flex-direction: column; - align-items: center; + align-items: flex-start; justify-content: center; min-width: 0; - overflow: hidden; - color: #333; + flex: 1; + color: #e4e4e7; +} + +:global(.theme-light) .chapter-info { + color: #333333; } .chapter-title { @@ -68,42 +98,58 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - text-align: center; line-height: 1.2; } .chapter-count { opacity: 0.5; - font-size: 0.75rem; + font-size: 0.7rem; } .progress-container { - flex: 1; - height: 6px; - background: rgba(0, 0, 0, 0.05); - border-radius: 3px; + width: 80px; + height: 4px; + background: rgba(255, 255, 255, 0.08); + border-radius: 2px; overflow: hidden; - margin: 0 1rem; + margin: 0 0.25rem; + flex-shrink: 0; +} + +:global(.theme-light) .progress-container { + background: rgba(0, 0, 0, 0.08); } .progress-bar { height: 100%; - background: #2ECC71; - border-radius: 3px; + background: var(--nexus-neon, #00ff99); + border-radius: 2px; transition: width 0.3s ease; } .meta-info { display: flex; align-items: center; - gap: 1rem; - font-size: 0.75rem; - color: #888; + gap: 0.75rem; + font-size: 0.7rem; + color: #a1a1aa; flex-shrink: 0; + font-family: monospace; +} + +:global(.theme-light) .meta-info { + color: #71717a; } .battery { display: flex; align-items: center; gap: 0.3rem; +} + +/* RWD constraint: floating toolbar visible ONLY on desktop (min-width: 1024px) */ +@media (max-width: 1023px) { + .reader-footer { + display: none !important; + } } \ No newline at end of file diff --git a/src/NexusReader.UI.Shared/Layout/ReaderLayout.razor b/src/NexusReader.UI.Shared/Layout/ReaderLayout.razor index f802a2b..ef16616 100644 --- a/src/NexusReader.UI.Shared/Layout/ReaderLayout.razor +++ b/src/NexusReader.UI.Shared/Layout/ReaderLayout.razor @@ -16,10 +16,11 @@ @inject IIdentityService IdentityService @inject NavigationManager NavigationManager @inject Microsoft.Extensions.Logging.ILogger Logger +@inject IThemeService ThemeService @implements IAsyncDisposable -
+
@Body @@ -291,6 +292,7 @@ InteractionService.OnAssistantRequested += HandleAssistantRequestedAsync; InteractionService.OnScrollPercentChanged += HandleScrollPercentChanged; GraphService.OnGraphUpdated += HandleGraphUpdatedAsync; + ThemeService.OnThemeChanged += HandleThemeChangedAsync; var context = PlatformService.GetDeviceContext(); if (context.IsSuccess) @@ -305,6 +307,8 @@ } } + private async Task HandleThemeChangedAsync() => await InvokeAsync(StateHasChanged); + private void SetActiveTab(SidebarTab tab) { _activeTab = tab; @@ -445,6 +449,7 @@ InteractionService.OnAssistantRequested -= HandleAssistantRequestedAsync; InteractionService.OnScrollPercentChanged -= HandleScrollPercentChanged; GraphService.OnGraphUpdated -= HandleGraphUpdatedAsync; + ThemeService.OnThemeChanged -= HandleThemeChangedAsync; try { diff --git a/src/NexusReader.UI.Shared/Layout/ReaderLayout.razor.css b/src/NexusReader.UI.Shared/Layout/ReaderLayout.razor.css index 7b633c6..cf847ae 100644 --- a/src/NexusReader.UI.Shared/Layout/ReaderLayout.razor.css +++ b/src/NexusReader.UI.Shared/Layout/ReaderLayout.razor.css @@ -4,18 +4,19 @@ width: 100vw; height: 100vh; overflow: hidden; - background: #121212; + background: var(--nexus-bg); } .reader-pane { - background: #F9F9F9; + background: var(--nexus-bg); position: relative; overflow: hidden; display: flex; flex-direction: column; z-index: 5; height: 100vh; + transition: background 0.2s ease, color 0.2s ease; } main { @@ -31,11 +32,12 @@ main { grid-template-columns: 50px 1fr; width: 100%; /* controlled by grid */ height: 100%; - background: #0d0d0d; + background: var(--nexus-card); box-shadow: -10px 0 30px rgba(0, 0, 0, 0.3); - border-left: 1px solid rgba(255, 255, 255, 0.1); + border-left: 1px solid rgba(255, 255, 255, 0.05); overflow: hidden; z-index: 10; + transition: background 0.2s ease, border-color 0.2s ease; } .resizer { @@ -94,7 +96,7 @@ main { border-bottom: 1px solid rgba(255, 255, 255, 0.05); font-family: var(--nexus-font-sans); font-size: 0.9rem; - color: #fff; + color: var(--nexus-text); flex-shrink: 0; } @@ -648,3 +650,58 @@ main { } /* Obsolescence managed: consolidated mobile toolbar and sheet styled inside respective components */ + +/* Theme-specific Overrides for Light Mode */ +.app-container.theme-light .intelligence-sidebar { + background: #ffffff; + border-left: 1px solid rgba(0, 0, 0, 0.08); +} + +.app-container.theme-light .intelligence-header { + background: rgba(0, 0, 0, 0.02); + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + color: #121212; +} + +.app-container.theme-light .close-btn { + color: #666; +} + +.app-container.theme-light .contextual-intelligence-panel { + background: rgba(0, 0, 0, 0.02); + border-top: 1px solid rgba(0, 0, 0, 0.05); +} + +.app-container.theme-light .panel-header { + background: rgba(0, 0, 0, 0.01); + border-bottom: 1px solid rgba(0, 0, 0, 0.05); +} + +.app-container.theme-light .panel-title { + color: rgba(0, 0, 0, 0.6); +} + +.app-container.theme-light .no-node-selected { + color: rgba(0, 0, 0, 0.5); +} + +.app-container.theme-light .node-details .section-title { + color: #121212; +} + +.app-container.theme-light .node-description { + color: #333333; +} + +.app-container.theme-light .node-summary { + color: #333333; +} + +.app-container.theme-light .key-term-item { + color: #333333; +} + +.app-container.theme-light .mobile-insight-body { + background: #f9f9f9; +} + diff --git a/src/NexusReader.UI.Shared/wwwroot/app.css b/src/NexusReader.UI.Shared/wwwroot/app.css index 2238d4d..b4b05e2 100644 --- a/src/NexusReader.UI.Shared/wwwroot/app.css +++ b/src/NexusReader.UI.Shared/wwwroot/app.css @@ -3,12 +3,22 @@ :root { --nexus-neon: #00ff99; --nexus-neon-glow: rgba(0, 255, 153, 0.3); - --nexus-bg: #121212; - --nexus-card: #1a1a1a; + --nexus-bg: #121214; + --nexus-card: #1a1a1e; --nexus-text: #ffffff; --nexus-paper: #F9F9F9; --nexus-font-sans: 'Inter', sans-serif; --nexus-font-serif: 'Merriweather', serif; + + /* Global Selection Style Override */ + --nexus-selection: rgba(0, 255, 153, 0.25); +} + +::selection { + background-color: var(--nexus-selection); + color: inherit; +} + /* Global Semantic Theme Mapping */ --nexus-primary: var(--nexus-neon);