-
-
Loading terminal...
-
Powered by Ghostty WASM
-
+ const showLoading = isLoading || !isInitialized;
+ const containerClassName = showLoading ? `${TERMINAL.minHeight} invisible` : TERMINAL.minHeight;
+
+ const loadingOverlay = (
+
+
+
Loading terminal...
+
Powered by Ghostty WASM
- );
- }
+
+ );
return (
-
+
+ {showLoading && loadingOverlay}
+
+
);
}
diff --git a/site/components/output-comparison/constants.ts b/site/components/output-comparison/constants.ts
index 6bd0ad4..6b3f2ba 100644
--- a/site/components/output-comparison/constants.ts
+++ b/site/components/output-comparison/constants.ts
@@ -1,4 +1,70 @@
-import type { OutputTab } from "./types";
+import type { OutputView, ViewMode, GhosttyTheme } from "./types";
+
+export const TEXT = {
+ title: {
+ highlight: "Real",
+ rest: "Output Comparison",
+ },
+ description: "See exactly what logsDX outputs for terminal vs browser",
+ labels: {
+ theme: "Theme",
+ terminalOutput: "Terminal Output",
+ browserOutput: "Browser Output",
+ processing: "Processing...",
+ loadingTerminal: "Loading terminal...",
+ significance: "The Terminal panel uses Ghostty, a real WebAssembly terminal emulator—not fake styling. What you see is exactly how these logs render in an actual terminal.",
+ },
+} as const;
+
+export const CLASSES = {
+ section: "py-24",
+ container: "container mx-auto px-4",
+ wrapper: "mx-auto max-w-6xl",
+ header: {
+ title: "mb-4 text-center text-5xl lg:text-6xl font-bold",
+ gradient: "bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent",
+ description: "mb-12 text-center text-xl text-slate-600 dark:text-slate-400",
+ },
+ grid: "grid gap-8 lg:grid-cols-3",
+ sidebar: "lg:col-span-1 space-y-6",
+ content: "lg:col-span-2",
+ label: "block text-sm font-medium mb-2 text-slate-700 dark:text-slate-300",
+ significanceText: "text-sm text-slate-500 dark:text-slate-400 mt-4 leading-relaxed",
+ tab: {
+ base: "px-4 py-2 rounded-lg text-sm font-medium transition-colors",
+ active: "bg-blue-600 text-white",
+ inactive: "bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600",
+ },
+ tabDescription: "text-sm text-slate-500 dark:text-slate-400 mb-4",
+ terminal: {
+ wrapper: "rounded-lg overflow-hidden border border-slate-700",
+ header: "bg-slate-800 px-4 py-2 flex items-center gap-2",
+ dots: "flex gap-1.5",
+ dot: {
+ red: "w-3 h-3 rounded-full bg-red-500",
+ yellow: "w-3 h-3 rounded-full bg-yellow-500",
+ green: "w-3 h-3 rounded-full bg-green-500",
+ },
+ title: "text-xs text-white/60 ml-2",
+ content: "p-4 min-h-[300px] overflow-auto",
+ },
+ outputCard: "bg-slate-100 dark:bg-slate-800 rounded-lg p-4",
+ outputTitle: "text-sm font-semibold mb-2",
+ outputCode: "text-xs text-slate-600 dark:text-slate-400 block",
+ outputFormat: "text-xs text-slate-500 mt-2",
+} as const;
+
+export const STYLES = {
+ headerDropShadow: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2))",
+ terminalBg: "#1e1e1e",
+} as const;
+
+export const TERMINAL = {
+ initTimeoutMs: 10000,
+ minHeight: "min-h-[300px]",
+ fontSize: 14,
+ fontFamily: "JetBrains Mono, Menlo, Monaco, Consolas, monospace",
+} as const;
export const SAMPLE_LOGS = [
"[INFO] Server started on port 3000",
@@ -8,31 +74,14 @@ export const SAMPLE_LOGS = [
"[SUCCESS] Deploy complete ✓",
];
-export const OUTPUT_TABS: {
- id: OutputTab;
- label: string;
- description: string;
-}[] = [
- {
- id: "ansi-raw",
- label: "ANSI (Raw)",
- description: "Raw escape codes sent to terminal",
- },
- {
- id: "ansi-rendered",
- label: "ANSI (Terminal)",
- description: "Real terminal rendering via Ghostty WASM",
- },
- {
- id: "html-raw",
- label: "HTML (Source)",
- description: "Raw HTML markup",
- },
- {
- id: "html-rendered",
- label: "HTML (Browser)",
- description: "How it looks in browser",
- },
+export const VIEWS: { id: OutputView; label: string }[] = [
+ { id: "terminal", label: "Terminal" },
+ { id: "html", label: "HTML" },
+];
+
+export const VIEW_MODES: { id: ViewMode; label: string }[] = [
+ { id: "rendered", label: "Rendered" },
+ { id: "source", label: "Source" },
];
export const THEME_OPTIONS = [
@@ -45,3 +94,16 @@ export const THEME_OPTIONS = [
"solarized-light",
"oh-my-zsh",
];
+
+export const ANSI_ESCAPE_REPLACEMENTS: [RegExp, string][] = [
+ [/\x1b/g, "\\x1b"],
+ [/\[/g, "["],
+];
+
+export const DEFAULT_GHOSTTY_THEME: GhosttyTheme = {
+ background: "#282a36", foreground: "#f8f8f2", cursor: "#f8f8f2", cursorAccent: "#282a36",
+ selectionBackground: "#44475a", black: "#000000", red: "#ff5555", green: "#50fa7b",
+ yellow: "#ffb86c", blue: "#ff79c6", magenta: "#bd93f9", cyan: "#8be9fd", white: "#f8f8f2",
+ brightBlack: "#6272a4", brightRed: "#ff6e6e", brightGreen: "#69ff94", brightYellow: "#ffca85",
+ brightBlue: "#ff92d0", brightMagenta: "#d6acff", brightCyan: "#a4ffff", brightWhite: "#ffffff",
+};
diff --git a/site/components/output-comparison/index.tsx b/site/components/output-comparison/index.tsx
index 30d6c0f..96106c6 100644
--- a/site/components/output-comparison/index.tsx
+++ b/site/components/output-comparison/index.tsx
@@ -1,290 +1,282 @@
"use client";
-import React, { useState, useEffect, useMemo } from "react";
+import React, { useState, useEffect } from "react";
import dynamic from "next/dynamic";
-import { getTheme, renderLine } from "logsdx";
-import { SAMPLE_LOGS, OUTPUT_TABS, THEME_OPTIONS } from "./constants";
-import type { OutputTab, ProcessedOutput } from "./types";
+import { getTheme } from "logsdx";
+import { SAMPLE_LOGS, THEME_OPTIONS, TEXT, CLASSES, STYLES, DEFAULT_GHOSTTY_THEME } from "./constants";
+import { themeToGhostty, processLogsWithTheme } from "./utils";
+import type { ViewMode, ProcessedOutput, GhosttyTheme } from "./types";
+
+const TerminalLoader = () => (
+
+ {TEXT.labels.loadingTerminal}
+
+);
const GhosttyTerminal = dynamic(
() => import("./GhosttyTerminal").then((mod) => mod.GhosttyTerminal),
- {
- ssr: false,
- loading: () => (
-
- Loading terminal...
-
- ),
- },
+ { ssr: false, loading: TerminalLoader },
);
-function escapeAnsiForDisplay(ansi: string): string {
- return ansi.replace(/\x1b/g, "\\x1b").replace(/\[/g, "[");
+function escapeHtmlForDisplay(html: string): string {
+ return html.replace(/&/g, "&").replace(//g, ">");
}
-function escapeHtmlForDisplay(html: string): string {
- return html
- .replace(/&/g, "&")
- .replace(//g, ">");
+const WINDOW_DOTS = ["red", "yellow", "green"] as const;
+const MODE_BUTTONS = [{ id: "rendered", label: "Rendered" }, { id: "source", label: "Source" }] as const;
+
+interface TerminalWindowProps {
+ title: string;
+ mode: ViewMode;
+ onModeChange: (mode: ViewMode) => void;
+ bgColor: string;
+ children: React.ReactNode;
}
-export function OutputComparison() {
- const [theme, setTheme] = useState("dracula");
- const [activeTab, setActiveTab] = useState
("ansi-raw");
+function TerminalWindowDots() {
+ const dots = WINDOW_DOTS.map((color) => {
+ const dotClass = CLASSES.terminal.dot[color];
+ return ;
+ });
+ return {dots}
;
+}
+
+interface ModeButtonsProps {
+ mode: ViewMode;
+ onModeChange: (mode: ViewMode) => void;
+}
+
+function getModeButtonClass(isActive: boolean): string {
+ const base = "px-2 py-1 text-xs rounded transition-colors";
+ if (isActive) return `${base} bg-white/20 text-white`;
+ return `${base} text-white/60 hover:text-white hover:bg-white/10`;
+}
+
+function ModeButtons({ mode, onModeChange }: ModeButtonsProps) {
+ const buttons = MODE_BUTTONS.map(({ id, label }) => {
+ const isActive = mode === id;
+ const className = getModeButtonClass(isActive);
+ const handleClick = () => onModeChange(id as ViewMode);
+ return ;
+ });
+ return {buttons}
;
+}
+
+function TerminalWindow({ title, mode, onModeChange, bgColor, children }: TerminalWindowProps) {
+ const wrapperClass = `${CLASSES.terminal.wrapper} h-full flex flex-col`;
+ const contentClass = `${CLASSES.terminal.content} flex-1`;
+ const contentStyle = { backgroundColor: bgColor };
+
+ return (
+
+
+
+ {title}
+
+
+
{children}
+
+ );
+}
+
+interface ThemeButtonProps {
+ theme: string;
+ isSelected: boolean;
+ onClick: () => void;
+}
+
+function getThemeButtonClass(isSelected: boolean): string {
+ const base = "w-full px-3 py-2 text-left text-sm rounded-lg transition-colors";
+ const selected = "bg-blue-600 text-white font-medium";
+ const unselected = "bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700";
+ if (isSelected) return `${base} ${selected}`;
+ return `${base} ${unselected}`;
+}
+
+function ThemeButton({ theme, isSelected, onClick }: ThemeButtonProps) {
+ const buttonClass = getThemeButtonClass(isSelected);
+ return ;
+}
+
+interface ThemeSidebarProps {
+ themeName: string;
+ onThemeChange: (theme: string) => void;
+}
+
+function ThemeSidebar({ themeName, onThemeChange }: ThemeSidebarProps) {
+ const themeButtons = THEME_OPTIONS.map((t) => {
+ const isSelected = themeName === t;
+ const handleClick = () => onThemeChange(t);
+ return ;
+ });
+
+ return (
+
+
+
+
{themeButtons}
+
+
{TEXT.labels.significance}
+
+ );
+}
+
+interface TerminalContentProps {
+ isLoading: boolean;
+ mode: ViewMode;
+ outputs: ProcessedOutput[];
+ ghosttyTheme: GhosttyTheme;
+}
+
+function TerminalContentSource({ outputs }: { outputs: ProcessedOutput[] }) {
+ const items = outputs.map((output, i) => (
+ {output.ansiVisible}
+ ));
+ return {items}
;
+}
+
+function TerminalContent({ isLoading, mode, outputs, ghosttyTheme }: TerminalContentProps) {
+ if (isLoading) {
+ return {TEXT.labels.processing}
;
+ }
+ if (mode === "rendered") {
+ const ansiOutputs = outputs.map((o) => o.ansi);
+ return ;
+ }
+ return ;
+}
+
+interface BrowserContentProps {
+ isLoading: boolean;
+ mode: ViewMode;
+ outputs: ProcessedOutput[];
+}
+
+function BrowserContentRendered({ outputs }: { outputs: ProcessedOutput[] }) {
+ const items = outputs.map((output, i) => {
+ const htmlContent = { __html: output.html };
+ return ;
+ });
+ return {items}
;
+}
+
+function BrowserContentSource({ outputs }: { outputs: ProcessedOutput[] }) {
+ const items = outputs.map((output, i) => {
+ const escaped = escapeHtmlForDisplay(output.html);
+ return {escaped}
;
+ });
+ return {items}
;
+}
+
+function BrowserContent({ isLoading, mode, outputs }: BrowserContentProps) {
+ if (isLoading) {
+ return {TEXT.labels.processing}
;
+ }
+ if (mode === "rendered") {
+ return ;
+ }
+ return ;
+}
+
+function useThemeLoader(themeName: string) {
const [outputs, setOutputs] = useState([]);
const [isLoading, setIsLoading] = useState(true);
- const [customLog, setCustomLog] = useState("");
-
- const logs = useMemo(() => {
- if (customLog.trim()) {
- return customLog.split("\n").filter(Boolean);
- }
- return SAMPLE_LOGS;
- }, [customLog]);
+ const [ghosttyTheme, setGhosttyTheme] = useState(DEFAULT_GHOSTTY_THEME);
useEffect(() => {
let cancelled = false;
+ setIsLoading(true);
- async function processLogs() {
- setIsLoading(true);
- try {
- const loadedTheme = await getTheme(theme);
-
+ getTheme(themeName)
+ .then((loadedTheme) => {
if (cancelled) return;
+ setGhosttyTheme(themeToGhostty(loadedTheme));
+ setOutputs(processLogsWithTheme(SAMPLE_LOGS, loadedTheme));
+ })
+ .catch((err) => !cancelled && console.error("Failed to process logs:", err))
+ .finally(() => !cancelled && setIsLoading(false));
- const results: ProcessedOutput[] = logs.map((log) => {
- const ansi = renderLine(log, loadedTheme, {
- outputFormat: "ansi",
- });
-
- const html = renderLine(log, loadedTheme, {
- outputFormat: "html",
- htmlStyleFormat: "css",
- escapeHtml: true,
- });
-
- return {
- ansi,
- html,
- ansiVisible: escapeAnsiForDisplay(ansi),
- };
- });
-
- if (!cancelled) {
- setOutputs(results);
- }
- } catch (err) {
- if (!cancelled) {
- console.error("Failed to process logs:", err);
- }
- } finally {
- if (!cancelled) {
- setIsLoading(false);
- }
- }
- }
-
- processLogs();
-
- return () => {
- cancelled = true;
- };
- }, [theme, logs]);
-
- const renderContent = () => {
- if (isLoading) {
- return (
-
- Processing...
-
- );
- }
-
- switch (activeTab) {
- case "ansi-raw":
- return (
-
- {outputs.map((output, i) => (
-
- {output.ansiVisible}
-
- ))}
-
- );
-
- case "ansi-rendered":
- return (
- o.ansi)}
- isLoading={isLoading}
- />
- );
-
- case "html-raw":
- return (
-
- {outputs.map((output, i) => (
-
- {escapeHtmlForDisplay(output.html)}
-
- ))}
-
- );
-
- case "html-rendered":
- return (
-
- {outputs.map((output, i) => (
-
- ))}
-
- );
- }
+ return () => { cancelled = true; };
+ }, [themeName]);
+
+ return { outputs, isLoading, ghosttyTheme };
+}
+
+function SectionHeader() {
+ const headerStyle = { filter: STYLES.headerDropShadow };
+ return (
+ <>
+
+ {TEXT.title.highlight} {TEXT.title.rest}
+
+ {TEXT.description}
+ >
+ );
+}
+
+interface OutputPanelsProps {
+ terminalMode: ViewMode;
+ browserMode: ViewMode;
+ onTerminalModeChange: (mode: ViewMode) => void;
+ onBrowserModeChange: (mode: ViewMode) => void;
+ outputs: ProcessedOutput[];
+ isLoading: boolean;
+ ghosttyTheme: GhosttyTheme;
+}
+
+function OutputPanels({ terminalMode, browserMode, onTerminalModeChange, onBrowserModeChange, outputs, isLoading, ghosttyTheme }: OutputPanelsProps) {
+ const bgColor = ghosttyTheme.background;
+ return (
+
+ );
+}
+
+function useOutputComparisonState() {
+ const [themeName, setThemeName] = useState("dracula");
+ const [terminalMode, setTerminalMode] = useState("rendered");
+ const [browserMode, setBrowserMode] = useState("rendered");
+ const themeData = useThemeLoader(themeName);
+ return { themeName, setThemeName, terminalMode, setTerminalMode, browserMode, setBrowserMode, ...themeData };
+}
+
+function buildPanelProps(state: ReturnType): OutputPanelsProps {
+ return {
+ terminalMode: state.terminalMode, browserMode: state.browserMode,
+ onTerminalModeChange: state.setTerminalMode, onBrowserModeChange: state.setBrowserMode,
+ outputs: state.outputs, isLoading: state.isLoading, ghosttyTheme: state.ghosttyTheme,
};
+}
+
+function OutputComparisonContent() {
+ const state = useOutputComparisonState();
+ const panelProps = buildPanelProps(state);
return (
-
-
-
-
-
- Real
- {" "}
- Output Comparison
-
-
- See exactly what logsDX outputs for terminal vs browser
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Output Formats
-
-
-
- ANSI:
-
- Escape codes for terminals
-
-
-
- HTML:
-
- Styled spans for browsers
-
-
-
-
-
-
-
-
- {OUTPUT_TABS.map((tab) => (
-
- ))}
-
-
-
- {OUTPUT_TABS.find((t) => t.id === activeTab)?.description}
-
-
-
-
-
-
- {activeTab.includes("ansi") ? "Terminal" : "Browser"}
-
-
-
- {renderContent()}
-
-
-
-
-
-
- Terminal Output
-
-
- logsdx.processLine(log)
-
-
- outputFormat: "ansi"
-
-
-
-
- Browser Output
-
-
- logsdx.processLine(log)
-
-
- outputFormat: "html"
-
-
-
-
-
+ <>
+
+
+
+
+
+ >
+ );
+}
+
+export function OutputComparison() {
+ return (
+
@@ -294,6 +286,7 @@ export function OutputComparison() {
export { GhosttyTerminal } from "./GhosttyTerminal";
export type {
OutputComparisonProps,
- OutputTab,
+ OutputView,
+ ViewMode,
GhosttyTerminalProps,
} from "./types";
diff --git a/site/components/output-comparison/types.ts b/site/components/output-comparison/types.ts
index c185b7c..5005f4d 100644
--- a/site/components/output-comparison/types.ts
+++ b/site/components/output-comparison/types.ts
@@ -8,13 +8,35 @@ export interface ProcessedOutput {
ansiVisible: string;
}
-export type OutputTab =
- | "ansi-raw"
- | "ansi-rendered"
- | "html-raw"
- | "html-rendered";
+export type OutputView = "terminal" | "html";
+export type ViewMode = "rendered" | "source";
export interface GhosttyTerminalProps {
ansiOutputs: string[];
isLoading: boolean;
+ theme: GhosttyTheme;
+}
+
+export interface GhosttyTheme {
+ background: string;
+ foreground: string;
+ cursor: string;
+ cursorAccent: string;
+ selectionBackground: string;
+ black: string;
+ red: string;
+ green: string;
+ yellow: string;
+ blue: string;
+ magenta: string;
+ cyan: string;
+ white: string;
+ brightBlack: string;
+ brightRed: string;
+ brightGreen: string;
+ brightYellow: string;
+ brightBlue: string;
+ brightMagenta: string;
+ brightCyan: string;
+ brightWhite: string;
}
diff --git a/site/components/output-comparison/utils.ts b/site/components/output-comparison/utils.ts
new file mode 100644
index 0000000..029a945
--- /dev/null
+++ b/site/components/output-comparison/utils.ts
@@ -0,0 +1,87 @@
+import type { Theme } from "logsdx";
+import { renderLine } from "logsdx";
+import type { GhosttyTheme, ProcessedOutput } from "./types";
+import { ANSI_ESCAPE_REPLACEMENTS } from "./constants";
+
+function escapeAnsiForDisplay(ansi: string): string {
+ return ANSI_ESCAPE_REPLACEMENTS.reduce((s, [pattern, replacement]) => s.replace(pattern, replacement), ansi);
+}
+
+export function processLogsWithTheme(logs: string[], theme: Theme): ProcessedOutput[] {
+ return logs.map((log) => {
+ const ansi = renderLine(log, theme, { outputFormat: "ansi" });
+ const html = renderLine(log, theme, { outputFormat: "html", htmlStyleFormat: "css", escapeHtml: true });
+ return { ansi, html, ansiVisible: escapeAnsiForDisplay(ansi) };
+ });
+}
+
+function adjustBrightness(hex: string, percent: number): string {
+ const num = parseInt(hex.replace("#", ""), 16);
+ const r = Math.min(255, Math.max(0, ((num >> 16) & 255) + Math.round(255 * percent / 100)));
+ const g = Math.min(255, Math.max(0, ((num >> 8) & 255) + Math.round(255 * percent / 100)));
+ const b = Math.min(255, Math.max(0, (num & 255) + Math.round(255 * percent / 100)));
+ return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, "0")}`;
+}
+
+const DARK = { bg: "#1e1e1e", fg: "#d4d4d4", black: "#000000", white: "", selBrightness: 20 };
+const LIGHT = { bg: "#ffffff", fg: "#1e1e1e", black: "", white: "#e5e5e5", selBrightness: -15 };
+const ANSI = { red: "#cd3131", green: "#0dbc79", yellow: "#e5e510", blue: "#2472c8", magenta: "#bc3fbc", cyan: "#11a8cd", muted: "#666666" };
+
+function or
(a: T | undefined, b: T): T {
+ if (a !== undefined) return a;
+ return b;
+}
+
+function getModeDefaults(theme: Theme) {
+ if (theme.mode === "dark" || theme.mode === "auto") return DARK;
+ return LIGHT;
+}
+
+function getSemanticColors(colors: NonNullable) {
+ const blue = or(colors.primary, or(colors.info, ANSI.blue));
+ const cyan = or(colors.info, or(colors.secondary, ANSI.cyan));
+ return {
+ red: or(colors.error, ANSI.red),
+ green: or(colors.success, ANSI.green),
+ yellow: or(colors.warning, ANSI.yellow),
+ blue,
+ magenta: or(colors.debug, ANSI.magenta),
+ cyan,
+ muted: or(colors.muted, ANSI.muted),
+ };
+}
+
+function getBrightColors(base: ReturnType) {
+ return {
+ brightRed: adjustBrightness(base.red, 15),
+ brightGreen: adjustBrightness(base.green, 15),
+ brightYellow: adjustBrightness(base.yellow, 15),
+ brightBlue: adjustBrightness(base.blue, 15),
+ brightMagenta: adjustBrightness(base.magenta, 15),
+ brightCyan: adjustBrightness(base.cyan, 15),
+ };
+}
+
+function getDerivedColors(mode: typeof DARK, bg: string, fg: string) {
+ const black = mode.black || adjustBrightness(bg, -10);
+ const white = mode.white || fg;
+ const selection = adjustBrightness(bg, mode.selBrightness);
+ return { black, white, selection };
+}
+
+export function themeToGhostty(theme: Theme): GhosttyTheme {
+ const mode = getModeDefaults(theme);
+ const colors = theme.colors || {};
+ const bg = or(colors.background, mode.bg);
+ const fg = or(colors.text, mode.fg);
+ const semantic = getSemanticColors(colors);
+ const bright = getBrightColors(semantic);
+ const derived = getDerivedColors(mode, bg, fg);
+
+ return {
+ background: bg, foreground: fg, cursor: fg, cursorAccent: bg,
+ selectionBackground: derived.selection,
+ black: derived.black, white: derived.white,
+ ...semantic, brightBlack: semantic.muted, ...bright, brightWhite: "#ffffff",
+ };
+}
diff --git a/site/components/schema-viz/constants.ts b/site/components/schema-viz/constants.ts
index 1a89c4f..60396b1 100644
--- a/site/components/schema-viz/constants.ts
+++ b/site/components/schema-viz/constants.ts
@@ -1,5 +1,92 @@
import type { SchemaSection } from "./types";
+export const TEXT = {
+ title: {
+ highlight: "Theme",
+ rest: "Schema",
+ },
+ description: "Understand how themes work under the hood",
+ labels: {
+ matchingPriority: "Matching Priority",
+ exampleTheme: "Example Theme",
+ howMatching: "How Matching Works",
+ required: "required",
+ themeJson: "my-theme.json",
+ },
+ matchingSteps: [
+ "Log line is tokenized into individual words and symbols",
+ "Each token is checked against matching rules in priority order",
+ "First matching rule determines the token's style",
+ "Unmatched tokens use defaultStyle",
+ "Styled tokens are rendered as ANSI or HTML",
+ ],
+} as const;
+
+export const CLASSES = {
+ section: "py-24",
+ container: "container mx-auto px-4",
+ wrapper: "mx-auto max-w-6xl",
+ header: {
+ title: "mb-4 text-center text-5xl lg:text-6xl font-bold",
+ gradient: "bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent",
+ description: "mb-12 text-center text-xl text-slate-600 dark:text-slate-400",
+ },
+ grid: "grid gap-8 lg:grid-cols-2",
+ tabs: {
+ wrapper: "flex gap-2 mb-6 flex-wrap",
+ button: {
+ base: "px-4 py-2 rounded-lg text-sm font-medium transition-colors",
+ active: "bg-blue-600 text-white",
+ inactive: "bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600",
+ },
+ },
+ card: "bg-white dark:bg-slate-800 rounded-lg p-6 border border-slate-200 dark:border-slate-700",
+ sectionTitle: "text-xl font-bold mb-2 text-blue-600 dark:text-blue-400",
+ sectionDescription: "text-slate-600 dark:text-slate-400 mb-6",
+ propertyList: "space-y-4",
+ property: {
+ wrapper: "border-l-2 border-blue-600/30 pl-4",
+ header: "flex items-center gap-2 mb-1",
+ name: "text-blue-600 dark:text-blue-400 font-semibold",
+ required: "text-xs bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400 px-1.5 py-0.5 rounded",
+ type: "text-xs text-slate-500 dark:text-slate-400",
+ description: "text-sm text-slate-600 dark:text-slate-400",
+ example: "text-xs text-slate-500 dark:text-slate-500 mt-1 block",
+ },
+ priority: {
+ wrapper: "space-y-2",
+ item: "flex items-center gap-3",
+ number: "w-6 h-6 rounded-full bg-blue-600/20 text-blue-600 dark:text-blue-400 text-xs flex items-center justify-center font-bold",
+ name: "text-sm text-blue-600 dark:text-blue-400",
+ description: "text-xs text-slate-500",
+ },
+ terminal: {
+ wrapper: "rounded-lg overflow-hidden border border-slate-700",
+ header: "bg-slate-800 px-4 py-2 flex items-center gap-2",
+ dots: "flex gap-1.5",
+ dot: {
+ red: "w-3 h-3 rounded-full bg-red-500",
+ yellow: "w-3 h-3 rounded-full bg-yellow-500",
+ green: "w-3 h-3 rounded-full bg-green-500",
+ },
+ title: "text-xs text-white/60 ml-2",
+ content: "p-4 bg-slate-900 text-sm overflow-auto max-h-[600px]",
+ code: "text-slate-300",
+ },
+ howMatching: {
+ wrapper: "mt-6 bg-gradient-to-r from-blue-600/10 to-purple-600/10 rounded-lg p-6 border border-blue-600/20",
+ title: "font-semibold mb-3 text-blue-600 dark:text-blue-400",
+ list: "space-y-3 text-sm text-slate-600 dark:text-slate-400",
+ item: "flex gap-2",
+ number: "text-blue-600",
+ },
+ sectionLabel: "font-semibold mb-4 text-slate-900 dark:text-white",
+} as const;
+
+export const STYLES = {
+ headerDropShadow: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2))",
+} as const;
+
export const SCHEMA_SECTIONS: SchemaSection[] = [
{
title: "Theme",
diff --git a/site/components/schema-viz/index.tsx b/site/components/schema-viz/index.tsx
index 4c08cac..1cd94a0 100644
--- a/site/components/schema-viz/index.tsx
+++ b/site/components/schema-viz/index.tsx
@@ -1,37 +1,40 @@
"use client";
import React, { useState } from "react";
-import { SCHEMA_SECTIONS, MATCHING_PRIORITY, EXAMPLE_THEME } from "./constants";
+import { SCHEMA_SECTIONS, MATCHING_PRIORITY, EXAMPLE_THEME, TEXT, CLASSES, STYLES } from "./constants";
export function SchemaVisualization() {
const [activeSection, setActiveSection] = useState(0);
const section = SCHEMA_SECTIONS[activeSection];
return (
-