/* Slow Weekend — cards & score visualizations.
   Exports RecommendationCard, ScoreViz, FilterBreakdown, TextureBlock. */

const CAT_ACCENT = {
  Recharge: "#7E9078",
  Explore:  "#A6884F",
  Connect:  "#8576A0",
};

// Soft abstract texture field built from a pick's tones — stands in for photography.
function TextureBlock({ tones, style, seed = 0, children, className }) {
  const [a, b, c] = tones;
  const bg = `radial-gradient(115% 85% at ${22 + seed * 6}% 18%, ${a}, transparent 58%),
              radial-gradient(95% 80% at 82% ${78 - seed * 4}%, ${c}, transparent 55%),
              radial-gradient(120% 120% at 50% 120%, ${a}, transparent 70%),
              ${b}`;
  return (
    <div className={className} style={{ position: "relative", background: bg, overflow: "hidden", ...style }}>
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage:
          "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E\")",
        mixBlendMode: "soft-light", opacity: 0.28, pointerEvents: "none",
      }} />
      {children}
    </div>
  );
}

// 0–2 score shown as up to two filled dots.
function CalmDots({ value, accent }) {
  return (
    <span style={{ display: "inline-flex", gap: 4 }}>
      {[0, 1].map((i) => (
        <span key={i} style={{
          width: 7, height: 7, borderRadius: "50%",
          background: i < value ? accent : "transparent",
          boxShadow: `inset 0 0 0 1.25px ${i < value ? accent : "rgba(47,47,47,0.22)"}`,
        }} />
      ))}
    </span>
  );
}

// Compact score on the card. variant: single | dots | ring
function ScoreViz({ variant, pick, accent }) {
  const dims = window.SLOW_WEEKEND_DATA.filterDimensions;

  if (variant === "ring") {
    const r = 22, c = 2 * Math.PI * r;
    const frac = pick.calmScore / 10;
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
        <svg width="56" height="56" viewBox="0 0 56 56">
          <circle cx="28" cy="28" r={r} fill="none" stroke="rgba(47,47,47,0.10)" strokeWidth="3.5" />
          <circle cx="28" cy="28" r={r} fill="none" stroke={accent} strokeWidth="3.5"
            strokeLinecap="round" strokeDasharray={c} strokeDashoffset={c * (1 - frac)}
            transform="rotate(-90 28 28)" />
          <text x="28" y="32" textAnchor="middle" style={{ font: "600 16px 'Source Serif 4', serif", fill: "#2F2F2F" }}>{pick.calmScore}</text>
        </svg>
        <div style={{ lineHeight: 1.3 }}>
          <div style={{ fontSize: 12, letterSpacing: "0.10em", textTransform: "uppercase", color: "rgba(47,47,47,0.5)" }}>Fit Score</div>
          <div style={{ fontSize: 13, color: "rgba(47,47,47,0.62)" }}></div>
        </div>
      </div>
    );
  }

  if (variant === "dots") {
    return (
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px 22px" }}>
        {dims.map((d) => (
          <div key={d.key} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14 }}>
            <span style={{ fontSize: 12.5, color: "rgba(47,47,47,0.6)" }}>{d.label}</span>
            <CalmDots value={pick.scores[d.key]} accent={accent} />
          </div>
        ))}
      </div>
    );
  }

  // single — dense, one-line (functional over the decorative ring; founder 2026-06-22)
  return (
    <div style={{ display: "flex", alignItems: "baseline", gap: 6 }}>
      <span style={{ font: "600 19px 'Source Serif 4', serif", color: "#2F2F2F" }}>{pick.calmScore}</span>
      <span style={{ fontSize: 12.5, color: "rgba(47,47,47,0.4)" }}>/10</span>
      <span style={{ marginLeft: 3, fontSize: 11, letterSpacing: "0.10em", textTransform: "uppercase", color: accent, fontWeight: 600 }}>Fit</span>
    </div>
  );
}

