// ================= Event handlers =================
    function bind() {
      document.querySelectorAll("[data-filter]").forEach(btn => btn.addEventListener("click", () => { state.filter = btn.dataset.filter; render(); }));
      document.querySelectorAll("[data-win]").forEach(btn => {
        btn.addEventListener("click", e => { if (!btn.dataset.suppressClick) openWin(btn.dataset.win); });
      });
      makeDraggable(document.querySelectorAll(".bubble"));
      makeWinMapPannable(document.getElementById("fullBoard"));
      makeWinMapPannable(document.getElementById("previewBoard"), { compact: true, stateKey: "winPreviewMapOffset" });

      const winForm = document.getElementById("winForm");
      if (winForm) winForm.addEventListener("submit", submitWinForm);
      const loginForm = document.getElementById("loginForm");
      if (loginForm) loginForm.addEventListener("submit", submitLoginForm);
      const logoutButton = document.getElementById("logoutButton");
      if (logoutButton) logoutButton.addEventListener("click", handleLogout);
      const checkinForm = document.getElementById("checkinForm");
      if (checkinForm) checkinForm.addEventListener("submit", submitCheckinForm);
      const shareAnon = document.getElementById("shareAnon");
      if (shareAnon) shareAnon.addEventListener("click", shareSuggestedAnon);
      const editSuggestion = document.getElementById("editSuggestion");
      if (editSuggestion) editSuggestion.addEventListener("click", () => { const p = state.suggestedWin; state.suggestedWin = null; routeTo(ROUTES.SUBMIT); setTimeout(() => renderPrefill(p), 0); });
      const skipSuggestion = document.getElementById("skipSuggestion");
      if (skipSuggestion) skipSuggestion.addEventListener("click", () => { state.suggestedWin = null; routeTo(ROUTES.HOME); });
      document.querySelectorAll("[data-admin-tab]").forEach(btn => btn.addEventListener("click", () => { state.adminTab = btn.dataset.adminTab; render(); }));
      document.querySelectorAll("[data-toggle-hide]").forEach(btn => btn.addEventListener("click", () => { const w = findWin(btn.dataset.toggleHide); w.isHidden = !w.isHidden; save(); render(); }));
      document.querySelectorAll("[data-toggle-feature]").forEach(btn => btn.addEventListener("click", () => { const w = findWin(btn.dataset.toggleFeature); w.isFeatured = !w.isFeatured; save(); render(); }));
      document.querySelectorAll("[data-request-reward]").forEach(btn => btn.addEventListener("click", () => {
        const reward = state.rewards.find(item => item.id === btn.dataset.requestReward);
        if (reward) requestReward(reward);
        render();
      }));
      document.querySelectorAll("[data-redeem]").forEach(btn => btn.addEventListener("click", () => {
        const redemption = state.redemptions.find(item => item.id === btn.dataset.redeem);
        if (redemption) {
          redemption.status = REWARD_STATUSES.REDEEMED;
          redemption.redeemedAt = new Date().toISOString().slice(0, 10);
          save();
        }
        render();
      }));
      document.querySelectorAll("[data-save-tip]").forEach(btn => btn.addEventListener("click", () => {
        awardRule("save_tip", "Saved a helpful tip");
        render();
      }));
      document.querySelectorAll("[data-meal-deck]").forEach(btn => btn.addEventListener("click", () => {
        const id = btn.dataset.mealDeck;
        const deck = mealDecks.find(item => item.id === id);
        state.mealDeckOpen = state.mealDeckOpen === id ? "" : id;
        if (deck) state.mealNeed = deck.need;
        state.expandedMealWhy = "";
        render();
      }));
      document.querySelectorAll("[data-meal-why]").forEach(btn => btn.addEventListener("click", e => {
        e.stopPropagation();
        const id = btn.dataset.mealWhy;
        state.expandedMealWhy = state.expandedMealWhy === id ? "" : id;
        render();
      }));
      document.querySelectorAll("[data-save-meal]").forEach(btn => btn.addEventListener("click", e => {
        e.stopPropagation();
        const id = btn.dataset.saveMeal;
        if (!state.savedMealIdeas.includes(id)) state.savedMealIdeas.push(id);
        state.mealSaveToast = id;
        awardRule("save_tip", "Saved a meal idea");
        save();
        render();
        setTimeout(() => {
          if (state.mealSaveToast === id) {
            state.mealSaveToast = "";
            render();
          }
        }, 1500);
      }));
      const mealSnapshotFile = document.getElementById("mealSnapshotFile");
      if (mealSnapshotFile) mealSnapshotFile.addEventListener("change", e => {
        const file = e.currentTarget.files?.[0];
        if (!file || !file.type.startsWith("image/")) return;
        const reader = new FileReader();
        reader.onload = () => {
          state.mealSnapshotPhoto = String(reader.result || "");
          state.mealSnapshotNotice = "";
          render();
        };
        reader.readAsDataURL(file);
      });
      const buildMealSnapshot = document.getElementById("buildMealSnapshot");
      if (buildMealSnapshot) buildMealSnapshot.addEventListener("click", () => {
        state.mealSnapshotIngredients = mockMealSnapshotResult.ingredients.map(item => ({ ...item }));
        state.mealSnapshotResult = { ...mockMealSnapshotResult };
        state.mealSnapshotStep = "confirm";
        state.mealSnapshotNotice = "";
        render();
      });
      document.querySelectorAll("[data-snapshot-step]").forEach(btn => btn.addEventListener("click", () => {
        state.mealSnapshotStep = btn.dataset.snapshotStep;
        state.mealSnapshotNotice = "";
        render();
      }));
      document.querySelectorAll("[data-remove-snapshot-ingredient]").forEach(btn => btn.addEventListener("click", () => {
        state.mealSnapshotIngredients.splice(Number(btn.dataset.removeSnapshotIngredient), 1);
        render();
      }));
      const addSnapshotIngredient = document.getElementById("addSnapshotIngredient");
      if (addSnapshotIngredient) addSnapshotIngredient.addEventListener("click", () => {
        state.mealSnapshotIngredients.push({ name: "New ingredient", amount: "adjust portion" });
        render();
      });
      const mealSnapshotIngredientsForm = document.getElementById("mealSnapshotIngredientsForm");
      if (mealSnapshotIngredientsForm) mealSnapshotIngredientsForm.addEventListener("submit", e => {
        e.preventDefault();
        const fd = new FormData(mealSnapshotIngredientsForm);
        state.mealSnapshotIngredients = state.mealSnapshotIngredients.map((item, index) => ({
          name: String(fd.get(`ingredient-${index}`) || item.name).trim(),
          amount: String(fd.get(`amount-${index}`) || item.amount).trim()
        })).filter(item => item.name);
        state.mealSnapshotStep = "analyzing";
        render();
        setTimeout(() => {
          if (state.mealSnapshotStep === "analyzing") {
            state.mealSnapshotStep = "result";
            render();
          }
        }, 1000);
      });
      const saveMealSnapshot = document.getElementById("saveMealSnapshot");
      if (saveMealSnapshot) saveMealSnapshot.addEventListener("click", () => {
        state.mealSnapshotSaved = true;
        state.mealSnapshotNotice = "Snapshot saved for this demo session.";
        render();
      });
      const discardMealSnapshot = document.getElementById("discardMealSnapshot");
      if (discardMealSnapshot) discardMealSnapshot.addEventListener("click", () => {
        state.mealSnapshotStep = "upload";
        state.mealSnapshotPhoto = "";
        state.mealSnapshotResult = null;
        state.mealSnapshotIngredients = [];
        state.mealSnapshotSaved = false;
        state.mealSnapshotNotice = "";
        routeTo(ROUTES.MEALS);
      });
      const mealForm = document.getElementById("mealForm");
      if (mealForm) mealForm.addEventListener("submit", e => { e.preventDefault(); const title = new FormData(mealForm).get("title") || "New meal idea"; state.meals.push([title, "Grocery Finds", "Admin-added placeholder meal idea.", "25g"]); save(); render(); });
      const tipForm = document.getElementById("tipForm");
      if (tipForm) tipForm.addEventListener("submit", e => { e.preventDefault(); const fd = new FormData(tipForm); state.tips.push({ id: crypto.randomUUID(), title: fd.get("title") || "New tip", body: fd.get("body") || "Prototype coach tip.", category: "Support", weekNumber: patient.currentWeek }); save(); render(); });
      document.querySelectorAll("[data-reward-form]").forEach(form => form.addEventListener("submit", e => {
        e.preventDefault();
        const reward = state.rewards.find(item => item.id === form.dataset.rewardForm);
        if (!reward) return;
        const fd = new FormData(form);
        const title = String(fd.get("title") || "").trim();
        const description = String(fd.get("description") || "").trim();
        const pointsCost = Number(fd.get("pointsCost"));
        const supportedTiers = Object.values(REWARD_TIERS);
        const tier = String(fd.get("tier") || "").trim();
        if (!title || !description || !Number.isFinite(pointsCost) || pointsCost <= 0 || !supportedTiers.includes(tier)) return;
        const isFeatured = fd.get("isFeatured") === "on";
        if (isFeatured) state.rewards.forEach(item => { item.isFeatured = false; });
        reward.tier = tier;
        reward.title = title;
        reward.description = description;
        reward.pointsCost = pointsCost;
        reward.icon = rewardIcon(fd.get("icon"));
        reward.finePrint = String(fd.get("finePrint") || "").trim();
        reward.isActive = fd.get("isActive") === "on";
        reward.isFeatured = isFeatured;
        reward.updatedAt = new Date().toISOString().slice(0, 10);
        save();
        render();
      }));
      const supportOverrideForm = document.getElementById("supportOverrideForm");
      if (supportOverrideForm) supportOverrideForm.addEventListener("submit", e => {
        e.preventDefault();
        const fd = new FormData(supportOverrideForm);
        const selection = ensureWeeklySupportSelection();
        selection.mealIdeaId = fd.get(SUPPORT_TYPES.MEAL_IDEA);
        selection.coachTipId = fd.get(SUPPORT_TYPES.COACH_TIP);
        selection.clinicSupportId = fd.get(SUPPORT_TYPES.CLINIC_SUPPORT);
        selection.mode = "manual";
        selection.updatedAt = new Date().toISOString().slice(0, 10);
        save();
        render();
      });
      const downloadCsvTemplateButton = document.getElementById("downloadCsvTemplate");
      if (downloadCsvTemplateButton) downloadCsvTemplateButton.addEventListener("click", downloadCsvTemplate);
      const supportCsvFile = document.getElementById("supportCsvFile");
      if (supportCsvFile) supportCsvFile.addEventListener("change", e => {
        const file = e.currentTarget.files?.[0];
        if (!file) return;
        if (!file.name.toLowerCase().endsWith(".csv")) {
          state.csvImport = { totalRows: 0, validRows: [], rows: [], errors: ["We could not import this file. Please choose a .csv file."] };
          render();
          return;
        }
        const reader = new FileReader();
        reader.onload = () => {
          try {
            state.csvImport = validateSupportCsv(String(reader.result || ""));
          } catch (error) {
            state.csvImport = { totalRows: 0, validRows: [], rows: [], errors: ["We could not import this file. Please check the CSV format."] };
          }
          render();
        };
        reader.readAsText(file);
      });
      const importCsvRowsButton = document.getElementById("importCsvRows");
      if (importCsvRowsButton) importCsvRowsButton.addEventListener("click", () => { importValidSupportRows(); render(); });
    }

    function renderPrefill(p) {
      document.getElementById("screens").innerHTML = submitWin({ headline: p.headline, details: p.details, category: "Pounds Lost", weekNumber: patient.currentWeek });
      bind();
    }

    async function submitLoginForm(e) {
      e.preventDefault();
      const fd = new FormData(e.currentTarget);
      try {
        await signInWithEmailPassword(String(fd.get("email") || ""), String(fd.get("password") || ""));
        await loadStateFromBackend();
        state.route = isAdminRole() ? ROUTES.ADMIN : ROUTES.HOME;
      } catch (error) {
        state.authError = error?.message || "Unable to sign in.";
      }
      render();
    }

    function handleLogout() {
      signOut();
      state.route = isAuthRequired() ? ROUTES.LOGIN : ROUTES.HOME;
      render();
    }

    function readWinPhoto(file) {
      if (!file || !file.name || !file.type?.startsWith("image/")) return Promise.resolve("");
      return new Promise(resolve => {
        const reader = new FileReader();
        reader.onerror = () => resolve("");
        reader.onload = () => {
          const image = new Image();
          image.onerror = () => resolve(String(reader.result || ""));
          image.onload = () => {
            const maxSide = 720;
            const scale = Math.min(1, maxSide / Math.max(image.width, image.height));
            const width = Math.max(1, Math.round(image.width * scale));
            const height = Math.max(1, Math.round(image.height * scale));
            const canvas = document.createElement("canvas");
            canvas.width = width;
            canvas.height = height;
            const ctx = canvas.getContext("2d");
            if (!ctx) {
              resolve(String(reader.result || ""));
              return;
            }
            ctx.drawImage(image, 0, 0, width, height);
            resolve(canvas.toDataURL("image/jpeg", .78));
          };
          image.src = String(reader.result || "");
        };
        reader.readAsDataURL(file);
      });
    }

    async function submitWinForm(e) {
      e.preventDefault();
      const fd = new FormData(e.currentTarget);
      const firstName = fd.get("display") === "first";
      const privateWin = fd.get("display") === "private";
      if (privateWin) {
        awardRule("private_win", "Private win logged");
      } else {
        const photoFile = fd.get("photo");
        const imageUrl = await readWinPhoto(photoFile);
        state.wins.unshift({
          id: crypto.randomUUID(),
          headline: fd.get("headline"),
          details: fd.get("details"),
          category: fd.get("category"),
          displayName: firstName ? patient.firstName : "Anonymous",
          isAnonymous: !firstName,
          weekNumber: Number(fd.get("weekNumber")) || patient.currentWeek,
          imageUrl,
          cheers: 0,
          isFeatured: false,
          isHidden: false,
          isFlagged: false,
          createdAt: new Date().toISOString().slice(0, 10)
        });
        awardRule("shared_win", "Shared a win to the Leaner Wins Board");
      }
      state.suggestedWin = null;
      save();
      routeTo(privateWin ? ROUTES.MOMENTUM : ROUTES.WINS);
    }

    function submitCheckinForm(e) {
      e.preventDefault();
      const fd = new FormData(e.currentTarget);
      const weight = Number(fd.get("weight"));
      const lost = Math.max(0, Math.round((patient.startingWeight - weight) * 10) / 10);
      const sideEffects = String(fd.get("sideEffects") || "no");
      state.checkIns.unshift({
        id: crypto.randomUUID(),
        patientId: patient.id,
        date: new Date().toISOString().slice(0, 10),
        weight,
        feeling: fd.get("feeling"),
        hungerLevel: fd.get("hunger"),
        energyLevel: fd.get("energy"),
        sideEffects,
        sideEffectsNote: fd.get("sideEffectsNote"),
        weeklyWin: fd.get("weeklyWin"),
        helpNeeded: fd.get("helpNeeded"),
        needsClinicFollowup: sideEffects === "needs_help"
      });
      state.suggestedWin = lost > 0 ? { lost, headline: `Down ${lost} lbs!`, details: fd.get("weeklyWin") || "Feeling encouraged by steady progress this week." } : null;
      awardRule("weekly_check_in", "Weekly check-in completed");
      awardRule("progress_update", "Progress update logged");
      if (patient.checkInStreak >= 4 && !state.pointEvents.some(event => event.type === "streak_bonus_4_week")) {
        awardRule("streak_bonus_4_week", "4-week consistency bonus");
      }
      if (patient.checkInStreak >= 8 && !state.pointEvents.some(event => event.type === "streak_bonus_8_week")) {
        awardRule("streak_bonus_8_week", "8-week consistency bonus");
      }
      save();
      render();
    }

    function shareSuggestedAnon() {
      const s = state.suggestedWin;
      state.wins.unshift({ id: crypto.randomUUID(), headline: s.headline, details: s.details, category: "Pounds Lost", displayName: "Anonymous", isAnonymous: true, weekNumber: patient.currentWeek, imageUrl: "", cheers: 0, isFeatured: false, isHidden: false, isFlagged: false, createdAt: new Date().toISOString().slice(0, 10) });
      awardRule("shared_win", "Shared a win to the Leaner Wins Board");
      state.suggestedWin = null;
      save();
      routeTo(ROUTES.WINS);
    }

    function prototypeWinDetails(win) {
      const category = winCategoryShortLabel(win.category).toLowerCase();
      return `${win.headline} became a ${category} win this week. A Leaner member shared this as a small moment of progress, and the community is cheering them on.`;
    }

    function openWin(id) {
      const realWin = findWin(id);
      const w = realWin || findCommunityMapWin(id);
      if (!w) return;
      const modal = document.getElementById("winModal");
      const imageUrl = isUsableImageUrl(w.imageUrl) ? w.imageUrl : winAvatarUrl(w);
      const isPrototypeWin = !realWin;
      const displayName = w.isAnonymous || isPrototypeWin ? "Anonymous Leaner member" : (w.displayName || "Leaner member");
      const cheerCount = Number(w.cheers) || 0;
      modal.innerHTML = `<div class="modal-card win-detail-card">
        <div class="win-detail-hero">
          <div class="win-detail-image">${imageUrl ? `<img class="modal-photo" src="${escapeAttr(imageUrl)}" alt="">` : "Leaner win"}</div>
          <div>
            <p class="eyebrow">Community win</p>
            <h2>${escapeHtml(w.headline)}</h2>
            <p class="win-detail-author">${escapeHtml(displayName)} &middot; ${escapeHtml(w.category)}</p>
          </div>
        </div>
        <p class="win-detail-story">${escapeHtml(w.details || prototypeWinDetails(w))}</p>
        <div class="win-detail-stats" aria-label="Win details">
          <span><strong>Week</strong>${escapeHtml(w.weekNumber || "?")}</span>
          <span><strong>Category</strong>${escapeHtml(w.category)}</span>
          <span><strong>Cheers</strong><span data-cheer-count>${cheerCount}</span></span>
        </div>
        <p class="win-detail-cheer-copy"><span data-cheer-copy>${cheerCount}</span> people cheered this win.</p>
        <div class="button-row win-detail-actions">
          <button class="secondary" id="cheerBtn" type="button">${icon("heart")} Cheer</button>
          <button class="ghost" id="reportBtn">Report</button>
          <button class="ghost" id="closeModal">Close</button>
        </div>
      </div>`;
      modal.classList.add("open");
      document.getElementById("cheerBtn").addEventListener("click", () => {
        w.cheers += 1;
        if (!isPrototypeWin) {
          awardRule("celebrate_win", "Celebrated a community win");
          save();
        }
        modal.querySelectorAll("[data-cheer-count], [data-cheer-copy]").forEach(node => {
          node.textContent = String(Number(w.cheers) || 0);
        });
        const cheerButton = document.getElementById("cheerBtn");
        if (cheerButton) cheerButton.innerHTML = `${icon("heart")} Cheered`;
      });
      document.getElementById("reportBtn").addEventListener("click", () => { if (!isPrototypeWin) { w.isFlagged = true; save(); } modal.classList.remove("open"); render(); });
      document.getElementById("closeModal").addEventListener("click", () => { modal.classList.remove("open"); render(); });
      modal.addEventListener("click", e => { if (e.target === modal) { modal.classList.remove("open"); render(); } }, { once: true });
    }

    function makeDraggable(nodes) {
      nodes.forEach(node => {
        let dragging = false, startX = 0, startY = 0, left = 0, top = 0, moved = false;
        node.addEventListener("pointerdown", e => {
          dragging = true; moved = false;
          node.setPointerCapture(e.pointerId);
          node.classList.add("dragging");
          startX = e.clientX; startY = e.clientY;
          left = parseFloat(node.style.getPropertyValue("--x")) || node.offsetLeft;
          top = parseFloat(node.style.getPropertyValue("--y")) || node.offsetTop;
        });
        node.addEventListener("pointermove", e => {
          if (!dragging) return;
          const dx = e.clientX - startX, dy = e.clientY - startY;
          if (Math.abs(dx) + Math.abs(dy) > 6) moved = true;
          const parent = node.parentElement.getBoundingClientRect();
          const maxX = parent.width - node.offsetWidth - 4;
          const maxY = parent.height - node.offsetHeight - 4;
          node.style.setProperty("--x", `${Math.max(4, Math.min(maxX, left + dx))}px`);
          node.style.setProperty("--y", `${Math.max(4, Math.min(maxY, top + dy))}px`);
        });
        node.addEventListener("pointerup", () => {
          dragging = false;
          node.classList.remove("dragging");
          if (moved) {
            node.dataset.suppressClick = "1";
            setTimeout(() => delete node.dataset.suppressClick, 160);
          }
        });
      });
    }

    function makeWinMapPannable(board, options = {}) {
      const world = options.compact ? board?.querySelector(".wins-map-world.compact") : board?.querySelector(".wins-map-world:not(.compact)");
      if (!board || !world) return;
      const offsetKey = options.stateKey || "winMapOffset";

      let dragging = false;
      let moved = false;
      let startX = 0;
      let startY = 0;
      let startOffset = { ...(state[offsetKey] || { x: 0, y: 0 }) };
      let targetWin = null;
      let focusFrame = 0;

      const requestFocusUpdate = () => {
        if (focusFrame) return;
        focusFrame = requestAnimationFrame(() => {
          focusFrame = 0;
          updateWinMapFocus(board);
        });
      };

      const clampOffset = (offset) => {
        const boardRect = board.getBoundingClientRect();
        const bubbles = Array.from(world.querySelectorAll(".win-map-bubble:not(.hidden-post)"));
        const worldRect = world.getBoundingClientRect();
        const extents = bubbles.reduce((bounds, bubble) => {
          const rect = bubble.getBoundingClientRect();
          const centerX = rect.left + rect.width / 2 - worldRect.left;
          const centerY = rect.top + rect.height / 2 - worldRect.top;
          const radius = Math.max(rect.width, rect.height) / 2;
          return {
            minX: Math.min(bounds.minX, centerX - radius),
            maxX: Math.max(bounds.maxX, centerX + radius),
            minY: Math.min(bounds.minY, centerY - radius),
            maxY: Math.max(bounds.maxY, centerY + radius)
          };
        }, { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity });
        const hasExtents = Number.isFinite(extents.minX) && Number.isFinite(extents.maxX);
        const bubbleWidth = hasExtents ? extents.maxX - extents.minX : world.offsetWidth;
        const bubbleHeight = hasExtents ? extents.maxY - extents.minY : world.offsetHeight;
        const maxX = Math.max(118, Math.min(340, Math.max(0, (bubbleWidth - boardRect.width) * .5 + 92)));
        const maxY = Math.max(140, Math.min(390, Math.max(0, (bubbleHeight - boardRect.height) * .5 + 112)));
        return {
          x: Math.max(-maxX, Math.min(maxX, offset.x)),
          y: Math.max(-maxY, Math.min(maxY, offset.y))
        };
      };

      const applyOffset = (offset) => {
        const nextOffset = clampOffset(offset);
        state[offsetKey] = nextOffset;
        world.style.setProperty("--map-x", `${nextOffset.x}px`);
        world.style.setProperty("--map-y", `${nextOffset.y}px`);
        requestFocusUpdate();
      };

      board.addEventListener("pointerdown", e => {
        if (e.button !== undefined && e.button !== 0) return;
        dragging = true;
        moved = false;
        startX = e.clientX;
        startY = e.clientY;
        startOffset = { ...(state[offsetKey] || { x: 0, y: 0 }) };
        targetWin = e.target.closest("[data-win]");
        if (targetWin) targetWin.dataset.pointerIntent = "tap";
        board.setPointerCapture(e.pointerId);
        board.classList.add("panning");
      });

      board.addEventListener("pointermove", e => {
        if (!dragging) return;
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;
        if (Math.abs(dx) + Math.abs(dy) > 7) moved = true;
        if (!moved) return;
        if (targetWin) targetWin.dataset.pointerIntent = "pan";
        applyOffset({ x: startOffset.x + dx, y: startOffset.y + dy });
      });

      const finishPan = e => {
        if (!dragging) return;
        dragging = false;
        board.classList.remove("panning");
        if (board.hasPointerCapture?.(e.pointerId)) board.releasePointerCapture(e.pointerId);
        if (!moved && targetWin?.dataset.win) {
          targetWin.dataset.suppressClick = "1";
          openWin(targetWin.dataset.win);
          setTimeout(() => delete targetWin.dataset.suppressClick, 180);
        } else if (moved && targetWin) {
          targetWin.dataset.suppressClick = "1";
          targetWin.dataset.pointerIntent = "pan";
          setTimeout(() => delete targetWin.dataset.suppressClick, 180);
        }
        if (targetWin) setTimeout(() => delete targetWin.dataset.pointerIntent, 180);
        targetWin = null;
      };

      board.addEventListener("pointerup", finishPan);
      board.addEventListener("pointercancel", finishPan);
      requestFocusUpdate();
    }

    function updateWinMapFocus(board) {
      const bubbles = Array.from(board?.querySelectorAll(".win-map-bubble:not(.hidden-post)") || []);
      if (!board || !bubbles.length) return;
      const world = board.querySelector(".wins-map-world");
      if (!world) return;
      const isCompactMap = world.classList.contains("compact");

      const boardRect = board.getBoundingClientRect();
      const worldRect = world.getBoundingClientRect();
      const worldScaleX = world.offsetWidth ? worldRect.width / world.offsetWidth : 1;
      const worldScaleY = world.offsetHeight ? worldRect.height / world.offsetHeight : 1;
      const centerX = boardRect.left + boardRect.width / 2;
      const centerY = boardRect.top + boardRect.height / 2;
      let focused = null;
      let closestDistance = Infinity;
      const bubbleGeometry = [];
      const measuredBubbles = [];

      bubbles.forEach(bubble => {
        bubble.classList.toggle("focused", false);
      });

      bubbles.forEach(bubble => {
        const bubbleX = worldRect.left + (bubble.offsetLeft + bubble.offsetWidth / 2) * worldScaleX;
        const bubbleY = worldRect.top + (bubble.offsetTop + bubble.offsetHeight / 2) * worldScaleY;
        const radius = Math.max(bubble.offsetWidth * worldScaleX, bubble.offsetHeight * worldScaleY) / 2;
        const distance = Math.hypot(bubbleX - centerX, bubbleY - centerY);
        const basePercentage = Number(bubble.dataset.basePercentage) || (100 / bubbles.length);
        const focusFactor = WinsBubbleMath.calculateFocusFactor(distance, 520);
        bubble.style.setProperty("--focus-z", String(Math.round((1 + focusFactor) * 80)));
        const id = bubble.dataset.mapId || bubble.dataset.win || `bubble-${measuredBubbles.length}`;
        measuredBubbles.push({
          id,
          bubble,
          currentPercentage: basePercentage,
          basePercentage,
          minPercentage: 2.2,
          maxPercentage: bubble.dataset.win === "w1" ? 32 : 24,
          distance,
          focusFactor,
          baseRadius: radius
        });
        bubbleGeometry.push({ bubble, x: bubbleX, y: bubbleY, radius });
        if (distance < closestDistance) {
          closestDistance = distance;
          focused = bubble;
        }
      });

      if (focused) {
        const focusedId = focused.dataset.mapId || focused.dataset.win || "";
        measuredBubbles.forEach(measured => {
          const gradientScale = WinsBubbleMath.calculateViewportFocusScale(measured.distance, {
            focusRadius: isCompactMap ? Math.max(260, Math.min(390, Math.min(boardRect.width, boardRect.height) * .92)) : Math.max(500, Math.min(640, Math.min(boardRect.width, boardRect.height) * 1.18)),
            minScale: 0.96,
            maxScale: measured.id === focusedId ? (isCompactMap ? 1.24 : 1.56) : (isCompactMap ? 1.13 : 1.34),
            curve: 1.16
          });
          const scale = measured.id === focusedId ? Math.max(isCompactMap ? 1.12 : 1.36, gradientScale) : gradientScale;
          const priority = Math.round(scale * 120 + measured.focusFactor * 100);
          measured.bubble.style.setProperty("--focus-scale", scale.toFixed(3));
          measured.bubble.style.setProperty("--focus-z", String(priority));
          measured.bubble.dataset.currentFocusScale = scale.toFixed(3);
        });
        focused.classList.add("focused");
        focused.style.setProperty("--focus-z", "260");
        state.focusedWinId = focused.dataset.win || "";
        const membraneDisplacements = WinsBubbleMath.calculateMembraneDisplacements(bubbleGeometry.map(item => {
          const id = item.bubble.dataset.mapId || item.bubble.dataset.win || "";
          return {
            id,
            x: item.x,
            y: item.y,
            radius: item.radius + 8,
            scale: Number(item.bubble.style.getPropertyValue("--focus-scale")) || 1
          };
        }), focusedId, {
          iterations: isCompactMap ? 72 : 56,
          minGap: isCompactMap ? 14 : 10,
          strength: isCompactMap ? 1.05 : 0.95,
          maxDisplacement: isCompactMap ? 96 : 158,
          pinFocused: true
        });
        bubbleGeometry.forEach(item => {
          const id = item.bubble.dataset.mapId || item.bubble.dataset.win || "";
          const displacement = membraneDisplacements[id] || { x: 0, y: 0 };
          item.bubble.style.setProperty("--focus-nudge-x", `${displacement.x.toFixed(1)}px`);
          item.bubble.style.setProperty("--focus-nudge-y", `${displacement.y.toFixed(1)}px`);
        });
      }
    }
