// ============================================================
// Husky Paths — Main App
// ============================================================

// ── Tweak defaults ─────────────────────────────────────────
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "mapStyle": "huskies",
  "pinCount": true,
  "arcStyle": "dashed",
  "showLabels": false,
  "language": "bi",
  "density": "comfy",
  "projection": "globe"
}/*EDITMODE-END*/;

// ── Disclaimer / 免责声明 ──────────────────────────────
// 同一个 card 用于：(a) 首次访问 gate；(b) 点击底部链接复看
const DISCLAIMER_STORAGE_KEY = "hp_disclaimer_ack_v1";

const DisclaimerCard = ({ mode, onAccept, onClose }) => {
  // mode: "gate" → 必须点 Got it 才能关；"info" → 仅查看
  return (
    <div className="hp-disclaimer-scrim" onClick={mode === "info" ? onClose : undefined}>
      <div className="hp-disclaimer-card" onClick={(e) => e.stopPropagation()}>
        <div>
          {window.Overline && (
            <window.Overline en="Before you explore" zh="在开始之前" color="var(--accent-coral)" />
          )}
          <h2 className="hp-modal-title">A friendly heads-up.</h2>
        </div>
        <ul className="hp-disclaimer-points">
          <li>
            <span>
              <strong style={{ color: "var(--fg-primary)", fontWeight: 600 }}>Not an official UW project.</strong>
              {" "}Husky Paths is a student-built side project by a UW community member, not affiliated with or endorsed by the University of Washington.
            </span>
          </li>
          <li>
            <span>
              <strong style={{ color: "var(--fg-primary)", fontWeight: 600 }}>All data is voluntary and self-reported.</strong>
              {" "}Destinations and notes are submitted by users. We do not verify employer offers, grad school admissions, or career outcomes.
            </span>
          </li>
          <li>
            <span>
              <strong style={{ color: "var(--fg-primary)", fontWeight: 600 }}>Preview profiles are not real people.</strong>
              {" "}Anything marked "Preview path · no contact data" is a placeholder sample to illustrate the map. Real submissions appear once verified Huskies fill it in.
            </span>
          </li>
          <li>
            <span>
              <strong style={{ color: "var(--fg-primary)", fontWeight: 600 }}>Contact details are for one-to-one, friendly outreach only.</strong>
              {" "}No recruiting spam, no mass messaging, no commercial pitches. Be the kind of senior you wish you'd had.
            </span>
          </li>
        </ul>
        <p className="hp-italic" style={{ fontSize: 12, color: "var(--fg-secondary)", margin: 0 }}>
          中文：本站为个人学生项目，非华盛顿大学官方产品；所有路径数据由用户自愿提交，未经核实；标记为 Preview 的资料为示意，并非真实校友；联系方式仅供一对一友善交流，禁止商业骚扰。
        </p>
        <div className="hp-disclaimer-foot">
          {mode === "info" ? (
            <button className="hp-btn-primary" onClick={onClose}>Close</button>
          ) : (
            <button className="hp-btn-primary" onClick={onAccept}>
              Got it · 我明白了 <span className="hp-btn-arrow">→</span>
            </button>
          )}
        </div>
      </div>
    </div>
  );
};

function DisclaimerGate({ onAccept }) {
  const [needsAck, setNeedsAck] = useState(false);
  useEffect(() => {
    try {
      const acked = window.localStorage?.getItem(DISCLAIMER_STORAGE_KEY);
      if (!acked) setNeedsAck(true);
    } catch (_e) {
      setNeedsAck(true);
    }
  }, []);
  if (!needsAck) return null;
  return (
    <DisclaimerCard
      mode="gate"
      onAccept={() => {
        try { window.localStorage?.setItem(DISCLAIMER_STORAGE_KEY, String(Date.now())); } catch (_e) {}
        setNeedsAck(false);
        onAccept?.();
      }}
    />
  );
}