// Full, transparent breakdown for the detail page.
function FilterBreakdown({ pick, accent }) {
  const dims = window.SLOW_WEEKEND_DATA.filterDimensions;
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
      {dims.map((d) => (
        <div key={d.key}>
          <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 8 }}>
            <span style={{ font: "500 17px 'Source Serif 4', serif", color: "#2F2F2F" }}>{d.label}</span>
            <span style={{ display: "flex", gap: 6 }}>
              {[0, 1].map((i) => (
                <span key={i} style={{
                  width: 9, height: 9, borderRadius: "50%",
                  background: i < pick.scores[d.key] ? accent : "transparent",
                  boxShadow: `inset 0 0 0 1.5px ${i < pick.scores[d.key] ? accent : "rgba(47,47,47,0.2)"}`,
                }} />
              ))}
            </span>
          </div>
          <p style={{ margin: 0, fontSize: 13.5, lineHeight: 1.5, color: "rgba(47,47,47,0.55)" }}>{d.note}</p>
        </div>
      ))}
    </div>
  );
}

/* ---- Card layouts. variant: editorial | quiet | panel ---- */

// Energy Level — a small "meter" glyph (1–3 bars) + label. Orthogonal to the
// category accent, so it gets its own muted scale: calm → sand → terracotta.
const ENERGY_COLOR = { Gentle: "#8FA9B8", Active: "#C2A878", Adventure: "#B0764F" };
function EnergyBadge({ level, lg }) {
  const meta = window.SW && SW.energyMeta ? SW.energyMeta(level) : null;
  if (!meta) return null;
  const color = ENERGY_COLOR[level] || "#7E9078";
  const h = lg ? [8, 11, 14] : [6, 9, 12];
  return (
    <span title={meta.note} style={{ display: "inline-flex", alignItems: "center", gap: 6, whiteSpace: "nowrap" }}>
      <span style={{ display: "inline-flex", alignItems: "flex-end", gap: 2 }}>
        {[0, 1, 2].map((i) => (
          <span key={i} style={{ width: 3, height: h[i], borderRadius: 1, background: i < meta.bars ? color : "rgba(47,47,47,0.16)" }} />
        ))}
      </span>
      <span style={{ fontSize: lg ? 13 : 12.5, color, fontWeight: 600 }}>{level}</span>
    </span>
  );
}

// Partner provenance — "Shared by <partner>". The trust-by-provenance signal for
// partner-seeded inventory. variant: chip (on cards) | full (detail block).
function PartnerBadge({ partnerKey, variant = "chip" }) {
  const reg = window.SLOW_WEEKEND_DATA.partners || {};
  const p = reg[partnerKey];
  if (!p) return null;
  if (variant === "full") {
    return (
      <div style={{ display: "flex", alignItems: "flex-start", gap: 13, background: "rgba(176,118,79,0.07)", border: "1px solid rgba(176,118,79,0.22)", borderRadius: 16, padding: "18px 20px" }}>
        <span style={{ flexShrink: 0, width: 36, height: 36, borderRadius: "50%", background: "radial-gradient(circle at 35% 30%, #C58FA0, #B0764F)", display: "inline-flex", alignItems: "center", justifyContent: "center", color: "#fff", fontSize: 16 }}>✦</span>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 12, letterSpacing: "0.12em", textTransform: "uppercase", color: "#9A6238", fontWeight: 600, marginBottom: 3 }}>Shared by a partner</div>
          <div style={{ font: "500 16px 'Source Serif 4', serif", color: "#2F2F2F" }}>{p.name} <span style={{ fontSize: 13, color: "rgba(47,47,47,0.45)", fontWeight: 400 }}>{p.handle}</span></div>
          <p style={{ margin: "6px 0 0", fontSize: 13.5, lineHeight: 1.5, color: "rgba(47,47,47,0.62)" }}>{p.blurb}</p>
        </div>
      </div>
    );
  }
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "rgba(176,118,79,0.1)", borderRadius: 999, padding: "4px 11px 4px 8px", whiteSpace: "nowrap" }}>
      <span style={{ width: 14, height: 14, borderRadius: "50%", background: "radial-gradient(circle at 35% 30%, #C58FA0, #B0764F)", display: "inline-flex", alignItems: "center", justifyContent: "center", color: "#fff", fontSize: 9 }}>✦</span>
      <span style={{ fontSize: 12, color: "#9A6238", fontWeight: 600 }}>{p.name}</span>
    </span>
  );
}

