/* =========================================================================== VESTIRÉ STUDIO — customer shopping flow -> window Home · Browse · Gown detail · Cart · Checkout · Confirmation Each screen takes a single { ctx } prop (router + cart + toast + tweaks). =========================================================================== */ const { useState, useEffect, useRef, useMemo } = React; /* ---- HOME ---------------------------------------------------------------- */ function CHome({ ctx }) { const featured = VS.gowns.slice(0, 4); const steps = [ { icon: "search", t: "Discover", d: "Browse rental & purchase pieces by occasion, colour and size." }, { icon: "calendar", t: "Reserve", d: "Pick your dates, submit a request - no payment until we confirm." }, { icon: "ruler", t: "Fit", d: "Come in for a fitting; we tailor the details to you." }, { icon: "sparkle", t: "Shine", d: "Collect your gown and make the entrance you imagined." } ]; return (
{/* HERO */}
Couture rental & atelier · est. 2026

Gowns for the
moments that matter.

Rent a showpiece for a single night or make it yours forever. Reserve online, fit in studio, and wear something unforgettable.

ctx.nav("browse")}>Browse the collection ctx.nav("browse", { transaction: "Rental" })}>How rental works
{[["120+", "Gowns in atelier"], ["3 days", "Avg. rental window"], ["4.9", "From 380 clients"]].map(([n, l]) => (
{n} {l}
))}
This week

Étoile Blanche

Rental from {VS.peso(6200)} · 2 left

{/* MARQUEE STRIP */}
{["Wedding", "Debut", "Cocktail", "Evening", "Civil Ceremony"].map((c) => ( {c} ))}
{/* FEATURED */}
The edit

Pieces we're loving now

ctx.nav("browse")}>See all {VS.gowns.length} gowns
{featured.map((g) => )}
{/* ATELIER SPLIT */}
Our atelier

Every gown, fitted by hand.

From Davao's own studio, each piece is steamed, fitted and finished for you before pickup. Rentals include one complimentary alteration; purchases, a full bespoke fitting.

ctx.nav("browse")}>Explore the collection ctx.nav("contact")}>Book a visit
{/* PROCESS */}
How it works

From browse to entrance

{steps.map((s, i) => (
0{i + 1}

{s.t}

{s.d}

))}
{/* QUOTE */}

"I rented the Lumière for our garden vows and it fit like it was made for me. The studio thought of everything."

