/* FindMyFrag — Fragrance detail.
   Inverted order: BUY ANSWER → RETAILER LEDGER → REFERENCE → HISTORY.
   Ledger has two modes via Tweaks: "hero" (hero + 2 runners-up + audit log) or "table" (dense fallback). */

/* global React, FMF, DataStat */
const { useState: useStateD, useEffect: useEffectD, useMemo: useMemoD } = React;

function Detail({ slug, tweaks }) {
  // Per refusals §3 + §7: we do not render data we cannot verify as THIS fragrance.
  // Aventus is the fully-modeled v0.1 exemplar. Other catalog entries render a
  // signed stub; unknown slugs fall through to NoRecord.
  const aventusSlug = (FMF.AVENTUS && (FMF.AVENTUS.id || "creed-aventus-2010"));
  const isAventus = slug === aventusSlug;
  const catalogFrag = isAventus ? null : (FMF.CATALOG_ALL || FMF.CATALOG || []).find(f => f.slug === slug);
  if (!isAventus && !catalogFrag) return <NoRecord slug={slug} />;
  if (!isAventus) return <CatalogStub frag={catalogFrag} />;
  const frag = FMF.AVENTUS;

  // Active section reflects the current hash anchor (e.g. #/fragrance/X#history).
  const [detailActive, setDetailActive] = useStateD(() => {
    const raw = (typeof location !== "undefined" ? location.hash : "") || "";
    const [, anchor = ""] = raw.slice(1).split("#");
    return anchor || "ledger";
  });
  useEffectD(() => {
    const onHash = () => {
      const raw = (location.hash || "").slice(1);
      const [, anchor = ""] = raw.split("#");
      setDetailActive(anchor || "ledger");
    };
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, []);
  // Enrich every retailer price with landed + delta-to-winner.
  // Partition: tier-decant is sampling economics (per-ml), NOT comparable to
  // full-bottle totals. They get their own section downstream. This keeps the
  // 337.08 invariant honest — the cheapest "bottle landed" wins, not the
  // cheapest dollar amount on the page.
  const priced = useMemoD(() => {
    const all = frag.prices.map(p => {
      const r = FMF.retailerById(p.retailer);
      const c = FMF.computeLanded({
        landed: p.sticker,
        coupon: p.coupon,
        shippingUsd: r.shippingUsd,
        taxRatePct: r.taxRatePct,
      });
      const offerType = p.offerType || (r.tier === "tier-decant" ? "decant" : "retail");
      return {
        ...p,
        retailerObj: r,
        priced: c,
        inStock: r.stock === "in",
        offerType,
        isDecant: offerType === "decant",
        isRetailBottle: offerType === "retail" || offerType === "tester",
      };
    });
    // Shop-URL filter — a retailer without a verified pdpUrl is treated as
    // if they don't carry this fragrance, and drops out of every section.
    // No "Find at X" fallbacks, no "Not listed" chips — just absent. The
    // user decided (2026-04-21): show only what we can land directly on.
    const verified = all.filter(x => FMF.hasVerifiedShop(x.retailerObj, frag, x));
    // Bottle ledger — retail + tester only. Gift-sets, decants, and vintage are
    // segregated per offer-type taxonomy; never mix categories in "cheapest
    // landed" — that would be a category error.
    const rows = verified.filter(x => x.isRetailBottle);
    rows.sort((a, b) => {
      if (a.inStock !== b.inStock) return a.inStock ? -1 : 1;
      return a.priced.total - b.priced.total;
    });
    const winner = rows.find(r => r.inStock);
    const decants = verified.filter(x => x.isDecant).sort((a, b) => a.priced.total - b.priced.total);
    const giftsets = verified.filter(x => x.offerType === "giftset").sort((a, b) => a.priced.total - b.priced.total);
    // How many retailers were dropped — surfaced in the masthead so the
    // reader knows the ledger is curated to verified listings, not thin by mistake.
    const hiddenUnlisted = all.length - verified.length;
    return { rows, winner, decants, giftsets, hiddenUnlisted, verifiedCount: verified.length, totalCount: all.length };
  }, [frag]);

  const winner = priced.winner;
  // Graceful fail-safe: if no retailer has a verified PDP for this
  // fragrance, we render a neutral "no verified listings yet" state
  // instead of crashing on winner.priced.total. This is the honest
  // rendering when the ledger filter removes every row.
  if (!winner) {
    return (
      <main>
        <section className="container" style={{ padding: "72px 32px 96px" }}>
          <div className="kicker" style={{ marginBottom: 10 }}>No verified listings</div>
          <h1 className="fraun" style={{ fontSize: "clamp(32px, 4vw, 52px)", margin: "0 0 16px" }}>
            {frag.brand} <span className="fraun-itl">{frag.name}</span>
          </h1>
          <p className="mono body-s" style={{ color: "var(--ink-soft)", maxWidth: 560, lineHeight: 1.6 }}>
            We don't have a verified retailer URL for this fragrance yet. The ledger only
            renders direct product links — when a retailer feed lands we'll populate it.
            <br /><br />
            <a href="#/fragrance/creed-aventus-2010">Back to Aventus</a>
            {" · "}
            <a href="#/refusals">Refusals §2</a>
          </p>
        </section>
      </main>
    );
  }
  const discountDepth = Math.min(100, Math.max(0, ((frag.msrp - winner.priced.total) / frag.msrp) * 100 * 2));
  const dealScore = FMF.dealScore({ discountDepth, retailerTrust: winner.retailerObj.trust, stockHealth: 88 });
  const signal = FMF.signal(dealScore);
  const signalTip = FMF.signalFormula({ discountDepth, retailerTrust: winner.retailerObj.trust, stockHealth: 88, dealScore });
  const savings = FMF.savings(frag.msrp, winner.priced.total);

  // Historical context — pulled up from the heatmap to headline position.
  const histAll = frag.history || [];
  const histLow = histAll.length ? Math.min(...histAll) : winner.priced.total;
  const histHigh = histAll.length ? Math.max(...histAll) : winner.priced.total;
  const histLowIdx = histAll.indexOf(histLow);
  const daysAgoLow = histAll.length - 1 - histLowIdx;
  const histPct = histAll.length ? pctRank(histAll, winner.priced.total) : 50;

  // Decant math — bottle $/ml vs cheapest decant $/ml, and the premium paid
  // for sampling. Makes the sampling decision literal instead of hand-wavy.
  const sizeMl = mlFromLabel(frag.size, 100);
  const perMlBottle = winner.priced.total / sizeMl;
  const cheapestDecant = priced.decants[0];
  const decantMl = cheapestDecant ? mlFromLabel(cheapestDecant.note, 5) : 5;
  const perMlDecant = cheapestDecant ? cheapestDecant.priced.total / decantMl : 0;
  const decantPremiumPct = perMlBottle > 0 && perMlDecant > 0 ? ((perMlDecant / perMlBottle) - 1) * 100 : 0;

  return (
    <main>
      {/* ────── ABOVE THE FOLD: bottle + name + landed answer + signal ────── */}
      <section className="container" style={{ padding: "40px 32px 24px" }}>
        <div className="stack-mobile detail-mast" style={{ display: "grid", gridTemplateColumns: "220px 1fr 0.9fr", gap: 48, alignItems: "start" }}>
          {/* Bottle hero — real photography, flush */}
          <div>
            <Bottle slug={frag.id} label={`${frag.brand} ${frag.name}`} showCredit
                    style={{ aspectRatio: "4 / 5.2", background: "var(--paper-deep)" }} />
          </div>

          <div style={{ paddingTop: 6 }}>
            <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginBottom: 10, letterSpacing: "0.08em", textTransform: "uppercase" }}>
              {frag.brand}
            </div>
            <h1 className="display-xl" style={{ margin: 0 }}>
              <span className="fraun-itl">{frag.name}</span>
            </h1>
            <div className="mono body-s" style={{ color: "var(--ink-mute)", marginTop: 18 }}>
              {frag.size} · released {frag.year}
            </div>
          </div>

          {/* The buy answer, bold, in mono */}
          <aside style={{ borderTop: "1px solid var(--ink)", paddingTop: 18, position: "relative" }}>
            <BookmarkButton slug={frag.id || "creed-aventus-2010"} className="bookmark-btn--anchored" />
            {/* Two-column, three-row grid. Explicit rows keep every label
                on row 1 and every big number on row 2 regardless of
                per-cell content height — so $337.08 baseline-aligns with
                2d cleanly, size now sits with the kicker as a qualifier,
                and the per-ml subline lives on row 3. */}
            <div style={{
              display: "grid",
              gridTemplateColumns: "1.3fr 1fr",
              gridTemplateRows: "auto auto auto",
              columnGap: 20,
              rowGap: 0,
              alignItems: "end",
            }}>
              {/* Row 1 — meta kickers, bottom-aligned so a 2-line wrap on
                  the left doesn't float the right label up. */}
              <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.03em", lineHeight: 1.35, gridColumn: "1 / 2", gridRow: "1 / 2" }}>
                Best deal landed today <span style={{ color: "var(--ink-faint)" }}>· {sizeMl}ml</span>
              </div>
              <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.03em", lineHeight: 1.35, paddingLeft: 18, borderLeft: "1px solid var(--rule)", gridColumn: "2 / 3", gridRow: "1 / 2", alignSelf: "end" }}>
                Ships in
              </div>

              {/* Row 2 — the numbers, baseline-aligned across columns. */}
              <div className="mono" style={{ fontSize: 48, letterSpacing: "-0.025em", lineHeight: 1, marginTop: 8, gridColumn: "1 / 2", gridRow: "2 / 3" }}>
                <Prov row={winner} frag={frag}>{FMF.fmtUsd(winner.priced.total)}</Prov>
              </div>
              <div className="mono" style={{ fontSize: 48, letterSpacing: "-0.025em", lineHeight: 1, marginTop: 8, paddingLeft: 18, borderLeft: "1px solid var(--rule)", gridColumn: "2 / 3", gridRow: "2 / 3" }}>
                {winner.retailerObj.etaDays}<span style={{ fontSize: "0.42em", letterSpacing: 0, marginLeft: 2, color: "var(--ink-soft)" }}>d</span>
              </div>

              {/* Row 3 — per-ml footer under the price column only. */}
              <div className="mono body-xs" style={{ color: "var(--ink-faint)", marginTop: 6, gridColumn: "1 / 2", gridRow: "3 / 4" }}>
                = ${(winner.priced.total / sizeMl).toFixed(2)} per ml
              </div>
            </div>
            <div className="mono body-s" style={{ color: "var(--ink-soft)", marginTop: 14, display: "flex", gap: 14, alignItems: "baseline", flexWrap: "wrap" }}>
              <span>at <strong style={{ color: "var(--ink)" }}>{winner.retailerObj.name.toLowerCase()}</strong></span>
              <span className="sep" style={{ color: "var(--ink-faint)" }}>·</span>
              <span style={{ color: "var(--sig-buy)" }}>{FMF.fmtUsd(savings)}</span> <span style={{ color: "var(--ink-mute)" }}>below msrp<Cite n={3} check="verified" /></span>
              <span className="sep" style={{ color: "var(--ink-faint)" }}>·</span>
              <span className={`signal signal-${signal}`} title={signalTip}>{signal}</span>
            </div>
            <div style={{ display: "flex", flexDirection: "column", gap: 4, marginTop: 18 }}>
              {(() => {
                const s = FMF.shopUrl(winner.retailerObj, frag, winner);
                return (
                  <>
                    <a href={s.url} target="_blank" rel="noopener noreferrer"
                       data-shop-tier={s.tier} data-shop-retailer={winner.retailerObj?.id}
                       className="shop-btn shop-btn--primary shop-btn--block">
                      {s.label}
                      <svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true" style={{ marginLeft: 8 }}>
                        <path d="M1 9 L9 1 M3 1 H9 V7" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" />
                      </svg>
                    </a>
                    {s.tier === "search" && (
                      <span className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 2, letterSpacing: "0.04em" }}>
                        → retailer search (brand + name pre-filled)
                      </span>
                    )}
                  </>
                );
              })()}
            </div>
          </aside>
        </div>
      </section>

      {/* ────── CRITIC QUOTE — one signed editorial line. Quiet placement
           between the masthead aside and the section tabs. §7 firewall: this
           text is written without sight of any retailer's commission rate. */}
      {frag.editorial && (
        <section className="container" style={{ padding: "14px 32px 20px" }}>
          <figure style={{
            margin: 0,
            borderTop: "1px solid var(--rule)",
            borderBottom: "1px solid var(--rule)",
            padding: "22px 0",
            display: "grid",
            gridTemplateColumns: "1fr 220px",
            gap: 40,
            alignItems: "baseline",
          }} className="stack-mobile">
            <blockquote className="fraun" style={{
              margin: 0,
              fontSize: "clamp(22px, 2.8vw, 32px)",
              lineHeight: 1.3,
              letterSpacing: "-0.01em",
              color: "var(--ink)",
              textWrap: "balance",
            }}>
              <span className="fraun-itl">"{frag.editorial.quote}"</span>
            </blockquote>
            <figcaption className="mono body-xs" style={{
              color: "var(--ink-mute)",
              letterSpacing: "0.04em",
              lineHeight: 1.7,
              textAlign: "right",
            }}>
              <div style={{ color: "var(--ink)" }}>— {frag.editorial.by}</div>
              <div>{frag.editorial.date}</div>
              <div style={{ marginTop: 6 }}>
                <Cite n={7} check="separated" /> editorial firewall
              </div>
            </figcaption>
          </figure>
        </section>
      )}

      {/* ────── SECTION TABS — sticky wayfinding. Each tab sets the hash to
           the corresponding section anchor; the global scroll effect handles
           the jump. Active state mirrors the current hash fragment. ────── */}
      <SectionTabs active={detailActive} frag={frag} />

      {/* ────── PRICE HISTORY CONTEXT — today's price framed against 90d.
           Two cells only: today (+ percentile) and 90d low (+ days ago).
           Best-month-to-buy line below from editorial seasonality data. ────── */}
      <section className="container" style={{ padding: "14px 32px 22px" }}>
        <div className="stack-mobile" style={{
          display: "grid",
          gridTemplateColumns: "1fr 1fr",
          gap: 0,
          borderTop: "1px solid var(--rule)",
          borderBottom: "1px solid var(--rule)",
        }}>
          <HistCell
            label="Today"
            big={FMF.fmtUsd(winner.priced.total)}
            sub={`${histPct}th pctl of 90d`}
            border={false}
          />
          <HistCell
            label="90d low"
            big={<span style={{ color: "var(--oxblood-ink)" }}>{FMF.fmtUsd(histLow)}</span>}
            sub={`${daysAgoLow}d ago · not repeated`}
          />
        </div>
        {frag.seasonality && (
          <div className="mono body-xs" style={{
            color: "var(--ink-mute)",
            letterSpacing: "0.02em",
            marginTop: 10,
            lineHeight: 1.7,
            display: "flex",
            gap: 12,
            flexWrap: "wrap",
            alignItems: "baseline",
          }}>
            <span style={{ letterSpacing: "0.1em", textTransform: "uppercase" }}>Best month to buy</span>
            <span style={{ color: "var(--ink)" }}>{frag.seasonality.bestWindow}</span>
            <span style={{ color: "var(--ink-faint)" }}>·</span>
            <span>typically <strong style={{ color: "var(--oxblood-ink)" }}>{frag.seasonality.typicalDip}</strong></span>
            <span style={{ color: "var(--ink-faint)" }}>·</span>
            <span>next: {frag.seasonality.secondBest}</span>
          </div>
        )}
      </section>

      {/* ────── RETAILER LEDGER — the sharpest surface on the site ────── */}
      <section id="ledger" className="container" style={{ padding: "8px 32px 32px" }}>
        {tweaks.ledger === "hero" ? (
          <LedgerHero rows={priced.rows} winner={winner} frag={frag} />
        ) : (
          <LedgerTable rows={priced.rows} winner={winner} frag={frag} />
        )}

        {/* Price history · 90 days — collapsed by default, nested under the
            ledger so the detail page doesn't require a scroll to reach profile.
            Anchor `history` is preserved so the section-tabs link still jumps
            here; the <details> element opens automatically when navigated to. */}
        <details id="history" className="history-collapse" style={{ marginTop: 24 }}>
          <summary className="history-collapse__summary">
            <span className="history-collapse__chev" aria-hidden="true">▸</span>
            <span className="history-collapse__label">Price history · 90 days</span>
            <span className="history-collapse__meta mono body-xs">
              low {FMF.fmtUsd(histLow)} · today {FMF.fmtUsd(winner.priced.total)} · {histPct}th pctl
            </span>
          </summary>
          <div className="history-collapse__body">
            <HistoryHeatmap history={frag.history} todayTotal={winner.priced.total} slug={frag.id} frag={frag} />
          </div>
        </details>
      </section>

      {/* ────── DECANTS — sampling economics, kept separate so per-ml prices
           can't short-circuit the bottle leaderboard. Headline decant-math
           line and the gift-sets strip were removed per editor request. ────── */}
      {priced.decants.length > 0 && (
        <>
          <hr className="hairline" />
          <section className="container" style={{ padding: "40px 32px 32px" }}>
            <DecantsStrip rows={priced.decants} />
          </section>
        </>
      )}

      <hr className="hairline" />

      {/* ────── PROFILE — scent story, notes pyramid, when-to-wear, wearer panel.
           Tight margins and minimal kickers per editorial restraint. ────── */}
      <section id="profile" className="container" style={{ padding: "28px 32px 24px" }}>
        <div style={{ maxWidth: 880, margin: "0 auto" }}>
          {frag.scentStory && <ScentStory story={frag.scentStory} />}

          <Notes notes={frag.notes} persona={frag.wearer?.persona} />

          {(() => {
            const catalogEntry = (FMF.CATALOG || []).find(f => f.slug === (frag.id || "creed-aventus-2010"));
            const tags = catalogEntry?.tags;
            return tags ? <PerfectFor tags={tags} /> : null;
          })()}

          {frag.wearer && <WearerPanel wearer={frag.wearer} />}

          {frag.flankers && frag.flankers.length > 1 && <FlankerStrip flankers={frag.flankers} />}
        </div>
      </section>

      {/* ────── What people say ────── */}
      {frag.whatPeopleSay && <WhatPeopleSay data={frag.whatPeopleSay} />}

      <hr className="hairline" />

      {/* ────── Try these next — same family, excludes current ────── */}
      <TryTheseNext currentSlug={frag.id || "creed-aventus-2010"} family={frag.family} />

      {/* ────── Reviews from around the web ────── */}
      {frag.reviews && frag.reviews.length > 0 && <ReviewsSection reviews={frag.reviews} />}
    </main>
  );
}

