// Customer-facing pages: Home, Shop, Upload, About, Contact

// ---------- Home ----------
// Guest order tracking — for customers who ordered without an account. They can
// look up by order number (from the thank-you screen) or the email they used.
function OrderTracker() {
  const [q, setQ] = useState("");
  const [state, setState] = useState({ loading: false, done: false, orders: [], err: "" });
  const prettyStatus = (s) => String(s || "placed").replace(/[_-]+/g, " ").replace(/\b\w/g, c => c.toUpperCase());
  const statusColor = (s) => { const n = String(s || "").toLowerCase(); if (/cancel|hold/.test(n)) return "var(--rose-deep)"; if (/complete|ship|ready|done|deliver|sent/.test(n)) return "var(--sage-deep)"; return "var(--gold-deep)"; };
  const svcLabel = (s) => s === "dtf" ? "DTF" : s === "mixed" ? "Embroidery + DTF" : "Embroidery";
  const track = () => {
    const query = q.trim();
    if (query.length < 4) { setState({ loading: false, done: false, orders: [], err: "Enter your order number or the email you used." }); return; }
    setState({ loading: true, done: false, orders: [], err: "" });
    api.trackOrder(query)
      .then(r => setState({ loading: false, done: true, orders: r.orders || [], err: "" }))
      .catch(e => setState({ loading: false, done: true, orders: [], err: e.message || "Couldn't look that up right now." }));
  };
  return (
    <section className="section-tight">
      <div className="stitched" style={{ padding: 24, maxWidth: 640, margin: "0 auto" }}>
        <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 6 }}>Track your order</div>
        <h2 style={{ marginTop: 0, marginBottom: 6 }}>Already ordered?</h2>
        <p style={{ color: "var(--ink-2)", marginTop: 0 }}>
          No account needed. Enter your <b>order number</b> (from your confirmation) or the <b>email</b> you used.
        </p>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          <input value={q} onChange={e => setQ(e.target.value)} onKeyDown={e => { if (e.key === "Enter") track(); }}
            placeholder="HB-250617-A1B2  or  you@email.com" style={{ flex: "1 1 240px" }} />
          <button type="button" className="btn" onClick={track} disabled={state.loading}>{state.loading ? "Looking…" : "Track order"}</button>
        </div>
        {state.err && <p style={{ color: "var(--rose-deep)", marginBottom: 0 }}>{state.err}</p>}
        {state.done && !state.err && state.orders.length === 0 && (
          <p style={{ color: "var(--ink-3)", marginBottom: 0 }}>
            No orders found for that. Double-check your order number or email, or <a href="#/contact">get in touch</a> and we'll find it.
          </p>
        )}
        {state.orders.length > 0 && (
          <div style={{ marginTop: 16, display: "flex", flexDirection: "column", gap: 10 }}>
            {state.orders.map(o => (
              <div key={o.ref} className="stitched" style={{ padding: 14, display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
                <div style={{ minWidth: 0 }}>
                  <div className="mono" style={{ fontWeight: 700 }}>{o.ref}</div>
                  <div style={{ color: "var(--ink-2)", fontSize: 14 }}>
                    {o.itemType || "Order"}{o.qty ? ` × ${o.qty}` : ""}{o.size && o.size !== "Various" ? ` (${o.size})` : ""} · {svcLabel(o.service)}
                  </div>
                  <div style={{ color: "var(--ink-3)", fontSize: 12 }}>
                    Placed {o.createdAt ? new Date(o.createdAt).toLocaleDateString() : "—"}{o.deadline ? ` · needed by ${o.deadline}` : ""}
                  </div>
                </div>
                <span className="smallcaps" style={{ fontSize: 12, color: statusColor(o.status), background: "var(--paper-2)", padding: "4px 10px", borderRadius: 8, whiteSpace: "nowrap" }}>{prettyStatus(o.status)}</span>
              </div>
            ))}
            <p style={{ color: "var(--ink-3)", fontSize: 12, margin: 0 }}>Questions about an order? <a href="#/contact">Contact Joy</a>.</p>
          </div>
        )}
      </div>
    </section>
  );
}

function HomePage() {
  const app = useApp();
  const featured = (app.items || []).filter(i => i.brand !== "couchcowboy").slice(0, 3);
  const cowboyItems = (app.items || []).filter(i => i.brand === "couchcowboy").slice(0, 3);
  return (
    <main>
      <div className="page">
        <section className="hero">
          <div>
            <div className="hero-eyebrow smallcaps">Embroidery · Print · Bespoke</div>
            <h1>
              Stitched with care, <span className="script">just for you.</span>
            </h1>
            <p className="hero-lede">
              Hi, I'm Joy — and Hazelbelle is my little studio for embroidery and bespoke print.
              Upload your own design, pick a piece from the shop, or send me an idea and we'll make it real.
            </p>
            <div className="hero-ctas">
              <a className="btn" href="#/upload">Upload your design →</a>
              <a className="btn btn-ghost" href="#/shop">Browse the shop</a>
            </div>
          </div>
          <div className="hero-art">
            <img src="assets/hazelbelle-logo.jpg" alt="Hazelbelle Embroidery and Print logo" />
          </div>
        </section>

        {/* Couch Cowboy partnership badge */}
        <section className="section-tight" style={{ paddingTop: 0 }}>
          <a href="#/shop?brand=couchcowboy" className="cc-strip stitched stitched-ink" style={{ display: "flex", alignItems: "center", gap: 22, padding: "20px 26px", textDecoration: "none", color: "inherit", flexWrap: "wrap" }}>
            <div className="cc-badge" aria-hidden="true">CC</div>
            <div style={{ flex: 1, minWidth: 220 }}>
              <div className="smallcaps" style={{ color: "var(--gold-deep)", marginBottom: 4 }}>Official supplier</div>
              <div style={{ fontSize: 22, fontFamily: "'Cormorant Garamond', serif", color: "var(--ink)" }}>
                Hazelbelle is the official supplier of <strong>Couch Cowboy Cattle Co.</strong>
              </div>
              <div style={{ color: "var(--ink-2)", marginTop: 4 }}>
                Shop the full Couch Cowboy collection — tees, caps, hoodies and totes, embroidered right here.
              </div>
            </div>
            <span className="btn btn-ghost btn-small">Shop the collection →</span>
          </a>
        </section>

        <ThreadDivider />

        {/* "How it works" trio */}
        <section className="section-tight">
          <div className="card-grid">
            <Step n="01" title="Send me your design" body="Upload a logo, sketch, photo or PDF. Pop in your name, email and the type of item — that's it." />
            <Step n="02" title="We chat & I quote" body="I'll reply by email with a friendly quote, mock-up if needed, and a timeline. No surprises." />
            <Step n="03" title="Stitched & sent" body="Once approved, I stitch or print it carefully and post it out — tracked, packed, with a thank-you note." />
          </div>
        </section>

        <OrderTracker />

        {/* Featured pieces */}
        <section className="section">
          <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 24, flexWrap: "wrap", gap: 10 }}>
            <h2><span className="script" style={{ color: "var(--rose-deep)" }}>A peek</span> at the shop</h2>
            <a className="nav-link" href="#/shop">See everything →</a>
          </div>
          {featured.length === 0 ? (
            <div className="stitched stitched-sage" style={{ textAlign: "center", padding: 40 }}>
              <p style={{ color: "var(--ink-2)", margin: 0 }}>Joy hasn't added any pieces yet — check back soon!</p>
            </div>
          ) : (
            <div className="card-grid">
              {featured.map(item => <ItemCard key={item.id} item={item} />)}
            </div>
          )}
        </section>

        {/* Couch Cowboy featured */}
        {cowboyItems.length > 0 && (
          <section className="section-tight">
            <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 20, flexWrap: "wrap", gap: 10 }}>
              <div>
                <div className="smallcaps" style={{ color: "var(--gold-deep)", marginBottom: 6 }}>Official supplier</div>
                <h2>The <span className="script" style={{ color: "var(--rose-deep)" }}>Couch Cowboy</span> collection</h2>
              </div>
              <a className="nav-link" href="#/shop?brand=couchcowboy">See the collection →</a>
            </div>
            <div className="card-grid">
              {cowboyItems.map(item => <ItemCard key={item.id} item={item} />)}
            </div>
          </section>
        )}

        <section className="section-tight">
          <div className="stitched studio-grid">
            <div>
              <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 8 }}>About the studio</div>
              <h2 style={{ marginBottom: 12 }}>
                {(app.siteContent?.home_studio_title?.value) || (
                  <>Small studio. <span className="script" style={{ color: "var(--rose-deep)" }}>Big love</span> for the little details.</>
                )}
              </h2>
              <p style={{ color: "var(--ink-2)", whiteSpace: "pre-line" }}>
                {(app.siteContent?.home_studio_body?.value) ||
                  "Every order at Hazelbelle is hand-finished — from school uniforms and team kit to memory cushions and christening gifts. I treat your design like it's my own."}
              </p>
              <a className="btn btn-ghost btn-small" href="#/about" style={{ marginTop: 14 }}>Read my story</a>
            </div>
            <div className="studio-photo">
              {app.siteContent?.home_studio_image?.image
                ? <img src={app.siteContent.home_studio_image.image} alt="Inside the Hazelbelle studio" loading="lazy" />
                : <Placeholder label="[ studio photo · drop one in ]" variant="sage" />}
            </div>
          </div>
        </section>

        {/* Studio journal — latest blog posts */}
        {(app.blogPosts || []).length > 0 && (
          <section className="section-tight">
            <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 18, flexWrap: "wrap", gap: 10 }}>
              <div>
                <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 6 }}>From the studio journal</div>
                <h2>Notes from the <span className="script" style={{ color: "var(--rose-deep)" }}>hoop</span></h2>
              </div>
              <a className="nav-link" href="#/blog">Read all posts →</a>
            </div>
            <div className="card-grid">
              {(app.blogPosts || []).slice().sort((a, b) => (b.image ? 1 : 0) - (a.image ? 1 : 0)).slice(0, 3).map(p => <BlogCard key={p.id} post={p} />)}
            </div>
          </section>
        )}
      </div>
    </main>
  );
}

function Step({ n, title, body }) {
  return (
    <div className="stitched" style={{ minHeight: 200 }}>
      <div className="mono" style={{ color: "var(--gold-deep)", fontSize: 13, marginBottom: 6 }}>{n}</div>
      <h3 style={{ marginBottom: 8 }}>{title}</h3>
      <p style={{ color: "var(--ink-2)", margin: 0, fontSize: 16 }}>{body}</p>
    </div>
  );
}

// ---------- Shop ----------
function ShopPage() {
  const app = useApp();
  // Initial filter can come from the hash query: #/shop?brand=couchcowboy
  const initialBrand = (() => {
    const m = location.hash.match(/brand=([^&]+)/);
    return m ? decodeURIComponent(m[1]) : "all";
  })();
  const [filter, setFilter] = useState("all");
  const [brand, setBrand] = useState(initialBrand);

  const allItems = app.items || [];
  // [HB] A shop item that's currently in stock shows in the "In stock now"
  // section below (as a buyable card), so don't also show it as a plain catalog
  // card up here — that would be a confusing duplicate.
  const stockedItemIds = new Set((app.storeStock || []).map(s => s.itemId).filter(Boolean));
  const items = allItems.filter(i => {
    if (stockedItemIds.has(i.id)) return false;
    if (brand !== "all" && (i.brand || "hazelbelle") !== brand) return false;
    if (filter === "all") return true;
    if (filter === "ready") return i.category === "ready";
    if (filter === "examples") return i.category === "example";
    return true;
  });

  const isCowboy = brand === "couchcowboy";

  return (
    <main className="page section">
      <div style={{ textAlign: "center", marginBottom: 12 }}>
        <div className="smallcaps" style={{ color: isCowboy ? "var(--gold-deep)" : "var(--sage-deep)", marginBottom: 8 }}>
          {isCowboy ? "Official supplier · Couch Cowboy Cattle Co." : "The little shop"}
        </div>
        <h1>
          {isCowboy
            ? <>The <span className="script" style={{ color: "var(--rose-deep)" }}>Couch Cowboy</span> collection</>
            : <>Ready-made &amp; <span className="script" style={{ color: "var(--rose-deep)" }}>made for you</span></>}
        </h1>
        <p className="hero-lede" style={{ margin: "14px auto 0" }}>
          {isCowboy
            ? "Branded apparel for Couch Cowboy Cattle Co., embroidered in-studio. Built to last, made to be worn."
            : "Examples of past commissions to spark ideas for your own — plus what's in stock and ready to ship, below."}
        </p>
      </div>

      <ThreadDivider />

      {/* Brand tabs */}
      <div style={{ display: "flex", gap: 8, justifyContent: "center", flexWrap: "wrap", marginBottom: 32 }}>
        <FilterPill on={brand === "all"} onClick={() => setBrand("all")}>All Hazelbelle</FilterPill>
        <FilterPill on={brand === "hazelbelle"} onClick={() => setBrand("hazelbelle")}>House pieces</FilterPill>
        <FilterPill on={brand === "couchcowboy"} onClick={() => setBrand("couchcowboy")}>Couch Cowboy Cattle Co.</FilterPill>
      </div>

      {items.length === 0 ? (
        <div className="stitched stitched-sage" style={{ textAlign: "center", padding: 48 }}>
          <p style={{ color: "var(--ink-2)", margin: 0 }}>Nothing here yet. Joy is busy stitching!</p>
        </div>
      ) : (
        <div className="card-grid">
          {items.map(item => <ItemCard key={item.id} item={item} />)}
        </div>
      )}

      {!isCowboy && (app.storeStock || []).length > 0 && (
        <section className="section-tight" style={{ marginTop: 8 }}>
          <div style={{ textAlign: "center", marginBottom: 18 }}>
            <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 6 }}>Ready to ship</div>
            <h2>In <span className="script" style={{ color: "var(--rose-deep)" }}>stock</span> now</h2>
          </div>
          <div className="card-grid">
            {(app.storeStock || []).map(s => <StockCard key={s.id} stock={s} />)}
          </div>
        </section>
      )}
    </main>
  );
}

