fix: sync demo theme with app

This commit is contained in:
SnapOtter
2026-07-04 13:22:33 +08:00
parent 7e6939bf0d
commit f6f7b5a4bc
7 changed files with 58 additions and 113 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SnapOtter Demo</title>
<meta name="description" content="Try SnapOtter - open-source, self-hosted file processing platform. Live demo." />
<meta name="theme-color" content="#3b82f6" />
<meta name="theme-color" content="#E07832" />
<link rel="icon" type="image/png" sizes="48x48" href="/favicon.png" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
+3 -3
View File
@@ -6,14 +6,14 @@ export function DemoBanner() {
if (dismissed) return null;
return (
<div className="relative z-[9999] flex items-center justify-center gap-3 bg-blue-600 px-4 py-2 text-sm text-white">
<div className="relative z-[9999] flex items-center justify-center gap-3 bg-primary px-4 py-2 text-sm text-primary-foreground">
<span>
This is a live demo. Processing is disabled.{" "}
<a
href="https://github.com/snapotter-hq/SnapOtter"
target="_blank"
rel="noopener noreferrer"
className="font-semibold underline underline-offset-2 hover:text-blue-100"
className="font-semibold underline underline-offset-2 hover:text-primary-subtle"
>
Self-host SnapOtter
</a>{" "}
@@ -22,7 +22,7 @@ export function DemoBanner() {
<button
type="button"
onClick={() => setDismissed(true)}
className="ms-2 shrink-0 rounded px-1.5 py-0.5 text-xs font-medium hover:bg-blue-700"
className="ms-2 shrink-0 rounded px-1.5 py-0.5 text-xs font-medium hover:bg-primary-dark"
aria-label="Dismiss banner"
>
Dismiss
+1 -1
View File
@@ -20,7 +20,7 @@ function patchDemoErrors(root: Element) {
if (el.dataset.demoPatch) return;
el.dataset.demoPatch = "1";
el.classList.remove("text-red-500");
el.style.color = "#3b82f6";
el.style.color = "var(--color-primary)";
el.style.fontSize = "13px";
el.style.lineHeight = "1.5";
+3 -106
View File
@@ -1,108 +1,5 @@
@import "tailwindcss";
@import "../../../web/src/styles/globals.css";
@source "../../../web/src/**/*.tsx";
@source "../../../web/src/**/*.ts";
:root {
font-family:
"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", "Noto Sans", "Noto Sans CJK SC", "Noto Sans CJK TC",
"Noto Sans CJK JP", "Noto Sans CJK KR", "Noto Sans Arabic",
"Noto Sans Devanagari", "Noto Sans Thai", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji";
}
@theme {
--color-primary: #3b82f6;
--color-primary-foreground: #ffffff;
--color-background: #ffffff;
--color-foreground: #0f172a;
--color-muted: #f1f5f9;
--color-muted-foreground: #64748b;
--color-border: #e2e8f0;
--color-card: #ffffff;
--color-card-foreground: #0f172a;
--color-sidebar: #f8fafc;
--color-sidebar-foreground: #334155;
--color-accent: #3b82f6;
--color-destructive: #ef4444;
}
.dark {
--color-background: #0f172a;
--color-foreground: #f8fafc;
--color-muted: #1e293b;
--color-muted-foreground: #94a3b8;
--color-border: #334155;
--color-card: #1e293b;
--color-card-foreground: #f8fafc;
--color-sidebar: #1e293b;
--color-sidebar-foreground: #cbd5e1;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
height: 4px;
border-radius: 9999px;
background: var(--color-muted);
outline: none;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--color-foreground);
cursor: pointer;
border: 2px solid var(--color-background);
box-shadow: 0 0 0 1px var(--color-border);
transition: transform 0.1s;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.15);
}
input[type="range"]::-moz-range-thumb {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--color-foreground);
cursor: pointer;
border: 2px solid var(--color-background);
box-shadow: 0 0 0 1px var(--color-border);
}
input[type="range"]::-moz-range-track {
height: 4px;
border-radius: 9999px;
background: var(--color-muted);
}
[dir="rtl"] {
direction: rtl;
text-align: right;
}
[dir="rtl"] input[type="text"],
[dir="rtl"] input[type="password"],
[dir="rtl"] input[type="email"],
[dir="rtl"] input[type="number"],
[dir="rtl"] input[type="search"],
[dir="rtl"] textarea,
[dir="rtl"] select {
text-align: right;
}
[dir="rtl"] input[type="range"] {
direction: ltr;
}
[dir="rtl"] pre,
[dir="rtl"] code {
direction: ltr;
text-align: left;
}
@source "../**/*.tsx";
@source "../**/*.ts";
+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>SnapOtter</title>
<meta name="description" content="Open-source, self-hosted file processing platform" />
<meta name="theme-color" content="#3b82f6" />
<meta name="theme-color" content="#E07832" />
<link rel="icon" type="image/png" sizes="48x48" href="/favicon.png" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
+1 -1
View File
@@ -5,7 +5,7 @@
{ "src": "/logo-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/logo-512.png", "sizes": "512x512", "type": "image/png" }
],
"theme_color": "#3b82f6",
"theme_color": "#E07832",
"background_color": "#ffffff",
"display": "standalone"
}
+48
View File
@@ -0,0 +1,48 @@
import { readFileSync } from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
const demoStylesPath = path.resolve(process.cwd(), "apps/demo/src/styles/globals.css");
const demoBannerPath = path.resolve(process.cwd(), "apps/demo/src/demo-banner.tsx");
const demoHtmlPath = path.resolve(process.cwd(), "apps/demo/index.html");
const demoMainPath = path.resolve(process.cwd(), "apps/demo/src/main.tsx");
const webHtmlPath = path.resolve(process.cwd(), "apps/web/index.html");
const webManifestPath = path.resolve(process.cwd(), "apps/web/public/manifest.json");
const webStylesPath = path.resolve(process.cwd(), "apps/web/src/styles/globals.css");
function readFixture(filePath: string): string {
return readFileSync(filePath, "utf8");
}
describe("demo theme", () => {
it("inherits the real web app global stylesheet instead of carrying copied color tokens", () => {
const demoStyles = readFixture(demoStylesPath);
expect(demoStyles).toContain('@import "../../../web/src/styles/globals.css";');
expect(demoStyles).toContain('@source "../../../web/src/**/*.tsx";');
expect(demoStyles).toContain('@source "../../../web/src/**/*.ts";');
expect(demoStyles).not.toContain("--color-primary:");
expect(demoStyles).not.toContain("#3b82f6");
});
it("does not hardcode the old blue demo accent in demo-only UI", () => {
const demoOnlyUi = [demoBannerPath, demoMainPath].map(readFixture).join("\n");
expect(demoOnlyUi).not.toMatch(/bg-blue-|text-blue-|hover:bg-blue-|hover:text-blue-|#3b82f6/i);
});
it("uses the current app primary color for browser theme metadata", () => {
const webStyles = readFixture(webStylesPath);
const primaryColor = webStyles.match(/--color-primary:\s*(#[0-9a-f]{6})/i)?.[1];
expect(primaryColor).toBeDefined();
for (const htmlPath of [demoHtmlPath, webHtmlPath]) {
expect(readFixture(htmlPath)).toContain(`name="theme-color" content="${primaryColor}"`);
}
expect(JSON.parse(readFixture(webManifestPath))).toMatchObject({
theme_color: primaryColor,
});
});
});