// ── UW Intro splash (first-load only) ──────────────────
function HuskyIntro({ onDone }) {
  useEffect(() => {
    const t = setTimeout(onDone, 2300);
    return () => clearTimeout(t);
  }, [onDone]);

  const word = "UNIVERSITY OF WASHINGTON";
  // Stagger starts after the W badge settles (~600ms)
  const baseDelay = 600;
  const step = 38;

  return (
    <div className="hp-intro" role="status" aria-label="Loading Husky Paths">
      <div className="hp-intro-stack">
        {/* W badge — UW Purple coin with chunky gold Block W */}
        <div className="hp-intro-badge">
          <svg viewBox="0 0 100 100" width="120" height="120">
            <circle cx="50" cy="50" r="48" fill="none" stroke="#4B2E83" strokeWidth="0.6" opacity="0.35" className="hp-intro-ring hp-intro-ring-1"/>
            <circle cx="50" cy="50" r="40" fill="none" stroke="#4B2E83" strokeWidth="0.7" opacity="0.45" className="hp-intro-ring hp-intro-ring-2"/>
            <circle cx="50" cy="50" r="34" fill="#4B2E83" className="hp-intro-coin"/>
            {/* Block W — Poppins ExtraBold in white on purple coin */}
            <text
              x="50" y="68"
              textAnchor="middle"
              fontFamily="Poppins, sans-serif"
              fontWeight="900"
              fontSize="50"
              fill="#FFFFFF"
              stroke="#FFFFFF"
              strokeWidth="2"
              letterSpacing="-3"
              className="hp-intro-w"
            >
              W
            </text>
          </svg>
        </div>

        {/* Letter-by-letter wordmark */}
        <div className="hp-intro-wordmark" aria-hidden>
          {word.split("").map((c, i) => (
            <span
              key={i}
              className="hp-intro-letter"
              style={{ animationDelay: `${baseDelay + i * step}ms` }}
            >
              {c === " " ? "\u00A0" : c}
            </span>
          ))}
        </div>

        {/* Editorial slogan */}
        <div className="hp-intro-sub">
          <span className="hp-intro-sparkle">✦</span>
          <span>BE BOUNDLESS</span>
          <span className="hp-intro-sparkle">✦</span>
        </div>
      </div>

      {/* Credit watermark inside the splash */}
      <div className="hp-intro-credit">
        <span className="hp-watermark-mark">✦</span>
        <span>Made by</span>
        <strong>Ao Gao</strong>
      </div>
    </div>
  );
}

