diff --git a/Notemark/Components/App.razor b/Notemark/Components/App.razor index 6b64856..81f5fa8 100644 --- a/Notemark/Components/App.razor +++ b/Notemark/Components/App.razor @@ -17,7 +17,14 @@ + + diff --git a/Notemark/Components/Layout/MainLayout.razor.css b/Notemark/Components/Layout/MainLayout.razor.css index 888471b..74aadd5 100644 --- a/Notemark/Components/Layout/MainLayout.razor.css +++ b/Notemark/Components/Layout/MainLayout.razor.css @@ -1,58 +1,3 @@ -/* Notemark CSS Variables for Light/Dark Theme */ -:root { - /* Light Mode (Default) */ - --bg: #F9FAFB; - --surface: #FFFFFF; - --border: #E5E7EB; - --primary-text: #111827; - --secondary-text: #6B7280; - --accent: #4F46E5; - --accent-hover: #4338CA; - --success: #10B981; - --error: #F43F5E; -} - -[data-theme="dark"] { - /* Dark Mode */ - --bg: #0B0F19; - --surface: #111827; - --border: #1F2937; - --primary-text: #F9FAFB; - --secondary-text: #9CA3AF; - --accent: #22D3EE; - --accent-hover: #06B6D4; - --success: #4ADE80; - --error: #FB7185; -} - -/* Auto-detect system preference */ -@media (prefers-color-scheme: dark) { - :root { - --bg: #0B0F19; - --surface: #111827; - --border: #1F2937; - --primary-text: #F9FAFB; - --secondary-text: #9CA3AF; - --accent: #22D3EE; - --accent-hover: #06B6D4; - --success: #4ADE80; - --error: #FB7185; - } -} - -/* Override system dark mode when explicitly set to light */ -[data-theme="light"] { - --bg: #F9FAFB; - --surface: #FFFFFF; - --border: #E5E7EB; - --primary-text: #111827; - --secondary-text: #6B7280; - --accent: #4F46E5; - --accent-hover: #4338CA; - --success: #10B981; - --error: #F43F5E; -} - /* Notemark App Layout */ .notemark-app { height: 100vh; @@ -70,11 +15,6 @@ box-sizing: border-box; } -body { - background-color: var(--bg); - color: var(--primary-text); -} - #blazor-error-ui { color-scheme: light only; background: lightyellow; diff --git a/Notemark/Components/Pages/Editor.razor b/Notemark/Components/Pages/Editor.razor index 9f8be34..eb46d67 100644 --- a/Notemark/Components/Pages/Editor.razor +++ b/Notemark/Components/Pages/Editor.razor @@ -56,6 +56,7 @@ } + diff --git a/Notemark/Components/Pages/Editor.razor.css b/Notemark/Components/Pages/Editor.razor.css index 98a5ab6..f583412 100644 --- a/Notemark/Components/Pages/Editor.razor.css +++ b/Notemark/Components/Pages/Editor.razor.css @@ -24,6 +24,7 @@ .top-bar-right { display: flex; align-items: center; + gap: 0.75rem; } .top-bar-center { diff --git a/Notemark/Components/Pages/SharedEditor.razor b/Notemark/Components/Pages/SharedEditor.razor index 7ca26c8..eefbefc 100644 --- a/Notemark/Components/Pages/SharedEditor.razor +++ b/Notemark/Components/Pages/SharedEditor.razor @@ -48,6 +48,7 @@ else
+
diff --git a/Notemark/Components/Pages/SharedPreview.razor b/Notemark/Components/Pages/SharedPreview.razor index 9713437..7821b04 100644 --- a/Notemark/Components/Pages/SharedPreview.razor +++ b/Notemark/Components/Pages/SharedPreview.razor @@ -42,6 +42,7 @@ else } + diff --git a/Notemark/Components/Shared/ThemeToggle.razor b/Notemark/Components/Shared/ThemeToggle.razor new file mode 100644 index 0000000..b61f32c --- /dev/null +++ b/Notemark/Components/Shared/ThemeToggle.razor @@ -0,0 +1,67 @@ +@inject IJSRuntime JSRuntime + + + +@code { + private string currentTheme = "auto"; + + protected override async Task OnInitializedAsync() + { + try + { + currentTheme = await JSRuntime.InvokeAsync("notemarkTheme.getCurrentTheme"); + } + catch + { + // Fallback if JS not ready + currentTheme = "auto"; + } + } + + private async Task ToggleTheme() + { + // Cycle through: auto -> light -> dark -> auto + currentTheme = currentTheme switch + { + "auto" => "light", + "light" => "dark", + "dark" => "auto", + _ => "auto" + }; + + try + { + await JSRuntime.InvokeVoidAsync("notemarkTheme.setTheme", currentTheme); + } + catch + { + // JS not ready, silently fail + } + + StateHasChanged(); + } + + private string GetThemeIcon() + { + return currentTheme switch + { + "light" => "☀️", + "dark" => "🌙", + "auto" => "🔄", + _ => "🔄" + }; + } + + private string GetThemeTooltip() + { + return currentTheme switch + { + "light" => "Switch to dark mode", + "dark" => "Switch to auto mode", + "auto" => "Switch to light mode", + _ => "Toggle theme" + }; + } +} diff --git a/Notemark/Components/Shared/ThemeToggle.razor.css b/Notemark/Components/Shared/ThemeToggle.razor.css new file mode 100644 index 0000000..eaacd10 --- /dev/null +++ b/Notemark/Components/Shared/ThemeToggle.razor.css @@ -0,0 +1,35 @@ +.theme-toggle { + padding: 0.5rem; + background: none; + border: 1px solid var(--border); + border-radius: 6px; + cursor: pointer; + font-size: 1rem; + line-height: 1; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + min-width: 2.5rem; + height: 2.5rem; +} + +.theme-toggle:hover { + background-color: var(--accent); + border-color: var(--accent); + transform: scale(1.05); +} + +.theme-toggle:active { + transform: scale(0.95); +} + +/* Mobile styles */ +@media (max-width: 768px) { + .theme-toggle { + min-width: 2rem; + height: 2rem; + padding: 0.375rem; + font-size: 0.875rem; + } +} diff --git a/Notemark/Components/_Imports.razor b/Notemark/Components/_Imports.razor index e6c5c93..5e12d67 100644 --- a/Notemark/Components/_Imports.razor +++ b/Notemark/Components/_Imports.razor @@ -9,3 +9,4 @@ @using Notemark @using Notemark.Components @using Notemark.Components.Layout +@using Notemark.Components.Shared diff --git a/Notemark/wwwroot/app.css b/Notemark/wwwroot/app.css index 73a69d6..4aee6c3 100644 --- a/Notemark/wwwroot/app.css +++ b/Notemark/wwwroot/app.css @@ -1,3 +1,65 @@ +/* Notemark Theme Variables */ +:root { + /* Light Mode (Default) */ + --bg: #F9FAFB; + --surface: #FFFFFF; + --border: #E5E7EB; + --primary-text: #111827; + --secondary-text: #6B7280; + --accent: #4F46E5; + --accent-hover: #4338CA; + --success: #10B981; + --error: #F43F5E; +} + +/* Auto-detect system preference for dark mode */ +@media (prefers-color-scheme: dark) { + :root:not([data-theme]) { + --bg: #0B0F19; + --surface: #111827; + --border: #1F2937; + --primary-text: #F9FAFB; + --secondary-text: #9CA3AF; + --accent: #22D3EE; + --accent-hover: #06B6D4; + --success: #4ADE80; + --error: #FB7185; + } +} + +/* Explicit light mode */ +:root[data-theme="light"] { + --bg: #F9FAFB !important; + --surface: #FFFFFF !important; + --border: #E5E7EB !important; + --primary-text: #111827 !important; + --secondary-text: #6B7280 !important; + --accent: #4F46E5 !important; + --accent-hover: #4338CA !important; + --success: #10B981 !important; + --error: #F43F5E !important; +} + +/* Explicit dark mode */ +:root[data-theme="dark"] { + --bg: #0B0F19 !important; + --surface: #111827 !important; + --border: #1F2937 !important; + --primary-text: #F9FAFB !important; + --secondary-text: #9CA3AF !important; + --accent: #22D3EE !important; + --accent-hover: #06B6D4 !important; + --success: #4ADE80 !important; + --error: #FB7185 !important; +} + +/* Apply theme variables globally */ +html, body { + background-color: var(--bg) !important; + color: var(--primary-text) !important; + transition: background-color 0.3s ease, color 0.3s ease; +} + html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } @@ -37,7 +99,7 @@ h1:focus { } .blazor-error-boundary { - background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA69.NzMwOSAyNjcuODc5IDY5LjU4NzcgMjY3Ljg3OSA2OS4zMTc5IDI2Ny44NzkgNjcuMTEyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; padding: 1rem 1rem 1rem 3.7rem; color: white; } diff --git a/Notemark/wwwroot/js/theme.js b/Notemark/wwwroot/js/theme.js new file mode 100644 index 0000000..1fd930c --- /dev/null +++ b/Notemark/wwwroot/js/theme.js @@ -0,0 +1,34 @@ +window.notemarkTheme = { + init: function() { + // Get saved theme or default to 'auto' + const savedTheme = localStorage.getItem('notemark-theme') || 'auto'; + this.applyTheme(savedTheme); + return savedTheme; + }, + + setTheme: function(theme) { + localStorage.setItem('notemark-theme', theme); + this.applyTheme(theme); + }, + + applyTheme: function(theme) { + const root = document.documentElement; + + if (theme === 'light') { + root.setAttribute('data-theme', 'light'); + } else if (theme === 'dark') { + root.setAttribute('data-theme', 'dark'); + } else { + // For 'auto', remove data-theme to let CSS @media handle it + root.removeAttribute('data-theme'); + } + }, + + getSystemTheme: function() { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + }, + + getCurrentTheme: function() { + return localStorage.getItem('notemark-theme') || 'auto'; + } +};