background image

This commit is contained in:
hamid zarghami
2026-06-22 09:20:08 +03:30
parent 3bb6ffac4e
commit e8a950b0b6
10 changed files with 286 additions and 61 deletions
+19 -2
View File
@@ -4,12 +4,19 @@ export const PRIMARY_LIGHT_OPACITY = 0.15;
export type RestaurantBackgroundSettings = {
bgUrl?: string | null;
bgBlur?: number | null;
bgOpacity?: number | null;
bgBlur?: number | string | null;
bgOpacity?: number | string | null;
bgOverlay?: string | null;
menuColor?: string | null;
};
export const normalizeBgNumber = (value: unknown): number | null => {
if (value == null || value === "") return null;
const parsed = typeof value === "string" ? Number(value) : value;
if (typeof parsed !== "number" || Number.isNaN(parsed)) return null;
return parsed;
};
export const hexToRgba = (hex: string, alpha: number): string => {
const normalized = hex.replace("#", "");
if (normalized.length !== 6) {
@@ -82,3 +89,13 @@ export const clearPatternCssVariables = () => {
root.style.removeProperty("--primary-light");
root.style.removeProperty("--background");
};
export const applyCustomImageCssVariables = () => {
const root = document.documentElement;
root.setAttribute("data-image-bg", "true");
root.removeAttribute("data-pattern-bg");
};
export const clearCustomImageCssVariables = () => {
document.documentElement.removeAttribute("data-image-bg");
};
+1 -1
View File
@@ -1,3 +1,3 @@
export function getThemeBootScript(): string {
return `(function(){try{var s=location.pathname.split("/").filter(Boolean)[0],t=null;if(s&&s!=="auth"){var r=localStorage.getItem("restaurant-theme:"+s);if(r)t=JSON.parse(r)}if(!t||t.version!==1||!t.menuColor){var l=localStorage.getItem("theme-primary-color");if(l){var x=l.replace("#","");if(x.length===6){var p=parseInt(x.slice(0,2),16),f=parseInt(x.slice(2,4),16),m=parseInt(x.slice(4,6),16),b="rgba("+p+","+f+","+m+",0.05)";document.documentElement.style.setProperty("--background",b)}}return}function h(n,a){var u=n.replace("#","");if(u.length!==6)return null;var v=parseInt(u.slice(0,2),16),g=parseInt(u.slice(2,4),16),y=parseInt(u.slice(4,6),16);return"rgba("+v+","+g+","+y+","+a+")"}var e=document.documentElement;if(t.primaryOklch){e.style.setProperty("--primary",t.primaryOklch);e.style.setProperty("--primary-foreground",t.primaryForeground||"oklch(1 0 0)")}if(!t.isPattern)return;e.setAttribute("data-pattern-bg","true");var k=t.backgroundTint||h(t.menuColor,0.05),w=t.bgPatternVector||h(t.menuColor,0.1),z=t.primaryLight||h(t.menuColor,0.15);k&&(e.style.setProperty("--background",k),e.style.setProperty("--bg-pattern-base",k));w&&e.style.setProperty("--bg-pattern-vector",w);z&&e.style.setProperty("--primary-light",z);if(k){var d=document.createElement("div");d.id="cached-theme-bg";d.setAttribute("aria-hidden","true");d.style.cssText="position:fixed;inset:0;pointer-events:none;z-index:0;background-color:"+k+(t.patternDataUrl?';background-image:url("'+t.patternDataUrl+'");background-repeat:repeat;background-size:auto':"")+";";document.body.appendChild(d)}}catch(u){}})();`;
return `(function(){try{var s=location.pathname.split("/").filter(Boolean)[0],t=null;if(s&&s!=="auth"){var r=localStorage.getItem("restaurant-theme:"+s);if(r)t=JSON.parse(r)}if(!t||t.version!==1||!t.menuColor){var l=localStorage.getItem("theme-primary-color");if(l){var x=l.replace("#","");if(x.length===6){var p=parseInt(x.slice(0,2),16),f=parseInt(x.slice(2,4),16),m=parseInt(x.slice(4,6),16),b="rgba("+p+","+f+","+m+",0.05)";document.documentElement.style.setProperty("--background",b)}}return}function h(n,a){var u=n.replace("#","");if(u.length!==6)return null;var v=parseInt(u.slice(0,2),16),g=parseInt(u.slice(2,4),16),y=parseInt(u.slice(4,6),16);return"rgba("+v+","+g+","+y+","+a+")"}function isUnset(v){return v==null||v===""||v===0}function isCustomImg(th){if(!th.bgUrl)return false;return !isUnset(th.bgBlur)||!isUnset(th.bgOpacity)||!isUnset(th.bgOverlay)}var e=document.documentElement;if(t.primaryOklch){e.style.setProperty("--primary",t.primaryOklch);e.style.setProperty("--primary-foreground",t.primaryForeground||"oklch(1 0 0)")}if(!t.isPattern&&!isCustomImg(t))return;if(t.isPattern){e.setAttribute("data-pattern-bg","true");var k=t.backgroundTint||h(t.menuColor,0.05),w=t.bgPatternVector||h(t.menuColor,0.1),z=t.primaryLight||h(t.menuColor,0.15);k&&(e.style.setProperty("--background",k),e.style.setProperty("--bg-pattern-base",k));w&&e.style.setProperty("--bg-pattern-vector",w);z&&e.style.setProperty("--primary-light",z);if(k){var d=document.createElement("div");d.id="cached-theme-bg";d.setAttribute("aria-hidden","true");d.style.cssText="position:fixed;inset:0;pointer-events:none;z-index:0;background-color:"+k+(t.patternDataUrl?';background-image:url("'+t.patternDataUrl+'");background-repeat:repeat;background-size:auto':"")+";";document.body.appendChild(d)}}else if(isCustomImg(t)){e.setAttribute("data-image-bg","true");var d2=document.createElement("div");d2.id="cached-theme-bg";d2.setAttribute("aria-hidden","true");d2.style.cssText="position:fixed;inset:0;pointer-events:none;z-index:0;overflow:hidden;";var i=document.createElement("div");i.id="cached-theme-bg-img";i.style.cssText="position:absolute;inset:-40px;background-size:cover;background-position:center;background-repeat:no-repeat;background-image:url(\\""+t.bgUrl+"\\");"+(t.bgBlur?"filter:blur("+t.bgBlur+"px);":"")+"opacity:"+(t.bgOpacity!=null?t.bgOpacity/100:1)+";";d2.appendChild(i);var ov=document.createElement("div");ov.id="cached-theme-bg-overlay";ov.style.cssText="position:absolute;inset:0;"+(t.bgOverlay&&Number(t.bgOverlay)>0?"background-color:rgba(0,0,0,"+Number(t.bgOverlay)/100+");":"");d2.appendChild(ov);document.body.appendChild(d2)}}catch(u){}})();`;
}
+121 -34
View File
@@ -1,7 +1,9 @@
import { calculateBrightness, hexToOklch } from "@/lib/helpers/colorUtils";
import {
hexToRgba,
isCustomImageBackground,
isPatternBackground,
normalizeBgNumber,
PATTERN_BACKGROUND_OPACITY,
PATTERN_VECTOR_OPACITY,
PRIMARY_LIGHT_OPACITY,
@@ -18,6 +20,9 @@ export type CachedRestaurantTheme = {
version: 1;
menuColor: string;
bgUrl?: string | null;
bgBlur?: number | null;
bgOpacity?: number | null;
bgOverlay?: string | null;
isPattern: boolean;
patternDataUrl?: string | null;
primaryOklch?: string;
@@ -34,6 +39,8 @@ const ABOUT_CACHE_PREFIX = "restaurant-about";
const MENU_CACHE_PREFIX = "restaurant-menu";
const CATEGORIES_CACHE_PREFIX = "restaurant-categories";
export const THEME_BG_ELEMENT_ID = "cached-theme-bg";
const THEME_BG_IMG_ID = "cached-theme-bg-img";
const THEME_BG_OVERLAY_ID = "cached-theme-bg-overlay";
export function getThemeCacheKey(slug: string): string {
return `${CACHE_KEY_PREFIX}:${slug}`;
@@ -215,7 +222,10 @@ export function setThemeCache(
const payload: CachedRestaurantTheme = {
...theme,
...css,
patternDataUrl: theme.patternDataUrl ?? existing?.patternDataUrl ?? null,
patternDataUrl:
theme.patternDataUrl !== undefined
? theme.patternDataUrl
: (existing?.patternDataUrl ?? null),
version: CACHE_VERSION,
};
@@ -238,18 +248,54 @@ export function setThemeCache(
export function buildThemeCacheFromRestaurant(
settings: RestaurantBackgroundSettings,
): Pick<CachedRestaurantTheme, "menuColor" | "bgUrl" | "isPattern"> {
): Pick<
CachedRestaurantTheme,
"menuColor" | "bgUrl" | "bgBlur" | "bgOpacity" | "bgOverlay" | "isPattern"
> {
return {
menuColor: settings.menuColor || "#1E3A8A",
bgUrl: settings.bgUrl,
bgBlur: normalizeBgNumber(settings.bgBlur),
bgOpacity: normalizeBgNumber(settings.bgOpacity),
bgOverlay: settings.bgOverlay ?? null,
isPattern: isPatternBackground(settings),
};
}
export function themeBackgroundMatches(
cached: CachedRestaurantTheme,
settings: RestaurantBackgroundSettings,
): boolean {
const next = buildThemeCacheFromRestaurant(settings);
return (
cached.menuColor === next.menuColor &&
cached.bgUrl === next.bgUrl &&
cached.bgBlur === next.bgBlur &&
cached.bgOpacity === next.bgOpacity &&
cached.bgOverlay === next.bgOverlay &&
cached.isPattern === next.isPattern
);
}
export function svgToDataUrl(svg: string): string {
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
}
function ensureBgElement(): HTMLElement {
let bgElement = document.getElementById(THEME_BG_ELEMENT_ID);
if (!bgElement) {
bgElement = document.createElement("div");
bgElement.id = THEME_BG_ELEMENT_ID;
bgElement.setAttribute("aria-hidden", "true");
bgElement.style.position = "fixed";
bgElement.style.inset = "0";
bgElement.style.pointerEvents = "none";
bgElement.style.zIndex = "0";
document.body.appendChild(bgElement);
}
return bgElement;
}
export function applyCachedThemeToDom(theme: CachedRestaurantTheme): void {
if (typeof document === "undefined") return;
@@ -263,44 +309,85 @@ export function applyCachedThemeToDom(theme: CachedRestaurantTheme): void {
);
}
if (!theme.isPattern) return;
const isCustomImage = isCustomImageBackground(theme);
root.setAttribute("data-pattern-bg", "true");
if (!theme.isPattern && !isCustomImage) return;
if (theme.backgroundTint) {
root.style.setProperty("--background", theme.backgroundTint);
root.style.setProperty("--bg-pattern-base", theme.backgroundTint);
}
if (theme.bgPatternVector) {
root.style.setProperty("--bg-pattern-vector", theme.bgPatternVector);
}
if (theme.primaryLight) {
root.style.setProperty("--primary-light", theme.primaryLight);
if (theme.isPattern) {
root.setAttribute("data-pattern-bg", "true");
root.removeAttribute("data-image-bg");
if (theme.backgroundTint) {
root.style.setProperty("--background", theme.backgroundTint);
root.style.setProperty("--bg-pattern-base", theme.backgroundTint);
}
if (theme.bgPatternVector) {
root.style.setProperty("--bg-pattern-vector", theme.bgPatternVector);
}
if (theme.primaryLight) {
root.style.setProperty("--primary-light", theme.primaryLight);
}
if (!theme.backgroundTint) return;
const bgElement = ensureBgElement();
bgElement.style.overflow = "";
document.getElementById(THEME_BG_IMG_ID)?.remove();
document.getElementById(THEME_BG_OVERLAY_ID)?.remove();
bgElement.style.backgroundColor = theme.backgroundTint;
if (theme.patternDataUrl) {
bgElement.style.backgroundImage = `url("${theme.patternDataUrl}")`;
bgElement.style.backgroundRepeat = "repeat";
bgElement.style.backgroundSize = "auto";
} else {
bgElement.style.removeProperty("background-image");
bgElement.style.removeProperty("background-repeat");
bgElement.style.removeProperty("background-size");
}
return;
}
if (!theme.backgroundTint) return;
if (isCustomImage && theme.bgUrl) {
root.setAttribute("data-image-bg", "true");
root.removeAttribute("data-pattern-bg");
let bgElement = document.getElementById(THEME_BG_ELEMENT_ID);
if (!bgElement) {
bgElement = document.createElement("div");
bgElement.id = THEME_BG_ELEMENT_ID;
bgElement.setAttribute("aria-hidden", "true");
bgElement.style.position = "fixed";
bgElement.style.inset = "0";
bgElement.style.pointerEvents = "none";
bgElement.style.zIndex = "0";
document.body.appendChild(bgElement);
}
const bgElement = ensureBgElement();
bgElement.style.backgroundColor = "";
bgElement.style.backgroundImage = "";
bgElement.style.overflow = "hidden";
bgElement.style.backgroundColor = theme.backgroundTint;
if (theme.patternDataUrl) {
bgElement.style.backgroundImage = `url("${theme.patternDataUrl}")`;
bgElement.style.backgroundRepeat = "repeat";
bgElement.style.backgroundSize = "auto";
} else {
bgElement.style.removeProperty("background-image");
bgElement.style.removeProperty("background-repeat");
bgElement.style.removeProperty("background-size");
let imgLayer = document.getElementById(THEME_BG_IMG_ID);
if (!imgLayer) {
imgLayer = document.createElement("div");
imgLayer.id = THEME_BG_IMG_ID;
imgLayer.style.position = "absolute";
imgLayer.style.inset = "-40px";
imgLayer.style.backgroundSize = "cover";
imgLayer.style.backgroundPosition = "center";
imgLayer.style.backgroundRepeat = "no-repeat";
bgElement.appendChild(imgLayer);
}
imgLayer.style.backgroundImage = `url("${theme.bgUrl}")`;
imgLayer.style.filter = theme.bgBlur ? `blur(${theme.bgBlur}px)` : "";
imgLayer.style.opacity =
theme.bgOpacity != null ? String(theme.bgOpacity / 100) : "1";
let overlayLayer = document.getElementById(THEME_BG_OVERLAY_ID);
if (!overlayLayer) {
overlayLayer = document.createElement("div");
overlayLayer.id = THEME_BG_OVERLAY_ID;
overlayLayer.style.position = "absolute";
overlayLayer.style.inset = "0";
bgElement.appendChild(overlayLayer);
}
const overlayOpacity =
theme.bgOverlay != null ? Number(theme.bgOverlay) / 100 : 0;
overlayLayer.style.backgroundColor =
overlayOpacity > 0 ? `rgba(0,0,0,${overlayOpacity})` : "";
}
}