// ============================================================
// vibn-marketplace · marketplace-components.jsx
// ------------------------------------------------------------
// Components specific to two-sided marketplaces. Built on top
// of vibn-ai-templates/components.jsx — load that first so
// Icon, Button, Card, Badge, Avatar etc. are available on the
// global scope.
//
// All visual properties read from CSS variables so any theme
// (atlas / minimal / dark / glass / editorial) restyles them.
//
// Components:
//   PhotoSlot, ListingCard, ListingCardHorizontal,
//   RatingStars, ReviewCard, RatingsSummary,
//   PriceTag, PriceBreakdown,
//   HostHeader, MessageBubble, MessageThread,
//   TrustBadge, AmenityChip, FilterChips,
//   SearchBar, MiniMap, CalendarMonth, AvailabilityHeatmap,
//   CategoryRail, PhotoGallery, StatTile.
// ============================================================

// ── PhotoSlot ────────────────────────────────────────────────
// Placeholder image — stripe pattern with a label. Used wherever
// the user is expected to plug in a real photo.
const PhotoSlot = ({ label = "Photo", aspect = "4/3", tone = "warm", style, children }) => {
  const palettes = {
    warm:   ["#efe2cc", "#e3d2b3"],
    sage:   ["#e0e8d4", "#cfdcb9"],
    blush:  ["#f1dcd2", "#e6c5b8"],
    ocean:  ["#d6e0e8", "#bccada"],
    night:  ["#1f2530", "#2a3140"],
    sand:   ["#ece4d3", "#dccfb3"],
  };
  const [a, b] = palettes[tone] || palettes.warm;
  const isDark = tone === "night";
  return (
    <div style={{
      width: "100%", aspectRatio: aspect, position: "relative",
      overflow: "hidden", borderRadius: "inherit",
      background: a,
      backgroundImage: `repeating-linear-gradient(135deg, ${a} 0 14px, ${b} 14px 15px)`,
      ...style,
    }}>
      {label && (
        <span style={{
          position: "absolute", left: 10, bottom: 10,
          fontFamily: "var(--font-mono)", fontSize: 10,
          letterSpacing: "0.08em", textTransform: "uppercase",
          padding: "3px 8px",
          color: isDark ? "rgba(255,255,255,0.7)" : "rgba(26,22,18,0.55)",
          background: isDark ? "rgba(0,0,0,0.4)" : "rgba(255,255,255,0.7)",
          border: `1px solid ${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.06)"}`,
          backdropFilter: "blur(4px)",
        }}>{label}</span>
      )}
      {children}
    </div>
  );
};

// ── RatingStars ─────────────────────────────────────────────
// `value` 0–5 (fractional ok). `size` and `gap` adjust scale.
const RatingStars = ({ value = 4.5, max = 5, size = 14, showValue = true, style }) => {
  const stars = [];
  for (let i = 0; i < max; i++) {
    const fill = Math.max(0, Math.min(1, value - i));
    stars.push(
      <span key={i} style={{
        position: "relative", display: "inline-block", width: size, height: size,
      }}>
        {/* Empty */}
        <svg width={size} height={size} viewBox="0 0 24 24" fill="var(--rating-empty)"
          style={{ position: "absolute", inset: 0 }} aria-hidden="true">
          <path d="m12 3 2.6 6.2 6.7.5-5.1 4.4 1.6 6.6L12 17.3 6.2 20.7l1.6-6.6L2.7 9.7l6.7-.5z"/>
        </svg>
        {/* Filled overlay clipped to fill */}
        {fill > 0 && (
          <span style={{
            position: "absolute", inset: 0, width: `${fill * 100}%`,
            overflow: "hidden",
          }}>
            <svg width={size} height={size} viewBox="0 0 24 24" fill="var(--rating)" aria-hidden="true">
              <path d="m12 3 2.6 6.2 6.7.5-5.1 4.4 1.6 6.6L12 17.3 6.2 20.7l1.6-6.6L2.7 9.7l6.7-.5z"/>
            </svg>
          </span>
        )}
      </span>
    );
  }
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 6, ...style,
    }}>
      <span style={{ display: "inline-flex", gap: 1 }}>{stars}</span>
      {showValue && <span style={{
        fontSize: "var(--text-sm)", color: "var(--text)",
        fontWeight: "var(--weight-medium)", fontVariantNumeric: "tabular-nums",
      }}>{value.toFixed(2)}</span>}
    </span>
  );
};