function StockCard({ stock }) {
  const app = useApp();
  const low = stock.quantity <= 3;
  const out = stock.quantity <= 0;
  const subtitle = [stock.size, stock.color].filter(Boolean).join(" · ");
  const [zoom, setZoom] = useState(false);   // [HB-ADD] photo lightbox
  const hasImg = !!stock.imageUrl;           // [HB-ADD]
  const inCartQty = (app.cart || []).find(x => x.inventoryId === stock.id)?.qty || 0; // [HB-ADD]
  const addToCart = () => {                  // [HB-ADD]
    if (out) return;
    app.addToCart({ inventoryId: stock.id, description: stock.description, unitPrice: stock.salePrice, imageUrl: stock.imageUrl, maxQty: stock.quantity });
    app.toast(inCartQty + 1 >= stock.quantity ? "Added — that's all we have in stock." : "Added to cart.");
  };
  const buyLabel = out ? "Out of stock" : inCartQty > 0 ? `Add another (${inCartQty} in cart)` : "Add to cart";
  const priceEl = stock.onSale
    ? <span className="item-card-price">{fmtPrice(stock.salePrice)} <span style={{ textDecoration: "line-through", color: "var(--ink-3)", fontWeight: 400, fontSize: "0.82em" }}>{fmtPrice(stock.listPrice)}</span></span>
    : <span className="item-card-price">{fmtPrice(stock.salePrice)}</span>;
  // [HB-ADD] the card image is cropped to a square; clicking it opens the full photo.
  return (
    <>
      <div className="item-card">
        <div
          className="item-card-img"
          style={{ position: "relative", ...(hasImg ? { cursor: "zoom-in" } : {}) }}
          {...(hasImg ? {
            role: "button",
            tabIndex: 0,
            title: "Click to enlarge",
            "aria-label": `View larger photo of ${stock.description}`,
            onClick: () => setZoom(true),
            onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setZoom(true); } },
          } : {})}
        >
          {stock.onSale && (
            <span style={{ position: "absolute", top: 8, left: 8, zIndex: 2, background: "var(--rose-deep)", color: "#fff", fontSize: 11, fontWeight: 600, letterSpacing: ".05em", textTransform: "uppercase", padding: "3px 8px", borderRadius: 999 }}>Sale</span>
          )}
          {hasImg
            ? <img src={stock.imageUrl} alt={stock.description} loading="lazy" />
            : <Placeholder label={stock.type === "consumable" ? "[ supply ]" : "[ blank ]"} variant="sage" />}
        </div>
        <div className="item-card-body">
          <div className="item-card-title">{stock.description}</div>
          {subtitle && <div style={{ color: "var(--ink-3)", fontSize: 14, marginTop: 2 }}>{subtitle}</div>}
          <div className="item-card-meta" style={{ marginTop: 8 }}>
            {priceEl}
            <span style={{ fontSize: 13, color: low ? "var(--rose-deep)" : "var(--sage-deep)" }}>
              {stock.quantity} in stock{low && !out ? " — low" : ""}
            </span>
          </div>
          <button className="btn btn-small" style={{ marginTop: 12, width: "100%", justifyContent: "center" }}
             onClick={addToCart} disabled={out || inCartQty >= stock.quantity}>
            {inCartQty >= stock.quantity && !out ? "All in cart" : buyLabel}
          </button>
        </div>
      </div>

      {/* [HB-ADD] full-size photo view */}
      <Modal open={zoom && hasImg} onClose={() => setZoom(false)}>
        <div style={{ textAlign: "center" }}>
          <img src={stock.imageUrl} alt={stock.description}
               style={{ width: "100%", height: "auto", maxHeight: "70vh", objectFit: "contain", borderRadius: 8, background: "var(--paper-2)" }} />
          <div className="item-card-title" style={{ marginTop: 14, marginBottom: 2 }}>{stock.description}</div>
          {subtitle && <div style={{ color: "var(--ink-3)", fontSize: 14 }}>{subtitle}</div>}
          <div style={{ marginTop: 8, display: "flex", gap: 12, justifyContent: "center", alignItems: "baseline", flexWrap: "wrap" }}>
            {priceEl}
            <span style={{ fontSize: 13, color: low ? "var(--rose-deep)" : "var(--sage-deep)" }}>
              {stock.quantity} in stock{low && !out ? " — low" : ""}
            </span>
          </div>
          <button className="btn btn-small" style={{ marginTop: 14 }} onClick={addToCart} disabled={out || inCartQty >= stock.quantity}>
            {out ? "Out of stock" : inCartQty >= stock.quantity ? "All in cart" : "Add to cart"}
          </button>
        </div>
      </Modal>
    </>
  );
}

function FilterPill({ on, onClick, children }) {
  return (
    <button
      onClick={onClick}
      className={classNames("btn", "btn-small", on ? "" : "btn-ghost")}
      style={{ background: on ? "var(--ink)" : "transparent", borderColor: on ? "var(--ink)" : "var(--ink-2)", color: on ? "#fff" : "var(--ink-2)" }}
    >{children}</button>
  );
}

function ItemCard({ item }) {
  const isCowboy = item.brand === "couchcowboy";
  // [HB-CHG] The gallery is examples/portfolio only — real purchases happen in the
  // "In stock now" section (inventory + cart). No more email-to-buy on these cards.
  return (
    <div className={classNames("item-card", isCowboy && "item-card-cc")}>
      <div className="item-card-img">
        {item.image
          ? <img src={item.image} alt={item.title} loading="lazy" />
          : <Placeholder label={`[ ${item.title || "item photo"} ]`} />}
        <div style={{ position: "absolute", top: 12, left: 12, display: "flex", gap: 6, flexWrap: "wrap" }}>
          <span className="tag tag-example">Example</span>
          {isCowboy && <span className="tag tag-cc">Couch Cowboy</span>}
        </div>
      </div>
      <div className="item-card-body">
        <div className="item-card-title">{item.title}</div>
        <p style={{ color: "var(--ink-2)", margin: "4px 0 0", fontSize: 15, minHeight: 44 }}>{item.description}</p>
        <div className="item-card-meta">
          <span className="item-card-price">Made to order</span>
          <a className="btn btn-small btn-ghost" href="#/upload">Order similar</a>
        </div>
      </div>
    </div>
  );
}

// ---------- Upload ----------
// Shared option lists + the piece cap for self-serve orders. Past HB_MAX_QTY
// total pieces, we route the customer to contact Joy for a custom quote.
const HB_ITEM_TYPES = ["T-shirt", "Polo shirt", "Hoodie / sweatshirt", "Tote bag", "Cap / hat", "Apron", "Cushion", "Baby blanket", "I'll provide my own", "Other (tell me below)"];
const HB_SIZES = ["XS", "S", "M", "L", "XL", "XXL", "3XL", "Kids 3-4", "Kids 5-6", "Kids 7-8", "N/A"];
const HB_AUDIENCES = ["Men", "Women", "Boys", "Girls", "Unisex"];
const HB_COLORS = ["Black", "White", "Blue", "Red", "Green", "Yellow", "Lime Green", "Orange", "Other (note below)"];
const HB_OTHER_COLOR = "Other (note below)";
const HB_PLACEMENTS = ["Left chest", "Right chest", "Front center", "Back", "Front & back", "Sleeve", "Pocket", "Decide with Joy"];
// Map a chosen color name to a swatch hex for the sample preview.
const HB_COLOR_HEX = { "Black": "#2b2b2b", "White": "#f7f4ee", "Blue": "#2147c9", "Red": "#cf2b2b", "Green": "#1f8a3b", "Yellow": "#f2d022", "Lime Green": "#84cc16", "Orange": "#ef8a23" };
const hbColorHex = (name) => {
  if (!name) return "#cdc4b4";
  if (HB_COLOR_HEX[name]) return HB_COLOR_HEX[name];
  const n = String(name).toLowerCase();
  if (/lime/.test(n)) return "#84cc16";
  if (/black|jet|onyx|coal/.test(n)) return "#2b2b2b";
  if (/charcoal|graphite|smoke/.test(n)) return "#4b4f54";
  if (/white|ivory|natural|cream/.test(n)) return "#f7f4ee";
  if (/navy|royal|blue|aqua|teal/.test(n)) return "#2147c9";
  if (/red|cardinal|maroon|burgundy|cherry/.test(n)) return "#cf2b2b";
  if (/green|kelly|forest|olive/.test(n)) return "#1f8a3b";
  if (/yellow|gold|maize/.test(n)) return "#f2d022";
  if (/orange/.test(n)) return "#ef8a23";
  if (/grey|gray|heather|silver|ash/.test(n)) return "#9aa0a6";
  if (/purple|violet|plum/.test(n)) return "#7c3aed";
  if (/pink|rose|coral/.test(n)) return "#ec4899";
  if (/brown|tan|khaki|sand|taupe|mocha|chocolate|coffee|chai|wood|clay|stone|oat|bone|mushroom/.test(n)) return "#8b6b4a";
  return "#cdc4b4";
};
const HB_MAX_QTY = 12;        // max pieces of a single line (the qty dropdown)
const HB_MAX_ITEMS = 10;      // max different item rows per self-serve order
const hbTotalQty = (lines) => (lines || []).reduce((n, l) => n + (parseInt(l.qty, 10) || 0), 0);
// SanMar product titles arrive with HTML entities (e.g. "District &#174;"). Decode
// them for display so customers don't see raw &#174;. Uses the browser's own parser.
const hbDecode = (s) => {
  const str = String(s == null ? "" : s);
  if (str.indexOf("&") === -1 || typeof document === "undefined") return str;
  const t = document.createElement("textarea");
  t.innerHTML = str;
  return t.value;
};
const hbBlankLabel = (b) => b ? hbDecode(`${b.brand} ${b.styleNo} — ${b.title}`).replace(/\s+/g, " ").trim().slice(0, 80) : "";

// An <img> that quietly falls back to a tidy placeholder when the source is
// missing or fails to load — so a dead SanMar image URL never shows the broken
// "torn picture" icon. `label` is the short text shown in the placeholder.
function BlankImg({ src, alt, label, style, round, bare }) {
  const [failed, setFailed] = useState(false);
  if (!src || failed) {
    if (bare) return null; // let the parent's background (e.g. a color dot) show through
    return (
      <div style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center",
        background: "var(--paper-2)", color: "var(--ink-3)", fontSize: 11, textAlign: "center", padding: 6,
        borderRadius: round ? "50%" : 0 }}>
        {label || ""}
      </div>
    );
  }
  return <img src={src} alt={alt || ""} loading="lazy" onError={() => setFailed(true)}
    style={{ width: "100%", height: "100%", objectFit: "cover", display: "block", ...(style || {}) }} />;
}


// A lightweight sample mock-up: a tinted garment silhouette with the customer's
// uploaded logo dropped onto the chosen placement (front, back, or both). It's
// a rough visual aid — Joy sends a real mock-up before stitching.
// Three shapes are supported: short-sleeve shirt, long-sleeve shirt, and cap.
const HB_SHAPES = {
  tee: {
    vb: "0 0 100 105", h: 97,
    paths: ["M50 8 C44 8 40 11 33 11 L18 18 L10 34 L22 41 L27 33 L27 96 L73 96 L73 33 L78 41 L90 34 L82 18 L67 11 C60 11 56 8 50 8 Z"],
    anchors: { leftChest: { x: 30, y: 36, w: 15 }, rightChest: { x: 55, y: 36, w: 15 }, frontCentre: { x: 36, y: 42, w: 28 }, pocket: { x: 33, y: 60, w: 13 }, sleeve: { x: 13, y: 32, w: 12 }, back: { x: 31, y: 32, w: 38 } },
  },
  longsleeve: {
    vb: "0 0 100 105", h: 97,
    paths: ["M50 8 C44 8 40 11 33 11 L20 15 L7 26 L2 70 L13 73 L24 34 L24 96 L76 96 L76 34 L87 73 L98 70 L93 26 L80 15 L67 11 C60 11 56 8 50 8 Z"],
    anchors: { leftChest: { x: 33, y: 36, w: 14 }, rightChest: { x: 53, y: 36, w: 14 }, frontCentre: { x: 37, y: 44, w: 26 }, pocket: { x: 34, y: 60, w: 12 }, sleeve: { x: 4, y: 44, w: 10 }, back: { x: 33, y: 34, w: 34 } },
  },
  cap: {
    // crown dome + brim
    vb: "0 0 100 68", h: 58,
    paths: ["M18 44 C18 16 82 16 82 44 Z", "M18 44 L5 52 C2 54 4 58 9 57 L42 49 Z"],
    anchors: { frontCentre: { x: 39, y: 24, w: 22 }, leftChest: { x: 39, y: 24, w: 22 }, rightChest: { x: 39, y: 24, w: 22 }, pocket: { x: 39, y: 24, w: 22 }, sleeve: { x: 39, y: 24, w: 22 }, back: { x: 39, y: 24, w: 22 } },
  },
};
function hbPickShape(itemType) {
  const t = String(itemType || "").toLowerCase();
  if (/\bcap\b|hat|beanie|trucker|visor|bucket/.test(t)) return "cap";
  if (/long.?sleeve|l\/?s\b|crew|sweat|hood|fleece|jacket|pullover/.test(t)) return "longsleeve";
  return "tee";
}
function placementViews(p) {
  switch (p) {
    case "Back": return [{ label: "Back", anchor: "back" }];
    case "Front & back": return [{ label: "Front", anchor: "frontCentre" }, { label: "Back", anchor: "back" }];
    case "Right chest": return [{ label: "Front", anchor: "rightChest" }];
    case "Front center": return [{ label: "Front", anchor: "frontCentre" }];
    case "Pocket": return [{ label: "Front", anchor: "pocket" }];
    case "Sleeve": return [{ label: "Front", anchor: "sleeve" }];
    case "Left chest": return [{ label: "Front", anchor: "leftChest" }];
    default: return [{ label: "Front", anchor: "frontCentre" }];
  }
}
function GarmentPreview({ line, logoSrc }) {
  const fill = hbColorHex(line.color);
  const shape = HB_SHAPES[hbPickShape(line.itemType)] || HB_SHAPES.tee;
  const views = placementViews(line.placement);
  return (
    <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
      {views.map((v, i) => {
        const pos = shape.anchors[v.anchor] || shape.anchors.frontCentre;
        return (
          <div key={i} style={{ textAlign: "center" }}>
            <svg viewBox={shape.vb} width="92" height={shape.h} role="img" aria-label={`${line.itemType} ${v.label}`}>
              {shape.paths.map((d, j) => <path key={j} d={d} fill={fill} stroke="rgba(0,0,0,0.25)" strokeWidth="1.2" />)}
              {logoSrc
                ? <image href={logoSrc} xlinkHref={logoSrc} x={pos.x} y={pos.y} width={pos.w} height={pos.w} preserveAspectRatio="xMidYMid meet" />
                : <rect x={pos.x} y={pos.y} width={pos.w} height={pos.w} fill="none" stroke="rgba(0,0,0,0.35)" strokeDasharray="3 2" />}
            </svg>
            <div className="smallcaps" style={{ fontSize: 10, color: "var(--ink-3)" }}>{v.label}</div>
          </div>
        );
      })}
    </div>
  );
}