Maria S. · Wedding, Davao

); } /* ---- GOWN CARD ----------------------------------------------------------- */ function priceLabel(g) { if (g.transaction === "Purchase") return "Buy " + VS.peso(g.priceBuy); if (g.transaction === "Rental") return "Rent " + VS.peso(g.priceRent) + "/" + "set"; return "Rent " + VS.peso(g.priceRent) + " · Buy " + VS.peso(g.priceBuy); } function GownCard({ g, ctx }) { const low = g.stock <= 1; return (
ctx.nav("detail", { id: g.id })}>
{g.name}
{g.transaction === "Both" ? "Rent · Buy" : g.transaction} {low && {g.stock === 0 ? "Sold out" : "1 left"}}
View piece

{g.name}

{g.category} · {g.color} {g.transaction === "Purchase" ? VS.peso(g.priceBuy) : VS.peso(g.priceRent)}
); } /* ---- BROWSE -------------------------------------------------------------- */ function CBrowse({ ctx }) { const init = ctx.route.params || {}; const [cat, setCat] = useState(init.category || "All"); const [trans, setTrans] = useState(init.transaction || "All"); const [q, setQ] = useState(""); const [sort, setSort] = useState("featured"); const list = useMemo(() => { let r = VS.gowns.filter((g) => (cat === "All" || g.category === cat) && (trans === "All" || g.transaction === trans || (trans !== "All" && g.transaction === "Both")) && (q === "" || g.name.toLowerCase().includes(q.toLowerCase()) || g.color.toLowerCase().includes(q.toLowerCase())) ); if (sort === "low") r = [...r].sort((a, b) => (a.priceRent || a.priceBuy) - (b.priceRent || b.priceBuy)); if (sort === "high") r = [...r].sort((a, b) => (b.priceBuy || b.priceRent) - (a.priceBuy || a.priceRent)); return r; }, [cat, trans, q, sort]); return (
The collection

Find your gown

{/* filter bar */}
{["All", ...VS.categories].map((c) => ( ))}
setQ(e.target.value)} />
Offered as {["All", "Rental", "Purchase", "Both"].map((tt) => ( ))} {list.length} piece{list.length !== 1 ? "s" : ""}
{list.length === 0 ? (

No gowns match those filters

{ setCat("All"); setTrans("All"); setQ(""); }}>Clear filters
) : (
{list.map((g) => )}
)}
); } /* ---- GOWN DETAIL --------------------------------------------------------- */ function CDetail({ ctx }) { const g = VS.gownById[ctx.route.params.id]; const offersBoth = g.transaction === "Both"; const [mode, setMode] = useState(g.transaction === "Purchase" ? "purchase" : "rental"); const [size, setSize] = useState(g.sizes[Math.min(1, g.sizes.length - 1)]); const [active, setActive] = useState(0); const [start, setStart] = useState(""); const [days, setDays] = useState(3); const unit = mode === "rental" ? g.priceRent : g.priceBuy; const soldOut = g.stock === 0; function add() { ctx.addToCart({ gownId: g.id, name: g.name, image: g.image, size, lineType: mode, unitPrice: unit, qty: 1, rentalStart: mode === "rental" ? start : null, rentalDays: mode === "rental" ? days : 0 }); ctx.showToast(g.name + " added to your bag", "bag"); } return (
ctx.nav("browse")} style={{ color: "var(--muted)" }}>Collection /{g.name}
{/* gallery */}
{g.name} {g.gallery.length > 1 && (
{g.gallery.map((src, i) => ( setActive(i)} alt="" style={{ width: 74, height: 92, objectFit: "cover", borderRadius: "var(--r-sm)", cursor: "pointer", border: active === i ? "2px solid var(--accent)" : "2px solid transparent", opacity: active === i ? 1 : 0.7 }} /> ))}
)}
{/* info */}
{g.category} {g.occasion} {g.stock <= 1 && {soldOut ? "Sold out" : "Only 1 left"}}

{g.name}

{g.desc}

{/* mode toggle */} {offersBoth && (
How would you like it?
{[["rental", "Rent", g.priceRent, "/event"], ["purchase", "Purchase", g.priceBuy, ""]].map(([m, lbl, pr, suf]) => ( ))}
)} {/* price (single-mode) */} {!offersBoth && (
{VS.peso(unit)} {mode === "rental" ? "per event · " + "3-day window" : "to own"}
)} {/* size */}
{["XS", "S", "M", "L", "XL"].map((s) => ( ))}
{/* rental dates */} {mode === "rental" && (
setStart(e.target.value)} />
)}
{soldOut ? "Sold out" : "Add to bag"} Save
{/* details */}
{[["Colour", g.color], ["Available sizes", g.sizes.join(" · ")], ["Occasion", g.occasion], ["In atelier", g.stock + " piece" + (g.stock !== 1 ? "s" : "")], ["Includes", mode === "rental" ? "1 fitting · steaming · garment bag" : "Bespoke fitting · garment bag"]].map(([k, v], i) => (
{k} {v}
))}
{/* related */}

You may also love

ctx.nav("browse")}>All gowns
{VS.gowns.filter((x) => x.id !== g.id && x.category === g.category).concat(VS.gowns.filter((x) => x.id !== g.id && x.category !== g.category)).slice(0, 4).map((x) => )}
); } Object.assign(window, { CHome, CBrowse, CDetail, GownCard, priceLabel });