// VELOUR Wardrobe — Step 4: Events & Budget → smart purchases.
// Add key events + set a budget; Claude recommends gap-filling pieces within budget,
// each with a projected cost-per-wear and a shop link. Closes the "shop your capsule" loop.
const { Card } = window.VelourDesignSystem_380ed4;

const FRESH = !!window.VELOUR_FRESH;
const EVENTS_KEY = FRESH ? "velour_fresh_events_v1" : "velour_events_v1";
const BUDGET_KEY = FRESH ? "velour_fresh_budget_v1" : "velour_budget_v1";

const DRESS_CODES = ["Casual", "Smart-casual", "Cocktail", "Formal / black-tie", "Work"];
const SEED_EVENTS = [
  { id: "e1", name: "Claire's wedding", date: "2026-07-11", dress: "Formal / black-tie" },
  { id: "e2", name: "Quarterly board review", date: "2026-06-24", dress: "Work" },
];

function loadJSON(key, fallback) { try { const r = localStorage.getItem(key); if (r) return JSON.parse(r); } catch (e) {} return fallback; }
function fmtDate(d) { try { return new Date(d + "T00:00").toLocaleDateString("en-US", { month: "short", day: "numeric" }); } catch (e) { return d; } }
function daysUntil(d) { const ms = new Date(d + "T00:00") - new Date(); return Math.ceil(ms / 86400000); }

// Ask the real advisor. The prompt lives server-side in api/advisor.js — the client
// sends the closet, the event and the budget, never a prompt, so the endpoint can't
// be used as an open LLM proxy. Web and mobile call this same endpoint.
async function recommendPieces(closet, event, remaining, gap) {
  // Return the whole envelope, not just the list: `omitted` tells us whether the
  // closet was too large to send in full, which the member is entitled to know.
  const out = await window.velourAiFetch("/api/advisor", {
    remaining,
    gap: gap || null,
    event: event ? { name: event.name, dress: event.dress, date: event.date } : null,
    items: closet.map((it) => ({ name: it.name, category: it.category })),
  });
  return { recommendations: out.recommendations || [], omitted: out.omitted || 0 };
}

// Google Shopping, capped at the budget. `ppr_max` is Google's max-price filter.
// THE QUERY IS SHORTENED FIRST — see velourShopQuery in _ds_bundle.js. It used to
// send the recommendation's full prose name, which Shopping answers with almost
// nothing before falling back to web results. Both surfaces call the same helper.
function shopUrl(name, price, category) {
  const q = `https://www.google.com/search?tbm=shop&q=${encodeURIComponent(window.velourShopQuery(name, category))}`;
  return price > 0 ? `${q}&tbs=${encodeURIComponent("mr:1,price:1,ppr_max:" + Math.ceil(price))}` : q;
}

// Retailer choices for a suggested piece — plain search links, no AI needed.
//
// Two things each entry carries beyond a URL:
//
// A PRICE BAND. The same four shops used to appear for every recommendation, so an
// $85 piece offered Nordstrom — where $85 outerwear barely exists — and every result
// was over budget. A shop now only appears when it plausibly sells at that price.
//
// A BUDGET CAP, where the shop's own URL supports one. Amazon's `p_36` filter (in
// cents) and price-ascending sort were both verified against the live site: a
// $85 cap returned nothing above $60.99 out of 5,000 results. Google's `ppr_max` is
// applied but could not be verified here — automated requests hit its bot check.
// Nordstrom and Shein get no parameter at all rather than a guessed one: a wrong
// filter usually returns zero results, which reads as "nothing exists in your
// budget" and is worse than an unfiltered list. The band does the work there.
const RETAILERS = [
  { label: "Shein", max: 50, url: (q) => `https://www.shein.com/pdsearch/${encodeURIComponent(q)}` },
  { label: "Amazon", max: 250, filters: true,
    url: (q, p) => `https://www.amazon.com/s?k=${encodeURIComponent(q)}` +
      (p > 0 ? `&rh=${encodeURIComponent("p_36:-" + Math.round(p * 100))}&s=price-asc-rank` : "") },
  { label: "Nordstrom", min: 120, url: (q) => `https://www.nordstrom.com/sr?keyword=${encodeURIComponent(q)}` },
  { label: "Google", filters: true, url: (q, p, cat) => shopUrl(q, p, cat) },
];
function retailersFor(price) {
  const p = Number(price) || 0;
  return RETAILERS.filter((r) => (r.min == null || p >= r.min) && (r.max == null || p <= r.max));
}

