/*__IIFE_WRAPPED__*/
;(function(){
// screens-sub.jsx — Amulet Map / Wish List
const { useState: useSub, useEffect: useESub } = React;
const {
  useApp: useAppS, Icon: IconS, Btn: BtnS, Chip: ChipS, Card: CardS, Avatar: AvatarS,
  ImgSlot: ImgSlotS, Eyebrow: EyebrowS, TopBar: TopBarS, Body: BodyS,
  Field: FieldS, BottomNav: BottomNavS, GoogleMapView: GMapS, NAV_H, SAFE_TOP, Amulet,
} = window;

const ME_FALLBACK = 'あなた';
const PARTNER_FALLBACK = 'パートナー';
const SHIBUYA_CENTER = { lat: 35.6635, lng: 139.6960 };

/* ── Mini spot confirmation card — defined outside AddSpotModal to avoid remount ── */
function MiniSpotCard({ spot, confirmed, onConfirm, onRetry }) {
  return (
    <div style={{ marginTop: 6, borderRadius: 14, overflow: 'hidden', border: '1px solid var(--line)', background: 'var(--surface)' }}>
      <div style={{ height: 88, background: 'color-mix(in oklch, var(--amethyst) 9%, var(--surface-2))', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative' }}>
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
          <IconS name="pin" size={28} style={{ color: 'var(--amethyst)' }} />
          <span style={{ fontSize: 10, color: 'var(--amethyst)', fontWeight: 700 }}>{spot.cat}</span>
        </div>
        <div style={{ position: 'absolute', bottom: 5, right: 8, fontSize: 9.5, color: 'var(--ink-faint)', fontFamily: 'monospace' }}>
          {spot.lat.toFixed(4)}, {spot.lng.toFixed(4)}
        </div>
      </div>
      <div style={{ padding: '10px 13px 6px' }}>
        <div style={{ fontWeight: 700, fontSize: 14, color: 'var(--ink)' }}>{spot.name}</div>
        <div style={{ fontSize: 12, color: 'var(--ink-faint)', marginTop: 3, lineHeight: 1.5 }}>{spot.note}</div>
      </div>
      {confirmed?.name === spot.name && (
        <div style={{ display: 'flex', gap: 8, padding: '8px 13px 13px' }}>
          <button onClick={onConfirm} style={{ flex: 1, padding: '10px', borderRadius: 12, border: 'none', background: 'var(--amethyst)', color: '#fff', fontFamily: 'var(--font-ui)', fontWeight: 700, fontSize: 14, cursor: 'pointer' }}>はい、追加！</button>
          <button onClick={onRetry} style={{ flex: 1, padding: '10px', borderRadius: 12, border: '1px solid var(--line-2)', background: 'transparent', color: 'var(--ink)', fontFamily: 'var(--font-ui)', fontWeight: 600, fontSize: 13, cursor: 'pointer' }}>違うかも</button>
        </div>
      )}
    </div>
  );
}

/* ── Add Spot modal — chat-first. 画像→detectSpot / テキスト→手動候補 ── */
function AddSpotModal({ onClose, live, toast }) {
  const { useState: useS, useRef: useR, useEffect: useE } = React;
  const isLive = !!live?.coupleId;
  const [msgs, setMsgs] = useS([
    { who: 'ai', text: '何を追加しますか？\n場所の写真を送るか、テキストで教えていただければ一緒に探します 📍' }
  ]);
  const [input, setInput] = useS('');
  const [confirmed, setConfirmed] = useS(null);
  const [imageLoading, setImageLoading] = useS(false);
  const [done, setDone] = useS(false);
  const [lastImage, setLastImage] = useS(null); // { base64, mimeType } — 直近の画像。テキストヒントで再推論するため保持
  const listRef = useR(null);
  const fileRef = useR(null);

  useE(() => {
    if (listRef.current) listRef.current.scrollTop = listRef.current.scrollHeight;
  }, [msgs, imageLoading]);

  const addMsg = (msg) => setMsgs(m => [...m, msg]);

  // 会話履歴を detectSpot の chatContext（{ role, text }[]）へ変換。
  // バックエンドが直近20件・4000字に丸めるので全件渡してよい。
  const toChatContext = (list) =>
    list
      .map(m => ({ role: m.who === 'ai' ? 'assistant' : 'user', text: (m.text || '').trim() }))
      .filter(m => m.text);

  // 画像 or テキストで場所推定を実行し、結果をチャットに反映（handleFile / sendChat 共通）。
  // base64=null かつ textQuery を渡すとテキストのみ検索（Places API）になる。
  const runDetect = async (base64, mimeType, chatContext, textQuery) => {
    setImageLoading(true);
    try {
      const r = await Amulet.detectSpot(base64, mimeType, chatContext, textQuery);
      setImageLoading(false);
      if (r.detected && r.spot) {
        const spot = { name: r.spot.name, lat: r.spot.lat, lng: r.spot.lng, note: r.spot.note, cat: r.spot.category,
          photoUrl: r.spot.photoUrl, placeId: r.spot.placeId, address: r.spot.address, rating: r.spot.rating, mapsUri: r.spot.mapsUri };
        setConfirmed(spot);
        addMsg({ who: 'ai', text: '見つかりました！追加したいのはこの場所ですか？', spot });
      } else {
        addMsg({ who: 'ai', text: base64
          ? 'この写真からは場所が分かりませんでした…。お店の名前や地域をテキストで教えてもらえますか？'
          : '場所が見つかりませんでした…。別の名前や地域で試してみてもらえますか？' });
      }
    } catch (err) {
      console.error(err);
      setImageLoading(false);
      addMsg({ who: 'ai', text: 'ごめんなさい、解析に失敗してしまいました。もう一度試してみてください。' });
    }
  };

  const handleImageAttach = () => {
    if (imageLoading || confirmed) return;
    if (fileRef.current) fileRef.current.click(); // 画像 → detectSpot（実API）で場所特定
  };

  const handleFile = async (e) => {
    const file = e.target.files?.[0];
    e.target.value = '';
    if (!file) return;
    const ctx = toChatContext(msgs); // 添付前までの会話をヒントに
    try {
      const { base64, mimeType } = await Amulet.fileToBase64(file);
      setLastImage({ base64, mimeType }); // テキストヒントで再推論できるよう保持
      // アップロードした画像そのものをチャット履歴に表示（テキスト表記より内容が分かりやすい）。
      addMsg({ who: 'user', image: `data:${mimeType};base64,${base64}` });
      await runDetect(base64, mimeType, ctx);
    } catch (err) {
      console.error(err);
      addMsg({ who: 'ai', text: 'ごめんなさい、画像の解析に失敗してしまいました。テキストでも教えてもらえますか？' });
    }
  };

  const sendChat = () => {
    if (!input.trim() || confirmed || imageLoading) return;
    const text = input;
    setInput('');
    const nextMsgs = [...msgs, { who: 'user', text }]; // 送ったヒントを確実に文脈へ含める
    addMsg({ who: 'user', text });
    if (lastImage) {
      // 保持画像 + ヒントを含む会話で再推論（テキスト情報を場所検索の入力として活用）。
      runDetect(lastImage.base64, lastImage.mimeType, toChatContext(nextMsgs));
      return;
    }
    // 画像なし: テキストのみで Places API 検索。
    runDetect(null, null, toChatContext(nextMsgs), text);
  };

  const handleConfirm = async () => {
    const spot = confirmed;
    setConfirmed(null);
    if (isLive) {
      try {
        await Amulet.addPin(live.coupleId, { name: spot.name, category: spot.cat, lat: spot.lat, lng: spot.lng, note: spot.note,
          photoUrl: spot.photoUrl, placeId: spot.placeId, address: spot.address, rating: spot.rating, mapsUri: spot.mapsUri });
      } catch (e) {
        console.error(e);
        toast && toast(Amulet.friendlyError(e));
        addMsg({ who: 'ai', text: '追加に失敗してしまいました。もう一度試してみてください。' });
        return;
      }
    }
    setDone(true);
    addMsg({ who: 'ai', text: `「${spot.name}」をAmulet Map に追加しました ✨` });
    setTimeout(onClose, 1400);
  };

  const handleRetry = () => {
    setConfirmed(null);
    addMsg({ who: 'ai', text: 'なるほど。もう少し詳しく教えてもらえますか？' });
  };

  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 50, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
      {/* backdrop */}
      <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(61,54,44,.44)', backdropFilter: 'blur(2px)' }} />
      <input ref={fileRef} type="file" accept="image/*" style={{ display: 'none' }} onChange={handleFile} />
      {/* sheet */}
      <div style={{ position: 'relative', background: 'var(--surface)', borderRadius: '24px 24px 0 0', height: '72%', display: 'flex', flexDirection: 'column', animation: 'risefade .28s ease' }}>
        {/* drag handle */}
        <div style={{ width: 36, height: 4, borderRadius: 999, background: 'var(--line-2)', margin: '10px auto 0', flexShrink: 0 }} />
        {/* header */}
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 18px 10px', borderBottom: '1px solid var(--line)', flexShrink: 0 }}>
          <div style={{ fontWeight: 700, fontSize: 16, color: 'var(--ink)' }}>スポットを追加</div>
          <button onClick={onClose} style={{ width: 30, height: 30, borderRadius: 999, border: 'none', background: 'var(--surface-2)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <IconS name="plus" size={15} style={{ transform: 'rotate(45deg)', color: 'var(--ink-faint)' }} />
          </button>
        </div>
        {/* message list */}
        <div ref={listRef} className="noscroll" style={{ flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 10, padding: '14px 16px 8px' }}>
          {msgs.map((m, i) => (
            <div key={i} style={{ display: 'flex', justifyContent: m.who === 'ai' ? 'flex-start' : 'flex-end' }}>
              <div style={{ maxWidth: '84%' }}>
                {m.image ? (
                  <img src={m.image} alt="添付した写真" style={{ display: 'block', maxWidth: '100%', width: 200, borderRadius: '18px 4px 18px 18px', border: '1px solid var(--line)', objectFit: 'cover' }} />
                ) : (
                  <div style={{ padding: '10px 14px', borderRadius: m.who === 'ai' ? '4px 18px 18px 18px' : '18px 4px 18px 18px', background: m.who === 'ai' ? 'var(--surface-2)' : 'var(--amethyst)', color: m.who === 'ai' ? 'var(--ink)' : '#fff', fontSize: 13.5, lineHeight: 1.55, whiteSpace: 'pre-wrap' }}>
                    {m.text}
                  </div>
                )}
                {m.spot && <MiniSpotCard spot={m.spot} confirmed={confirmed} onConfirm={handleConfirm} onRetry={handleRetry} />}
              </div>
            </div>
          ))}
          {imageLoading && (
            <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
              <div style={{ width: 20, height: 20, borderRadius: '50%', border: '2.5px solid var(--amethyst)', borderTopColor: 'transparent', animation: 'spin 1s linear infinite', flexShrink: 0 }} />
              <span style={{ fontSize: 12, color: 'var(--ink-faint)' }}>場所を確認中…</span>
            </div>
          )}
        </div>
        {/* input bar */}
        <div style={{ display: 'flex', gap: 8, padding: '10px 16px 28px', borderTop: '1px solid var(--line)', flexShrink: 0, alignItems: 'center' }}>
          <button onClick={handleImageAttach} disabled={imageLoading || !!confirmed || done} style={{ width: 44, height: 44, borderRadius: 14, border: '1px solid var(--line-2)', background: 'var(--surface-2)', cursor: imageLoading || confirmed ? 'default' : 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, opacity: (imageLoading || !!confirmed || done) ? 0.38 : 1, transition: 'opacity .2s' }}>
            <IconS name="camera" size={19} style={{ color: 'var(--ink-soft)' }} />
          </button>
          <textarea value={input} onChange={e => setInput(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) { e.preventDefault(); sendChat(); } }}
            disabled={!!confirmed || done} placeholder={done ? '追加完了！' : confirmed ? '上のカードを確認してね' : '場所の名前や特徴を教えて…'} style={{ flex: 1, padding: '11px 14px', borderRadius: 14, border: '1px solid var(--line-2)', background: 'var(--surface-2)', fontFamily: 'var(--font-ui)', fontSize: 14, color: 'var(--ink)', outline: 'none', opacity: (confirmed || done) ? 0.5 : 1, transition: 'opacity .2s', resize: 'none', minHeight: 44, maxHeight: 120, boxSizing: 'border-box' }} />
          <button onClick={sendChat} disabled={!input.trim() || !!confirmed || done} style={{ width: 44, height: 44, borderRadius: 14, border: 'none', background: (input.trim() && !confirmed && !done) ? 'var(--amethyst)' : 'var(--line-2)', color: '#fff', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, transition: 'background .15s' }}>
            <IconS name="arrow" size={20} />
          </button>
        </div>
      </div>
    </div>
  );
}

/* ════════════════ AMULET MAP ════════════════ */
/* ── スポット全画面詳細（大きな画像＋説明＋削除）── */
function SpotDetail({ pin, whoName, whoColor, onClose, onDelete, deleting }) {
  const [confirm, setConfirm] = useSub(false);
  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 80, background: 'var(--bg)', display: 'flex', flexDirection: 'column', animation: 'risefade .24s ease' }}>
      {/* hero image */}
      <div style={{ position: 'relative', flexShrink: 0 }}>
        <ImgSlotS label={pin.cat} src={pin.photo} h={288} r={0} />
        <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none', background: 'linear-gradient(180deg, rgba(0,0,0,.30) 0%, transparent 32%, transparent 72%, rgba(0,0,0,.10) 100%)' }} />
        <button onClick={onClose} aria-label="閉じる" style={{ position: 'absolute', top: SAFE_TOP - 6, left: 14, width: 38, height: 38, borderRadius: 999, border: 'none', background: 'rgba(251,248,241,.9)', WebkitBackdropFilter: 'blur(8px)', backdropFilter: 'blur(8px)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <IconS name="back" size={20} style={{ color: 'var(--ink)' }} />
        </button>
      </div>

      {/* body */}
      <div className="noscroll" style={{ flex: 1, overflowY: 'auto', padding: '18px 18px 28px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 8 }}>
          <span style={{ width: 9, height: 9, borderRadius: '50%', background: whoColor(pin.who) }} />
          <span style={{ fontSize: 12, fontWeight: 700, color: whoColor(pin.who) }}>{whoName(pin.who)}が追加</span>
          <span style={{ fontSize: 11.5, color: 'var(--ink-faint)' }}>· {pin.cat}</span>
        </div>
        <h1 style={{ fontFamily: 'var(--font-serif)', fontWeight: 400, fontSize: 26, color: 'var(--ink)', margin: '0 0 12px', lineHeight: 1.35 }}>{pin.name}</h1>
        {pin.note && <p style={{ fontSize: 14, color: 'var(--ink-soft)', lineHeight: 1.85, margin: '0 0 16px' }}>{pin.note}</p>}

        <div style={{ display: 'flex', flexDirection: 'column', gap: 9, marginBottom: 22 }}>
          {pin.address && (
            <div style={{ display: 'flex', alignItems: 'flex-start', gap: 9, fontSize: 13, color: 'var(--ink-soft)' }}>
              <IconS name="pin" size={16} style={{ color: 'var(--ink-faint)', flexShrink: 0, marginTop: 1 }} /><span>{pin.address}</span>
            </div>
          )}
          {pin.rating != null && (
            <div style={{ display: 'flex', alignItems: 'center', gap: 9, fontSize: 13, color: 'var(--ink-soft)' }}>
              <IconS name="sparkle" size={16} style={{ color: 'var(--gold)', flexShrink: 0 }} /><span>評価 {pin.rating}</span>
            </div>
          )}
        </div>

        {pin.mapsUri && (
          <a href={pin.mapsUri} target="_blank" rel="noreferrer" style={{ textDecoration: 'none', display: 'block', marginBottom: 4 }}>
            <BtnS variant="soft" full icon="map">Googleマップで開く</BtnS>
          </a>
        )}

        {/* delete */}
        <div style={{ marginTop: 20, borderTop: '1px solid var(--line)', paddingTop: 18 }}>
          {!confirm ? (
            <button onClick={() => setConfirm(true)} disabled={deleting} style={{ width: '100%', padding: '14px', borderRadius: 14, border: '1px solid color-mix(in oklch, var(--me) 40%, transparent)', background: 'color-mix(in oklch, var(--me) 8%, var(--paper))', color: 'var(--me)', fontFamily: 'var(--font-ui)', fontWeight: 700, fontSize: 15, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8 }}>
              <IconS name="plus" size={18} style={{ transform: 'rotate(45deg)' }} /> この場所を削除
            </button>
          ) : (
            <div style={{ background: 'color-mix(in oklch, var(--me) 8%, var(--paper))', border: '1px solid color-mix(in oklch, var(--me) 30%, transparent)', borderRadius: 16, padding: 14 }}>
              <div style={{ fontSize: 13.5, fontWeight: 700, color: 'var(--ink)', marginBottom: 3 }}>この場所を削除しますか？</div>
              <div style={{ fontSize: 12, color: 'var(--ink-soft)', lineHeight: 1.6, marginBottom: 12 }}>Amulet Map から「{pin.name}」を削除します。元に戻せません。</div>
              <div style={{ display: 'flex', gap: 10 }}>
                <button onClick={() => setConfirm(false)} disabled={deleting} style={{ flex: 1, padding: '11px', borderRadius: 12, border: '1px solid var(--line-2)', background: 'var(--surface)', color: 'var(--ink)', fontFamily: 'var(--font-ui)', fontWeight: 600, fontSize: 14, cursor: 'pointer' }}>やめる</button>
                <button onClick={onDelete} disabled={deleting} style={{ flex: 1, padding: '11px', borderRadius: 12, border: 'none', background: 'var(--me)', color: '#fff', fontFamily: 'var(--font-ui)', fontWeight: 700, fontSize: 14, cursor: 'pointer', opacity: deleting ? 0.6 : 1 }}>{deleting ? '削除中…' : '削除する'}</button>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function AmuletMapScreen() {
  const { go, live, toast } = useAppS();
  const [view, setView] = useSub('map');
  const [filter, setFilter] = useSub('すべて');
  const [sel, setSel] = useSub(null);
  const [detail, setDetail] = useSub(null); // 全画面詳細で表示中のピン
  const [deleting, setDeleting] = useSub(false);
  const [addSpotOpen, setAddSpotOpen] = useSub(false);
  const [livePins, setLivePins] = useSub(null);
  const [locating, setLocating] = useSub(false);
  const mapRef = React.useRef(null); // 全体表示・現在地・ピン選択ズームで地図を直接操作するため
  const isLive = !!live.coupleId;

  const handleDelete = async () => {
    if (!detail) return;
    setDeleting(true);
    try {
      await Amulet.deletePin(live.coupleId, detail.id);
      setDetail(null);
      setSel(null);
      toast && toast('🗑 削除しました');
    } catch (e) {
      console.error(e);
      toast && toast(Amulet.friendlyError(e));
    } finally {
      setDeleting(false);
    }
  };

  useESub(() => {
    if (!isLive) return;
    return Amulet.listenPins(live.coupleId, docs => {
      setLivePins(docs.map(d => ({
        id: d.id, name: d.name, cat: d.category || 'スポット',
        who: d.addedBy === live.user?.uid ? 'me' : 'partner',
        note: d.note || '', pos: { lat: d.lat, lng: d.lng },
        photo: d.photoUrl, address: d.address, rating: d.rating, mapsUri: d.mapsUri,
      })));
    });
  }, [live.coupleId]);

  const myName = live.userDoc?.name || ME_FALLBACK;
  const partnerName = live.partner?.name || PARTNER_FALLBACK;
  const allPins = isLive ? (livePins || []) : [];
  const cats = ['すべて', ...new Set(allPins.map(p => p.cat))];
  const pins = allPins.filter(p => filter === 'すべて' || p.cat === filter);
  const whoColor = w => (w === 'me' ? 'var(--me)' : 'var(--partner)');
  const whoName = w => (w === 'me' ? myName : partnerName);

  // ピンの色は「選択状態」で分ける：未選択はAmuletのプライマリ（紫）、選択中は赤で強調。
  const markerColor = selected => {
    const cs = getComputedStyle(document.documentElement);
    if (selected) return '#E5484D';
    return cs.getPropertyValue('--amethyst').trim() || '#8E7DA6';
  };

  // 全ピンが収まる倍率に自動フィットするための署名（座標のみ／選択では変化しない）。
  const fitSignature = pins.map(p => `${p.pos.lat.toFixed(5)},${p.pos.lng.toFixed(5)}`).sort().join('|');

  const SELECT_ZOOM = 16;

  // 全体表示ボタン：全ピンが収まるビューへ、なめらかに引いて戻す。
  const fitAll = () => {
    const map = mapRef.current;
    if (!map || !window.google) return;
    const positions = pins.map(p => p.pos).filter(Boolean);
    if (positions.length === 0) { window.mapFlyTo(map, SHIBUYA_CENTER, 15); return; }
    if (positions.length === 1) { window.mapFlyTo(map, positions[0], 15); return; }
    window.mapFlyToBounds(map, positions);
  };

  // ピン選択：選択トグル。選択時はその場所へ、パン＋段階ズームでなめらかに寄る。
  const selectPin = (p) => {
    const next = sel?.id === p.id ? null : p;
    setSel(next);
    if (next && mapRef.current) {
      const z = Math.max(mapRef.current.getZoom(), SELECT_ZOOM);
      window.mapFlyTo(mapRef.current, p.pos, z);
    }
  };

  // 現在地ボタン：位置情報の許可があれば現在地へ移動＋ズームイン。
  const locateMe = () => {
    const map = mapRef.current;
    if (!map || !navigator.geolocation) { toast && toast('この端末では現在地を取得できません'); return; }
    setLocating(true);
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        setLocating(false);
        window.mapFlyTo(map, { lat: pos.coords.latitude, lng: pos.coords.longitude }, SELECT_ZOOM);
      },
      () => { setLocating(false); toast && toast('現在地を取得できませんでした'); },
      { enableHighAccuracy: true, timeout: 8000, maximumAge: 30000 }
    );
  };

  return (
    <div style={{ height: '100%', background: 'var(--bg)', display: 'flex', flexDirection: 'column', position: 'relative' }}>
      <TopBarS title="Amulet Map" right={
        <div style={{ display: 'flex', gap: 4, background: 'var(--surface-2)', borderRadius: 999, padding: 3, border: '1px solid var(--line)' }}>
          {[['map', 'map'], ['list', 'note']].map(([id, ic]) => (
            <button key={id} onClick={() => setView(id)} style={{ width: 36, height: 30, borderRadius: 999, border: 'none', cursor: 'pointer', background: view === id ? 'var(--surface)' : 'transparent', color: view === id ? 'var(--amethyst)' : 'var(--ink-faint)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}><IconS name={ic} size={17} /></button>
          ))}
        </div>
      } />

      {/* filters */}
      <div className="noscroll" style={{ display: 'flex', gap: 8, overflowX: 'auto', padding: '4px 16px 12px' }}>
        {cats.map(c => <ChipS key={c} active={filter === c} onClick={() => { setFilter(c); setSel(null); }} color="var(--amethyst)">{c}</ChipS>)}
      </div>

      {view === 'map' ? (
        /* map view — paddingBottom prevents BottomNav overlap */
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', paddingBottom: NAV_H }}>
          {/* map rounded container */}
          <div style={{ flex: 1, margin: '0 16px 0', borderRadius: sel ? '24px 24px 0 0' : '24px', overflow: 'hidden', border: '1px solid var(--line)', boxShadow: '0 18px 36px -28px rgba(61,54,44,.5)', position: 'relative' }}>
            <GMapS
              center={pins[0]?.pos || SHIBUYA_CENTER}
              zoom={15}
              fitSignature={fitSignature}
              onReady={(map) => { mapRef.current = map; }}
              markers={pins.map(p => ({
                id: p.id, pos: p.pos, name: p.name,
                color: markerColor(sel?.id === p.id),
                active: sel?.id === p.id,
                onClick: () => selectPin(p),
              }))}
              style={{ borderRadius: 0 }}
            />
            {/* map controls — 全体表示 / 現在地 */}
            <div style={{ position: 'absolute', top: 12, right: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
              <button onClick={fitAll} aria-label="全体表示" title="全体表示" style={{ width: 40, height: 40, borderRadius: 12, border: '1px solid var(--line)', background: 'rgba(251,248,241,.92)', WebkitBackdropFilter: 'blur(8px)', backdropFilter: 'blur(8px)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 4px 14px -8px rgba(61,54,44,.5)' }}>
                <IconS name="expand" size={19} style={{ color: 'var(--amethyst)' }} />
              </button>
              <button onClick={locateMe} disabled={locating} aria-label="現在地" title="現在地" style={{ width: 40, height: 40, borderRadius: 12, border: '1px solid var(--line)', background: 'rgba(251,248,241,.92)', WebkitBackdropFilter: 'blur(8px)', backdropFilter: 'blur(8px)', cursor: locating ? 'default' : 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 4px 14px -8px rgba(61,54,44,.5)' }}>
                {locating
                  ? <div style={{ width: 17, height: 17, borderRadius: '50%', border: '2.5px solid var(--amethyst)', borderTopColor: 'transparent', animation: 'spin 1s linear infinite' }} />
                  : <IconS name="locate" size={19} style={{ color: 'var(--amethyst)' }} />}
              </button>
            </div>
            {/* legend */}
            <div style={{ position: 'absolute', top: 12, left: 12, display: 'flex', gap: 8, background: 'rgba(251,248,241,.88)', backdropFilter: 'blur(8px)', borderRadius: 999, padding: '6px 11px', border: '1px solid var(--line)', pointerEvents: 'none' }}>
              {[['スポット', 'var(--amethyst)'], ['選択中', '#E5484D']].map(([l, c]) => (
                <div key={l} style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 11, fontWeight: 700, color: 'var(--ink-soft)' }}>
                  <span style={{ width: 9, height: 9, borderRadius: '50%', background: c }} />{l}
                </div>
              ))}
            </div>
            {/* empty state */}
            {isLive && livePins && livePins.length === 0 && (
              <div style={{ position: 'absolute', left: '50%', top: '50%', transform: 'translate(-50%,-50%)', background: 'rgba(251,248,241,.92)', backdropFilter: 'blur(6px)', borderRadius: 18, padding: '18px 22px', border: '1px solid var(--line)', textAlign: 'center', pointerEvents: 'none' }}>
                <div style={{ marginBottom: 6, color: 'var(--ink-faint)' }}><IconS name="pin" size={26} /></div>
                <div style={{ fontWeight: 700, fontSize: 13.5, color: 'var(--ink)' }}>まだピンがありません</div>
                <div style={{ fontSize: 11.5, color: 'var(--ink-soft)', marginTop: 3 }}>右下の＋から行きたい場所を追加</div>
              </div>
            )}
          </div>
          {/* selected card — outside overflow:hidden so it's always visible */}
          {sel && (
            <div onClick={() => setDetail(sel)} style={{ margin: '0 16px', background: 'var(--surface)', borderRadius: '0 0 24px 24px', border: '1px solid var(--line)', borderTop: 'none', padding: '14px 15px 16px', animation: 'floatup .22s ease', display: 'flex', gap: 12, flexShrink: 0, cursor: 'pointer' }}>
              <ImgSlotS label={sel.cat} src={sel.photo} h={52} style={{ width: 52, flexShrink: 0 }} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                  <span style={{ width: 8, height: 8, borderRadius: '50%', background: whoColor(sel.who), flexShrink: 0 }} />
                  <span style={{ fontSize: 11, color: whoColor(sel.who), fontWeight: 700 }}>{whoName(sel.who)}が追加 · {sel.cat}</span>
                </div>
                <div style={{ fontWeight: 700, fontSize: 15.5, color: 'var(--ink)', margin: '3px 0 2px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{sel.name}</div>
                <div style={{ fontSize: 12, color: 'var(--ink-faint)', lineHeight: 1.5 }}>{sel.note}</div>
              </div>
              <button onClick={(e) => { e.stopPropagation(); setSel(null); }} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--ink-faint)', alignSelf: 'flex-start' }}>
                <IconS name="plus" size={18} style={{ transform: 'rotate(45deg)' }} />
              </button>
            </div>
          )}
        </div>
      ) : (
        <BodyS nav>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 11 }}>
            {pins.map(p => (
              <div key={p.id} onClick={() => setDetail(p)} style={{ display: 'flex', gap: 12, alignItems: 'center', background: 'var(--surface)', borderRadius: 16, padding: 13, border: '1px solid var(--line)', cursor: 'pointer' }}>
                <ImgSlotS label={p.cat} src={p.photo} h={50} style={{ width: 50, flexShrink: 0 }} />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                    <span style={{ width: 8, height: 8, borderRadius: '50%', background: whoColor(p.who) }} />
                    <span style={{ fontSize: 11, color: whoColor(p.who), fontWeight: 700 }}>{whoName(p.who)} · {p.cat}</span>
                  </div>
                  <div style={{ fontWeight: 700, fontSize: 15, color: 'var(--ink)', marginTop: 2 }}>{p.name}</div>
                  <div style={{ fontSize: 11.5, color: 'var(--ink-faint)', marginTop: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{p.note}</div>
                </div>
                <IconS name="chevR" size={16} style={{ color: 'var(--ink-faint)' }} />
              </div>
            ))}
            {pins.length === 0 && <div style={{ textAlign: 'center', color: 'var(--ink-faint)', fontSize: 13, padding: '30px 0' }}>まだピンがありません</div>}
          </div>
        </BodyS>
      )}

      {/* FAB — visible in both map and list modes, sits above BottomNav */}
      <button
        onClick={() => setAddSpotOpen(true)}
        style={{ position: 'absolute', bottom: NAV_H + 14, right: 22, width: 52, height: 52, borderRadius: '50%', background: 'var(--amethyst)', color: '#fff', border: 'none', cursor: 'pointer', boxShadow: '0 4px 18px rgba(61,54,44,.32)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 20, animation: 'pinpop .3s ease' }}
      >
        <IconS name="plus" size={26} />
      </button>

      <BottomNavS active="amulet-map" go={go} />

      {addSpotOpen && <AddSpotModal onClose={() => setAddSpotOpen(false)} live={live} toast={toast} />}

      {detail && (
        <SpotDetail pin={detail} whoName={whoName} whoColor={whoColor}
          onClose={() => setDetail(null)} onDelete={handleDelete} deleting={deleting} />
      )}
    </div>
  );
}

/* ════════════════ WISH LIST ════════════════ */
function WishListScreen() {
  const { go, live } = useAppS();
  const isLive = !!live.coupleId;
  const [liveItems, setLiveItems] = useSub(null); // Firestore items 配列そのまま
  const [tab, setTab] = useSub('todo');
  const [adding, setAdding] = useSub('');

  useESub(() => {
    if (!isLive) return;
    return Amulet.listenWishlist(live.coupleId, setLiveItems);
  }, [live.coupleId]);

  const partnerName = live.partner?.name || PARTNER_FALLBACK;

  /* items は {text, category, done, createdAt}。UIの形に整える（実データのみ） */
  const wishes = (liveItems || []).map((it, i) => ({ id: `${i}`, text: it.text, cat: it.category || 'メモ', pri: it.pri || '中', done: !!it.done, _raw: it }));

  const persist = (next) => {
    // UI形 → Firestore items 形に戻して全置き換え保存
    const items = next.map(w => ({ ...(w._raw || {}), text: w.text, category: w.cat, done: w.done, createdAt: (w._raw && w._raw.createdAt) || new Date().toISOString() }));
    Amulet.saveWishlist(live.coupleId, items).catch(console.error);
  };

  const toggle = id => {
    if (!isLive) return;
    const next = wishes.map(w => w.id === id ? { ...w, done: !w.done } : w);
    setLiveItems(next.map(w => ({ ...(w._raw || {}), text: w.text, category: w.cat, done: w.done })));
    persist(next);
  };
  const add = () => {
    if (!adding.trim() || !isLive) return;
    const next = [{ id: 'new', text: adding, cat: 'メモ', pri: '中', done: false }, ...wishes];
    setLiveItems(next.map(w => ({ ...(w._raw || {}), text: w.text, category: w.cat, done: w.done })));
    persist(next);
    setAdding('');
  };

  const list = wishes.filter(w => tab === 'todo' ? !w.done : w.done);
  const priColor = p => p === '高' ? 'var(--me)' : p === '中' ? 'var(--gold)' : 'var(--ink-faint)';

  return (
    <div style={{ height: '100%', background: 'var(--bg)', display: 'flex', flexDirection: 'column', position: 'relative' }}>
      <TopBarS title="小言キーパー" sub={`${partnerName}のための、ひみつの準備`} />
      <BodyS nav>
        {/* privacy note */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 9, background: 'color-mix(in srgb, var(--amethyst) 9%, var(--paper))', border: '1px solid color-mix(in oklch, var(--amethyst) 22%, transparent)', borderRadius: 14, padding: '11px 14px', marginBottom: 16 }}>
          <window.LottieBox src="/assets/dog_blue.json" viewBox="560 120 640 820" style={{ width: 30, height: 30, flexShrink: 0 }} />
          <span style={{ fontSize: 12, color: 'color-mix(in oklch, var(--amethyst) 68%, var(--ink))', fontWeight: 600, lineHeight: 1.5 }}>このリストは{partnerName}には表示されません。なんでも相談チャットの「小言」からAIが自動で整理しています。</span>
        </div>

        {/* add */}
        <div style={{ display: 'flex', gap: 8, marginBottom: 18 }}>
          <div style={{ flex: 1 }}><FieldS placeholder="気づいたことをメモ…" value={adding} onChange={setAdding} icon="note" /></div>
          <button onClick={add} style={{ width: 50, borderRadius: 14, border: 'none', background: 'var(--amethyst)', color: '#fff', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}><IconS name="plus" size={22} /></button>
        </div>

        {/* tabs */}
        <div style={{ display: 'flex', gap: 4, background: 'var(--surface-2)', borderRadius: 12, padding: 4, marginBottom: 16, border: '1px solid var(--line)' }}>
          {[['todo', `未チェック ${wishes.filter(w => !w.done).length}`], ['done', `済み ${wishes.filter(w => w.done).length}`]].map(([id, lab]) => (
            <button key={id} onClick={() => setTab(id)} style={{ flex: 1, padding: '10px', borderRadius: 9, border: 'none', cursor: 'pointer', fontFamily: 'var(--font-ui)', fontWeight: tab === id ? 700 : 500, fontSize: 13.5, background: tab === id ? 'var(--surface)' : 'transparent', color: tab === id ? 'var(--ink)' : 'var(--ink-faint)', boxShadow: tab === id ? '0 2px 8px -4px rgba(61,54,44,.4)' : 'none' }}>{lab}</button>
          ))}
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {list.map(w => (
            <div key={w.id} style={{ display: 'flex', gap: 12, alignItems: 'flex-start', background: 'var(--surface)', borderRadius: 16, padding: 15, border: '1px solid var(--line)', opacity: w.done ? 0.6 : 1 }}>
              <button onClick={() => toggle(w.id)} style={{ width: 24, height: 24, borderRadius: 8, flexShrink: 0, cursor: 'pointer', marginTop: 1, border: w.done ? 'none' : '2px solid var(--line-2)', background: w.done ? 'var(--partner)' : 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                {w.done && <IconS name="check" size={15} stroke="#fff" sw={2.4} />}
              </button>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 14.5, color: 'var(--ink)', lineHeight: 1.6, fontWeight: 500, textDecoration: w.done ? 'line-through' : 'none' }}>{w.text}</div>
                <div style={{ display: 'flex', gap: 7, marginTop: 8 }}>
                  <span style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink-soft)', background: 'var(--surface-2)', borderRadius: 999, padding: '3px 10px' }}>{w.cat}</span>
                  <span style={{ fontSize: 11, fontWeight: 700, color: priColor(w.pri), background: `color-mix(in oklch, ${priColor(w.pri)} 14%, var(--paper))`, borderRadius: 999, padding: '3px 10px' }}>優先 {w.pri}</span>
                </div>
              </div>
            </div>
          ))}
          {list.length === 0 && (
            <div style={{ textAlign: 'center', color: 'var(--ink-faint)', fontSize: 13, padding: '30px 0', lineHeight: 1.8 }}>
              まだありません<br />
              <span style={{ fontSize: 12 }}>なんでも相談チャットで「{partnerName}が◯◯欲しいって言ってた」と話すと、自動でここに溜まります</span>
            </div>
          )}
        </div>
      </BodyS>
      <BottomNavS active="wish-list" go={go} />
    </div>
  );
}

Object.assign(window, { AmuletMapScreen, WishListScreen });

})();
