// TownPost Design System — warm community palette + helpers
// Loaded as a Babel script. Exposes everything onto window.
//
// THEME SYSTEM:
//   TP starts in LIGHT. Call applyTheme('dark' | 'light') to swap colors in place.
//   Components that use TP.* will pick up the new colors on next render. The App
//   bumps a `key` on its tree to force a full re-render when theme flips.

const TP_LIGHT = {
  // Backgrounds — warm cream tones
  cream: '#F5EFE6',
  creamDeep: '#EDE4D4',
  paper: '#FBF7F0',
  card: '#FFFFFF',

  // Foregrounds — warm charcoal
  ink: '#1F1B16',
  inkSoft: '#3A332A',
  muted: '#6B6359',
  mutedSoft: '#9A9085',
  hairline: 'rgba(31,27,22,0.08)',
  hairlineStrong: 'rgba(31,27,22,0.14)',

  // Brand primaries
  forest: '#2D5016',
  forestSoft: '#4A7C30',
  moss: '#849C5C',
  terracotta: '#C97B4A',
  terracottaSoft: '#E29D72',
  ochre: '#D4A24C',

  // Status
  alertRed: '#B23A2E',
  alertAmber: '#D4791E',
  alertBlue: '#2E5F8E',
  goodGreen: '#3F7A3A',

  // Tinted surfaces
  forestTint: '#E8EFE0',
  terracottaTint: '#F7E8DC',
  ochreTint: '#F7EED8',
  redTint: '#F4DDD8',
  amberTint: '#F7E5CC',
  blueTint: '#DCE5EE',

  // Stage / page background (outside the device)
  stage: '#EDE4D4',
  stageGrad: '#F4ECDC',

  // Theme tag
  __theme: 'light',
};

// Cool charcoal dark — neutral, modern, iOS/Material-friendly
const TP_DARK = {
  // Backgrounds
  cream: '#15171A',          // app background — deep neutral
  creamDeep: '#0D0F12',       // page/stage
  paper: '#1C1F23',           // cards / chrome surface
  card: '#23272D',            // raised card

  // Foregrounds
  ink: '#F2F4F7',
  inkSoft: '#D6DBE2',
  muted: '#9CA3AE',
  mutedSoft: '#6B727B',
  hairline: 'rgba(255,255,255,0.08)',
  hairlineStrong: 'rgba(255,255,255,0.14)',

  // Brand primaries — lifted for dark legibility
  forest: '#7FB069',          // brighter green that reads on charcoal
  forestSoft: '#A4C98F',
  moss: '#B8C99B',
  terracotta: '#E29D72',
  terracottaSoft: '#F0BC9C',
  ochre: '#E5BC7A',

  // Status — slightly desaturated so they don't glow
  alertRed: '#E26E63',
  alertAmber: '#E89A4D',
  alertBlue: '#7AA3D1',
  goodGreen: '#84C97F',

  // Tinted surfaces — barely-there washes on dark
  forestTint: 'rgba(127,176,105,0.14)',
  terracottaTint: 'rgba(226,157,114,0.14)',
  ochreTint: 'rgba(229,188,122,0.14)',
  redTint: 'rgba(226,110,99,0.14)',
  amberTint: 'rgba(232,154,77,0.14)',
  blueTint: 'rgba(122,163,209,0.16)',

  // Stage / page background
  stage: '#0A0B0D',
  stageGrad: '#15171A',

  __theme: 'dark',
};

// Mutable TP — components read TP.cream etc. We swap the values in place.
const TP = { ...TP_LIGHT };

// Type / radii live outside theme — same in both modes
TP.serif = '"Instrument Serif", "Cormorant Garamond", Georgia, serif';
TP.sans = '"Geist", -apple-system, "SF Pro", system-ui, sans-serif';
TP.mono = '"JetBrains Mono", "Fira Code", ui-monospace, monospace';
TP.r = { sm: 8, md: 12, lg: 16, xl: 22, pill: 9999 };

function applyTheme(name) {
  const src = name === 'dark' ? TP_DARK : TP_LIGHT;
  // Overwrite color keys; keep type/radii intact
  Object.keys(src).forEach((k) => { TP[k] = src[k]; });
}

// React context so children can subscribe and re-render when theme flips.
const TPThemeContext = React.createContext({ theme: 'light', setTheme: () => {} });

function useTPTheme() {
  return React.useContext(TPThemeContext);
}