// ── TrustBadge ───────────────────────────────────────────────
const TrustBadge = ({ kind = "verified", label, style }) => {
  const kinds = {
    verified:    { icon: "shield",   txt: "Verified host",  tone: "info"    },
    superhost:   { icon: "star",     txt: "Superhost",       tone: "warn"   },
    "top-rated": { icon: "star",     txt: "Top rated",       tone: "accent" },
    instant:     { icon: "bolt",     txt: "Instant book",    tone: "success"},
    new:         { icon: "spark",    txt: "New on Atlas",    tone: "info"   },
  };
  const k = kinds[kind] || kinds.verified;
  return (
    <Badge tone={k.tone} leadingIcon={<Icon name={k.icon} size={11} stroke={2}/>} style={style}>
      {label || k.txt}
    </Badge>
  );
};

// ── PriceTag ─────────────────────────────────────────────────
// Inline pricing display. `period` like "/ night", "/ project".
// `original` shows a strikethrough above for discounts.
const PriceTag = ({ amount, currency = "$", period, original, size = "md", emphasis = true, style }) => {
  const sizing = {
    sm: { num: 14, lbl: 11 },
    md: { num: 20, lbl: 12 },
    lg: { num: 28, lbl: 13 },
    xl: { num: 36, lbl: 14 },
  }[size];
  return (
    <span style={{
      display: "inline-flex", alignItems: "baseline", gap: 6,
      color: emphasis ? "var(--text)" : "var(--text-2)",
      ...style,
    }}>
      {original && (
        <span style={{
          fontSize: sizing.lbl, color: "var(--text-3)",
          textDecoration: "line-through", marginRight: 2,
        }}>{currency}{original}</span>
      )}
      <span style={{
        fontSize: sizing.num, fontWeight: "var(--weight-semibold)",
        letterSpacing: "-0.01em", fontVariantNumeric: "tabular-nums",
      }}>{currency}{amount}</span>
      {period && <span style={{ fontSize: sizing.lbl, color: "var(--text-2)" }}>{period}</span>}
    </span>
  );
};