function HeartIcon({ filled, color, size = 21 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={filled ? color : "none"} stroke={color} strokeWidth={filled ? 0 : 1.7} strokeLinejoin="round" style={{ display: "block" }} aria-hidden="true">
      <path d="M12 20.3C5 16 2.5 12.4 2.5 8.9 2.5 6.3 4.5 4.5 6.9 4.5c1.8 0 3.3 1.1 4.1 2.5.8-1.4 2.3-2.5 4.1-2.5 2.4 0 4.4 1.8 4.4 4.4 0 3.5-2.5 7.1-7.5 11.4z" />
    </svg>
  );
}

// Quiet "save" affordance: a heart, not a social like (accent fill, no counts).
function HeartButton({ saved, onToggle, accent }) {
  const a = accent || CAT_ACCENT.Recharge;
  const [pop, setPop] = React.useState(false);
  return (
    <button
      onClick={(e) => { e.stopPropagation(); setPop(true); setTimeout(() => setPop(false), 240); onToggle && onToggle(); }}
      title={saved ? "Saved · tap to remove" : "Save for later"}
      aria-label={saved ? "Remove from saved" : "Save for later"} aria-pressed={!!saved}
      style={{
        border: "1px solid rgba(47,47,47,0.08)", background: "rgba(255,255,255,0.9)", cursor: "pointer",
        width: 36, height: 36, borderRadius: "50%", display: "inline-flex", alignItems: "center", justifyContent: "center",
        boxShadow: "0 6px 16px -10px rgba(47,47,47,0.5)", padding: 0,
        transform: pop ? "scale(1.18)" : "scale(1)", transition: "transform 0.24s cubic-bezier(.2,.8,.2,1)",
      }}>
      <HeartIcon filled={!!saved} color={a} />
    </button>
  );
}

// "In season now" — the shared timing chip (collections, cards, hike detail all
// speak the same visual language for "this is at its best right now").
function SeasonNowChip() {
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "rgba(176,118,79,0.12)", borderRadius: 999, padding: "3px 10px", whiteSpace: "nowrap" }}>
      <span className="sw-breath" style={{ width: 6, height: 6, borderRadius: "50%", background: "#B0764F" }} />
      <span style={{ fontSize: 11, letterSpacing: "0.06em", textTransform: "uppercase", color: "#9A6238", fontWeight: 600 }}>In season now</span>
    </span>
  );
}

// The living draw, surfaced on the card so it isn't buried on the detail page.
function DrawHint({ pick, accent }) {
  const t = pick.trail;
  if (!t) return null;
  const lead = t.hint || (t.draws && t.draws[0]);
  if (!lead) return null;
  const season = window.SW && SW.trailSeasonStatus ? SW.trailSeasonStatus(pick) : { inBest: false };
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginBottom: 2 }}>
      <span style={{ display: "inline-flex", alignItems: "center", gap: 7, background: accent + "14", borderRadius: 999, padding: "4px 11px" }}>
        <span style={{ width: 6, height: 6, borderRadius: "50%", background: accent, flexShrink: 0 }} />
        <span style={{ fontSize: 12.5, color: accent, fontWeight: 600 }}>{lead}</span>
      </span>
      {season.inBest && <SeasonNowChip />}
    </div>
  );
}

function CardMeta({ pick, accent, compact }) {
  const dot = <span style={{ width: 3, height: 3, borderRadius: "50%", background: "rgba(47,47,47,0.22)", flexShrink: 0 }} />;
  const txt = { fontSize: compact ? 13 : 13.5, color: "rgba(47,47,47,0.6)", whiteSpace: "nowrap" };
  const row = { display: "flex", alignItems: "center", gap: compact ? 8 : 10 };
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: compact ? 4 : 6 }}>
      <DrawHint pick={pick} accent={accent} />
      <div style={row}>
        <span style={txt}>{pick.city}</span>
        {dot}
        <span style={txt}>{SW.whenLabel(pick)}</span>
      </div>
      <div style={row}>
        <span style={txt}>{pick.ageRange}</span>
        {pick.energyLevel && dot}
        {pick.energyLevel && <EnergyBadge level={pick.energyLevel} />}
      </div>
    </div>
  );
}

