diff --git a/css/style.css b/css/style.css index f0bec25..a89a290 100644 --- a/css/style.css +++ b/css/style.css @@ -7,6 +7,26 @@ --ground: #f5f8fb; --panel: #ffffff; --line: #dde4ec; + --img-bg: #ffffff; + --bg-blue-tint: #eef4f9; + --bg-green-tint: #f5f9ec; + --bg-error: #faf0ed; + --border-error: #c0563c; +} +[data-theme="dark"] { + --blue: #5b9fd4; + --blue-dark: #4a87bb; + --green: #a8cf35; + --ink: #e4edf5; + --dim: #8fa0b3; + --ground: #0f1923; + --panel: #192636; + --line: #283a4e; + --img-bg: #1e2e3e; + --bg-blue-tint: #0a1e2e; + --bg-green-tint: #0c1a06; + --bg-error: #1e0e0a; + --border-error: #d06550; } * { box-sizing: border-box; } body { @@ -19,8 +39,13 @@ a { color: var(--blue); } padding: 14px 24px; background: var(--panel); border-bottom: 1px solid var(--line); } .brand { display: flex; align-items: center; gap: 10px; font-weight: 700; text-decoration: none; color: var(--blue); font-size: 1.05rem; } -.site-header nav { display: flex; gap: 18px; } +.site-header nav { display: flex; gap: 18px; align-items: center; } .site-header nav a { color: var(--dim); text-decoration: none; } +.theme-btn { + background: none; border: none; cursor: pointer; color: var(--dim); + font-size: 1rem; padding: 0; line-height: 1; visibility: hidden; +} +.theme-btn:hover { color: var(--ink); } main { max-width: 1080px; margin: 0 auto; padding: 32px 24px 64px; } .site-footer { text-align: center; color: var(--dim); font-size: .85rem; padding: 24px; border-top: 1px solid var(--line); } .loading { color: var(--dim); } @@ -40,7 +65,7 @@ main { max-width: 1080px; margin: 0 auto; padding: 32px 24px 64px; } padding: 14px; text-decoration: none; color: inherit; transition: border-color .15s, box-shadow .15s; } .device-card:hover { border-color: var(--blue); box-shadow: 0 4px 14px -8px rgba(65,122,171,.5); } -.device-card img { width: 100%; aspect-ratio: 1; object-fit: contain; border-radius: 6px; background: #fff; } +.device-card img { width: 100%; aspect-ratio: 1; object-fit: contain; border-radius: 6px; background: var(--img-bg); } .device-card h3 { margin: 10px 0 2px; font-size: 1rem; } .device-card p { margin: 0; color: var(--dim); font-size: .82rem; } .device-card .go { color: var(--blue); font-weight: 600; font-size: .85rem; margin-top: 8px; display: inline-block; } @@ -48,7 +73,7 @@ main { max-width: 1080px; margin: 0 auto; padding: 32px 24px 64px; } /* Device wizard */ .device-page .back { display: inline-block; margin-bottom: 16px; text-decoration: none; color: var(--dim); } .device-head { display: flex; gap: 20px; align-items: center; margin-bottom: 20px; } -.device-head img { width: 110px; height: 110px; object-fit: contain; background: #fff; border: 1px solid var(--line); border-radius: 10px; } +.device-head img { width: 110px; height: 110px; object-fit: contain; background: var(--img-bg); border: 1px solid var(--line); border-radius: 10px; } .device-head h1 { margin: 0; font-size: 1.6rem; } .device-head p { margin: 2px 0 6px; color: var(--dim); } .device-head .links { font-size: .85rem; } @@ -67,10 +92,10 @@ esp-web-install-button button, .install-btn { background: var(--blue); color: #fff; border: 0; border-radius: 8px; padding: 10px 22px; font-size: .95rem; font-weight: 700; cursor: pointer; } -.fallback { border-left: 3px solid var(--blue); background: #eef4f9; border-radius: 0 8px 8px 0; padding: 12px 16px; } +.fallback { border-left: 3px solid var(--blue); background: var(--bg-blue-tint); border-radius: 0 8px 8px 0; padding: 12px 16px; } .fallback ul { margin: 8px 0 0; padding-left: 20px; } -.release-notes { border-left: 3px solid var(--green); background: #f5f9ec; border-radius: 0 8px 8px 0; padding: 12px 16px; margin-top: 14px; font-size: .88rem; } +.release-notes { border-left: 3px solid var(--green); background: var(--bg-green-tint); border-radius: 0 8px 8px 0; padding: 12px 16px; margin-top: 14px; font-size: .88rem; } .release-notes summary { cursor: pointer; font-weight: 600; } .release-notes pre { white-space: pre-wrap; font-family: inherit; margin: 8px 0; } .done-check { color: var(--green); font-weight: 700; } -.error-box { border-left: 3px solid #c0563c; background: #faf0ed; border-radius: 0 8px 8px 0; padding: 12px 16px; } +.error-box { border-left: 3px solid var(--border-error); background: var(--bg-error); border-radius: 0 8px 8px 0; padding: 12px 16px; } diff --git a/index.html b/index.html index 7e521c1..40b9d65 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,16 @@ Apollo Installer + @@ -18,6 +28,7 @@ Wiki Shop Discord +

