// pages.jsx — sub-page content components.
// Catalog data (PRODUCTS, COLLECTIONS) is loaded from Firestore at runtime
// by window.loadCatalog() in src/firebase.js. The empty arrays below are
// captured-by-reference so loadCatalog can mutate them in place.
window.PRODUCTS = window.PRODUCTS || [];
window.COLLECTIONS = window.COLLECTIONS || [];
window.UNRELEASED_PRODUCTS = window.UNRELEASED_PRODUCTS || [];
window.ARCHIVED_PRODUCTS = window.ARCHIVED_PRODUCTS || [];
const PRODUCTS = window.PRODUCTS;
const COLLECTIONS = window.COLLECTIONS;
const UNRELEASED_PRODUCTS = window.UNRELEASED_PRODUCTS;
const ARCHIVED_PRODUCTS = window.ARCHIVED_PRODUCTS;

// Win95 hourglass — pixel-art SVG that the .ap-hourglass CSS rule rotates.
function Hourglass({ size = 24 }) {
  return (
    <svg className="ap-hourglass" width={size} height={size}
         viewBox="0 0 16 16" shapeRendering="crispEdges"
         aria-label="loading">
      {/* outer frame */}
      <rect x="3" y="1" width="10" height="1" fill="#000" />
      <rect x="3" y="14" width="10" height="1" fill="#000" />
      {/* upper funnel walls */}
      <rect x="3" y="2" width="1" height="1" fill="#000" />
      <rect x="12" y="2" width="1" height="1" fill="#000" />
      <rect x="4" y="3" width="1" height="1" fill="#000" />
      <rect x="11" y="3" width="1" height="1" fill="#000" />
      <rect x="5" y="4" width="1" height="1" fill="#000" />
      <rect x="10" y="4" width="1" height="1" fill="#000" />
      <rect x="6" y="5" width="1" height="1" fill="#000" />
      <rect x="9" y="5" width="1" height="1" fill="#000" />
      <rect x="7" y="6" width="2" height="2" fill="#000" />
      {/* lower funnel walls */}
      <rect x="6" y="8" width="1" height="1" fill="#000" />
      <rect x="9" y="8" width="1" height="1" fill="#000" />
      <rect x="5" y="9" width="1" height="1" fill="#000" />
      <rect x="10" y="9" width="1" height="1" fill="#000" />
      <rect x="4" y="10" width="1" height="1" fill="#000" />
      <rect x="11" y="10" width="1" height="1" fill="#000" />
      <rect x="3" y="11" width="1" height="3" fill="#000" />
      <rect x="12" y="11" width="1" height="3" fill="#000" />
      {/* sand — top half full */}
      <rect x="4" y="2" width="8" height="1" fill="#ffd84d" />
      <rect x="5" y="3" width="6" height="1" fill="#ffd84d" />
      <rect x="6" y="4" width="4" height="1" fill="#ffd84d" />
      <rect x="7" y="5" width="2" height="1" fill="#ffd84d" />
      {/* sand — small pile bottom */}
      <rect x="6" y="13" width="4" height="1" fill="#ffd84d" />
    </svg>
  );
}

// <img> wrapper that shows a Win95 hourglass overlay until the image loads.
// Falls back to the supplied `fallback` (or nothing) on error.
function ImgWithLoader({ src, alt, loaderSize = 24, fallback = null, style, onError, ...rest }) {
  const [loaded, setLoaded] = React.useState(false);
  const [errored, setErrored] = React.useState(false);
  React.useEffect(() => { setLoaded(false); setErrored(false); }, [src]);
  if (errored && fallback) return fallback;
  return (
    <>
      {!loaded && !errored && (
        <div className="ap-img-loader"><Hourglass size={loaderSize} /></div>
      )}
      <img src={src} alt={alt} loading="lazy"
           onLoad={() => setLoaded(true)}
           onError={(e) => { setErrored(true); onError && onError(e); }}
           style={{
             width: '100%', height: '100%', objectFit: 'cover',
             opacity: loaded ? 1 : 0,
             transition: 'opacity 0.15s linear',
             ...style,
           }}
           {...rest} />
    </>
  );
}

function ProductThumb({ product }) {
  const [errored, setErrored] = React.useState(false);
  const showImage = product.image && !errored;
  return (
    <div className="thumb" style={{
      background: showImage ? '#fff' : 'linear-gradient(135deg, #ff7ad9, #6cf2ff)',
      position: 'relative', overflow: 'hidden',
    }}>
      {showImage ? (
        <ImgWithLoader src={product.image} alt={product.name} loaderSize={28}
             onError={() => setErrored(true)}
             fallback={<ProductSilhouette kind={product.kind} />} />
      ) : (
        <ProductSilhouette kind={product.kind} />
      )}
      <div style={{
        position: 'absolute', bottom: 2, right: 2, fontSize: 8,
        background: '#000', color: '#fff', padding: '1px 3px', fontFamily: 'monospace',
      }}>
        IMG_{(product.id || '').slice(-3).toUpperCase() || '001'}
      </div>
    </div>
  );
}

function ProductSilhouette({ kind, color = '#000' }) {
  if (kind === 'shoe') return (
    <svg viewBox="0 0 100 100" width="70%" style={{ filter: 'drop-shadow(2px 2px 0 rgba(0,0,0,0.3))' }}>
      <path d="M10 70 L25 55 L40 60 L60 50 L80 55 L90 65 L90 80 L10 80 Z"
            fill={color} stroke="#000" strokeWidth="1"/>
      <path d="M30 60 L50 55 L65 58" stroke="#fff" strokeWidth="2" fill="none" opacity="0.5"/>
    </svg>
  );
  if (kind === 'pants') return (
    <svg viewBox="0 0 100 100" width="70%" style={{ filter: 'drop-shadow(2px 2px 0 rgba(0,0,0,0.3))' }}>
      <path d="M30 15 L70 15 L72 50 L65 90 L52 90 L50 50 L48 90 L35 90 L28 50 Z"
            fill={color} stroke="#000" strokeWidth="1"/>
    </svg>
  );
  if (kind === 'hoodie') return (
    <svg viewBox="0 0 100 100" width="70%" style={{ filter: 'drop-shadow(2px 2px 0 rgba(0,0,0,0.3))' }}>
      <path d="M30 25 Q30 15 50 12 Q70 15 70 25 L85 35 L80 50 L70 45 L70 90 L30 90 L30 45 L20 50 L15 35 Z"
            fill={color} stroke="#000" strokeWidth="1"/>
      <path d="M40 22 Q50 18 60 22 L58 35 L42 35 Z" fill="#fff" opacity="0.4"/>
    </svg>
  );
  // tee default
  return (
    <svg viewBox="0 0 100 100" width="70%" style={{ filter: 'drop-shadow(2px 2px 0 rgba(0,0,0,0.3))' }}>
      <path d="M22 28 L36 16 L42 22 L58 22 L64 16 L78 28 L72 42 L62 36 L62 90 L38 90 L38 36 L28 42 Z"
            fill={color} stroke="#000" strokeWidth="1"/>
    </svg>
  );
}

function HeartButton({ active, onClick, size = 18, style }) {
  return (
    <button onClick={(e) => { e.stopPropagation(); onClick && onClick(); }}
      title={active ? 'Remove from wishlist' : 'Save to wishlist'}
      aria-label={active ? 'Remove from wishlist' : 'Save to wishlist'}
      aria-pressed={!!active}
      className="ap-wishlist-heart"
      style={{
        width: size + 6, height: size + 6, padding: 0,
        background: active ? '#ff3eb5' : 'rgba(255,255,255,0.85)',
        color: active ? '#fff' : '#888',
        border: '1px solid #000', cursor: 'pointer',
        fontSize: size - 2, lineHeight: 1, fontFamily: 'inherit',
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        ...(style || {}),
      }}>
      ♥
    </button>
  );
}

