/* ============================================================
   SVLM — shared UI kit: icons, buttons, chips, tiles, image slots
   Ported verbatim from the Claude Design project (app/ui.jsx) — pure
   presentation, no mock-data dependency, so no adaptation needed.
   ============================================================ */
const P = window.PALETTE;
const S = window.SHAPE;
const SH = window.SHADOW;

/* ---- Type scale, resolved per language.
   window.typeFor gives Thai its own line-heights and drops the design's negative tracking; see the
   note beside TYPE_LATIN in data-adapter.js for why that is not optional. ---- */
function txt(scale, lang, extra) {
  return Object.assign({}, window.typeFor(scale, lang), extra || {});
}

/* ---- Viewport class.
   The reference kit is a 375px phone design. On a desktop browser the same layout stranded in a
   480px column wastes the screen, so the shell asks this hook whether it may spread out. Declared
   with React.useState rather than a destructured useState because ui.jsx loads before app.jsx,
   where that destructuring lives. ---- */
function useIsDesktop(min = 900) {
  const query = '(min-width: ' + min + 'px)';
  const [wide, setWide] = React.useState(
    () => typeof window.matchMedia === 'function' && window.matchMedia(query).matches
  );
  React.useEffect(() => {
    if (typeof window.matchMedia !== 'function') return undefined;
    const mq = window.matchMedia(query);
    const onChange = (e) => setWide(e.matches);
    setWide(mq.matches);
    mq.addEventListener('change', onChange);
    return () => mq.removeEventListener('change', onChange);
  }, [query]);
  return wide;
}

