"use strict"; function resizeCanvas() { const rect = canvas.getBoundingClientRect(); const perf = world?.performanceLevel ? world.performanceLevel() : 0; const maxDpr = perf >= 2 ? 1 : (perf === 1 ? 1.5 : 2); const dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, maxDpr)); const nextW = Math.max(1, Math.floor(rect.width * dpr)); const nextH = Math.max(1, Math.floor(rect.height * dpr)); if (canvas.width === nextW && canvas.height === nextH && uiCache.canvasDpr === dpr) return; uiCache.canvasDpr = dpr; canvas.width = nextW; canvas.height = nextH; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.fillStyle = "#cfe3d3"; ctx.fillRect(0, 0, Math.max(1, rect.width), Math.max(1, rect.height)); world.setViewportSize(Math.max(1, rect.width), Math.max(1, rect.height)); uiCache.lastChartDraw = ""; if (typeof render === "function") render(); } function applyFieldLayout(fieldType = world.fieldType || "garden") { const field = FIELD_TYPES?.[fieldType] || FIELD_TYPES.garden; const root = document.documentElement; root.style.setProperty("--field-height", field.height); root.style.setProperty("--field-min-height", field.minHeight || field.height); root.style.setProperty("--field-mobile-height", field.mobileHeight || field.height); root.style.setProperty("--field-mobile-min-height", field.mobileMinHeight || field.mobileHeight || "460px"); world.fieldType = field.id; const rect = canvas.getBoundingClientRect(); world.setViewportSize(rect.width, rect.height); resizeCanvas(); } function openFieldDialog() { ui.fieldDialog?.classList.remove("hidden"); } function closeFieldDialog() { ui.fieldDialog?.classList.add("hidden"); } function hydrateLazyImages(root) { const scope = root || document; for (const img of scope.querySelectorAll("img[data-src]")) { if (!img.getAttribute("src")) img.setAttribute("src", img.dataset.src || ""); } } function applyToolTips() { const tips = window.TEXT_CATALOG?.toolTips || {}; if (!ui.toolPalette) return; let pop = document.getElementById("toolTipPopover"); if (!pop) { pop = document.createElement("div"); pop.id = "toolTipPopover"; pop.className = "tool-tip-popover hidden"; document.body.appendChild(pop); } const showTip = (btn) => { const baseTip = btn?.dataset?.tip || ""; if (!baseTip) return; const tip = baseTip; pop.textContent = tip; pop.classList.remove("hidden"); const rect = btn.getBoundingClientRect(); const popW = pop.offsetWidth || 220; const popH = pop.offsetHeight || 36; const left = rect.left - popW - 12; pop.style.left = `${clamp(left, 8, Math.max(8, window.innerWidth - popW - 8))}px`; pop.style.top = `${clamp(rect.top + rect.height / 2 - popH / 2, 8, Math.max(8, window.innerHeight - popH - 8))}px`; }; const hideTip = () => pop.classList.add("hidden"); for (const btn of ui.toolPalette.querySelectorAll(".tool[data-tool]")) { const tip = tips[btn.dataset.tool]; if (!tip) continue; btn.dataset.tip = tip; if (btn.dataset.tipBound === "1") continue; btn.dataset.tipBound = "1"; btn.addEventListener("mouseenter", () => showTip(btn)); btn.addEventListener("focus", () => showTip(btn)); btn.addEventListener("mouseleave", hideTip); btn.addEventListener("blur", hideTip); } } function renderEcologyCards() { if (!ui.ecologyGrid || ui.ecologyGrid.dataset.ready === "1") return; const cards = window.TEXT_CATALOG?.ecologyCards || []; ui.ecologyGrid.innerHTML = cards.map(card => `
${escapeHtml(card.title || "")}

${escapeHtml(card.text || "")}

`).join(""); ui.ecologyGrid.dataset.ready = "1"; } function openEcologyDialog() { renderEcologyCards(); hydrateLazyImages(ui.ecologyDialog); ui.ecologyDialog?.classList.remove("hidden"); } function closeEcologyDialog() { ui.ecologyDialog?.classList.add("hidden"); } function openArchiveResetDialog() { ui.archiveResetDialog?.classList.remove("hidden"); } function closeArchiveResetDialog() { ui.archiveResetDialog?.classList.add("hidden"); } function resetWithField(fieldType) { const field = FIELD_TYPES?.[fieldType] || FIELD_TYPES.garden; closeFieldDialog(); applyFieldLayout(field.id); world.reset(null, field.id); uiCache.archiveVersion = ""; renderLog(world.logs); renderArchive(); renderStats(); render(); } function screenToWorld(e) { const rect = canvas.getBoundingClientRect(); const touch = e.touches ? e.touches[0] : e; const sx = touch.clientX - rect.left; const sy = touch.clientY - rect.top; return world.screenToWorld ? world.screenToWorld(sx, sy) : { x: sx, y: sy }; } function resizeFieldFromWheel(e) { if (!world.setFieldZoom) return; e.preventDefault(); const rect = canvas.getBoundingClientRect(); const sx = e.clientX - rect.left; const sy = e.clientY - rect.top; const focus = world.screenToWorld ? world.screenToWorld(sx, sy) : { x: sx, y: sy }; const factor = e.deltaY > 0 ? 1 / 1.08 : 1.08; if (!world.setFieldZoom((world.fieldZoom || 1) * factor)) return; const off = world.fieldScreenOffset ? world.fieldScreenOffset() : { x: 0, y: 0 }; const scale = world.viewScale ? world.viewScale() : 1; world.cameraX = focus.x - (sx - off.x) / scale; world.cameraY = focus.y - (sy - off.y) / scale; world.clampCamera?.(); render(); } function selectTool(tool) { world.tool = tool; for (const btn of uiCache.toolButtons) { btn.classList.toggle("selected", btn.dataset.tool === tool); } syncToolSizeBadges(); canvas.style.cursor = tool === "observe" ? "default" : (tool === "pinch" ? "grab" : (tool === "poke" || tool === "delete" ? "pointer" : "crosshair")); } function showToast(text) { ui.toast.textContent = text; ui.toast.classList.remove("hidden"); clearTimeout(showToast._timer); showToast._timer = setTimeout(() => ui.toast.classList.add("hidden"), 1400); } function openCreditsDialog() { ui.creditsDialog?.classList.remove("hidden"); } function closeCreditsDialog() { ui.creditsDialog?.classList.add("hidden"); } function syncTopButtons() { ui.pauseBtn.textContent = world.paused ? "\u518d\u958b" : "\u4e00\u6642\u505c\u6b62"; ui.speedBtn.textContent = `\u901f\u5ea6 x${world.speed}`; syncAudioControls(); syncArchiveUpdateButton(); } function archiveAutoUpdateEnabled() { return Boolean(uiCache.archiveUpdateEnabled); } function syncArchiveUpdateButton() { const btn = ui.archiveUpdateToggleBtn; if (!btn) return; const on = archiveAutoUpdateEnabled(); btn.textContent = on ? "\u66f4\u65b0 ON" : "\u66f4\u65b0 OFF"; btn.classList.toggle("active", on); btn.setAttribute("aria-pressed", on ? "true" : "false"); } function setArchiveUpdateEnabled(on) { uiCache.archiveUpdateEnabled = Boolean(on); syncArchiveUpdateButton(); if (uiCache.archiveUpdateEnabled) { uiCache.archiveVersion = ""; uiCache.archiveFamilyVersion = null; world.familyTreeDirty = true; scheduleArchiveWindowRender?.(); showToast("\u5bb6\u7cfb\u56f3\u306e\u66f4\u65b0\u3092ON\u306b\u3057\u307e\u3057\u305f\u3002"); } else { if (uiCache.archiveRenderTimer) { clearTimeout(uiCache.archiveRenderTimer); uiCache.archiveRenderTimer = 0; } uiCache.archiveScheduled = false; showToast("\u5bb6\u7cfb\u56f3\u306e\u66f4\u65b0\u3092OFF\u306b\u3057\u307e\u3057\u305f\u3002"); } }