// nyc-pages-v2.jsx — v2 catalog/detail/perks, quiet chrome, no flair.

const { useMemo: _useMemo, useState: _useState, useEffect: _useEffect, useRef: _useRef } = React;

const FEEDBACK_FORM_URL =
  'https://docs.google.com/forms/d/e/1FAIpQLSdbUr1-r5yC1JZUAAsp3OkAueoHkgrFheh98iWTKUbAasxXRA/viewform?usp=publish-editor';

function linkifyText(text) {
  const parts = String(text).split(/(https?:\/\/[^\s<>"')\]]+)/g);
  if (parts.length === 1) return text;
  return parts.map((part, i) => {
    if (i % 2 === 0) return part;
    const href = part.replace(/[.,;:!?)]+$/, '');
    const trailing = part.slice(href.length);
    return (
      <React.Fragment key={i}>
        <a href={href} target="_blank" rel="noopener noreferrer">this link</a>
        {trailing}
      </React.Fragment>
    );
  });
}

function formatPerkExportText(perk) {
  const steps = (perk.steps || []).map((s, i) => `${i + 1}. ${s}`).join('\n');
  return `# ${perk.name}
Source: ${perk.url}
Systems: ${perk.system.join(', ')}
Category: ${perk.cat}

Description:
${perk.desc}

How to access:
${steps}

Caveats:
${perk.caveats || '(none on record)'}

Estimated retail value: ${perk.value_label}${perk.cadence ? ' ' + perk.cadence : ''}

Note from NYC Perks: this dataset is community-maintained and growing. Treat
the access steps as a starting point — verify on the source URL above and
let us know if anything's stale.`;
}

function formatSavedPerksExport(perks) {
  if (!perks.length) return '';
  const header = `My NYC Perks (${perks.length} saved)\n${'='.repeat(40)}\n`;
  return header + perks.map(formatPerkExportText).join('\n\n---\n\n');
}

/* =====================================================
   PERK PHOTO — Unsplash fetch hook + display component.
   Returns null when no image is available (no placeholder).
   Fires the required Unsplash download trigger once per mount when loaded.

   variant="card"   → shows hover attribution overlay
   variant="detail" → no overlay (parent renders attribution)
   ===================================================== */
// observerRef is optional. When provided, the API fetch is deferred until the
// element enters the viewport (lazy loading). When omitted, the fetch fires
// immediately (used by the 4 intro bridge cards where volume is not a concern).
function usePerkPhoto(perkId, photoHint, observerRef) {
  const [photo, setPhoto] = React.useState(null);
  const [resolved, setResolved] = React.useState(false);
  // If no ref given, treat as already visible so fetching starts immediately.
  const [visible, setVisible] = React.useState(!observerRef);

  // Watch the element with an IntersectionObserver; pre-load 300 px ahead of
  // the viewport so images are ready before the user actually sees the card.
  React.useEffect(() => {
    if (!observerRef || visible) return;
    const el = observerRef.current;
    if (!el) return;
    const io = new IntersectionObserver(
      ([entry]) => { if (entry.isIntersecting) setVisible(true); },
      { rootMargin: '300px' }
    );
    io.observe(el);
    return () => io.disconnect();
  }, [observerRef, visible]);

  React.useEffect(() => {
    if (!visible) return;
    if (!window.Unsplash) {
      setPhoto(null);
      setResolved(true);
      return;
    }
    let cancelled = false;
    setPhoto(null);
    setResolved(false);
    window.Unsplash.fetchPhoto(perkId, photoHint).then((p) => {
      if (!cancelled) {
        setPhoto(p || null);
        setResolved(true);
      }
    });
    return () => { cancelled = true; };
  }, [perkId, photoHint, visible]);

  return { photo, resolved, hasPhoto: !!photo };
}

function PerkImage({ photo, photoHint, variant }) {
  const [loaded, setLoaded] = React.useState(false);
  const didTrigger = React.useRef(false);

  React.useEffect(() => {
    setLoaded(false);
    didTrigger.current = false;
  }, [photo?.url]);

  function handleLoad() {
    setLoaded(true);
    if (!didTrigger.current && photo?.downloadTrigger) {
      didTrigger.current = true;
      window.Unsplash.triggerDownload(photo.downloadTrigger);
    }
  }

  if (!photo) return null;

  return (
    <div className="perk-img-wrap" style={{ background: photo.color || 'var(--paper-2)' }}>
      <img
        src={photo.url}
        alt={photo.query || photoHint}
        onLoad={handleLoad}
        className={`perk-img${loaded ? ' perk-img--loaded' : ''}`}
      />
      {variant === 'card' && (
        <div className="unsplash-attr" onClick={(e) => e.stopPropagation()}>
          <a href={photo.photographerUrl} target="_blank" rel="noopener noreferrer">
            {photo.photographer}
          </a>
          {' / '}
          <a href={photo.photoPageUrl} target="_blank" rel="noopener noreferrer">
            Unsplash
          </a>
        </div>
      )}
    </div>
  );
}

/* =====================================================
   HEADER v2 — slim, no deposit banner, no marquee
   ===================================================== */
function HeaderV2({ route, perksCount, onNav, onSearch, searchQuery = '' }) {
  const totalPerks = window.PERKS ? window.PERKS.length : 0;
  const [q, setQ] = _useState(searchQuery);

  _useEffect(() => {
    setQ(searchQuery || '');
  }, [searchQuery]);

  const submitSearch = () => {
    if (!onSearch) return;
    onSearch(q);
  };

  const onSearchKeyDown = (e) => {
    if (e.key !== 'Enter') return;
    e.preventDefault();
    submitSearch();
  };

  return (
    <header className="h2">
      <div className="h2-inner">
        <a className="h2-logo" onClick={() => onNav('welcome')}>
          <span>NYC Perks&trade;</span>
          <small>Get Yours</small>
        </a>
        <div className="h2-search">
          <button
            type="button"
            className="h2-search-btn"
            aria-label="Search perks"
            onClick={submitSearch}
          >
            🔍
          </button>
          <input
            placeholder={`Search ${totalPerks} perks — "ancestry", "stream", "laptop"…`}
            value={q}
            onChange={(e) => setQ(e.target.value)}
            onKeyDown={onSearchKeyDown}
          />
        </div>
        <div className="h2-actions">
          <div className="h2-nav-top">
            <a
              className="h2-coffee"
              href="https://www.buymeacoffee.com/joshstruppz"
              target="_blank"
              rel="noopener noreferrer"
              aria-label="Buy me a coffee"
            >
              <span className="h2-coffee-full">☕ Buy me a coffee</span>
              <span className="h2-coffee-short" aria-hidden="true">☕</span>
            </a>
            <a onClick={() => onNav('perks')} className={route === 'perks' ? 'primary' : ''}>
              My Perks <span className="count">{perksCount}</span>
            </a>
          </div>
          <a
            className={`h2-catalog${route === 'browse' ? ' primary' : ''}`}
            onClick={() => onNav('browse')}
          >
            Catalog
          </a>
        </div>
      </div>
    </header>
  );
}

/* =====================================================
   FOOTER v2 — quiet
   ===================================================== */
function FooterV2() {
  const total = window.PERKS ? window.PERKS.length : 0;
  return (
    <footer className="f2">
      <h3>Thank you for choosing New York City.</h3>
      <p>
        Pilot release · NYPL + DOE + DPR + HRA ({total} perks).
        Data current to May 2026. We grow this list constantly &mdash; if something&rsquo;s off,
        let us know on the feedback form.
      </p>
      <div className="f2-feedback">
        <p>Got ideas, corrections, or perks we&rsquo;re missing? Your input shapes what we build next.</p>
        <a href={FEEDBACK_FORM_URL} target="_blank" rel="noopener noreferrer">
          Share feedback ↗
        </a>
      </div>
    </footer>
  );
}

function perkCardRetailLine(perk) {
  const raw = (perk.cadence || '').trim();
  if (!raw) return null;
  const first = raw.split(/\s+/)[0];
  return first.startsWith('/') ? first : raw;
}

/* =====================================================
   PERK CARD v2 — no sale oval, no stamps, no FREE!
   ===================================================== */
function PerkCardV2({ perk, inPerks, onOpen, onAdd }) {
  const isGateway = perk.value_kind === 'gateway';
  const isFree    = perk.value_kind === 'free';
  const isInLib   = (perk.caveats || '').toLowerCase().includes('in-library only');
  const cadenceShort = perkCardRetailLine(perk);
  const cardRef  = React.useRef(null);
  const { photo, hasPhoto } = usePerkPhoto(perk.id, perk.photo_hint, cardRef);

  return (
    <article ref={cardRef} className={`perk2 ${inPerks ? 'in-perks' : ''}`} onClick={() => onOpen(perk)}>
      {hasPhoto && (
        <div className="perk2-hero">
          <PerkImage photo={photo} photoHint={perk.photo_hint} variant="card" />
          {isInLib && <span className="ribbon lib">in-library only</span>}
          {isGateway && !isInLib && <span className="ribbon">gateway perk</span>}
        </div>
      )}
      <div className="perk2-body">
        <div className="perk2-cat">{perk.cat}</div>
        <div className="perk2-name">{perk.name}</div>
        <div className="perk2-blurb">{perk.desc}</div>
        <SystemChips systems={perk.system} small />
        <div className="perk2-price">
          {!isGateway && !isFree && (
            <>
              <span className="perk2-price-label">Retail estimate</span>
              <span className="now">
                {perk.value_label}
                {cadenceShort && <small>{cadenceShort}</small>}
              </span>
            </>
          )}
          {isFree && (
            <span className="now">Always free<small>/ no card needed</small></span>
          )}
          {isGateway && (
            <span className="now" style={{ fontSize: 17 }}>Start here<small>/ gateway perk</small></span>
          )}
        </div>
      </div>
      <div className="perk2-cta" onClick={(ev) => { ev.stopPropagation(); onAdd(perk); }}>
        <span>{inPerks ? '✓ saved' : '+ save'}</span>
      </div>
    </article>
  );
}

/* =====================================================
   BROWSE v2 — quiet hero, typographic section heads
   ===================================================== */
const FACET_DESKTOP_MIN = 1100;

function BrowseV2({
  onOpen,
  onAdd,
  perksSet,
  searchQuery = '',
  onSearchChange,
  facets,
  onToggleFacet,
  onClearFacets,
  sortMode = 'az',
  onSortChange,
}) {
  // Only *ephemeral* UI state is local. The catalog view itself — search query,
  // facet selections, sort — is owned by <App> (see nyc-perks-prototype-v2.html)
  // so it survives navigating into a perk detail page and back.
  const [isWide, setIsWide] = _useState(
    () => typeof window !== 'undefined' && window.matchMedia(`(min-width: ${FACET_DESKTOP_MIN}px)`).matches
  );
  const [facetsOpen, setFacetsOpen] = _useState(false);

  _useEffect(() => {
    console.info('[nyc-perks v2] BrowseV2 mounted — restored view', {
      searchQuery,
      sortMode,
      facets: {
        borough: [...facets.borough],
        system:  [...facets.system],
        cat:     [...facets.cat],
        access:  [...facets.access],
      },
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  _useEffect(() => {
    const mq = window.matchMedia(`(min-width: ${FACET_DESKTOP_MIN}px)`);
    const sync = () => {
      setIsWide(mq.matches);
      if (mq.matches) setFacetsOpen(false);
    };
    sync();
    mq.addEventListener('change', sync);
    return () => mq.removeEventListener('change', sync);
  }, []);

  const activeFilterCount =
    facets.borough.size + facets.system.size + facets.cat.size + facets.access.size;

  const filtered = _useMemo(() => {
    return window.PERKS.filter((p) => {
      if (facets.system.size && !p.system.some((s) => facets.system.has(s))) return false;
      if (facets.borough.size && !p.boroughs.some((b) => facets.borough.has(b) || (b === 'All NYC'))) return false;
      if (facets.cat.size && !facets.cat.has(p.cat_top)) return false;
      if (facets.access.size) {
        const isInLib = (p.caveats || '').toLowerCase().includes('in-library only');
        const wantHome = facets.access.has('home');
        const wantLib  = facets.access.has('in-library');
        if (wantHome && isInLib) return false;
        if (wantLib && !isInLib) return false;
      }
      if (searchQuery.trim()) {
        const q = searchQuery.toLowerCase();
        const hay = [p.name, p.desc, p.cat, p.caveats || '', p.how].join(' ').toLowerCase();
        if (!hay.includes(q)) return false;
      }
      return true;
    });
  }, [facets, searchQuery]);

  const sorted = _useMemo(() => {
    const items = [...filtered];

    if (sortMode === 'az') {
      items.sort((a, b) => a.name.localeCompare(b.name));
      return items;
    }

    if (sortMode === 'newest') {
      return items.reverse();
    }

    // best: highest numeric value first, then A-Z for stable tie breaking.
    items.sort((a, b) => {
      const av = typeof a.value_num === 'number' ? a.value_num : -1;
      const bv = typeof b.value_num === 'number' ? b.value_num : -1;
      if (bv !== av) return bv - av;
      return a.name.localeCompare(b.name);
    });
    return items;
  }, [filtered, sortMode]);

  return (
    <div className="page v2">
      <div className="cat2-wrap">

        {/* HERO */}
        <div className="cat2-hero">
          <h1>Your perks.</h1>
          <p className="sub">
            Everything in here is already included in your membership.
            Browse the perks, save what you want, and see how to get it.
          </p>
          <div className="cat2-stats">
            <div><span className="k">Perks</span><span className="v t">{window.PERKS ? window.PERKS.length : '—'}</span></div>
            <div><span className="k">Systems</span><span className="v">{(window.SYSTEMS || ['NYPL', 'BPL', 'QPL', 'DOE', 'DPR', 'HRA']).join(' · ')}</span></div>
          </div>
        </div>

        {/* MAIN GRID */}
        <div className="cat2-layout">

          {!isWide && (
            <div className="cat2-facets-bar">
              <button
                type="button"
                className={`cat2-facets-trigger${facetsOpen ? ' is-open' : ''}`}
                aria-expanded={facetsOpen}
                aria-controls="cat2-facets-panel"
                onClick={() => setFacetsOpen((open) => !open)}
              >
                Filters
                {activeFilterCount > 0 && (
                  <span className="cat2-facets-badge" aria-label={`${activeFilterCount} filters applied`}>
                    {activeFilterCount}
                  </span>
                )}
              </button>
              {activeFilterCount > 0 && (
                <button type="button" className="cat2-facets-clear" onClick={onClearFacets}>
                  Clear
                </button>
              )}
            </div>
          )}

          <aside
            className={[
              'cat2-facets',
              !isWide && 'cat2-facets--drawer',
              !isWide && facetsOpen && 'is-open',
            ].filter(Boolean).join(' ')}
            id="cat2-facets-panel"
            aria-hidden={!isWide && !facetsOpen}
          >
            {!isWide && (
              <div className="cat2-facets-drawer-head">
                <span>Filter perks</span>
                <button
                  type="button"
                  className="cat2-facets-close"
                  aria-label="Close filters"
                  onClick={() => setFacetsOpen(false)}
                >
                  ✕
                </button>
              </div>
            )}

            <details className="cat2-facet-group" open={isWide}>
              <summary className="cat2-facet-sum">Where you live</summary>
              <div className="list">
                {['Manhattan', 'Bronx', 'Brooklyn', 'Queens', 'Staten Island'].map((b) => (
                  <label key={b}>
                    <input type="checkbox" checked={facets.borough.has(b)} onChange={() => onToggleFacet('borough', b)} />
                    {b}
                  </label>
                ))}
              </div>
            </details>

            <details className="cat2-facet-group" open={isWide}>
              <summary className="cat2-facet-sum">System</summary>
              <div className="list">
                {(window.SYSTEMS || ['NYPL', 'BPL', 'QPL', 'DOE', 'DPR', 'HRA']).map((s) => (
                  <label key={s}>
                    <input type="checkbox" checked={facets.system.has(s)} onChange={() => onToggleFacet('system', s)} />
                    {s}
                  </label>
                ))}
              </div>
            </details>

            <details className="cat2-facet-group" open={isWide}>
              <summary className="cat2-facet-sum">Category</summary>
              <div className="list">
                {window.CATEGORIES_TOP.map((c) => (
                  <label key={c.key}>
                    <input type="checkbox" checked={facets.cat.has(c.key)} onChange={() => onToggleFacet('cat', c.key)} />
                    {c.label}
                    <span className="count">{c.count}</span>
                  </label>
                ))}
              </div>
            </details>

            <details className="cat2-facet-group" open={isWide}>
              <summary className="cat2-facet-sum">Access</summary>
              <div className="list">
                <label><input type="checkbox" checked={facets.access.has('home')}       onChange={() => onToggleFacet('access', 'home')} />Home access</label>
                <label><input type="checkbox" checked={facets.access.has('in-library')} onChange={() => onToggleFacet('access', 'in-library')} />In-library only</label>
              </div>
            </details>
          </aside>

          {/* RESULTS */}
          <div className="cat2-results">
            <div className="cat2-toolbar">
              <div className="input">
                <span>🔍</span>
                <input
                  placeholder="Search — &quot;wifi&quot;, &quot;chinese&quot;, &quot;ancestry&quot;, &quot;test prep&quot;…"
                  value={searchQuery}
                  onChange={(e) => onSearchChange(e.target.value)}
                />
              </div>
              <button type="button" className={`chip ${sortMode === 'best' ? 'on' : ''}`} onClick={() => onSortChange('best')}>best value ↓</button>
              <button type="button" className={`chip ${sortMode === 'az' ? 'on' : ''}`} onClick={() => onSortChange('az')}>A–Z</button>
              <button type="button" className={`chip ${sortMode === 'newest' ? 'on' : ''}`} onClick={() => onSortChange('newest')}>newest</button>
              <span className="count">{filtered.length} of {window.PERKS.length}</span>
            </div>

            {sorted.length === 0 ? (
              <div className="cat2-empty">
                No perks match your current filters. Try removing a filter or broadening your search.
              </div>
            ) : (
              <div className="cat2-grid">
                {sorted.map((p) => (
                  <PerkCardV2 key={p.id} perk={p} inPerks={perksSet.has(p.id)} onOpen={onOpen} onAdd={onAdd} />
                ))}
              </div>
            )}
          </div>
        </div>
      </div>

      <FooterV2 />
    </div>
  );
}

/* =====================================================
   DETAIL v2 — quieter
   ===================================================== */
function perkShareUrl(perkId) {
  const base = `${window.location.origin}${window.location.pathname}${window.location.search}`;
  return `${base}#detail/${encodeURIComponent(perkId)}`;
}

function PerkDetailV2({ perk, onBack, inPerks, onAdd }) {
  if (!perk) return null;
  const isGateway = perk.value_kind === 'gateway';
  const isInLib   = (perk.caveats || '').toLowerCase().includes('in-library only');
  const { photo, hasPhoto } = usePerkPhoto(perk.id, perk.photo_hint);
  const [linkCopied, setLinkCopied] = _useState(false);
  const linkCopyTimeoutRef = _useRef(null);

  const llm = formatPerkExportText(perk);
  const shareUrl = perkShareUrl(perk.id);

  const copyLLM = () => {
    navigator.clipboard.writeText(llm).then(() => alert('Copied to clipboard.'));
  };

  const copyLink = () => {
    navigator.clipboard.writeText(shareUrl).then(
      () => {
        setLinkCopied(true);
        if (linkCopyTimeoutRef.current) clearTimeout(linkCopyTimeoutRef.current);
        linkCopyTimeoutRef.current = setTimeout(() => setLinkCopied(false), 2000);
      },
      () => alert('Could not copy — your browser may need permission for the clipboard.')
    );
  };

  _useEffect(() => () => {
    if (linkCopyTimeoutRef.current) clearTimeout(linkCopyTimeoutRef.current);
  }, []);

  return (
    <div className="page v2">
      <div className="det2">
        <div className="crumbs">
          <button onClick={onBack}>← catalog</button>
          <span className="path">Catalog · {perk.cat_top} · {perk.name}</span>
        </div>

        <div className="det2-grid">
          <div>
            <div className="meta-row">
              <span className="tag">{perk.cat}</span>
              <SystemChips systems={perk.system} small />
              <span className="pill">{isInLib ? 'in-library only' : 'home access'}</span>
              {isGateway && <span className="pill">gateway perk</span>}
            </div>

            <h1>{perk.name}</h1>
            <p className="lede">{perk.desc}</p>

            {hasPhoto && (
              <div className="photo">
                <PerkImage photo={photo} photoHint={perk.photo_hint} variant="detail" />
              </div>
            )}
            {hasPhoto && (
              <p className="photo-attr">
                Photo by{' '}
                <a href={photo.photographerUrl} target="_blank" rel="noopener noreferrer">
                  {photo.photographer}
                </a>
                {' '}on{' '}
                <a href={photo.photoPageUrl} target="_blank" rel="noopener noreferrer">
                  Unsplash
                </a>
              </p>
            )}

            <h3>How to redeem</h3>
            <ol className="steps">
              {perk.steps.map((s, i) => (
                <li key={i}><span className="step-body">{linkifyText(s)}</span></li>
              ))}
            </ol>

            <div className="faq">
              <div>
                <h5>Fine print</h5>
                <div className="body">
                  {perk.caveats || <span className="ink-faint italic">No published caveats — but always double-check on the source.</span>}
                </div>
              </div>
              <div>
                <h5>Where it works</h5>
                <div className="body">
                  <strong>System:</strong> {perk.system.join(', ')}<br />
                  <strong>Borough:</strong> {perk.boroughs.join(', ')}<br />
                  <strong>Access:</strong> {isInLib ? 'in-branch only' : 'home, work, anywhere'}
                </div>
              </div>
            </div>
          </div>

          {/* BUY BOX */}
          <aside className="det2-buy">
            <div className="row1">
              <div className="retail">
                Estimated retail
                <s>{perk.value_label}</s>
              </div>
            </div>
            <div className="pay">$0</div>
            <div className="pay-sub">{perk.cadence || 'included in your membership'}</div>

            <div className="actions">
              <a className="primary" href={perk.url} target="_blank" rel="noopener">
                Go to {new URL(perk.url).hostname.replace('www.', '')} ↗
              </a>
              <button
                className={`secondary ${inPerks ? 'on' : ''}`}
                onClick={() => onAdd(perk)}
              >
                {inPerks ? '✓ saved to my perks' : '+ save to my perks'}
              </button>
              <button
                type="button"
                className="secondary"
                disabled={linkCopied}
                onClick={copyLink}
              >
                {linkCopied ? 'Link copied!' : 'Copy link to share'}
              </button>
            </div>

            <ul className="quick">
              <li>Eligibility: anyone with a card</li>
              <li>Setup: ~{perk.steps.length * 2} min</li>
              <li>Renewal: as long as your card is active</li>
              <li>Where: {isInLib ? 'branch only' : 'anywhere'}</li>
            </ul>
          </aside>
        </div>

        <div className="llm det2-llm-footer">
          <h5>Copy information</h5>
          <button type="button" className="copy" onClick={copyLLM}>copy</button>
          <p>
            The perks and instructions to redeem aren&rsquo;t perfect. Feel free to copy the data and information to do your own research.
          </p>
          <pre>{llm}</pre>
        </div>
      </div>
      <FooterV2 />
    </div>
  );
}

/* =====================================================
   PERKS PAGE v2 — quiet
   ===================================================== */
function PerksPageV2({ saved, onOpen, onRemove, onBackToBrowse }) {
  const items = window.PERKS.filter((p) => saved.has(p.id));
  const totalRetail = items.reduce((s, p) => s + (p.value_num || 0), 0);
  const exportText = formatSavedPerksExport(items);
  const [copied, setCopied] = _useState(false);
  const copyTimeoutRef = _useRef(null);

  _useEffect(() => {
    setCopied(false);
    if (copyTimeoutRef.current) {
      clearTimeout(copyTimeoutRef.current);
      copyTimeoutRef.current = null;
    }
  }, [items.length]);

  _useEffect(() => () => {
    if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current);
  }, []);

  const copyAll = () => {
    if (!items.length) return;
    navigator.clipboard.writeText(exportText).then(
      () => {
        setCopied(true);
        if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current);
        copyTimeoutRef.current = setTimeout(() => setCopied(false), 2000);
      },
      () => alert('Could not copy — your browser may need permission for the clipboard.')
    );
  };

  return (
    <div className="page v2">
      <div className="pl2-wrap">
        <div className="pl2-hero">
          <h1>My perks</h1>
          <p className="sub">
            {items.length === 0
              ? 'Your list is empty. Pop back to the catalog and start saving.'
              : `${items.length} perks saved · est. retail $${totalRetail.toLocaleString()}/yr`}
          </p>
          <div className="pl2-actions">
            <a
              className="pl2-feedback-link"
              href={FEEDBACK_FORM_URL}
              target="_blank"
              rel="noopener noreferrer"
            >
              Share feedback ↗
            </a>
            <button type="button" className="primary" disabled={!items.length || copied} onClick={copyAll}>
              {copied ? 'Copied!' : '📋 copy information'}
            </button>
          </div>
        </div>

        {items.length === 0 && (
          <div className="pl2-empty">
            <div className="ico">∅</div>
            <p>Tap any <strong>+ save</strong> button in the catalog to start your stack.</p>
            <button className="wv2-cta" onClick={onBackToBrowse} style={{ marginTop: 8 }}>
              ← browse the catalog
            </button>
          </div>
        )}

        {items.length > 0 && (
          <>
            {items.map((p, i) => {
              const isInLib = (p.caveats || '').toLowerCase().includes('in-library only');
              return (
                <div key={p.id} className="pl2-row" onClick={() => onOpen(p)}>
                  <div className="num">{String(i + 1).padStart(2, '0')}</div>
                  <div>
                    <div className="name">{p.name}</div>
                    <div className="meta">
                      {p.cat} · {p.system.join('/')} · {p.steps.length} steps · {isInLib ? 'in-library only' : 'home access'}
                    </div>
                    {p.caveats && <div className="meta" style={{ marginTop: 2 }}>⚠ {p.caveats}</div>}
                  </div>
                  <div className="price">
                    {p.value_label}
                    <small>retail / {p.cadence?.replace(/^\//, '') || 'yr'}</small>
                  </div>
                  <div className="actions">
                    <a className="primary" href={p.url} target="_blank" rel="noopener" onClick={(ev) => ev.stopPropagation()}>open ↗</a>
                    <button onClick={(ev) => { ev.stopPropagation(); onRemove(p); }}>remove</button>
                  </div>
                </div>
              );
            })}

            <div className="pl2-total">
              <div>
                <div className="label">total retail value locked in</div>
                <div className="amt">${totalRetail.toLocaleString()}<small style={{ fontSize: 18, color: 'var(--ink-soft)', marginLeft: 6 }}>/ yr</small></div>
              </div>
              <div className="note">
                Perks your membership already includes. <br/>Not bad!
              </div>
            </div>
          </>
        )}
      </div>
      <FooterV2 />
    </div>
  );
}

/* =====================================================
   EXPORTS
   ===================================================== */
Object.assign(window, {
  HeaderV2, FooterV2, PerkCardV2, PerkImage, usePerkPhoto,
  BrowseV2, PerkDetailV2, PerksPageV2,
});
