// ========== PREMIUM BUILDING BLOCKS ==========
// Gradient portrait (initials + film grain), live ticker, Caribbean map,
// booking cinematic, notifications, loyalty ring.

// ---- Deterministic hash ----
const _hash = (s) => {
  let h = 0;
  for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) | 0;
  return Math.abs(h);
};

// ---- Premium Portrait ----
// Gradient based on seed + initials + film grain overlay. Much richer than flat color blocks.
const PALETTES = [
  ['#ff6b8a','#c25bd6','#8b5cf6'],
  ['#f97316','#ff9a6b','#ff6b8a'],
  ['#14b8a6','#0ea5e9','#6366f1'],
  ['#f5b800','#ff9a6b','#c25bd6'],
  ['#8b5cf6','#6366f1','#0ea5e9'],
  ['#d946ef','#ec4899','#ff6b8a'],
  ['#10b981','#14b8a6','#8b5cf6'],
  ['#f43f5e','#f97316','#f5b800'],
];

const PremPortrait = ({ seed = 'x', name = '', size = 56, radius = '50%', style = {}, children, photoUrl = null }) => {
  const h = _hash(seed || name);
  const pal = PALETTES[h % PALETTES.length];
  const ang = (h % 360);
  const initials = (name || seed).split(/\s+/).map(w => w[0]).filter(Boolean).slice(0,2).join('').toUpperCase();
  const offX = (h % 40) - 20;
  const offY = ((h >> 3) % 40) - 20;
  const numericSize = typeof size === 'number' ? size : 56;
  const [imgFailed, setImgFailed] = React.useState(false);
  const showImage = photoUrl && !imgFailed;
  return (
    <div className="prem-portrait" style={{
      width: size, height: size, borderRadius: radius,
      background: `linear-gradient(${ang}deg, ${pal[0]} 0%, ${pal[1]} 50%, ${pal[2]} 100%)`,
      position: 'relative', overflow: 'hidden', flexShrink: 0,
      ...style,
    }}>
      {showImage && (
        <img
          src={photoUrl}
          alt={name || seed}
          onError={() => setImgFailed(true)}
          style={{ position:'absolute', inset: 0, width:'100%', height:'100%', objectFit:'cover', display:'block' }}
        />
      )}
      {!showImage && (
        <>
          <div style={{
            position:'absolute', inset: 0,
            background: `radial-gradient(circle at ${50+offX}% ${50+offY}%, rgba(255,255,255,0.35), transparent 55%)`,
          }}/>
          <div className="grain-overlay"/>
          {children || (
            <div style={{
              position:'absolute', inset: 0,
              display:'grid', placeItems:'center',
              color:'rgba(255,255,255,0.92)',
              fontFamily:"'Fraunces', serif",
              fontSize: Math.max(11, numericSize * 0.38),
              fontWeight: 500,
              letterSpacing: '-0.02em',
              textShadow:'0 1px 2px rgba(20,10,40,0.25)',
            }}>{initials}</div>
          )}
        </>
      )}
    </div>
  );
};

// ---- Glow Circle loyalty ring ----
const GLOW_TIERS = {
  sand:  { name: 'Sand',  ring: 'linear-gradient(135deg,#e6d9c2,#d4b896)', min: 0 },
  amber: { name: 'Amber', ring: 'linear-gradient(135deg,#f5b800,#ff9a6b)', min: 5 },
  coral: { name: 'Coral', ring: 'linear-gradient(135deg,#ff6b8a,#c25bd6,#8b5cf6)', min: 20 },
};
const GlowRing = ({ tier = 'sand', size = 56, name = 'Chloé A.', seed = 'chloe' }) => {
  const t = GLOW_TIERS[tier];
  return (
    <div style={{position:'relative', width: size+8, height: size+8}}>
      <div style={{
        position:'absolute', inset: 0,
        borderRadius:'50%',
        background: t.ring,
        padding: 3,
      }}>
        <div style={{background:'#fff', borderRadius:'50%', width:'100%', height:'100%', padding: 2}}>
          <PremPortrait seed={seed} name={name} size={size-6}/>
        </div>
      </div>
    </div>
  );
};

