Skip to content

Commit 6545790

Browse files
AssafWooclaude
andcommitted
feat(docs): make docs site mobile responsive
- Add hamburger menu with sidebar overlay on mobile - Hide right TOC on mobile, full-width main content - Grids stack to single column on small screens - StatRow collapses to 2 columns on mobile - DataTable gets horizontal scroll on overflow - Entropy chart rows flex-wrap on small screens Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1491349 commit 6545790

1 file changed

Lines changed: 82 additions & 25 deletions

File tree

docs/src/App.tsx

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useRef } from 'react'
1+
import { useState, useEffect, useRef, createContext, useContext } from 'react'
22
import {
33
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
44
PieChart, Pie, Cell, ResponsiveContainer,
@@ -25,6 +25,10 @@ const T = {
2525
link: '#38bdf8',
2626
}
2727

28+
// ─── Mobile context ───────────────────────────────────────────────────────────
29+
const MobileCtx = createContext(false)
30+
function useIsMobile() { return useContext(MobileCtx) }
31+
2832
// ─── Data ─────────────────────────────────────────────────────────────────────
2933
const tokenTable = [
3034
{ cmd: 'pip install', before: 1787, after: 9, pct: 99 },
@@ -256,9 +260,11 @@ function Callout({ type = 'tip', children }: { type?: 'tip' | 'note' | 'warning'
256260
}
257261

258262
function StatRow({ items }: { items: { value: string; label: string; color?: string }[] }) {
263+
const isMobile = useIsMobile()
264+
const cols = isMobile ? 2 : items.length
259265
return (
260266
<div style={{
261-
display: 'grid', gridTemplateColumns: `repeat(${items.length}, 1fr)`,
267+
display: 'grid', gridTemplateColumns: `repeat(${cols}, 1fr)`,
262268
gap: 1, background: T.border, borderRadius: 10, overflow: 'hidden',
263269
marginBottom: 28,
264270
}}>
@@ -280,9 +286,9 @@ function DataTable({ headers, rows, highlight }: {
280286
return (
281287
<div style={{
282288
background: T.card, border: `1px solid ${T.border}`, borderRadius: 10,
283-
overflow: 'hidden', marginBottom: 28,
289+
overflow: 'auto', marginBottom: 28,
284290
}}>
285-
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
291+
<table style={{ width: '100%', minWidth: 480, borderCollapse: 'collapse', fontSize: 13 }}>
286292
<thead>
287293
<tr style={{ background: T.sidebar }}>
288294
{headers.map((h, i) => (
@@ -533,6 +539,7 @@ panda gain # see token savings from this session
533539
}
534540

535541
function SectionAgents() {
542+
const isMobile = useIsMobile()
536543
const agents = [
537544
{
538545
name: 'Claude Code',
@@ -605,7 +612,7 @@ function SectionAgents() {
605612
verifies the installation automatically.
606613
</P>
607614

608-
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 28 }}>
615+
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 12, marginBottom: 28 }}>
609616
{agents.map(({ name, cmd, color, config, script, desc, logo }) => (
610617
<div key={name} style={{
611618
background: T.card, border: `1px solid ${T.border}`, borderRadius: 10,
@@ -717,6 +724,7 @@ function SectionPipeline() {
717724
}
718725

719726
function SectionBert() {
727+
const isMobile = useIsMobile()
720728
const useCases = [
721729
{
722730
role: 'Output summarization',
@@ -752,7 +760,7 @@ function SectionBert() {
752760

753761
<H3>Model</H3>
754762
<div style={{
755-
display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1,
763+
display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 1,
756764
background: T.border, borderRadius: 10, overflow: 'hidden', marginBottom: 24,
757765
}}>
758766
{[
@@ -837,7 +845,7 @@ function SectionBert() {
837845
</P>
838846

839847
<div style={{
840-
display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 20,
848+
display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 16, marginBottom: 20,
841849
}}>
842850
{[
843851
{
@@ -888,16 +896,16 @@ function SectionBert() {
888896
{ range: '0.10 ≤ entropy ≤ 0.35', budget: 'Linear interpolation', label: 'Mixed content', color: T.amber },
889897
{ range: 'entropy > 0.35', budget: '100% of max budget', label: 'Diverse / rich output', color: T.emerald },
890898
].map(({ range, budget, label, color }) => (
891-
<div key={range} style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
892-
<code style={{ width: 200, flexShrink: 0, fontSize: 12, color, fontFamily: 'JetBrains Mono, monospace' }}>{range}</code>
893-
<div style={{ flex: 1, height: 6, background: T.border, borderRadius: 3, overflow: 'hidden' }}>
899+
<div key={range} style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: isMobile ? 8 : 16 }}>
900+
<code style={{ width: isMobile ? '100%' : 200, flexShrink: 0, fontSize: 12, color, fontFamily: 'JetBrains Mono, monospace' }}>{range}</code>
901+
<div style={{ flex: 1, minWidth: 60, height: 6, background: T.border, borderRadius: 3, overflow: 'hidden' }}>
894902
<div style={{
895903
height: '100%', borderRadius: 3, background: color,
896904
width: color === T.red ? '5%' : color === T.amber ? '50%' : '100%',
897905
}} />
898906
</div>
899-
<span style={{ fontSize: 12.5, color: T.sub, width: 160, flexShrink: 0 }}>{budget}</span>
900-
<span style={{ fontSize: 12, color: T.muted }}>{label}</span>
907+
<span style={{ fontSize: 12.5, color: T.sub, width: isMobile ? 'auto' : 160, flexShrink: 0 }}>{budget}</span>
908+
{!isMobile && <span style={{ fontSize: 12, color: T.muted }}>{label}</span>}
901909
</div>
902910
))}
903911
</div>
@@ -1734,9 +1742,18 @@ export default function App() {
17341742
const [query, setQuery] = useState('')
17351743
const [searchFocused, setSearchFocused] = useState(false)
17361744
const [selectedIdx, setSelectedIdx] = useState(0)
1745+
const [isMobile, setIsMobile] = useState(false)
1746+
const [sidebarOpen, setSidebarOpen] = useState(false)
17371747
const searchRef = useRef<HTMLInputElement>(null)
17381748
const contentRef = useRef<HTMLDivElement>(null)
17391749

1750+
useEffect(() => {
1751+
const check = () => setIsMobile(window.innerWidth < 768)
1752+
check()
1753+
window.addEventListener('resize', check)
1754+
return () => window.removeEventListener('resize', check)
1755+
}, [])
1756+
17401757
const results = query.trim().length > 0
17411758
? SEARCH_INDEX.filter(item => {
17421759
const q = query.toLowerCase()
@@ -1751,6 +1768,7 @@ export default function App() {
17511768
function goTo(id: string) {
17521769
setQuery('')
17531770
setSearchFocused(false)
1771+
setSidebarOpen(false)
17541772
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' })
17551773
}
17561774

@@ -1771,6 +1789,7 @@ export default function App() {
17711789
}, [])
17721790

17731791
return (
1792+
<MobileCtx.Provider value={isMobile}>
17741793
<div style={{ background: T.bg, color: T.text, minHeight: '100vh', fontFamily: 'Inter, system-ui, sans-serif' }}>
17751794
{/* Top nav */}
17761795
<nav style={{
@@ -1779,20 +1798,38 @@ export default function App() {
17791798
padding: '0 28px', background: T.sidebar, borderBottom: `1px solid ${T.border}`,
17801799
}}>
17811800
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
1801+
{/* Hamburger — mobile only */}
1802+
{isMobile && (
1803+
<button
1804+
onClick={() => setSidebarOpen(o => !o)}
1805+
style={{
1806+
background: 'none', border: 'none', cursor: 'pointer',
1807+
color: T.sub, padding: '4px 6px', borderRadius: 6,
1808+
display: 'flex', flexDirection: 'column', gap: 4,
1809+
}}
1810+
aria-label="Toggle menu"
1811+
>
1812+
<span style={{ display: 'block', width: 18, height: 2, background: T.sub, borderRadius: 1 }} />
1813+
<span style={{ display: 'block', width: 18, height: 2, background: T.sub, borderRadius: 1 }} />
1814+
<span style={{ display: 'block', width: 18, height: 2, background: T.sub, borderRadius: 1 }} />
1815+
</button>
1816+
)}
17821817
<img src={`${import.meta.env.BASE_URL}logo.png`} alt="PandaFilter" style={{ width: 32, height: 32, objectFit: 'contain' }} />
17831818
<span style={{ fontWeight: 700, fontSize: 15, color: T.text }}>PandaFilter</span>
1784-
<span style={{
1785-
fontSize: 11, padding: '2px 8px', borderRadius: 999,
1786-
background: 'rgba(34,211,238,0.1)', color: T.cyan, border: `1px solid rgba(34,211,238,0.25)`,
1787-
marginLeft: 4,
1788-
}}>docs</span>
1819+
{!isMobile && (
1820+
<span style={{
1821+
fontSize: 11, padding: '2px 8px', borderRadius: 999,
1822+
background: 'rgba(34,211,238,0.1)', color: T.cyan, border: `1px solid rgba(34,211,238,0.25)`,
1823+
marginLeft: 4,
1824+
}}>docs</span>
1825+
)}
17891826
</div>
1790-
<div style={{ display: 'flex', gap: 24, alignItems: 'center' }}>
1827+
<div style={{ display: 'flex', gap: isMobile ? 12 : 24, alignItems: 'center' }}>
17911828
{[
17921829
{ label: 'GitHub', href: 'https://github.com/AssafWoo/PandaFilter' },
17931830
{ label: 'Discord', href: 'https://discord.com/invite/FFQC3bxYQ' },
17941831
].map(({ label, href }) => (
1795-
<a key={label} href={href} style={{ fontSize: 13.5, color: T.sub, textDecoration: 'none' }}
1832+
<a key={label} href={href} style={{ fontSize: isMobile ? 12 : 13.5, color: T.sub, textDecoration: 'none' }}
17961833
onMouseEnter={e => (e.currentTarget.style.color = T.text)}
17971834
onMouseLeave={e => (e.currentTarget.style.color = T.sub)}>
17981835
{label}
@@ -1801,14 +1838,31 @@ export default function App() {
18011838
</div>
18021839
</nav>
18031840

1841+
{/* Mobile sidebar backdrop */}
1842+
{isMobile && sidebarOpen && (
1843+
<div
1844+
onClick={() => setSidebarOpen(false)}
1845+
style={{
1846+
position: 'fixed', inset: 0, zIndex: 149,
1847+
background: 'rgba(0,0,0,0.5)',
1848+
}}
1849+
/>
1850+
)}
1851+
18041852
{/* Body */}
18051853
<div style={{ display: 'flex', paddingTop: 56, minHeight: '100vh' }}>
18061854

18071855
{/* Left sidebar */}
18081856
<aside style={{
1809-
width: 240, flexShrink: 0, position: 'fixed', top: 56, bottom: 0,
1857+
width: 240, flexShrink: 0,
1858+
position: 'fixed', top: 56, bottom: 0,
18101859
background: T.sidebar, borderRight: `1px solid ${T.border}`,
18111860
overflowY: 'auto', display: 'flex', flexDirection: 'column',
1861+
zIndex: 150,
1862+
...(isMobile ? {
1863+
transform: sidebarOpen ? 'translateX(0)' : 'translateX(-100%)',
1864+
transition: 'transform 0.25s ease',
1865+
} : {}),
18121866
}}>
18131867
{/* Search */}
18141868
<div style={{ padding: '16px 16px 12px', position: 'relative', flexShrink: 0 }}>
@@ -1924,8 +1978,10 @@ export default function App() {
19241978

19251979
{/* Main content */}
19261980
<main ref={contentRef} style={{
1927-
flex: 1, marginLeft: 240, marginRight: 200,
1928-
padding: '48px 56px 96px',
1981+
flex: 1,
1982+
marginLeft: isMobile ? 0 : 240,
1983+
marginRight: isMobile ? 0 : 200,
1984+
padding: isMobile ? '32px 20px 80px' : '48px 56px 96px',
19291985
minWidth: 0,
19301986
}}>
19311987
<div id="overview">
@@ -1957,8 +2013,8 @@ export default function App() {
19572013
</div>
19582014
</main>
19592015

1960-
{/* Right TOC */}
1961-
<aside style={{
2016+
{/* Right TOC — hidden on mobile */}
2017+
{!isMobile && <aside style={{
19622018
width: 200, flexShrink: 0, position: 'fixed', top: 56, right: 0, bottom: 0,
19632019
padding: '28px 20px', overflowY: 'auto',
19642020
borderLeft: `1px solid ${T.border}`,
@@ -1984,8 +2040,9 @@ export default function App() {
19842040
</a>
19852041
)
19862042
})}
1987-
</aside>
2043+
</aside>}
19882044
</div>
19892045
</div>
2046+
</MobileCtx.Provider>
19902047
)
19912048
}

0 commit comments

Comments
 (0)