import { useTheme } from "next-themes"; import { LiquidGlass, type LiquidGlassProps } from "simple-liquid-glass"; import { useTouchInput } from "~/lib/input-modality.ts"; import { cn } from "~/lib/utils.ts"; /** * 液态玻璃面:simple-liquid-glass 的薄封装,材质按 meniscus 的尺寸分档。 * - lens:小控件(图标键、头像),几乎不糊,只在边带折射; * - regular:栏(顶栏胶囊、播放器底栏); * - frost:大块、字多的悬浮层(菜单)。只有真模糊,不做折射(见下方 effectMode)。 * 折射只有 Chromium 桌面有(SVG 位移进 backdrop-filter);Safari / Firefox / 触屏设备走包自带的磨砂。 * 只放在压着图片、视频、海报的层上;背后没细节的内容卡继续用 pg-slab。 */ export type LiquidTier = "lens" | "regular" | "frost"; const TIERS: Record< LiquidTier, { blur: number; displacement: number; fill: { dark: string; light: string }; } > = { lens: { blur: 1, displacement: 6, fill: { dark: "rgba(0,0,0,0.14)", light: "rgba(255,255,255,0.08)" } }, regular: { blur: 5, displacement: 10, fill: { dark: "rgba(10,11,14,0.30)", light: "rgba(255,255,255,0.16)" } }, frost: { blur: 20, displacement: 8, fill: { dark: "rgba(14,15,19,0.58)", light: "rgba(255,255,255,0.5)" } }, }; const FROST_BLUR = "blur(24px) saturate(160%)"; // 模糊之上再压一层底色,浅色字(次要说明、退出登录)压在暖色画面上也读得清 const FROST_FILL = { dark: "rgba(14,15,19,0.55)", light: "rgba(255,255,255,0.6)" }; interface Props extends Omit { tier?: LiquidTier; radius: number; /** 播放器等恒暗区域固定用夜间参数 */ forceDark?: boolean; /** 不画包自带的边光(主按钮的棱镜高光就是它的边光) */ rimless?: boolean; /** 折射边缘带色散:背后画面在棱边被拆成彩色(只在 Chromium 桌面、背后有画面时看得到) */ dispersive?: boolean; /** 只要玻璃外观与模糊,不做折射(测速页:实时曲线在重绘,不加 SVG 滤镜开销) */ noRefraction?: boolean; } export function LiquidSurface({ tier = "regular", radius, forceDark, rimless, dispersive, noRefraction, className, style, children, ...rest }: Props) { const { resolvedTheme } = useTheme(); const dark = forceDark || resolvedTheme !== "light"; // 触屏设备(手机、平板、车机)不跑 SVG 折射,退回同参数的磨砂:车机这类 Linux 触屏会被包当成桌面 const touch = useTouchInput(); // 不折射、只模糊的情况:包在这个模式下不铺 glassColor,还会在顶部画一道 50% 白的光泽(夜间压住第一行字), // 底色由这里铺,光泽在 app.css 里按档收淡 / 去掉 const blurOnly = touch || noRefraction || tier === "frost"; const t = TIERS[tier]; return ( {children} ); } /** * 给压在大图上的按钮加真折射:外面套一层 lens 玻璃,按钮本身挂 .btn-liquid 去掉自己的底和模糊, * 只留字、图标和主按钮的彩虹环。radius 取按钮高度的一半。 */ export function LiquidLens({ radius, primary, className, children, }: { radius: number; primary?: boolean; className?: string; children: React.ReactNode; }) { return ( {children} ); }