function ShopGridContent({ collection, onAdd, onView, products = PRODUCTS, wishlist = [], onToggleWish }) {
  const base = collection
    ? products.filter(p => (p.cols || []).includes(collection.id))
    : products;
  const [q, setQ] = React.useState('');
  const open = (p) => onView ? onView(p) : onAdd && onAdd(p);
  const query = q.trim().toLowerCase();
  const filtered = query
    ? base.filter(p => (p.name || '').toLowerCase().includes(query)
        || (p.slug || '').toLowerCase().includes(query))
    : base;

  // Track view_search_results once the user pauses typing for 600ms. Skipped
  // when the query is empty (that's just the default catalog view) or when
  // there are zero matches (the empty-state component handles that visual).
  React.useEffect(() => {
    if (!query || typeof window.apTrack !== 'function') return;
    const t = setTimeout(() => {
      window.apTrack('view_search_results', {
        search_term: query,
        results_count: filtered.length,
      });
    }, 600);
    return () => clearTimeout(t);
  }, [query, filtered.length]);
  return (
    <div>
      {collection && (
        <div style={{
          background: collection.color, padding: '4px 6px', marginBottom: 6,
          border: '1px solid #000', fontWeight: 'bold', fontSize: 11,
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        }}>
          <span>{collection.name}</span>
          <span style={{ fontSize: 9 }}>{filtered.length} items</span>
        </div>
      )}
      <div style={{ display: 'flex', gap: 4, marginBottom: 6 }}>
        <input className="w95-input" value={q}
          placeholder="🔍 search products…"
          onChange={(e) => setQ(e.target.value)}
          style={{ flex: 1, fontSize: 11 }} />
        {q && (
          <button className="w95-btn" style={{ padding: '2px 6px', fontSize: 10 }}
            onClick={() => setQ('')}>×</button>
        )}
      </div>
      {filtered.length === 0 ? (
        <div className="w95-inset" style={{ background: '#fff', padding: 16, textAlign: 'center', fontSize: 10, color: '#666' }}>
          <div style={{ fontFamily: 'monospace', fontSize: 28, lineHeight: 1, color: '#ff3eb5', marginBottom: 6 }}>(╯°□°)╯</div>
          <div style={{ fontWeight: 'bold', fontSize: 11, color: '#000', marginBottom: 4 }}>no signal for "{q}"</div>
          <div style={{ marginBottom: 8 }}>try a different frequency</div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, justifyContent: 'center' }}>
            {['tee', 'hoodie', 'vapor', 'art', 'music'].map(tag => (
              <button key={tag} className="w95-btn" onClick={() => setQ(tag)}
                style={{ fontSize: 9, padding: '2px 6px' }}>#{tag}</button>
            ))}
          </div>
        </div>
      ) : (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
          {filtered.map(p => {
            const wished = wishlist.includes(p.slug);
            return (
              <div key={p.id} className="product-card" onClick={() => open(p)} style={{ position: 'relative' }}>
                <ProductThumb product={p} />
                {onToggleWish && (
                  <div style={{ position: 'absolute', top: 4, right: 4, zIndex: 2 }}>
                    <HeartButton active={wished} onClick={() => onToggleWish(p.slug)} size={16} />
                  </div>
                )}
                {(() => {
                  const s = stockInfo(p);
                  if (s.level === 'normal') return null;
                  return (
                    <div style={{ position: 'absolute', top: 4, left: 4, zIndex: 2 }}>
                      <StockBadge product={p} compact />
                    </div>
                  );
                })()}
                <div className="name">{p.name}</div>
                {(() => {
                  const r = reviewsFor(p);
                  if (!r.count) return null;
                  return (
                    <div style={{ marginBottom: 2 }}>
                      <Stars rating={r.rating} size={9} showCount count={r.count} />
                    </div>
                  );
                })()}
                {(() => {
                  const u = upsellInfo(p);
                  if (!u) return <div className="price">₹{p.price}</div>;
                  return (
                    <div className="price" style={{ display: 'flex', alignItems: 'baseline', gap: 4, flexWrap: 'wrap' }}>
                      <span>₹{u.sale}</span>
                      <span style={{ fontSize: 9, color: '#888', textDecoration: 'line-through', fontWeight: 'normal' }}>
                        ₹{u.original}
                      </span>
                      <span style={{
                        fontSize: 8, fontWeight: 'bold', color: '#fff',
                        background: '#0a8a0a', padding: '0 3px',
                        border: '1px solid #064c06',
                      }}>
                        −{u.percent}%
                      </span>
                    </div>
                  );
                })()}
                {stockInfo(p).level === 'restock' ? (
                  <button className="w95-btn" disabled
                    style={{ minWidth: 0, padding: '2px 4px', fontSize: 10, opacity: 0.6 }}>
                    sold out
                  </button>
                ) : (
                  <button className="w95-btn" style={{ minWidth: 0, padding: '2px 4px', fontSize: 10 }}
                          onClick={(e) => { e.stopPropagation(); onAdd && onAdd(p); }}>
                    Add to cart ★
                  </button>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// Deterministic upsell — flags ~35% of products as "on sale" by computing
// a fake higher MSRP. Same product always gets the same result so the
// price doesn't flicker on re-render. Hash is just slug → uint32.
function upsellInfo(product) {
  if (!product || !product.slug || !product.price) return null;
  let h = 2166136261;
  for (let i = 0; i < product.slug.length; i++) {
    h ^= product.slug.charCodeAt(i);
    h = (h * 16777619) >>> 0;
  }
  if ((h % 100) >= 35) return null;
  // Pick a discount in [25, 50]% — what we *show* the customer they're saving.
  const percent = 25 + ((h >>> 8) % 26);
  const sale = product.price;
  const original = Math.round(sale / (1 - percent / 100) / 10) * 10 - 1;
  return { original, sale, percent };
}
window.upsellInfo = upsellInfo;

// Deterministic stock level derived from the slug hash. We don't have real
// inventory yet — once `products/{slug}.stock` exists in Firestore the
// runtime will read that instead. Until then this gives consistent-looking
// scarcity signals so urgency copy ("only 2 left") matches across visits.
//
// Distribution (rough): 12% low (1-3 left), 8% restocking (sold out), 80% in
// stock. The bias toward "in stock" keeps the shop browsable without the
// page screaming scarcity at every product.
function stockInfo(product) {
  if (!product || !product.slug) return { level: 'normal' };
  // If the product carries a real `stock` field, use it.
  if (typeof product.stock === 'number') {
    if (product.stock <= 0) return { level: 'restock' };
    if (product.stock <= 3) return { level: 'low', count: product.stock };
    return { level: 'normal', count: product.stock };
  }
  let h = 0;
  const s = product.slug;
  for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0;
  const r = h % 100;
  if (r < 8) return { level: 'restock' };
  if (r < 20) return { level: 'low', count: 1 + ((h >>> 7) % 3) }; // 1, 2, or 3
  return { level: 'normal' };
}
window.stockInfo = stockInfo;

// Deterministic review pseudo-data from slug hash. Until real reviews land
// in Firestore, this gives every product a stable rating + a few snippets.
// Once `productDetails/{slug}.reviews` exists, the runtime will prefer it.
const REVIEW_SNIPPETS = [
  ['fits like a prophecy', 5, 'Devansh, DEL'],
  ['quality is unreal', 5, 'Riya, BLR'],
  ['exactly as pictured', 4, 'Aanya, BOM'],
  ['runs slightly oversized — TTS works', 5, 'Karan, PUN'],
  ['saved for the next drop', 4, 'Meher, GGN'],
  ['print is crisp, fabric is heavy', 5, 'Tanvi, MAA'],
  ['shipped fast, fits perfect', 5, 'Aryan, HYD'],
  ['cosmic vibes ★★★', 5, 'Ishita, CCU'],
  ['size up if you want it baggy', 4, 'Rohan, BLR'],
  ['10/10 would cop again', 5, 'Saanvi, DEL'],
];
function reviewsFor(product) {
  if (!product || !product.slug) return { rating: 0, count: 0, items: [] };
  // Use real reviews from PRODUCT_DETAILS if present.
  const details = (typeof PRODUCT_DETAILS !== 'undefined' && PRODUCT_DETAILS[product.slug]) || {};
  if (Array.isArray(details.reviews) && details.reviews.length) {
    const items = details.reviews;
    const sum = items.reduce((a, b) => a + (b.stars || 5), 0);
    return { rating: sum / items.length, count: items.length, items };
  }
  let h = 0;
  const s = product.slug;
  for (let i = 0; i < s.length; i++) h = (h * 33 + s.charCodeAt(i)) >>> 0;
  const count = 3 + (h % 4); // 3-6 reviews
  const items = [];
  for (let i = 0; i < count; i++) {
    const idx = (h + i * 7) % REVIEW_SNIPPETS.length;
    const [text, stars, by] = REVIEW_SNIPPETS[idx];
    items.push({ text, stars, by });
  }
  const totalCount = 12 + (h % 200); // public count is bigger than visible items
  const sum = items.reduce((a, b) => a + b.stars, 0);
  return { rating: sum / items.length, count: totalCount, items };
}
window.reviewsFor = reviewsFor;

// Stars renderer — 5-star row, gold filled / grey unfilled, optional count.
function Stars({ rating = 0, size = 10, showCount = false, count = 0 }) {
  const r = Math.round(rating * 2) / 2; // half-star precision
  const stars = [];
  for (let i = 1; i <= 5; i++) {
    const filled = i <= r;
    const half = !filled && i - 0.5 === r;
    stars.push(
      <span key={i} style={{
        color: filled || half ? '#ffb000' : '#bbb',
        fontSize: size, lineHeight: 1, textShadow: filled ? '0 0 1px #000' : 'none',
      }}>{half ? '✬' : '★'}</span>
    );
  }
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 2 }}>
      {stars}
      {showCount && (
        <span style={{ fontSize: Math.max(8, size - 1), color: '#666', marginLeft: 2 }}>
          ({count})
        </span>
      )}
    </span>
  );
}
window.Stars = Stars;

// Stock badge component — small inline pill with colour per level.
function StockBadge({ product, compact = false }) {
  const info = stockInfo(product);
  if (info.level === 'normal') return null;
  const isLow = info.level === 'low';
  const text = isLow
    ? (compact ? `${info.count} left` : `★ only ${info.count} left ★`)
    : (compact ? 'sold out' : '◯ restocking soon');
  return (
    <span style={{
      display: 'inline-block', padding: '1px 5px',
      fontSize: 9, fontWeight: 'bold',
      background: isLow ? '#ffe14d' : '#888',
      color: isLow ? '#000' : '#fff',
      border: '1px solid #000',
    }}>{text}</span>
  );
}
window.StockBadge = StockBadge;

function ProductDetailContent({ product, onAdd, onBack, wishlist = [], onToggleWish, onView }) {
  const wished = wishlist.includes(product.slug);
  const details = (typeof PRODUCT_DETAILS !== 'undefined' && PRODUCT_DETAILS[product.slug]) || {};
  const gallery = (details.gallery && details.gallery.length) ? details.gallery : (product.image ? [product.image] : []);
  const options = details.options || [];
  const [imgIdx, setImgIdx] = React.useState(0);
  const [selected, setSelected] = React.useState(() =>
    Object.fromEntries(options.map(o => [o.name, (o.choices && o.choices[0] && o.choices[0].label) || '']))
  );
  const [qty, setQty] = React.useState(1);
  const [tab, setTab] = React.useState('info');

  const main = gallery[imgIdx] || '';
  const upsell = upsellInfo(product);

  return (
    <div style={{ fontSize: 11, padding: 4 }}>
      {onBack && (
        <button className="w95-btn" onClick={onBack}
                style={{ marginBottom: 6, padding: '2px 6px', fontSize: 10 }}>
          ← Back to shop
        </button>
      )}

      <div style={{
        background: '#fff', border: '1px solid #888', aspectRatio: '1/1',
        display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden',
        marginBottom: 4, position: 'relative',
      }}>
        {main ? (
          <ImgWithLoader src={main} alt={product.name} loaderSize={40}
               fallback={<ProductSilhouette kind={product.kind} />} />
        ) : (
          <ProductSilhouette kind={product.kind} />
        )}
      </div>

      {gallery.length > 1 && (
        <div style={{ display: 'flex', gap: 4, marginBottom: 6, overflowX: 'auto' }}>
          {gallery.map((g, i) => (
            <button key={i} onClick={() => setImgIdx(i)} style={{
              width: 44, height: 44, padding: 0, flexShrink: 0,
              background: '#fff', cursor: 'pointer', position: 'relative', overflow: 'hidden',
              border: '2px solid', borderColor: i === imgIdx ? '#ff3eb5' : '#888',
            }}>
              <ImgWithLoader src={g} alt={`${product.name} – view ${i + 1}`} loaderSize={16} />
            </button>
          ))}
        </div>
      )}

      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 6 }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 'bold', fontSize: 13, lineHeight: 1.2, marginBottom: 2 }}>
            {product.name}
          </div>
          {(() => {
            const r = reviewsFor(product);
            if (!r.count) return null;
            return (
              <button onClick={() => setTab('reviews')}
                style={{
                  display: 'inline-flex', alignItems: 'center', gap: 4,
                  background: 'transparent', border: 0, cursor: 'pointer',
                  padding: 0, marginBottom: 4, color: '#444',
                }}>
                <Stars rating={r.rating} size={11} />
                <span style={{ fontSize: 9, color: '#666', textDecoration: 'underline' }}>
                  {r.rating.toFixed(1)} · {r.count} reviews
                </span>
              </button>
            );
          })()}
          {(() => {
            const s = stockInfo(product);
            if (s.level === 'normal') return null;
            return <div style={{ marginBottom: 4 }}><StockBadge product={product} /></div>;
          })()}
          {upsell ? (
            <div style={{ marginBottom: 6 }}>
              <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, flexWrap: 'wrap' }}>
                <div style={{ fontSize: 16, color: '#ff3eb5', fontWeight: 'bold' }}>
                  ₹{upsell.sale}
                </div>
                <div style={{ fontSize: 11, color: '#888', textDecoration: 'line-through' }}>
                  ₹{upsell.original}
                </div>
                <div style={{
                  fontSize: 9, fontWeight: 'bold', color: '#fff',
                  background: '#0a8a0a', padding: '1px 5px',
                  border: '1px solid #064c06',
                }}>
                  −{upsell.percent}% OFF
                </div>
              </div>
              <div style={{ fontSize: 9, color: '#0a8a0a', fontWeight: 'bold', marginTop: 2 }}>
                ★ you save ₹{upsell.original - upsell.sale} ★
              </div>
            </div>
          ) : (
            <div style={{ fontSize: 16, color: '#ff3eb5', fontWeight: 'bold', marginBottom: 6 }}>
              ₹{product.price}
            </div>
          )}
        </div>
        {onToggleWish && (
          <HeartButton active={wished} onClick={() => onToggleWish(product.slug)} size={20} />
        )}
      </div>

      {details.desc && (
        <div style={{ fontSize: 10, lineHeight: 1.5, marginBottom: 8, color: '#222' }}>
          {details.desc}
        </div>
      )}

      {options.map(opt => (
        <div key={opt.name} style={{ marginBottom: 8 }}>
          <div style={{ fontSize: 10, fontWeight: 'bold', marginBottom: 3 }}>
            {opt.name}: <span style={{ fontWeight: 'normal', color: '#666' }}>{selected[opt.name]}</span>
          </div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
            {opt.choices.map(c => {
              const isSel = selected[opt.name] === c.label;
              if (opt.type === 'color' && c.hex) {
                return (
                  <button key={c.label}
                          onClick={() => setSelected(s => ({ ...s, [opt.name]: c.label }))}
                          title={c.label}
                          style={{
                            width: 24, height: 24, padding: 0, cursor: 'pointer',
                            background: c.hex,
                            border: '2px solid', borderColor: isSel ? '#ff3eb5' : '#888',
                          }} />
                );
              }
              return (
                <button key={c.label}
                        onClick={() => setSelected(s => ({ ...s, [opt.name]: c.label }))}
                        style={{
                          padding: '3px 8px', fontSize: 10, cursor: 'pointer',
                          background: isSel ? '#ff3eb5' : '#fff',
                          color: isSel ? '#fff' : '#000',
                          border: '1px solid #000', fontFamily: 'inherit',
                        }}>
                  {c.label}
                </button>
              );
            })}
          </div>
        </div>
      ))}

      <div style={{ display: 'flex', gap: 4, alignItems: 'center', marginBottom: 8 }}>
        <div style={{ fontSize: 10, fontWeight: 'bold' }}>QTY</div>
        <button className="w95-btn" style={{ minWidth: 0, padding: '0 6px' }}
                onClick={() => setQty(q => Math.max(1, q - 1))}>−</button>
        <div className="w95-inset" style={{ background: '#fff', padding: '2px 8px', minWidth: 18, textAlign: 'center' }}>
          {qty}
        </div>
        <button className="w95-btn" style={{ minWidth: 0, padding: '0 6px' }}
                onClick={() => setQty(q => q + 1)}>+</button>
      </div>
      {stockInfo(product).level === 'restock' ? (
        <button className="w95-btn" disabled style={{
          width: '100%', background: '#888', color: '#fff',
          fontWeight: 'bold', padding: '6px', marginBottom: 8, cursor: 'not-allowed', opacity: 0.7,
        }}>
          SOLD OUT — restocking soon
        </button>
      ) : (
        <button className="w95-btn" onClick={() => {
          const variant = Object.keys(selected).length
            ? ' (' + Object.entries(selected).map(([k,v]) => `${k}: ${v}`).join(', ') + ')'
            : '';
          for (let i = 0; i < qty; i++) onAdd && onAdd({ ...product, name: product.name + variant });
        }} style={{
          width: '100%', background: '#ff3eb5', color: '#fff',
          fontWeight: 'bold', padding: '6px', marginBottom: 8,
        }}>
          ADD TO CART · ₹{product.price * qty}
        </button>
      )}

      <div style={{ display: 'flex', gap: 0, borderBottom: '1px solid #888', marginBottom: 4 }}>
        {[['info','Info'], ['reviews','Reviews'], ['shipping','Shipping'], ['returns','Returns'], ['size','Sizes']].map(([k, l]) => (
          <button key={k} onClick={() => setTab(k)} style={{
            flex: 1, padding: '4px 2px', fontSize: 9, cursor: 'pointer',
            background: tab === k ? '#fff' : '#c0c0c0',
            border: '1px solid', borderColor: tab === k ? '#fff #888 #fff #fff' : '#fff #888 #888 #fff',
            borderBottom: tab === k ? 'none' : '1px solid #888',
            fontFamily: 'inherit', fontWeight: tab === k ? 'bold' : 'normal',
          }}>
            {l}
          </button>
        ))}
      </div>
      <div className="w95-inset" style={{ background: '#fff', padding: 6, fontSize: 10, lineHeight: 1.5 }}>
        {tab === 'info' && (typeof SHARED_INFO !== 'undefined' ? SHARED_INFO.fabric : '180GSM · 100% Cotton · Pre-Shrunk · Unisex Regular Fit')}
        {tab === 'reviews' && (() => {
          const r = reviewsFor(product);
          return (
            <div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6, paddingBottom: 4, borderBottom: '1px solid #ddd' }}>
                <div style={{ fontSize: 22, fontWeight: 'bold', color: '#ff3eb5' }}>
                  {r.rating.toFixed(1)}
                </div>
                <div>
                  <Stars rating={r.rating} size={11} />
                  <div style={{ fontSize: 9, color: '#666' }}>{r.count} verified reviews</div>
                </div>
              </div>
              {r.items.map((rev, i) => (
                <div key={i} style={{ marginBottom: 6, paddingBottom: 4, borderBottom: i < r.items.length - 1 ? '1px solid #eee' : 'none' }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 4, marginBottom: 2 }}>
                    <Stars rating={rev.stars} size={9} />
                    <span style={{ fontSize: 9, color: '#0a8a0a', fontWeight: 'bold' }}>✓ verified</span>
                  </div>
                  <div style={{ fontSize: 10, marginBottom: 2 }}>"{rev.text}"</div>
                  <div style={{ fontSize: 9, color: '#666' }}>— {rev.by}</div>
                </div>
              ))}
            </div>
          );
        })()}
        {tab === 'shipping' && (typeof SHARED_INFO !== 'undefined' ? SHARED_INFO.shipping : 'Ships in 3-5 days, delivery 7-9 days.')}
        {tab === 'returns' && (typeof SHARED_INFO !== 'undefined' ? SHARED_INFO.returns : '5-day returns. Reverse pick-up ₹100.')}
        {tab === 'size' && typeof SHARED_INFO !== 'undefined' && (
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9 }}>
            <tbody>
              {SHARED_INFO.sizeChart.map((row, ri) => (
                <tr key={ri}>
                  {row.map((cell, ci) => (
                    <td key={ci} style={{
                      border: '1px solid #888', padding: '2px 4px',
                      fontWeight: ri === 0 || ci === 0 ? 'bold' : 'normal',
                      background: ri === 0 ? '#ffd6e8' : '#fff',
                    }}>{cell}</td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </div>

      {/* "More from this collection" — pulls up to 4 sibling products from
          the first collection this product belongs to. Skips the current
          product and respects sold-out state via stockInfo. */}
      {(() => {
        const colId = (product.cols || []).find((c) => c !== 'all');
        if (!colId) return null;
        const siblings = PRODUCTS
          .filter((p) => p.slug !== product.slug && (p.cols || []).includes(colId))
          .slice(0, 4);
        if (siblings.length === 0) return null;
        const col = COLLECTIONS.find((c) => c.id === colId);
        const colName = col ? col.name : 'this collection';
        return (
          // `minWidth: 0` on the wrapper + `minmax(0, 1fr)` on the grid
          // prevents the cells' min-content (the product name with
          // `white-space: nowrap`) from blowing the column widths and
          // pushing the parent window beyond its 360px frame.
          <div style={{ marginTop: 12, minWidth: 0 }}>
            <div style={{ fontSize: 10, fontWeight: 'bold', color: '#444', marginBottom: 4, textTransform: 'uppercase', letterSpacing: '0.1em' }}>
              ━━ more from {colName} ━━
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 4 }}>
              {siblings.map((p) => (
                <div key={p.slug} onClick={() => onView && onView(p)}
                  style={{
                    cursor: 'pointer', background: '#fff', border: '1px solid #888',
                    padding: 2, display: 'flex', flexDirection: 'column',
                    minWidth: 0, overflow: 'hidden',
                  }}>
                  <div style={{
                    width: '100%', aspectRatio: '1/1', overflow: 'hidden', position: 'relative',
                    background: p.image ? '#fff' : 'linear-gradient(135deg,#ff7ad9,#6cf2ff)',
                  }}>
                    {p.image ? (
                      <img src={p.image} alt={p.name} loading="lazy"
                        style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
                    ) : (
                      <ProductSilhouette kind={p.kind} />
                    )}
                  </div>
                  <div style={{ fontSize: 9, fontWeight: 'bold', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', marginTop: 2, minWidth: 0 }}>
                    {p.name}
                  </div>
                  <div style={{ fontSize: 9, color: '#ff3eb5', fontWeight: 'bold' }}>₹{p.price}</div>
                </div>
              ))}
            </div>
          </div>
        );
      })()}
    </div>
  );
}

function CollectionsContent({ onOpen }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <div style={{ fontSize: 10, color: '#000', marginBottom: 4 }}>
        Browse by collection — click a folder to enter
      </div>
      {COLLECTIONS.map(c => (
        <button key={c.id} onClick={() => onOpen(c)} style={{
          display: 'flex', alignItems: 'center', gap: 8, padding: 6,
          background: '#fff', border: '1px solid', borderColor: '#fff #888 #888 #fff',
          cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left',
        }}>
          <IconFolder size={28} color={c.color} />
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 11, fontWeight: 'bold' }}>{c.name}</div>
            <div style={{ fontSize: 9, color: '#666' }}>{c.count} items · synced from astralproject.in</div>
          </div>
          <div style={{ fontSize: 12 }}>›</div>
        </button>
      ))}
    </div>
  );
}

function AboutContent() {
  return (
    <div style={{ fontSize: 11, lineHeight: 1.5, padding: 4 }}>
      <div style={{ fontWeight: 'bold', fontSize: 14, marginBottom: 6, color: '#ff3eb5', textShadow: '1px 1px 0 #000' }}>
        ★ ABOUT.txt ★
      </div>
      <p style={{ margin: '0 0 8px' }}>
        ＡＳＴＲＡＬ ＰＲＯＪＥＣＴ is a small-batch streetwear studio operating since 2024.
      </p>
      <p style={{ margin: '0 0 8px' }}>
        We make wearable software — graphic tees, sneakers and accessories that look like screenshots from a half-remembered operating system.
      </p>
      <div className="w95-inset" style={{ background: '#000080', color: '#ffe14d', padding: 6, fontSize: 10, fontFamily: 'monospace', marginTop: 8 }}>
        &gt; CATALOG (149 products, 10 collections)<br/>
        &gt; · tshirts × 141<br/>
        &gt; · music × 97<br/>
        &gt; · shoes × 7<br/>
        &gt; · the cat — morale
      </div>
      <div style={{ marginTop: 8, fontSize: 10, color: '#666' }}>
        © 2024-2026 Astral Project. astralproject.in ▍
      </div>
    </div>
  );
}

// CHAT.EXE — BBS/IRC-styled wrapper around the same submitContact callable.
// The conversation collects the user's question first, then their email,
// and submits once both are in hand. The backend payload is identical to
// what ContactContent sends: { name, email, subject, message }. The "name"
// is auto-pulled from the auth user when present; otherwise inferred from
// the email local-part.
function ChatExeContent({ user, onToast }) {
  // Each entry is { who: 'sys'|'them'|'me', text }. 'sys' = grey announce
  // line, 'them' = astralprojectco, 'me' = the user.
  const initialMessages = React.useMemo(() => ([
    { who: 'sys', text: '* connecting to ASTRAL.NET BBS …' },
    { who: 'sys', text: '* astralprojectco joined #help' },
    { who: 'them', text: "what's up — got a question, wholesale, press, or just saying hi?" },
  ]), []);
  const [msgs, setMsgs] = React.useState(initialMessages);
  const [draft, setDraft] = React.useState('');
  const [stage, setStage] = React.useState('question'); // question → email → done
  const [question, setQuestion] = React.useState('');
  const [email, setEmail] = React.useState((user && user.email) || '');
  const [busy, setBusy] = React.useState(false);
  const [typing, setTyping] = React.useState(false);
  const scrollRef = React.useRef(null);

  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [msgs, typing]);

  // If we already have an email from auth, skip the second prompt.
  const hasAuthEmail = !!(user && user.email);

  const pushThem = (text, delay = 800) => {
    setTyping(true);
    setTimeout(() => {
      setTyping(false);
      setMsgs(m => [...m, { who: 'them', text }]);
    }, delay);
  };

  const submitContact = async (q, e) => {
    if (typeof window.callFn !== 'function') {
      pushThem('⚠ backend offline — try again in a moment');
      setBusy(false); return;
    }
    setBusy(true);
    try {
      const name = (user && user.displayName) || (e.split('@')[0] || 'Guest');
      await window.callFn('submitContact', {
        name, email: e, subject: 'chat.exe', message: q,
      });
      pushThem("got it ★ logged your message — we'll DM you within 24h", 600);
      setStage('done');
      onToast && onToast('★ message transmitted');
    } catch (err) {
      console.error('[chat.exe]', err);
      pushThem('⚠ could not send — try once more?', 400);
    } finally {
      setBusy(false);
    }
  };

  const send = () => {
    const text = draft.trim();
    if (!text || busy || typing) return;
    setMsgs(m => [...m, { who: 'me', text }]);
    setDraft('');

    if (stage === 'question') {
      setQuestion(text);
      if (hasAuthEmail) {
        // skip the email step
        pushThem('thanks — sending now ☆', 500);
        setTimeout(() => submitContact(text, email), 1100);
        setStage('done');
      } else {
        pushThem('cool — drop your email so we can reply', 700);
        setStage('email');
      }
    } else if (stage === 'email') {
      if (!/^\S+@\S+\.\S+$/.test(text)) {
        pushThem("that doesn't look like an email — try again?", 500);
        return;
      }
      setEmail(text);
      pushThem('locked in. sending …', 500);
      setTimeout(() => submitContact(question, text), 1100);
      setStage('done');
    } else {
      // already done — just acknowledge
      pushThem('we got it — anything else? close the window when you\'re done', 600);
    }
  };

  const onKey = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      send();
    }
  };

  const placeholder = stage === 'email'
    ? 'your.email@domain.com'
    : (stage === 'done' ? 'say something else …' : 'type your message …');

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', height: '100%',
      background: '#000', color: '#5fff5f', fontFamily: 'Consolas, "Courier New", monospace',
      fontSize: 11,
    }}>
      <div style={{
        padding: '4px 6px', borderBottom: '1px solid #5fff5f',
        background: '#003300', color: '#9cf09c',
      }}>
        #help · 2 users online · /topic = ask anything
      </div>
      <div ref={scrollRef} style={{
        flex: 1, padding: 8, overflowY: 'auto', minHeight: 0,
        lineHeight: 1.4,
      }}>
        {msgs.map((m, i) => {
          if (m.who === 'sys') {
            return <div key={i} style={{ color: '#777' }}>{m.text}</div>;
          }
          const isMe = m.who === 'me';
          return (
            <div key={i} style={{ marginBottom: 2 }}>
              <span style={{ color: isMe ? '#ffe14d' : '#ff7ad9', fontWeight: 'bold' }}>
                &lt;{isMe ? 'you' : 'astralprojectco'}&gt;
              </span>
              <span style={{ color: '#fff', marginLeft: 6 }}>{m.text}</span>
            </div>
          );
        })}
        {typing && (
          <div style={{ color: '#9cf09c', fontStyle: 'italic' }}>
            astralprojectco is typing<span className="blink-cursor">…</span>
          </div>
        )}
      </div>
      <div style={{
        display: 'flex', gap: 4, padding: 4,
        borderTop: '1px solid #5fff5f', background: '#001a00',
      }}>
        <input
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
          onKeyDown={onKey}
          disabled={busy || typing}
          placeholder={placeholder}
          autoComplete={stage === 'email' ? 'email' : 'off'}
          inputMode={stage === 'email' ? 'email' : 'text'}
          autoCapitalize={stage === 'email' ? 'none' : 'sentences'}
          spellCheck={stage !== 'email'}
          style={{
            flex: 1, background: '#000', color: '#5fff5f',
            border: '1px solid #5fff5f', padding: '3px 5px',
            fontFamily: 'inherit', fontSize: 11, outline: 'none',
          }} />
        <button onClick={send} disabled={busy || typing || !draft.trim()}
          className="ap-touch-target"
          style={{
            background: '#5fff5f', color: '#000', border: '1px solid #5fff5f',
            fontFamily: 'inherit', fontWeight: 'bold', fontSize: 11,
            padding: '3px 10px', cursor: 'pointer',
            opacity: (busy || typing || !draft.trim()) ? 0.5 : 1,
          }}>
          SEND
        </button>
      </div>
    </div>
  );
}

