"use strict"; function bindUI() { renderToolPalette(); loadLogPushSettings(); syncLogPushControls(); syncAudioControls(); syncVisualSettingsPanel(); window.TarinaiTooltips.applyToolPalette(); window.TarinaiSaveSystem.bindSaveSystem(); uiCache.toolButtons = Array.from(ui.toolPalette?.querySelectorAll(".tool") || []); function isActiveToolMode() { return Boolean(world?.tool && world.tool !== "observe"); } function clickedInsideGameOrToolUi(target) { return Boolean((canvas && canvas.contains?.(target)) || ui.toolPalette?.contains?.(target) || ui.panelQuickTabs?.contains?.(target) || ui.mobileModeControls?.contains?.(target) || ui.mobileRotateControls?.contains?.(target) || ui.backToGameBtn?.contains?.(target)); } function panelSectionForTab(tab = "") { if (tab === "game") return document.querySelector(".stage-wrap") || canvas; if (tab === "selected") return ui.selectedCard; if (tab === "tools") return ui.toolCard; if (tab === "colony") return document.querySelector(".colony-card"); if (tab === "events") return document.querySelector(".log-card"); if (tab === "family") return document.querySelector(".lineage-main-card"); return null; } function setActivePanelTab(tab = "") { for (const btn of ui.panelQuickTabs?.querySelectorAll("[data-panel-tab]") || []) { btn.classList.toggle("active", btn.dataset.panelTab === tab); } } function isTouchMobileLayout() { return Boolean(window.TarinaiInputMode.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980); } function isDesktopLayout() { return window.matchMedia?.("(min-width: 981px)")?.matches ?? window.innerWidth >= 981; } function syncVisualSettingsPanel() { const settings = window.TarinaiPerf?.visualSettings?.() || {}; for (const input of ui.visualSettingsPanel?.querySelectorAll?.("[data-visual-setting]") || []) { const key = input.dataset.visualSetting || ""; if (input.type === "checkbox") input.checked = settings[key] === (input.dataset.visualOn || "high"); else input.checked = settings[key] === input.dataset.visualValue; } ui.visualSettingsBtn?.setAttribute("aria-expanded", ui.visualSettingsPanel && !ui.visualSettingsPanel.classList.contains("hidden") ? "true" : "false"); } function closeVisualSettingsPanel() { ui.visualSettingsPanel?.classList.add("hidden"); syncVisualSettingsPanel(); } function toggleVisualSettingsPanel() { const opening = Boolean(ui.visualSettingsPanel?.classList.contains("hidden")); if (opening && typeof closeSoundPanel === "function") closeSoundPanel(); ui.visualSettingsPanel?.classList.toggle("hidden", !opening); syncVisualSettingsPanel(); } function markCollapsibleCardState(card, collapsed, { user = false } = {}) { if (!card) return; card.classList.toggle("collapsed", Boolean(collapsed)); if (user) { if (collapsed) card.dataset.userCollapsed = "1"; else delete card.dataset.userCollapsed; } const toggle = card.querySelector(".panel-card-toggle"); toggle?.setAttribute("aria-expanded", collapsed ? "false" : "true"); const state = toggle?.querySelector(".panel-card-state"); if (state) state.textContent = collapsed ? "\u5C55\u958B" : "\u6298\u308A\u7573\u307F"; } function markToolCategoryState(category, collapsed, { user = false } = {}) { if (!category) return; category.classList.toggle("collapsed", Boolean(collapsed)); if (user) { if (collapsed) category.dataset.userCollapsed = "1"; else delete category.dataset.userCollapsed; } category.querySelector(".tool-category-toggle")?.setAttribute("aria-expanded", collapsed ? "false" : "true"); } function scrollWindowToElement(target, offset = 0, behavior = "auto") { if (!target) return; const scroller = document.scrollingElement || document.documentElement; const rect = target.getBoundingClientRect?.(); if (!rect) return; const maxTop = Math.max(0, scroller.scrollHeight - window.innerHeight); const top = Math.max(0, Math.min(window.scrollY + rect.top - offset, maxTop)); window.scrollTo({ top, behavior: "auto" }); } function scrollPanelToTab(tab = "") { const panel = ui.panel; if (!panel) return false; window.TarinaiTooltips.hide(); if (tab === "selected" && (!world.selected || world.selected.dead || !world.tarinai.includes(world.selected))) { uiCache.showSelectedEmpty = true; uiCache.selectedEmpty = false; renderSelected(); } const target = panelSectionForTab(tab); if (!target) return false; target.classList?.remove?.("hidden"); if (tab === "family") { // \u66F4\u65B0OFF\u3067\u3082\u3001\u30E6\u30FC\u30B6\u30FC\u304C\u5BB6\u7CFB\u56F3\u3078\u79FB\u52D5\u3057\u305F\u6642\u3060\u3051\u73FE\u5728\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u63CF\u753B\u3059\u308B\u3002 window.resetArchiveRenderState?.(); renderArchive?.(); } const isDesktop = window.matchMedia?.("(min-width: 981px)")?.matches ?? window.innerWidth >= 981; if (isDesktop && tab !== "game" && tab !== "family") { const tabsH = ui.panelQuickTabs?.offsetHeight || 0; const top = Math.max(0, target.offsetTop - tabsH - 8); panel.scrollTo({ top, behavior: "auto" }); } else { // \u30B9\u30DE\u30DB\u306F\u30DA\u30FC\u30B8\u5168\u4F53\u30B9\u30AF\u30ED\u30FC\u30EB\u3002\u76EE\u7684\u5730UI\u306E\u4E0A\u7AEF\u3092\u753B\u9762\u4E0A\u7AEF\u3078\u5408\u308F\u305B\u308B\u3002 scrollWindowToElement(target, 0, "auto"); } setActivePanelTab(tab); return true; } const activatePanelQuickTab = (e) => { const btn = e.target.closest("[data-panel-tab]"); if (!btn) return; const now = performance?.now?.() ?? Date.now(); if (now - Number(ui.panelQuickTabs?.dataset?.lastActivateAt || 0) < 50) return; if (ui.panelQuickTabs) ui.panelQuickTabs.dataset.lastActivateAt = String(now); e.preventDefault(); audio.uiClick?.(); scrollPanelToTab(btn.dataset.panelTab || ""); }; ui.panelQuickTabs?.addEventListener("click", activatePanelQuickTab); ui.panelQuickTabs?.addEventListener("pointerup", activatePanelQuickTab, { passive: false }); ui.panelQuickTabs?.addEventListener("touchend", activatePanelQuickTab, { passive: false }); const applyTemperatureSlider = () => { if (!world || !ui.temperatureSlider) return; world.setManualTemperature?.(Number(ui.temperatureSlider.value)); world.setTemperatureAuto?.(false); renderStats?.(); }; ui.temperatureAutoToggle?.addEventListener("change", () => { if (!world) return; world.setTemperatureAuto?.(ui.temperatureAutoToggle.checked); if (ui.temperatureSlider && world.temperatureAuto === false) world.setManualTemperature?.(Number(ui.temperatureSlider.value)); audio.uiClick?.(); renderStats?.(); }); ui.temperatureSlider?.addEventListener("input", applyTemperatureSlider); ui.temperatureSlider?.addEventListener("change", applyTemperatureSlider); const colonyLimitControls = (kind) => kind === "tarinai" ? { slider: ui.tarinaiPopulationLimitSlider, input: ui.tarinaiPopulationLimitInput } : { slider: ui.objectLimitSlider, input: ui.objectLimitInput }; const syncColonyLimitControls = (kind, value) => { const { slider, input } = colonyLimitControls(kind); const limit = Math.max(0, Math.floor(Number(value || 0) || 0)); if (input && document.activeElement !== input) input.value = limit > 0 ? String(limit) : ""; if (slider && document.activeElement !== slider) { const max = Math.max(0, Number(slider.max || 0) || 0); slider.value = String(max > 0 ? Math.min(limit, max) : limit); slider.setAttribute("aria-valuetext", limit > 0 ? String(limit) : "\u4e0a\u9650\u306a\u3057"); } }; window.syncColonyLimitControls = syncColonyLimitControls; const applyColonyLimit = (kind, source = "number") => { if (!world) return; const { slider, input } = colonyLimitControls(kind); const sourceEl = String(source).startsWith("slider") ? slider : input; if (!sourceEl) return; const raw = sourceEl.value?.trim?.() === "" ? 0 : Number(sourceEl.value); const value = Math.max(0, Math.floor(Number(raw) || 0)); if (kind === "tarinai") world.setTarinaiPopulationLimit?.(value); else world.setObjectLimit?.(value); syncColonyLimitControls(kind, value); if (source !== "slider-live") audio.uiClick?.(); renderStats?.(); }; ui.tarinaiPopulationLimitSlider?.addEventListener("input", () => applyColonyLimit("tarinai", "slider-live")); ui.tarinaiPopulationLimitSlider?.addEventListener("change", () => applyColonyLimit("tarinai", "slider")); ui.objectLimitSlider?.addEventListener("input", () => applyColonyLimit("object", "slider-live")); ui.objectLimitSlider?.addEventListener("change", () => applyColonyLimit("object", "slider")); ui.tarinaiPopulationLimitInput?.addEventListener("change", () => applyColonyLimit("tarinai", "number")); ui.objectLimitInput?.addEventListener("change", () => applyColonyLimit("object", "number")); ui.panel?.addEventListener("scroll", () => { const panel = ui.panel; if (!panel || !ui.panelQuickTabs) return; const tabsH = ui.panelQuickTabs.offsetHeight || 0; const entries = [ ["game", document.querySelector(".stage-wrap") || canvas], ["selected", ui.selectedCard], ["tools", ui.toolCard], ["colony", document.querySelector(".colony-card")], ["events", document.querySelector(".log-card")], ["family", document.querySelector(".lineage-main-card")], ].filter(([, el]) => el && !el.classList.contains("hidden")); let active = entries[0]?.[0] || ""; const y = panel.scrollTop + tabsH + 18; for (const [key, el] of entries) { if (el.offsetTop <= y) active = key; } setActivePanelTab(active); }, { passive: true }); syncToolSizeBadges(); setActivePanelTab(ui.selectedCard?.classList.contains("hidden") ? "tools" : "selected"); function setCollapsibleCardState(card, collapsed) { markCollapsibleCardState(card, collapsed); } function applyDesktopInitialPanelState() { const touchFirst = Boolean(window.TarinaiInputMode.classify().touchFirst || document.body.classList.contains("touch-mobile-ui")); const desktop = !touchFirst && isDesktopLayout(); if (!desktop || uiCache.desktopInitialPanelStateApplied) return; uiCache.desktopInitialPanelStateApplied = true; for (const card of document.querySelectorAll(".collapsible-card")) markCollapsibleCardState(card, false); for (const category of ui.toolPalette?.querySelectorAll(".tool-category") || []) markToolCategoryState(category, false); } function applyMobileInitialPanelState() { const touchFirst = Boolean(window.TarinaiInputMode.classify().touchFirst || document.body.classList.contains("touch-mobile-ui")); if (!touchFirst || uiCache.mobileInitialPanelStateApplied) return; uiCache.mobileInitialPanelStateApplied = true; setCollapsibleCardState(document.querySelector(".colony-card"), true); setCollapsibleCardState(document.querySelector(".log-card"), true); for (const category of ui.toolPalette?.querySelectorAll(".tool-category") || []) { const keepOpen = category.dataset.toolCategory === "operate"; markToolCategoryState(category, !keepOpen); } } applyDesktopInitialPanelState(); applyMobileInitialPanelState(); window.addEventListener("resize", () => { applyDesktopInitialPanelState(); applyMobileInitialPanelState(); }, { passive: true }); function updateBackToGameButton() { const btn = ui.backToGameBtn; if (!btn) return; const stage = document.querySelector(".stage-wrap"); const touchFirst = Boolean(window.TarinaiInputMode.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980); if (!touchFirst || !stage) { btn.hidden = true; return; } const rect = stage.getBoundingClientRect(); const visible = rect.bottom > 42 && rect.top < Math.min(window.innerHeight * 0.72, window.innerHeight - 90); btn.hidden = visible; } ui.backToGameBtn?.addEventListener("click", (e) => { e.preventDefault(); audio.uiClick?.(); scrollPanelToTab("game"); }); window.addEventListener("scroll", updateBackToGameButton, { passive: true }); window.addEventListener("resize", updateBackToGameButton, { passive: true }); updateBackToGameButton(); window.TarinaiEvents.on("selection:changed", () => { if (world?._scrollSelectedDataTopOnNextSelection && isDesktopLayout() && world.selected && !world.selected.dead) { world._scrollSelectedDataTopOnNextSelection = false; window.setTimeout(() => { renderSelected?.(); scrollPanelToTab("selected"); }, 40); return; } world && (world._scrollSelectedDataTopOnNextSelection = false); const touchFirst = Boolean(window.TarinaiInputMode.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980); const mode = window.TarinaiInputMode.currentMode || uiCache.mobileInputMode || "auto"; if (!touchFirst || mode !== "auto" || world.tool !== "observe" || !world.selected || world.selected.dead) return; window.setTimeout(() => { renderSelected?.(); scrollPanelToTab("selected"); }, 40); }); function activeRotatableToolType() { const type = toolItemType(world?.tool || ""); return type && isRotatableItemType(type) ? type : ""; } function syncMobileRotateControls() { const controls = ui.mobileRotateControls; if (!controls) return; const show = isTouchMobileLayout() && (window.TarinaiInputMode.currentMode || uiCache.mobileInputMode || "auto") !== "camera" && Boolean(activeRotatableToolType()); controls.hidden = !show; } window.syncMobileRotateControls = syncMobileRotateControls; function rotateMobileTool(direction = "right") { const type = activeRotatableToolType(); if (!type || !world?.rotateToolAngle) return false; const step = 45 * Math.PI / 180; world.rotateToolAngle(type, direction === "left" ? -step : step); audio.uiClick?.(); render(); syncMobileRotateControls(); return true; } ui.mobileRotateControls?.addEventListener("click", (e) => { const btn = e.target.closest?.("[data-mobile-rotate]"); if (!btn) return; e.preventDefault(); rotateMobileTool(btn.dataset.mobileRotate || "right"); }); window.TarinaiEvents.on("tool:selected", syncMobileRotateControls); window.addEventListener("resize", syncMobileRotateControls, { passive: true }); syncMobileRotateControls(); window.addEventListener("keydown", (e) => { if (e.key === "Escape" && !ui.topMenuPanel?.classList.contains("hidden")) closeTopMenu(); const tag = String(e.target?.tagName || "").toLowerCase(); if (tag === "input" || tag === "textarea" || e.target?.isContentEditable) return; const type = activeRotatableToolType(); if (!type || !world?.rotateToolAngle) return; const key = String(e.key || "").toLowerCase(); const step = (e.shiftKey ? 5 : 45) * Math.PI / 180; if (key === "q" || key === "[") { e.preventDefault(); world.rotateToolAngle(type, -step); render(); syncMobileRotateControls(); } else if (key === "e" || key === "]") { e.preventDefault(); world.rotateToolAngle(type, step); render(); syncMobileRotateControls(); } else if (key === "r") { e.preventDefault(); world.setToolAngle?.(type, defaultItemAngle(type)); render(); syncMobileRotateControls(); } }); for (const toggle of document.querySelectorAll(".panel-card-toggle")) { toggle.addEventListener("click", () => { const card = toggle.closest(".collapsible-card"); if (!card) return; audio.uiFold?.(); const collapsed = !card.classList.contains("collapsed"); markCollapsibleCardState(card, collapsed, { user: true }); }); } ui.colonyChartTabs?.addEventListener("click", (e) => { const btn = e.target.closest("button[data-chart]"); if (!btn) return; uiCache.chartMode = btn.dataset.chart || "population"; for (const b of ui.colonyChartTabs.querySelectorAll("button")) b.classList.toggle("active", b === btn); uiCache.lastChartDraw = ""; renderStats(); }); const toggleChartSeries = (target) => { const control = target?.closest?.("[data-series-key]"); if (!control) return false; const key = String(control.dataset.seriesKey || ""); if (!key) return false; if (!(uiCache.chartHiddenSeries instanceof Set)) uiCache.chartHiddenSeries = new Set(); if (uiCache.chartHiddenSeries.has(key)) uiCache.chartHiddenSeries.delete(key); else uiCache.chartHiddenSeries.add(key); uiCache.lastChartDraw = ""; audio.uiClick?.(); renderStats(); return true; }; ui.colonyChartLegend?.addEventListener("click", (e) => toggleChartSeries(e.target)); ui.logPushEnabled?.addEventListener("change", () => { uiCache.logPushEnabled = Boolean(ui.logPushEnabled.checked); audio.notify?.(); saveLogPushSettings(); showToast(uiCache.logPushEnabled ? "\u30ed\u30b0\u306e\u30d7\u30c3\u30b7\u30e5\u901a\u77e5\u3092ON\u306b\u3057\u307e\u3057\u305f\u3002" : "\u30ed\u30b0\u306e\u30d7\u30c3\u30b7\u30e5\u901a\u77e5\u3092OFF\u306b\u3057\u307e\u3057\u305f\u3002"); }); ui.logPushControls?.addEventListener("change", (e) => { const input = e.target.closest("[data-push-kind]"); if (!input) return; const kind = input.dataset.pushKind || ""; audio.uiClick?.(); if (input.checked) uiCache.logPushKinds.add(kind); else uiCache.logPushKinds.delete(kind); saveLogPushSettings(); }); ui.soundCategoryControls?.addEventListener("change", (e) => { const input = e.target.closest("[data-sound-category]"); if (!input) return; const category = input.dataset.soundCategory || ""; audio.setCategory?.(category, Boolean(input.checked)); if (audio.enabled && input.checked) audio.uiClick?.(); syncAudioControls(); showToast(`${audio.soundCategories?.[category] || category} ${input.checked ? "ON" : "OFF"}`); }); ui.archiveContent?.addEventListener("scroll", scheduleArchiveWindowRender, { passive: true }); window.addEventListener("scroll", schedulePendingArchiveRender, { passive: true }); window.addEventListener("resize", schedulePendingArchiveRender, { passive: true }); const bindArchiveHorizontal = (root) => { if (!root) return; root.addEventListener("wheel", (e) => { const scroller = e.target.closest(".lineage-family-tree, .lineage-main-content"); if (!scroller) return; const canX = scroller.scrollWidth > scroller.clientWidth + 4; if (!canX) return; const dominant = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : (e.shiftKey ? e.deltaY : 0); if (!dominant) return; e.preventDefault(); scroller.scrollLeft += dominant; }, { passive: false }); }; bindArchiveHorizontal(ui.archiveContent); ui.archiveContent?.addEventListener("click", (e) => { const node = e.target.closest("[data-family-tarinai-id]"); if (!node) return; const id = node.getAttribute("data-family-tarinai-id"); const t = world.tarinai.find(x => (x.id === id || x.familyKey === id) && !x.dead); if (!t) { showToast("\u305d\u306e\u500b\u4f53\u306f\u3082\u3046\u9078\u629e\u3067\u304d\u307e\u305b\u3093\u3002"); return; } window.TarinaiCommands.dispatch(world, { type: "selection.focus", target: t, options: { pulse: 1.15 } }); renderStats(); showToast(`${t.name}\u3092\u9078\u629e\u3057\u307e\u3057\u305f\u3002`); }); if (!uiCache.archiveAutoRefreshTimer && typeof window !== "undefined") { uiCache.archiveAutoRefreshTimer = window.setInterval(() => { if (!ui.archiveContent || !archiveAutoUpdateEnabled()) return; if (typeof lineageArchiveNeedsRender === "function" && !lineageArchiveNeedsRender()) return; scheduleArchiveWindowRender(); }, 5000); } ui.selectedInfo?.addEventListener("click", (e) => { const categoryToggle = e.target.closest(".selected-category-toggle"); if (categoryToggle) { const category = categoryToggle.closest(".selected-category"); const id = category?.dataset?.selectedCategory || ""; if (!category || !id) return; if (!uiCache.selectedCollapsedCategories) uiCache.selectedCollapsedCategories = new Set(); audio.uiFold?.(); category.classList.toggle("collapsed"); const collapsed = category.classList.contains("collapsed"); if (collapsed) uiCache.selectedCollapsedCategories.add(id); else uiCache.selectedCollapsedCategories.delete(id); categoryToggle.setAttribute("aria-expanded", collapsed ? "false" : "true"); return; } const focusBtn = e.target.closest("[data-focus-tarinai-id]"); if (focusBtn) { e.preventDefault(); focusTarinaiById(focusBtn.getAttribute("data-focus-tarinai-id")); return; } const btn = e.target.closest("[data-selected-action=\"favorite\"]"); if (!btn || !world.selected || world.selected.dead) return; const favResult = window.TarinaiCommands.dispatch(world, { type: "selection.favorite.toggle" }); if (!favResult?.ok) return; uiCache.selectedSnapshot = ""; renderSelected(); render(); showToast(favResult.favorite ? "\u304a\u6c17\u306b\u5165\u308a\u306b\u3057\u307e\u3057\u305f\u3002" : "\u304a\u6c17\u306b\u5165\u308a\u3092\u89e3\u9664\u3057\u307e\u3057\u305f\u3002"); }); ui.selectedInfo?.addEventListener("change", (e) => { const input = e.target.closest("[data-selected-name]"); if (!input) return; renameSelectedTarinai(input.value); }); ui.selectedInfo?.addEventListener("keydown", (e) => { const input = e.target.closest("[data-selected-name]"); if (!input) return; if (e.key === "Enter") { e.preventDefault(); input.blur(); } if (e.key === "Escape") { e.preventDefault(); input.value = world.selected?.name || ""; input.blur(); } }); ui.selectedCloseBtn?.addEventListener("click", () => { window.TarinaiCommands.dispatch(world, { type: "selection.clear" }); uiCache.showSelectedEmpty = false; uiCache.selectedSnapshot = ""; uiCache.selectedEmpty = false; renderSelected(); render(); }); ui.pauseBtn?.addEventListener("click", () => { audio.uiClick?.(); const pauseResult = window.TarinaiCommands.dispatch(world, { type: "simulation.paused.toggle" }); const paused = pauseResult?.ok ? pauseResult.paused : world.paused; const archiveStale = uiCache.archiveFamilyVersion !== (world.familyVersion || 0); if (archiveAutoUpdateEnabled() && paused && archiveStale) { scheduleArchiveWindowRender(); } syncTopButtons(); }); function closeTopMenu() { ui.topMenuPanel?.classList.add("hidden"); ui.topMenuBtn?.setAttribute("aria-expanded", "false"); if (typeof closeSoundPanel === "function") closeSoundPanel(); closeVisualSettingsPanel(); } function toggleTopMenu() { const opening = Boolean(ui.topMenuPanel?.classList.contains("hidden")); ui.topMenuPanel?.classList.toggle("hidden", !opening); ui.topMenuBtn?.setAttribute("aria-expanded", opening ? "true" : "false"); if (!opening && typeof closeSoundPanel === "function") closeSoundPanel(); if (!opening) closeVisualSettingsPanel(); } ui.topMenuBtn?.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); audio.uiClick?.(); toggleTopMenu(); }); ui.topMenuPanel?.addEventListener("click", (e) => e.stopPropagation()); ui.speedBtn?.addEventListener("click", () => { audio.uiClick?.(); window.TarinaiCommands.dispatch(world, { type: "simulation.speed.cycle", speeds: [1, 2, 4] }); syncTopButtons(); }); ui.ecologyBtn?.addEventListener("click", () => { audio.uiClick?.(); closeTopMenu(); openEcologyDialog(); }); ui.soundBtn?.addEventListener("click", (e) => { e.stopPropagation(); closeVisualSettingsPanel(); toggleSoundPanel(); }); ui.soundPanel?.addEventListener("click", (e) => e.stopPropagation()); ui.soundMasterToggle?.addEventListener("change", () => { const on = audio.setEnabled ? audio.setEnabled(ui.soundMasterToggle.checked) : audio.toggle(); if (on) audio.uiClick?.(); syncAudioControls(); showToast(on ? "\u52b9\u679c\u97f3\u3092ON\u306b\u3057\u307e\u3057\u305f\u3002" : "\u52b9\u679c\u97f3\u3092OFF\u306b\u3057\u307e\u3057\u305f\u3002"); }); document.addEventListener("click", (e) => { if (ui.soundMenu?.contains(e.target) || ui.visualSettingsMenu?.contains(e.target)) return; if (typeof closeSoundPanel === "function") closeSoundPanel(); closeVisualSettingsPanel(); if (!ui.topMenu?.contains(e.target)) closeTopMenu(); }); document.addEventListener("click", (e) => { if (!isActiveToolMode()) return; if (clickedInsideGameOrToolUi(e.target)) return; selectTool("observe"); // \u9053\u5177\u8a2d\u7f6e\u30e2\u30fc\u30c9\u3092\u89e3\u9664 }); ui.creditsBtn?.addEventListener("click", () => { audio.uiClick?.(); closeTopMenu(); ui.creditsDialog?.classList.remove("hidden"); }); ui.creditsCloseBtn?.addEventListener("click", () => ui.creditsDialog?.classList.add("hidden")); ui.creditsDialog?.addEventListener("click", (e) => { if (e.target === ui.creditsDialog) ui.creditsDialog.classList.add("hidden"); }); ui.visualSettingsBtn?.addEventListener("click", (e) => { e.stopPropagation(); audio.uiClick?.(); toggleVisualSettingsPanel(); }); ui.visualSettingsPanel?.addEventListener("click", (e) => e.stopPropagation()); ui.visualSettingsPanel?.addEventListener("change", (e) => { const input = e.target.closest("[data-visual-setting]"); if (!input) return; const value = input.type === "checkbox" ? (input.checked ? (input.dataset.visualOn || "high") : (input.dataset.visualOff || "off")) : (input.dataset.visualValue || ""); const ok = window.TarinaiPerf?.setVisualSetting?.(input.dataset.visualSetting || "", value); if (!ok) return; audio.uiClick?.(); syncVisualSettingsPanel(); resizeCanvas?.(); render?.(); }); ui.resetBtn?.addEventListener("click", () => { audio.uiClick?.(); openFieldDialog(); }); ui.fieldCancelBtn?.addEventListener("click", closeFieldDialog); ui.fieldConfirmBtn?.addEventListener("click", () => { audio.uiClick?.(); resetWithField(uiCache.resetFieldType || "garden", uiCache.resetPreset || "default"); }); ui.fieldDialog?.addEventListener("click", (e) => { if (e.target === ui.fieldDialog) { closeFieldDialog(); return; } const fieldBtn = e.target.closest("[data-field-type]"); if (fieldBtn) { audio.uiClick?.(); selectResetFieldType(fieldBtn.dataset.fieldType || "garden"); return; } const presetBtn = e.target.closest("[data-reset-preset]"); if (presetBtn) { audio.uiClick?.(); selectResetPreset(presetBtn.dataset.resetPreset || "default"); } }); ui.ecologyCloseBtn?.addEventListener("click", closeEcologyDialog); ui.ecologyDialog?.addEventListener("click", (e) => { if (e.target === ui.ecologyDialog) closeEcologyDialog(); }); ui.archiveUpdateToggleBtn?.addEventListener("click", () => { audio.uiClick?.(); setArchiveUpdateEnabled(!archiveAutoUpdateEnabled()); }); syncArchiveUpdateButton(); ui.archiveResetBtn?.addEventListener("click", () => { audio.uiClick?.(); openArchiveResetDialog(); }); ui.archiveResetCancelBtn?.addEventListener("click", closeArchiveResetDialog); ui.archiveResetDialog?.addEventListener("click", (e) => { if (e.target === ui.archiveResetDialog) closeArchiveResetDialog(); }); ui.archiveResetConfirmBtn?.addEventListener("click", () => { world.resetFamilyTree?.(); window.resetArchiveRenderState?.(); closeArchiveResetDialog(); renderArchive(); renderSelected(); showToast("\u5bb6\u7cfb\u56f3\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u307e\u3057\u305f\u3002"); }); const TOOL_SHORTCUTS = window.TarinaiItemToolMetadata?.TOOL_SHORTCUTS || {}; const TOOL_BY_SHORTCUT = Object.freeze(Object.fromEntries(Object.entries(TOOL_SHORTCUTS).map(([tool, key]) => [String(key), tool]))); const isKeyboardTypingTarget = (target) => { const tag = String(target?.tagName || "").toLowerCase(); return tag === "input" || tag === "textarea" || tag === "select" || Boolean(target?.isContentEditable); }; function activateControlTool(tool) { if (!tool || !world) return false; audio.uiClick?.(); if (tool === "undo" || tool === "redo") { const result = window.TarinaiCommands.dispatch(world, { type: tool === "undo" ? "history.undo" : "history.redo" }); showToast(result?.ok ? (tool === "undo" ? "Undo" : "Redo") : (tool === "undo" ? "Nothing to undo" : "Nothing to redo")); renderStats(); return true; } if (tool === "clear_tool") { world.pendingLinkEndpoint = null; world.copyBuffer = null; selectTool("observe"); for (const b of uiCache.toolButtons || []) b.classList.remove("selected"); showToast("\u9053\u5177\u9078\u629E\u3092\u89E3\u9664\u3057\u307E\u3057\u305F\u3002"); return true; } if (world.tool === tool) { if (cycleToolSize(tool)) return true; } selectTool(tool); return true; } ui.toolPalette?.addEventListener("click", (e) => { const toggle = e.target.closest(".tool-category-toggle"); if (toggle) { const category = toggle.closest(".tool-category"); if (!category) return; audio.uiFold?.(); const collapsed = !category.classList.contains("collapsed"); markToolCategoryState(category, collapsed, { user: true }); return; } const pickModeBtn = e.target.closest("[data-pick-mode-control]"); if (pickModeBtn) { e.preventDefault(); e.stopPropagation(); audio.uiClick?.(); cycleToolPickMode(); window.TarinaiTooltips?.showSelection?.(pickModeBtn, { delay: 5000 }); return; } const btn = e.target.closest(".tool"); if (!btn) return; if (e.target.closest(".tool-size-badge")) { e.preventDefault(); e.stopPropagation(); audio.uiClick?.(); cycleToolSize(btn.dataset.tool); return; } activateControlTool(btn.dataset.tool); window.TarinaiTooltips?.showSelection?.(btn, { delay: 5000 }); }); window.addEventListener("keydown", (e) => { if (e.defaultPrevented || e.ctrlKey || e.metaKey || e.altKey || e.isComposing) return; if (isKeyboardTypingTarget(e.target)) return; const tool = TOOL_BY_SHORTCUT[String(e.key || "")]; if (!tool) return; e.preventDefault(); activateControlTool(tool); }); const normalizedWheelDeltaY = (e) => { const line = 18; const page = Math.max(160, window.innerHeight * 0.82); return e.deltaY * (e.deltaMode === 1 ? line : (e.deltaMode === 2 ? page : 1)); }; const applyScrollDelta = (el, dy) => { if (!el || !Number.isFinite(dy) || Math.abs(dy) < 0.001) return dy; const max = Math.max(0, el.scrollHeight - el.clientHeight); const before = clamp(el.scrollTop || 0, 0, max); const next = clamp(before + dy, 0, max); el.scrollTop = next; return dy - (next - before); }; const chainScroll = (el) => { if (!el) return; el.addEventListener("wheel", (e) => { if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return; const dy = normalizedWheelDeltaY(e); const max = Math.max(0, el.scrollHeight - el.clientHeight); const atTop = el.scrollTop <= 0.5; const atBottom = el.scrollTop >= max - 0.5; if (!((dy < 0 && atTop) || (dy > 0 && atBottom))) return; e.preventDefault(); e.stopPropagation(); applyScrollDelta(document.scrollingElement || document.documentElement, dy); }, { passive: false }); }; const transferEdgeScroll = (child, parent) => { if (!child || !parent) return; child.addEventListener("wheel", (e) => { if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return; const dy = normalizedWheelDeltaY(e); const childCanScroll = child.scrollHeight > child.clientHeight + 1; const childMax = Math.max(0, child.scrollHeight - child.clientHeight); const movingInside = childCanScroll && ((dy < 0 && child.scrollTop > 0.5) || (dy > 0 && child.scrollTop < childMax - 0.5)); e.preventDefault(); e.stopPropagation(); let rest = dy; if (movingInside) rest = applyScrollDelta(child, dy); if (Math.abs(rest) > 0.001) { const parentRest = applyScrollDelta(parent, rest); if (Math.abs(parentRest) > 0.001) applyScrollDelta(document.scrollingElement || document.documentElement, parentRest); } }, { passive: false }); }; const rightPanel = document.querySelector(".panel"); chainScroll(rightPanel); chainScroll(ui.archiveContent); transferEdgeScroll(ui.log, rightPanel); const inputContext = window.TarinaiUIInputShared.createInputContext({ canvas, world, ui, uiCache }); if (!inputContext) throw new Error("Tarinai UI input context is not available"); window.TarinaiTouchInput.bindTouchInput(inputContext); window.TarinaiMouseInput.bindMouseInput(inputContext); window.addEventListener("keydown", (e) => { if (e.key !== "Escape") return; closeFieldDialog(); closeEcologyDialog(); closeVisualSettingsDialog(); closeArchiveResetDialog(); ui.creditsDialog?.classList.add("hidden"); }); window.addEventListener("resize", resizeCanvas); }