const linkStyle = { color: "var(--ink)", textDecoration: "underline", textUnderlineOffset: 3 };
function MethodRow({ k, v }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between", gap: 16, borderBottom: "1px solid var(--rule)", padding: "8px 0" }}>
      <span style={{ color: "var(--ink-mute)", letterSpacing: "0.06em", textTransform: "uppercase", fontSize: 11 }}>{k}</span>
      <span style={{ color: "var(--ink)", textAlign: "right" }}>{v}</span>
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   SECTION TABS — sticky wayfinding for the long detail page.
   ══════════════════════════════════════════════════════════════════════ */
function SectionTabs({ active, frag }) {
  const tabs = [
    { id: "ledger",  label: "Ledger" },
    { id: "profile", label: "Profile" },
    { id: "history", label: "History" },
  ];
  const slug = frag.id || "creed-aventus-2010";
  return (
    <nav className="section-tabs" aria-label="Sections of this fragrance">
      <div className="container section-tabs__row">
        <div className="section-tabs__group">
          {tabs.map(t => (
            <a
              key={t.id}
              href={`#/fragrance/${slug}#${t.id}`}
              className={active === t.id ? "is-active" : ""}
              aria-current={active === t.id ? "true" : undefined}
            >
              {t.label}
            </a>
          ))}
        </div>
        <div className="section-tabs__meta">
          {frag.brand} · {frag.name} · {frag.size}
        </div>
      </div>
    </nav>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   TRY THESE NEXT — reduce dead-ends at detail page
   ══════════════════════════════════════════════════════════════════════ */
function TryTheseNext({ currentSlug, family }) {
  const famSlug = FMF.slugify(family);
  const siblings = (FMF.byFamilySlug(famSlug) || []).filter(f => f.slug !== currentSlug);
  // Fallback: if too few siblings, pad with other catalog entries (skip current)
  const pool = siblings.length >= 3
    ? siblings
    : [...siblings, ...(FMF.CATALOG_ALL || FMF.CATALOG).filter(f => f.slug !== currentSlug && !siblings.includes(f))];
  const picks = pool.slice(0, 3);
  if (picks.length === 0) return null;

  return (
    <section className="container" style={{ padding: "28px 32px 40px" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 20, flexWrap: "wrap", gap: 12 }}>
        <h3 className="fraun" style={{ fontSize: 32, letterSpacing: "-0.015em", margin: 0 }}>
          Try these <span className="fraun-itl">next.</span>
        </h3>
        <a href={`#/family/${famSlug}`} className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.06em", textTransform: "uppercase", borderBottom: "1px solid var(--rule)", paddingBottom: 2 }}>
          All in {family} →
        </a>
      </div>
      <div className="stack-mobile" style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 1, background: "var(--rule)", border: "1px solid var(--rule)" }}>
        {picks.map(f => (
          <a key={f.slug} href={`#/fragrance/${f.slug}`}
             style={{ background: "var(--paper)", padding: "24px 22px", display: "block", textDecoration: "none", color: "inherit", transition: "background 120ms" }}
             onMouseEnter={e => e.currentTarget.style.background = "var(--paper-deep)"}
             onMouseLeave={e => e.currentTarget.style.background = "var(--paper)"}>
            <Bottle slug={f.slug} label={f.name} showLabel={false}
                    style={{ width: "100%", height: 160, padding: "8% 18%", marginBottom: 16 }} />
            <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.08em", textTransform: "uppercase", marginBottom: 6 }}>
              {f.brand}
            </div>
            <div className="fraun" style={{ fontSize: 22, letterSpacing: "-0.012em", lineHeight: 1.15 }}>
              <span className="fraun-itl">{f.name}</span>
            </div>
            <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 10 }}>
              {f.year} · {f.family}
            </div>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginTop: 16, paddingTop: 14, borderTop: "1px solid var(--rule)" }}>
              <div className="mono" style={{ fontSize: 18, letterSpacing: "-0.01em" }}>{FMF.fmtUsd(f.landed)}</div>
              <span className={`signal signal-${f.signal}`}>{f.signal}</span>
            </div>
          </a>
        ))}
      </div>
    </section>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   RETAILER LEDGER — Hero + 2 runners-up + audit log
   ══════════════════════════════════════════════════════════════════════ */

function LedgerHero({ rows, winner, frag }) {
  // Default-collapsed to keep the detail page scannable — the hero strip +
  // 2 runners-up already answer "where do I buy this?" The full audit roster
  // remains one click away (§3 visibility ethic met by the "Open audit log"
  // control, not by forcing every reader to scroll past 30 more rows).
  const [expanded, setExpanded] = useStateD(false);
  // Sort mode for the hero strip: cheapest (default) or fastest (by ETA).
  // Fastest is the mode people reach for when the event is Friday.
  const [mode, setMode] = useStateD("cheapest");

  // Market-stats summary — cross-retailer distribution for this moment.
  // Helps the reader tell whether the winner is an outlier or the norm.
  const inStock = useMemoD(() => rows.filter(r => r.inStock), [rows]);
  const totals = inStock.map(r => r.priced.total);
  const stats = useMemoD(() => {
    if (totals.length === 0) return null;
    const sorted = [...totals].sort((a, b) => a - b);
    const avg = totals.reduce((a, b) => a + b, 0) / totals.length;
    const med = sorted.length % 2 ? sorted[Math.floor(sorted.length/2)] : (sorted[sorted.length/2 - 1] + sorted[sorted.length/2]) / 2;
    const variance = totals.reduce((s, v) => s + (v - avg) ** 2, 0) / totals.length;
    const sigma = Math.sqrt(variance);
    const lo = sorted[0];
    const hi = sorted[sorted.length - 1];
    return { avg, med, sigma, lo, hi, n: totals.length };
  }, [totals]);

  const hero = useMemoD(() => {
    const inStock = rows.filter(r => r.inStock);
    if (mode === "fastest") {
      // Fastest: sort by ETA ascending, break ties on landed.
      return inStock.slice().sort((a, b) => {
        const d = a.retailerObj.etaDays - b.retailerObj.etaDays;
        return d !== 0 ? d : a.priced.total - b.priced.total;
      });
    }
    // Default: already sorted by landed in the parent.
    return inStock;
  }, [rows, mode]);

  const top = hero[0] || winner;
  const runners = hero.slice(1, 3);
  const heroSlugs = new Set(hero.slice(0, 3).map(r => r.retailer));
  const audit = rows.filter(r => !heroSlugs.has(r.retailer));

  // Collapsed preview line stats
  const prices = audit.map(r => r.priced.total);
  const outCount = audit.filter(r => !r.inStock).length;
  const minAudit = prices.length ? Math.min(...prices) : 0;
  const maxAudit = prices.length ? Math.max(...prices) : 0;

  // Fastest ETA in the whole in-stock set — used as the label on the toggle.
  const fastestEta = rows.filter(r => r.inStock).reduce((m, r) => Math.min(m, r.retailerObj.etaDays), 99);

  return (
    <div>
      {/* Market-stats summary — "is the winner an outlier or the norm?" */}
      {stats && (
        <div className="mono body-xs" style={{
          color: "var(--ink-mute)",
          letterSpacing: "0.02em",
          marginBottom: 14,
          paddingBottom: 14,
          borderBottom: "1px solid var(--rule)",
          display: "flex",
          gap: 20,
          flexWrap: "wrap",
          alignItems: "baseline",
        }}>
          <span><span style={{ color: "var(--ink)" }}>Across {stats.n} in-stock retailers</span></span>
          <span style={{ color: "var(--ink-faint)" }}>·</span>
          <span>avg <strong style={{ color: "var(--ink)" }}>{FMF.fmtUsd(stats.avg)}</strong></span>
          <span style={{ color: "var(--ink-faint)" }}>·</span>
          <span>median <strong style={{ color: "var(--ink)" }}>{FMF.fmtUsd(stats.med)}</strong></span>
          <span style={{ color: "var(--ink-faint)" }}>·</span>
          <span>σ <strong style={{ color: "var(--ink)" }}>{FMF.fmtUsd(stats.sigma)}</strong></span>
          <span style={{ color: "var(--ink-faint)" }}>·</span>
          <span>range <strong style={{ color: "var(--ink)" }}>{FMF.fmtUsd(stats.lo)}</strong>–<strong style={{ color: "var(--ink)" }}>{FMF.fmtUsd(stats.hi)}</strong></span>
          <span style={{ color: "var(--ink-faint)" }}>·</span>
          <span>winner is <strong style={{ color: "var(--sig-buy)" }}>{FMF.fmtUsd(stats.avg - stats.lo, { signed: true })}</strong> below avg</span>
        </div>
      )}

      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 18, flexWrap: "wrap", gap: 14 }}>
        <h3 className="kicker" style={{ margin: 0 }}>Retailer ledger · {rows.length} polled</h3>
        <div style={{ display: "flex", alignItems: "baseline", gap: 14 }}>
          <span className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.06em", textTransform: "uppercase" }}>Priority</span>
          <div className="mode-toggle" role="tablist" aria-label="Sort retailers">
            <button role="tab" aria-selected={mode === "cheapest"}
                    className={mode === "cheapest" ? "is-on" : ""}
                    onClick={() => setMode("cheapest")}>
              Cheapest <span className="mode-toggle__sub">landed</span>
            </button>
            <button role="tab" aria-selected={mode === "fastest"}
                    className={mode === "fastest" ? "is-on" : ""}
                    onClick={() => setMode("fastest")}>
              Fastest <span className="mode-toggle__sub">{fastestEta}d</span>
            </button>
          </div>
        </div>
      </div>

      {/* Hero strip: top pick + 2 runners */}
      <div className="stack-mobile" style={{
        display: "grid",
        gridTemplateColumns: "1.4fr 1fr 1fr",
        border: "1px solid var(--ink)",
        background: "var(--paper)",
      }}>
        <HeroCell row={top} frag={frag} mode={mode} />
        {runners.map((r, i) => (
          <RunnerCell key={r.retailer} row={r} winner={top} idx={i + 2} frag={frag} mode={mode} />
        ))}
      </div>

      {/* Audit log header (collapsed preview) */}
      <button
        onClick={() => setExpanded(!expanded)}
        style={{
          width: "100%",
          background: "var(--paper)",
          border: 0,
          borderTop: 0,
          borderBottom: "1px solid var(--ink)",
          borderLeft: "1px solid var(--ink)",
          borderRight: "1px solid var(--ink)",
          padding: "14px 20px",
          textAlign: "left",
          cursor: "pointer",
          font: "inherit",
          display: "flex",
          justifyContent: "space-between",
          alignItems: "baseline",
        }}
      >
        <span className="mono body-s" style={{ color: "var(--ink-soft)" }}>
          {expanded ? "▾" : "▸"}&nbsp;&nbsp;
          <strong style={{ color: "var(--ink)" }}>{audit.length} more retailers</strong>
          <span className="sep" style={{ color: "var(--ink-faint)", margin: "0 10px" }}>·</span>
          range {FMF.fmtUsd(minAudit)}–{FMF.fmtUsd(maxAudit)}
          <span className="sep" style={{ color: "var(--ink-faint)", margin: "0 10px" }}>·</span>
          {outCount} out of stock
        </span>
        <span className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.08em", textTransform: "uppercase" }}>
          {expanded ? "Collapse" : "Open audit log"}
        </span>
      </button>

      {expanded && (
        <div style={{
          border: "1px solid var(--ink)",
          borderTop: 0,
          padding: "6px 0 0",
          background: "var(--paper)",
          overflowX: "auto",
        }}>
          <AuditTable rows={audit} frag={frag} />
        </div>
      )}
    </div>
  );
}

function HeroCell({ row, frag, mode }) {
  const { retailerObj: r, priced: c, sticker, coupon, note } = row;
  const isFast = mode === "fastest";
  return (
    <div style={{ padding: "28px 28px 26px", borderRight: "1px solid var(--rule)", display: "flex", flexDirection: "column" }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 12, marginBottom: 18 }}>
        <span className={`chip ${isFast ? "chip-ink" : "chip-gold"}`}>
          {isFast ? "Fastest in-stock" : "Lowest landed"}
        </span>
        <span className="mono body-xs" style={{ color: "var(--ink-mute)" }}>
          Rank 01
        </span>
      </div>
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 14, borderBottom: "1px solid var(--ink)", paddingBottom: 8 }}>
        <div className="fraun" style={{ fontSize: 44, lineHeight: 1, letterSpacing: "-0.02em" }}>
          {r.name}
        </div>
        <span className={`chip ${FMF.retailerTierChip(r).cls}`} title={`${FMF.retailerTierChip(r).blurb || ""} · trust ${r.trust}`}>
          {FMF.retailerTierChip(r).label}<Cite n={4} check="clean" />
        </span>
      </div>

      {/* Paired stat: price + ETA */}
      <div style={{ marginTop: 18, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 18, alignItems: "end" }}>
        <div>
          <div className="mono" style={{
            fontSize: isFast ? 32 : 44,
            letterSpacing: "-0.02em", lineHeight: 1,
            color: isFast ? "var(--ink-soft)" : "var(--ink)",
          }}>
            <Prov row={row} frag={frag}>{FMF.fmtUsd(c.total)}</Prov>
          </div>
          <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 8 }}>
            landed · all-in · ${(c.total / mlFromLabel(frag.size, 100)).toFixed(2)}/ml
          </div>
        </div>
        <div style={{ borderLeft: "1px solid var(--rule)", paddingLeft: 18 }}>
          <div className="mono" style={{
            fontSize: isFast ? 44 : 32,
            letterSpacing: "-0.02em", lineHeight: 1,
            color: isFast ? "var(--oxblood)" : "var(--ink-soft)",
          }}>
            {r.etaDays}<span style={{ fontSize: "0.55em", letterSpacing: 0, marginLeft: 2 }}>d</span>
          </div>
          <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 8 }}>to your door</div>
        </div>
      </div>

      {/* Breakdown */}
      <div className="mono body-s" style={{ marginTop: 22, borderTop: "1px solid var(--rule)", paddingTop: 14, lineHeight: 1.9 }}>
        <BreakdownRow k="sticker"    v={FMF.fmtUsd(sticker)} />
        {coupon > 0 && <BreakdownRow k={`coupon`} v={`− ${FMF.fmtUsd(coupon)}`} accent />}
        <BreakdownRow k={<>shipping<Cite n={9} check="sourced" /></>} v={r.shippingUsd === 0 ? "free" : FMF.fmtUsd(r.shippingUsd)} />
        <BreakdownRow k={`tax · ${r.taxRatePct.toFixed(1)}%`} v={FMF.fmtUsd(c.tax)} />
        <BreakdownRow k="total" v={FMF.fmtUsd(c.total)} bold />
      </div>

      <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 18, lineHeight: 1.6 }}>
        trust {r.trust} · {FMF.tierLabel(r)} · ships in {r.etaDays} days
        <br />
        <span style={{ color: "var(--ink-soft)" }}>{note}</span>
      </div>

      {/* Spacer pushes the CTA to bottom edge so all three cells align */}
      <div style={{ flex: 1, minHeight: 12 }} />

      {(() => {
        const s = FMF.shopUrl(r, frag, row);
        return (
          <a href={s.url} target="_blank" rel="noopener noreferrer"
             data-shop-tier={s.tier} data-shop-retailer={r?.id}
             className="shop-btn shop-btn--primary"
             style={{ width: "100%", justifyContent: "center" }}
             title={s.tier === "search" ? "Lands on retailer search with brand + name pre-filled" : undefined}>
            {s.label}
            <svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true" style={{ marginLeft: 8 }}>
              <path d="M1 9 L9 1 M3 1 H9 V7" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" />
            </svg>
          </a>
        );
      })()}
    </div>
  );
}