// ── ListingCard (vertical) ───────────────────────────────────
// Standard photo-on-top card.
//   listing.photo   — { label, tone }  (placeholder hints)
//   listing.title   — string
//   listing.subtitle— string (location, host, dates etc.)
//   listing.price   — { amount, period }
//   listing.rating  — number
//   listing.reviews — number
//   listing.badges  — array of TrustBadge kinds
//   listing.tags    — short strings for top-left chips
const ListingCard = ({ listing = {}, onClick, style }) => {
  const { photo = {}, title, subtitle, price = {}, rating, reviews, badges = [], tags = [], favorite } = listing;
  return (
    <div onClick={onClick} style={{
      cursor: onClick ? "pointer" : "default",
      display: "flex", flexDirection: "column", gap: 12,
      ...style,
    }}>
      <div style={{
        position: "relative", borderRadius: "var(--listing-radius)",
        overflow: "hidden", boxShadow: "var(--listing-shadow)",
      }}>
        <PhotoSlot label={photo.label || title} tone={photo.tone} aspect="4/3"/>
        {/* Top-left tags */}
        {tags.length > 0 && (
          <div style={{
            position: "absolute", top: 12, left: 12,
            display: "flex", gap: 6, flexWrap: "wrap",
          }}>
            {tags.map(t => (
              <span key={t} style={{
                padding: "3px 9px", borderRadius: 999,
                background: "rgba(255,255,255,0.9)",
                color: "var(--text)", fontSize: 11,
                fontWeight: "var(--weight-medium)",
                backdropFilter: "blur(6px)",
              }}>{t}</span>
            ))}
          </div>
        )}
        {/* Favorite */}
        <button aria-label="Favorite" style={{
          position: "absolute", top: 10, right: 10,
          width: 32, height: 32, borderRadius: "50%",
          background: "rgba(255,255,255,0.95)", border: "none",
          color: favorite ? "var(--accent)" : "var(--text)",
          cursor: "pointer", display: "flex",
          alignItems: "center", justifyContent: "center",
          backdropFilter: "blur(6px)",
        }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill={favorite ? "currentColor" : "none"}
            stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
            <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
          </svg>
        </button>
      </div>

      <div>
        <div style={{
          display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 10,
        }}>
          <h3 style={{
            margin: 0, fontSize: "var(--text-md)",
            fontWeight: "var(--weight-semibold)",
            color: "var(--text)", letterSpacing: "-0.01em",
            whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
            flex: 1, minWidth: 0,
          }}>{title}</h3>
          {rating != null && (
            <span style={{
              display: "inline-flex", alignItems: "center", gap: 4,
              fontSize: "var(--text-sm)", color: "var(--text)",
              fontWeight: "var(--weight-medium)",
            }}>
              <svg width="13" height="13" viewBox="0 0 24 24" fill="var(--rating)" aria-hidden="true">
                <path d="m12 3 2.6 6.2 6.7.5-5.1 4.4 1.6 6.6L12 17.3 6.2 20.7l1.6-6.6L2.7 9.7l6.7-.5z"/>
              </svg>
              {rating.toFixed(2)}
            </span>
          )}
        </div>
        {subtitle && (
          <div style={{
            fontSize: "var(--text-sm)", color: "var(--text-2)", marginTop: 2,
          }}>{subtitle}</div>
        )}
        {reviews != null && (
          <div style={{ fontSize: "var(--text-sm)", color: "var(--text-3)", marginTop: 2 }}>
            {reviews} reviews
          </div>
        )}
        {badges.length > 0 && (
          <div style={{ display: "flex", gap: 6, marginTop: 8, flexWrap: "wrap" }}>
            {badges.map(b => <TrustBadge key={b} kind={b}/>)}
          </div>
        )}
        {price.amount != null && (
          <div style={{ marginTop: 10 }}>
            <PriceTag {...price} size="md"/>
          </div>
        )}
      </div>
    </div>
  );
};

// ── ListingCardHorizontal — for search results lists ─────────
const ListingCardHorizontal = ({ listing = {}, onClick, style }) => {
  const { photo = {}, title, subtitle, price = {}, rating, reviews, badges = [], description, amenities = [] } = listing;
  return (
    <div onClick={onClick} style={{
      display: "grid", gridTemplateColumns: "320px 1fr",
      gap: 20, padding: 16, borderRadius: "var(--card-radius)",
      background: "var(--surface)", border: "1px solid var(--border)",
      cursor: onClick ? "pointer" : "default",
      ...style,
    }}>
      <div style={{
        borderRadius: "var(--listing-radius)", overflow: "hidden",
        boxShadow: "var(--listing-shadow)", alignSelf: "stretch",
      }}>
        <PhotoSlot label={photo.label || title} tone={photo.tone} aspect="4/3"/>
      </div>
      <div style={{ display: "flex", flexDirection: "column", gap: 8, minWidth: 0 }}>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          {badges.map(b => <TrustBadge key={b} kind={b}/>)}
        </div>
        <h3 style={{
          margin: 0, fontSize: "var(--text-lg)",
          fontWeight: "var(--weight-semibold)", letterSpacing: "-0.01em",
          color: "var(--text)",
        }}>{title}</h3>
        {subtitle && (
          <div style={{ fontSize: "var(--text-sm)", color: "var(--text-2)" }}>{subtitle}</div>
        )}
        {description && (
          <p style={{
            margin: "4px 0 0", fontSize: "var(--text-sm)",
            color: "var(--text-2)", lineHeight: 1.5,
            display: "-webkit-box", WebkitLineClamp: 2,
            WebkitBoxOrient: "vertical", overflow: "hidden",
          }}>{description}</p>
        )}
        {amenities.length > 0 && (
          <div style={{ display: "flex", gap: 6, marginTop: 4, flexWrap: "wrap" }}>
            {amenities.slice(0, 4).map(a => <AmenityChip key={a} label={a}/>)}
          </div>
        )}
        <div style={{
          marginTop: "auto", paddingTop: 12,
          display: "flex", justifyContent: "space-between", alignItems: "flex-end",
        }}>
          <div>
            {rating != null && <RatingStars value={rating} size={12}/>}
            {reviews != null && <div style={{
              fontSize: "var(--text-xs)", color: "var(--text-3)", marginTop: 4,
            }}>{reviews} reviews</div>}
          </div>
          {price.amount != null && <PriceTag {...price} size="md"/>}
        </div>
      </div>
    </div>
  );
};

// ── AmenityChip ──────────────────────────────────────────────
const AmenityChip = ({ label, icon, style }) => (
  <span style={{
    display: "inline-flex", alignItems: "center", gap: 6,
    padding: "4px 10px", borderRadius: 999,
    background: "var(--surface-2)", color: "var(--text-2)",
    border: "1px solid var(--border)",
    fontSize: "var(--text-xs)", fontWeight: "var(--weight-medium)",
    ...style,
  }}>
    {icon && <Icon name={icon} size={11}/>}
    {label}
  </span>
);

// ── ReviewCard ───────────────────────────────────────────────
const ReviewCard = ({ author, avatarColor, rating, date, body, location, style }) => (
  <div style={{
    padding: 0, ...style,
  }}>
    <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 10 }}>
      <Avatar name={author} color={avatarColor} size={40}/>
      <div style={{ minWidth: 0, flex: 1 }}>
        <div style={{
          fontSize: "var(--text-md)", fontWeight: "var(--weight-semibold)",
          color: "var(--text)",
        }}>{author}</div>
        <div style={{ fontSize: "var(--text-xs)", color: "var(--text-3)" }}>
          {location && <>{location} · </>}{date}
        </div>
      </div>
    </div>
    {rating != null && (
      <div style={{ marginBottom: 8 }}>
        <RatingStars value={rating} size={12} showValue={false}/>
      </div>
    )}
    <p style={{
      margin: 0, fontSize: "var(--text-md)", color: "var(--text)",
      lineHeight: 1.55,
    }}>{body}</p>
  </div>
);