// Local fallback recommender — suggests staples only for categories the closet
// is actually missing (a real gap), within brand voice. Swapped for the AI model
// (recommendPieces) in production; returns [] when there's no genuine gap.
const GAP_SUGGESTIONS = {
  Tops: { name: "White cotton button-down", price: 45, wears: 90, why: "A clean neutral top anchors nearly every outfit — the highest-leverage gap to close." },
  Denim: { name: "Straight-leg dark denim", price: 60, wears: 120, why: "One well-cut pair quietly outworks three trend pairs." },
  Outerwear: { name: "Tailored wool-blend blazer", price: 90, wears: 70, why: "Lifts a casual capsule to put-together in a single layer." },
  Shoes: { name: "Leather ankle boots", price: 95, wears: 80, why: "Bridges day to evening and pairs with most of what you own." },
  Dresses: { name: "Simple midi dress", price: 80, wears: 45, why: "One easy dress removes the hardest what-do-I-wear mornings." },
  "Trousers & skirts": { name: "Black tailored trousers", price: 75, wears: 90, why: "One sharp pair takes a plain top from day to dinner." },
  Knits: { name: "Merino crew sweater", price: 70, wears: 65, why: "Warm, layerable, and quietly elevates denim." },
};
function recommendLocally(closet, event) {
  const count = {};
  closet.forEach((it) => { count[it.category] = (count[it.category] || 0) + 1; });
  const dc = ((event && event.dress) || "").toLowerCase();
  let priority;
  if (event && /formal|black-tie|cocktail/.test(dc)) priority = ["Dresses", "Shoes", "Outerwear"];
  else if (event && /work/.test(dc)) priority = ["Outerwear", "Tops", "Shoes"];
  else if (event) priority = ["Tops", "Dresses", "Shoes"];
  else priority = ["Tops", "Denim", "Outerwear", "Shoes"];
  return priority.filter((c) => !count[c]).slice(0, 3).map((c) => ({ name: GAP_SUGGESTIONS[c].name, category: c, price: GAP_SUGGESTIONS[c].price, estimatedWearsPerYear: GAP_SUGGESTIONS[c].wears, why: GAP_SUGGESTIONS[c].why }));
}