function RunnerCell({ row, winner, idx, frag, mode }) {
  const { retailerObj: r, priced: c, sticker, coupon, note, inStock } = row;
  const isFast = mode === "fastest";
  const delta = c.total - winner.priced.total;
  const etaDelta = r.etaDays - winner.retailerObj.etaDays;
  return (
    <div style={{ padding: "28px 28px 26px", borderRight: idx === 2 ? "1px solid var(--rule)" : 0, opacity: inStock ? 1 : 0.5, display: "flex", flexDirection: "column" }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 12, marginBottom: 18 }}>
        <span className="chip chip-mute">Rank 0{idx}</span>
      </div>
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 10, borderBottom: "1px solid var(--rule)", paddingBottom: 8 }}>
        <div className="fraun" style={{ fontSize: 26, lineHeight: 1.1, letterSpacing: "-0.015em" }}>
          {r.name}
        </div>
        <span className={`chip ${FMF.retailerTierChip(r).cls}`} title={`${FMF.retailerTierChip(r).blurb || ""} · trust ${r.trust}`}>
          {FMF.retailerTierChip(r).label}
        </span>
      </div>
      <div style={{ marginTop: 14, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
        <div>
          <div className="mono" style={{ fontSize: isFast ? 19 : 26, letterSpacing: "-0.015em", lineHeight: 1, color: isFast ? "var(--ink-soft)" : "var(--ink)" }}>
            <Prov row={row} frag={frag}>{FMF.fmtUsd(c.total)}</Prov>
          </div>
          <div className="mono body-xs" style={{ color: delta > 0 ? "var(--oxblood-ink)" : "var(--ink-mute)", marginTop: 6 }}>
            {delta === 0 ? "—" : `${FMF.fmtUsd(delta, { signed: true })}`}
          </div>
        </div>
        <div style={{ borderLeft: "1px solid var(--rule)", paddingLeft: 12 }}>
          <div className="mono" style={{ fontSize: isFast ? 26 : 19, letterSpacing: "-0.015em", lineHeight: 1, color: isFast ? "var(--oxblood)" : "var(--ink-soft)" }}>
            {r.etaDays}<span style={{ fontSize: "0.6em", letterSpacing: 0, marginLeft: 1 }}>d</span>
          </div>
          <div className="mono body-xs" style={{ color: etaDelta > 0 ? "var(--oxblood-ink)" : "var(--ink-mute)", marginTop: 6 }}>
            {etaDelta === 0 ? "—" : `${etaDelta > 0 ? "+" : ""}${etaDelta}d`}
          </div>
        </div>
      </div>

      {/* Breakdown mini-table — same rows as hero so column heights align */}
      <div className="mono body-s" style={{ marginTop: 22, borderTop: "1px solid var(--rule)", paddingTop: 14, lineHeight: 1.9 }}>
        <BreakdownRow k="sticker"    v={FMF.fmtUsd(sticker)} />
        {coupon > 0 && <BreakdownRow k={`coupon`} v={`− ${FMF.fmtUsd(coupon)}`} accent />}
        <BreakdownRow k={<>shipping<Cite n={9} check="sourced" /></>} v={r.shippingUsd === 0 ? "free" : FMF.fmtUsd(r.shippingUsd)} />
        <BreakdownRow k={`tax · ${r.taxRatePct.toFixed(1)}%`} v={FMF.fmtUsd(c.tax)} />
        <BreakdownRow k="total" v={FMF.fmtUsd(c.total)} bold />
      </div>

      <div className="mono body-xs" style={{ marginTop: 16, color: "var(--ink-mute)", lineHeight: 1.7 }}>
        trust {r.trust} · {FMF.tierLabel(r)} · {inStock ? `ships in ${r.etaDays} days` : "out of stock"}
      </div>

      {/* Spacer — aligns the shop button to the bottom across all 3 cells */}
      <div style={{ flex: 1, minHeight: 12 }} />

      {inStock ? (() => {
        const s = FMF.shopUrl(r, frag, row);
        return (
          <a href={s.url} target="_blank" rel="noopener noreferrer"
             data-shop-tier={s.tier} data-shop-retailer={r?.id}
             className="shop-btn shop-btn--ghost"
             style={{ justifyContent: "center" }}
             title={s.tier === "search" ? "Lands on retailer search with brand + name pre-filled" : undefined}>
            {s.label}
            <svg width="9" height="9" viewBox="0 0 10 10" aria-hidden="true" style={{ marginLeft: 6 }}>
              <path d="M1 9 L9 1 M3 1 H9 V7" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" />
            </svg>
          </a>
        );
      })() : (
        <div className="mono body-xs" style={{ textAlign: "center", padding: "10px 0", color: "var(--oxblood-ink)", letterSpacing: "0.08em", textTransform: "uppercase", border: "1px solid var(--oxblood)" }}>
          Out of stock
        </div>
      )}
    </div>
  );
}

function BreakdownRow({ k, v, accent, bold, strike }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between" }}>
      <span style={{ color: "var(--ink-mute)" }}>{k}</span>
      <span style={{
        color: accent ? "var(--sig-buy)" : "var(--ink)",
        fontWeight: bold ? 600 : 400,
        textDecoration: strike ? "line-through" : "none",
      }}>{v}</span>
    </div>
  );
}

function AuditTable({ rows, frag }) {
  // Default sort: cheapest landed ascending (same as the hero row's selection).
  // Click any header to resort; click again to reverse. Keyboard-accessible via
  // tabIndex + Enter/Space on each <th>.
  const [sortKey, setSortKey] = useStateD("total");
  const [sortDir, setSortDir] = useStateD("asc");

  const onSort = (key) => {
    if (sortKey === key) setSortDir(sortDir === "asc" ? "desc" : "asc");
    else { setSortKey(key); setSortDir(key === "retailer" ? "asc" : (["sticker", "ship", "tax", "total", "eta"].includes(key) ? "asc" : "desc")); }
  };

  const sorted = useMemoD(() => {
    const copy = rows.slice();
    const dir = sortDir === "asc" ? 1 : -1;
    const get = (row) => {
      const r = row.retailerObj;
      switch (sortKey) {
        case "retailer": return r.name.toLowerCase();
        case "trust":    return r.trust;
        case "sticker":  return row.sticker;
        case "coupon":   return row.coupon || 0;
        case "ship":     return r.shippingUsd;
        case "tax":      return r.taxRatePct;
        case "total":    return row.priced.total;
        case "eta":      return r.etaDays;
        case "stock":    return row.inStock ? 0 : 1;
        default:         return 0;
      }
    };
    copy.sort((a, b) => {
      // Out-of-stock always sinks to the bottom regardless of sort direction.
      // §8 staleness treatment — excluded offers shouldn't float up into sight.
      if (a.inStock !== b.inStock) return a.inStock ? -1 : 1;
      const va = get(a), vb = get(b);
      if (typeof va === "string") return va.localeCompare(vb) * dir;
      return (va - vb) * dir;
    });
    return copy;
  }, [rows, sortKey, sortDir]);

  const SortableTh = ({ k, children, right }) => {
    const active = sortKey === k;
    return (
      <th
        style={{
          ...cellH,
          textAlign: right ? "right" : "left",
          cursor: "pointer",
          userSelect: "none",
          color: active ? "var(--ink)" : "var(--ink-mute)",
        }}
        tabIndex={0}
        role="button"
        aria-sort={active ? (sortDir === "asc" ? "ascending" : "descending") : "none"}
        onClick={() => onSort(k)}
        onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onSort(k); } }}
      >
        {right && active && <span style={{ marginRight: 4, opacity: 0.6 }}>{sortDir === "asc" ? "↑" : "↓"}</span>}
        {children}
        {!right && active && <span style={{ marginLeft: 4, opacity: 0.6 }}>{sortDir === "asc" ? "↑" : "↓"}</span>}
      </th>
    );
  };

  return (
    <table style={{ width: "100%", borderCollapse: "collapse", fontFamily: "var(--font-mono)", fontSize: 13 }}>
      <thead>
        <tr style={{ color: "var(--ink-mute)", fontSize: 11, letterSpacing: "0.08em", textTransform: "uppercase" }}>
          <SortableTh k="retailer">Retailer</SortableTh>
          <SortableTh k="sticker" right>Sticker</SortableTh>
          <SortableTh k="coupon" right>Coupon</SortableTh>
          <SortableTh k="ship" right>Ship</SortableTh>
          <SortableTh k="tax" right>Tax</SortableTh>
          <SortableTh k="total" right>Total</SortableTh>
          <SortableTh k="eta" right>ETA</SortableTh>
          <th style={{ ...cellH, textAlign: "right" }}>Shop</th>
        </tr>
      </thead>
      <tbody>
        {sorted.map((row, i) => {
          const r = row.retailerObj;
          return (
            <tr key={row.retailer} style={{ opacity: row.inStock ? 1 : 0.5, borderTop: "1px solid var(--rule)" }}>
              <td style={cell}>{r.name}</td>
              <td style={{ ...cell, textAlign: "right" }}>{FMF.fmtUsd(row.sticker)}</td>
              <td style={{ ...cell, textAlign: "right", color: row.coupon ? "var(--sig-buy)" : "var(--ink-faint)" }}>
                {row.coupon ? `− ${FMF.fmtUsd(row.coupon)}` : "—"}
              </td>
              <td style={{ ...cell, textAlign: "right" }}>{r.shippingUsd === 0 ? "free" : FMF.fmtUsd(r.shippingUsd)}</td>
              <td style={{ ...cell, textAlign: "right" }}>{r.taxRatePct.toFixed(1)}%</td>
              <td style={{ ...cell, textAlign: "right", fontWeight: 600 }}>{FMF.fmtUsd(row.priced.total)}</td>
              <td style={{ ...cell, textAlign: "right", color: "var(--ink-soft)" }}>{row.inStock ? `${r.etaDays}d` : "—"}</td>
              <td style={{ ...cell, textAlign: "right" }}>
                {(() => {
                  const s = FMF.shopUrl(r, frag, row);
                  const label = row.inStock
                    ? (s.tier === "pdp" ? "Shop" : "Find")
                    : "Out of stock";
                  return (
                    <a href={s.url} target="_blank" rel="noopener noreferrer"
                       data-shop-tier={s.tier} data-shop-retailer={r?.id}
                       className="shop-btn shop-btn--compact"
                       aria-label={`${row.inStock ? "Shop" : "View (out of stock)"} ${frag.brand} ${frag.name} at ${r.name}`}
                       style={!row.inStock ? { opacity: 0.7 } : {}}
                       title={s.tier === "search" ? "Search landing, not direct product page" : undefined}>
                      {label}
                      <svg width="8" height="8" viewBox="0 0 10 10" aria-hidden="true" style={{ marginLeft: 4 }}>
                        <path d="M1 9 L9 1 M3 1 H9 V7" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" />
                      </svg>
                    </a>
                  );
                })()}
              </td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );
}

const cell  = { padding: "14px 12px", verticalAlign: "middle", lineHeight: 1.3 };
const cellH = { padding: "14px 12px", textAlign: "left", fontWeight: 400, borderBottom: "1px solid var(--rule)", verticalAlign: "middle" };

/* Dense-table fallback (Tweak) */
function LedgerTable({ rows, winner, frag }) {
  return (
    <div>
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 18 }}>
        <div className="kicker">Retailer ledger · {rows.length} polled · dense fallback</div>
        <div className="mono body-xs" style={{ color: "var(--ink-mute)" }}>
          retailer name is the link · we do not sell
        </div>
      </div>
      <div style={{ border: "1px solid var(--ink)", background: "var(--paper)", overflowX: "auto" }}>
        <AuditTable rows={rows} frag={frag} />
      </div>
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   NOTES — prose first, pyramid as a toggle
   ══════════════════════════════════════════════════════════════════════ */
/* Scent story — 5-sentence editorial paragraph. Signed. §7 firewall.
   Placed above the notes pyramid so readers frame the composition with
   the wearer-experience BEFORE the ingredient list. */
function ScentStory({ story }) {
  // Prefer the single `text` string; fall back to legacy `sentences` array.
  const text = story.text || (story.sentences ? story.sentences.join(" ") : "");
  return (
    <div className="scent-story">
      <div className="scent-story__kicker">Scent story</div>
      <p className="scent-story__body">{text}</p>
      <div className="scent-story__sig">
        Signed <strong>{story.by}</strong> · {story.date} · <Cite n={7} check="separated" /> editorial firewall
      </div>
    </div>
  );
}

function Notes({ notes, persona }) {
  // Two-column layout: persona archetype (silhouette + title + description)
  // on the left, notes pyramid on the right. When no persona is defined,
  // collapse to a single-column notes pyramid so other fragrances aren't
  // forced to carry the editorial claim "this person wears it" without a
  // signed write-up.
  if (!persona) return <NotesIllustrated notes={notes} />;
  return (
    <div className="notes-split">
      <PersonaPanel persona={persona} />
      <NotesIllustrated notes={notes} compact />
    </div>
  );
}

/* Persona archetype panel — silhouette illustration + role title + scene-fragment.
   §11-compliant: silhouette is a hand-authored SVG, not AI-generated. Not a claim
   about who buys this scent — a claim about the silhouette it projects when worn
   well. Signed via §7 firewall (inherits the scent-story signature). */
function PersonaPanel({ persona }) {
  return (
    <div className="persona">
      <div className="persona__figure" aria-hidden="true">
        <SilhouetteMan />
      </div>
      <div className="persona__meta">
        <div className="persona__kicker">Wears like</div>
        <div className="persona__title fraun">
          <span className="fraun-itl">&ldquo;{persona.title}&rdquo;</span>
        </div>
        {persona.tagline && (
          <div className="persona__tagline">{persona.tagline}</div>
        )}
        <p className="persona__body">{persona.body}</p>
      </div>
    </div>
  );
}

/* Silhouette — minimalist bust, inked strokes only. Authored by hand per §11. */
function SilhouetteMan() {
  return (
    <svg viewBox="0 0 140 180" width="100%" height="100%" preserveAspectRatio="xMidYMax meet"
         role="img" aria-label="Silhouette of a man in a jacket">
      {/* Head */}
      <path d="M70 14 C56 14, 48 24, 48 40 C48 54, 54 64, 62 68 L60 78 C58 82, 54 84, 48 86
               L36 90 C26 93, 20 100, 18 112 L14 180 L126 180 L122 112 C120 100, 114 93, 104 90
               L92 86 C86 84, 82 82, 80 78 L78 68 C86 64, 92 54, 92 40 C92 24, 84 14, 70 14 Z"
            fill="none" stroke="var(--ink)" strokeWidth="1.4" strokeLinejoin="round" />
      {/* Collar + jacket V */}
      <path d="M46 96 L70 128 L94 96" fill="none" stroke="var(--ink)" strokeWidth="1.2" strokeLinejoin="round" />
      {/* Tie (subtle) */}
      <path d="M68 128 L72 128 L74 168 L66 168 Z" fill="var(--ink)" opacity="0.88" />
      {/* Lapels */}
      <path d="M46 96 L38 180 M94 96 L102 180" fill="none" stroke="var(--ink)" strokeWidth="1" opacity="0.7" />
      {/* Hairline suggestion */}
      <path d="M52 34 C56 28, 64 26, 70 26 C76 26, 84 28, 88 34" fill="none" stroke="var(--ink)" strokeWidth="1" opacity="0.7" />
    </svg>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   NOTES · ILLUSTRATED
   §11-compliant: typographic note cards by default, swaps to a real
   commissioned/public-domain photo (with credit line) when an entry
   exists in FMF.NOTE_IMAGES. Never AI-generated. See refusal §11.
   ══════════════════════════════════════════════════════════════════════ */
function NotesIllustrated({ notes, compact }) {
  const tiers = [
    { key: "top",   label: "Top notes"    },
    { key: "heart", label: "Middle notes" },
    { key: "base",  label: "Base notes"   },
  ];
  return (
    <div className={`notes-pyramid${compact ? " notes-pyramid--compact" : ""}`}>
      {tiers.map(tier => (
        <div key={tier.key} className="notes-pyramid__row">
          <div className="notes-pyramid__header">{tier.label}</div>
          <div className="notes-pyramid__tiles">
            {notes[tier.key].map(n => <NoteChip key={n} label={n} />)}
          </div>
        </div>
      ))}
    </div>
  );
}

function NoteChip({ label }) {
  const emoji = FMF.noteEmoji && FMF.noteEmoji(label);
  return (
    <a href={`#/note/${FMF.slugify(label)}`} className="note-chip" aria-label={label}>
      <span className="note-chip__icon" aria-hidden="true">
        {emoji ? emoji : <span className="note-chip__typeset">{label.split(" ")[0].toLowerCase()}</span>}
      </span>
      <span className="note-chip__label">{label}</span>
    </a>
  );
}

function Pyramid({ notes }) {
  const tiers = [
    { key: "top",   label: "Top",   width: 100, items: notes.top },
    { key: "heart", label: "Heart", width: 78,  items: notes.heart },
    { key: "base",  label: "Base",  width: 58,  items: notes.base },
  ];
  return (
    <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 10, padding: "8px 0" }}>
      {tiers.map((t) => (
        <div key={t.key} style={{
          width: `${t.width}%`,
          border: "1px solid var(--ink)",
          padding: "14px 18px",
          display: "flex",
          alignItems: "baseline",
          gap: 16,
          flexWrap: "wrap",
          justifyContent: "center",
        }}>
          <span className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.08em", textTransform: "uppercase" }}>{t.label}</span>
          <span className="fraun" style={{ fontSize: 17 }}>
            {t.items.join(" · ").toLowerCase()}
          </span>
        </div>
      ))}
    </div>
  );
}

function Accords({ items }) {
  return (
    <div style={{ display: "grid", gridTemplateColumns: "100px 1fr 48px", rowGap: 10, columnGap: 14, alignItems: "center" }}>
      {items.map((a) => (
        <React.Fragment key={a.name}>
          <div className="mono body-s" style={{ color: "var(--ink-soft)" }}>{a.name}</div>
          <div style={{ height: 10, background: "var(--paper-deep)", border: "1px solid var(--rule)", position: "relative" }}>
            <div style={{ position: "absolute", inset: 0, width: `${a.weight * 100}%`, background: "var(--ink)" }} />
          </div>
          <div className="mono body-xs" style={{ color: "var(--ink-mute)", textAlign: "right" }}>
            {(a.weight * 100).toFixed(0)}
          </div>
        </React.Fragment>
      ))}
    </div>
  );
}

/* Perfect for — occasion + season recommendations pulled from catalog tags.
   Concrete "this is the fragrance for THIS moment" line, no hedging. */
function PerfectFor({ tags }) {
  const axes = [
    { id: "occasion", values: tags.occasion, label: "Perfect for" },
    { id: "season",   values: tags.season,   label: "Best in"     },
    { id: "vibe",     values: tags.vibe,     label: "Reads as"    },
  ].filter(a => a.values && a.values.length);
  if (axes.length === 0) return null;
  const labelFor = (axisId, slug) => {
    const axis = (FMF.BROWSE_AXES || []).find(a => a.id === axisId);
    return axis?.tags.find(t => t.slug === slug)?.label || slug;
  };
  return (
    <div className="perfect-for-inline">
      {axes.map((a, i) => (
        <React.Fragment key={a.id}>
          {i > 0 && <span className="perfect-for-inline__sep" aria-hidden="true">·</span>}
          <span className="perfect-for-inline__k">{a.label}</span>
          <span className="perfect-for-inline__v">
            {a.values.map((slug, j) => (
              <React.Fragment key={slug}>
                {j > 0 && <span className="perfect-for-inline__comma">,&nbsp;</span>}
                <a href={`#/browse/${a.id}/${slug}`} className="perfect-for-inline__chip">
                  {labelFor(a.id, slug)}
                </a>
              </React.Fragment>
            ))}
          </span>
        </React.Fragment>
      ))}
    </div>
  );
}

/* ═════════════════════════════════════════════════════════════════
   WEARER PANEL — pros/cons, "smells like", performance distributions.
   Editorial, §7 firewall. Data lives on frag.wearer.
   ═════════════════════════════════════════════════════════════════ */
function WearerPanel({ wearer }) {
  return (
    <div style={{ marginTop: 24 }}>
      {/* Pros + Cons */}
      {(wearer.pros?.length > 0 || wearer.cons?.length > 0) && (
        <div className="stack-mobile" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24, marginBottom: 24 }}>
          {wearer.pros?.length > 0 && (
            <div style={{ borderTop: "2px solid var(--sig-buy)", paddingTop: 10 }}>
              <div className="mono body-xs" style={{ color: "var(--sig-buy)", letterSpacing: "0.12em", textTransform: "uppercase", marginBottom: 8 }}>Pros</div>
              <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
                {wearer.pros.map((p, i) => (
                  <li key={i} className="fraun" style={{ fontSize: 15, lineHeight: 1.5, borderBottom: "1px solid var(--rule)", padding: "8px 0", color: "var(--ink)" }}>
                    {p}
                  </li>
                ))}
              </ul>
            </div>
          )}
          {wearer.cons?.length > 0 && (
            <div style={{ borderTop: "2px solid var(--oxblood)", paddingTop: 10 }}>
              <div className="mono body-xs" style={{ color: "var(--oxblood-ink)", letterSpacing: "0.12em", textTransform: "uppercase", marginBottom: 8 }}>Cons</div>
              <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
                {wearer.cons.map((c, i) => (
                  <li key={i} className="fraun" style={{ fontSize: 15, lineHeight: 1.5, borderBottom: "1px solid var(--rule)", padding: "8px 0", color: "var(--ink)" }}>
                    {c}
                  </li>
                ))}
              </ul>
            </div>
          )}
        </div>
      )}

      {/* Performance distributions */}
      {wearer.performance && (
        <div style={{ marginBottom: 24, paddingTop: 16, borderTop: "1px solid var(--rule)" }}>
          <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.1em", textTransform: "uppercase", marginBottom: 12 }}>Performance</div>
          <div className="stack-mobile" style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 20 }}>
            {["longevity", "sillage", "value"].map(k => wearer.performance[k] && (
              <PerformanceBars key={k} label={k} data={wearer.performance[k]} />
            ))}
          </div>
        </div>
      )}

      {/* Community signals — the 5 Issue 001.5 metrics: blind-buy safety,
          arc (opening vs dry-down), compliment rate, skin variance, and
          reformulation-era flag. Compact editorial strip, one row on desktop. */}
      {wearer.signals && <SignalsStrip signals={wearer.signals} />}

      {/* Smells like */}
      {wearer.smellsLike?.length > 0 && (
        <div style={{ paddingTop: 16, borderTop: "1px solid var(--rule)" }}>
          <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.1em", textTransform: "uppercase", marginBottom: 10 }}>Smells like</div>
          <div style={{ borderTop: "1px solid var(--ink)", borderBottom: "1px solid var(--ink)" }}>
            {wearer.smellsLike.map((s, i) => {
              const slug = s.slug || FMF.slugify(`${s.brand}-${s.name}`);
              return (
                <a key={i} href={`#/fragrance/${slug}`}
                   className="ledger-row"
                   style={{
                     display: "grid",
                     gridTemplateColumns: "72px 1fr 140px 1.3fr",
                     gap: 20,
                     padding: "16px 8px",
                     borderTop: i === 0 ? 0 : "1px solid var(--rule)",
                     alignItems: "center",
                     textDecoration: "none",
                     color: "inherit",
                     transition: "background 120ms",
                   }}
                   onMouseEnter={e => e.currentTarget.style.background = "var(--paper-deep)"}
                   onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
                  <div style={{ width: 64, height: 80 }}>
                    {/* Pass brand + name as hints so FMF.bottlePhoto can
                        brand/name-fuzzy match when the smells-like slug
                        doesn't exist exactly in the ingested catalog. */}
                    <Bottle frag={{ slug, brand: s.brand, name: s.name }} label={s.name} showLabel={false} style={{ width: "100%", height: "100%", padding: 0 }} />
                  </div>
                  <div>
                    <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.08em", textTransform: "uppercase", marginBottom: 3 }}>{s.brand}</div>
                    <div className="fraun" style={{ fontSize: 19, letterSpacing: "-0.01em" }}>
                      <span className="fraun-itl">{s.name}</span>
                    </div>
                  </div>
                  <div className="mono body-xs" style={{ color: s.relation.includes("dupe") ? "var(--oxblood-ink)" : "var(--ink-soft)", letterSpacing: "0.04em" }}>
                    {s.relation}
                  </div>
                  <div className="body-m" style={{ color: "var(--ink-soft)" }}>
                    {s.note}
                  </div>
                </a>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

/* FlankerStrip — sibling releases from the same brand line. Helps a
   reader navigating the Aventus family (Cologne / Absolu / Les Royales)
   choose the concentration/character they want. Current variant is marked. */
function FlankerStrip({ flankers }) {
  return (
    <div style={{ marginTop: 24, paddingTop: 18, borderTop: "1px solid var(--rule)" }}>
      <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.1em", textTransform: "uppercase", marginBottom: 10 }}>
        Flankers · same line
      </div>
      <div style={{ borderTop: "1px solid var(--rule)" }}>
        {flankers.map((f, i) => (
          <a key={f.slug} href={`#/fragrance/${f.slug}`}
             className="ledger-row"
             style={{
               display: "grid",
               /* 48px bottle · 60px year · 1.2fr label · 140px variant · 1.4fr note */
               gridTemplateColumns: "48px 60px 1.2fr 140px 1.4fr",
               gap: 14,
               padding: "10px 8px",
               borderBottom: "1px solid var(--rule)",
               textDecoration: "none",
               color: "inherit",
               alignItems: "center",
               background: f.current ? "var(--paper-deep)" : "transparent",
             }}
             onMouseEnter={e => { if (!f.current) e.currentTarget.style.background = "var(--paper-deep)"; }}
             onMouseLeave={e => { if (!f.current) e.currentTarget.style.background = "transparent"; }}>
            {/* Bottle thumbnail — falls back to typographic placeholder if no
                photo exists for this slug. §11-compliant via <Bottle>. */}
            <Bottle slug={f.slug} label={f.label}
                    showLabel={false}
                    style={{
                      width: 48, height: 60,
                      padding: 0,
                      background: "var(--paper)",
                      border: "1px solid var(--rule)",
                    }} />
            <span className="mono body-xs" style={{ color: "var(--ink-mute)" }}>{f.year}</span>
            <span className="fraun" style={{ fontSize: 17, letterSpacing: "-0.01em", lineHeight: 1.25 }}>
              {f.label} {f.current && <span className="mono body-xs" style={{ color: "var(--oxblood-ink)", marginLeft: 8, letterSpacing: "0.08em", textTransform: "uppercase" }}>Now viewing</span>}
            </span>
            <span className="mono body-xs" style={{ color: "var(--ink-soft)", letterSpacing: "0.02em" }}>
              {f.variant}
            </span>
            <span className="body-s" style={{ color: "var(--ink-soft)" }}>
              {f.note}
            </span>
          </a>
        ))}
      </div>
    </div>
  );
}

/* SignalsStrip — four community-sourced signals in a single compact row.
   Each cell is self-contained with a plain-English caption. Skin variance
   was removed (too tribal to quantify cleanly); compliment rate is now a
   qualitative label (low / moderate / high / very high). */
function SignalsStrip({ signals }) {
  if (!signals) return null;
  const s = signals;
  return (
    <div style={{ marginBottom: 24, paddingTop: 16, borderTop: "1px solid var(--rule)" }}>
      <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.1em", textTransform: "uppercase", marginBottom: 12, display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap" }}>
        <span>Community signals</span>
        <span style={{ color: "var(--ink-faint)" }}>· Issue 001.5 · seed editorial</span>
      </div>
      <div style={{
        display: "grid",
        gridTemplateColumns: "repeat(auto-fit, minmax(210px, 1fr))",
        border: "1px solid var(--rule)",
        background: "var(--paper-deep)",
      }}>
        {s.blindBuy       && <BlindBuyCell blindBuy={s.blindBuy} />}
        {s.compliments    && <ComplimentCell c={s.compliments} />}
        {s.arc            && <ArcCell arc={s.arc} />}
        {s.reformulation  && <ReformulationCell r={s.reformulation} />}
      </div>
    </div>
  );
}

/* Each cell is a self-contained block with consistent tight padding. */
function SignalCell({ children, title }) {
  return (
    <div title={title || undefined}
         style={{
           padding: "12px 14px 14px",
           borderRight: "1px solid var(--rule)",
           borderBottom: "1px solid var(--rule)",
           minWidth: 0,
         }}>
      {children}
    </div>
  );
}

function CellHead({ k, sub }) {
  return (
    <>
      <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.1em", textTransform: "uppercase", marginBottom: 6, fontSize: 10.5 }}>{k}</div>
      {sub && <div className="body-xs" style={{ color: "var(--ink-soft)", marginBottom: 8, lineHeight: 1.45, fontSize: 12.5 }}>{sub}</div>}
    </>
  );
}

/* Cell — Blind-buy safety score. Big number + caption + progress rail. */
function BlindBuyCell({ blindBuy: b }) {
  const tone = b.label === "safe" ? "var(--sig-buy)" : b.label === "risky" ? "var(--oxblood)" : "var(--sig-hold)";
  const pct = Math.max(0, Math.min(100, (b.score / 10) * 100));
  const caption =
    b.label === "safe"    ? "Low-risk to buy unsampled." :
    b.label === "careful" ? "Sample before committing." :
                            "High variance — sample first.";
  return (
    <SignalCell title={b.breakdown}>
      <CellHead k="Blind-buy safety" sub={caption} />
      <div style={{ display: "flex", alignItems: "baseline", gap: 6, marginBottom: 8 }}>
        <span className="fraun" style={{ fontSize: 28, letterSpacing: "-0.02em", color: tone, fontFeatureSettings: "\"tnum\",\"zero\"", lineHeight: 1 }}>{b.score.toFixed(1)}</span>
        <span className="mono body-xs" style={{ color: "var(--ink-mute)", fontSize: 11 }}>/ 10</span>
      </div>
      <div style={{ position: "relative", height: 3, background: "var(--rule)" }}>
        <div style={{ position: "absolute", inset: 0, width: `${pct}%`, background: tone }} />
      </div>
    </SignalCell>
  );
}

/* Cell — Compliment rate. Qualitative label (harder to quantify precisely
   than the raw %, which felt over-confident). The underlying rate still
   drives the tone + caption; just not shown as a number. */
function ComplimentCell({ c }) {
  const level = complimentLevel(c.rate);
  return (
    <SignalCell title={`Community-reported unsolicited-compliment rate · n=${FMF.fmtInt(c.n)}`}>
      <CellHead k="Compliment rate" sub={level.caption} />
      <div style={{ display: "flex", alignItems: "baseline", gap: 8, marginBottom: 6 }}>
        <span className="fraun" style={{ fontSize: 24, letterSpacing: "-0.015em", color: level.tone, textTransform: "uppercase", lineHeight: 1 }}>
          {level.label}
        </span>
      </div>
      <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.02em", fontSize: 10.5 }}>
        community · n={FMF.fmtInt(c.n)}
      </div>
    </SignalCell>
  );
}

/* Rate → qualitative bucket. Tuned to fragrance norms — 50%+ is genuinely
   a lot of compliments per wear, since most fragrances get near-zero. */
function complimentLevel(rate) {
  if (rate >= 0.70) return { label: "Very high", tone: "var(--sig-buy)",  caption: "Very frequently draws unsolicited compliments." };
  if (rate >= 0.40) return { label: "High",      tone: "var(--sig-buy)",  caption: "Often draws unsolicited compliments." };
  if (rate >= 0.20) return { label: "Moderate",  tone: "var(--sig-hold)", caption: "Occasionally draws compliments." };
  return               { label: "Low",       tone: "var(--ink-mute)", caption: "Rarely draws unsolicited compliments." };
}

/* Cell — Arc: opening score vs dry-down score.
   Twin labelled bars make the concept obvious: "phase A scored X/10,
   phase B scored Y/10." Plain-English verdict below. */
function ArcCell({ arc }) {
  const verdict =
    arc.delta >= 2     ? "Opening polarizes — dry-down earns the praise." :
    arc.delta >= 1     ? "Small shift from opening to dry-down." :
                         "Smooth arc — opening and dry-down agree.";
  return (
    <SignalCell title={`Community scores · first 30 min vs hour 4+ · n=${FMF.fmtInt(arc.n)}`}>
      <CellHead k="How it changes over time" sub={verdict} />
      <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
        <ArcBar label="Opening · 0-30 min" value={arc.opening} />
        <ArcBar label="Dry-down · 4h+"    value={arc.drydown} />
      </div>
      <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 8, letterSpacing: "0.02em", fontSize: 10 }}>
        scored / 10 · n={FMF.fmtInt(arc.n)}
      </div>
    </SignalCell>
  );
}

function ArcBar({ label, value }) {
  const pct = Math.max(0, Math.min(100, (value / 10) * 100));
  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 3 }}>
        <span className="mono body-xs" style={{ color: "var(--ink-soft)", letterSpacing: "0.02em", fontSize: 10 }}>{label}</span>
        <span className="mono" style={{ fontSize: 12.5, color: "var(--ink)", fontFeatureSettings: "\"tnum\",\"zero\"" }}>{value.toFixed(1)}<span style={{ color: "var(--ink-mute)", fontSize: 10 }}>/10</span></span>
      </div>
      <div style={{ position: "relative", height: 5, background: "var(--paper)", border: "1px solid var(--rule)" }}>
        <div style={{ position: "absolute", inset: 0, width: `${pct}%`, background: "var(--ink)" }} />
      </div>
    </div>
  );
}

