Release Nouri 1.3.2 with mobile selection improvements

This commit is contained in:
2026-04-29 10:54:40 +02:00
parent 1034ea72a8
commit 8fc2492918
12 changed files with 402 additions and 41 deletions
+173
View File
@@ -659,6 +659,10 @@ h3 {
margin: 0 0 1rem;
}
.selected-component-stack.is-live {
margin: 0.8rem 0 1rem;
}
.selected-components-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
@@ -677,6 +681,10 @@ h3 {
align-content: start;
}
.selected-component-card.is-needed {
border-color: color-mix(in srgb, var(--accent) 42%, var(--line) 58%);
}
.selected-component-main {
display: grid;
gap: 0.15rem;
@@ -692,6 +700,103 @@ h3 {
line-height: 1.25;
}
.selected-component-main small {
color: var(--muted);
font-size: 0.82rem;
line-height: 1.25;
}
.meal-component-search {
display: grid;
gap: 0.75rem;
}
.meal-component-results {
display: grid;
gap: 0.75rem;
}
.meal-component-option-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
gap: 0.75rem;
}
.meal-component-option {
position: relative;
display: block;
}
.meal-component-option input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.meal-component-option-card {
min-height: 92px;
display: grid;
grid-template-columns: 58px minmax(0, 1fr);
gap: 0.85rem;
align-items: center;
padding: 0.85rem 0.95rem;
border-radius: 22px;
border: 1px solid var(--line);
background: color-mix(in srgb, var(--surface-strong) 86%, var(--accent-soft) 14%);
color: var(--text);
cursor: pointer;
transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
}
.meal-component-option-card:hover {
transform: translateY(-1px);
border-color: color-mix(in srgb, var(--accent) 38%, var(--line) 62%);
}
.meal-component-option input:focus-visible + .meal-component-option-card {
outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent 55%);
outline-offset: 3px;
}
.meal-component-option input:checked + .meal-component-option-card {
border-color: color-mix(in srgb, var(--accent) 58%, var(--line) 42%);
background: color-mix(in srgb, var(--accent-soft) 28%, var(--surface-strong) 72%);
box-shadow: 0 12px 28px color-mix(in srgb, var(--accent) 13%, transparent 87%);
}
.meal-component-option-visual {
width: 58px;
height: 58px;
border-radius: 18px;
overflow: hidden;
display: grid;
place-items: center;
background: color-mix(in srgb, var(--surface-soft) 84%, transparent 16%);
border: 1px solid color-mix(in srgb, var(--line) 80%, transparent 20%);
}
.meal-component-option-visual img {
width: 100%;
height: 100%;
object-fit: cover;
}
.meal-component-option-visual .ui-icon {
width: 1.2rem;
height: 1.2rem;
}
.meal-component-option-copy {
min-width: 0;
display: grid;
gap: 0.22rem;
}
.meal-component-option-copy strong {
overflow-wrap: anywhere;
line-height: 1.18;
}
.selected-component-visual {
display: flex;
align-items: center;
@@ -765,6 +870,42 @@ h3 {
border-color: color-mix(in srgb, rgba(243, 177, 125, 0.36) 48%, var(--line) 52%);
}
[data-theme="dark"] .selected-component-card.is-needed,
[data-theme="dark"] .meal-component-option input:checked + .meal-component-option-card {
border-color: color-mix(in srgb, rgba(243, 177, 125, 0.54) 52%, var(--line) 48%);
}
[data-theme="dark"] .meal-component-option-card {
background: linear-gradient(
180deg,
color-mix(in srgb, var(--surface-soft) 70%, #4a3e3a 30%),
color-mix(in srgb, var(--surface) 92%, #241f1d 8%)
);
border-color: color-mix(in srgb, var(--line) 58%, rgba(243, 177, 125, 0.16) 42%);
color: var(--text);
}
[data-theme="dark"] .meal-component-option input:checked + .meal-component-option-card {
background: linear-gradient(
180deg,
color-mix(in srgb, var(--surface-soft) 58%, #5d4a40 42%),
color-mix(in srgb, var(--surface) 88%, #302824 12%)
);
}
[data-theme="dark"] .meal-component-option-visual {
background: linear-gradient(
180deg,
color-mix(in srgb, var(--surface-soft) 60%, #453a37 40%),
color-mix(in srgb, var(--surface) 90%, #2b2523 10%)
);
border-color: color-mix(in srgb, var(--line) 58%, rgba(243, 177, 125, 0.18) 42%);
}
[data-theme="dark"] .food-status-badge.is-needed {
color: color-mix(in srgb, var(--accent-strong) 82%, white 18%);
}
[data-theme="dark"] .selected-component-fallback {
background:
linear-gradient(
@@ -1226,6 +1367,10 @@ h3 {
align-items: end;
}
.shopping-add-form .shopping-add-results {
grid-column: 1 / -1;
}
.shopping-add-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
@@ -1256,6 +1401,12 @@ h3 {
border-color: color-mix(in srgb, var(--line) 72%, var(--accent) 28%);
}
.shopping-add-card[hidden],
.meal-component-option[hidden],
.selected-component-card[data-selected-preview-card][hidden] {
display: none;
}
.shopping-add-card-visual,
.shopping-add-card-fallback,
.shopping-entry-visual,
@@ -1301,6 +1452,23 @@ h3 {
font-size: 0.92rem;
}
.food-status-badge {
width: fit-content;
display: inline-flex;
align-items: center;
gap: 0.28rem;
color: var(--muted);
}
.food-status-badge .ui-icon {
width: 0.86rem;
height: 0.86rem;
}
.food-status-badge.is-needed {
color: color-mix(in srgb, var(--accent-strong) 72%, var(--text) 28%);
}
.shopping-entry-card {
position: relative;
display: grid;
@@ -2904,6 +3072,11 @@ legend {
mask-image: url("../icons/fa/cart-shopping.svg");
}
.icon-shopping-cart {
-webkit-mask-image: url("../icons/fa/shopping-cart.svg");
mask-image: url("../icons/fa/shopping-cart.svg");
}
.icon-calendar {
-webkit-mask-image: url("../icons/fa/calendar.svg");
mask-image: url("../icons/fa/calendar.svg");
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M0 0L72 0C125 0 168 43 168 96L168 120L576 120L516.9 359.3C506.2 402 467.8 432 423.8 432L168 432C115 432 72 389 72 336L72 96C72 69.5 50.5 48 24 48L0 48L0 0zM168 168L168 336C168 362.5 189.5 384 216 384L375.8 384C397.8 384 417 369 422.4 347.6L467.3 168L168 168zM240 528C240 554.5 225.6 576 192 576C158.4 576 144 554.5 144 528C144 501.5 158.4 480 192 480C225.6 480 240 501.5 240 528zM384 576C417.6 576 432 554.5 432 528C432 501.5 417.6 480 384 480C350.4 480 336 501.5 336 528C336 554.5 350.4 576 384 576z"/></svg>

After

Width:  |  Height:  |  Size: 768 B

+54 -1
View File
@@ -112,6 +112,7 @@
const filterGroups = Array.from(container.querySelectorAll("[data-filter-group]"));
const resultLimit = Number.parseInt(input.getAttribute("data-filter-limit") || "", 10);
const hasLimit = Number.isFinite(resultLimit) && resultLimit > 0;
const hideWhenEmpty = input.hasAttribute("data-filter-hide-empty");
const scoreItem = (label, term) => {
if (label === term) return 0;
@@ -137,11 +138,13 @@
const term = input.value.trim().toLowerCase();
if (!term) {
items.forEach((item, index) => {
item.hidden = hasLimit ? index >= resultLimit : false;
item.hidden = hideWhenEmpty || (hasLimit ? index >= resultLimit : false);
});
container.hidden = hideWhenEmpty;
syncGroups();
return;
}
container.hidden = false;
const rankedMatches = items
.map((item, index) => {
@@ -167,6 +170,55 @@
});
};
const initSelectedPreviews = () => {
document.querySelectorAll("[data-selected-preview]").forEach((preview) => {
const form = preview.closest("form");
const sourceSelector = preview.getAttribute("data-selected-preview");
if (!form || !sourceSelector) return;
const source = document.querySelector(sourceSelector);
if (!source) return;
const emptyText = preview.querySelector("[data-selected-preview-empty]");
const cards = Array.from(preview.querySelectorAll("[data-selected-preview-card]"));
const sync = () => {
let visibleCount = 0;
cards.forEach((card) => {
const value = card.getAttribute("data-selected-preview-card");
const input = value
? Array.from(form.querySelectorAll('input[name="component_ids"]')).find((candidate) => candidate.value === value)
: null;
const checked = input instanceof HTMLInputElement && input.checked;
card.hidden = !checked;
if (checked) visibleCount += 1;
});
if (emptyText) {
emptyText.hidden = visibleCount > 0;
}
};
source.addEventListener("change", (event) => {
const target = event.target;
if (target instanceof HTMLInputElement && target.name === "component_ids") {
sync();
}
});
preview.addEventListener("click", (event) => {
const button = event.target.closest("[data-uncheck-component]");
if (!(button instanceof HTMLElement)) return;
const value = button.getAttribute("data-uncheck-component");
if (!value) return;
const input = Array.from(form.querySelectorAll('input[name="component_ids"]')).find((candidate) => candidate.value === value);
if (input instanceof HTMLInputElement) {
input.checked = false;
input.dispatchEvent(new Event("change", { bubbles: true }));
}
});
sync();
});
};
const initIosPullToRefresh = () => {
const isAppleTouchDevice = /iP(ad|hone|od)/.test(navigator.userAgent)
|| (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
@@ -270,6 +322,7 @@
initPostFormScrollMemory();
initMobileSheet();
initFilterInputs();
initSelectedPreviews();
initIosPullToRefresh();
initDialogs();
});
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE_NAME = "nouri-v1-0-0";
const CACHE_NAME = "nouri-v1-3-2";
const OFFLINE_URL = "/static/pwa/offline.html";
const STATIC_ASSETS = [
"/static/css/styles.css",