Apply liquid glass to filter panel in recipe list

This commit is contained in:
2026-04-22 18:42:29 +02:00
parent bc505d6b4c
commit 7049cb1d48
3 changed files with 110 additions and 29 deletions

View File

@@ -504,6 +504,7 @@
position: relative; position: relative;
border: 1px solid rgba(255, 255, 255, 0.32) !important; border: 1px solid rgba(255, 255, 255, 0.32) !important;
background: rgba(255, 255, 255, 0.2) !important; background: rgba(255, 255, 255, 0.2) !important;
background-image: none !important;
color: rgb(var(--text-body-rgb)); color: rgb(var(--text-body-rgb));
box-shadow: box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.6),
@@ -517,6 +518,7 @@
.dark #app-bottom-nav .recipe-nav-toggle { .dark #app-bottom-nav .recipe-nav-toggle {
border: 1px solid rgba(255, 255, 255, 0.12) !important; border: 1px solid rgba(255, 255, 255, 0.12) !important;
background: rgba(255, 255, 255, 0.04) !important; background: rgba(255, 255, 255, 0.04) !important;
background-image: none !important;
color: rgb(var(--text-body-rgb)); color: rgb(var(--text-body-rgb));
box-shadow: box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.24), inset 0 1px 0 rgba(255, 255, 255, 0.24),
@@ -841,6 +843,7 @@
padding: var(--dock-pad); padding: var(--dock-pad);
border-radius: var(--dock-radius); border-radius: var(--dock-radius);
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
background-image: none;
border: 1px solid rgba(255, 255, 255, 0.32); border: 1px solid rgba(255, 255, 255, 0.32);
overflow: hidden; overflow: hidden;
backdrop-filter: blur(28px) saturate(180%); backdrop-filter: blur(28px) saturate(180%);
@@ -861,6 +864,7 @@
} }
.dark #app-bottom-nav .bottom-dock { .dark #app-bottom-nav .bottom-dock {
background: rgba(255, 255, 255, 0.04); background: rgba(255, 255, 255, 0.04);
background-image: none;
border: 1px solid rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow: box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.24), inset 0 1px 0 rgba(255, 255, 255, 0.24),

View File