// Trust strip — vintage software-box claim styling. Four hard-bordered
// stickers sit above the primary CTA on the cart and checkout. Labels
// kept short so the 4-across grid doesn't truncate at the smallest
// window width (~260px content).
function TrustBadges() {
  const badges = [
    { glyph: '🔒', label: 'SSL\nRAZORPAY' },
    { glyph: '↩',  label: '5-DAY\nRETURNS' },
    { glyph: '✦',  label: 'MUMBAI\n3–5 DAYS' },
    { glyph: '♥',  label: 'HAND-CUT\nSTICKER' },
  ];
  return (
    <div style={{
      display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 3,
      margin: '8px 0',
    }}>
      {badges.map((b, i) => (
        <div key={i} style={{
          border: '1px solid #000',
          background: '#dfdfdf',
          padding: '4px 2px',
          fontSize: 7.5,
          letterSpacing: '0.04em',
          textAlign: 'center',
          fontWeight: 'bold',
          whiteSpace: 'pre-line',
          lineHeight: 1.15,
          color: '#000',
        }}>
          <div style={{ fontSize: 12, marginBottom: 2, lineHeight: 1 }}>{b.glyph}</div>
          {b.label}
        </div>
      ))}
    </div>
  );
}
window.TrustBadges = TrustBadges;

