From c2e2c835de7e6faa64e480f57ba1df08addb2577 Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Mon, 27 Apr 2026 14:09:06 -0500 Subject: [PATCH] feat: add demo mode feature flag using Redacted Script font --- .../web/src/components/layout/Sidebar.tsx | 67 ++++++++++++++----- packages/web/src/hooks/useDemo.ts | 16 +++++ packages/web/src/index.css | 17 +++++ packages/web/src/lib/demo.ts | 10 +++ packages/web/src/main.tsx | 3 + 5 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 packages/web/src/hooks/useDemo.ts create mode 100644 packages/web/src/lib/demo.ts diff --git a/packages/web/src/components/layout/Sidebar.tsx b/packages/web/src/components/layout/Sidebar.tsx index 4b94e68..6ae2929 100644 --- a/packages/web/src/components/layout/Sidebar.tsx +++ b/packages/web/src/components/layout/Sidebar.tsx @@ -1,6 +1,17 @@ import { Link, useMatchRoute } from "@tanstack/react-router"; import { motion } from "framer-motion"; -import { Boxes, Brain, ChevronRight, LayoutDashboard, Moon, Settings, Sun } from "lucide-react"; +import { + Boxes, + Brain, + ChevronRight, + Eye, + EyeOff, + LayoutDashboard, + Moon, + Settings, + Sun, +} from "lucide-react"; +import { useDemo } from "@/hooks/useDemo"; import { useTheme } from "@/hooks/useTheme"; import { loadConfig } from "@/lib/config"; import { COLOR } from "@/lib/constants"; @@ -15,6 +26,7 @@ export function Sidebar() { const matchRoute = useMatchRoute(); const config = loadConfig(); const { theme, toggle } = useTheme(); + const { demo, toggle: toggleDemo } = useDemo(); return ( API v3

- +
+ + +
); diff --git a/packages/web/src/hooks/useDemo.ts b/packages/web/src/hooks/useDemo.ts new file mode 100644 index 0000000..7467cce --- /dev/null +++ b/packages/web/src/hooks/useDemo.ts @@ -0,0 +1,16 @@ +import { useEffect, useState } from "react"; +import { applyDemoMode, getDemoMode } from "@/lib/demo"; + +export function useDemo() { + const [demo, setDemo] = useState(() => getDemoMode()); + + useEffect(() => { + applyDemoMode(demo); + }, [demo]); + + function toggle() { + setDemo((d) => !d); + } + + return { demo, toggle }; +} diff --git a/packages/web/src/index.css b/packages/web/src/index.css index 47566f0..8e1d4cf 100644 --- a/packages/web/src/index.css +++ b/packages/web/src/index.css @@ -5,6 +5,23 @@ @import "@fontsource/dm-sans/500.css"; @import "@fontsource/dm-sans/600.css"; +/* Redacted font — loaded on demand for demo mode */ +@font-face { + font-family: "Redacted Script"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: + url("https://cdn.jsdelivr.net/gh/christiannaths/redacted-font@0.0.2/fonts/web/redacted-script-regular.woff2") + format("woff2"), + url("https://cdn.jsdelivr.net/gh/christiannaths/redacted-font@0.0.2/fonts/web/redacted-script-regular.woff") + format("woff"); +} + +[data-demo="true"] *:not(svg):not(path):not(circle):not(line):not(polyline):not(rect):not(polygon) { + font-family: "Redacted Script", cursive !important; +} + /* ─── Tailwind v4 theme bridge ─── */ @theme inline { --color-background: var(--bg); diff --git a/packages/web/src/lib/demo.ts b/packages/web/src/lib/demo.ts new file mode 100644 index 0000000..bc58373 --- /dev/null +++ b/packages/web/src/lib/demo.ts @@ -0,0 +1,10 @@ +const DEMO_KEY = "openconcho:demo"; + +export function getDemoMode(): boolean { + return localStorage.getItem(DEMO_KEY) === "true"; +} + +export function applyDemoMode(enabled: boolean): void { + document.documentElement.setAttribute("data-demo", String(enabled)); + localStorage.setItem(DEMO_KEY, String(enabled)); +} diff --git a/packages/web/src/main.tsx b/packages/web/src/main.tsx index 6dc7e15..9c4bf2a 100644 --- a/packages/web/src/main.tsx +++ b/packages/web/src/main.tsx @@ -2,9 +2,12 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { createRouter, RouterProvider } from "@tanstack/react-router"; import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; +import { applyDemoMode, getDemoMode } from "@/lib/demo"; import { routeTree } from "./routeTree.gen"; import "./index.css"; +applyDemoMode(getDemoMode()); + const queryClient = new QueryClient({ defaultOptions: { queries: {