// In-page popup to browse SanMar blanks and hand a pick back to the order form,
// so a customer can choose a blank without leaving the Upload page.
function BlankPickerModal({ onPick, onClose }) {
  const [q, setQ] = useState("");
  const [page, setPage] = useState(1);
  const [data, setData] = useState({ total: 0, page: 1, pageSize: 24, styles: [] });
  const [loading, setLoading] = useState(true);
  const [err, setErr] = useState("");
  const [styleNo, setStyleNo] = useState(null);   // when set, show the detail view
  const [detail, setDetail] = useState(null);
  const [color, setColor] = useState(0);

  const loadList = () => {
    setLoading(true); setErr("");
    const qs = new URLSearchParams();
    if (q) qs.set("q", q);
    qs.set("page", String(page));
    api.sanmarShop(qs.toString())
      .then(setData)
      .catch(e => setErr(e.message || "The blank catalog isn't available right now."))
      .finally(() => setLoading(false));
  };
  useEffect(() => { if (!styleNo) loadList(); }, [page, styleNo]);
  useEffect(() => { if (!styleNo) { setPage(1); loadList(); } }, [q]);
  useEffect(() => {
    if (!styleNo) { setDetail(null); return; }
    setDetail(null); setColor(0);
    api.sanmarShopStyle(styleNo).then(d => { setDetail(d); setColor(0); }).catch(e => setErr(e.message || "Couldn't load that blank."));
  }, [styleNo]);

  const totalPages = Math.max(1, Math.ceil((data.total || 0) / (data.pageSize || 24)));
  const c = detail?.colors?.[color];

  const use = () => {
    onPick({
      styleNo: detail.styleNo, title: detail.title, brand: detail.brand,
      category: detail.category || "", color: c ? c.color : "",
      fromPrice: detail.fromPrice || 0, image: c?.image || detail.image || "",
    });
  };

  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, background: "rgba(58,42,38,0.5)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000, padding: 16 }}>
      <div onClick={e => e.stopPropagation()} className="stitched" style={{ background: "var(--card)", maxWidth: 760, width: "100%", maxHeight: "88vh", overflow: "auto", padding: 20 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, marginBottom: 12 }}>
          <h3 style={{ margin: 0 }}>{styleNo ? (detail ? `${detail.brand} ${detail.styleNo}` : "Loading…") : "Choose a blank"}</h3>
          <button type="button" className="btn btn-small btn-ghost" onClick={onClose}>Close ×</button>
        </div>
        {err && <p style={{ color: "var(--rose-deep)" }}>{err}</p>}

        {!styleNo ? (
          <>
            <input placeholder="Search style, brand or product…" value={q} onChange={e => setQ(e.target.value)} style={{ width: "100%", marginBottom: 12 }} />
            {loading ? <p style={{ color: "var(--ink-3)" }}>Loading…</p> : (
              data.styles.length === 0
                ? <p style={{ color: "var(--ink-3)" }}>No blanks match that search.</p>
                : <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(150px, 1fr))", gap: 10 }}>
                    {data.styles.map(s => (
                      <button key={s.styleNo} type="button" className="stitched" onClick={() => setStyleNo(s.styleNo)}
                        style={{ textAlign: "left", padding: 0, overflow: "hidden", cursor: "pointer", display: "flex", flexDirection: "column" }}>
                        <div style={{ aspectRatio: "1 / 1", background: "var(--paper-2)" }}>
                          <BlankImg src={s.image} alt={hbDecode(s.title)} label={s.styleNo} />
                        </div>
                        <div style={{ padding: 8 }}>
                          <div style={{ fontSize: 11, color: "var(--ink-3)" }}>{s.brand}</div>
                          <div style={{ fontSize: 13, fontWeight: 600, lineHeight: 1.25 }}>{hbDecode(s.title)}</div>
                        </div>
                      </button>
                    ))}
                  </div>
            )}
            {totalPages > 1 && !loading && (
              <div style={{ display: "flex", gap: 8, justifyContent: "center", alignItems: "center", marginTop: 14 }}>
                <button type="button" className="btn btn-small btn-ghost" disabled={page <= 1} onClick={() => setPage(p => Math.max(1, p - 1))}>← Prev</button>
                <span style={{ fontSize: 13, color: "var(--ink-2)" }}>{page} / {totalPages}</span>
                <button type="button" className="btn btn-small btn-ghost" disabled={page >= totalPages} onClick={() => setPage(p => p + 1)}>Next →</button>
              </div>
            )}
          </>
        ) : (
          !detail ? <p style={{ color: "var(--ink-3)" }}>Loading…</p> : (
            <>
              <button type="button" className="btn btn-small btn-ghost" onClick={() => setStyleNo(null)} style={{ marginBottom: 10 }}>← All blanks</button>
              <div style={{ display: "grid", gridTemplateColumns: "minmax(0,1fr) minmax(0,1fr)", gap: 16, alignItems: "start" }}>
                <div style={{ aspectRatio: "1 / 1", background: "var(--paper-2)", borderRadius: 10, overflow: "hidden" }}>
                  <BlankImg src={c?.image || detail.image} alt={hbDecode(detail.title)} label={detail.styleNo} />
                </div>
                <div>
                  <div style={{ fontWeight: 600 }}>{hbDecode(detail.title)}</div>
                  <div style={{ color: "var(--ink-3)", fontSize: 13, margin: "2px 0 10px" }}>{detail.brand} {detail.styleNo}</div>
                  {detail.colors?.length > 0 && (
                    <>
                      <div className="smallcaps" style={{ color: "var(--sage-deep)", fontSize: 11 }}>Color · {c?.color}</div>
                      <div style={{ display: "flex", flexWrap: "wrap", gap: 6, margin: "6px 0 12px" }}>
                        {detail.colors.map((col, i) => (
                          <button key={col.color} type="button" title={col.color} onClick={() => setColor(i)}
                            style={{ width: 24, height: 24, borderRadius: "50%", border: i === color ? "2px solid var(--sage-deep)" : "1px solid var(--line)", padding: 0, overflow: "hidden", background: hbColorHex(col.color), cursor: "pointer" }}>
                            <BlankImg src={col.swatch} alt="" round bare />
                          </button>
                        ))}
                      </div>
                    </>
                  )}
                  {c && <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 12 }}>
                    {c.sizes.map(sz => <span key={sz.uniqueKey} style={{ fontSize: 12, padding: "2px 8px", borderRadius: 6, border: "1px solid var(--line)", color: sz.inStock ? "var(--ink)" : "var(--ink-3)" }}>{sz.size}{sz.inStock ? "" : " · out"}</span>)}
                  </div>}
                  <button type="button" className="btn" onClick={use}>Use this blank →</button>
                </div>
              </div>
            </>
          )
        )}
      </div>
    </div>
  );
}

// Default full-catalog destination (Joy's CompanyCasuals storefront). Joy can
// override this in the SanMar admin tab (setting: sanmar_catalog_url) — point it
// at the UNPRICED generic version so customers browse to pick a style and come
// back to order, rather than checking out blanks on CompanyCasuals.
const HB_CATALOG_URL_DEFAULT = "https://www.companycasuals.com/customstitchesfl/start.jsp";
let _hbCatalogUrl = null; // module-level cache so we fetch the setting once
function useCatalogUrl() {
  const [url, setUrl] = useState(_hbCatalogUrl || HB_CATALOG_URL_DEFAULT);
  useEffect(() => {
    if (_hbCatalogUrl) return;
    let alive = true;
    api.listPublicSettings()
      .then(s => {
        const u = (s && s.sanmar_catalog_url && String(s.sanmar_catalog_url).trim()) || HB_CATALOG_URL_DEFAULT;
        _hbCatalogUrl = u;
        if (alive) setUrl(u);
      })
      .catch(() => {});
    return () => { alive = false; };
  }, []);
  return url;
}

