()
| 39 | } |
| 40 | |
| 41 | export default function Home() { |
| 42 | const [search, setSearch] = useState(""); |
| 43 | const [cat, setCat] = useState("all"); |
| 44 | const [bm, setBm] = useState<string[]>([]); |
| 45 | const [sort, setSort] = useState<Sort>("default"); |
| 46 | const [open, setOpen] = useState<Project | null>(null); |
| 47 | const [showBmOnly, setShowBmOnly] = useState(false); |
| 48 | |
| 49 | // Bookmarks load client-side only — keeps SSR/hydration consistent. |
| 50 | useEffect(() => setBm(getBookmarks()), []); |
| 51 | |
| 52 | const toggleBm = (id: string) => setBm(toggleBookmark(id)); |
| 53 | |
| 54 | const filtered = useMemo(() => { |
| 55 | let arr = PROJECTS.filter((p) => { |
| 56 | if (showBmOnly && !bm.includes(p.id)) return false; |
| 57 | if (cat !== "all" && p.cat !== cat) return false; |
| 58 | if (search) { |
| 59 | const q = search.toLowerCase(); |
| 60 | return ( |
| 61 | p.name.toLowerCase().includes(q) || |
| 62 | p.blurb.toLowerCase().includes(q) || |
| 63 | p.deps.toLowerCase().includes(q) |
| 64 | ); |
| 65 | } |
| 66 | return true; |
| 67 | }); |
| 68 | if (sort === "alpha") arr = [...arr].sort((a, b) => a.name.localeCompare(b.name)); |
| 69 | if (sort === "short") arr = [...arr].sort((a, b) => a.lines - b.lines); |
| 70 | if (sort === "bookmarks") |
| 71 | arr = [...arr].sort( |
| 72 | (a, b) => (bm.includes(b.id) ? 1 : 0) - (bm.includes(a.id) ? 1 : 0), |
| 73 | ); |
| 74 | return arr; |
| 75 | }, [search, cat, bm, sort, showBmOnly]); |
| 76 | |
| 77 | return ( |
| 78 | <div> |
| 79 | <div className="s-shell"> |
| 80 | {/* NAV */} |
| 81 | <nav className="s-nav"> |
| 82 | <Link className="s-logo" href="/"> |
| 83 | <div className="s-logomark"> |
| 84 | <StickerLogo /> |
| 85 | </div> |
| 86 | <div> |
| 87 | <div className="s-brand-name">pyBegin</div> |
| 88 | <div className="s-brand-sub">250+ projects · 1 weekend each</div> |
| 89 | </div> |
| 90 | </Link> |
| 91 | <div className="s-navlinks"> |
| 92 | <button className="active">Home</button> |
| 93 | <button onClick={() => scrollTo("gallery")}>Projects</button> |
| 94 | <button onClick={() => scrollTo("paths")}>Learn</button> |
| 95 | <Link href="/playground">Playground</Link> |
| 96 | <button onClick={() => scrollTo("crew")}>Contribute</button> |
| 97 | </div> |
| 98 | <div className="s-nav-right"> |
nothing calls this directly
no test coverage detected