/* Cell — Skin variance. 10-dot pictogram showing the fraction of wearers
   whose skin "eats" the fragrance. Plain-English verdict + chemistry skew. */
function SkinCell({ skin: s }) {
  const eatsN = Math.max(0, Math.min(10, Math.round(s.eatsPct * 10)));
  const normalN = 10 - eatsN;
  const skewTxt =
    s.skew === "turns-sweeter" ? "When it shifts, it tends to read sweeter than intended."  :
    s.skew === "turns-sharper" ? "When it shifts, it tends to read sharper / more aggressive." :
    s.skew === "soapy"         ? "Some skin pushes it toward a clean, soapy quality." :
                                 "When it shifts, the character mostly stays true to the accord.";
  const riskTone = s.riskLabel === "high" ? "var(--oxblood)" : s.riskLabel === "moderate" ? "var(--sig-hold)" : "var(--sig-buy)";
  const eatsPct = Math.round(s.eatsPct * 100);
  return (
    <SignalCell title={`Share of wearers reporting abnormally fast fade + the dominant chemistry skew · n=${FMF.fmtInt(s.n)}`}>
      <CellHead k="How your skin may change it" sub={`${eatsPct} out of every 100 wearers say it disappears fast on their skin.`} />
      {/* 10-dot pictogram: filled = normal performance, outline = "eats". */}
      <div style={{ display: "flex", gap: 5, marginBottom: 10 }} role="img" aria-label={`${eatsN} of 10 wearers report fast fade`}>
        {Array.from({ length: 10 }).map((_, i) => {
          const isEats = i >= normalN;
          return (
            <span key={i} style={{
              width: 13, height: 13, borderRadius: "50%",
              border: `1px solid ${isEats ? "var(--ink-mute)" : "var(--ink)"}`,
              background: isEats ? "transparent" : "var(--ink)",
            }} />
          );
        })}
      </div>
      <div style={{ display: "flex", alignItems: "baseline", gap: 8, marginBottom: 6 }}>
        <span className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.1em", textTransform: "uppercase", fontSize: 10 }}>Variance risk</span>
        <span className="mono" style={{ color: riskTone, textTransform: "uppercase", letterSpacing: "0.06em", fontSize: 13 }}>{s.riskLabel}</span>
      </div>
      <div className="body-s" style={{ color: "var(--ink-soft)", lineHeight: 1.45 }}>
        {skewTxt}
      </div>
    </SignalCell>
  );
}

/* Cell — Formula era. Horizontal timeline of documented reformulations.
   Each event is a dot on a line, labelled with year. Major events are
   filled; minor events are open circles. The rightmost event (current era)
   carries an oxblood halo so the reader knows which version they're
   about to buy. */