// Per-line blank control: a "use a blank" link, the chosen blank with
// Change / Remove, OR a typed full-catalog style (for items not in the curated
// shop). Shared by the Upload and Quote item rows.
function BlankLineControl({ line, onBrowse, onRemove, onSetCatalog, onClearCatalog, catalogUrl }) {
  const url = catalogUrl || HB_CATALOG_URL_DEFAULT;
  const linkBtn = { background: "none", border: "none", padding: 0, color: "var(--rose-deep)", cursor: "pointer", textDecoration: "underline", font: "inherit", fontSize: 13 };
  // Open the full catalog in a sized popup window so the customer keeps their
  // order page. If the browser blocks the popup, fall through to the normal
  // new-tab link (we only preventDefault when window.open succeeds). An iframe
  // isn't an option — CompanyCasuals blocks being embedded in another site.
  const openCatalog = (e) => {
    try {
      const w = window.open(url, "hb_full_catalog", "width=1100,height=820,scrollbars=yes,resizable=yes");
      if (w) { if (e) e.preventDefault(); w.focus(); }
    } catch (_) {}
  };

  if (line.blank) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 6, flexWrap: "wrap", fontSize: 13 }}>
        {line.blank.image && <img src={line.blank.image} alt="" style={{ width: 28, height: 28, objectFit: "cover", borderRadius: 6 }} />}
        <span style={{ color: "var(--ink-2)" }}><b>Blank:</b> {line.blank.brand} {line.blank.styleNo}{line.blank.color ? ` · ${line.blank.color}` : ""}</span>
        <button type="button" className="btn btn-small btn-ghost" onClick={onBrowse}>Change</button>
        <button type="button" className="btn btn-small btn-ghost" onClick={onRemove}>Remove</button>
      </div>
    );
  }

  if (line.catalogStyle) {
    const cs = line.catalogStyle;
    const set = (k, v) => onSetCatalog && onSetCatalog({ ...cs, [k]: v });
    return (
      <div style={{ marginTop: 6, fontSize: 13 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
          <span style={{ color: "var(--ink-2)" }}><b>From full catalog:</b></span>
          <input placeholder="Style # (e.g. DT6000)" value={cs.style || ""} onChange={e => set("style", e.target.value)} style={{ width: 140 }} />
          <input placeholder="Color" value={cs.color || ""} onChange={e => set("color", e.target.value)} style={{ width: 110 }} />
          <input placeholder="Size" value={cs.size || ""} onChange={e => set("size", e.target.value)} style={{ width: 64 }} />
          <select value={cs.service || "embroidery"} onChange={e => set("service", e.target.value)} style={{ width: 124 }} title="Decoration method">
            <option value="embroidery">Embroidery</option>
            <option value="dtf">DTF</option>
          </select>
          <a href={url} target="_blank" rel="noopener noreferrer" onClick={openCatalog} style={{ color: "var(--rose-deep)", fontSize: 12 }}>Browse ↗</a>
          <button type="button" className="btn btn-small btn-ghost" onClick={onClearCatalog}>Remove</button>
        </div>
        <div className="field-hint" style={{ marginTop: 4 }}>Joy will confirm price &amp; availability for this style.</div>
      </div>
    );
  }

  return (
    <div style={{ display: "flex", alignItems: "center", gap: 12, marginTop: 6, flexWrap: "wrap" }}>
      <button type="button" onClick={onBrowse} style={linkBtn}>+ Use a SanMar blank for this item</button>
      {onSetCatalog && (
        <>
          <span style={{ color: "var(--ink-3)", fontSize: 13 }}>or</span>
          <a href={url} target="_blank" rel="noopener noreferrer" onClick={openCatalog} style={{ color: "var(--ink-2)", fontSize: 13 }}>browse the full catalog ↗</a>
          <button type="button" onClick={() => onSetCatalog({ style: "", color: "", size: "", service: "embroidery" })} style={linkBtn}>+ enter a style I found</button>
        </>
      )}
    </div>
  );
}

function UploadPage() {
  const app = useApp();
  const [form, setForm] = useState(() => ({
    name: "",
    email: "",
    orderRef: "",
    service: "embroidery",
    lines: [{ itemType: "T-shirt", size: "M", qty: 1, audience: "Unisex", color: "", placement: "Left chest", service: "embroidery", blank: null, catalogStyle: null }],
    placement: "Chest",
    threadColors: [],
    notes: "",
    deadline: "",
    website: "", // [HB] honeypot
  }));
  const [files, setFiles] = useState([]);
  const [submitted, setSubmitted] = useState(null);
  const [drag, setDrag] = useState(false);
  const [pickerFor, setPickerFor] = useState(null); // line index the blank picker targets
  const inputRef = useRef(null);

  // Saved-logo library (only for signed-in customers) + the ones chosen to reuse.
  const [logoLibrary, setLogoLibrary] = useState([]);
  const [savedLogos, setSavedLogos] = useState([]);
  useEffect(() => {
    let alive = true;
    if (!app.customer) { setLogoLibrary([]); setSavedLogos([]); return; }
    (async () => {
      try { const list = await app.accountLogos(); if (alive) setLogoLibrary(list || []); }
      catch (e) { if (alive) setLogoLibrary([]); }
    })();
    return () => { alive = false; };
  }, [app.customer]);
  const toggleSavedLogo = (logo) => setSavedLogos(prev =>
    prev.some(l => l.id === logo.id) ? prev.filter(l => l.id !== logo.id) : [...prev, logo]
  );

  // If a customer is signed in, prefill their details (the upload links to
  // their account server-side via the session, so it shows in order tracking).
  useEffect(() => {
    const c = app.customer;
    if (!c) return;
    setForm(f => ({
      ...f,
      name: f.name || c.name || "",
      email: f.email || c.email || "",
    }));
  }, [app.customer]);

  // If the customer arrived from "Shop blanks → Start an order with this blank",
  // pull that selection in and prefill the item. Consume it so a later, unrelated
  // upload doesn't inherit a stale blank.
  // Apply a chosen blank to a specific line: it becomes that line's item type
  // (so Joy sees the real product) and carries its color. Each line is independent.
  const applyBlankToLine = (index, pick) => {
    if (!pick || !pick.styleNo || index == null) return;
    setForm(f => ({ ...f, lines: f.lines.map((l, i) => i === index ? { ...l, blank: pick, itemType: hbBlankLabel(pick), color: pick.color || l.color } : l) }));
  };
  const removeBlankFromLine = (index) => setForm(f => ({ ...f, lines: f.lines.map((l, i) => i === index ? { ...l, blank: null, itemType: "T-shirt", color: "" } : l) }));

  useEffect(() => {
    const pick = (typeof window !== "undefined") ? window.HB_BLANK_PICK : null;
    if (!pick || !pick.styleNo) return;
    try { delete window.HB_BLANK_PICK; } catch (e) { window.HB_BLANK_PICK = null; }
    applyBlankToLine(0, pick);
  }, []);

  // Arriving from "Get a quote → Start your order": pull the items they built.
  useEffect(() => {
    const pick = (typeof window !== "undefined") ? window.HB_QUOTE_PICK : null;
    if (!pick) return;
    try { delete window.HB_QUOTE_PICK; } catch (e) { window.HB_QUOTE_PICK = null; }
    setForm(f => {
      const lines = (pick.lines && pick.lines.length)
        ? pick.lines.map(l => ({
            itemType: l.itemType || "T-shirt",
            size: l.size || "M",
            qty: Math.max(1, Math.min(HB_MAX_QTY, parseInt(l.qty, 10) || 1)),
            audience: l.audience || "Unisex",
            color: l.color || "",
            placement: l.placement || "Left chest",
            service: l.service === "dtf" ? "dtf" : "embroidery",
            blank: l.blank || null,
            catalogStyle: l.catalogStyle || null,
          })).slice(0, HB_MAX_ITEMS)
        : f.lines;
      return { ...f, lines };
    });
  }, []);

  const setField = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const setLine = (i, k, v) => setForm(f => ({ ...f, lines: f.lines.map((l, idx) => idx === i ? { ...l, [k]: v } : l) }));
  const addLine = () => setForm(f => f.lines.length >= HB_MAX_ITEMS ? f : ({ ...f, lines: [...f.lines, { itemType: f.lines[f.lines.length - 1]?.itemType || "T-shirt", size: "M", qty: 1, audience: f.lines[f.lines.length - 1]?.audience || "Unisex", color: "", placement: f.lines[f.lines.length - 1]?.placement || "Left chest", service: f.lines[f.lines.length - 1]?.service || "embroidery", blank: null, catalogStyle: null }] }));
  const removeLine = (i) => setForm(f => ({ ...f, lines: f.lines.length > 1 ? f.lines.filter((_, idx) => idx !== i) : f.lines }));
  // Full-catalog (long-tail) style typed by the customer for one line.
  const catalogUrl = useCatalogUrl();
  const setCatalogForLine = (i, cs) => setForm(f => ({ ...f, lines: f.lines.map((l, idx) => idx === i ? { ...l, catalogStyle: cs, blank: null, itemType: cs.style ? `SanMar ${String(cs.style).trim()}` : l.itemType, color: cs.color || l.color } : l) }));
  const clearCatalogFromLine = (i) => setForm(f => ({ ...f, lines: f.lines.map((l, idx) => idx === i ? { ...l, catalogStyle: null, itemType: "T-shirt", color: "" } : l) }));
  const totalQty = hbTotalQty(form.lines);
  const overCap = totalQty > HB_MAX_QTY;
  const previewLogo = (files.find(f => (f.type || "").startsWith("image/")) || {}).dataUrl || (files.find(f => f.thumb) || {}).thumb || (savedLogos.find(l => (l.type || "").startsWith("image/")) || {}).viewUrl || "";

  const handleFiles = async (fileList) => {
    const arr = Array.from(fileList);
    const next = [];
    for (const f of arr) {
      // Skip generating data URLs for non-image files we can't preview anyway,
      // to keep localStorage usage down in demo mode.
      const dataUrl = f.type.startsWith("image/") ? await readFileAsDataURL(f) : "";
      const thumb = f.type.startsWith("image/") ? await makeThumbnail(f) : "";
      next.push({ file: f, name: f.name, size: f.size, type: f.type, dataUrl, thumb });
    }
    setFiles(prev => [...prev, ...next]);
  };

  const onDrop = (e) => {
    e.preventDefault();
    setDrag(false);
    if (e.dataTransfer?.files?.length) handleFiles(e.dataTransfer.files);
  };

  const toggleThread = (c) => {
    setForm(f => ({
      ...f,
      threadColors: f.threadColors.includes(c)
        ? f.threadColors.filter(x => x !== c)
        : [...f.threadColors, c]
    }));
  };

  const submit = async (e) => {
    e.preventDefault();
    if (!form.name || !form.email) {
      app.toast("Please add your name and email so Joy can reply.");
      return;
    }
    if (files.length === 0 && savedLogos.length === 0) {
      app.toast("Please add at least one design file, or pick a saved logo.");
      return;
    }
    const ref = form.orderRef || makeOrderRef();
    const lines = form.lines.filter(l => (parseInt(l.qty, 10) || 0) > 0);
    if (!lines.length) { app.toast("Please add at least one item."); return; }
    const total = hbTotalQty(lines);
    const svcLabel = (s) => s === "dtf" ? "DTF" : "Embroidery";
    // A line's decoration: the full-catalog dropdown wins when set, else the line's own.
    const lineSvc = (l) => (l.catalogStyle && l.catalogStyle.service)
      ? (l.catalogStyle.service === "dtf" ? "dtf" : "embroidery")
      : (l.service === "dtf" ? "dtf" : "embroidery");
    const itemsBlock = "Items:\n" + lines.map(l => {
      const bits = [l.itemType, l.audience, l.color].filter(Boolean).join(" — ");
      return `- ${bits} — ${l.size} × ${l.qty} — ${svcLabel(lineSvc(l))} — logo: ${l.placement || "Decide with Joy"}${l.blank ? " — SanMar blank" : ""}${l.catalogStyle && l.catalogStyle.style ? ` — full catalog: ${String(l.catalogStyle.style).trim()}${l.catalogStyle.color ? "/" + l.catalogStyle.color : ""} (Joy to confirm price)` : ""}`;
    }).join("\n");
    const placementSummary = Array.from(new Set(lines.map(l => l.placement).filter(Boolean)));
    const services = Array.from(new Set(lines.map(lineSvc)));
    const serviceSummary = services.length > 1 ? "mixed" : services[0];
    const composedNotes = [itemsBlock, form.notes ? `Notes: ${form.notes}` : ""].filter(Boolean).join("\n\n");
    const record = {
      id: uid() + "-" + uid(),
      ref,
      name: form.name,
      email: form.email,
      orderRef: form.orderRef,
      service: serviceSummary,
      // Keep the single columns meaningful for storage; full breakdown is in lines + notes.
      itemType: lines.length > 1 ? "Multiple items" : lines[0].itemType,
      qty: total,
      size: lines.length > 1 ? "Various" : lines[0].size,
      lines,
      placement: placementSummary.length ? placementSummary.join(", ") : "Decide with Joy",
      threadColors: form.threadColors,
      notes: composedNotes,
      deadline: form.deadline,
      files: files.map(f => ({ name: f.name, size: f.size, type: f.type, dataUrl: f.dataUrl, thumb: f.thumb })),
      website: form.website || "", // [HB] honeypot
      status: "placed",
      createdAt: Date.now(),
    };
    try {
      const fileObjects = files.map(f => f.file).filter(Boolean);
      const saved = await app.addUpload(record, fileObjects, savedLogos.map(l => l.id));
      setSubmitted(saved || record);
      window.scrollTo({ top: 0, behavior: "smooth" });
    } catch (err) {
      app.toast(err.message || "Couldn't send — please try again.");
    }
  };

  if (submitted) return <UploadSuccess record={submitted} onAnother={() => { setSubmitted(null); setFiles([]); setForm({ name: form.name, email: form.email, orderRef: "", service: "embroidery", lines: [{ itemType: "T-shirt", size: "M", qty: 1, audience: "Unisex", color: "", placement: "Left chest", service: "embroidery", blank: null, catalogStyle: null }], placement: "Chest", threadColors: [], notes: "", deadline: "", website: "" }); }} />;

  const threadPalette = [
    { name: "Dusty rose", hex: "#b86a78" },
    { name: "Rose deep",  hex: "#934e5c" },
    { name: "Sage",        hex: "#8a9966" },
    { name: "Forest",      hex: "#4d5a3a" },
    { name: "Gold",        hex: "#c5a36b" },
    { name: "Cream",       hex: "#f5ebd9" },
    { name: "Ink",         hex: "#3a2a26" },
    { name: "Slate blue",  hex: "#6b7a92" },
    { name: "Wine",        hex: "#5a2a35" },
    { name: "Powder pink", hex: "#e8c4cb" },
  ];

  return (
    <main className="page section">
      <div style={{ textAlign: "center", marginBottom: 20 }}>
        <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 8 }}>Step into the studio</div>
        <h1>Upload your <span className="script" style={{ color: "var(--rose-deep)" }}>design</span></h1>
        <p className="hero-lede" style={{ margin: "14px auto 0" }}>
          Pop your details below and I'll be in touch within a day or two to confirm pricing and timeline.
        </p>
      </div>

      <ThreadDivider />

      <form onSubmit={submit} style={{ display: "grid", gridTemplateColumns: "1.5fr 1fr", gap: 32 }} className="upload-form">
        <Honeypot value={form.website} onChange={v => setForm(f => ({ ...f, website: v }))} />
        <div>
          <section className="stitched" style={{ marginBottom: 24 }}>
            <h3 style={{ marginBottom: 14 }}>Your details</h3>
            <div className="field-row">
              <div className="field">
                <label>Your name</label>
                <input value={form.name} onChange={e => setField("name", e.target.value)} placeholder="Jane Smith" />
              </div>
              <div className="field">
                <label>Email</label>
                <input type="email" value={form.email} onChange={e => setField("email", e.target.value)} placeholder="jane@example.com" />
              </div>
            </div>
            <div className="field">
              <label>Order reference (if Joy has given you one)</label>
              <input value={form.orderRef} onChange={e => setField("orderRef", e.target.value)} placeholder="Leave blank — we'll create one for you" />
              <div className="field-hint">Returning customers: pop in your previous reference to keep things together.</div>
            </div>
          </section>

          <section className="stitched stitched-sage" style={{ marginBottom: 24 }}>
            <h3 style={{ marginBottom: 14 }}>Your design</h3>
            <div
              className={classNames("dropzone", drag && "drag")}
              onClick={() => inputRef.current?.click()}
              onDragOver={e => { e.preventDefault(); setDrag(true); }}
              onDragLeave={() => setDrag(false)}
              onDrop={onDrop}
            >
              <div className="dropzone-icon">+</div>
              <div style={{ fontSize: 18, color: "var(--sage-deep)" }}>Drop your files here, or click to choose</div>
              <div className="field-hint" style={{ marginTop: 6 }}>PNG, JPG, PDF, SVG — up to about 10MB each</div>
              <input ref={inputRef} type="file" multiple style={{ display: "none" }}
                accept="image/*,.pdf,.svg"
                onChange={e => handleFiles(e.target.files)}
              />
            </div>
            {files.length > 0 && (
              <div className="file-chips">
                {files.map((f, i) => (
                  <FileChip key={i} file={f} onRemove={() => setFiles(arr => arr.filter((_, j) => j !== i))} />
                ))}
              </div>
            )}
            {logoLibrary.length > 0 && (
              <div style={{ marginTop: 16, borderTop: "1px dashed var(--line)", paddingTop: 14 }}>
                <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 8 }}>Or reuse a saved logo</div>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
                  {logoLibrary.map(logo => {
                    const on = savedLogos.some(l => l.id === logo.id);
                    return (
                      <button type="button" key={logo.id} onClick={() => toggleSavedLogo(logo)}
                        style={{ width: 110, padding: 8, borderRadius: 8, cursor: "pointer",
                          border: on ? "2px solid var(--sage-deep)" : "1px solid var(--line)",
                          background: on ? "var(--paper-2)" : "#fff", textAlign: "center" }}>
                        <div style={{ height: 64, display: "flex", alignItems: "center", justifyContent: "center", overflow: "hidden" }}>
                          {(logo.type || "").startsWith("image/")
                            ? <img src={logo.viewUrl} alt={logo.name} style={{ maxWidth: "100%", maxHeight: "100%", objectFit: "contain" }} />
                            : <span style={{ fontSize: 11, color: "var(--ink-3)", wordBreak: "break-word" }}>{logo.fileName}</span>}
                        </div>
                        <div style={{ fontSize: 12, marginTop: 6, color: on ? "var(--sage-deep)" : "var(--ink-2)", fontWeight: on ? 600 : 400, wordBreak: "break-word" }}>
                          {on ? "✓ " : ""}{logo.name}
                        </div>
                      </button>
                    );
                  })}
                </div>
                <div className="field-hint" style={{ marginTop: 8 }}>Tap to add a saved logo to this order. Manage your logos in your account.</div>
              </div>
            )}
          </section>

          <section className="stitched" style={{ marginBottom: 24 }}>
            <h3 style={{ marginBottom: 14 }}>What it's going on</h3>
            <div className="field" style={{ marginBottom: 8 }}>
              <label>Items &amp; sizes</label>
              <div className="field-hint" style={{ marginTop: 0, marginBottom: 8 }}>Add a row for each size or item — pick Embroidery or DTF per item, so one order can mix both.</div>
              {form.lines.map((l, i) => (
                <div key={i} style={{ marginBottom: 10, paddingBottom: 10, borderBottom: i < form.lines.length - 1 ? "1px dashed var(--line)" : "none" }}>
                  <div style={{ display: "flex", gap: 8, alignItems: "flex-end", flexWrap: "wrap" }}>
                  <div className="field" style={{ flex: "2 1 160px", marginBottom: 0 }}>
                    {i === 0 && <label style={{ fontSize: 12 }}>Item type</label>}
                    <select value={l.itemType} onChange={e => setLine(i, "itemType", e.target.value)} disabled={!!l.blank}>
                      {(HB_ITEM_TYPES.includes(l.itemType) ? HB_ITEM_TYPES : [l.itemType, ...HB_ITEM_TYPES]).map(t => <option key={t}>{t}</option>)}
                    </select>
                  </div>
                  <div className="field" style={{ flex: "1 1 120px", marginBottom: 0 }}>
                    {i === 0 && <label style={{ fontSize: 12 }}>Method</label>}
                    <select value={l.service} onChange={e => setLine(i, "service", e.target.value)}>
                      <option value="embroidery">Embroidery</option>
                      <option value="dtf">DTF transfer</option>
                    </select>
                  </div>
                  <div className="field" style={{ flex: "1 1 100px", marginBottom: 0 }}>
                    {i === 0 && <label style={{ fontSize: 12 }}>Fit</label>}
                    <select value={l.audience} onChange={e => setLine(i, "audience", e.target.value)}>
                      {HB_AUDIENCES.map(a => <option key={a}>{a}</option>)}
                    </select>
                  </div>
                  <div className="field" style={{ flex: "1 1 120px", marginBottom: 0 }}>
                    {i === 0 && <label style={{ fontSize: 12 }}>Color</label>}
                    <select value={l.color} onChange={e => setLine(i, "color", e.target.value)}>
                      <option value="">Choose…</option>
                      {(l.color && !HB_COLORS.includes(l.color) ? [l.color, ...HB_COLORS] : HB_COLORS).map(c => <option key={c}>{c}</option>)}
                    </select>
                  </div>
                  <div className="field" style={{ flex: "0 1 80px", marginBottom: 0 }}>
                    {i === 0 && <label style={{ fontSize: 12 }}>Size</label>}
                    <select value={l.size} onChange={e => setLine(i, "size", e.target.value)}>
                      {HB_SIZES.map(sz => <option key={sz}>{sz}</option>)}
                    </select>
                  </div>
                  <div className="field" style={{ flex: "0 1 70px", marginBottom: 0 }}>
                    {i === 0 && <label style={{ fontSize: 12 }}>Qty</label>}
                    <select value={l.qty} onChange={e => setLine(i, "qty", Number(e.target.value))}>
                      {Array.from({ length: HB_MAX_QTY }, (_, n) => n + 1).map(n => <option key={n} value={n}>{n}</option>)}
                    </select>
                  </div>
                  <div className="field" style={{ flex: "1 1 130px", marginBottom: 0 }}>
                    {i === 0 && <label style={{ fontSize: 12 }}>Logo placement</label>}
                    <select value={l.placement} onChange={e => setLine(i, "placement", e.target.value)}>
                      {HB_PLACEMENTS.map(p => <option key={p}>{p}</option>)}
                    </select>
                  </div>
                  <button type="button" className="btn btn-small btn-ghost" disabled={form.lines.length <= 1}
                    onClick={() => removeLine(i)} title="Remove this row" style={{ marginBottom: 0 }}>×</button>
                  </div>
                  <BlankLineControl line={l} onBrowse={() => setPickerFor(i)} onRemove={() => removeBlankFromLine(i)}
                    onSetCatalog={(cs) => setCatalogForLine(i, cs)} onClearCatalog={() => clearCatalogFromLine(i)} catalogUrl={catalogUrl} />
                </div>
              ))}
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 8 }}>
                <button type="button" className="btn btn-small btn-ghost" onClick={addLine} disabled={form.lines.length >= HB_MAX_ITEMS}>+ Add another size / item</button>
                <span style={{ fontSize: 13, color: "var(--ink-2)" }}>Total: <b>{totalQty}</b> {totalQty === 1 ? "piece" : "pieces"} · {form.lines.length}/{HB_MAX_ITEMS} items</span>
              </div>
              {form.lines.length >= HB_MAX_ITEMS && (
                <div className="stitched stitched-sage" style={{ padding: 12, marginTop: 10 }}>
                  <b style={{ color: "var(--sage-deep)" }}>That's the {HB_MAX_ITEMS}-item max for online orders.</b> Need more variety in one order?
                  Please <a href="#/contact">contact Joy</a> and she'll set it up for you.
                </div>
              )}
              {form.lines.some(l => l.color === HB_OTHER_COLOR) && (
                <div className="field-hint" style={{ marginTop: 8, color: "var(--rose-deep)" }}>
                  You chose <b>Other</b> for a color — please type the exact color(s) you want in <b>Notes &amp; special instructions</b> below.
                </div>
              )}
            </div>
            <div className="field">
              <label>Deadline (if any)</label>
              <input type="date" value={form.deadline} onChange={e => setField("deadline", e.target.value)} />
            </div>
            <div className="field">
              <label>Thread / print colours</label>
              <div className="field-hint">Tap any that fit — or describe in the notes.</div>
              <div className="swatches">
                {threadPalette.map(c => (
                  <div
                    key={c.hex}
                    title={c.name}
                    className={classNames("swatch", form.threadColors.includes(c.name) && "on")}
                    style={{ background: c.hex, borderColor: c.hex === "#f5ebd9" ? "var(--ink-2)" : c.hex }}
                    onClick={() => toggleThread(c.name)}
                  />
                ))}
              </div>
            </div>
            <div className="field">
              <label>Notes &amp; special instructions</label>
              <textarea value={form.notes} onChange={e => setField("notes", e.target.value)}
                placeholder="Anything I should know — sizing notes, who it's for, fonts you love, references…" />
            </div>
          </section>

          <button className="btn" type="submit" style={{ fontSize: 18, padding: "14px 28px" }}>
            Send to Joy →
          </button>
        </div>

        <aside style={{ position: "sticky", top: 90, alignSelf: "start" }}>
          <div className="stitched stitched-ink">
            <h3 style={{ marginBottom: 10 }}>What happens next</h3>
            <ol style={{ paddingLeft: 18, margin: 0, color: "var(--ink-2)", lineHeight: 1.7 }}>
              <li>You hit send. Your design is safely with Joy.</li>
              <li>She'll reply by email within 1–2 working days with a quote.</li>
              <li>Approve the quote &amp; mock-up, then stitching begins.</li>
              <li>Your finished piece is posted out, tracked.</li>
            </ol>
            <hr className="divider-dashed" style={{ margin: "18px 0" }} />
            <div className="mono" style={{ fontSize: 13, color: "var(--ink-3)" }}>
              No payment is taken on this form. Joy will invoice you once everything's agreed.
            </div>
          </div>

          <div className="stitched" style={{ marginTop: 16 }}>
            <h3 style={{ marginBottom: 4 }}>Sample preview</h3>
            <div className="field-hint" style={{ marginTop: 0, marginBottom: 12 }}>
              A rough mock-up of your items with your logo. Joy sends an exact mock-up before stitching.
            </div>
            {!previewLogo && (
              <div style={{ color: "var(--ink-3)", fontSize: 13, marginBottom: 12 }}>
                Upload a design above to see it placed on your items.
              </div>
            )}
            <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
              {form.lines.map((l, i) => (
                <div key={i} style={{ display: "flex", gap: 12, alignItems: "center" }}>
                  <GarmentPreview line={l} logoSrc={previewLogo} />
                  <div style={{ fontSize: 13, color: "var(--ink-2)", minWidth: 0 }}>
                    <div style={{ fontWeight: 600 }}>{l.itemType} × {l.qty}</div>
                    <div style={{ color: "var(--ink-3)" }}>
                      {[l.audience, l.color || "color TBD", l.size].filter(Boolean).join(" · ")}
                    </div>
                    <div style={{ color: "var(--ink-3)" }}>Logo: {l.placement}</div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        </aside>
      </form>

      <style>{`
        @media (max-width: 880px) {
          .upload-form { grid-template-columns: 1fr !important; }
        }
      `}</style>
      {pickerFor !== null && <BlankPickerModal onClose={() => setPickerFor(null)} onPick={(p) => { applyBlankToLine(pickerFor, p); setPickerFor(null); }} />}
    </main>
  );
}

function UploadSuccess({ record, onAnother }) {
  const app = useApp();
  const lines = (record.lines && record.lines.length) ? record.lines : null;
  const files = record.files || [];
  const isImg = (f) => (f.thumb || ((f.type || "").startsWith("image/") && f.dataUrl));
  return (
    <main className="page section">
      <style>{`
        @media print {
          body * { visibility: hidden !important; }
          #hb-receipt, #hb-receipt * { visibility: visible !important; }
          #hb-receipt { position: absolute; left: 0; top: 0; width: 100%; margin: 0; box-shadow: none; }
          .no-print { display: none !important; }
          #hb-receipt { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
        }
      `}</style>
      <div id="hb-receipt" className="stitched stitched-sage" style={{ maxWidth: 720, margin: "0 auto", textAlign: "center", padding: 40 }}>
        <div className="script" style={{ fontSize: 64, color: "var(--rose-deep)", lineHeight: 1 }}>Thank you</div>
        <p style={{ fontSize: 19, color: "var(--ink-2)", marginTop: 4 }}>
          Your design is safely with Joy. She'll reply to <strong>{record.email}</strong> within a day or two.
        </p>
        <div className="summary-box" style={{ textAlign: "left", marginTop: 18 }}>
          <span className="ref">{record.ref}</span>
          <div>Name: {record.name}</div>
          <div>Email: {record.email}</div>
          {record.deadline && <div>Needed by: {fmtDate(record.deadline)}</div>}

          <div className="smallcaps" style={{ color: "var(--sage-deep)", marginTop: 14, marginBottom: 4 }}>Items ({lines ? lines.length : 1})</div>
          {lines
            ? <ol style={{ margin: 0, paddingLeft: 22, display: "flex", flexDirection: "column", gap: 4 }}>
                {lines.map((l, i) => (
                  <li key={i}>
                    {[l.itemType, l.audience, l.color].filter(Boolean).join(" — ")} — {l.size} × {l.qty} — {l.service === "dtf" ? "DTF" : "Embroidery"} — logo: {l.placement}{l.blank ? " · SanMar blank" : ""}
                  </li>
                ))}
              </ol>
            : <div>{record.itemType} × {record.qty} ({record.size}) — {record.placement}</div>}
        </div>

        {files.length > 0 && (
          <div style={{ textAlign: "left", marginTop: 18 }}>
            <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 8 }}>Your design files ({files.length})</div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 10 }}>
              {files.map((f, i) => (
                <div key={i} style={{ width: 92, textAlign: "center" }}>
                  <div style={{ width: 92, height: 92, borderRadius: 10, overflow: "hidden", background: "var(--paper-2)", border: "1px solid var(--line)", display: "flex", alignItems: "center", justifyContent: "center" }}>
                    {isImg(f)
                      ? <img src={f.thumb || f.dataUrl} alt={f.name} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
                      : <span className="smallcaps" style={{ fontSize: 11, color: "var(--ink-3)" }}>{(f.name || "file").split(".").pop().toUpperCase()}</span>}
                  </div>
                  <div style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 4, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={f.name}>{f.name}</div>
                </div>
              ))}
            </div>
          </div>
        )}

        <div className="no-print" style={{ display: "flex", gap: 10, justifyContent: "center", marginTop: 24, flexWrap: "wrap" }}>
          <button className="btn" onClick={() => window.print()}>Print / Save as PDF</button>
          <button className="btn btn-ghost" onClick={onAnother}>Upload another</button>
          <a className="btn btn-ghost" href="#/shop">Back to the shop</a>
          <a className="btn btn-ghost" href={app.tweaks.facebookUrl || "https://www.facebook.com/61590039444991"} target="_blank" rel="noopener">Find me on Facebook</a>
        </div>
        <p className="mono" style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 22 }}>
          Keep your order number <b>{record.ref}</b> — quote it in emails, or track your order anytime from the <a href="#/">home page</a> using that number or your email.
        </p>
      </div>
    </main>
  );
}

