From 11769218b93be84f3372a23dece95fe6d1dc85a2 Mon Sep 17 00:00:00 2001 From: Egor Date: Thu, 22 Jan 2026 22:29:40 -0800 Subject: [PATCH] theme switcher --- NOTICE | 1 + server/public/index.css | 116 +++++++++++++++++++++++++++++------- server/public/index.html | 3 + server/public/manifest.json | 6 -- server/public/theme.js | 43 +++++++++++++ 5 files changed, 141 insertions(+), 28 deletions(-) create mode 100644 server/public/theme.js diff --git a/NOTICE b/NOTICE index c53801a..dba9421 100644 --- a/NOTICE +++ b/NOTICE @@ -11,6 +11,7 @@ The Android application (android/) contains modified code from: Licensed under Apache License 2.0 Major modifications: + - Completely replaced HTTP/WebSocket polling with Signal-based push delivery - Removed all user authentication, attachment downloads, and action buttons - Removed Firebase, certificate pinning, and instant delivery features diff --git a/server/public/index.css b/server/public/index.css index 24a6c61..106a9c3 100644 --- a/server/public/index.css +++ b/server/public/index.css @@ -1,3 +1,50 @@ +:root { + --bg-primary: #f5f5f5; + --bg-secondary: #fdfdfd; + --text-primary: #000; + --text-secondary: #666; + --border-color: rgba(0, 0, 0, 0.1); + --accent: #8159b8; + --success: #28a745; + --error: #dc3545; + --spinner-track: #e0e0e0; +} + +@media (prefers-color-scheme: dark) { + :root { + --bg-primary: #1a1a1a; + --bg-secondary: #2d2d2d; + --text-primary: #e0e0e0; + --text-secondary: #a0a0a0; + --border-color: rgba(255, 255, 255, 0.1); + --success: #28a745; + --error: #ef4444; + --spinner-track: #4a4a4a; + } +} + +[data-theme="light"] { + --bg-primary: #f5f5f5; + --bg-secondary: #fdfdfd; + --text-primary: #000; + --text-secondary: #666; + --border-color: rgba(0, 0, 0, 0.1); + --success: #28a745; + --error: #dc3545; + --spinner-track: #e0e0e0; +} + +[data-theme="dark"] { + --bg-primary: #1a1a1a; + --bg-secondary: #2d2d2d; + --text-primary: #e0e0e0; + --text-secondary: #a0a0a0; + --border-color: rgba(255, 255, 255, 0.1); + --success: #28a745; + --error: #ef4444; + --spinner-track: #4a4a4a; +} + /* CSS Reset */ *, *::before, @@ -46,16 +93,17 @@ body { font-family: system-ui; max-width: 50rem; margin: 1.25rem auto; - padding: 1.25rem; - background: #f5f5f5; + padding: 0 1.25rem 1.25rem; + background: var(--bg-primary); + color: var(--text-primary); } .card { - background: white; + background: var(--bg-secondary); border-radius: 0.5rem; padding: 1.25rem; margin-bottom: 1.25rem; - box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.1); + box-shadow: 0 0.125rem 0.25rem var(--border-color); } h2 { @@ -87,16 +135,18 @@ h2 { left: 50%; transform: translateX(-50%); margin-bottom: 0.5rem; - background: #333; - color: white; + background: var(--bg-secondary); + color: var(--text-primary); padding: 0.375rem 0.625rem; border-radius: 0.25rem; + border: 1px solid var(--border-color); font-size: 0.875rem; font-weight: 400; white-space: nowrap; transition: opacity 0.2s; pointer-events: none; z-index: 10; + box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.2); } .status-item .tooltip::after { @@ -106,7 +156,7 @@ h2 { left: 50%; transform: translateX(-50%); border: 0.3125rem solid transparent; - border-top-color: #333; + border-top-color: var(--bg-secondary); } .status-item:hover .tooltip { @@ -114,14 +164,33 @@ h2 { opacity: 1; } +.theme-toggle { + position: fixed; + bottom: 1.5rem; + right: 1.5rem; + background: transparent; + border: none; + cursor: pointer; + font-size: 1.5rem; + padding: 0; + line-height: 1; + opacity: 0.4; + transition: opacity 0.2s; + z-index: 100; +} + +.theme-toggle:hover { + opacity: 0.8; +} + .status-ok { - background: #d4edda; - color: #155724; + background: var(--success); + color: white; } .status-error { - background: #f8d7da; - color: #721c24; + background: var(--error); + color: white; } .endpoint-list { @@ -135,7 +204,7 @@ h2 { display: flex; align-items: center; padding: 0.5rem 0.75rem; - background: #f8f9fa; + background: var(--bg-primary); border-radius: 0.25rem; margin-bottom: 0.375rem; } @@ -147,29 +216,31 @@ h2 { .btn-delete { padding: 0.375rem 0.75rem; - background: #dc3545; + background: var(--error); color: white; border: none; border-radius: 0.25rem; cursor: pointer; + transition: filter 0.2s; } .btn-delete:hover { - background: #c82333; + filter: brightness(0.9); } .btn-cancel { margin-top: 1rem; padding: 0.5rem 1rem; - background: #6c757d; + background: var(--text-secondary); color: white; border: none; border-radius: 0.25rem; cursor: pointer; + transition: filter 0.2s; } .btn-cancel:hover { - background: #5a6268; + filter: brightness(0.9); } .unlink-details { @@ -211,21 +282,22 @@ h2 { .link-button { display: inline-block; padding: 0.625rem 1.25rem; - background: #8159b8; + background: var(--accent); color: white; text-decoration: none; border-radius: 0.25rem; margin-top: 0.625rem; border: none; cursor: pointer; + transition: filter 0.2s; } .link-button:hover { - background: #6b4a99; + filter: brightness(0.9); } .loading { - color: #666; + color: var(--text-secondary); display: flex; align-items: center; gap: 0.5rem; @@ -234,8 +306,8 @@ h2 { .spinner { width: 1.25rem; height: 1.25rem; - border: 0.125rem solid #e0e0e0; - border-top-color: #8159b8; + border: 0.125rem solid var(--spinner-track); + border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; } @@ -248,6 +320,6 @@ h2 { .loading-subtitle { font-size: 0.875rem; - color: #666; + color: var(--text-secondary); margin-top: 0.5rem; } diff --git a/server/public/index.html b/server/public/index.html index 676ca4e..f178401 100644 --- a/server/public/index.html +++ b/server/public/index.html @@ -9,8 +9,11 @@ + + +
{ + currentIndex = (currentIndex + 1) % themes.length; + const newTheme = themes[currentIndex]; + applyTheme(newTheme); + updateButtonText(newTheme); +}; + +function updateButtonText(theme) { + const btn = document.getElementById('theme-toggle'); + if (btn) { + btn.textContent = theme === 'system' ? '🌓' : theme === 'light' ? '☀️' : '🌙'; + } +} + +applyTheme(savedTheme); + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + updateButtonText(themes[currentIndex]); + const btn = document.getElementById('theme-toggle'); + if (btn) btn.addEventListener('click', window.cycleTheme); + }); +} else { + updateButtonText(themes[currentIndex]); + const btn = document.getElementById('theme-toggle'); + if (btn) btn.addEventListener('click', window.cycleTheme); +}