// One-line policy summary. Renders under the cart/checkout total. Kept
// dim + small so it reads as fine-print rather than a banner.
function PolicyLine() {
  return (
    <div style={{
      fontSize: 9, color: '#666', textAlign: 'center',
      margin: '4px 0', letterSpacing: '0.04em',
    }}>
      Free 5-day returns · Ships 3–5d from Mumbai · ★ Razorpay-secured
    </div>
  );
}
window.PolicyLine = PolicyLine;

function CartContent({ items, onRemove, onUpdateQty, threshold, onCheckout, onShop }) {
  const subtotal = items.reduce((s, it) => s + it.price * it.qty, 0);
  const remaining = Math.max(0, threshold - subtotal);

  return (
    <div style={{ fontSize: 11 }}>
      {items.length === 0 ? (
        <div style={{ padding: 18, textAlign: 'center', color: '#666' }}>
          <pre style={{ fontFamily: 'monospace', fontSize: 14, color: '#ff3eb5', margin: 0, lineHeight: 1.1, marginBottom: 8 }}>
{`┌──────────┐
│  ░░░░░░  │
│  ░EMPTY░  │
│  ░░░░░░  │
└─┬──────┬─┘`}
          </pre>
          <div style={{ fontWeight: 'bold', fontSize: 11, color: '#000', marginBottom: 2 }}>EMPTY_FOLDER.EXE</div>
          <div style={{ fontSize: 10, marginBottom: 10 }}>no items found in this dimension</div>
          {onShop && (
            <button className="w95-btn" onClick={onShop}
              style={{ padding: '4px 12px', fontSize: 11, fontWeight: 'bold', background: '#ff3eb5', color: '#fff', cursor: 'pointer' }}>
              Browse Shop →
            </button>
          )}
        </div>
      ) : (
        <>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 6 }}>
            {items.map((it, i) => (
              <div key={i} style={{
                display: 'flex', gap: 6, alignItems: 'center', padding: 4,
                background: '#fff', border: '1px solid #888',
              }}>
                <div style={{ width: 28, height: 28, position: 'relative' }}>
                  <ProductThumb product={it} />
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontWeight: 'bold', fontSize: 10, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {it.name}
                  </div>
                  <div style={{ fontSize: 9, color: '#666' }}>₹{it.price} × {it.qty} = ₹{it.price * it.qty}</div>
                </div>
                {onUpdateQty && (
                  <div style={{ display: 'flex', alignItems: 'center', gap: 2 }}>
                    <button className="w95-btn" style={{ minWidth: 0, padding: '0 5px', fontSize: 11 }}
                            onClick={() => onUpdateQty(it.id, -1)}>−</button>
                    <button className="w95-btn" style={{ minWidth: 0, padding: '0 5px', fontSize: 11 }}
                            onClick={() => onUpdateQty(it.id, +1)}>+</button>
                  </div>
                )}
                <button className="w95-btn" style={{ minWidth: 0, padding: '0 4px', fontSize: 9 }}
                        onClick={() => onRemove(it.id)}>×</button>
              </div>
            ))}
          </div>
          <div className="w95-inset" style={{ padding: 6, background: '#fff', fontSize: 11 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}>
              <span>Subtotal:</span>
              <b>₹{subtotal}</b>
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', color: '#666' }}>
              <span>Shipping:</span>
              <span>{remaining > 0 ? `₹100` : 'FREE ★'}</span>
            </div>
            <div style={{ borderTop: '1px solid #888', marginTop: 4, paddingTop: 4,
                          display: 'flex', justifyContent: 'space-between', fontWeight: 'bold' }}>
              <span>Total:</span>
              <b>₹{subtotal + (remaining > 0 ? 100 : 0)}</b>
            </div>
          </div>
          {remaining > 0 ? (
            <div style={{ fontSize: 10, color: '#ff7a18', marginTop: 4, textAlign: 'center', fontWeight: 'bold' }}>
              ★ Add ₹{remaining} more for FREE shipping ★
            </div>
          ) : (
            <div style={{ fontSize: 10, color: '#7dc94f', marginTop: 4, textAlign: 'center', fontWeight: 'bold' }}>
              ✓ Free shipping unlocked!
            </div>
          )}
          <PolicyLine />
          <TrustBadges />
          <button className="w95-btn" style={{
            width: '100%', marginTop: 4, background: '#ff3eb5', color: '#fff',
            fontWeight: 'bold', padding: '6px',
          }} onClick={onCheckout}>
            CHECKOUT →
          </button>
        </>
      )}
    </div>
  );
}