// ---------- About ----------
function AboutPage() {
  const app = useApp();
  const intro = app.siteContent?.about_intro?.value;
  const body  = app.siteContent?.about_body?.value;
  const loveList = app.siteContent?.about_love?.value;
  const photo1 = app.siteContent?.about_photo_1?.image || "";
  const photo2 = app.siteContent?.about_photo_2?.image || "";
  return (
    <main className="page section">
      <div style={{ textAlign: "center", marginBottom: 20 }}>
        <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 8 }}>The story</div>
        <h1>Hello — I'm <span className="script" style={{ color: "var(--rose-deep)" }}>Joy</span></h1>
      </div>
      <ThreadDivider />

      <div className="about-grid">
        <div>
          <p style={{ fontSize: 20, color: "var(--ink-2)", whiteSpace: "pre-line" }}>
            {intro || "Hazelbelle started at my kitchen table with one little embroidery machine, a tangle of threads, and a head full of ideas. A few years on, the studio's a bit tidier (only a bit!) and I get to make beautiful things for lovely people — every single day."}
          </p>
          {body ? (
            <p style={{ whiteSpace: "pre-line", color: "var(--ink-2)" }}>{body}</p>
          ) : (
            <>
              <p>
                I work with everyone from new parents wanting a christening blanket, to local football clubs
                kitting out the whole team, to small businesses who want a uniform that <em>feels</em> like
                them. I love a tricky brief.
              </p>
              <p>
                Every order is hand-finished here, by me. No big factory, no shortcuts — just careful work
                and a real human at the other end of the email.
              </p>
            </>
          )}
          <hr className="divider-dashed" />
          <h3 style={{ marginBottom: 8 }}>What I love stitching</h3>
          <ul style={{ paddingLeft: 18, color: "var(--ink-2)" }}>
            {(loveList ? loveList.split("\n").map(s => s.replace(/^[-•*]\s*/, "").trim()).filter(Boolean) : [
              "Personalised baby blankets & christening gifts",
              "Team kit, club polos & uniforms",
              "Small-business merch — aprons, totes, hoodies",
              "Memory pieces from a loved one's shirt",
              "Wedding gifts & keepsakes",
            ]).map((s, i) => <li key={i}>{s}</li>)}
          </ul>
          <a className="btn" href="#/upload" style={{ marginTop: 12 }}>Bring me your idea →</a>
        </div>

        <div style={{ display: "grid", gap: 22 }}>
          <div className="polaroid">
            <div className="polaroid-img">
              {photo1
                ? <img src={photo1} alt="Joy at the embroidery machine" loading="lazy" />
                : <Placeholder label="[ photo of Joy at the machine ]" variant="sage" />}
            </div>
            <div className="polaroid-caption">— Joy, at the machine ·</div>
          </div>
          <div className="polaroid" style={{ marginLeft: 28 }}>
            <div className="polaroid-img">
              {photo2
                ? <img src={photo2} alt="The Hazelbelle thread cupboard" loading="lazy" />
                : <Placeholder label="[ rainbow of threads ]" />}
            </div>
            <div className="polaroid-caption">— the colour cupboard ·</div>
          </div>
        </div>
      </div>

      <section style={{ marginTop: 64 }}>
        <div className="stitched" style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 24 }}>
          <Stat n="600+" label="Pieces stitched" />
          <Stat n="5 yrs" label="Of careful work" />
          <Stat n="1 of 1" label="Every single order" />
        </div>
      </section>
    </main>
  );
}

function Stat({ n, label }) {
  return (
    <div style={{ textAlign: "center" }}>
      <div style={{ fontSize: 48, fontFamily: "'Cormorant Garamond', serif", color: "var(--rose-deep)", lineHeight: 1 }}>{n}</div>
      <div className="smallcaps" style={{ color: "var(--ink-2)", marginTop: 6 }}>{label}</div>
    </div>
  );
}

// ---------- Contact ----------
function ContactPage() {
  const app = useApp();
  const [form, setForm] = useState({ name: "", email: "", message: "", website: "" });
  const [sent, setSent] = useState(false);
  const submit = async (e) => {
    e.preventDefault();
    if (!form.name || !form.email || !form.message) {
      app.toast("Please fill out everything so Joy can reply.");
      return;
    }
    try {
      await app.addMessage({ ...form, id: uid() + "-" + uid(), createdAt: Date.now() });
      setSent(true);
    } catch (err) {
      app.toast(err.message || "Couldn't send — please try again.");
    }
  };
  return (
    <main className="page section">
      <div style={{ textAlign: "center", marginBottom: 20 }}>
        <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 8 }}>Say hello</div>
        <h1>Let's <span className="script" style={{ color: "var(--rose-deep)" }}>chat</span></h1>
        <p className="hero-lede" style={{ margin: "14px auto 0" }}>
          Got a question, an idea, or want to check timings? I'd love to hear from you.
        </p>
      </div>
      <ThreadDivider />

      <div style={{ display: "grid", gridTemplateColumns: "1.2fr 1fr", gap: 32 }} className="contact-grid">
        <div className="stitched">
          {sent ? (
            <div style={{ textAlign: "center", padding: 28 }}>
              <div className="script" style={{ fontSize: 56, color: "var(--rose-deep)", lineHeight: 1 }}>Sent!</div>
              <p style={{ color: "var(--ink-2)" }}>Joy will reply to <strong>{form.email}</strong> soon.</p>
              <button className="btn btn-ghost" onClick={() => { setSent(false); setForm({ name: "", email: "", message: "", website: "" }); }}>Write another</button>
            </div>
          ) : (
            <form onSubmit={submit}>
              <h3 style={{ marginBottom: 14 }}>Send a message</h3>
              <Honeypot value={form.website} onChange={v => setForm(f => ({ ...f, website: v }))} />
              <div className="field-row">
                <div className="field">
                  <label>Your name</label>
                  <input value={form.name} onChange={e => setForm(f => ({ ...f, name: e.target.value }))} />
                </div>
                <div className="field">
                  <label>Email</label>
                  <input type="email" value={form.email} onChange={e => setForm(f => ({ ...f, email: e.target.value }))} />
                </div>
              </div>
              <div className="field">
                <label>Message</label>
                <textarea rows="5" value={form.message} onChange={e => setForm(f => ({ ...f, message: e.target.value }))} placeholder="Tell me a bit about what you're after…" />
              </div>
              <button className="btn" type="submit">Send message →</button>
            </form>
          )}
        </div>
        <aside className="stitched stitched-sage">
          <h3 style={{ marginBottom: 12 }}>Other ways to reach me</h3>
          <div style={{ display: "grid", gap: 14 }}>
            <ContactRow label="Email" value={app.tweaks.contactEmail || "hazelbelleemb@gmail.com"} href={`mailto:${app.tweaks.contactEmail || "hazelbelleemb@gmail.com"}`} />
            <ContactRow label="Facebook" value="@hazelbelle.embroidery" href={app.tweaks.facebookUrl || "https://www.facebook.com/profile.php?id=61590039444991"} />
            <ContactRow label="Instagram" value="@hazelbelle.embroidery" href={app.tweaks.instagramUrl || "https://www.instagram.com/hazelbelleemb/"} />
            <ContactRow label="Studio" value={app.tweaks.location || "North Fort Myers, Florida"} />
            <ContactRow label="Hours" value={"Mon–Fri · 9–5\nWeekends by appointment"} />
          </div>
          <hr className="divider-dashed" />
          <div className="mono" style={{ fontSize: 13, color: "var(--ink-3)" }}>
            Got an existing order? Quote your <strong>HB-</strong> reference and I'll find you faster.
          </div>
        </aside>
      </div>
      <style>{`@media (max-width: 800px) { .contact-grid { grid-template-columns: 1fr !important; } }`}</style>
    </main>
  );
}

function ContactRow({ label, value, href }) {
  const inner = (
    <div>
      <div className="smallcaps" style={{ color: "var(--ink-3)", fontSize: 11 }}>{label}</div>
      <div style={{ fontSize: 17, color: "var(--ink)", whiteSpace: "pre-line" }}>{value}</div>
    </div>
  );
  return href ? <a href={href} target={href.startsWith("http") ? "_blank" : undefined} rel="noopener" style={{ color: "inherit" }}>{inner}</a> : inner;
}