// ─── Tiny inline icon set (24px stroke icons) ──────────────────────────
function TPIcon({ name, size = 22, color = 'currentColor', stroke = 1.8, fill, filled = false, tint }) {
  const p = { fill: fill || 'none', stroke: color, strokeWidth: stroke, strokeLinecap: 'round', strokeLinejoin: 'round' };
  // Soft duotone fill colour for "filled" tab icons. Defaults to a 14%-opacity wash of the stroke colour.
  const tintFill = tint || (color === 'currentColor' ? 'rgba(45,80,22,0.14)' : color);
  const fp = { fill: tintFill, stroke: 'none' };
  const sp = { fill: 'none', stroke: color, strokeWidth: stroke, strokeLinecap: 'round', strokeLinejoin: 'round' };
  const paths = {
    home: <><path d="M3 11l9-7 9 7v9a1 1 0 01-1 1h-5v-6h-6v6H4a1 1 0 01-1-1z" {...p}/></>,
    feed: <><path d="M4 5h13a2 2 0 012 2v11a2 2 0 002-2V9" {...p}/><path d="M4 5a1 1 0 00-1 1v12a2 2 0 002 2h14" {...p}/><path d="M7 9h7M7 13h7M7 17h4" {...p}/></>,
    alert: <><path d="M12 3l10 17H2z" {...p}/><path d="M12 10v4M12 17h.01" {...p}/></>,
    map: <><path d="M3 6l6-2 6 2 6-2v14l-6 2-6-2-6 2z" {...p}/><path d="M9 4v16M15 6v16" {...p}/></>,
    profile: <><circle cx="12" cy="8" r="4" {...p}/><path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8" {...p}/></>,
    bell: <><path d="M6 8a6 6 0 0112 0c0 7 3 7 3 9H3c0-2 3-2 3-9z" {...p}/><path d="M10 21a2 2 0 004 0" {...p}/></>,
    // Inbox tray — bin with a V-notch in the top edge for incoming items.
    // Adapted from FontAwesome 7 "inbox" silhouette: outer container, inner divider line,
    // and a small downward notch at the dividers where mail slots in.
    mail: <>
      {/* Outer bin: walls slope outward toward the top (matches FA proportions) */}
      <path d="M4 13.5l1.4-7.2A2 2 0 017.4 4.7h9.2a2 2 0 011.96 1.6L20 13.5V18a2 2 0 01-2 2H6a2 2 0 01-2-2z" {...p}/>
      {/* Inbox slot: horizontal divider with the downward V where the tray accepts items */}
      <path d="M4 13.5h4.4l1.1 2.2a1.6 1.6 0 001.45.9h2.1a1.6 1.6 0 001.45-.9l1.1-2.2H20" {...p}/>
    </>,
    search: <><circle cx="11" cy="11" r="7" {...p}/><path d="M21 21l-4.3-4.3" {...p}/></>,
    plus: <><path d="M12 5v14M5 12h14" {...p}/></>,
    chat: <><path d="M21 12a8 8 0 01-11.3 7.3L3 21l1.7-6.7A8 8 0 1121 12z" {...p}/></>,
    heart: <><path d="M12 21s-7-4.5-9.3-9.3C1 8 3 4 7 4c2 0 3.5 1 5 3 1.5-2 3-3 5-3 4 0 6 4 4.3 7.7C19 16.5 12 21 12 21z" {...p}/></>,
    repost: <><path d="M17 4l4 4-4 4M3 12V8a4 4 0 014-4h14M7 20l-4-4 4-4M21 12v4a4 4 0 01-4 4H3" {...p}/></>,
    share: <><circle cx="6" cy="12" r="2.5" {...p}/><circle cx="18" cy="6" r="2.5" {...p}/><circle cx="18" cy="18" r="2.5" {...p}/><path d="M8 11l8-4M8 13l8 4" {...p}/></>,
    bookmark: <><path d="M6 4h12v18l-6-4-6 4z" {...p}/></>,
    pin: <><path d="M12 2v8M12 10c-3 0-5 2-5 4 0 1 4 8 5 8s5-7 5-8c0-2-2-4-5-4z" {...p}/></>,
    location: <><circle cx="12" cy="10" r="3" {...p}/><path d="M12 22s8-7 8-12a8 8 0 10-16 0c0 5 8 12 8 12z" {...p}/></>,
    calendar: <><rect x="3" y="5" width="18" height="16" rx="2" {...p}/><path d="M3 10h18M8 3v4M16 3v4" {...p}/></>,
    tag: <><path d="M3 12V4h8l10 10-8 8z" {...p}/><circle cx="8" cy="9" r="1.5" fill={color} stroke="none"/></>,
    eye: <><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z" {...p}/><circle cx="12" cy="12" r="3" {...p}/></>,
    arrowRight: <><path d="M5 12h14M13 5l7 7-7 7" {...p}/></>,
    arrowUp: <><path d="M12 19V5M5 12l7-7 7 7" {...p}/></>,
    chevronRight: <><path d="M9 6l6 6-6 6" {...p}/></>,
    chevronLeft: <><path d="M15 6l-6 6 6 6" {...p}/></>,
    chevronDown: <><path d="M6 9l6 6 6-6" {...p}/></>,
    close: <><path d="M6 6l12 12M18 6l-12 12" {...p}/></>,
    sun: <><circle cx="12" cy="12" r="4" {...p}/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.5 1.5M17.5 17.5L19 19M5 19l1.5-1.5M17.5 6.5L19 5" {...p}/></>,
    cloud: <><path d="M7 18a5 5 0 010-10 6 6 0 0111 1 4 4 0 010 9z" {...p}/></>,
    leaf: <><path d="M4 20s2-12 16-16c0 14-7 17-12 17a4 4 0 01-4-1z" {...p}/><path d="M4 20l8-8" {...p}/></>,
    shield: <><path d="M12 2l8 4v6c0 5-3.5 9-8 10-4.5-1-8-5-8-10V6z" {...p}/></>,
    car: <><path d="M3 14l2-6h14l2 6v5h-3v-2H6v2H3z" {...p}/><circle cx="7" cy="17" r="1.5" fill={color} stroke="none"/><circle cx="17" cy="17" r="1.5" fill={color} stroke="none"/></>,
    paw: <><circle cx="6" cy="10" r="2" {...p}/><circle cx="10" cy="5" r="2" {...p}/><circle cx="14" cy="5" r="2" {...p}/><circle cx="18" cy="10" r="2" {...p}/><path d="M8 18a4 4 0 018 0c0 2-2 3-4 3s-4-1-4-3z" {...p}/></>,
    cart: <><path d="M3 4h2l3 12h11l2-8H7" {...p}/><circle cx="9" cy="20" r="1.5" fill={color} stroke="none"/><circle cx="18" cy="20" r="1.5" fill={color} stroke="none"/></>,
    coffee: <><path d="M4 8h12v8a4 4 0 01-4 4H8a4 4 0 01-4-4z" {...p}/><path d="M16 10h2a3 3 0 010 6h-2" {...p}/></>,
    book: <><path d="M4 4h7a4 4 0 014 4v12H8a4 4 0 01-4-4z" {...p}/><path d="M20 4h-7a4 4 0 00-4 4v12h7a4 4 0 004-4z" {...p}/></>,
    briefcase: <><rect x="3" y="7" width="18" height="13" rx="2" {...p}/><path d="M9 7V5a2 2 0 012-2h2a2 2 0 012 2v2" {...p}/></>,
    image: <><rect x="3" y="4" width="18" height="16" rx="2" {...p}/><circle cx="9" cy="10" r="1.5" {...p}/><path d="M3 17l6-5 5 4 4-3 3 2" {...p}/></>,
    mic: <><rect x="9" y="3" width="6" height="12" rx="3" {...p}/><path d="M5 11a7 7 0 0014 0M12 18v3" {...p}/></>,
    settings: <><circle cx="12" cy="12" r="3" {...p}/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 11-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 11-4 0v-.09a1.65 1.65 0 00-1-1.51 1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 11-2.83-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 110-4h.09a1.65 1.65 0 001.51-1 1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 112.83-2.83l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 114 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 112.83 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 110 4h-.09a1.65 1.65 0 00-1.51 1z" {...p}/></>,
    filter: <><path d="M3 5h18l-7 9v6l-4-2v-4z" {...p}/></>,
    flag: <><path d="M5 3v18M5 4h13l-3 4 3 4H5" {...p}/></>,
    dot: <><circle cx="12" cy="12" r="3" fill={color} stroke="none"/></>,
    check: <><path d="M5 12l5 5 9-11" {...p}/></>,
    fire: <><path d="M12 3s4 4 4 8a4 4 0 01-8 0c0-2 2-3 2-5 0-1 2-3 2-3z" {...p}/><path d="M12 11s2 1 2 3a2 2 0 11-4 0c0-1 1-2 2-3z" {...p}/></>,
    handshake: <><path d="M3 12l4-4 5 5 4-4 5 5-4 4-2-2-3 3-2-2-3 3z" {...p}/></>,
    // Thumbs-up — used for "Helpful". Cuff at the bottom, four-finger fist with thumb extended up.
    // Stroke-based for the 24-grid; reads cleanly at 12-18px.
    helpful: <>
      {/* Cuff / sleeve */}
      <path d="M3 11h3v9H3z" {...p}/>
      {/* Hand body — palm with thumb extended up and four fingers curled over */}
      <path d="M6 11l4-6c.6-.9 1.7-1.4 2.7-1.2.9.2 1.5 1 1.4 1.9L13.5 9h5.2c1.3 0 2.3 1.2 2.1 2.5l-1.1 6.4c-.2 1.2-1.2 2.1-2.4 2.1H8" {...p}/>
    </>,
    news: <><rect x="3" y="4" width="14" height="16" rx="1" {...p}/><path d="M17 8h4v10a2 2 0 01-4 0M6 8h8M6 12h8M6 16h5" {...p}/></>,
    question: <><circle cx="12" cy="12" r="9" {...p}/><path d="M9.5 9a2.5 2.5 0 015 0c0 2.5-2.5 2-2.5 4M12 17h.01" {...p}/></>,

    // ─── Marketplace category glyphs ──────────────
    catAll: <><rect x="3.5" y="3.5" width="7" height="7" rx="1.2" {...p}/><rect x="13.5" y="3.5" width="7" height="7" rx="1.2" {...p}/><rect x="3.5" y="13.5" width="7" height="7" rx="1.2" {...p}/><rect x="13.5" y="13.5" width="7" height="7" rx="1.2" {...p}/></>,
    catFree: <><path d="M12 21s-7-4.5-9.3-9.3C1 8 3 4 7 4c2 0 3.5 1 5 3 1.5-2 3-3 5-3 4 0 6 4 4.3 7.7C19 16.5 12 21 12 21z" {...p}/></>,
    catFurniture: <><path d="M3 11a3 3 0 016 0v3h6v-3a3 3 0 016 0v7h-2v-3H5v3H3z" {...p}/></>,
    catBike: <><circle cx="6" cy="17" r="3.5" {...p}/><circle cx="18" cy="17" r="3.5" {...p}/><path d="M6 17l4-9h5l3 9M10 8h-2M13 8l3 5" {...p}/></>,
    catKids: <><circle cx="12" cy="6.5" r="2.5" {...p}/><path d="M8 21v-6h-2v-3a4 4 0 014-4h4a4 4 0 014 4v3h-2v6" {...p}/></>,
    catPlants: <><path d="M12 21V11M12 11c0-3-2-5-5-5-1 2 0 5 3 6 1 .3 2 0 2 0M12 11c0-3 2-5 5-5 1 2 0 5-3 6-1 .3-2 0-2 0" {...p}/><path d="M7 21h10" {...p}/></>,
    catTools: <><path d="M14 4a4 4 0 014 4 4 4 0 01-1 2.5l8 8-2 2-8-8A4 4 0 1114 4z" {...p}/><path d="M5 16l-2 2 3 3 2-2" {...p}/></>,
    catMusic: <><path d="M9 18V5l11-2v13" {...p}/><circle cx="6" cy="18" r="3" {...p}/><circle cx="17" cy="16" r="3" {...p}/></>,
    catHome: <><path d="M3 11l9-7 9 7v9a1 1 0 01-1 1h-5v-6h-6v6H4a1 1 0 01-1-1z" {...p}/></>,
    catApparel: <><path d="M8 4h2a2 2 0 004 0h2l4 3-3 3-1-1v11H7V9L6 10 3 7z" {...p}/></>,

    // ─── Premium tab-bar icons (24×24, designed as a set) ──────────────
    // Each icon supports `filled` (duotone fill behind outline) for active states.
    // Stroke weight kept consistent; geometry tuned for optical balance at 22px.

    // Home: gabled cottage with off-center chimney + door cut. Reads warm, residential.
    tabHome: <>
      {filled && <path d="M3.7 11.7L12 4.4l8.3 7.3v7.5a1.6 1.6 0 01-1.6 1.6H5.3a1.6 1.6 0 01-1.6-1.6z" {...fp}/>}
      <path d="M3 11.5L12 3.6l9 7.9" {...sp}/>
      <path d="M5.4 10.6v8.5a1.4 1.4 0 001.4 1.4h10.4a1.4 1.4 0 001.4-1.4v-8.5" {...sp}/>
      <path d="M10.4 20.5v-4.6a1.6 1.6 0 011.6-1.6 1.6 1.6 0 011.6 1.6v4.6" {...sp}/>
      <path d="M16.2 5.6V8" {...sp}/>
    </>,

    // Feed: community discussion — chat bubble with three linked circles representing
    // multiple voices in conversation. Adapted from FontAwesome 7 "comments" silhouette
    // (rounded bubble with tail + small connected secondary group).
    tabFeed: <>
      {filled && (
        <path d="M3.4 9.4a5.6 5.6 0 015.6-5.6h4.8a5.6 5.6 0 015.6 5.6v3.2a5.6 5.6 0 01-5.6 5.6H9.6L5.4 21a.6.6 0 01-.95-.5l.6-3.6A5.6 5.6 0 013.4 12.6z" {...fp}/>
      )}
      {/* Main chat bubble with downward-left tail */}
      <path d="M3.4 9.4a5.6 5.6 0 015.6-5.6h4.8a5.6 5.6 0 015.6 5.6v3.2a5.6 5.6 0 01-5.6 5.6H9.6L5.4 21a.6.6 0 01-.95-.5l.6-3.6A5.6 5.6 0 013.4 12.6z" {...sp}/>
      {/* Three participants — circles tracking the bubble's interior to suggest a group chat */}
      <circle cx="8.4" cy="11" r="1.05" {...(filled ? { fill: 'currentColor', stroke: 'none' } : sp)} />
      <circle cx="11.4" cy="11" r="1.05" {...(filled ? { fill: 'currentColor', stroke: 'none' } : sp)} />
      <circle cx="14.4" cy="11" r="1.05" {...(filled ? { fill: 'currentColor', stroke: 'none' } : sp)} />
    </>,

    // Radar: figure with broadcast arcs on either side (community broadcast / scanning)
    // Adapted from FontAwesome 7 "tower-broadcast" silhouette — two pairs of waves + central figure.
    tabAlerts: <>
      {filled && <>
        <circle cx="12" cy="8.4" r="2.4" {...fp}/>
        <rect x="11.1" y="10.2" width="1.8" height="11" rx="0.9" {...fp}/>
      </>}
      {/* Inner waves */}
      <path d="M8.6 5.8a8 8 0 000 9.4" {...sp}/>
      <path d="M15.4 5.8a8 8 0 010 9.4" {...sp}/>
      {/* Outer waves */}
      <path d="M5.8 3.6a11 11 0 000 13.8" {...sp}/>
      <path d="M18.2 3.6a11 11 0 010 13.8" {...sp}/>
      {/* Central figure: head + body */}
      <circle cx="12" cy="8.4" r="2.2" {...sp}/>
      <path d="M12 10.6v10.4" {...sp}/>
    </>,

    // Map: folded map with a single pin. Cleaner creases.
    tabMap: <>
      {filled && <path d="M3.4 7l5.6-2 6 2 5.6-2v12l-5.6 2-6-2-5.6 2z" {...fp}/>}
      <path d="M3.4 7l5.6-2 6 2 5.6-2v12l-5.6 2-6-2-5.6 2z" {...sp}/>
      <path d="M9 5v15M15 7v15" {...sp}/>
      <path d="M12 9.6c-1.4 0-2.4 1-2.4 2.2 0 1.6 2.4 4 2.4 4s2.4-2.4 2.4-4c0-1.2-1-2.2-2.4-2.2z" {...sp}/>
      <circle cx="12" cy="11.7" r="0.7" fill={color} stroke="none"/>
    </>,

    // Profile: head + shoulders with a circular surround that hints at a portrait crop
    tabProfile: <>
      {filled && <circle cx="12" cy="9" r="3.6" {...fp}/>}
      <circle cx="12" cy="9" r="3.6" {...sp}/>
      <path d="M5 19.5c1.4-3 4-4.5 7-4.5s5.6 1.5 7 4.5" {...sp}/>
    </>,

    // Market: refined storefront — clean awning scallops, framed door
    tabMarket: <>
      {filled && <path d="M4.5 9.4l1.4-3.6a1 1 0 01.9-.6h10.4a1 1 0 01.9.6l1.4 3.6V19a1 1 0 01-1 1H5.5a1 1 0 01-1-1z" {...fp}/>}
      <path d="M4 9.5l1.5-4a1 1 0 011-.6h11a1 1 0 011 .6L20 9.5" {...sp}/>
      <path d="M4 9.5h16" {...sp}/>
      <path d="M4 9.5c0 1.5 1 2.5 2.5 2.5S9 11 9 9.5 10 12 11.5 12 14 11 14 9.5 15 12 16.5 12 19 11 19 9.5" {...sp}/>
      <path d="M5.5 12V20h13V12" {...sp}/>
      <path d="M10.5 20v-4a1.5 1.5 0 013 0v4" {...sp}/>
    </>,

    // More: 2×3 grid of rounded squares — classic "all apps / launcher" silhouette.
    // Adapted from FontAwesome 7 "grid-2" / app-grid icon.
    tabMore: (() => {
      // Grid layout: 3 columns × 2 rows, centered in the 24-grid.
      // Each cell is 5.4×5.4 with 1.6 gap; square corners use rx=1.6 for soft rounding.
      const cells = [
        [4.4, 4.6], [9.8, 4.6], [15.2, 4.6],
        [4.4, 12.0], [9.8, 12.0], [15.2, 12.0],
      ];
      return (
        <>
          {filled && cells.map(([cx, cy], i) => (
            <rect key={`f${i}`} x={cx} y={cy} width="4.4" height="4.4" rx="1.4" {...fp} />
          ))}
          {cells.map(([cx, cy], i) => (
            <rect key={`s${i}`} x={cx} y={cy} width="4.4" height="4.4" rx="1.4" {...sp} />
          ))}
        </>
      );
    })(),

    // Search (used by some bars in place of Market) — magnifier with crisp handle
    tabSearch: <>
      {filled && <circle cx="11" cy="11" r="6.2" {...fp}/>}
      <circle cx="11" cy="11" r="6.2" {...sp}/>
      <path d="M16 16l4.2 4.2" {...sp}/>
    </>,
  };
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" style={{ flexShrink: 0, display: 'block' }}>
      {paths[name] || paths.dot}
    </svg>
  );
}

