Add a dark/light theme switcher to the navbar

The palette already lived in CSS custom properties, so dark mode is a
token-override block in the new theme.css, applied via data-theme on
the document element. A moon/sun toggle sits top right in the navbar;
an explicit choice persists in localStorage, otherwise the app follows
the OS preference live. An inline head script applies the theme before
first paint so a dark reload never flashes light.

Token hygiene sweep alongside: literal colours that broke under a dark
palette (timeline chip text, color-mix against white, calendar stripes
and bands, warning banners, nav background) moved into ten new tokens
with light-mode values unchanged. The flip clock keeps its fixed dark
look by design; the map keeps standard light tiles in both themes.
This commit is contained in:
2026-08-30 14:07:21 +02:00
parent 48c7a55bbd
commit 2f55723585
6 changed files with 207 additions and 16 deletions
+24 -12
View File
@@ -28,6 +28,18 @@
--shadow-lg: 0 18px 48px rgba(15, 23, 42, 0.22);
--font: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
/* Theme-hygiene tokens (dark values in theme.css) */
--chip-text: #334155;
--nav-bg: rgba(255, 255, 255, 0.9);
--auth-tint: #dbe7ff;
--stripe-a: #f1f5f9;
--stripe-b: #eef2f7;
--warn-soft-bg: #fffbeb;
--warn-soft-border: #fde68a;
--warn-soft-text: #92400e;
--role-owner-bg: #fef3c7;
--chip-price-bg: rgba(15, 23, 42, 0.06);
}
* { box-sizing: border-box; }
@@ -123,7 +135,7 @@ textarea.input { resize: vertical; }
position: sticky; top: 0; z-index: 20;
display: flex; align-items: center; justify-content: space-between;
padding: 0.7rem 1.4rem;
background: rgba(255, 255, 255, 0.9);
background: var(--nav-bg);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--border);
}
@@ -148,7 +160,7 @@ textarea.input { resize: vertical; }
/* ---------- Auth ---------- */
.auth-wrap { min-height: 100vh; display: grid; place-items: center; padding: 1.5rem;
background: radial-gradient(1200px 600px at 50% -10%, #dbe7ff 0%, var(--bg) 55%); }
background: radial-gradient(1200px 600px at 50% -10%, var(--auth-tint) 0%, var(--bg) 55%); }
.auth-card { width: 100%; max-width: 400px; padding: 1.8rem; }
.auth-head { text-align: center; margin-bottom: 1.3rem; }
.auth-head h1 { margin: 0.4rem 0 0.2rem; }
@@ -161,7 +173,7 @@ textarea.input { resize: vertical; }
.auth-lead { color: var(--text-muted); font-size: 0.92rem; }
.token-input { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; letter-spacing: 0.08em; text-transform: uppercase; }
.token-reveal { gap: 1rem; }
.token-warning { background: #fffbeb; border: 1px solid #fde68a; color: #92400e; font-size: 0.85rem; padding: 0.7rem 0.85rem; border-radius: var(--radius-sm); line-height: 1.45; }
.token-warning { background: var(--warn-soft-bg); border: 1px solid var(--warn-soft-border); color: var(--warn-soft-text); font-size: 0.85rem; padding: 0.7rem 0.85rem; border-radius: var(--radius-sm); line-height: 1.45; }
.token-box { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 1.35rem; font-weight: 700; letter-spacing: 0.1em; text-align: center; padding: 0.9rem; background: var(--surface-2); border: 2px dashed var(--border-strong); border-radius: var(--radius-sm); user-select: all; word-break: break-all; }
.token-actions { display: flex; justify-content: center; }
@@ -196,7 +208,7 @@ textarea.input { resize: vertical; }
.trip-card.drop-before { box-shadow: inset 0 3px 0 var(--brand); }
.trip-card.drop-after { box-shadow: inset 0 -3px 0 var(--brand); }
.role-badge { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em; font-weight: 700; padding: 0.15rem 0.5rem; border-radius: 999px; }
.role-owner { background: #fef3c7; color: #92400e; }
.role-owner { background: var(--role-owner-bg); color: var(--warn-soft-text); }
.role-editor { background: var(--brand-soft); color: var(--brand-dark); }
.new-trip-form { display: flex; flex-direction: column; gap: 0.8rem; }
@@ -227,8 +239,8 @@ textarea.input { resize: vertical; }
a 7-column grid mirroring the days row; bands place themselves via inline
grid-column/row (lane) styles from stayBands.js. */
.cal-week-bands { --band-h: 1.45rem; display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px 6px; }
.cal-band { height: var(--band-h); display: flex; align-items: center; min-width: 0; padding: 0 0.5rem; font-size: 0.72rem; font-weight: 600; color: #334155; background: color-mix(in srgb, var(--chip) 20%, white); border: 1px solid color-mix(in srgb, var(--chip) 45%, white); border-left: 3px solid var(--chip); cursor: pointer; transition: background 0.12s; }
.cal-band:hover { background: color-mix(in srgb, var(--chip) 32%, white); }
.cal-band { height: var(--band-h); display: flex; align-items: center; min-width: 0; padding: 0 0.5rem; font-size: 0.72rem; font-weight: 600; color: var(--chip-text); background: color-mix(in srgb, var(--chip) 20%, var(--surface)); border: 1px solid color-mix(in srgb, var(--chip) 45%, var(--surface)); border-left: 3px solid var(--chip); cursor: pointer; transition: background 0.12s; }
.cal-band:hover { background: color-mix(in srgb, var(--chip) 32%, var(--surface)); }
.cal-band.round-l { border-top-left-radius: 999px; border-bottom-left-radius: 999px; }
.cal-band.round-r { border-top-right-radius: 999px; border-bottom-right-radius: 999px; }
.cal-band-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -238,14 +250,14 @@ textarea.input { resize: vertical; }
.cal-day { min-width: 0; min-height: 92px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0.35rem; display: flex; flex-direction: column; gap: 0.25rem; }
.cal-day.clickable { cursor: pointer; transition: border-color 0.12s, box-shadow 0.12s; }
.cal-day.clickable:hover { border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand-soft); }
.cal-day.cal-out { background: repeating-linear-gradient(45deg, #f1f5f9, #f1f5f9 8px, #eef2f7 8px, #eef2f7 16px); color: var(--text-faint); }
.cal-day.cal-out { background: repeating-linear-gradient(45deg, var(--stripe-a), var(--stripe-a) 8px, var(--stripe-b) 8px, var(--stripe-b) 16px); color: var(--text-faint); }
.cal-day-head { display: flex; align-items: center; gap: 0.3rem; }
.cal-daynum { font-weight: 700; font-size: 0.82rem; }
.cal-out .cal-daynum { color: var(--text-faint); font-weight: 600; }
.cal-month { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; color: var(--brand); }
.cal-flag { margin-left: auto; color: var(--warn); font-size: 0.75rem; }
.cal-chips { display: flex; flex-direction: column; gap: 3px; overflow: hidden; }
.cal-chip { display: flex; align-items: center; gap: 0.25rem; min-width: 0; font-size: 0.72rem; padding: 0.12rem 0.35rem; border-radius: 6px; background: color-mix(in srgb, var(--chip) 14%, white); border-left: 3px solid var(--chip); color: #334155; }
.cal-chip { display: flex; align-items: center; gap: 0.25rem; min-width: 0; font-size: 0.72rem; padding: 0.12rem 0.35rem; border-radius: 6px; background: color-mix(in srgb, var(--chip) 14%, var(--surface)); border-left: 3px solid var(--chip); color: var(--chip-text); }
.cal-chip .chip-price { flex-shrink: 0; }
.chip-icon { flex-shrink: 0; }
.chip-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -272,7 +284,7 @@ textarea.input { resize: vertical; }
.wp-dot-wrap { background: none; border: none; }
.wp-dot { display: block; width: 10px; height: 10px; border-radius: 50%; background: var(--wp-color, var(--brand)); border: 2px solid #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.35); }
.map-popup strong { font-size: 0.95rem; }
.map-popup-sub { color: #64748b; font-size: 0.8rem; margin-top: 0.15rem; }
.map-popup-sub { color: var(--text-muted); font-size: 0.8rem; margin-top: 0.15rem; }
.map-popup-loc { font-size: 0.82rem; margin-top: 0.15rem; }
.leg-list, .leg-list-empty { margin-top: 1rem; }
@@ -350,7 +362,7 @@ textarea.input { resize: vertical; }
.field-currency, .field-price { flex: 0 0 110px; min-width: 90px; }
.input-currency { text-transform: uppercase; }
.currency-tag { display: inline-block; font-weight: 700; font-size: 0.7rem; letter-spacing: 0.04em; background: var(--surface-2); border: 1px solid var(--border); color: var(--text-muted); padding: 0.05rem 0.4rem; border-radius: 5px; }
.chip-price { margin-left: auto; font-size: 0.66rem; font-weight: 700; background: rgba(15,23,42,0.06); color: #334155; padding: 0 0.25rem; border-radius: 4px; white-space: nowrap; }
.chip-price { margin-left: auto; font-size: 0.66rem; font-weight: 700; background: var(--chip-price-bg); color: var(--chip-text); padding: 0 0.25rem; border-radius: 4px; white-space: nowrap; }
.entry-title-row { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
.entry-price { font-size: 0.82rem; font-weight: 700; color: var(--brand-dark); white-space: nowrap; }
.entry-cost { font-size: 0.78rem; margin-top: 0.15rem; }
@@ -371,7 +383,7 @@ textarea.input { resize: vertical; }
.costs-total { margin-bottom: 0.9rem; padding: 0.9rem; text-align: center; background: linear-gradient(135deg, #0f766e, #059669); color: #fff; border-radius: var(--radius-sm); display: flex; flex-direction: column; }
.costs-total-value { font-size: 1.7rem; font-weight: 800; }
.costs-total-label { font-size: 0.78rem; opacity: 0.9; }
.costs-warn { background: #fffbeb; border: 1px solid #fde68a; color: #92400e; font-size: 0.82rem; padding: 0.5rem 0.7rem; border-radius: var(--radius-sm); margin-bottom: 0.9rem; }
.costs-warn { background: var(--warn-soft-bg); border: 1px solid var(--warn-soft-border); color: var(--warn-soft-text); font-size: 0.82rem; padding: 0.5rem 0.7rem; border-radius: var(--radius-sm); margin-bottom: 0.9rem; }
.cost-block { margin-top: 1rem; }
.cost-block h3 { margin-bottom: 0.5rem; }
.bytype-row { display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0; font-size: 0.88rem; }
@@ -446,7 +458,7 @@ textarea.input { resize: vertical; }
/* Secondary "dropoff" calendar chip */
.cal-chip-dropoff { background: transparent; border: 1px dashed var(--chip); border-left: 3px dashed var(--chip); color: var(--text-muted); cursor: pointer; font-style: italic; }
.cal-chip-dropoff:hover { background: color-mix(in srgb, var(--chip) 10%, white); }
.cal-chip-dropoff:hover { background: color-mix(in srgb, var(--chip) 10%, var(--surface)); }
/* Over-budget driven distance */
.km-row.km-over .km-row-value { color: var(--danger); }