1const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ 2 "palette": "Sand", 3 "type": "Archivo + Spectral" 4}/*EDITMODE-END*/; 5 6const PALETTES = { 7 "Sand": { bg: "#e9e0d2", panel: "#f3ece0", ink: "#1c1813", soft: "#766d5d", line: "rgba(28,24,19,.13)" }, 8 "Cool paper": { bg: "#eceef0", panel: "#f6f7f8", ink: "#15181c", soft: "#697079", line: "rgba(21,24,28,.12)" }, 9 "Ivory": { bg: "#f4efe3", panel: "#fbf8f0", ink: "#211d15", soft: "#7a7363", line: "rgba(33,29,21,.12)" }, 10 "Slate": { bg: "#17181a", panel: "#202225", ink: "#ededea", soft: "#9a9a95", line: "rgba(237,237,234,.14)" }, 11}; 12const TYPESETS = { 13 "Archivo + Spectral": { display: "'Archivo','Helvetica Neue',Arial,sans-serif", body: "'Spectral',Georgia,serif" }, 14 "All Archivo": { display: "'Archivo','Helvetica Neue',Arial,sans-serif", body: "'Archivo','Helvetica Neue',Arial,sans-serif" }, 15 "Archivo + Newsreader": { display: "'Archivo','Helvetica Neue',Arial,sans-serif", body: "'Newsreader',Georgia,serif" }, 16}; 17 18function applyTheme(t) { 19 const p = PALETTES[t.palette] || PALETTES["Sand"]; 20 const ty = TYPESETS[t.type] || TYPESETS["Archivo + Spectral"]; 21 const r = document.documentElement.style; 22 r.setProperty("--cv-bg", p.bg); 23 r.setProperty("--cv-panel", p.panel); 24 r.setProperty("--cv-ink", p.ink); 25 r.setProperty("--cv-soft", p.soft); 26 r.setProperty("--cv-line", p.line); 27 r.setProperty("--font-display", ty.display); 28 r.setProperty("--font-body", ty.body); 29 document.documentElement.dataset.palette = t.palette === "Slate" ? "dark" : "light"; 30} 31 32/* Curated marquee sets for the home carousels */ 33const ICONIC_FUNDS = ["usv-2004", "bench-7", "seq-8", "thrive-2", "rb-1", "mk-1"]; 34const ICONIC_PEOPLE = [ 35 "seq-valentine", "seq-moritz", "bench-gurley", "kp-doerr", "ff-thiel", "ff-rabois", 36 "ff-stephens", "usv-wilson", "thrive-kushner", "mk-hsu", "mk-rannala", "fr-kopelman", "rb-malka", 37]; 38 39/* One-line teasers leading with the notable investments / returns that made each fund iconic */ 40const ICONIC_FUND_TEASERS = { 41 "usv-2004": "Twitter, Zynga, Etsy & Tumblr â ~13.9x on a $125M fund, Twitter alone ~8x.", 42 "bench-7": "Led Uber's 2011 Series A â one of the greatest venture returns ever recorded.", 43 "seq-8": "Sequoia's 1998 vintage â the Google bet (~$12M â billions), plus PayPal.", 44 "thrive-2": "Instagram in 2011 â ~2x in 72 hours â as the concentration thesis took shape.", 45 "rb-1": "Ribbit's debut â Coinbase, Credit Karma & the seed of a fintech dynasty.", 46 "mk-1": "Honey returned ~$280M (23x the fund); ServiceTitan IPO'd at ~$9B â 43â53x net.", 47}; 48 49/* One-sentence teasers for what made each iconic person iconic */ 50const ICONIC_PEOPLE_TEASERS = { 51 "seq-valentine": "The father of Silicon Valley venture â backed Atari, Apple, Oracle & Cisco.", 52 "seq-moritz": "Wrote the early checks into Google, Yahoo & PayPal.", 53 "bench-gurley": "Led Benchmark's Uber bet and defined the internet-marketplace playbook.", 54 "kp-doerr": "Backed Google and Amazon; brought OKRs to the Valley.", 55 "ff-thiel": "First outside investor in Facebook; co-founded PayPal and Palantir.", 56 "ff-rabois": "The operator-investor behind Square, Affirm, Ramp & Faire.", 57 "ff-stephens": "Co-founded Anduril and turned American Dynamism into a thesis.", 58 "usv-wilson": "Built USV on a daily blog; early in Twitter, Etsy & Coinbase.", 59 "thrive-kushner": "Turned a $5M first fund into Thrive; led OpenAI's record round.", 60 "mk-hsu": "Ex-eBay & AT&T operator who built Mucker's LA-first micro-VC.", 61 "mk-rannala": "Coined the 'Blue Collar VC' and rode Honey to a 23x fund.", 62 "fr-kopelman": "Invented institutional seed; wrote First Round's first check into Uber.", 63 "rb-malka": "Fintech operator-investor behind Coinbase, Nubank & Robinhood.", 64}; 65 66/* A firm-swatch used in group headers and the home collage */ 67function FirmSwatch({ firm }) { 68 return ( 69 <span className="dirgroup__swatch" style={{ background: firm.bg, color: firm.ink }}> 70 {firm.logoSrc ? <img src={firm.logoSrc} alt="" /> : firm.name.split(" ").map((w) => w[0]).slice(0, 2).join("")} 71 </span> 72 ); 73} 74 75/* Directory view â every fund, grouped by firm */ 76function FundsDirectory({ nav, search, setSearch }) { 77 return ( 78 <main className="home"> 79 <TopBar search={search} setSearch={setSearch} onMenu={nav.menu} onHome={nav.home} nav={nav} /> 80 <div className="pagenav" style={{ padding: "0 44px" }}> 81 <Pill variant="ghost" onClick={nav.back}><span style={{ marginRight: 8 }}>â</span> Back</Pill> 82 </div> 83 <div className="dirhead"> 84 <h1 className="dirhead__title">Every fund</h1> 85 <p className="dirhead__sub">All {window.FUNDS.length} funds across the directory, grouped by firm and ordered by vintage.</p> 86 </div> 87 {window.FIRMS.map((firm) => { 88 const funds = window.fundsForFirm(firm.id).sort((a, b) => (a.vintage || 0) - (b.vintage || 0)); 89 if (!funds.length) return null; 90 return ( 91 <section className="dirgroup" key={firm.id}> 92 <div className="dirgroup__head"> 93 <FirmSwatch firm={firm} /> 94 <button className="dirgroup__name" style={{ background: "none", border: "none", cursor: "pointer", padding: 0 }} onClick={() => nav.firm(firm.id)}>{firm.name}</button>
95 <span className="dirgroup__count">{funds.length} funds</span> 96 </div> 97 <DirectoryGrid> 98 {funds.map((fd) => ( 99 <Tile key={fd.id} title={fd.name} tag={String(fd.vintage)} 100 teaser={fd.strategy ? (fd.strategy.length > 116 ? fd.strategy.slice(0, 113).replace(/\s+\S*$/, "") + "â¦" : fd.strategy) : ""} 101 bg={firm.bg} ink={firm.ink} accent={firm.accent} onClick={() => nav.fund(fd.id)} /> 102 ))} 103 </DirectoryGrid> 104 </section> 105 ); 106 })} 107 <SiteFooter /> 108 </main> 109 ); 110} 111 112/* Directory view â every person, grouped by firm */ 113function PeopleDirectory({ nav, search, setSearch }) { 114 return ( 115 <main className="home"> 116 <TopBar search={search} setSearch={setSearch} onMenu={nav.menu} onHome={nav.home} nav={nav} /> 117 <div className="pagenav" style={{ padding: "0 44px" }}> 118 <Pill variant="ghost" onClick={nav.back}><span style={{ marginRight: 8 }}>â</span> Back</Pill> 119 </div> 120 <div className="dirhead"> 121 <h1 className="dirhead__title">Every person</h1> 122 <p className="dirhead__sub">All {window.PEOPLE.length} investors profiled across the directory, grouped by firm.</p> 123 </div> 124 {window.FIRMS.map((firm) => { 125 const people = window.peopleForFirm(firm.id); 126 if (!people.length) return null; 127 return ( 128 <section className="dirgroup" key={firm.id}> 129 <div className="dirgroup__head"> 130 <FirmSwatch firm={firm} /> 131 <button className="dirgroup__name" style={{ background: "none", border: "none", cursor: "pointer", padding: 0 }} onClick={() => nav.firm(firm.id)}>{firm.name}</button> 132 <span className="dirgroup__count">{people.length} people</span> 133 </div> 134 <DirectoryGrid> 135 {people.map((p) => ( 136 <Tile key={p.id} variant="person" initials={initialsOf(p.name)} fullName={p.noPhoto ? p.name : null} title={p.name} tag={p.name} 137 teaser={<React.Fragment><span className="tile__teaserrole">{p.role}</span><br />{p.tenure}</React.Fragment>} 138 bg={p.noPhoto ? "#111111" : firm.bg} ink={p.noPhoto ? "#ffffff" : firm.ink} accent={firm.accent} 139 logoId={p.noPhoto ? undefined : "dir-headshot-" + p.id} src={p.noPhoto ? undefined : p.headshotSrc} onClick={() => nav.person(p.id)} /> 140 ))} 141 </DirectoryGrid> 142 </section> 143 ); 144 })} 145 <SiteFooter /> 146 </main> 147 ); 148} 149 150/* ===== URL routing (real paths, e.g. /firm/sequoia) ===== 151 Paths are shareable and SEO-indexable. The host must rewrite all routes to 152 index.html (vercel.json / serve.json included in this folder). */ 153function viewToPath(v) { 154 if (v.kind === "home") return "/"; 155 if (v.kind === "funds") return "/funds"; 156 if (v.kind === "people") return "/people"; 157 return "/" + v.kind + "/" + v.id; 158} 159function pathToView(path) { 160 const p = (path || "/").replace(/\/+$/, "") || "/"; 161 if (p === "/" || p.endsWith("index.html") || p.endsWith(".html")) return { kind: "home" }; 162 if (p === "/funds") return { kind: "funds" }; 163 if (p === "/people") return { kind: "people" }; 164 const m = p.match(/^\/(firm|fund|person)\/([\w-]+)$/); 165 if (m) { 166 const lookup = { firm: window.getFirm, fund: window.getFund, person: window.getPerson }[m[1]]; 167 if (lookup(m[2])) return { kind: m[1], id: m[2] }; 168 } 169 return { kind: "home" }; 170} 171/* Build the logical ancestry for a deep-linked view so the in-app Back works. */ 172function stackForView(v) { 173 const home = { kind: "home" }; 174 if (v.kind === "home") return [home]; 175 if (v.kind === "funds" || v.kind === "people") return [home, v]; 176 if (v.kind === "firm") return [home, v]; 177 const rec = v.kind === "fund" ? window.getFund(v.id) : window.getPerson(v.id); 178 return [home, { kind: "firm", id: rec.firmId }, v]; 179} 180function viewTitle(v) { 181 const base = "The Vintage Project"; 182 if (v.kind === "firm") return window.getFirm(v.id).name + " · " + base; 183 if (v.kind === "fund") { const f = window.getFund(v.id); return f.name + " · " + window.getFirm(f.firmId).name + " · " + base; } 184 if (v.kind === "person") { const p = window.getPerson(v.id); return p.name + " · " + window.getFirm(p.firmId).name + " · " + base; }
185 if (v.kind === "funds") return "Every fund · " + base; 186 if (v.kind === "people") return "Every person · " + base; 187 return base + " | Every investment firm has a story."; 188} 189 190function App() { 191 const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); 192 const [stack, setStack] = React.useState(() => stackForView(pathToView(window.location.pathname))); 193 const [search, setSearch] = React.useState(""); 194 const [menuOpen, setMenuOpen] = React.useState(false); 195 196 React.useEffect(() => { applyTheme(t); }, [t.palette, t.type]); 197 198 const view = stack[stack.length - 1]; 199 200 /* Keep the address bar + tab title in sync with the current view. */ 201 React.useEffect(() => { 202 const path = viewToPath(view); 203 if (window.location.pathname !== path && window.location.protocol !== "file:") { 204 window.history.pushState({}, "", path); 205 } 206 document.title = viewTitle(view); 207 }, [view]); 208 209 /* Browser back/forward â rebuild the view from the URL. */ 210 React.useEffect(() => { 211 const onPop = () => { setStack(stackForView(pathToView(window.location.pathname))); window.scrollTo(0, 0); }; 212 window.addEventListener("popstate", onPop); 213 return () => window.removeEventListener("popstate", onPop); 214 }, []); 215 216 const go = (v) => { setStack((s) => [...s, v]); window.scrollTo(0, 0); }; 217 const nav = { 218 home: () => { setStack([{ kind: "home" }]); window.scrollTo(0, 0); }, 219 back: () => setStack((s) => (s.length > 1 ? s.slice(0, -1) : s)), 220 firm: (id) => go({ kind: "firm", id }), 221 fund: (id) => go({ kind: "fund", id }), 222 person: (id) => go({ kind: "person", id }), 223 funds: () => go({ kind: "funds" }), 224 people: () => go({ kind: "people" }), 225 menu: () => setMenuOpen(true),
226 }; 227 228 const filtered = React.useMemo(() => { 229 const q = search.trim().toLowerCase(); 230 if (!q) return window.FIRMS; 231 return window.FIRMS.filter((f) => (f.name + " " + f.teaser + " " + f.blurb).toLowerCase().includes(q)); 232 }, [search]); 233 234 return ( 235 <div className="app"> 236 {view.kind === "home" && ( 237 <main className="home"> 238 <TopBar search={search} setSearch={setSearch} onMenu={nav.menu} onHome={nav.home} nav={nav} /> 239 240 {search.trim() ? ( 241 filtered.length === 0 ? ( 242 <div className="empty">No firms match â{search}â.</div> 243 ) : ( 244 <DirectoryGrid> 245 {filtered.map((f) => ( 246 <Tile key={f.id} title={f.name} tag={f.name} teaser={f.teaser} logoId={"logo-" + f.id} 247 src={f.logoSrc} bg={f.bg} ink={f.ink} accent={f.accent} onClick={() => nav.firm(f.id)} /> 248 ))} 249 </DirectoryGrid> 250 ) 251 ) : ( 252 <React.Fragment> 253 {/* 1 â Firm directory, above the fold, no header */} 254 <DirectoryGrid> 255 {window.FIRMS.map((f) => ( 256 <Tile key={f.id} title={f.name} tag={f.name} teaser={f.teaser} logoId={"logo-" + f.id} 257 src={f.logoSrc} bg={f.bg} ink={f.ink} accent={f.accent} onClick={() => nav.firm(f.id)} /> 258 ))} 259 </DirectoryGrid> 260 261 {/* 2 â Every firm has a story */} 262 <section className="storyband"> 263 <h1 className="storyband__title">Every investment firm has a story.</h1> 264 <p className="storyband__sub">We research the funds, people, and decisions that have created the most iconic firms in the world.</p> 265 <div className="storyband__cta"> 266 <ArrowPill onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}>Explore The Vintage Project</ArrowPill> 267 </div> 268 </section> 269 270 {/* 3 â Iconic funds */} 271 <Carousel title="Iconic funds" sub="The vintages that rewrote what a single fund could return." onMore={nav.funds}> 272 {ICONIC_FUNDS.map((id) => { 273 const fd = window.getFund(id); if (!fd) return null; 274 return <FundCard key={id} fund={fd} firm={window.getFirm(fd.firmId)} teaser={ICONIC_FUND_TEASERS[id]} onClick={() => nav.fund(id)} />; 275 })} 276 </Carousel> 277 278 {/* 4 â Iconic people */} 279 <Carousel title="Iconic people" sub="The investors who defined their firms and eras." onMore={nav.people}> 280 {ICONIC_PEOPLE.map((id) => { 281 const p = window.getPerson(id); if (!p) return null; 282 return <PersonCard key={id} person={p} firm={window.getFirm(p.firmId)} teaser={ICONIC_PEOPLE_TEASERS[id]} onClick={() => nav.person(id)} />; 283 })} 284 </Carousel> 285 </React.Fragment> 286 )} 287 288 <SiteFooter /> 289 </main> 290 )} 291 292 {view.kind === "funds" && <FundsDirectory nav={nav} search={search} setSearch={setSearch} />} 293 {view.kind === "people" && <PeopleDirectory nav={nav} search={search} setSearch={setSearch} />} 294 295 {view.kind === "firm" && ( 296 <FirmView firm={window.getFirm(view.id)} nav={nav} search={search} setSearch={setSearch} /> 297 )} 298 {view.kind === "fund" && ( 299 <FundView fund={window.getFund(view.id)} nav={nav} search={search} setSearch={setSearch} /> 300 )} 301 {view.kind === "person" && ( 302 <PersonView person={window.getPerson(view.id)} nav={nav} search={search} setSearch={setSearch} /> 303 )} 304 305 <MenuOverlay open={menuOpen} onClose={() => setMenuOpen(false)} onHome={nav.home} /> 306 307 <NewsletterPopup key={view.kind + (view.id || "")} /> 308 309 {view.kind === "home" && <WelcomePopup />} 310 311 <TweaksPanel> 312 <TweakSection label="Palette" /> 313 <TweakRadio label="Canvas" value={t.palette} 314 options={["Sand", "Cool paper", "Ivory", "Slate"]} 315 onChange={(v) => setTweak("palette", v)} /> 316 <TweakSection label="Typography" /> 317 <TweakRadio label="Type set" value={t.type} 318 options={["Archivo + Spectral", "All Archivo", "Archivo + Newsreader"]} 319 onChange={(v) => setTweak("type", v)} /> 320 </TweaksPanel> 321 </div> 322 ); 323} 324 325ReactDOM.createRoot(document.getElementById("root")).render(<App />);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.