// ─── Radar icon with scanning pulse animation ──────────────────────────
// Used in the bottom tab when "Radar" is active, in the radar screen header,
// and as a hero icon. The pulse waves expand outward from the center figure,
// suggesting a broadcast/scan in progress.
function TPRadarIcon({ size = 22, color = 'currentColor', stroke = 1.8, scanning = true, tint }) {
  const sp = { fill: 'none', stroke: color, strokeWidth: stroke, strokeLinecap: 'round', strokeLinejoin: 'round' };
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" style={{ flexShrink: 0, display: 'block', overflow: 'visible' }}>
      <style>{`
        @keyframes tpRadarPulseInner {
          0%   { transform: scale(0.55); opacity: 0; }
          25%  { opacity: 0.9; }
          100% { transform: scale(1.18); opacity: 0; }
        }
        @keyframes tpRadarPulseOuter {
          0%   { transform: scale(0.45); opacity: 0; }
          30%  { opacity: 0.55; }
          100% { transform: scale(1.35); opacity: 0; }
        }
      `}</style>

      {/* Static central figure: head + body */}
      <circle cx="12" cy="8.4" r="2.2" {...sp}/>
      <path d="M12 10.6v10.4" {...sp}/>

      {/* Static base waves (always visible, low opacity if scanning to keep silhouette readable) */}
      <g opacity={scanning ? 0.35 : 1}>
        <path d="M8.6 5.8a8 8 0 000 9.4" {...sp}/>
        <path d="M15.4 5.8a8 8 0 010 9.4" {...sp}/>
        <path d="M5.8 3.6a11 11 0 000 13.8" {...sp}/>
        <path d="M18.2 3.6a11 11 0 010 13.8" {...sp}/>
      </g>

      {/* Scanning waves */}
      {scanning && (
        <>
          {/* Inner pulse */}
          <g style={{ transformOrigin: '12px 10.5px', animation: 'tpRadarPulseInner 1800ms cubic-bezier(.4,.1,.3,1) infinite' }}>
            <path d="M8.6 5.8a8 8 0 000 9.4" {...sp}/>
            <path d="M15.4 5.8a8 8 0 010 9.4" {...sp}/>
          </g>
          {/* Outer pulse — offset so they cascade */}
          <g style={{ transformOrigin: '12px 10.5px', animation: 'tpRadarPulseOuter 1800ms cubic-bezier(.4,.1,.3,1) infinite', animationDelay: '600ms' }}>
            <path d="M5.8 3.6a11 11 0 000 13.8" {...sp}/>
            <path d="M18.2 3.6a11 11 0 010 13.8" {...sp}/>
          </g>
        </>
      )}
    </svg>
  );
}

