feat(web): theme import/export, more themable colors, hideable local/ical calendars
Theme: - Export/import themes via UI as <date>_<time>.theme (JSON, readable keys, link to THEME.md). Import is partial-aware: only present params are written (server accepts partial); prompts before importing files with unknown params. - Dynamic favicon + theme-color tinted to the primary colour on load/save. - New themable colours: general hover-highlight, day hover/selected/bg, today background, plus two unified sidebar action-icon colours (inactive/active) covering bell, hide, delete and read-only icons. - All new colours are per-setting syncable; documented in THEME.md. UX: - Styled confirm dialog (#modal-confirm) replaces window.confirm() for calendar delete and account disconnect. - Birthday/local calendars and iCal subscriptions can now be hidden from the sidebar via Settings (new sidebar_hidden column + hide toggle). Backend: additive nullable columns + idempotent migrations for user_settings colours and local_calendars/ical_subscriptions.sidebar_hidden. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,17 @@
|
||||
--border-light: #242438;
|
||||
--scrollbar: #30303c;
|
||||
|
||||
/* Fine-grained element colours (customisable via Settings → Farben).
|
||||
Defaults reference the derived values so the look is unchanged until the
|
||||
user overrides them; applyTheme() writes concrete overrides on top. */
|
||||
--hover-highlight: var(--bg-hover); /* general interactive hover */
|
||||
--icon-inactive-color: var(--text-2); /* sidebar action icons: resting/off/not-hovered */
|
||||
--icon-active-color: var(--text-1); /* sidebar action icons: hovered / on / pressed */
|
||||
--day-hover-color: var(--bg-hover); /* calendar day-cell hover */
|
||||
--day-selected-base: var(--primary); /* selected day (tinted via color-mix) */
|
||||
--day-bg: transparent; /* normal day background */
|
||||
--today-bg-base: var(--today-color); /* today's day (tinted via color-mix) */
|
||||
|
||||
--topbar-h: 64px;
|
||||
--sidebar-w: 256px;
|
||||
--shadow: 0 2px 12px rgba(0,0,0,.45);
|
||||
@@ -201,7 +212,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
transform .1s ease;
|
||||
}
|
||||
.icon-btn svg { width: 20px; height: 20px; fill: currentColor; }
|
||||
.icon-btn:hover { background: var(--bg-hover); color: var(--text-1); }
|
||||
.icon-btn:hover { background: var(--hover-highlight); color: var(--text-1); }
|
||||
.icon-btn:active { transform: scale(.92); }
|
||||
.icon-btn:focus-visible {
|
||||
outline: 2px solid var(--primary);
|
||||
@@ -310,7 +321,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 9px 10px; border-radius: 8px; cursor: pointer;
|
||||
}
|
||||
.cal-select-option:hover { background: var(--bg-hover); }
|
||||
.cal-select-option:hover { background: var(--hover-highlight); }
|
||||
.cal-select-option.selected { background: var(--primary-dim); }
|
||||
|
||||
/* ── Date/time input dark mode ──────────────────────────── */
|
||||
@@ -481,7 +492,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
transition: background var(--transition), color var(--transition);
|
||||
border-radius: 20px;
|
||||
}
|
||||
.view-btn:hover { background: var(--bg-hover); color: var(--text-1); }
|
||||
.view-btn:hover { background: var(--hover-highlight); color: var(--text-1); }
|
||||
.view-btn.active { background: var(--primary-dim); color: var(--primary); }
|
||||
|
||||
.user-menu-wrapper { position: relative; }
|
||||
@@ -510,7 +521,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
background: none; color: var(--text-2); font-size: 13px;
|
||||
cursor: pointer; transition: background var(--transition);
|
||||
}
|
||||
.dropdown-item:hover { background: var(--bg-hover); color: var(--text-1); }
|
||||
.dropdown-item:hover { background: var(--hover-highlight); color: var(--text-1); }
|
||||
.dropdown-item svg { flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -555,7 +566,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
.mini-cal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
||||
.mini-title { font-size: 13px; font-weight: 500; color: var(--text-1); }
|
||||
.mini-btn { width: 28px; height: 28px; font-size: 18px; color: var(--text-2); }
|
||||
.mini-btn:hover { color: var(--text-1); background: var(--bg-hover); }
|
||||
.mini-btn:hover { color: var(--text-1); background: var(--hover-highlight); }
|
||||
.mini-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); text-align: center; }
|
||||
.mini-dow { font-size: 11px; color: var(--text-3); padding: 2px 0; font-weight: 500; }
|
||||
.mini-cal-days { display: grid; grid-template-columns: repeat(7, 1fr); text-align: center; }
|
||||
@@ -566,13 +577,13 @@ a { color: var(--primary); text-decoration: none; }
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
margin: 1px auto; position: relative;
|
||||
}
|
||||
.mini-day:hover { background: var(--bg-hover); color: var(--text-1); }
|
||||
.mini-day:hover { background: var(--day-hover-color); color: var(--text-1); }
|
||||
.mini-day.other-month { color: var(--text-3); }
|
||||
.mini-day.today {
|
||||
background: var(--today-color);
|
||||
color: #fff; font-weight: 700;
|
||||
}
|
||||
.mini-day.selected:not(.today) { background: var(--primary-dim); color: var(--primary); font-weight: 600; }
|
||||
.mini-day.selected:not(.today) { background: color-mix(in srgb, var(--day-selected-base) 15%, transparent); color: var(--primary); font-weight: 600; }
|
||||
.mini-day.has-events::after {
|
||||
content: ''; position: absolute; bottom: 2px; left: 50%; transform: translateX(-50%);
|
||||
width: 4px; height: 4px; border-radius: 50%; background: var(--primary);
|
||||
@@ -599,7 +610,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
text-align: left; font-size: 13px; color: var(--text-1);
|
||||
background: none; border: none; cursor: pointer;
|
||||
}
|
||||
.add-cal-dropdown button:hover { background: var(--bg-hover); }
|
||||
.add-cal-dropdown button:hover { background: var(--hover-highlight); }
|
||||
.cal-item {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 6px 16px; cursor: pointer;
|
||||
@@ -607,7 +618,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
border-radius: 0 20px 20px 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.cal-item:hover { background: var(--bg-hover); }
|
||||
.cal-item:hover { background: var(--hover-highlight); }
|
||||
.cal-item-dot {
|
||||
width: 14px; height: 14px; border-radius: 3px; flex-shrink: 0; cursor: pointer;
|
||||
}
|
||||
@@ -624,11 +635,16 @@ a { color: var(--primary); text-decoration: none; }
|
||||
.cal-account-name { font-size: 11px; color: var(--text-3); padding: 4px 16px 2px; font-weight: 500; }
|
||||
/* Fixed min-height so 28px mini-btns never change row height when they appear. */
|
||||
.cal-item { position: relative; min-height: 40px; }
|
||||
.cal-item-bell { display: none; flex-shrink: 0; }
|
||||
/* Sidebar action icons (bell / hide / delete / read-only flag) share two theme
|
||||
colours: "inactive" (resting/off) and "active" (hovered/on/pressed). */
|
||||
.cal-item-bell { display: none; flex-shrink: 0; color: var(--icon-active-color); } /* bell shown = reminders on = active */
|
||||
.cal-item:hover .cal-item-remove,
|
||||
.cal-item:hover .cal-item-bell { display: inline-flex; }
|
||||
.cal-item-bell.off { display: inline-flex; opacity: .55; color: var(--text-3); }
|
||||
.cal-item:hover .cal-item-bell.off { opacity: 1; color: inherit; }
|
||||
.cal-item-bell.off { display: inline-flex; opacity: .55; color: var(--icon-inactive-color); }
|
||||
.cal-item:hover .cal-item-bell.off { opacity: 1; color: var(--icon-inactive-color); }
|
||||
.cal-item-bell:hover, .cal-item-bell.off:hover { color: var(--icon-active-color); }
|
||||
.cal-item-remove { color: var(--icon-inactive-color); }
|
||||
.cal-item-remove:hover { color: var(--icon-active-color); }
|
||||
|
||||
/* ── Month View ─────────────────────────────────────────── */
|
||||
.month-view { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
||||
@@ -661,11 +677,12 @@ a { color: var(--primary); text-decoration: none; }
|
||||
flex: 1; border-right: 1px solid var(--border);
|
||||
cursor: pointer; transition: background var(--transition);
|
||||
padding: 4px 4px 0; min-width: 0;
|
||||
background: var(--day-bg);
|
||||
}
|
||||
.month-col:last-child { border-right: none; }
|
||||
.month-col:hover { background: var(--bg-hover); }
|
||||
.month-col.today { background: color-mix(in srgb, var(--today-color) 10%, transparent); }
|
||||
.month-col.month-selected { background: var(--primary-dim); }
|
||||
.month-col:hover { background: var(--day-hover-color); }
|
||||
.month-col.today { background: color-mix(in srgb, var(--today-bg-base) 10%, transparent); }
|
||||
.month-col.month-selected { background: color-mix(in srgb, var(--day-selected-base) 15%, transparent); }
|
||||
.month-col.month-selected .cell-day { border: 2px solid var(--primary); color: var(--primary); font-weight: 700; }
|
||||
.month-col.month-selected .cell-day.today { background: var(--today-color); color: #fff; border: none; }
|
||||
.month-col.other-month .cell-day { color: var(--text-3); }
|
||||
@@ -786,7 +803,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
padding: 4px 12px; cursor: pointer;
|
||||
border-radius: 6px; margin: 0 4px;
|
||||
}
|
||||
.mop-row:hover { background: var(--bg-hover); }
|
||||
.mop-row:hover { background: var(--hover-highlight); }
|
||||
.mop-dot {
|
||||
width: 8px; height: 8px; border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
@@ -847,7 +864,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
border-left: 1px solid var(--border); cursor: pointer;
|
||||
transition: background var(--transition);
|
||||
}
|
||||
.week-day-header:hover { background: var(--bg-hover); }
|
||||
.week-day-header:hover { background: var(--day-hover-color); }
|
||||
.week-day-header .day-name {
|
||||
font-size: 11px; font-weight: 600; text-transform: uppercase;
|
||||
letter-spacing: .5px; color: var(--text-2);
|
||||
@@ -999,7 +1016,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
border-radius: 4px;
|
||||
min-height: 36px;
|
||||
}
|
||||
.qtr-cell:hover { background: var(--bg-hover); }
|
||||
.qtr-cell:hover { background: var(--day-hover-color); }
|
||||
.qtr-cell.today .qtr-day-num {
|
||||
background: var(--today-color, var(--primary));
|
||||
color: #fff;
|
||||
@@ -1062,7 +1079,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
cursor: pointer; transition: background var(--transition);
|
||||
margin-left: 54px; margin-bottom: 4px;
|
||||
}
|
||||
.agenda-event:hover { background: var(--bg-hover); }
|
||||
.agenda-event:hover { background: var(--day-hover-color); }
|
||||
.agenda-event.past { opacity: .45; }
|
||||
.agenda-ev-color { width: 10px; height: 10px; border-radius: 50%; margin-top: 4px; flex-shrink: 0; }
|
||||
.agenda-ev-info { flex: 1; }
|
||||
@@ -1114,7 +1131,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: background var(--transition), color var(--transition);
|
||||
}
|
||||
.rec-day-btn:hover { background: var(--bg-hover); }
|
||||
.rec-day-btn:hover { background: var(--hover-highlight); }
|
||||
.rec-day-btn.active { background: var(--primary); color: #fff; border-color: var(--primary); }
|
||||
|
||||
/* ── Day Context Menu ──────────────────────────────────── */
|
||||
@@ -1127,7 +1144,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
.ctx-item {
|
||||
padding: 8px 16px; font-size: 13px; color: var(--text-1); cursor: pointer;
|
||||
}
|
||||
.ctx-item:hover { background: var(--bg-hover); }
|
||||
.ctx-item:hover { background: var(--hover-highlight); }
|
||||
|
||||
/* ── Event Popup ──────────────────────────────────────────
|
||||
Layout: Color-Dot + Title links, kleine Icon-Toolbar rechts oben.
|
||||
@@ -1251,7 +1268,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
border-radius: 10px;
|
||||
transition: background var(--transition);
|
||||
}
|
||||
.popup-copy-item:hover { background: var(--bg-hover); }
|
||||
.popup-copy-item:hover { background: var(--hover-highlight); }
|
||||
.popup-copy-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
||||
.popup-copy-edit-toggle {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
@@ -1300,7 +1317,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
background: none; border: none; cursor: pointer;
|
||||
transition: background .15s, color .15s;
|
||||
}
|
||||
.settings-nav-btn:hover { background: var(--bg-hover); color: var(--text-1); }
|
||||
.settings-nav-btn:hover { background: var(--hover-highlight); color: var(--text-1); }
|
||||
.settings-nav-btn.active { background: var(--primary-dim); color: var(--primary); font-weight: 600; }
|
||||
.settings-panels {
|
||||
flex: 1; overflow-y: auto; padding: 24px 28px;
|
||||
@@ -1338,7 +1355,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
transition: background var(--transition), color var(--transition);
|
||||
}
|
||||
.contrast-btn:last-child { border-right: none; }
|
||||
.contrast-btn:hover { background: var(--bg-hover); color: var(--text-1); }
|
||||
.contrast-btn:hover { background: var(--hover-highlight); color: var(--text-1); }
|
||||
.contrast-btn.active { background: var(--primary); color: #fff; }
|
||||
.contrast-btn span { font-size: 18px; font-weight: 700; line-height: 1; }
|
||||
.contrast-btn.active span { color: #fff !important; }
|
||||
@@ -1365,6 +1382,14 @@ a { color: var(--primary); text-decoration: none; }
|
||||
.sync-global-row .panel-desc { margin: 0; }
|
||||
|
||||
.settings-table { display: flex; flex-direction: column; }
|
||||
.theme-io-row { display: flex; align-items: center; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
|
||||
.theme-io-help {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 20px; height: 20px; border-radius: 50%;
|
||||
background: var(--bg-hover); color: var(--text-2);
|
||||
font-size: 12px; font-weight: 700; text-decoration: none;
|
||||
}
|
||||
.theme-io-help:hover { background: var(--hover-highlight); color: var(--text-1); }
|
||||
.settings-table-section {
|
||||
font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
|
||||
color: var(--text-3); margin: 18px 0 6px; padding: 0 2px;
|
||||
@@ -1421,7 +1446,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
transition: color var(--transition), background var(--transition);
|
||||
justify-self: center;
|
||||
}
|
||||
.sync-icon:hover { background: var(--bg-hover); color: var(--text-2); }
|
||||
.sync-icon:hover { background: var(--hover-highlight); color: var(--text-2); }
|
||||
.sync-icon.on { color: var(--primary); background: var(--primary-dim); }
|
||||
.sync-icon .si-on, .sync-icon .si-off { align-items: center; }
|
||||
.sync-icon .si-on { display: none; }
|
||||
@@ -1713,7 +1738,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
padding: 2px 6px; border-radius: var(--radius-sm);
|
||||
transition: background var(--transition), color var(--transition);
|
||||
}
|
||||
.dtp-nav-btn:hover { background: var(--bg-hover); color: var(--text-1); }
|
||||
.dtp-nav-btn:hover { background: var(--hover-highlight); color: var(--text-1); }
|
||||
/* Day-of-week headers */
|
||||
.dtp-grid {
|
||||
display: grid; grid-template-columns: repeat(7, 1fr);
|
||||
@@ -1730,9 +1755,9 @@ a { color: var(--primary); text-decoration: none; }
|
||||
font-size: 13px; font-weight: 500; color: var(--text-1);
|
||||
cursor: pointer; transition: background var(--transition);
|
||||
}
|
||||
.dtp-day:hover { background: var(--bg-hover); }
|
||||
.dtp-day:hover { background: var(--day-hover-color); }
|
||||
.dtp-day.other { color: var(--text-3); }
|
||||
.dtp-day.other:hover { background: var(--bg-hover); }
|
||||
.dtp-day.other:hover { background: var(--day-hover-color); }
|
||||
.dtp-day.today { color: var(--primary); font-weight: 700; }
|
||||
.dtp-day.selected {
|
||||
background: var(--primary) !important;
|
||||
@@ -2327,7 +2352,7 @@ a { color: var(--primary); text-decoration: none; }
|
||||
.group-emoji { flex: 0 0 auto; font-size: 16px; cursor: pointer; line-height: 1; }
|
||||
.cal-shared-flag { flex: 0 0 auto; font-size: 12px; opacity: .8; }
|
||||
/* Read-only (shared with me) indicator: a struck-through pencil. */
|
||||
.cal-readonly-flag { color: var(--text-3); opacity: .7; margin-left: 2px; }
|
||||
.cal-readonly-flag { color: var(--icon-inactive-color); opacity: .7; margin-left: 2px; }
|
||||
.group-icon-picker { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.group-icon-opt {
|
||||
width: 38px; height: 38px;
|
||||
|
||||
Reference in New Issue
Block a user