Implement theme switcher functionality and enhance styling with CSS variables. Add dark and light theme support, update UI elements for better accessibility, and ensure consistent theming across tools.

This commit is contained in:
dzakdzaks
2026-06-07 00:51:22 +07:00
parent e82bc93d83
commit 6b1e4b3cf3
6 changed files with 146 additions and 15 deletions
+89 -14
View File
@@ -7,17 +7,57 @@
--success: #2ec4b6;
--danger: #e63946;
--warning: #f4a261;
/* surface palette */
--bg: #f5f6fa;
--surface: #ffffff;
--text: #2d3436;
--text-light: #636e72;
--muted: var(--text-light);
--border: #dfe6e9;
--shadow: 0 2px 8px rgba(0,0,0,0.08);
/* semantic accent */
--accent-soft: #eef1ff;
--accent-soft-hover: #dde3ff;
/* notes/warning block */
--note-bg: #fff8e1;
--note-border: #f5b700;
--note-text: #5a4200;
--note-summary: #8a6300;
--note-link: #b26f00;
--note-code-bg: rgba(0,0,0,.06);
/* upload hover */
--upload-hover-bg: #f0f3ff;
/* result states */
--result-success-bg: #e8faf8;
--result-success-text: #1a7a6d;
--result-error-bg: #fdeaea;
/* layout */
--sidebar-w: 260px;
--radius: 8px;
--shadow: 0 2px 8px rgba(0,0,0,0.08);
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
[data-theme="dark"] {
--bg: #1a1d23;
--surface: #22262e;
--text: #e4e6eb;
--text-light: #9aa0ac;
--border: #3a3f4b;
--shadow: 0 2px 8px rgba(0,0,0,0.35);
--accent-soft: rgba(67,97,238,.2);
--accent-soft-hover: rgba(67,97,238,.35);
--note-bg: #2a2000;
--note-border: #c49a00;
--note-text: #f0c040;
--note-summary: #d4a800;
--note-link: #e8c000;
--note-code-bg: rgba(255,255,255,.08);
--upload-hover-bg: rgba(67,97,238,.12);
--result-success-bg: #082e2a;
--result-success-text: #4ecdc4;
--result-error-bg: #2d0a0a;
}
html { font-size: 15px; }
body {
font-family: var(--font);
@@ -98,7 +138,7 @@ a { color: var(--primary); text-decoration: none; }
transition: background .15s, color .15s;
}
.nav-item:hover, .nav-item.active {
background: #eef1ff;
background: var(--accent-soft);
color: var(--primary);
}
@@ -132,6 +172,38 @@ a { color: var(--primary); text-decoration: none; }
color: var(--text);
}
/* ── Theme Switcher ───────────────────────────── */
.theme-switcher {
display: flex;
align-items: center;
gap: 2px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 20px;
padding: 3px;
margin-left: auto;
}
.theme-btn {
border: none;
background: none;
cursor: pointer;
color: var(--text-light);
border-radius: 16px;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
font-size: .9rem;
transition: background .15s, color .15s;
}
.theme-btn:hover { background: var(--border); color: var(--text); }
.theme-btn.active {
background: var(--surface);
color: var(--primary);
box-shadow: 0 1px 3px rgba(0,0,0,.15);
}
.content-area {
flex: 1;
padding: 2rem;
@@ -227,19 +299,19 @@ a { color: var(--primary); text-decoration: none; }
.tool-header p { color: var(--text-light); }
.tool-notes {
background: #fff8e1;
border-left: 3px solid #f5b700;
background: var(--note-bg);
border-left: 3px solid var(--note-border);
border-radius: 4px;
padding: .75rem 1rem;
margin-bottom: 1.25rem;
font-size: .88rem;
line-height: 1.5;
color: #5a4200;
color: var(--note-text);
}
.tool-notes p { margin: 0 0 .5rem; }
.tool-notes p:last-child { margin-bottom: 0; }
.tool-notes code {
background: rgba(0,0,0,.06);
background: var(--note-code-bg);
padding: .1rem .35rem;
border-radius: 3px;
font-size: .82rem;
@@ -248,12 +320,12 @@ a { color: var(--primary); text-decoration: none; }
.tool-notes details > summary {
cursor: pointer;
font-weight: 600;
color: #8a6300;
color: var(--note-summary);
}
.tool-notes details[open] > summary { margin-bottom: .5rem; }
.tool-notes ol, .tool-notes ul { margin: .25rem 0 .5rem 1.2rem; padding: 0; }
.tool-notes li { margin-bottom: .2rem; }
.tool-notes a { color: #b26f00; }
.tool-notes a { color: var(--note-link); }
.capability-status {
border: 1px solid var(--border);
@@ -308,7 +380,7 @@ a { color: var(--primary); text-decoration: none; }
}
.upload-zone:hover, .upload-zone.dragover {
border-color: var(--primary);
background: #f0f3ff;
background: var(--upload-hover-bg);
}
.upload-zone input[type="file"] {
position: absolute;
@@ -362,6 +434,7 @@ textarea {
font: inherit;
font-size: .9rem;
background: var(--surface);
color: var(--text);
transition: border-color .15s;
}
input:focus, select:focus, textarea:focus {
@@ -397,12 +470,12 @@ textarea { resize: vertical; }
flex-wrap: wrap;
}
.result-success {
background: #e8faf8;
color: #1a7a6d;
background: var(--result-success-bg);
color: var(--result-success-text);
}
.result-success .btn { margin-left: auto; }
.result-error {
background: #fdeaea;
background: var(--result-error-bg);
color: var(--danger);
}
.result-text-box {
@@ -474,6 +547,8 @@ textarea { resize: vertical; }
font-family: "Consolas", "Monaco", monospace;
font-size: .85rem;
outline: none;
background: var(--surface);
color: var(--text);
}
.pane-body pre {
min-height: 250px;
@@ -511,8 +586,8 @@ textarea { resize: vertical; }
transition: background .1s;
}
.calc-btn:hover { background: var(--bg); }
.calc-btn.op { background: #eef1ff; color: var(--primary); font-weight: 600; }
.calc-btn.op:hover { background: #dde3ff; }
.calc-btn.op { background: var(--accent-soft); color: var(--primary); font-weight: 600; }
.calc-btn.op:hover { background: var(--accent-soft-hover); }
.calc-btn.eq { background: var(--primary); color: #fff; }
.calc-btn.eq:hover { background: var(--primary-dark); }
.calc-btn.span2 { grid-column: span 2; }
+37
View File
@@ -1,3 +1,39 @@
/* ── Theme ────────────────────────────────────── */
const THEME_KEY = "theme";
function getStoredTheme() {
return localStorage.getItem(THEME_KEY) || "system";
}
function resolveTheme(mode) {
if (mode === "dark") return "dark";
if (mode === "light") return "light";
return (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light";
}
function applyTheme(mode) {
localStorage.setItem(THEME_KEY, mode);
document.documentElement.dataset.theme = resolveTheme(mode);
document.querySelectorAll(".theme-btn").forEach(btn => {
btn.classList.toggle("active", btn.dataset.themeMode === mode);
});
}
function initTheme() {
const mode = getStoredTheme();
applyTheme(mode);
document.querySelectorAll(".theme-btn").forEach(btn => {
btn.addEventListener("click", () => applyTheme(btn.dataset.themeMode));
});
if (window.matchMedia) {
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
if (getStoredTheme() === "system") {
document.documentElement.dataset.theme = resolveTheme("system");
}
});
}
}
/* ── Sidebar ──────────────────────────────────── */
function toggleCategory(btn) {
btn.classList.toggle("open");
@@ -30,6 +66,7 @@ document.addEventListener("DOMContentLoaded", () => {
}
});
initTheme();
initUploadZone();
initToolForm();
initDependentOptions();
+14
View File
@@ -4,6 +4,15 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Your Everyday Tools{% endblock %}</title>
<script>
(function () {
var stored = localStorage.getItem('theme') || 'system';
var resolved = stored === 'dark' ? 'dark'
: stored === 'light' ? 'light'
: (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.dataset.theme = resolved;
})();
</script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/icons.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
@@ -37,6 +46,11 @@
<header class="top-bar">
<button class="menu-btn" onclick="openSidebar()"><i class="bi bi-list"></i></button>
<span class="top-title">{% block top_title %}Your Everyday Tools{% endblock %}</span>
<div class="theme-switcher" id="theme-switcher" role="group" aria-label="Color theme">
<button class="theme-btn" data-theme-mode="system" title="System theme"><i class="bi bi-circle-half"></i></button>
<button class="theme-btn" data-theme-mode="light" title="Light theme"><i class="bi bi-sun-fill"></i></button>
<button class="theme-btn" data-theme-mode="dark" title="Dark theme"><i class="bi bi-moon-fill"></i></button>
</div>
</header>
<div class="content-area">
{% block content %}{% endblock %}
+1 -1
View File
@@ -67,7 +67,7 @@ function showStrength(pw, poolSize) {
let label, color, pct;
if (entropy < 40) { label = "Weak"; color = "var(--danger)"; pct = 25; }
else if (entropy < 60) { label = "Fair"; color = "var(--warning)"; pct = 50; }
else if (entropy < 80) { label = "Strong"; color = "#2ec4b6"; pct = 75; }
else if (entropy < 80) { label = "Strong"; color = "var(--success)"; pct = 75; }
else { label = "Very Strong"; color = "var(--primary)"; pct = 100; }
document.getElementById("pw-strength").innerHTML =
+2
View File
@@ -46,6 +46,8 @@
.match-idx { color: var(--text-light); min-width: 2rem; }
.match-val { font-family: Consolas,Monaco,monospace; }
.match-pos { color: var(--text-light); font-size: .8rem; }
[data-theme="dark"] .regex-hl { background: rgba(244,162,97,.22); }
[data-theme="dark"] .regex-hl:nth-of-type(even) { background: rgba(23,162,184,.22); border-bottom-color: #17a2b8; }
</style>
<script>
function testRegex() {
+3
View File
@@ -39,6 +39,9 @@
.diff-stats { font-size: .85rem; margin-bottom: .5rem; }
.diff-stats .add { color: #28a745; font-weight: 600; }
.diff-stats .del { color: var(--danger); font-weight: 600; }
[data-theme="dark"] .diff-add { background: #0d2e14; }
[data-theme="dark"] .diff-del { background: #2e0d0d; }
[data-theme="dark"] .diff-stats .add { color: #4ecdc4; }
</style>
<script>
function computeDiff() {