// ---------- Blog ----------
function BlogCardImage({ src, alt }) {
  const [failed, setFailed] = useState(false);
  if (!src || failed) return <Placeholder label="[ journal post ]" variant="sage" />;
  return <img src={src} alt={alt} loading="lazy" onError={() => setFailed(true)} />;
}

function BlogCard({ post }) {
  return (
    <a className="item-card blog-card" href={`#/blog/${post.slug}`} style={{ display: "block", color: "inherit" }}>
      <div className="item-card-img">
        <BlogCardImage src={post.image} alt={post.title} />
      </div>
      <div className="item-card-body">
        <div className="smallcaps" style={{ color: "var(--ink-3)", marginBottom: 6 }}>{fmtDate(post.createdAt)}</div>
        <div className="item-card-title">{post.title}</div>
        <p style={{ color: "var(--ink-2)", margin: "6px 0 0", fontSize: 15, minHeight: 44 }}>{post.excerpt || ""}</p>
        {(post.tags || []).length > 0 && (
          <div style={{ marginTop: 10, display: "flex", gap: 6, flexWrap: "wrap" }}>
            {post.tags.slice(0, 3).map(t => <span key={t} className="tag tag-example">{t}</span>)}
          </div>
        )}
        <div className="mono" style={{ marginTop: 12, fontSize: 13, color: "var(--rose-deep)" }}>Read post →</div>
      </div>
    </a>
  );
}

function BlogPage() {
  const app = useApp();
  const posts = app.blogPosts || [];
  return (
    <main className="page section">
      <div style={{ textAlign: "center", marginBottom: 20 }}>
        <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 8 }}>From the studio</div>
        <h1>The <span className="script" style={{ color: "var(--rose-deep)" }}>journal</span></h1>
        <p className="hero-lede" style={{ margin: "14px auto 0" }}>
          What's on the embroidery hoop this week — peeks behind the scenes, new shop pieces, and the occasional thread emergency.
        </p>
      </div>
      <ThreadDivider />
      {posts.length === 0 ? (
        <div className="stitched stitched-sage" style={{ textAlign: "center", padding: 48 }}>
          <p style={{ color: "var(--ink-2)", margin: 0 }}>Joy hasn't published any journal posts yet — check back soon.</p>
        </div>
      ) : (
        <div className="card-grid">
          {posts.map(p => <BlogCard key={p.id} post={p} />)}
        </div>
      )}
    </main>
  );
}

function BlogPostPage({ slug }) {
  const app = useApp();
  const post = (app.blogPosts || []).find(p => p.slug === slug);

  // Update document title for SEO when a post is open.
  useEffect(() => {
    if (post) {
      const original = document.title;
      document.title = `${post.title} · Hazelbelle Embroidery & Print`;
      // Inject article JSON-LD for AI / search crawlers.
      const ld = document.createElement("script");
      ld.type = "application/ld+json";
      ld.id = "blog-jsonld";
      ld.text = JSON.stringify({
        "@context": "https://schema.org",
        "@type": "BlogPosting",
        "headline": post.title,
        "description": post.excerpt || "",
        "image": post.image ? [post.image] : undefined,
        "datePublished": new Date(post.createdAt).toISOString(),
        "dateModified": new Date(post.updatedAt || post.createdAt).toISOString(),
        "author": { "@type": "Person", "name": "Joy" },
        "publisher": {
          "@type": "Organization",
          "name": "Hazelbelle Embroidery & Print",
          "logo": { "@type": "ImageObject", "url": "/assets/hazelbelle-logo.jpg" }
        }
      });
      document.head.appendChild(ld);
      return () => {
        document.title = original;
        document.getElementById("blog-jsonld")?.remove();
      };
    }
  }, [post]);

  if (!post) {
    return (
      <main className="page section">
        <div className="stitched stitched-sage" style={{ maxWidth: 640, margin: "0 auto", textAlign: "center", padding: 40 }}>
          <h2>Hmm — can't find that post.</h2>
          <p style={{ color: "var(--ink-2)" }}>It might have been moved, or hasn't been published yet.</p>
          <a className="btn" href="#/blog">Back to the journal</a>
        </div>
      </main>
    );
  }
  return (
    <main className="page section">
      <article style={{ maxWidth: 760, margin: "0 auto" }}>
        <a className="nav-link" href="#/blog" style={{ fontSize: 14 }}>← Back to the journal</a>
        <div style={{ marginTop: 18, marginBottom: 8 }}>
          <div className="smallcaps" style={{ color: "var(--ink-3)" }}>{fmtDate(post.createdAt)}</div>
          <h1 style={{ marginTop: 6 }}>{post.title}</h1>
          {post.excerpt && <p className="hero-lede" style={{ marginTop: 12 }}>{post.excerpt}</p>}
        </div>
        {post.image && (
          <div className="stitched" style={{ padding: 0, overflow: "hidden", borderRadius: 14, margin: "20px 0 24px" }}>
            <img src={post.image} alt={post.title} style={{ width: "100%", display: "block" }} />
          </div>
        )}
        <div className="blog-body" style={{ fontSize: 19, lineHeight: 1.7, color: "var(--ink-2)" }}>
          {(post.content || "").split(/\n{2,}/).map((para, i) => (
            <p key={i} style={{ marginBottom: 18, whiteSpace: "pre-line" }}>{para}</p>
          ))}
        </div>
        {(post.tags || []).length > 0 && (
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginTop: 18 }}>
            {post.tags.map(t => <span key={t} className="tag tag-example">{t}</span>)}
          </div>
        )}
        <hr className="divider-dashed" style={{ margin: "36px 0 24px" }} />
        <div style={{ display: "flex", gap: 10, flexWrap: "wrap", justifyContent: "center" }}>
          <a className="btn" href="#/upload">Start your own piece →</a>
          <a className="btn btn-ghost" href="#/blog">More journal posts</a>
        </div>
      </article>
    </main>
  );
}

Object.assign(window, { HomePage, ShopPage, UploadPage, AboutPage, ContactPage, ItemCard, BlogPage, BlogPostPage, BlogCard, FAQPage });

// ---------- FAQ (public) ----------
function FAQItem({ faq, open, onToggle }) {
  return (
    <div className="faq-item">
      <button className="faq-q" onClick={onToggle} aria-expanded={open}>
        <span>{faq.question}</span>
        <span className="faq-caret" style={{ transform: open ? "rotate(45deg)" : "none" }}>+</span>
      </button>
      {open && <div className="faq-a">{(faq.answer || "").split(/\n{2,}/).map((p, i) => <p key={i} style={{ margin: i ? "10px 0 0" : 0 }}>{p}</p>)}</div>}
    </div>
  );
}

function FAQPage() {
  const app = useApp();
  const faqs = app.faqs || [];
  const [open, setOpen] = useState(null);
  return (
    <main className="page section">
      <div style={{ textAlign: "center", marginBottom: 8 }}>
        <div className="smallcaps" style={{ color: "var(--sage-deep)" }}>Questions &amp; answers</div>
        <h1>Frequently <span className="script" style={{ color: "var(--rose-deep)" }}>asked</span></h1>
      </div>
      {faqs.length === 0 ? (
        <div className="stitched stitched-sage" style={{ textAlign: "center", padding: 40, maxWidth: 600, margin: "20px auto" }}>
          <p style={{ color: "var(--ink-2)", margin: 0 }}>No questions posted yet — feel free to <a href="#/contact">reach out</a> and Joy will be glad to help.</p>
        </div>
      ) : (
        <div style={{ maxWidth: 720, margin: "10px auto 0" }}>
          {faqs.map((f, i) => <FAQItem key={f.id} faq={f} open={open === i} onToggle={() => setOpen(open === i ? null : i)} />)}
        </div>
      )}
      <div style={{ textAlign: "center", marginTop: 28 }}>
        <p style={{ color: "var(--ink-2)" }}>Still have a question?</p>
        <a className="btn" href="#/contact">Get in touch →</a>
      </div>
    </main>
  );
}