// ─── Avatar (initials + warm tones) ──────────────────────────
function TPAvatar({ name = 'NA', size = 36, hue = 0, src }) {
  const palettes = [
    ['#E8EFE0', '#2D5016'],   // forest
    ['#F7E8DC', '#A8551E'],   // terracotta
    ['#F7EED8', '#7A5410'],   // ochre
    ['#DCE5EE', '#2E5F8E'],   // blue
    ['#F4DDD8', '#8B2A20'],   // red
    ['#E5DDEC', '#5C3A78'],   // plum
    ['#E0E8DD', '#3F7A3A'],   // grass
    ['#F0E5D8', '#7A4220'],   // tan
  ];
  const [bg, fg] = palettes[hue % palettes.length];
  const initials = name.split(' ').map(s => s[0]).filter(Boolean).slice(0, 2).join('').toUpperCase();
  if (src) {
    return <div style={{ width: size, height: size, borderRadius: '50%', background: src, backgroundSize: 'cover', backgroundPosition: 'center', flexShrink: 0 }} />;
  }
  return (
    <div style={{
      width: size, height: size, borderRadius: '50%',
      background: bg, color: fg, flexShrink: 0,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      fontFamily: TP.sans, fontWeight: 600, fontSize: size * 0.36,
      letterSpacing: 0.2,
    }}>{initials}</div>
  );
}