// ── RatingsSummary ──────────────────────────────────────────
// Header block for review section: big number + category bars.
const RatingsSummary = ({ value = 4.92, total = 184, categories = [], style }) => (
  <div style={{ ...style }}>
    <div style={{ display: "flex", alignItems: "baseline", gap: 14, marginBottom: 16 }}>
      <span style={{
        fontFamily: "var(--font-display)", fontSize: 44,
        fontWeight: "var(--weight-medium)", letterSpacing: "-0.02em",
        color: "var(--text)", lineHeight: 1,
      }}>★ {value.toFixed(2)}</span>
      <span style={{ fontSize: "var(--text-md)", color: "var(--text-2)" }}>
        · {total} reviews
      </span>
    </div>
    <div style={{ display: "grid", gridTemplateColumns: "repeat(2, 1fr)", gap: "10px 28px" }}>
      {categories.map(c => (
        <div key={c.label} style={{
          display: "flex", justifyContent: "space-between", alignItems: "center",
          gap: 14, padding: "6px 0", borderBottom: "1px solid var(--divider)",
        }}>
          <span style={{ fontSize: "var(--text-sm)", color: "var(--text)" }}>{c.label}</span>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 10 }}>
            <span style={{
              width: 120, height: 4, borderRadius: 2,
              background: "var(--surface-alt)", overflow: "hidden",
            }}>
              <span style={{
                display: "block", height: "100%", width: `${(c.value / 5) * 100}%`,
                background: "var(--text)",
              }}/>
            </span>
            <span style={{
              fontSize: "var(--text-sm)", color: "var(--text-2)",
              fontVariantNumeric: "tabular-nums",
            }}>{c.value.toFixed(1)}</span>
          </span>
        </div>
      ))}
    </div>
  </div>
);

// ── PriceBreakdown ───────────────────────────────────────────
// Receipt-style line items + total.
//   items: [{ label, amount, note?, strike? }]
//   total: number
//   currency
const PriceBreakdown = ({ items = [], total, currency = "$", style }) => (
  <div style={{ ...style }}>
    {items.map((it, i) => (
      <div key={i} style={{
        display: "flex", justifyContent: "space-between", alignItems: "baseline",
        padding: "8px 0", fontSize: "var(--text-md)",
        color: "var(--text-2)",
      }}>
        <span style={{
          textDecoration: it.note === "discount" ? "none" : "underline",
          textDecorationStyle: "dotted", textUnderlineOffset: 2,
        }}>{it.label}</span>
        <span style={{
          color: it.note === "discount" ? "var(--success)" : "var(--text)",
          fontVariantNumeric: "tabular-nums",
        }}>
          {it.note === "discount" ? "−" : ""}{currency}{it.amount}
        </span>
      </div>
    ))}
    <div style={{
      display: "flex", justifyContent: "space-between",
      paddingTop: 14, marginTop: 6,
      borderTop: "1px solid var(--border-strong)",
      fontSize: "var(--text-lg)", fontWeight: "var(--weight-semibold)",
      color: "var(--text)",
    }}>
      <span>Total</span>
      <span style={{ fontVariantNumeric: "tabular-nums" }}>{currency}{total}</span>
    </div>
  </div>
);