function WardrobeBudget({ items, onNavigate }) {
  const [events, setEvents] = React.useState(() => loadJSON(EVENTS_KEY, FRESH ? [] : SEED_EVENTS));
  const [budget, setBudget] = React.useState(() => loadJSON(BUDGET_KEY, { total: FRESH ? 0 : 400, planned: [] }));
  const [adding, setAdding] = React.useState(false);
  const [draft, setDraft] = React.useState({ name: "", date: "", dress: "Smart-casual" });
  const [selEvent, setSelEvent] = React.useState("");
  const [recs, setRecs] = React.useState(null);
  const [omitted, setOmitted] = React.useState(0);
  // A gap handed down from the outfit formulas above. The plus tile on a missing slot
  // dispatches velour:fill-gap; we scroll ourselves into view and lead with that category.
  const [gapFocus, setGapFocus] = React.useState(null);
  const rootRef = React.useRef(null);
  React.useEffect(() => {
    const onFill = (e) => {
      const d = (e && e.detail) || {};
      if (!d.label) return;
      setGapFocus({ category: d.category || null, label: d.label });
      if (rootRef.current) {
        const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
        rootRef.current.scrollIntoView({ behavior: reduce ? "auto" : "smooth", block: "start" });
      }
    };
    window.addEventListener("velour:fill-gap", onFill);
    return () => window.removeEventListener("velour:fill-gap", onFill);
  }, []);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);
  // Events and the budget travel with the wardrobe pillar, so they land here too.
  if (window.useVelourSynced) window.useVelourSynced("wardrobe", () => {
    setEvents(loadJSON(EVENTS_KEY, FRESH ? [] : SEED_EVENTS));
    setBudget(loadJSON(BUDGET_KEY, { total: FRESH ? 0 : 400, planned: [] }));
  });

  React.useEffect(() => { try { localStorage.setItem(EVENTS_KEY, JSON.stringify(events)); } catch (e) {} }, [events]);
  React.useEffect(() => { try { localStorage.setItem(BUDGET_KEY, JSON.stringify(budget)); } catch (e) {} }, [budget]);

  const plan = window.useVelourPlan ? window.useVelourPlan() : { budgetAdvisor: true };
  // After every hook above, so the gate never changes the hook order between renders.
  if (!plan.budgetAdvisor) {
    return <window.UpgradePrompt feature="budgetAdvisor" onNavigate={onNavigate} />;
  }

  const spent = budget.planned.reduce((s, p) => s + p.price, 0);
  const remaining = Math.max(0, budget.total - spent);
  const upcoming = [...events].sort((a, b) => new Date(a.date) - new Date(b.date));

  const addEvent = () => {
    if (!draft.name.trim() || !draft.date) return;
    setEvents([...events, { ...draft, id: "e" + Math.random().toString(36).slice(2, 7) }]);
    setDraft({ name: "", date: "", dress: "Smart-casual" }); setAdding(false);
  };
  const removeEvent = (id) => setEvents(events.filter((e) => e.id !== id));

  const findPieces = async () => {
    if (loading) return;
    setLoading(true); setError(null); setRecs(null); setOmitted(0);
    try {
      const ev = events.find((e) => e.id === selEvent) || null;
      let r = null;
      // Try the real advisor; fall back to the deterministic local recommender on any
      // failure (endpoint missing in local dev, API key unset, model unavailable).
      try { r = await recommendPieces(items, ev, remaining, gapFocus && gapFocus.category); } catch (e) { r = null; }
      // The local fallback sees the whole closet, so nothing is ever omitted there.
      if (!r) { await new Promise((res) => setTimeout(res, 500)); r = { recommendations: recommendLocally(items, ev), omitted: 0 }; }
      setOmitted(r.omitted || 0);
      setRecs(r.recommendations.map((x) => ({ ...x, id: "r" + Math.random().toString(36).slice(2, 7), cpw: x.estimatedWearsPerYear > 0 ? x.price / x.estimatedWearsPerYear : null })));
    } catch (e) {
      setError("Couldn't draft picks just now. Try again in a moment.");
    } finally { setLoading(false); }
  };

  const planBuy = (rec) => {
    if (budget.planned.some((p) => p.id === rec.id)) return;
    setBudget({ ...budget, planned: [...budget.planned, { id: rec.id, name: rec.name, price: rec.price }] });
  };
  const unplan = (id) => setBudget({ ...budget, planned: budget.planned.filter((p) => p.id !== id) });
  // A planned price starts as VELOUR's estimate. What the member actually pays is the
  // only number that keeps the budget honest, so it has to be editable — otherwise a
  // $90 estimate spent at $50 quietly removes $40 of real budget.
  const setPlannedPrice = (id, v) => {
    const price = Math.max(0, Math.round(Number(v) || 0));
    setBudget({ ...budget, planned: budget.planned.map((p) => (p.id === id ? { ...p, price } : p)) });
  };

  const pct = budget.total > 0 ? Math.min(100, (spent / budget.total) * 100) : 0;

  return (
    <div ref={rootRef}>
      <div style={{ margin: "8px 2px 16px" }}>
        <h2 style={{ fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 24, color: "var(--text-primary)", margin: 0, letterSpacing: "-0.01em" }}>Plan ahead</h2>
        <p style={{ fontSize: 14, color: "var(--text-secondary)", margin: "6px 0 0" }}>Add what's coming up and set a budget. VELOUR only suggests a purchase when your capsule truly has a gap.</p>
      </div>

      {gapFocus && (
        <div style={{ display: "flex", gap: 11, alignItems: "flex-start", background: "var(--amber-fill)", borderRadius: 13, padding: "14px 16px", marginBottom: 16 }}>
          <i className="ph ph-arrow-down-right" style={{ fontSize: 18, color: "var(--amber)", marginTop: 1 }}></i>
          <div style={{ fontSize: 14, color: "var(--amber-text)", lineHeight: 1.55, flex: 1 }}>
            Looking for a {gapFocus.label.toLowerCase()} to finish that look. Set a budget below and VELOUR will lead with it.
          </div>
          <button onClick={() => setGapFocus(null)} title="Clear" style={{ background: "none", border: "none", padding: 0, cursor: "pointer", color: "var(--amber-text)", fontSize: 16, lineHeight: 1 }}>
            <i className="ph ph-x"></i>
          </button>
        </div>
      )}

      <div style={{ display: "grid", gridTemplateColumns: "1.3fr 1fr", gap: 16, marginBottom: 16 }}>
        {/* Events */}
        <Card padding={22}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 16 }}>
            <span style={{ fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--amber-text)", fontWeight: 500 }}>Key events</span>
            <button onClick={() => setAdding((v) => !v)} style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "none", border: "none", cursor: "pointer", color: "var(--amber-text)", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 500 }}><i className={`ph ph-${adding ? "x" : "plus"}`} style={{ fontSize: 14 }}></i>{adding ? "Close" : "Add event"}</button>
          </div>

          {adding && (
            <div style={{ background: "var(--surface-sunken)", borderRadius: 12, padding: 14, marginBottom: 14, display: "flex", flexDirection: "column", gap: 10 }}>
              <input value={draft.name} onChange={(e) => setDraft({ ...draft, name: e.target.value })} placeholder="What's the occasion?" style={{ padding: "10px 12px", borderRadius: 9, border: "var(--border-width) solid var(--border)", background: "var(--surface-card)", fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-primary)", outline: "none" }} />
              <div style={{ display: "flex", gap: 10 }}>
                <input type="date" value={draft.date} onChange={(e) => setDraft({ ...draft, date: e.target.value })} style={{ flex: 1, padding: "10px 12px", borderRadius: 9, border: "var(--border-width) solid var(--border)", background: "var(--surface-card)", fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-primary)", outline: "none" }} />
                <select value={draft.dress} onChange={(e) => setDraft({ ...draft, dress: e.target.value })} style={{ flex: 1, padding: "10px 12px", borderRadius: 9, border: "var(--border-width) solid var(--border)", background: "var(--surface-card)", fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-primary)", outline: "none" }}>
                  {DRESS_CODES.map((c) => <option key={c} value={c}>{c}</option>)}
                </select>
              </div>
              <button onClick={addEvent} style={{ alignSelf: "flex-start", background: "var(--ink)", color: "var(--parchment)", border: "none", borderRadius: 999, padding: "9px 18px", fontFamily: "var(--font-sans)", fontSize: 13.5, fontWeight: 500, cursor: "pointer" }}>Add</button>
            </div>
          )}

          <div style={{ display: "flex", flexDirection: "column" }}>
            {upcoming.length === 0 && <div style={{ fontSize: 13.5, color: "var(--text-tertiary)", padding: "8px 0" }}>Nothing on the calendar yet.</div>}
            {upcoming.map((e, i) => {
              const d = daysUntil(e.date);
              return (
                <div key={e.id} className="vr-item" style={{ display: "flex", alignItems: "center", gap: 13, padding: "13px 0", borderTop: i === 0 ? "none" : "var(--border-width) solid var(--divider)" }}>
                  <div style={{ width: 44, height: 44, borderRadius: 11, flexShrink: 0, background: "var(--amber-fill)", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", lineHeight: 1 }}>
                    <span style={{ fontSize: 15, fontFamily: "var(--font-serif)", color: "var(--amber-text)" }}>{fmtDate(e.date).split(" ")[1]}</span>
                    <span style={{ fontSize: 9.5, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--amber-text)", opacity: 0.8 }}>{fmtDate(e.date).split(" ")[0]}</span>
                  </div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 14.5, color: "var(--text-primary)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{e.name}</div>
                    <div style={{ fontSize: 12.5, color: "var(--text-tertiary)", marginTop: 2 }}>{e.dress}{d >= 0 ? ` · in ${d} day${d === 1 ? "" : "s"}` : " · past"}</div>
                  </div>
                  <button onClick={() => removeEvent(e.id)} aria-label="Remove event" className="vr-item-actions" style={{ width: 26, height: 26, borderRadius: "50%", border: "none", background: "var(--surface-sunken)", color: "var(--text-secondary)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}><i className="ph ph-x" style={{ fontSize: 12 }}></i></button>
                </div>
              );
            })}
          </div>
        </Card>

        {/* Budget */}
        <Card padding={22} style={{ display: "flex", flexDirection: "column" }}>
          <span style={{ fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--amber-text)", fontWeight: 500, marginBottom: 16 }}>New-pieces budget</span>
          <div style={{ display: "flex", alignItems: "baseline", gap: 6 }}>
            <span style={{ fontFamily: "var(--font-serif)", fontSize: 34, color: "var(--text-primary)", letterSpacing: "-0.01em" }}>${remaining}</span>
            <span style={{ fontSize: 13, color: "var(--text-tertiary)", marginLeft: 2 }}>left of ${budget.total}</span>
          </div>
          <div style={{ height: 8, borderRadius: 999, background: "var(--surface-sunken)", marginTop: 14, overflow: "hidden" }}>
            <div style={{ height: "100%", width: `${pct}%`, background: "var(--amber)", borderRadius: 999, transition: "width var(--dur-base) var(--ease-out)" }}></div>
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 16 }}>
            <span style={{ fontSize: 12.5, color: "var(--text-tertiary)" }}>Set budget</span>
            <input type="range" min="0" max="2000" step="50" value={budget.total} onChange={(e) => setBudget({ ...budget, total: Number(e.target.value) })} style={{ flex: 1, accentColor: "var(--amber)" }} />
          </div>
          {budget.planned.length > 0 && (
            <div style={{ marginTop: 16, paddingTop: 14, borderTop: "var(--border-width) solid var(--divider)" }}>
              <div style={{ fontSize: 11.5, color: "var(--text-tertiary)", marginBottom: 8 }}>Planned</div>
              {budget.planned.map((p) => (
                <div key={p.id} style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 13.5, color: "var(--text-primary)", padding: "4px 0" }}>
                  <span style={{ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p.name}</span>
                  <span style={{ color: "var(--text-tertiary)" }}>$</span>
                  <input type="number" min="0" step="1" value={p.price}
                    onChange={(e) => setPlannedPrice(p.id, e.target.value)}
                    aria-label={`What you paid for ${p.name}`} title="Change this to what you actually paid"
                    style={{ width: 62, textAlign: "right", padding: "3px 6px", borderRadius: 7, border: "var(--border-width) solid var(--border)", background: "var(--surface-card)", fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-primary)", outline: "none" }} />
                  <button onClick={() => unplan(p.id)} aria-label="Remove planned" style={{ border: "none", background: "none", cursor: "pointer", color: "var(--text-tertiary)", display: "flex", padding: 2 }}><i className="ph ph-x" style={{ fontSize: 12 }}></i></button>
                </div>
              ))}
              <div style={{ fontSize: 11.5, color: "var(--text-tertiary)", marginTop: 8, lineHeight: 1.45 }}>Prices start as VELOUR's estimate. Change one to what you actually paid and the budget follows.</div>
            </div>
          )}
        </Card>
      </div>

      {/* Smart picks */}
      <Card padding={26}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 14, marginBottom: recs || loading || error ? 20 : 0 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 11 }}>
            <i className="ph ph-shopping-bag-open" style={{ fontSize: 20, color: "var(--amber)" }}></i>
            <div>
              <div style={{ fontFamily: "var(--font-serif)", fontSize: 19, color: "var(--text-primary)", letterSpacing: "-0.01em" }}>Pieces worth buying</div>
              <div style={{ fontSize: 13, color: "var(--text-secondary)", marginTop: 2 }}>Within ${remaining}, only where there's a real gap.</div>
            </div>
          </div>
          <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
            <select value={selEvent} onChange={(e) => setSelEvent(e.target.value)} style={{ padding: "10px 12px", borderRadius: 10, border: "var(--border-width) solid var(--border)", background: "var(--surface-card)", fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-primary)", outline: "none" }}>
              <option value="">Everyday gaps</option>
              {upcoming.map((e) => <option key={e.id} value={e.id}>For {e.name}</option>)}
            </select>
            <button onClick={findPieces} disabled={loading} style={{ display: "inline-flex", alignItems: "center", gap: 8, background: "var(--ink)", color: "var(--parchment)", border: "none", borderRadius: 999, padding: "11px 20px", fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 500, cursor: loading ? "default" : "pointer", opacity: loading ? 0.6 : 1, whiteSpace: "nowrap" }}><i className="ph ph-sparkle" style={{ fontSize: 15 }}></i>Find pieces</button>
          </div>
        </div>

        {loading && (
          <div style={{ display: "flex", alignItems: "center", gap: 12, color: "var(--text-secondary)", fontSize: 14 }}>
            <span className="vr-spin" style={{ width: 18, height: 18, borderRadius: "50%", border: "2px solid var(--border)", borderTopColor: "var(--amber)", display: "inline-block" }}></span>
            Weighing your gaps against the budget…
          </div>
        )}
        {error && !loading && <div style={{ fontSize: 14, color: "var(--text-secondary)" }}>{error}</div>}

        {recs && !loading && window.velourOmittedNote && window.velourOmittedNote(omitted) && (
          <div style={{ display: "flex", gap: 11, alignItems: "flex-start", fontSize: 13.5, color: "var(--text-tertiary)", lineHeight: 1.55 }}>
            <i className="ph ph-stack" style={{ fontSize: 16, marginTop: 1 }}></i>
            <div>{window.velourOmittedNote(omitted)}</div>
          </div>
        )}

        {recs && recs.length === 0 && !loading && (
          <div style={{ display: "flex", gap: 11, alignItems: "flex-start", background: "var(--sage-fill)", borderRadius: 12, padding: "16px 18px" }}>
            <i className="ph ph-check-circle" style={{ fontSize: 19, color: "var(--sage)", marginTop: 1 }}></i>
            <div style={{ fontSize: 14.5, color: "var(--sage-text)", lineHeight: 1.55 }}>No real gap right now — your capsule already covers this. The smartest buy is the one you skip.</div>
          </div>
        )}

        {recs && recs.length > 0 && !loading && (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 200px), 1fr))", gap: 14 }}>
            {recs.map((r) => {
              const planned = budget.planned.some((p) => p.id === r.id);
              const overBudget = r.price > remaining && !planned;
              return (
                <div key={r.id} style={{ display: "flex", flexDirection: "column", background: "var(--surface-card)", border: "var(--border-width) solid var(--border)", borderRadius: 14, padding: 16, boxShadow: "var(--shadow-sm)" }}>
                  <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10 }}>
                    <span style={{ fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--amber-text)", fontWeight: 500 }}>{r.category}</span>
                    <span style={{ fontFamily: "var(--font-serif)", fontSize: 18, color: "var(--text-primary)" }}>${r.price}</span>
                  </div>
                  <div style={{ fontSize: 14.5, color: "var(--text-primary)", fontWeight: 500, lineHeight: 1.35 }}>{r.name}</div>
                  <div style={{ fontSize: 13, color: "var(--text-secondary)", marginTop: 8, lineHeight: 1.55, flex: 1 }}>{r.why}</div>
                  {r.cpw != null && (
                    <div style={{ display: "inline-flex", alignItems: "center", gap: 6, marginTop: 12, fontSize: 12.5, color: "var(--amber-text)", background: "var(--amber-fill)", borderRadius: 999, padding: "5px 11px", alignSelf: "flex-start", whiteSpace: "nowrap" }}>
                      <i className="ph ph-trend-down" style={{ fontSize: 13 }}></i>≈ {window.velourMoney(r.cpw)}/wear · yr one
                    </div>
                  )}
                  <div style={{ marginTop: 14 }}>
                    <div style={{ fontSize: 11, color: "var(--text-tertiary)", marginBottom: 7 }}>Shop at</div>
                    <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 8 }}>
                      {retailersFor(r.price).map((rt) => (
                        <a key={rt.label} href={rt.url(window.velourShopQuery(r.name, r.category), r.price, r.category)} target="_blank" rel="noopener noreferrer" style={{ display: "inline-flex", alignItems: "center", gap: 4, textDecoration: "none", background: "var(--surface-sunken)", color: "var(--text-primary)", borderRadius: 8, padding: "6px 10px", fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 500 }}>{rt.label}<i className="ph ph-arrow-up-right" style={{ fontSize: 11, opacity: 0.6 }}></i></a>
                      ))}
                    </div>
                    <div style={{ fontSize: 11.5, color: "var(--text-tertiary)", marginBottom: 10, lineHeight: 1.45 }}>
                      {retailersFor(r.price).some((rt) => rt.filters)
                        ? `Capped at ${window.velourMoney(r.price)} where the shop allows it — the rest you'll want to filter.`
                        : "These open as plain searches, so filter to your budget."}
                    </div>
                    <button onClick={() => planBuy(r)} disabled={overBudget} style={{ width: "100%", display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 6, background: planned ? "var(--amber-fill)" : "var(--ink)", color: planned ? "var(--amber-text)" : "var(--parchment)", border: "none", borderRadius: 9, padding: "9px", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 500, cursor: overBudget ? "default" : "pointer", opacity: overBudget ? 0.5 : 1, boxSizing: "border-box" }}>
                      <i className={`ph ph-${planned ? "check" : "plus"}`} style={{ fontSize: 14 }}></i>{planned ? "Planned" : overBudget ? "Over budget" : "Plan it"}
                    </button>
                  </div>
                </div>
              );
            })}
          </div>
        )}
      </Card>
    </div>
  );
}

window.WardrobeBudget = WardrobeBudget;
