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: {