Portfolios · React
Data scientist
Crisp portfolio for a data scientist, with projects, methods, results, experience and references.

What you get
A structured portfolio for a data scientist or analyst: a results snapshot, selected projects, the methods they use, a stats band, an experience timeline, references and a contact form. All content lives in src/site.ts.
Files
Exactly what is copied into a new project: 13 files, nothing generated.
Explorer 13
import { useState, type ReactNode } from "react";import { Icon } from "./Icon";import type { Link, Section, Visual } from "./types"; type Of<K extends Section["kind"]> = Extract<Section, { kind: K }>; function Container({ children, className = "" }: { children: ReactNode; className?: string }) { return <div className={`mx-auto w-full max-w-6xl px-5 sm:px-8 ${className}`}>{children}</div>;} function Eyebrow({ children }: { children?: ReactNode }) { if (!children) return null; return <p className="mb-3 text-sm font-semibold tracking-wide text-accent">{children}</p>;} function Heading({ eyebrow, title, text, center }: { eyebrow?: string; title: string; text?: string; center?: boolean }) { return ( <div className={center ? "mx-auto max-w-2xl text-center" : "max-w-2xl"}> <Eyebrow>{eyebrow}</Eyebrow> <h2 className="font-heading text-3xl font-semibold leading-tight tracking-tight sm:text-4xl">{title}</h2> {text ? <p className="mt-4 text-lg leading-relaxed text-muted">{text}</p> : null} </div> );} export function Button({ link, variant = "primary" }: { link: Link; variant?: "primary" | "secondary" }) { const base = "inline-flex h-11 items-center justify-center gap-2 rounded-card px-5 text-[15px] font-medium transition focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"; return ( <a href={link.href} className={variant === "primary" ? `${base} bg-accent text-accent-fg hover:opacity-90` : `${base} border border-line bg-bg text-fg hover:bg-surface`}> {link.label} </a> );} /** Soft light falloff so flat colour blocks read as surfaces, not swatches. */const SHADE = "radial-gradient(120% 90% at 20% 10%, rgba(255,255,255,0.18), transparent 55%), linear-gradient(180deg, transparent 50%, rgba(0,0,0,0.16))"; function VisualBlock({ visual }: { visual: Visual }) { if (visual.type === "none") return null; if (visual.type === "tiles") { const [a, b, c, d] = visual.tones; return ( <figure className="grid aspect-[5/4] grid-cols-6 grid-rows-6 gap-3"> <div className="col-span-4 row-span-4 rounded-card" style={{ background: `${SHADE}, ${a}` }} /> <div className="col-span-2 row-span-3 rounded-card" style={{ background: `${SHADE}, ${b ?? a}` }} /> <div className="col-span-2 row-span-3 rounded-card" style={{ background: `${SHADE}, ${c ?? b ?? a}` }} /> <div className="col-span-4 row-span-2 flex items-end rounded-card p-4" style={{ background: `${SHADE}, ${d ?? c ?? a}` }}> {visual.caption ? <figcaption className="text-sm font-medium text-white/90">{visual.caption}</figcaption> : null} </div> </figure> ); } if (visual.type === "card") { return ( <figure className="flex aspect-[5/4] flex-col justify-end rounded-card p-7 text-white" style={{ background: `${SHADE}, ${visual.tone}` }}> <p className="font-heading text-2xl font-semibold">{visual.title}</p> <ul className="mt-4 space-y-2 text-[15px] text-white/85"> {visual.lines.map((l) => ( <li key={l} className="flex items-center gap-2"> <Icon name="check" className="h-4 w-4" /> {l} </li> ))} </ul> </figure> ); } return ( <figure className="overflow-hidden rounded-card border border-line bg-bg shadow-[0_24px_60px_-28px_rgba(0,0,0,0.35)]"> <div className="flex items-center gap-1.5 border-b border-line px-4 py-3"> <span className="h-2.5 w-2.5 rounded-full bg-line" /> <span className="h-2.5 w-2.5 rounded-full bg-line" /> <span className="h-2.5 w-2.5 rounded-full bg-line" /> <span className="ml-3 text-sm font-medium">{visual.title}</span> </div> <ul className="divide-y divide-line"> {visual.rows.map((r) => ( <li key={r.label} className="flex items-center justify-between gap-4 px-5 py-3.5 text-[15px]"> <span className="flex items-center gap-3"> <span className="h-2 w-2 rounded-full" style={{ background: r.tone ?? "var(--color-accent)" }} /> {r.label} </span> <span className="font-medium tabular-nums">{r.value}</span> </li> ))} </ul> {visual.footer ? <figcaption className="border-t border-line bg-surface px-5 py-3 text-sm text-muted">{visual.footer}</figcaption> : null} </figure> );} function Hero({ s }: { s: Of<"hero"> }) { const actions = ( <div className="mt-8 flex flex-wrap gap-3"> <Button link={s.primary} /> {s.secondary ? <Button link={s.secondary} variant="secondary" /> : null} </div> ); if (s.variant === "center") { return ( <section className="pb-16 pt-16 sm:pt-24"> <Container className="text-center"> <Eyebrow>{s.eyebrow}</Eyebrow> <h1 className="mx-auto max-w-3xl font-heading text-4xl font-semibold leading-[1.05] tracking-tight sm:text-6xl">{s.title}</h1> <p className="mx-auto mt-6 max-w-2xl text-lg leading-relaxed text-muted sm:text-xl">{s.text}</p> <div className="flex justify-center">{actions}</div> {s.note ? <p className="mt-4 text-sm text-muted">{s.note}</p> : null} {s.visual.type !== "none" ? ( <div className="mx-auto mt-14 max-w-4xl text-left"> <VisualBlock visual={s.visual} /> </div> ) : null} </Container> </section> ); } if (s.variant === "editorial") { return ( <section className="pb-12 pt-14 sm:pt-20"> <Container> <Eyebrow>{s.eyebrow}</Eyebrow> <h1 className="max-w-5xl font-heading text-5xl font-medium leading-[0.98] tracking-tight sm:text-7xl">{s.title}</h1> <div className="mt-10 grid gap-10 border-t border-line pt-8 md:grid-cols-[1fr_1.4fr]"> <div> <p className="text-lg leading-relaxed text-muted">{s.text}</p> {actions} </div> <VisualBlock visual={s.visual} /> </div> </Container> </section> ); } return ( <section className="pb-16 pt-12 sm:pt-20"> <Container className="grid items-center gap-12 lg:grid-cols-[1.05fr_1fr]"> <div> <Eyebrow>{s.eyebrow}</Eyebrow> <h1 className="font-heading text-4xl font-semibold leading-[1.05] tracking-tight sm:text-[3.4rem]">{s.title}</h1> <p className="mt-6 max-w-xl text-lg leading-relaxed text-muted">{s.text}</p> {actions} {s.note ? <p className="mt-4 text-sm text-muted">{s.note}</p> : null} </div> <VisualBlock visual={s.visual} /> </Container> </section> );} function Band({ id, children, tinted }: { id?: string; children: ReactNode; tinted?: boolean }) { return ( <section id={id} className={`scroll-mt-20 py-20 sm:py-24 ${tinted ? "bg-surface" : ""}`}> <Container>{children}</Container> </section> );} function Features({ s }: { s: Of<"features"> }) { const cols = s.columns === 2 ? "sm:grid-cols-2" : s.columns === 4 ? "sm:grid-cols-2 lg:grid-cols-4" : "sm:grid-cols-2 lg:grid-cols-3"; return ( <Band id={s.id}> <Heading eyebrow={s.eyebrow} title={s.title} text={s.text} /> <ul className={`mt-12 grid gap-px overflow-hidden rounded-card border border-line bg-line ${cols}`}> {s.items.map((f) => ( <li key={f.title} className="bg-bg p-7"> <span className="grid h-10 w-10 place-items-center rounded-card bg-surface text-accent"> <Icon name={f.icon} /> </span> <h3 className="mt-5 text-lg font-semibold">{f.title}</h3> <p className="mt-2 leading-relaxed text-muted">{f.text}</p> </li> ))} </ul> </Band> );} function Split({ s }: { s: Of<"split"> }) { return ( <Band id={s.id} tinted> <div className={`grid items-center gap-12 lg:grid-cols-2 ${s.reverse ? "lg:[&>*:first-child]:order-2" : ""}`}> <div> <Heading eyebrow={s.eyebrow} title={s.title} text={s.text} /> <ul className="mt-8 space-y-3"> {s.bullets.map((b) => ( <li key={b} className="flex gap-3 text-[16px]"> <span className="mt-0.5 text-accent"> <Icon name="check" /> </span> {b} </li> ))} </ul> </div> <VisualBlock visual={s.visual} /> </div> </Band> );} function Menu({ s }: { s: Of<"menu"> }) { return ( <Band id={s.id} tinted> <Heading eyebrow={s.eyebrow} title={s.title} text={s.text} center /> <div className="mt-12 grid gap-10 md:grid-cols-2"> {s.groups.map((g) => ( <div key={g.name}> <h3 className="border-b border-line pb-3 font-heading text-xl font-semibold">{g.name}</h3> <ul className="mt-4 space-y-5"> {g.items.map((i) => ( <li key={i.name}> <div className="flex items-baseline gap-3"> <span className="font-medium">{i.name}</span> <span className="flex-1 border-b border-dotted border-line" /> <span className="font-medium tabular-nums">{i.price}</span> </div> {i.text ? <p className="mt-1 text-[15px] text-muted">{i.text}</p> : null} </li> ))} </ul> </div> ))} </div> </Band> );} function Pricing({ s }: { s: Of<"pricing"> }) { return ( <Band id={s.id}> <Heading eyebrow={s.eyebrow} title={s.title} text={s.text} center /> <div className={`mt-12 grid gap-5 ${s.plans.length === 2 ? "mx-auto max-w-3xl md:grid-cols-2" : "md:grid-cols-3"}`}> {s.plans.map((p) => ( <div key={p.name} className={`flex flex-col rounded-card border p-7 ${p.featured ? "border-accent shadow-[0_20px_50px_-30px_rgba(0,0,0,0.4)]" : "border-line"}`}> <div className="flex items-center justify-between"> <h3 className="text-lg font-semibold">{p.name}</h3> {p.featured ? <span className="rounded-full bg-accent px-2.5 py-0.5 text-xs font-medium text-accent-fg">Most popular</span> : null} </div> <p className="mt-4 flex items-baseline gap-1"> <span className="font-heading text-4xl font-semibold tracking-tight">{p.price}</span> {p.period ? <span className="text-muted">{p.period}</span> : null} </p> <p className="mt-3 text-muted">{p.text}</p> <ul className="mt-6 flex-1 space-y-2.5 text-[15px]"> {p.features.map((f) => ( <li key={f} className="flex gap-2.5"> <span className="text-accent"> <Icon name="check" className="h-5 w-5" /> </span> {f} </li> ))} </ul> <div className="mt-8 [&>a]:w-full"> <Button link={p.cta} variant={p.featured ? "primary" : "secondary"} /> </div> </div> ))} </div> </Band> );} function Gallery({ s }: { s: Of<"gallery"> }) { return ( <Band id={s.id}> <Heading eyebrow={s.eyebrow} title={s.title} text={s.text} /> <ul className="mt-12 grid grid-cols-2 gap-4 md:grid-cols-3"> {s.items.map((g, i) => ( <li key={g.title} className={i % 5 === 0 ? "md:row-span-2" : ""}> <figure className="flex h-full min-h-48 flex-col justify-end rounded-card p-5 text-white" style={{ background: `${SHADE}, ${g.tone}` }}> <figcaption> <p className="font-medium">{g.title}</p> {g.caption ? <p className="text-sm text-white/80">{g.caption}</p> : null} </figcaption> </figure> </li> ))} </ul> </Band> );} function Projects({ s }: { s: Of<"projects"> }) { return ( <Band id={s.id}> <Heading eyebrow={s.eyebrow} title={s.title} /> <ul className="mt-12 grid gap-x-6 gap-y-10 sm:grid-cols-2"> {s.items.map((p) => ( <li key={p.title}> <a href="#contact" className="group block"> <div className="aspect-[4/3] rounded-card transition group-hover:opacity-90" style={{ background: `${SHADE}, ${p.tone}` }} /> <div className="mt-4 flex items-baseline justify-between gap-4"> <h3 className="text-lg font-semibold group-hover:underline">{p.title}</h3> <span className="text-sm tabular-nums text-muted">{p.year}</span> </div> <p className="text-muted">{p.tag}</p> </a> </li> ))} </ul> </Band> );} function Quotes({ s }: { s: Of<"quotes"> }) { return ( <Band id={s.id} tinted> <Heading eyebrow={s.eyebrow} title={s.title} /> <ul className="mt-12 grid gap-5 md:grid-cols-3"> {s.items.map((q) => ( <li key={q.name} className="flex flex-col rounded-card border border-line bg-bg p-7"> <p className="flex-1 text-[16px] leading-relaxed">“{q.quote}”</p> <p className="mt-6 font-medium">{q.name}</p> <p className="text-sm text-muted">{q.role}</p> </li> ))} </ul> </Band> );} function Stats({ s }: { s: Of<"stats"> }) { return ( <section id={s.id} className="border-y border-line py-12"> <Container> <dl className="grid grid-cols-2 gap-8 md:grid-cols-4"> {s.items.map((x) => ( <div key={x.label}> <dd className="font-heading text-3xl font-semibold tracking-tight sm:text-4xl">{x.value}</dd> <dt className="mt-1 text-muted">{x.label}</dt> </div> ))} </dl> </Container> </section> );} function Steps({ s }: { s: Of<"steps"> }) { return ( <Band id={s.id}> <Heading eyebrow={s.eyebrow} title={s.title} /> <ol className="mt-12 grid gap-8 md:grid-cols-3"> {s.items.map((x, i) => ( <li key={x.title} className="border-t-2 border-fg pt-5"> <span className="text-sm font-semibold tabular-nums text-accent">{String(i + 1).padStart(2, "0")}</span> <h3 className="mt-2 text-lg font-semibold">{x.title}</h3> <p className="mt-2 leading-relaxed text-muted">{x.text}</p> </li> ))} </ol> </Band> );} function Team({ s }: { s: Of<"team"> }) { return ( <Band id={s.id}> <Heading eyebrow={s.eyebrow} title={s.title} /> <ul className="mt-12 grid grid-cols-2 gap-6 md:grid-cols-4"> {s.items.map((p) => ( <li key={p.name}> <div className="grid aspect-square place-items-center rounded-card font-heading text-4xl font-semibold text-white/90" style={{ background: `${SHADE}, ${p.tone}` }}> {p.name .split(" ") .map((w) => w[0]) .join("")} </div> <p className="mt-3 font-medium">{p.name}</p> <p className="text-sm text-muted">{p.role}</p> </li> ))} </ul> </Band> );} function Timeline({ s }: { s: Of<"timeline"> }) { return ( <Band id={s.id} tinted> <Heading eyebrow={s.eyebrow} title={s.title} /> <ol className="mt-10 divide-y divide-line border-y border-line"> {s.items.map((t) => ( <li key={t.title + t.period} className="grid gap-2 py-6 sm:grid-cols-[180px_1fr]"> <span className="text-sm tabular-nums text-muted">{t.period}</span> <div> <h3 className="font-semibold">{t.title}</h3> <p className="mt-1 text-muted">{t.text}</p> </div> </li> ))} </ol> </Band> );} function Faq({ s }: { s: Of<"faq"> }) { return ( <Band id={s.id}> <div className="grid gap-10 lg:grid-cols-[1fr_1.6fr]"> <Heading eyebrow={s.eyebrow} title={s.title} /> <div className="divide-y divide-line border-y border-line"> {s.items.map((f) => ( <details key={f.q} className="group py-5"> <summary className="flex cursor-pointer list-none items-center justify-between gap-4 font-medium"> {f.q} <span className="text-muted transition group-open:rotate-45">+</span> </summary> <p className="mt-3 leading-relaxed text-muted">{f.a}</p> </details> ))} </div> </div> </Band> );} function Products({ s }: { s: Of<"products"> }) { const categories = ["All", ...Array.from(new Set(s.items.map((p) => p.category)))]; const [filter, setFilter] = useState("All"); const [cart, setCart] = useState<Record<string, number>>({}); const [open, setOpen] = useState(false); const money = (n: number) => `${s.currency}${n.toFixed(2).replace(/\.00$/, "")}`; const count = Object.values(cart).reduce((a, b) => a + b, 0); const total = s.items.reduce((sum, p) => sum + (cart[p.name] ?? 0) * p.price, 0); const shown = filter === "All" ? s.items : s.items.filter((p) => p.category === filter); return ( <Band id={s.id}> <div className="flex flex-wrap items-end justify-between gap-6"> <Heading eyebrow={s.eyebrow} title={s.title} text={s.text} /> <button type="button" onClick={() => setOpen(true)} className="inline-flex h-11 items-center gap-2 rounded-card border border-line px-4 font-medium hover:bg-surface"> <Icon name="box" /> Cart <span className="rounded-full bg-accent px-2 text-sm text-accent-fg tabular-nums">{count}</span> </button> </div> <div className="mt-8 flex flex-wrap gap-2" role="group" aria-label="Filter products"> {categories.map((c) => ( <button key={c} type="button" onClick={() => setFilter(c)} aria-pressed={filter === c} className={`h-9 rounded-full px-4 text-sm font-medium ${filter === c ? "bg-fg text-bg" : "border border-line hover:bg-surface"}`}> {c} </button> ))} </div> <ul className="mt-8 grid grid-cols-2 gap-x-5 gap-y-10 lg:grid-cols-4"> {shown.map((p) => ( <li key={p.name} className="flex flex-col"> <div className="relative aspect-[4/5] rounded-card" style={{ background: `${SHADE}, ${p.tone}` }}> {p.badge ? <span className="absolute left-3 top-3 rounded-full bg-bg px-2.5 py-1 text-xs font-medium">{p.badge}</span> : null} </div> <h3 className="mt-4 font-medium">{p.name}</h3> <p className="mt-1 flex-1 text-sm text-muted">{p.text}</p> <div className="mt-3 flex items-center justify-between"> <span className="font-semibold tabular-nums">{money(p.price)}</span> <button type="button" onClick={() => setCart((c) => ({ ...c, [p.name]: (c[p.name] ?? 0) + 1 }))} className="h-9 rounded-card bg-accent px-3.5 text-sm font-medium text-accent-fg hover:opacity-90"> Add to cart </button> </div> </li> ))} </ul> {open ? ( <div className="fixed inset-0 z-50 flex justify-end bg-black/30" onClick={() => setOpen(false)}> <aside className="flex h-full w-full max-w-sm flex-col bg-bg p-6 shadow-2xl" onClick={(e) => e.stopPropagation()} aria-label="Cart"> <div className="flex items-center justify-between"> <h3 className="font-heading text-xl font-semibold">Your cart</h3> <button type="button" onClick={() => setOpen(false)} className="text-muted hover:text-fg" aria-label="Close cart"> ✕ </button> </div> <ul className="mt-6 flex-1 divide-y divide-line overflow-auto"> {s.items .filter((p) => cart[p.name]) .map((p) => ( <li key={p.name} className="flex items-center justify-between gap-3 py-4"> <span> {p.name} <span className="block text-sm text-muted"> {cart[p.name]} × {money(p.price)} </span> </span> <button type="button" className="text-sm text-muted underline" onClick={() => setCart(({ [p.name]: _, ...rest }) => rest)}> Remove </button> </li> ))} {count === 0 ? <li className="py-8 text-center text-muted">Your cart is empty.</li> : null} </ul> <div className="border-t border-line pt-5"> <p className="flex justify-between font-semibold"> <span>Subtotal</span> <span className="tabular-nums">{money(total)}</span> </p> <button type="button" disabled={count === 0} className="mt-4 h-11 w-full rounded-card bg-accent font-medium text-accent-fg disabled:opacity-50"> Checkout </button> <p className="mt-2 text-center text-xs text-muted">Demo store: connect Stripe or Razorpay to take payments.</p> </div> </aside> </div> ) : null} </Band> );} function Hours({ s }: { s: Of<"hours"> }) { return ( <Band id={s.id}> <div className="grid gap-12 md:grid-cols-2"> <div> <Heading eyebrow={s.eyebrow} title={s.title} text={s.text} /> <ul className="mt-8 space-y-3 text-[16px]"> <li className="flex gap-3"> <span className="text-accent"> <Icon name="pin" /> </span> {s.address} </li> {s.phone ? ( <li className="flex gap-3"> <span className="text-accent"> <Icon name="phone" /> </span> {s.phone} </li> ) : null} {s.email ? ( <li className="flex gap-3"> <span className="text-accent"> <Icon name="mail" /> </span> {s.email} </li> ) : null} </ul> </div> <dl className="divide-y divide-line rounded-card border border-line"> {s.rows.map((r) => ( <div key={r.day} className="flex justify-between gap-4 px-6 py-4"> <dt className="font-medium">{r.day}</dt> <dd className="tabular-nums text-muted">{r.time}</dd> </div> ))} </dl> </div> </Band> );} function Cta({ s }: { s: Of<"cta"> }) { return ( <section id={s.id} className="py-16"> <Container> <div className="rounded-card bg-fg px-8 py-14 text-center text-bg sm:px-16"> <h2 className="mx-auto max-w-2xl font-heading text-3xl font-semibold tracking-tight sm:text-4xl">{s.title}</h2> <p className="mx-auto mt-4 max-w-xl text-lg opacity-75">{s.text}</p> <div className="mt-8 flex flex-wrap justify-center gap-3"> <a href={s.primary.href} className="inline-flex h-11 items-center rounded-card bg-accent px-5 font-medium text-accent-fg hover:opacity-90"> {s.primary.label} </a> {s.secondary ? ( <a href={s.secondary.href} className="inline-flex h-11 items-center rounded-card border border-current/30 px-5 font-medium opacity-90 hover:opacity-100"> {s.secondary.label} </a> ) : null} </div> </div> </Container> </section> );} const FIELD: Record<Of<"contact">["fields"][number], { label: string; type: string; autoComplete?: string }> = { name: { label: "Name", type: "text", autoComplete: "name" }, email: { label: "Email", type: "email", autoComplete: "email" }, phone: { label: "Phone", type: "tel", autoComplete: "tel" }, date: { label: "Date", type: "date" }, guests: { label: "Guests", type: "number" }, company: { label: "Company", type: "text", autoComplete: "organization" }, message: { label: "Message", type: "textarea" },}; function Contact({ s }: { s: Of<"contact"> }) { const [sent, setSent] = useState(false); return ( <Band id={s.id ?? "contact"} tinted> <div className="grid gap-12 lg:grid-cols-2"> <Heading eyebrow={s.eyebrow} title={s.title} text={s.text} /> {sent ? ( <div className="rounded-card border border-line bg-bg p-8"> <p className="font-heading text-xl font-semibold">Thank you</p> <p className="mt-2 text-muted">We have your message and will reply soon. (Demo form: connect an email service to receive these.)</p> </div> ) : ( <form className="grid gap-4 rounded-card border border-line bg-bg p-7 sm:grid-cols-2" onSubmit={(e) => { e.preventDefault(); setSent(true); }} > {s.fields.map((f) => { const d = FIELD[f]; const id = `field-${f}`; return ( <label key={f} htmlFor={id} className={`text-sm font-medium ${f === "message" ? "sm:col-span-2" : ""}`}> {d.label} {d.type === "textarea" ? ( <textarea id={id} rows={4} required className="mt-1.5 block w-full rounded-card border border-line bg-bg px-3 py-2.5 text-[15px] outline-none focus:border-accent" /> ) : ( <input id={id} type={d.type} autoComplete={d.autoComplete} required={f === "name" || f === "email"} className="mt-1.5 block h-11 w-full rounded-card border border-line bg-bg px-3 text-[15px] outline-none focus:border-accent" /> )} </label> ); })} <button type="submit" className="h-11 rounded-card bg-accent font-medium text-accent-fg hover:opacity-90 sm:col-span-2"> {s.button} </button> </form> )} </div> </Band> );} export function SectionView({ s }: { s: Section }) { switch (s.kind) { case "hero": return <Hero s={s} />; case "features": return <Features s={s} />; case "split": return <Split s={s} />; case "menu": return <Menu s={s} />; case "pricing": return <Pricing s={s} />; case "gallery": return <Gallery s={s} />; case "projects": return <Projects s={s} />; case "quotes": return <Quotes s={s} />; case "stats": return <Stats s={s} />; case "steps": return <Steps s={s} />; case "team": return <Team s={s} />; case "timeline": return <Timeline s={s} />; case "faq": return <Faq s={s} />; case "products": return <Products s={s} />; case "hours": return <Hours s={s} />; case "cta": return <Cta s={s} />; case "contact": return <Contact s={s} />; }}Remix
Remix this template
Describe what to change and the agent starts from Data scientist's files. The box is already started for you: finish “Start from Data scientist and …”
Dependencies
Documented from the template's package.json. The runtime has a prebuilt dependency set for Vite + React + TypeScript, so installs are fast.
- react^19.1.0
- react-dom^19.1.0
- vite^6.3.0
- @vitejs/plugin-react^4.4.0
- tailwindcss^4.1.0
- @tailwindcss/vite^4.1.0
- typescript^5.8.0
How it runs
- Framework
- Vite + React + TypeScript
- Dev command
- npm run dev
- Build command
- npm run build
- Output directory
- dist
- Port
- 5173
- Entry route
- /
Projects run in an isolated sandbox with a live preview. Read the projects documentation for settings, snapshots and deployment.
Keep browsing
All templates
Brand design studio
Bold dark site for an independent brand and identity studio, with case work, services and team.

Product designer
Case-study portfolio for a product designer, with process, experience timeline and references.

3D & motion designer
High-contrast dark portfolio for a 3D and motion designer, with reels, projects, rates and process.
Build on Data scientist
Create a free account, start from this template, and ask the AI agent to turn it into your app.