// Structured facts (PRD §7) — the scannable details that prevent a bad day.
// variant: compact (chips on a card) | full (labelled grid on the detail page).
function FactChip({ label, tone }) {
  const c = tone === "free" ? "#5d6b58" : tone === "paid" ? "#9A6238" : "rgba(47,47,47,0.62)";
  const bg = tone === "free" ? "rgba(126,144,120,0.12)" : tone === "paid" ? "rgba(166,136,79,0.14)" : "rgba(47,47,47,0.05)";
  return <span style={{ fontSize: 11.5, color: c, background: bg, borderRadius: 999, padding: "3px 10px", whiteSpace: "nowrap", fontWeight: 500 }}>{label}</span>;
}

function OutingFacts({ facts, variant, bookingHref, onBook }) {
  if (!facts) return null;
  if (variant === "full") {
    const rows = [
      ["Cost", facts.cost || "—", facts.paid ? "paid" : facts.cost ? "free" : null],
      ["Parking", facts.parking || "Not listed", null],
      ["Food", facts.food || "None on site", null],
      ["Reservation", facts.reservation ? "Reserve ahead" : "Just show up", facts.reservation ? "paid" : null],
    ];
    const valColor = (tone) => tone === "free" ? "#5d6b58" : tone === "paid" ? "#9A6238" : "#2F2F2F";
    return (
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "14px 22px" }}>
        {rows.map(([label, val, tone]) => {
          const bookable = bookingHref && ((label === "Cost" && /ticket|admission/i.test(facts.cost || "")) || (label === "Reservation" && facts.reservation));
          return (
            <div key={label}>
              <div style={{ fontSize: 11.5, letterSpacing: "0.1em", textTransform: "uppercase", color: "rgba(47,47,47,0.42)", fontWeight: 600, marginBottom: 4 }}>{label}</div>
              {bookable ? (
                <a href={bookingHref} target="_blank" rel="noopener noreferrer" onClick={onBook}
                  style={{ fontSize: 15, color: valColor(tone), fontWeight: 600, textDecoration: "underline", textUnderlineOffset: 2, textDecorationColor: "rgba(154,98,56,0.4)" }}>{val} ↗</a>
              ) : (
                <div style={{ fontSize: 15, color: valColor(tone), fontWeight: tone ? 600 : 400 }}>{val}</div>
              )}
            </div>
          );
        })}
      </div>
    );
  }
  const chips = [];
  if (facts.cost) chips.push({ label: facts.cost, tone: facts.paid ? "paid" : "free" });
  if (facts.reservation) chips.push({ label: "Reserve ahead", tone: "paid" });
  if (facts.food) chips.push({ label: facts.food, tone: null });
  if (!chips.length) return null;
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
      {chips.map((c, i) => <FactChip key={i} label={c.label} tone={c.tone} />)}
    </div>
  );
}

function CategoryTag({ pick, accent }) {
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
      <span style={{ width: 8, height: 8, borderRadius: "50%", background: accent }} />
      <span style={{ fontSize: 12.5, letterSpacing: "0.16em", textTransform: "uppercase", color: accent, fontWeight: 600 }}>
        {pick.category}
      </span>
      <span style={{ fontSize: 12.5, color: "rgba(47,47,47,0.4)" }}>· {pick.texture}</span>
    </span>
  );
}

