"use strict"; (function (global) { const MODES = Object.freeze(["auto", "camera", "tool", "family"]); const STORAGE_KEY = "tarinai_mobile_input_mode_v1"; class InputModeManager { constructor() { this.storageKey = STORAGE_KEY; this.currentMode = this.loadMode(); this.gestureState = { active: false, kind: "", startedAt: 0, pointerCount: 0, }; this.classification = this.classify(); } loadMode() { try { const mode = global.localStorage?.getItem(STORAGE_KEY) || "auto"; return MODES.includes(mode) ? mode : "auto"; } catch (_) { return "auto"; } } setMode(mode = "auto") { this.currentMode = MODES.includes(mode) ? mode : "auto"; try { global.localStorage?.setItem(STORAGE_KEY, this.currentMode); } catch (_) {} this.emit("input:mode", this.snapshot()); return this.currentMode; } classify() { const nav = global.navigator || {}; const ua = String(nav.userAgent || ""); const mm = typeof global.matchMedia === "function" ? global.matchMedia.bind(global) : null; const width = Number(global.innerWidth || 9999); const height = Number(global.innerHeight || 9999); const shortSide = Math.min(width, height); const longSide = Math.max(width, height); const pointerCoarse = Boolean(mm?.("(pointer: coarse)")?.matches); const hasHover = Boolean(mm?.("(hover: hover)")?.matches); const noHover = Boolean(mm?.("(hover: none)")?.matches); const isTouchDevice = Number(nav.maxTouchPoints || 0) > 0 || "ontouchstart" in global; const isCompactViewport = shortSide <= 720 && longSide <= 1100; const userAgentMobile = /Android|iPhone|iPod|iPad|Mobile|Silk|Tablet/i.test(ua); const touchFirst = Boolean(pointerCoarse && noHover && !hasHover && isTouchDevice && (isCompactViewport || userAgentMobile)); this.classification = Object.freeze({ isTouchDevice, isCompactViewport, hasHover, pointerCoarse, userAgentMobile, touchFirst, width, height, }); return this.classification; } refresh() { const previous = this.classification; const next = this.classify(); if (JSON.stringify(previous) !== JSON.stringify(next)) this.emit("input:classification", this.snapshot()); return next; } beginGesture(kind = "", pointerCount = 1) { this.gestureState = { active: true, kind: String(kind || ""), startedAt: performance?.now?.() ?? Date.now(), pointerCount: Math.max(0, Number(pointerCount) || 0), }; return this.gestureState; } endGesture() { this.gestureState = { ...this.gestureState, active: false, pointerCount: 0 }; return this.gestureState; } canPanCanvas() { if (!this.classification?.touchFirst) return true; return this.currentMode === "auto" || this.currentMode === "camera"; } canPlaceTools() { if (!this.classification?.touchFirst) return true; return this.currentMode === "auto" || this.currentMode === "tool"; } canFamilyTreeCaptureTouch() { if (!this.classification?.touchFirst) return true; return this.currentMode === "auto" || this.currentMode === "family"; } snapshot() { return Object.freeze({ ...this.classification, currentMode: this.currentMode, gestureState: { ...this.gestureState }, canPanCanvas: this.canPanCanvas(), canPlaceTools: this.canPlaceTools(), canFamilyTreeCaptureTouch: this.canFamilyTreeCaptureTouch(), }); } emit(type, detail) { global.TarinaiEvents?.emit?.(type, detail); } } global.InputModeManager = InputModeManager; global.TarinaiInputMode = global.TarinaiInputMode || new InputModeManager(); })(typeof window !== "undefined" ? window : globalThis);