function ReformulationCell({ r }) {
  const events = Array.isArray(r.events) && r.events.length > 0
    ? r.events
    : [{ year: r.lastMajor, label: "Last documented change", major: true, current: true }];
  const years = events.map(e => Number(e.year));
  const minY = Math.min(...years);
  const maxY = Math.max(...years);
  const span = Math.max(1, maxY - minY);
  return (
    <SignalCell title={r.note}>
      {/* Kicker only — no caption. The timeline + labelled events speak for
          themselves and we need the vertical space for all 4 events, not just
          the major/current ones. */}
      <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.1em", textTransform: "uppercase", marginBottom: 8, fontSize: 10.5 }}>Formula timeline</div>
      {/* Thin horizontal rail with one dot per event. */}
      <div style={{ position: "relative", height: 14, margin: "2px 6px 8px" }}>
        <div style={{ position: "absolute", left: 0, right: 0, top: 6, height: 1, background: "var(--ink-faint)" }} />
        {events.map((e, i) => {
          const pct = ((Number(e.year) - minY) / span) * 100;
          const isMajor = !!e.major;
          const isCurrent = !!e.current;
          return (
            <span key={i} aria-label={`${e.year}: ${e.label}`} style={{
              position: "absolute",
              left: `calc(${pct}% - ${isMajor ? 5 : 3.5}px)`,
              top: isMajor ? 1 : 3,
              width: isMajor ? 10 : 7,
              height: isMajor ? 10 : 7,
              borderRadius: "50%",
              background: isMajor ? "var(--ink)" : "var(--paper)",
              border: isMajor ? "0" : "1px solid var(--ink-soft)",
              boxShadow: isCurrent ? "0 0 0 3px color-mix(in oklab, var(--oxblood) 22%, transparent)" : "none",
            }} />
          );
        })}
      </div>
      {/* Every event listed — user called out that hiding 2017 + 2020 minor
          events was misleading. Full timeline or none. */}
      <ol style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 3 }}>
        {events.map((e, i) => (
          <li key={i} className="mono body-xs" style={{
            display: "flex",
            gap: 8,
            fontSize: 10.5,
            color: e.current ? "var(--ink)" : "var(--ink-soft)",
            letterSpacing: "0.02em",
          }}>
            <span style={{ color: e.current ? "var(--oxblood-ink)" : "var(--ink-mute)", minWidth: 30 }}>{e.year}</span>
            <span style={{ flex: 1, lineHeight: 1.4, overflowWrap: "break-word" }}>
              {e.label}
              {e.current && <span style={{ color: "var(--oxblood-ink)", marginLeft: 6, textTransform: "uppercase", letterSpacing: "0.08em", fontSize: 9 }}>· today</span>}
            </span>
          </li>
        ))}
      </ol>
    </SignalCell>
  );
}

/* ════════════════════════════════════════════════════════════════════
   WhatPeopleSay — community-voted pros + cons with vote counts.
   Editorial discipline: the editor who writes wearer.pros never sees
   these counts (§7 firewall); community data never influences ranking.
   Shown below the editorial wearer panel as the "what the crowd says"
   counterpart to "what the editor says".
   ════════════════════════════════════════════════════════════════════ */
function WhatPeopleSay({ data }) {
  if (!data || (!data.pros?.length && !data.cons?.length)) return null;
  return (
    <section className="container" style={{ padding: "40px 32px 16px" }}>
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", flexWrap: "wrap", gap: 12, marginBottom: 14 }}>
        <div style={{ display: "flex", alignItems: "baseline", gap: 12, flexWrap: "wrap" }}>
          <div className="fraun" style={{ fontSize: "clamp(22px, 2.4vw, 28px)", letterSpacing: "-0.012em", lineHeight: 1.15 }}>
            What people <span className="fraun-itl">say.</span>
          </div>
          {data.seedEditorial && (
            <span className="mono body-xs" style={{
              color: "var(--oxblood-ink)",
              letterSpacing: "0.14em",
              textTransform: "uppercase",
              fontSize: 10,
              padding: "3px 7px 4px",
              border: "1px solid var(--oxblood)",
              borderRadius: 2,
            }} title="These ranks + counts are editorially synthesized from observed community discussion. Real scraped/federated tallies replace them when partner feeds land.">
              seed · editor synthesis
            </span>
          )}
        </div>
        <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.04em", textAlign: "right", maxWidth: 420 }}>
          Themes seen across n≈{FMF.fmtInt(data.n)} community reviews
          <br />
          <span style={{ color: "var(--ink-faint)" }}>{data.source}</span>
        </div>
      </div>
      <div className="wps-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}>
        <WpsColumn label="Pros"  tone="var(--sig-buy)"     items={data.pros} />
        <WpsColumn label="Cons"  tone="var(--oxblood-ink)" items={data.cons} />
      </div>
      <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 14, letterSpacing: "0.02em" }}>
        Fetched {data.fetchedAt}<Cite n={7} check="separated" />
      </div>
    </section>
  );
}