// ─── Generative placeholder image (warm striped) ──────────────────────────
function TPImage({ label = 'photo', height = 180, hue = 0, kind = 'stripe', radius = 14, onClick, gallery, galleryIndex = 0 }) {
  const themes = [
    ['#E8DFCE', '#D4C5AA'], // tan
    ['#DCE6D5', '#B8C9A8'], // sage
    ['#F0DBC8', '#D9B796'], // peach
    ['#D9DEE6', '#B6BFCC'], // sky
    ['#E4D5C4', '#C0A88A'], // wheat
    ['#D5DDD0', '#9FAE94'], // moss
  ];
  const [a, b] = themes[hue % themes.length];
  const bg = kind === 'sky'
    ? `linear-gradient(180deg, ${b} 0%, ${a} 60%, ${a} 100%)`
    : `repeating-linear-gradient(135deg, ${a} 0 14px, ${b} 14px 28px)`;
  const handleClick = (e) => {
    if (onClick) { onClick(e); return; }
    // Fire global lightbox event
    const detail = gallery && gallery.length
      ? { images: gallery, index: galleryIndex }
      : { images: [{ label, hue, kind }], index: 0 };
    window.dispatchEvent(new CustomEvent('tp-lightbox-open', { detail }));
  };
  return (
    <div onClick={handleClick} style={{
      width: '100%', height, background: bg, position: 'relative',
      display: 'flex', alignItems: 'flex-end', padding: 10, boxSizing: 'border-box',
      borderRadius: radius, overflow: 'hidden',
      cursor: 'zoom-in',
    }}>
      <div style={{
        fontFamily: TP.mono, fontSize: 10, color: 'rgba(31,27,22,0.55)',
        background: 'rgba(255,255,255,0.65)', padding: '2px 6px', borderRadius: 4,
        textTransform: 'uppercase', letterSpacing: 0.6,
      }}>{label}</div>
    </div>
  );
}