function App() {
  // 语言初值:localStorage 记住的手动选择 > 跟随浏览器
  const initialLang = (() => {
    try { return localStorage.getItem("hp_lang") || hpDefaultLang(); }
    catch (_e) { return hpDefaultLang(); }
  })();
  const tweakState = window.useTweaks
    ? window.useTweaks({ ...TWEAK_DEFAULTS, language: initialLang })
    : [{ ...TWEAK_DEFAULTS, language: initialLang }, () => {}];
  const tweaks = tweakState[0];
  const setTweak = tweakState[1];

  // 同步到全局,供 t(en, zh) 在任意组件读取(每次渲染前更新)
  window.__HP_LANG = tweaks.language;

  // 语言切换:更新 tweak + 记住选择
  const setLang = (lang) => {
    setTweak("language", lang);
    window.__HP_LANG = lang;
    try { localStorage.setItem("hp_lang", lang); } catch (_e) {}
  };

  // Data state
  const [allProfiles, setAllProfiles] = useState(PROFILES);
  const backend = window.HuskyBackend;
  const backendReady = !!backend?.isConfigured?.();
  const [authSession, setAuthSession] = useState(null);
  const [myProfile, setMyProfile] = useState(null);
  const [backendStatus, setBackendStatus] = useState(
    backendReady
      ? "Verify your @uw.edu email to load live submissions."
      : "Demo mode: add your Supabase URL and anon key to enable the backend."
  );

  const loadLiveProfiles = async () => {
    if (!backendReady) return;
    try {
      // 公开去向：未登录访客也能读（RLS 已开放 anon 读 public_profiles，联系方式仍受保护）
      const liveProfiles = await backend.fetchProfiles();
      // 审核通过的提交(Supabase 里 status=approved 即自动上墙);后端不通则忽略
      let approved = [];
      try { approved = await backend.fetchApprovedSubmissions(); } catch (_e) { approved = []; }
      // 去重:已在 PROFILES(社区/示例)里的同名同城不重复显示
      const seen = new Set(PROFILES.map((p) => (p.name || "") + "|" + p.city));
      const approvedNew = approved.filter((a) => !seen.has((a.name || "") + "|" + a.city));
      // 真实数据还少时用少量示例垫底;攒够 40+ 后只显示真实(审核通过的始终保留)
      const display = liveProfiles.length >= 40
        ? [...approvedNew, ...liveProfiles]
        : [...PROFILES, ...approvedNew, ...liveProfiles];
      setAllProfiles(display);
      setBackendStatus(`${liveProfiles.length + approvedNew.length} verified paths`);
    } catch (err) {
      setBackendStatus(err.message || "Could not load live profiles.");
    }
  };

  // 自己的完整资料（含联系方式）只有登录用户能读
  const loadMyProfile = async () => {
    try { setMyProfile(await backend.getMyProfile()); }
    catch (_e) { setMyProfile(null); }
  };

  useEffect(() => {
    if (!backendReady) return;
    let active = true;

    backend.getSession()
      .then((session) => {
        if (!active) return;
        setAuthSession(session);
        loadLiveProfiles();              // 总是加载真实去向（未登录访客也能看见）
        if (session) loadMyProfile();
      })
      .catch((err) => setBackendStatus(err.message || "Could not restore Supabase session."));

    const unsubscribe = backend.onAuthStateChange((session) => {
      setAuthSession(session);
      loadLiveProfiles();                // 登录/登出都重新加载（地图始终显示真实去向）
      if (session) loadMyProfile();
      else setMyProfile(null);           // 登出只清自己的资料，地图仍保留真实去向
    });

    return () => {
      active = false;
      unsubscribe();
    };
  }, [backendReady]);

  // Filter state — v2: 路径类型改单选 + 新增 openOnly
  const [query, setQuery] = useState("");
  const [activeType, setActiveType] = useState(null); // 单选；null = All
  const [openOnly, setOpenOnly] = useState(false);
  const [activeYear, setActiveYear] = useState(null);

  // All distinct years for the scrubber, ascending
  const allYears = useMemo(() => {
    const s = new Set(allProfiles.map((p) => Number(p.year)));
    return [...s].sort((a, b) => a - b);
  }, [allProfiles]);

  // Selection state
  const [selectedCity, setSelectedCity] = useState(null);
  const [hoveredCity, setHoveredCity] = useState(null);
  const [submitOpen, setSubmitOpen] = useState(false);
  const [editingProfile, setEditingProfile] = useState(null);
  const [verifyAccessOpen, setVerifyAccessOpen] = useState(false);
  const [pendingContactProfile, setPendingContactProfile] = useState(null);
  const [contactProfile, setContactProfile] = useState(null);

  // UI state
  const [railCollapsed, setRailCollapsed] = useState(false);
  const [userToggledRail, setUserToggledRail] = useState(false);
  const [mobileSheetOpen, setMobileSheetOpen] = useState(false);
  const [legalInfoOpen, setLegalInfoOpen] = useState(false);
  const [feedbackOpen, setFeedbackOpen] = useState(false);
  const [reviewOpen, setReviewOpen] = useState(false);
  const [reviewPrefill, setReviewPrefill] = useState(null);
  // Intro splash — show on first mount only, ~2.3s total
  const [introDone, setIntroDone] = useState(false);
  const [introFading, setIntroFading] = useState(false);
  // After the entire entrance choreography finishes, drop the 1.9s arc
  // draw-delay so filter changes feel snappy.
  const [settled, setSettled] = useState(false);
  useEffect(() => {
    const t = setTimeout(() => setSettled(true), 3500);
    return () => clearTimeout(t);
  }, []);

  // 兜底：清理邮件 magic-link 回跳带来的 #error（如链接过期/无效），
  // 避免一串报错留在地址栏；并提示用户改用 6 位验证码。
  useEffect(() => {
    const hash = window.location.hash || "";
    if (/error_code=|error=access_denied/.test(hash)) {
      const expired = /otp_expired|expired|invalid/i.test(hash);
      window.history.replaceState(null, "", window.location.pathname + window.location.search);
      if (expired) {
        setBackendStatus("That email link expired. Enter the 6-digit code from your email instead, or resend a new one. · 邮件链接已过期，请改用邮件里的 6 位验证码，或重新发送。");
      }
    }
  }, []);

  // Auto-collapse the rail when the spotlight is open and viewport is narrow
  // (so the rail's React content matches the CSS-collapsed column width).
  useEffect(() => {
    if (userToggledRail) return;
    const apply = () => {
      const narrow = window.innerWidth < 1180;
      setRailCollapsed(narrow && !!selectedCity);
    };
    apply();
    window.addEventListener("resize", apply);
    return () => window.removeEventListener("resize", apply);
  }, [selectedCity, userToggledRail]);

  const onToggleRail = () => {
    setUserToggledRail(true);
    setRailCollapsed((v) => !v);
  };

  // Apply filters — v2: 单选 activeType + openOnly
  const filteredProfiles = useMemo(() => {
    const q = query.trim().toLowerCase();
    return allProfiles.filter((p) => {
      if (activeType && p.pathType !== activeType) return false;
      if (activeYear != null && Number(p.year) !== activeYear) return false;
      if (openOnly && !p.openToConnect) return false;
      if (!q) return true;
      return (
        (p.name || "").toLowerCase().includes(q) ||
        p.major.toLowerCase().includes(q) ||
        p.orgName.toLowerCase().includes(q) ||
        p.city.toLowerCase().includes(q) ||
        (p.pathType || "").toLowerCase().includes(q)
      );
    });
  }, [allProfiles, query, activeType, activeYear, openOnly]);

  // For spotlight: profiles in selected city (intersected with filters)
  const cityProfiles = useMemo(() => {
    if (!selectedCity) return [];
    return filteredProfiles.filter((p) => p.city === selectedCity);
  }, [filteredProfiles, selectedCity]);

  const clearFilters = () => {
    setQuery("");
    setActiveType(null);
    setOpenOnly(false);
    setActiveYear(null);
  };

  const handleAddProfile = async (newProfile) => {
    if (!backendReady) {
      setAllProfiles((prev) => [...prev, newProfile]);
      return newProfile;
    }

    const savedProfile = await backend.upsertProfile(newProfile);
    await loadLiveProfiles();
    setEditingProfile(null);
    return savedProfile;
  };

  const openSubmitFlow = (profile = null) => {
    setEditingProfile(profile);
    setSubmitOpen(true);
  };

  const closeSubmitFlow = () => {
    setSubmitOpen(false);
    setEditingProfile(null);
  };

  const handleDeleteMyPath = async () => {
    if (!backendReady || !myProfile) return;
    const confirmed = window.confirm("Delete your Husky Paths submission? This removes your destination and contact details.");
    if (!confirmed) return;
    try {
      await backend.deleteMyProfile();
      await loadLiveProfiles();
      setBackendStatus("Your path was deleted.");
    } catch (err) {
      setBackendStatus(err.message || "Could not delete your path.");
    }
  };

  const handleConnect = (profile) => {
    if (!profile.openToConnect) return;
    // 社区/审核通过的条目:联系方式是本人主动公开的,任何访客可直接看,不走 UW 验证
    if (backendReady && !authSession && !profile.isCommunity) {
      setPendingContactProfile(profile);
      setVerifyAccessOpen(true);
      return;
    }
    setContactProfile(profile);
  };

  // 显式登录入口(复用 OTP 验证流程,不针对某条联系方式)
  const openLogin = () => {
    setPendingContactProfile(null);
    setVerifyAccessOpen(true);
  };

  const handleSignOut = async () => {
    try { if (backendReady) await backend.signOut(); } catch (_e) {}
    setAuthSession(null);
    setMyProfile(null);
  };

  const handleContactVerified = async (session) => {
    setAuthSession(session);
    setVerifyAccessOpen(false);
    setPendingContactProfile(null);
    if (backendReady) {
      await loadLiveProfiles();
    }
  };

  const cityHasProfiles = !!selectedCity && (selectedCity === "Seattle" || cityProfiles.length > 0);

  return (
    <div className={`hp-app ${railCollapsed ? "rail-collapsed" : ""} ${selectedCity && cityHasProfiles ? "has-spotlight" : ""}`}>
      {/* UW intro splash (first load) */}
      {!introDone && (
        <div className={`hp-intro-shell ${introFading ? "is-fading" : ""}`}>
          <HuskyIntro onDone={() => {
            setIntroFading(true);
            setTimeout(() => setIntroDone(true), 500);
          }}/>
        </div>
      )}
      {/* Mobile top bar */}
      <header className="hp-mobile-bar">
        <div className="hp-brand-compact">
          <HPIcons.ScanMark size={22} color="var(--accent-coral)" />
          <span>Husky Paths</span>
        </div>
        <div className="hp-mobile-bar-right">
          <div className="hp-lang-toggle" role="group" aria-label="Language">
            <button className={`hp-lang-opt ${tweaks.language !== "zh" ? "is-active" : ""}`} onClick={() => setLang("en")}>EN</button>
            <button className={`hp-lang-opt ${tweaks.language === "zh" ? "is-active" : ""}`} onClick={() => setLang("zh")}>中</button>
          </div>
          {backendReady && (
            authSession
              ? <button className="hp-auth-chip" onClick={handleSignOut}><span className="hp-auth-dot" />{t("Out", "登出")}</button>
              : <button className="hp-auth-chip is-cta" onClick={openLogin}>{t("Sign in", "登录")}</button>
          )}
          <button className="hp-icon-btn" onClick={() => setMobileSheetOpen(true)} title="Open explore">
            <HPIcons.Search />
          </button>
        </div>
      </header>

      {!mobileSheetOpen && !selectedCity && (
        <div className="hp-mobile-hero-card">
          <div>
            <div className="hp-mobile-kicker">UW alumni atlas</div>
            <h1>Where Huskies go next.</h1>
            <p>Browse destinations. Verify UW email before viewing contact details.</p>
          </div>
          <div className="hp-mobile-actions">
            <button className="hp-btn-ghost" onClick={() => setMobileSheetOpen(true)}>Explore paths</button>
            <button className="hp-btn-primary" onClick={() => openSubmitFlow()}>{t("Add my path", "添加我的路径")}</button>
          </div>
        </div>
      )}

      {/* Left rail (desktop) */}
      <div className="hp-rail-slot">
        <ExplorePanel
          profiles={allProfiles}
          filteredProfiles={filteredProfiles}
          clearFilters={clearFilters}
          onCityClick={(c) => setSelectedCity(c)}
          onConnect={handleConnect}
          canViewContacts={!backendReady || !!authSession}
          onAddPath={() => openSubmitFlow()}
          tweaks={tweaks}
          collapsed={railCollapsed}
          onToggleCollapse={onToggleRail}
        />
      </div>

      {/* Map column: filter bar on top, map below */}
      <main className="hp-map-slot">
        <FilterBar
          total={allProfiles.length}
          filteredCount={filteredProfiles.length}
          query={query} setQuery={setQuery}
          activeType={activeType} setActiveType={setActiveType}
          openOnly={openOnly} setOpenOnly={setOpenOnly}
          activeYear={activeYear} setActiveYear={setActiveYear}
          clearFilters={clearFilters}
          tweaks={tweaks}
        />
        <WorldMap
          filteredProfiles={filteredProfiles}
          selectedCity={selectedCity}
          hoveredCity={hoveredCity}
          onCityHover={setHoveredCity}
          onCityClick={(c) => setSelectedCity(c)}
          onBackgroundClick={() => setSelectedCity(null)}
          tweaks={tweaks}
          setTweak={setTweak}
          activeYear={activeYear}
          setActiveYear={setActiveYear}
          allYears={allYears}
          settled={settled}
        />
      </main>

      {/* Spotlight */}
      {selectedCity && cityHasProfiles && (
        <aside className="hp-spotlight-slot">
          <DestinationSpotlight
            city={selectedCity}
            profiles={cityProfiles}
            onClose={() => setSelectedCity(null)}
            onConnect={handleConnect}
            canViewContacts={!backendReady || !!authSession}
            tweaks={tweaks}
          />
        </aside>
      )}

      {/* Spotlight: nothing matches filters */}
      {selectedCity && !cityHasProfiles && (
        <aside className="hp-spotlight-slot">
          <div className="hp-spotlight">
            <div className="hp-spotlight-head">
              <div>
                <Overline en="Destination" zh="目的地" color="var(--accent-coral)" />
                <h2 className="hp-spot-title">{selectedCity}</h2>
              </div>
              <button className="hp-close-btn" onClick={() => setSelectedCity(null)}><HPIcons.X /></button>
            </div>
            <div className="hp-spot-empty">
              <div className="hp-spot-mark">
                <HPIcons.ScanMark size={56} color="var(--fg-muted)" />
              </div>
              <h3 className="hp-card-h">No Huskies match this filter.</h3>
              <p className="hp-italic">
                There <em>are</em> alumni here, but they're filtered out by your current selection.
                <br/>
                <button className="hp-link-btn" onClick={clearFilters}>Clear filters</button> to see them all.
              </p>
            </div>
          </div>
        </aside>
      )}

      {/* Mobile bottom sheet (Explore) */}
      <div className={`hp-mobile-sheet ${mobileSheetOpen ? "is-open" : ""}`} onClick={() => setMobileSheetOpen(false)}>
        <div className="hp-mobile-sheet-card" onClick={(e) => e.stopPropagation()}>
          <div className="hp-mobile-sheet-handle"></div>
          <ExplorePanel
            profiles={allProfiles}
            filteredProfiles={filteredProfiles}
            clearFilters={clearFilters}
            onCityClick={(c) => { setSelectedCity(c); setMobileSheetOpen(false); }}
            onConnect={handleConnect}
            canViewContacts={!backendReady || !!authSession}
            onAddPath={() => { openSubmitFlow(); setMobileSheetOpen(false); }}
            tweaks={tweaks}
            collapsed={false}
            onToggleCollapse={() => setMobileSheetOpen(false)}
          />
        </div>
      </div>

      {/* Floating + button (mobile + when rail collapsed) */}
      <button className={`hp-fab ${!selectedCity && !mobileSheetOpen ? "is-hidden-mobile" : ""}`} onClick={() => openSubmitFlow()} title="Add my path">
        ＋
      </button>

      {authSession && (
        <div className="hp-account-dock">
          <div className="hp-account-copy">
            <strong>{myProfile ? t("My path is live", "我的路径已上线") : t("Verified UW member", "已验证 UW 成员")}</strong>
            <span>{myProfile ? `${myProfile.orgName} · ${myProfile.city}` : authSession.user.email}</span>
          </div>
          {myProfile ? (
            <>
              <button className="hp-dock-btn" onClick={() => openSubmitFlow(myProfile)}>{t("Edit", "编辑")}</button>
              <button className="hp-dock-btn hp-dock-danger" onClick={handleDeleteMyPath}>{t("Delete", "删除")}</button>
            </>
          ) : (
            <button className="hp-dock-btn" onClick={() => openSubmitFlow()}>{t("Add path", "添加路径")}</button>
          )}
        </div>
      )}

      {/* Submit flow */}
      <SubmitFlow
        isOpen={submitOpen}
        onClose={closeSubmitFlow}
        onSubmit={handleAddProfile}
        backendReady={backendReady}
        authSession={authSession}
        onAuthChanged={setAuthSession}
        backendStatus={backendStatus}
        tweaks={tweaks}
        initialProfile={editingProfile}
        onSubmitForReview={(prefill) => { closeSubmitFlow(); setReviewPrefill(prefill || null); setReviewOpen(true); }}
      />

      {/* 失效 @uw.edu 校友的审核提交通道 */}
      <SubmitForReviewModal
        isOpen={reviewOpen}
        onClose={() => { setReviewOpen(false); setReviewPrefill(null); }}
        backendReady={backendReady}
        initialData={reviewPrefill}
      />

      <VerifyAccessModal
        isOpen={verifyAccessOpen}
        onClose={() => { setVerifyAccessOpen(false); setPendingContactProfile(null); }}
        onVerified={handleContactVerified}
        backendStatus={backendStatus}
        tweaks={tweaks}
        overline={pendingContactProfile ? undefined : t("Sign in", "登录")}
        title={pendingContactProfile ? undefined : t("Sign in with your UW email.", "用 UW 邮箱登录。")}
        subtitle={pendingContactProfile ? undefined : t("Verified Huskies can add or edit their path and see contacts of those open to connect.", "验证后即可添加/编辑你的路径,并查看开放联系的校友的联系方式。")}
      />

      {/* Contact card */}
      {contactProfile && (
        <ContactModal
          profile={contactProfile}
          onClose={() => setContactProfile(null)}
          tweaks={tweaks}
        />
      )}

      {/* Tweaks panel */}
      <HuskyTweaksPanel tweaks={tweaks} setTweak={setTweak} />

      {/* Top-right cluster: sign in / out + language toggle */}
      <div className="hp-topbar-right">
        {backendReady && (
          authSession ? (
            <button className="hp-auth-chip" onClick={handleSignOut} title={authSession.user?.email || ""}>
              <span className="hp-auth-dot" /> {t("Sign out", "登出")}
            </button>
          ) : (
            <button className="hp-auth-chip is-cta" onClick={openLogin}>
              {t("Sign in", "登录")}
            </button>
          )
        )}
        <div className="hp-lang-toggle" role="group" aria-label="Language">
          <button
            className={`hp-lang-opt ${tweaks.language !== "zh" ? "is-active" : ""}`}
            onClick={() => setLang("en")}
          >EN</button>
          <button
            className={`hp-lang-opt ${tweaks.language === "zh" ? "is-active" : ""}`}
            onClick={() => setLang("zh")}
          >中</button>
        </div>
      </div>

      {/* Watermark — top-right credit */}
      <div className="hp-watermark" aria-label="Made by Ao Gao">
        <span className="hp-watermark-mark">✦</span>
        <span>Made by</span>
        <strong>Ao Gao</strong>
      </div>

      {/* 底部常驻免责 */}
      <div className="hp-footer-bar" role="contentinfo">
        <span className="hp-footer-disclaimer-text">
          {t("Independent student project · Not affiliated with UW", "学生独立项目 · 非 UW 官方产品")}
        </span>
        <span className="hp-footer-sep">·</span>
        <button type="button" className="hp-footer-link" onClick={() => setLegalInfoOpen(true)}>
          {t("Disclaimer & Use", "免责声明")}
        </button>
        <span className="hp-footer-sep">·</span>
        <button type="button" className="hp-footer-link" onClick={() => setFeedbackOpen(true)}>
          {t("Feedback", "反馈")}
        </button>
      </div>

      {/* 首次访问 gate */}
      <DisclaimerGate />

      {/* 用户主动复看的 info modal */}
      {legalInfoOpen && (
        <DisclaimerCard mode="info" onClose={() => setLegalInfoOpen(false)} />
      )}

      {/* 反馈弹窗 */}
      <FeedbackModal
        isOpen={feedbackOpen}
        onClose={() => setFeedbackOpen(false)}
        backendReady={backendReady}
      />
    </div>
  );
}