function RecommendationCard({ pick, variant, scoreVariant, onOpen, isSaved, onToggleSave }) {
  const accent = CAT_ACCENT[pick.category];
  const [hover, setHover] = React.useState(false);
  const baseCard = {
    position: "relative",
    // Flex column + grow so sibling hero cards in a row share one height and
    // their footers (score + CTA) line up regardless of blurb length.
    display: "flex",
    flexDirection: "column",
    flex: "1 1 auto",
    background: "#FFFFFF",
    borderRadius: 18,
    boxShadow: hover ? "0 22px 50px -28px rgba(47,47,47,0.32)" : "0 14px 40px -30px rgba(47,47,47,0.28)",
    transition: "box-shadow 0.7s cubic-bezier(.2,.6,.2,1), transform 0.7s cubic-bezier(.2,.6,.2,1)",
    transform: hover ? "translateY(-3px)" : "translateY(0)",
    cursor: "pointer",
    overflow: "hidden",
    border: "1px solid rgba(47,47,47,0.06)",
  };
  const open = () => onOpen(pick.id);
  const handlers = { onMouseEnter: () => setHover(true), onMouseLeave: () => setHover(false), onClick: open };
  const heart = onToggleSave ? (
    <div style={{ position: "absolute", top: 14, right: 14, zIndex: 3 }}>
      <HeartButton saved={isSaved} accent={accent} onToggle={() => onToggleSave(pick.id)} />
    </div>
  ) : null;

  const cta = (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 13, color: accent, fontWeight: 600, whiteSpace: "nowrap" }}>
      Why it made the cut
      <span style={{ transition: "transform 0.5s", transform: hover ? "translateX(4px)" : "none" }}>→</span>
    </span>
  );

  if (variant === "quiet") {
    return (
      <div {...handlers} style={{ ...baseCard, padding: "38px 40px" }}>
        {heart}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flexWrap: "wrap", paddingRight: onToggleSave ? 44 : 0 }}>
          <CategoryTag pick={pick} accent={accent} />
          {pick.partner && <PartnerBadge partnerKey={pick.partner} />}
        </div>
        <h3 style={{ font: "500 27px/1.18 'Source Serif 4', serif", color: "#2F2F2F", margin: "20px 0 14px", letterSpacing: "-0.01em" }}>{pick.title}</h3>
        <p style={{ margin: "0 0 18px", fontSize: 15.5, lineHeight: 1.6, color: "rgba(47,47,47,0.66)" }}>{pick.blurb}</p>
        <CardMeta pick={pick} accent={accent} />
        <div style={{ marginTop: 14 }}><OutingFacts facts={pick.facts} /></div>
        <div style={{ height: 1, background: "rgba(47,47,47,0.08)", margin: "20px 0 20px", marginTop: "auto" }} />
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }}>
          <ScoreViz variant={scoreVariant} pick={pick} accent={accent} />
          {cta}
        </div>
      </div>
    );
  }

  if (variant === "panel") {
    return (
      <div {...handlers} style={{ ...baseCard, display: "grid", gridTemplateColumns: "210px 1fr" }}>
        {heart}
        <TextureBlock tones={pick.tones} seed={1} style={{ minHeight: 230 }}>
          <div style={{ position: "absolute", left: 22, bottom: 20 }}>
            <span style={{ fontSize: 12.5, letterSpacing: "0.16em", textTransform: "uppercase", color: "rgba(255,255,255,0.92)", fontWeight: 600, textShadow: "0 1px 8px rgba(47,47,47,0.25)" }}>{pick.category}</span>
          </div>
        </TextureBlock>
        <div style={{ padding: "30px 34px" }}>
          {pick.partner && <div style={{ marginBottom: 12 }}><PartnerBadge partnerKey={pick.partner} /></div>}
          <h3 style={{ font: "500 25px/1.2 'Source Serif 4', serif", color: "#2F2F2F", margin: "0 0 12px", letterSpacing: "-0.01em" }}>{pick.title}</h3>
          <CardMeta pick={pick} accent={accent} compact />
          <div style={{ margin: "12px 0 16px" }}><OutingFacts facts={pick.facts} /></div>
          <p style={{ margin: "0 0 18px", fontSize: 15, lineHeight: 1.6, color: "rgba(47,47,47,0.66)" }}>{pick.blurb}</p>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, flexWrap: "wrap" }}>
            <ScoreViz variant={scoreVariant} pick={pick} accent={accent} />
            {cta}
          </div>
        </div>
      </div>
    );
  }

  // editorial (default)
  return (
    <div {...handlers} style={{ ...baseCard }}>
      {heart}
      {/* Slimmed texture block (the §16 "earn its place" shrink): a thin accent that
          still names the kind and is ready to host a real photo later, not a big
          decorative gradient eating the fold. */}
      <TextureBlock tones={pick.tones} style={{ height: 88 }}>
        <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "flex-end", padding: "13px 20px" }}>
          <span style={{ fontSize: 12, letterSpacing: "0.18em", textTransform: "uppercase", color: "rgba(255,255,255,0.95)", fontWeight: 600, textShadow: "0 1px 10px rgba(47,47,47,0.3)" }}>
            {pick.category} · {pick.texture}
          </span>
        </div>
      </TextureBlock>
      <div style={{ padding: "20px 28px 22px", flex: 1, display: "flex", flexDirection: "column" }}>
        {pick.partner && <div style={{ marginBottom: 10 }}><PartnerBadge partnerKey={pick.partner} /></div>}
        <h3 style={{ font: "500 21px/1.25 'Source Serif 4', serif", color: "#2F2F2F", margin: "0 0 10px", letterSpacing: "-0.01em" }}>{pick.title}</h3>
        <CardMeta pick={pick} accent={accent} compact />
        <div style={{ margin: "10px 0 14px" }}><OutingFacts facts={pick.facts} /></div>
        <p style={{ margin: "0 0 16px", fontSize: 14.5, lineHeight: 1.55, color: "rgba(47,47,47,0.66)" }}>{pick.blurb}</p>
        <div style={{ height: 1, background: "rgba(47,47,47,0.08)", marginBottom: 14, marginTop: "auto" }} />
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, flexWrap: "wrap" }}>
          <ScoreViz variant={scoreVariant} pick={pick} accent={accent} />
          {cta}
        </div>
      </div>
    </div>
  );
}