/* ---- Minimal line icons (stroke-based, single-weight) ---- */
function Icon({ name, size = 24, color = 'currentColor', sw = 1.8, fill = 'none' }) {
  const common = { width: size, height: size, viewBox: '0 0 24 24', fill, stroke: color,
    strokeWidth: sw, strokeLinecap: 'round', strokeLinejoin: 'round' };
  const paths = {
    sparkle: <><path d="M12 3l1.8 4.6L18.5 9.4 13.8 11.2 12 16l-1.8-4.8L5.5 9.4l4.7-1.8L12 3z"/><path d="M19 14l.7 1.8 1.8.7-1.8.7L19 19l-.7-1.8-1.8-.7 1.8-.7L19 14z"/></>,
    shield: <><path d="M12 3l7 3v5c0 4.4-3 7.7-7 9-4-1.3-7-4.6-7-9V6l7-3z"/><path d="M9 12l2 2 4-4"/></>,
    key: <><circle cx="8" cy="8" r="4"/><path d="M11 11l8 8m-3-3l2-2m-4 0l2-2"/></>,
    snow: <><path d="M12 3v18M5 7l14 10M19 7L5 17"/><path d="M12 3l-2 2m2-2l2 2M12 21l-2-2m2 2l2-2M5 7l.3 2.7M5 7l2.7-.3M19 17l-.3-2.7M19 17l-2.7.3M19 7l-2.7.3M19 7l-.3 2.7M5 17l2.7.3M5 17l.3-2.7"/></>,
    bug: <><path d="M9 9V7a3 3 0 016 0v2"/><rect x="7" y="9" width="10" height="9" rx="5"/><path d="M3 11h4m10 0h4M4 16h3m10 0h3M5 7l2 2m12-2l-2 2"/></>,
    wrench: <path d="M15 7a4 4 0 00-5.2 5.2L4 18l2 2 5.8-5.8A4 4 0 0017 9.5L14.5 12 12 9.5 14.5 7z"/>,
    drop: <path d="M12 3s6 6.4 6 10.5A6 6 0 116 13.5C6 9.4 12 3 12 3z"/>,
    bolt: <path d="M13 3L5 13h5l-1 8 8-11h-5l1-7z"/>,
    roller: <><rect x="4" y="4" width="13" height="6" rx="1.5"/><path d="M17 7h2.5a1.5 1.5 0 011.5 1.5V11a1.5 1.5 0 01-1.5 1.5H11A1.5 1.5 0 009.5 14v1"/><rect x="8" y="15" width="3" height="6" rx="1"/></>,
    box: <><path d="M4 8l8-4 8 4v8l-8 4-8-4V8z"/><path d="M4 8l8 4 8-4M12 12v8"/></>,
    home: <><path d="M4 11l8-7 8 7"/><path d="M6 10v9h12v-9"/></>,
    grid: <><rect x="4" y="4" width="6" height="6" rx="1.5"/><rect x="14" y="4" width="6" height="6" rx="1.5"/><rect x="4" y="14" width="6" height="6" rx="1.5"/><rect x="14" y="14" width="6" height="6" rx="1.5"/></>,
    cal: <><rect x="4" y="5" width="16" height="16" rx="2.5"/><path d="M4 9h16M8 3v4m8-4v4"/></>,
    user: <><circle cx="12" cy="8" r="3.5"/><path d="M5 20c0-3.6 3.1-6 7-6s7 2.4 7 6"/></>,
    cart: <><circle cx="9" cy="20" r="1.4"/><circle cx="17" cy="20" r="1.4"/><path d="M3 4h2l2.2 11.2A1.5 1.5 0 008.7 16.5H17a1.5 1.5 0 001.5-1.2L20 7H6"/></>,
    clock: <><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2"/></>,
    pin: <><path d="M12 21s7-5.6 7-11a7 7 0 10-14 0c0 5.4 7 11 7 11z"/><circle cx="12" cy="10" r="2.6"/></>,
    star: <path d="M12 4l2.3 5 5.2.5-3.9 3.5 1.2 5.1L12 16.9 7.2 18.6l1.2-5.1L4.5 9.5 9.7 9 12 4z"/>,
    check: <path d="M5 12.5l4.5 4.5L19 6.5"/>,
    chevR: <path d="M9 6l6 6-6 6"/>,
    chevL: <path d="M15 6l-6 6 6 6"/>,
    chevD: <path d="M6 9l6 6 6-6"/>,
    plus: <path d="M12 5v14M5 12h14"/>,
    minus: <path d="M5 12h14"/>,
    x: <path d="M6 6l12 12M18 6L6 18"/>,
    phone: <path d="M5 4h3l1.5 5-2 1.5a12 12 0 006 6l1.5-2 5 1.5v3a2 2 0 01-2 2A16 16 0 013 6a2 2 0 012-2z"/>,
    nav: <path d="M3 11l18-8-8 18-2.5-7.5L3 11z"/>,
    bell: <><path d="M6 9a6 6 0 1112 0c0 5 2 6 2 6H4s2-1 2-6z"/><path d="M10.5 20a2 2 0 003 0"/></>,
    trash: <><path d="M5 7h14M9 7V5a2 2 0 012-2h2a2 2 0 012 2v2m-9 0l1 13a2 2 0 002 2h6a2 2 0 002-2l1-13"/></>,
    archive: <><rect x="4" y="5" width="16" height="4" rx="1"/><path d="M5 9v9a1.5 1.5 0 001.5 1.5h11A1.5 1.5 0 0019 18V9M10 13h4"/></>,
    search: <><circle cx="10.5" cy="10.5" r="6.5"/><path d="M20 20l-4.8-4.8"/></>,
    wallet: <><rect x="3" y="6" width="18" height="13" rx="2.5"/><path d="M3 10h18M17 14h.01"/></>,
    promo: <><path d="M4 8l4-4h4l8 8-8 8-8-8V8z" /><circle cx="9" cy="9" r="1.3"/></>,
    globe: <><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17M12 3.5c2.5 2.4 2.5 14.6 0 17M12 3.5c-2.5 2.4-2.5 14.6 0 17"/></>,
    chat: <path d="M5 5h14a1.5 1.5 0 011.5 1.5V15A1.5 1.5 0 0119 16.5H9l-4 3.5V6.5A1.5 1.5 0 016.5 5z"/>,
    edit: <path d="M14 5l5 5M4 20l1-4L16 5l3 3L8 19l-4 1z"/>,
    logout: <><path d="M15 4h3a1.5 1.5 0 011.5 1.5v13A1.5 1.5 0 0118 20h-3"/><path d="M10 12H3m0 0l3-3m-3 3l3 3"/></>,
    repeat: <path d="M4 9a6 6 0 0110-2.5L18 9M18 9V5m0 4h-4M20 15a6 6 0 01-10 2.5L6 15m0 0v4m0-4h4"/>,
    badge: <><circle cx="12" cy="9" r="5"/><path d="M9 13l-1.5 7L12 18l4.5 2L15 13"/></>,
    sofa: <><path d="M5 11V8.5A2.5 2.5 0 017.5 6h9A2.5 2.5 0 0119 8.5V11"/><path d="M3 12.5A2 2 0 015 14.5V17h14v-2.5a2 2 0 014 0V12.5a2 2 0 00-2-2 2 2 0 00-2 2v1H7v-1a2 2 0 00-2-2 2 2 0 00-2 2z" fill="none"/><path d="M6 17v2m12-2v2"/></>,
    bed: <><path d="M3 8v11M3 13h18v6M21 13v6M7 13v-2.5A1.5 1.5 0 018.5 9h7a1.5 1.5 0 011.5 1.5V13"/></>,
    carpet: <><rect x="4" y="6" width="16" height="12" rx="1"/><path d="M4 9.5h16M4 14.5h16M3 6l1.4-2m15.6 2l-1.4-2M3 18l1.4 2m15.6-2l-1.4 2"/></>,
    building: <><rect x="5" y="3" width="14" height="18" rx="1.5"/><path d="M9 7h2m2 0h2M9 11h2m2 0h2M9 15h2m2 0h2M4 21h16"/></>,
    tank: <><rect x="6" y="7" width="12" height="14" rx="3"/><path d="M9 7V5a3 3 0 016 0v2M6 12h12"/></>,
    washer: <><rect x="5" y="3" width="14" height="18" rx="2"/><circle cx="12" cy="13" r="4"/><path d="M8 6h.01M11 6h.01"/></>,
    flame: <path d="M12 3c3 3 4 5 4 7.5a4 4 0 01-8 0c0-1.2.6-2.2 1.6-3.2-.2 1.2.4 1.7 1.4 1.7 1 0 1-1.2 1-2.2 0-1.6 0-2.6 0-3.8z"/>,
    camera: <><path d="M4 8.6l11-3a1 1 0 011.2.7l.7 2.6a1 1 0 01-.7 1.2l-11 3z"/><path d="M6 12.5V16a2 2 0 002 2h3M17 7l3-1"/></>,
    tv: <><rect x="3" y="5" width="18" height="12" rx="2"/><path d="M8 21h8m-4-4v4"/></>,
    curtain: <><path d="M3 4h18M4 4v16c3 0 4-2 4-6V4M20 4v16c-3 0-4-2-4-6V4"/></>,
    umbrella: <><path d="M12 3a9 9 0 019 8H3a9 9 0 019-8z"/><path d="M12 11v7a2 2 0 003 0"/></>,
    spray: <><path d="M9 8.5h5l1.6 2.6V20a1 1 0 01-1 1H8.4a1 1 0 01-1-1v-8.9z"/><path d="M9 8.5V5h3V3M16 5.5h2M16 8.5h2M19.5 4h.01M19.5 7h.01"/></>,
    plant: <><path d="M12 21v-8M12 13c0-3-2-5-5.5-5C6.5 11.5 8.5 13 12 13zM12 11c0-3 2-5 5.5-5C17.5 9.5 15.5 11 12 11zM8 21h8"/></>,
    truck: <><path d="M3 6.5h11v9H3zM14 9.5h4l3 3v3h-7z"/><circle cx="7" cy="18" r="1.6"/><circle cx="17" cy="18" r="1.6"/></>,
    fridge: <><rect x="6" y="3" width="12" height="18" rx="2"/><path d="M6 10h12M9.5 6v2M9.5 13v3"/></>,
    share: <><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.7l7.4-4.4M8.3 13.3l7.4 4.4"/></>,
    facebook: <><circle cx="12" cy="12" r="9"/><path d="M14 8.5h-1.5A2 2 0 0010.5 10.5V12H9v2.5h1.5V21h2.5v-6.5H15l.5-2.5h-2v-1.2c0-.5.3-.8.8-.8H15V8.5z" fill="currentColor" stroke="none"/></>,
    instagram: <><rect x="3.5" y="3.5" width="17" height="17" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17" cy="7" r="1.1" fill="currentColor" stroke="none"/></>,
    tiktok: <path d="M15 3v10.5a3.3 3.3 0 11-3.3-3.3c.3 0 .5 0 .8.1V13a1.6 1.6 0 101.6 1.6V3h.9a4.4 4.4 0 004.4 4.4V10a6.9 6.9 0 01-4.4-1.6V3H15z" fill="currentColor" stroke="none"/>,
  };
  return <svg {...common}>{paths[name] || null}</svg>;
}

