/* =========================================================================== VESTIRÉ STUDIO — design system + shared UI primitives -> window Editorial-luxury refresh. Themes/type switch via data-attrs on the app root. =========================================================================== */ const { useState, useEffect, useRef, useMemo } = React; /* ---- STYLESHEET ---------------------------------------------------------- */ const VS_CSS = ` :root{ --font-display:'Playfair Display', Georgia, serif; --font-sans:'Outfit', system-ui, sans-serif; --r-xs:6px; --r-sm:10px; --r-md:16px; --r-lg:24px; --r-xl:34px; --shadow-sm:0 1px 2px rgba(40,30,18,.05), 0 2px 8px rgba(40,30,18,.05); --shadow-md:0 6px 18px rgba(40,30,18,.07), 0 18px 44px rgba(40,30,18,.07); --shadow-lg:0 20px 60px rgba(40,30,18,.16); --ease:cubic-bezier(.2,.7,.2,1); } /* THEME - Atelier Ivory (default) */ [data-theme="atelier"]{ --paper:#eef1ea; --paper-2:#e3e8dd; --surface:#fbfdf9; --surface-2:#eef2ea; --ink:#1e241b; --ink-2:#46503f; --muted:#7e8a76; --line:#d7ded0; --line-2:#e6ebe0; --accent:#6f8a63; --accent-ink:#fff; --accent-soft:#e0e8d8; --accent-2:#4f6a47; --pending:#9a7d2e; --pending-bg:#f1ecd6; --processing:#3f6f8f; --processing-bg:#e1ecf2; --completed:#4f7a52; --completed-bg:#e2eedd; --cancelled:#a8504a; --cancelled-bg:#f2ded9; } /* THEME - Editorial Noir */ [data-theme="noir"]{ --paper:#15120d; --paper-2:#1d1912; --surface:#211c14; --surface-2:#2a241a; --ink:#f4ecdd; --ink-2:#d3c8b4; --muted:#9a8e78; --line:#332c20; --line-2:#3d3527; --accent:#c8a360; --accent-ink:#1a1610; --accent-soft:#2c2518; --accent-2:#e0bd76; --pending:#d6a747; --pending-bg:#2e2614; --processing:#6fa6c8; --processing-bg:#162630; --completed:#7fb182; --completed-bg:#16271a; --cancelled:#d98079; --cancelled-bg:#2e1715; } /* THEME - Blush Couture */ [data-theme="blush"]{ --paper:#f4e7e3; --paper-2:#eed9d4; --surface:#fffafa; --surface-2:#faedeb; --ink:#3a2530; --ink-2:#664a56; --muted:#a07f88; --line:#ecd4cf; --line-2:#f3e2de; --accent:#b06a73; --accent-ink:#fff; --accent-soft:#f0dcdb; --accent-2:#8c4b56; --pending:#bb7a36; --pending-bg:#f6e6d4; --processing:#6f7fa8; --processing-bg:#e6e8f2; --completed:#5f8a63; --completed-bg:#e3efe1; --cancelled:#b0565a; --cancelled-bg:#f4dde0; } *{box-sizing:border-box;} .vs-root{font-family:var(--font-sans); color:var(--ink); background:var(--paper); -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;} .vs-root ::selection{background:var(--accent); color:var(--accent-ink);} h1,h2,h3,h4{margin:0; font-weight:600; line-height:1.05;} p{margin:0;} a{color:inherit; text-decoration:none;} button{font-family:inherit;} img{display:block; max-width:100%;} .serif{font-family:var(--font-display);} .disp{font-family:var(--font-display); font-weight:500; letter-spacing:.005em; font-variant-numeric:lining-nums;} /* All numerals: consistent lining figures app-wide; prices/tables also tabular */ .vs-root{font-variant-numeric:lining-nums;} .price{font-family:var(--font-display); font-weight:600; font-variant-numeric:tabular-nums lining-nums; letter-spacing:0; line-height:1.1;} .vtable td,.vtable th{font-variant-numeric:tabular-nums lining-nums;} .eyebrow{font-size:11px; letter-spacing:.28em; text-transform:uppercase; color:var(--accent-2); font-weight:600;} .kicker{font-size:12px; letter-spacing:.22em; text-transform:uppercase; color:var(--muted); font-weight:600;} .muted{color:var(--muted);} .center{text-align:center;} /* layout helpers */ .row{display:flex; align-items:center;} .col{display:flex; flex-direction:column;} .gap2{gap:2px;}.gap3{gap:3px;}.gap4{gap:4px;}.gap5{gap:5px;}.gap6{gap:6px;}.gap8{gap:8px;}.gap10{gap:10px;}.gap12{gap:12px;}.gap14{gap:14px;}.gap16{gap:16px;}.gap18{gap:18px;}.gap20{gap:20px;}.gap22{gap:22px;}.gap24{gap:24px;}.gap26{gap:26px;}.gap28{gap:28px;}.gap30{gap:30px;}.gap32{gap:32px;} .wrap{flex-wrap:wrap;} .between{justify-content:space-between;} .acenter{align-items:center;} .aend{align-items:flex-end;} .grow{flex:1;} .container{max-width:1240px; margin:0 auto; padding:0 28px;} .divider{height:1px; background:var(--line);} /* BUTTONS */ .vbtn{display:inline-flex; align-items:center; justify-content:center; gap:9px; font-family:var(--font-sans); font-weight:600; font-size:13px; letter-spacing:.04em; padding:12px 22px; border-radius:50px; border:1px solid transparent; cursor:pointer; transition:transform .18s var(--ease), background .2s, color .2s, border-color .2s, box-shadow .2s; white-space:nowrap;} .vbtn svg{width:16px; height:16px;} .vbtn:active{transform:translateY(1px) scale(.99);} .vbtn--primary{background:var(--ink); color:var(--paper);} .vbtn--primary:hover{background:var(--accent-2); color:#fff;} .vbtn--accent{background:var(--accent); color:var(--accent-ink);} .vbtn--accent:hover{filter:brightness(1.06); box-shadow:0 8px 22px -8px var(--accent);} .vbtn--ghost{background:transparent; color:var(--ink); border-color:var(--line);} .vbtn--ghost:hover{border-color:var(--ink); background:var(--surface);} .vbtn--soft{background:var(--surface-2); color:var(--ink); border-color:var(--line);} .vbtn--soft:hover{background:var(--accent-soft); border-color:var(--accent-soft);} .vbtn--danger{background:transparent; color:var(--cancelled); border-color:color-mix(in srgb,var(--cancelled) 35%, transparent);} .vbtn--danger:hover{background:var(--cancelled-bg);} .vbtn--sm{padding:8px 15px; font-size:12px;} .vbtn--lg{padding:15px 30px; font-size:14px;} .vbtn--block{width:100%;} .vbtn[disabled]{opacity:.45; cursor:not-allowed;} /* link chip */ .lnk{display:inline-flex; align-items:center; gap:7px; font-weight:600; font-size:13px; color:var(--accent-2); cursor:pointer; letter-spacing:.02em;} .lnk svg{width:15px;height:15px; transition:transform .2s var(--ease);} .lnk:hover svg{transform:translateX(3px);} /* CARD / surfaces */ .card{background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); box-shadow:var(--shadow-sm);} .pad14{padding:14px;}.pad16{padding:16px;}.pad18{padding:18px;}.pad20{padding:20px;}.pad22{padding:22px;}.pad24{padding:24px;}.pad26{padding:26px;}.pad28{padding:28px;}.pad30{padding:30px;}.pad32{padding:32px;} /* PILL / badge */ .pill{display:inline-flex; align-items:center; gap:7px; font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; padding:5px 11px 5px 9px; border-radius:50px; line-height:1;} .pill .dot{width:7px; height:7px; border-radius:50%; background:currentColor;} .pill--pending{color:var(--pending); background:var(--pending-bg);} .pill--processing{color:var(--processing); background:var(--processing-bg);} .pill--completed{color:var(--completed); background:var(--completed-bg);} .pill--cancelled{color:var(--cancelled); background:var(--cancelled-bg);} .tag{display:inline-flex; align-items:center; gap:6px; font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--accent-2); background:var(--accent-soft); padding:4px 10px; border-radius:50px;} .tag--line{background:transparent; border:1px solid var(--line); color:var(--muted);} /* FORM */ .field{display:flex; flex-direction:column; gap:7px;} .label{font-size:11px; letter-spacing:.14em; text-transform:uppercase; font-weight:700; color:var(--muted);} .input,.select,.textarea{font-family:var(--font-sans); font-size:14px; color:var(--ink); background:var(--surface); border:1px solid var(--line); border-radius:var(--r-sm); padding:12px 14px; outline:none; transition:border-color .2s, box-shadow .2s; width:100%;} .input:focus,.select:focus,.textarea:focus{border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft);} .textarea{resize:vertical; min-height:96px; line-height:1.5;} .input::placeholder,.textarea::placeholder{color:color-mix(in srgb,var(--muted) 80%, transparent);} /* chip selectors */ .chip{font-size:12px; font-weight:600; letter-spacing:.03em; padding:9px 16px; border-radius:50px; border:1px solid var(--line); background:var(--surface); color:var(--ink-2); cursor:pointer; transition:.18s var(--ease);} .chip:hover{border-color:var(--accent); color:var(--ink);} .chip--on{background:var(--ink); color:var(--paper); border-color:var(--ink);} .sizechip{min-width:42px; height:42px; display:inline-flex; align-items:center; justify-content:center; border:1px solid var(--line); border-radius:var(--r-sm); font-weight:600; font-size:13px; cursor:pointer; background:var(--surface); transition:.15s;} .sizechip--on{background:var(--ink); color:var(--paper); border-color:var(--ink);} .sizechip[disabled]{opacity:.3; cursor:not-allowed; text-decoration:line-through;} /* STEPPER (status) */ .stepper{display:flex; align-items:center; width:100%;} .stepper .node{display:flex; flex-direction:column; align-items:center; gap:8px; position:relative;} .stepper .dot{width:30px; height:30px; border-radius:50%; display:flex; align-items:center; justify-content:center; border:2px solid var(--line); background:var(--surface); color:var(--muted); flex:none; transition:.25s;} .stepper .dot svg{width:15px; height:15px;} .stepper .seg{height:2px; flex:1; background:var(--line); margin:0 -2px; align-self:flex-start; margin-top:14px;} .stepper .lbl{font-size:11px; letter-spacing:.1em; text-transform:uppercase; font-weight:700; color:var(--muted);} .stepper .node--done .dot{background:var(--accent); border-color:var(--accent); color:var(--accent-ink);} .stepper .node--done .lbl{color:var(--ink);} .stepper .node--now .dot{border-color:var(--accent); color:var(--accent); box-shadow:0 0 0 4px var(--accent-soft);} .stepper .node--now .lbl{color:var(--ink);} .stepper .seg--done{background:var(--accent);} .stepper--cancelled .dot{border-color:var(--cancelled) !important; color:var(--cancelled) !important; background:var(--cancelled-bg) !important;} /* TIMELINE (status) */ .timeline{display:flex; flex-direction:column;} .tl-item{display:flex; gap:14px; padding-bottom:18px; position:relative;} .tl-item:last-child{padding-bottom:0;} .tl-rail{display:flex; flex-direction:column; align-items:center; flex:none;} .tl-bead{width:14px; height:14px; border-radius:50%; border:2px solid var(--line); background:var(--surface); flex:none; margin-top:2px;} .tl-line{width:2px; flex:1; background:var(--line); margin:3px 0;} .tl-item--done .tl-bead{background:var(--accent); border-color:var(--accent);} .tl-item--done .tl-line{background:var(--accent);} .tl-item--now .tl-bead{border-color:var(--accent); box-shadow:0 0 0 4px var(--accent-soft);} .tl-item--cancel .tl-bead{background:var(--cancelled); border-color:var(--cancelled);} /* progress bar */ .prog{height:6px; border-radius:50px; background:var(--line-2); overflow:hidden;} .prog > i{display:block; height:100%; border-radius:50px; background:var(--accent); transition:width .5s var(--ease);} /* TABLE */ .vtable{width:100%; border-collapse:separate; border-spacing:0;} .vtable th{font-size:11px; letter-spacing:.12em; text-transform:uppercase; font-weight:700; color:var(--muted); text-align:left; padding:13px 16px; border-bottom:1px solid var(--line); white-space:nowrap;} .vtable td{padding:14px 16px; border-bottom:1px solid var(--line-2); font-size:14px; vertical-align:middle;} .vtable tbody tr{transition:background .15s;} .vtable tbody tr:hover{background:var(--surface-2);} .vtable tr:last-child td{border-bottom:none;} /* thumbs */ .thumb{border-radius:var(--r-sm); object-fit:cover; background:var(--surface-2); flex:none;} .avatar{width:38px; height:38px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:var(--accent-soft); color:var(--accent-2); font-weight:700; font-size:13px; flex:none; letter-spacing:.02em;} /* scrollbars */ .vs-root *::-webkit-scrollbar{width:10px; height:10px;} .vs-root *::-webkit-scrollbar-thumb{background:var(--line); border-radius:50px; border:3px solid transparent; background-clip:padding-box;} .vs-root *::-webkit-scrollbar-thumb:hover{background:var(--muted);} /* animations - entrance only; resting state is always visible (safe for print / reduced-motion) */ @keyframes vsUp{from{opacity:0; transform:translateY(14px);} to{opacity:1; transform:none;}} @keyframes vsFade{from{opacity:0;} to{opacity:1;}} @keyframes vsPop{from{opacity:0; transform:scale(.96) translateY(8px);} to{opacity:1; transform:none;}} .anim-up,.anim-fade,.anim-pop{opacity:1;} @media (prefers-reduced-motion: no-preference){ .anim-up{animation:vsUp .5s var(--ease) both;} .anim-fade{animation:vsFade .4s ease both;} .anim-pop{animation:vsPop .35s var(--ease) both;} } /* toast */ .toast{position:fixed; left:50%; bottom:30px; transform:translateX(-50%); z-index:9000; background:var(--ink); color:var(--paper); padding:13px 22px; border-radius:50px; box-shadow:var(--shadow-lg); display:flex; align-items:center; gap:10px; font-size:13px; font-weight:600; animation:vsPop .3s var(--ease) both;} .toast svg{width:17px; height:17px; color:var(--accent);} /* utilities */ .hide-sb::-webkit-scrollbar{display:none;} .clamp2{display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;} /* ---- SALES REPORT (preview + print) ------------------------------------- */ .report-overlay{position:fixed; inset:0; z-index:10000; display:flex; flex-direction:column; background:rgba(30,36,27,.55); -webkit-backdrop-filter:blur(5px); backdrop-filter:blur(5px); animation:vsFade .2s ease both;} .report-bar{display:flex; align-items:center; justify-content:space-between; gap:12px; padding:12px 18px; background:var(--surface); border-bottom:1px solid var(--line); flex:none;} .report-scroll{flex:1; overflow:auto; padding:24px; display:flex; justify-content:center;} .print-doc{background:#fff; color:#1e241b; width:100%; max-width:1040px; padding:38px 42px; box-shadow:0 24px 70px rgba(0,0,0,.3); border-radius:6px; font-family:var(--font-sans); align-self:flex-start;} .report-head{margin-bottom:6px;} .report-mark{width:48px; height:48px; border-radius:50%; border:1.5px solid #a9824a; color:#a9824a; display:flex; align-items:center; justify-content:center; font-family:var(--font-display); font-size:26px; font-weight:600; flex:none;} .report-rule{height:2px; background:#a9824a; opacity:.5; margin:16px 0;} .report-head .rl{font-size:10px; letter-spacing:.12em; text-transform:uppercase; color:#8a7a5e; font-weight:700;} .report-head .rv{font-size:15px; font-weight:600; margin-top:3px;} .report-table{width:100%; border-collapse:collapse; margin-top:20px; font-size:11px;} .report-table th{text-align:left; background:#f5f1e8; color:#6b5d44; text-transform:uppercase; font-size:9px; letter-spacing:.05em; padding:8px 9px; border-bottom:1.5px solid #e3d9c8;} .report-table td{padding:7px 9px; border-bottom:1px solid #efefef; vertical-align:top;} .report-table tfoot td{border-top:1.5px solid #d7ded0; border-bottom:none; font-size:12px; padding-top:10px;} .report-status{font-size:9px; font-weight:700; text-transform:uppercase; padding:2px 8px; border-radius:50px; white-space:nowrap;} .rs-pending{background:#f4eed8; color:#9a7d2e;} .rs-processing{background:#e1ecf2; color:#3f6f8f;} .rs-completed{background:#e2eedd; color:#4f7a52;} .rs-cancelled{background:#f3ded9; color:#a8504a;} .report-foot{margin-top:24px; padding-top:12px; border-top:1px solid #eee; display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap; font-size:9px; color:#999;} @media print { @page { size: A4 landscape; margin: 12mm; } body * { visibility: hidden !important; } .report-overlay, .report-overlay * { visibility: visible !important; } .report-overlay{ position:static !important; background:#fff !important; -webkit-backdrop-filter:none !important; backdrop-filter:none !important; } .report-bar{ display:none !important; } .report-scroll{ overflow:visible !important; padding:0 !important; display:block !important; } .print-doc{ box-shadow:none !important; max-width:none !important; width:100% !important; padding:0 !important; border-radius:0 !important; } .twk-panel{ display:none !important; } } `; function injectVSStyles() { if (document.getElementById("vs-css")) return; const s = document.createElement("style"); s.id = "vs-css"; s.textContent = VS_CSS; document.head.appendChild(s); } /* ---- ICONS (thin-line) --------------------------------------------------- */ const PATHS = { cart: '', user: '', search: '', heart: '', check: '', clock: '', x: '', arrow: '', arrowL: '', chevD: '', chevR: '', menu: '', calendar: '', bag: '', mail: '', grid: '', hanger: '', users: '', chart: '', plus: '', trash: '', edit: '', filter: '', star: '', scissors: '', sparkle: '', pin: '', phone: '', logout: '', truck: '', box: '', eye: '', ruler: '' }; function Icon({ name, size = 18, stroke = 1.6, style, className }) { return React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", style, className, dangerouslySetInnerHTML: { __html: PATHS[name] || "" } }); } /* ---- LOGO ---------------------------------------------------------------- */ function Logo({ size = 30, sub = true, light = false }) { return (
Vestiré Studio
Vestiré Studio {sub && Atelier · Davao}
); } /* ---- BUTTON / LINK ------------------------------------------------------- */ function Btn({ variant = "primary", size, block, icon, iconR, children, ...rest }) { const cls = ["vbtn", "vbtn--" + variant, size === "sm" ? "vbtn--sm" : size === "lg" ? "vbtn--lg" : "", block ? "vbtn--block" : ""].join(" "); return ( ); } function Link({ icon, iconR = "arrow", children, ...rest }) { return ( {icon && } {children} {iconR && } ); } /* ---- STATUS PRIMITIVES --------------------------------------------------- */ function StatusPill({ status }) { return ( {status} ); } const STATUS_ICON = { Pending: "clock", Processing: "scissors", Completed: "check" }; function StatusStepper({ status }) { const steps = VS.statusOrder; // Pending, Processing, Completed const cancelled = status === "Cancelled"; const idx = cancelled ? 1 : steps.indexOf(status); return (
{steps.map((s, i) => ( {i > 0 &&
}
{cancelled && i === 1 ? "Cancelled" : s}
))}
); } function StatusTimeline({ req }) { const steps = [ { k: "Pending", label: "Request received", note: "We've got your request and will confirm availability.", date: req.createdAt }, { k: "Processing", label: "In preparation", note: req.type === "rental" ? "Fitting & reservation in progress." : "Order confirmed, preparing your gown.", date: req.status !== "Pending" ? req.updatedAt : null }, { k: "Completed", label: req.type === "rental" ? "Ready for pickup" : "Completed", note: req.type === "rental" ? "Collect on your reserved date." : "All done - enjoy!", date: req.status === "Completed" ? req.updatedAt : null } ]; const order = VS.statusOrder; const cancelled = req.status === "Cancelled"; const idx = cancelled ? 0 : order.indexOf(req.status); return (
{(cancelled ? steps.slice(0, 1).concat([{ k: "Cancelled", label: "Request cancelled", note: "This request was cancelled.", date: req.updatedAt }]) : steps).map((s, i, arr) => { const done = !cancelled && i < idx; const now = !cancelled && i === idx; const cancel = s.k === "Cancelled"; return (
{i < arr.length - 1 &&
}
{s.label} {s.date && · {fmtDate(s.date)}}
{s.note}
); })}
); } /* ---- MISC HELPERS -------------------------------------------------------- */ function fmtDate(d) { if (!d) return "—"; const dt = new Date(d.length <= 10 ? d + "T00:00:00" : d.replace(" ", "T")); return dt.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); } function initials(name) { return name.split(" ").filter(Boolean).slice(0, 2).map((w) => w[0]).join("").toUpperCase(); } function Stat({ label, value, sub, icon, accent }) { return (
{label} {icon && }
{value}
{sub && {sub}}
); } function Field({ label, children }) { return (); } function Toast({ msg, icon = "check" }) { return (
{msg}
); } Object.assign(window, { injectVSStyles, Icon, Logo, Btn, Link, StatusPill, StatusStepper, StatusTimeline, fmtDate, initials, Stat, Field, Toast });