// ── HostHeader ───────────────────────────────────────────────
const HostHeader = ({ name, avatar, color, joined, location, languages = [], blurb, kpis = [], style }) => (
  <div style={{
    display: "grid", gridTemplateColumns: "auto 1fr", gap: 20,
    ...style,
  }}>
    <div style={{
      display: "flex", flexDirection: "column", alignItems: "center",
      gap: 12, padding: 22,
      background: "var(--surface)", border: "1px solid var(--border)",
      borderRadius: "var(--card-radius)", boxShadow: "var(--shadow-sm)",
      minWidth: 200,
    }}>
      <Avatar name={name} color={color} size={80}/>
      <div style={{ textAlign: "center" }}>
        <div style={{
          fontFamily: "var(--font-display)", fontSize: "var(--text-xl)",
          fontWeight: "var(--weight-semibold)", color: "var(--text)",
        }}>{name}</div>
        <div style={{ fontSize: "var(--text-sm)", color: "var(--text-3)", marginTop: 2 }}>Host</div>
      </div>
      <div style={{ display: "flex", gap: 6, flexWrap: "wrap", justifyContent: "center" }}>
        <TrustBadge kind="superhost"/>
      </div>
    </div>
    <div style={{ display: "flex", flexDirection: "column", justifyContent: "center", minWidth: 0 }}>
      <div style={{
        display: "grid", gridTemplateColumns: `repeat(${kpis.length}, auto)`,
        gap: 26, marginBottom: 14,
      }}>
        {kpis.map(k => (
          <div key={k.label}>
            <div style={{
              fontSize: "var(--text-xl)", fontWeight: "var(--weight-semibold)",
              color: "var(--text)", fontFamily: "var(--font-display)",
            }}>{k.value}</div>
            <div style={{ fontSize: "var(--text-xs)", color: "var(--text-3)",
              textTransform: "uppercase", letterSpacing: "0.06em", marginTop: 2,
            }}>{k.label}</div>
          </div>
        ))}
      </div>
      {(joined || location) && (
        <div style={{ fontSize: "var(--text-sm)", color: "var(--text-2)", marginBottom: 6 }}>
          {location && <>Lives in {location}{joined ? " · " : ""}</>}
          {joined && <>Hosting since {joined}</>}
        </div>
      )}
      {languages.length > 0 && (
        <div style={{ fontSize: "var(--text-sm)", color: "var(--text-2)", marginBottom: 10 }}>
          Speaks {languages.join(", ")}
        </div>
      )}
      {blurb && (
        <p style={{
          margin: 0, fontSize: "var(--text-md)", color: "var(--text)",
          lineHeight: 1.55, maxWidth: 580,
        }}>{blurb}</p>
      )}
    </div>
  </div>
);

// ── MessageBubble ────────────────────────────────────────────
const MessageBubble = ({ mine, name, body, time, avatar, color, attachment, style }) => (
  <div style={{
    display: "flex", flexDirection: mine ? "row-reverse" : "row",
    gap: 10, alignItems: "flex-end", marginBottom: 12, ...style,
  }}>
    {!mine && <Avatar name={name || "?"} color={color} size={28}/>}
    <div style={{ maxWidth: "62%" }}>
      <div style={{
        padding: "10px 14px", borderRadius: 16,
        background: mine ? "var(--text)" : "var(--surface-2)",
        color: mine ? "var(--bg)" : "var(--text)",
        fontSize: "var(--text-md)", lineHeight: 1.45,
        borderBottomRightRadius: mine ? 4 : 16,
        borderBottomLeftRadius: mine ? 16 : 4,
        boxShadow: "var(--shadow-sm)",
      }}>{body}</div>
      {attachment && (
        <div style={{
          marginTop: 6, padding: "8px 12px",
          background: "var(--surface)", border: "1px solid var(--border)",
          borderRadius: 12, fontSize: "var(--text-sm)", color: "var(--text)",
          display: "inline-flex", alignItems: "center", gap: 8,
        }}>
          <Icon name="doc" size={14}/>
          {attachment}
        </div>
      )}
      {time && <div style={{
        fontSize: "var(--text-xs)", color: "var(--text-3)",
        marginTop: 4, textAlign: mine ? "right" : "left",
      }}>{time}</div>}
    </div>
  </div>
);