/* ---- Striped image placeholder (used only where no real photo is set) ---- */
function Shot({ label, h = 150, r = 18, tone = 'blue', style = {} }) {
  const tones = {
    blue: ['#fde4c8', '#f9d3a8', '#cf8a33'],
    navy: ['#eaddca', '#dcc7ab', '#8a6336'],
    mint: ['#d3efe6', '#bfe6d8', '#2f9a7c'],
  };
  const [c1, c2, tx] = tones[tone] || tones.blue;
  return (
    <div style={{
      height: h, borderRadius: r, position: 'relative', overflow: 'hidden',
      background: `repeating-linear-gradient(135deg, ${c1} 0 14px, ${c2} 14px 28px)`,
      display: 'flex', alignItems: 'center', justifyContent: 'center', ...style,
    }}>
      {label && <span style={{
        fontFamily: 'ui-monospace, "SF Mono", Menlo, monospace', fontSize: 11,
        letterSpacing: 0.4, color: tx, background: 'rgba(255,255,255,0.72)',
        padding: '4px 9px', borderRadius: 999, fontWeight: 600,
      }}>{label}</span>}
    </div>
  );
}

/* ---- Share a link via the OS share sheet (Facebook/LINE/IG/TikTok all appear there on a real
   phone) — Web Share API level 1, so no per-platform share-URL wiring needed. Falls back to
   clipboard on desktop browsers that don't support navigator.share (e.g. most desktop Chrome/
   Firefox) — returns 'copied' so the caller can show its own confirmation toast/alert. ---- */
