chore: release v0.23.5

This commit is contained in:
Ulas Kalayci
2026-04-22 08:42:20 +02:00
parent e5669fec24
commit a30a069d05
6 changed files with 62 additions and 23 deletions
+11
View File
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.23.5] - 2026-04-22
### Changed
- Dashboard: each widget now uses its module accent color (green for tasks, violet for calendar, orange for meals, pink for shopping, amber for notes) for its header icon, badge, and link instead of the global indigo accent
- Dashboard: meal slots now display their type-specific color (amber for breakfast, green for lunch, indigo for dinner, orange for snack) on icon and label when a meal is planned
- Dashboard: pinned note cards now show a subtle background tint matching the note's color
- Dashboard: widget and card hover lift increased from 1 px to 2 px for more perceptible feedback on desktop
- Navigation: active bottom-nav tab now shows a pill-shaped highlight behind the icon for a clearer location indicator
- Shopping widget: progress bar height increased from 4 px to 6 px for better visual weight
- Empty state icons inside widgets now use the tertiary text color instead of the disabled color for improved visibility
## [0.23.4] - 2026-04-22
### Changed
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "oikos",
"version": "0.23.4",
"version": "0.23.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "oikos",
"version": "0.23.4",
"version": "0.23.5",
"license": "MIT",
"dependencies": {
"bcrypt": "^6.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "oikos",
"version": "0.23.4",
"version": "0.23.5",
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
"main": "server/index.js",
"type": "module",
+9 -9
View File
@@ -215,7 +215,7 @@ function renderGreeting(user, stats = {}) {
function renderUrgentTasks(tasks) {
if (!tasks.length) {
return `<div class="widget">
return `<div class="widget widget--tasks">
${widgetHeader('check-square', t('nav.tasks'), 0, '/tasks')}
<div class="widget__empty">
<i data-lucide="check-circle" class="empty-state__icon" style="color:var(--color-success)" aria-hidden="true"></i>
@@ -241,7 +241,7 @@ function renderUrgentTasks(tasks) {
`;
}).join('');
return `<div class="widget">
return `<div class="widget widget--tasks">
${widgetHeader('check-square', t('nav.tasks'), tasks.length, '/tasks')}
<div class="widget__body">${items}</div>
</div>`;
@@ -249,7 +249,7 @@ function renderUrgentTasks(tasks) {
function renderUpcomingEvents(events) {
if (!events.length) {
return `<div class="widget">
return `<div class="widget widget--calendar">
${widgetHeader('calendar', t('nav.calendar'), 0, '/calendar')}
<div class="widget__empty">
<i data-lucide="calendar-check" class="empty-state__icon" aria-hidden="true"></i>
@@ -280,7 +280,7 @@ function renderUpcomingEvents(events) {
`;
}).join('');
return `<div class="widget">
return `<div class="widget widget--calendar">
${widgetHeader('calendar', t('nav.calendar'), events.length, '/calendar')}
<div class="widget__body">${items}</div>
</div>`;
@@ -293,7 +293,7 @@ function renderTodayMeals(meals) {
const slots = MEAL_ORDER.map((type) => {
const meal = meals.find((m) => m.meal_type === type);
return `
<div class="meal-slot ${meal ? 'meal-slot--filled' : ''}" data-route="/meals" role="button" tabindex="0">
<div class="meal-slot ${meal ? 'meal-slot--filled' : ''}" data-type="${type}" data-route="/meals" role="button" tabindex="0">
<i data-lucide="${MEAL_ICONS[type]}" class="meal-slot__icon" aria-hidden="true"></i>
<div class="meal-slot__type">${mealLabels[type]}</div>
<div class="meal-slot__title">${meal ? esc(meal.title) : '-'}</div>
@@ -309,7 +309,7 @@ function renderTodayMeals(meals) {
function renderPinnedNotes(notes) {
if (!notes.length) {
return `<div class="widget">
return `<div class="widget widget--notes">
${widgetHeader('pin', t('nav.notes'), 0, '/notes')}
<div class="widget__empty">
<i data-lucide="sticky-note" class="empty-state__icon" aria-hidden="true"></i>
@@ -326,7 +326,7 @@ function renderPinnedNotes(notes) {
</div>
`).join('');
return `<div class="widget widget--wide">
return `<div class="widget widget--notes widget--wide">
${widgetHeader('pin', t('nav.notes'), notes.length, '/notes')}
<div class="notes-grid-widget">${items}</div>
</div>`;
@@ -373,7 +373,7 @@ function renderShoppingLists(lists) {
`;
}).join('');
return `<div class="widget">
return `<div class="widget widget--shopping">
${widgetHeader('shopping-cart', t('nav.shopping'), totalOpen, '/shopping')}
<div class="widget__body">${listsHtml}</div>
</div>`;
@@ -409,7 +409,7 @@ function renderWeatherWidget(weather) {
}).join('');
return `
<div class="widget weather-widget" id="weather-widget">
<div class="widget widget--weather weather-widget" id="weather-widget">
<button class="weather-widget__refresh" id="weather-refresh-btn" aria-label="${t('dashboard.weatherRefresh')}" title="${t('dashboard.weatherRefreshTitle')}">
<i data-lucide="refresh-cw" class="icon-md" aria-hidden="true"></i>
</button>
+29 -10
View File
@@ -158,6 +158,19 @@
background: var(--color-warning-translucent);
}
/* --------------------------------------------------------
* Widget-Modul-Akzentfarben
* Jedes Widget nutzt seine eigene Modulfarbe für Icon, Badge und Link.
* -------------------------------------------------------- */
.widget--tasks { --widget-accent: var(--module-tasks); }
.widget--calendar { --widget-accent: var(--module-calendar); }
.widget--shopping { --widget-accent: var(--module-shopping); }
.widget--meals { --widget-accent: var(--module-meals); }
.widget--notes { --widget-accent: var(--module-notes); }
.widget--budget { --widget-accent: var(--module-budget); }
.widget--contacts { --widget-accent: var(--module-contacts); }
.widget--weather { --widget-accent: var(--module-dashboard); }
/* --------------------------------------------------------
* Basis-Widget (Card)
*
@@ -209,12 +222,12 @@
.widget__title-icon {
width: 16px;
height: 16px;
color: var(--color-accent);
color: var(--widget-accent, var(--color-accent));
}
.widget__link {
font-size: var(--text-xs);
color: var(--color-accent);
color: var(--widget-accent, var(--color-accent));
font-weight: var(--font-weight-medium);
display: inline-flex;
align-items: center;
@@ -230,7 +243,7 @@
display: inline-flex;
align-items: center;
justify-content: center;
background-color: var(--color-accent);
background-color: var(--widget-accent, var(--color-accent));
color: var(--color-text-on-accent);
font-size: var(--text-xs);
font-weight: var(--font-weight-bold);
@@ -260,17 +273,17 @@
.widget__empty .empty-state__icon {
width: 28px;
height: 28px;
color: var(--color-text-disabled);
color: var(--color-text-tertiary);
margin-bottom: var(--space-1);
}
/* Widget hover lift (desktop) - dezent, max 1px */
/* Widget hover lift (desktop) */
@media (min-width: 1024px) {
.widget {
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
.widget:hover {
transform: translateY(-1px);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
}
@@ -470,6 +483,12 @@
border-bottom-right-radius: var(--radius-md);
}
/* Meal-Typ-Farben aus tokens.css */
.meal-slot[data-type="breakfast"] { --slot-color: var(--meal-breakfast); }
.meal-slot[data-type="lunch"] { --slot-color: var(--meal-lunch); }
.meal-slot[data-type="dinner"] { --slot-color: var(--meal-dinner); }
.meal-slot[data-type="snack"] { --slot-color: var(--meal-snack); }
.meal-slot__icon {
width: 18px;
height: 18px;
@@ -478,7 +497,7 @@
}
.meal-slot--filled .meal-slot__icon {
color: var(--color-accent);
color: var(--slot-color, var(--color-accent));
}
.meal-slot__type {
@@ -490,7 +509,7 @@
}
.meal-slot--filled .meal-slot__type {
color: var(--color-text-secondary);
color: var(--slot-color, var(--color-text-secondary));
}
.meal-slot__title {
@@ -571,7 +590,7 @@
}
.shopping-widget-list__progress {
height: 4px;
height: 6px;
background-color: var(--color-surface-3);
border-radius: var(--radius-full);
overflow: hidden;
@@ -649,7 +668,7 @@
cursor: pointer;
transition: opacity var(--transition-fast), transform var(--transition-fast);
border-left: 3px solid var(--note-color, var(--color-accent));
background-color: var(--color-surface-2);
background-color: color-mix(in srgb, var(--note-color, var(--color-accent)) 6%, var(--color-surface-2));
}
.note-item:hover {
+10 -1
View File
@@ -408,6 +408,15 @@
color: var(--active-module-accent, var(--color-accent));
}
/* Pill-Hintergrund hinter dem Icon (nur Bottom-Nav, nicht Sidebar) */
.nav-bottom .nav-item[aria-current="page"] .nav-item__icon-wrap {
background-color: color-mix(in srgb, var(--active-module-accent, var(--color-accent)) 14%, transparent);
border-radius: var(--radius-lg);
padding: var(--space-1) var(--space-4);
margin-bottom: 1px;
transition: background-color var(--transition-fast);
}
.nav-item__icon {
width: var(--space-5);
height: var(--space-5);
@@ -776,7 +785,7 @@
}
.card--interactive:hover {
transform: translateY(-1px);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}