diff --git a/backend/main.py b/backend/main.py index 9c122f4..dade943 100644 --- a/backend/main.py +++ b/backend/main.py @@ -97,6 +97,18 @@ def _migrate(): except Exception: pass + try: + conn.execute(text("ALTER TABLE user_settings ADD COLUMN month_divider_color VARCHAR(7) DEFAULT '#7090c0'")) + conn.commit() + except Exception: + pass + + try: + conn.execute(text("ALTER TABLE user_settings ADD COLUMN month_label_color VARCHAR(7) DEFAULT '#7090c0'")) + conn.commit() + except Exception: + pass + _migrate() app = FastAPI(title="Calendarr", docs_url=None, redoc_url=None) diff --git a/backend/models.py b/backend/models.py index 92a57ec..1892076 100644 --- a/backend/models.py +++ b/backend/models.py @@ -82,6 +82,8 @@ class UserSettings(Base): line_contrast = Column(Integer, default=3) hour_height = Column(Integer, default=60) language = Column(String(5), default="de") + month_divider_color = Column(String(7), default="#7090c0") + month_label_color = Column(String(7), default="#7090c0") user = relationship("User", back_populates="settings") diff --git a/backend/routers/settings_router.py b/backend/routers/settings_router.py index 967b4c1..a82ecb0 100644 --- a/backend/routers/settings_router.py +++ b/backend/routers/settings_router.py @@ -22,6 +22,8 @@ class SettingsUpdate(BaseModel): line_contrast: Optional[int] = None hour_height: Optional[int] = None language: Optional[str] = None + month_divider_color: Optional[str] = None + month_label_color: Optional[str] = None def _settings_dict(s: models.UserSettings) -> dict: @@ -36,6 +38,8 @@ def _settings_dict(s: models.UserSettings) -> dict: "line_contrast": s.line_contrast or 3, "hour_height": s.hour_height or 60, "language": s.language or "de", + "month_divider_color": s.month_divider_color or "#7090c0", + "month_label_color": s.month_label_color or "#7090c0", } diff --git a/frontend/css/app.css b/frontend/css/app.css index 944e58a..eb1455d 100644 --- a/frontend/css/app.css +++ b/frontend/css/app.css @@ -497,6 +497,30 @@ a { color: var(--primary); text-decoration: none; } border-radius: 50%; flex-shrink: 0; } .cell-day.today { background: var(--today-color); color: #fff; font-weight: 700; } + +/* Month boundary marker: thicker line + month abbreviation on the 1st */ +.month-col.first-of-month { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0; +} +.month-col.month-divider-left { + box-shadow: inset 3px 0 0 0 var(--month-divider-color, #7090c0); +} +.month-row.month-divider-top { + box-shadow: inset 0 3px 0 0 var(--month-divider-color, #7090c0); +} +.month-marker { + font-size: 14px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: .5px; + color: var(--month-label-color, #7090c0); + line-height: 1; + padding: 0 2px; + margin-bottom: -2px; +} /* Events overlay — pointer-events:none so clicks pass to columns */ .month-events-overlay { position: absolute; top: 30px; left: 0; right: 0; bottom: 0; diff --git a/frontend/index.html b/frontend/index.html index 6043c48..b145cc6 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -603,6 +603,20 @@
+Helligkeit der Beschriftungen und Texte
diff --git a/frontend/js/calendar.js b/frontend/js/calendar.js index 7f65df4..061431e 100644 --- a/frontend/js/calendar.js +++ b/frontend/js/calendar.js @@ -1966,9 +1966,11 @@ function openSettingsModal() { document.getElementById('cfg-default-view').value = s.default_view || 'month'; document.getElementById('cfg-week-start').value = s.week_start_day || 'monday'; const colors = [ - { id: 'cfg-primary', val: s.primary_color || '#4285f4' }, - { id: 'cfg-accent', val: s.accent_color || '#ea4335' }, - { id: 'cfg-today', val: s.today_color || '#4285f4' }, + { id: 'cfg-primary', val: s.primary_color || '#4285f4' }, + { id: 'cfg-accent', val: s.accent_color || '#ea4335' }, + { id: 'cfg-today', val: s.today_color || '#4285f4' }, + { id: 'cfg-month-divider', val: s.month_divider_color || '#7090c0' }, + { id: 'cfg-month-label', val: s.month_label_color || '#7090c0' }, ]; colors.forEach(({ id, val }) => { document.getElementById(id + '-hex').value = val.toUpperCase(); @@ -2291,7 +2293,7 @@ async function loadUsers() { } function bindSettingsModal() { - ['cfg-primary','cfg-accent','cfg-today'].forEach(prefix => { + ['cfg-primary','cfg-accent','cfg-today','cfg-month-divider','cfg-month-label'].forEach(prefix => { const preview = document.getElementById(prefix + '-preview'); const hex = document.getElementById(prefix + '-hex'); preview.addEventListener('click', async () => { @@ -2345,16 +2347,18 @@ function bindSettingsModal() { return btn ? Number(btn.dataset.val) : null; }; const settings = { - default_view: document.getElementById('cfg-default-view').value, - week_start_day: document.getElementById('cfg-week-start').value, - primary_color: document.getElementById('cfg-primary-hex').value, - accent_color: document.getElementById('cfg-accent-hex').value, - today_color: document.getElementById('cfg-today-hex').value, - dim_past_events: document.getElementById('cfg-dim-past').checked, - text_contrast: getActive('cfg-text-contrast') || 3, - line_contrast: getActive('cfg-line-contrast') || 3, - hour_height: getActive('cfg-hour-height') || 44, - language: document.getElementById('cfg-language').value, + default_view: document.getElementById('cfg-default-view').value, + week_start_day: document.getElementById('cfg-week-start').value, + primary_color: document.getElementById('cfg-primary-hex').value, + accent_color: document.getElementById('cfg-accent-hex').value, + today_color: document.getElementById('cfg-today-hex').value, + month_divider_color: document.getElementById('cfg-month-divider-hex').value, + month_label_color: document.getElementById('cfg-month-label-hex').value, + dim_past_events: document.getElementById('cfg-dim-past').checked, + text_contrast: getActive('cfg-text-contrast') || 3, + line_contrast: getActive('cfg-line-contrast') || 3, + hour_height: getActive('cfg-hour-height') || 44, + language: document.getElementById('cfg-language').value, }; try { await api.put('/settings/', settings); diff --git a/frontend/js/i18n.js b/frontend/js/i18n.js index 66a15f4..b484017 100644 --- a/frontend/js/i18n.js +++ b/frontend/js/i18n.js @@ -65,6 +65,8 @@ const translations = { settings_colors: 'Farben', settings_primary_color: 'Primärfarbe', settings_accent_color: 'Akzentfarbe', settings_today_color: 'Heutige-Tag-Farbe', + settings_month_divider_color: 'Monatswechsel-Linie', + settings_month_label_color: 'Monatskürzel-Farbe', settings_text_contrast: 'Schriftkontrast', settings_text_contrast_desc: 'Helligkeit der Beschriftungen und Texte', contrast_dark: 'Dunkel', contrast_medium: 'Mittel', @@ -274,6 +276,8 @@ const translations = { settings_colors: 'Colors', settings_primary_color: 'Primary color', settings_accent_color: 'Accent color', settings_today_color: 'Today highlight color', + settings_month_divider_color: 'Month divider line', + settings_month_label_color: 'Month label color', settings_text_contrast: 'Text contrast', settings_text_contrast_desc: 'Brightness of labels and text', contrast_dark: 'Dark', contrast_medium: 'Medium', diff --git a/frontend/js/utils.js b/frontend/js/utils.js index 58a151a..5db6786 100644 --- a/frontend/js/utils.js +++ b/frontend/js/utils.js @@ -94,6 +94,9 @@ export function applyTheme(settings) { const hh = settings.hour_height || 44; root.style.setProperty('--hour-h', hh + 'px'); + + root.style.setProperty('--month-divider-color', settings.month_divider_color || '#7090c0'); + root.style.setProperty('--month-label-color', settings.month_label_color || '#7090c0'); } function hexToRgba(hex, alpha) { diff --git a/frontend/js/views/month.js b/frontend/js/views/month.js index b9a3431..fb65ac3 100644 --- a/frontend/js/views/month.js +++ b/frontend/js/views/month.js @@ -121,8 +121,9 @@ export function renderMonth(container, currentDate, events, onDayClick, onEventC }); // Full-height column divs (click targets + borders) + const monthsShort = t('months_short'); let colsHtml = ''; - rowCells.forEach(cell => { + rowCells.forEach((cell, idx) => { const key = dateKey(cell); const isOther = cell.getMonth() !== primaryMonth; const todayCls = isToday(cell) ? 'today' : ''; @@ -130,12 +131,24 @@ export function renderMonth(container, currentDate, events, onDayClick, onEventC const selDate = selectedDate || currentDate; const selectedCls = isSameDay(cell, selDate) ? 'month-selected' : ''; const numCls = isToday(cell) ? 'today' : ''; - colsHtml += `