feat(glass): per-scheme background images + glass-over-image mode
Land the UAT-approved backdrop direction as a real feature (replacing the dev thumbnail prototype): - 6 generated abstract SVG backdrops in public/backdrops/, one tuned to each accent scheme (dark, mixed-colour, few light areas). Non-figurative + generated = repo-safe, tiny, accent-matched. - New data-backdrop="on" state (dark theme + the setting + not perf) paints the per-scheme image on <body> (app root now transparent so it shows app-wide and the glass refracts it) AND switches the glass to its translucent tier (the user's tuned values: surface 50 / card 60 / scrim 30 / blur 10 / edge 25). Off / light / perf -> flat colour + solid glass (the readable fallback). - Settings > Appearance 'Background image' toggle (bgImage pref, dark-only), trilingual. Flat is the fallback + opt-out. - Remove the FeedBackdrop prototype + its tuner controls; tuner baseline resynced to the glass-over-image tier, add a Backdrop-fade slider. GOTCHA fixed: the dark-mode ambient 'background' shorthand reset background-size to auto (image rendered un-covered); out-specified it with [data-theme="dark"].
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import FeedBackdrop from "./FeedBackdrop";
|
||||
|
||||
/**
|
||||
* DEV-ONLY live appearance tuner. Mounted at the App root so it stays reachable on every page.
|
||||
@@ -26,18 +25,20 @@ type Slider = {
|
||||
};
|
||||
|
||||
// def = the BAKED baseline in index.css (:root). "Current" preset + the export diff both key off it.
|
||||
// def = the default rendered baseline = the "glass over image" (backdrop-on) tier from index.css.
|
||||
const SLIDERS: Slider[] = [
|
||||
{ k: "--glass-blur", label: "Blur radius", min: 0, max: 40, step: 1, unit: "px", def: 20 },
|
||||
{ k: "--glass-saturate", label: "Saturation", min: 1, max: 2.5, step: 0.05, unit: "", def: 1.8 },
|
||||
{ k: "--glass-dark-bright", label: "Dark brightness", min: 1, max: 2, step: 0.05, unit: "", def: 1.25 },
|
||||
{ k: "--glass-surface-alpha", label: "Panel opacity", min: 40, max: 100, step: 1, unit: "%", def: 94 },
|
||||
{ k: "--glass-card-alpha", label: "Card opacity", min: 40, max: 100, step: 1, unit: "%", def: 94 },
|
||||
{ k: "--glass-menu-alpha", label: "Menu opacity", min: 60, max: 100, step: 1, unit: "%", def: 98 },
|
||||
{ k: "--glass-border-alpha", label: "Border opacity", min: 20, max: 100, step: 1, unit: "%", def: 78 },
|
||||
{ k: "--glass-edge", label: "Edge-light rim", min: 0, max: 60, step: 1, unit: "%", def: 10 },
|
||||
{ k: "--glass-inset", label: "Top highlight", min: 0, max: 40, step: 1, unit: "%", def: 16 },
|
||||
{ k: "--glass-scrim", label: "Under-text scrim", min: 0, max: 85, step: 1, unit: "%", def: 14 },
|
||||
{ k: "--glass-blur", label: "Blur radius", min: 0, max: 40, step: 1, unit: "px", def: 10 },
|
||||
{ k: "--glass-saturate", label: "Saturation", min: 1, max: 2.5, step: 0.05, unit: "", def: 1.6 },
|
||||
{ k: "--glass-dark-bright", label: "Dark brightness", min: 1, max: 2, step: 0.05, unit: "", def: 1.2 },
|
||||
{ k: "--glass-surface-alpha", label: "Panel opacity", min: 40, max: 100, step: 1, unit: "%", def: 50 },
|
||||
{ k: "--glass-card-alpha", label: "Card opacity", min: 40, max: 100, step: 1, unit: "%", def: 60 },
|
||||
{ k: "--glass-menu-alpha", label: "Menu opacity", min: 40, max: 100, step: 1, unit: "%", def: 66 },
|
||||
{ k: "--glass-border-alpha", label: "Border opacity", min: 20, max: 100, step: 1, unit: "%", def: 80 },
|
||||
{ k: "--glass-edge", label: "Edge-light rim", min: 0, max: 60, step: 1, unit: "%", def: 25 },
|
||||
{ k: "--glass-inset", label: "Top highlight", min: 0, max: 40, step: 1, unit: "%", def: 15 },
|
||||
{ k: "--glass-scrim", label: "Under-text scrim", min: 0, max: 85, step: 1, unit: "%", def: 30 },
|
||||
{ k: "--ambient", label: "Ambient intensity", min: 0, max: 2, step: 0.05, unit: "", def: 1.1 },
|
||||
{ k: "--bg-fade", label: "Backdrop fade", min: 40, max: 97, step: 1, unit: "%", def: 60 },
|
||||
];
|
||||
|
||||
const PALETTE: { k: string; label: string }[] = [
|
||||
@@ -52,22 +53,20 @@ const PALETTE: { k: string; label: string }[] = [
|
||||
|
||||
// Each preset resets every slider to its default, then applies these overrides.
|
||||
const PRESETS: { id: string; name: string; hint: string; over: Record<string, number> }[] = [
|
||||
{ id: "current", name: "Current", hint: "The baked baseline", over: {} },
|
||||
{ id: "current", name: "Glass/image", hint: "The default (over image)", over: {} },
|
||||
{
|
||||
id: "media",
|
||||
name: "Media (glassy)",
|
||||
hint: "For art-backed pages",
|
||||
id: "solid",
|
||||
name: "Solid (flat)",
|
||||
hint: "As shown w/ image off",
|
||||
over: {
|
||||
"--glass-blur": 10,
|
||||
"--glass-saturate": 1.6,
|
||||
"--glass-dark-bright": 1.2,
|
||||
"--glass-surface-alpha": 50,
|
||||
"--glass-card-alpha": 60,
|
||||
"--glass-menu-alpha": 66,
|
||||
"--glass-border-alpha": 80,
|
||||
"--glass-edge": 25,
|
||||
"--glass-inset": 15,
|
||||
"--glass-scrim": 30,
|
||||
"--glass-blur": 20,
|
||||
"--glass-dark-bright": 1.25,
|
||||
"--glass-surface-alpha": 94,
|
||||
"--glass-card-alpha": 94,
|
||||
"--glass-menu-alpha": 98,
|
||||
"--glass-edge": 10,
|
||||
"--glass-inset": 16,
|
||||
"--glass-scrim": 14,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -78,19 +77,13 @@ const PRESETS: { id: string; name: string; hint: string; over: Record<string, nu
|
||||
},
|
||||
];
|
||||
|
||||
// Dev-prototype feed-backdrop control (only meaningful while the backdrop toggle is on).
|
||||
const FEEDBG_SLIDERS: Slider[] = [
|
||||
{ k: "--feedbg-fade", label: "Backdrop fade", min: 60, max: 97, step: 1, unit: "%", def: 86 },
|
||||
];
|
||||
|
||||
const ALL = [...SLIDERS, ...FEEDBG_SLIDERS];
|
||||
const ALL = SLIDERS;
|
||||
const root = () => document.documentElement;
|
||||
const readVar = (k: string) => getComputedStyle(root()).getPropertyValue(k).trim();
|
||||
|
||||
type Saved = {
|
||||
vars?: Record<string, number>;
|
||||
palette?: Record<string, string>;
|
||||
feedbg?: boolean;
|
||||
open?: boolean;
|
||||
};
|
||||
|
||||
@@ -110,7 +103,6 @@ export default function GlassTuner() {
|
||||
return { ...base, ...(initial.vars || {}) };
|
||||
});
|
||||
const [palette, setPalette] = useState<Record<string, string>>(() => initial.palette || {});
|
||||
const [feedbg, setFeedbg] = useState<boolean>(() => Boolean(initial.feedbg));
|
||||
const [open, setOpen] = useState<boolean>(() => initial.open ?? true);
|
||||
const [tab, setTab] = useState<"glass" | "palette">("glass");
|
||||
const [copied, setCopied] = useState(false);
|
||||
@@ -207,17 +199,10 @@ export default function GlassTuner() {
|
||||
persist({ open: v });
|
||||
}
|
||||
|
||||
function toggleFeedbg(v: boolean) {
|
||||
setFeedbg(v);
|
||||
persist({ feedbg: v });
|
||||
}
|
||||
|
||||
if (!DEV_TUNER) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{feedbg && <FeedBackdrop />}
|
||||
<div className="fixed right-0 top-24 z-[9999] flex items-start" style={{ pointerEvents: "none" }}>
|
||||
<div className="fixed right-0 top-24 z-[9999] flex items-start" style={{ pointerEvents: "none" }}>
|
||||
{!open && (
|
||||
<button
|
||||
onClick={() => persistOpen(true)}
|
||||
@@ -264,17 +249,6 @@ export default function GlassTuner() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feed backdrop (dev prototype) */}
|
||||
<label className="flex items-center gap-2 text-xs text-fg cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={feedbg}
|
||||
onChange={(e) => toggleFeedbg(e.target.checked)}
|
||||
className="accent-accent"
|
||||
/>
|
||||
Feed backdrop <span className="text-muted">(blurred thumbnail)</span>
|
||||
</label>
|
||||
|
||||
{/* Tab switch */}
|
||||
<div className="flex gap-1.5">
|
||||
{(["glass", "palette"] as const).map((t) => (
|
||||
@@ -295,13 +269,6 @@ export default function GlassTuner() {
|
||||
{SLIDERS.map((s) => (
|
||||
<Ctl key={s.k} s={s} value={vars[s.k]} onChange={(v) => setVar(s.k, v)} />
|
||||
))}
|
||||
{feedbg && (
|
||||
<div className="flex flex-col gap-2.5 pt-1 border-t border-border/50">
|
||||
{FEEDBG_SLIDERS.map((s) => (
|
||||
<Ctl key={s.k} s={s} value={vars[s.k]} onChange={(v) => setVar(s.k, v)} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -346,8 +313,7 @@ export default function GlassTuner() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user