function CheckoutContent({ items, threshold, onPaid, onToast, user }) {
  const subtotal = items.reduce((s, it) => s + it.price * it.qty, 0);

  const [form, setForm] = React.useState(() => ({
    name: (user && user.displayName) || '',
    email: (user && user.email) || '',
    phone: '', address: '', city: '', pincode: '',
  }));
  const [addressLoaded, setAddressLoaded] = React.useState(false);

  // Reset the loaded flag whenever the signed-in user changes (sign-out →
  // sign-in-as-different-account). Otherwise the second user inherits the
  // first user's `addressLoaded === true` and never gets pre-fill.
  React.useEffect(() => {
    setAddressLoaded(false);
  }, [user && user.uid]);

  // Saved-address flow: pre-fill the form from users/{uid}.address on mount
  // for signed-in users. Anonymous users get nothing — saved checkout is a
  // perk of the linked Google account. The form remains fully editable;
  // any change is persisted on successful pay (handler in pay() below).
  React.useEffect(() => {
    if (!user || user.isAnonymous || addressLoaded) return;
    if (typeof window.loadUserPrefs !== 'function') return;
    let cancelled = false;
    window.loadUserPrefs(user.uid).then((prefs) => {
      if (cancelled || !prefs.address) return;
      setForm((f) => ({
        ...f,
        // user-provided values win — never overwrite something the user typed.
        name: f.name || prefs.address.name || f.name,
        phone: f.phone || prefs.address.phone || '',
        address: f.address || prefs.address.address || '',
        city: f.city || prefs.address.city || '',
        pincode: f.pincode || prefs.address.pincode || '',
      }));
      setAddressLoaded(true);
    });
    return () => { cancelled = true; };
  }, [user && user.uid, addressLoaded]);
  const [coupon, setCoupon] = React.useState('');
  const [discount, setDiscount] = React.useState(0);
  const [couponMsg, setCouponMsg] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));

  const discounted = Math.max(0, subtotal - discount);
  const shipping = discounted >= threshold ? 0 : 100;
  const total = discounted + shipping;

  async function applyCouponLocal() {
    setCouponMsg('');
    const code = coupon.trim().toUpperCase();
    if (!code) { setDiscount(0); return; }
    // WELCOME10 is hardcoded on the server too — keep the snappy local path.
    if (code === 'WELCOME10') {
      setDiscount(Math.round(subtotal * 0.10));
      setCouponMsg('★ 10% applied — verified at checkout');
      return;
    }
    // Otherwise look up the live coupon from Firestore. Public read rule
    // on coupons/{code} lets anonymous users preview the discount before
    // creating an order. Server priceCart re-validates at pay time.
    if (window.db) {
      try {
        const snap = await window.db.collection('coupons').doc(code).get();
        if (snap.exists) {
          const c = snap.data();
          const stillValid = c.active !== false
            && (c.scope === undefined || c.scope === 'stores')
            && (!c.expirationTime || c.expirationTime.toMillis() > Date.now())
            && (!c.startTime || c.startTime.toMillis() <= Date.now());
          if (stillValid) {
            let d = 0;
            if (c.kind === 'percent') d = Math.round((subtotal * Number(c.value)) / 100);
            else if (c.kind === 'flat') d = Math.min(Number(c.value) || 0, subtotal);
            setDiscount(d);
            setCouponMsg(d > 0
              ? `★ ${c.label || code} applied — verified at checkout`
              : `★ ${c.label || code} — won't reduce this cart`);
            return;
          }
          setDiscount(0);
          setCouponMsg(c.scope && c.scope !== 'stores'
            ? `${c.label || code} is for ${c.scope}, not store items.`
            : 'Code is inactive or expired.');
          return;
        }
      } catch (_e) { /* fall through to "unknown" */ }
    }
    setDiscount(0);
    setCouponMsg('Unknown code. Server will reject at pay time.');
  }

  function validate() {
    const phone = form.phone.replace(/\D/g, '');
    const pincode = form.pincode.replace(/\D/g, '');
    if (!form.name.trim()) return 'Enter your full name.';
    if (!/\S+@\S+\.\S+/.test(form.email)) return 'Enter a valid email.';
    if (phone.length !== 10) return 'Phone must be 10 digits.';
    if (!form.address.trim()) return 'Enter your address.';
    if (!form.city.trim()) return 'Enter your city.';
    if (pincode.length !== 6) return 'Pincode must be 6 digits.';
    if (!items || items.length === 0) return 'Your cart is empty.';
    return '';
  }

  async function pay() {
    setErr('');
    if (busy) return;
    const v = validate();
    if (v) { setErr(v); return; }
    if (typeof window.callFn !== 'function') {
      setErr('Backend not initialized — refresh the page.');
      console.error('[checkout] window.callFn missing');
      return;
    }
    if (typeof window.Razorpay !== 'function') {
      setErr('Razorpay script blocked — disable adblock and reload.');
      console.error('[checkout] window.Razorpay missing');
      return;
    }
    setBusy(true);
    try {
      const cartItems = items.map((it) => ({
        slug: it.slug || it.id,
        qty: Number(it.qty) || 1,
      }));
      const customer = {
        ...form,
        phone: form.phone.replace(/\D/g, ''),
        pincode: form.pincode.replace(/\D/g, ''),
      };
      const submitCoupon = coupon.trim() ? coupon.trim().toUpperCase() : undefined;
      console.log('[checkout] createOrder ->', { items: cartItems, customer, coupon: submitCoupon });
      const created = await window.callFn('createOrder', { items: cartItems, customer, coupon: submitCoupon });
      // Server is the source of truth for total. Refresh local view if it differs.
      if (typeof created.total === 'number') {
        if (created.discount !== discount) setDiscount(created.discount || 0);
      }
      console.log('[checkout] order created:', created);
      const rzp = new window.Razorpay({
        key: created.keyId,
        order_id: created.orderId,
        amount: created.amount,
        currency: created.currency,
        name: 'Astral Project',
        description: `Order ${created.orderId}`,
        prefill: { name: customer.name, email: customer.email, contact: customer.phone },
        theme: { color: '#ff3eb5' },
        modal: {
          ondismiss: () => {
            console.log('[checkout] modal dismissed');
            setBusy(false);
          },
        },
        handler: async (response) => {
          console.log('[checkout] payment response:', response);
          try {
            const v = await window.callFn('verifyPayment', response);
            // Persist the address to the user's profile for one-tap reuse on
            // future checkouts. Fire-and-forget — if the write fails the
            // order still succeeds.
            if (user && !user.isAnonymous && typeof window.saveUserPrefs === 'function') {
              window.saveUserPrefs(user.uid, {
                address: {
                  name: form.name, phone: form.phone, address: form.address,
                  city: form.city, pincode: form.pincode,
                },
              });
            }
            onPaid && onPaid({ orderId: created.orderId, total: (v && v.total) || total });
          } catch (vErr) {
            console.error('[checkout] verify failed:', vErr);
            setErr('Payment captured but verification failed. Save the payment id: ' + response.razorpay_payment_id);
            setBusy(false);
          }
        },
      });
      if (typeof rzp.on === 'function') {
        rzp.on('payment.failed', (resp) => {
          console.error('[checkout] payment.failed:', resp);
          setErr('Payment failed: ' + (resp && resp.error && resp.error.description || 'unknown'));
          setBusy(false);
        });
      }
      rzp.open();
      console.log('[checkout] Razorpay modal opened');
    } catch (e) {
      console.error('[checkout] createOrder failed:', e);
      const msg = (e && (e.message || e.code)) || 'Could not start checkout';
      setErr(msg);
      setBusy(false);
    }
  }

  const fld = { display: 'block', width: '100%', marginBottom: 4, fontSize: 11 };
  const lbl = { fontSize: 10, color: '#444', marginBottom: 2, display: 'block' };

  return (
    <div style={{ fontSize: 11 }}>
      <div className="w95-inset" style={{ padding: 6, background: '#fff', marginBottom: 8 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
          <span>Subtotal:</span><b>₹{subtotal}</b>
        </div>
        {discount > 0 && (
          <div style={{ display: 'flex', justifyContent: 'space-between', color: '#0a8a0a' }}>
            <span>Discount{coupon ? ` (${coupon.toUpperCase()})` : ''}:</span><b>−₹{discount}</b>
          </div>
        )}
        <div style={{ display: 'flex', justifyContent: 'space-between', color: '#666' }}>
          <span>Shipping:</span><span>{shipping ? `₹${shipping}` : 'FREE ★'}</span>
        </div>
        <div style={{ borderTop: '1px solid #888', marginTop: 4, paddingTop: 4,
                      display: 'flex', justifyContent: 'space-between', fontWeight: 'bold' }}>
          <span>Total:</span><b>₹{total}</b>
        </div>
      </div>
      <div style={{ display: 'flex', gap: 4, marginBottom: 8 }}>
        <input className="w95-input" placeholder="promo code (e.g. WELCOME10)"
          value={coupon} onChange={(e) => setCoupon(e.target.value)}
          style={{ flex: 1, fontSize: 11, textTransform: 'uppercase' }} />
        <button className="w95-btn" onClick={applyCouponLocal}
          style={{ padding: '2px 8px', fontSize: 10 }}>Apply</button>
      </div>
      {couponMsg && (
        <div style={{ marginTop: -4, marginBottom: 6, fontSize: 9,
          color: discount > 0 ? '#0a8a0a' : '#aa5500', fontWeight: 'bold' }}>
          {couponMsg}
        </div>
      )}
      <label style={lbl}>Full name</label>
      <input className="w95-input" style={fld} value={form.name} onChange={set('name')}
        autoComplete="name" autoCapitalize="words" />
      <label style={lbl}>Email</label>
      <input className="w95-input" style={fld} type="email" value={form.email} onChange={set('email')}
        inputMode="email" autoComplete="email" autoCapitalize="none" spellCheck={false} />
      <label style={lbl}>Phone (10 digits)</label>
      <input className="w95-input" style={fld} value={form.phone} onChange={set('phone')}
        type="tel" inputMode="tel" autoComplete="tel" maxLength={10} />
      <label style={lbl}>Address</label>
      <input className="w95-input" style={fld} value={form.address} onChange={set('address')}
        autoComplete="street-address" />
      <div style={{ display: 'flex', gap: 6 }}>
        <div style={{ flex: 1 }}>
          <label style={lbl}>City</label>
          <input className="w95-input" style={fld} value={form.city} onChange={set('city')}
            autoComplete="address-level2" autoCapitalize="words" />
        </div>
        <div style={{ width: 90 }}>
          <label style={lbl}>Pincode</label>
          <input className="w95-input" style={fld} value={form.pincode} onChange={set('pincode')}
            inputMode="numeric" autoComplete="postal-code" maxLength={6} />
        </div>
      </div>
      <PolicyLine />
      <TrustBadges />
      <button className="w95-btn" disabled={busy} style={{
        width: '100%', marginTop: 4, background: busy ? '#999' : '#ff3eb5',
        color: '#fff', fontWeight: 'bold', padding: '6px',
      }} onClick={pay}>
        {busy ? 'PROCESSING…' : `PAY ₹${total} →`}
      </button>
      {err && (
        <div style={{
          marginTop: 6, padding: 6, background: '#ffe1e1',
          border: '1px solid #c00', color: '#900', fontSize: 10,
        }}>
          <div style={{ fontWeight: 'bold', marginBottom: 4 }}>⚠ {err}</div>
          <div style={{ display: 'flex', gap: 4 }}>
            <button className="w95-btn" disabled={busy} onClick={pay}
              style={{ fontSize: 10, padding: '2px 8px', fontWeight: 'bold' }}>
              Try again →
            </button>
            <button className="w95-btn" onClick={() => setErr('')}
              style={{ fontSize: 10, padding: '2px 8px' }}>
              Dismiss
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

function OrderSuccessContent({ orderId, total, onClose, onTrack }) {
  const copy = () => {
    try { navigator.clipboard.writeText(orderId); } catch {}
  };
  return (
    <div style={{ fontSize: 11, padding: 8, textAlign: 'center' }}>
      <div style={{ fontSize: 36, marginBottom: 8 }}>✓</div>
      <div style={{ fontWeight: 'bold', marginBottom: 4 }}>Payment received!</div>
      <div style={{ color: '#444', marginBottom: 8 }}>
        Order <b style={{ fontFamily: 'monospace' }}>{orderId}</b>
        <button className="w95-btn" onClick={copy} style={{ marginLeft: 4, padding: '0 4px', fontSize: 9 }}>copy</button>
        <br/>
        Total paid: <b>₹{total}</b>
      </div>
      <div style={{ fontSize: 10, color: '#666', marginBottom: 10 }}>
        Save your order id — use it to track delivery.
      </div>
      <div style={{ display: 'flex', gap: 4, justifyContent: 'center' }}>
        {onTrack && <button className="w95-btn" onClick={onTrack}>Track →</button>}
        <button className="w95-btn" onClick={onClose}>Close</button>
      </div>
    </div>
  );
}

const TRACK_TIMELINE = [
  { key: 'created',  label: 'Order received' },
  { key: 'paid',     label: 'Payment confirmed' },
  { key: 'packed',   label: 'Picked &amp; packed' },
  { key: 'shipped',  label: 'Handed to courier' },
  { key: 'transit',  label: 'In transit' },
  { key: 'out',      label: 'Out for delivery' },
  { key: 'delivered',label: 'Delivered' },
];

function trackProgressFor(status) {
  // Map a Razorpay/Astral order status to a timeline cursor.
  const s = (status || '').toLowerCase();
  if (s === 'created')   return 1;
  if (s === 'paid')      return 2;
  if (s === 'packed')    return 3;
  if (s === 'shipped')   return 4;
  if (s === 'transit')   return 5;
  if (s === 'out')       return 6;
  if (s === 'delivered') return 7;
  if (s === 'failed')    return 0;
  return 1;
}

function TrackOrderContent({ user }) {
  const [orderId, setOrderId] = React.useState('');
  const [email, setEmail] = React.useState((user && user.email) || '');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [result, setResult] = React.useState(null);

  const lookup = async () => {
    setErr(''); setResult(null);
    if (!orderId.trim()) { setErr('Enter your order id.'); return; }
    if (typeof window.callFn !== 'function') { setErr('Backend not ready.'); return; }
    setBusy(true);
    try {
      const r = await window.callFn('trackOrder', { orderId: orderId.trim(), email: email.trim() });
      setResult(r);
    } catch (e) {
      console.error('[track]', e);
      setErr((e && (e.message || e.code)) || 'Could not find that order.');
    } finally {
      setBusy(false);
    }
  };

  const cursor = result ? trackProgressFor(result.status) : 0;

  return (
    <div style={{ fontSize: 11 }}>
      <div style={{ marginBottom: 6 }}>
        Enter your order id (from the confirmation screen / email):
      </div>
      <input className="w95-input" value={orderId}
        onChange={e => setOrderId(e.target.value)}
        placeholder="order_xxxxxxxxxxxxxx or AP-xxxxxx"
        style={{ width: '100%', marginBottom: 4, fontSize: 11 }} />
      <input className="w95-input" type="email" value={email}
        onChange={e => setEmail(e.target.value)}
        placeholder="email used at checkout"
        inputMode="email" autoComplete="email" autoCapitalize="none" spellCheck={false}
        style={{ width: '100%', marginBottom: 4, fontSize: 11 }} />
      <button className="w95-btn" disabled={busy} onClick={lookup}
        style={{ width: '100%', marginBottom: 8, fontWeight: 'bold' }}>
        {busy ? 'Looking up…' : 'Track →'}
      </button>
      {err && (
        <div style={{ padding: 6, background: '#ffe1e1', border: '1px solid #c00',
          color: '#900', fontSize: 10, fontWeight: 'bold' }}>⚠ {err}</div>
      )}
      {result && (
        <div className="w95-inset" style={{ background: '#000', color: '#5fff5f', padding: 6, fontFamily: 'monospace', fontSize: 10 }}>
          <div>ORDER: {result.receipt || result.id}</div>
          <div>STATUS: <span style={{ color: '#ffe14d' }}>{(result.status || '').toUpperCase()}</span></div>
          <div>TOTAL: ₹{result.total} · {result.itemCount} item(s)</div>
          {result.shipTo && (
            <div>SHIP TO: {result.shipTo.name}, {result.shipTo.city} {result.shipTo.pincode}</div>
          )}
          <div>━━━━━━━━━━━━━━━━━━━━</div>
          {TRACK_TIMELINE.map((step, i) => {
            const done = i < cursor;
            const active = i === cursor - 1;
            const mark = done ? '✓' : (active ? '▸' : '○');
            const color = active ? '#ff7ad9' : (done ? '#5fff5f' : '#666');
            return (
              <div key={step.key} style={{ color }}>
                {mark} <span dangerouslySetInnerHTML={{ __html: step.label }} />
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// Knowledge base. Organized as five tabs: how-to, FAQ, shipping zones,
// returns policy, and a size chart. Each panel sits on a white inset
// surface for readability; folder-tab metaphor follows design.md §6.4.
const HELP_TABS = [
  { id: 'help',     label: 'HELP'      },
  { id: 'faq',      label: 'FAQ.TXT'   },
  { id: 'shipping', label: 'SHIPPING'  },
  { id: 'returns',  label: 'RETURNS'   },
  { id: 'sizes',    label: 'SIZES'     },
];

const FAQ_ENTRIES = [
  {
    q: 'How long does delivery take?',
    a: 'We dispatch from a small studio in Mumbai within 3–5 business days. Metro pincodes (DEL/MUM/BLR/HYD/CHN/PNQ) usually arrive in 4–6 days; rest of India in 7–10 days. Delhi-NCR is fastest in our experience.',
  },
  {
    q: 'How do I track my order?',
    a: 'Open Track Order from the desktop and paste your order id (it’s in the confirmation email and the order-success window). Status updates every time we hand off to the courier.',
  },
  {
    q: 'What payment methods are accepted?',
    a: 'All Indian cards (Visa / Mastercard / RuPay), UPI (PhonePe / GPay / Paytm), net banking from 50+ banks, and major wallets. Razorpay handles the actual transaction — we never see your card details.',
  },
  {
    q: 'Is COD available?',
    a: 'Not yet. Returns + COD don’t play well at our scale. We may turn it on for select pincodes once volume justifies the reverse-logistics overhead.',
  },
  {
    q: 'How do the tees fit?',
    a: 'Cut intentionally oversized — drop shoulders, slightly cropped torso, boxy fit. If you’re between sizes, stay true to size for the intended look or bump up one for extra room. Size chart is the SIZES tab.',
  },
  {
    q: 'What\'s the fabric?',
    a: '180GSM 100% cotton, pre-shrunk, garment-dyed where the shade calls for it. Heavy enough to drape well, light enough for Indian summers.',
  },
  {
    q: 'How do I wash these?',
    a: 'Cold-water machine wash, inside out. Mild detergent, no bleach. Hang dry in shade — direct sun fades graphic prints over time. Iron inside out on low if needed.',
  },
  {
    q: 'Can I exchange a size?',
    a: 'Yes — within 5 days of delivery, unworn with tags. Mail us at hello@astralproject.in with your order id and we’ll arrange a reverse pickup. Reverse pickup costs ₹100 (waived for first-time exchanges).',
  },
  {
    q: 'When is the next drop?',
    a: 'Open coming_soon.exe on the desktop — it shows a live countdown to the soonest unreleased product. Hit ★ notify me on any teaser tile to be alerted when it drops.',
  },
  {
    q: 'Do you ship internationally?',
    a: 'Currently India only. International shipping is on the roadmap once we sort out duties and reverse logistics.',
  },
  {
    q: 'I see a struck-out price. What\'s the actual cost?',
    a: 'The pink number is what you pay. The grey strike-through is the original list price for products we’re running a deal on. The cart and Razorpay total always reflect the live price.',
  },
  {
    q: 'How do I earn Star Points?',
    a: '1 point for every ₹10 you spend, awarded after payment is captured. Open Account → Rewards to redeem. Tier perks (free shipping, early access) unlock automatically as your lifetime spend climbs.',
  },
  {
    q: 'Do these prices include GST?',
    a: 'Yes. Every price you see on the site is GST-inclusive. No surprises at checkout.',
  },
  {
    q: 'I have a code from somewhere — where do I enter it?',
    a: 'Promo code field is right at the top of the Checkout window, above the form. Apply before paying — server validates and rejects invalid or expired codes.',
  },
  {
    q: 'Why does this look like Windows 95?',
    a: 'Because the future was supposed to look like this. Also because every other streetwear store looks identical and we got bored.',
  },
];

const SHIPPING_ZONES = [
  { zone: 'METRO',     pincodes: 'DEL · MUM · BLR · HYD · CHN · PNQ', eta: '4–6 business days' },
  { zone: 'TIER 2',    pincodes: 'JAI · LKO · CHD · IND · BPL · NAG', eta: '6–8 business days' },
  { zone: 'REST OF IN', pincodes: 'all other Indian pincodes',         eta: '7–10 business days' },
  { zone: 'NORTH-EAST', pincodes: 'AS · MN · MZ · NL · TR · ML · AR',  eta: '8–12 business days' },
];

function HelpContent({ defaultTab = 'help' } = {}) {
  const [tab, setTab] = React.useState(defaultTab);

  const tabBtn = (id, label) => (
    <button key={id} onClick={() => setTab(id)} style={{
      padding: '4px 6px',
      fontSize: 9, fontWeight: tab === id ? 'bold' : 'normal',
      background: tab === id ? '#dfdfdf' : '#a8a8a8',
      color: tab === id ? '#000' : '#222',
      border: '1px solid',
      borderColor: '#fff #000 transparent #fff',
      borderBottom: tab === id ? '1px solid #dfdfdf' : '1px solid #888',
      marginBottom: tab === id ? '-1px' : 0,
      marginRight: 1,
      cursor: 'pointer',
      letterSpacing: '0.05em',
    }}>{label}</button>
  );

  return (
    <div style={{ fontSize: 11, lineHeight: 1.45 }}>
      <div style={{ display: 'flex', alignItems: 'flex-end', borderBottom: '1px solid #888', marginBottom: -1 }}>
        {HELP_TABS.map(t => tabBtn(t.id, t.label))}
      </div>
      <div className="w95-inset" style={{ background: '#dfdfdf', padding: 8, minHeight: 240 }}>

        {tab === 'help' && (
          <div>
            <div style={{ fontFamily: 'monospace', fontSize: 10, color: '#444', marginBottom: 8 }}>
              &gt; ASTRAL.95 user manual · last modified 2026-05-03
            </div>
            <FaqRow q="How do I navigate?" a="Tap any desktop icon to open a window. Drag the title bar to move it. Tap × to close, _ to minimize, □ to maximize. Esc closes the topmost window." />
            <FaqRow q="How do I add to cart?" a="On any product card, tap ADD TO CART. The orange bar up top tracks your progress to free shipping (₹200+). Tap the cart icon to review and check out." />
            <FaqRow q="How do I checkout?" a="Cart → CHECKOUT → fill the form → PAY. Razorpay handles the rest. You'll get an email and a track-able order id." />
            <FaqRow q="Where's the size chart / FAQ / shipping policy?" a="Right here — tap a tab above. The SIZES chart also lives in every product page under the SIZES tab." />
            <FaqRow q="I'm stuck — how do I reach a human?" a="Open Chat.exe from the desktop, or email hello@astralproject.in. We answer within a day, usually faster." last />
          </div>
        )}

        {tab === 'faq' && (
          <div>
            <div style={{ fontFamily: 'monospace', fontSize: 10, color: '#444', marginBottom: 8 }}>
              &gt; FREQUENTLY ASKED · {FAQ_ENTRIES.length} entries
            </div>
            {FAQ_ENTRIES.map((e, i) => (
              <FaqRow key={i} q={e.q} a={e.a} last={i === FAQ_ENTRIES.length - 1} />
            ))}
          </div>
        )}

        {tab === 'shipping' && (
          <div>
            <div style={{ fontFamily: 'monospace', fontSize: 10, color: '#444', marginBottom: 8 }}>
              &gt; SHIPPING.TXT · india · ₹100 / FREE over ₹200
            </div>
            <div style={{ background: '#fff', border: '1px solid #888', padding: 8, marginBottom: 8 }}>
              <div style={{ fontWeight: 'bold', marginBottom: 4 }}>Dispatch</div>
              <div style={{ fontSize: 10, color: '#222' }}>
                Orders ship from our studio in Mumbai within <b>3–5 business days</b> of payment. Every package is hand-packed with a sticker.
              </div>
            </div>
            <div style={{ background: '#fff', border: '1px solid #888', padding: 8, marginBottom: 8 }}>
              <div style={{ fontWeight: 'bold', marginBottom: 4 }}>Delivery times by zone</div>
              <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 10 }}>
                <thead>
                  <tr style={{ background: '#ffd6e8' }}>
                    <th style={shipTh}>ZONE</th>
                    <th style={shipTh}>PINCODES</th>
                    <th style={shipTh}>ETA</th>
                  </tr>
                </thead>
                <tbody>
                  {SHIPPING_ZONES.map((z, i) => (
                    <tr key={i}>
                      <td style={{ ...shipTd, fontWeight: 'bold' }}>{z.zone}</td>
                      <td style={shipTd}>{z.pincodes}</td>
                      <td style={shipTd}>{z.eta}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
              <div style={{ fontSize: 9, color: '#666', marginTop: 6 }}>
                ETAs are business days from dispatch. Add 1–2 days during sale or drop weeks.
              </div>
            </div>
            <div style={{ background: '#fff', border: '1px solid #888', padding: 8 }}>
              <div style={{ fontWeight: 'bold', marginBottom: 4 }}>Tracking</div>
              <div style={{ fontSize: 10, color: '#222' }}>
                Tracking number lands in your email when the package leaves us. You can also paste your order id into Track Order on the desktop for live status.
              </div>
            </div>
          </div>
        )}

        {tab === 'returns' && (
          <div>
            <div style={{ fontFamily: 'monospace', fontSize: 10, color: '#444', marginBottom: 8 }}>
              &gt; RETURNS.TXT · 5-day window · reverse pickup
            </div>
            <div style={{ background: '#fff', border: '1px solid #888', padding: 8, marginBottom: 8 }}>
              <div style={{ fontWeight: 'bold', marginBottom: 4 }}>Window</div>
              <div style={{ fontSize: 10, color: '#222' }}>
                <b>5 days</b> from delivery. Item must be unworn, unwashed, and with original tags + sticker pack intact.
              </div>
            </div>
            <div style={{ background: '#fff', border: '1px solid #888', padding: 8, marginBottom: 8 }}>
              <div style={{ fontWeight: 'bold', marginBottom: 4 }}>How to return</div>
              <div style={{ fontSize: 10, color: '#222' }}>
                <ol style={{ paddingLeft: 18, margin: '4px 0' }}>
                  <li>Email <b>hello@astralproject.in</b> with your order id + photo of the item.</li>
                  <li>We schedule a reverse pickup with our courier (₹100 fee, waived for first-time exchanges).</li>
                  <li>Once we receive + inspect (1–2 business days), refund hits your original payment method.</li>
                </ol>
              </div>
            </div>
            <div style={{ background: '#fff', border: '1px solid #888', padding: 8, marginBottom: 8 }}>
              <div style={{ fontWeight: 'bold', marginBottom: 4 }}>Refund timeline</div>
              <div style={{ fontSize: 10, color: '#222' }}>
                Cards / UPI / wallets: <b>5–7 business days</b>. Net banking: 7–10 days. We have no control over the bank's last leg.
              </div>
            </div>
            <div style={{ background: '#fff', border: '1px solid #888', padding: 8 }}>
              <div style={{ fontWeight: 'bold', marginBottom: 4 }}>What we can't take back</div>
              <div style={{ fontSize: 10, color: '#222' }}>
                Worn / washed pieces, items without tags, sticker packs (sealed for hygiene), final-sale drops marked &quot;no return&quot;.
              </div>
            </div>
          </div>
        )}

        {tab === 'sizes' && (
          <div>
            <div style={{ fontFamily: 'monospace', fontSize: 10, color: '#444', marginBottom: 8 }}>
              &gt; SIZE_GUIDE.EXE · oversized fit · cm / in
            </div>
            <SizeFinder />
            <div style={{ marginTop: 12 }}>
              {typeof SHARED_INFO !== 'undefined' && Array.isArray(SHARED_INFO.sizeChart) && SHARED_INFO.sizeChart.length > 0 ? (
                <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 10 }}>
                  <tbody>
                    {SHARED_INFO.sizeChart.map((row, ri) => (
                      <tr key={ri}>
                        {row.map((cell, ci) => (
                          <td key={ci} style={{
                            border: '1px solid #888', padding: '3px 5px',
                            fontWeight: ri === 0 || ci === 0 ? 'bold' : 'normal',
                            background: ri === 0 ? '#ffd6e8' : '#fff',
                          }}>{cell}</td>
                        ))}
                      </tr>
                    ))}
                  </tbody>
                </table>
              ) : (
                <div style={{ fontSize: 10, color: '#666' }}>Size chart not loaded yet.</div>
              )}
              <div style={{ fontSize: 9, color: '#666', marginTop: 6 }}>
                Cut oversized — drop shoulders + boxy. Between sizes? True to size for the intended look, one up for roomier.
              </div>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

const shipTh = {
  border: '1px solid #888', padding: '3px 5px', textAlign: 'left',
  fontSize: 9, letterSpacing: '0.05em',
};
const shipTd = { border: '1px solid #888', padding: '3px 5px', fontSize: 10 };

// Q/A row used in HELP and FAQ tabs. Bold question, indented answer,
// thin divider between rows so the panel reads as a structured list.
function FaqRow({ q, a, last }) {
  return (
    <div style={{ marginBottom: last ? 0 : 8, paddingBottom: last ? 0 : 8, borderBottom: last ? 'none' : '1px dashed #aaa' }}>
      <div style={{ fontWeight: 'bold', fontSize: 11, marginBottom: 2 }}>★ {q}</div>
      <div style={{ fontSize: 10, color: '#222', paddingLeft: 12 }}>{a}</div>
    </div>
  );
}

// Height-based size recommender. Oversized cut → bias one size up.
// Returns the suggested size letter (S/M/L/XL/XXL).
function sizeFromHeight(cm) {
  if (!cm || cm < 140) return null;
  if (cm < 162) return 'S';
  if (cm < 170) return 'M';
  if (cm < 178) return 'L';
  if (cm < 186) return 'XL';
  return 'XXL';
}

function SizeFinder() {
  const [cm, setCm] = React.useState('');
  const [fit, setFit] = React.useState('regular'); // 'regular' | 'roomier'
  const num = parseInt(cm, 10);
  const base = sizeFromHeight(num);
  const sizes = ['S', 'M', 'L', 'XL', 'XXL'];
  const idx = base ? sizes.indexOf(base) : -1;
  const recommended = idx === -1 ? null : sizes[Math.min(sizes.length - 1, idx + (fit === 'roomier' ? 1 : 0))];

  return (
    <div style={{ background: '#fff', border: '1px solid #888', padding: 8 }}>
      <div style={{ fontWeight: 'bold', fontSize: 11, marginBottom: 6 }}>★ find my size</div>
      <div style={{ display: 'flex', gap: 6, alignItems: 'center', marginBottom: 6 }}>
        <label style={{ fontSize: 10 }}>height (cm)</label>
        <input className="w95-input" inputMode="numeric" maxLength={3}
          value={cm} onChange={(e) => setCm(e.target.value.replace(/\D/g, '').slice(0, 3))}
          style={{ width: 56, fontSize: 11 }} />
        <label style={{ fontSize: 10, marginLeft: 8 }}>fit</label>
        <select value={fit} onChange={(e) => setFit(e.target.value)} style={{ fontSize: 10 }}>
          <option value="regular">regular</option>
          <option value="roomier">roomier</option>
        </select>
      </div>
      <div style={{ fontSize: 10, color: '#222', minHeight: 18 }}>
        {recommended ? (
          <>recommended → <b style={{ background: '#ffd6e8', padding: '1px 6px', border: '1px solid #000' }}>{recommended}</b></>
        ) : num ? (
          <span style={{ color: '#aa5500' }}>height looks off — enter 140-220 cm</span>
        ) : (
          <span style={{ color: '#666' }}>enter your height to see a recommendation</span>
        )}
      </div>
    </div>
  );
}

// Fallback drop date when no products carry a `releasesAt` yet (e.g. fresh
// install). 2026-08-01 IST. Once the admin marks at least one product as
// `status: 'unreleased'` with a real `releasesAt`, this fallback isn't used.
const FALLBACK_NEXT_DROP_AT = Date.UTC(2026, 7, 1, 0, 0, 0) - (5 * 60 + 30) * 60 * 1000;

function ComingSoonContent({ user, onToast }) {
  // Re-read UNRELEASED_PRODUCTS each render — it's mutated in place
  // by loadCatalog and may not be populated when this component first
  // mounts. The 1s `now` interval also forces a re-evaluation.
  const [now, setNow] = React.useState(() => Date.now());
  const [notified, setNotified] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('ap-drop-notify') || '{}'); } catch { return {}; }
  });
  React.useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(id);
  }, []);

  // Pull the next-up unreleased product (if any) for the headline countdown.
  const unreleased = UNRELEASED_PRODUCTS;
  const nextDrop = unreleased.find((p) => p.releasesAtMs && p.releasesAtMs > now);
  const targetMs = nextDrop ? nextDrop.releasesAtMs : FALLBACK_NEXT_DROP_AT;
  const dropName = nextDrop ? (nextDrop.name || 'Untitled drop') : 'HELLENIC';
  const dropLabel = nextDrop ? `Next: ${nextDrop.name}` : 'Drop 02 — HELLENIC';

  const ms = Math.max(0, targetMs - now);
  const s = Math.floor(ms / 1000);
  const days = Math.floor(s / 86400);
  const hrs  = Math.floor((s % 86400) / 3600);
  const mins = Math.floor((s % 3600) / 60);
  const secs = s % 60;
  const pad = (n, w = 2) => String(n).padStart(w, '0');

  const persistNotified = (next) => {
    setNotified(next);
    try { localStorage.setItem('ap-drop-notify', JSON.stringify(next)); } catch {}
    if (user && !user.isAnonymous && typeof window.saveUserPrefs === 'function') {
      window.saveUserPrefs(user.uid, { dropNotify: next });
    }
  };
  const toggleNotify = (slug) => {
    const next = { ...notified, [slug]: !notified[slug] };
    if (!next[slug]) delete next[slug];
    persistNotified(next);
    if (window.apSound) window.apSound.click();
    onToast && onToast(next[slug] ? `★ you'll be alerted when ${slug} drops` : '✗ alert removed');
  };

  const fmtRelative = (target) => {
    const dms = Math.max(0, target - now);
    const ds = Math.floor(dms / 1000);
    const dd = Math.floor(ds / 86400);
    const dh = Math.floor((ds % 86400) / 3600);
    if (dd > 0) return `in ${dd}d ${dh}h`;
    const dm = Math.floor((ds % 3600) / 60);
    if (dh > 0) return `in ${dh}h ${dm}m`;
    return `in ${dm}m`;
  };

  return (
    <div style={{ padding: 12, textAlign: 'center', fontSize: 11 }}>
      <div style={{
        fontSize: 36, fontWeight: 'bold', color: '#ff3eb5',
        textShadow: '2px 2px 0 #000, 4px 4px 0 #6cf2ff',
        letterSpacing: '0.05em', marginBottom: 8, fontFamily: 'serif',
      }}>
        SOON™
      </div>
      <div style={{ marginBottom: 12 }}>{dropLabel}</div>
      <div className="w95-inset" style={{ background: '#000', padding: 8, color: '#5fff5f', fontFamily: 'monospace', fontSize: 16, textAlign: 'center' }}>
        {ms === 0 ? 'LIVE NOW ★' : `${pad(days, 3)}:${pad(hrs)}:${pad(mins)}:${pad(secs)}`}
        <div style={{ fontSize: 9, color: '#888', marginTop: 2 }}>days : hrs : min : sec</div>
      </div>
      {!nextDrop && (
        <div style={{ fontSize: 10, marginTop: 8, color: '#666' }}>
          Drops 01 Aug 2026, 00:00 IST. Subscribe to get the link first.
        </div>
      )}

      {/* Silhouette teasers — one tile per unreleased product. */}
      {unreleased.length > 0 && (
        <div style={{ marginTop: 12, textAlign: 'left' }}>
          <div style={{ fontSize: 10, fontWeight: 'bold', color: '#444', marginBottom: 4, textTransform: 'uppercase', letterSpacing: '0.1em' }}>
            ━━ unreleased ━━
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
            {unreleased.slice(0, 6).map((p) => {
              const isNotified = !!notified[p.slug];
              return (
                <div key={p.slug} style={{
                  padding: 4, background: '#fff', border: '1px solid #888',
                  display: 'flex', flexDirection: 'column', alignItems: 'center',
                }}>
                  <div style={{
                    width: '100%', aspectRatio: '1/1', position: 'relative',
                    background: 'linear-gradient(135deg,#000,#1a0033)',
                    border: '1px solid #000', overflow: 'hidden', marginBottom: 4,
                  }}>
                    {/* Silhouette: solid black overlay over the actual image so
                        the shape teases without revealing detail. */}
                    {p.image && (
                      <img src={p.image} alt={`${p.name} silhouette`}
                        style={{
                          width: '100%', height: '100%', objectFit: 'cover',
                          filter: 'brightness(0) invert(0)',
                          opacity: 0.4,
                        }} />
                    )}
                    <div style={{
                      position: 'absolute', inset: 0,
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      fontSize: 22, color: '#ff3eb5', fontWeight: 'bold',
                      textShadow: '0 0 8px #000',
                    }}>?</div>
                  </div>
                  <div style={{ fontSize: 9, fontWeight: 'bold', textAlign: 'center', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', width: '100%' }}>
                    {p.name || '???'}
                  </div>
                  {p.releasesAtMs && (
                    <div style={{ fontSize: 8, color: '#888', marginBottom: 4 }}>
                      {fmtRelative(p.releasesAtMs)}
                    </div>
                  )}
                  <button className="w95-btn" onClick={() => toggleNotify(p.slug)}
                    style={{
                      fontSize: 9, padding: '2px 4px', width: '100%',
                      background: isNotified ? 'var(--ap-cyan)' : undefined,
                      fontWeight: 'bold',
                    }}>
                    {isNotified ? '✓ alert set' : '★ notify me'}
                  </button>
                </div>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

const LOOKBOOK_ASSETS = {
  video: 'Lookbook/MainPage1 - Made with Clipchamp.mp4',
  shots: [
    { src: 'Lookbook/oversized-tee-mockup-of-a-man-walking-in-the-city-holding-his-coffee-m25218.png', caption: 'morning walk' },
    { src: 'Lookbook/mockup-featuring-a-man-wearing-a-blinkstore-t-shirt-and-sunglasses-on-the-street-m39313.png', caption: 'street / shades' },
    { src: 'Lookbook/oversized-t-shirt-mockup-of-a-man-posing-with-a-serious-look-in-a-new-pop-wave-themed-setting-m38763.png', caption: 'pop wave' },
    { src: 'Lookbook/back-view-mockup-featuring-a-man-wearing-an-oversized-tee-in-a-new-pop-wave-inspired-aesthetic-m38765_edited_edited.jpg', caption: 'back view' },
    { src: 'Lookbook/stanley-stella-t-shirt-mockup-featuring-a-man-walking-by-the-street-m36226.png', caption: 'stanley / street' },
    { src: 'Lookbook/stanley-stella-tee-mockup-of-a-tattooed-man-posing-in-front-of-a-muscle-car-m36828.png', caption: 'muscle car' },
    { src: 'Lookbook/athleisure-mockup-featuring-a-serious-man-wearing-a-stanley-stella-t-shirt-m36231.png', caption: 'athleisure' },
    { src: 'Lookbook/mockup-of-a-man-wearing-a-round-neck-blinkstore-t-shirt-for-a-pride-month-event-m39064.png', caption: 'pride' },
    { src: 'Lookbook/unisex-t-shirt-mockup-featuring-a-woman-out-and-about-with-a-casual-look-22786.png', caption: 'casual day' },
    { src: 'Lookbook/mockup-of-a-woman-with-colorful-hair-wearing-an-oversized-t-shirt-43020-r-el2.png', caption: 'colorful' },
    { src: 'Lookbook/oversized-t-shirt-mockup-of-a-happy-woman-with-silver-hair-43087-r-el2.png', caption: 'silver' },
    { src: 'Lookbook/unisex-t-shirt-mockup-of-a-woman-with-colored-hair-and-a-big-flower-bouquet-43019-r-el2.png', caption: 'bouquet' },
  ].map(s => ({ ...s, src: s.src.split('/').map(encodeURIComponent).join('/') })),
};
LOOKBOOK_ASSETS.video = LOOKBOOK_ASSETS.video.split('/').map(encodeURIComponent).join('/');

function LookbookContent({ onViewImage }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <div style={{
        border: '2px inset #c0c0c0',
        background: '#000',
        position: 'relative',
        overflow: 'hidden',
        display: 'flex',
        justifyContent: 'center',
      }}>
        <video
          src={LOOKBOOK_ASSETS.video}
          autoPlay
          loop
          muted
          playsInline
          controls
          style={{ width: 'auto', height: 480, maxWidth: '100%', display: 'block', background: '#000' }}
        />
        <div style={{
          position: 'absolute', top: 4, left: 4,
          background: '#ff00aa', color: '#fff', fontSize: 9,
          padding: '1px 4px', fontFamily: 'monospace', letterSpacing: 1,
          border: '1px solid #000',
        }}>
          ● REC — SPRING 95
        </div>
      </div>

      <div style={{
        fontSize: 10, fontFamily: 'monospace', textAlign: 'center',
        background: '#000', color: '#6cf2ff', padding: '2px 4px',
        letterSpacing: 2, border: '1px solid #000',
      }}>
        ✦ ASTRAL.LOOKBOOK / VOL.01 ✦
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }}>
        {LOOKBOOK_ASSETS.shots.map((s, i) => (
          <div
            key={i}
            onClick={() => onViewImage && onViewImage({ ...s, index: i })}
            style={{
              aspectRatio: '4/5',
              background: '#fff',
              border: '1px solid #000',
              position: 'relative',
              overflow: 'hidden',
              cursor: 'pointer',
            }}
          >
            <ImgWithLoader src={s.src} alt={s.caption} loaderSize={32} />
            <div style={{
              position: 'absolute', bottom: 2, left: 2,
              background: '#000', color: '#fff', fontSize: 8,
              padding: '1px 3px', fontFamily: 'monospace',
            }}>
              {String(i+1).padStart(2,'0')} — {s.caption}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function LookbookImageContent({ image }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <div style={{
        border: '2px inset #c0c0c0',
        background: '#000',
        padding: 4,
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        minHeight: 360,
      }}>
        <img
          src={image.src}
          alt={image.caption}
          style={{ maxWidth: '100%', maxHeight: 420, display: 'block' }}
        />
      </div>
      <div style={{
        fontSize: 10, fontFamily: 'monospace', textAlign: 'center',
        background: '#000', color: '#6cf2ff', padding: '3px 4px',
        letterSpacing: 1, border: '1px solid #000',
      }}>
        {typeof image.index === 'number' ? `${String(image.index+1).padStart(2,'0')} — ` : ''}{image.caption}
      </div>
    </div>
  );
}

// FileManagerContent — fake "My Computer" file browser. Treats the live
// catalog as a filesystem: collections are folders, products are .txt
// files inside them. View modes mirror Win95 (Icons / List / Details).
// Double-clicking a product file opens the product detail window via the
// onView prop. SYSTEM/ contains a couple flavor entries that route to
// existing windows (about, help).
function FileManagerContent({ onView, onOpen }) {
  const [path, setPath] = React.useState(['C:']);
  const [view, setView] = React.useState('icons'); // icons | list | details
  const [selected, setSelected] = React.useState(null);
  const lastTap = React.useRef({ key: null, t: 0 });

  // Build the tree once. C:\ contains COLLECTIONS\ and SYSTEM\. Each
  // collection folder lists its products as .txt files.
  const tree = React.useMemo(() => {
    const root = { type: 'folder', name: 'C:', children: {} };
    const collections = { type: 'folder', name: 'COLLECTIONS', children: {} };
    COLLECTIONS.forEach((c) => {
      const folder = { type: 'folder', name: c.name.toUpperCase(), children: {}, color: c.color };
      const inCol = PRODUCTS.filter((p) => (p.cols || []).includes(c.id));
      inCol.forEach((p) => {
        folder.children[`${p.slug}.txt`] = { type: 'file', name: `${p.slug}.txt`, product: p };
      });
      collections.children[c.name.toUpperCase()] = folder;
    });
    const system = { type: 'folder', name: 'SYSTEM', children: {
      'README.TXT':  { type: 'shortcut', name: 'README.TXT',  target: 'about' },
      'HELP.TXT':    { type: 'shortcut', name: 'HELP.TXT',    target: 'help' },
      'CONTACT.EXE': { type: 'shortcut', name: 'CONTACT.EXE', target: 'contact' },
      'CHANGELOG.TXT': { type: 'shortcut', name: 'CHANGELOG.TXT', target: 'soon' },
    } };
    root.children['COLLECTIONS'] = collections;
    root.children['SYSTEM'] = system;
    return root;
  }, []);

  // Walk the tree to the current path and return the entries to render.
  const node = React.useMemo(() => {
    let n = tree;
    for (let i = 1; i < path.length; i++) {
      n = n.children[path[i]];
      if (!n) return tree;
    }
    return n;
  }, [path, tree]);
  const entries = node.children
    ? Object.values(node.children).sort((a, b) => {
        if (a.type !== b.type) return a.type === 'folder' ? -1 : 1;
        return a.name.localeCompare(b.name);
      })
    : [];

  const open = (entry) => {
    if (entry.type === 'folder') {
      setPath((p) => [...p, entry.name]);
      setSelected(null);
    } else if (entry.type === 'file' && entry.product) {
      onView && onView(entry.product);
    } else if (entry.type === 'shortcut' && entry.target) {
      onOpen && onOpen(entry.target);
    }
  };

  const tapEntry = (entry) => {
    const key = entry.name;
    const t = Date.now();
    if (lastTap.current.key === key && t - lastTap.current.t < 400) {
      open(entry);
    } else {
      setSelected(key);
    }
    lastTap.current = { key, t };
  };

  const goUp = () => setPath((p) => p.length > 1 ? p.slice(0, -1) : p);

  const iconFor = (entry) => {
    if (entry.type === 'folder') return '📁';
    if (entry.type === 'shortcut') return '⚡';
    return '📄';
  };
  const sizeFor = (entry) => {
    if (entry.type === 'folder') {
      const n = entry.children ? Object.keys(entry.children).length : 0;
      return `${n} item${n === 1 ? '' : 's'}`;
    }
    if (entry.product) return `₹${entry.product.price}`;
    return '—';
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', fontSize: 11 }}>
      {/* Toolbar */}
      <div style={{ display: 'flex', gap: 4, padding: 4, borderBottom: '1px solid #888', background: '#c0c0c0', alignItems: 'center' }}>
        <button className="w95-btn" disabled={path.length <= 1} onClick={goUp}
          style={{ padding: '2px 6px', fontSize: 10 }}>↑ Up</button>
        <div style={{ flex: 1, padding: '2px 6px', background: '#fff', border: '1px solid #888', fontFamily: 'monospace', fontSize: 10, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
          {path.join('\\') + '\\'}
        </div>
        <div style={{ display: 'flex', gap: 0 }}>
          {[['icons', '◫'], ['list', '☰'], ['details', '☷']].map(([k, ic]) => (
            <button key={k} className="w95-btn"
              aria-pressed={view === k}
              onClick={() => setView(k)}
              style={{
                padding: '2px 6px', fontSize: 11,
                background: view === k ? '#888' : undefined,
                color: view === k ? '#fff' : undefined,
              }}>{ic}</button>
          ))}
        </div>
      </div>

      {/* Body */}
      <div className="w95-inset w95-scroll" style={{ flex: 1, background: '#fff', overflow: 'auto', minHeight: 0, padding: 4 }}>
        {view === 'icons' ? (
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, 70px)', gap: 6 }}>
            {entries.map((entry) => (
              <div key={entry.name} onClick={() => tapEntry(entry)}
                style={{
                  display: 'flex', flexDirection: 'column', alignItems: 'center',
                  padding: 4, cursor: 'pointer',
                  background: selected === entry.name ? '#000080' : 'transparent',
                  color: selected === entry.name ? '#fff' : '#000',
                }}>
                <div style={{ fontSize: 24, lineHeight: 1, marginBottom: 2 }}>{iconFor(entry)}</div>
                <div style={{ fontSize: 9, textAlign: 'center', wordBreak: 'break-all', lineHeight: 1.1 }}>
                  {entry.name}
                </div>
              </div>
            ))}
          </div>
        ) : view === 'list' ? (
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 1 }}>
            {entries.map((entry) => (
              <div key={entry.name} onClick={() => tapEntry(entry)}
                style={{
                  padding: '2px 4px', cursor: 'pointer',
                  background: selected === entry.name ? '#000080' : 'transparent',
                  color: selected === entry.name ? '#fff' : '#000',
                  display: 'flex', gap: 4, alignItems: 'center',
                  overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                }}>
                <span>{iconFor(entry)}</span>
                <span style={{ fontSize: 10, overflow: 'hidden', textOverflow: 'ellipsis' }}>{entry.name}</span>
              </div>
            ))}
          </div>
        ) : (
          <div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 80px 60px', gap: 4, padding: '2px 4px', borderBottom: '1px solid #888', fontWeight: 'bold', fontSize: 9, color: '#444' }}>
              <span>Name</span><span>Size</span><span>Type</span>
            </div>
            {entries.map((entry) => (
              <div key={entry.name} onClick={() => tapEntry(entry)}
                style={{
                  display: 'grid', gridTemplateColumns: '1fr 80px 60px', gap: 4,
                  padding: '2px 4px', cursor: 'pointer', fontSize: 10,
                  background: selected === entry.name ? '#000080' : 'transparent',
                  color: selected === entry.name ? '#fff' : '#000',
                }}>
                <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                  {iconFor(entry)} {entry.name}
                </span>
                <span style={{ fontFamily: 'monospace' }}>{sizeFor(entry)}</span>
                <span style={{ color: '#666' }}>{entry.type}</span>
              </div>
            ))}
          </div>
        )}
        {entries.length === 0 && (
          <div style={{ padding: 16, textAlign: 'center', color: '#666', fontSize: 10 }}>
            this folder is empty
          </div>
        )}
      </div>

      {/* Status bar */}
      <div style={{
        padding: '2px 6px', background: '#c0c0c0', borderTop: '1px solid #fff',
        fontSize: 10, color: '#000', display: 'flex', justifyContent: 'space-between',
      }}>
        <span>{entries.length} object(s)</span>
        <span>{selected || 'Select an item to view properties'}</span>
      </div>
    </div>
  );
}

Object.assign(window, {
  PRODUCTS, COLLECTIONS, ProductThumb, ProductSilhouette, HeartButton,
  ShopGridContent, ProductDetailContent, CollectionsContent, AboutContent,
  CartContent, TrackOrderContent, HelpContent, ComingSoonContent, LookbookContent, LookbookImageContent,
  ChatExeContent, FileManagerContent,
});
