glass mode

This commit is contained in:
hamid zarghami
2026-06-21 12:58:29 +03:30
parent d3ef8e1a98
commit b5653906dc
17 changed files with 325 additions and 211 deletions
@@ -6,6 +6,7 @@ import CategorySmallItemRenderer from "@/components/listview/CategorySmallItemRe
import HorizontalScrollView from "@/components/listview/HorizontalScrollView";
import clsx from "clsx";
import { usePathname } from "next/navigation";
import { GLASS_SURFACE_SELECTED } from "@/lib/styles/glassSurface";
/** TODO: remove when backend sends avatarRenderMode */
const COLORED_SVG_RESTAURANT_SLUGS = new Set(["havasone"]);
@@ -110,7 +111,7 @@ const CategoryScroll = ({ categories, selectedCategory, onSelect, variant = "lar
const isSelected = item.id === selectedCategory;
return (
<Renderer key={item.id} className={clsx(isSelected && "bg-container!")} onClick={handleSelect(item.id)}>
<Renderer key={item.id} className={clsx(isSelected && GLASS_SURFACE_SELECTED)} onClick={handleSelect(item.id)}>
<CategoryImage src={item.avatarUrl || "/assets/images/food-image.png"} size={imageSize} alt="category image" tintWithPrimary={!usesColoredSvg} />
<span className="text-xs text-foreground text-center whitespace-nowrap">{item.title}</span>
</Renderer>
@@ -12,6 +12,7 @@ import useToggle from "@/hooks/helpers/useToggle";
import clsx from "clsx";
import { motion } from "framer-motion";
import { Candle2 } from "iconsax-react";
import { glassSurfaceFlat } from "@/lib/styles/glassSurface";
import { useQueryState } from "next-usequerystate";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -175,11 +176,11 @@ const MenuIndex = () => {
<div className="flex flex-wrap gap-2 items-center relative" ref={smallCategoriesRef}>
<span className="sm:text-base text-sm font-medium max-w-[113px] sm:max-w-auto">{selectedCategory === "0" ? "" : categories.find((c) => c.id === selectedCategory)?.title || ""}</span>
<div className="flex min-w-[247px] flex-1 gap-2 justify-end items-center">
<button onClick={toggleFilterModal} className="rounded-xl h-8 bg-container pattern-secondary-bg ps-4 pe-2 py-1.5 inline-flex items-center justify-between gap-[7px]">
<button onClick={toggleFilterModal} className={glassSurfaceFlat('rounded-xl h-8 ps-4 pe-2 py-1.5 inline-flex items-center justify-between gap-[7px]')}>
<Candle2 className="stroke-foreground" size={16} />
<span className="text-xs leading-5 font-medium whitespace-nowrap">{tMenu("MenuFilterDrawer.Label")}</span>
</button>
<button onClick={toggleSortingModal} className="rounded-xl h-8 bg-container pattern-secondary-bg ps-4 pe-2 py-1.5 inline-flex items-center gap-2">
<button onClick={toggleSortingModal} className={glassSurfaceFlat('rounded-xl h-8 ps-4 pe-2 py-1.5 inline-flex items-center gap-2')}>
<TextAlignIcon className="text-foreground" />
<span className="text-xs leading-5 font-medium whitespace-nowrap">{activeSortingLabel}</span>
</button>
@@ -196,7 +197,7 @@ const MenuIndex = () => {
opacity: smallCategoriesVisible ? 1 : 0,
}}
transition={{ duration: 0.1 }}
className={clsx("fixed left-0 z-10 top-0 px-4 pt-16 bg-[#F4F5F9CC] dark:bg-background/70 backdrop-blur-[44px] right-0 xl:pr-72 xl:pt-20", !smallCategoriesVisible && "pointer-events-none")}
className={clsx(glassSurfaceFlat('fixed left-0 z-10 top-0 px-4 pt-16 right-0 xl:pr-72 xl:pt-20 border-x-0 border-t-0 rounded-none'), !smallCategoriesVisible && "pointer-events-none")}
>
<CategoryScroll categories={categories} selectedCategory={selectedCategory} onSelect={updateCategory} variant="small" />
</motion.div>
@@ -6,6 +6,7 @@ import CartChecker from "@/components/CartChecker";
import RestaurantHeadManager from "@/components/RestaurantHeadManager";
import RestaurantNotFoundGuard from "@/components/RestaurantNotFoundGuard";
import PreferenceWrapper from "@/components/wrapper/PreferenceWrapper";
import { PatternBackgroundProvider } from "@/components/background/PatternBackgroundProvider";
type Props = {
children: React.ReactNode;
@@ -16,10 +17,12 @@ export default function RestaurantLayoutClient({ children }: Props) {
<>
<RestaurantHeadManager />
<RestaurantNotFoundGuard>
<PatternBackgroundProvider>
<PreferenceWrapper>
<AppBackground />
{children}
</PreferenceWrapper>
</PatternBackgroundProvider>
</RestaurantNotFoundGuard>
<CartChecker />
<ActiveChecker />
+1 -1
View File
@@ -1,6 +1,7 @@
@import "tailwindcss";
@import "../../public/assets/css/fonts.css";
@import "tw-animate-css";
@import "../styles/glass.css";
/* @custom-variant dark (&:is(.dark *)); */
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
@@ -400,7 +401,6 @@ html[data-theme="dark"] {
html[data-pattern-bg="true"] #root {
position: relative;
z-index: 1;
isolation: isolate;
}
html[data-pattern-bg="true"] [data-slot="dialog-content"] {
+4 -81
View File
@@ -1,87 +1,17 @@
"use client";
import { useGetAbout } from "@/app/[name]/(Main)/about/hooks/useAboutData";
import {
applyPatternCssVariables,
clearPatternCssVariables,
colorizeSvg,
hexToRgba,
isPatternBackground,
PATTERN_BACKGROUND_OPACITY,
} from "@/lib/helpers/backgroundUtils";
import { useParams } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { usePatternBackground } from "@/components/background/PatternBackgroundProvider";
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
const DEFAULT_MENU_COLOR = "#1E3A8A";
export default function AppBackground() {
const { name: restaurantSlug } = useParams<{ name: string }>();
const { data: aboutData } = useGetAbout();
const restaurant = aboutData?.data;
const { isPattern, backgroundStyle } = usePatternBackground();
const [mounted, setMounted] = useState(false);
const [patternImageUrl, setPatternImageUrl] = useState<string | null>(null);
const blobUrlRef = useRef<string | null>(null);
const isPattern = restaurant ? isPatternBackground(restaurant) : false;
const menuColor = restaurant?.menuColor || DEFAULT_MENU_COLOR;
const baseTint = hexToRgba(menuColor, PATTERN_BACKGROUND_OPACITY);
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
const revokeBlobUrl = () => {
if (blobUrlRef.current) {
URL.revokeObjectURL(blobUrlRef.current);
blobUrlRef.current = null;
}
setPatternImageUrl(null);
};
if (!isPattern || !restaurant?.bgUrl || !restaurantSlug) {
clearPatternCssVariables();
revokeBlobUrl();
return;
}
applyPatternCssVariables(menuColor);
let cancelled = false;
const proxyUrl = `/${restaurantSlug}/api/proxy-svg?url=${encodeURIComponent(restaurant.bgUrl)}`;
fetch(proxyUrl)
.then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch background pattern");
}
return response.text();
})
.then((svg) => {
if (cancelled) return;
revokeBlobUrl();
const blob = new Blob([colorizeSvg(svg, menuColor)], {
type: "image/svg+xml;charset=utf-8",
});
const blobUrl = URL.createObjectURL(blob);
blobUrlRef.current = blobUrl;
setPatternImageUrl(blobUrl);
})
.catch(() => {
if (!cancelled) {
revokeBlobUrl();
}
});
return () => {
cancelled = true;
clearPatternCssVariables();
revokeBlobUrl();
};
}, [isPattern, menuColor, restaurant?.bgUrl, restaurantSlug]);
if (!mounted || !isPattern) {
return null;
}
@@ -92,14 +22,7 @@ export default function AppBackground() {
className="fixed inset-0 pointer-events-none"
style={{
zIndex: 0,
backgroundColor: baseTint,
...(patternImageUrl
? {
backgroundImage: `url("${patternImageUrl}")`,
backgroundRepeat: "repeat",
backgroundSize: "auto",
}
: {}),
...backgroundStyle,
}}
/>,
document.body,
@@ -0,0 +1,126 @@
"use client";
import { useGetAbout } from "@/app/[name]/(Main)/about/hooks/useAboutData";
import {
applyPatternCssVariables,
clearPatternCssVariables,
colorizeSvg,
hexToRgba,
isPatternBackground,
PATTERN_BACKGROUND_OPACITY,
} from "@/lib/helpers/backgroundUtils";
import { useParams } from "next/navigation";
import {
createContext,
useContext,
useEffect,
useMemo,
useRef,
useState,
type CSSProperties,
type ReactNode,
} from "react";
const DEFAULT_MENU_COLOR = "#1E3A8A";
type PatternBackgroundContextValue = {
isPattern: boolean;
backgroundStyle: CSSProperties;
};
const PatternBackgroundContext = createContext<PatternBackgroundContextValue>({
isPattern: false,
backgroundStyle: {},
});
export function PatternBackgroundProvider({ children }: { children: ReactNode }) {
const { name: restaurantSlug } = useParams<{ name: string }>();
const { data: aboutData } = useGetAbout();
const restaurant = aboutData?.data;
const [patternImageUrl, setPatternImageUrl] = useState<string | null>(null);
const blobUrlRef = useRef<string | null>(null);
const isPattern = restaurant ? isPatternBackground(restaurant) : false;
const menuColor = restaurant?.menuColor || DEFAULT_MENU_COLOR;
const baseTint = hexToRgba(menuColor, PATTERN_BACKGROUND_OPACITY);
useEffect(() => {
const revokeBlobUrl = () => {
if (blobUrlRef.current) {
URL.revokeObjectURL(blobUrlRef.current);
blobUrlRef.current = null;
}
setPatternImageUrl(null);
};
if (!isPattern || !restaurant?.bgUrl || !restaurantSlug) {
clearPatternCssVariables();
revokeBlobUrl();
return;
}
applyPatternCssVariables(menuColor);
let cancelled = false;
const proxyUrl = `/${restaurantSlug}/api/proxy-svg?url=${encodeURIComponent(restaurant.bgUrl)}`;
fetch(proxyUrl)
.then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch background pattern");
}
return response.text();
})
.then((svg) => {
if (cancelled) return;
revokeBlobUrl();
const blob = new Blob([colorizeSvg(svg, menuColor)], {
type: "image/svg+xml;charset=utf-8",
});
const blobUrl = URL.createObjectURL(blob);
blobUrlRef.current = blobUrl;
setPatternImageUrl(blobUrl);
})
.catch(() => {
if (!cancelled) {
revokeBlobUrl();
}
});
return () => {
cancelled = true;
clearPatternCssVariables();
revokeBlobUrl();
};
}, [isPattern, menuColor, restaurant?.bgUrl, restaurantSlug]);
const backgroundStyle = useMemo<CSSProperties>(
() => ({
backgroundColor: baseTint,
...(patternImageUrl
? {
backgroundImage: `url("${patternImageUrl}")`,
backgroundRepeat: "repeat",
backgroundSize: "auto",
}
: {}),
}),
[baseTint, patternImageUrl],
);
const value = useMemo(
() => ({ isPattern, backgroundStyle }),
[backgroundStyle, isPattern],
);
return (
<PatternBackgroundContext.Provider value={value}>
{children}
</PatternBackgroundContext.Provider>
);
}
export function usePatternBackground() {
return useContext(PatternBackgroundContext);
}
+4 -4
View File
@@ -2,7 +2,7 @@
import React from 'react'
import SearchIcon from '../icons/SearchIcon'
import clsx from 'clsx'
import { glassSurfaceFlat } from '@/lib/styles/glassSurface'
interface SearchboxProps
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'results'> {
@@ -19,9 +19,9 @@ export default function SearchBox({
}: SearchboxProps) {
return (
<div
className={clsx(
'bg-container inline-flex rounded-xl px-4 h-10 w-full items-center content-center',
className
className={glassSurfaceFlat(
'inline-flex rounded-xl px-4 h-10 w-full items-center content-center',
className,
)}
>
<SearchIcon stroke='#8C90A3' />
@@ -1,5 +1,6 @@
'use client';
import { glassSurface } from '@/lib/styles/glassSurface';
import React from 'react';
type Props = {
@@ -8,7 +9,7 @@ type Props = {
function CategoryItemRenderer({ children, ...rest }: Props) {
return (
<div className={`${rest.className} cursor-pointer transition-all duration-200 ease-out gradient-border overflow-hidden rounded-xl backdrop-blur-md bg-container/40`}>
<div className={glassSurface(rest.className, 'cursor-pointer transition-all duration-200 ease-out overflow-hidden rounded-xl')}>
<div
{...rest}
className="rounded-normal w-[108px] h-[88px] flex flex-col justify-center items-center gap-2"
@@ -1,5 +1,6 @@
'use client';
import { glassSurface } from '@/lib/styles/glassSurface';
import React from 'react';
type Props = {
@@ -8,7 +9,7 @@ type Props = {
function CategorySmallItemRenderer({ children, ...rest }: Props) {
return (
<div className={`${rest.className} cursor-pointer transition-all duration-200 ease-out gradient-border overflow-hidden rounded-xl backdrop-blur-md bg-container/40 dark:bg-background`}>
<div className={glassSurface(rest.className, 'cursor-pointer transition-all duration-200 ease-out overflow-hidden rounded-xl')}>
<div
{...rest}
className="rounded-normal h-[44px] flex flex-row justify-center items-center p-2.5 gap-2"
+5 -1
View File
@@ -1,6 +1,7 @@
'use client';
import React from 'react';
import { glassSurface } from '@/lib/styles/glassSurface';
type Props = {
children: React.ReactNode;
@@ -10,7 +11,10 @@ function MenuItemRendererComponent({ children, ...rest }: Props) {
return (
<div
{...rest}
className={`${rest.className} box-shadow-normal !pb-4 py-4 transition-all duration-200 ease-out w-full bg-container rounded-3xl px-4 flex items-center justify-between gap-4`}>
className={glassSurface(
rest.className,
'!pb-4 py-4 transition-all duration-200 ease-out w-full rounded-3xl px-4 flex items-center justify-between gap-4',
)}>
{children}
</div>
+2 -1
View File
@@ -19,6 +19,7 @@ import { useGetAbout } from '@/app/[name]/(Main)/about/hooks/useAboutData';
import Image from 'next/image';
import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData';
import { toast, toastLoginRequired } from '../Toast';
import { glassSurface } from '@/lib/styles/glassSurface';
type MenuItemType = {
href: string | undefined;
@@ -344,7 +345,7 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
</motion.nav>
</BlurredOverlayContainer>
<nav
className="hidden fixed top-4 bottom-4 right-4 bg-container xl:w-[250px] xl:flex flex-col z-40 overflow-clip xl:rounded-4xl"
className={glassSurface('hidden fixed top-4 bottom-4 right-4 xl:w-[250px] xl:flex flex-col z-40 overflow-clip xl:rounded-4xl')}
>
{renderMenu()}
</nav>
+13 -56
View File
@@ -13,6 +13,8 @@ import { useTranslation } from 'react-i18next';
import { useCart } from '@/app/[name]/(Main)/cart/hook/useCart';
import { Building } from 'iconsax-react';
import clsx from 'clsx';
import { glassSurfaceNav } from '@/lib/styles/glassSurface';
import { getBottomNavMaskStyle } from './bottomNavShape';
import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData';
import { toastLoginRequired } from '../Toast';
@@ -37,6 +39,8 @@ function BottomNavBar({ onPagerClick }: BottomNavBarProps) {
});
const { items } = useCart();
const navMaskStyle = React.useMemo(() => getBottomNavMaskStyle(), []);
const cartItemsCount = React.useMemo(() => {
return Object.values(items).reduce((total, item) => {
return total + (item?.quantity || 0);
@@ -56,24 +60,18 @@ function BottomNavBar({ onPagerClick }: BottomNavBarProps) {
return (
<div className="fixed bottom-0 left-0 w-full bg-transparent pointer-events-none">
<div className="max-w-md mx-auto w-full aspect-436/104 relative z-30">
<svg
viewBox="0 0 436 104"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="w-full h-full block"
preserveAspectRatio="xMidYMid meet"
>
<g filter="url(#filter0_d_9095_18036)" className='fill-container'>
<path
className='pointer-events-auto'
d="M218 16C229.034 16 238.711 22.0313 244.148 31.0943C245.844 33.9222 248.724 36 252.022 36H406C414.837 36 422 43.1634 422 52V80C422 88.8366 414.837 96 406 96H30C21.1634 96 14 88.8366 14 80V52C14 43.1634 21.1634 36 30 36H183.979C187.277 36 190.157 33.9222 191.853 31.0943C197.29 22.0315 206.966 16.0001 218 16Z"
<div
aria-hidden
className={glassSurfaceNav('absolute inset-0 pointer-events-none')}
style={navMaskStyle}
/>
</g>
{/* 🔽 Your HTML content inside SVG */}
<foreignObject x="0" y="10" width="100%" height="80">
<nav
className="h-full px-4 grid grid-cols-5 gap-x-1 text-[10px] items-end"
className="absolute left-0 right-0 grid grid-cols-5 gap-x-1 items-end px-4 text-[10px] pointer-events-auto"
style={{
top: 'calc(10 / 104 * 100%)',
height: 'calc(80 / 104 * 100%)',
}}
>
<BottomNavLink
href={`/${name}/cart`}
@@ -127,48 +125,7 @@ function BottomNavBar({ onPagerClick }: BottomNavBarProps) {
</Link>
<BottomNavLink href={`/${name}/favorite`} onClick={handleFavoritesClick} icon={<HeartIcon width={20} height={20} />} value={t('Favorites')} />
</nav>
</foreignObject>
<defs>
<filter
id="filter0_d_9095_18036"
x="0"
y="0"
width="100%"
height="100%"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="-2" />
<feGaussianBlur stdDeviation="7" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_9095_18036"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_9095_18036"
result="shape"
/>
</filter>
</defs>
</svg>
{/* Badge Layer - خارج از SVG - دقیقاً مثل foreignObject */}
{cartItemsCount > 0 && (
<div
className="absolute left-0 right-0 pointer-events-none"
@@ -0,0 +1,20 @@
import type { CSSProperties } from "react";
export const BOTTOM_NAV_SHAPE_PATH =
"M218 16C229.034 16 238.711 22.0313 244.148 31.0943C245.844 33.9222 248.724 36 252.022 36H406C414.837 36 422 43.1634 422 52V80C422 88.8366 414.837 96 406 96H30C21.1634 96 14 88.8366 14 80V52C14 43.1634 21.1634 36 30 36H183.979C187.277 36 190.157 33.9222 191.853 31.0943C197.29 22.0315 206.966 16.0001 218 16Z";
export const BOTTOM_NAV_VIEWBOX = "0 0 436 104";
export function getBottomNavMaskStyle(): CSSProperties {
const maskSvg = `<svg viewBox="${BOTTOM_NAV_VIEWBOX}" xmlns="http://www.w3.org/2000/svg"><path fill="white" d="${BOTTOM_NAV_SHAPE_PATH}"/></svg>`;
const maskUrl = `url("data:image/svg+xml,${encodeURIComponent(maskSvg)}")`;
return {
WebkitMaskImage: maskUrl,
maskImage: maskUrl,
WebkitMaskSize: "100% 100%",
maskSize: "100% 100%",
WebkitMaskRepeat: "no-repeat",
maskRepeat: "no-repeat",
};
}
@@ -9,6 +9,7 @@ import { usePathname, useRouter } from 'next/navigation';
import usePreference from '@/hooks/helpers/usePreference';
import PagerModal from '../pager/PagerModal';
import clsx from 'clsx';
import { glassSurface } from '@/lib/styles/glassSurface';
type Props = {} & React.AllHTMLAttributes<HTMLBodyElementEventMap>
@@ -54,7 +55,7 @@ function ClientMenuRouteWrapper({ children }: Props) {
return (
<div className="h-svh overflow-hidden pt-10 flex flex-col pb-4 xl:pt-12">
<div className="z-50 fixed right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-4 bg-container justify-between rounded-[32px] xl:w-[calc(100%-305px)]">
<div className={glassSurface('z-50 fixed right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-4 justify-between rounded-[32px] xl:w-[calc(100%-305px)]')}>
<TopBar
profileDropState={profileDrop}
toggleProfileDropState={toggleProfileDrop}
+7 -3
View File
@@ -40,15 +40,19 @@ export const isCustomImageBackground = (
export const isPatternBackground = (settings: RestaurantBackgroundSettings): boolean =>
Boolean(settings.bgUrl) && !isCustomImageBackground(settings);
export const colorizeSvg = (svg: string, color: string): string => {
export const colorizeSvg = (
svg: string,
color: string,
vectorOpacity: number = PATTERN_VECTOR_OPACITY,
): string => {
let result = svg.replace(/fill="(?!black|none)[^"]+"/g, `fill="${color}"`);
result = result.replace(
/fill-opacity="[^"]+"/g,
`fill-opacity="${PATTERN_VECTOR_OPACITY}"`,
`fill-opacity="${vectorOpacity}"`,
);
result = result.replace(
/(<(?:path|circle|rect|ellipse|polygon)[^>]*fill="(?!black|none)[^"]+")(?![^>]*fill-opacity)/g,
`$1 fill-opacity="${PATTERN_VECTOR_OPACITY}"`,
`$1 fill-opacity="${vectorOpacity}"`,
);
return result;
};
+22
View File
@@ -0,0 +1,22 @@
import clsx, { type ClassValue } from "clsx";
export const GLASS_SURFACE = "glass-surface";
export const GLASS_SURFACE_FLAT = "glass-surface--flat";
export const GLASS_SURFACE_SELECTED = "glass-surface--selected";
export const GLASS_SURFACE_NAV = "glass-surface-nav";
export function glassSurfaceNav(...extra: ClassValue[]) {
return clsx(GLASS_SURFACE_NAV, extra);
}
export function glassSurface(...extra: ClassValue[]) {
return clsx(GLASS_SURFACE, extra);
}
export function glassSurfaceFlat(...extra: ClassValue[]) {
return clsx(GLASS_SURFACE, GLASS_SURFACE_FLAT, extra);
}
export function glassSurfaceSelected(...extra: ClassValue[]) {
return clsx(GLASS_SURFACE, GLASS_SURFACE_SELECTED, extra);
}
+49
View File
@@ -0,0 +1,49 @@
/* Glass surface — Figma: white 60%, border, shadow, backdrop blur */
.glass-surface {
background-color: rgba(255, 255, 255, 0.6);
border: 1px solid rgba(255, 255, 255, 0.8);
-webkit-backdrop-filter: blur(3px);
backdrop-filter: blur(3px);
box-shadow: 0px 2px 16px 0px rgba(0, 0, 0, 0.08);
}
html[data-pattern-bg="true"] .glass-surface {
-webkit-backdrop-filter: blur(1px);
backdrop-filter: blur(1px);
}
html[data-theme="dark"] .glass-surface {
background-color: color-mix(in oklch, var(--container) 60%, transparent);
border-color: rgba(255, 255, 255, 0.2);
}
.glass-surface--flat {
box-shadow: none;
}
.glass-surface--selected {
background-color: rgba(255, 255, 255, 0.85) !important;
}
html[data-theme="dark"] .glass-surface--selected {
background-color: color-mix(in oklch, var(--container) 85%, transparent) !important;
}
.glass-surface-nav {
background-color: rgba(255, 255, 255, 0.32);
border: 1px solid rgba(255, 255, 255, 0.8);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
box-shadow: 0px -2px 14px 0px rgba(0, 0, 0, 0.15);
}
html[data-pattern-bg="true"] .glass-surface-nav {
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
}
html[data-theme="dark"] .glass-surface-nav {
background-color: color-mix(in oklch, var(--container) 32%, transparent);
border-color: rgba(255, 255, 255, 0.2);
}