// ── SearchBar — destination + dates + guests ────────────────
const SearchBar = ({ destination, dates, guests, compact, onSearch, style }) => {
  const segPad = compact ? "10px 18px" : "14px 22px";
  const Segment = ({ label, value, placeholder, last }) => (
    <div style={{
      flex: 1, padding: segPad, minWidth: 0,
      borderRight: last ? "none" : "1px solid var(--border)",
      cursor: "pointer",
    }}>
      <div style={{
        fontSize: "var(--text-xs)", fontWeight: "var(--weight-semibold)",
        color: "var(--text)", letterSpacing: "0.02em",
      }}>{label}</div>
      <div style={{
        fontSize: compact ? "var(--text-sm)" : "var(--text-md)",
        color: value ? "var(--text)" : "var(--text-3)",
        marginTop: 2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
      }}>{value || placeholder}</div>
    </div>
  );
  return (
    <div style={{
      display: "flex", alignItems: "center",
      background: "var(--surface)", border: "1px solid var(--border)",
      borderRadius: 999, boxShadow: "var(--shadow)",
      padding: 4, ...style,
    }}>
      <Segment label="Where"     value={destination} placeholder="Search destinations"/>
      <Segment label="Check in"  value={dates?.in}   placeholder="Add dates"/>
      <Segment label="Check out" value={dates?.out}  placeholder="Add dates"/>
      <Segment label="Who"       value={guests}      placeholder="Add guests" last/>
      <button onClick={onSearch} aria-label="Search" style={{
        width: compact ? 38 : 50, height: compact ? 38 : 50,
        borderRadius: 999, border: "none",
        background: "var(--accent)", color: "var(--text-on-accent)",
        cursor: "pointer", display: "flex", alignItems: "center",
        justifyContent: "center", marginLeft: 6,
      }}>
        <Icon name="search" size={compact ? 15 : 18} stroke={2.2}/>
      </button>
    </div>
  );
};

// ── CategoryRail — horizontal scrolling category chips ───────
const CategoryRail = ({ categories = [], active, onChange = () => {}, style }) => (
  <div style={{
    display: "flex", gap: 28, overflowX: "auto", padding: "10px 0",
    ...style,
  }}>
    {categories.map(c => {
      const sel = c.id === active || c.label === active;
      return (
        <button key={c.id || c.label} onClick={() => onChange(c.id || c.label)} style={{
          display: "flex", flexDirection: "column", alignItems: "center",
          gap: 6, padding: "4px 2px", background: "transparent",
          border: "none", cursor: "pointer", color: sel ? "var(--text)" : "var(--text-2)",
          borderBottom: sel ? "2px solid var(--text)" : "2px solid transparent",
          fontFamily: "var(--font-sans)", flexShrink: 0,
          opacity: sel ? 1 : 0.7,
        }}>
          <span style={{ fontSize: 22, lineHeight: 1 }}>{c.emoji || "○"}</span>
          <span style={{ fontSize: "var(--text-xs)", fontWeight: 500, whiteSpace: "nowrap" }}>{c.label}</span>
        </button>
      );
    })}
  </div>
);

// ── FilterChips — horizontal chip strip ──────────────────────
const FilterChips = ({ filters = [], onClear, style }) => (
  <div style={{
    display: "flex", gap: 8, flexWrap: "wrap",
    alignItems: "center", ...style,
  }}>
    {filters.map((f, i) => (
      <span key={i} style={{
        display: "inline-flex", alignItems: "center", gap: 6,
        padding: "6px 12px", borderRadius: 999,
        background: f.active ? "var(--text)" : "var(--surface)",
        color: f.active ? "var(--bg)" : "var(--text)",
        border: `1px solid ${f.active ? "var(--text)" : "var(--border)"}`,
        fontSize: "var(--text-sm)", fontWeight: "var(--weight-medium)",
        cursor: "pointer",
      }}>
        {f.label}
        {f.count != null && <span style={{
          fontSize: 10, padding: "1px 6px", borderRadius: 999,
          background: f.active ? "rgba(255,255,255,0.18)" : "var(--surface-alt)",
          color: "inherit",
        }}>{f.count}</span>}
      </span>
    ))}
    {onClear && (
      <button onClick={onClear} style={{
        marginLeft: 4, padding: "6px 12px", borderRadius: 999,
        background: "transparent", border: "none", cursor: "pointer",
        fontSize: "var(--text-sm)", color: "var(--text-2)",
        textDecoration: "underline",
      }}>Clear all</button>
    )}
  </div>
);