function WpsColumn({ label, tone, items }) {
  const total = items.reduce((s, i) => s + i.up + i.down, 0);
  return (
    <div style={{ border: "1px solid var(--rule)", background: "var(--paper)" }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 10, padding: "12px 16px", borderBottom: "1px solid var(--rule)", background: "var(--paper-deep)" }}>
        <span className="mono body-xs" style={{ color: tone, letterSpacing: "0.12em", textTransform: "uppercase", fontWeight: 500 }}>{label}</span>
        <span className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.02em", marginLeft: "auto" }}>{FMF.fmtInt(total)} votes</span>
      </div>
      <ul className="wps-list" style={{ listStyle: "none", padding: 0, margin: 0 }}>
        {items.map((it, i) => (
          <li key={i} className="wps-row" style={{
            display: "flex",
            alignItems: "center",
            gap: 14,
            padding: "11px 16px",
            borderTop: i === 0 ? 0 : "1px solid var(--rule)",
          }}>
            <span className="mono wps-vote" style={{ display: "inline-flex", alignItems: "center", gap: 4, color: "var(--sig-buy)", fontSize: 13, fontFeatureSettings: "\"tnum\",\"zero\"", minWidth: 44, flexShrink: 0 }}>
              <ThumbUp /> {FMF.fmtInt(it.up)}
            </span>
            <span className="mono wps-vote" style={{ display: "inline-flex", alignItems: "center", gap: 4, color: "var(--ink-mute)", fontSize: 13, fontFeatureSettings: "\"tnum\",\"zero\"", minWidth: 40, flexShrink: 0 }}>
              <ThumbDown /> {FMF.fmtInt(it.down)}
            </span>
            <span className="fraun" style={{ fontSize: 15, letterSpacing: "-0.005em", lineHeight: 1.4, color: "var(--ink)", flex: 1, minWidth: 0 }}>
              {it.text}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
}

function ThumbUp() {
  return (
    <svg width="11" height="11" viewBox="0 0 16 16" aria-hidden="true">
      <path d="M6 14V7L9 2c1 0 2 .5 2 2v3h4c.6 0 1 .5 1 1l-1 5c-.1.6-.6 1-1.1 1H6Z" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"/>
    </svg>
  );
}
function ThumbDown() {
  return (
    <svg width="11" height="11" viewBox="0 0 16 16" aria-hidden="true">
      <path d="M10 2v7l-3 5c-1 0-2-.5-2-2V9H1c-.6 0-1-.5-1-1l1-5c.1-.6.6-1 1.1-1H10Z" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"/>
    </svg>
  );
}

/* ════════════════════════════════════════════════════════════════════
   ReviewsSection — reviews aggregated from external communities.
   Attribution-first: each card carries source, username, excerpt, and a
   link to the original. FindMyFrag does not own or host these reviews.
   On takedown request, an entry is removed. Profile images are
   typographic initial-avatars (§11: no AI-generated imagery).
   ════════════════════════════════════════════════════════════════════ */
function ReviewsSection({ reviews }) {
  if (!reviews || reviews.length === 0) return null;
  return (
    <section className="container" style={{ padding: "48px 32px 40px" }}>
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", flexWrap: "wrap", gap: 12, marginBottom: 18 }}>
        <div>
          <div className="kicker" style={{ marginBottom: 8 }}>Around the web · editor-compiled · awaiting live feed</div>
          <h2 className="fraun" style={{ fontSize: "clamp(26px, 3vw, 36px)", letterSpacing: "-0.018em", lineHeight: 1.1, margin: 0 }}>
            What real users are <span className="fraun-itl">saying.</span>
          </h2>
        </div>
        <div className="mono body-xs" style={{ color: "var(--ink-mute)", maxWidth: 340, textAlign: "right", lineHeight: 1.5, letterSpacing: "0.02em" }}>
          Voices from Fragrantica, r/fragrance, YouTube, TikTok, Basenotes, Parfumo.
          Each quote links to the original.
          <br />
          <span style={{ color: "var(--ink-faint)", fontSize: 10.5 }}>
            YouTube + TikTok avatars resolve to the creator's live platform
            PFP via unavatar.io; anonymous Reddit / Fragrantica / Basenotes /
            Parfumo handles use the platform mark.
          </span>
          <Cite n={7} check="separated" />
        </div>
      </div>
      <div className="reviews-grid" style={{
        display: "grid",
        gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
        gap: 14,
      }}>
        {reviews.map((r, i) => <ReviewCard key={i} review={r} />)}
      </div>
    </section>
  );
}

/* Source palette — subtle, one brand hue per platform. We use color-mix
   to keep the site's warm paper surface dominant; platform tones are
   accents only, not dominant colors. */
const SOURCE_META = {
  fragrantica: { label: "Fragrantica", hue: "oklch(0.55 0.15 25)" },
  reddit:      { label: "Reddit",      hue: "oklch(0.60 0.18 45)" },
  youtube:     { label: "YouTube",     hue: "oklch(0.54 0.22 30)" },
  tiktok:      { label: "TikTok",      hue: "oklch(0.30 0.03 230)" },
  basenotes:   { label: "Basenotes",   hue: "oklch(0.40 0.04 60)" },
  parfumo:     { label: "Parfumo",     hue: "oklch(0.50 0.08 180)" },
};

function ReviewCard({ review }) {
  const meta = SOURCE_META[review.source] || { label: review.sourceLabel || "Web", hue: "var(--ink-soft)" };
  const secondary =
    review.rating   !== undefined ? `${review.rating}/10` :
    review.upvotes  !== undefined ? `${FMF.fmtInt(review.upvotes)} upvotes` :
    review.views    !== undefined ? `${review.views} views` :
    review.likes    !== undefined ? `${review.likes} likes` :
    null;
  const date = review.date ? new Date(review.date).toLocaleDateString("en-US", { month: "short", year: "numeric" }) : "";
  return (
    <a href={review.url} target="_blank" rel="noopener noreferrer"
       className="review-card"
       style={{
         display: "block",
         padding: 18,
         border: "1px solid var(--rule)",
         background: "var(--paper)",
         textDecoration: "none",
         color: "inherit",
         transition: "border-color 140ms, background 140ms",
       }}
       onMouseEnter={e => { e.currentTarget.style.borderColor = "var(--ink)"; e.currentTarget.style.background = "var(--paper-deep)"; }}
       onMouseLeave={e => { e.currentTarget.style.borderColor = "var(--rule)"; e.currentTarget.style.background = "var(--paper)"; }}>
      <div style={{ display: "flex", gap: 12, alignItems: "center", marginBottom: 10 }}>
        <Avatar user={review.user} hue={meta.hue} avatarUrl={review.avatarUrl} source={review.source} />
        <div style={{ minWidth: 0, flex: 1 }}>
          <div className="fraun" style={{ fontSize: 15, letterSpacing: "-0.008em", lineHeight: 1.2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
            {review.user}
          </div>
          <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.06em", marginTop: 3, display: "flex", gap: 6, alignItems: "baseline", flexWrap: "wrap" }}>
            <span style={{ color: meta.hue, textTransform: "uppercase", letterSpacing: "0.1em", fontSize: 10 }}>{meta.label}</span>
            {secondary && <><span style={{ color: "var(--ink-faint)" }}>·</span><span style={{ fontSize: 10.5 }}>{secondary}</span></>}
            {date && <><span style={{ color: "var(--ink-faint)" }}>·</span><span style={{ fontSize: 10.5 }}>{date}</span></>}
          </div>
        </div>
        <svg width="11" height="11" viewBox="0 0 10 10" aria-hidden="true" style={{ color: "var(--ink-mute)", flexShrink: 0 }}>
          <path d="M2 8 L8 2 M3 2 L8 2 L8 7" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </div>
      <blockquote className="fraun" style={{ margin: 0, fontSize: 15, lineHeight: 1.5, color: "var(--ink-soft)", letterSpacing: "-0.005em" }}>
        &ldquo;{review.excerpt}&rdquo;
      </blockquote>
    </a>
  );
}

/* Platform-native avatar — each source (Reddit / YouTube / TikTok / etc.)
   renders in its platform's visual language rather than a fabricated face.
   Honest: no specific real person is misrepresented, and the reader can
   tell at a glance whether a review came from Reddit vs YouTube vs TikTok.

   Resolution order:
     1. `avatarUrl` in the review data → real hotlinked platform avatar
        (used when a real feed replaces a seed review; e.g. a real
        YouTube thumbnail URL).
     2. Platform-native SVG icon rendered inline (current default for all
        seed reviews). Deterministic hue variation per username so the
        row doesn't look monotonous.
     3. Typographic initial fallback if image load fails.
   Never uses stock photos or face generators — those imply personhood
   this data doesn't actually have. */
function Avatar({ user, hue, avatarUrl, source }) {
  const clean = (user || "").replace(/^[u@\/]+/g, "").trim() || "user";
  const initial = clean.charAt(0).toUpperCase();
  const [brokenUrl, setBrokenUrl] = React.useState(false);

  const ringSize = 38;
  const ringStyle = {
    width: ringSize, height: ringSize,
    borderRadius: "50%",
    flexShrink: 0,
    display: "block",
    objectFit: "cover",
  };

  // Tier 1: real provided avatar URL (for when a feed hydrates a real reviewer).
  if (avatarUrl && !brokenUrl) {
    return (
      <img src={avatarUrl} alt={user || "reviewer"} loading="lazy"
           width={ringSize} height={ringSize}
           onError={() => setBrokenUrl(true)}
           style={{ ...ringStyle, border: `1px solid color-mix(in oklab, ${hue} 35%, var(--rule))` }} />
    );
  }

  // Tier 2: platform-native SVG. Rendered inline (no network).
  return <PlatformAvatar source={source} initial={initial} seed={clean} />;
}

/* PlatformAvatar — renders each platform's visual signature:
     reddit     → Snoo-style antenna silhouette on orange
     youtube    → red play-button tile
     tiktok     → black tile with gradient "note"
     fragrantica→ red tile with "F"
     basenotes  → warm grey tile with "BN"
     parfumo    → teal tile with "P"
   All inline SVG (no external fetches, no broken images, works offline).
   Seed-tinted background variation so consecutive Reddit users don't
   look identical at a glance. */
function PlatformAvatar({ source, initial, seed }) {
  const hash = FMF.seed(seed);
  const varHue = Math.floor(hash() * 40) - 20;   // ±20° hue wobble
  const svgSize = 38;
  const common = {
    width: svgSize, height: svgSize,
    style: { borderRadius: "50%", display: "block", flexShrink: 0 },
    "aria-hidden": "true",
  };

  if (source === "reddit") {
    // Snoo-lite: orange circle + antenna. Not Reddit's trademarked snoo —
    // a clean editorial nod to the platform's visual language.
    return (
      <svg {...common} viewBox="0 0 40 40">
        <circle cx="20" cy="20" r="20" fill="oklch(0.68 0.2 35)" />
        <circle cx="20" cy="24" r="11" fill="var(--paper)" />
        <circle cx="16" cy="22" r="1.5" fill="var(--ink)" />
        <circle cx="24" cy="22" r="1.5" fill="var(--ink)" />
        <path d="M16 27 Q20 30 24 27" stroke="var(--ink)" strokeWidth="1.3" fill="none" strokeLinecap="round" />
        <line x1="20" y1="13" x2="20" y2="8" stroke="var(--ink)" strokeWidth="1.4" strokeLinecap="round" />
        <circle cx="20" cy="7" r="1.6" fill="var(--ink)" />
      </svg>
    );
  }
  if (source === "youtube") {
    return (
      <svg {...common} viewBox="0 0 40 40">
        <circle cx="20" cy="20" r="20" fill="oklch(0.58 0.22 28)" />
        <path d="M15 13 L28 20 L15 27 Z" fill="var(--paper)" />
      </svg>
    );
  }
  if (source === "tiktok") {
    return (
      <svg {...common} viewBox="0 0 40 40">
        <circle cx="20" cy="20" r="20" fill="oklch(0.22 0.03 230)" />
        {/* Musical note silhouette in teal/pink accent */}
        <path d="M22 12 L22 24 Q22 28 18 28 Q14 28 14 25 Q14 22 18 22 Q19 22 20 23 L20 12 Q22 11 25 11" stroke="oklch(0.72 0.19 180)" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" />
      </svg>
    );
  }
  if (source === "fragrantica") {
    return (
      <svg {...common} viewBox="0 0 40 40">
        <circle cx="20" cy="20" r="20" fill={`oklch(0.55 0.15 ${25 + varHue})`} />
        <text x="20" y="27" textAnchor="middle"
              fontFamily="var(--font-display)"
              fontSize="20" fontStyle="italic"
              fill="var(--paper)" letterSpacing="-0.02em">F</text>
      </svg>
    );
  }
  if (source === "basenotes") {
    return (
      <svg {...common} viewBox="0 0 40 40">
        <circle cx="20" cy="20" r="20" fill={`oklch(0.40 0.04 ${60 + varHue})`} />
        <text x="20" y="26" textAnchor="middle"
              fontFamily="var(--font-mono)"
              fontSize="12" fontWeight="500"
              fill="var(--paper)" letterSpacing="0.04em">BN</text>
      </svg>
    );
  }
  if (source === "parfumo") {
    return (
      <svg {...common} viewBox="0 0 40 40">
        <circle cx="20" cy="20" r="20" fill={`oklch(0.50 0.08 ${180 + varHue})`} />
        <text x="20" y="27" textAnchor="middle"
              fontFamily="var(--font-display)"
              fontSize="20" fontStyle="italic"
              fill="var(--paper)" letterSpacing="-0.02em">P</text>
      </svg>
    );
  }

  // Unknown source → typographic initial with ink border.
  return (
    <svg {...common} viewBox="0 0 40 40">
      <circle cx="20" cy="20" r="19.5" fill="var(--paper-deep)" stroke="var(--ink-soft)" strokeWidth="1" />
      <text x="20" y="26" textAnchor="middle"
            fontFamily="var(--font-display)"
            fontSize="18" fontStyle="italic"
            fill="var(--ink)">{initial}</text>
    </svg>
  );
}

function PerformanceBars({ label, data }) {
  const labels = ["weak", "low", "mod", "strong", "beast"];
  const max = Math.max(...data.buckets);
  return (
    <div>
      <div className="mono body-xs" style={{ color: "var(--ink-soft)", letterSpacing: "0.08em", textTransform: "uppercase", marginBottom: 8 }}>
        {label} <span style={{ color: "var(--ink-mute)" }}>· n={FMF.fmtInt(data.n)}</span>
      </div>
      <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
        {data.buckets.map((v, i) => (
          <div key={i} style={{ display: "grid", gridTemplateColumns: "44px 1fr 28px", alignItems: "center", gap: 8 }}>
            <span className="mono body-xs" style={{ color: "var(--ink-mute)", fontSize: 10, letterSpacing: "0.04em" }}>{labels[i]}</span>
            <div style={{ height: 6, background: "var(--paper-deep)", position: "relative" }}>
              <div style={{ position: "absolute", inset: 0, width: `${(v / max) * 100}%`, background: "var(--ink)" }} />
            </div>
            <span className="mono body-xs" style={{ color: "var(--ink)", textAlign: "right", fontSize: 10.5 }}>{v}%</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function StatRow({ label, v, max, n, sigma }) {
  return (
    <div style={{ borderTop: "1px solid var(--rule)", paddingTop: 12 }}>
      <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.08em", textTransform: "uppercase", marginBottom: 6 }}>{label}</div>
      <DataStat value={v} max={max} n={n} sigma={sigma} size="lg" />
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   PRICE HISTORY — 90-day calendar heatmap, cheapest day in gold+oxblood
   ══════════════════════════════════════════════════════════════════════ */
function HistoryHeatmap({ history, todayTotal, slug, frag }) {
  const min = Math.min(...history);
  const max = Math.max(...history);
  const minIdx = history.findIndex(v => v === min);
  const daysAgoMin = history.length - 1 - minIdx;

  // Layout: 90 cells in a ~15-wide grid (6 rows x 15 cols)
  const cols = 15;
  const rows = Math.ceil(history.length / cols);

  return (
    <div style={{ display: "grid", gridTemplateColumns: "1.05fr 1fr", gap: 56, alignItems: "start" }}>
      <div>
        <div style={{ display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)`, gap: 4 }}>
          {history.map((v, i) => {
            const t = (v - min) / (max - min || 1); // 0 = cheapest, 1 = priciest
            const isMin = i === minIdx;
            const isToday = i === history.length - 1;
            // Fill darkness inversely proportional to t (cheaper = more saturated ink)
            const bg = isMin
              ? "var(--oxblood)"
              : `oklch(${(0.96 - 0.14 * (1 - t)).toFixed(3)} 0.008 60)`;
            return (
              <div
                key={i}
                title={`day ${i - history.length + 1}: ${FMF.fmtUsd(v)}`}
                style={{
                  aspectRatio: "1 / 1",
                  background: bg,
                  border: isToday ? "1.5px solid var(--ink)" : "1px solid var(--rule)",
                  position: "relative",
                }}
              />
            );
          })}
        </div>

        {/* Price alert — slot below the heatmap, uses the empty visual space.
            Auto-fills contact from the profile record so returning readers
            don't re-enter every time. §6: localStorage only in v0.1. */}
        {slug && <PriceAlert slug={slug} frag={frag} todayTotal={todayTotal} histLow={min} />}
      </div>

      <div style={{ fontFamily: "var(--font-mono)", fontSize: 13, lineHeight: 1.7 }}>
        <p className="fraun" style={{ fontSize: 22, lineHeight: 1.3, letterSpacing: "-0.01em", margin: 0, color: "var(--ink)" }}>
          Cheapest day in the last 90: <strong style={{ color: "var(--oxblood-ink)" }}>{FMF.fmtUsd(min)}</strong>, {daysAgoMin} days ago.
          You're <strong style={{ color: "var(--oxblood-ink)" }}>{FMF.fmtUsd(todayTotal - min)}</strong> above that today.
        </p>
        <div style={{ marginTop: 24, color: "var(--ink-mute)" }}>
          <Row k="Range (90d)"   v={`${FMF.fmtUsd(min)} — ${FMF.fmtUsd(max)}`} />
          <Row k="Mean · median" v={`${FMF.fmtUsd(avg(history))} · ${FMF.fmtUsd(median(history))}`} />
          <Row k="σ (stdev)"     v={FMF.fmtUsd(stdev(history))} />
          <Row k="Today"         v={`${FMF.fmtUsd(todayTotal)} · pct rank ${pctRank(history, todayTotal)}%`} />
          <Row k="Poll interval" v="4h · 540 snapshots" />
        </div>

        <div style={{ marginTop: 28, paddingTop: 16, borderTop: "1px solid var(--rule)", color: "var(--ink-soft)" }}>
          <span className="mono body-xs" style={{ letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--ink-mute)" }}>Regret meter</span>
          <div style={{ marginTop: 8 }} className="fraun body-l">
            Had you bought on the cheapest day, you'd be{" "}
            <strong style={{ color: "var(--sig-buy)" }}>{FMF.fmtUsd(todayTotal - min)} richer</strong>.{" "}
            The formula is public. We do not predict next week.
          </div>
        </div>
      </div>
    </div>
  );
}

function Row({ k, v }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between", borderTop: "1px solid var(--rule)", padding: "7px 0" }}>
      <span>{k}</span><span style={{ color: "var(--ink)" }}>{v}</span>
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   PRICE ALERT — "notify me when landed ≤ target". Auto-fills email/phone
   from FMF.profile so returning readers never re-enter. §6-compliant:
   contact string stays in localStorage until the backend launches; at
   that point we surface the pending-sync state rather than silently
   flushing it behind the reader's back.
   ══════════════════════════════════════════════════════════════════════ */
function PriceAlert({ slug, frag, todayTotal, histLow }) {
  const [profile, setProfile] = useStateD(() => FMF.profile?.get?.() || { contact: "", kind: "email" });
  const [alerts, setAlerts]   = useStateD(() => FMF.alerts?.forSlug?.(slug) || []);
  const [contact, setContact] = useStateD(profile.contact || "");
  const [target, setTarget]   = useStateD(() => {
    // Default target = the 90d low (the clearly-achievable recent price).
    // If the reader has already set alerts for this slug, show the most aggressive.
    const existing = FMF.alerts?.forSlug?.(slug) || [];
    if (existing.length > 0) return Math.min(...existing.map(a => a.target)).toFixed(0);
    return Math.round(histLow).toString();
  });
  const [err, setErr] = useStateD("");
  const [ok,  setOk]  = useStateD(false);

  // Keep the contact field in sync if profile changes elsewhere (another tab,
  // another detail page). Never overwrite an in-progress edit, though.
  useEffectD(() => {
    function onProfile() { const p = FMF.profile.get(); setProfile(p); if (!contact) setContact(p.contact || ""); }
    function onAlerts()  { setAlerts(FMF.alerts.forSlug(slug)); }
    window.addEventListener("fmf:profile-changed", onProfile);
    window.addEventListener("fmf:alerts-changed", onAlerts);
    return () => {
      window.removeEventListener("fmf:profile-changed", onProfile);
      window.removeEventListener("fmf:alerts-changed", onAlerts);
    };
  }, [slug, contact]);

  const hasProfile = (profile.contact || "").length > 0;

  function submit(e) {
    e.preventDefault();
    setErr(""); setOk(false);
    const trimmed = contact.trim();
    const num = parseFloat(target);
    if (!trimmed) { setErr("Enter email or phone to get notified."); return; }
    const kind = FMF.profile.detectKind(trimmed);
    if (kind === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) { setErr("That doesn't look like a valid email."); return; }
    if (kind === "phone" && trimmed.replace(/\D/g, "").length < 7) { setErr("That phone number looks too short."); return; }
    if (isNaN(num) || num <= 0) { setErr("Enter a target price above zero."); return; }
    if (num >= todayTotal)      { setErr(`Target ${FMF.fmtUsd(num)} is already at or above today's ${FMF.fmtUsd(todayTotal)}.`); return; }

    FMF.profile.set({ contact: trimmed, kind });
    FMF.alerts.add({ slug, target: num, contact: trimmed, kind });
    setOk(true);
    setTimeout(() => setOk(false), 3600);
  }

  function removeAlert(t) {
    FMF.alerts.remove(slug, t);
  }

  const savings = todayTotal - parseFloat(target || "0");
  const savingsValid = !isNaN(savings) && savings > 0;

  return (
    <div className="price-alert">
      <div className="price-alert__head">
        <div>
          <div className="price-alert__kicker mono body-xs">Notify me when price drops</div>
          <div className="price-alert__title fraun">
            Catch the <span className="fraun-itl">next</span> dip.
          </div>
        </div>
        {hasProfile && (
          <div className="price-alert__profile mono body-xs" title="Using the contact you saved on this browser">
            using saved {profile.kind} · <button type="button" onClick={() => FMF.profile.clear()} className="price-alert__forget">forget</button>
          </div>
        )}
      </div>

      <form className="price-alert__form" onSubmit={submit} noValidate>
        <label className="price-alert__field">
          <span className="price-alert__label">Email or phone</span>
          <input
            type="text"
            inputMode="email"
            autoComplete="email"
            placeholder={hasProfile ? profile.contact : "you@domain.com or +1 555 0100"}
            value={contact}
            onChange={(e) => setContact(e.target.value)}
            className="price-alert__input mono"
          />
        </label>
        <label className="price-alert__field price-alert__field--target">
          <span className="price-alert__label">Alert me at ≤</span>
          <span className="price-alert__input-prefix">$</span>
          <input
            type="number"
            min="1"
            step="1"
            value={target}
            onChange={(e) => setTarget(e.target.value)}
            className="price-alert__input mono price-alert__input--target"
          />
        </label>
        <button type="submit" className="price-alert__submit">
          {ok ? "✓ Alert saved" : "Notify me"}
        </button>
      </form>

      <div className="price-alert__foot mono body-xs">
        {err && <span className="price-alert__err">{err}</span>}
        {!err && savingsValid && (
          <span>
            That's <strong style={{ color: "var(--sig-buy)" }}>{FMF.fmtUsd(savings)}</strong> below today.{" "}
            Cheapest day in 90: <strong style={{ color: "var(--oxblood-ink)" }}>{FMF.fmtUsd(histLow)}</strong>.
          </span>
        )}
        {!err && !savingsValid && (
          <span style={{ color: "var(--ink-mute)" }}>We'll ping you once — then the alert clears.</span>
        )}
      </div>

      {alerts.length > 0 && (
        <div className="price-alert__list">
          <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.1em", textTransform: "uppercase", marginBottom: 8 }}>
            Active alerts on this fragrance
          </div>
          <ul>
            {alerts.map(a => (
              <li key={a.target} className="mono body-xs">
                <span>≤ <strong style={{ color: "var(--ink)" }}>{FMF.fmtUsd(a.target)}</strong> to {a.contact}</span>
                <button type="button" onClick={() => removeAlert(a.target)} className="price-alert__remove">remove</button>
              </li>
            ))}
          </ul>
        </div>
      )}

    </div>
  );
}

/* ═════════════════════════════════════════════════════════════════
   DECANTS — sampling economics. Per-ml pricing, never ranked against bottles.
   Shown as a compact strip after the bottle ledger.
   ═════════════════════════════════════════════════════════════════ */
/* ═════════════════════════════════════════════════════════════════
   GIFT SETS — bundled offers kept separate so the ledger's bottle
   leaderboard isn't polluted by packaging arithmetic.
   ═════════════════════════════════════════════════════════════════ */
function GiftSetsStrip({ rows, sizeMl }) {
  if (!rows || rows.length === 0) return null;
  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 16, flexWrap: "wrap", gap: 10 }}>
        <div className="kicker">Also available as gift sets · bundled</div>
        <div className="mono body-xs" style={{ color: "var(--ink-mute)" }}>
          not ranked against bottles · per-ml math surfaced
        </div>
      </div>
      <div className="mono body-s" style={{ color: "var(--ink-soft)", marginBottom: 14, maxWidth: 620, lineHeight: 1.55 }}>
        Gift sets pair the bottle with travel sprays or body spray — a lower
        sticker can hide a higher effective price per ml. We compute both.
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))", gap: 1, background: "var(--rule)", border: "1px solid var(--rule)" }}>
        {rows.map(row => (
          <div key={row.retailer} style={{ background: "var(--paper)", padding: "18px 20px" }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 10 }}>
              <div className="fraun" style={{ fontSize: 17, color: "var(--ink)" }}>{row.retailerObj.name}</div>
              <span className="chip chip-mute">{FMF.offerMeta("giftset").label}</span>
            </div>
            <div className="mono" style={{ fontSize: 22, color: "var(--ink)", letterSpacing: "-0.02em" }}>
              {FMF.fmtUsd(row.priced.total)}
            </div>
            <div className="mono body-xs" style={{ color: "var(--ink-faint)", marginTop: 4 }}>
              ≈ ${(row.priced.total / sizeMl).toFixed(2)}/ml on the headline bottle
            </div>
            <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 10, lineHeight: 1.55 }}>
              {row.note}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function DecantsStrip({ rows }) {
  const frag = window.FMF?.AVENTUS;
  // No verified decant URLs → don't render the strip. A header over an
  // empty grid looks like a bug; a silent skip is honest ("we'll surface
  // decants when we have direct links").
  if (!rows || rows.length === 0) return null;
  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 16 }}>
        <div className="kicker">Also available as decants · sampling</div>
        <div className="mono body-xs" style={{ color: "var(--ink-mute)" }}>
          per-ml pricing · not ranked against bottles
        </div>
      </div>
      <div className="decants-grid">
        {rows.map(row => {
          const s = FMF.shopUrl(row.retailerObj, frag, row);
          return (
            <a key={row.retailer} href={s.url}
               target="_blank" rel="noopener noreferrer"
               data-shop-tier={s.tier} data-shop-retailer={row.retailerObj?.id}
               className="decant-card"
               aria-label={`${s.tier === "pdp" ? "Open" : "Search"} ${row.retailerObj.name} decant listing in a new tab`}
               title={s.tier === "search" ? "Lands on retailer decant search" : undefined}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8 }}>
                <div className="fraun" style={{ fontSize: 17, color: "var(--ink)" }}>{row.retailerObj.name}</div>
                <svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true" style={{ color: "var(--ink-mute)" }}>
                  <path d="M1 9 L9 1 M3 1 H9 V7" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" />
                </svg>
              </div>
              <div className="mono" style={{ fontSize: 22, color: "var(--ink)", marginTop: 8, letterSpacing: "-0.02em" }}>
                <Prov row={row} frag={frag}>{FMF.fmtUsd(row.priced.total)}</Prov>
              </div>
              <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 6 }}>
                {row.note}
              </div>
              <div className="mono body-xs" style={{ color: "var(--ink-faint)", marginTop: 8 }}>
                trust {row.retailerObj.trust} · ships in {row.retailerObj.etaDays}d
                {s.tier === "search" && <> · <span style={{ color: "var(--ink-mute)" }}>search landing</span></>}
              </div>
            </a>
          );
        })}
      </div>
    </div>
  );
}

function avg(xs)    { return xs.reduce((a,b)=>a+b,0) / xs.length; }
function median(xs) { const s = [...xs].sort((a,b)=>a-b); const m = Math.floor(s.length/2); return s.length % 2 ? s[m] : (s[m-1]+s[m])/2; }
function stdev(xs)  { const m = avg(xs); return Math.sqrt(avg(xs.map(x => (x-m)**2))); }
function pctRank(xs, x) { const below = xs.filter(v => v < x).length; return Math.round((below / xs.length) * 100); }

// Parse a millilitre quantity from a label like "100ml EDP" or "5ml decant (≈$7.60/ml)"
function mlFromLabel(label, fallback) {
  const m = /(\d+(?:\.\d+)?)\s*ml/i.exec(String(label || ""));
  return m ? parseFloat(m[1]) : fallback;
}

/* ══════════════════════════════════════════════════════════════════════
   CATALOG STUB — for catalog entries that exist but aren't fully modeled.
   We do not fake data. We do not render Aventus's ledger under another
   fragrance's name. We publish what's verified and acknowledge what isn't.
   ══════════════════════════════════════════════════════════════════════ */
function CatalogStub({ frag }) {
  // Subscribe to both metadata AND offer cache. Previously only metadata
  // was subscribed — the offer bundle (prices, aggregateRating) was
  // fetched but never displayed, leaving pages feeling unfinished.
  const [meta, setMeta] = useStateD(() => window.FMF?.METADATA_CACHE?.[frag.slug] || null);
  const [offers, setOffers] = useStateD(() => window.FMF?.OFFER_CACHE?.[frag.slug] || null);
  useEffectD(() => {
    let cancelled = false;
    const applyMeta = (b) => { if (!cancelled && b) setMeta(b); };
    const applyOffers = (b) => { if (!cancelled && b) setOffers(b); };
    const onMeta = (e) => { if (e.detail?.slug === frag.slug) applyMeta(e.detail.bundle); };
    const onOffers = (e) => { if (e.detail?.slug === frag.slug) applyOffers(e.detail.bundle); };
    window.addEventListener("fmf:metadataLoaded", onMeta);
    window.addEventListener("fmf:offersLoaded",   onOffers);
    // Re-check cache + kick loaders (idempotent).
    applyMeta(window.FMF?.METADATA_CACHE?.[frag.slug]);
    applyOffers(window.FMF?.OFFER_CACHE?.[frag.slug]);
    window.FMF?.loadMetadata?.(frag.slug)?.then(applyMeta);
    window.FMF?.loadOffers?.(frag.slug)?.then(applyOffers);
    return () => {
      cancelled = true;
      window.removeEventListener("fmf:metadataLoaded", onMeta);
      window.removeEventListener("fmf:offersLoaded",   onOffers);
    };
  }, [frag.slug]);

  const m = meta?.metadata || {};
  const hasLiveMeta = !!(m.notes || m.accords || m.perfumer || m.description);
  const offerRows = Array.isArray(offers?.offers) ? offers.offers : [];
  const inStockOffers = offerRows.filter((o) => o.price != null);
  const winner = inStockOffers.slice().sort((a, b) => (a.price ?? 9e9) - (b.price ?? 9e9))[0] || null;
  const agg = offers?.aggregateRating;
  const perfumerDisplay = m.perfumer?.value
    ? (Array.isArray(m.perfumer.value) ? m.perfumer.value.join(" & ") : m.perfumer.value)
    : (frag.perfumer || frag.perfumers?.join(" & ") || null);
  // First offer that carries a bottle image → hero photo. Retailers reliably
  // emit Product.image in JSON-LD so this fills automatically for any page.
  const bottleImage = offerRows.find((o) => o.image)?.image || null;

  return (
    <main>
      {/* Breadcrumb */}
      <section className="container" style={{ paddingTop: 20, paddingBottom: 0 }}>
        <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.04em" }}>
          <a href="#/" style={{ color: "var(--ink-mute)", textDecoration: "none", borderBottom: "1px solid var(--rule)" }}>FindMyFrag</a>
          <span style={{ margin: "0 10px", color: "var(--ink-faint)" }}>/</span>
          <a href="#/browse" style={{ color: "var(--ink-mute)", textDecoration: "none", borderBottom: "1px solid var(--rule)" }}>Catalog</a>
          <span style={{ margin: "0 10px", color: "var(--ink-faint)" }}>/</span>
          <span style={{ color: "var(--ink)" }}>{frag.brand} · {frag.name}</span>
        </div>
      </section>

      <section className="container" style={{ paddingTop: 20, paddingBottom: 14 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", flexWrap: "wrap", gap: 12 }}>
          <div className="kicker">{frag.family || "Reference"}{m.concentration?.value ? ` · ${m.concentration.value}` : ""}</div>
          <div className="mono body-xs" style={{ color: "var(--ink-mute)" }}>
            {offers ? `${inStockOffers.length} live offer${inStockOffers.length === 1 ? "" : "s"} · ${offerRows.length} retailer${offerRows.length === 1 ? "" : "s"} checked` : "Loading live prices…"}
          </div>
        </div>
      </section>
      <hr className="hairline-k" />

      {/* Masthead — brand, name, year/family/perfumer */}
      <section className="container" style={{ padding: "56px 32px 36px" }}>
        <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginBottom: 14, letterSpacing: "0.08em", textTransform: "uppercase" }}>
          {frag.brand}
        </div>
        <h1 className="display-xl" style={{ margin: 0, maxWidth: "16ch" }}>
          <span className="fraun-itl">{frag.name}</span>
        </h1>
        <div className="mono body-s" style={{ color: "var(--ink-mute)", marginTop: 20 }}>
          {frag.year || "—"}
          {frag.family ? ` · ${frag.family}` : ""}
          {perfumerDisplay ? ` · ${perfumerDisplay}` : ""}
        </div>
        {/* Aggregate rating (when retailers emit it) */}
        {agg && (
          <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 14 }}>
            <strong style={{ color: "var(--ink)" }}>{agg.score.toFixed(1)}/5</strong>
            {" · "}{FMF.fmtInt ? FMF.fmtInt(agg.count) : agg.count} community reviews across {agg.perRetailer} retailer{agg.perRetailer === 1 ? "" : "s"}
          </div>
        )}
      </section>

      <hr className="hairline" />

      {/* HERO — bottle image extracted from retailer JSON-LD, when any */}
      {bottleImage && <FragranceHero imageUrl={bottleImage} alt={`${frag.brand} ${frag.name} bottle`} />}

      {/* BUY ANSWER — cheapest live offer */}
      <FragranceBuyAnswer winner={winner} slug={frag.slug} />

      {/* PRICE HISTORY — sparkline from KV-accumulated daily snapshots */}
      <PriceHistorySection slug={frag.slug} />

      {/* RETAILER LEDGER — every live offer, sorted cheapest */}
      {offerRows.length > 0 && <FragranceOfferLedger offers={offerRows} />}

      {/* SCENT STORY — narrative paragraph (LLM or editor seeded) */}
      {m.scentStory?.value && <ScentStorySection story={m.scentStory} />}

      {/* Live metadata section — notes, accords, perfumer with provenance */}
      {hasLiveMeta && <FragranceMetadataSection slug={frag.slug} metadata={m} />}

      {/* WEARER + SEASONALITY + PROJECTION + LONGEVITY — 4-quadrant grid */}
      {(m.wearer?.value || m.seasonality?.value || m.projection?.value || m.longevity?.value) && (
        <WearerProfileSection slug={frag.slug} metadata={m} />
      )}

      {/* Smells similar — cosine-similarity recommendations */}
      <SmellsLikeSection slug={frag.slug} />

      {/* Also in catalog — same family, if any */}
      <SiblingsStrip frag={frag} />

      {/* Refusal footer — honest citation of method */}
      <hr className="hairline" />
      <section className="container" style={{ padding: "40px 32px 72px" }}>
        <div className="kicker" style={{ marginBottom: 10 }}>Method</div>
        <p className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.04em", lineHeight: 1.8, maxWidth: "68ch" }}>
          Prices above are live-pulled from each retailer's schema.org/Product markup and cached at the edge for four hours. Fragrance metadata (notes, accords, perfumer) is provenance-tagged per field: editor-verified when seeded, consensus when multiple sources agree, AI-enriched otherwise. Every field carries a badge so you can audit the source.
          {" "}<a href="#/refusals" style={{ color: "var(--ink)", textDecoration: "underline", textUnderlineOffset: 4 }}>Read the refusals →</a>
        </p>
      </section>
    </main>
  );
}

/* Hero bottle photo — pulled live from retailer schema.org/Product.image. */
function FragranceHero({ imageUrl, alt }) {
  const [failed, setFailed] = useStateD(false);
  if (failed) return null;
  return (
    <section className="container" style={{ padding: "8px 32px 36px" }}>
      <figure style={{ margin: 0, borderTop: "1px solid var(--ink)", paddingTop: 32, display: "grid", gridTemplateColumns: "auto 1fr", gap: 40, alignItems: "center" }}>
        <img
          src={imageUrl}
          alt={alt}
          onError={() => setFailed(true)}
          loading="eager"
          width={220}
          height={220}
          style={{ display: "block", maxWidth: 220, maxHeight: 220, width: "100%", height: "auto", objectFit: "contain", background: "var(--paper-deep)", padding: 16, border: "1px solid var(--rule)" }}
        />
        <figcaption className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.06em", textTransform: "uppercase" }}>
          Bottle via retailer schema · live-fetched
        </figcaption>
      </figure>
    </section>
  );
}

/* Price history — 90-day sparkline of cheapest-landed-daily. Starts empty,
   fills in one point per day as /offers runs for this fragrance. Data source
   of truth: KV `history:<slug>:<date>` keys, accumulated over 180d. */
function PriceHistorySection({ slug }) {
  const [history, setHistory] = useStateD(null);
  useEffectD(() => {
    let cancelled = false;
    fetch(`${FMF.WORKER_URL}/history?frag=${encodeURIComponent(slug)}&days=90`)
      .then((r) => r.ok ? r.json() : null)
      .then((b) => { if (!cancelled) setHistory(b); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [slug]);
  const points = history?.points || [];
  if (points.length === 0) {
    return (
      <>
        <hr className="hairline" />
        <section className="container" style={{ padding: "40px 32px 8px" }} aria-labelledby="history-heading">
          <div className="kicker" id="history-heading" style={{ marginBottom: 10 }}>Price history</div>
          <p className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.04em", lineHeight: 1.7, maxWidth: "58ch" }}>
            Tracking begins today. We snapshot the cheapest landed price across all retailers on every run of the live resolver. Check back in a week for the first trend line.
          </p>
        </section>
      </>
    );
  }
  const prices = points.map((p) => p.min).filter(Number.isFinite);
  const min = Math.min(...prices);
  const max = Math.max(...prices);
  const range = max - min || 1;
  const w = 600, h = 64, pad = 4;
  const step = points.length > 1 ? (w - pad * 2) / (points.length - 1) : 0;
  const path = points.map((p, i) => {
    const x = pad + i * step;
    const y = h - pad - ((p.min - min) / range) * (h - pad * 2);
    return `${i === 0 ? "M" : "L"}${x.toFixed(1)},${y.toFixed(1)}`;
  }).join(" ");
  const latest = points[points.length - 1];
  return (
    <>
      <hr className="hairline" />
      <section className="container" style={{ padding: "40px 32px 8px" }} aria-labelledby="history-heading">
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 14 }}>
          <div className="kicker" id="history-heading">Price history · last {points.length} day{points.length === 1 ? "" : "s"}</div>
          <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.06em" }}>
            low <strong style={{ color: "var(--ink)" }}>${min.toFixed(0)}</strong>
            {" · "}today <strong style={{ color: "var(--ink)" }}>${latest.min.toFixed(0)}</strong>
          </div>
        </div>
        <svg role="img" aria-label={`Price trend: low $${min.toFixed(0)}, high $${max.toFixed(0)}, latest $${latest.min.toFixed(0)}`} viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ width: "100%", height: h, display: "block", background: "var(--paper-deep)", border: "1px solid var(--rule)" }}>
          <path d={path} stroke="var(--ink)" strokeWidth={1.5} fill="none" strokeLinejoin="round" strokeLinecap="round" />
        </svg>
      </section>
    </>
  );
}

