Docs/Extra
// extra
Componenti bonus
Oltre ai componenti della landing standard, nel repo c'è un secondo strato di roba: illustrazioni disegnate a mano, una sezione founder, un terminale animato che nessuna pagina usa ancora, e tre cartelle vuote che servono a provare varianti senza rompere la home. Nessuno di questi pezzi è obbligatorio — sono lì perché ti servano il giorno in cui la landing di default non ti basta più.
1. FounderNote
components/FounderNote.tsx è la sezione “due parole dal founder”: ritratto disegnato a sinistra, lettera in serif a destra, tre numeri sotto. È già montata in app/page.tsx tra <FileExplorer /> e <Pricing />, con id="proof" così puoi puntarci un link.
Tutto il contenuto sta in cima al file, in due array. I numeri:
const stats = [
{ value: "1h30", label: "build più veloce con questo repo — in diretta davanti alla telecamera" },
{ value: "200k", label: "persone hanno visto quella build sui social" },
{ value: "4 giorni", label: "dall'idea al primo ricavo, più di una volta" },
];
// Add real quotes from early buyers here — the section stays hidden while empty.
const testimonials: { quote: string; name: string; role: string }[] = [];Il dettaglio furbo è testimonials: la griglia delle citazioni è avvolta in un testimonials.length > 0, quindi finché l'array è vuoto la sezione semplicemente non esiste. Il giorno che hai le prime recensioni vere le aggiungi lì e compaiono da sole — niente placeholder finti in produzione.
Il volto è <FounderFace /> da components/drawn: è un disegno generico, sostituiscilo con una tua foto dentro un <Image /> quando ne hai voglia.
2. HeroTerminal e TerminalWindow
Questi due non sono importati da nessuna pagina: sono un visual alternativo per l'hero, pronto ma in panchina. La home oggi usa <FolderOrbit />.
components/TerminalWindow.tsx è il guscio: un server component senza logica, con la barra dei tre pallini e il titolo. Prende tre prop:
const TerminalWindow = ({
title = "zsh — zerotoship",
children,
className = "",
}: {
title?: string;
children: ReactNode;
className?: string;
}) => { /* ... */ };components/HeroTerminal.tsx è il contenuto animato, ed è un client component perché usa useState e useEffect. Scrive a macchina il comando git clone zerotoship, poi fa comparire una riga alla volta i cinque moduli, poi la riga finale. Il contenuto è tutto in due costanti in cima:
const COMMAND = "git clone zerotoship";
const LINES = [
{ label: "auth", note: "(Supabase + Google login)" },
{ label: "payments", note: "(Stripe checkout + webhooks)" },
{ label: "database", note: "(Supabase, ready to go)" },
{ label: "emails", note: "(Resend)" },
{ label: "SEO", note: "(meta tags + sitemap + blog)" },
] as const;Due cose fatte bene che vale la pena non rompere quando ci metti mano: controlla prefers-reduced-motion e, se l'utente ha chiesto meno animazioni, salta direttamente allo stato finale; e la cleanup dello useEffect cancella tutti i setTimeout, quindi non ti restano timer appesi quando cambi pagina.
Un avvertimento onesto: TerminalWindow è stilizzato con le classi DaisyUI (bg-base-200, text-primary, border-base-content/10) e non con i token del tema brutalist (--cream, --ink, .card-brutal) che usa il resto della landing. Se lo infili nella home così com'è, stona. Cambia le classi prima.
3. components/drawn/
Dieci illustrazioni SVG scritte a mano come componenti React, più un index.ts che fa da barrel: le importi tutte da @/components/drawn. Nessuna dipendenza esterna, nessun file immagine, niente Figma.
import { DrawnArrow, Squiggle, WobbleBorder } from "@/components/drawn";
<WobbleBorder className="p-8">
<h2>Contenuto dentro un bordo tremolante</h2>
</WobbleBorder>
<Squiggle className="w-40 h-3" color="var(--brick)" />
<DrawnArrow direction="down-right" className="w-14 h-20" />Si dividono in due famiglie. Le primitive, che prendono className, color e strokeWidth e usano currentColor di default, quindi ereditano l'inchiostro dal genitore:
Squiggle— la linea ondulata da mettere sotto una headline. Per la sottolineatura inline conviene la utility.squigglediglobals.css: questo componente serve quando la vuoi isolata.DrawnArrow— freccia storta. La propdirectionaccettadown,up,left,right,down-left,down-righte ruota l'SVG di conseguenza.FloppyIcon— floppy disk in linea, buono per un microcopy nostalgico.FounderFace— il volto usato daFounderNote.TiredMaker— il maker esausto, pensato come centro della sezione Problem.
E i contenitori, che prendono dei figli o dei dati e costruiscono una scena. Questi usano direttamente i token del tema (--ink, --cream, --kraft, --mustard, --brick):
WobbleBorder— avvolge i figli in un bordo disegnato. Propstroke(defaultvar(--ink)) estrokeWidth. Padding e sfondo li gestisci tu conclassName.MacFrame— un Macintosh vintage. I figli finiscono dentro lo “schermo”, posizionato in percentuale sopra l'SVG.CardboardBox— scatola di cartone aperta con delle etichette che spuntano fuori. Vuoletags: { icon, label, color? }[], fino a otto, più untoLine(default"your saas").AllInOne— cerchio storto che racchiude sei etichette, con unacaption(default"one repo").FolderOrbit— cartella al centro e otto etichette che le orbitano intorno con ritmi diversi. È quello che vedi nell'hero:components/Hero.tsxgli passa l'arrayroutes.
Nei tre contenitori a etichette (CardboardBox, AllInOne, FolderOrbit) ogni tag accetta un color tra cream, mustard e brick; il default è cream. Le posizioni sono array fissi dentro ogni file: se ti serve una disposizione diversa, quello è il punto da toccare.
import { FolderOrbit } from "@/components/drawn";
const routes = [
{ icon: "🔐", label: "/auth" },
{ icon: "💳", label: "/payments", color: "mustard" as const },
{ icon: "🗄️", label: "/database" },
{ icon: "🤖", label: ".cursorrules", color: "brick" as const },
];
<FolderOrbit routes={routes} />4. components/heroes/ e components/problem-variants/
Occhio, perché su questo la documentazione di solito mente: queste due cartelle esistono ma sono vuote. Non contengono nessun file, e siccome git non traccia le cartelle vuote potresti non vederle nemmeno dopo un clone pulito.
Sono uno slot di lavoro, non una libreria. L'idea è: quando vuoi provare un hero diverso non riscrivi components/Hero.tsx — ne fai una copia lì dentro, la monti in app/page.tsx al posto dell'originale, e se non ti convince cancelli il file invece di fare git revert. Stessa cosa per components/Problem.tsx e problem-variants/.
cp components/Hero.tsx components/heroes/HeroTerminale.tsx
# poi sostituisci <FolderOrbit /> con <HeroTerminal /> dentro la copia// import Hero from "@/components/Hero";
import Hero from "@/components/heroes/HeroTerminale";Se una variante vince, spostala su components/Hero.tsx e svuota la cartella. Non lasciarci dentro sei versioni: fra un mese non ricordi quale è quella buona.
5. La route /preview/heroes
Anche app/preview/heroes/ è una cartella vuota: senza un page.tsx dentro, la rotta /preview/heroes oggi risponde 404. È il posto previsto per una pagina di confronto — tutte le varianti impilate una sotto l'altra, così le guardi affiancate invece di ricaricare la home ogni volta.
import Hero from "@/components/Hero";
import HeroTerminale from "@/components/heroes/HeroTerminale";
const VARIANTI = [
{ nome: "A — FolderOrbit (attuale)", Component: Hero },
{ nome: "B — Terminale animato", Component: HeroTerminale },
];
export default function PreviewHeroes() {
return (
<main style={{ background: "var(--cream)" }}>
{VARIANTI.map(({ nome, Component }) => (
<section key={nome}>
<p className="font-mono text-sm px-6 py-3" style={{ color: "var(--brick)" }}>
{"//"} {nome}
</p>
<Component />
<div className="divider-drawn my-6" />
</section>
))}
</main>
);
}Due note prima di dimenticartela online. Il middleware.ts intercetta anche questa rotta — rinfresca solo la sessione Supabase, non la blocca — quindi la pagina resta pubblica: chiunque indovini l'URL la vede. E finisce nel sitemap come qualsiasi altra pagina, a meno che tu non la aggiunga all'array exclude di next-sitemap.config.js. La soluzione più pulita è cancellare la cartella prima del deploy.