// ── MiniMap — schematic map with pins ────────────────────────
// Not a real map — a stylized SVG you can drop in for layout.
// Pins: [{ x: 0-100, y: 0-100, price, active }]
const MiniMap = ({ pins = [], style }) => (
  <div style={{
    position: "relative", width: "100%", height: "100%",
    borderRadius: "var(--card-radius)", overflow: "hidden",
    background: "var(--surface-2)", border: "1px solid var(--border)",
    ...style,
  }}>
    {/* Map illustration via SVG */}
    <svg width="100%" height="100%" viewBox="0 0 600 600" preserveAspectRatio="xMidYMid slice"
      style={{ position: "absolute", inset: 0 }}>
      <defs>
        <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
          <path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--border)" strokeWidth="0.5"/>
        </pattern>
      </defs>
      <rect width="600" height="600" fill="var(--surface-2)"/>
      <rect width="600" height="600" fill="url(#grid)" opacity="0.6"/>
      {/* Faux water */}
      <path d="M0,380 Q100,360 220,400 T440,420 T600,400 L600,600 L0,600 Z"
        fill="var(--accent-soft)" opacity="0.7"/>
      {/* Faux roads */}
      <path d="M0,200 Q200,210 380,180 T600,220"
        fill="none" stroke="var(--text-3)" strokeWidth="2" strokeDasharray="6 4" opacity="0.3"/>
      <path d="M280,0 L260,600" fill="none" stroke="var(--text-3)"
        strokeWidth="2" strokeDasharray="6 4" opacity="0.3"/>
      {/* Faux blocks */}
      {[[80,80,90,70],[200,60,80,100],[340,90,110,80],[470,70,80,90],
        [60,250,80,90],[180,260,110,80],[330,260,90,70],[460,260,80,90]].map(([x,y,w,h], i) => (
        <rect key={i} x={x} y={y} width={w} height={h} rx="4"
          fill="var(--surface)" stroke="var(--border)" strokeWidth="1"/>
      ))}
    </svg>
    {/* Pins */}
    {pins.map((p, i) => (
      <div key={i} style={{
        position: "absolute", left: `${p.x}%`, top: `${p.y}%`,
        transform: "translate(-50%, -100%)",
        padding: "4px 10px", borderRadius: 999,
        background: p.active ? "var(--text)" : "var(--surface)",
        color: p.active ? "var(--bg)" : "var(--text)",
        border: `1.5px solid ${p.active ? "var(--text)" : "var(--border-strong)"}`,
        fontSize: "var(--text-xs)",
        fontWeight: "var(--weight-semibold)",
        fontVariantNumeric: "tabular-nums",
        boxShadow: "var(--shadow)", cursor: "pointer", whiteSpace: "nowrap",
        zIndex: p.active ? 2 : 1,
      }}>${p.price}</div>
    ))}
    {/* Map controls */}
    <div style={{
      position: "absolute", right: 12, top: 12,
      display: "flex", flexDirection: "column", gap: 0,
      background: "var(--surface)", borderRadius: "var(--radius)",
      border: "1px solid var(--border)", boxShadow: "var(--shadow)",
    }}>
      <button style={{
        width: 32, height: 32, border: "none", background: "transparent",
        borderBottom: "1px solid var(--border)", cursor: "pointer",
        color: "var(--text)", fontSize: 16,
      }}>+</button>
      <button style={{
        width: 32, height: 32, border: "none", background: "transparent",
        cursor: "pointer", color: "var(--text)", fontSize: 16,
      }}>−</button>
    </div>
  </div>
);

// ── CalendarMonth — single-month booking calendar ────────────
// `start` and `end` are 1–31 day numbers within the displayed
// month. Tinted range between them, range endpoints highlighted.
const CalendarMonth = ({ month = "August 2026", firstWeekday = 6, daysInMonth = 31, start, end, today, blocked = [], style }) => {
  const days = [];
  // Blank cells before day 1
  for (let i = 0; i < firstWeekday; i++) days.push(null);
  for (let d = 1; d <= daysInMonth; d++) days.push(d);

  const inRange = (d) => start != null && end != null && d > start && d < end;
  const isEnd = (d) => d === start || d === end;
  const isBlocked = (d) => blocked.includes(d);

  return (
    <div style={{ ...style }}>
      <div style={{
        display: "flex", justifyContent: "space-between", alignItems: "center",
        marginBottom: 12,
      }}>
        <IconButton name="chevLeft" size="sm" label="Previous month"/>
        <span style={{
          fontFamily: "var(--font-display)", fontSize: "var(--text-lg)",
          fontWeight: "var(--weight-semibold)", color: "var(--text)",
        }}>{month}</span>
        <IconButton name="chevRight" size="sm" label="Next month"/>
      </div>
      <div style={{
        display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 4,
        fontSize: "var(--text-xs)", color: "var(--text-3)",
        textAlign: "center", marginBottom: 6,
        textTransform: "uppercase", letterSpacing: "0.08em",
      }}>
        {["S","M","T","W","T","F","S"].map((d, i) => <span key={i}>{d}</span>)}
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 2 }}>
        {days.map((d, i) => {
          if (d == null) return <span key={i}/>;
          const blocked = isBlocked(d);
          const range = inRange(d);
          const end = isEnd(d);
          return (
            <button key={i} disabled={blocked} style={{
              aspectRatio: "1", border: "none", cursor: blocked ? "not-allowed" : "pointer",
              borderRadius: end ? "50%" : 6,
              background: end ? "var(--text)" : range ? "var(--surface-alt)" : "transparent",
              color: end ? "var(--bg)" : blocked ? "var(--text-3)" : "var(--text)",
              fontSize: "var(--text-sm)",
              fontWeight: end ? 600 : 400,
              textDecoration: blocked ? "line-through" : "none",
              opacity: blocked ? 0.5 : 1,
              outline: d === today ? "1px solid var(--accent)" : "none",
              fontFamily: "var(--font-sans)",
            }}>{d}</button>
          );
        })}
      </div>
    </div>
  );
};

