From 40b3ccf33ea06c8466d86a7d10d409b3537a8bd9 Mon Sep 17 00:00:00 2001 From: teernisse Date: Fri, 30 Jan 2026 01:09:24 -0500 Subject: [PATCH] Add dark design system with CSS custom properties and refined typography Establish a cohesive dark UI foundation: - Define CSS custom properties for surfaces (4-level depth hierarchy), borders, foreground text (3-tier), accent colors, and canvas background - Add Inter (text) and JetBrains Mono (code) font loading via Google Fonts - Extend Tailwind with semantic color tokens, typography scale (caption/body/ subheading/heading), box shadows (card, glow), and animations (fade-in, slide-in, skeleton shimmer) - Add component-layer utilities: .btn system (primary/secondary/ghost/danger), .glass frosted overlays, .custom-checkbox, .skeleton loaders - Add .prose-message typographic styles for rendered markdown content - Add search minimap CSS (tick marks, viewport indicator) - Restyle scrollbars for thin dark appearance (WebKit + Firefox) - Replace hardcoded Tailwind color classes in CATEGORY_COLORS with semantic tokens (dot/border/text) mapped to the new design system Co-Authored-By: Claude Opus 4.5 --- src/client/index.html | 3 + src/client/lib/constants.ts | 59 ++++- src/client/styles/main.css | 478 +++++++++++++++++++++++++++++++++++- tailwind.config.js | 126 +++++++++- 4 files changed, 643 insertions(+), 23 deletions(-) diff --git a/src/client/index.html b/src/client/index.html index fec5bc8..88ae03e 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -4,6 +4,9 @@ Session Viewer + + +
diff --git a/src/client/lib/constants.ts b/src/client/lib/constants.ts index 4dc2915..b80423b 100644 --- a/src/client/lib/constants.ts +++ b/src/client/lib/constants.ts @@ -1,13 +1,52 @@ import type { MessageCategory } from "./types"; -export const CATEGORY_COLORS: Record = { - user_message: { border: "border-l-blue-500", bg: "bg-blue-50" }, - assistant_text: { border: "border-l-emerald-500", bg: "bg-white" }, - thinking: { border: "border-l-violet-500", bg: "bg-violet-50" }, - tool_call: { border: "border-l-amber-500", bg: "bg-amber-50" }, - tool_result: { border: "border-l-indigo-500", bg: "bg-indigo-50" }, - system_message: { border: "border-l-gray-500", bg: "bg-gray-100" }, - hook_progress: { border: "border-l-gray-400", bg: "bg-gray-50" }, - file_snapshot: { border: "border-l-pink-500", bg: "bg-pink-50" }, - summary: { border: "border-l-teal-500", bg: "bg-teal-50" }, +export const CATEGORY_COLORS: Record< + MessageCategory, + { dot: string; border: string; text: string } +> = { + user_message: { + dot: "bg-category-user", + border: "border-category-user-border", + text: "text-category-user", + }, + assistant_text: { + dot: "bg-category-assistant", + border: "border-category-assistant-border", + text: "text-category-assistant", + }, + thinking: { + dot: "bg-category-thinking", + border: "border-category-thinking-border", + text: "text-category-thinking", + }, + tool_call: { + dot: "bg-category-tool", + border: "border-category-tool-border", + text: "text-category-tool", + }, + tool_result: { + dot: "bg-category-result", + border: "border-category-result-border", + text: "text-category-result", + }, + system_message: { + dot: "bg-category-system", + border: "border-category-system-border", + text: "text-category-system", + }, + hook_progress: { + dot: "bg-category-hook", + border: "border-category-hook-border", + text: "text-category-hook", + }, + file_snapshot: { + dot: "bg-category-snapshot", + border: "border-category-snapshot-border", + text: "text-category-snapshot", + }, + summary: { + dot: "bg-category-summary", + border: "border-category-summary-border", + text: "text-category-summary", + }, }; diff --git a/src/client/styles/main.css b/src/client/styles/main.css index 2abdd02..fa721a1 100644 --- a/src/client/styles/main.css +++ b/src/client/styles/main.css @@ -2,41 +2,495 @@ @tailwind components; @tailwind utilities; -/* Custom scrollbar */ +/* ═══════════════════════════════════════════════ + Design System: CSS Custom Properties + Refined dark palette with proper depth hierarchy + ═══════════════════════════════════════════════ */ + +@layer base { + :root { + /* Surfaces — clear elevation hierarchy */ + --color-surface: #14181f; + --color-surface-raised: #1a1f2b; + --color-surface-overlay: #242a38; + --color-surface-inset: #0f1218; + + /* Borders — subtle separation */ + --color-border: #2a3140; + --color-border-muted: #1e2433; + + /* Foreground — text hierarchy */ + --color-foreground: #e8edf5; + --color-foreground-secondary: #8d96a8; + --color-foreground-muted: #505a6e; + + /* Accent — refined blue with depth */ + --color-accent: #5b9cf5; + --color-accent-light: #162544; + --color-accent-dark: #7db4ff; + + /* Layout background — deepest layer */ + --color-canvas: #0c1017; + + /* Glow/highlight colors */ + --color-glow-accent: rgba(91, 156, 245, 0.12); + --color-glow-success: rgba(63, 185, 80, 0.12); + + /* Inter font from Google Fonts CDN */ + font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-feature-settings: "cv02", "cv03", "cv04", "cv11"; + } + + /* Smooth transitions on all interactive elements */ + button, a, input, [role="button"] { + transition-property: color, background-color, border-color, box-shadow, opacity, transform; + transition-duration: 150ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } +} + +/* ═══════════════════════════════════════════════ + Custom scrollbar — thin, minimal, with fade + ═══════════════════════════════════════════════ */ + ::-webkit-scrollbar { width: 6px; + height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { - background: #d1d5db; + background: var(--color-border); border-radius: 3px; } +::-webkit-scrollbar-thumb:hover { + background: var(--color-foreground-muted); +} + +/* Firefox */ +* { + scrollbar-width: thin; + scrollbar-color: var(--color-border) transparent; +} + +/* ═══════════════════════════════════════════════ + Glass / frosted overlays + ═══════════════════════════════════════════════ */ + +.glass { + background: rgba(26, 31, 43, 0.82); + backdrop-filter: blur(16px) saturate(180%); + -webkit-backdrop-filter: blur(16px) saturate(180%); +} + +.glass-subtle { + background: rgba(20, 24, 31, 0.65); + backdrop-filter: blur(12px) saturate(160%); + -webkit-backdrop-filter: blur(12px) saturate(160%); +} + +/* ═══════════════════════════════════════════════ + Code blocks — refined syntax highlighting + ═══════════════════════════════════════════════ */ -/* Highlight.js overrides for client */ .hljs { - background: #f6f8fa; + background: var(--color-surface-inset); padding: 1rem; - border-radius: 6px; + border-radius: 0.5rem; + border: 1px solid var(--color-border-muted); overflow-x: auto; + font-size: 0.8125rem; + line-height: 1.6; + white-space: pre-wrap; + word-break: break-word; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); } -/* Search highlight */ +/* Code block container with language label + copy button */ +.code-block-wrapper { + position: relative; + margin: 0.75rem 0; +} + +.code-block-wrapper .code-lang-label { + position: absolute; + top: 0; + right: 0; + padding: 0.25rem 0.625rem; + font-size: 0.6875rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--color-foreground-muted); + background: var(--color-surface-inset); + border-bottom-left-radius: 0.375rem; + border-top-right-radius: 0.5rem; + border-left: 1px solid var(--color-border-muted); + border-bottom: 1px solid var(--color-border-muted); +} + +.code-block-wrapper .code-copy-btn { + position: absolute; + top: 0.375rem; + right: 0.375rem; + padding: 0.25rem 0.5rem; + font-size: 0.6875rem; + color: var(--color-foreground-muted); + background: var(--color-surface-raised); + border: 1px solid var(--color-border); + border-radius: 0.25rem; + cursor: pointer; + opacity: 0; + transition: opacity 150ms, background-color 150ms, color 150ms; +} + +.code-block-wrapper:hover .code-copy-btn { + opacity: 1; +} + +.code-block-wrapper .code-copy-btn:hover { + color: var(--color-foreground); + background: var(--color-surface-overlay); +} + +/* ═══════════════════════════════════════════════ + Search highlight — warm amber glow + ═══════════════════════════════════════════════ */ + mark.search-highlight { - background: #fde68a; + background: rgba(250, 204, 21, 0.2); color: inherit; - padding: 0 2px; - border-radius: 2px; + padding: 1px 3px; + border-radius: 3px; + box-shadow: 0 0 0 1px rgba(250, 204, 21, 0.35), + 0 0 8px rgba(250, 204, 21, 0.08); } -/* Redaction selection indicator */ +.search-match-focused { + outline: 2px solid #f59e0b; + outline-offset: 2px; + box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.15); +} + +/* ═══════════════════════════════════════════════ + Search minimap — scrollbar tick track + ═══════════════════════════════════════════════ */ + +.search-minimap { + position: absolute; + top: 0; + right: 0; + width: 8px; + height: 100%; + pointer-events: none; + z-index: 10; +} + +.search-minimap-tick { + position: absolute; + right: 0; + width: 8px; + height: 4px; + background: rgba(254, 240, 138, 0.7); + border: none; + padding: 0; + cursor: pointer; + pointer-events: auto; + border-radius: 1px; + transition: background-color 150ms, height 150ms, box-shadow 150ms; +} + +.search-minimap-tick:hover { + background: #fde047; + box-shadow: 0 0 4px rgba(253, 224, 71, 0.4); +} + +.search-minimap-viewport { + position: absolute; + right: 0; + width: 8px; + background: rgba(148, 163, 184, 0.18); + border-radius: 1px; + pointer-events: none; + transition: top 60ms linear, height 60ms linear; + min-height: 4px; +} + +.search-minimap-tick-active { + background: #f59e0b; + height: 6px; + box-shadow: 0 0 6px rgba(245, 158, 11, 0.5); +} + +/* ═══════════════════════════════════════════════ + Redaction selection indicator + ═══════════════════════════════════════════════ */ + .redaction-selected { outline: 2px solid #ef4444; outline-offset: 2px; + background-image: repeating-linear-gradient( + -45deg, + transparent, + transparent 4px, + rgba(239, 68, 68, 0.06) 4px, + rgba(239, 68, 68, 0.06) 8px + ); } -/* Message dimming for search */ +/* ═══════════════════════════════════════════════ + Message dimming for search + ═══════════════════════════════════════════════ */ + .message-dimmed { - opacity: 0.3; + opacity: 0.2; + transition: opacity 200ms ease; +} + +.message-dimmed:hover { + opacity: 0.45; +} + +/* ═══════════════════════════════════════════════ + Skeleton loading animation — refined shimmer + ═══════════════════════════════════════════════ */ + +.skeleton { + background: linear-gradient( + 90deg, + var(--color-border-muted) 0%, + var(--color-border) 40%, + var(--color-border-muted) 80% + ); + background-size: 300% 100%; + animation: skeletonShimmer 1.8s ease-in-out infinite; + border-radius: 0.375rem; +} + +@keyframes skeletonShimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -100% 0; } +} + +/* ═══════════════════════════════════════════════ + Prose overrides for message content + ═══════════════════════════════════════════════ */ + +.prose-message h1 { + font-size: 1.25rem; + font-weight: 600; + margin-top: 1.25rem; + margin-bottom: 0.5rem; + letter-spacing: -0.02em; + color: var(--color-foreground); +} + +.prose-message h2 { + font-size: 1.125rem; + font-weight: 600; + margin-top: 1rem; + margin-bottom: 0.375rem; + letter-spacing: -0.01em; + color: var(--color-foreground); +} + +.prose-message h3 { + font-size: 1rem; + font-weight: 600; + margin-top: 0.875rem; + margin-bottom: 0.375rem; + color: var(--color-foreground); +} + +.prose-message p { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + line-height: 1.625; +} + +.prose-message p:first-child { + margin-top: 0; +} + +.prose-message p:last-child { + margin-bottom: 0; +} + +.prose-message ul, +.prose-message ol { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + padding-left: 1.25rem; +} + +.prose-message li { + margin-top: 0.25rem; + margin-bottom: 0.25rem; + line-height: 1.5; +} + +.prose-message code:not(pre code) { + font-family: "JetBrains Mono", "Fira Code", "SF Mono", ui-monospace, monospace; + font-size: 0.8125em; + padding: 0.125rem 0.375rem; + border-radius: 0.25rem; + background: var(--color-surface-inset); + border: 1px solid var(--color-border-muted); + color: #c4a1ff; + font-weight: 500; +} + +.prose-message pre { + margin-top: 0.75rem; + margin-bottom: 0.75rem; + max-width: 100%; + overflow-x: auto; +} + +.prose-message blockquote { + border-left: 3px solid var(--color-border); + padding-left: 0.75rem; + color: var(--color-foreground-secondary); + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.prose-message a { + color: var(--color-accent); + text-decoration: underline; + text-underline-offset: 2px; + text-decoration-color: rgba(91, 156, 245, 0.3); + transition: text-decoration-color 150ms; +} + +.prose-message a:hover { + color: var(--color-accent-dark); + text-decoration-color: rgba(125, 180, 255, 0.6); +} + +.prose-message table { + width: 100%; + border-collapse: collapse; + margin-top: 0.75rem; + margin-bottom: 0.75rem; + font-size: 0.8125rem; +} + +.prose-message th, +.prose-message td { + border: 1px solid var(--color-border-muted); + padding: 0.375rem 0.75rem; + text-align: left; +} + +.prose-message th { + background: var(--color-surface-inset); + font-weight: 600; +} + +/* ═══════════════════════════════════════════════ + Focus ring system + ═══════════════════════════════════════════════ */ + +:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; +} + +/* Custom checkbox styling */ +.custom-checkbox { + appearance: none; + width: 1rem; + height: 1rem; + border: 1.5px solid var(--color-border); + border-radius: 0.25rem; + background: var(--color-surface); + cursor: pointer; + flex-shrink: 0; + transition: all 150ms; + position: relative; +} + +.custom-checkbox:hover { + border-color: var(--color-foreground-muted); +} + +.custom-checkbox:checked { + background: var(--color-accent); + border-color: var(--color-accent); + box-shadow: 0 0 0 2px rgba(91, 156, 245, 0.15); +} + +.custom-checkbox:checked::after { + content: ""; + position: absolute; + top: 1px; + left: 4px; + width: 5px; + height: 9px; + border: solid #0c1017; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +.custom-checkbox.checkbox-danger:checked { + background: #ef4444; + border-color: #ef4444; + box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.15); +} + +/* ═══════════════════════════════════════════════ + Button system + ═══════════════════════════════════════════════ */ + +@layer components { + .btn { + @apply inline-flex items-center justify-center font-medium rounded-lg transition-all; + @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2; + @apply active:scale-[0.97]; + --tw-ring-offset-color: var(--color-surface); + } + + .btn-sm { + @apply px-3 py-1.5 text-caption; + } + + .btn-md { + @apply px-4 py-2 text-body; + } + + .btn-primary { + background: linear-gradient(135deg, #5b9cf5, #4a8be0); + @apply text-white; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08); + @apply hover:brightness-110; + @apply disabled:opacity-50 disabled:cursor-not-allowed; + @apply focus-visible:ring-accent; + } + + .btn-secondary { + @apply bg-surface-raised text-foreground-secondary border border-border; + @apply hover:bg-surface-overlay hover:text-foreground hover:border-foreground-muted/30; + @apply disabled:opacity-50 disabled:cursor-not-allowed; + @apply focus-visible:ring-accent; + } + + .btn-ghost { + @apply text-foreground-secondary; + @apply hover:bg-surface-overlay hover:text-foreground; + @apply disabled:opacity-50 disabled:cursor-not-allowed; + @apply focus-visible:ring-accent; + } + + .btn-danger { + background: linear-gradient(135deg, #dc2626, #c42020); + @apply text-white; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); + @apply hover:brightness-110; + @apply disabled:opacity-50 disabled:cursor-not-allowed; + @apply focus-visible:ring-red-500; + } } diff --git a/tailwind.config.js b/tailwind.config.js index de50715..d96f3d1 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,7 +2,131 @@ export default { content: ["./src/client/**/*.{html,tsx,ts}"], theme: { - extend: {}, + extend: { + colors: { + // Semantic surface colors via CSS variables (dark-mode ready) + surface: { + DEFAULT: "var(--color-surface)", + raised: "var(--color-surface-raised)", + overlay: "var(--color-surface-overlay)", + inset: "var(--color-surface-inset)", + }, + border: { + DEFAULT: "var(--color-border)", + muted: "var(--color-border-muted)", + }, + foreground: { + DEFAULT: "var(--color-foreground)", + secondary: "var(--color-foreground-secondary)", + muted: "var(--color-foreground-muted)", + }, + accent: { + DEFAULT: "var(--color-accent)", + light: "var(--color-accent-light)", + dark: "var(--color-accent-dark)", + }, + + // Cohesive message category colors — unified saturation/luminance band + category: { + user: { DEFAULT: "#5b9cf5", light: "#111e35", border: "#1e3560" }, + assistant: { DEFAULT: "#4ebe68", light: "#0f2519", border: "#1b4a30" }, + thinking: { DEFAULT: "#b78ef5", light: "#1a1432", border: "#362868" }, + tool: { DEFAULT: "#d4a030", light: "#1a1810", border: "#4a3818" }, + result: { DEFAULT: "#8890f5", light: "#161830", border: "#2a2c5e" }, + system: { DEFAULT: "#8996a8", light: "#161a22", border: "#2a3140" }, + hook: { DEFAULT: "#586070", light: "#12161e", border: "#222830" }, + snapshot: { DEFAULT: "#f07ab5", light: "#221428", border: "#552040" }, + summary: { DEFAULT: "#38d4b8", light: "#102024", border: "#1a4a42" }, + }, + }, + fontFamily: { + sans: [ + "Inter", + "system-ui", + "-apple-system", + "BlinkMacSystemFont", + "Segoe UI", + "sans-serif", + ], + mono: [ + "JetBrains Mono", + "Fira Code", + "SF Mono", + "Cascadia Code", + "ui-monospace", + "SFMono-Regular", + "monospace", + ], + }, + fontSize: { + // Typography scale: caption, body, subheading, heading + caption: ["0.75rem", { lineHeight: "1rem", letterSpacing: "0.01em" }], + body: ["0.875rem", { lineHeight: "1.5rem", letterSpacing: "0" }], + subheading: ["0.9375rem", { lineHeight: "1.5rem", letterSpacing: "-0.01em" }], + heading: ["1.125rem", { lineHeight: "1.75rem", letterSpacing: "-0.02em" }], + "heading-lg": ["1.5rem", { lineHeight: "2rem", letterSpacing: "-0.025em" }], + }, + spacing: { + // Refined spacing for layout rhythm + 0.5: "0.125rem", + 1.5: "0.375rem", + 2.5: "0.625rem", + 4.5: "1.125rem", + 18: "4.5rem", + }, + borderRadius: { + DEFAULT: "0.375rem", + lg: "0.5rem", + xl: "0.75rem", + "2xl": "1rem", + }, + boxShadow: { + "xs": "0 1px 2px 0 rgb(0 0 0 / 0.05)", + "sm": "0 1px 3px 0 rgb(0 0 0 / 0.08), 0 1px 2px -1px rgb(0 0 0 / 0.05)", + "DEFAULT": "0 2px 8px -2px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.06)", + "md": "0 4px 12px -2px rgb(0 0 0 / 0.1), 0 2px 6px -2px rgb(0 0 0 / 0.06)", + "lg": "0 12px 28px -6px rgb(0 0 0 / 0.15), 0 4px 12px -4px rgb(0 0 0 / 0.08)", + "card": "0 1px 3px 0 rgb(0 0 0 / 0.06), 0 1px 2px -1px rgb(0 0 0 / 0.04)", + "card-hover": "0 6px 16px -4px rgb(0 0 0 / 0.14), 0 2px 6px -2px rgb(0 0 0 / 0.06)", + "glow-accent": "0 0 16px rgba(91, 156, 245, 0.12)", + "glow-success": "0 0 16px rgba(78, 190, 104, 0.12)", + }, + transitionDuration: { + DEFAULT: "150ms", + }, + transitionTimingFunction: { + DEFAULT: "cubic-bezier(0.4, 0, 0.2, 1)", + }, + animation: { + "fade-in": "fadeIn 250ms ease-out", + "slide-in": "slideIn 250ms ease-out", + "slide-up": "slideUp 300ms cubic-bezier(0.16, 1, 0.3, 1)", + "skeleton": "skeleton 1.5s ease-in-out infinite", + "pulse-subtle": "pulseSubtle 2s ease-in-out infinite", + }, + keyframes: { + fadeIn: { + "0%": { opacity: "0", transform: "translateY(6px)" }, + "100%": { opacity: "1", transform: "translateY(0)" }, + }, + slideIn: { + "0%": { opacity: "0", transform: "translateX(-8px)" }, + "100%": { opacity: "1", transform: "translateX(0)" }, + }, + slideUp: { + "0%": { opacity: "0", transform: "translateY(12px)" }, + "100%": { opacity: "1", transform: "translateY(0)" }, + }, + skeleton: { + "0%, 100%": { opacity: "1" }, + "50%": { opacity: "0.4" }, + }, + pulseSubtle: { + "0%, 100%": { opacity: "1" }, + "50%": { opacity: "0.7" }, + }, + }, + }, }, plugins: [], };