// ---- Live ticker — derived from real providers loaded from the backend.
// We keep the ticker visible (it's a marketing element on the landing page),
// but every name + place comes from the live database via window.PROVIDERS,
// not hardcoded customer journeys. If real data hasn't loaded yet, the ticker
// quietly hides until it does.
const LiveTicker = () => {
  const [i, setI] = React.useState(0);
  const events = React.useMemo(() => {
    const provs = (typeof window !== 'undefined' && Array.isArray(window.PROVIDERS)) ? window.PROVIDERS : [];
    if (!provs.length) return [];
    const verbs = [
      { v: 'just opened new slots in', useArea: true },
      { v: 'is taking bookings in', useArea: true },
      { v: 'is verified on CaribGlow in', useArea: true },
      { v: 'now offers services in', useArea: true },
    ];
    return provs.slice(0, 12).map((p, idx) => {
      const verb = verbs[idx % verbs.length];
      const place = (p.area || '').split(',')[0].trim() || 'the Caribbean';
      return { name: p.name || 'A provider', action: verb.v, place, mins: 1 + idx * 3 };
    });
  }, [typeof window !== 'undefined' ? window.PROVIDERS : null]);

  React.useEffect(() => {
    if (!events.length) return;
    const t = setInterval(() => setI(x => (x + 1) % events.length), 3400);
    return () => clearInterval(t);
  }, [events.length]);

  if (!events.length) return null;
  const ev = events[i % events.length];
  return (
    <div className="live-ticker">
      <div className="live-dot"/>
      <div className="live-body" key={i}>
        <strong>{ev.name}</strong> {ev.action} <span className="pl">{ev.place}</span>
        <span className="mins"> · {ev.mins}m ago</span>
      </div>
    </div>
  );
};
// Kept exported as null array for any stale references; the component now
// computes events from window.PROVIDERS at render time.
const LIVE_TICKER_EVENTS = [];

