Skip to content

Commit 513e3fd

Browse files
samselclaude
andcommitted
Unify top-right nav: profiler, blog, and GitHub icons in shared container
Move blog and GitHub links out of HeroSection into a shared fixed nav bar in App.tsx so all three icons share the same flex container with identical sizing and alignment across all app states. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 652d39c commit 513e3fd

3 files changed

Lines changed: 92 additions & 67 deletions

File tree

src/App.tsx

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function App() {
3131
const [lastImportAt, setLastImportAt] = useState<string | null>(null)
3232
const [profilerEnabled, setProfilerEnabled] = useState(false)
3333
const [profilerReport, setProfilerReport] = useState<ProfilerReport | null>(null)
34+
const [profilerPanelOpen, setProfilerPanelOpen] = useState(false)
3435
const workerRef = useRef<Worker | null>(null)
3536
const generationRef = useRef(0) as MutableRefObject<number>
3637
const profilerEnabledRef = useRef(false)
@@ -169,11 +170,73 @@ function App() {
169170
existingFlightsRef.current = []
170171
}, [createWorker])
171172

172-
const profilerOverlay = (
173+
const topNav = (
174+
<div className="fixed top-5 right-5 z-[9999] flex items-center gap-1">
175+
{/* Profiler toggle */}
176+
<button
177+
onClick={() => {
178+
if (!profilerEnabled) {
179+
handleProfilerToggle()
180+
} else {
181+
setProfilerPanelOpen(!profilerPanelOpen)
182+
}
183+
}}
184+
className={`relative p-2 transition-colors duration-200 ${
185+
profilerEnabled
186+
? 'text-green-400 hover:text-green-300'
187+
: 'text-gray-500 hover:text-white'
188+
}`}
189+
title={profilerEnabled
190+
? profilerReport
191+
? `Pipeline profiler — click to ${profilerPanelOpen ? 'close' : 'open'} details`
192+
: 'Profiler on — process a file to see timings'
193+
: 'Enable pipeline profiler — measures timing for each processing step'
194+
}
195+
>
196+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
197+
<circle cx="12" cy="13" r="8" />
198+
<path d="M12 9v4l2 2" />
199+
<path d="M10 2h4" />
200+
<path d="M12 2v2" />
201+
</svg>
202+
{profilerEnabled && (
203+
<span className="absolute top-1 right-1 w-2 h-2 bg-green-400 rounded-full" />
204+
)}
205+
</button>
206+
{/* Blog post */}
207+
<a
208+
href="https://samselvanathan.com/posts/flightwrapped-on-device-ai-flight-visualizer"
209+
target="_blank"
210+
rel="noopener noreferrer"
211+
className="p-2 text-gray-500 hover:text-white transition-colors duration-200"
212+
aria-label="Read the blog post"
213+
>
214+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
215+
<path d="M4 19.5v-15A2.5 2.5 0 016.5 2H20v20H6.5a2.5 2.5 0 010-5H20" />
216+
</svg>
217+
</a>
218+
{/* GitHub */}
219+
<a
220+
href="https://github.com/samsel/FlightWrapped"
221+
target="_blank"
222+
rel="noopener noreferrer"
223+
className="p-2 text-gray-500 hover:text-white transition-colors duration-200"
224+
aria-label="View source on GitHub"
225+
>
226+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
227+
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0022 12.017C22 6.484 17.522 2 12 2z" />
228+
</svg>
229+
</a>
230+
</div>
231+
)
232+
233+
const profilerPanel = (
173234
<ProfilerOverlay
174235
enabled={profilerEnabled}
175236
onToggle={handleProfilerToggle}
176237
report={profilerReport}
238+
panelOpen={profilerPanelOpen}
239+
onPanelToggle={() => setProfilerPanelOpen(false)}
177240
/>
178241
)
179242

@@ -195,7 +258,8 @@ function App() {
195258
</button>
196259
</div>
197260
)}
198-
{profilerOverlay}
261+
{topNav}
262+
{profilerPanel}
199263
</div>
200264
)
201265
}
@@ -205,7 +269,8 @@ function App() {
205269
<div className="min-h-screen glass-bg text-white flex flex-col items-center justify-center px-4 animate-fade-in">
206270
<h1 className="text-3xl font-bold mb-8">FlightWrapped</h1>
207271
<ParsingProgress progress={progress} onReset={resetToLanding} />
208-
{profilerOverlay}
272+
{topNav}
273+
{profilerPanel}
209274
</div>
210275
)
211276
}
@@ -219,7 +284,8 @@ function App() {
219284
archetype={archetype}
220285
onComplete={() => setAppState('results')}
221286
/>
222-
{profilerOverlay}
287+
{topNav}
288+
{profilerPanel}
223289
</>
224290
)
225291
}
@@ -238,7 +304,8 @@ function App() {
238304
onFileUpload={handleFileUpload}
239305
/>
240306
</div>
241-
{profilerOverlay}
307+
{topNav}
308+
{profilerPanel}
242309
</ErrorBoundary>
243310
)
244311
}

src/components/ProfilerOverlay.tsx

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ interface Props {
55
enabled: boolean
66
onToggle: () => void
77
report: ProfilerReport | null
8+
panelOpen: boolean
9+
onPanelToggle: () => void
810
}
911