// ---------- Cart + checkout (Option B: reserve & invoice) ----------
// Customer reviews their in-stock items and places the order. Placing it
// reserves the stock and pings Joy, who sends a QuickBooks invoice to pay.
function CartPage() {
  const app = useApp();
  const cart = app.cart || [];
  const c = app.customer;
  const [form, setForm] = useState({
    name: c?.name || "",
    email: c?.email || "",
    phone: c?.phone || "",
    shipAddress: [c?.addressLine1, c?.addressLine2, [c?.city, c?.state].filter(Boolean).join(", "), c?.zip].filter(Boolean).join("\n") || "",
    notes: "",
  });
  const set = (k, v) => setForm(s => ({ ...s, [k]: v }));
  const [busy, setBusy] = useState(false);
  const [placed, setPlaced] = useState(null); // { ref, subtotal, email } on success
  // [HB] Win-back / promo code redemption.
  const [code, setCode] = useState("");
  const [codeInfo, setCodeInfo] = useState(null); // { ok, discountPct, message }
  const [codeBusy, setCodeBusy] = useState(false);

  useEffect(() => { app.refreshStoreStock?.(); }, []);

  const subtotal = cart.reduce((s, x) => s + (Number(x.unitPrice) || 0) * x.qty, 0);
  const discountPct = (codeInfo && codeInfo.ok) ? (codeInfo.discountPct || 0) : 0;
  const discount = Math.round(subtotal * discountPct) / 100;
  const total = Math.max(0, Math.round((subtotal - discount) * 100) / 100);

  const applyCode = async () => {
    const c = code.trim();
    if (!c) { setCodeInfo(null); return; }
    if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(form.email.trim())) { app.toast("Add your email first — codes are tied to the email they were sent to."); return; }
    setCodeBusy(true);
    try {
      const r = await api.validateStoreCode(c, form.email.trim());
      setCodeInfo(r);
      if (r.ok) app.toast(`${r.discountPct}% off applied.`);
    } catch (e) {
      setCodeInfo({ ok: false, message: e.message || "Couldn't check that code." });
    } finally { setCodeBusy(false); }
  };
  const clearCode = () => { setCode(""); setCodeInfo(null); };

  const checkout = async () => {
    if (cart.length === 0) return;
    if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(form.email.trim())) { app.toast("Add a valid email so Joy can send your invoice."); return; }
    setBusy(true);
    try {
      const order = await api.placeStoreOrder({
        email: form.email.trim(), name: form.name.trim(), phone: form.phone.trim(),
        shipAddress: form.shipAddress.trim(), notes: form.notes.trim(),
        items: cart.map(x => ({ inventoryId: x.inventoryId, qty: x.qty })),
        code: (codeInfo && codeInfo.ok) ? code.trim() : "",
      });
      setPlaced({ ref: order.ref, subtotal: order.subtotal, total: order.total != null ? order.total : order.subtotal, discountAmount: order.discountAmount || 0, discountPct: order.discountPct || 0, email: form.email.trim() });
      app.clearCart();
      app.refreshStoreStock?.();
    } catch (e) {
      app.toast(e.message || "Sorry — couldn't place the order.");
      app.refreshStoreStock?.(); // stock may have shifted
    } finally { setBusy(false); }
  };

  if (placed) {
    return (
      <main className="container" style={{ maxWidth: 640, margin: "0 auto", padding: "40px 20px" }}>
        <div className="stitched" style={{ textAlign: "center", padding: 32 }}>
          <h1 style={{ marginTop: 0 }}>Order placed — thank you!</h1>
          <p style={{ color: "var(--ink-2)" }}>
            Your reference is <b className="mono">{placed.ref}</b>. We've set these items aside for you.
          </p>
          {placed.discountAmount > 0 && (
            <p style={{ color: "var(--sage-deep)" }}>
              Your code took <b>{fmtPrice(placed.discountAmount)}</b> ({placed.discountPct}%) off — nice one!
            </p>
          )}
          <p style={{ color: "var(--ink-2)" }}>
            Joy will email a QuickBooks invoice to <b>{placed.email}</b> for <b>{fmtPrice(placed.total)}</b>.
            Pay it there (card or Venmo) and your receipt will appear in your account.
          </p>
          <a className="btn" href="#/shop" style={{ marginTop: 10 }}>Continue shopping</a>
        </div>
      </main>
    );
  }

  return (
    <main className="container" style={{ maxWidth: 820, margin: "0 auto", padding: "32px 20px" }}>
      <h1>Your cart</h1>
      {cart.length === 0 ? (
        <div className="stitched" style={{ textAlign: "center", padding: 32, marginTop: 12 }}>
          <p style={{ color: "var(--ink-2)", margin: "0 0 14px" }}>Your cart is empty.</p>
          <a className="btn" href="#/shop">Browse what's in stock</a>
        </div>
      ) : (
        <>
          <div style={{ marginTop: 12 }}>
            {cart.map(x => (
              <div key={x.inventoryId} className="stitched" style={{ display: "flex", flexWrap: "wrap", gap: 14, alignItems: "center", marginBottom: 10 }}>
                <div style={{ width: 64, height: 64, flex: "0 0 auto", borderRadius: 8, overflow: "hidden", background: "var(--paper-2)" }}>
                  {x.imageUrl ? <img src={x.imageUrl} alt={x.description} style={{ width: "100%", height: "100%", objectFit: "cover" }} /> : null}
                </div>
                <div style={{ flex: "1 1 140px", minWidth: 0 }}>
                  <div style={{ fontSize: 16 }}>{x.description}</div>
                  <div style={{ color: "var(--ink-3)", fontSize: 14 }}>{fmtPrice(x.unitPrice)} each</div>
                </div>
                <div style={{ display: "flex", alignItems: "center", gap: 12, marginLeft: "auto" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                    <button className="btn btn-ghost btn-small" onClick={() => app.setCartQty(x.inventoryId, x.qty - 1)} aria-label="Decrease">−</button>
                    <span style={{ minWidth: 24, textAlign: "center" }}>{x.qty}</span>
                    <button className="btn btn-ghost btn-small" onClick={() => app.setCartQty(x.inventoryId, x.qty + 1)} disabled={Number.isFinite(x.maxQty) && x.qty >= x.maxQty} aria-label="Increase">+</button>
                  </div>
                  <div style={{ minWidth: 70, textAlign: "right", fontWeight: 600 }}>{fmtPrice((Number(x.unitPrice) || 0) * x.qty)}</div>
                  <button className="btn btn-ghost btn-small" onClick={() => app.removeFromCart(x.inventoryId)} aria-label="Remove" style={{ color: "var(--rose-deep)" }}>×</button>
                </div>
              </div>
            ))}
          </div>

          <div style={{ margin: "8px 4px 8px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
            <button className="btn btn-ghost btn-small" onClick={() => app.clearCart()}>Clear cart</button>
          </div>

          {/* [HB] Win-back / promo code */}
          <div className="stitched" style={{ padding: 14, marginBottom: 12 }}>
            <label style={{ fontSize: 13, color: "var(--ink-2)", display: "block", marginBottom: 6 }}>Have a discount code?</label>
            {(codeInfo && codeInfo.ok) ? (
              <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
                <span className="badge" style={{ background: "var(--sage-deep)" }}>{code.trim().toUpperCase()}</span>
                <span style={{ color: "var(--sage-deep)", fontSize: 14 }}>{codeInfo.discountPct}% off applied</span>
                <button className="btn btn-ghost btn-small" onClick={clearCode} style={{ marginLeft: "auto" }}>Remove</button>
              </div>
            ) : (
              <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                <input value={code} onChange={e => { setCode(e.target.value); if (codeInfo) setCodeInfo(null); }}
                  onKeyDown={e => { if (e.key === "Enter") { e.preventDefault(); applyCode(); } }}
                  placeholder="e.g. JOY15-A7QK" style={{ flex: "1 1 180px", textTransform: "uppercase" }} />
                <button className="btn btn-ghost btn-small" onClick={applyCode} disabled={codeBusy || !code.trim()}>{codeBusy ? "Checking…" : "Apply"}</button>
              </div>
            )}
            {codeInfo && !codeInfo.ok && <div style={{ color: "var(--rose-deep)", fontSize: 13, marginTop: 6 }}>{codeInfo.message}</div>}
          </div>

          <div className="stitched" style={{ padding: 14, marginBottom: 24 }}>
            <div style={{ display: "flex", justifyContent: "space-between", fontSize: 15, padding: "2px 0" }}>
              <span style={{ color: "var(--ink-2)" }}>Subtotal</span><span>{fmtPrice(subtotal)}</span>
            </div>
            {discount > 0 && (
              <div style={{ display: "flex", justifyContent: "space-between", fontSize: 15, padding: "2px 0", color: "var(--sage-deep)" }}>
                <span>Discount ({discountPct}%)</span><span>−{fmtPrice(discount)}</span>
              </div>
            )}
            <div style={{ display: "flex", justifyContent: "space-between", fontSize: 18, fontWeight: 700, borderTop: "1px solid var(--line)", marginTop: 6, paddingTop: 8 }}>
              <span>Total</span><span>{fmtPrice(total)}</span>
            </div>
          </div>

          <h2>Your details</h2>
          <p style={{ color: "var(--ink-2)", marginTop: 0 }}>
            We'll set these aside and Joy will email you a QuickBooks invoice to pay (card or Venmo).
          </p>
          <div className="stitched">
            <div className="field-row">
              <div className="field"><label>Name</label><input value={form.name} onChange={e => set("name", e.target.value)} /></div>
              <div className="field"><label>Email <span style={{ color: "var(--rose-deep)" }}>*</span></label><input type="email" value={form.email} onChange={e => set("email", e.target.value)} placeholder="you@example.com" /></div>
            </div>
            <div className="field" style={{ maxWidth: 280 }}><label>Phone</label><input value={form.phone} onChange={e => set("phone", e.target.value)} /></div>
            <div className="field"><label>Shipping address</label><textarea value={form.shipAddress} onChange={e => set("shipAddress", e.target.value)} placeholder="Where should we ship this?" /></div>
            <div className="field"><label>Note for Joy <span style={{ color: "var(--ink-3)" }}>(optional)</span></label><textarea value={form.notes} onChange={e => set("notes", e.target.value)} placeholder="Anything we should know?" /></div>
            <button className="btn" onClick={checkout} disabled={busy} style={{ width: "100%", justifyContent: "center" }}>
              {busy ? "Placing order…" : `Place order · ${fmtPrice(total)}`}
            </button>
            <p style={{ color: "var(--ink-3)", fontSize: 13, textAlign: "center", marginBottom: 0 }}>
              No payment is taken here — you'll pay the invoice Joy sends you.
            </p>
          </div>
        </>
      )}
    </main>
  );
}

// ---------- Live quote (#2) ----------
// Instant rough estimate built from Joy's pricing config. Always labelled an
// estimate — Joy confirms the real price. An active sale is applied automatically.
function QuotePage() {
  const [cfg, setCfg] = useState(null);
  const [err, setErr] = useState("");
  const [sel, setSel] = useState({ service: "embroidery", lines: [{ itemIndex: 0, size: "M", qty: 1, audience: "Unisex", color: "", service: "embroidery", blank: null, catalogStyle: null }], stitches: 8000, locations: 1, newDesign: true });
  const [pickerFor, setPickerFor] = useState(null);
  const catalogUrl = useCatalogUrl();
  const s = (k, v) => setSel(p => ({ ...p, [k]: v }));
  const setLine = (i, k, v) => setSel(p => ({ ...p, lines: p.lines.map((l, idx) => idx === i ? { ...l, [k]: v } : l) }));
  const addLine = () => setSel(p => p.lines.length >= HB_MAX_ITEMS ? p : ({ ...p, lines: [...p.lines, { itemIndex: p.lines[p.lines.length - 1]?.itemIndex || 0, size: "M", qty: 1, audience: p.lines[p.lines.length - 1]?.audience || "Unisex", color: "", service: p.lines[p.lines.length - 1]?.service || "embroidery", blank: null, catalogStyle: null }] }));
  const removeLine = (i) => setSel(p => ({ ...p, lines: p.lines.length > 1 ? p.lines.filter((_, idx) => idx !== i) : p.lines }));
  const applyBlankToLine = (index, pick) => {
    if (!pick || !pick.styleNo || index == null) return;
    setSel(p => ({ ...p, lines: p.lines.map((l, i) => i === index ? { ...l, blank: pick, catalogStyle: null, color: pick.color || l.color } : l) }));
  };
  const removeBlankFromLine = (index) => setSel(p => ({ ...p, lines: p.lines.map((l, i) => i === index ? { ...l, blank: null } : l) }));
  const setCatalogForLine = (index, cs) => setSel(p => ({ ...p, lines: p.lines.map((l, i) => i === index ? { ...l, catalogStyle: cs, blank: null, color: cs.color || l.color } : l) }));
  const clearCatalogFromLine = (index) => setSel(p => ({ ...p, lines: p.lines.map((l, i) => i === index ? { ...l, catalogStyle: null } : l) }));

  useEffect(() => {
    api.getPricing().then(setCfg).catch(() => setErr("Couldn't load pricing right now — please try again shortly."));
  }, []);

  const wrap = (inner) => <main className="container" style={{ maxWidth: 860, margin: "0 auto", padding: "32px 20px" }}>{inner}</main>;
  if (err) return wrap(<><h1>Get a quote</h1><p style={{ color: "var(--rose-deep)" }}>{err}</p></>);
  if (!cfg) return wrap(<><h1>Get a quote</h1><p style={{ color: "var(--ink-3)" }}>Loading…</p></>);

  const items = cfg.items || [];
  const st = cfg.stitch || { firstK: 10, addlK: 1.5 };
  const dtf = cfg.dtf || { enabled: false };
  const lines = sel.lines.length ? sel.lines : [{ itemIndex: 0, size: "M", qty: 1, audience: "Unisex", color: "", service: "embroidery", blank: null }];
  const lineSvc = (l) => (dtf.enabled && l.service === "dtf") ? "dtf" : "embroidery";
  const totalQty = Math.max(0, lines.reduce((n, l) => n + (parseInt(l.qty, 10) || 0), 0));
  const overCap = totalQty > HB_MAX_QTY;
  const locations = Math.max(1, Math.min(6, sel.locations));

  const stitches = Math.max(0, parseInt(sel.stitches, 10) || 0);
  const blocks = stitches > 0 ? Math.ceil(stitches / 1000) : 0; // 1,000-stitch blocks (rounded up)
  const embPerLocation = blocks > 0 ? (Number(st.firstK) || 0) + Math.max(0, blocks - 1) * (Number(st.addlK) || 0) : 0;
  const embPerPiece = embPerLocation * locations;
  const dtfPerPiece = Number(dtf.price) || 0;

  const anyEmb = lines.some(l => lineSvc(l) === "embroidery");
  const anyDtf = lines.some(l => lineSvc(l) === "dtf");

  const minPP = Number(cfg.minPerPiece) || 0;
  const lineCalc = lines.map(l => {
    const isCatalog = !!(l.catalogStyle && l.catalogStyle.style);
    const it = items[l.itemIndex] || { type: "—", base: 0 };
    const g = isCatalog ? 0 : (l.blank ? (Number(l.blank.fromPrice) || 0) : (Number(it.base) || 0));
    const label = isCatalog ? `SanMar ${String(l.catalogStyle.style).trim()}` : (l.blank ? hbBlankLabel(l.blank) : it.type);
    const svc = lineSvc(l);
    const spp = svc === "dtf" ? dtfPerPiece : embPerPiece;
    let pp = g + spp;
    const minHit = !isCatalog && minPP && pp < minPP;
    if (minHit) pp = minPP;
    return { it, g, label, blank: l.blank, svc, size: l.size, qty: parseInt(l.qty, 10) || 0, pp, minHit, audience: l.audience, color: l.color, tbd: isCatalog };
  });
  const minApplied = lineCalc.some(c => c.minHit);
  const merch = lineCalc.reduce((sum, c) => sum + (c.tbd ? 0 : c.pp * c.qty), 0);

  // One-time fees: digitizing once if any embroidery + new design; DTF setup once if any DTF.
  const oneTimes = [];
  if (anyEmb && sel.newDesign && Number(cfg.digitizingFee) > 0) oneTimes.push({ label: "One-time digitizing", amount: Number(cfg.digitizingFee) });
  if (anyDtf && Number(dtf.setupFee) > 0) oneTimes.push({ label: "One-time DTF setup", amount: Number(dtf.setupFee) });
  const oneTimeTotal = oneTimes.reduce((s, o) => s + o.amount, 0);

  const hit = (cfg.qtyBreaks || []).filter(b => totalQty >= b.min).sort((a, b) => b.min - a.min)[0];
  const qtyPct = hit ? hit.pct : 0;
  const afterQty = merch * (1 - qtyPct / 100);

  const sale = (cfg.sale && cfg.sale.active) ? cfg.sale : null;
  const salePct = sale ? sale.pct : 0;
  const afterSale = afterQty * (1 - salePct / 100);

  const total = afterSale + oneTimeTotal;

  const row = (label, value, opts = {}, key) => (
    <div key={key} style={{ display: "flex", justifyContent: "space-between", gap: 12, padding: "6px 0", color: opts.accent || (opts.strong ? "var(--ink)" : "var(--ink-2)"), fontSize: opts.strong ? 18 : 15, fontWeight: opts.strong ? 600 : 400 }}>
      <span>{label}</span><span>{value}</span>
    </div>
  );

  return wrap(
    <>
      <h1>Get a quote</h1>
      <p style={{ color: "var(--ink-2)", marginTop: 0 }}>Build a rough estimate in a few clicks.</p>

      {sale && (
        <div className="stitched stitched-sage" style={{ padding: 12, marginBottom: 16 }}>
          <b style={{ color: "var(--sage-deep)" }}>Sale on now{sale.label ? ` — ${sale.label}` : ""}:</b> {sale.pct}% off, already applied below.
        </div>
      )}

      <div className="stitched" style={{ marginBottom: 16 }}>
        <div className="field">
          <label>Items &amp; sizes</label>
          <div style={{ color: "var(--ink-3)", fontSize: 13, marginBottom: 8 }}>Add a row per size or item.{dtf.enabled ? " Pick Embroidery or DTF per item — one order can mix both." : ""}</div>
          {lines.map((l, i) => (
            <div key={i} style={{ marginBottom: 10, paddingBottom: 10, borderBottom: i < lines.length - 1 ? "1px dashed var(--line)" : "none" }}>
            <div style={{ display: "flex", gap: 8, alignItems: "flex-end", flexWrap: "wrap" }}>
              <div className="field" style={{ flex: "2 1 200px", marginBottom: 0 }}>
                {i === 0 && <label style={{ fontSize: 12 }}>Item</label>}
                {l.blank
                  ? <input type="text" value={hbBlankLabel(l.blank)} readOnly title="From SanMar blanks" style={{ width: "100%" }} />
                  : <select value={l.itemIndex} onChange={e => setLine(i, "itemIndex", Number(e.target.value))}>
                      {items.map((it, idx) => <option key={idx} value={idx}>{it.type}{it.base > 0 ? ` — from ${fmtPrice(it.base)}` : ""}</option>)}
                    </select>}
              </div>
              {dtf.enabled && (
                <div className="field" style={{ flex: "1 1 120px", marginBottom: 0 }}>
                  {i === 0 && <label style={{ fontSize: 12 }}>Method</label>}
                  <select value={l.service} onChange={e => setLine(i, "service", e.target.value)}>
                    <option value="embroidery">Embroidery</option>
                    <option value="dtf">{dtf.label || "DTF transfer"}</option>
                  </select>
                </div>
              )}
              <div className="field" style={{ flex: "1 1 110px", marginBottom: 0 }}>
                {i === 0 && <label style={{ fontSize: 12 }}>Fit</label>}
                <select value={l.audience} onChange={e => setLine(i, "audience", e.target.value)}>
                  {HB_AUDIENCES.map(a => <option key={a}>{a}</option>)}
                </select>
              </div>
              <div className="field" style={{ flex: "1 1 130px", marginBottom: 0 }}>
                {i === 0 && <label style={{ fontSize: 12 }}>Color</label>}
                <select value={l.color} onChange={e => setLine(i, "color", e.target.value)}>
                  <option value="">Choose…</option>
                  {(l.color && !HB_COLORS.includes(l.color) ? [l.color, ...HB_COLORS] : HB_COLORS).map(c => <option key={c}>{c}</option>)}
                </select>
              </div>
              <div className="field" style={{ flex: "1 1 90px", marginBottom: 0 }}>
                {i === 0 && <label style={{ fontSize: 12 }}>Size</label>}
                <select value={l.size} onChange={e => setLine(i, "size", e.target.value)}>
                  {HB_SIZES.map(sz => <option key={sz}>{sz}</option>)}
                </select>
              </div>
              <div className="field" style={{ flex: "0 1 80px", marginBottom: 0 }}>
                {i === 0 && <label style={{ fontSize: 12 }}>Qty</label>}
                <select value={l.qty} onChange={e => setLine(i, "qty", Number(e.target.value))}>
                  {Array.from({ length: HB_MAX_QTY }, (_, n) => n + 1).map(n => <option key={n} value={n}>{n}</option>)}
                </select>
              </div>
              <button type="button" className="btn btn-small btn-ghost" disabled={lines.length <= 1} onClick={() => removeLine(i)} title="Remove" style={{ marginBottom: 0 }}>×</button>
            </div>
            <BlankLineControl line={l} onBrowse={() => setPickerFor(i)} onRemove={() => removeBlankFromLine(i)}
              onSetCatalog={(cs) => setCatalogForLine(i, cs)} onClearCatalog={() => clearCatalogFromLine(i)} catalogUrl={catalogUrl} />
            </div>
          ))}
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 8 }}>
            <button type="button" className="btn btn-small btn-ghost" onClick={addLine} disabled={lines.length >= HB_MAX_ITEMS}>+ Add another size / item</button>
            <span style={{ fontSize: 13, color: "var(--ink-2)" }}>Total: <b>{totalQty}</b> {totalQty === 1 ? "piece" : "pieces"} · {lines.length}/{HB_MAX_ITEMS} items</span>
          </div>
          {lines.length >= HB_MAX_ITEMS && (
            <div style={{ color: "var(--ink-2)", fontSize: 13, marginTop: 8 }}>
              You've reached the {HB_MAX_ITEMS}-item max for online orders — for more variety in one order, <a href="#/contact">contact Joy</a>.
            </div>
          )}
          {lines.some(l => l.color === HB_OTHER_COLOR) && (
            <div style={{ color: "var(--ink-2)", fontSize: 13, marginTop: 8 }}>
              For a color that isn't listed, choose <b>Other</b> and type the exact color in the notes when you start your order.
            </div>
          )}
        </div>
        {anyEmb && (
          <>
            <div className="field-row">
              <div className="field">
                <label>Stitch count {stitches > 0 ? <span style={{ color: "var(--ink-3)" }}>(~{stitches.toLocaleString()} = {blocks} × 1k)</span> : null}</label>
                <input type="number" min="0" step="500" value={sel.stitches} onChange={e => s("stitches", e.target.value)} />
                <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 8 }}>
                  {(cfg.stitchPresets || []).map((p, i) => (
                    <button key={i} type="button" className="btn btn-ghost btn-small" onClick={() => s("stitches", p.stitches)}>{p.label}</button>
                  ))}
                </div>
                <div style={{ color: "var(--ink-3)", fontSize: 13, marginTop: 6 }}>
                  Applies to embroidered items. Not sure? Pick the closest above — Joy confirms the exact count from your file. ({fmtPrice(st.firstK)} first 1,000 stitches, then {fmtPrice(st.addlK)} per 1,000.)
                </div>
              </div>
              <div className="field">
                <label>Embroidery locations</label>
                <select value={locations} onChange={e => s("locations", Number(e.target.value))}>
                  {[1, 2, 3, 4].map(n => <option key={n} value={n}>{n} {n === 1 ? "location" : "locations"}</option>)}
                </select>
              </div>
            </div>
            <label style={{ display: "flex", alignItems: "center", gap: 8, color: "var(--ink-2)" }}>
              <input type="checkbox" checked={sel.newDesign} onChange={e => s("newDesign", e.target.checked)} />
              Brand-new design (one-time digitizing {fmtPrice(cfg.digitizingFee)})
            </label>
          </>
        )}
        {anyDtf && (
          <div style={{ color: "var(--ink-2)", fontSize: 14, marginTop: anyEmb ? 12 : 0 }}>
            {dtf.label || "DTF transfer"}: <b>{fmtPrice(dtf.price)}</b> each{Number(dtf.setupFee) > 0 ? <>, plus a one-time <b>{fmtPrice(dtf.setupFee)}</b> setup fee</> : ""}.
          </div>
        )}
      </div>

      <div className="stitched stitched-sage">
        <h2 style={{ marginTop: 0 }}>Your estimate</h2>
        {lineCalc.map((c, i) => row(
          `${c.label}${c.tbd ? "" : (c.g ? "" : " (you provide)")} — ${[c.audience, c.color].filter(Boolean).join(", ")}${(c.audience || c.color) ? " · " : ""}${c.svc === "dtf" ? "DTF" : "Embroidery"} — ${c.size} × ${c.qty}`,
          c.tbd ? "Joy will confirm" : fmtPrice(c.pp * c.qty), {}, i)
        )}
        {lineCalc.some(c => c.tbd) ? row("(full-catalog items are priced by Joy — not in this estimate)", "") : null}
        {minApplied ? row(`(minimum ${fmtPrice(cfg.minPerPiece)}/piece applied)`, "") : null}
        {qtyPct > 0 ? row(`Bulk discount (${qtyPct}% off ${totalQty}+)`, `– ${fmtPrice(merch - afterQty)}`, { accent: "var(--sage-deep)" }) : null}
        {salePct > 0 ? row(`Sale (${salePct}% off)`, `– ${fmtPrice(afterQty - afterSale)}`, { accent: "var(--sage-deep)" }) : null}
        {oneTimes.map((o, i) => row(o.label, fmtPrice(o.amount), {}, `ot${i}`))}
        <div style={{ borderTop: "1px dashed var(--ink-3)", margin: "8px 0" }} />
        {row("Estimated total", fmtPrice(total), { strong: true })}
        {overCap && (
          <div className="stitched" style={{ padding: 12, marginTop: 12 }}>
            <b style={{ color: "var(--sage-deep)" }}>Ordering more than {HB_MAX_QTY} pieces?</b> This estimate still gives you a ballpark, but larger
            runs qualify for better pricing and need a quick stock check — <a href="#/contact">contact Joy</a> for an exact quote.
          </div>
        )}
        {(() => {
          const so = cfg.sewout || {};
          if (!(Number(so.fee) > 0)) return null;
          const text = (so.note || "").trim() || `The first sew-out is free. Any additional updates that require a new sew-out are ${fmtPrice(so.fee)} each.`;
          return (
            <div className="stitched stitched-sage" style={{ marginTop: 12, padding: 12, fontSize: 14, color: "var(--ink-2)" }}>
              <span className="smallcaps" style={{ color: "var(--sage-deep)", fontSize: 11, marginRight: 8 }}>Good to know</span>
              {text}
            </div>
          );
        })()}
        <p style={{ color: "var(--ink-3)", fontSize: 13, marginTop: 10 }}>{cfg.disclaimer}</p>
        <button type="button" className="btn" style={{ marginTop: 6 }}
          onClick={() => {
            try {
              window.HB_QUOTE_PICK = {
                estimate: total,
                lines: lines.map(l => ({ itemType: l.catalogStyle && l.catalogStyle.style ? `SanMar ${String(l.catalogStyle.style).trim()}` : (l.blank ? hbBlankLabel(l.blank) : ((items[l.itemIndex] || {}).type || "Other (tell me below)")), size: l.size, qty: parseInt(l.qty, 10) || 1, audience: l.audience || "Unisex", color: l.color || "", service: lineSvc(l), blank: l.blank || null, catalogStyle: l.catalogStyle || null })),
              };
            } catch (e) {}
            location.hash = "#/upload";
          }}>Start your order →</button>
      </div>
      {pickerFor !== null && <BlankPickerModal onClose={() => setPickerFor(null)} onPick={(p) => { applyBlankToLine(pickerFor, p); setPickerFor(null); }} />}
    </>
  );
}