// ---- Caribbean Map SVG (realistic geography) ----
// Accurate coastlines approximated from Natural Earth data, painted in satellite-style tones.
// Islands: Cuba, Hispaniola (Haiti/DR), Jamaica, Puerto Rico, Bahamas chain, Lesser Antilles,
// Trinidad & Tobago, Barbados, plus coasts of FL, Yucatan, Central Am, N. South Am.
const ISLAND_PATHS = {
  // Approximate real-shape paths, scaled for viewBox 800x500 (~Western Caribbean to Lesser Antilles)
  cuba: "M70,178 C95,168 125,160 155,159 C185,158 215,162 245,166 C275,170 305,178 330,188 C352,197 370,205 385,213 C395,218 398,224 392,228 C380,233 360,232 338,227 C315,222 288,215 258,212 C228,209 198,208 168,207 C140,206 115,205 95,200 C80,197 68,192 68,186 C68,182 69,180 70,178 Z",
  bahamas_andros: "M358,95 C370,90 380,94 384,106 C387,118 383,135 378,148 C372,160 365,158 360,150 C356,138 354,122 354,108 C354,100 354,97 358,95 Z",
  bahamas_eleuthera: "M395,78 C402,76 407,82 408,92 C409,105 405,120 400,128 C395,133 391,128 390,120 C389,108 390,92 392,84 C393,80 393,79 395,78 Z",
  bahamas_nassau: "M378,112 C386,110 391,115 392,122 C392,128 389,132 384,132 C378,131 375,126 375,120 C375,116 376,113 378,112 Z",
  bahamas_long: "M425,115 C432,114 436,120 438,130 C440,145 437,158 432,166 C428,170 425,165 424,158 C422,145 421,128 422,120 C422,117 423,116 425,115 Z",
  bahamas_inagua: "M430,175 C440,172 448,178 452,188 C455,198 452,208 446,210 C438,212 430,206 427,196 C425,188 425,180 428,176 C429,175 430,175 430,175 Z",
  turks_caicos: "M478,170 C488,167 496,172 499,180 C501,187 497,192 490,191 C482,190 475,183 474,177 C473,173 475,171 478,170 Z",
  caymans: "M215,245 C222,244 228,247 230,251 C231,255 228,257 223,257 C216,256 209,252 208,248 C208,246 211,245 215,245 Z M245,248 C252,248 257,252 256,256 C255,259 250,259 245,257 C241,256 240,252 242,250 C243,249 244,248 245,248 Z",
  jamaica: "M210,282 C232,276 258,274 285,276 C305,277 322,281 335,288 C342,292 344,298 338,302 C325,307 305,308 282,306 C255,304 228,300 210,294 C200,290 198,286 202,283 C204,282 207,282 210,282 Z",
  hispaniola: "M385,250 C410,242 445,240 478,243 C510,246 538,253 555,262 C568,269 572,278 562,284 C548,290 520,293 488,290 C455,287 420,280 395,272 C378,266 370,258 373,254 C375,251 379,250 385,250 Z",
  puerto_rico: "M570,268 C585,265 605,266 618,271 C626,274 627,280 620,283 C608,287 590,287 575,284 C563,281 558,276 562,271 C564,269 567,268 570,268 Z",
  // Lesser Antilles – each island individually shaped
  vi: "M640,262 C648,261 652,264 650,268 C647,271 640,272 636,269 C634,266 636,263 640,262 Z",
  anguilla: "M660,268 C666,267 670,270 668,273 C665,275 659,275 656,273 C655,271 657,269 660,268 Z",
  stmartin: "M662,275 C669,274 673,277 672,280 C669,283 662,283 658,281 C656,279 658,276 662,275 Z",
  stbarts: "M670,284 C674,283 677,285 676,287 C673,289 669,289 666,288 C665,286 667,285 670,284 Z",
  antigua: "M678,298 C685,296 691,300 691,305 C690,309 684,311 678,308 C674,306 673,301 676,299 C676,298 677,298 678,298 Z",
  guadeloupe: "M688,325 C697,322 706,327 708,335 C709,342 703,346 696,344 C688,342 681,336 680,330 C680,327 684,325 688,325 Z",
  dominica: "M690,352 C696,350 700,354 701,362 C702,370 698,374 693,373 C688,371 685,364 685,358 C685,354 687,352 690,352 Z",
  martinique: "M690,378 C697,376 702,381 703,388 C703,394 698,397 693,395 C686,393 683,386 684,382 C685,380 687,378 690,378 Z",
  stlucia: "M690,402 C696,401 700,405 700,412 C700,417 696,420 692,418 C686,416 683,410 684,406 C685,403 687,402 690,402 Z",
  stvincent: "M683,424 C688,423 692,427 692,432 C691,436 688,438 684,437 C680,435 678,430 679,426 C680,425 681,424 683,424 Z",
  grenada: "M672,450 C678,449 682,453 682,459 C681,463 677,465 673,463 C668,461 666,456 667,452 C668,451 670,450 672,450 Z",
  barbados: "M720,410 C728,408 734,414 735,422 C736,430 731,434 724,432 C717,430 713,422 714,416 C715,412 717,410 720,410 Z",
  trinidad: "M648,458 C665,454 690,454 710,460 C725,464 730,472 720,478 C705,484 680,484 660,480 C645,477 638,472 640,466 C642,461 645,459 648,458 Z",
  tobago: "M705,448 C712,446 718,450 718,455 C717,459 712,461 706,459 C701,457 699,452 702,450 C703,449 704,448 705,448 Z",
  // Continental fringes (just the tips that peek into the frame)
  florida: "M225,10 L315,10 L330,45 C335,70 325,85 305,85 C285,85 265,70 255,55 C245,45 235,30 228,18 Z",
  yucatan: "M0,155 L85,155 C95,170 90,195 75,205 C55,215 25,212 5,200 L0,190 Z",
  centralAm: "M0,270 L90,270 L130,315 L180,360 L220,420 L200,450 L150,470 L90,480 L0,478 Z",
  southAm: "M420,495 L620,495 L800,495 L800,500 L0,500 L0,495 L420,495 Z M560,460 L640,452 L695,448 L735,440 L790,438 L800,445 L800,480 L600,485 L480,480 L430,476 Z",
};