function ShareIcon({ size = 15 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7" />
      <polyline points="16 6 12 2 8 6" />
      <line x1="12" y1="2" x2="12" y2="15" />
    </svg>
  );
}

// One calm Share affordance (PRD §8) — native share sheet on mobile, copy-link
// with a quiet "Link copied" on desktop. Public content only (bank test); no
// share counts or vanity metrics. `route` is {type: outing|plan|collection|week, id}.
function ShareButton({ route, label = "Share", variant = "pill" }) {
  const [copied, setCopied] = React.useState(false);
  const onShare = async (e) => {
    e.stopPropagation();
    const data = window.SLOW_WEEKEND_DATA;
    const { title, text } = SW.shareText(route, data);
    // Share through the /s endpoint so the pasted link gets per-link OG previews
    // (api/share.js); /s redirects humans straight into the SPA on open.
    const base = (location.origin || "") + "/s";
    const url = SW.shareUrl(route, base);
    const track = (method) => { try { if (window.SWA) SWA.track("share", { type: route.type, id: route.id || route.window, method }); } catch (er) {} };
    const flash = () => { setCopied(true); setTimeout(() => setCopied(false), 1800); };
    try {
      if (navigator.share) { await navigator.share({ title, text, url }); track("native"); }
      else { await navigator.clipboard.writeText(url); flash(); track("copy"); }
    } catch (err) {
      if (err && err.name === "AbortError") return; // user dismissed the share sheet
      try { await navigator.clipboard.writeText(url); flash(); track("copy"); } catch (e2) {}
    }
  };
  if (variant === "icon") {
    return (
      <button onClick={onShare} aria-label={`Share ${label}`} title="Share" style={{
        border: "1px solid rgba(47,47,47,0.12)", background: "rgba(255,255,255,0.92)", cursor: "pointer",
        width: 36, height: 36, borderRadius: "50%", display: "inline-flex", alignItems: "center", justifyContent: "center",
        color: copied ? "#5d6b58" : "rgba(47,47,47,0.6)", backdropFilter: "blur(4px)",
      }}>{copied ? "✓" : <ShareIcon size={15} />}</button>
    );
  }
  return (
    <button onClick={onShare} style={{
      border: "1px solid rgba(47,47,47,0.16)", background: "none", cursor: "pointer", font: "inherit",
      fontSize: 13.5, fontWeight: 600, color: copied ? "#5d6b58" : "rgba(47,47,47,0.62)", borderRadius: 999,
      padding: "8px 16px", display: "inline-flex", alignItems: "center", gap: 8, whiteSpace: "nowrap", transition: "color .2s",
    }}>{copied ? <span style={{ fontSize: 14 }}>✓</span> : <ShareIcon size={14} />}{copied ? "Link copied" : label}</button>
  );
}

Object.assign(window, {
  CAT_ACCENT, ENERGY_COLOR, TextureBlock, CalmDots, ScoreViz, FilterBreakdown,
  CardMeta, CategoryTag, EnergyBadge, PartnerBadge, FactChip, OutingFacts, RecommendationCard,
  SeasonNowChip, DrawHint, HeartButton, ShareIcon, ShareButton,
});
