diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Layout/MainLayout.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Layout/MainLayout.razor index acb5b2d4..b43c4b99 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Layout/MainLayout.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Layout/MainLayout.razor @@ -15,44 +15,13 @@ @@ -69,22 +38,6 @@ await base.OnInitializedAsync(); } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - await ThemeService.InitializeAsync(); - } - - private async Task OnToggleDarkMode() - => await ThemeService.ToggleDarkModeAsync(); - - private async Task OnThemeChanged(ChangeEventArgs e) - { - var value = e.Value?.ToString(); - if (!string.IsNullOrEmpty(value)) - await ThemeService.SetThemeAsync(value); - } - public void Dispose() => ThemeService.OnChange -= _themeChangeHandler; } diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Layout/ThemeControls.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Layout/ThemeControls.razor new file mode 100644 index 00000000..bf937741 --- /dev/null +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Layout/ThemeControls.razor @@ -0,0 +1,95 @@ +@using EnvelopeGenerator.Server.Client.Services +@rendermode InteractiveWebAssembly +@implements IDisposable + +@inject ThemeService ThemeService + +
+ + @* Moon β€” left end (blazing-dark) *@ + + + + + @* Track wrapper: range input + left/mid/right ticks *@ +
+ @* Left tick *@ +
+ @* Mid tick (marks the middle position) *@ +
+ @* Right tick *@ +
+ + +
+ + @* Sun β€” right end (purple) *@ + + + + + + + + + + + + +
+ +@code { + private Action? _themeChangeHandler; + + /// + /// Maps the active theme to a 0-based slider step. + /// 0 = blazing-dark (πŸŒ™ left) + /// 1 = blazing-berry (middle) + /// 2 = purple (β˜€οΈ right) + /// + private int SliderValue => ThemeService.ThemeName switch + { + "blazing-dark" => 0, + "purple" => 2, + _ => 1 + }; + + protected override void OnInitialized() + { + _themeChangeHandler = () => InvokeAsync(StateHasChanged); + ThemeService.OnChange += _themeChangeHandler; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + await ThemeService.InitializeAsync(); + } + + private async Task OnSliderChanged(ChangeEventArgs e) + { + if (!int.TryParse(e.Value?.ToString(), out var step)) return; + + var themeName = step switch + { + 0 => "blazing-dark", + 2 => "purple", + _ => "blazing-berry" + }; + + await ThemeService.SetThemeAsync(themeName); + } + + public void Dispose() + => ThemeService.OnChange -= _themeChangeHandler; +} diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/app.css b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/app.css index d8bc6ad3..55879faa 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/app.css +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/app.css @@ -342,7 +342,7 @@ article { height: 36px; display: flex; align-items: center; - justify-content: center; + justify-content: space-between; /* ← left group | right group */ gap: 10px; padding: 0 20px; background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%); @@ -363,65 +363,136 @@ article { text-decoration: underline; } +.receiver-footer__left { + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; +} + .receiver-footer__sep { opacity: 0.4; } -/* ── Footer: Theme & Dark Mode Controls ─────────────────────────────────── */ -/* Positioned at the right end of the fixed footer bar. */ -/* Styled to match the footer's gradient background β€” small, unobtrusive. */ +/* ── Footer: Theme Slider ────────────────────────────────────────────────── */ +/* πŸŒ™ blazing-dark ──◇──·──◇── purple β˜€οΈ */ +/* Left group: copyright | datenschutz β†’ Right group: slider */ -.sf-footer-controls { +.sf-theme-slider { display: flex; align-items: center; - gap: 6px; - margin-left: auto; /* push to the right */ -} - -.sf-footer-select { - height: 22px; - padding: 0 5px; - font-size: 0.68rem; - line-height: 1; - color: rgba(255, 255, 255, 0.85); - background: rgba(255, 255, 255, 0.12); - border: 1px solid rgba(255, 255, 255, 0.22); - border-radius: 4px; - cursor: pointer; - outline: none; - /* Native select options use browser defaults β€” acceptable in a footer context */ -} - -.sf-footer-select:hover, -.sf-footer-select:focus { - background: rgba(255, 255, 255, 0.20); - border-color: rgba(255, 255, 255, 0.38); -} - -/* Force option text to be readable in browsers that support it */ -.sf-footer-select option { - color: #1e2e3e; - background: #ffffff; -} - -.sf-footer-toggle { - width: 24px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; - padding: 0; - color: rgba(255, 255, 255, 0.85); - background: rgba(255, 255, 255, 0.12); - border: 1px solid rgba(255, 255, 255, 0.22); - border-radius: 4px; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; + gap: 7px; flex-shrink: 0; } -.sf-footer-toggle:hover { - background: rgba(255, 255, 255, 0.24); - border-color: rgba(255, 255, 255, 0.40); - color: #ffffff; +/* Moon / Sun SVG icons */ +.sf-theme-icon { + display: block; + opacity: 0.55; /* ↑ was 0.42 β€” more visible as base */ + flex-shrink: 0; + transition: opacity 0.18s; + align-self: center; +} + +.sf-theme-icon--active { + opacity: 0.92; +} + +.sf-theme-icon--mid { + opacity: 0.62; +} + +/* ── Wrapper holds the range input + tick marks ───────────────────────────── */ +.sf-theme-track-wrapper { + position: relative; + display: flex; + align-items: center; + width: 80px; + height: 16px; /* enough height so ticks + thumb don't clip */ +} + +/* Actual range input β€” fills the wrapper */ +.sf-theme-range { + -webkit-appearance: none; + appearance: none; + position: relative; + z-index: 1; + width: 100%; + height: 2px; + background: rgba(255, 255, 255, 0.28); + border-radius: 1px; + outline: none; + cursor: pointer; + margin: 0; + /* no extra padding so ticks align correctly */ +} + +.sf-theme-range:hover { + background: rgba(255, 255, 255, 0.42); +} + +/* Thumb β€” white circle */ +.sf-theme-range::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 11px; + height: 11px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.90); + cursor: pointer; + border: none; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.28); + transition: transform 0.12s, box-shadow 0.12s; +} + +.sf-theme-range::-webkit-slider-thumb:hover { + transform: scale(1.25); + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.38); +} + +/* Firefox */ +.sf-theme-range::-moz-range-thumb { + width: 11px; + height: 11px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.90); + cursor: pointer; + border: none; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.28); +} + +.sf-theme-range::-moz-range-track { + height: 2px; + background: rgba(255, 255, 255, 0.28); + border-radius: 1px; +} + +/* ── End ticks (left & right): thin vertical bars ────────────────────────── */ +.sf-theme-tick { + position: absolute; + z-index: 0; + width: 2px; + height: 7px; + border-radius: 1px; + background: rgba(255, 255, 255, 0.45); + top: 50%; + transform: translateY(-50%); + pointer-events: none; +} + +.sf-theme-tick--left { left: 0; } +.sf-theme-tick--right { right: 0; } + +/* Mid tick β€” thinner, subtler */ +.sf-theme-tick--mid { + left: 50%; + transform: translate(-50%, -50%); + width: 1px; + height: 5px; + background: rgba(255, 255, 255, 0.50); /* ↑ was 0.22 */ +} + +/* Sun icon: always visible regardless of active state */ +.sf-theme-icon--sun { + opacity: 0.72; /* minimum visibility */ } \ No newline at end of file