1012
function formatMs(ms: number): string {
@@ -75,8 +77,7 @@ function EmailRow({ email, expanded, onToggle }: { email: EmailTiming; expanded:
7577
type SortKey = 'index' | 'total' | 'llm' | 'normalize' | 'flights'
7678
type Tab = 'mbox' | 'emails'
7779

78-
export default function ProfilerOverlay({ enabled, onToggle, report }: Props) {
79-
const [panelOpen, setPanelOpen] = useState(false)
80+
export default function ProfilerOverlay({ enabled, onToggle, report, panelOpen, onPanelToggle }: Props) {
8081
const [activeTab, setActiveTab] = useState<Tab>('mbox')
8182
const [expandedEmail, setExpandedEmail] = useState<number | null>(null)
8283
const [sortKey, setSortKey] = useState<SortKey>('index')
@@ -127,45 +128,30 @@ export default function ProfilerOverlay({ enabled, onToggle, report }: Props) {
127128
? Math.max(...report.mboxSegments.map((s) => s.durationMs), 1)
128129
: 1
129130

130-
// Toggle button (always visible in bottom-right)
131-
return (
132-
<>
133-
{/* Toggle button */}
134-
<button
135-
onClick={() => {
136-
if (!enabled) {
137-
onToggle()
138-
} else {
139-
setPanelOpen(!panelOpen)
140-
}
141-
}}
142-
className={`fixed bottom-4 right-4 z-[9999] px-3 py-1.5 text-xs font-mono transition-all duration-200 shadow-lg ${
143-
enabled
144-
? 'bg-green-900/90 text-green-300 border border-green-700/50 hover:bg-green-800/90'
145-
: 'bg-gray-900/90 text-gray-500 border border-gray-700/50 hover:bg-gray-800/90 hover:text-gray-300'
146-
}`}
147-
title={enabled ? 'Toggle profiler panel' : 'Enable profiler'}
148-
>
149-
{enabled ? (report ? `Profiler ${formatMs(report.totalMs)}` : 'Profiler ON') : 'Profiler'}
150-
</button>
131+
if (!enabled || !panelOpen) return null
151132

152-
{/* Disable button when panel is open */}
153-
{enabled && panelOpen && (
133+
return (
134+
<div className="fixed top-14 right-5 z-[9998] w-[600px] max-w-[90vw] max-h-[70vh] bg-gray-950/95 border border-gray-700/50 shadow-2xl flex flex-col backdrop-blur-sm">
135+
{/* Panel header */}
136+
<div className="flex items-center justify-between px-4 py-2 border-b border-gray-800 shrink-0">
137+
<div className="flex items-center gap-2">
138+
<span className="text-xs font-medium text-gray-300">Pipeline Profiler</span>
139+
{report && (
140+
<span className="text-[10px] font-mono text-gray-500">
141+
{formatMs(report.totalMs)} total
142+
</span>
143+
)}
144+
</div>
154145
<button
155146
onClick={() => {
156147
onToggle()
157-
setPanelOpen(false)
148+
onPanelToggle()
158149
}}
159-
className="fixed bottom-4 right-44 z-[9999] px-2 py-1.5 text-xs font-mono bg-red-900/80 text-red-400 border border-red-700/50 hover:bg-red-800/90 transition-colors"
160-
title="Disable profiler"
150+
className="text-[10px] text-red-400 hover:text-red-300 font-medium transition-colors"
161151
>
162152
Disable
163153
</button>
164-
)}
165-
166-
{/* Panel */}
167-
{enabled && panelOpen && (
168-
<div className="fixed bottom-12 right-4 z-[9998] w-[600px] max-w-[90vw] max-h-[70vh] bg-gray-950/95 border border-gray-700/50 shadow-2xl flex flex-col backdrop-blur-sm">
154+
</div>
169155
{/* Tab bar */}
170156
<div className="flex border-b border-gray-800 shrink-0">
171157
<button
@@ -281,9 +267,7 @@ export default function ProfilerOverlay({ enabled, onToggle, report }: Props) {
281267
</div>
282268
</div>
283269
)}
284-
</div>
285-
)}
286-
</>
270+
</div>
287271
)
288272
}
289273

src/components/landing/HeroSection.tsx

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,32 +44,6 @@ export default function HeroSection({ onError, onDemoClick, onFileUpload }: Hero
4444
<div className="absolute inset-0 bg-gradient-to-b from-gray-950 via-gray-950/70 to-gray-950 pointer-events-none" />
4545
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_rgba(59,130,246,0.08)_0%,_transparent_60%)] pointer-events-none" />
4646

47-
{/* Top-right links */}
48-
<div className="absolute top-5 right-5 z-20 flex items-center gap-1">
49-
<a
50-
href="https://samselvanathan.com/posts/flightwrapped-on-device-ai-flight-visualizer"
51-
target="_blank"
52-
rel="noopener noreferrer"
53-
className="p-2 text-gray-500 hover:text-white transition-colors duration-200"
54-
aria-label="Read the blog post"
55-
>
56-
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
57-
<path d="M4 19.5v-15A2.5 2.5 0 016.5 2H20v20H6.5a2.5 2.5 0 010-5H20" />
58-
</svg>
59-
</a>
60-
<a
61-
href="https://github.com/samsel/FlightWrapped"
62-
target="_blank"
63-
rel="noopener noreferrer"
64-
className="p-2 text-gray-500 hover:text-white transition-colors duration-200"
65-
aria-label="View source on GitHub"
66-
>
67-
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
68-
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0022 12.017C22 6.484 17.522 2 12 2z" />
69-
</svg>
70-
</a>
71-
</div>
72-
7347
{/* Content */}
7448
<div className="relative z-10 max-w-4xl mx-auto px-6 py-24 text-center w-full">
7549
{/* Privacy badge */}

0 commit comments

Comments
 (0)