Loading devices…

diff --git a/js/app.js b/js/app.js index 40a2d13..a6e4c39 100644 --- a/js/app.js +++ b/js/app.js @@ -1,6 +1,7 @@ import { loadRegistry } from './registry.js'; import { renderHub } from './views/hub.js'; import { renderDevice } from './views/device.js'; +import { initThemeToggle } from './theme.js'; const app = document.getElementById('app'); @@ -29,3 +30,4 @@ async function route() { window.addEventListener('hashchange', route); route(); +initThemeToggle(); diff --git a/js/theme.js b/js/theme.js new file mode 100644 index 0000000..c56e10a --- /dev/null +++ b/js/theme.js @@ -0,0 +1,63 @@ +// js/theme.js — theme toggle: System / Dark / Light (cycles on click) +const THEMES = ['system', 'dark', 'light']; +const ICONS = { system: '⬤', dark: '🌙', light: '☀' }; +const LABELS = { system: 'System theme', dark: 'Dark theme', light: 'Light theme' }; +const NEXT_LABEL = { + system: 'Switch to dark theme', + dark: 'Switch to light theme', + light: 'Switch to system theme', +}; + +function storedPref() { + try { return localStorage.getItem('theme') || 'system'; } catch (e) { return 'system'; } +} + +function savePref(pref) { + try { + if (pref === 'system') localStorage.removeItem('theme'); + else localStorage.setItem('theme', pref); + } catch (e) {} +} + +function applyPref(pref) { + const root = document.documentElement; + const isDark = + pref === 'dark' || + (pref === 'system' && matchMedia('(prefers-color-scheme: dark)').matches); + if (isDark) root.setAttribute('data-theme', 'dark'); + else root.removeAttribute('data-theme'); +} + +export function initThemeToggle() { + const btn = document.getElementById('theme-toggle'); + if (!btn) return; + + const mq = matchMedia('(prefers-color-scheme: dark)'); + + function updateBtn(pref) { + btn.textContent = ICONS[pref]; + btn.setAttribute('aria-label', LABELS[pref]); + btn.title = NEXT_LABEL[pref]; + } + + function setPref(pref) { + savePref(pref); + applyPref(pref); + updateBtn(pref); + } + + // Keep system mode in sync when OS preference changes + mq.addEventListener('change', () => { + if (storedPref() === 'system') applyPref('system'); + }); + + btn.addEventListener('click', () => { + const current = storedPref(); + const next = THEMES[(THEMES.indexOf(current) + 1) % THEMES.length]; + setPref(next); + }); + + // Initialise button state (makes it visible) + updateBtn(storedPref()); + btn.style.visibility = ''; +}