// ── Custom Tweaks Panel for Husky Paths ──────────────────
function HuskyTweaksPanel({ tweaks, setTweak }) {
  if (!window.TweaksPanel) return null;
  const { TweaksPanel, TweakSection, TweakRadio, TweakSelect, TweakToggle } = window;

  return (
    <TweaksPanel title="Tweaks">
      <TweakSection label="View · 视角" />
      <TweakRadio
        label="Projection"
        value={tweaks.projection}
        onChange={(v) => setTweak("projection", v)}
        options={[
          { value: "flat",  label: "Flat · 平面" },
          { value: "globe", label: "Globe · 球面" },
        ]}
      />

      <TweakSection label="Look · 外观" />
      <TweakSelect
        label="Map style"
        value={tweaks.mapStyle}
        onChange={(v) => setTweak("mapStyle", v)}
        options={[
          { value: "huskies", label: "Husky Purple · 紫金" },
          { value: "paper",   label: "Lavender Paper · 淡紫" },
          { value: "minimal", label: "Minimal · 极简" },
          { value: "warm",    label: "Aubergine · 茄色" },
        ]}
      />
      <TweakRadio
        label="Arcs"
        value={tweaks.arcStyle}
        onChange={(v) => setTweak("arcStyle", v)}
        options={[
          { value: "solid",  label: "Solid" },
          { value: "dashed", label: "Dashed" },
        ]}
      />

      <TweakSection label="Pins · 图钉" />
      <TweakToggle
        label="Count badges"
        value={tweaks.pinCount}
        onChange={(v) => setTweak("pinCount", v)}
      />
      <TweakToggle
        label="Always show city labels"
        value={tweaks.showLabels}
        onChange={(v) => setTweak("showLabels", v)}
      />

      <TweakSection label="Voice · 文案" />
      <TweakRadio
        label="Language"
        value={tweaks.language}
        onChange={(v) => setTweak("language", v)}
        options={[
          { value: "en", label: "English" },
          { value: "zh", label: "中文" },
        ]}
      />
      <TweakRadio
        label="Density"
        value={tweaks.density}
        onChange={(v) => setTweak("density", v)}
        options={[
          { value: "comfy",   label: "Comfy" },
          { value: "compact", label: "Compact" },
        ]}
      />
    </TweaksPanel>
  );
}

// ── Root：按 #admin 路由到后台,否则渲染地图 App ──
function Root() {
  const [hash, setHash] = useState(typeof window !== "undefined" ? window.location.hash : "");
  useEffect(() => {
    const onHash = () => setHash(window.location.hash);
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, []);
  const isAdmin = hash.replace(/^#\/?/, "").toLowerCase() === "admin";
  if (isAdmin && window.AdminPanel) return <window.AdminPanel />;
  return <App />;
}

// Mount
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Root />);