async function shareLink(url, title, text) {
  if (navigator.share) {
    try { await navigator.share({ title, text, url }); return 'shared'; }
    catch (e) { return e.name === 'AbortError' ? 'cancelled' : 'failed'; }
  }
  try { await navigator.clipboard.writeText(url); return 'copied'; }
  catch (e) { return 'failed'; }
}

/* ---- Real photo, falling back to Shot when a service/product has none uploaded ---- */
function Photo({ src, label, h = 150, r = 18, tone = 'blue', style = {} }) {
  if (!src) return <Shot label={label} h={h} r={r} tone={tone} style={style} />;
  return <img src={src} alt={label || ''} style={{ width: '100%', height: h, borderRadius: r, objectFit: 'cover', display: 'block', ...style }} />;
}

/* ---- Service glyph tile (rounded square w/ icon) ---- */
function SvcTile({ name, size = 52, on = false }) {
  return (
    <div style={{
      width: size, height: size, borderRadius: size * 0.30, flexShrink: 0,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      background: on ? P.primary : P.tintBlue,
      transition: 'all .2s',
    }}>
      <Icon name={name} size={size * 0.5} color={on ? '#fff' : P.primary} sw={1.7} />
    </div>
  );
}

/* ---- Button ---- */
function Btn({ children, onClick, variant = 'primary', size = 'lg', disabled, style = {}, icon }) {
  const base = {
    border: 'none', borderRadius: 16, cursor: disabled ? 'default' : 'pointer',
    fontFamily: 'inherit', fontWeight: 700, display: 'inline-flex', alignItems: 'center',
    justifyContent: 'center', gap: 8, width: '100%', transition: 'transform .08s, filter .15s',
    opacity: disabled ? 0.45 : 1, WebkitTapHighlightColor: 'transparent',
  };
  const sizes = { lg: { height: 54, fontSize: 16.5 }, md: { height: 46, fontSize: 15 }, sm: { height: 38, fontSize: 13.5, borderRadius: 12, width: 'auto', padding: '0 16px' } };
  const variants = {
    primary: { background: P.primary, color: '#fff', boxShadow: '0 8px 20px rgba(238,123,22,0.30)' },
    dark:    { background: P.navy, color: '#fff' },
    mint:    { background: P.mint, color: '#fff', boxShadow: '0 8px 20px rgba(15,163,127,0.26)' },
    soft:    { background: P.tintBlue, color: P.primary, boxShadow: 'none' },
    ghost:   { background: '#fff', color: P.ink, border: `1.5px solid ${P.line}`, boxShadow: 'none' },
  };
  return (
    <button disabled={disabled} onClick={onClick}
      onMouseDown={e => !disabled && (e.currentTarget.style.transform = 'scale(0.97)')}
      onMouseUp={e => (e.currentTarget.style.transform = 'scale(1)')}
      onMouseLeave={e => (e.currentTarget.style.transform = 'scale(1)')}
      style={{ ...base, ...sizes[size], ...variants[variant], ...style }}>
      {icon && <Icon name={icon} size={size === 'sm' ? 17 : 20} color="currentColor" sw={2} />}
      {children}
    </button>
  );
}