// Real-tile Leaflet map of the Caribbean. Tiles come from CartoCDN's Voyager
// basemap (free, no API key, Google-Maps-style street view). Pins are
// HTML divs anchored at real lat/lng coords passed in via the `pins` prop.
const CaribbeanMap = ({ pins = [], onPinClick, activeId }) => {
  const mapDivRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const markersRef = React.useRef({});

  // Initialise the Leaflet map once on mount.
  React.useEffect(() => {
    if (!mapDivRef.current || typeof L === 'undefined') return;
    if (mapRef.current) return; // already initialised

    const map = L.map(mapDivRef.current, {
      center: [17.5, -73.5],   // roughly the centre of the Caribbean basin
      zoom: 5,
      minZoom: 4,
      maxZoom: 11,
      zoomControl: true,
      scrollWheelZoom: false,    // don't hijack page scroll
      attributionControl: true,
    });

    L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &copy; <a href="https://carto.com/attributions">CARTO</a>',
      subdomains: 'abcd',
      maxZoom: 19,
    }).addTo(map);

    mapRef.current = map;

    // Resize handler — Leaflet needs a kick if the container resizes after init.
    const ro = new ResizeObserver(() => { try { map.invalidateSize(); } catch (e) {} });
    ro.observe(mapDivRef.current);

    return () => {
      try { ro.disconnect(); } catch (e) {}
      try { map.remove(); } catch (e) {}
      mapRef.current = null;
      markersRef.current = {};
    };
  }, []);

  // Sync markers whenever the pins prop changes.
  React.useEffect(() => {
    const map = mapRef.current;
    if (!map) return;

    // Remove markers no longer in the pin set
    const validIds = new Set(pins.map(p => p.id));
    Object.keys(markersRef.current).forEach(id => {
      if (!validIds.has(id)) {
        try { map.removeLayer(markersRef.current[id]); } catch (e) {}
        delete markersRef.current[id];
      }
    });

    pins.forEach(p => {
      if (typeof p.lat !== 'number' || typeof p.lng !== 'number') return;
      const isActive = activeId === p.id;
      const html = (
        '<div class="cg-pin' + (isActive ? ' active' : '') + '">' +
          '<div class="cg-pin-dot"></div>' +
          (p.label ? '<div class="cg-pin-label">' + p.label.replace(/[<>&]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;'}[c])) + '</div>' : '') +
        '</div>'
      );
      const icon = L.divIcon({
        html,
        className: 'cg-pin-wrap',
        iconSize: [22, 22],
        iconAnchor: [11, 11],
      });

      let marker = markersRef.current[p.id];
      if (!marker) {
        marker = L.marker([p.lat, p.lng], { icon }).addTo(map);
        marker.on('click', () => { if (onPinClick) onPinClick(p.id); });
        markersRef.current[p.id] = marker;
      } else {
        marker.setLatLng([p.lat, p.lng]);
        marker.setIcon(icon);
      }
    });

    // Auto-fit to all pins on first load
    if (pins.length > 1) {
      const bounds = L.latLngBounds(pins.filter(p => typeof p.lat === 'number').map(p => [p.lat, p.lng]));
      if (bounds.isValid()) {
        try { map.fitBounds(bounds, { padding: [40, 40], maxZoom: 7 }); } catch (e) {}
      }
    } else if (pins.length === 1 && typeof pins[0].lat === 'number') {
      try { map.setView([pins[0].lat, pins[0].lng], 9); } catch (e) {}
    }
  }, [pins, activeId, onPinClick]);

  return (
    <div ref={mapDivRef} className="caribbean-map-leaflet" style={{width:'100%', height:'100%'}}/>
  );
};

// Legacy SVG map (kept for backward compat, not currently used). The real
// Leaflet-based component above replaces it on the landing page.
const _LegacyCaribbeanMap = ({ pins = [], onPinClick, activeId }) => {
  return (
    <svg viewBox="0 0 800 500" className="caribbean-map" preserveAspectRatio="xMidYMid slice">
      <defs>
        {/* Deep ocean gradient — realistic satellite-view blue */}
        <linearGradient id="oceanDeep" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0%" stopColor="#1e3a5f"/>
          <stop offset="40%" stopColor="#27507f"/>
          <stop offset="100%" stopColor="#1a4068"/>
        </linearGradient>
        {/* Shallow water near coasts — turquoise */}
        <radialGradient id="shallow" cx="50%" cy="55%" r="65%">
          <stop offset="0%" stopColor="rgba(86,180,200,0.15)"/>
          <stop offset="55%" stopColor="rgba(86,180,200,0.08)"/>
          <stop offset="100%" stopColor="rgba(86,180,200,0)"/>
        </radialGradient>
        {/* Bahamas Banks — famous shallow shelf */}
        <radialGradient id="bahamasBank" cx="48%" cy="22%" r="18%">
          <stop offset="0%" stopColor="rgba(120,210,220,0.55)"/>
          <stop offset="70%" stopColor="rgba(120,210,220,0.18)"/>
          <stop offset="100%" stopColor="rgba(120,210,220,0)"/>
        </radialGradient>
        {/* Land texture — satellite green-brown with vegetation variance */}
        <linearGradient id="landGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="#7ea25a"/>
          <stop offset="40%" stopColor="#678c48"/>
          <stop offset="100%" stopColor="#4f6f38"/>
        </linearGradient>
        {/* Mountainous interior — Haiti, DR, Cuba interior */}
        <linearGradient id="mountains" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0%" stopColor="#8b7355"/>
          <stop offset="100%" stopColor="#5d4a35"/>
        </linearGradient>
        {/* Coastal sand/coral highlight */}
        <radialGradient id="coralEdge" cx="50%" cy="50%" r="50%">
          <stop offset="70%" stopColor="rgba(222,203,168,0)"/>
          <stop offset="100%" stopColor="rgba(222,203,168,0.35)"/>
        </radialGradient>
        {/* Paper/terrain texture — subtle noise via dot pattern */}
        <pattern id="landNoise" x="0" y="0" width="3" height="3" patternUnits="userSpaceOnUse">
          <rect width="3" height="3" fill="transparent"/>
          <circle cx="1" cy="1" r="0.4" fill="rgba(0,0,0,0.08)"/>
          <circle cx="2.3" cy="2.3" r="0.3" fill="rgba(255,255,255,0.06)"/>
        </pattern>
        <pattern id="oceanNoise" x="0" y="0" width="4" height="4" patternUnits="userSpaceOnUse">
          <rect width="4" height="4" fill="transparent"/>
          <circle cx="2" cy="2" r="0.35" fill="rgba(255,255,255,0.04)"/>
        </pattern>
        {/* Atmospheric haze / cloud band */}
        <radialGradient id="cloud1" cx="30%" cy="20%" r="35%">
          <stop offset="0%" stopColor="rgba(255,255,255,0.20)"/>
          <stop offset="100%" stopColor="rgba(255,255,255,0)"/>
        </radialGradient>
        <radialGradient id="cloud2" cx="75%" cy="68%" r="28%">
          <stop offset="0%" stopColor="rgba(255,255,255,0.14)"/>
          <stop offset="100%" stopColor="rgba(255,255,255,0)"/>
        </radialGradient>
        {/* Drop shadow for islands to create depth */}
        <filter id="islandShadow" x="-10%" y="-10%" width="120%" height="130%">
          <feGaussianBlur in="SourceAlpha" stdDeviation="1.2"/>
          <feOffset dx="0.8" dy="1.4" result="off"/>
          <feComponentTransfer><feFuncA type="linear" slope="0.55"/></feComponentTransfer>
          <feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
        </filter>
        <filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
          <feGaussianBlur stdDeviation="6"/>
        </filter>
      </defs>

      {/* Ocean base */}
      <rect width="800" height="500" fill="url(#oceanDeep)"/>
      <rect width="800" height="500" fill="url(#oceanNoise)" opacity="0.6"/>
      <rect width="800" height="500" fill="url(#shallow)"/>

      {/* Shallow shelf highlights — Bahamas & Caribbean Sea */}
      <ellipse cx="395" cy="120" rx="95" ry="70" fill="url(#bahamasBank)"/>
      <ellipse cx="270" cy="295" rx="55" ry="22" fill="rgba(120,210,220,0.15)"/>
      <ellipse cx="480" cy="280" rx="80" ry="28" fill="rgba(120,210,220,0.13)"/>

      {/* Bathymetric depth contours */}
      <g fill="none" stroke="rgba(255,255,255,0.05)" strokeWidth="0.5">
        <path d="M50,220 C180,210 320,230 460,235 C580,239 700,250 790,255"/>
        <path d="M30,340 C150,330 300,345 450,350 C590,355 720,360 790,365"/>
        <path d="M100,150 C250,140 420,155 580,160 C670,162 750,170 795,175"/>
      </g>

      {/* Continental shelves (background, dimmer) */}
      <g fill="url(#landGrad)" opacity="0.85" filter="url(#islandShadow)">
        <path d={ISLAND_PATHS.florida}/>
        <path d={ISLAND_PATHS.yucatan}/>
        <path d={ISLAND_PATHS.centralAm}/>
        <path d={ISLAND_PATHS.southAm}/>
      </g>
      <g fill="url(#landNoise)" opacity="0.4">
        <path d={ISLAND_PATHS.florida}/>
        <path d={ISLAND_PATHS.yucatan}/>
        <path d={ISLAND_PATHS.centralAm}/>
        <path d={ISLAND_PATHS.southAm}/>
      </g>

      {/* Main islands with terrain */}
      <g filter="url(#islandShadow)">
        {/* Cuba — with mountains in east */}
        <path d={ISLAND_PATHS.cuba} fill="url(#landGrad)"/>
        <path d="M300,195 C330,195 355,200 375,210 C385,215 390,220 385,225 C370,228 340,225 315,220 C300,216 290,210 295,200 C297,197 299,196 300,195 Z" fill="url(#mountains)" opacity="0.55"/>
        <path d={ISLAND_PATHS.cuba} fill="url(#landNoise)" opacity="0.35"/>

        {/* Hispaniola — strong mountain spine */}
        <path d={ISLAND_PATHS.hispaniola} fill="url(#landGrad)"/>
        <path d="M405,258 C440,253 485,253 520,260 C540,264 545,270 535,273 C510,277 470,275 435,270 C415,267 398,262 402,259 C403,258 404,258 405,258 Z" fill="url(#mountains)" opacity="0.65"/>
        <path d={ISLAND_PATHS.hispaniola} fill="url(#landNoise)" opacity="0.35"/>

        {/* Jamaica — Blue Mountains ridge */}
        <path d={ISLAND_PATHS.jamaica} fill="url(#landGrad)"/>
        <path d="M248,284 C272,282 298,284 318,288 C326,290 327,293 322,295 C306,297 282,296 260,293 C248,291 244,288 247,285 C247,284 248,284 248,284 Z" fill="url(#mountains)" opacity="0.55"/>
        <path d={ISLAND_PATHS.jamaica} fill="url(#landNoise)" opacity="0.4"/>

        {/* Puerto Rico */}
        <path d={ISLAND_PATHS.puerto_rico} fill="url(#landGrad)"/>
        <path d="M578,273 C595,271 612,273 617,276 C619,278 615,280 608,280 C595,280 580,279 575,276 C574,275 575,274 578,273 Z" fill="url(#mountains)" opacity="0.5"/>
        <path d={ISLAND_PATHS.puerto_rico} fill="url(#landNoise)" opacity="0.35"/>

        {/* Bahamas chain */}
        {['bahamas_andros','bahamas_eleuthera','bahamas_nassau','bahamas_long','bahamas_inagua','turks_caicos'].map(k => (
          <path key={k} d={ISLAND_PATHS[k]} fill="url(#landGrad)"/>
        ))}

        {/* Caymans */}
        <path d={ISLAND_PATHS.caymans} fill="url(#landGrad)"/>

        {/* Lesser Antilles */}
        {['vi','anguilla','stmartin','stbarts','antigua','guadeloupe','dominica','martinique','stlucia','stvincent','grenada','barbados','trinidad','tobago'].map(k => (
          <g key={k}>
            <path d={ISLAND_PATHS[k]} fill="url(#landGrad)"/>
            <path d={ISLAND_PATHS[k]} fill="url(#landNoise)" opacity="0.3"/>
          </g>
        ))}
      </g>

      {/* Coral/sand halos around islands */}
      <g opacity="0.6">
        <ellipse cx="230" cy="289" rx="80" ry="24" fill="url(#coralEdge)"/>
        <ellipse cx="470" cy="268" rx="110" ry="30" fill="url(#coralEdge)"/>
        <ellipse cx="220" cy="185" rx="165" ry="40" fill="url(#coralEdge)"/>
      </g>

      {/* Atmospheric clouds */}
      <g>
        <ellipse cx="240" cy="100" rx="140" ry="50" fill="url(#cloud1)"/>
        <ellipse cx="600" cy="340" rx="120" ry="45" fill="url(#cloud2)"/>
        <ellipse cx="120" cy="400" rx="90" ry="30" fill="url(#cloud2)" opacity="0.7"/>
      </g>

      {/* Labels — subtle, like a real nav chart */}
      <g fontFamily="JetBrains Mono, monospace" fill="rgba(255,255,255,0.92)" style={{textShadow:'0 1px 3px rgba(0,0,0,0.6)', pointerEvents:'none'}}>
        <text x="195" y="188" fontSize="11" letterSpacing="0.18em" fontWeight="600">CUBA</text>
        <text x="255" y="320" fontSize="10" letterSpacing="0.16em" fontWeight="600">JAMAICA</text>
        <text x="440" y="272" fontSize="9" letterSpacing="0.14em" fontWeight="600">HISPANIOLA</text>
        <text x="582" y="304" fontSize="8" letterSpacing="0.12em" fontWeight="600">PUERTO RICO</text>
        <text x="395" y="72" fontSize="10" letterSpacing="0.18em" fontWeight="600">BAHAMAS</text>
        <text x="735" y="416" fontSize="8" letterSpacing="0.12em" fontWeight="600">BARBADOS</text>
        <text x="660" y="495" fontSize="8" letterSpacing="0.12em" fontWeight="600">TRINIDAD</text>
        <text x="720" y="340" fontSize="7" letterSpacing="0.14em" fontWeight="500" opacity="0.85">LESSER</text>
        <text x="720" y="350" fontSize="7" letterSpacing="0.14em" fontWeight="500" opacity="0.85">ANTILLES</text>
      </g>

      {/* Sea labels */}
      <g fontFamily="Fraunces, serif" fontStyle="italic" fill="rgba(255,255,255,0.55)" style={{pointerEvents:'none'}}>
        <text x="420" y="380" fontSize="18" letterSpacing="0.25em">Caribbean Sea</text>
        <text x="250" y="75" fontSize="12" letterSpacing="0.2em" opacity="0.7">Atlantic Ocean</text>
      </g>

      {/* Pins */}
      {pins.map(p => (
        <g key={p.id} className={`map-pin-svg ${activeId === p.id ? 'active' : ''}`}
           transform={`translate(${p.x}, ${p.y})`}
           onClick={() => onPinClick && onPinClick(p.id)}
           style={{cursor:'pointer'}}>
          <circle r="22" fill="var(--primary-deep)" opacity="0.18" className="pulse-r"/>
          <circle r="13" fill="#fff" opacity="0.9" filter="url(#softGlow)"/>
          <circle r="9" fill="var(--primary-deep)"/>
          <circle r="4" fill="#fff"/>
          {p.label && (
            <g transform="translate(0, -22)">
              <rect x="-30" y="-12" width="60" height="18" rx="9" fill="#fff" stroke="var(--line)" strokeWidth="0.5" style={{filter:'drop-shadow(0 2px 6px rgba(0,0,0,0.2))'}}/>
              <text y="1" textAnchor="middle" fontSize="10" fontWeight="600" fill="var(--ink)" style={{pointerEvents:'none'}}>{p.label}</text>
            </g>
          )}
        </g>
      ))}
    </svg>
  );
};

// ---- Booking Cinematic ----
// Full-screen overlay that plays for ~2.6s when a booking is confirmed.
const BookingCinematic = ({ open, provider, service, date, time }) => {
  if (!open || !provider) return null;
  return (
    <div className="cinematic-overlay">
      <div className="cine-bg"/>
      <div className="cine-content">
        <div className="cine-spark">✦</div>
        <div className="cine-paint-arc"/>
        <div className="cine-eyebrow">Booked</div>
        <div className="cine-provider">
          {provider.name} <span className="i">·</span> <em>{(service && service.name) || 'your service'}</em>
        </div>
        <div className="cine-lines">
          <div className="cine-line"><span>DATE</span><span>Apr {date || 22}, 2026</span></div>
          <div className="cine-line"><span>TIME</span><span>{time || '2:30'} PM</span></div>
          <div className="cine-line"><span>WITH</span><span>{provider.name}</span></div>
          <div className="cine-line"><span>STUDIO</span><span>{provider.area || '—'}</span></div>
        </div>
      </div>
    </div>
  );
};

// ---- Notifications Center ----
// Pulls real notifications from the backend: recent bookings (status changes,
// upcoming) + unread message count. If the user isn't signed in we just show
// an empty state with a sign-in prompt instead of pretending notifications.
const NOTIF_ICONS = {
  booking:'calendar', msg:'msg', price:'tag', review:'star', payout:'dollar', system:'sparkles',
};
const NotificationsPanel = ({ open, onClose, nav }) => {
  const [tab, setTab] = React.useState('all');
  const [items, setItems] = React.useState([]);
  const [loading, setLoading] = React.useState(false);

  React.useEffect(() => {
    if (!open) return;
    if (!cgApi.isAuthed()) { setItems([]); return; }
    setLoading(true);
    let alive = true;
    (async () => {
      try {
        const out = [];
        const [bks, unread] = await Promise.all([
          cgApi.listClientBookings().catch(() => []),
          cgApi.unreadCount().catch(() => 0),
        ]);
        if (Number(unread) > 0) {
          out.push({
            id: 'msg-unread',
            kind: 'msg',
            title: unread + ' new message' + (unread === 1 ? '' : 's'),
            sub: 'Open Messages to read them',
            time: 'now',
            unread: true,
            target: 'messages',
          });
        }
        (bks || []).slice(0, 6).forEach((b, i) => {
          const isUpcoming = b.status === 'upcoming';
          const isCompleted = b.status === 'completed';
          const isCancelled = b.status === 'cancelled';
          const title = isUpcoming
            ? (b.providerName || 'Provider') + ' has your booking'
            : isCompleted
              ? 'Booking completed with ' + (b.providerName || 'provider')
              : isCancelled
                ? 'Booking cancelled'
                : 'Booking update';
          out.push({
            id: 'b-' + (b.id || i),
            kind: 'booking',
            title,
            sub: (b.service || 'Service') + ' · ' + (b.date || '') + ' ' + (b.day || '') + ' · ' + (b.time || ''),
            time: '',
            unread: isUpcoming || i < 1,
            target: 'bookings',
          });
        });
        if (alive) setItems(out);
      } catch (e) { if (alive) setItems([]); }
      finally { if (alive) setLoading(false); }
    })();
    return () => { alive = false; };
  }, [open]);

  const filtered = tab === 'unread' ? items.filter(n => n.unread) : items;
  const unreadCount = items.filter(n => n.unread).length;
  const NOTIFICATIONS = items;
  return (
    <>
      {open && <div className="notif-backdrop" onClick={onClose}/>}
      <div className={`notif-panel ${open ? 'open' : ''}`}>
        <div className="notif-head">
          <div>
            <div className="eyebrow" style={{marginBottom: 4}}>Notifications</div>
            <h3>What's new</h3>
          </div>
          <button className="close" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        <div className="notif-tabs">
          <button className={tab==='all'?'active':''} onClick={()=>setTab('all')}>All <span className="ct">{NOTIFICATIONS.length}</span></button>
          <button className={tab==='unread'?'active':''} onClick={()=>setTab('unread')}>Unread <span className="ct">{unreadCount}</span></button>
          <div style={{flex:1}}/>
          <button className="mark" onClick={onClose}>Mark all read</button>
        </div>
        <div className="notif-list">
          {!cgApi.isAuthed() ? (
            <div style={{padding:'32px 20px', textAlign:'center', color:'var(--muted)', fontSize: 13}}>
              <div style={{marginBottom: 12}}>Sign in to see your notifications.</div>
              <button className="btn btn-brand btn-sm" onClick={() => { onClose(); if (nav) nav('login'); }}>Sign in</button>
            </div>
          ) : loading ? (
            <div style={{padding:'24px 20px', textAlign:'center', color:'var(--muted)', fontSize: 13}}>Loading…</div>
          ) : filtered.length === 0 ? (
            <div style={{padding:'32px 20px', textAlign:'center', color:'var(--muted)', fontSize: 13}}>
              You're all caught up. New messages and booking updates show up here.
            </div>
          ) : filtered.map(n => (
            <div key={n.id} className={`notif ${n.unread ? 'unread':''}`} onClick={() => {
              if (n.target && nav) nav(n.target);
              onClose();
            }}>
              <div className={`notif-ico k-${n.kind}`}>
                <Icon name={NOTIF_ICONS[n.kind]} size={14}/>
              </div>
              <div className="notif-body">
                <div className="t">{n.title}</div>
                <div className="s">{n.sub}</div>
              </div>
              <div className="notif-time">{n.time}</div>
            </div>
          ))}
        </div>
      </div>
    </>
  );
};

Object.assign(window, {
  PremPortrait, GlowRing, GLOW_TIERS,
  LiveTicker, LIVE_TICKER_EVENTS,
  CaribbeanMap,
  BookingCinematic,
  NotificationsPanel,
});