// ---------- Post-payment thank-you (return page from QuickBooks) ----------
// Where a customer lands after paying their QuickBooks invoice. Works with no
// params; if the payment link includes ?ref=S-1234 (or ?order=), it's shown.
function ThankYouPage() {
  const ref = (() => {
    const m = location.hash.match(/[?&](?:ref|order|invoice)=([^&]+)/i);
    return m ? decodeURIComponent(m[1]) : "";
  })();
  return (
    <main className="container" style={{ maxWidth: 640, margin: "0 auto", padding: "48px 20px" }}>
      <div className="stitched stitched-sage" style={{ textAlign: "center", padding: 36 }}>
        <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 8 }}>Payment received</div>
        <h1 style={{ marginTop: 0 }}>Thank you for your purchase!</h1>
        <p style={{ color: "var(--ink-2)", maxWidth: 460, margin: "0 auto" }}>
          Your payment came through and we're so grateful for your support.
          {ref ? <> Your reference is <b className="mono">{ref}</b>.</> : null}
        </p>
        <p style={{ color: "var(--ink-2)", maxWidth: 460, margin: "12px auto 0" }}>
          Joy will be in touch as your order moves along, and your receipt is available any
          time under <b>Invoices &amp; receipts</b> in your account.
        </p>
        <div style={{ display: "flex", gap: 10, justifyContent: "center", flexWrap: "wrap", marginTop: 22 }}>
          <a className="btn" href="#/account">View my account</a>
          <a className="btn btn-ghost" href="#/shop">Keep shopping</a>
        </div>
      </div>
      <p style={{ textAlign: "center", color: "var(--ink-3)", fontSize: 13, marginTop: 16 }}>
        Questions about your order? <a href="#/contact">Get in touch</a>.
      </p>
    </main>
  );
}

// ---------------------------------------------------------------------------
// Shop blanks — browse the SanMar blank catalog (visible, in-stock styles).
// Picking one routes to Upload a design to start a decorated order.
// ---------------------------------------------------------------------------
function BlanksPage() {
  const [q, setQ] = useState("");
  const [page, setPage] = useState(1);
  const [data, setData] = useState({ total: 0, page: 1, pageSize: 24, styles: [] });
  const [loading, setLoading] = useState(true);
  const [err, setErr] = useState("");
  const [open, setOpen] = useState(null); // styleNo

  const load = () => {
    setLoading(true); setErr("");
    const qs = new URLSearchParams();
    if (q) qs.set("q", q);
    qs.set("page", String(page));
    api.sanmarShop(qs.toString())
      .then(setData)
      .catch(e => setErr(e.message || "The blank catalog isn't available right now."))
      .finally(() => setLoading(false));
  };
  useEffect(() => { load(); }, [page]);
  useEffect(() => { setPage(1); load(); }, [q]);

  const totalPages = Math.max(1, Math.ceil((data.total || 0) / (data.pageSize || 24)));

  return (
    <main className="page section">
      <div style={{ textAlign: "center" }}>
        <div className="smallcaps" style={{ color: "var(--sage-deep)", marginBottom: 8 }}>Blanks we decorate</div>
        <h1>Shop <span className="script" style={{ color: "var(--rose-deep)" }}>blanks</span></h1>
        <p className="hero-lede" style={{ margin: "14px auto 0" }}>
          Pick a blank to be embroidered or printed — these are the garments we decorate.
          Choose one and upload your design to get started, and Joy will put together a quote for you.
        </p>
      </div>

      <div style={{ maxWidth: 420, margin: "0 auto 18px" }}>
        <input placeholder="Search by style, brand or product…" value={q} onChange={e => setQ(e.target.value)} style={{ width: "100%" }} />
      </div>

      {err && <p style={{ color: "var(--rose-deep)", textAlign: "center" }}>{err}</p>}
      {loading ? <p style={{ color: "var(--ink-3)", textAlign: "center" }}>Loading the catalog…</p> : (
        <>
          {data.styles.length === 0 ? (
            <p style={{ color: "var(--ink-3)", textAlign: "center" }}>No blanks match that search.</p>
          ) : (
            <div className="card-grid">
              {data.styles.map(s => (
                <button key={s.styleNo} className="stitched" onClick={() => setOpen(s.styleNo)}
                  style={{ textAlign: "left", padding: 0, overflow: "hidden", cursor: "pointer", display: "flex", flexDirection: "column" }}>
                  <div style={{ aspectRatio: "1 / 1", background: "var(--paper-2)" }}>
                    <BlankImg src={s.image} alt={hbDecode(s.title)} label={s.styleNo} />
                  </div>
                  <div style={{ padding: 12 }}>
                    <div style={{ fontSize: 12, color: "var(--ink-3)" }}>{s.brand}</div>
                    <div style={{ fontWeight: 600, fontSize: 14, lineHeight: 1.3 }}>{hbDecode(s.title)}</div>
                  </div>
                </button>
              ))}
            </div>
          )}

          {totalPages > 1 && (
            <div style={{ display: "flex", gap: 8, justifyContent: "center", alignItems: "center", marginTop: 20 }}>
              <button className="btn btn-small btn-ghost" disabled={page <= 1} onClick={() => setPage(p => Math.max(1, p - 1))}>← Prev</button>
              <span style={{ fontSize: 13, color: "var(--ink-2)" }}>{page} / {totalPages}</span>
              <button className="btn btn-small btn-ghost" disabled={page >= totalPages} onClick={() => setPage(p => p + 1)}>Next →</button>
            </div>
          )}
        </>
      )}

      {open && <BlankDetail styleNo={open} onClose={() => setOpen(null)} />}
    </main>
  );
}

function BlankDetail({ styleNo, onClose }) {
  const [data, setData] = useState(null);
  const [err, setErr] = useState("");
  const [color, setColor] = useState(0);
  useEffect(() => {
    api.sanmarShopStyle(styleNo)
      .then(d => { setData(d); setColor(0); })
      .catch(e => setErr(e.message || "Couldn't load that blank."));
  }, [styleNo]);

  const startOrder = () => {
    // Remember the chosen blank so the order/quote flow can reference it.
    try {
      const c = data?.colors?.[color];
      window.HB_BLANK_PICK = {
        styleNo: data.styleNo, title: data.title, brand: data.brand,
        category: data.category || "", color: c ? c.color : "",
        fromPrice: data.fromPrice || 0, image: c?.image || data.image || "",
      };
    } catch (e) {}
    location.hash = "#/upload";
  };

  const c = data?.colors?.[color];
  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, background: "rgba(58,42,38,0.5)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000, padding: 16 }}>
      <div onClick={e => e.stopPropagation()} className="stitched" style={{ background: "var(--card)", maxWidth: 640, width: "100%", maxHeight: "88vh", overflow: "auto", padding: 20 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
          <h2 style={{ margin: 0 }}>{data ? hbDecode(data.title) : styleNo}</h2>
          <button className="btn btn-small btn-ghost" onClick={onClose}>Close ×</button>
        </div>
        {err && <p style={{ color: "var(--rose-deep)" }}>{err}</p>}
        {!data && !err && <p style={{ color: "var(--ink-3)" }}>Loading…</p>}
        {data && (
          <>
            <p style={{ color: "var(--ink-3)", fontSize: 13, marginTop: 4 }}>{data.brand} {data.styleNo}</p>
            <div style={{ display: "grid", gridTemplateColumns: "minmax(0,1fr) minmax(0,1fr)", gap: 16, alignItems: "start" }}>
              <div style={{ aspectRatio: "1 / 1", background: "var(--paper-2)", borderRadius: 10, overflow: "hidden" }}>
                <BlankImg src={c?.image || data.image} alt={hbDecode(data.title)} label={data.styleNo} />
              </div>
              <div>
                {data.description && <p style={{ fontSize: 13, color: "var(--ink-2)", marginTop: 0 }}>{hbDecode(data.description)}</p>}
                {data.colors?.length > 0 && (
                  <>
                    <div className="smallcaps" style={{ color: "var(--sage-deep)", fontSize: 11 }}>Color · {c?.color}</div>
                    <div style={{ display: "flex", flexWrap: "wrap", gap: 6, margin: "6px 0 12px" }}>
                      {data.colors.map((col, i) => (
                        <button key={col.color} title={col.color} onClick={() => setColor(i)}
                          style={{ width: 26, height: 26, borderRadius: "50%", border: i === color ? "2px solid var(--sage-deep)" : "1px solid var(--line)", padding: 0, overflow: "hidden", background: hbColorHex(col.color), cursor: "pointer" }}>
                          <BlankImg src={col.swatch} alt="" round bare />
                        </button>
                      ))}
                    </div>
                  </>
                )}
                {c && (
                  <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
                    {c.sizes.map(sz => (
                      <span key={sz.uniqueKey} style={{ fontSize: 12, padding: "3px 9px", borderRadius: 6, border: "1px solid var(--line)", color: sz.inStock ? "var(--ink)" : "var(--ink-3)", background: sz.inStock ? "var(--paper-2)" : "transparent" }}>
                        {sz.size}{sz.inStock ? "" : " · out"}
                      </span>
                    ))}
                  </div>
                )}
              </div>
            </div>
            <div style={{ marginTop: 16, display: "flex", gap: 10, flexWrap: "wrap" }}>
              <button className="btn" onClick={startOrder}>Start an order with this blank →</button>
              <a className="btn btn-ghost" href="#/quote">Get a quote first</a>
            </div>
            <p style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 10 }}>
              Stock and pricing come straight from SanMar and can change — we confirm both when we quote your order.
            </p>
          </>
        )}
      </div>
    </div>
  );
}