// ─── Image gallery: single image OR horizontal slider for multiple ─────────
function TPImageGallery({ images, height = 240, radius = 16 }) {
  if (!images || images.length === 0) return null;
  const list = Array.isArray(images) ? images : [images];
  const scrollerRef = React.useRef(null);
  const [idx, setIdx] = React.useState(0);
  const onScroll = () => {
    const el = scrollerRef.current;
    if (!el) return;
    const w = el.clientWidth;
    const i = Math.round(el.scrollLeft / w);
    if (i !== idx) setIdx(i);
  };
  if (list.length === 1) {
    const img = list[0];
    return <TPImage {...img} height={height} radius={radius} gallery={list} galleryIndex={0} />;
  }
  return (
    <div style={{ position: 'relative' }}>
      <div
        ref={scrollerRef}
        onScroll={onScroll}
        style={{
          display: 'flex', overflowX: 'auto', scrollSnapType: 'x mandatory',
          borderRadius: radius, gap: 0,
        }}>
        {list.map((img, i) => (
          <div key={i} style={{ flex: '0 0 100%', scrollSnapAlign: 'start' }}>
            <TPImage {...img} height={height} radius={0} gallery={list} galleryIndex={i} />
          </div>
        ))}
      </div>
      {/* Counter */}
      <div style={{
        position: 'absolute', top: 10, right: 10,
        background: 'rgba(31,27,22,0.72)', color: '#fff',
        fontFamily: TP.mono, fontSize: 10, fontWeight: 600, letterSpacing: 0.4,
        padding: '4px 8px', borderRadius: TP.r.pill,
        backdropFilter: 'blur(6px)',
      }}>{idx + 1} / {list.length}</div>
      {/* Dots */}
      <div style={{
        position: 'absolute', bottom: 10, left: '50%', transform: 'translateX(-50%)',
        display: 'flex', gap: 5, padding: '5px 8px',
        background: 'rgba(31,27,22,0.45)', borderRadius: TP.r.pill,
        backdropFilter: 'blur(6px)',
      }}>
        {list.map((_, i) => (
          <div key={i} style={{
            width: i === idx ? 16 : 5, height: 5, borderRadius: 999,
            background: i === idx ? '#fff' : 'rgba(255,255,255,0.55)',
            transition: 'width 220ms ease',
          }} />
        ))}
      </div>
    </div>
  );
}