/* ---- Price with an optional crossed-out "was" price — storewide sale campaign display
   (Ops -> Services/Products -> "Storewide sale"). `original` is the pre-sale price, already
   VAT-inclusive, or null/undefined when nothing is on sale for this row. ---- */
function PriceTag({ price, original, size = 'md' }) {
  const big = size === 'lg';
  return (
    <span style={{ display: 'inline-flex', alignItems: 'baseline', gap: 6, flexWrap: 'wrap' }}>
      {original && original > price && (
        <span style={{ fontSize: big ? 13 : 11, color: P.faint, textDecoration: 'line-through' }}>{baht(original)}</span>
      )}
      <b style={{ color: P.primary }}>{baht(price)}</b>
    </span>
  );
}

/* ---- "From ฿x", or the reason there is no list price ----
   A transport-priced service (Move House) has no price of its own — the whole charge is the
   vehicle/distance fee worked out once the customer enters their addresses. Rendering the raw
   "From ฿0" there reads as free, so these show what actually sets the price instead. Used
   everywhere a service price appears, so the catalogue, the category grid and the share text can't
   disagree with each other. */
function isTransportPriced(s) {
  return !!(s && s.deliveryMode) && Number(s.from ?? 0) === 0;
}

function ServicePrice({ app, s, size = 'md' }) {
  if (isTransportPriced(s)) {
    return (
      <span style={{ fontSize: 11.5, color: P.muted, fontWeight: 600 }}>
        {app.t('price_by_distance') || 'Priced by distance'}
      </span>
    );
  }
  return (
    <>
      <span style={{ fontSize: 11, color: P.faint }}>{app.t('from_price') || 'From'} </span>
      <PriceTag price={app.priceVat(s)} original={app.priceVatOriginal(s)} size={size} />
    </>
  );
}

/* ---- Small star rating row ---- */
function Stars({ value, jobs, size = 13 }) {
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 13, color: P.muted, fontWeight: 600 }}>
      <Icon name="star" size={size} color={P.star} fill={P.star} sw={0} />
      <span style={{ color: P.ink }}>{value}</span>
      {jobs && <span style={{ color: P.faint, fontWeight: 500 }}>({jobs})</span>}
    </span>
  );
}