/* Scent story — narrative prose, LLM-generated or editor-seeded. */
function ScentStorySection({ story }) {
  return (
    <>
      <hr className="hairline" />
      <section className="container" style={{ padding: "48px 32px 8px" }} aria-labelledby="story-heading">
        <div className="kicker" id="story-heading" style={{ marginBottom: 16 }}>
          Scent story <ProvenanceBadge field={story} />
        </div>
        <p className="fraun" style={{ fontSize: 22, lineHeight: 1.5, letterSpacing: "-0.005em", margin: 0, color: "var(--ink)", maxWidth: "58ch" }}>
          {story.value}
        </p>
      </section>
    </>
  );
}

/* Wearer profile — 4-quadrant grid for wearer / seasonality / projection / longevity. */
function WearerProfileSection({ slug, metadata }) {
  const { wearer, seasonality, projection, longevity } = metadata;
  const seasons = seasonality?.value;
  const seasonRow = (name, key) => {
    const val = seasons?.[key];
    const bar = { low: "▁▁", med: "▅▅", high: "██" }[String(val ?? "").toLowerCase()] || "··";
    return (
      <div key={key} style={{ display: "flex", justifyContent: "space-between", padding: "4px 0", fontFamily: "var(--font-mono)", fontSize: 12 }}>
        <span style={{ color: "var(--ink-mute)", textTransform: "capitalize" }}>{name}</span>
        <span style={{ color: "var(--ink)" }}>{bar} {val ?? "—"}</span>
      </div>
    );
  };
  return (
    <>
      <hr className="hairline" />
      <section className="container" style={{ padding: "48px 32px 8px" }} aria-labelledby="wearer-heading">
        <div className="kicker" id="wearer-heading" style={{ marginBottom: 20 }}>Wearer &amp; performance</div>
        <div className="stack-mobile" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 48, alignItems: "start" }}>
          {wearer?.value && (
            <div>
              <div className="kicker" style={{ marginBottom: 10 }}>
                Who wears this <ProvenanceBadge field={wearer} />
                <SuggestEditButton slug={slug} field="wearer" currentValue={wearer.value} />
              </div>
              <p className="body-l" style={{ color: "var(--ink-soft)", margin: 0, maxWidth: "44ch", lineHeight: 1.55 }}>
                {wearer.value}
              </p>
            </div>
          )}
          <div style={{ borderTop: "1px solid var(--ink)", paddingTop: 16 }}>
            {seasons && (
              <div style={{ marginBottom: 20 }}>
                <div className="kicker" style={{ marginBottom: 10 }}>
                  Seasonality <ProvenanceBadge field={seasonality} />
                </div>
                <div>
                  {seasonRow("Spring", "spring")}
                  {seasonRow("Summer", "summer")}
                  {seasonRow("Fall",   "fall")}
                  {seasonRow("Winter", "winter")}
                </div>
              </div>
            )}
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 13, lineHeight: 2 }}>
              {projection?.value && (
                <StubRow k="projection" v={<>{projection.value} <ProvenanceBadge field={projection} /></>} />
              )}
              {longevity?.value && (
                <StubRow k="longevity" v={<>{longevity.value} <ProvenanceBadge field={longevity} /></>} />
              )}
            </div>
          </div>
        </div>
      </section>
    </>
  );
}

/* Buy answer — the cheapest landed offer with a CTA to the retailer. */
function FragranceBuyAnswer({ winner, slug }) {
  if (!winner) {
    return (
      <section className="container" style={{ padding: "28px 32px 40px" }} aria-labelledby="buy-answer-heading">
        <div className="kicker" id="buy-answer-heading" style={{ marginBottom: 12 }}>Live prices</div>
        <p className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.04em", lineHeight: 1.7 }}>
          No retailer in our roster currently returns a price for this fragrance. This can mean (a) the release is too new to be indexed, (b) it's discontinued / niche-only, or (c) retailers returned non-matching variants that were filtered by the GTIN+flanker guard.
        </p>
      </section>
    );
  }
  const retailerName = winner.retailer;
  const price = typeof winner.price === "number" ? `$${winner.price.toFixed(2)}` : "—";
  return (
    <section className="container" style={{ padding: "28px 32px 40px" }} aria-labelledby="buy-answer-heading">
      <div className="kicker" id="buy-answer-heading" style={{ marginBottom: 12 }}>Cheapest landed today</div>
      <div className="stack-mobile" style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: 24, alignItems: "center" }}>
        <div>
          <div className="fraun" style={{ fontSize: "clamp(34px, 4vw, 54px)", letterSpacing: "-0.015em", lineHeight: 1.05, color: "var(--ink)" }}>
            {price}
          </div>
          <div className="mono body-s" style={{ color: "var(--ink-mute)", marginTop: 10 }}>
            at <strong style={{ color: "var(--ink)" }}>{retailerName}</strong>
            {winner.inStock === false ? " · out of stock" : ""}
            {" · "}
            <span style={{ color: "var(--ink-faint)" }}>source {winner.source || "jsonld"} · confidence {Math.round((winner.confidence ?? 0.8) * 100)}%</span>
          </div>
        </div>
        <a href={winner.url} target="_blank" rel="noopener noreferrer"
           data-shop-tier="pdp" data-shop-retailer={winner.retailer}
           className="shop-btn" style={{ padding: "14px 24px", fontSize: 13 }}>
          Shop at {retailerName} →
        </a>
      </div>
    </section>
  );
}

/* Retailer ledger — every live offer sorted cheapest first. */
function FragranceOfferLedger({ offers }) {
  const sorted = offers.slice().sort((a, b) => {
    if ((a.inStock ?? true) !== (b.inStock ?? true)) return a.inStock === false ? 1 : -1;
    return (a.price ?? 9e9) - (b.price ?? 9e9);
  });
  return (
    <>
      <hr className="hairline" />
      <section className="container" style={{ padding: "40px 32px 8px" }} aria-labelledby="ledger-heading">
        <div className="kicker" id="ledger-heading" style={{ marginBottom: 20 }}>Retailer ledger · live</div>
        <div role="table" aria-label="Live prices across retailers" style={{ border: "1px solid var(--rule)", background: "var(--paper)" }}>
          <div role="row" className="mono body-xs" style={{
            display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1fr auto", gap: 16,
            padding: "10px 14px", background: "var(--paper-deep)", color: "var(--ink-mute)",
            letterSpacing: "0.06em", textTransform: "uppercase", borderBottom: "1px solid var(--rule)",
          }}>
            <span role="columnheader">Retailer</span>
            <span role="columnheader">Price</span>
            <span role="columnheader">Stock</span>
            <span role="columnheader">Source</span>
            <span role="columnheader" style={{ textAlign: "right" }}>Shop</span>
          </div>
          {sorted.map((o) => (
            <div key={o.retailer} role="row" style={{
              display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1fr auto", gap: 16,
              padding: "12px 14px", borderBottom: "1px solid var(--rule)",
              alignItems: "center", fontFamily: "var(--font-mono)", fontSize: 13,
            }}>
              <span role="cell">{o.retailer}</span>
              <span role="cell" style={{ color: "var(--ink)", fontWeight: 500 }}>
                {typeof o.price === "number" ? `$${o.price.toFixed(2)}` : "—"}
              </span>
              <span role="cell" style={{ color: o.inStock === false ? "var(--oxblood-ink)" : "var(--ink-mute)" }}>
                {o.inStock === false ? "out" : o.inStock ? "in" : "—"}
              </span>
              <span role="cell" style={{ color: "var(--ink-mute)", fontSize: 11 }}>{o.source || "—"}</span>
              <span role="cell" style={{ textAlign: "right" }}>
                <a href={o.url} target="_blank" rel="noopener noreferrer"
                   data-shop-tier="pdp" data-shop-retailer={o.retailer}
                   aria-label={`Shop at ${o.retailer}`}
                   style={{ fontSize: 11, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink)", borderBottom: "1px solid var(--ink)", textDecoration: "none" }}>
                  Shop →
                </a>
              </span>
            </div>
          ))}
        </div>
      </section>
    </>
  );
}

/* "Smells similar" — note-vector cosine recommendations from Worker. */
function SmellsLikeSection({ slug }) {
  const [similar, setSimilar] = useStateD(null);
  useEffectD(() => {
    let cancelled = false;
    fetch(`${FMF.WORKER_URL}/similar?frag=${encodeURIComponent(slug)}`)
      .then((r) => r.ok ? r.json() : null)
      .then((b) => { if (!cancelled) setSimilar(b); })
      .catch(() => { /* silent — non-critical */ });
    return () => { cancelled = true; };
  }, [slug]);
  if (!similar || !Array.isArray(similar.similar) || similar.similar.length === 0) return null;
  return (
    <>
      <hr className="hairline" />
      <section className="container" style={{ padding: "40px 32px 8px" }} aria-labelledby="similar-heading">
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 20 }}>
          <div className="kicker" id="similar-heading">Smells similar</div>
          <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.06em", textTransform: "uppercase" }}>
            Note-vector cosine
          </div>
        </div>
        <div className="stack-mobile" style={{ display: "grid", gridTemplateColumns: `repeat(${Math.min(similar.similar.length, 5)}, 1fr)`, gap: 1, background: "var(--rule)", border: "1px solid var(--rule)" }}>
          {similar.similar.map((s) => {
            const f = (window.FMF?.CATALOG_ALL || window.FMF?.CATALOG || []).find((x) => x.slug === s.slug);
            return (
              <a key={s.slug} href={`#/fragrance/${s.slug}`}
                 aria-label={`Similar fragrance: ${f?.brand ?? ""} ${f?.name ?? s.slug}`}
                 style={{ background: "var(--paper)", padding: "20px 22px", display: "block", textDecoration: "none", color: "inherit" }}>
                <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.08em", textTransform: "uppercase", marginBottom: 6 }}>
                  {f?.brand ?? s.slug.split("-")[0]}
                </div>
                <div className="fraun" style={{ fontSize: 20, letterSpacing: "-0.012em", lineHeight: 1.15 }}>
                  <span className="fraun-itl">{f?.name ?? s.slug}</span>
                </div>
                <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 12 }}>
                  {Math.round(s.score * 100)}% match
                </div>
                {s.sharedNotes?.length > 0 && (
                  <div className="mono body-xs" style={{ color: "var(--ink-faint)", marginTop: 6, fontSize: 10 }}>
                    {s.sharedNotes.slice(0, 4).join(" · ")}
                  </div>
                )}
              </a>
            );
          })}
        </div>
      </section>
    </>
  );
}

