Skip to content

Commit 517f977

Browse files
samselclaude
andcommitted
Redesign UI with Seed.com-inspired light theme
Replace dark glass-morphism with warm cream backgrounds (#F5F1EB), forest green accents (#2D5A27), DM Serif Display headings, pill-shaped buttons, white cards with subtle borders, and nature-inspired chart palette. Globe and reveal sections retain dark backgrounds. All 250 tests pass with zero functionality changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 341e738 commit 517f977

22 files changed

Lines changed: 230 additions & 207 deletions

index.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<link rel="icon" type="image/svg+xml" href="logo.svg" />
77
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
88
<meta name="referrer" content="no-referrer" />
9-
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' https://huggingface.co https://raw.githubusercontent.com; worker-src 'self' blob:; font-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" />
9+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob:; connect-src 'self' https://huggingface.co https://raw.githubusercontent.com; worker-src 'self' blob:; font-src 'self' https://fonts.gstatic.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" />
1010
<meta name="description" content="Turn flight confirmation emails into beautiful travel analytics. Distances, airlines, routes, and more. 100% private, runs entirely in your browser." />
1111
<meta property="og:title" content="FlightWrapped | Visualize Your Flight History" />
1212
<meta property="og:description" content="Beautiful travel analytics from your flight confirmation emails. Distances, airlines, routes. 100% private, in your browser." />
@@ -15,7 +15,10 @@
1515
<meta name="twitter:card" content="summary_large_image" />
1616
<meta name="twitter:title" content="FlightWrapped | Visualize Your Flight History" />
1717
<meta name="twitter:description" content="Beautiful travel analytics from your flight confirmation emails. Distances, airlines, routes. 100% private, in your browser." />
18-
<meta name="theme-color" content="#030712" />
18+
<meta name="theme-color" content="#F5F1EB" />
19+
<link rel="preconnect" href="https://fonts.googleapis.com" />
20+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
21+
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&display=swap" rel="stylesheet" />
1922
<link rel="manifest" href="manifest.webmanifest" />
2023
<link rel="apple-touch-icon" href="icons/apple-touch-icon.png" />
2124
<title>FlightWrapped | Visualize Your Flight History from Email</title>

src/App.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,8 @@ function App() {
269269
}}
270270
className={`relative p-2 transition-colors duration-200 ${
271271
profilerEnabled
272-
? 'text-green-400 hover:text-green-300'
273-
: 'text-gray-500 hover:text-white'
272+
? 'text-[#2D5A27] hover:text-[#1B3409]'
273+
: 'text-[#9A9690] hover:text-[#1A1A1A]'
274274
}`}
275275
title={profilerEnabled
276276
? profilerReport
@@ -286,15 +286,15 @@ function App() {
286286
<path d="M12 2v2" />
287287
</svg>
288288
{profilerEnabled && (
289-
<span className="absolute top-1 right-1 w-2 h-2 bg-green-400 rounded-full" />
289+
<span className="absolute top-1 right-1 w-2 h-2 bg-[#2D5A27] rounded-full" />
290290
)}
291291
</button>
292292
{/* Blog post */}
293293
<a
294294
href="https://samselvanathan.com/posts/flightwrapped-on-device-ai-flight-visualizer"
295295
target="_blank"
296296
rel="noopener noreferrer"
297-
className="p-2 text-gray-500 hover:text-white transition-colors duration-200"
297+
className="p-2 text-[#9A9690] hover:text-[#1A1A1A] transition-colors duration-200"
298298
aria-label="Read the blog post"
299299
>
300300
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
@@ -306,7 +306,7 @@ function App() {
306306
href="https://github.com/samsel/FlightWrapped"
307307
target="_blank"
308308
rel="noopener noreferrer"
309-
className="p-2 text-gray-500 hover:text-white transition-colors duration-200"
309+
className="p-2 text-[#9A9690] hover:text-[#1A1A1A] transition-colors duration-200"
310310
aria-label="View source on GitHub"
311311
>
312312
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
@@ -343,7 +343,7 @@ function App() {
343343
}
344344
window.location.reload()
345345
}}
346-
className="p-2 text-gray-500 hover:text-red-400 transition-colors duration-200"
346+
className="p-2 text-[#9A9690] hover:text-[#9B3A2A] transition-colors duration-200"
347347
title="Clear all caches — removes AI model, flight data, and service workers"
348348
>
349349
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
@@ -378,9 +378,9 @@ function App() {
378378
onViewCached={handleViewCached}
379379
/>
380380
{error && (
381-
<div className="fixed bottom-4 left-1/2 -translate-x-1/2 bg-red-900/90 text-red-200 px-6 py-3 max-w-[90vw] text-sm z-50">
381+
<div className="fixed bottom-4 left-1/2 -translate-x-1/2 bg-[#FDDDD5] text-[#9B3A2A] px-6 py-3 max-w-[90vw] text-sm z-50 rounded-lg border border-[#F0B8AA] shadow-lg">
382382
{error}
383-
<button onClick={() => setError(null)} className="ml-4 text-red-400 hover:text-red-300">
383+
<button onClick={() => setError(null)} className="ml-4 text-[#9B3A2A]/60 hover:text-[#9B3A2A]">
384384
Dismiss
385385
</button>
386386
</div>
@@ -393,8 +393,8 @@ function App() {
393393

394394
if (appState === 'parsing') {
395395
return (
396-
<div className="min-h-screen glass-bg text-white flex flex-col items-center justify-center px-4 animate-fade-in">
397-
<h1 className="text-3xl font-bold mb-8">FlightWrapped</h1>
396+
<div className="min-h-screen glass-bg text-[#1A1A1A] flex flex-col items-center justify-center px-4 animate-fade-in">
397+
<h1 className="text-3xl font-bold mb-8" style={{ fontFamily: "'DM Serif Display', Georgia, serif" }}>FlightWrapped</h1>
398398
<ParsingProgress progress={progress} onReset={resetToLanding} />
399399
{topNav}
400400
{profilerPanel}

src/components/ErrorBoundary.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@ export default class ErrorBoundary extends Component<Props, State> {
2626
if (!this.state.hasError) return this.props.children
2727

2828
return (
29-
<div className="min-h-screen bg-gray-950 text-white flex flex-col items-center justify-center px-4">
29+
<div className="min-h-screen bg-[#F5F1EB] text-[#1A1A1A] flex flex-col items-center justify-center px-4">
3030
<div className="max-w-md text-center">
31-
<h1 className="text-2xl font-bold mb-3">Something went wrong</h1>
32-
<p className="text-gray-400 text-sm mb-6">
31+
<h1 className="text-2xl font-bold mb-3" style={{ fontFamily: "'DM Serif Display', Georgia, serif" }}>Something went wrong</h1>
32+
<p className="text-[#6B6960] text-sm mb-6">
3333
An unexpected error occurred while rendering the dashboard.
3434
</p>
3535
{this.state.error && (
36-
<pre className="text-xs text-red-400/80 bg-red-950/30 border border-red-900/30 p-4 mb-6 text-left overflow-x-auto max-h-32">
36+
<pre className="text-xs text-[#9B3A2A]/80 bg-[#FDDDD5]/30 border border-[#FDDDD5] p-4 mb-6 text-left overflow-x-auto max-h-32 rounded-lg">
3737
{this.state.error.message}
3838
</pre>
3939
)}
4040
<button
4141
onClick={this.handleReset}
42-
className="bg-blue-600 hover:bg-blue-500 text-white text-sm font-medium px-6 py-3 transition-colors"
42+
className="bg-[#2D5A27] hover:bg-[#3A7233] text-white text-sm font-medium px-6 py-3 transition-colors rounded-full"
4343
>
4444
Start Over
4545
</button>

src/components/InputScreen.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ function formatTimeAgo(iso: string): string {
2222

2323
export default function InputScreen({ onError, onDemoClick, onFileUpload, cachedData, onViewCached }: InputScreenProps) {
2424
return (
25-
<div className="min-h-screen glass-bg text-white">
25+
<div className="min-h-screen glass-bg text-[#1A1A1A]">
2626
{cachedData && cachedData.flights.length > 0 && (
2727
<div className="fixed top-0 left-0 right-0 z-50 glass-header">
2828
<div className="max-w-4xl mx-auto px-4 py-3 flex items-center justify-between gap-3">
2929
<div className="flex items-center gap-3 text-sm">
30-
<span className="text-gray-400">
30+
<span className="text-[#6B6960]">
3131
{cachedData.flights.length} flights saved
3232
{cachedData.lastImportAt && <> &middot; imported {formatTimeAgo(cachedData.lastImportAt)}</>}
3333
</span>
3434
</div>
3535
<button
3636
onClick={onViewCached}
37-
className="text-sm font-medium text-blue-400 hover:text-blue-300 transition-colors px-4 py-1.5 bg-blue-500/10 hover:bg-blue-500/15"
37+
className="text-sm font-medium text-[#2D5A27] hover:text-[#1B3409] transition-colors px-4 py-1.5 bg-[#E8F0E4] hover:bg-[#D8E8D2] rounded-full"
3838
>
3939
View Dashboard
4040
</button>

src/components/MboxUpload.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ export default function MboxUpload({ onFileUpload, onError }: MboxUploadProps) {
3737
onDragLeave={() => setDragging(false)}
3838
onDrop={handleDrop}
3939
onClick={() => inputRef.current?.click()}
40-
className={`flex items-center gap-3 px-7 py-4 bg-white text-gray-800 font-medium text-base hover:bg-gray-50 transition-all duration-200 shadow-lg shadow-black/20 border cursor-pointer active:scale-[0.98] ${
41-
dragging ? 'border-blue-400 bg-blue-50' : 'border-gray-200'
40+
className={`flex items-center gap-3 px-7 py-4 bg-[#2D5A27] text-white font-medium text-base hover:bg-[#3A7233] transition-all duration-200 shadow-lg shadow-[#2D5A27]/20 border cursor-pointer active:scale-[0.98] rounded-full ${
41+
dragging ? 'border-[#4A8B42] bg-[#3A7233]' : 'border-[#2D5A27]'
4242
}`}
4343
>
44-
<svg className="w-5 h-5 flex-shrink-0 text-gray-600" fill="none" stroke="currentColor" strokeWidth={1.5} viewBox="0 0 24 24">
44+
<svg className="w-5 h-5 flex-shrink-0 text-white/80" fill="none" stroke="currentColor" strokeWidth={1.5} viewBox="0 0 24 24">
4545
<path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13" />
4646
</svg>
4747
<span>

src/components/ParsingProgress.tsx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ function StepIndicator({ phase }: { phase: ParseProgress['phase'] }) {
3838
<div key={p} className="flex items-center gap-1">
3939
<div className="flex items-center gap-1.5">
4040
<div
41-
className={`w-5 h-5 flex items-center justify-center shrink-0 transition-all duration-300 ${
41+
className={`w-5 h-5 rounded-full flex items-center justify-center shrink-0 transition-all duration-300 ${
4242
isComplete
43-
? 'bg-blue-500 text-white'
43+
? 'bg-[#2D5A27] text-white'
4444
: isActive
45-
? 'bg-blue-500/20 text-blue-400 border-2 border-blue-500'
46-
: 'bg-gray-800 text-gray-600 border border-gray-700'
45+
? 'bg-[#E8F0E4] text-[#2D5A27] border-2 border-[#2D5A27]'
46+
: 'bg-[#E5E0D5] text-[#9A9690] border border-[#D5D0C8]'
4747
}`}
4848
>
4949
{isComplete && (
@@ -53,17 +53,17 @@ function StepIndicator({ phase }: { phase: ParseProgress['phase'] }) {
5353
<span
5454
className={`text-xs font-medium transition-colors duration-300 ${
5555
isComplete
56-
? 'text-blue-400'
56+
? 'text-[#2D5A27]'
5757
: isActive
58-
? 'text-gray-200'
59-
: 'text-gray-600'
58+
? 'text-[#1A1A1A]'
59+
: 'text-[#9A9690]'
6060
}`}
6161
>
6262
{STEP_LABELS[p]}
6363
</span>
6464
</div>
6565
{i < PHASES.length - 1 && (
66-
<div className={`w-5 h-px mx-1 transition-colors duration-300 ${i < currentIndex ? 'bg-blue-500' : 'bg-gray-700'}`} />
66+
<div className={`w-5 h-px mx-1 transition-colors duration-300 ${i < currentIndex ? 'bg-[#2D5A27]' : 'bg-[#E5E0D5]'}`} />
6767
)}
6868
</div>
6969
)
@@ -83,10 +83,10 @@ export default function ParsingProgress({ progress, onReset }: ParsingProgressPr
8383
<StepIndicator phase={progress.phase} />
8484

8585
<div className="mb-4 text-center">
86-
<p className="text-lg font-medium text-gray-200">
86+
<p className="text-lg font-medium text-[#1A1A1A]">
8787
{phaseLabels[progress.phase]}
8888
</p>
89-
<p className="text-sm text-gray-400 mt-1">
89+
<p className="text-sm text-[#6B6960] mt-1">
9090
{progress.phase === 'done' ? (
9191
progress.flightsFound > 0
9292
? `Found ${progress.flightsFound.toLocaleString()} flights`
@@ -99,7 +99,7 @@ export default function ParsingProgress({ progress, onReset }: ParsingProgressPr
9999
<>
100100
{progress.message}
101101
{progress.flightsFound > 0 && (
102-
<span className="ml-2 text-blue-400">
102+
<span className="ml-2 text-[#2D5A27]">
103103
({progress.flightsFound} flights found)
104104
</span>
105105
)}
@@ -108,7 +108,7 @@ export default function ParsingProgress({ progress, onReset }: ParsingProgressPr
108108
<>
109109
{progress.current.toLocaleString()} of {progress.total.toLocaleString()} emails
110110
{progress.flightsFound > 0 && (
111-
<span className="ml-2 text-blue-400">
111+
<span className="ml-2 text-[#2D5A27]">
112112
({progress.flightsFound} flights found)
113113
</span>
114114
)}
@@ -117,25 +117,25 @@ export default function ParsingProgress({ progress, onReset }: ParsingProgressPr
117117
</p>
118118
</div>
119119

120-
<div className="w-full bg-gray-800 h-2.5 overflow-hidden">
120+
<div className="w-full bg-[#E5E0D5] h-2.5 overflow-hidden rounded-full">
121121
{isIndeterminate ? (
122-
<div className="h-full w-full bg-blue-500 animate-pulse-bar" />
122+
<div className="h-full w-full bg-[#2D5A27] animate-pulse-bar rounded-full" />
123123
) : (
124124
<div
125-
className="h-full bg-gradient-to-r from-blue-600 to-blue-400 transition-all duration-300"
125+
className="h-full bg-gradient-to-r from-[#2D5A27] to-[#4A8B42] transition-all duration-300 rounded-full"
126126
style={{ width: `${percent}%` }}
127127
/>
128128
)}
129129
</div>
130130

131131
{!isIndeterminate && (
132-
<p className="text-xs text-gray-500 text-center mt-2">{percent}%</p>
132+
<p className="text-xs text-[#9A9690] text-center mt-2">{percent}%</p>
133133
)}
134134

135135
{/* Flights found counter */}
136136
{isProcessing && progress.flightsFound > 0 && (
137137
<div className="mt-4 text-center">
138-
<span className="inline-flex items-center gap-1.5 text-sm text-blue-300 bg-blue-500/10 border border-blue-500/20 px-4 py-1.5">
138+
<span className="inline-flex items-center gap-1.5 text-sm text-[#2D5A27] bg-[#E8F0E4] border border-[#C8DCC2] px-4 py-1.5 rounded-full">
139139
<span className="font-bold">{progress.flightsFound}</span> flights found so far
140140
</span>
141141
</div>
@@ -146,7 +146,7 @@ export default function ParsingProgress({ progress, onReset }: ParsingProgressPr
146146
<div className="text-center mt-6">
147147
<button
148148
onClick={onReset}
149-
className="text-sm text-gray-400 hover:text-white border border-gray-700 hover:border-gray-600 bg-gray-800/50 hover:bg-gray-800 px-5 py-2.5 transition-all"
149+
className="text-sm text-[#6B6960] hover:text-[#1A1A1A] border border-[#E5E0D5] hover:border-[#D5D0C8] bg-white hover:bg-[#F5F1EB] px-5 py-2.5 transition-all rounded-full"
150150
>
151151
Cancel
152152
</button>
@@ -157,7 +157,7 @@ export default function ParsingProgress({ progress, onReset }: ParsingProgressPr
157157
<div className="text-center mt-6">
158158
<button
159159
onClick={onReset}
160-
className="text-sm bg-gray-800 hover:bg-gray-700 text-gray-300 hover:text-white px-5 py-3 transition-colors border border-gray-700"
160+
className="text-sm bg-[#2D5A27] hover:bg-[#3A7233] text-white px-5 py-3 transition-colors rounded-full"
161161
>
162162
Try Again
163163
</button>

src/components/ProfilerOverlay.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function SegmentBar({ segment, maxMs }: { segment: SegmentTiming; maxMs: number
2525
<span className="w-28 truncate text-gray-400 font-mono">{segment.name}</span>
2626
<div className="flex-1 bg-gray-800 h-3 overflow-hidden">
2727
<div
28-
className="h-full bg-blue-500/70"
28+
className="h-full bg-[#2D5A27]/70"
2929
style={{ width: `${Math.max(pct, 1)}%` }}
3030
/>
3131
</div>
@@ -158,7 +158,7 @@ export default function ProfilerOverlay({ enabled, onToggle, report, panelOpen,
158158
onClick={() => setActiveTab('mbox')}
159159
className={`px-4 py-2 text-xs font-medium transition-colors ${
160160
activeTab === 'mbox'
161-
? 'text-blue-400 border-b-2 border-blue-400'
161+
? 'text-[#4A8B42] border-b-2 border-[#4A8B42]'
162162
: 'text-gray-500 hover:text-gray-300'
163163
}`}
164164
>
@@ -168,7 +168,7 @@ export default function ProfilerOverlay({ enabled, onToggle, report, panelOpen,
168168
onClick={() => setActiveTab('emails')}
169169
className={`px-4 py-2 text-xs font-medium transition-colors ${
170170
activeTab === 'emails'
171-
? 'text-blue-400 border-b-2 border-blue-400'
171+
? 'text-[#4A8B42] border-b-2 border-[#4A8B42]'
172172
: 'text-gray-500 hover:text-gray-300'
173173
}`}
174174
>
@@ -235,7 +235,7 @@ export default function ProfilerOverlay({ enabled, onToggle, report, panelOpen,
235235
type="checkbox"
236236
checked={hideFiltered}
237237
onChange={(e) => setHideFiltered(e.target.checked)}
238-
className="accent-blue-500"
238+
className="accent-[#2D5A27]"
239239
/>
240240
Hide filtered
241241
</label>
@@ -311,7 +311,7 @@ function SortBtn({
311311
<button
312312
onClick={() => onClick(sortKey)}
313313
className={`${width} ${align} hover:text-gray-300 transition-colors ${
314-
active ? 'text-blue-400' : ''
314+
active ? 'text-[#4A8B42]' : ''
315315
}`}
316316
>
317317
{label}

0 commit comments

Comments
 (0)