// ── AvailabilityHeatmap — for host calendar ──────────────────
// 4-week strip showing booked vs free vs blocked nights.
//   weeks: array of 7-element arrays of "open"|"booked"|"blocked"
const AvailabilityHeatmap = ({ weeks = [], style }) => {
  const colors = {
    open:    { bg: "var(--success-soft)", dot: "var(--success)" },
    booked:  { bg: "var(--accent-soft)",  dot: "var(--accent)" },
    blocked: { bg: "var(--surface-alt)",  dot: "var(--text-3)" },
  };
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 4, ...style }}>
      {weeks.map((w, i) => (
        <div key={i} style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 4 }}>
          {w.map((cell, j) => (
            <div key={j} style={{
              aspectRatio: "1", borderRadius: 4,
              background: colors[cell]?.bg || "transparent",
              border: `1px solid ${colors[cell]?.dot || "transparent"}33`,
            }}/>
          ))}
        </div>
      ))}
    </div>
  );
};

// ── PhotoGallery — main + 4 thumbs grid ──────────────────────
const PhotoGallery = ({ photos = [], style }) => {
  // photos: [{ label, tone }] up to 5
  const [main, ...rest] = photos;
  return (
    <div style={{
      display: "grid",
      gridTemplateColumns: "2fr 1fr 1fr",
      gridTemplateRows: "1fr 1fr",
      gap: 8, borderRadius: "var(--card-radius)", overflow: "hidden",
      height: 460, ...style,
    }}>
      <div style={{ gridColumn: "1 / 2", gridRow: "1 / 3" }}>
        <PhotoSlot label={main?.label || "Photo 1"} tone={main?.tone} aspect="auto" style={{ height: "100%", aspectRatio: "auto" }}/>
      </div>
      {rest.slice(0, 4).map((p, i) => (
        <PhotoSlot key={i} label={p.label || `Photo ${i + 2}`} tone={p.tone}
          aspect="auto" style={{ height: "100%", aspectRatio: "auto" }}/>
      ))}
    </div>
  );
};

// ── StatTile — small dashboard stat ──────────────────────────
const StatTile = ({ label, value, sub, trend, icon, style }) => (
  <Card padding={20} style={style}>
    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
      <span style={{
        fontSize: "var(--text-xs)", color: "var(--text-3)",
        textTransform: "uppercase", letterSpacing: "0.06em",
        fontWeight: "var(--weight-medium)",
      }}>{label}</span>
      {icon && (
        <span style={{
          width: 28, height: 28, borderRadius: 8,
          background: "var(--accent-soft)", color: "var(--accent)",
          display: "flex", alignItems: "center", justifyContent: "center",
        }}>
          <Icon name={icon} size={14}/>
        </span>
      )}
    </div>
    <div style={{
      fontFamily: "var(--font-display)",
      fontSize: 28, fontWeight: "var(--weight-semibold)",
      letterSpacing: "-0.02em", color: "var(--text)",
      marginTop: 10,
    }}>{value}</div>
    {(sub || trend) && (
      <div style={{ fontSize: "var(--text-xs)", color: "var(--text-3)", marginTop: 4 }}>
        {trend != null && (
          <span style={{ color: trend >= 0 ? "var(--success)" : "var(--danger)", marginRight: 4 }}>
            {trend >= 0 ? "↑" : "↓"} {Math.abs(trend)}%
          </span>
        )}
        {sub}
      </div>
    )}
  </Card>
);

// ─── Exports ─────────────────────────────────────────────────
Object.assign(window, {
  PhotoSlot, ListingCard, ListingCardHorizontal,
  RatingStars, ReviewCard, RatingsSummary,
  PriceTag, PriceBreakdown,
  HostHeader, MessageBubble,
  TrustBadge, AmenityChip, FilterChips,
  SearchBar, MiniMap, CalendarMonth, AvailabilityHeatmap,
  CategoryRail, PhotoGallery, StatTile,
});