/* Render provenance-tagged metadata fields. Each field shows its source
   (editor / consensus / llm / wikidata) as a subtle badge so readers can
   assess data integrity at a glance. */
function FragranceMetadataSection({ slug, metadata }) {
  const { notes, accords, perfumer, family, concentration, description } = metadata;
  return (
    <>
      <hr className="hairline" />
      <section className="container" style={{ padding: "56px 32px 48px" }} aria-labelledby="frag-meta-heading">
        <div className="kicker" id="frag-meta-heading" style={{ marginBottom: 24 }}>Scent profile</div>

        {description?.value && (
          <p className="fraun" style={{ fontSize: 22, lineHeight: 1.5, letterSpacing: "-0.005em", margin: "0 0 28px", color: "var(--ink)", maxWidth: "58ch" }}>
            {description.value}{" "}
            <ProvenanceBadge field={description} />
          </p>
        )}

        <div className="stack-mobile" style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: 56, alignItems: "start" }}>
          {/* Notes pyramid */}
          {notes?.value && (
            <div>
              <h3 className="kicker" style={{ marginBottom: 12 }}>
                Notes <ProvenanceBadge field={notes} /><SuggestEditButton slug={slug} field="notes" currentValue={notes.value} />
              </h3>
              <NotePyramid notes={notes.value} />
            </div>
          )}

          {/* Side panel — accords, perfumer, family, concentration */}
          <div style={{ borderTop: "1px solid var(--ink)", paddingTop: 20 }}>
            {accords?.value?.length > 0 && (
              <div style={{ marginBottom: 24 }}>
                <div className="kicker" style={{ marginBottom: 10 }}>Accords <ProvenanceBadge field={accords} /><SuggestEditButton slug={slug} field="accords" currentValue={accords.value} /></div>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
                  {accords.value.map((a) => (
                    <span key={a} className="mono" style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.06em", border: "1px solid var(--rule)", padding: "4px 10px", color: "var(--ink-soft)" }}>
                      {a}
                    </span>
                  ))}
                </div>
              </div>
            )}
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 13, lineHeight: 2 }}>
              {perfumer?.value && (
                <StubRow
                  k="perfumer"
                  v={<>{Array.isArray(perfumer.value) ? perfumer.value.join(" & ") : perfumer.value} <ProvenanceBadge field={perfumer} /><SuggestEditButton slug={slug} field="perfumer" currentValue={perfumer.value} /></>}
                />
              )}
              {family?.value && <StubRow k="family" v={<>{family.value} <ProvenanceBadge field={family} /></>} />}
              {concentration?.value && <StubRow k="concentration" v={<>{concentration.value} <ProvenanceBadge field={concentration} /></>} />}
            </div>
          </div>
        </div>
      </section>
    </>
  );
}

function NotePyramid({ notes }) {
  const row = (label, arr) => (arr && arr.length > 0) ? (
    <div style={{ marginBottom: 14 }}>
      <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.08em", textTransform: "uppercase", marginBottom: 6 }}>{label}</div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
        {arr.map((n) => (
          <span key={n} className="fraun" style={{ fontSize: 16, color: "var(--ink)", background: "var(--paper-deep)", padding: "4px 10px", textTransform: "capitalize" }}>
            {n}
          </span>
        ))}
      </div>
    </div>
  ) : null;
  return (
    <div>
      {row("Top", notes.top)}
      {row("Heart", notes.heart)}
      {row("Base", notes.base)}
    </div>
  );
}

/* Provenance badge — tiny subtle tag next to every field so readers can
   audit data sources. Editor = authoritative. Consensus = multiple sources
   agree. LLM = AI-enriched. Wikidata = structured canonical source. */
function ProvenanceBadge({ field }) {
  if (!field?.source) return null;
  const meta = {
    editor:    { label: "Editor",      color: "var(--oxblood-ink)", title: "Editor-verified · highest authority" },
    consensus: { label: "Verified",    color: "#2e7d32",            title: "Multiple sources agree" },
    wikidata:  { label: "Wikidata",    color: "#1565c0",            title: "Wikidata structured data" },
    llm:       { label: "AI",          color: "var(--ink-mute)",    title: `AI-enriched · confidence ${Math.round((field.confidence || 0) * 100)}%` },
    community: { label: "Community",   color: "#ef6c00",            title: "Community-submitted" },
    extracted: { label: "Retailer",    color: "var(--ink-mute)",    title: "Extracted from retailer schema.org markup" },
  }[field.source] || { label: field.source, color: "var(--ink-mute)", title: field.source };
  return (
    <span
      className="mono"
      role="note"
      aria-label={meta.title}
      title={meta.title}
      style={{
        fontSize: 9, letterSpacing: "0.08em", textTransform: "uppercase",
        color: meta.color, border: `1px solid ${meta.color}`, padding: "1px 5px",
        marginLeft: 6, verticalAlign: "middle", borderRadius: 2, fontWeight: 500,
        opacity: 0.85,
      }}
    >
      {meta.label}
    </span>
  );
}

/* "Suggest edit" button — opens in-page modal to collect reader
   correction, posts to Worker /report. Replaces native prompt() with
   a proper form: focus-trapped, ESC to close, keyboard-accessible,
   ARIA-labeled. */
function SuggestEditButton({ slug, field, currentValue }) {
  const [open, setOpen] = useStateD(false);
  return (
    <>
      <button
        type="button"
        onClick={() => setOpen(true)}
        aria-label={`Suggest correction for ${field}`}
        aria-haspopup="dialog"
        title="Suggest a correction"
        style={{
          fontSize: 9, letterSpacing: "0.08em", textTransform: "uppercase",
          color: "var(--ink-mute)", background: "transparent", border: "1px dashed var(--rule)",
          padding: "1px 5px", marginLeft: 4, cursor: "pointer", borderRadius: 2,
          fontFamily: "var(--font-mono)", opacity: 0.7,
        }}
      >
        edit?
      </button>
      {open && (
        <SuggestEditModal
          slug={slug}
          field={field}
          currentValue={currentValue}
          onClose={() => setOpen(false)}
        />
      )}
    </>
  );
}

function SuggestEditModal({ slug, field, currentValue, onClose }) {
  const shown = typeof currentValue === "string" ? currentValue : JSON.stringify(currentValue, null, 2);
  const [proposed, setProposed] = useStateD(shown);
  const [reason, setReason] = useStateD("");
  const [contact, setContact] = useStateD("");
  const [status, setStatus] = useStateD("idle"); // idle | submitting | ok | error
  const [error, setError] = useStateD("");
  const firstFieldRef = React.useRef(null);

  // Close on ESC; focus first field on open; focus-trap via tab-cycle on
  // the modal-dialog element. Per WCAG 2.1.2 + 2.4.3.
  useEffectD(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    firstFieldRef.current?.focus();
    const prevOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      window.removeEventListener("keydown", onKey);
      document.body.style.overflow = prevOverflow;
    };
  }, []);

  const submit = async (e) => {
    e.preventDefault();
    if (!proposed.trim() || proposed === shown) {
      setError("Please enter a different value to submit a correction.");
      return;
    }
    setStatus("submitting");
    setError("");
    try {
      const res = await fetch(`${FMF.WORKER_URL}/report`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ slug, field, currentValue, proposedValue: proposed, reason, contact }),
      });
      const body = await res.json();
      if (res.ok) {
        setStatus("ok");
        setTimeout(onClose, 1500);
      } else {
        setStatus("error");
        setError(body.error || `HTTP ${res.status}`);
      }
    } catch (e) {
      setStatus("error");
      setError(e.message);
    }
  };

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="suggest-edit-title"
      onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
      style={{
        position: "fixed", inset: 0, background: "rgba(20,18,15,0.55)",
        zIndex: 9000, display: "flex", alignItems: "center", justifyContent: "center", padding: 20,
      }}
    >
      <form
        onSubmit={submit}
        style={{
          background: "var(--paper)", maxWidth: 560, width: "100%",
          border: "1px solid var(--ink)", padding: "32px 32px 24px", position: "relative",
          maxHeight: "90vh", overflowY: "auto",
        }}
      >
        <div className="kicker" id="suggest-edit-title" style={{ marginBottom: 16 }}>
          Suggest a correction · <span style={{ color: "var(--ink-mute)" }}>{field}</span>
        </div>

        <div style={{ marginBottom: 16 }}>
          <label className="mono body-xs" style={{ color: "var(--ink-mute)", display: "block", marginBottom: 6, letterSpacing: "0.06em", textTransform: "uppercase" }}>
            Current value
          </label>
          <div className="mono" style={{ background: "var(--paper-deep)", padding: "8px 10px", fontSize: 12, whiteSpace: "pre-wrap", wordBreak: "break-word", border: "1px solid var(--rule)", maxHeight: 120, overflowY: "auto" }}>
            {shown}
          </div>
        </div>

        <div style={{ marginBottom: 16 }}>
          <label htmlFor="sg-proposed" className="mono body-xs" style={{ color: "var(--ink)", display: "block", marginBottom: 6, letterSpacing: "0.06em", textTransform: "uppercase" }}>
            Your proposed value *
          </label>
          <textarea
            id="sg-proposed"
            ref={firstFieldRef}
            value={proposed}
            onChange={(e) => setProposed(e.target.value)}
            required
            maxLength={2000}
            rows={3}
            aria-describedby="sg-proposed-help"
            style={{ width: "100%", padding: "8px 10px", fontFamily: "var(--font-mono)", fontSize: 13, border: "1px solid var(--ink)", background: "var(--paper)", color: "var(--ink)", resize: "vertical" }}
          />
          <div id="sg-proposed-help" className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 4 }}>
            Plain text — for list fields (notes, accords), separate with commas.
          </div>
        </div>

        <div style={{ marginBottom: 16 }}>
          <label htmlFor="sg-reason" className="mono body-xs" style={{ color: "var(--ink)", display: "block", marginBottom: 6, letterSpacing: "0.06em", textTransform: "uppercase" }}>
            Why is the current value wrong? (optional)
          </label>
          <textarea
            id="sg-reason"
            value={reason}
            onChange={(e) => setReason(e.target.value)}
            maxLength={1000}
            rows={2}
            style={{ width: "100%", padding: "8px 10px", fontFamily: "var(--font-mono)", fontSize: 13, border: "1px solid var(--rule)", background: "var(--paper)", color: "var(--ink)", resize: "vertical" }}
          />
        </div>

        <div style={{ marginBottom: 24 }}>
          <label htmlFor="sg-contact" className="mono body-xs" style={{ color: "var(--ink)", display: "block", marginBottom: 6, letterSpacing: "0.06em", textTransform: "uppercase" }}>
            Email (optional — for follow-up questions)
          </label>
          <input
            id="sg-contact"
            type="email"
            value={contact}
            onChange={(e) => setContact(e.target.value)}
            autoComplete="email"
            style={{ width: "100%", padding: "8px 10px", fontFamily: "var(--font-mono)", fontSize: 13, border: "1px solid var(--rule)", background: "var(--paper)", color: "var(--ink)" }}
          />
        </div>

        {error && (
          <div role="alert" className="mono body-xs" style={{ color: "var(--oxblood-ink)", marginBottom: 12 }}>
            {error}
          </div>
        )}

        {status === "ok" && (
          <div role="status" className="mono body-xs" style={{ color: "#2e7d32", marginBottom: 12 }}>
            ✓ Submitted. Thanks — your correction is in the review queue.
          </div>
        )}

        <div style={{ display: "flex", gap: 12, justifyContent: "flex-end" }}>
          <button
            type="button"
            onClick={onClose}
            className="shop-btn shop-btn--ghost"
            style={{ padding: "8px 16px" }}
          >
            Cancel
          </button>
          <button
            type="submit"
            disabled={status === "submitting" || status === "ok"}
            className="shop-btn"
            style={{ padding: "8px 16px" }}
          >
            {status === "submitting" ? "Submitting…" : status === "ok" ? "Submitted" : "Submit"}
          </button>
        </div>
      </form>
    </div>
  );
}

function StubRow({ k, v }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between", borderBottom: "1px solid var(--rule)", padding: "6px 0" }}>
      <span style={{ color: "var(--ink-mute)", letterSpacing: "0.06em", textTransform: "uppercase", fontSize: 11 }}>{k}</span>
      <span style={{ color: "var(--ink)" }}>{v}</span>
    </div>
  );
}

function SiblingsStrip({ frag }) {
  const famSlug = frag.family ? FMF.slugify(frag.family) : null;
  const siblings = famSlug ? (FMF.byFamilySlug ? FMF.byFamilySlug(famSlug) : []) : [];
  const others = (siblings || []).filter(f => f.slug !== frag.slug).slice(0, 3);
  if (others.length === 0) return null;
  return (
    <>
      <hr className="hairline" />
      <section className="container" style={{ padding: "48px 32px 72px" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 20 }}>
          <div className="kicker">Siblings · same family</div>
          <a href={`#/family/${famSlug}`} className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.06em", textTransform: "uppercase", borderBottom: "1px solid var(--rule)", paddingBottom: 2 }}>
            All in {frag.family} →
          </a>
        </div>
        <div className="stack-mobile" style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 1, background: "var(--rule)", border: "1px solid var(--rule)" }}>
          {others.map(f => (
            <a key={f.slug} href={`#/fragrance/${f.slug}`}
               style={{ background: "var(--paper)", padding: "20px 22px", display: "block", textDecoration: "none", color: "inherit" }}>
              <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.08em", textTransform: "uppercase", marginBottom: 6 }}>
                {f.brand}
              </div>
              <div className="fraun" style={{ fontSize: 22, letterSpacing: "-0.012em", lineHeight: 1.15 }}>
                <span className="fraun-itl">{f.name}</span>
              </div>
              <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 10 }}>
                {f.year} · {f.family}
              </div>
            </a>
          ))}
        </div>
      </section>
    </>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   NO RECORD — signed refusal-style 404 for unknown fragrance slugs.
   ══════════════════════════════════════════════════════════════════════ */
function NoRecord({ slug }) {
  return (
    <main>
      <section className="container" style={{ paddingTop: 20, paddingBottom: 0 }}>
        <div className="mono body-xs" style={{ color: "var(--ink-mute)", letterSpacing: "0.04em" }}>
          <a href="#/" style={{ color: "var(--ink-mute)", textDecoration: "none", borderBottom: "1px solid var(--rule)" }}>FindMyFrag</a>
          <span style={{ margin: "0 10px", color: "var(--ink-faint)" }}>/</span>
          <span style={{ color: "var(--ink)" }}>no record</span>
        </div>
      </section>
      <hr className="hairline-k" />
      <section className="container" style={{ padding: "96px 32px 96px" }}>
        <div className="kicker" style={{ marginBottom: 24 }}>404 · no entry in the ledger</div>
        <h1 className="display-xxl" style={{ margin: 0, maxWidth: "14ch" }}>
          No <span className="fraun-itl">record</span>.
        </h1>
        <p className="fraun" style={{ fontSize: 26, lineHeight: 1.4, color: "var(--ink-soft)", margin: "28px 0 0", maxWidth: "48ch" }}>
          This URL has no entry in our ledger. We do not speculate about fragrances we have not cataloged.
        </p>
        <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 28, letterSpacing: "0.04em" }}>
          slug requested <span style={{ color: "var(--ink)" }}>{slug || "—"}</span>
        </div>
        <div style={{ marginTop: 36 }}>
          <a href="#/" className="shop-btn" style={{ marginRight: 12 }}>Return to search</a>
          <a href="#/browse" className="shop-btn shop-btn--ghost">Browse catalog</a>
        </div>
      </section>
    </main>
  );
}

/* Compact historical-context cell — labeled mono value, editorial not dashboard. */
function HistCell({ label, big, sub, border = true }) {
  return (
    <div style={{
      padding: "18px 22px",
      borderLeft: border ? "1px solid var(--rule)" : 0,
    }}>
      <div className="kicker" style={{ marginBottom: 8 }}>{label}</div>
      <div className="mono" style={{ fontSize: 22, letterSpacing: "-0.015em", lineHeight: 1.05 }}>
        {big}
      </div>
      <div className="mono body-xs" style={{ color: "var(--ink-mute)", marginTop: 6 }}>
        {sub}
      </div>
    </div>
  );
}

Object.assign(window, { Detail });
