From e23774dabd22f560ecc29c92bba3f199ff96f6a3 Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:19:29 +0200 Subject: [PATCH] refactor(design): the bot demo was the sixth colour system #144 branded the bot page sections. It did not reach the demo underneath them, so every first-party bot still ran the per-colour palette in its interactive step -- the buttons, focus rings, progress dots, message bubbles, dropzone and context panel -- plus the bot switcher in the nav. The colour came from lib/demo/botDemoConfigs.ts, which carried its own accentColor per bot (blue, green, red, indigo, amber). That is the sixth place a colour was chosen, after: globals.css, lib/config/colors.ts, lib/hooks/navigationConfig.ts, ACCENT_HERO_CLASSES, and data/professionals.ts. DemoSection and BotSwitcher are used ONLY by the first-party bot pages (verified: no custom-bot page imports either), so both are now brand: BRAND_BUTTON, BRAND_FOCUS_RING, BRAND_STARTER, BRAND_CHECKBOX, BRAND_PROGRESS, BRAND_MESSAGE, BRAND_DROPZONE, BRAND_CONTEXT_PANEL, BRAND_SWITCHER. accentColor is gone from the demo config, its type, and the whole prop chain. What still emits raw palette, correctly: VISION_STATUS_CONFIG roadmap status -- completed green, in-progress blue, planned amber, vision purple. Four states that have to stay tellable apart; collapsing them to ochre would destroy the information. the /personal templates per-template identity in the memorial/legends product, chosen from an allowlisted surface. Both live behind lib/config/colors.ts, which the guard already documents as an exception -- so the guard passing while those classes ship is correct, not a blind spot. The blind spot was data/professionals.ts, fixed in #149. verify: format, lint, typecheck, 256 tests, build -- all green. Bot demo confirmed ochre end to end against a local production build. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HVwg8DKHQktxJuHeLM3xpG --- app/bots/BotNavigation.tsx | 3 -- components/navigation/BotSwitcher.tsx | 7 ++--- components/shared/demo/DemoChat.tsx | 18 ++++------- components/shared/demo/DemoContextPanel.tsx | 7 ++--- components/shared/demo/DemoFileUpload.tsx | 7 ++--- components/shared/demo/DemoIntake.tsx | 15 +++------- components/shared/demo/DemoMessage.tsx | 18 ++++------- components/shared/demo/DemoProgress.tsx | 8 ++--- components/shared/demo/DemoSection.tsx | 6 +--- lib/config/colors.ts | 33 +++++++++++++++++++++ lib/demo/botDemoConfigs.ts | 6 ---- lib/demo/types.ts | 3 -- 12 files changed, 57 insertions(+), 74 deletions(-) diff --git a/app/bots/BotNavigation.tsx b/app/bots/BotNavigation.tsx index 52ce5637..0b642c8d 100644 --- a/app/bots/BotNavigation.tsx +++ b/app/bots/BotNavigation.tsx @@ -15,7 +15,6 @@ interface BotNavigationProps { botTitle: string; botEmoji: string; botDescription?: string; - accentColor?: string; menuItems: BotMenuItem[]; chatLink?: string; sections?: boolean; @@ -30,7 +29,6 @@ export const BotNavigation: React.FC = ({ botTitle, botEmoji, botDescription: _botDescription = '', - accentColor = 'blue', menuItems: sectionMenuItems, chatLink, sections = true, @@ -79,7 +77,6 @@ export const BotNavigation: React.FC = ({ currentBotSlug={botSlug} currentBotTitle={botTitle} currentBotEmoji={botEmoji} - accentColor={accentColor} /> diff --git a/components/navigation/BotSwitcher.tsx b/components/navigation/BotSwitcher.tsx index 59349eac..c574486b 100644 --- a/components/navigation/BotSwitcher.tsx +++ b/components/navigation/BotSwitcher.tsx @@ -5,26 +5,23 @@ import { Popover, Transition, Portal } from '@headlessui/react'; import Link from 'next/link'; import bots from '@/data/bots'; import { getBotPath } from '@/lib/routes'; -import { BOT_SWITCHER_COLORS, type CustomBotAccentColor } from '@/lib/config/colors'; +import { BRAND_SWITCHER } from '@/lib/config/colors'; interface BotSwitcherProps { currentBotSlug: string; currentBotTitle: string; currentBotEmoji: string; - accentColor?: string; } export function BotSwitcher({ currentBotSlug, currentBotTitle, currentBotEmoji, - accentColor = 'blue', }: BotSwitcherProps) { const buttonRef = useRef(null); // Filter out current bot and bots without nav config const otherBots = bots.filter((bot) => bot.slug !== currentBotSlug && bot.nav); - const colors = - BOT_SWITCHER_COLORS[accentColor as CustomBotAccentColor] || BOT_SWITCHER_COLORS.blue; + const colors = BRAND_SWITCHER; // Get button position for portal positioning const getDropdownStyle = () => { diff --git a/components/shared/demo/DemoChat.tsx b/components/shared/demo/DemoChat.tsx index 19a5a417..a1a1bbce 100644 --- a/components/shared/demo/DemoChat.tsx +++ b/components/shared/demo/DemoChat.tsx @@ -2,12 +2,7 @@ import { useState, useRef, useEffect, type FC, type FormEvent, type KeyboardEvent } from 'react'; import type { ChatMessage, DemoOutputConfig } from '@/lib/demo/types'; -import type { BotAccentColor } from '@/types/bot'; -import { - ACCENT_BUTTON_CLASSES, - ACCENT_FOCUS_RING_CLASSES, - ACCENT_STARTER_CLASSES, -} from '@/lib/config/colors'; +import { BRAND_BUTTON, BRAND_FOCUS_RING, BRAND_STARTER } from '@/lib/config/colors'; import { DemoMessage, DemoDisclaimer } from './DemoMessage'; interface DemoChatProps { @@ -16,7 +11,6 @@ interface DemoChatProps { isLoading: boolean; starterQuestions: string[]; botIcon: string; - accentColor: BotAccentColor; outputConfig: DemoOutputConfig; } @@ -42,15 +36,14 @@ export const DemoChat: FC = ({ isLoading, starterQuestions, botIcon, - accentColor, outputConfig, }) => { const [input, setInput] = useState(''); const messagesEndRef = useRef(null); const textareaRef = useRef(null); - const buttonClass = ACCENT_BUTTON_CLASSES[accentColor]; - const focusClass = ACCENT_FOCUS_RING_CLASSES[accentColor]; - const starterClass = ACCENT_STARTER_CLASSES[accentColor]; + const buttonClass = BRAND_BUTTON; + const focusClass = BRAND_FOCUS_RING; + const starterClass = BRAND_STARTER; // Show starter questions only when there's just the welcome message const showStarters = messages.length === 1 && messages[0].role === 'assistant'; @@ -109,7 +102,6 @@ export const DemoChat: FC = ({ key={message.id} message={message} botIcon={botIcon} - accentColor={accentColor} outputConfig={outputConfig} /> ))} @@ -142,7 +134,7 @@ export const DemoChat: FC = ({ {/* Disclaimer */} {outputConfig.showDisclaimer && (
- +
)} diff --git a/components/shared/demo/DemoContextPanel.tsx b/components/shared/demo/DemoContextPanel.tsx index 82781f99..1b0c4315 100644 --- a/components/shared/demo/DemoContextPanel.tsx +++ b/components/shared/demo/DemoContextPanel.tsx @@ -2,15 +2,13 @@ import { useState, type FC } from 'react'; import type { IntakeQuestion, IntakeResponses, UploadedFile } from '@/lib/demo/types'; -import type { BotAccentColor } from '@/types/bot'; -import { ACCENT_CONTEXT_PANEL_CLASSES } from '@/lib/config/colors'; +import { BRAND_CONTEXT_PANEL } from '@/lib/config/colors'; interface DemoContextPanelProps { intakeQuestions: IntakeQuestion[]; intakeResponses: IntakeResponses; files: UploadedFile[]; onReset: () => void; - accentColor: BotAccentColor; } export const DemoContextPanel: FC = ({ @@ -18,10 +16,9 @@ export const DemoContextPanel: FC = ({ intakeResponses, files, onReset, - accentColor, }) => { const [isExpanded, setIsExpanded] = useState(false); - const colors = ACCENT_CONTEXT_PANEL_CLASSES[accentColor]; + const colors = BRAND_CONTEXT_PANEL; // Filter to only show answered questions const answeredQuestions = intakeQuestions.filter((q) => { diff --git a/components/shared/demo/DemoFileUpload.tsx b/components/shared/demo/DemoFileUpload.tsx index 243f6864..d201ffa1 100644 --- a/components/shared/demo/DemoFileUpload.tsx +++ b/components/shared/demo/DemoFileUpload.tsx @@ -2,8 +2,7 @@ import { useState, useRef, type FC, type DragEvent, type ChangeEvent } from 'react'; import type { FileCategory, UploadedFile } from '@/lib/demo/types'; -import type { BotAccentColor } from '@/types/bot'; -import { ACCENT_DROPZONE_CLASSES } from '@/lib/config/colors'; +import { BRAND_DROPZONE } from '@/lib/config/colors'; import { formatBytes } from '@/lib/format'; interface DemoFileUploadProps { @@ -12,7 +11,6 @@ interface DemoFileUploadProps { onUpload: (files: File[]) => Promise; onRemove: (fileId: string) => void; isUploading: boolean; - accentColor: BotAccentColor; } function getStatusIcon(status: UploadedFile['status']) { @@ -95,11 +93,10 @@ export const DemoFileUpload: FC = ({ onUpload, onRemove, isUploading, - accentColor, }) => { const [isDragging, setIsDragging] = useState(false); const fileInputRef = useRef(null); - const colors = ACCENT_DROPZONE_CLASSES[accentColor]; + const colors = BRAND_DROPZONE; // Get all accepted file types from categories const acceptedTypes = Array.from(new Set(categories.flatMap((c) => c.acceptedTypes))).join(','); diff --git a/components/shared/demo/DemoIntake.tsx b/components/shared/demo/DemoIntake.tsx index a5c7938d..8f5f326f 100644 --- a/components/shared/demo/DemoIntake.tsx +++ b/components/shared/demo/DemoIntake.tsx @@ -2,19 +2,13 @@ import { useState, type FC, type FormEvent } from 'react'; import type { IntakeQuestion, IntakeResponses } from '@/lib/demo/types'; -import type { BotAccentColor } from '@/types/bot'; -import { - ACCENT_BUTTON_CLASSES, - ACCENT_FOCUS_RING_CLASSES, - ACCENT_CHECKBOX_CLASSES, -} from '@/lib/config/colors'; +import { BRAND_BUTTON, BRAND_FOCUS_RING, BRAND_CHECKBOX } from '@/lib/config/colors'; interface DemoIntakeProps { questions: IntakeQuestion[]; responses: IntakeResponses; onUpdate: (questionId: string, value: string | string[]) => void; onStart: () => void; - accentColor: BotAccentColor; icon: string; botName: string; } @@ -24,14 +18,13 @@ export const DemoIntake: FC = ({ responses, onUpdate, onStart, - accentColor, icon, botName, }) => { const [showAdvanced, setShowAdvanced] = useState(false); - const buttonClass = ACCENT_BUTTON_CLASSES[accentColor]; - const focusClass = ACCENT_FOCUS_RING_CLASSES[accentColor]; - const checkboxClass = ACCENT_CHECKBOX_CLASSES[accentColor]; + const buttonClass = BRAND_BUTTON; + const focusClass = BRAND_FOCUS_RING; + const checkboxClass = BRAND_CHECKBOX; const essentialQuestions = questions.filter((q) => q.phase === 'essential'); const advancedQuestions = questions.filter((q) => q.phase === 'advanced'); diff --git a/components/shared/demo/DemoMessage.tsx b/components/shared/demo/DemoMessage.tsx index a7e1d7c9..baf13c30 100644 --- a/components/shared/demo/DemoMessage.tsx +++ b/components/shared/demo/DemoMessage.tsx @@ -2,24 +2,17 @@ import type { FC } from 'react'; import type { ChatMessage, DemoOutputConfig } from '@/lib/demo/types'; -import type { BotAccentColor } from '@/types/bot'; -import { ACCENT_MESSAGE_CLASSES } from '@/lib/config/colors'; +import { BRAND_MESSAGE } from '@/lib/config/colors'; interface DemoMessageProps { message: ChatMessage; botIcon: string; - accentColor: BotAccentColor; outputConfig: DemoOutputConfig; } -export const DemoMessage: FC = ({ - message, - botIcon, - accentColor, - outputConfig, -}) => { +export const DemoMessage: FC = ({ message, botIcon, outputConfig }) => { const isUser = message.role === 'user'; - const colors = ACCENT_MESSAGE_CLASSES[accentColor]; + const colors = BRAND_MESSAGE; return (
@@ -100,13 +93,12 @@ export const DemoMessage: FC = ({ // Disclaimer component shown at bottom of chat interface DemoDisclaimerProps { config: DemoOutputConfig; - accentColor: BotAccentColor; } -export const DemoDisclaimer: FC = ({ config, accentColor }) => { +export const DemoDisclaimer: FC = ({ config }) => { if (!config.showDisclaimer || !config.disclaimerText) return null; - const colors = ACCENT_MESSAGE_CLASSES[accentColor]; + const colors = BRAND_MESSAGE; return (
diff --git a/components/shared/demo/DemoProgress.tsx b/components/shared/demo/DemoProgress.tsx index a62d13cf..e21e4f4e 100644 --- a/components/shared/demo/DemoProgress.tsx +++ b/components/shared/demo/DemoProgress.tsx @@ -2,12 +2,10 @@ import type { FC } from 'react'; import type { DemoStep } from '@/lib/demo/types'; -import type { BotAccentColor } from '@/types/bot'; -import { ACCENT_PROGRESS_CLASSES } from '@/lib/config/colors'; +import { BRAND_PROGRESS } from '@/lib/config/colors'; interface DemoProgressProps { currentStep: DemoStep; - accentColor: BotAccentColor; } const steps: { id: DemoStep; label: string; number: number }[] = [ @@ -15,8 +13,8 @@ const steps: { id: DemoStep; label: string; number: number }[] = [ { id: 'chat', label: 'Chat with your assistant', number: 2 }, ]; -export const DemoProgress: FC = ({ currentStep, accentColor }) => { - const colors = ACCENT_PROGRESS_CLASSES[accentColor]; +export const DemoProgress: FC = ({ currentStep }) => { + const colors = BRAND_PROGRESS; const currentIndex = steps.findIndex((s) => s.id === currentStep); return ( diff --git a/components/shared/demo/DemoSection.tsx b/components/shared/demo/DemoSection.tsx index 344650e3..b47e262e 100644 --- a/components/shared/demo/DemoSection.tsx +++ b/components/shared/demo/DemoSection.tsx @@ -39,7 +39,7 @@ export const DemoSection: FC = ({ botSlug, className = '' }) = return (
{/* Progress indicator */} - + {/* Intake step */} {state.step === 'intake' && ( @@ -49,7 +49,6 @@ export const DemoSection: FC = ({ botSlug, className = '' }) = responses={state.intakeResponses} onUpdate={state.updateIntake} onStart={state.startChat} - accentColor={config.accentColor} icon={config.icon} botName={botName} /> @@ -67,7 +66,6 @@ export const DemoSection: FC = ({ botSlug, className = '' }) = isLoading={state.isLoading} starterQuestions={config.starterQuestions} botIcon={config.icon} - accentColor={config.accentColor} outputConfig={config.outputConfig} />
@@ -80,7 +78,6 @@ export const DemoSection: FC = ({ botSlug, className = '' }) = intakeResponses={state.intakeResponses} files={state.files} onReset={state.reset} - accentColor={config.accentColor} /> {/* File upload */} @@ -108,7 +105,6 @@ export const DemoSection: FC = ({ botSlug, className = '' }) = onUpload={state.uploadFiles} onRemove={state.removeFile} isUploading={state.isUploading} - accentColor={config.accentColor} />
)} diff --git a/lib/config/colors.ts b/lib/config/colors.ts index 31c16454..086cb801 100644 --- a/lib/config/colors.ts +++ b/lib/config/colors.ts @@ -528,6 +528,39 @@ const ACCENT_CLASS_SETS: Record = { * app/globals.css. The per-colour sets below remain for user-created custom * bots, where accent_color is a real column the owner chooses. */ +// --------------------------------------------------------------------------- +// Brand equivalents of the per-colour maps above. +// +// First-party surfaces (the six bots, their demo, the bot switcher) use these. +// The per-colour maps remain for user-created bots, where the owner picks +// accent_color and it is stored on the row. +// --------------------------------------------------------------------------- + +export const BRAND_BUTTON = 'bg-action hover:bg-action-hover focus:ring-action'; +export const BRAND_FOCUS_RING = 'focus:border-action focus:ring-action'; +export const BRAND_STARTER = 'border-edge hover:border-action hover:bg-action-tint text-action'; +export const BRAND_CHECKBOX = 'text-action focus:ring-action'; +export const BRAND_PROGRESS = { + active: 'border-action bg-action-tint', + completed: 'bg-action border-action', + text: 'text-action', +}; +export const BRAND_MESSAGE = { bg: 'bg-action-tint', border: 'border-edge' }; +export const BRAND_DROPZONE = { + border: 'border-edge', + bg: 'bg-action-tint', + text: 'text-action', +}; +export const BRAND_CONTEXT_PANEL = { + bg: 'bg-action-tint', + text: 'text-action', + border: 'border-edge', +}; +export const BRAND_SWITCHER = { + button: 'hover:bg-action-tint', + active: 'bg-action-tint text-action', +}; + export const BRAND_ACCENT: AccentClasses = { primary: 'bg-action', text: 'text-action', diff --git a/lib/demo/botDemoConfigs.ts b/lib/demo/botDemoConfigs.ts index b5fdb17e..b1dd9577 100644 --- a/lib/demo/botDemoConfigs.ts +++ b/lib/demo/botDemoConfigs.ts @@ -11,7 +11,6 @@ import type { BotDemoConfig } from './types'; export const lexConfig: BotDemoConfig = { slug: 'legal-expert', - accentColor: 'blue', icon: '⚖️', systemPrompt: `You are Lex, an AI legal assistant created by Botsmann. You provide helpful legal information based on the user's documents and context. @@ -137,7 +136,6 @@ Remember: You're here to help users understand their legal situation, not to pro export const imhotepConfig: BotDemoConfig = { slug: 'medical-expert', - accentColor: 'green', icon: '⚕️', systemPrompt: `You are Imhotep, an AI health assistant created by Botsmann. You provide evidence-based health information and help users understand medical topics. @@ -273,7 +271,6 @@ Remember: You're here to help users understand health information, not to diagno export const heidiConfig: BotDemoConfig = { slug: 'swiss-german-teacher', - accentColor: 'red', icon: '🇨🇭', systemPrompt: `You are Heidi, a friendly Swiss German language teacher created by Botsmann. You help users learn Schwyzerdütsch (Swiss German) with cultural context, pronunciation guides, and practical examples. @@ -382,7 +379,6 @@ Remember: You're making Swiss German accessible and fun!`, export const nerdConfig: BotDemoConfig = { slug: 'research-assistant', - accentColor: 'indigo', icon: '🔬', systemPrompt: `You are Nerd, an AI research assistant created by Botsmann. You help users explore academic topics, understand research papers, and synthesize information from multiple sources. @@ -494,7 +490,6 @@ Remember: You're helping users think critically and explore topics deeply!`, export const tridentConfig: BotDemoConfig = { slug: 'product-manager', - accentColor: 'amber', icon: '🔱', systemPrompt: `You are Trident, an AI product assistant created by Botsmann. You help users find, compare, and understand products based on their needs and preferences. @@ -598,7 +593,6 @@ Remember: You're helping users make informed decisions, not selling products!`, export const museConfig: BotDemoConfig = { slug: 'artistic-advisor', - accentColor: 'indigo', icon: '🎨', systemPrompt: `You are Muse, an AI creative assistant created by Botsmann. You help users with creative projects including writing, brainstorming, design concepts, and artistic exploration. diff --git a/lib/demo/types.ts b/lib/demo/types.ts index 841f8ff4..a185c7d5 100644 --- a/lib/demo/types.ts +++ b/lib/demo/types.ts @@ -1,5 +1,3 @@ -import type { BotAccentColor } from '@/types/bot'; - /** * Type definitions for the unified bot demo system */ @@ -84,7 +82,6 @@ export interface BotDemoConfig { slug: string; // Theming - accentColor: BotAccentColor; icon: string; // Interview/Intake Questions