Skip to content

Commit b93d784

Browse files
catomeanclaude
andauthored
refactor(design): one colour system on the bot pages (#144)
app/globals.css defines the identity -- editorial ink, one ochre accent -- and says so in a comment: "No generic AI gradient blue/purple." The bot pages ignored it and ran a parallel palette, so a single viewport of /bots/legal-expert showed a blue logo, a blue nav button and an ochre CTA at the same time. There were four colour sources, not two: lib/config/colors.ts 16-colour accent sets lib/hooks/navigationConfig its own 5-colour NAV_COLOR_CLASSES ACCENT_HERO_CLASSES a fifth set (whose green and indigo entries both set chatBg to 'bg-blue-50' -- it was not even self-consistent) raw Tailwind greys text-gray-900 / bg-white / border-gray-200 The six professional bots are Botsmann, so they now wear the brand: BRAND_ACCENT and BRAND_HERO, plus a single NAV_COLORS scheme. 113 raw grey/white classes became semantic tokens (text-ink, text-ink-muted, bg-surface, bg-paper, border-edge). The page ground is bg-paper, matching the homepage instead of flat white. The 'B' mark in BotNavigation was literally `bg-gradient-to-r from-blue-600 to-purple-600` -- the exact thing globals.css forbids by name. It now renders the shared <Logo>, so it cannot drift again. Per-bot accentColor is gone from the first-party config and its plumbing. The per-colour sets STAY for user-created bots: custom_bots.accent_color is a real column with a CHECK constraint and the owner picks it. That is user data, not a design token. Also deleted components/shared/BotPageTemplate.tsx -- nothing imported it. Not in this change: /try, /enterprise, /bots and /knowledge still carry blue/purple gradients (72 files match the raw palette). Same class, separate pass, so this one stays reviewable. verify: format, lint, typecheck, 240 tests, build -- all green. Verified visually against a local production build. Claude-Session: https://claude.ai/code/session_01HVwg8DKHQktxJuHeLM3xpG Co-authored-by: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 20ed465 commit b93d784

16 files changed

Lines changed: 180 additions & 391 deletions

app/bots/BotNavigation.tsx

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Link from 'next/link';
55
import type { Route } from 'next';
66
import { Dialog, Transition } from '@headlessui/react';
77
import { BotSwitcher, SiteMenuDropdown } from '@/components/navigation';
8+
import { Logo } from '@/components/shared/Logo';
89
import { menuItems } from '@/data/menuItems';
910
import { useScrollNavigation, getNavColors } from '@/lib/hooks';
1011
import type { BotMenuItem } from '@/types/bot';
@@ -53,11 +54,11 @@ export const BotNavigation: React.FC<BotNavigationProps> = ({
5354
};
5455

5556
// Get color classes from centralized config
56-
const colors = getNavColors(accentColor);
57+
const colors = getNavColors();
5758

5859
const navClasses = isScrolled
59-
? 'bg-white shadow-md border-b border-gray-200'
60-
: 'bg-white border-b border-gray-200';
60+
? 'bg-surface shadow-md border-b border-edge'
61+
: 'bg-surface border-b border-edge';
6162

6263
return (
6364
<nav
@@ -67,17 +68,11 @@ export const BotNavigation: React.FC<BotNavigationProps> = ({
6768
<div className="flex justify-between items-center h-12">
6869
{/* Left side: Botsmann logo + divider + Bot Switcher */}
6970
<div className="flex items-center h-full">
70-
{/* Botsmann 'B' logo - links to home */}
71-
<Link
72-
href="/"
73-
className="flex items-center justify-center w-9 h-9 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-bold text-sm rounded-lg hover:opacity-90 transition-opacity"
74-
aria-label="Go to Botsmann home"
75-
>
76-
B
77-
</Link>
71+
{/* Botsmann 'B' mark — the shared Logo, so it cannot drift again */}
72+
<Logo href="/" showText={false} size="sm" />
7873

7974
{/* Vertical divider */}
80-
<div className="h-6 w-px bg-gray-300 mx-3" />
75+
<div className="h-6 w-px bg-edge mx-3" />
8176

8277
{/* Bot Switcher dropdown */}
8378
<BotSwitcher
@@ -95,7 +90,7 @@ export const BotNavigation: React.FC<BotNavigationProps> = ({
9590
key={item.id}
9691
onClick={() => handleScrollToSection(item.section)}
9792
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors whitespace-nowrap flex items-center ${
98-
activeSection === item.section ? colors.active : `text-gray-600 ${colors.hover}`
93+
activeSection === item.section ? colors.active : `text-ink-muted ${colors.hover}`
9994
} ${item.id === 'roadmap' ? `border ${colors.border}` : ''}`}
10095
>
10196
{item.icon && <span className="mr-1">{item.icon}</span>}
@@ -108,7 +103,7 @@ export const BotNavigation: React.FC<BotNavigationProps> = ({
108103
<div className="flex items-center gap-2">
109104
{/* Mobile menu button */}
110105
<button
111-
className="md:hidden p-2 rounded-md text-gray-600 hover:text-gray-800 hover:bg-gray-100 focus:outline-none"
106+
className="md:hidden p-2 rounded-md text-ink-muted hover:text-ink hover:bg-paper focus:outline-none"
112107
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
113108
aria-label={isMobileMenuOpen ? 'Close menu' : 'Open menu'}
114109
>
@@ -182,20 +177,20 @@ export const BotNavigation: React.FC<BotNavigationProps> = ({
182177
leaveTo="translate-x-full"
183178
>
184179
<Dialog.Panel className="pointer-events-auto w-screen max-w-md">
185-
<div className="flex h-full flex-col overflow-y-scroll bg-white shadow-xl">
180+
<div className="flex h-full flex-col overflow-y-scroll bg-surface shadow-xl">
186181
{/* Header with Botsmann branding */}
187-
<div className="flex items-center justify-between px-4 py-6 border-b border-gray-200 bg-gradient-to-r from-blue-50 to-purple-50">
182+
<div className="flex items-center justify-between px-4 py-6 border-b border-edge bg-gradient-to-r from-blue-50 to-purple-50">
188183
<div className="flex items-center gap-3">
189184
<div className="flex items-center justify-center w-9 h-9 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-bold text-sm rounded-lg">
190185
B
191186
</div>
192-
<Dialog.Title className="text-lg font-semibold text-gray-900">
187+
<Dialog.Title className="text-lg font-semibold text-ink">
193188
Botsmann
194189
</Dialog.Title>
195190
</div>
196191
<button
197192
type="button"
198-
className="rounded-md p-2 text-gray-400 hover:bg-white/50"
193+
className="rounded-md p-2 text-ink-muted hover:bg-surface/50"
199194
onClick={() => setIsMobileMenuOpen(false)}
200195
>
201196
<span className="sr-only">Close menu</span>
@@ -221,7 +216,7 @@ export const BotNavigation: React.FC<BotNavigationProps> = ({
221216
{/* Home link */}
222217
<Link
223218
href="/"
224-
className="flex items-center px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:bg-gray-100"
219+
className="flex items-center px-3 py-2 rounded-md text-base font-medium text-ink hover:bg-paper"
225220
onClick={() => setIsMobileMenuOpen(false)}
226221
>
227222
<svg
@@ -243,7 +238,7 @@ export const BotNavigation: React.FC<BotNavigationProps> = ({
243238
{/* All Bots link */}
244239
<Link
245240
href="/bots"
246-
className="flex items-center px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:bg-gray-100"
241+
className="flex items-center px-3 py-2 rounded-md text-base font-medium text-ink hover:bg-paper"
247242
onClick={() => setIsMobileMenuOpen(false)}
248243
>
249244
<svg
@@ -262,27 +257,27 @@ export const BotNavigation: React.FC<BotNavigationProps> = ({
262257
All Bots
263258
</Link>
264259

265-
<hr className="my-4 border-gray-200" />
260+
<hr className="my-4 border-edge" />
266261

267262
{/* Site Navigation */}
268-
<p className="px-3 text-xs font-medium text-gray-500 uppercase tracking-wider mb-2">
263+
<p className="px-3 text-xs font-medium text-ink-muted uppercase tracking-wider mb-2">
269264
Botsmann
270265
</p>
271266
{siteNavItems.map((item) => (
272267
<Link
273268
key={item.label}
274269
href={item.path}
275-
className="flex items-center px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:bg-gray-100"
270+
className="flex items-center px-3 py-2 rounded-md text-base font-medium text-ink hover:bg-paper"
276271
onClick={() => setIsMobileMenuOpen(false)}
277272
>
278273
<span>{item.label}</span>
279274
</Link>
280275
))}
281276

282-
<hr className="my-4 border-gray-200" />
277+
<hr className="my-4 border-edge" />
283278

284279
{/* Bot Section links */}
285-
<p className="px-3 text-xs font-medium text-gray-500 uppercase tracking-wider mb-2">
280+
<p className="px-3 text-xs font-medium text-ink-muted uppercase tracking-wider mb-2">
286281
{botTitle} Sections
287282
</p>
288283
{sectionMenuItems.map((item) => (
@@ -292,7 +287,7 @@ export const BotNavigation: React.FC<BotNavigationProps> = ({
292287
className={`w-full text-left flex items-center px-3 py-2 rounded-md text-base font-medium transition-colors ${
293288
activeSection === item.section
294289
? colors.active
295-
: `text-gray-700 hover:bg-gray-100`
290+
: `text-ink hover:bg-paper`
296291
}`}
297292
>
298293
{item.icon && <span className="mr-3 text-lg">{item.icon}</span>}
@@ -302,7 +297,7 @@ export const BotNavigation: React.FC<BotNavigationProps> = ({
302297

303298
{chatLink && (
304299
<>
305-
<hr className="my-4 border-gray-200" />
300+
<hr className="my-4 border-edge" />
306301
<Link
307302
href={chatLink as Route}
308303
className={`w-full flex items-center justify-center px-4 py-3 ${colors.accent} text-white text-base font-medium rounded-md transition-colors`}

app/bots/[slug]/page.tsx

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import type {
3030
DemoContent,
3131
SectionConfig,
3232
} from '@/lib/config/bot-pages';
33-
import type { BotAccentColor } from '@/types/bot';
3433

3534
/**
3635
* Dynamic bot page that renders from config
@@ -52,16 +51,14 @@ export default function BotPage() {
5251

5352
const rawTryLink = getBotTryLink(bot);
5453
const tryLink = rawTryLink || '#waitlist';
55-
const accentColor = pageConfig.accentColor;
5654

5755
return (
58-
<div className="min-h-screen bg-white">
56+
<div className="min-h-screen bg-paper">
5957
<BotNavigation
6058
botSlug={bot.slug}
6159
botTitle={bot.nav.navTitle}
6260
botEmoji={bot.nav.emoji}
6361
botDescription={bot.nav.navDescription}
64-
accentColor={bot.nav.accentColor}
6562
menuItems={bot.nav.menuItems}
6663
chatLink={tryLink}
6764
/>
@@ -71,7 +68,6 @@ export default function BotPage() {
7168
<SectionRenderer
7269
key={section.id}
7370
section={section}
74-
accentColor={accentColor}
7571
botSlug={slug}
7672
tryLink={tryLink}
7773
heroConfig={heroConfig}
@@ -87,19 +83,12 @@ export default function BotPage() {
8783
*/
8884
interface SectionRendererProps {
8985
section: SectionConfig;
90-
accentColor: BotAccentColor;
9186
botSlug: string;
9287
tryLink: string;
9388
heroConfig: ReturnType<typeof getBotHeroConfig>;
9489
}
9590

96-
function SectionRenderer({
97-
section,
98-
accentColor,
99-
botSlug,
100-
tryLink,
101-
heroConfig,
102-
}: SectionRendererProps) {
91+
function SectionRenderer({ section, botSlug, tryLink, heroConfig }: SectionRendererProps) {
10392
const { id, type, content, isLast } = section;
10493

10594
// Hero section uses external config
@@ -109,13 +98,13 @@ function SectionRenderer({
10998
...heroConfig,
11099
primaryCTA: { ...heroConfig.primaryCTA, href: tryLink },
111100
};
112-
return <BotHeroSection config={config} accentColor={accentColor} />;
101+
return <BotHeroSection config={config} />;
113102
}
114103

115104
// All other sections wrapped in BotSection
116105
return (
117106
<BotSection id={id} isLast={isLast}>
118-
{renderSectionContent(type, content, accentColor, botSlug, tryLink)}
107+
{renderSectionContent(type, content, botSlug, tryLink)}
119108
</BotSection>
120109
);
121110
}
@@ -126,7 +115,6 @@ function SectionRenderer({
126115
function renderSectionContent(
127116
type: SectionConfig['type'],
128117
content: SectionConfig['content'],
129-
accentColor: BotAccentColor,
130118
botSlug: string,
131119
tryLink: string,
132120
): React.ReactNode {
@@ -140,29 +128,25 @@ function renderSectionContent(
140128
return <DemoSectionWrapper content={content as DemoContent} botSlug={botSlug} />;
141129

142130
case 'features':
143-
return <FeaturesSection content={content as FeaturesContent} accentColor={accentColor} />;
131+
return <FeaturesSection content={content as FeaturesContent} />;
144132

145133
case 'how-it-works':
146-
return <HowItWorksSection content={content as HowItWorksContent} accentColor={accentColor} />;
134+
return <HowItWorksSection content={content as HowItWorksContent} />;
147135

148136
case 'benefits':
149-
return <BenefitsSection content={content as BenefitsContent} accentColor={accentColor} />;
137+
return <BenefitsSection content={content as BenefitsContent} />;
150138

151139
case 'testimonials':
152-
return (
153-
<TestimonialsSection content={content as TestimonialsContent} accentColor={accentColor} />
154-
);
140+
return <TestimonialsSection content={content as TestimonialsContent} />;
155141

156142
case 'vision':
157-
return <VisionSection content={content as VisionContent} accentColor={accentColor} />;
143+
return <VisionSection content={content as VisionContent} />;
158144

159145
case 'technology':
160-
return <TechnologySection content={content as TechnologyContent} accentColor={accentColor} />;
146+
return <TechnologySection content={content as TechnologyContent} />;
161147

162148
case 'cta':
163-
return (
164-
<CTASection content={content as CTAContent} accentColor={accentColor} tryLink={tryLink} />
165-
);
149+
return <CTASection content={content as CTAContent} tryLink={tryLink} />;
166150

167151
default:
168152
return null;

components/shared/BotHeroSection.tsx

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
'use client';
22

33
import { type FC, type ReactNode } from 'react';
4-
import type { BotAccentColor } from '@/types/bot';
5-
import { ACCENT_HERO_CLASSES } from '@/lib/config/colors';
4+
import { BRAND_HERO } from '@/lib/config/colors';
65

76
/**
87
* Chat message for the hero preview
@@ -69,7 +68,6 @@ export interface BotHeroConfig {
6968

7069
interface BotHeroSectionProps {
7170
config: BotHeroConfig;
72-
accentColor: BotAccentColor;
7371
/** Optional custom content for the right panel instead of chat preview */
7472
rightPanel?: ReactNode;
7573
}
@@ -93,12 +91,11 @@ interface BotHeroSectionProps {
9391
* { role: 'user', content: 'I need a summary of recent data privacy cases.' },
9492
* ],
9593
* }}
96-
* accentColor="blue"
9794
* />
9895
* ```
9996
*/
100-
export const BotHeroSection: FC<BotHeroSectionProps> = ({ config, accentColor, rightPanel }) => {
101-
const colors = ACCENT_HERO_CLASSES[accentColor];
97+
export const BotHeroSection: FC<BotHeroSectionProps> = ({ config, rightPanel }) => {
98+
const colors = BRAND_HERO;
10299
const {
103100
badge,
104101
title,
@@ -126,7 +123,7 @@ export const BotHeroSection: FC<BotHeroSectionProps> = ({ config, accentColor, r
126123
</div>
127124
)}
128125

129-
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 mb-3 sm:mb-4">
126+
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-ink mb-3 sm:mb-4">
130127
{title}
131128
{titleSuffix && (
132129
<>
@@ -136,7 +133,7 @@ export const BotHeroSection: FC<BotHeroSectionProps> = ({ config, accentColor, r
136133
)}
137134
</h1>
138135

139-
<p className="text-base sm:text-lg lg:text-xl text-gray-700 mb-4 sm:mb-6">{subtitle}</p>
136+
<p className="text-base sm:text-lg lg:text-xl text-ink mb-4 sm:mb-6">{subtitle}</p>
140137

141138
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4">
142139
<a
@@ -167,12 +164,12 @@ export const BotHeroSection: FC<BotHeroSectionProps> = ({ config, accentColor, r
167164
{/* Mobile key benefits */}
168165
{showMobileBenefits && keyBenefits && keyBenefits.length > 0 && (
169166
<div className="mt-8 md:hidden">
170-
<h3 className="text-lg font-semibold text-gray-800 mb-3">Key Benefits:</h3>
167+
<h3 className="text-lg font-semibold text-ink mb-3">Key Benefits:</h3>
171168
<ul className="space-y-2">
172169
{keyBenefits.slice(0, 3).map((benefit, idx) => (
173170
<li key={idx} className="flex items-start">
174171
<span className="text-green-500 mr-2"></span>
175-
<span className="text-gray-700">{benefit.text}</span>
172+
<span className="text-ink">{benefit.text}</span>
176173
</li>
177174
))}
178175
</ul>
@@ -205,8 +202,8 @@ export const BotHeroSection: FC<BotHeroSectionProps> = ({ config, accentColor, r
205202
<span className="text-xl md:text-2xl">{botInfo.emoji}</span>
206203
</div>
207204
<div>
208-
<h3 className="font-semibold text-gray-900">{botInfo.name}</h3>
209-
<p className="text-sm text-gray-600">{botInfo.description}</p>
205+
<h3 className="font-semibold text-ink">{botInfo.name}</h3>
206+
<p className="text-sm text-ink-muted">{botInfo.description}</p>
210207
</div>
211208
</div>
212209

@@ -217,24 +214,24 @@ export const BotHeroSection: FC<BotHeroSectionProps> = ({ config, accentColor, r
217214
key={idx}
218215
className={`p-3 rounded-lg shadow-sm ${
219216
message.role === 'bot'
220-
? 'bg-white ml-auto max-w-[85%]'
217+
? 'bg-surface ml-auto max-w-[85%]'
221218
: `${colors.userBubble} max-w-[85%]`
222219
}`}
223220
>
224-
<p className="text-gray-800 text-sm md:text-base">{message.content}</p>
221+
<p className="text-ink text-sm md:text-base">{message.content}</p>
225222
</div>
226223
))}
227224
</div>
228225

229226
{/* Desktop key benefits inside chat panel */}
230227
{showMobileBenefits && keyBenefits && keyBenefits.length > 0 && (
231228
<div className="mt-6 hidden md:block">
232-
<h4 className="font-medium text-gray-900 mb-2">Key Benefits:</h4>
229+
<h4 className="font-medium text-ink mb-2">Key Benefits:</h4>
233230
<div className="grid grid-cols-2 gap-2">
234231
{keyBenefits.slice(0, 4).map((benefit, idx) => (
235-
<div key={idx} className="bg-white p-2 rounded-md flex items-center">
232+
<div key={idx} className="bg-surface p-2 rounded-md flex items-center">
236233
<span className="text-green-500 mr-2"></span>
237-
<span className="text-sm text-gray-700">{benefit.text}</span>
234+
<span className="text-sm text-ink">{benefit.text}</span>
238235
</div>
239236
))}
240237
</div>

0 commit comments

Comments
 (0)