// ─── Lightbox: fullscreen image viewer with swipe/arrow nav ───────────────
function TPLightbox() {
  const [state, setState] = React.useState(null); // { images, index } | null
  const [idx, setIdx] = React.useState(0);
  const scrollerRef = React.useRef(null);

  React.useEffect(() => {
    const onOpen = (e) => {
      setState(e.detail);
      setIdx(e.detail.index || 0);
      document.body.style.overflow = 'hidden';
    };
    window.addEventListener('tp-lightbox-open', onOpen);
    return () => window.removeEventListener('tp-lightbox-open', onOpen);
  }, []);

  React.useEffect(() => {
    const onKey = (e) => {
      if (!state) return;
      if (e.key === 'Escape') close();
      if (e.key === 'ArrowRight') goTo(idx + 1);
      if (e.key === 'ArrowLeft') goTo(idx - 1);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [state, idx]);

  React.useEffect(() => {
    if (state && scrollerRef.current) {
      const w = scrollerRef.current.clientWidth;
      scrollerRef.current.scrollTo({ left: idx * w, behavior: 'instant' });
    }
  }, [state]);

  const close = () => {
    setState(null);
    document.body.style.overflow = '';
  };
  const goTo = (i) => {
    if (!state) return;
    const len = state.images.length;
    const next = Math.max(0, Math.min(len - 1, i));
    setIdx(next);
    if (scrollerRef.current) {
      const w = scrollerRef.current.clientWidth;
      scrollerRef.current.scrollTo({ left: next * w, behavior: 'smooth' });
    }
  };
  const onScroll = () => {
    if (!scrollerRef.current) return;
    const w = scrollerRef.current.clientWidth;
    const i = Math.round(scrollerRef.current.scrollLeft / w);
    if (i !== idx) setIdx(i);
  };

  if (!state) return null;
  const themes = [
    ['#E8DFCE', '#D4C5AA'],['#DCE6D5', '#B8C9A8'],['#F0DBC8', '#D9B796'],
    ['#D9DEE6', '#B6BFCC'],['#E4D5C4', '#C0A88A'],['#D5DDD0', '#9FAE94'],
  ];
  const renderBg = (img) => {
    const [a, b] = themes[(img.hue || 0) % themes.length];
    return img.kind === 'sky'
      ? `linear-gradient(180deg, ${b} 0%, ${a} 60%, ${a} 100%)`
      : `repeating-linear-gradient(135deg, ${a} 0 28px, ${b} 28px 56px)`;
  };

  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 9999,
      background: 'rgba(8, 7, 5, 0.96)',
      display: 'flex', flexDirection: 'column',
      animation: 'tpLbFade 180ms ease',
    }}>
      <style>{`@keyframes tpLbFade { from { opacity: 0; } to { opacity: 1; } }`}</style>
      {/* Top bar */}
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '14px 16px', color: '#fff',
      }}>
        <div style={{ fontFamily: TP.mono, fontSize: 11, letterSpacing: 1.2, fontWeight: 600, opacity: 0.85 }}>
          {state.images.length > 1 ? `${idx + 1} / ${state.images.length}` : 'IMAGE'}
        </div>
        <button onClick={close} style={{
          width: 36, height: 36, borderRadius: '50%',
          background: 'rgba(255,255,255,0.10)', border: 'none', color: '#fff',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer',
        }}>
          <TPIcon name="close" size={20} color="#fff" stroke={2} />
        </button>
      </div>

      {/* Scroller */}
      <div
        ref={scrollerRef}
        onScroll={onScroll}
        style={{
          flex: 1, display: 'flex', overflowX: 'auto', scrollSnapType: 'x mandatory',
          alignItems: 'center',
        }}>
        {state.images.map((img, i) => (
          <div key={i} style={{
            flex: '0 0 100%', scrollSnapAlign: 'center',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            padding: '0 24px', boxSizing: 'border-box',
          }}>
            <div style={{
              width: '100%', maxWidth: 720, aspectRatio: '4 / 3',
              background: renderBg(img), borderRadius: 14,
              boxShadow: '0 30px 80px -20px rgba(0,0,0,0.6)',
              display: 'flex', alignItems: 'flex-end', padding: 18, boxSizing: 'border-box',
            }}>
              <div style={{
                fontFamily: TP.mono, fontSize: 11, color: 'rgba(31,27,22,0.6)',
                background: 'rgba(255,255,255,0.7)', padding: '4px 8px', borderRadius: 4,
                textTransform: 'uppercase', letterSpacing: 0.6,
              }}>{img.label}</div>
            </div>
          </div>
        ))}
      </div>

      {/* Arrow buttons (desktop) */}
      {state.images.length > 1 && idx > 0 && (
        <button onClick={() => goTo(idx - 1)} style={{
          position: 'absolute', left: 16, top: '50%', transform: 'translateY(-50%)',
          width: 44, height: 44, borderRadius: '50%',
          background: 'rgba(255,255,255,0.12)', border: 'none', color: '#fff',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer',
        }}>
          <TPIcon name="chevronLeft" size={22} color="#fff" stroke={2.2} />
        </button>
      )}
      {state.images.length > 1 && idx < state.images.length - 1 && (
        <button onClick={() => goTo(idx + 1)} style={{
          position: 'absolute', right: 16, top: '50%', transform: 'translateY(-50%)',
          width: 44, height: 44, borderRadius: '50%',
          background: 'rgba(255,255,255,0.12)', border: 'none', color: '#fff',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer',
        }}>
          <TPIcon name="chevronRight" size={22} color="#fff" stroke={2.2} />
        </button>
      )}

      {/* Bottom dots */}
      {state.images.length > 1 && (
        <div style={{
          display: 'flex', justifyContent: 'center', gap: 6, padding: '14px 0 26px',
        }}>
          {state.images.map((_, i) => (
            <button key={i} onClick={() => goTo(i)} style={{
              width: i === idx ? 22 : 7, height: 7, borderRadius: 999,
              background: i === idx ? '#fff' : 'rgba(255,255,255,0.4)',
              border: 'none', padding: 0, cursor: 'pointer',
              transition: 'width 220ms ease',
            }} />
          ))}
        </div>
      )}
    </div>
  );
}