@@ -3,15 +3,15 @@ import { MEAL_SLOTS } from '../planner/mealSlots.js';
import { applyFilters, getFilterState } from './RecipeList.js'; import { applyFilters, getFilterState } from './RecipeList.js';
const FILTER_PANEL_TRANSITION = 'opacity 180ms ease, transform 180ms ease'; const FILTER_PANEL_TRANSITION = 'opacity 180ms ease, transform 180ms ease';
const FILTER_SURFACE = 'rgb(var(--sunken-rgb))'; const FILTER_SURFACE = 'var(--filter-liquid-panel-bg)';
const FILTER_SURFACE_SOFT = 'rgb(var(--app-bg-rgb))'; const FILTER_SURFACE_SOFT = 'var(--filter-liquid-chip-bg)';
const FILTER_BORDER = 'rgb(var(--border-input-rgb))'; const FILTER_BORDER = 'var(--filter-liquid-border)';
const FILTER_CHIP_ACTIVE_BG = 'rgb(var(--card-rgb))'; const FILTER_CHIP_ACTIVE_BG = 'var(--filter-liquid-chip-active-bg)';
const FILTER_TEXT_SECONDARY = 'rgb(var(--text-body-soft-rgb))'; const FILTER_TEXT_SECONDARY = 'var(--filter-liquid-text-secondary)';
const FILTER_TEXT_MUTED = 'rgb(var(--text-muted-rgb))'; const FILTER_TEXT_MUTED = 'var(--filter-liquid-text-muted)';
const FILTER_TEXT_ACTIVE = 'rgb(var(--text-emphasis-rgb))'; const FILTER_TEXT_ACTIVE = 'var(--filter-liquid-text-active)';
const FILTER_TRACK = 'rgb(var(--card-rgb))'; const FILTER_TRACK = 'var(--filter-liquid-track-bg)';
const FILTER_TRACK_FILL = 'rgba(var(--border-input-rgb), 0.58)'; const FILTER_TRACK_FILL = 'var(--filter-liquid-accent-bg)';
const PREP_TIME_MIN = 5; const PREP_TIME_MIN = 5;
const PREP_TIME_MAX = 120; const PREP_TIME_MAX = 120;
const PREP_TIME_STEP = 5; const PREP_TIME_STEP = 5;
@@ -57,10 +57,61 @@ function collectAllTags() {
export function getFilterHTML() { export function getFilterHTML() {
return ` return `
<style id="filter-view-styles"> <style id="filter-view-styles">
#filter-view {
--filter-liquid-panel-bg: rgba(var(--app-bg-rgb), 0.78);
--filter-liquid-panel-shine: none;
--filter-liquid-border: rgba(255, 255, 255, 0.32);
--filter-liquid-chip-bg: rgba(var(--surface-rgb), 0.68);
--filter-liquid-chip-active-bg: rgba(var(--surface-rgb), 0.92);
--filter-liquid-track-bg: rgba(var(--overlay-rgb), 0.16);
--filter-liquid-accent-bg: rgba(255, 255, 255, 0.72);
--filter-liquid-text-active: rgb(var(--text-emphasis-rgb));
--filter-liquid-text-secondary: rgb(var(--text-body-soft-rgb));
--filter-liquid-text-muted: rgb(var(--text-muted-rgb));
--filter-liquid-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.6),
inset 0 -1px 0 rgba(var(--overlay-rgb), 0.06),
0 8px 20px rgba(var(--overlay-rgb), 0.16),
0 22px 52px rgba(var(--overlay-rgb), 0.24);
}
.dark #filter-view {
--filter-liquid-panel-bg: rgba(var(--app-bg-rgb), 0.82);
--filter-liquid-border: rgba(255, 255, 255, 0.12);
--filter-liquid-chip-bg: rgba(255, 255, 255, 0.08);
--filter-liquid-chip-active-bg: rgba(255, 255, 255, 0.16);
--filter-liquid-track-bg: rgba(255, 255, 255, 0.1);
--filter-liquid-accent-bg: rgba(255, 255, 255, 0.32);
--filter-liquid-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.24),
inset 0 -1px 0 rgba(0, 0, 0, 0.22),
0 10px 24px rgba(0, 0, 0, 0.36),
0 26px 60px rgba(0, 0, 0, 0.46);
}
#filter-view.filter-open { #filter-view.filter-open {
pointer-events: auto; pointer-events: auto;
} }
#filter-panel {
isolation: isolate;
background: ${FILTER_SURFACE} !important;
background-image: none !important;
border: 1px solid ${FILTER_BORDER} !important;
box-shadow: var(--filter-liquid-shadow) !important;
backdrop-filter: blur(28px) saturate(180%);
-webkit-backdrop-filter: blur(28px) saturate(180%);
}
#filter-panel::before {
display: none;
}
#filter-panel > * {
position: relative;
z-index: 1;
}
#filter-view .prep-time-range-track, #filter-view .prep-time-range-track,
#filter-view .prep-time-range-fill { #filter-view .prep-time-range-fill {
border-radius: 9999px; border-radius: 9999px;
@@ -71,15 +122,27 @@ export function getFilterHTML() {
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
border-radius: 9999px; border-radius: 9999px;
border: 1px solid rgba(var(--text-emphasis-rgb),0.16); border: 1px solid rgba(255,255,255,0.34);
background: ${FILTER_TRACK_FILL}; background: rgba(var(--surface-rgb),0.42);
box-shadow: 0 0 0 1px rgba(var(--overlay-rgb),0.12); box-shadow:
inset 0 1px 0 rgba(255,255,255,0.42),
0 0 0 1px rgba(var(--overlay-rgb),0.1),
0 4px 10px rgba(var(--overlay-rgb),0.18);
touch-action: none; touch-action: none;
outline: none; outline: none;
} }
.dark #filter-view .prep-time-range-handle {
border-color: rgba(255,255,255,0.18);
background: rgba(255,255,255,0.26);
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.24),
0 0 0 1px rgba(0,0,0,0.22),
0 4px 12px rgba(0,0,0,0.34);
}
</style> </style>
<div id="filter-view" class="absolute inset-0 z-[70] hidden opacity-0 transition-opacity duration-150" style="pointer-events:none; background:transparent !important; background-image:none !important;" aria-hidden="true"> <div id="filter-view" class="absolute inset-0 z-[70] hidden opacity-0 transition-opacity duration-150" style="pointer-events:none; background:transparent !important; background-image:none !important;" aria-hidden="true">
<div id="filter-panel" class="absolute flex flex-col overflow-hidden rounded-[1.35rem]" style="background:${FILTER_SURFACE} !important; background-image:none !important; border:1px solid ${FILTER_BORDER} !important; opacity:0; transform:translateY(-0.5rem) scale(0.98); transform-origin:top center; transition:${FILTER_PANEL_TRANSITION}; box-shadow:var(--shadow-shell); width:min(calc(100% - 1.5rem), 22rem);"> <div id="filter-panel" class="absolute flex flex-col overflow-hidden rounded-[1.35rem]" style="opacity:0; transform:translateY(0.65rem) scale(0.98); transform-origin:bottom center; transition:${FILTER_PANEL_TRANSITION}; width:min(calc(100% - 1.5rem), 22rem);">
<div class="shrink-0 px-3 pt-3 pb-2 flex items-center justify-between gap-3"> <div class="shrink-0 px-3 pt-3 pb-2 flex items-center justify-between gap-3">
<p class="text-[11px] font-semibold leading-none" style="color:${FILTER_TEXT_ACTIVE};">Filtry</p> <p class="text-[11px] font-semibold leading-none" style="color:${FILTER_TEXT_ACTIVE};">Filtry</p>
<button id="filter-clear-btn" type="button" class="h-8 px-2 rounded-full text-[11px] font-semibold transition-colors" style="background:transparent; border:none; color:${FILTER_TEXT_MUTED};">Wyczyść</button> <button id="filter-clear-btn" type="button" class="h-8 px-2 rounded-full text-[11px] font-semibold transition-colors" style="background:transparent; border:none; color:${FILTER_TEXT_MUTED};">Wyczyść</button>
@@ -179,8 +242,8 @@ function getChipStyle(active) {
const color = active ? FILTER_TEXT_ACTIVE : FILTER_TEXT_SECONDARY; const color = active ? FILTER_TEXT_ACTIVE : FILTER_TEXT_SECONDARY;
const borderRule = active ? `border:1px solid ${FILTER_BORDER};` : 'border:none;'; const borderRule = active ? `border:1px solid ${FILTER_BORDER};` : 'border:none;';
const shadow = active const shadow = active
? 'box-shadow:inset 0 1px 0 rgba(255,255,255,0.04), 0 0 0 1px rgba(var(--overlay-rgb),0.08);' ? 'box-shadow:0 1px 2px rgba(var(--overlay-rgb),0.08);'
: ''; : 'box-shadow:none;';
return `background:${background}; ${borderRule} color:${color}; ${shadow}`; return `background:${background}; ${borderRule} color:${color}; ${shadow}`;
} }
@@ -197,6 +260,12 @@ function getSliderPercent(value) {
return ((value - PREP_TIME_MIN) / (PREP_TIME_MAX - PREP_TIME_MIN)) * 100; return ((value - PREP_TIME_MIN) / (PREP_TIME_MAX - PREP_TIME_MIN)) * 100;
} }
function getClosedPanelTransform(panel) {
return panel?.dataset.placement === 'below'
? 'translateY(-0.5rem) scale(0.98)'
: 'translateY(0.65rem) scale(0.98)';
}
function setActiveTimeHandle(activeHandle) { function setActiveTimeHandle(activeHandle) {
const minHandle = document.getElementById('prep-time-min-handle'); const minHandle = document.getElementById('prep-time-min-handle');
const maxHandle = document.getElementById('prep-time-max-handle'); const maxHandle = document.getElementById('prep-time-max-handle');
@@ -244,26 +313,33 @@ function positionFilterPanel() {
const viewRect = view.getBoundingClientRect(); const viewRect = view.getBoundingClientRect();
const anchorRect = (searchShell || button).getBoundingClientRect(); const anchorRect = (searchShell || button).getBoundingClientRect();
const gap = 8; const isRecipeContext = activeFilterContext === 'recipes';
const gap = isRecipeContext ? 12 : 8;
const margin = 12; const margin = 12;
const width = Math.min(anchorRect.width, viewRect.width - margin * 2); const maxPanelWidth = isRecipeContext ? 352 : anchorRect.width;
const width = Math.min(maxPanelWidth, viewRect.width - margin * 2);
const spaceBelow = viewRect.bottom - anchorRect.bottom - margin; const spaceBelow = viewRect.bottom - anchorRect.bottom - margin;
const preferredMaxHeight = Math.min(420, viewRect.height - margin * 2); const preferredMaxHeight = Math.min(420, viewRect.height - margin * 2);
const spaceAbove = anchorRect.top - viewRect.top - gap - margin; const spaceAbove = anchorRect.top - viewRect.top - gap - margin;
const opensUpward = spaceBelow < 260 && anchorRect.top - viewRect.top > spaceBelow; const opensUpward = isRecipeContext || (spaceBelow < 260 && anchorRect.top - viewRect.top > spaceBelow);
const maxHeight = opensUpward const maxHeight = opensUpward
? Math.max(220, Math.min(preferredMaxHeight, spaceAbove)) ? Math.max(220, Math.min(preferredMaxHeight, spaceAbove))
: Math.max(220, viewRect.height - Math.max(margin, anchorRect.bottom - viewRect.top + gap) - margin); : Math.max(220, viewRect.height - Math.max(margin, anchorRect.bottom - viewRect.top + gap) - margin);
const top = opensUpward const leftBase = isRecipeContext
? Math.max(margin, anchorRect.top - viewRect.top - gap - maxHeight) ? (viewRect.width - width) / 2
: Math.max(margin, anchorRect.bottom - viewRect.top + gap); : anchorRect.left - viewRect.left;
const left = Math.max( const left = Math.max(margin, Math.min(leftBase, viewRect.width - width - margin));
margin,
Math.min(anchorRect.left - viewRect.left, viewRect.width - width - margin),
);
panel.style.width = `${width}px`; panel.style.width = `${width}px`;
panel.style.left = `${left}px`; panel.style.left = `${left}px`;
panel.style.top = `${top}px`; panel.style.transformOrigin = opensUpward ? 'bottom center' : 'top center';
panel.dataset.placement = opensUpward ? 'above' : 'below';
if (opensUpward) {
panel.style.top = 'auto';
panel.style.bottom = `${Math.max(margin, viewRect.bottom - anchorRect.top + gap)}px`;
} else {
panel.style.top = `${Math.max(margin, anchorRect.bottom - viewRect.top + gap)}px`;
panel.style.bottom = 'auto';
}
panel.style.maxHeight = `${maxHeight}px`; panel.style.maxHeight = `${maxHeight}px`;
if (body) body.style.maxHeight = `${maxHeight - 56}px`; if (body) body.style.maxHeight = `${maxHeight - 56}px`;
} }
@@ -283,6 +359,7 @@ function showFilterPanel() {
view.style.pointerEvents = 'auto'; view.style.pointerEvents = 'auto';
view.setAttribute('aria-hidden', 'false'); view.setAttribute('aria-hidden', 'false');
positionFilterPanel(); positionFilterPanel();
panel.style.transform = getClosedPanelTransform(panel);
setRecipeAreaBlur(true); setRecipeAreaBlur(true);
syncPanelCount(); syncPanelCount();
@@ -303,7 +380,7 @@ function hideFilterPanel() {
view.style.pointerEvents = 'none'; view.style.pointerEvents = 'none';
view.setAttribute('aria-hidden', 'true'); view.setAttribute('aria-hidden', 'true');
panel.style.opacity = '0'; panel.style.opacity = '0';
panel.style.transform = 'translateY(-0.5rem) scale(0.98)'; panel.style.transform = getClosedPanelTransform(panel);
setRecipeAreaBlur(false); setRecipeAreaBlur(false);
syncPanelCount(); syncPanelCount();
@@ -381,7 +458,7 @@ function syncPanelCount() {
if (button) { if (button) {
const highlight = isFilterPanelOpen() || count > 0; const highlight = isFilterPanelOpen() || count > 0;
const isRecipeGlassButton = buttonId === 'recipe-filter-btn'; const isRecipeGlassButton = buttonId === 'recipe-filter-btn';
if (isRecipeGlassButton && !highlight) { if (isRecipeGlassButton) {
button.style.removeProperty('background'); button.style.removeProperty('background');
button.style.removeProperty('border-color'); button.style.removeProperty('border-color');
button.style.removeProperty('color'); button.style.removeProperty('color');

View File

@@ -109,7 +109,7 @@ export function getRecipeListHTML() {
<div id="recipe-filter-wrap" class="pointer-events-auto absolute bottom-0 right-4"> <div id="recipe-filter-wrap" class="pointer-events-auto absolute bottom-0 right-4">
<button type="button" id="recipe-filter-btn" class="recipe-glass-btn recipe-bottom-action relative flex items-center justify-center transition-all duration-200" aria-label="Otwórz filtry"> <button type="button" id="recipe-filter-btn" class="recipe-glass-btn recipe-bottom-action relative flex items-center justify-center transition-all duration-200" aria-label="Otwórz filtry">
<i class="fas fa-sliders-h" aria-hidden="true"></i> <i class="fas fa-sliders-h" aria-hidden="true"></i>
<span id="recipe-filter-count" class="hidden absolute -top-1 -right-1 min-w-[1.1rem] h-[1.1rem] px-1 rounded-full text-[9px] font-bold leading-none items-center justify-center" style="background:rgb(var(--sunken-rgb)); border:1px solid rgb(var(--border-input-rgb)); color:rgb(var(--text-emphasis-rgb));"></span> <span id="recipe-filter-count" class="hidden absolute -top-1 -right-1 min-w-[1.1rem] h-[1.1rem] px-1 rounded-full text-[9px] font-bold leading-none items-center justify-center" style="background:rgba(var(--text-emphasis-rgb),0.86); background-image:none !important; border:1px solid rgba(255,255,255,0.42); color:rgb(var(--app-bg-rgb)); box-shadow:0 2px 6px rgba(var(--overlay-rgb),0.22);"></span>
</button> </button>
</div> </div>
</div> </div>