/* ---- Pill chip ---- */
function Chip({ children, on, onClick, style = {} }) {
  return (
    <button onClick={onClick} style={{
      border: on ? `1.5px solid ${P.primary}` : `1.5px solid ${P.line}`,
      background: on ? P.primary : '#fff', color: on ? '#fff' : P.muted,
      borderRadius: S.chip, padding: '8px 15px', fontFamily: 'inherit', fontSize: 13.5,
      fontWeight: 600, cursor: 'pointer', whiteSpace: 'nowrap', flexShrink: 0,
      WebkitTapHighlightColor: 'transparent', ...style,
    }}>{children}</button>
  );
}

/* ---- Section header ---- */
function SectionHead({ title, action, onAction }) {
  return (
    <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', margin: '0 0 12px' }}>
      <h3 style={{ margin: 0, fontSize: 18, fontWeight: 800, color: P.ink, letterSpacing: -0.3 }}>{title}</h3>
      {action && <button onClick={onAction} style={{ border: 'none', background: 'none', color: P.primary, fontFamily: 'inherit', fontWeight: 700, fontSize: 13.5, cursor: 'pointer' }}>{action}</button>}
    </div>
  );
}

/* ---- Card surface ---- */
function Card({ children, style = {}, onClick, pad = 16 }) {
  return (
    <div onClick={onClick} style={{
      background: '#fff', borderRadius: S.card, padding: pad,
      boxShadow: SH.card,
      cursor: onClick ? 'pointer' : 'default', WebkitTapHighlightColor: 'transparent', ...style,
    }}>{children}</div>
  );
}

/* ---- Sticky bottom action bar (above home indicator) ---- */
function BottomBar({ children }) {
  return (
    <div style={{
      position: 'fixed', left: 0, right: 0, bottom: 0, zIndex: 30, maxWidth: 480, margin: '0 auto',
      padding: '14px 20px calc(20px + env(safe-area-inset-bottom))', background: 'linear-gradient(to top, #fff 62%, rgba(255,255,255,0))',
    }}>{children}</div>
  );
}

/* ---- Top app header (custom, replaces iOS nav) ---- */
function AppHeader({ left, title, sub, right, onBack, dark }) {
  const tc = dark ? '#fff' : P.ink;
  const sc = dark ? 'rgba(255,255,255,0.7)' : P.muted;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px 20px 10px' }}>
      {onBack && (
        <button onClick={onBack} style={{
          width: 40, height: 40, borderRadius: 13, border: `1px solid ${dark ? 'rgba(255,255,255,0.18)' : P.line}`,
          background: dark ? 'rgba(255,255,255,0.08)' : '#fff', display: 'flex', alignItems: 'center',
          justifyContent: 'center', cursor: 'pointer', flexShrink: 0,
        }}><Icon name="chevL" size={20} color={tc} /></button>
      )}
      {left}
      <div style={{ flex: 1, minWidth: 0 }}>
        {sub && <div style={{ fontSize: 12.5, color: sc, fontWeight: 600 }}>{sub}</div>}
        {title && <div style={{ fontSize: 20, fontWeight: 800, color: tc, letterSpacing: -0.4, lineHeight: 1.15, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{title}</div>}
      </div>
      {right}
    </div>
  );
}

/* ---- SVLM brand lockup.
   The new mark spells SVLM inside the house shape, so setting the word beside it read the same four
   letters twice at two different sizes, and squeezed the mark down to where its own lettering went
   to mush. The mark now stands alone and takes the height the pair used to occupy. showText is kept
   for anywhere a spelled-out wordmark is genuinely wanted. ---- */
function Logo({ h = 26, style = {} }) {
  return <img src="/logo.png" alt="SVLM" style={{ height: h, width: 'auto', display: 'block', ...style }} />;
}
function Brand({ h = 30, dark = false, showText = false }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      <Logo h={h} />
      {showText && <span style={{ fontWeight: 800, fontSize: h * 0.74, letterSpacing: 0.5, color: dark ? '#fff' : P.navy }}>SVLM</span>}
    </div>
  );
}

