feat(appearance): opt-in glass tuner toggle in Settings
Graduate the floating glass tuner from a localhost-only dev tool to a user-facing opt-in: it's now gated on a per-user `theme.showTuner` pref (off by default) with a "Show the glass tuner" switch in Settings → Appearance, riding the existing theme draft-save. Softened the dev-only framing. Behaviour and look are unchanged; the full preset system is a separate epic.
This commit is contained in:
@@ -985,8 +985,8 @@ export default function App() {
|
||||
<ErrorDialog />
|
||||
{/* Re-binds to the active page's scroll container on navigation (page or channel change). */}
|
||||
<BackToTop dep={channelView ? `chan:${channelView.id}` : page} />
|
||||
{/* Local-only live appearance tuner (renders only on localhost; null everywhere else). */}
|
||||
<GlassTuner />
|
||||
{/* Opt-in live appearance tuner (Settings → Appearance; off by default). */}
|
||||
<GlassTuner enabled={theme.showTuner} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
/**
|
||||
* Local-only live appearance tuner. Mounted at the app root so it's reachable on every page; it
|
||||
* A floating live appearance tuner. Mounted at the app root so it's reachable on every page; it
|
||||
* writes the glass CSS custom properties (index.css `:root`) inline on the document element for
|
||||
* instant, build-free feedback, and persists to localStorage.
|
||||
*
|
||||
* Only active on localhost, so it never appears in a hosted build. A scratch tool for dialling in
|
||||
* the surface values — "Copy CSS" exports them to paste into index.css.
|
||||
* Opt-in via Settings → Appearance (`theme.showTuner`, off by default) — a secondary "geek toy"
|
||||
* for dialling in the surface values. "Copy CSS" exports the values.
|
||||
*/
|
||||
const DEV_TUNER =
|
||||
typeof window !== "undefined" && /^(localhost|127\.0\.0\.1)$/.test(window.location.hostname);
|
||||
|
||||
const LS_KEY = "siftlode.devGlassTuner";
|
||||
|
||||
type Slider = {
|
||||
@@ -93,7 +90,7 @@ function loadSaved(): Saved {
|
||||
}
|
||||
}
|
||||
|
||||
export default function GlassTuner() {
|
||||
export default function GlassTuner({ enabled }: { enabled: boolean }) {
|
||||
const initial = useMemo(loadSaved, []);
|
||||
const [vars, setVars] = useState<Record<string, number>>(() => {
|
||||
const base: Record<string, number> = {};
|
||||
@@ -216,7 +213,7 @@ export default function GlassTuner() {
|
||||
persist({ open: v });
|
||||
}
|
||||
|
||||
if (!DEV_TUNER) return null;
|
||||
if (!enabled) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed right-0 top-24 z-[9999] flex items-start" style={{ pointerEvents: "none" }}>
|
||||
@@ -225,7 +222,7 @@ export default function GlassTuner() {
|
||||
onClick={() => persistOpen(true)}
|
||||
style={{ pointerEvents: "auto" }}
|
||||
className="glass-menu glass-hover rounded-l-xl rounded-r-none px-2 py-3 text-xs font-semibold tracking-wide text-fg"
|
||||
title="Open the glass tuner (dev only)"
|
||||
title="Open the glass tuner"
|
||||
>
|
||||
<span style={{ writingMode: "vertical-rl" }}>◐ GLASS</span>
|
||||
</button>
|
||||
@@ -237,7 +234,7 @@ export default function GlassTuner() {
|
||||
>
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-border/60">
|
||||
<span className="text-sm font-semibold text-fg">◐ Glass Tuner</span>
|
||||
<span className="text-[10px] uppercase tracking-wider text-muted">dev</span>
|
||||
<span className="text-[10px] uppercase tracking-wider text-muted">lab</span>
|
||||
<button
|
||||
onClick={() => persistOpen(false)}
|
||||
className="ml-auto text-muted hover:text-fg text-lg leading-none px-1"
|
||||
|
||||
@@ -205,6 +205,16 @@ function Appearance({ prefs }: { prefs: PrefsController }) {
|
||||
>
|
||||
<Switch label={t("settings.appearance.showHints")} checked={hints} onChange={setHints} />
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("settings.appearance.showTuner")}
|
||||
hint={t("settings.appearance.showTunerHint")}
|
||||
>
|
||||
<Switch
|
||||
label={t("settings.appearance.showTuner")}
|
||||
checked={theme.showTuner}
|
||||
onChange={(v) => setTheme({ ...theme, showTuner: v })}
|
||||
/>
|
||||
</SettingRow>
|
||||
</Section>
|
||||
|
||||
<Section title={t("settings.appearance.textSize")}>
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
"performanceModeHint": "Turns off the translucent glass blur and soft shadows for snappier rendering on slower machines.",
|
||||
"showHints": "Show hints",
|
||||
"showHintsHint": "These little hover explanations. Turn them off once you know your way around.",
|
||||
"showTuner": "Show the glass tuner",
|
||||
"showTunerHint": "A floating panel for live-tweaking the frosted-glass look. A geek toy — off by default.",
|
||||
"textSize": "Text size"
|
||||
},
|
||||
"notifications": {
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
"performanceModeHint": "Kikapcsolja az áttetsző üvegelmosást és a lágy árnyékokat a gyorsabb megjelenítésért lassabb gépeken.",
|
||||
"showHints": "Tippek megjelenítése",
|
||||
"showHintsHint": "Ezek a kis lebegő magyarázatok. Kapcsold ki őket, ha már kiismerted magad.",
|
||||
"showTuner": "Glass tuner megjelenítése",
|
||||
"showTunerHint": "Lebegő panel a mattüveg-hatás élő hangolásához. Geek-játékszer — alapból kikapcsolva.",
|
||||
"textSize": "Betűméret"
|
||||
},
|
||||
"notifications": {
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface ThemePrefs {
|
||||
bgImage: boolean;
|
||||
/** How strongly the background image is faded out, 0–100% (higher = fainter). */
|
||||
bgFade: number;
|
||||
/** Show the floating glass/theme tuner (a secondary "geek toy"); off by default. */
|
||||
showTuner: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_THEME: ThemePrefs = {
|
||||
@@ -28,6 +30,7 @@ export const DEFAULT_THEME: ThemePrefs = {
|
||||
fontScale: 1.06,
|
||||
bgImage: true,
|
||||
bgFade: 60,
|
||||
showTuner: false,
|
||||
};
|
||||
|
||||
export function applyTheme(t: ThemePrefs): void {
|
||||
|
||||
Reference in New Issue
Block a user