/* "Build a site" — a fully interactive, simulated Horizons flow, end to end: chat + live preview → "ready to build in code?" → grant source access → Horizons builds the code → connect a Hostinger domain → live countdown. All inside Horizons / Hostinger (no external tools). */ const HZDS = window.HostingerHPanelDesignSystem_3ef285; const { Button: HzBtn, Card: HzCard, SpotlightPanel: HzSpot, Badge: HzBadge, Icon: HzIcon, Input: HzInput } = HZDS; const BakerySiteCmp = window.BakerySite; /* ───────────────── small shared chrome ───────────────── */ function HzMark({ size = 26, label, labelColor = "#fff" }) { return ( {label && {label}} ); } function HzBrowserChrome({ url, children, live, scrollable }) { return (
{["#E2484D", "#E29400", "#18A971"].map((c) => ())}
{live ? : } {url}
{live && Live}
{children}
); } /* ───────────────── chat panel ───────────────── */ const SEED_MSGS = [ { who: "ai", text: "Hi — I'm Horizons. Tell me what you'd like to build and I'll design it for you, live." }, { who: "me", text: "A warm, modern site for my bakery, Sweet Crumb. I want a hero, our signature bakes, the story, and a way to order ahead." }, { who: "ai", text: "Love it. I've designed a homepage for Sweet Crumb — warm terracotta and cream colours, an elegant heading, your three signature bakes, a short story, and an order section. It's on the right. Want to change anything?" }, ]; const AI_REPLIES = [ "Done — I refreshed the preview on the right with that change.", "Updated the layout. Take a look at the preview.", "Nice idea. I adjusted the design — check the right side.", ]; function ChatPanel({ onRegenerate }) { const [msgs, setMsgs] = React.useState(SEED_MSGS); const [draft, setDraft] = React.useState(""); const [thinking, setThinking] = React.useState(false); const scrollRef = React.useRef(null); const replyIx = React.useRef(0); React.useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [msgs, thinking]); function send() { const t = draft.trim(); if (!t || thinking) return; setMsgs((m) => [...m, { who: "me", text: t }]); setDraft(""); setThinking(true); onRegenerate && onRegenerate(); setTimeout(() => { setThinking(false); setMsgs((m) => [...m, { who: "ai", text: AI_REPLIES[replyIx.current++ % AI_REPLIES.length] }]); }, 1300); } return (
Designing
{msgs.map((m, i) => ( m.who === "ai" ? (
{m.text}
) : (
{m.text}
) ))} {thinking && (
{[0, 1, 2].map((d) => ())}
)}
setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") send(); }} placeholder="Ask Horizons to change anything…" style={{ flex: 1, border: "none", outline: "none", background: "transparent", fontFamily: "var(--font-sans)", fontSize: "var(--fs-sm)", color: "var(--text-body)" }} />
); } /* ───────────────── overlay shell ───────────────── */ function HzOverlay({ children, onClose, max = 520 }) { return (
e.stopPropagation()} style={{ width: "100%", maxWidth: max, background: "var(--neutral-0)", borderRadius: "var(--radius-lg)", boxShadow: "0 24px 70px rgba(14,14,17,.34)", overflow: "hidden", animation: "hzpop .18s ease" }}> {children}
); } /* ───────────────── ready-to-build modal ───────────────── */ function ReadyModal({ onYes, onClose }) { return (

Happy with your design?

When you're ready, Horizons turns this design into a real website and puts it online for you. No tech skills needed — we handle the rest.

Yes, build my website Keep editing
); } /* ───────────────── connect-source modal ───────────────── */ function HostingerTile({ size = 26 }) { return ; } function GitHubTile({ size = 26 }) { return ( ); } function FolderTile({ size = 26 }) { return ; } const SOURCES = [ { id: "hostinger", title: "Keep it on Hostinger", body: "The simplest choice — we host and look after everything for you.", Tile: HostingerTile, recommended: true }, { id: "local", title: "Download to my computer", body: "Save a copy of your website's files to your computer.", Tile: FolderTile }, { id: "github", title: "Save to GitHub", body: "For developers — keep a copy of the code on GitHub.", Tile: GitHubTile }, ]; function ConnectModal({ onBuild, onClose }) { const [sel, setSel] = React.useState("hostinger"); const [connecting, setConnecting] = React.useState(false); function grant() { setConnecting(true); setTimeout(() => onBuild(SOURCES.find((s) => s.id === sel)), 1100); } return (

Where should we keep your website?

Choose where your website should live. Not sure? Keep it on Hostinger — it's the easiest.

{SOURCES.map((s) => { const active = sel === s.id; return ( ); })}
{connecting ? "Setting up…" : "Save here & continue"} {!connecting && Back}
); } /* ───────────────── preview popup ───────────────── */ function PreviewModal({ onBuild, onClose }) { return (
{/* guidance banner */}
This is a preview of your website
Happy with it? Click Build my website to put it online — or keep editing to change anything.
Keep editing Build my website
{/* the site */}
); } /* ───────────────── building stage ───────────────── */ const BUILD_STEPS = [ "Looking at your design", "Building your website", "Putting your pages together", "Getting your photos ready", "Putting it online", ]; const BUILD_FILES = ["your homepage", "the menu section", "your story", "your photos", "the order button", "the final touches"]; function BuildingStage({ source, onDone }) { const [pct, setPct] = React.useState(0); const [files, setFiles] = React.useState([]); React.useEffect(() => { const t = setInterval(() => setPct((p) => Math.min(100, p + 2)), 90); return () => clearInterval(t); }, []); React.useEffect(() => { const shown = Math.floor((pct / 100) * BUILD_FILES.length); setFiles(BUILD_FILES.slice(0, shown)); if (pct >= 100) { const t = setTimeout(onDone, 850); return () => clearTimeout(t); } }, [pct]); const activeStep = Math.min(BUILD_STEPS.length - 1, Math.floor((pct / 100) * BUILD_STEPS.length)); return (
Building your website
{source.title}
{pct}%
{/* progress bar */}
{/* steps */}
{BUILD_STEPS.map((s, i) => { const done = i < activeStep || pct >= 100; const now = i === activeStep && pct < 100; return (
{done ? : now ? : null} {s}
); })}
{/* file log */}
{files.map((f, i) => (
Added {f}
))} {pct < 100 &&
Working on it…
}
); } /* ───────────────── domain stage ───────────────── */ const OWNED = [ { name: "sweetcrumb.com", note: "You own this · not used yet", pick: true }, { name: "adalovelace.dev", note: "Already used by another site" }, { name: "ada.shop", note: "You own this · not used yet" }, ]; const AVAILABLE = [ { name: "sweetcrumb.bakery", price: "$24.99/yr" }, { name: "sweetcrumb.shop", price: "$2.99/yr" }, { name: "getsweetcrumb.com", price: "$9.99/yr" }, ]; function DomainStage({ onConnect }) { const [mode, setMode] = React.useState("have"); // have | need const [ownedPick, setOwnedPick] = React.useState("sweetcrumb.com"); const [needPick, setNeedPick] = React.useState("sweetcrumb.bakery"); const chosen = mode === "have" ? ownedPick : needPick; return (
Your website is built and ready

Give your website an address

Your address is what people type to visit you, like sweetcrumb.com. Do you already have one, or need a new one?

{/* segmented */}
{[["have", "I have one"], ["need", "I need a new one"]].map(([v, l]) => ( ))}
{/* list */}
{mode === "have" ? OWNED.map((d) => { const active = ownedPick === d.name; return ( ); }) : ( <>
Addresses you can get for "sweetcrumb"
{AVAILABLE.map((d) => { const active = needPick === d.name; return ( ); })} )}
onConnect(chosen, mode)}> {mode === "have" ? `Use ${chosen}` : `Get ${chosen}`}
); } function rowStyle(active) { return { display: "flex", alignItems: "center", gap: 12, padding: "12px 14px", cursor: "pointer", borderRadius: "var(--radius-md)", background: active ? "var(--brand-soft-bg)" : "var(--neutral-0)", border: active ? "1.5px solid var(--brand)" : "1.5px solid var(--border-default)", transition: "all var(--dur-fast)", }; } function radioDot(active) { return { flex: "none", width: 18, height: 18, borderRadius: "50%", border: active ? "5px solid var(--brand)" : "2px solid var(--border-default)", transition: "all var(--dur-fast)" }; } /* ───────────────── live countdown stage ───────────────── */ function LiveStage({ domain, onReset }) { const TOTAL = 90; const [left, setLeft] = React.useState(TOTAL); const live = left <= 0; React.useEffect(() => { if (live) return; const t = setInterval(() => setLeft((s) => Math.max(0, s - 1)), 1000); return () => clearInterval(t); }, [live]); const mm = String(Math.floor(left / 60)).padStart(1, "0"); const ss = String(left % 60).padStart(2, "0"); const frac = (TOTAL - left) / TOTAL; const R = 54, C = 2 * Math.PI * R; return (
{!live ? ( <>
{mm}:{ss} Publishing

We're putting your website online

Sweet Crumb will be ready in a moment. Pop back soon and see it live at:

{domain}

You can close this window — we'll email you the moment it's ready.

) : ( <>

Your website is live!

Sweet Crumb is now online for everyone to see, safe and secure.

Visit {domain} Build another website
)}
); } /* ════════════════════ ORCHESTRATOR ════════════════════ */ function BuildScreen() { const [phase, setPhase] = React.useState("builder"); // builder | building | domain | live const [modal, setModal] = React.useState(null); // null | ready | connect const [source, setSource] = React.useState(null); const [domain, setDomain] = React.useState("sweetcrumb.com"); const [regen, setRegen] = React.useState(false); const regenTimer = React.useRef(null); function pingPreview() { setRegen(true); clearTimeout(regenTimer.current); regenTimer.current = setTimeout(() => setRegen(false), 1400); } function reset() { setPhase("builder"); setModal(null); setSource(null); } return (
{/* builder top bar */}

Sweet Crumb

Saved automatically
setModal("preview")}>Preview setModal("ready")}>Build my website
{/* builder body: chat + preview */}
{regen && (
Updating design…
)}
{/* overlays */} {modal === "preview" && setModal(null)} onBuild={() => setModal("ready")} />} {modal === "ready" && setModal(null)} onYes={() => setModal("connect")} />} {modal === "connect" && setModal("ready")} onBuild={(src) => { setSource(src); setModal(null); setPhase("building"); }} />} {phase === "building" && source && setPhase("domain")} />} {phase === "domain" && { setDomain(d); setPhase("live"); }} />} {phase === "live" && }
); } window.BuildScreen = BuildScreen;