/* ---- The dark band at the top of a screen, with the white content sheet riding over it.
   This is the reference design's defining move: the brand colour owns the status-bar area and the
   content sits on a sheet whose top corners are rounded into it. Ours is navy where theirs is
   near-black. ---- */
function NavyTop({ children, sheet, lang }) {
  return (
    <div style={{ background: P.navy, minHeight: '100vh' }}>
      <div style={{ paddingTop: 'env(safe-area-inset-top)' }}>{children}</div>
      <div style={{
        background: P.card, borderTopLeftRadius: S.sheet, borderTopRightRadius: S.sheet,
        marginTop: 10, minHeight: '70vh', paddingBottom: 4,
      }}>{sheet}</div>
    </div>
  );
}

/* ---- Category card: a tinted panel with the icon in a paler disc and the label beneath.
   The reference ships three of these across the top of the dashboard. ---- */
function CatCard({ icon, label, tone = 'orange', onClick, lang }) {
  const tones = {
    orange: { bg: '#fff1e2', disc: '#ffdfbd', ink: P.primary },
    navy:   { bg: '#eef3f9', disc: '#d9e5f3', ink: P.navy2 },
    mint:   { bg: '#e6f6f0', disc: '#c9eade', ink: P.mint },
    rose:   { bg: '#fdecec', disc: '#f9d5d5', ink: '#d23a3a' },
  };
  const c = tones[tone] || tones.orange;
  return (
    <button onClick={onClick} style={{
      background: c.bg, border: 'none', borderRadius: S.tile, cursor: 'pointer',
      fontFamily: 'inherit', padding: '16px 8px 14px', display: 'flex', flexDirection: 'column',
      alignItems: 'center', gap: 10, width: '100%', WebkitTapHighlightColor: 'transparent',
    }}>
      <span style={{
        width: 46, height: 46, borderRadius: '50%', background: c.disc,
        display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
      }}><Icon name={icon} size={23} color={c.ink} sw={1.9} /></span>
      <span style={{ ...txt('small', lang), color: P.ink, textAlign: 'center' }}>{label}</span>
    </button>
  );
}

/* ---- Round service tile with an optional corner badge — the reference's "Other Services" row. ---- */
function RoundTile({ icon, label, tone = 'orange', badge, onClick, lang }) {
  const tones = {
    orange: { bg: '#fff1e2', ink: P.primary }, navy: { bg: '#eef3f9', ink: P.navy2 },
    mint: { bg: '#e6f6f0', ink: P.mint }, rose: { bg: '#fdecec', ink: '#d23a3a' },
  };
  const c = tones[tone] || tones.orange;
  return (
    <button onClick={onClick} style={{
      background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', padding: 0,
      display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, width: '100%',
      WebkitTapHighlightColor: 'transparent',
    }}>
      <span style={{ position: 'relative', display: 'block' }}>
        <span style={{
          width: 62, height: 62, borderRadius: '50%', background: c.bg, display: 'flex',
          alignItems: 'center', justifyContent: 'center',
        }}><Icon name={icon} size={27} color={c.ink} sw={1.8} /></span>
        {badge && (
          <span style={{
            position: 'absolute', top: -4, right: -8, background: P.mint, color: '#fff',
            borderRadius: S.pill, padding: '2px 8px', fontSize: 9.5, fontWeight: 800,
            border: '2px solid #fff', whiteSpace: 'nowrap',
          }}>{badge}</span>
        )}
      </span>
      <span style={{ ...txt('small', lang), color: P.ink, textAlign: 'center' }}>{label}</span>
    </button>
  );
}

Object.assign(window, {
  Icon, Shot, Photo, SvcTile, Btn, Stars, Chip, SectionHead, Card, BottomBar, AppHeader, Logo, Brand, PriceTag,
  txt, useIsDesktop, NavyTop, CatCard, RoundTile,
});