// ─── Pill / chip ──────────────────────────
function TPChip({ children, tone = 'default', size = 'md', icon }) {
  const tones = {
    default: { bg: '#fff', fg: TP.inkSoft, border: TP.hairlineStrong },
    forest:  { bg: TP.forestTint, fg: TP.forest, border: 'transparent' },
    terra:   { bg: TP.terracottaTint, fg: '#A8551E', border: 'transparent' },
    ochre:   { bg: TP.ochreTint, fg: '#7A5410', border: 'transparent' },
    red:     { bg: TP.redTint, fg: TP.alertRed, border: 'transparent' },
    amber:   { bg: TP.amberTint, fg: '#8A4A0E', border: 'transparent' },
    blue:    { bg: TP.blueTint, fg: TP.alertBlue, border: 'transparent' },
    ink:     { bg: TP.ink, fg: '#FBF7F0', border: 'transparent' },
  };
  const t = tones[tone] || tones.default;
  const sz = size === 'sm'
    ? { fs: 11, py: 3, px: 8, gap: 4 }
    : { fs: 12, py: 4, px: 10, gap: 5 };
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: sz.gap,
      background: t.bg, color: t.fg,
      border: `1px solid ${t.border}`,
      borderRadius: TP.r.pill,
      padding: `${sz.py}px ${sz.px}px`,
      fontFamily: TP.sans, fontSize: sz.fs, fontWeight: 500,
      letterSpacing: 0.1, lineHeight: 1, whiteSpace: 'nowrap',
    }}>
      {icon && <TPIcon name={icon} size={12} stroke={2.2} />}
      {children}
    </span>
  );
}

// ─── TownPost Logo ──────────────────────────
function TPLogo({ size = 36, showWordmark = false, dark = false, accent = true }) {
  const fg = dark ? TP.cream : TP.cream;
  const bg = TP.forest;
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
      <div style={{
        width: size, height: size, borderRadius: size * 0.28,
        background: `linear-gradient(155deg, ${TP.forestSoft} 0%, ${TP.forest} 60%, #1F3A0F 100%)`,
        position: 'relative', overflow: 'hidden',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.18), 0 2px 4px rgba(45,80,22,0.25)',
      }}>
        {/* terracotta postmark ring */}
        {accent && (
          <div style={{
            position: 'absolute', top: size * 0.14, right: size * 0.14,
            width: size * 0.22, height: size * 0.22, borderRadius: '50%',
            background: TP.terracotta,
            boxShadow: `0 0 0 ${size * 0.04}px rgba(201,123,74,0.25)`,
          }} />
        )}
        {/* T monogram in serif */}
        <span style={{
          fontFamily: TP.serif, fontSize: size * 0.62, color: fg,
          lineHeight: 1, paddingTop: size * 0.04, marginLeft: -size * 0.04,
          letterSpacing: -1, fontWeight: 400,
        }}>T</span>
      </div>
      {showWordmark && (
        <div style={{ display: 'flex', flexDirection: 'column', lineHeight: 1 }}>
          <span style={{ fontFamily: TP.serif, fontSize: size * 0.62, color: TP.ink, letterSpacing: -0.4 }}>TownPost</span>
          <span style={{ fontFamily: TP.mono, fontSize: size * 0.22, color: TP.muted, letterSpacing: 1.4, marginTop: 3, fontWeight: 600 }}>QUINTE WEST</span>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { TP, TP_LIGHT, TP_DARK, applyTheme, TPThemeContext, useTPTheme, TPIcon, TPRadarIcon, TPAvatar, TPImage, TPImageGallery, TPLightbox, TPChip, TPLogo });
