@ait-co/devtools 0.1.41 → 0.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["refreshPanel"],"sources":["../../src/i18n/en.ts","../../src/i18n/ko.ts","../../src/i18n/index.ts","../../src/mock/state.ts","../../src/telemetry/consent-toast.ts","../../src/telemetry/state.ts","../../src/telemetry/send.ts","../../src/telemetry/tier0.ts","../../src/telemetry/index.ts","../../src/panel/helpers.ts","../../src/panel/styles.ts","../../src/mock/device/_helpers.ts","../../src/mock/permissions.ts","../../src/mock/device/camera.ts","../../src/mock/device/clipboard.ts","../../src/mock/device/contacts.ts","../../src/mock/device/haptic.ts","../../src/mock/device/location.ts","../../src/mock/proxy.ts","../../src/mock/device/storage.ts","../../src/panel/tabs/device.ts","../../src/mock/observe.ts","../../src/mock/ads/index.ts","../../src/panel/tabs/ads.ts","../../src/panel/tabs/analytics.ts","../../src/panel/tabs/environment.ts","../../src/panel/tabs/events.ts","../../src/mock/iap/index.ts","../../src/panel/tabs/iap.ts","../../src/panel/tabs/location.ts","../../src/panel/tabs/notifications.ts","../../src/panel/tabs/permissions.ts","../../src/mock/preset-store.ts","../../src/mock/presets.ts","../../src/panel/tabs/presets.ts","../../src/panel/tabs/storage.ts","../../src/mock/navigation/index.ts","../../src/panel/device-emulation.ts","../../src/panel/viewport.ts","../../src/panel/tabs/viewport.ts","../../src/panel/tabs/index.ts","../../src/panel/index.ts"],"sourcesContent":["import type { StringKey } from './ko.js';\n\n// English translations. Mirrors every key in `ko.ts`; missing keys fall back to\n// the key string at runtime (see `t()` in index.ts), but the `Record<StringKey,\n// string>` type below means a missing key will typecheck-fail.\n\nexport const en: Record<StringKey, string> = {\n // Panel chrome\n 'panel.title': 'AIT DevTools',\n 'panel.toggle.title': 'AIT DevTools',\n 'panel.close': 'Close',\n 'panel.editMode.on': 'EDIT',\n 'panel.editMode.off': 'READ-ONLY',\n 'panel.editMode.toggleTitle': 'Toggle panel edit mode',\n 'panel.tabError': 'Error rendering \"{tab}\" tab.',\n\n // Tab names\n 'panel.tab.env': 'Environment',\n 'panel.tab.presets': 'Presets',\n 'panel.tab.viewport': 'Viewport',\n 'panel.tab.permissions': 'Permissions',\n 'panel.tab.notifications': 'Notifications',\n 'panel.tab.location': 'Location',\n 'panel.tab.device': 'Device',\n 'panel.tab.iap': 'IAP',\n 'panel.tab.ads': 'Ads',\n 'panel.tab.events': 'Events',\n 'panel.tab.analytics': 'Analytics',\n 'panel.tab.storage': 'Storage',\n\n // Common\n 'common.readOnly': 'Read-only — mock responses are controlled at build time.',\n\n // Consent toast\n 'toast.consent.title': 'Send anonymous usage stats?',\n 'toast.consent.body':\n 'We collect anonymous events only, to improve the tool. You can turn this off anytime in the Environment tab.',\n 'toast.consent.learnMore': 'Learn more',\n 'toast.consent.accept': 'Yes, send',\n 'toast.consent.deny': 'No, thanks',\n\n // Environment tab\n 'env.section.platform': 'Platform',\n 'env.row.os': 'OS',\n 'env.row.appVersion': 'App Version',\n 'env.row.environment': 'Environment',\n 'env.row.locale': 'Locale',\n 'env.section.network': 'Network',\n 'env.row.networkStatus': 'Status',\n 'env.section.safeArea': 'Safe Area Insets',\n 'env.row.safeArea.top': 'Top',\n 'env.row.safeArea.bottom': 'Bottom',\n 'env.section.navigation': 'Navigation',\n 'env.row.iosSwipeGesture': 'iOS swipe-back',\n 'env.value.iosSwipeGesture.unset': 'not called',\n 'env.value.iosSwipeGesture.enabled': 'enabled',\n 'env.value.iosSwipeGesture.disabled': 'disabled',\n 'env.hint.iosSwipeGesture':\n 'Last value passed to setIosSwipeGestureEnabled. Switching Environment to toss lets a toss-gated guard toggle this.',\n\n // Environment > Telemetry section\n 'env.telemetry.section': 'Telemetry',\n // Tier 0 — opt-out anonymous signal\n 'env.telemetry.t0Row': 'Anonymous usage signal (Tier 0)',\n 'env.telemetry.t0On': 'On',\n 'env.telemetry.t0Off': 'Off',\n 'env.telemetry.t0TurnOn': 'Turn on',\n 'env.telemetry.t0TurnOff': 'Turn off',\n 'env.telemetry.t0Desc': 'Version + date only, no PII. Once per day. Helps improve the package.',\n // Tier 1 — opt-in extended telemetry\n 'env.telemetry.row': 'Extended telemetry (Tier 1)',\n 'env.telemetry.on': 'On',\n 'env.telemetry.off': 'Off',\n 'env.telemetry.turnOn': 'Turn on',\n 'env.telemetry.turnOff': 'Turn off',\n 'env.telemetry.anonIdLabel': 'anon_id: {value}',\n 'env.telemetry.anonIdNotSet': '(not yet set)',\n 'env.telemetry.anonIdCopyTitle': 'Click to copy full anon_id',\n 'env.telemetry.deleteBtn': 'Delete my data',\n 'env.telemetry.deleting': 'Deleting…',\n 'env.telemetry.deleted': 'Deleted',\n 'env.telemetry.deleteFailedRetry': 'Delete failed (please retry)',\n 'env.telemetry.deleteFailed': 'Delete failed',\n 'env.telemetry.privacyLink': 'Privacy policy →',\n\n // Environment > Language toggle (new)\n 'env.section.language': 'Language',\n 'env.language.row': 'Language',\n 'env.language.ko': '한국어',\n 'env.language.en': 'English',\n\n // Permissions tab\n 'permissions.section.device': 'Device Permissions',\n\n // Location tab\n 'location.section.current': 'Current Location',\n 'location.row.latitude': 'Latitude',\n 'location.row.longitude': 'Longitude',\n 'location.row.accuracy': 'Accuracy',\n\n // Device tab\n 'device.section.modes': 'Device API Modes',\n 'device.row.camera': 'Camera',\n 'device.row.photos': 'Photos',\n 'device.row.location': 'Location',\n 'device.row.network': 'Network',\n 'device.row.clipboard': 'Clipboard',\n 'device.section.mockImages': 'Mock Images ({count})',\n 'device.btn.add': '+ Add',\n 'device.btn.useDefaults': 'Use defaults',\n 'device.btn.clear': 'Clear',\n 'device.prompt.camera.title': 'Camera Prompt — Select an image',\n 'device.prompt.photos.title': 'Photos Prompt — Select images',\n 'device.prompt.location.title': 'Location Prompt — Enter coordinates',\n 'device.prompt.locationUpdate.title': 'Location Update — Send coordinates',\n 'device.prompt.fallbackTitle': 'Prompt: {type}',\n 'device.prompt.label.lat': 'Lat',\n 'device.prompt.label.lng': 'Lng',\n 'device.prompt.send': 'Send',\n 'device.prompt.cancel': 'Cancel',\n\n // Device tab — Haptic section\n 'device.section.haptic': 'Haptic',\n 'device.haptic.lastCall': 'Last haptic',\n 'device.haptic.noneYet': '(none yet)',\n 'device.haptic.trigger': 'Trigger haptic',\n\n // Viewport tab\n 'viewport.section.device': 'Device',\n 'viewport.row.preset': 'Preset',\n 'viewport.row.orientation': 'Orientation',\n 'viewport.row.notchSide': 'Notch side',\n 'viewport.section.custom': 'Custom size',\n 'viewport.row.width': 'Width (px)',\n 'viewport.row.height': 'Height (px)',\n 'viewport.section.appearance': 'Appearance',\n 'viewport.row.showFrame': 'Show frame',\n 'viewport.row.showAitNavBar': 'Show Apps in Toss nav bar',\n 'viewport.row.navBarType': 'Nav bar type',\n 'viewport.status.noConstraint': 'No viewport constraint — body fills the window.',\n 'viewport.status.cssPhysical': 'CSS / physical',\n 'viewport.status.safeArea': 'Safe area',\n 'viewport.status.aitNavBar': 'AIT nav bar',\n 'viewport.status.aitNavBarValue': '{height}px → SafeArea top · {type}',\n 'viewport.orientation.autoSuffix': '{orient} (auto)',\n\n // IAP tab\n 'iap.section.simulator': 'IAP Simulator',\n 'iap.row.nextResult': 'Next Purchase Result',\n 'iap.section.tossPay': 'TossPay',\n 'iap.row.tossPayResult': 'Next Payment Result',\n 'iap.section.pending': 'Pending Orders ({count})',\n 'iap.empty.pending': '(no pending orders)',\n 'iap.section.completed': 'Completed Orders ({count})',\n 'iap.empty.completed': '(no completed orders)',\n 'iap.btn.complete': 'Complete',\n 'iap.label.pending': 'PENDING',\n\n // Events tab\n 'events.section.navigation': 'Navigation Events',\n 'events.btn.triggerBack': 'Trigger Back Event',\n 'events.btn.triggerHome': 'Trigger Home Event',\n 'events.section.login': 'Login',\n 'events.row.loggedIn': 'Logged In',\n 'events.row.tossLoginIntegrated': 'Toss Login Integrated',\n\n // Analytics tab\n 'analytics.section.log': 'Analytics Log ({count})',\n 'analytics.btn.clear': 'Clear',\n 'analytics.calls.section': 'SDK Calls ({count})',\n 'analytics.calls.btn.clear': 'Clear',\n 'analytics.calls.empty': '(no SDK calls yet)',\n\n // Storage tab\n 'storage.section.title': 'Storage ({count} items)',\n 'storage.btn.clearAll': 'Clear All',\n 'storage.empty': 'No items in storage',\n\n // Presets tab\n 'presets.section.builtIn': 'Built-in scenarios',\n 'presets.section.saved': 'Saved presets ({count})',\n 'presets.section.save': 'Save',\n 'presets.save.description': 'Capture network / permissions / auth / IAP / ads / payment slices.',\n 'presets.btn.saveCurrent': 'Save current as preset',\n 'presets.btn.apply': 'Apply',\n 'presets.btn.reApply': 'Re-apply',\n 'presets.btn.delete': 'Delete',\n 'presets.empty.saved': 'No saved presets yet.',\n 'presets.empty.builtIn': 'No built-in presets.',\n 'presets.prompt.label': 'Preset label?',\n 'presets.confirm.delete': 'Delete preset \"{label}\"?',\n\n // Ads tab\n 'ads.section.state': 'Ads State',\n 'ads.row.isLoaded': 'isLoaded',\n 'ads.row.forceNoFill': 'Force \"no fill\"',\n 'ads.empty.events': 'No events yet',\n 'ads.section.googleAdMob': 'GoogleAdMob',\n 'ads.section.tossAds': 'TossAds',\n 'ads.section.fullScreenAd': 'FullScreenAd',\n 'ads.btn.load': 'Load',\n 'ads.btn.show': 'Show',\n 'ads.section.tossAdsBanner': 'TossAds Banner',\n 'ads.row.rewardUnitType': 'Reward unit type',\n 'ads.row.rewardAmount': 'Reward amount',\n 'ads.btn.render': 'Render',\n 'ads.btn.noFill': 'No-fill',\n 'ads.btn.click': 'Click',\n 'ads.btn.destroy': 'Destroy',\n\n // Notifications tab\n 'notifications.section.title': 'requestNotificationAgreement',\n 'notifications.option.newAgreement': 'newAgreement (first-time agree)',\n 'notifications.option.alreadyAgreed': 'alreadyAgreed (already opted-in)',\n 'notifications.option.agreementRejected': 'agreementRejected (user declined)',\n};\n","// Korean string catalog (source of truth — keys are typed from this file).\n// Keys follow `<area>.<purpose>` convention. Variable interpolation uses\n// `{name}` placeholders resolved by `t(key, { name: value })`.\n//\n// Some chrome (button labels like \"Load\", \"Show\", \"Clear\", \"Apply\") is left as\n// English in both locales because the panel is an internal devtools surface\n// and these terms are universally recognised by developers in both locales.\n\nexport const ko = {\n // Panel chrome\n 'panel.title': 'AIT DevTools',\n 'panel.toggle.title': 'AIT DevTools',\n 'panel.close': 'Close',\n 'panel.editMode.on': 'EDIT',\n 'panel.editMode.off': 'READ-ONLY',\n 'panel.editMode.toggleTitle': '패널 편집 모드 전환',\n 'panel.tabError': '\"{tab}\" 탭 렌더링 중 오류가 발생했습니다.',\n\n // Tab names\n 'panel.tab.env': 'Environment',\n 'panel.tab.presets': 'Presets',\n 'panel.tab.viewport': 'Viewport',\n 'panel.tab.permissions': 'Permissions',\n 'panel.tab.notifications': 'Notifications',\n 'panel.tab.location': 'Location',\n 'panel.tab.device': 'Device',\n 'panel.tab.iap': 'IAP',\n 'panel.tab.ads': 'Ads',\n 'panel.tab.events': 'Events',\n 'panel.tab.analytics': 'Analytics',\n 'panel.tab.storage': 'Storage',\n\n // Common\n 'common.readOnly': '읽기 전용 — mock 응답은 빌드 타임에 고정됩니다.',\n\n // Consent toast\n 'toast.consent.title': '익명 사용 통계를 보낼까요?',\n 'toast.consent.body': '도구 개선을 위해 익명 이벤트만 수집해요. 언제든 환경 탭에서 끌 수 있어요.',\n 'toast.consent.learnMore': '더 알아보기',\n 'toast.consent.accept': '네, 보낼게요',\n 'toast.consent.deny': '아니요',\n\n // Environment tab\n 'env.section.platform': 'Platform',\n 'env.row.os': 'OS',\n 'env.row.appVersion': 'App Version',\n 'env.row.environment': 'Environment',\n 'env.row.locale': 'Locale',\n 'env.section.network': 'Network',\n 'env.row.networkStatus': 'Status',\n 'env.section.safeArea': 'Safe Area Insets',\n 'env.row.safeArea.top': 'Top',\n 'env.row.safeArea.bottom': 'Bottom',\n 'env.section.navigation': 'Navigation',\n 'env.row.iosSwipeGesture': 'iOS swipe-back',\n 'env.value.iosSwipeGesture.unset': '미호출',\n 'env.value.iosSwipeGesture.enabled': 'enabled',\n 'env.value.iosSwipeGesture.disabled': 'disabled',\n 'env.hint.iosSwipeGesture':\n 'setIosSwipeGestureEnabled의 마지막 호출값. Environment를 toss로 바꾸면 toss-gated 가드가 이 값을 토글합니다.',\n\n // Environment > Telemetry section\n 'env.telemetry.section': 'Telemetry',\n // Tier 0 — opt-out anonymous signal\n 'env.telemetry.t0Row': '익명 사용 신호 (Tier 0)',\n 'env.telemetry.t0On': 'On',\n 'env.telemetry.t0Off': 'Off',\n 'env.telemetry.t0TurnOn': 'Turn on',\n 'env.telemetry.t0TurnOff': 'Turn off',\n 'env.telemetry.t0Desc': '버전·날짜만 수집, PII 없음. 하루 1회. 패키지 개선에 사용됩니다.',\n // Tier 1 — opt-in extended telemetry\n 'env.telemetry.row': '확장 텔레메트리 (Tier 1)',\n 'env.telemetry.on': 'On',\n 'env.telemetry.off': 'Off',\n 'env.telemetry.turnOn': 'Turn on',\n 'env.telemetry.turnOff': 'Turn off',\n 'env.telemetry.anonIdLabel': 'anon_id: {value}',\n 'env.telemetry.anonIdNotSet': '(not yet set)',\n 'env.telemetry.anonIdCopyTitle': '전체 anon_id 복사',\n 'env.telemetry.deleteBtn': '내 데이터 삭제',\n 'env.telemetry.deleting': '삭제 중…',\n 'env.telemetry.deleted': '삭제 완료',\n 'env.telemetry.deleteFailedRetry': '삭제 실패 (다시 시도해주세요)',\n 'env.telemetry.deleteFailed': '삭제 실패',\n 'env.telemetry.privacyLink': '개인정보 처리방침 →',\n\n // Environment > Language toggle (new)\n 'env.section.language': 'Language',\n 'env.language.row': 'Language',\n 'env.language.ko': '한국어',\n 'env.language.en': 'English',\n\n // Permissions tab\n 'permissions.section.device': 'Device Permissions',\n\n // Location tab\n 'location.section.current': 'Current Location',\n 'location.row.latitude': 'Latitude',\n 'location.row.longitude': 'Longitude',\n 'location.row.accuracy': 'Accuracy',\n\n // Device tab\n 'device.section.modes': 'Device API Modes',\n 'device.row.camera': 'Camera',\n 'device.row.photos': 'Photos',\n 'device.row.location': 'Location',\n 'device.row.network': 'Network',\n 'device.row.clipboard': 'Clipboard',\n 'device.section.mockImages': 'Mock Images ({count})',\n 'device.btn.add': '+ Add',\n 'device.btn.useDefaults': 'Use defaults',\n 'device.btn.clear': 'Clear',\n 'device.prompt.camera.title': 'Camera Prompt — 이미지를 선택하세요',\n 'device.prompt.photos.title': 'Photos Prompt — 이미지를 선택하세요',\n 'device.prompt.location.title': 'Location Prompt — 좌표 입력',\n 'device.prompt.locationUpdate.title': 'Location Update — 좌표 전송',\n 'device.prompt.fallbackTitle': 'Prompt: {type}',\n 'device.prompt.label.lat': 'Lat',\n 'device.prompt.label.lng': 'Lng',\n 'device.prompt.send': 'Send',\n 'device.prompt.cancel': 'Cancel',\n\n // Device tab — Haptic section\n 'device.section.haptic': 'Haptic',\n 'device.haptic.lastCall': '마지막 haptic',\n 'device.haptic.noneYet': '(아직 없음)',\n 'device.haptic.trigger': 'Haptic 트리거',\n\n // Viewport tab\n 'viewport.section.device': 'Device',\n 'viewport.row.preset': 'Preset',\n 'viewport.row.orientation': 'Orientation',\n 'viewport.row.notchSide': 'Notch side',\n 'viewport.section.custom': 'Custom size',\n 'viewport.row.width': 'Width (px)',\n 'viewport.row.height': 'Height (px)',\n 'viewport.section.appearance': 'Appearance',\n 'viewport.row.showFrame': 'Show frame',\n 'viewport.row.showAitNavBar': 'Apps in Toss 내비게이션 바 표시',\n 'viewport.row.navBarType': 'Nav bar type',\n 'viewport.status.noConstraint': '뷰포트 제약 없음 — body가 창을 가득 채웁니다.',\n 'viewport.status.cssPhysical': 'CSS / physical',\n 'viewport.status.safeArea': 'Safe area',\n 'viewport.status.aitNavBar': 'AIT nav bar',\n 'viewport.status.aitNavBarValue': '{height}px → SafeArea top · {type}',\n 'viewport.orientation.autoSuffix': '{orient} (auto)',\n\n // IAP tab\n 'iap.section.simulator': 'IAP Simulator',\n 'iap.row.nextResult': 'Next Purchase Result',\n 'iap.section.tossPay': 'TossPay',\n 'iap.row.tossPayResult': 'Next Payment Result',\n 'iap.section.pending': 'Pending Orders ({count})',\n 'iap.empty.pending': '(대기 중인 주문 없음)',\n 'iap.section.completed': 'Completed Orders ({count})',\n 'iap.empty.completed': '(완료된 주문 없음)',\n 'iap.btn.complete': 'Complete',\n 'iap.label.pending': 'PENDING',\n\n // Events tab\n 'events.section.navigation': 'Navigation Events',\n 'events.btn.triggerBack': 'Back 이벤트 발생',\n 'events.btn.triggerHome': 'Home 이벤트 발생',\n 'events.section.login': 'Login',\n 'events.row.loggedIn': 'Logged In',\n 'events.row.tossLoginIntegrated': 'Toss Login Integrated',\n\n // Analytics tab\n 'analytics.section.log': 'Analytics Log ({count})',\n 'analytics.btn.clear': 'Clear',\n 'analytics.calls.section': 'SDK Calls ({count})',\n 'analytics.calls.btn.clear': 'Clear',\n 'analytics.calls.empty': '(아직 SDK 호출 없음)',\n\n // Storage tab\n 'storage.section.title': 'Storage ({count} items)',\n 'storage.btn.clearAll': 'Clear All',\n 'storage.empty': '저장된 항목이 없습니다',\n\n // Presets tab\n 'presets.section.builtIn': 'Built-in scenarios',\n 'presets.section.saved': 'Saved presets ({count})',\n 'presets.section.save': 'Save',\n 'presets.save.description':\n 'network / permissions / auth / IAP / ads / payment 슬라이스를 캡처합니다.',\n 'presets.btn.saveCurrent': '현재 상태를 프리셋으로 저장',\n 'presets.btn.apply': 'Apply',\n 'presets.btn.reApply': 'Re-apply',\n 'presets.btn.delete': 'Delete',\n 'presets.empty.saved': '저장된 프리셋이 아직 없습니다.',\n 'presets.empty.builtIn': '내장 프리셋이 없습니다.',\n 'presets.prompt.label': '프리셋 라벨을 입력하세요',\n 'presets.confirm.delete': '\"{label}\" 프리셋을 삭제할까요?',\n\n // Ads tab\n 'ads.section.state': 'Ads State',\n 'ads.row.isLoaded': 'isLoaded',\n 'ads.row.forceNoFill': '강제 \"no fill\"',\n 'ads.empty.events': '아직 이벤트가 없습니다',\n 'ads.section.googleAdMob': 'GoogleAdMob',\n 'ads.section.tossAds': 'TossAds',\n 'ads.section.fullScreenAd': 'FullScreenAd',\n 'ads.btn.load': 'Load',\n 'ads.btn.show': 'Show',\n 'ads.section.tossAdsBanner': 'TossAds 배너',\n 'ads.row.rewardUnitType': '리워드 단위 타입',\n 'ads.row.rewardAmount': '리워드 수량',\n 'ads.btn.render': 'Render',\n 'ads.btn.noFill': 'No-fill',\n 'ads.btn.click': 'Click',\n 'ads.btn.destroy': 'Destroy',\n\n // Notifications tab\n 'notifications.section.title': 'requestNotificationAgreement',\n 'notifications.option.newAgreement': 'newAgreement (최초 동의)',\n 'notifications.option.alreadyAgreed': 'alreadyAgreed (이미 동의됨)',\n 'notifications.option.agreementRejected': 'agreementRejected (사용자 거절)',\n} as const;\n\nexport type StringKey = keyof typeof ko;\n","/**\n * Vanilla TS i18n for the floating DevTools panel.\n *\n * Public surface:\n * - `t(key, vars?)` — look up a UI string, with `{name}` placeholder\n * interpolation. Falls back to the key itself if a translation is missing.\n * - `getLocale()` / `setLocale(locale)` — read/persist the active locale.\n * `setLocale` dispatches `__ait:localechange` so the panel can remount.\n * - `detectLocale()` — first-run heuristic from `navigator.language`.\n *\n * `ko` is the source of truth (keys are typed from it). `en` is also a full\n * `Record<StringKey, string>` (devtools is developer-facing, en is a real\n * audience). The `Partial` lookup table preserves the runtime `?? key` safety\n * net even though we ship complete catalogs today.\n */\n\nimport { en } from './en.js';\nimport { ko, type StringKey } from './ko.js';\n\nexport type Locale = 'ko' | 'en';\n\nconst LOCALE_STORAGE_KEY = '__ait_locale';\nconst LOCALE_CHANGE_EVENT = '__ait:localechange';\n\nconst tables: Record<Locale, Partial<Record<StringKey, string>>> = { ko, en };\n\nlet currentLocale: Locale | null = null;\n\nfunction safeReadStorage(): Locale | null {\n if (typeof localStorage === 'undefined') return null;\n try {\n const raw = localStorage.getItem(LOCALE_STORAGE_KEY);\n if (raw === 'ko' || raw === 'en') return raw;\n } catch {\n /* localStorage can throw in privacy modes — fall back silently */\n }\n return null;\n}\n\nfunction safeWriteStorage(locale: Locale): void {\n if (typeof localStorage === 'undefined') return;\n try {\n localStorage.setItem(LOCALE_STORAGE_KEY, locale);\n } catch {\n /* ignore quota / privacy errors */\n }\n}\n\n/**\n * Read `navigator.language` and decide a locale. `ko` (and `ko-*`) → `'ko'`,\n * everything else → `'en'`. Pure function; does not touch storage.\n */\nexport function detectLocale(): Locale {\n if (typeof navigator === 'undefined') return 'en';\n const lang = navigator.language ?? '';\n return /^ko\\b/i.test(lang) ? 'ko' : 'en';\n}\n\n/**\n * Resolve the active locale, in order:\n * 1. previously set in-memory value (set by `setLocale`)\n * 2. localStorage `__ait_locale`\n * 3. `detectLocale()` from navigator\n */\nexport function getLocale(): Locale {\n if (currentLocale) return currentLocale;\n const stored = safeReadStorage();\n currentLocale = stored ?? detectLocale();\n return currentLocale;\n}\n\n/**\n * Persist a locale choice and notify listeners. The panel listens for\n * `__ait:localechange` and re-mounts so every string re-evaluates.\n */\nexport function setLocale(locale: Locale): void {\n currentLocale = locale;\n safeWriteStorage(locale);\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(LOCALE_CHANGE_EVENT));\n }\n}\n\n/**\n * Look up a UI string for the current locale. Falls back to the key if missing,\n * so a forgotten key surfaces visibly rather than rendering empty.\n */\nexport function t(key: StringKey, vars?: Record<string, string | number>): string {\n const raw = tables[getLocale()][key] ?? key;\n if (!vars) return raw;\n return raw.replace(/\\{(\\w+)\\}/g, (match, name: string) => {\n const value = vars[name];\n return value === undefined ? match : String(value);\n });\n}\n\nexport type { StringKey };\nexport { LOCALE_CHANGE_EVENT, LOCALE_STORAGE_KEY };\n\n/**\n * Test-only escape hatch — resets the cached in-memory locale so subsequent\n * `getLocale()` calls re-read storage / re-detect. Production code never needs\n * this; tests use it between cases.\n */\nexport function _resetLocaleCacheForTests(): void {\n currentLocale = null;\n}\n","/**\n * @ait-co/devtools 중앙 상태 관리\n * DevTools Panel과 mock 구현체가 이 상태를 공유한다.\n */\n\nimport type { AitSdkCall } from '../mcp/ait-source.js';\nimport type {\n AnalyticsLogEntry,\n DeviceModes,\n IapNextResult,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n NotificationAgreementResult,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n ViewportState,\n} from './types.js';\n\nexport type { AitSdkCall, AitSdkCallFidelity } from '../mcp/ait-source.js';\nexport type {\n AitNavBarType,\n AnalyticsLogEntry,\n AppOrientation,\n DeviceApiMode,\n DeviceModes,\n HapticFeedbackType,\n IapNextResult,\n LandscapeSide,\n LocationCoords,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n NotchType,\n NotificationAgreementResult,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n SafeAreaProvenance,\n ViewportOrientation,\n ViewportPreset,\n ViewportPresetId,\n ViewportState,\n} from './types.js';\n\ntype Listener = () => void;\n\n/** SDK 호출 로그 ring buffer 상한 */\nconst SDK_CALL_LOG_MAX = 200;\n\nexport interface AitDevtoolsState {\n // 환경\n platform: PlatformOS;\n environment: OperationalEnvironment;\n appVersion: string;\n locale: string;\n schemeUri: string;\n groupId: string;\n deploymentId: string;\n deviceId: string;\n\n // 브랜드\n brand: {\n displayName: string;\n icon: string;\n primaryColor: string;\n };\n\n // 네트워크\n networkStatus: NetworkStatus;\n\n // 네비게이션 동작 — real은 native bridge로 발화하는 no-op API들의 마지막 호출값을\n // 관측 가능한 state로 mirror (real ground-truth: devtools#171 on-device relay).\n // null = 앱이 아직 호출 안 함(real 기본 동작 = iOS 엣지 스와이프 뒤로가기 enabled).\n navigation: {\n iosSwipeGestureEnabled: boolean | null;\n };\n\n // 권한\n permissions: Record<PermissionName, PermissionStatus>;\n\n // 위치\n location: MockLocation;\n\n // Safe Area\n safeAreaInsets: SafeAreaInsets;\n\n // 연락처\n contacts: MockContact[];\n\n // IAP\n iap: {\n products: MockIapProduct[];\n nextResult: IapNextResult;\n pendingOrders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }>;\n completedOrders: Array<{\n orderId: string;\n sku: string;\n status: 'COMPLETED' | 'REFUNDED';\n date: string;\n }>;\n };\n\n // 결제 (TossPay)\n payment: {\n nextResult: 'success' | 'fail';\n failReason: string;\n };\n\n // 로그인\n auth: {\n isLoggedIn: boolean;\n isTossLoginIntegrated: boolean;\n userKeyHash: string;\n anonymousKeyHash: string;\n };\n\n // 알림\n notification: {\n nextResult: NotificationAgreementResult;\n };\n\n // 광고\n ads: {\n isLoaded: boolean;\n nextEvent:\n | 'loaded'\n | 'clicked'\n | 'dismissed'\n | 'failedToShow'\n | 'impression'\n | 'userEarnedReward';\n forceNoFill: boolean;\n lastEvent: { type: string; timestamp: number } | null;\n /** AdMob reward 단위 타입 (기본: 'coins') */\n rewardUnitType: string;\n /** AdMob reward 단위 수량 (기본: 10) */\n rewardAmount: number;\n };\n\n // 게임\n game: {\n profile: { nickname: string; profileImageUri: string } | null;\n leaderboardScores: Array<{ score: string; timestamp: number }>;\n };\n\n // 분석 로그\n analyticsLog: AnalyticsLogEntry[];\n\n // SDK 호출 로그 (ring buffer, 상한 SDK_CALL_LOG_MAX)\n sdkCallLog: AitSdkCall[];\n\n // 디바이스 API 모드\n deviceModes: DeviceModes;\n\n // mock 모드용 더미 데이터\n mockData: MockData;\n\n // mock 활성화 상태\n panelEditable: boolean;\n\n // 뷰포트 시뮬레이션 (devtools 전용, SDK와 무관)\n viewport: ViewportState;\n}\n\nconst DEFAULT_STATE: AitDevtoolsState = {\n platform: 'ios',\n environment: 'sandbox',\n appVersion: '5.240.0',\n locale: 'ko-KR',\n schemeUri: '/',\n groupId: 'mock-group-id',\n deploymentId: 'mock-deployment-id',\n deviceId: '',\n\n brand: {\n displayName: 'Mock App',\n icon: '',\n primaryColor: '#3182F6',\n },\n\n networkStatus: 'WIFI',\n\n // null = 앱이 setIosSwipeGestureEnabled를 아직 호출 안 함.\n navigation: {\n iosSwipeGestureEnabled: null,\n },\n\n permissions: {\n clipboard: 'allowed',\n contacts: 'allowed',\n photos: 'allowed',\n geolocation: 'allowed',\n camera: 'allowed',\n microphone: 'notDetermined',\n },\n\n location: {\n coords: {\n latitude: 37.5665,\n longitude: 126.978,\n altitude: 0,\n accuracy: 10,\n altitudeAccuracy: 0,\n heading: 0,\n },\n timestamp: Date.now(),\n accessLocation: 'FINE',\n },\n\n // iPhone 15 Pro relay 실측값(devtools#190)과 정합: partner WebView portrait에서\n // SafeAreaInsets.get()이 반환한 top=54(토스 nav bar 높이), bottom=34(home indicator).\n // env(safe-area-inset-top)는 0이었으므로 OS 노치는 이 top에 들어가지 않는다.\n // preset이 'none'/'custom'이면 syncSafeAreaFromViewport가 건드리지 않으므로 이 값이\n // SafeAreaInsets.get()의 out-of-box 계약값으로 남는다. preset을 고르면 그 값으로 sync됨.\n safeAreaInsets: { top: 54, bottom: 34, left: 0, right: 0 },\n\n contacts: [\n { name: '홍길동', phoneNumber: '010-1234-5678' },\n { name: '김토스', phoneNumber: '010-9876-5432' },\n ],\n\n iap: {\n products: [\n {\n sku: 'mock-gem-100',\n type: 'CONSUMABLE',\n displayName: '보석 100개',\n displayAmount: '1,000원',\n iconUrl: '',\n description: '게임에서 사용할 수 있는 보석 100개',\n },\n ],\n nextResult: 'success',\n pendingOrders: [],\n completedOrders: [],\n },\n\n payment: {\n nextResult: 'success',\n failReason: '',\n },\n\n auth: {\n isLoggedIn: true,\n isTossLoginIntegrated: true,\n userKeyHash: 'mock-user-hash-abc123',\n anonymousKeyHash: 'mock-anon-hash-xyz789',\n },\n\n notification: {\n nextResult: 'newAgreement',\n },\n\n ads: {\n isLoaded: false,\n nextEvent: 'loaded',\n forceNoFill: false,\n lastEvent: null,\n rewardUnitType: 'coins',\n rewardAmount: 10,\n },\n\n game: {\n profile: { nickname: 'MockPlayer', profileImageUri: '' },\n leaderboardScores: [],\n },\n\n analyticsLog: [],\n\n sdkCallLog: [],\n\n deviceModes: {\n camera: 'mock',\n photos: 'mock',\n location: 'mock',\n network: 'mock',\n // 'mock' so the clipboard mock is self-contained. With 'web' the mock\n // calls `navigator.clipboard.readText()` directly, which — when paired\n // with `@ait-co/polyfill` — recurses: polyfill routes `navigator.clipboard`\n // back to the SDK's `getClipboardText`, which is this mock, which calls\n // `navigator.clipboard.readText`, … Users who want true browser\n // clipboard integration can flip this to 'web' from the panel.\n clipboard: 'mock',\n },\n\n mockData: {\n images: [],\n clipboardText: '',\n },\n\n panelEditable: true,\n\n viewport: {\n preset: 'none',\n orientation: 'auto',\n appOrientation: null,\n landscapeSide: 'left',\n customWidth: 402,\n customHeight: 874,\n frame: false,\n aitNavBar: true,\n aitNavBarType: 'partner',\n },\n};\n\nfunction generateDeviceId(): string {\n const stored = localStorage.getItem('__ait_device_id');\n if (stored) return stored;\n const id = crypto.randomUUID();\n localStorage.setItem('__ait_device_id', id);\n return id;\n}\n\nexport class AitStateManager {\n private _state: AitDevtoolsState;\n private _listeners = new Set<Listener>();\n private _inTransaction = false;\n\n constructor() {\n this._state = structuredClone(DEFAULT_STATE);\n try {\n this._state.deviceId = generateDeviceId();\n } catch {\n this._state.deviceId = `mock-device-${Math.random().toString(36).slice(2)}`;\n }\n }\n\n get state(): AitDevtoolsState {\n return this._state;\n }\n\n update(partial: Partial<AitDevtoolsState>) {\n this._state = { ...this._state, ...partial };\n this._notify();\n }\n\n /** 중첩 객체 업데이트용 */\n patch<K extends keyof AitDevtoolsState>(key: K, partial: Partial<AitDevtoolsState[K]>) {\n const current = this._state[key];\n if (typeof current === 'object' && current !== null && !Array.isArray(current)) {\n this._state = {\n ...this._state,\n [key]: { ...(current as Record<string, unknown>), ...(partial as Record<string, unknown>) },\n };\n } else {\n this._state = { ...this._state, [key]: partial as AitDevtoolsState[K] };\n }\n this._notify();\n }\n\n subscribe(listener: Listener): () => void {\n this._listeners.add(listener);\n return () => this._listeners.delete(listener);\n }\n\n /**\n * 한 묶음의 update/patch 호출을 묶어 listener notify 1회로 만든다.\n * preset 적용처럼 여러 슬라이스를 동시에 바꿀 때 panel re-render 폭주를\n * 방지한다. 중첩 호출은 outermost transaction이 끝날 때 한 번만 notify\n * (inner도 throw해도 outer finally가 flag를 복구한다).\n *\n * Rollback은 없다 — `fn`이 throw해도 그때까지의 state 변경은 유지된다.\n * 구독자가 partial state를 영원히 못 보는 사고를 막기 위해, throw 여부와\n * 무관하게 항상 한 번 notify한 뒤 throw를 propagate한다. DB transaction이\n * 아니라 \"여러 mutation을 한 notify로 묶는 batch\"라고 생각하면 된다.\n *\n * Listener는 throw해선 안 된다 — finally 안의 `_notify()`가 throw하면 원래\n * `fn`의 throw를 덮어버린다. 우리 구독자는 panel re-render뿐이라 실제\n * 발생 사례는 없지만, 외부에서 listener를 등록할 때 주의.\n */\n transaction(fn: () => void): void {\n if (this._inTransaction) {\n fn();\n return;\n }\n this._inTransaction = true;\n try {\n fn();\n } finally {\n this._inTransaction = false;\n this._notify();\n }\n }\n\n /** 분석 로그 추가 */\n logAnalytics(entry: Omit<AnalyticsLogEntry, 'timestamp'>) {\n this._state = {\n ...this._state,\n analyticsLog: [...this._state.analyticsLog, { ...entry, timestamp: Date.now() }],\n };\n this._notify();\n }\n\n /**\n * SDK 호출 로그 추가 (ring buffer, 상한 SDK_CALL_LOG_MAX).\n * `observe()`가 호출하고, proxy의 KNOWN_UNIMPLEMENTED 경로도 직접 호출한다.\n */\n logSdkCall(entry: AitSdkCall) {\n const log = this._state.sdkCallLog;\n const next = log.length >= SDK_CALL_LOG_MAX ? log.slice(1 - SDK_CALL_LOG_MAX) : log;\n this._state = { ...this._state, sdkCallLog: [...next, entry] };\n this._notify();\n }\n\n /** 이벤트 트리거 (backEvent, homeEvent 등) */\n trigger(event: string) {\n window.dispatchEvent(new CustomEvent(`__ait:${event}`));\n }\n\n reset() {\n const deviceId = this._state.deviceId;\n this._state = { ...structuredClone(DEFAULT_STATE), deviceId };\n this._notify();\n }\n\n private _notify() {\n if (this._inTransaction) return;\n for (const listener of this._listeners) {\n listener();\n }\n }\n}\n\n// `tsdown.config.ts`는 mock/panel/unplugin entry를 별도 config object로 빌드한다\n// (\"every entry is self-contained\"). 그 결과 소비자가 두 entry(예: `@ait-co/devtools` +\n// `@ait-co/devtools/panel`)를 동시에 import하면 `state.ts`가 entry별로 따로 번들되어\n// `AitStateManager` 인스턴스가 entry당 1개씩 만들어진다. panel이 toggle한 state는\n// mock SDK가 보는 state와 다른 인스턴스가 되어 모든 토글이 비기능이 된다.\n//\n// build pipeline을 건드리지 않고 runtime guard로 해결한다: globalThis에 인스턴스를\n// 캐시해 같은 페이지의 모든 entry가 동일 인스턴스를 공유하도록 한다.\nconst SINGLETON_KEY = '__aitDevtoolsStateSingleton__';\ntype GlobalWithSingleton = typeof globalThis & { [SINGLETON_KEY]?: AitStateManager };\nconst globalRef = globalThis as GlobalWithSingleton;\nif (!globalRef[SINGLETON_KEY]) {\n globalRef[SINGLETON_KEY] = new AitStateManager();\n}\nexport const aitState: AitStateManager = globalRef[SINGLETON_KEY]!;\n\n// 브라우저 콘솔에서 접근 가능하도록\nif (typeof window !== 'undefined') {\n window.__ait = aitState;\n}\n","/**\n * Consent toast UI — vanilla DOM, fixed bottom-right.\n *\n * Shows once per \"undecided + reprompt window cleared\" session.\n * Calls onAccept / onDeny callbacks; caller is responsible for persisting state.\n */\n\nimport { t } from '../i18n/index.js';\n\nconst TOAST_ID = '__ait-telemetry-toast';\n\nconst TOAST_STYLES = `\n #${TOAST_ID} {\n position: fixed;\n z-index: 100001;\n bottom: 80px;\n right: 16px;\n width: 280px;\n background: #1a1a2e;\n border: 1px solid #3a3a5a;\n border-radius: 10px;\n padding: 14px 16px;\n box-shadow: 0 4px 24px rgba(0,0,0,0.4);\n font-family: -apple-system, BlinkMacSystemFont, 'Pretendard', sans-serif;\n font-size: 13px;\n color: #e0e0e0;\n box-sizing: border-box;\n }\n #${TOAST_ID} .ait-toast-header {\n font-size: 13px;\n font-weight: 600;\n color: #e0e0e0;\n margin-bottom: 6px;\n }\n #${TOAST_ID} .ait-toast-body {\n font-size: 12px;\n color: #aaa;\n margin-bottom: 12px;\n line-height: 1.5;\n }\n #${TOAST_ID} .ait-toast-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n justify-content: flex-end;\n }\n #${TOAST_ID} .ait-toast-btn-primary {\n background: #3182F6;\n color: white;\n border: none;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 12px;\n cursor: pointer;\n font-family: inherit;\n }\n #${TOAST_ID} .ait-toast-btn-primary:hover { background: #1b6ef3; }\n #${TOAST_ID} .ait-toast-btn-secondary {\n background: #2a2a4a;\n color: #e0e0e0;\n border: 1px solid #3a3a5a;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 12px;\n cursor: pointer;\n font-family: inherit;\n }\n #${TOAST_ID} .ait-toast-btn-secondary:hover { background: #3a3a5a; }\n #${TOAST_ID} .ait-toast-link {\n font-size: 11px;\n color: #666;\n text-decoration: none;\n margin-right: auto;\n }\n #${TOAST_ID} .ait-toast-link:hover { color: #aaa; }\n`;\n\nfunction injectStyles(): void {\n if (document.getElementById(`${TOAST_ID}-style`)) return;\n const style = document.createElement('style');\n style.id = `${TOAST_ID}-style`;\n style.textContent = TOAST_STYLES;\n document.head.appendChild(style);\n}\n\nfunction removeToast(): void {\n document.getElementById(TOAST_ID)?.remove();\n document.getElementById(`${TOAST_ID}-style`)?.remove();\n}\n\nexport interface ConsentToastOptions {\n onAccept: () => void;\n onDeny: () => void;\n}\n\n/**\n * Renders and shows the consent toast.\n * If the toast is already visible, does nothing.\n */\nexport function showConsentToast({ onAccept, onDeny }: ConsentToastOptions): void {\n if (document.getElementById(TOAST_ID)) return;\n\n injectStyles();\n\n const toast = document.createElement('div');\n toast.id = TOAST_ID;\n\n const header = document.createElement('div');\n header.className = 'ait-toast-header';\n header.textContent = t('toast.consent.title');\n\n const body = document.createElement('div');\n body.className = 'ait-toast-body';\n body.textContent = t('toast.consent.body');\n\n const learnMore = document.createElement('a');\n learnMore.className = 'ait-toast-link';\n learnMore.href = 'https://docs.aitc.dev/privacy';\n learnMore.target = '_blank';\n learnMore.rel = 'noopener noreferrer';\n learnMore.textContent = t('toast.consent.learnMore');\n\n const yesBtn = document.createElement('button');\n yesBtn.className = 'ait-toast-btn-primary';\n yesBtn.textContent = t('toast.consent.accept');\n yesBtn.addEventListener('click', () => {\n removeToast();\n onAccept();\n });\n\n const noBtn = document.createElement('button');\n noBtn.className = 'ait-toast-btn-secondary';\n noBtn.textContent = t('toast.consent.deny');\n noBtn.addEventListener('click', () => {\n removeToast();\n onDeny();\n });\n\n const actions = document.createElement('div');\n actions.className = 'ait-toast-actions';\n actions.append(learnMore, noBtn, yesBtn);\n\n toast.append(header, body, actions);\n document.body.appendChild(toast);\n}\n\nexport { removeToast };\n","/**\n * Telemetry consent state machine + localStorage I/O.\n *\n * localStorage keys are LOCKED — do not rename without updating the privacy page.\n */\n\nexport type ConsentState = 'granted' | 'denied' | 'undecided';\n\n// Key names — locked per privacy page spec\nconst KEY_CONSENT = '__ait_telemetry:consent';\nconst KEY_REPROMPT_AFTER = '__ait_telemetry:reprompt_after';\nconst KEY_POLICY_VERSION = '__ait_telemetry:policy_version';\nconst KEY_ANON_ID = '__ait_telemetry:anon_id';\n\n// Tier 0 keys\nexport const KEY_T0_LAST_SENT = '__ait_telemetry:t0_last_sent';\nexport const KEY_T0_OFF = '__ait_telemetry:t0_off';\n\n// ---------------------------------------------------------------------------\n// Tier 0 opt-out helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if Tier 0 ping is enabled.\n * Disabled when `localStorage.__ait_telemetry:t0_off = '1'`\n * or `process.env.AITC_TELEMETRY === 'off'`.\n */\nexport function isTier0Enabled(): boolean {\n if (typeof process !== 'undefined' && process.env.AITC_TELEMETRY === 'off') return false;\n try {\n return localStorage.getItem(KEY_T0_OFF) !== '1';\n } catch {\n return true;\n }\n}\n\n/**\n * Sets or clears the Tier 0 opt-out marker.\n */\nexport function setTier0Enabled(enabled: boolean): void {\n try {\n if (enabled) {\n localStorage.removeItem(KEY_T0_OFF);\n } else {\n localStorage.setItem(KEY_T0_OFF, '1');\n }\n } catch {\n /* storage unavailable */\n }\n}\n\n/**\n * Returns true if Tier 0 has already been sent today (YYYY-MM-DD).\n */\nexport function hasSentTier0Today(): boolean {\n try {\n const stored = localStorage.getItem(KEY_T0_LAST_SENT);\n if (!stored) return false;\n const today = new Date().toISOString().slice(0, 10);\n return stored === today;\n } catch {\n return false;\n }\n}\n\n/**\n * Records that Tier 0 was sent today.\n */\nexport function markTier0Sent(): void {\n try {\n const today = new Date().toISOString().slice(0, 10);\n localStorage.setItem(KEY_T0_LAST_SENT, today);\n } catch {\n /* storage unavailable */\n }\n}\n\n/**\n * Current policy version. Bump this string whenever the privacy policy changes.\n * Users who previously granted on an older version will be re-prompted once.\n */\nexport const CURRENT_POLICY_VERSION = '2026-05-18';\n\n/** 30 days in milliseconds */\nconst THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;\n\n// ---------------------------------------------------------------------------\n// Reads\n// ---------------------------------------------------------------------------\n\nexport function readConsentState(): ConsentState {\n const raw = localStorage.getItem(KEY_CONSENT);\n if (raw === 'granted' || raw === 'denied') return raw;\n return 'undecided';\n}\n\nexport function readRepromptAfter(): number {\n const raw = localStorage.getItem(KEY_REPROMPT_AFTER);\n if (raw === null) return 0;\n const n = Number(raw);\n return Number.isFinite(n) ? n : 0;\n}\n\nexport function readPolicyVersion(): string | null {\n return localStorage.getItem(KEY_POLICY_VERSION);\n}\n\n/**\n * Returns the stored anon_id, or generates + persists a new UUID v4 on first call.\n * Once generated it is never overwritten.\n */\nexport function getOrCreateAnonId(): string {\n const existing = localStorage.getItem(KEY_ANON_ID);\n if (existing) return existing;\n const id = crypto.randomUUID();\n localStorage.setItem(KEY_ANON_ID, id);\n return id;\n}\n\n// ---------------------------------------------------------------------------\n// Writes / transitions\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve effective consent, handling the policy-version bump rule:\n * - If stored = \"granted\" but stored version ≠ CURRENT → revert to undecided\n * - If stored = \"denied\" and version changed → stay denied (no re-prompt)\n *\n * Call this at init time to normalise state before checking whether to show a toast.\n * Returns the effective ConsentState after applying the version-bump rule.\n */\nexport function resolveEffectiveConsent(): ConsentState {\n const raw = localStorage.getItem(KEY_CONSENT);\n if (raw === 'granted') {\n const storedVersion = readPolicyVersion();\n if (storedVersion !== CURRENT_POLICY_VERSION) {\n // Policy changed — treat as undecided so user gets re-prompted once\n localStorage.removeItem(KEY_CONSENT);\n localStorage.removeItem(KEY_POLICY_VERSION);\n return 'undecided';\n }\n return 'granted';\n }\n if (raw === 'denied') return 'denied';\n return 'undecided';\n}\n\n/**\n * User clicked \"Yes, send\".\n * Sets consent = granted, records policy version.\n */\nexport function acceptConsent(): void {\n localStorage.setItem(KEY_CONSENT, 'granted');\n localStorage.setItem(KEY_POLICY_VERSION, CURRENT_POLICY_VERSION);\n // Ensure reprompt_after is cleared (shouldn't matter, but keep state clean)\n localStorage.removeItem(KEY_REPROMPT_AFTER);\n}\n\n/**\n * User clicked \"No, thanks\".\n * First denial: sets reprompt_after = now + 30 days.\n * Second denial (reprompt_after was already set to a past finite value that triggered\n * re-prompt): sets reprompt_after = MAX_SAFE_INTEGER → permanent silence.\n */\nexport function denyConsent(): void {\n localStorage.setItem(KEY_CONSENT, 'denied');\n const existing = readRepromptAfter();\n if (existing > 0 && existing < Number.MAX_SAFE_INTEGER) {\n // This is the second denial — silence permanently\n localStorage.setItem(KEY_REPROMPT_AFTER, String(Number.MAX_SAFE_INTEGER));\n } else {\n // First denial\n localStorage.setItem(KEY_REPROMPT_AFTER, String(Date.now() + THIRTY_DAYS_MS));\n }\n}\n\n/**\n * Environment-tab toggle: free transition between granted/denied.\n * Does NOT touch reprompt_after.\n */\nexport function setConsentViaToggle(granted: boolean): void {\n if (granted) {\n localStorage.setItem(KEY_CONSENT, 'granted');\n localStorage.setItem(KEY_POLICY_VERSION, CURRENT_POLICY_VERSION);\n } else {\n localStorage.setItem(KEY_CONSENT, 'denied');\n }\n}\n\n/**\n * Returns true if the toast should be shown now.\n * Conditions:\n * - undecided (no prior choice or policy bumped to a newer version)\n * - denied + reprompt_after set + reprompt_after < now (one re-prompt after\n * the configured silence window; `denyConsent` flips to permanent silence\n * on the second denial by setting reprompt_after to MAX_SAFE_INTEGER).\n */\nexport function shouldShowToast(): boolean {\n const state = resolveEffectiveConsent();\n if (state === 'undecided') {\n const repromptAfter = readRepromptAfter();\n if (repromptAfter === 0) return true;\n return Date.now() > repromptAfter;\n }\n if (state === 'denied') {\n const repromptAfter = readRepromptAfter();\n if (repromptAfter === 0 || repromptAfter >= Number.MAX_SAFE_INTEGER) return false;\n return Date.now() > repromptAfter;\n }\n return false;\n}\n\n/**\n * Sends the DELETE request to remove the user's data from the server, and\n * rotates the local anon_id on success so any subsequent events are unlinkable\n * from the deleted history.\n */\nexport async function deleteMyData(endpoint: string): Promise<boolean> {\n const anonId = localStorage.getItem(KEY_ANON_ID);\n if (!anonId) return false;\n try {\n const res = await fetch(`${endpoint}/e?anon_id=${encodeURIComponent(anonId)}`, {\n method: 'DELETE',\n });\n if (!res.ok) return false;\n localStorage.setItem(KEY_ANON_ID, crypto.randomUUID());\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Telemetry send + retry.\n *\n * Rules:\n * 1. If consent ≠ \"granted\" — drop silently.\n * 2. POST event as JSON with 5 s timeout.\n * 3. On network error or non-2xx: retry ONCE after 2 s. On second failure: drop.\n * 4. console.debug on retry, development only (NODE_ENV !== \"production\").\n * 5. For \"session_duration\": use sendBeacon if available, fall back to fetch keepalive.\n *\n * Max meta size: 256 bytes (JSON-serialized). Over-size meta is dropped to undefined.\n */\n\nimport { TELEMETRY_ENDPOINT } from './index.js';\nimport { getOrCreateAnonId, readConsentState } from './state.js';\n\nexport type TelemetryEvent = 'panel_mount' | 'panel_open' | 'tab_view' | 'session_duration';\n\nexport interface EventPayload {\n tier: 1;\n source: 'devtools';\n event: TelemetryEvent;\n anon_id: string;\n version: string;\n ts: number;\n meta?: Record<string, unknown>;\n}\n\n/** Meta cap per server contract (JSON bytes). */\nconst META_BYTE_CAP = 256;\n\nfunction sanitizeMeta(\n meta: Record<string, unknown> | undefined,\n): Record<string, unknown> | undefined {\n if (meta === undefined) return undefined;\n const serialized = JSON.stringify(meta);\n if (new TextEncoder().encode(serialized).length > META_BYTE_CAP) {\n // Drop oversized meta rather than sending something the server will reject\n return undefined;\n }\n return meta;\n}\n\nasync function doFetch(payload: EventPayload): Promise<boolean> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 5_000);\n try {\n const res = await fetch(`${TELEMETRY_ENDPOINT}/e`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n return res.ok;\n } catch {\n return false;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Send a telemetry event. Drops silently if consent is not \"granted\".\n */\nexport async function send(\n event: TelemetryEvent,\n version: string,\n meta?: Record<string, unknown>,\n): Promise<void> {\n if (readConsentState() !== 'granted') return;\n\n const payload: EventPayload = {\n tier: 1,\n source: 'devtools',\n event,\n anon_id: getOrCreateAnonId(),\n version,\n ts: Date.now(),\n meta: sanitizeMeta(meta),\n };\n\n const ok = await doFetch(payload);\n if (ok) return;\n\n // Retry once after 2 s\n if (process.env.NODE_ENV !== 'production') {\n console.debug('[@ait-co/devtools] telemetry: retrying after failure', event);\n }\n await delay(2_000);\n await doFetch(payload);\n // Second failure → drop silently (no further action)\n}\n\n/**\n * Send the \"session_duration\" event via sendBeacon (unload-safe).\n * Falls back to fetch with keepalive if sendBeacon is unavailable.\n * No retry during page unload.\n */\nexport function sendBeaconEvent(\n event: 'session_duration',\n version: string,\n meta: Record<string, unknown>,\n): void {\n if (readConsentState() !== 'granted') return;\n\n const payload: EventPayload = {\n tier: 1,\n source: 'devtools',\n event,\n anon_id: getOrCreateAnonId(),\n version,\n ts: Date.now(),\n meta: sanitizeMeta(meta),\n };\n\n const body = JSON.stringify(payload);\n\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n navigator.sendBeacon(`${TELEMETRY_ENDPOINT}/e`, new Blob([body], { type: 'application/json' }));\n return;\n }\n\n // Fallback: fetch with keepalive (no retry — page is unloading)\n fetch(`${TELEMETRY_ENDPOINT}/e`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n keepalive: true,\n }).catch(() => {\n /* unload — nothing we can do */\n });\n}\n","/**\n * Tier 0 telemetry — opt-out, fire-and-forget daily ping.\n *\n * Payload: { tier: 0, source: 'devtools', ts: number, version: string }\n * No anon_id. No event name. No meta.\n *\n * Rules:\n * - Sent once per calendar day (localStorage daily marker).\n * - Skipped when __ait_telemetry:t0_off = '1' or AITC_TELEMETRY=off.\n * - 5 s timeout, no retry. Failure is silently dropped.\n */\n\nimport { TELEMETRY_ENDPOINT } from './index.js';\nimport { hasSentTier0Today, isTier0Enabled, markTier0Sent } from './state.js';\n\nexport interface Tier0Payload {\n tier: 0;\n source: 'devtools';\n ts: number;\n version: string;\n}\n\n/**\n * Sends the Tier 0 daily ping if eligible.\n * Returns true if a ping was sent, false if skipped or failed.\n */\nexport async function sendTier0Ping(version: string): Promise<boolean> {\n if (!isTier0Enabled()) return false;\n if (hasSentTier0Today()) return false;\n\n const payload: Tier0Payload = {\n tier: 0,\n source: 'devtools',\n ts: Date.now(),\n version,\n };\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 5_000);\n\n try {\n await fetch(`${TELEMETRY_ENDPOINT}/e`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n // Mark as sent regardless of server response code — avoids re-spam on 400s too.\n markTier0Sent();\n return true;\n } catch {\n // Network error or timeout — drop silently, do NOT mark as sent (retry tomorrow).\n return false;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n","/**\n * Telemetry client — internal to @ait-co/devtools.\n *\n * NOT exported from src/mock/index.ts — this is panel-internal only.\n *\n * Usage: import { telemetry } from './telemetry/index.js' (from panel code).\n */\n\nimport { showConsentToast } from './consent-toast.js';\nimport { send, sendBeaconEvent } from './send.js';\nimport {\n acceptConsent,\n denyConsent,\n getOrCreateAnonId,\n resolveEffectiveConsent,\n shouldShowToast,\n} from './state.js';\nimport { sendTier0Ping } from './tier0.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Telemetry ingest endpoint.\n * Overridable at build time via define (e.g., for e2e / local dev).\n * Do NOT expose this as a public env-var surface.\n */\nfunction readGlobalString(key: string): string | undefined {\n const val = (globalThis as Record<string, unknown>)[key];\n return typeof val === 'string' ? val : undefined;\n}\n\nexport const TELEMETRY_ENDPOINT: string =\n readGlobalString('__TELEMETRY_ENDPOINT__') ?? 'https://t.aitc.dev';\n\n// Version is injected by tsdown define (__VERSION__) as a compile-time text\n// substitution — same mechanism panel/index.ts uses for its header label.\n// It is NOT a runtime global, so a globalThis lookup would always miss.\nfunction getVersion(): string {\n return __VERSION__;\n}\n\n// ---------------------------------------------------------------------------\n// Session duration tracking\n// ---------------------------------------------------------------------------\n\nlet panelVisibleSince: number | null = null;\nlet accumulatedMs = 0;\nlet pagehideWired = false;\n\nfunction onPanelVisible(): void {\n if (panelVisibleSince === null) {\n panelVisibleSince = Date.now();\n }\n}\n\nfunction onPanelHidden(): void {\n if (panelVisibleSince !== null) {\n accumulatedMs += Date.now() - panelVisibleSince;\n panelVisibleSince = null;\n }\n}\n\nfunction wirePagehide(): void {\n if (pagehideWired) return;\n pagehideWired = true;\n\n // pagehide covers bfcache (Safari) and regular navigation. Preferred over beforeunload.\n window.addEventListener('pagehide', () => {\n onPanelHidden();\n if (accumulatedMs > 0) {\n sendBeaconEvent('session_duration', getVersion(), { ms: accumulatedMs });\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport type TabId = string;\n\n/**\n * Call once after panel mounts.\n * Handles: Tier 0 ping, consent check, optional toast, panel_mount event, pagehide wiring.\n */\nfunction init(): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n\n wirePagehide();\n\n // Tier 0: fire-and-forget daily ping (opt-out, no consent needed).\n void sendTier0Ping(getVersion());\n\n const effectiveConsent = resolveEffectiveConsent();\n\n if (effectiveConsent === 'granted') {\n // Ensure anon_id exists before firing\n getOrCreateAnonId();\n void send('panel_mount', getVersion());\n return;\n }\n\n if (shouldShowToast()) {\n const showToast = () => {\n showConsentToast({\n onAccept: () => {\n acceptConsent();\n getOrCreateAnonId();\n void send('panel_mount', getVersion());\n },\n onDeny: () => {\n denyConsent();\n },\n });\n };\n\n if (typeof requestIdleCallback === 'function') {\n requestIdleCallback(showToast, { timeout: 3_000 });\n } else {\n setTimeout(showToast, 1_500);\n }\n }\n}\n\n/**\n * Call when the panel is opened/toggled visible.\n */\nfunction onPanelOpen(): void {\n void send('panel_open', getVersion());\n onPanelVisible();\n}\n\n/**\n * Call when the panel is closed/hidden.\n */\nfunction onPanelClose(): void {\n onPanelHidden();\n}\n\n/**\n * Call when the user switches tabs.\n */\nfunction onTabView(tabId: TabId): void {\n void send('tab_view', getVersion(), { tab: tabId });\n}\n\nexport const telemetry = {\n init,\n onPanelOpen,\n onPanelClose,\n onTabView,\n} as const;\n","/**\n * 공통 DOM 헬퍼 함수\n */\n\nimport { t } from '../i18n/index.js';\n\nexport function h<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n attrs?: Record<string, string>,\n ...children: (string | Node)[]\n): HTMLElementTagNameMap[K] {\n const el = document.createElement(tag);\n if (attrs) {\n for (const [k, v] of Object.entries(attrs)) {\n if (k === 'className') el.className = v;\n else el.setAttribute(k, v);\n }\n }\n for (const child of children) {\n el.append(typeof child === 'string' ? document.createTextNode(child) : child);\n }\n return el;\n}\n\nexport function selectRow(\n label: string,\n options: string[],\n value: string,\n onChange: (v: string) => void,\n disabled = false,\n): HTMLElement {\n const select = h('select', { className: 'ait-select' });\n if (disabled) select.disabled = true;\n for (const opt of options) {\n const option = h('option', { value: opt }, opt);\n if (opt === value) option.selected = true;\n select.appendChild(option);\n }\n select.addEventListener('change', () => onChange(select.value));\n return h('div', { className: 'ait-row' }, h('label', {}, label), select);\n}\n\nexport function inputRow(\n label: string,\n value: string,\n onChange: (v: string) => void,\n disabled = false,\n): HTMLElement {\n const input = h('input', { className: 'ait-input', value });\n if (disabled) input.disabled = true;\n input.addEventListener('change', () => onChange(input.value));\n return h('div', { className: 'ait-row' }, h('label', {}, label), input);\n}\n\nexport function monitoringNotice(): HTMLElement {\n return h('div', { className: 'ait-monitoring-notice' }, t('common.readOnly'));\n}\n","/**\n * Floating Panel CSS (inline, 외부 의존성 없음)\n */\n\nexport const PANEL_WIDTH = 360;\nexport const PANEL_HEIGHT = 480;\nexport const PANEL_FULLSCREEN_BREAKPOINT = 720;\n\n// Viewport simulation frame styling\nexport const VIEWPORT_FRAME_BORDER_RADIUS = 36;\nexport const VIEWPORT_FRAME_BEZEL_INNER = 10; // first ring (outer device shell)\nexport const VIEWPORT_FRAME_BEZEL_OUTER = 12; // second ring (chrome highlight)\nexport const VIEWPORT_FRAME_BEZEL_COLOR_INNER = '#1a1a2e';\nexport const VIEWPORT_FRAME_BEZEL_COLOR_OUTER = '#3a3a5a';\nexport const VIEWPORT_BG_COLOR = '#0a0a14';\nexport const VIEWPORT_BODY_MARGIN = 24;\n// Status bar strip drawn above the WebView (body) when the frame is on. The OS\n// notch / Dynamic Island lives here, outside the WebView — matching the real\n// device where env(safe-area-inset-top) is 0 and the SDK top inset reports the\n// nav bar, not the notch. Tall enough to seat a Dynamic Island (37px) with margin.\nexport const VIEWPORT_STATUS_BAR_HEIGHT = 50;\n\nexport const PANEL_STYLES = /* css */ `\n .ait-panel-toggle {\n position: fixed;\n z-index: 99999;\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: #3182F6;\n border: none;\n cursor: pointer;\n box-shadow: 0 2px 12px rgba(0,0,0,0.2);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 20px;\n color: white;\n transition: transform 0.15s;\n font-family: -apple-system, BlinkMacSystemFont, sans-serif;\n touch-action: none;\n user-select: none;\n }\n .ait-panel-toggle:hover:not(.dragging) {\n transform: scale(1.1);\n }\n\n .ait-panel {\n position: fixed;\n z-index: 99998;\n width: ${PANEL_WIDTH}px;\n height: ${PANEL_HEIGHT}px;\n background: #1a1a2e;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.4);\n font-family: -apple-system, BlinkMacSystemFont, 'Pretendard', sans-serif;\n font-size: 13px;\n color: #e0e0e0;\n overflow: hidden;\n display: none;\n }\n .ait-panel.open {\n display: flex;\n flex-direction: column;\n }\n\n .ait-panel-header {\n padding: 12px 16px;\n background: #16213e;\n font-weight: 600;\n font-size: 14px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-bottom: 1px solid #2a2a4a;\n }\n .ait-panel-header > span:first-child {\n color: #3182F6;\n }\n\n .ait-panel-tabs {\n display: flex;\n background: #16213e;\n border-bottom: 1px solid #2a2a4a;\n overflow-x: auto;\n scrollbar-width: none;\n }\n .ait-panel-tabs::-webkit-scrollbar { display: none; }\n\n .ait-panel-tab {\n padding: 8px 12px;\n font-size: 12px;\n color: #888;\n cursor: pointer;\n white-space: nowrap;\n border-bottom: 2px solid transparent;\n background: none;\n border-top: none;\n border-left: none;\n border-right: none;\n font-family: inherit;\n }\n .ait-panel-tab:hover {\n color: #bbb;\n }\n .ait-panel-tab.active {\n color: #3182F6;\n border-bottom-color: #3182F6;\n }\n\n .ait-panel-body {\n padding: 12px 16px;\n overflow-y: auto;\n flex: 1;\n min-height: 0;\n }\n\n .ait-section {\n margin-bottom: 16px;\n }\n .ait-section-title {\n font-size: 11px;\n text-transform: uppercase;\n color: #666;\n margin-bottom: 8px;\n letter-spacing: 0.5px;\n }\n\n .ait-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .ait-row label {\n color: #aaa;\n font-size: 12px;\n }\n\n .ait-select {\n background: #2a2a4a;\n color: #e0e0e0;\n border: 1px solid #3a3a5a;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 12px;\n font-family: inherit;\n cursor: pointer;\n }\n\n .ait-input {\n background: #2a2a4a;\n color: #e0e0e0;\n border: 1px solid #3a3a5a;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 12px;\n width: 100px;\n font-family: inherit;\n }\n\n .ait-btn {\n background: #3182F6;\n color: white;\n border: none;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 12px;\n cursor: pointer;\n font-family: inherit;\n }\n .ait-btn:hover {\n background: #1b6ef3;\n }\n .ait-btn-sm {\n padding: 4px 8px;\n font-size: 11px;\n }\n .ait-btn-danger {\n background: #e74c3c;\n }\n .ait-btn-danger:hover {\n background: #c0392b;\n }\n\n .ait-log-entry {\n font-family: 'SF Mono', 'Menlo', monospace;\n font-size: 11px;\n padding: 3px 0;\n border-bottom: 1px solid #2a2a4a;\n color: #aaa;\n }\n .ait-log-entry .ait-log-type {\n color: #3182F6;\n font-weight: 600;\n margin-right: 6px;\n }\n .ait-log-entry .ait-log-time {\n color: #555;\n margin-right: 6px;\n }\n\n .ait-storage-row {\n font-family: 'SF Mono', 'Menlo', monospace;\n font-size: 11px;\n display: flex;\n gap: 8px;\n padding: 4px 0;\n border-bottom: 1px solid #2a2a4a;\n }\n .ait-storage-key {\n color: #e8a87c;\n min-width: 80px;\n word-break: break-all;\n }\n .ait-storage-value {\n color: #95e6cb;\n flex: 1;\n word-break: break-all;\n }\n\n /* Device tab */\n .ait-image-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 8px;\n }\n .ait-image-thumb {\n position: relative;\n width: 64px;\n height: 64px;\n border-radius: 4px;\n overflow: hidden;\n border: 1px solid #3a3a5a;\n }\n .ait-image-thumb img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n .ait-image-thumb .ait-image-remove {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: rgba(231,76,60,0.9);\n color: white;\n border: none;\n cursor: pointer;\n font-size: 10px;\n line-height: 18px;\n text-align: center;\n padding: 0;\n }\n .ait-btn-row {\n display: flex;\n gap: 6px;\n margin-top: 8px;\n }\n .ait-btn-secondary {\n background: #2a2a4a;\n color: #e0e0e0;\n border: 1px solid #3a3a5a;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 11px;\n cursor: pointer;\n font-family: inherit;\n }\n .ait-btn-secondary:hover {\n background: #3a3a5a;\n }\n\n /* Prompt notification */\n .ait-prompt-banner {\n background: #2d1b69;\n border: 1px solid #6c3bd5;\n border-radius: 6px;\n padding: 10px 12px;\n margin-bottom: 12px;\n }\n .ait-prompt-banner .ait-prompt-title {\n color: #b388ff;\n font-size: 12px;\n font-weight: 600;\n margin-bottom: 8px;\n }\n .ait-prompt-input-row {\n display: flex;\n gap: 6px;\n align-items: center;\n margin-top: 6px;\n }\n .ait-prompt-input-row input {\n background: #2a2a4a;\n color: #e0e0e0;\n border: 1px solid #3a3a5a;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 12px;\n width: 80px;\n font-family: inherit;\n }\n .ait-prompt-input-row label {\n color: #aaa;\n font-size: 11px;\n min-width: 30px;\n }\n\n .ait-panel-close {\n display: none;\n background: none;\n border: none;\n color: #888;\n font-size: 18px;\n cursor: pointer;\n padding: 0 4px;\n font-family: inherit;\n }\n .ait-panel-close:hover {\n color: #e0e0e0;\n }\n\n /* Disabled state for monitoring-only mode */\n .ait-select:disabled,\n .ait-input:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .ait-btn:disabled,\n .ait-btn-secondary:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .ait-btn-danger:disabled {\n background: #5a5a5a;\n }\n\n /* Mock status badge */\n .ait-mock-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.3px;\n cursor: pointer;\n }\n .ait-mock-badge-on {\n background: #1a4731;\n color: #4ade80;\n }\n .ait-mock-badge-off {\n background: #4a1a1a;\n color: #f87171;\n }\n\n /* Monitoring-only notice */\n .ait-monitoring-notice {\n background: #2a1a00;\n border: 1px solid #6b4c00;\n border-radius: 4px;\n padding: 6px 10px;\n margin-bottom: 12px;\n font-size: 11px;\n color: #fbbf24;\n }\n\n .ait-panel-tab-error {\n padding: 12px;\n color: #e53e3e; /* readable on both light (#fff) and dark (#1a1a2e) panel backgrounds */\n }\n\n /* Presets tab */\n .ait-preset-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 0;\n border-bottom: 1px solid #2a2a4a;\n }\n .ait-preset-row.ait-preset-active .ait-preset-label {\n color: #4ade80;\n font-weight: 600;\n }\n .ait-preset-label {\n font-size: 12px;\n color: #ddd;\n flex: 1;\n word-break: break-word;\n }\n .ait-preset-actions {\n display: flex;\n gap: 4px;\n flex-shrink: 0;\n }\n .ait-preset-description {\n font-size: 11px;\n color: #777;\n padding: 0 0 6px 4px;\n border-bottom: 1px solid #2a2a4a;\n margin-bottom: 0;\n }\n\n /* Viewport tab status rows */\n .ait-status-row {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n font-size: 11px;\n color: #888;\n padding: 3px 0;\n border-bottom: 1px dashed #2a2a4a;\n gap: 8px;\n }\n .ait-status-row:last-child { border-bottom: none; }\n .ait-status-row .ait-status-value {\n font-family: 'SF Mono', 'Menlo', monospace;\n color: #95e6cb;\n font-size: 11px;\n text-align: right;\n word-break: break-word;\n }\n\n /* === Viewport simulation === */\n /* Static rules. Dynamic per-preset values (width/height, navbar top offset)\n are still injected via a separate <style id=\"__ait-viewport-style\">. */\n html.ait-viewport-active {\n background: ${VIEWPORT_BG_COLOR};\n min-height: 100dvh;\n }\n html.ait-viewport-active body {\n position: relative;\n /* isolation: isolate creates a stacking context so notch/navbar z-index\n cannot escape body and paint over the floating Panel toggle. */\n isolation: isolate;\n margin: ${VIEWPORT_BODY_MARGIN}px auto;\n overflow: auto;\n background: #fff;\n box-sizing: border-box;\n }\n html.ait-viewport-framed body {\n border-radius: ${VIEWPORT_FRAME_BORDER_RADIUS}px;\n /* Reserve the status bar strip above the WebView so the notch sits outside\n the body (OS notch is outside the WebView; env top=0). */\n margin-top: ${VIEWPORT_BODY_MARGIN + VIEWPORT_STATUS_BAR_HEIGHT}px;\n box-shadow:\n 0 0 0 ${VIEWPORT_FRAME_BEZEL_INNER}px ${VIEWPORT_FRAME_BEZEL_COLOR_INNER},\n 0 0 0 ${VIEWPORT_FRAME_BEZEL_OUTER}px ${VIEWPORT_FRAME_BEZEL_COLOR_OUTER},\n 0 24px 48px rgba(0,0,0,0.5);\n }\n\n /* Notch / Dynamic Island / punch-hole — drawn in the status bar strip ABOVE\n the WebView (negative top puts it in the reserved margin), so it never\n overlaps the nav bar (which sits at the body's top edge). */\n .ait-notch {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n background: #000;\n z-index: 10;\n pointer-events: none;\n }\n .ait-notch-dynamic-island {\n top: -${VIEWPORT_STATUS_BAR_HEIGHT - 6}px;\n width: 126px; height: 37px; border-radius: 20px;\n }\n .ait-notch-pill {\n top: -${VIEWPORT_STATUS_BAR_HEIGHT}px;\n width: 160px; height: 30px;\n border-bottom-left-radius: 20px; border-bottom-right-radius: 20px;\n }\n .ait-notch-punch-hole {\n top: -${VIEWPORT_STATUS_BAR_HEIGHT - 10}px;\n width: 12px; height: 12px; border-radius: 50%;\n }\n\n /* Home indicator pill (bottom of body, iPhones with safe-area bottom > 0) */\n .ait-home-indicator {\n position: absolute;\n bottom: 8px;\n left: 50%;\n transform: translateX(-50%);\n width: 134px;\n height: 5px;\n border-radius: 3px;\n background: rgba(0, 0, 0, 0.85);\n z-index: 10;\n pointer-events: none;\n }\n\n /* Apps in Toss host nav bar — sits at the top of the WebView (body). The OS\n notch lives outside the WebView (env top=0), so the nav bar bottom is the\n content's top edge; applyViewport gives body padding-top = nav bar height\n so content starts exactly below it. */\n .ait-navbar {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 54px; /* AIT_NAV_BAR_HEIGHT_PARTNER (relay 실측) */\n background: rgba(255, 255, 255, 0.92);\n backdrop-filter: blur(8px);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 12px;\n box-sizing: border-box;\n font: 500 15px -apple-system, BlinkMacSystemFont, 'Pretendard', sans-serif;\n color: #1a1a1a;\n z-index: 10;\n }\n .ait-navbar-title {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1;\n margin-left: 4px;\n overflow: hidden;\n }\n .ait-navbar-icon {\n width: 22px;\n height: 22px;\n border-radius: 6px;\n background: linear-gradient(135deg, #3182f6, #7c3aed);\n flex-shrink: 0;\n }\n .ait-navbar-name {\n font-size: 15px;\n font-weight: 600;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .ait-navbar-actions {\n display: flex;\n align-items: center;\n background: rgba(0, 0, 0, 0.05);\n border-radius: 999px;\n padding: 4px 8px;\n gap: 4px;\n }\n .ait-navbar-btn {\n background: none;\n border: none;\n padding: 2px 8px;\n font: inherit;\n font-size: 18px;\n color: inherit;\n line-height: 1;\n cursor: pointer;\n }\n .ait-navbar-btn:hover { color: #3182f6; }\n .ait-navbar-back { padding: 0 8px; font-size: 24px; }\n .ait-navbar-divider { width: 1px; height: 16px; background: rgba(0, 0, 0, 0.15); }\n\n /* Game variant: 투명 배경, 우측 actions만 — 풀스크린 게임 캔버스를 가리지 않는다 */\n .ait-navbar.ait-navbar-game {\n background: transparent;\n backdrop-filter: none;\n justify-content: flex-end;\n color: #fff;\n }\n .ait-navbar.ait-navbar-game .ait-navbar-actions {\n background: rgba(0, 0, 0, 0.35);\n color: #fff;\n }\n .ait-navbar.ait-navbar-game .ait-navbar-divider {\n background: rgba(255, 255, 255, 0.3);\n }\n .ait-navbar.ait-navbar-game .ait-navbar-btn:hover { color: #8ab4ff; }\n\n @media (max-width: ${PANEL_FULLSCREEN_BREAKPOINT}px) {\n .ait-panel.open {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n max-height: 100%;\n border-radius: 0;\n }\n .ait-panel-toggle {\n z-index: 100000;\n }\n .ait-panel-close {\n display: block;\n }\n }\n`;\n","/**\n * 디바이스 모듈 내부 공유 헬퍼\n */\n\nimport { aitState } from '../state.js';\n\n// --- Placeholder Image Generator ---\n\nfunction generatePlaceholderImage(\n width: number,\n height: number,\n text: string,\n color: string,\n): string {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n // jsdom 등 Canvas API 미지원 환경에서는 간단한 SVG data URI 반환\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\"><rect fill=\"${color}\" width=\"${width}\" height=\"${height}\"/><text x=\"50%\" y=\"50%\" fill=\"white\" font-size=\"16\" text-anchor=\"middle\" dominant-baseline=\"middle\">${text}</text></svg>`;\n return `data:image/svg+xml;base64,${btoa(svg)}`;\n }\n ctx.fillStyle = color;\n ctx.fillRect(0, 0, width, height);\n ctx.fillStyle = 'white';\n ctx.font = '16px sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, width / 2, height / 2);\n return canvas.toDataURL('image/png');\n}\n\nconst DEFAULT_PLACEHOLDERS = [\n { text: 'Mock Photo 1', color: '#3182F6' },\n { text: 'Mock Photo 2', color: '#27ae60' },\n { text: 'Mock Photo 3', color: '#e67e22' },\n];\n\nlet cachedPlaceholders: string[] | null = null;\n\nexport function getDefaultPlaceholderImages(): string[] {\n if (!cachedPlaceholders) {\n cachedPlaceholders = DEFAULT_PLACEHOLDERS.map((p) =>\n generatePlaceholderImage(320, 240, p.text, p.color),\n );\n }\n return [...cachedPlaceholders];\n}\n\n/** @internal device 모듈 내부 전용 */\nexport function getMockImages(): string[] {\n const images = aitState.state.mockData.images;\n if (images.length > 0) return images;\n return getDefaultPlaceholderImages();\n}\n\n// --- Prompt Mode Helper ---\n\nconst PROMPT_TIMEOUT_MS = 30_000;\n\n/** @internal device 모듈 내부 전용 */\nexport function waitForPromptResponse<T>(type: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const eventName = `__ait:prompt-response:${type}`;\n const cancelName = '__ait:prompt-cancel';\n\n function cleanup() {\n clearTimeout(timer);\n window.removeEventListener(eventName, handler);\n window.removeEventListener(cancelName, cancelHandler);\n }\n\n const timer = setTimeout(() => {\n cleanup();\n const panelMounted = !!document.querySelector('.ait-panel');\n const hint = panelMounted\n ? 'Please provide input via the DevTools panel.'\n : 'Is @ait-co/devtools/panel imported?';\n reject(\n new Error(\n `[@ait-co/devtools] Prompt timeout for \"${type}\" after ${PROMPT_TIMEOUT_MS / 1000}s. ${hint}`,\n ),\n );\n }, PROMPT_TIMEOUT_MS);\n\n const handler = (e: Event) => {\n cleanup();\n resolve((e as CustomEvent).detail as T);\n };\n\n const cancelHandler = () => {\n cleanup();\n reject(new Error(`[@ait-co/devtools] Prompt cancelled for \"${type}\"`));\n };\n\n window.addEventListener(eventName, handler);\n window.addEventListener(cancelName, cancelHandler);\n window.dispatchEvent(new CustomEvent('__ait:prompt-request', { detail: { type } }));\n });\n}\n","/**\n * 권한 시스템 mock\n * 각 디바이스 API (.getPermission, .openPermissionDialog)에 부착된다.\n */\n\nimport { aitState } from './state.js';\nimport type { PermissionName, PermissionStatus } from './types.js';\n\nexport async function getPermission(name: PermissionName): Promise<PermissionStatus> {\n return aitState.state.permissions[name];\n}\n\nexport async function openPermissionDialog(name: PermissionName): Promise<'allowed' | 'denied'> {\n const current = aitState.state.permissions[name];\n if (current === 'allowed') return 'allowed';\n // notDetermined나 denied일 때 — Panel에서 설정된 값을 사용\n // 기본적으로는 allowed로 전환\n aitState.patch('permissions', { [name]: 'allowed' });\n return 'allowed';\n}\n\nexport async function requestPermission(permission: {\n name: PermissionName;\n access: string;\n}): Promise<'allowed' | 'denied'> {\n return openPermissionDialog(permission.name);\n}\n\n/** 권한이 필요한 함수에 .getPermission(), .openPermissionDialog()를 부착 */\nexport function withPermission<T extends (...args: never[]) => unknown>(\n fn: T,\n permissionName: PermissionName,\n): T & {\n getPermission: () => Promise<PermissionStatus>;\n openPermissionDialog: () => Promise<'allowed' | 'denied'>;\n} {\n const enhanced = fn as T & {\n getPermission: () => Promise<PermissionStatus>;\n openPermissionDialog: () => Promise<'allowed' | 'denied'>;\n };\n enhanced.getPermission = () => getPermission(permissionName);\n enhanced.openPermissionDialog = () => openPermissionDialog(permissionName);\n return enhanced;\n}\n\n/** 권한 체크 후 denied면 에러 throw */\nexport function checkPermission(name: PermissionName, fnName: string): void {\n const status = aitState.state.permissions[name];\n if (status === 'denied') {\n throw new Error(\n `[@ait-co/devtools] ${fnName}: Permission \"${name}\" is denied. Change it in the DevTools panel.`,\n );\n }\n}\n","/**\n * Camera & Album Photos & Album Items mock\n * mock/web/prompt 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\nimport { getMockImages, waitForPromptResponse } from './_helpers.js';\n\n// --- 타입 ---\n\nexport type AlbumItemType = 'PHOTO' | 'VIDEO';\n\n// --- Camera ---\n\nasync function openCameraMock(): Promise<{ id: string; dataUri: string }> {\n const images = getMockImages();\n return { id: crypto.randomUUID(), dataUri: images[0] };\n}\n\nasync function openCameraWeb(): Promise<{ id: string; dataUri: string }> {\n return new Promise((resolve, reject) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.capture = 'environment';\n let settled = false;\n input.onchange = () => {\n settled = true;\n const file = input.files?.[0];\n if (!file) {\n reject(new Error('No file selected'));\n return;\n }\n const reader = new FileReader();\n reader.onload = () => resolve({ id: crypto.randomUUID(), dataUri: reader.result as string });\n reader.onerror = () => reject(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n };\n // Detect file picker cancel via focus heuristic.\n // Note: unreliable on some mobile browsers and Safari where focus events differ.\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) reject(new Error('File picker cancelled'));\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function openCameraPrompt(): Promise<{ id: string; dataUri: string }> {\n const dataUri = await waitForPromptResponse<string>('camera');\n return { id: crypto.randomUUID(), dataUri };\n}\n\nconst _openCamera = async (_options?: {\n base64?: boolean;\n maxWidth?: number;\n}): Promise<{ id: string; dataUri: string }> => {\n checkPermission('camera', 'openCamera');\n const mode = aitState.state.deviceModes.camera;\n if (mode === 'web') return openCameraWeb();\n if (mode === 'prompt') return openCameraPrompt();\n return openCameraMock();\n};\nexport const openCamera = withPermission(_openCamera, 'camera');\n\n// --- Album Photos ---\n\nasync function fetchAlbumPhotosMock(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n const images = getMockImages();\n return images.slice(0, maxCount).map((dataUri) => ({ id: crypto.randomUUID(), dataUri }));\n}\n\nasync function fetchAlbumPhotosWeb(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n return new Promise((resolve, reject) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.multiple = true;\n let settled = false;\n input.onchange = async () => {\n settled = true;\n const files = Array.from(input.files ?? []).slice(0, maxCount);\n if (files.length === 0) {\n reject(new Error('No files selected'));\n return;\n }\n const results = await Promise.all(\n files.map(\n (file) =>\n new Promise<{ id: string; dataUri: string }>((res, rej) => {\n const reader = new FileReader();\n reader.onload = () =>\n res({ id: crypto.randomUUID(), dataUri: reader.result as string });\n reader.onerror = () => rej(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n }),\n ),\n );\n resolve(results);\n };\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) reject(new Error('File picker cancelled'));\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function fetchAlbumPhotosPrompt(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n const dataUris = await waitForPromptResponse<string[]>('photos');\n return dataUris.slice(0, maxCount).map((dataUri) => ({ id: crypto.randomUUID(), dataUri }));\n}\n\nconst _fetchAlbumPhotos = async (options?: {\n maxCount?: number;\n maxWidth?: number;\n base64?: boolean;\n}): Promise<Array<{ id: string; dataUri: string }>> => {\n checkPermission('photos', 'fetchAlbumPhotos');\n const maxCount = options?.maxCount ?? 10;\n const mode = aitState.state.deviceModes.photos;\n if (mode === 'web') return fetchAlbumPhotosWeb(maxCount);\n if (mode === 'prompt') return fetchAlbumPhotosPrompt(maxCount);\n return fetchAlbumPhotosMock(maxCount);\n};\nexport const fetchAlbumPhotos = withPermission(_fetchAlbumPhotos, 'photos');\n\n// --- Album Items (사진·동영상 복합 선택) ---\n\nexport interface FetchAlbumItemsOptions {\n types?: AlbumItemType[];\n maxCount?: number;\n maxWidth?: number;\n base64?: boolean;\n}\n\nexport interface AlbumItemResponse {\n id: string;\n dataUri: string;\n type: AlbumItemType;\n}\n\nasync function fetchAlbumItemsMock(\n maxCount: number,\n types: AlbumItemType[],\n): Promise<AlbumItemResponse[]> {\n const images = getMockImages();\n return images\n .slice(0, maxCount)\n .filter(() => types.includes('PHOTO'))\n .map((dataUri) => ({ id: crypto.randomUUID(), dataUri, type: 'PHOTO' as AlbumItemType }));\n}\n\nasync function fetchAlbumItemsWeb(\n maxCount: number,\n types: AlbumItemType[],\n): Promise<AlbumItemResponse[]> {\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = types.includes('VIDEO') ? 'image/*,video/*' : 'image/*';\n input.multiple = true;\n let settled = false;\n input.onchange = async () => {\n settled = true;\n const files = Array.from(input.files ?? []).slice(0, maxCount);\n if (files.length === 0) {\n resolve([]);\n return;\n }\n const results = await Promise.all(\n files.map(\n (file) =>\n new Promise<AlbumItemResponse>((res, rej) => {\n const itemType: AlbumItemType = file.type.startsWith('video/') ? 'VIDEO' : 'PHOTO';\n const reader = new FileReader();\n reader.onload = () =>\n res({ id: crypto.randomUUID(), dataUri: reader.result as string, type: itemType });\n reader.onerror = () => rej(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n }),\n ),\n );\n resolve(results);\n };\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) resolve([]);\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function fetchAlbumItemsPrompt(maxCount: number): Promise<AlbumItemResponse[]> {\n const dataUris = await waitForPromptResponse<string[]>('photos');\n return dataUris\n .slice(0, maxCount)\n .map((dataUri) => ({ id: crypto.randomUUID(), dataUri, type: 'PHOTO' as AlbumItemType }));\n}\n\nconst _fetchAlbumItems = async (options?: FetchAlbumItemsOptions): Promise<AlbumItemResponse[]> => {\n checkPermission('photos', 'fetchAlbumItems');\n const maxCount = options?.maxCount ?? 10;\n const types = options?.types ?? ['PHOTO'];\n const mode = aitState.state.deviceModes.photos;\n if (mode === 'web') return fetchAlbumItemsWeb(maxCount, types);\n if (mode === 'prompt') return fetchAlbumItemsPrompt(maxCount);\n return fetchAlbumItemsMock(maxCount, types);\n};\nexport const fetchAlbumItems = withPermission(_fetchAlbumItems, 'photos');\n","/**\n * Clipboard mock\n * mock/web 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\n\nconst _getClipboardText = async (): Promise<string> => {\n checkPermission('clipboard', 'getClipboardText');\n const mode = aitState.state.deviceModes.clipboard;\n if (mode === 'mock') return aitState.state.mockData.clipboardText;\n // web mode (default)\n try {\n return await navigator.clipboard.readText();\n } catch {\n return '';\n }\n};\nexport const getClipboardText = withPermission(_getClipboardText, 'clipboard');\n\nconst _setClipboardText = async (text: string): Promise<void> => {\n checkPermission('clipboard', 'setClipboardText');\n const mode = aitState.state.deviceModes.clipboard;\n if (mode === 'mock') {\n aitState.patch('mockData', { clipboardText: text });\n return;\n }\n // web mode (default)\n await navigator.clipboard.writeText(text);\n};\nexport const setClipboardText = withPermission(_setClipboardText, 'clipboard');\n","/**\n * Contacts mock\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\n\nconst _fetchContacts = async (options: {\n size: number;\n offset: number;\n query?: { contains?: string };\n}) => {\n checkPermission('contacts', 'fetchContacts');\n let contacts = aitState.state.contacts;\n if (options.query?.contains) {\n const q = options.query.contains.toLowerCase();\n contacts = contacts.filter(\n (c) => c.name.toLowerCase().includes(q) || c.phoneNumber.includes(q),\n );\n }\n const sliced = contacts.slice(options.offset, options.offset + options.size);\n const nextOffset = options.offset + options.size;\n return {\n result: sliced,\n nextOffset: nextOffset < contacts.length ? nextOffset : null,\n done: nextOffset >= contacts.length,\n };\n};\nexport const fetchContacts = withPermission(_fetchContacts, 'contacts');\n","/**\n * Haptic Feedback & saveBase64Data mock\n *\n * generateHapticFeedback — 영역 3 (하드웨어 API 관측):\n * - 10종 HapticFeedbackType을 navigator.vibrate 패턴으로 매핑(근사, best-effort).\n * - `typeof navigator.vibrate === 'function'` 가드 — API 없는 환경에서 throw 없이 skip.\n * - sdkCallLog에 🟡(partial)로 기록. params: { hapticType, vibrated: boolean }.\n * - 시그니처 불변 — __typecheck.ts의 Assert<Mock, Original> 통과.\n */\n\nimport { aitState } from '../state.js';\nimport type { HapticFeedbackType } from '../types.js';\n\n/**\n * HapticFeedbackType 10종 → navigator.vibrate 패턴 매핑.\n * 숫자: 진동 ms. 배열: [진동, 정지, 진동, …] 교대 패턴.\n */\nexport const HAPTIC_VIBRATE_PATTERN: Record<HapticFeedbackType, VibratePattern> = {\n tickWeak: 10,\n tap: 20,\n tickMedium: 30,\n softMedium: 40,\n basicWeak: 15,\n basicMedium: 50,\n success: [10, 40, 10],\n error: [40, 30, 40],\n wiggle: [20, 20, 20, 20, 20],\n confetti: [10, 20, 10, 20, 10, 20, 10],\n};\n\nexport async function generateHapticFeedback(options: { type: HapticFeedbackType }): Promise<void> {\n const timestamp = Date.now();\n aitState.logAnalytics({ type: 'haptic', params: { hapticType: options.type } });\n\n const pattern = HAPTIC_VIBRATE_PATTERN[options.type] ?? 30;\n const vibrated = typeof navigator.vibrate === 'function' ? navigator.vibrate(pattern) : false;\n\n aitState.logSdkCall({\n method: 'generateHapticFeedback',\n args: [{ type: options.type }],\n timestamp,\n status: 'resolved',\n result: { hapticType: options.type, vibrated },\n fidelity: 'partial',\n });\n}\n\nexport async function saveBase64Data(params: {\n data: string;\n fileName: string;\n mimeType: string;\n}): Promise<void> {\n const a = document.createElement('a');\n a.href = `data:${params.mimeType};base64,${params.data}`;\n a.download = params.fileName;\n a.click();\n}\n","/**\n * Location mock (getCurrentLocation, startUpdateLocation)\n * mock/web/prompt 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\nimport type { MockLocation } from '../types.js';\nimport { waitForPromptResponse } from './_helpers.js';\n\nenum Accuracy {\n Lowest = 1,\n Low = 2,\n Balanced = 3,\n High = 4,\n Highest = 5,\n BestForNavigation = 6,\n}\n\nexport { Accuracy };\n\nfunction buildLocation(): MockLocation {\n return {\n coords: { ...aitState.state.location.coords },\n timestamp: Date.now(),\n accessLocation: aitState.state.location.accessLocation,\n };\n}\n\n// -- getCurrentLocation --\n\nasync function getCurrentLocationMock(): Promise<MockLocation> {\n return buildLocation();\n}\n\nasync function getCurrentLocationWeb(): Promise<MockLocation> {\n return new Promise((resolve) => {\n if (!navigator.geolocation) {\n console.warn('[@ait-co/devtools] Geolocation API not available, falling back to mock');\n resolve(buildLocation());\n return;\n }\n navigator.geolocation.getCurrentPosition(\n (pos) => {\n resolve({\n coords: {\n latitude: pos.coords.latitude,\n longitude: pos.coords.longitude,\n altitude: pos.coords.altitude ?? 0,\n accuracy: pos.coords.accuracy,\n altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,\n heading: pos.coords.heading ?? 0,\n },\n timestamp: pos.timestamp,\n accessLocation: 'FINE',\n });\n },\n () => {\n console.warn('[@ait-co/devtools] Geolocation failed, falling back to mock');\n resolve(buildLocation());\n },\n );\n });\n}\n\nasync function getCurrentLocationPrompt(): Promise<MockLocation> {\n return waitForPromptResponse<MockLocation>('location');\n}\n\nconst _getCurrentLocation = async (_options?: { accuracy: Accuracy }): Promise<MockLocation> => {\n checkPermission('geolocation', 'getCurrentLocation');\n const mode = aitState.state.deviceModes.location;\n if (mode === 'web') return getCurrentLocationWeb();\n if (mode === 'prompt') return getCurrentLocationPrompt();\n return getCurrentLocationMock();\n};\nexport const getCurrentLocation = withPermission(_getCurrentLocation, 'geolocation');\n\n// -- startUpdateLocation --\n\ninterface StartUpdateLocationEventParams {\n onEvent: (response: MockLocation) => void;\n onError: (error: unknown) => void;\n options: { accuracy: Accuracy; timeInterval: number; distanceInterval: number };\n}\n\nfunction startUpdateLocationMock(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent, options } = eventParams;\n const interval = Math.max(options.timeInterval, 500);\n const id = setInterval(() => {\n const loc = buildLocation();\n loc.coords.latitude += (Math.random() - 0.5) * 0.0001;\n loc.coords.longitude += (Math.random() - 0.5) * 0.0001;\n onEvent(loc);\n }, interval);\n return () => clearInterval(id);\n}\n\nfunction startUpdateLocationWeb(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent, onError } = eventParams;\n if (!navigator.geolocation) {\n console.warn('[@ait-co/devtools] Geolocation API not available, falling back to mock');\n return startUpdateLocationMock(eventParams);\n }\n const watchId = navigator.geolocation.watchPosition(\n (pos) => {\n onEvent({\n coords: {\n latitude: pos.coords.latitude,\n longitude: pos.coords.longitude,\n altitude: pos.coords.altitude ?? 0,\n accuracy: pos.coords.accuracy,\n altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,\n heading: pos.coords.heading ?? 0,\n },\n timestamp: pos.timestamp,\n accessLocation: 'FINE',\n });\n },\n (err) => onError(err),\n );\n return () => navigator.geolocation.clearWatch(watchId);\n}\n\nfunction startUpdateLocationPrompt(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent } = eventParams;\n const handler = (e: Event) => {\n onEvent((e as CustomEvent).detail as MockLocation);\n };\n window.addEventListener('__ait:prompt-response:location-update', handler);\n window.dispatchEvent(\n new CustomEvent('__ait:prompt-request', { detail: { type: 'location-update' } }),\n );\n return () => window.removeEventListener('__ait:prompt-response:location-update', handler);\n}\n\nconst _startUpdateLocation = (eventParams: StartUpdateLocationEventParams): (() => void) => {\n const mode = aitState.state.deviceModes.location;\n if (mode === 'web') return startUpdateLocationWeb(eventParams);\n if (mode === 'prompt') return startUpdateLocationPrompt(eventParams);\n return startUpdateLocationMock(eventParams);\n};\nexport const startUpdateLocation = withPermission(_startUpdateLocation, 'geolocation');\n","/**\n * 미구현 API용 Proxy 트립와이어.\n *\n * 미구현 프로퍼티에 접근하면 throw한다. 이는 \"devtools에서는 멀쩡히 돌지만\n * 실 SDK에선 실제로 동작하는\" 시나리오를 차단하기 위한 의도적 선택이다.\n * mock이 미구현인 API는 실 SDK에서는 존재할 수 있고, 사용자가 이를 인지하지\n * 못한 채 개발을 이어가면 배포 시점에 놀라게 된다. 에러 메시지에 이슈 URL을\n * 포함해 사용자가 mock 누락을 제보할 수 있게 한다.\n *\n * ## KNOWN_UNIMPLEMENTED 정책\n * SDK에 존재하는 것으로 알려져 있으나 현재 mock이 없는 API 이름만 이 집합에 둔다.\n * 이 경우에만 throw 대신 🔴 inert no-op을 반환하고 sdkCallLog에 기록한다.\n * 완전히 미지의 이름은 여전히 throw — \"잘 되는 척\" 방지.\n */\n\nimport { aitState } from './state.js';\n\nconst ISSUES_URL = 'https://github.com/apps-in-toss-community/devtools/issues';\n\n/**\n * SDK에 존재하나 mock이 아직 없는 것으로 확인된 이름 목록.\n * 새 API가 SDK에 추가되면 여기에 추가하고 별도 PR에서 mock 구현으로 이동한다.\n * 확인되지 않은 이름은 절대 여기에 추가하지 않는다 — throw가 더 안전하다.\n */\nconst KNOWN_UNIMPLEMENTED = new Set<string>([\n // 예: 'someNewSdkApi',\n]);\n\nexport function createMockProxy<T extends Record<string, unknown>>(\n moduleName: string,\n implementations: T,\n): T {\n return new Proxy(implementations, {\n get(target, prop) {\n // 심볼 접근(Symbol.toPrimitive, Symbol.iterator 등)은 프레임워크/런타임이\n // 내부적으로 호출하므로 throw하면 console.log, 구조분해 등이 깨진다.\n if (typeof prop === 'symbol') return undefined;\n if (prop in target) return target[prop];\n\n const name = String(prop);\n\n // SDK에 존재하나 mock 미구현으로 확인된 API — throw 대신 🔴 inert no-op 반환.\n if (KNOWN_UNIMPLEMENTED.has(name)) {\n return (...args: unknown[]): undefined => {\n console.warn(\n `[@ait-co/devtools] ${moduleName}.${name} is known-unimplemented (🔴 inert). ` +\n `Returning undefined. Please file or upvote an issue: ${ISSUES_URL}`,\n );\n aitState.logSdkCall({\n method: `${moduleName}.${name}`,\n args: args,\n timestamp: Date.now(),\n status: 'resolved',\n result: undefined,\n fidelity: 'inert',\n });\n return undefined;\n };\n }\n\n throw new Error(\n `[@ait-co/devtools] ${moduleName}.${prop} is not mocked. ` +\n `This API may exist in @apps-in-toss/web-framework, ` +\n `but devtools' mock does not cover it yet. ` +\n `Please file an issue: ${ISSUES_URL}`,\n );\n },\n }) as T;\n}\n","/**\n * Storage mock\n * localStorage에 `__ait_storage:` prefix로 저장하여 앱 자체 localStorage와 분리\n */\n\nimport { createMockProxy } from '../proxy.js';\n\nexport const Storage = createMockProxy('Storage', {\n getItem: async (key: string): Promise<string | null> => {\n return localStorage.getItem(`__ait_storage:${key}`);\n },\n setItem: async (key: string, value: string): Promise<void> => {\n localStorage.setItem(`__ait_storage:${key}`, value);\n },\n removeItem: async (key: string): Promise<void> => {\n localStorage.removeItem(`__ait_storage:${key}`);\n },\n clearItems: async (): Promise<void> => {\n const keys = Object.keys(localStorage).filter((k) => k.startsWith('__ait_storage:'));\n for (const k of keys) {\n localStorage.removeItem(k);\n }\n },\n});\n","import { type StringKey, t } from '../../i18n/index.js';\nimport {\n generateHapticFeedback,\n getDefaultPlaceholderImages,\n HAPTIC_VIBRATE_PATTERN,\n} from '../../mock/device/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type { HapticFeedbackType } from '../../mock/types.js';\nimport { h, monitoringNotice, selectRow } from '../helpers.js';\n\n// --- Prompt mode state ---\ninterface PendingPrompt {\n type: string;\n}\nlet pendingPrompt: PendingPrompt | null = null;\n\nlet refreshPanel: () => void = () => {};\n\nexport function setDeviceRefreshPanel(fn: () => void) {\n refreshPanel = fn;\n}\n\n// Listen for prompt requests from device APIs\nif (typeof window !== 'undefined') {\n window.addEventListener('__ait:prompt-request', (e: Event) => {\n const detail = (e as CustomEvent).detail as { type: string };\n pendingPrompt = { type: detail.type };\n // Auto-switch to device tab and open panel — handled by index.ts listener which also calls refreshPanel\n window.dispatchEvent(new CustomEvent('__ait:panel-switch-tab', { detail: { tab: 'device' } }));\n });\n}\n\nfunction resolvePrompt(type: string, data: unknown) {\n window.dispatchEvent(new CustomEvent(`__ait:prompt-response:${type}`, { detail: data }));\n pendingPrompt = null;\n refreshPanel();\n}\n\nfunction renderPromptBanner(): HTMLElement | null {\n if (!pendingPrompt) return null;\n\n const banner = h('div', { className: 'ait-prompt-banner' });\n\n if (pendingPrompt.type === 'camera') {\n banner.append(h('div', { className: 'ait-prompt-title' }, t('device.prompt.camera.title')));\n const input = h('input', {\n type: 'file',\n accept: 'image/*',\n style: 'font-size:11px;color:#aaa',\n });\n input.addEventListener('change', () => {\n const file = (input as HTMLInputElement).files?.[0];\n if (!file) return;\n const reader = new FileReader();\n reader.onload = () => resolvePrompt('camera', reader.result as string);\n reader.readAsDataURL(file);\n });\n banner.appendChild(input);\n } else if (pendingPrompt.type === 'photos') {\n banner.append(h('div', { className: 'ait-prompt-title' }, t('device.prompt.photos.title')));\n const input = h('input', {\n type: 'file',\n accept: 'image/*',\n multiple: '',\n style: 'font-size:11px;color:#aaa',\n });\n input.addEventListener('change', () => {\n const files = Array.from((input as HTMLInputElement).files ?? []);\n if (files.length === 0) return;\n Promise.all(\n files.map(\n (file) =>\n new Promise<string>((res) => {\n const reader = new FileReader();\n reader.onload = () => res(reader.result as string);\n reader.readAsDataURL(file);\n }),\n ),\n ).then((dataUris) => resolvePrompt('photos', dataUris));\n });\n banner.appendChild(input);\n } else if (pendingPrompt.type === 'location' || pendingPrompt.type === 'location-update') {\n banner.append(\n h(\n 'div',\n { className: 'ait-prompt-title' },\n pendingPrompt.type === 'location'\n ? t('device.prompt.location.title')\n : t('device.prompt.locationUpdate.title'),\n ),\n );\n const latInput = h('input', {\n className: 'ait-input',\n value: String(aitState.state.location.coords.latitude),\n style: 'width:80px',\n });\n const lngInput = h('input', {\n className: 'ait-input',\n value: String(aitState.state.location.coords.longitude),\n style: 'width:80px',\n });\n const sendBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('device.prompt.send'));\n sendBtn.addEventListener('click', () => {\n const loc = {\n coords: {\n latitude: Number((latInput as HTMLInputElement).value),\n longitude: Number((lngInput as HTMLInputElement).value),\n altitude: 0,\n accuracy: 10,\n altitudeAccuracy: 0,\n heading: 0,\n },\n timestamp: Date.now(),\n accessLocation: 'FINE' as const,\n };\n resolvePrompt(pendingPrompt!.type, loc);\n });\n banner.append(\n h(\n 'div',\n { className: 'ait-prompt-input-row' },\n h('label', {}, t('device.prompt.label.lat')),\n latInput,\n h('label', {}, t('device.prompt.label.lng')),\n lngInput,\n sendBtn,\n ),\n );\n } else {\n // Fallback for unknown prompt types\n banner.append(\n h(\n 'div',\n { className: 'ait-prompt-title' },\n t('device.prompt.fallbackTitle', { type: pendingPrompt.type }),\n ),\n );\n }\n\n // Cancel button for all prompt types\n const cancelBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger', style: 'margin-top:8px' },\n t('device.prompt.cancel'),\n );\n cancelBtn.addEventListener('click', () => {\n pendingPrompt = null;\n window.dispatchEvent(new CustomEvent('__ait:prompt-cancel'));\n refreshPanel();\n });\n banner.appendChild(cancelBtn);\n\n return banner;\n}\n\nexport function renderDeviceTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n // Prompt banner (if active, only when panelEditable)\n if (s.panelEditable) {\n const promptBanner = renderPromptBanner();\n if (promptBanner) container.appendChild(promptBanner);\n }\n\n // Device API Mode selectors\n const modeEntries: Array<{\n labelKey: StringKey;\n key: keyof typeof s.deviceModes;\n options: string[];\n }> = [\n { labelKey: 'device.row.camera', key: 'camera', options: ['mock', 'web', 'prompt'] },\n { labelKey: 'device.row.photos', key: 'photos', options: ['mock', 'web', 'prompt'] },\n { labelKey: 'device.row.location', key: 'location', options: ['mock', 'web', 'prompt'] },\n { labelKey: 'device.row.network', key: 'network', options: ['mock', 'web'] },\n { labelKey: 'device.row.clipboard', key: 'clipboard', options: ['mock', 'web'] },\n ];\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('device.section.modes')),\n ...modeEntries.map((entry) =>\n selectRow(\n t(entry.labelKey),\n entry.options,\n s.deviceModes[entry.key],\n (v) => {\n aitState.patch('deviceModes', { [entry.key]: v } as Partial<typeof s.deviceModes>);\n },\n disabled,\n ),\n ),\n ),\n );\n\n // Mock Images management\n const images = s.mockData.images;\n const imageGrid = h('div', { className: 'ait-image-grid' });\n images.forEach((dataUri, idx) => {\n const thumb = h('div', { className: 'ait-image-thumb' });\n const img = h('img', { src: dataUri });\n const removeBtn = h('button', { className: 'ait-image-remove' }, 'x');\n removeBtn.addEventListener('click', () => {\n const newImages = [...aitState.state.mockData.images];\n newImages.splice(idx, 1);\n aitState.patch('mockData', { images: newImages });\n });\n if (disabled) removeBtn.disabled = true;\n thumb.append(img, removeBtn);\n imageGrid.appendChild(thumb);\n });\n\n const addBtn = h('button', { className: 'ait-btn-secondary' }, t('device.btn.add'));\n addBtn.addEventListener('click', () => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.multiple = true;\n input.onchange = () => {\n const files = Array.from(input.files ?? []);\n Promise.all(\n files.map(\n (file) =>\n new Promise<string>((res) => {\n const reader = new FileReader();\n reader.onload = () => res(reader.result as string);\n reader.readAsDataURL(file);\n }),\n ),\n ).then((dataUris) => {\n aitState.patch('mockData', { images: [...aitState.state.mockData.images, ...dataUris] });\n });\n };\n input.click();\n });\n if (disabled) addBtn.disabled = true;\n\n const defaultsBtn = h('button', { className: 'ait-btn-secondary' }, t('device.btn.useDefaults'));\n defaultsBtn.addEventListener('click', () => {\n aitState.patch('mockData', { images: [...getDefaultPlaceholderImages()] });\n });\n if (disabled) defaultsBtn.disabled = true;\n\n const clearImagesBtn = h('button', { className: 'ait-btn-secondary' }, t('device.btn.clear'));\n clearImagesBtn.addEventListener('click', () => {\n aitState.patch('mockData', { images: [] });\n });\n if (disabled) clearImagesBtn.disabled = true;\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('device.section.mockImages', { count: images.length }),\n ),\n imageGrid,\n h('div', { className: 'ait-btn-row' }, addBtn, defaultsBtn, clearImagesBtn),\n ),\n );\n\n // Haptic section — sdkCallLog에서 마지막 haptic 호출을 읽어 표시한다\n container.appendChild(renderHapticSection());\n\n return container;\n}\n\nfunction renderHapticSection(): HTMLElement {\n // 마지막 generateHapticFeedback 호출을 sdkCallLog에서 찾는다.\n const log = aitState.state.sdkCallLog;\n const lastEntry = [...log].reverse().find((e) => e.method === 'generateHapticFeedback');\n const lastResult = lastEntry?.result as { hapticType: string; vibrated: boolean } | undefined;\n\n const lastCallValue = lastResult\n ? `${lastResult.hapticType} (vibrated: ${String(lastResult.vibrated)})`\n : t('device.haptic.noneYet');\n\n const lastCallRow = h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('device.haptic.lastCall')),\n h('span', { className: 'ait-value' }, lastCallValue),\n );\n\n const hapticTypes = Object.keys(HAPTIC_VIBRATE_PATTERN) as HapticFeedbackType[];\n const triggerSection = h(\n 'div',\n { className: 'ait-section-subtitle' },\n t('device.haptic.trigger'),\n );\n const btnRow = h(\n 'div',\n { className: 'ait-btn-row' },\n ...hapticTypes.map((type) => {\n const btn = h(\n 'button',\n { className: 'ait-btn-secondary', 'data-testid': `haptic-${type}-btn` },\n type,\n );\n btn.addEventListener('click', () => {\n void generateHapticFeedback({ type }).then(() => {\n refreshPanel();\n });\n });\n return btn;\n }),\n );\n\n return h(\n 'div',\n { className: 'ait-section', 'data-testid': 'section-haptic' },\n h('div', { className: 'ait-section-title' }, t('device.section.haptic')),\n lastCallRow,\n triggerSection,\n btnRow,\n );\n}\n","/**\n * SDK 호출 관측 래퍼.\n *\n * `observe(apiName, fidelity, fn)` — fn의 시그니처를 그대로 보존하면서\n * 호출 시 args·resolve·reject 결과를 `aitState.sdkCallLog`에 기록한다.\n *\n * **signature 보존 절대 조건**: 제네릭 pass-through로 `__typecheck.ts`의\n * `Assert<Mock, Original>` 불변을 유지한다. observe()로 감싼 함수는\n * 원본과 동일한 타입을 가진다.\n */\n\nimport type { AitSdkCallFidelity } from '../mcp/ait-source.js';\nimport { aitState } from './state.js';\n\n/**\n * fn을 observe로 감싼다.\n *\n * @param apiName - 로그에 기록할 SDK 메서드 이름 (예: `'setScreenAwakeMode'`)\n * @param fidelity - 이 mock의 fidelity grade ('faithful' | 'partial' | 'inert')\n * @param fn - 실제 mock 구현체. 시그니처를 그대로 통과시킨다.\n * @returns fn과 동일한 타입의 래퍼 함수\n */\nexport function observe<TArgs extends unknown[], TReturn>(\n apiName: string,\n fidelity: AitSdkCallFidelity,\n fn: (...args: TArgs) => TReturn,\n): (...args: TArgs) => TReturn {\n return (...args: TArgs): TReturn => {\n const timestamp = Date.now();\n // args를 JSON-safe하게 직렬화한다. 직렬화할 수 없는 값(함수·순환 참조 등)은\n // 문자열 표현으로 대체해 로그가 깨지지 않도록 한다.\n const safeArgs: unknown[] = args.map((a) => safeSerialize(a));\n\n const result = fn(...args);\n\n if (result instanceof Promise) {\n // pending 상태로 먼저 기록\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'pending',\n fidelity,\n });\n\n // resolve/reject 결과로 업데이트 (ring buffer에서 마지막 pending 항목 덮어쓰기)\n (result as Promise<unknown>).then(\n (value) => {\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'resolved',\n result: safeSerialize(value),\n fidelity,\n });\n },\n (err: unknown) => {\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'rejected',\n error: err instanceof Error ? err.message : String(err),\n fidelity,\n });\n },\n );\n\n return result;\n }\n\n // 동기 반환 — 즉시 resolved로 기록\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'resolved',\n result: safeSerialize(result),\n fidelity,\n });\n\n return result;\n };\n}\n\n/**\n * 값을 JSON-safe한 형태로 변환한다.\n * - null / primitive — 그대로.\n * - 함수 — `'[Function: name]'` 문자열.\n * - 기타 객체 — JSON.stringify 실패 시 `'[unserializable]'`.\n */\nfunction safeSerialize(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value === 'function') return `[Function: ${value.name || 'anonymous'}]`;\n if (typeof value !== 'object') return value;\n try {\n return JSON.parse(JSON.stringify(value));\n } catch {\n return '[unserializable]';\n }\n}\n","/**\n * 광고 mock (GoogleAdMob, TossAds, FullScreenAd)\n *\n * 변경 이력 (#196):\n * - slot 레지스트리로 TossAds destroy/destroyAll 누수 수정 (🟡→🟢)\n * - attachBanner BannerSlotCallbacks 발화 (onAdRendered/onAdImpression/onNoFill 등)\n * - initialize onInitialized/onInitializationFailed 발화\n * - AdMob reward 파라미터화 (state.ads.rewardUnitType/rewardAmount)\n * - 모든 호출 observe()로 sdkCallLog에 기록\n */\n\nimport { observe } from '../observe.js';\nimport { createMockProxy } from '../proxy.js';\nimport { aitState } from '../state.js';\n\nfunction withIsSupported<T extends (...args: never[]) => unknown>(\n fn: T,\n): T & { isSupported: () => boolean } {\n (fn as T & { isSupported: () => boolean }).isSupported = () => true;\n return fn as T & { isSupported: () => boolean };\n}\n\n// --- slot 레지스트리 (TossAds destroy 누수 수정) ---\n// attachBanner가 생성한 placeholder를 slotId로 추적해서\n// destroy/destroyAll이 실제 el.remove()를 수행할 수 있게 한다.\nconst _slotRegistry = new Map<string, HTMLElement>();\n\nlet _slotCounter = 0;\nfunction _nextSlotId(adGroupId: string): string {\n _slotCounter += 1;\n return `mock-slot-${adGroupId}-${_slotCounter}`;\n}\n\n/** 테스트에서 레지스트리를 초기화할 수 있게 export */\nexport function _resetSlotRegistry(): void {\n _slotRegistry.clear();\n _slotCounter = 0;\n}\n\n// --- Google AdMob ---\n\nexport const GoogleAdMob = createMockProxy('GoogleAdMob', {\n loadAppsInTossAdMob: withIsSupported(\n observe(\n 'GoogleAdMob.loadAppsInTossAdMob',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n args.onError(new Error('No fill'));\n return;\n }\n aitState.patch('ads', { isLoaded: true });\n args.onEvent({ type: 'loaded', data: { adGroupId: args.options?.adGroupId } });\n }, 200);\n return () => {};\n },\n ),\n ),\n\n showAppsInTossAdMob: withIsSupported(\n observe(\n 'GoogleAdMob.showAppsInTossAdMob',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'requested' }), 50);\n setTimeout(() => args.onEvent({ type: 'show' }), 100);\n setTimeout(() => args.onEvent({ type: 'impression' }), 150);\n setTimeout(() => {\n const { rewardUnitType, rewardAmount } = aitState.state.ads;\n args.onEvent({\n type: 'userEarnedReward',\n data: { unitType: rewardUnitType, unitAmount: rewardAmount },\n });\n }, 1000);\n setTimeout(() => {\n args.onEvent({ type: 'dismissed' });\n aitState.patch('ads', { isLoaded: false });\n }, 1500);\n return () => {};\n },\n ),\n ),\n\n isAppsInTossAdMobLoaded: withIsSupported(\n observe(\n 'GoogleAdMob.isAppsInTossAdMobLoaded',\n 'faithful',\n async (_options: { adGroupId?: string }): Promise<boolean> => aitState.state.ads.isLoaded,\n ),\n ),\n});\n\n// --- TossAds ---\n\nexport const TossAds = createMockProxy('TossAds', {\n initialize: withIsSupported(\n observe(\n 'TossAds.initialize',\n 'partial',\n (options: {\n callbacks?: { onInitialized?: () => void; onInitializationFailed?: (error: Error) => void };\n }): void => {\n // forceNoFill을 initialization failure로도 활용한다\n if (aitState.state.ads.forceNoFill) {\n options.callbacks?.onInitializationFailed?.(new Error('No fill'));\n return;\n }\n options.callbacks?.onInitialized?.();\n },\n ),\n ),\n\n attach: withIsSupported(\n observe(\n 'TossAds.attach',\n 'partial',\n (_adGroupId: string, target: string | HTMLElement, _options?: unknown): void => {\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n if (el) {\n const placeholder = document.createElement('div');\n placeholder.style.cssText =\n 'background:#f0f0f0;border:1px dashed #999;padding:16px;text-align:center;color:#666;font-size:14px;';\n placeholder.textContent = '[@ait-co/devtools] TossAds Placeholder';\n el.appendChild(placeholder);\n }\n },\n ),\n ),\n\n attachBanner: withIsSupported(\n observe(\n 'TossAds.attachBanner',\n 'faithful',\n (\n adGroupId: string,\n target: string | HTMLElement,\n options?: {\n theme?: 'auto' | 'light' | 'dark';\n tone?: 'blackAndWhite' | 'grey';\n variant?: 'card' | 'expanded';\n callbacks?: {\n onAdRendered?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdViewable?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdClicked?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdImpression?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdFailedToRender?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: Record<string, never>;\n error: { code: number; message: string; domain?: string };\n }) => void;\n onNoFill?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: Record<string, never>;\n }) => void;\n };\n },\n ): { destroy: () => void } => {\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n const slotId = _nextSlotId(adGroupId);\n\n const placeholder = document.createElement('div');\n\n // AttachBannerOptions를 placeholder 스타일에 반영\n const theme = options?.theme ?? 'auto';\n const variant = options?.variant ?? 'card';\n const isDark =\n theme === 'dark' ||\n (theme === 'auto' &&\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-color-scheme: dark)').matches);\n const bg = isDark ? '#1a1a1a' : '#f0f0f0';\n const textColor = isDark ? '#aaa' : '#666';\n const borderColor = isDark ? '#555' : '#999';\n const height = variant === 'expanded' ? '120px' : '60px';\n\n placeholder.dataset.aitSlotId = slotId;\n placeholder.style.cssText = `background:${bg};border:1px dashed ${borderColor};padding:8px 12px;text-align:center;color:${textColor};font-size:12px;min-height:${height};display:flex;align-items:center;justify-content:center;`;\n placeholder.textContent = `[@ait-co/devtools] Banner Ad (${variant})`;\n\n if (el) {\n el.appendChild(placeholder);\n _slotRegistry.set(slotId, placeholder);\n }\n\n const destroySlot = () => {\n const registered = _slotRegistry.get(slotId);\n if (registered) {\n registered.remove();\n _slotRegistry.delete(slotId);\n }\n };\n\n // 콜백 발화 (setTimeout으로 비동기 — 실 SDK와 동일하게 렌더 완료 후)\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n options?.callbacks?.onNoFill?.({\n slotId,\n adGroupId,\n adMetadata: {},\n });\n options?.callbacks?.onAdFailedToRender?.({\n slotId,\n adGroupId,\n adMetadata: {},\n error: { code: 0, message: 'No fill' },\n });\n return;\n }\n\n const eventPayload = {\n slotId,\n adGroupId,\n adMetadata: { creativeId: `mock-creative-${slotId}`, requestId: `mock-req-${slotId}` },\n };\n options?.callbacks?.onAdRendered?.(eventPayload);\n options?.callbacks?.onAdImpression?.(eventPayload);\n }, 100);\n\n return { destroy: destroySlot };\n },\n ),\n ),\n\n destroy: withIsSupported(\n observe('TossAds.destroy', 'faithful', (slotId: string): void => {\n const el = _slotRegistry.get(slotId);\n if (el) {\n el.remove();\n _slotRegistry.delete(slotId);\n }\n }),\n ),\n\n destroyAll: withIsSupported(\n observe('TossAds.destroyAll', 'faithful', (): void => {\n for (const el of _slotRegistry.values()) {\n el.remove();\n }\n _slotRegistry.clear();\n }),\n ),\n});\n\n// --- FullScreen Ad ---\n\nexport const loadFullScreenAd = withIsSupported(\n observe(\n 'loadFullScreenAd',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n args.onError(new Error('No fill'));\n return;\n }\n aitState.patch('ads', { isLoaded: true });\n args.onEvent({ type: 'loaded', data: { adGroupId: args.options?.adGroupId } });\n }, 200);\n return () => {};\n },\n ),\n);\n\nexport const showFullScreenAd = withIsSupported(\n observe(\n 'showFullScreenAd',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'show' }), 100);\n setTimeout(() => args.onEvent({ type: 'dismissed' }), 1500);\n return () => {};\n },\n ),\n);\n","import { t } from '../../i18n/index.js';\nimport { GoogleAdMob, loadFullScreenAd, showFullScreenAd, TossAds } from '../../mock/ads/index.js';\nimport { aitState } from '../../mock/state.js';\nimport { h, inputRow, monitoringNotice } from '../helpers.js';\n\nfunction recordEvent(type: string) {\n aitState.patch('ads', { lastEvent: { type, timestamp: Date.now() } });\n}\n\nfunction recordError(message: string) {\n recordEvent(`error: ${message}`);\n}\n\nfunction statusRow(label: string, value: string): HTMLElement {\n return h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, label),\n h('span', { style: 'font-family:SF Mono,Menlo,monospace;font-size:11px;color:#aaa' }, value),\n );\n}\n\nfunction lastEventLine(): HTMLElement {\n const last = aitState.state.ads.lastEvent;\n if (!last) {\n return h(\n 'div',\n { className: 'ait-log-entry' },\n h('span', { style: 'color:#555' }, t('ads.empty.events')),\n );\n }\n const time = new Date(last.timestamp).toLocaleTimeString();\n const isError = last.type.startsWith('error:');\n return h(\n 'div',\n { className: 'ait-log-entry' },\n h('span', { className: 'ait-log-type', style: isError ? 'color:#e74c3c' : '' }, last.type),\n h('span', { className: 'ait-log-time' }, time),\n );\n}\n\nfunction adSection(\n title: string,\n onLoad: () => void,\n onShow: () => void,\n disabled: boolean,\n): HTMLElement {\n const loadBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.load'));\n const showBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.show'));\n if (disabled) {\n loadBtn.disabled = true;\n showBtn.disabled = true;\n }\n loadBtn.addEventListener('click', onLoad);\n showBtn.addEventListener('click', onShow);\n\n return h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, title),\n h('div', { className: 'ait-btn-row' }, loadBtn, showBtn),\n );\n}\n\n/** TossAds 배너 인터랙티브 섹션 — Render/No-fill/Click/Destroy 버튼 */\nfunction tossAdsBannerSection(disabled: boolean): HTMLElement {\n // 패널 내부 더미 mount 대상\n const mountTarget = h('div', {\n style: 'min-height:60px;background:#111;border-radius:4px;margin-bottom:6px;overflow:hidden;',\n });\n\n const renderBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.render'));\n const noFillBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.noFill'));\n const clickBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.click'));\n const destroyBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.destroy'));\n\n if (disabled) {\n renderBtn.disabled = true;\n noFillBtn.disabled = true;\n clickBtn.disabled = true;\n destroyBtn.disabled = true;\n }\n\n // 가장 최근 attachBanner 반환 handle 보존 (panel 내부 추적)\n let currentHandle: { destroy: () => void } | null = null;\n\n renderBtn.addEventListener('click', () => {\n // 기존 슬롯 정리 후 새로 Render\n currentHandle?.destroy();\n currentHandle = null;\n mountTarget.innerHTML = '';\n\n const handle = TossAds.attachBanner('mock-banner-group', mountTarget, {\n theme: 'auto',\n variant: 'card',\n callbacks: {\n onAdRendered: (p) => recordEvent(`onAdRendered(${p.slotId})`),\n onAdImpression: (p) => recordEvent(`onAdImpression(${p.slotId})`),\n onAdClicked: (p) => recordEvent(`onAdClicked(${p.slotId})`),\n onAdFailedToRender: (p) => recordEvent(`error: onAdFailedToRender(${p.slotId})`),\n onNoFill: (p) => recordEvent(`error: onNoFill(${p.slotId})`),\n },\n });\n currentHandle = handle;\n });\n\n noFillBtn.addEventListener('click', () => {\n // 기존 슬롯 정리\n currentHandle?.destroy();\n currentHandle = null;\n mountTarget.innerHTML = '';\n\n // no-fill 경로: 패널 상의 forceNoFill 상태와 무관하게 즉시 no-fill 콜백을 발화한다.\n // (forceNoFill을 일시 toggle하면 attachBanner 내부 setTimeout이 복원 후 실행돼\n // 실제 forceNoFill 상태를 읽어 기본 경로가 돌아가는 race condition이 생긴다.)\n const slotId = `mock-slot-no-fill-${Date.now()}`;\n recordEvent(`error: onNoFill(${slotId})`);\n recordEvent(`error: onAdFailedToRender(${slotId})`);\n });\n\n clickBtn.addEventListener('click', () => {\n recordEvent('banner:clicked');\n });\n\n destroyBtn.addEventListener('click', () => {\n if (currentHandle) {\n currentHandle.destroy();\n currentHandle = null;\n mountTarget.innerHTML = '';\n recordEvent('banner:destroyed');\n } else {\n recordError('No banner to destroy');\n }\n });\n\n return h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('ads.section.tossAdsBanner')),\n mountTarget,\n h('div', { className: 'ait-btn-row' }, renderBtn, noFillBtn, clickBtn, destroyBtn),\n );\n}\n\nexport function renderAdsTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n const forceNoFillCb = h('input', { type: 'checkbox', className: 'ait-checkbox' });\n forceNoFillCb.checked = s.ads.forceNoFill;\n if (disabled) forceNoFillCb.disabled = true;\n forceNoFillCb.addEventListener('change', () => {\n aitState.patch('ads', { forceNoFill: forceNoFillCb.checked });\n });\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('ads.section.state')),\n statusRow(t('ads.row.isLoaded'), String(s.ads.isLoaded)),\n h('div', { className: 'ait-row' }, h('label', {}, t('ads.row.forceNoFill')), forceNoFillCb),\n inputRow(\n t('ads.row.rewardUnitType'),\n s.ads.rewardUnitType,\n (v) => aitState.patch('ads', { rewardUnitType: v }),\n disabled,\n ),\n inputRow(\n t('ads.row.rewardAmount'),\n String(s.ads.rewardAmount),\n (v) => {\n const n = Number(v);\n if (!Number.isNaN(n)) aitState.patch('ads', { rewardAmount: n });\n },\n disabled,\n ),\n lastEventLine(),\n ),\n adSection(\n t('ads.section.googleAdMob'),\n () => {\n GoogleAdMob.loadAppsInTossAdMob({\n onEvent: (e) => recordEvent(e.type),\n onError: (err) => recordError(err.message),\n });\n },\n () => {\n GoogleAdMob.showAppsInTossAdMob({\n onEvent: (e) => recordEvent(e.type),\n onError: (err) => recordError(err.message),\n });\n },\n disabled,\n ),\n adSection(\n t('ads.section.tossAds'),\n () => {\n // TossAds initialize: live state를 읽어 forceNoFill 분기\n TossAds.initialize({\n callbacks: {\n onInitialized: () => {\n aitState.patch('ads', { isLoaded: true });\n recordEvent('loaded');\n },\n onInitializationFailed: (err) => recordError(err.message),\n },\n });\n },\n () => {\n if (!aitState.state.ads.isLoaded) {\n recordError('Ad not loaded');\n return;\n }\n recordEvent('show');\n setTimeout(() => {\n recordEvent('dismissed');\n aitState.patch('ads', { isLoaded: false });\n }, 1500);\n },\n disabled,\n ),\n tossAdsBannerSection(disabled),\n adSection(\n t('ads.section.fullScreenAd'),\n () => {\n loadFullScreenAd({\n onEvent: (e) => recordEvent(e.type),\n onError: (err) => recordError(err.message),\n });\n },\n () => {\n showFullScreenAd({\n onEvent: (e) => recordEvent(e.type),\n onError: (err) => recordError(err.message),\n });\n },\n disabled,\n ),\n );\n\n return container;\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport { h, monitoringNotice } from '../helpers.js';\n\nconst FIDELITY_BADGE: Record<string, string> = {\n faithful: '🟢',\n partial: '🟡',\n inert: '🔴',\n};\n\nexport function renderAnalyticsTab(): HTMLElement {\n const disabled = !aitState.state.panelEditable;\n const container = h('div');\n if (disabled) container.appendChild(monitoringNotice());\n\n // --- Analytics Log section ---\n const logs = aitState.state.analyticsLog;\n\n const clearAnalyticsBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger' },\n t('analytics.btn.clear'),\n );\n if (disabled) clearAnalyticsBtn.disabled = true;\n clearAnalyticsBtn.addEventListener('click', () => {\n aitState.update({ analyticsLog: [] });\n });\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-row' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('analytics.section.log', { count: logs.length }),\n ),\n clearAnalyticsBtn,\n ),\n ...logs\n .slice(-30)\n .reverse()\n .map((entry) => {\n const time = new Date(entry.timestamp).toLocaleTimeString();\n return h(\n 'div',\n { className: 'ait-log-entry' },\n h('span', { className: 'ait-log-time' }, time),\n h('span', { className: 'ait-log-type' }, entry.type),\n JSON.stringify(entry.params),\n );\n }),\n ),\n );\n\n // --- SDK Calls section ---\n const calls = aitState.state.sdkCallLog;\n\n const clearCallsBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger' },\n t('analytics.calls.btn.clear'),\n );\n if (disabled) clearCallsBtn.disabled = true;\n clearCallsBtn.addEventListener('click', () => {\n aitState.update({ sdkCallLog: [] });\n });\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-row' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('analytics.calls.section', { count: calls.length }),\n ),\n clearCallsBtn,\n ),\n calls.length === 0\n ? h('div', { className: 'ait-log-entry ait-log-empty' }, t('analytics.calls.empty'))\n : h(\n 'div',\n {},\n ...calls\n .slice(-50)\n .reverse()\n .map((call) => {\n const badge = FIDELITY_BADGE[call.fidelity] ?? '⬜';\n const time = new Date(call.timestamp).toLocaleTimeString();\n const argsStr =\n call.args.length > 0\n ? `(${call.args.map((a) => JSON.stringify(a)).join(', ')})`\n : '()';\n const statusSuffix =\n call.status === 'rejected'\n ? ` ✗ ${call.error ?? 'error'}`\n : call.status === 'pending'\n ? ' …'\n : '';\n\n return h(\n 'div',\n { className: `ait-log-entry ait-sdk-call ait-sdk-call-${call.fidelity}` },\n h('span', { className: 'ait-log-badge' }, badge),\n h('span', { className: 'ait-log-method' }, call.method),\n h('span', { className: 'ait-log-args' }, argsStr),\n h('span', { className: 'ait-log-time' }, ` · ${time}`),\n statusSuffix ? h('span', { className: 'ait-log-status' }, statusSuffix) : '',\n );\n }),\n ),\n ),\n );\n\n return container;\n}\n","import { getLocale, type Locale, setLocale, t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type { NetworkStatus, OperationalEnvironment, PlatformOS } from '../../mock/types.js';\nimport { TELEMETRY_ENDPOINT } from '../../telemetry/index.js';\nimport {\n deleteMyData,\n isTier0Enabled,\n readConsentState,\n setConsentViaToggle,\n setTier0Enabled,\n} from '../../telemetry/state.js';\nimport { h, inputRow, monitoringNotice, selectRow } from '../helpers.js';\n\nexport function renderEnvironmentTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.section.platform')),\n selectRow(\n t('env.row.os'),\n ['ios', 'android'],\n s.platform,\n (v) => aitState.update({ platform: v as PlatformOS }),\n disabled,\n ),\n inputRow(\n t('env.row.appVersion'),\n s.appVersion,\n (v) => aitState.update({ appVersion: v }),\n disabled,\n ),\n selectRow(\n t('env.row.environment'),\n ['toss', 'sandbox'],\n s.environment,\n (v) => aitState.update({ environment: v as OperationalEnvironment }),\n disabled,\n ),\n inputRow(t('env.row.locale'), s.locale, (v) => aitState.update({ locale: v }), disabled),\n ),\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.section.network')),\n selectRow(\n t('env.row.networkStatus'),\n ['WIFI', '4G', '5G', '3G', '2G', 'OFFLINE', 'WWAN', 'UNKNOWN'],\n s.networkStatus,\n (v) => aitState.update({ networkStatus: v as NetworkStatus }),\n disabled,\n ),\n ),\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.section.safeArea')),\n inputRow(\n t('env.row.safeArea.top'),\n String(s.safeAreaInsets.top),\n (v) => aitState.patch('safeAreaInsets', { top: Number(v) }),\n disabled,\n ),\n inputRow(\n t('env.row.safeArea.bottom'),\n String(s.safeAreaInsets.bottom),\n (v) => aitState.patch('safeAreaInsets', { bottom: Number(v) }),\n disabled,\n ),\n ),\n buildNavigationSection(),\n buildLanguageSection(),\n buildTelemetrySection(),\n );\n return container;\n}\n\n/**\n * Navigation 동작 관측 (read-only) — real(토스 WebView)에서 native bridge로 발화하는\n * no-op API의 마지막 호출값을 보여준다. Environment를 toss로 바꾸면 toss-gated 가드\n * (예: sdk-example `useDisableIosSwipeGestureInToss`)가 돌면서 이 값이 토글되므로,\n * \"toss 진입 → 가드 실행 → 관측 가능한 state 변화\" 루프를 패널에서 한눈에 확인할 수 있다.\n */\nfunction buildNavigationSection(): HTMLElement {\n const swipe = aitState.state.navigation.iosSwipeGestureEnabled;\n const valueText =\n swipe === null\n ? t('env.value.iosSwipeGesture.unset')\n : swipe\n ? t('env.value.iosSwipeGesture.enabled')\n : t('env.value.iosSwipeGesture.disabled');\n\n return h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.section.navigation')),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('env.row.iosSwipeGesture')),\n h(\n 'span',\n {\n style: `font-family:'SF Mono','Menlo',monospace;font-size:12px;color:${\n swipe === null ? '#888' : '#95e6cb'\n }`,\n },\n valueText,\n ),\n ),\n h('div', { style: 'font-size:11px;color:#666;margin-top:4px' }, t('env.hint.iosSwipeGesture')),\n );\n}\n\nfunction buildLanguageSection(): HTMLElement {\n const current = getLocale();\n const select = h('select', { className: 'ait-select' }) as HTMLSelectElement;\n const options: Array<{ value: Locale; labelKey: 'env.language.ko' | 'env.language.en' }> = [\n { value: 'ko', labelKey: 'env.language.ko' },\n { value: 'en', labelKey: 'env.language.en' },\n ];\n for (const opt of options) {\n const option = h('option', { value: opt.value }, t(opt.labelKey));\n if (opt.value === current) option.selected = true;\n select.appendChild(option);\n }\n select.addEventListener('change', () => {\n setLocale(select.value as Locale);\n });\n\n return h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.section.language')),\n h('div', { className: 'ait-row' }, h('label', {}, t('env.language.row')), select),\n );\n}\n\nfunction buildTelemetrySection(): HTMLElement {\n // --- Tier 0 row ---\n const t0Enabled = isTier0Enabled();\n const t0StatusLabel = h(\n 'span',\n {\n style: `font-size:12px;font-weight:600;color:${t0Enabled ? '#4ade80' : '#888'}`,\n },\n t0Enabled ? t('env.telemetry.t0On') : t('env.telemetry.t0Off'),\n );\n const t0ToggleBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm', style: 'font-size:11px' },\n t0Enabled ? t('env.telemetry.t0TurnOff') : t('env.telemetry.t0TurnOn'),\n );\n t0ToggleBtn.addEventListener('click', () => {\n setTier0Enabled(!t0Enabled);\n window.dispatchEvent(new CustomEvent('__ait:panel-switch-tab', { detail: { tab: 'env' } }));\n });\n const t0Row = h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('env.telemetry.t0Row')),\n h('span', { style: 'display:flex;align-items:center;gap:8px' }, t0StatusLabel, t0ToggleBtn),\n );\n const t0Desc = h(\n 'div',\n { style: 'font-size:11px;color:#666;margin-bottom:6px' },\n t('env.telemetry.t0Desc'),\n );\n\n // --- Tier 1 row ---\n const consent = readConsentState();\n const isGranted = consent === 'granted';\n\n const statusLabel = h(\n 'span',\n {\n style: `font-size:12px;font-weight:600;color:${isGranted ? '#4ade80' : '#888'}`,\n },\n isGranted ? t('env.telemetry.on') : t('env.telemetry.off'),\n );\n\n const toggleBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm', style: 'font-size:11px' },\n isGranted ? t('env.telemetry.turnOff') : t('env.telemetry.turnOn'),\n );\n toggleBtn.addEventListener('click', () => {\n setConsentViaToggle(!isGranted);\n window.dispatchEvent(new CustomEvent('__ait:panel-switch-tab', { detail: { tab: 'env' } }));\n });\n\n const statusRow = h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('env.telemetry.row')),\n h('span', { style: 'display:flex;align-items:center;gap:8px' }, statusLabel, toggleBtn),\n );\n\n // anon_id display (truncated to 8 chars + ellipsis, click-to-copy)\n const rawAnonId = localStorage.getItem('__ait_telemetry:anon_id');\n const displayAnonId = rawAnonId ?? t('env.telemetry.anonIdNotSet');\n const truncatedId = displayAnonId.length > 8 ? `${displayAnonId.slice(0, 8)}…` : displayAnonId;\n\n const anonIdEl = h(\n 'span',\n {\n style: \"font-family:'SF Mono','Menlo',monospace;font-size:11px;color:#95e6cb;cursor:pointer\",\n title: t('env.telemetry.anonIdCopyTitle'),\n },\n t('env.telemetry.anonIdLabel', { value: truncatedId }),\n );\n anonIdEl.addEventListener('click', () => {\n if (!rawAnonId) return;\n navigator.clipboard.writeText(rawAnonId).catch(() => {\n /* clipboard unavailable — silently ignore */\n });\n });\n\n // Delete my data button\n const deleteBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger' },\n t('env.telemetry.deleteBtn'),\n );\n const deleteStatus = h('span', { style: 'font-size:11px;color:#aaa' });\n\n deleteBtn.addEventListener('click', () => {\n deleteBtn.disabled = true;\n deleteStatus.textContent = t('env.telemetry.deleting');\n deleteMyData(TELEMETRY_ENDPOINT)\n .then((ok) => {\n deleteStatus.textContent = ok\n ? t('env.telemetry.deleted')\n : t('env.telemetry.deleteFailedRetry');\n deleteBtn.disabled = false;\n })\n .catch(() => {\n deleteStatus.textContent = t('env.telemetry.deleteFailed');\n deleteBtn.disabled = false;\n });\n });\n\n // Privacy link\n const privacyLink = h('a', {\n href: 'https://docs.aitc.dev/privacy',\n target: '_blank',\n rel: 'noopener noreferrer',\n style: 'font-size:11px;color:#666;text-decoration:none',\n });\n privacyLink.textContent = t('env.telemetry.privacyLink');\n\n return h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.telemetry.section')),\n t0Row,\n t0Desc,\n statusRow,\n h('div', { style: 'margin-bottom:6px' }, anonIdEl),\n h(\n 'div',\n { className: 'ait-btn-row', style: 'align-items:center;gap:8px;margin-top:6px' },\n deleteBtn,\n deleteStatus,\n ),\n h('div', { style: 'margin-top:8px' }, privacyLink),\n );\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport { h, monitoringNotice, selectRow } from '../helpers.js';\n\nexport function renderEventsTab(): HTMLElement {\n const disabled = !aitState.state.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n const backBtn = h('button', { className: 'ait-btn' }, t('events.btn.triggerBack'));\n backBtn.addEventListener('click', () => aitState.trigger('backEvent'));\n if (disabled) backBtn.disabled = true;\n\n const homeBtn = h('button', { className: 'ait-btn' }, t('events.btn.triggerHome'));\n homeBtn.addEventListener('click', () => aitState.trigger('homeEvent'));\n if (disabled) homeBtn.disabled = true;\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('events.section.navigation')),\n h('div', { className: 'ait-row' }, backBtn, homeBtn),\n ),\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('events.section.login')),\n selectRow(\n t('events.row.loggedIn'),\n ['true', 'false'],\n String(aitState.state.auth.isLoggedIn),\n (v) => {\n aitState.patch('auth', { isLoggedIn: v === 'true' });\n },\n disabled,\n ),\n selectRow(\n t('events.row.tossLoginIntegrated'),\n ['true', 'false'],\n String(aitState.state.auth.isTossLoginIntegrated),\n (v) => {\n aitState.patch('auth', { isTossLoginIntegrated: v === 'true' });\n },\n disabled,\n ),\n ),\n );\n return container;\n}\n","/**\n * IAP (인앱결제) mock\n */\n\nimport { createMockProxy } from '../proxy.js';\nimport { aitState } from '../state.js';\n\n// orderCounter는 모듈 레벨 상태로 reset()에 의해 초기화되지 않는다.\n// 테스트에서는 orderId를 stringContaining('mock-order-')로 검증하여 카운터 값에 의존하지 않는다.\nlet orderCounter = 0;\n\nfunction generateOrderId(): string {\n return `mock-order-${++orderCounter}-${Date.now()}`;\n}\n\ninterface IapCreateOneTimePurchaseOrderOptions {\n options: {\n sku?: string;\n productId?: string;\n processProductGrant: (params: { orderId: string }) => boolean | Promise<boolean>;\n };\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>;\n onError: (error: unknown) => void | Promise<void>;\n}\n\ninterface CreateSubscriptionPurchaseOrderOptions {\n options: {\n sku: string;\n offerId?: string | null;\n processProductGrant: (params: {\n orderId: string;\n subscriptionId?: string;\n }) => boolean | Promise<boolean>;\n };\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>;\n onError: (error: unknown) => void | Promise<void>;\n}\n\ninterface IapOrderResult {\n orderId: string;\n displayName: string;\n displayAmount: string;\n amount: number;\n currency: string;\n fraction: number;\n miniAppIconUrl: string | null;\n}\n\nfunction buildOrderResult(sku: string): IapOrderResult {\n const product = aitState.state.iap.products.find((p) => p.sku === sku);\n const amountStr = product?.displayAmount?.replace(/[^0-9]/g, '') ?? '1000';\n return {\n orderId: generateOrderId(),\n displayName: product?.displayName ?? 'Mock Product',\n displayAmount: product?.displayAmount ?? '1,000원',\n amount: parseInt(amountStr, 10) || 1000,\n currency: 'KRW',\n fraction: 0,\n miniAppIconUrl: product?.iconUrl || null,\n };\n}\n\nasync function handlePurchase(\n sku: string,\n processProductGrant: (params: {\n orderId: string;\n subscriptionId?: string;\n }) => boolean | Promise<boolean>,\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>,\n onError: (error: unknown) => void | Promise<void>,\n): Promise<void> {\n const nextResult = aitState.state.iap.nextResult;\n\n // 비동기 시뮬레이션 (실제로는 결제 UI가 뜨는 시간)\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult !== 'success') {\n onError({ code: nextResult });\n return;\n }\n\n const result = buildOrderResult(sku);\n\n try {\n const granted = await processProductGrant({ orderId: result.orderId });\n if (!granted) {\n onError({ code: 'PRODUCT_NOT_GRANTED_BY_PARTNER' });\n return;\n }\n } catch (e) {\n onError(e);\n return;\n }\n\n // 주문 완료 기록\n aitState.patch('iap', {\n completedOrders: [\n ...aitState.state.iap.completedOrders,\n {\n orderId: result.orderId,\n sku,\n status: 'COMPLETED' as const,\n date: new Date().toISOString(),\n },\n ],\n });\n\n await onEvent({ type: 'success', data: result });\n}\n\nexport const IAP = createMockProxy('IAP', {\n // 반환되는 cancel 함수는 mock에서는 no-op이다 (실제 SDK는 결제 UI를 닫음)\n createOneTimePurchaseOrder(params: IapCreateOneTimePurchaseOrderOptions): () => void {\n const sku = params.options.sku ?? params.options.productId ?? '';\n handlePurchase(sku, params.options.processProductGrant, params.onEvent, params.onError).catch(\n (e) => console.error('[@ait-co/devtools] IAP unexpected error:', e),\n );\n return () => {};\n },\n\n createSubscriptionPurchaseOrder(params: CreateSubscriptionPurchaseOrderOptions): () => void {\n handlePurchase(\n params.options.sku,\n params.options.processProductGrant,\n params.onEvent,\n params.onError,\n ).catch((e) => console.error('[@ait-co/devtools] IAP unexpected error:', e));\n return () => {};\n },\n\n async getProductItemList(): Promise<{ products: unknown[] }> {\n return {\n products: aitState.state.iap.products.map((p) => ({\n ...p,\n ...(p.type === 'SUBSCRIPTION' ? { renewalCycle: p.renewalCycle ?? 'MONTHLY' } : {}),\n })),\n };\n },\n\n async getPendingOrders(): Promise<{\n orders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }>;\n }> {\n return { orders: [...aitState.state.iap.pendingOrders] };\n },\n\n async getCompletedOrRefundedOrders(): Promise<{\n hasNext: boolean;\n nextKey?: string | null;\n orders: Array<{ orderId: string; sku: string; status: 'COMPLETED' | 'REFUNDED'; date: string }>;\n }> {\n return {\n hasNext: false,\n nextKey: null,\n orders: [...aitState.state.iap.completedOrders],\n };\n },\n\n async completeProductGrant(args: { params: { orderId: string } }): Promise<boolean> {\n // pending → completed 전이\n const idx = aitState.state.iap.pendingOrders.findIndex(\n (o) => o.orderId === args.params.orderId,\n );\n if (idx !== -1) {\n const order = aitState.state.iap.pendingOrders[idx];\n const pendingOrders = aitState.state.iap.pendingOrders.filter((_, i) => i !== idx);\n const completedOrders = [\n ...aitState.state.iap.completedOrders,\n {\n orderId: order.orderId,\n sku: order.sku,\n status: 'COMPLETED' as const,\n date: new Date().toISOString(),\n },\n ];\n aitState.patch('iap', { pendingOrders, completedOrders });\n }\n return true;\n },\n\n async getSubscriptionInfo(_args: { params: { orderId: string } }) {\n return {\n subscription: {\n catalogId: 1,\n status: 'ACTIVE',\n expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),\n isAutoRenew: true,\n gracePeriodExpiresAt: null,\n isAccessible: true,\n },\n };\n },\n});\n\n// --- TossPay ---\n\nexport async function checkoutPayment(options: {\n params: { payToken: string };\n}): Promise<{ success: boolean; reason?: string }> {\n const { nextResult, failReason } = aitState.state.payment;\n console.log('[@ait-co/devtools] checkoutPayment:', options.params.payToken);\n\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult === 'success') {\n return { success: true };\n }\n return { success: false, reason: failReason || 'Mock payment failed' };\n}\n\nexport const requestTossPayPaysBilling = Object.assign(\n async function requestTossPayPaysBilling(options: {\n params: { wrappedToken: string };\n }): Promise<{ success: boolean; reason?: string } | undefined> {\n const { nextResult, failReason } = aitState.state.payment;\n console.log('[@ait-co/devtools] requestTossPayPaysBilling:', options.params.wrappedToken);\n\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult === 'success') {\n return { success: true };\n }\n return { success: false, reason: failReason || 'Mock billing auth failed' };\n },\n { isSupported: () => true },\n);\n","import { t } from '../../i18n/index.js';\nimport { IAP } from '../../mock/iap/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type { IapNextResult } from '../../mock/types.js';\nimport { h, monitoringNotice, selectRow } from '../helpers.js';\n\nfunction formatTimestamp(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return iso;\n return d.toLocaleTimeString();\n}\n\nfunction shortOrderId(orderId: string): string {\n return orderId.length > 12 ? `…${orderId.slice(-10)}` : orderId;\n}\n\nexport function renderIapTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n const results: IapNextResult[] = [\n 'success',\n 'USER_CANCELED',\n 'INVALID_PRODUCT_ID',\n 'PAYMENT_PENDING',\n 'NETWORK_ERROR',\n 'ITEM_ALREADY_OWNED',\n 'INTERNAL_ERROR',\n ];\n\n if (disabled) container.appendChild(monitoringNotice());\n\n const pendingOrders = s.iap.pendingOrders;\n const pendingSection = h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('iap.section.pending', { count: pendingOrders.length }),\n ),\n );\n if (pendingOrders.length === 0) {\n pendingSection.appendChild(h('div', { className: 'ait-log-entry' }, t('iap.empty.pending')));\n } else {\n for (const o of pendingOrders) {\n const completeBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('iap.btn.complete'));\n if (disabled) completeBtn.disabled = true;\n completeBtn.addEventListener('click', () => {\n IAP.completeProductGrant({ params: { orderId: o.orderId } }).catch((err) =>\n console.error('[@ait-co/devtools] completeProductGrant error:', err),\n );\n });\n pendingSection.appendChild(\n h(\n 'div',\n { className: 'ait-log-entry' },\n h('span', { className: 'ait-log-type' }, t('iap.label.pending')),\n `${o.sku} (${shortOrderId(o.orderId)}) · ${formatTimestamp(o.paymentCompletedDate)} `,\n completeBtn,\n ),\n );\n }\n }\n\n const completedOrders = s.iap.completedOrders;\n const completedSection = h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('iap.section.completed', { count: completedOrders.length }),\n ),\n );\n if (completedOrders.length === 0) {\n completedSection.appendChild(\n h('div', { className: 'ait-log-entry' }, t('iap.empty.completed')),\n );\n } else {\n for (const o of completedOrders) {\n completedSection.appendChild(\n h(\n 'div',\n { className: 'ait-log-entry' },\n h('span', { className: 'ait-log-type' }, o.status),\n `${o.sku} (${shortOrderId(o.orderId)}) · ${formatTimestamp(o.date)}`,\n ),\n );\n }\n }\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('iap.section.simulator')),\n selectRow(\n t('iap.row.nextResult'),\n results,\n s.iap.nextResult,\n (v) => {\n aitState.patch('iap', { nextResult: v as IapNextResult });\n },\n disabled,\n ),\n ),\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('iap.section.tossPay')),\n selectRow(\n t('iap.row.tossPayResult'),\n ['success', 'fail'],\n s.payment.nextResult,\n (v) => {\n aitState.patch('payment', { nextResult: v as 'success' | 'fail' });\n },\n disabled,\n ),\n ),\n pendingSection,\n completedSection,\n );\n return container;\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport { h, inputRow, monitoringNotice } from '../helpers.js';\n\nexport function renderLocationTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('location.section.current')),\n inputRow(\n t('location.row.latitude'),\n String(s.location.coords.latitude),\n (v) => {\n const coords = { ...s.location.coords, latitude: Number(v) };\n aitState.patch('location', { coords } as Partial<typeof s.location>);\n },\n disabled,\n ),\n inputRow(\n t('location.row.longitude'),\n String(s.location.coords.longitude),\n (v) => {\n const coords = { ...s.location.coords, longitude: Number(v) };\n aitState.patch('location', { coords } as Partial<typeof s.location>);\n },\n disabled,\n ),\n inputRow(\n t('location.row.accuracy'),\n String(s.location.coords.accuracy),\n (v) => {\n const coords = { ...s.location.coords, accuracy: Number(v) };\n aitState.patch('location', { coords } as Partial<typeof s.location>);\n },\n disabled,\n ),\n ),\n );\n return container;\n}\n","import { type StringKey, t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type { NotificationAgreementResult } from '../../mock/types.js';\nimport { h, monitoringNotice } from '../helpers.js';\n\nconst RESULTS: Array<{ value: NotificationAgreementResult; labelKey: StringKey }> = [\n { value: 'newAgreement', labelKey: 'notifications.option.newAgreement' },\n { value: 'alreadyAgreed', labelKey: 'notifications.option.alreadyAgreed' },\n { value: 'agreementRejected', labelKey: 'notifications.option.agreementRejected' },\n];\n\nfunction radioRow(\n name: string,\n current: NotificationAgreementResult,\n option: { value: NotificationAgreementResult; labelKey: StringKey },\n disabled: boolean,\n): HTMLElement {\n const input = h('input', { type: 'radio', name, value: option.value });\n input.checked = current === option.value;\n if (disabled) input.disabled = true;\n input.addEventListener('change', () => {\n if (input.checked) {\n aitState.patch('notification', { nextResult: option.value });\n }\n });\n return h('label', { className: 'ait-row' }, input, h('span', {}, t(option.labelKey)));\n}\n\nexport function renderNotificationsTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('notifications.section.title')),\n ...RESULTS.map((opt) =>\n radioRow('ait-notification-result', s.notification.nextResult, opt, disabled),\n ),\n ),\n );\n return container;\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type { PermissionName, PermissionStatus } from '../../mock/types.js';\nimport { h, monitoringNotice, selectRow } from '../helpers.js';\n\nexport function renderPermissionsTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n const names: PermissionName[] = [\n 'camera',\n 'photos',\n 'geolocation',\n 'clipboard',\n 'contacts',\n 'microphone',\n ];\n const statuses: PermissionStatus[] = ['allowed', 'denied', 'notDetermined'];\n\n if (disabled) container.appendChild(monitoringNotice());\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('permissions.section.device')),\n ...names.map((name) =>\n selectRow(\n name,\n statuses,\n s.permissions[name],\n (v) => {\n aitState.patch('permissions', { [name]: v as PermissionStatus });\n },\n disabled,\n ),\n ),\n ),\n );\n return container;\n}\n","/**\n * 사용자 저장 preset CRUD. localStorage `__ait_preset:<id>` 한 키당 하나로 저장한다.\n * 패널-내부 storage(`__ait_btn_pos`, `__ait_device_id`, `__ait_storage:`)와 같은\n * 패턴 — 새 storage 도입 없음.\n *\n * 외부 의존성 0. SSR 환경(Node)에서 import만 되어도 안전하도록 모든 접근은\n * `localStorage` 존재 여부를 확인한다.\n */\n\nimport type { MockPreset, MockPresetState } from './presets.js';\n\nconst PREFIX = '__ait_preset:';\n\nfunction safeLocalStorage(): Storage | null {\n try {\n if (typeof localStorage === 'undefined') return null;\n return localStorage;\n } catch {\n return null;\n }\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/**\n * Storage에서 읽은 임의 JSON을 MockPreset으로 검증. id/label 필수, state는\n * object여야 함. 실패하면 null — caller가 storage entry를 무시하거나 정리하면 된다.\n *\n * `state`의 내부 키/값은 검증하지 않는다. `applyPreset`이 `pickKnownKeys`로\n * 키만 거른 뒤 그대로 state에 패치하므로 잘못된 enum 값이 통과될 수 있지만,\n * mock state라 보안 위협은 없다 — 새 enum 값이 추가됐을 때 저장된 preset을\n * reject하지 않으려는 의도.\n */\nfunction parsePreset(raw: string): MockPreset | null {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!isObject(parsed)) return null;\n const { id, label, description, state } = parsed;\n if (typeof id !== 'string' || id.length === 0) return null;\n if (typeof label !== 'string' || label.length === 0) return null;\n if (!isObject(state)) return null;\n return {\n id,\n label,\n description: typeof description === 'string' ? description : undefined,\n state: state as MockPresetState,\n };\n } catch {\n return null;\n }\n}\n\nexport function listUserPresets(): MockPreset[] {\n const ls = safeLocalStorage();\n if (!ls) return [];\n const out: MockPreset[] = [];\n for (let i = 0; i < ls.length; i++) {\n const key = ls.key(i);\n if (!key?.startsWith(PREFIX)) continue;\n const raw = ls.getItem(key);\n if (!raw) continue;\n const preset = parsePreset(raw);\n if (preset) out.push(preset);\n }\n return out.sort((a, b) => a.label.localeCompare(b.label));\n}\n\n/**\n * Preset을 저장한다. label에서 slug를 derive — 같은 slug가 이미 있으면 `-2`, `-3`\n * suffix를 붙여 새 entry를 만든다 (기존 entry 덮어쓰기 아님). UI는 label만 받으면 된다.\n *\n * Throws:\n * - label trim한 뒤 빈 문자열일 때\n * - localStorage 미가용 환경일 때 (SSR 등)\n * - `setItem` 실패 (`QuotaExceededError` 등) — caller가 처리해야 함\n */\nexport function saveUserPreset(\n label: string,\n state: MockPresetState,\n description?: string,\n): MockPreset {\n const trimmed = label.trim();\n if (trimmed.length === 0) {\n throw new Error('Preset label cannot be empty');\n }\n const ls = safeLocalStorage();\n if (!ls) throw new Error('localStorage not available');\n const id = generateId(trimmed, ls);\n const preset: MockPreset = {\n id,\n label: trimmed,\n state,\n ...(description !== undefined && description.length > 0 ? { description } : {}),\n };\n ls.setItem(PREFIX + id, JSON.stringify(preset));\n return preset;\n}\n\nexport function deleteUserPreset(id: string): void {\n const ls = safeLocalStorage();\n if (!ls) return;\n ls.removeItem(PREFIX + id);\n}\n\n/** 충돌 시 `-2`, `-3` 등 suffix를 붙여 unique한 id 만든다. */\nfunction generateId(label: string, ls: Storage): string {\n const base =\n label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 40) || 'preset';\n let candidate = base;\n let n = 2;\n while (ls.getItem(PREFIX + candidate) !== null) {\n candidate = `${base}-${n}`;\n n += 1;\n }\n return candidate;\n}\n","/**\n * Mock state preset library.\n *\n * 자주 쓰는 QA 시나리오(권한 거부, 오프라인, 미로그인 등)를 한 클릭으로 적용할 수 있는\n * preset 정의 + apply 유틸. 토글 한 번에 여러 mock 키가 동시에 일정 상태가 되어야 하는\n * 경우 매번 손으로 맞추는 번잡함을 제거한다.\n *\n * Preset state는 `aitState`의 일부만 다룬다 — preset이 어떤 키도 건드리지 않으면 해당\n * 키는 현재 값을 유지한다. forward-compat: schema 외 키가 들어와도 무시된다.\n */\n\nimport type { AitDevtoolsState } from './state.js';\nimport { aitState } from './state.js';\n\n/**\n * Preset이 덮어쓸 수 있는 mock state slice. 모든 키는 optional —\n * 한 preset이 모든 분야를 정의할 필요 없다.\n *\n * 일부러 좁게 잡았다: viewport / brand / mockData / analyticsLog 등 QA 시나리오와\n * 직접 관련 없는 영역은 preset 대상에서 제외한다 (preset 적용으로 unrelated state가\n * 흔들리는 사고 방지). 필요해지면 추가.\n *\n * `iap` slice는 일부러 `nextResult`만 노출한다 — products / pendingOrders /\n * completedOrders는 array/object 비교가 까다롭고 QA 시나리오에서 강제할 일이 거의\n * 없다. `captureCurrentState` / `matchesPreset` 의 iap 처리 범위와 동기화.\n */\nexport interface MockPresetState {\n networkStatus?: AitDevtoolsState['networkStatus'];\n permissions?: Partial<AitDevtoolsState['permissions']>;\n auth?: Partial<AitDevtoolsState['auth']>;\n iap?: { nextResult?: AitDevtoolsState['iap']['nextResult'] };\n ads?: Partial<AitDevtoolsState['ads']>;\n payment?: Partial<AitDevtoolsState['payment']>;\n}\n\nexport interface MockPreset {\n id: string;\n label: string;\n description?: string;\n state: MockPresetState;\n}\n\nexport const builtInPresets: readonly MockPreset[] = [\n {\n id: 'all-allowed',\n label: 'All allowed (default-ish)',\n description: '모든 권한 허용, WIFI, 로그인됨, IAP success',\n state: {\n networkStatus: 'WIFI',\n permissions: {\n camera: 'allowed',\n photos: 'allowed',\n geolocation: 'allowed',\n clipboard: 'allowed',\n contacts: 'allowed',\n microphone: 'allowed',\n },\n auth: { isLoggedIn: true },\n iap: { nextResult: 'success' },\n ads: { forceNoFill: false },\n payment: { nextResult: 'success', failReason: '' },\n },\n },\n {\n id: 'permission-denied',\n label: 'Permissions denied',\n description: 'camera / photos / geolocation / contacts 거부',\n state: {\n permissions: {\n camera: 'denied',\n photos: 'denied',\n geolocation: 'denied',\n contacts: 'denied',\n },\n },\n },\n {\n id: 'offline',\n label: 'Offline',\n description: 'getNetworkStatus → OFFLINE, IAP NETWORK_ERROR',\n state: {\n networkStatus: 'OFFLINE',\n iap: { nextResult: 'NETWORK_ERROR' },\n payment: { nextResult: 'fail', failReason: 'NETWORK_ERROR' },\n },\n },\n {\n id: 'logged-out',\n label: 'Logged out',\n description: 'auth.isLoggedIn=false. login flow 검증용',\n state: {\n auth: { isLoggedIn: false },\n },\n },\n {\n id: 'iap-pending',\n label: 'IAP payment pending',\n description: '결제 진행 중 분기 검증',\n state: {\n iap: { nextResult: 'PAYMENT_PENDING' },\n },\n },\n {\n id: 'ads-no-fill',\n label: 'Ads — no fill',\n description: '광고 fill 실패 분기 검증',\n state: {\n networkStatus: 'WIFI',\n ads: { forceNoFill: true },\n },\n },\n];\n\n/**\n * Preset의 nested slice를 검증된 키만 골라서 풀어낸다. Forward-compat 차원에서\n * 알지 못하는 키는 drop, drop된 키 전부를 모아 한 번에 warn한다.\n *\n * Value 단위 검증은 하지 않는다 — `permissions.camera`에 enum 외 값이 들어와도\n * 그대로 통과한다. mock state라 잘못된 값은 mock 함수 분기 결과만 흔든다.\n * 새 enum 값이 추가됐을 때 저장된 preset을 reject하지 않으려는 의도.\n */\nfunction pickKnownKeys<T extends object>(\n input: unknown,\n allowed: readonly (keyof T)[],\n): Partial<T> {\n if (typeof input !== 'object' || input === null) return {};\n const out: Partial<T> = {};\n const dropped: string[] = [];\n for (const [key, value] of Object.entries(input)) {\n if ((allowed as readonly string[]).includes(key)) {\n (out as Record<string, unknown>)[key] = value;\n } else {\n dropped.push(key);\n }\n }\n if (dropped.length > 0) {\n console.warn(`[@ait-co/devtools] Preset dropped unknown keys: ${dropped.join(', ')}`);\n }\n return out;\n}\n\nconst PERMISSION_KEYS = [\n 'camera',\n 'photos',\n 'geolocation',\n 'clipboard',\n 'contacts',\n 'microphone',\n] as const;\nconst AUTH_KEYS = ['isLoggedIn', 'isTossLoginIntegrated', 'userKeyHash'] as const;\nconst IAP_KEYS = ['nextResult'] as const;\nconst ADS_KEYS = ['isLoaded', 'nextEvent', 'forceNoFill', 'lastEvent'] as const;\nconst PAYMENT_KEYS = ['nextResult', 'failReason'] as const;\n\n/**\n * Preset state를 현재 `aitState`에 적용한다. 정의된 키만 덮어쓰고, 알지 못하는 키는\n * 조용히 drop한다 (한 번 warn). 여러 슬라이스를 적용해도 listener notify는 한 번이다\n * (`aitState.transaction` 사용 — panel re-render 폭주 방지).\n */\nexport function applyPreset(state: MockPresetState): void {\n aitState.transaction(() => {\n if (state.networkStatus !== undefined) {\n aitState.update({ networkStatus: state.networkStatus });\n }\n if (state.permissions !== undefined) {\n aitState.patch(\n 'permissions',\n pickKnownKeys<AitDevtoolsState['permissions']>(state.permissions, PERMISSION_KEYS),\n );\n }\n if (state.auth !== undefined) {\n aitState.patch('auth', pickKnownKeys<AitDevtoolsState['auth']>(state.auth, AUTH_KEYS));\n }\n if (state.iap !== undefined) {\n const picked = pickKnownKeys<{ nextResult: AitDevtoolsState['iap']['nextResult'] }>(\n state.iap,\n IAP_KEYS,\n );\n aitState.patch('iap', picked);\n }\n if (state.ads !== undefined) {\n aitState.patch('ads', pickKnownKeys<AitDevtoolsState['ads']>(state.ads, ADS_KEYS));\n }\n if (state.payment !== undefined) {\n aitState.patch(\n 'payment',\n pickKnownKeys<AitDevtoolsState['payment']>(state.payment, PAYMENT_KEYS),\n );\n }\n });\n}\n\n/**\n * Preset의 모든 정의된 슬라이스가 현재 state와 일치하는지 검사. UI에서 dirty\n * indicator를 그릴 때 쓴다.\n *\n * 일치한다 = preset이 정의한 키 전부가 그대로다. preset이 정의하지 않은 키는\n * 비교 대상이 아니다 — preset은 partial이므로 다른 토글이 바뀌어도 dirty가 아니다.\n */\nexport function matchesPreset(snapshot: AitDevtoolsState, preset: MockPresetState): boolean {\n if (preset.networkStatus !== undefined && snapshot.networkStatus !== preset.networkStatus) {\n return false;\n }\n if (preset.permissions !== undefined) {\n for (const k of PERMISSION_KEYS) {\n const want = preset.permissions[k];\n if (want !== undefined && snapshot.permissions[k] !== want) return false;\n }\n }\n if (preset.auth !== undefined) {\n for (const k of AUTH_KEYS) {\n const want = preset.auth[k];\n if (want !== undefined && snapshot.auth[k] !== want) return false;\n }\n }\n if (preset.iap !== undefined) {\n if (preset.iap.nextResult !== undefined && snapshot.iap.nextResult !== preset.iap.nextResult) {\n return false;\n }\n }\n if (preset.ads !== undefined) {\n if (preset.ads.forceNoFill !== undefined && snapshot.ads.forceNoFill !== preset.ads.forceNoFill)\n return false;\n if (preset.ads.isLoaded !== undefined && snapshot.ads.isLoaded !== preset.ads.isLoaded)\n return false;\n if (preset.ads.nextEvent !== undefined && snapshot.ads.nextEvent !== preset.ads.nextEvent)\n return false;\n }\n if (preset.payment !== undefined) {\n for (const k of PAYMENT_KEYS) {\n const want = preset.payment[k];\n if (want !== undefined && snapshot.payment[k] !== want) return false;\n }\n }\n return true;\n}\n\n/**\n * 현재 state에서 preset에 저장할 만한 슬라이스를 추출. \"save current as preset\"에서 쓴다.\n */\nexport function captureCurrentState(snapshot: AitDevtoolsState): MockPresetState {\n return {\n networkStatus: snapshot.networkStatus,\n permissions: { ...snapshot.permissions },\n auth: {\n isLoggedIn: snapshot.auth.isLoggedIn,\n isTossLoginIntegrated: snapshot.auth.isTossLoginIntegrated,\n userKeyHash: snapshot.auth.userKeyHash,\n },\n iap: { nextResult: snapshot.iap.nextResult },\n ads: {\n forceNoFill: snapshot.ads.forceNoFill,\n isLoaded: snapshot.ads.isLoaded,\n nextEvent: snapshot.ads.nextEvent,\n },\n payment: { ...snapshot.payment },\n };\n}\n","import { t } from '../../i18n/index.js';\nimport { deleteUserPreset, listUserPresets, saveUserPreset } from '../../mock/preset-store.js';\nimport {\n applyPreset,\n builtInPresets,\n captureCurrentState,\n type MockPreset,\n matchesPreset,\n} from '../../mock/presets.js';\nimport { type AitDevtoolsState, aitState } from '../../mock/state.js';\nimport { h, monitoringNotice } from '../helpers.js';\n\nexport function renderPresetsTab(refreshPanel: () => void): HTMLElement {\n const disabled = !aitState.state.panelEditable;\n const container = h('div');\n if (disabled) container.appendChild(monitoringNotice());\n\n const userPresets = listUserPresets();\n const snapshot = aitState.state;\n\n container.append(\n renderSection(\n t('presets.section.builtIn'),\n builtInPresets,\n disabled,\n snapshot,\n refreshPanel,\n false,\n ),\n );\n\n container.append(\n renderSection(\n t('presets.section.saved', { count: userPresets.length }),\n userPresets,\n disabled,\n snapshot,\n refreshPanel,\n true,\n ),\n );\n\n // Save current state as preset\n const saveBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('presets.btn.saveCurrent'));\n if (disabled) saveBtn.disabled = true;\n saveBtn.addEventListener('click', () => {\n const label = window.prompt(t('presets.prompt.label'));\n if (label === null) return;\n try {\n saveUserPreset(label, captureCurrentState(aitState.state));\n } catch (err) {\n window.alert((err as Error).message);\n return;\n }\n refreshPanel();\n });\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('presets.section.save')),\n h(\n 'div',\n { style: 'color:#888;font-size:11px;margin-bottom:6px' },\n t('presets.save.description'),\n ),\n saveBtn,\n ),\n );\n\n return container;\n}\n\nfunction renderSection(\n title: string,\n presets: readonly MockPreset[],\n disabled: boolean,\n snapshot: AitDevtoolsState,\n refreshPanel: () => void,\n deletable: boolean,\n): HTMLElement {\n const section = h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, title),\n );\n\n if (presets.length === 0) {\n section.append(\n h(\n 'div',\n { style: 'color:#555;font-size:12px' },\n deletable ? t('presets.empty.saved') : t('presets.empty.builtIn'),\n ),\n );\n return section;\n }\n\n for (const preset of presets) {\n const isActive = matchesPreset(snapshot, preset.state);\n\n const labelEl = h(\n 'span',\n { className: 'ait-preset-label' },\n isActive ? `✓ ${preset.label}` : preset.label,\n );\n\n const applyBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm' },\n isActive ? t('presets.btn.reApply') : t('presets.btn.apply'),\n );\n if (disabled) applyBtn.disabled = true;\n applyBtn.addEventListener('click', () => {\n applyPreset(preset.state);\n refreshPanel();\n });\n\n const buttons: Node[] = [applyBtn];\n if (deletable) {\n const delBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger' },\n t('presets.btn.delete'),\n );\n if (disabled) delBtn.disabled = true;\n delBtn.addEventListener('click', () => {\n if (!window.confirm(t('presets.confirm.delete', { label: preset.label }))) return;\n deleteUserPreset(preset.id);\n refreshPanel();\n });\n buttons.push(delBtn);\n }\n\n const actions = h('span', { className: 'ait-preset-actions' }, ...buttons);\n\n const row = h(\n 'div',\n { className: `ait-preset-row${isActive ? ' ait-preset-active' : ''}` },\n labelEl,\n actions,\n );\n section.append(row);\n\n if (preset.description) {\n section.append(h('div', { className: 'ait-preset-description' }, preset.description));\n }\n }\n\n return section;\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport { h, monitoringNotice } from '../helpers.js';\n\nexport function renderStorageTab(refreshPanel: () => void): HTMLElement {\n const disabled = !aitState.state.panelEditable;\n const container = h('div');\n if (disabled) container.appendChild(monitoringNotice());\n const prefix = '__ait_storage:';\n const entries: Array<[string, string]> = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(prefix)) {\n entries.push([key.slice(prefix.length), localStorage.getItem(key) ?? '']);\n }\n }\n\n const clearBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger' },\n t('storage.btn.clearAll'),\n );\n if (disabled) clearBtn.disabled = true;\n clearBtn.addEventListener('click', () => {\n for (const [key] of entries) {\n localStorage.removeItem(prefix + key);\n }\n refreshPanel();\n });\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-row' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('storage.section.title', { count: entries.length }),\n ),\n clearBtn,\n ),\n entries.length === 0\n ? h('div', { style: 'color:#555;font-size:12px' }, t('storage.empty'))\n : h(\n 'div',\n {},\n ...entries.map(([key, value]) =>\n h(\n 'div',\n { className: 'ait-storage-row' },\n h('span', { className: 'ait-storage-key' }, key),\n h(\n 'span',\n { className: 'ait-storage-value' },\n value.length > 100 ? `${value.slice(0, 100)}...` : value,\n ),\n ),\n ),\n ),\n ),\n );\n return container;\n}\n","/**\n * 화면/네비게이션/이벤트 mock\n */\n\nimport { getNetworkStatusByMode } from '../device/index.js';\nimport { observe } from '../observe.js';\nimport { aitState } from '../state.js';\nimport type { NetworkStatus } from '../types.js';\n\nexport async function closeView(): Promise<void> {\n console.log('[@ait-co/devtools] closeView called');\n window.history.back();\n}\n\nexport async function openURL(url: string): Promise<void> {\n console.log('[@ait-co/devtools] openURL:', url);\n window.open(url, '_blank');\n}\n\nexport async function share(message: { message: string }): Promise<void> {\n if (navigator.share) {\n await navigator.share({ text: message.message });\n return;\n }\n console.log('[@ait-co/devtools] share:', message.message);\n}\n\nexport async function getTossShareLink(path: string, _ogImageUrl?: string): Promise<string> {\n return `https://toss.im/share/mock${path}`;\n}\n\nexport async function setIosSwipeGestureEnabled(options: { isEnabled: boolean }): Promise<void> {\n console.log('[@ait-co/devtools] setIosSwipeGestureEnabled:', options.isEnabled);\n // real(토스 WebView)에선 이 호출이 native bridge로 발화한다(devtools#171 실측). mock은\n // 그 \"마지막 호출값\"을 관측 가능한 state로 mirror해, toss-gated 가드(예: sdk-example\n // useDisableIosSwipeGestureInToss)가 실제로 돌았는지를 AIT.getMockState로 대조할 수 있게 한다.\n aitState.patch('navigation', { iosSwipeGestureEnabled: options.isEnabled });\n}\n\nexport async function setDeviceOrientation(options: {\n type: 'portrait' | 'landscape';\n}): Promise<void> {\n const current = aitState.state.viewport.orientation;\n if (current === 'auto') {\n console.log('[@ait-co/devtools] setDeviceOrientation:', options.type);\n // appOrientation은 Panel이 'auto'일 때 effective orientation을 결정하는 별도 필드.\n // viewport.orientation은 사용자 의도이므로 SDK가 임의로 덮어쓰지 않는다 — 그래야\n // 앱이 같은 방향으로 여러 번 호출해도 매번 정상 반영된다.\n aitState.patch('viewport', { appOrientation: options.type });\n return;\n }\n console.warn(\n `[@ait-co/devtools] setDeviceOrientation(${options.type}) ignored — Panel is forcing \"${current}\". Change the Viewport tab's orientation to \"auto\" to let the app control rotation.`,\n );\n}\n\nexport const setScreenAwakeMode = observe(\n 'setScreenAwakeMode',\n 'inert',\n async (options: { enabled: boolean }): Promise<{ enabled: boolean }> => {\n console.log('[@ait-co/devtools] setScreenAwakeMode:', options.enabled);\n return { enabled: options.enabled };\n },\n);\n\nexport const setSecureScreen = observe(\n 'setSecureScreen',\n 'inert',\n async (options: { enabled: boolean }): Promise<{ enabled: boolean }> => {\n console.log('[@ait-co/devtools] setSecureScreen:', options.enabled);\n return { enabled: options.enabled };\n },\n);\n\nconst _requestReviewImpl = observe('requestReview', 'inert', async (): Promise<void> => {\n console.log('[@ait-co/devtools] requestReview called');\n});\nexport const requestReview: typeof _requestReviewImpl & { isSupported: () => boolean } =\n _requestReviewImpl as typeof _requestReviewImpl & { isSupported: () => boolean };\nrequestReview.isSupported = () => true;\n\n// --- 환경 정보 ---\n\nexport function getPlatformOS(): 'ios' | 'android' {\n return aitState.state.platform;\n}\n\nexport function getOperationalEnvironment(): 'toss' | 'sandbox' {\n return aitState.state.environment;\n}\n\nexport function getTossAppVersion(): string {\n return aitState.state.appVersion;\n}\n\nexport function isMinVersionSupported(minVersions: { android: string; ios: string }): boolean {\n const platform = aitState.state.platform;\n const required = platform === 'ios' ? minVersions.ios : minVersions.android;\n if (required === 'always') return true;\n if (required === 'never') return false;\n\n const current = aitState.state.appVersion.split('.').map(Number);\n const min = required.split('.').map(Number);\n for (let i = 0; i < 3; i++) {\n if ((current[i] ?? 0) > (min[i] ?? 0)) return true;\n if ((current[i] ?? 0) < (min[i] ?? 0)) return false;\n }\n return true; // equal\n}\n\nexport function getSchemeUri(): string {\n return aitState.state.schemeUri || window.location.pathname;\n}\n\nexport function getLocale(): string {\n return aitState.state.locale;\n}\n\nexport function getDeviceId(): string {\n return aitState.state.deviceId;\n}\n\nexport function getGroupId(): string {\n return aitState.state.groupId;\n}\n\nexport async function getNetworkStatus(): Promise<NetworkStatus> {\n const modeResult = getNetworkStatusByMode();\n if (modeResult) return modeResult;\n return aitState.state.networkStatus;\n}\n\nexport async function getServerTime(): Promise<number | undefined> {\n return Date.now();\n}\n(getServerTime as unknown as { isSupported: () => boolean }).isSupported = () => true;\n\n// --- 이벤트 시스템 ---\n\ninterface GraniteEventMap {\n backEvent: { onEvent: () => void; onError?: (error: Error) => void; options?: undefined };\n homeEvent: { onEvent: () => void; onError?: (error: Error) => void; options?: undefined };\n}\n\nexport const graniteEvent = {\n addEventListener<K extends keyof GraniteEventMap>(\n event: K,\n {\n onEvent,\n onError,\n }: {\n onEvent: GraniteEventMap[K]['onEvent'];\n onError?: GraniteEventMap[K]['onError'];\n options?: GraniteEventMap[K]['options'];\n },\n ): () => void {\n const handler = () => {\n try {\n onEvent();\n } catch (e) {\n onError?.(e instanceof Error ? e : new Error(String(e)));\n }\n };\n window.addEventListener(`__ait:${event}`, handler);\n return () => window.removeEventListener(`__ait:${event}`, handler);\n },\n};\n\nexport const appsInTossEvent = {\n addEventListener<K extends string>(\n _event: K,\n _handlers: {\n onEvent: (...args: unknown[]) => void;\n onError?: (error: Error) => void;\n options?: unknown;\n },\n ): () => void {\n return () => {};\n },\n};\n\ninterface TdsEventMap {\n navigationAccessoryEvent: {\n onEvent: (data: { id: string }) => void;\n onError?: (error: Error) => void;\n options: undefined;\n };\n}\n\nexport const tdsEvent = {\n addEventListener<K extends keyof TdsEventMap>(\n event: K,\n {\n onEvent,\n }: {\n onEvent: TdsEventMap[K]['onEvent'];\n onError?: TdsEventMap[K]['onError'];\n options?: TdsEventMap[K]['options'];\n },\n ): () => void {\n const handler = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n onEvent(detail);\n };\n window.addEventListener(`__ait:${event}`, handler);\n return () => window.removeEventListener(`__ait:${event}`, handler);\n },\n};\n\nexport function onVisibilityChangedByTransparentServiceWeb(eventParams: {\n options: { callbackId: string };\n onEvent: (isVisible: boolean) => void;\n onError: (error: unknown) => void;\n}): () => void {\n const handler = () => eventParams.onEvent(!document.hidden);\n document.addEventListener('visibilitychange', handler);\n return () => document.removeEventListener('visibilitychange', handler);\n}\n\n// --- env / globals ---\n\nexport const env = {\n getDeploymentId: () => aitState.state.deploymentId,\n};\n\nexport function getAppsInTossGlobals() {\n return {\n deploymentId: aitState.state.deploymentId,\n brandDisplayName: aitState.state.brand.displayName,\n brandIcon: aitState.state.brand.icon,\n brandPrimaryColor: aitState.state.brand.primaryColor,\n };\n}\n\n// --- SafeAreaInsets ---\n\ntype SafeAreaInsetsValue = { top: number; bottom: number; left: number; right: number };\ntype SafeAreaInsetsSubscribeHandler = { onEvent: (data: SafeAreaInsetsValue) => void };\n\nexport const SafeAreaInsets = {\n get: (): SafeAreaInsetsValue => ({ ...aitState.state.safeAreaInsets }),\n // NOTE: aitState.subscribe에 위임하므로 safeAreaInsets 외 상태 변경에도 콜백이 호출된다.\n // 실제 SDK는 insets 변경 시에만 호출되지만, mock에서는 간소화를 위해 필터링하지 않는다.\n subscribe: ({ onEvent }: SafeAreaInsetsSubscribeHandler): (() => void) => {\n return aitState.subscribe(() => onEvent({ ...aitState.state.safeAreaInsets }));\n },\n};\n\n/** @deprecated */\nexport function getSafeAreaInsets(): number {\n return aitState.state.safeAreaInsets.top;\n}\n","/**\n * 기기 preset ↔ 브라우저 특성 정합\n *\n * Viewport preset이 active일 때(`none`/`custom` 아님), 그 preset이 주장하는 기기와\n * `navigator.userAgent`·`navigator.platform`·`window.devicePixelRatio`·`screen.*`를\n * 일치시킨다. 특정 기기 frame 가상환경을 제공하는 이상 UA/DPR만 호스트 데스크톱 값으로\n * 남으면 비일관적이기 때문이다 (#190).\n *\n * 한계 — page-JS override는 **JS 읽기값만** 바꾼다. 실 CSS media query(`@media`),\n * 실제 터치 이벤트, 엔진 레벨 레이아웃은 호스트 브라우저 값이 그대로다. 픽셀/입력 단위\n * 완전 emulation이 필요하면 Chrome DevTools device-mode(또는 CDP)를 쓴다. preset이\n * `none`/`custom`이면 override를 걸지 않아 일반 dev의 호스트 환경을 건드리지 않는다.\n *\n * 구현 — 대상 속성은 setter가 없어도 `configurable: true`이므로 `Object.defineProperty`로\n * getter를 덮어쓸 수 있다. 원복을 위해 최초 override 직전의 디스크립터를 저장한다.\n */\n\nimport { aitState } from '../mock/state.js';\nimport type { PlatformOS, ViewportPreset } from '../mock/types.js';\n\n/** preset id → 플랫폼. Apple 계열은 ios, 그 외(Galaxy)는 android. */\nexport function platformForPreset(presetId: string): PlatformOS {\n return presetId.startsWith('iphone') || presetId.startsWith('ipad') ? 'ios' : 'android';\n}\n\nexport interface DeviceProfile {\n platform: PlatformOS;\n userAgent: string;\n navigatorPlatform: string;\n devicePixelRatio: number;\n /** 세로 기준 물리 해상도 (CSS px × dpr). landscape면 swap해서 적용. */\n screenWidth: number;\n screenHeight: number;\n}\n\n/**\n * preset + 토스 앱 버전으로 기기 프로필을 합성한다.\n *\n * UA는 표준 모바일 UA 뒤에 `AppsInToss TossApp/<appVersion>` 토큰을 붙인다 — #171\n * 실측(`AppsInToss TossApp/5.261.0`)에서 확인된 토스 WebView UA 형태.\n *\n * @param preset portrait 기준 width/height/dpr를 가진 device preset\n * @param appVersion `aitState.state.appVersion` (UA suffix의 버전 토큰)\n * @param landscape true면 screen width/height를 swap\n */\nexport function buildDeviceProfile(\n preset: ViewportPreset,\n appVersion: string,\n landscape: boolean,\n): DeviceProfile {\n const platform = platformForPreset(preset.id);\n const tossToken = `AppsInToss TossApp/${appVersion}`;\n\n const baseUa =\n platform === 'ios'\n ? // iOS Safari WebView 형태 (iOS 17 계열 — 토스 WebView가 보고하는 라인과 정합).\n 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) ' +\n 'AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148'\n : // Android Chrome WebView 형태.\n 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) ' +\n 'Chrome/120.0.0.0 Mobile Safari/537.36';\n\n const physWidth = Math.round(preset.width * preset.dpr);\n const physHeight = Math.round(preset.height * preset.dpr);\n\n return {\n platform,\n userAgent: `${baseUa} ${tossToken}`,\n navigatorPlatform: platform === 'ios' ? 'iPhone' : 'Linux armv8l',\n devicePixelRatio: preset.dpr,\n screenWidth: landscape ? physHeight : physWidth,\n screenHeight: landscape ? physWidth : physHeight,\n };\n}\n\n// --- override apply / revert ---------------------------------------------\n\ninterface SavedDescriptor {\n target: object;\n prop: string;\n descriptor: PropertyDescriptor | undefined;\n}\n\nlet savedDescriptors: SavedDescriptor[] | null = null;\n\nfunction override(target: object, prop: string, value: unknown, saved: SavedDescriptor[]): void {\n // 같은 override 세션에서 이미 저장했으면 다시 저장하지 않는다 (원본 보존).\n if (!saved.some((s) => s.target === target && s.prop === prop)) {\n saved.push({ target, prop, descriptor: Object.getOwnPropertyDescriptor(target, prop) });\n }\n try {\n Object.defineProperty(target, prop, {\n configurable: true,\n get: () => value,\n });\n } catch {\n // 일부 환경(엄격한 jsdom 등)에서 redefine이 막히면 조용히 건너뛴다.\n }\n}\n\n/**\n * 기기 프로필을 현재 페이지의 `navigator`/`window`/`screen`에 적용한다.\n * 호출 시 직전 디스크립터를 저장하므로 `revertDeviceEmulation()`으로 원복 가능.\n */\nexport function applyDeviceEmulation(profile: DeviceProfile): void {\n if (typeof navigator === 'undefined' || typeof window === 'undefined') return;\n // 이전 override가 남아 있으면 먼저 원복하고 새로 적용 (preset 전환 시 누적 방지).\n revertDeviceEmulation();\n\n const saved: SavedDescriptor[] = [];\n override(navigator, 'userAgent', profile.userAgent, saved);\n override(navigator, 'platform', profile.navigatorPlatform, saved);\n override(window, 'devicePixelRatio', profile.devicePixelRatio, saved);\n if (typeof screen !== 'undefined') {\n override(screen, 'width', profile.screenWidth, saved);\n override(screen, 'height', profile.screenHeight, saved);\n }\n savedDescriptors = saved;\n}\n\n/** `applyDeviceEmulation`이 덮어쓴 속성을 원래 디스크립터로 되돌린다. */\nexport function revertDeviceEmulation(): void {\n if (!savedDescriptors) return;\n for (const { target, prop, descriptor } of savedDescriptors) {\n try {\n if (descriptor) {\n Object.defineProperty(target, prop, descriptor);\n } else {\n // 원래 own descriptor가 없었으면 (프로토타입 상속 getter) own override만 제거.\n delete (target as Record<string, unknown>)[prop];\n }\n } catch {\n // redefine/delete 실패는 조용히 무시 — best-effort 원복.\n }\n }\n savedDescriptors = null;\n}\n\n/**\n * Viewport state를 받아 preset이 active면 emulation 적용, 아니면 원복.\n * `applyViewport`가 매 viewport 변경마다 호출한다.\n */\nexport function syncDeviceEmulation(preset: ViewportPreset | null, landscape: boolean): void {\n if (!preset || preset.id === 'none' || preset.id === 'custom') {\n revertDeviceEmulation();\n return;\n }\n const profile = buildDeviceProfile(preset, aitState.state.appVersion, landscape);\n applyDeviceEmulation(profile);\n // SDK 계약값 정합: getPlatformOS()는 aitState.platform을 읽으므로 함께 끌고 간다.\n // 값이 실제로 바뀔 때만 update — applyViewport는 viewport subscriber 안에서 호출되므로\n // 무조건 update하면 재진입 루프가 된다 (syncSafeAreaFromViewport와 같은 idempotent 규약).\n if (aitState.state.platform !== profile.platform) {\n aitState.update({ platform: profile.platform });\n }\n}\n","/**\n * Viewport 시뮬레이션 유틸\n *\n * Panel에서 선택한 디바이스 프리셋을 `document.body`에 적용한다. 정적 CSS는\n * `panel/styles.ts`에 정의되어 있고 (Panel mount 시 head에 주입), 여기서는 프리셋별\n * 동적 값(width/height, 콘텐츠 push용 body padding-top)만 별도 `<style>` 엘리먼트로\n * 관리한다.\n */\n\nimport { closeView } from '../mock/navigation/index.js';\nimport { aitState } from '../mock/state.js';\nimport type {\n AitNavBarType,\n AppOrientation,\n LandscapeSide,\n SafeAreaInsets,\n SafeAreaProvenance,\n ViewportOrientation,\n ViewportPreset,\n ViewportPresetId,\n ViewportState,\n} from '../mock/types.js';\nimport { revertDeviceEmulation, syncDeviceEmulation } from './device-emulation.js';\nimport { h } from './helpers.js';\n\nexport const VIEWPORT_STORAGE_KEY = '__ait_viewport';\n\n/** Custom width/height의 안전 상한 (CSS px). 4K + 여유. */\nexport const VIEWPORT_CUSTOM_MAX = 4096;\n\n/**\n * Apps in Toss host nav bar 높이 (CSS px), `partner` type 기준.\n *\n * iPhone 15 Pro on-device relay 실측값(devtools#190): `SafeAreaInsets.get().top`이\n * **54 px**를 반환했고, 같은 시점 `env(safe-area-inset-top)`은 0이었다. 즉 SDK가 top으로\n * 주는 값은 OS 노치 inset이 아니라 토스 네이티브 nav bar 높이 그 자체다 — nav bar는 호스트\n * chrome이라 기기에 무관하므로 모든 preset이 이 단일 상수를 공유한다(이전 추정치 48은 폐기).\n *\n * type별 동작:\n * - `partner` (기본): nav bar가 콘텐츠를 밀어내므로 SDK top = 이 값.\n * - `game`: nav bar가 투명 오버레이라 콘텐츠를 밀어내지 않음(인게임 full-screen이 출시 요건)\n * → SDK top = 0. `external` type은 아직 시뮬레이션하지 않는다.\n *\n * landscape에서의 nav bar 거동은 아직 실측하지 못해 portrait 모델만 확정이다(landscape는\n * 노치를 측면 inset으로 돌리고 top=0 유지 — 후속 실측 대상).\n */\nexport const AIT_NAV_BAR_HEIGHT_PARTNER = 54;\n\nconst NONE_PRESET: ViewportPreset = {\n id: 'none',\n label: 'None (full window)',\n width: 0,\n height: 0,\n dpr: 1,\n notch: 'none',\n notchInset: 0,\n navBarHeight: 0,\n safeAreaBottom: 0,\n};\n\nconst CUSTOM_PRESET: ViewportPreset = {\n id: 'custom',\n label: 'Custom',\n width: 0,\n height: 0,\n dpr: 1,\n notch: 'none',\n notchInset: 0,\n navBarHeight: 0,\n safeAreaBottom: 0,\n};\n\n/** Shorthands used when building preset provenance entries. */\nconst EXTRAPOLATED: SafeAreaProvenance = { source: 'extrapolated' };\nconst PLACEHOLDER: SafeAreaProvenance = { source: 'placeholder' };\n\n/**\n * Device presets (2026). CSS viewport 크기는 실제 기기의 `window.innerWidth/innerHeight`.\n * iPhone 17 시리즈는 2025-09 출시. iPhone Air는 2026-04 출시.\n * Galaxy S26 시리즈는 2026-03-11 출시 — viewport 값은 phone-simulator.com에서 보고된\n * 측정치를 사용.\n *\n * safe-area 모델 (devtools#190 relay 실측 반영):\n * - `notchInset` = OS 노치/status bar inset. 기기별 물리값(landscape 측면 inset + 시각\n * 노치 오버레이용). iPhone 15 Pro 실측에서 `env(safe-area-inset-top)`은 0이었으므로 이\n * 값은 portrait SDK top에는 들어가지 않는다.\n * - `navBarHeight` = 토스 호스트 nav bar 높이. partner type portrait의 SDK `top`(실측 54).\n * 호스트 chrome이라 기기 무관 — 전 preset이 `AIT_NAV_BAR_HEIGHT_PARTNER` 공유.\n * - `safeAreaBottom` = home-indicator inset. 기기별(노치 iPhone 34, 홈버튼/Android 0).\n * iPhone 15 Pro 실측 bottom 34와 일치.\n *\n * 단, navBarHeight 54는 iOS partner에서만 실측됐다 — Android nav bar 높이와 game type\n * 미세 차이는 후속 실측 대상(현재는 같은 값을 잠정 적용).\n *\n * iPhone 17과 17 Pro는 CSS viewport / DPR / safe area가 동일 — 이는 의도이며 카피-페이스트\n * 실수가 아니다. Apple의 17 lineup은 base와 Pro의 web-relevant 스펙이 같다.\n *\n * safeAreaProvenance: 각 preset의 safe-area 값 신뢰도 출처.\n * - `measured` — relay 실기기 세션(measure_safe_area)으로 직접 확인한 값.\n * 현재 iPhone 15 Pro portrait iOS partner만 해당 (devtools#190).\n * - `extrapolated` — Apple 스펙/같은 시리즈 기기에서 유추한 값.\n * - `placeholder` — 연결 기기 없이 추정한 값. QA ground truth로 쓰지 말 것.\n * `measure_safe_area` MCP 툴로 relay 세션에서 `measured`로 승급 필요.\n */\nexport const VIEWPORT_PRESETS: ViewportPreset[] = [\n NONE_PRESET,\n // Apple\n {\n id: 'iphone-se-3',\n label: 'iPhone SE (3rd gen)',\n width: 375,\n height: 667,\n dpr: 2,\n notch: 'none',\n notchInset: 20,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n // SE 3는 홈버튼 기기 — OS 노치 없고 home indicator도 없음. bottom=0은 확정.\n // navBarHeight는 iOS partner 실측(54)에서 기기 무관 상수. extrapolated.\n safeAreaProvenance: EXTRAPOLATED,\n },\n {\n id: 'iphone-15-pro',\n label: 'iPhone 15 Pro',\n width: 393,\n height: 852,\n dpr: 3,\n notch: 'dynamic-island',\n notchInset: 59,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n // devtools#190 relay 실측: iOS partner portrait에서 navBarHeight=54, bottom=34 확인.\n safeAreaProvenance: { source: 'measured', device: 'iPhone 15 Pro', date: '2026-05-25' },\n },\n {\n id: 'iphone-16e',\n label: 'iPhone 16e',\n width: 390,\n height: 844,\n dpr: 3,\n notch: 'notch',\n notchInset: 47,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n // 16e는 notch 기기. bottom 34는 Apple 스펙에서 유추. 실측 미진행.\n safeAreaProvenance: EXTRAPOLATED,\n },\n {\n id: 'iphone-17',\n label: 'iPhone 17',\n width: 402,\n height: 874,\n dpr: 3,\n notch: 'dynamic-island',\n notchInset: 59,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n // 17 시리즈 Dynamic Island — bottom 34 Apple 스펙 유추. 실측 미진행.\n safeAreaProvenance: EXTRAPOLATED,\n },\n {\n id: 'iphone-air',\n label: 'iPhone Air',\n width: 420,\n height: 912,\n dpr: 3,\n notch: 'dynamic-island',\n notchInset: 59,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n // iPhone Air (2026-04 출시) — Dynamic Island 유추. 실측 미진행.\n safeAreaProvenance: EXTRAPOLATED,\n },\n {\n id: 'iphone-17-pro',\n label: 'iPhone 17 Pro',\n width: 402,\n height: 874,\n dpr: 3,\n notch: 'dynamic-island',\n notchInset: 59,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n // 17 Pro — 17와 web-relevant 스펙 동일. 유추.\n safeAreaProvenance: EXTRAPOLATED,\n },\n {\n id: 'iphone-17-pro-max',\n label: 'iPhone 17 Pro Max',\n width: 440,\n height: 956,\n dpr: 3,\n notch: 'dynamic-island',\n notchInset: 62,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n // Pro Max — notchInset 62는 Apple 스펙 유추. 실측 미진행.\n safeAreaProvenance: EXTRAPOLATED,\n },\n // Samsung\n //\n // Galaxy S26 series shipped 2026-03-11. Viewport widths/heights below come\n // from phone-simulator.com's measured values. safe-area top/bottom remain\n // S25-derived placeholders because we have no toss host live-measure yet —\n // do not treat them as ground truth for QA.\n {\n id: 'galaxy-s26',\n label: 'Galaxy S26',\n width: 360,\n height: 773,\n dpr: 3,\n notch: 'punch-hole-center',\n notchInset: 32,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n // Android safe-area는 relay 실측 없음. placeholder.\n safeAreaProvenance: PLACEHOLDER,\n },\n {\n id: 'galaxy-s26-plus',\n label: 'Galaxy S26+',\n width: 480,\n height: 1040,\n dpr: 3,\n notch: 'punch-hole-center',\n notchInset: 32,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n safeAreaProvenance: PLACEHOLDER,\n },\n {\n id: 'galaxy-s26-ultra',\n label: 'Galaxy S26 Ultra',\n width: 480,\n height: 1040,\n dpr: 3,\n notch: 'punch-hole-center',\n notchInset: 40,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n safeAreaProvenance: PLACEHOLDER,\n },\n {\n id: 'galaxy-z-flip7',\n label: 'Galaxy Z Flip7',\n width: 412,\n height: 990,\n dpr: 3,\n notch: 'punch-hole-center',\n notchInset: 36,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n safeAreaProvenance: PLACEHOLDER,\n },\n {\n id: 'galaxy-z-fold7-folded',\n label: 'Galaxy Z Fold7 (folded)',\n width: 384,\n height: 870,\n dpr: 3,\n notch: 'punch-hole-center',\n notchInset: 32,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n safeAreaProvenance: PLACEHOLDER,\n },\n {\n id: 'galaxy-z-fold7-unfolded',\n label: 'Galaxy Z Fold7 (unfolded)',\n width: 768,\n height: 884,\n dpr: 2.625,\n notch: 'punch-hole-center',\n notchInset: 32,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n safeAreaProvenance: PLACEHOLDER,\n },\n CUSTOM_PRESET,\n];\n\nexport function getPreset(id: ViewportPresetId): ViewportPreset {\n return VIEWPORT_PRESETS.find((p) => p.id === id) ?? NONE_PRESET;\n}\n\n/**\n * 실제로 화면에 표시될 orientation을 결정한다.\n *\n * - Panel `orientation === 'auto'`: 앱이 마지막으로 SDK로 요청한 값\n * (`appOrientation`)을 따른다. 호출 전이면 portrait.\n * - Panel `orientation === 'portrait' | 'landscape'`: Panel 값이 우선.\n */\nexport function effectiveOrientation(state: ViewportState): 'portrait' | 'landscape' {\n if (state.orientation === 'auto') {\n return state.appOrientation ?? 'portrait';\n }\n return state.orientation;\n}\n\n/**\n * 선택된 뷰포트의 실제 width/height를 계산한다.\n * preset === 'custom'이면 customWidth/customHeight, 그 외에는 preset의 값.\n * effective orientation이 landscape이면 width/height를 swap한다.\n */\nexport function resolveViewportSize(state: ViewportState): { width: number; height: number } {\n if (state.preset === 'none') return { width: 0, height: 0 };\n const base =\n state.preset === 'custom'\n ? { width: state.customWidth, height: state.customHeight }\n : getPreset(state.preset);\n return effectiveOrientation(state) === 'landscape'\n ? { width: base.height, height: base.width }\n : { width: base.width, height: base.height };\n}\n\n/**\n * 프리셋 + orientation + nav bar 상태로부터 SDK `SafeAreaInsets.get()`이 반환할 insets를\n * 계산한다. iPhone 15 Pro on-device relay 실측(devtools#190)에 맞춘 모델:\n *\n * - **Portrait top = 토스 nav bar 높이** (OS 노치가 아니다). 실측에서\n * `env(safe-area-inset-top)` = 0, `SafeAreaInsets.get().top` = 54 였고, 그 54는 호스트\n * nav bar다. 따라서 nav bar가 떠 있고 `partner` type일 때만 `navBarHeight`를 top에 준다.\n * `game`(투명 오버레이, 콘텐츠 안 밀어냄) 또는 nav bar 미표시면 top = 0.\n * - **Bottom = `safeAreaBottom`** (home-indicator). 실측 34와 일치.\n * - **Landscape iPhone(notch/Dynamic Island)**: 노치가 한쪽으로 가므로 `landscapeSide`에\n * 따라 left/right 한쪽에만 `notchInset`을 준다. top은 0(landscape nav bar 거동은\n * 미실측 — portrait 모델만 확정), home-indicator는 bottom에 유지.\n * - **Android punch-hole(status bar)**: landscape에서도 top에 status bar(`notchInset`)가\n * 유지된다.\n */\nexport function computeSafeAreaInsets(\n preset: ViewportPreset,\n landscape: boolean,\n side: LandscapeSide,\n navBarVisible: boolean,\n navBarType: AitNavBarType,\n): SafeAreaInsets {\n if (preset.id === 'none' || preset.id === 'custom') {\n return { top: 0, bottom: 0, left: 0, right: 0 };\n }\n // partner nav bar가 떠 있을 때만 콘텐츠를 밀어낸다 (game은 투명 오버레이).\n const navBarTop = navBarVisible && navBarType === 'partner' ? preset.navBarHeight : 0;\n if (!landscape) {\n return { top: navBarTop, bottom: preset.safeAreaBottom, left: 0, right: 0 };\n }\n if (preset.notch === 'notch' || preset.notch === 'dynamic-island') {\n return {\n top: 0,\n bottom: preset.safeAreaBottom,\n left: side === 'left' ? preset.notchInset : 0,\n right: side === 'right' ? preset.notchInset : 0,\n };\n }\n // Android status bar stays on the top edge even in landscape.\n return {\n top: preset.notchInset,\n bottom: preset.safeAreaBottom,\n left: 0,\n right: 0,\n };\n}\n\n/** viewport preset 또는 orientation이 바뀌면 safe-area insets도 자동 갱신한다. */\nfunction syncSafeAreaFromViewport(state: ViewportState): void {\n if (state.preset === 'none' || state.preset === 'custom') return;\n const preset = getPreset(state.preset);\n const next = computeSafeAreaInsets(\n preset,\n effectiveOrientation(state) === 'landscape',\n state.landscapeSide,\n state.aitNavBar,\n state.aitNavBarType,\n );\n const current = aitState.state.safeAreaInsets;\n if (\n current.top === next.top &&\n current.bottom === next.bottom &&\n current.left === next.left &&\n current.right === next.right\n ) {\n return;\n }\n aitState.update({ safeAreaInsets: next });\n}\n\nconst STYLE_ELEMENT_ID = '__ait-viewport-style';\nconst NOTCH_ELEMENT_ID = '__ait-viewport-notch';\nconst HOME_INDICATOR_ID = '__ait-viewport-home-indicator';\nconst NAV_BAR_ELEMENT_ID = '__ait-viewport-navbar';\n\nlet bodyScrollHintEmitted = false;\n\nfunction ensureStyleElement(): HTMLStyleElement | null {\n if (typeof document === 'undefined') return null;\n let el = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n if (!el) {\n el = document.createElement('style');\n el.id = STYLE_ELEMENT_ID;\n document.head.appendChild(el);\n }\n return el;\n}\n\nfunction removeById(id: string): void {\n const el = document.getElementById(id);\n if (el) el.remove();\n}\n\nfunction removeNotchElement(): void {\n removeById(NOTCH_ELEMENT_ID);\n}\n\nfunction removeHomeIndicator(): void {\n removeById(HOME_INDICATOR_ID);\n}\n\nfunction removeNavBarElement(): void {\n removeById(NAV_BAR_ELEMENT_ID);\n}\n\n/**\n * Apps in Toss host nav bar 렌더. OS status bar(notch) 아래에 쌓인다.\n *\n * 변형(SDK `webViewProps.type`과 의미 일치):\n * - `partner` (기본): 흰 배경, 좌측 뒤로가기(‹), 앱 아이콘 + 이름(`brand.displayName`),\n * 우측 `⋯` + 구분선 + `×`.\n * - `game`: 투명 배경, 게임 캔버스를 가리지 않도록 우측 `⋯` + 구분선 + `×`만.\n *\n * nav bar는 WebView(body) 좌표계의 최상단(top 0)에 앉는다 — 실기기에서 OS notch는\n * WebView 밖(status bar)이라 `env(safe-area-inset-top)`이 0이고, WebView 콘텐츠 영역은\n * nav bar 바로 아래(= SDK `SafeAreaInsets.get().top` = `navBarHeight`)에서 시작한다.\n * 콘텐츠를 그만큼 밀어내는 건 `applyViewport`의 body `padding-top`이 담당하므로, nav bar\n * 바닥과 콘텐츠 시작이 정확히 맞물린다. 시각 notch 오버레이는 body 밖 위쪽(status bar\n * 영역)에 따로 그린다(`renderNotchOverlay`) — body 안이 아니다.\n *\n * 뒤로가기 버튼은 `__ait:backEvent`를 트리거하고, X 버튼은 `closeView()`를 호출한다.\n * 실제 SDK 이벤트 플러밍을 한 곳에서 검증할 수 있다.\n */\nfunction renderNavBar(displayName: string, type: AitNavBarType): void {\n removeNavBarElement();\n const el = h('div', {\n id: NAV_BAR_ELEMENT_ID,\n className: `ait-navbar ait-navbar-${type}`,\n 'aria-hidden': 'true',\n });\n\n const moreBtn = h('button', {\n className: 'ait-navbar-btn',\n type: 'button',\n 'aria-label': 'More',\n });\n moreBtn.textContent = '⋯';\n\n const closeBtn = h('button', {\n className: 'ait-navbar-btn',\n type: 'button',\n 'aria-label': 'Close',\n });\n closeBtn.textContent = '×';\n closeBtn.addEventListener('click', () => {\n closeView().catch((err) => console.error('[@ait-co/devtools] navbar close failed:', err));\n });\n\n const actions = h(\n 'div',\n { className: 'ait-navbar-actions' },\n moreBtn,\n h('span', { className: 'ait-navbar-divider' }),\n closeBtn,\n );\n\n if (type === 'game') {\n // Game: 투명 배경, back/title 없음, 우측 actions만 (실제 토스 host 동작과 일치).\n el.append(actions);\n } else {\n const backBtn = h('button', {\n className: 'ait-navbar-btn ait-navbar-back',\n type: 'button',\n 'aria-label': 'Back',\n });\n backBtn.textContent = '‹';\n backBtn.addEventListener('click', () => {\n aitState.trigger('backEvent');\n });\n\n const nameSpan = h('span', { className: 'ait-navbar-name' });\n nameSpan.textContent = displayName;\n\n el.append(\n backBtn,\n h(\n 'div',\n { className: 'ait-navbar-title' },\n h('span', { className: 'ait-navbar-icon' }),\n nameSpan,\n ),\n actions,\n );\n }\n\n document.body.appendChild(el);\n}\n\n/**\n * 현재 preset의 notch/Dynamic Island/punch-hole을 body 상단에 시각적으로 렌더한다.\n * landscape 시에는 노치가 한쪽 변에 있는 것이 실제 기기 동작이지만, 시뮬레이터에서는\n * landscape에서 오버레이를 그리지 않는다 (safeAreaInsets의 left/right로 이미 반영).\n */\nfunction renderNotchOverlay(preset: ViewportPreset): void {\n removeNotchElement();\n if (preset.notch === 'none') return;\n\n const variant =\n preset.notch === 'dynamic-island'\n ? 'ait-notch-dynamic-island'\n : preset.notch === 'notch'\n ? 'ait-notch-pill'\n : 'ait-notch-punch-hole';\n\n const notch = h('div', {\n id: NOTCH_ELEMENT_ID,\n className: `ait-notch ${variant}`,\n 'aria-hidden': 'true',\n });\n document.body.appendChild(notch);\n}\n\n/** brand 이름만 바뀐 경우 nav bar 전체를 다시 만들지 않고 텍스트 노드만 교체한다. */\nfunction refreshNavBarBrand(displayName: string): void {\n const name = document.querySelector(`#${NAV_BAR_ELEMENT_ID} .ait-navbar-name`);\n if (name) name.textContent = displayName;\n}\n\nfunction renderHomeIndicator(): void {\n removeHomeIndicator();\n const el = h('div', {\n id: HOME_INDICATOR_ID,\n className: 'ait-home-indicator',\n 'aria-hidden': 'true',\n });\n document.body.appendChild(el);\n}\n\n/**\n * 모든 viewport DOM mutation을 원복하고 aitState 구독도 해제한다.\n * 외부 consumer가 패널을 동적으로 제거할 때 호출. 호출 후에는 aitState 변경이\n * DOM에 반영되지 않으므로 안전하게 panel을 떼어낼 수 있다.\n */\nexport function disposeViewport(): void {\n if (typeof document === 'undefined') return;\n if (viewportUnsubscribe) viewportUnsubscribe();\n const html = document.documentElement;\n html.classList.remove('ait-viewport-active');\n html.classList.remove('ait-viewport-framed');\n removeById(STYLE_ELEMENT_ID);\n removeNotchElement();\n removeHomeIndicator();\n removeNavBarElement();\n revertDeviceEmulation();\n bodyScrollHintEmitted = false;\n}\n\n/**\n * DOM에 뷰포트 제약을 적용한다.\n * - `html.ait-viewport-active` 클래스로 정적 CSS(styles.ts) 활성화\n * - body의 width/height는 preset 값으로, navbar top offset은 notchInset으로 인라인 주입\n */\nexport function applyViewport(state: ViewportState): void {\n if (typeof document === 'undefined') return;\n const html = document.documentElement;\n const style = ensureStyleElement();\n if (!style) return;\n\n const size = resolveViewportSize(state);\n\n if (state.preset === 'none' || size.width === 0 || size.height === 0) {\n html.classList.remove('ait-viewport-active');\n html.classList.remove('ait-viewport-framed');\n style.textContent = '';\n removeNotchElement();\n removeHomeIndicator();\n removeNavBarElement();\n syncDeviceEmulation(null, false);\n return;\n }\n\n if (!bodyScrollHintEmitted) {\n bodyScrollHintEmitted = true;\n console.info(\n '[@ait-co/devtools] Viewport simulation active — scroll happens on body, not window. ' +\n 'See README \"Known limitations\" for details.',\n );\n }\n\n html.classList.add('ait-viewport-active');\n html.classList.toggle('ait-viewport-framed', state.frame);\n\n const preset = state.preset === 'custom' ? null : getPreset(state.preset);\n const landscape = effectiveOrientation(state) === 'landscape';\n\n // 기기 preset이면 UA/DPR/screen/platform을 그 기기와 정합 (custom은 치수만 강제).\n syncDeviceEmulation(preset, landscape);\n\n // partner nav bar는 실기기 토스 호스트처럼 콘텐츠를 밀어낸다 — body padding-top으로\n // 재현한다. game은 투명 오버레이라 안 밀고(0), nav bar 미표시·landscape도 0. 미는 양은\n // SDK `SafeAreaInsets.get().top`과 같은 값이라 computeSafeAreaInsets의 top을 단일 진실로\n // 쓴다 (오버레이로만 얹으면 nav bar가 콘텐츠 첫 픽셀을 덮어 실기기와 어긋난다).\n const contentTop = preset\n ? computeSafeAreaInsets(\n preset,\n landscape,\n state.landscapeSide,\n state.aitNavBar,\n state.aitNavBarType,\n ).top\n : 0;\n\n // Dynamic per-preset values only — static rules live in styles.ts.\n style.textContent = /* css */ `\n html.ait-viewport-active body {\n width: ${size.width}px;\n max-width: ${size.width}px;\n min-height: ${size.height}px;\n max-height: ${size.height}px;\n padding-top: ${contentTop}px;\n }\n `;\n\n // Notch / home indicator / nav bar are gated in JS so document.getElementById\n // becomes a reliable \"is overlay present\" predicate.\n if (preset && state.frame && !landscape) renderNotchOverlay(preset);\n else removeNotchElement();\n\n if (preset && state.frame && !landscape && preset.safeAreaBottom > 0) renderHomeIndicator();\n else removeHomeIndicator();\n\n if (preset && state.aitNavBar && !landscape) {\n renderNavBar(aitState.state.brand.displayName, state.aitNavBarType);\n } else {\n removeNavBarElement();\n }\n}\n\nfunction isViewportPresetId(v: unknown): v is ViewportPresetId {\n return typeof v === 'string' && VIEWPORT_PRESETS.some((p) => p.id === v);\n}\n\nfunction isViewportOrientation(v: unknown): v is ViewportOrientation {\n return v === 'auto' || v === 'portrait' || v === 'landscape';\n}\n\nfunction isAppOrientation(v: unknown): v is AppOrientation {\n return v === null || v === 'portrait' || v === 'landscape';\n}\n\nfunction isLandscapeSide(v: unknown): v is LandscapeSide {\n return v === 'left' || v === 'right';\n}\n\n/** 1 이상의 정수 + VIEWPORT_CUSTOM_MAX 이하인지 검사. sessionStorage 보호용. */\nfunction isValidCustomDimension(v: unknown): v is number {\n return typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= VIEWPORT_CUSTOM_MAX;\n}\n\n/** Custom 입력에서 사용. 잘린 정수 + 클램프된 안전한 값 또는 null 반환. */\nexport function clampCustomDimension(raw: number): number | null {\n if (!Number.isFinite(raw)) return null;\n const n = Math.floor(raw);\n if (n < 1) return null;\n return Math.min(n, VIEWPORT_CUSTOM_MAX);\n}\n\n/**\n * sessionStorage에 저장된 뷰포트 상태를 읽어서 현재 state에 merge한다.\n * 값이 없거나 파싱 실패 시 no-op.\n */\nexport function loadViewportFromStorage(): Partial<ViewportState> | null {\n if (typeof sessionStorage === 'undefined') return null;\n const raw = sessionStorage.getItem(VIEWPORT_STORAGE_KEY);\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return null;\n const obj = parsed as Record<string, unknown>;\n const next: Partial<ViewportState> = {};\n if (isViewportPresetId(obj.preset)) next.preset = obj.preset;\n if (isViewportOrientation(obj.orientation)) next.orientation = obj.orientation;\n if (isAppOrientation(obj.appOrientation)) next.appOrientation = obj.appOrientation;\n if (isLandscapeSide(obj.landscapeSide)) next.landscapeSide = obj.landscapeSide;\n if (isValidCustomDimension(obj.customWidth)) next.customWidth = obj.customWidth;\n if (isValidCustomDimension(obj.customHeight)) next.customHeight = obj.customHeight;\n if (typeof obj.frame === 'boolean') next.frame = obj.frame;\n if (typeof obj.aitNavBar === 'boolean') next.aitNavBar = obj.aitNavBar;\n if (obj.aitNavBarType === 'partner' || obj.aitNavBarType === 'game') {\n next.aitNavBarType = obj.aitNavBarType;\n }\n return next;\n } catch {\n return null;\n }\n}\n\nexport function saveViewportToStorage(state: ViewportState): void {\n if (typeof sessionStorage === 'undefined') return;\n try {\n sessionStorage.setItem(VIEWPORT_STORAGE_KEY, JSON.stringify(state));\n } catch {\n /* ignore quota errors */\n }\n}\n\nlet viewportInitialized = false;\nlet viewportUnsubscribe: (() => void) | null = null;\n\n/**\n * Panel mount 시 호출. sessionStorage 복원 → aitState에 반영 → DOM 적용.\n * aitState 변경을 구독해서 DOM / storage / safe-area insets를 자동 동기화한다.\n *\n * Idempotent: 두 번째 호출은 기존 unsubscribe를 그대로 반환한다 (HMR / 재mount 안전).\n * 테스트는 반환된 unsubscribe를 afterEach에서 호출해 cleanup해야 한다.\n */\nexport function initViewport(): () => void {\n if (typeof window === 'undefined') return () => {};\n if (viewportInitialized && viewportUnsubscribe) return viewportUnsubscribe;\n\n const restored = loadViewportFromStorage();\n if (restored) {\n aitState.patch('viewport', restored);\n }\n applyViewport(aitState.state.viewport);\n syncSafeAreaFromViewport(aitState.state.viewport);\n\n let lastViewportJson = JSON.stringify(aitState.state.viewport);\n let lastBrandName = aitState.state.brand.displayName;\n\n const unsubscribeFn = aitState.subscribe(() => {\n const vp = aitState.state.viewport;\n const brandName = aitState.state.brand.displayName;\n const json = JSON.stringify(vp);\n\n const viewportChanged = json !== lastViewportJson;\n const brandChanged = brandName !== lastBrandName;\n\n if (!viewportChanged && !brandChanged) return;\n lastViewportJson = json;\n lastBrandName = brandName;\n\n if (viewportChanged) {\n applyViewport(vp);\n saveViewportToStorage(vp);\n syncSafeAreaFromViewport(vp);\n } else {\n // Brand-only change: refresh just the nav bar text instead of rebuilding all overlays.\n refreshNavBarBrand(brandName);\n }\n });\n\n viewportInitialized = true;\n viewportUnsubscribe = () => {\n unsubscribeFn();\n viewportInitialized = false;\n viewportUnsubscribe = null;\n };\n return viewportUnsubscribe;\n}\n\n/**\n * @internal Test helper. Production code never touches this — use `disposeViewport()`.\n */\nexport function _resetViewportInit(): void {\n if (viewportUnsubscribe) viewportUnsubscribe();\n viewportInitialized = false;\n viewportUnsubscribe = null;\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type {\n AitNavBarType,\n LandscapeSide,\n SafeAreaProvenance,\n ViewportOrientation,\n ViewportPresetId,\n} from '../../mock/types.js';\nimport { h, monitoringNotice } from '../helpers.js';\nimport {\n clampCustomDimension,\n computeSafeAreaInsets,\n effectiveOrientation,\n getPreset,\n resolveViewportSize,\n VIEWPORT_PRESETS,\n} from '../viewport.js';\n\n/**\n * Renders a small inline provenance badge for safe-area values.\n * - `measured` — no badge (confirmed value)\n * - `extrapolated` — \"(추정치)\" in muted gray\n * - `placeholder` — \"(미측정)\" in amber\n */\nfunction provenanceBadge(provenance: SafeAreaProvenance | undefined): HTMLElement | null {\n if (!provenance || provenance.source === 'measured') return null;\n const text = provenance.source === 'placeholder' ? '(미측정)' : '(추정치)';\n const color = provenance.source === 'placeholder' ? '#b45309' : '#888';\n const badge = h('span', {\n className: 'ait-provenance-badge',\n title:\n provenance.source === 'placeholder'\n ? 'safe-area 값이 미실측 추정치입니다. relay 세션에서 measure_safe_area로 실측 후 승급하세요.'\n : 'safe-area 값이 기기 스펙에서 유추한 추정치입니다. relay 세션에서 measure_safe_area로 확인하세요.',\n });\n badge.textContent = text;\n badge.style.cssText = `font-size:10px;color:${color};margin-left:4px`;\n return badge;\n}\n\nexport function renderViewportTab(): HTMLElement {\n const s = aitState.state;\n const vp = s.viewport;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n // --- Preset selector ---\n const presetSelect = h('select', { className: 'ait-select' });\n if (disabled) presetSelect.disabled = true;\n for (const preset of VIEWPORT_PRESETS) {\n const label =\n preset.id === 'none' || preset.id === 'custom'\n ? preset.label\n : `${preset.label} (${preset.width}×${preset.height})`;\n const option = h('option', { value: preset.id }, label);\n if (preset.id === vp.preset) option.selected = true;\n presetSelect.appendChild(option);\n }\n presetSelect.addEventListener('change', () => {\n const id = presetSelect.value as ViewportPresetId;\n const patch: Partial<typeof vp> = { preset: id };\n // custom으로 전환할 때 현재 선택값을 custom 필드의 시드로 복사해둔다.\n if (id === 'custom') {\n const current = getPreset(vp.preset);\n if (current.width > 0) patch.customWidth = current.width;\n if (current.height > 0) patch.customHeight = current.height;\n }\n aitState.patch('viewport', patch);\n });\n\n // --- Orientation toggle ---\n const orientationSelect = h('select', { className: 'ait-select' });\n if (disabled) orientationSelect.disabled = true;\n for (const opt of ['auto', 'portrait', 'landscape'] as ViewportOrientation[]) {\n const option = h('option', { value: opt }, opt);\n if (opt === vp.orientation) option.selected = true;\n orientationSelect.appendChild(option);\n }\n orientationSelect.addEventListener('change', () => {\n aitState.patch('viewport', {\n orientation: orientationSelect.value as ViewportOrientation,\n });\n });\n\n // --- Landscape side (only meaningful when landscape) ---\n const landscapeSideSelect = h('select', { className: 'ait-select' });\n if (disabled) landscapeSideSelect.disabled = true;\n for (const opt of ['left', 'right'] as LandscapeSide[]) {\n const option = h('option', { value: opt }, opt);\n if (opt === vp.landscapeSide) option.selected = true;\n landscapeSideSelect.appendChild(option);\n }\n landscapeSideSelect.addEventListener('change', () => {\n aitState.patch('viewport', { landscapeSide: landscapeSideSelect.value as LandscapeSide });\n });\n\n // --- Custom width/height inputs (custom 모드에서만 활성화) ---\n const customRow = h('div', { className: 'ait-section' });\n if (vp.preset === 'custom') {\n const widthInput = h('input', {\n className: 'ait-input',\n type: 'number',\n min: '1',\n value: String(vp.customWidth),\n }) as HTMLInputElement;\n const heightInput = h('input', {\n className: 'ait-input',\n type: 'number',\n min: '1',\n value: String(vp.customHeight),\n }) as HTMLInputElement;\n if (disabled) {\n widthInput.disabled = true;\n heightInput.disabled = true;\n }\n widthInput.addEventListener('change', () => {\n const clamped = clampCustomDimension(Number(widthInput.value));\n if (clamped !== null) {\n aitState.patch('viewport', { customWidth: clamped });\n widthInput.value = String(clamped);\n }\n });\n heightInput.addEventListener('change', () => {\n const clamped = clampCustomDimension(Number(heightInput.value));\n if (clamped !== null) {\n aitState.patch('viewport', { customHeight: clamped });\n heightInput.value = String(clamped);\n }\n });\n customRow.append(\n h('div', { className: 'ait-section-title' }, t('viewport.section.custom')),\n h('div', { className: 'ait-row' }, h('label', {}, t('viewport.row.width')), widthInput),\n h('div', { className: 'ait-row' }, h('label', {}, t('viewport.row.height')), heightInput),\n );\n }\n\n // --- Frame decoration toggle ---\n const frameCheckbox = h('input', { type: 'checkbox' }) as HTMLInputElement;\n frameCheckbox.checked = vp.frame;\n if (disabled) frameCheckbox.disabled = true;\n frameCheckbox.addEventListener('change', () => {\n aitState.patch('viewport', { frame: frameCheckbox.checked });\n });\n\n // --- Apps in Toss nav bar toggle ---\n const navBarCheckbox = h('input', { type: 'checkbox' }) as HTMLInputElement;\n navBarCheckbox.checked = vp.aitNavBar;\n if (disabled) navBarCheckbox.disabled = true;\n navBarCheckbox.addEventListener('change', () => {\n aitState.patch('viewport', { aitNavBar: navBarCheckbox.checked });\n });\n\n // --- Nav bar type (partner / game) — only meaningful when aitNavBar is on ---\n const navBarTypeSelect = h('select', { className: 'ait-select' });\n if (disabled || !vp.aitNavBar) navBarTypeSelect.disabled = true;\n for (const opt of ['partner', 'game'] as AitNavBarType[]) {\n const option = h('option', { value: opt }, opt);\n if (opt === vp.aitNavBarType) option.selected = true;\n navBarTypeSelect.appendChild(option);\n }\n navBarTypeSelect.addEventListener('change', () => {\n aitState.patch('viewport', { aitNavBarType: navBarTypeSelect.value as AitNavBarType });\n });\n\n // --- Status panel: applied size + HiDPI + safe area ---\n const size = resolveViewportSize(vp);\n const statusEl = h('div', { className: 'ait-section' });\n\n if (vp.preset === 'none' || size.width === 0) {\n statusEl.appendChild(\n h('div', { style: 'color:#888;font-size:11px' }, t('viewport.status.noConstraint')),\n );\n } else {\n const preset = vp.preset === 'custom' ? null : getPreset(vp.preset);\n const effOrient = effectiveOrientation(vp);\n const landscape = effOrient === 'landscape';\n const rows: Array<HTMLElement> = [];\n\n // Viewport: CSS @DPR | physical\n const dpr = preset?.dpr ?? 1;\n const physW = Math.round(size.width * dpr);\n const physH = Math.round(size.height * dpr);\n const orientDisplay =\n vp.orientation === 'auto'\n ? t('viewport.orientation.autoSuffix', { orient: effOrient })\n : effOrient;\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, t('viewport.status.cssPhysical')),\n h(\n 'span',\n { className: 'ait-status-value' },\n `${size.width}×${size.height}@${dpr}x | ${physW}×${physH} ${orientDisplay}`,\n ),\n ),\n );\n\n if (preset) {\n const insets = computeSafeAreaInsets(\n preset,\n landscape,\n vp.landscapeSide,\n vp.aitNavBar,\n vp.aitNavBarType,\n );\n const safeAreaValueEl = h(\n 'span',\n { className: 'ait-status-value' },\n `T${insets.top} R${insets.right} B${insets.bottom} L${insets.left}`,\n );\n const badge = provenanceBadge(preset.safeAreaProvenance);\n if (badge) safeAreaValueEl.appendChild(badge);\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, t('viewport.status.safeArea')),\n safeAreaValueEl,\n ),\n );\n }\n\n if (vp.aitNavBar && !landscape) {\n // partner는 콘텐츠를 navBarHeight만큼 밀어내고, game은 투명 오버레이라 0.\n const navBarTop = vp.aitNavBarType === 'partner' ? (preset?.navBarHeight ?? 0) : 0;\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, t('viewport.status.aitNavBar')),\n h(\n 'span',\n { className: 'ait-status-value' },\n t('viewport.status.aitNavBarValue', {\n height: navBarTop,\n type: vp.aitNavBarType,\n }),\n ),\n ),\n );\n }\n\n for (const row of rows) statusEl.appendChild(row);\n }\n\n // --- Compose ---\n const deviceSection = h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('viewport.section.device')),\n h('div', { className: 'ait-row' }, h('label', {}, t('viewport.row.preset')), presetSelect),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('viewport.row.orientation')),\n orientationSelect,\n ),\n );\n\n // Landscape side row only shown when effective orientation is landscape and\n // the device has a notch (otherwise the value has no visible effect).\n if (effectiveOrientation(vp) === 'landscape' && vp.preset !== 'none' && vp.preset !== 'custom') {\n const notch = getPreset(vp.preset).notch;\n if (notch === 'notch' || notch === 'dynamic-island') {\n deviceSection.appendChild(\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('viewport.row.notchSide')),\n landscapeSideSelect,\n ),\n );\n }\n }\n\n container.append(\n deviceSection,\n customRow,\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('viewport.section.appearance')),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('viewport.row.showFrame')),\n frameCheckbox,\n ),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('viewport.row.showAitNavBar')),\n navBarCheckbox,\n ),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('viewport.row.navBarType')),\n navBarTypeSelect,\n ),\n ),\n statusEl,\n );\n\n return container;\n}\n","import { type StringKey, t } from '../../i18n/index.js';\nimport { renderAdsTab } from './ads.js';\nimport { renderAnalyticsTab } from './analytics.js';\nimport { renderDeviceTab } from './device.js';\nimport { renderEnvironmentTab } from './environment.js';\nimport { renderEventsTab } from './events.js';\nimport { renderIapTab } from './iap.js';\nimport { renderLocationTab } from './location.js';\nimport { renderNotificationsTab } from './notifications.js';\nimport { renderPermissionsTab } from './permissions.js';\nimport { renderPresetsTab } from './presets.js';\nimport { renderStorageTab } from './storage.js';\nimport { renderViewportTab } from './viewport.js';\n\nexport type TabId =\n | 'env'\n | 'presets'\n | 'permissions'\n | 'notifications'\n | 'location'\n | 'iap'\n | 'ads'\n | 'events'\n | 'analytics'\n | 'storage'\n | 'device'\n | 'viewport';\n\n// Tab ordering + label-key map. `label` is re-resolved through `t()` at each\n// mount so locale changes pick up the new translation.\nconst TAB_DEFS: Array<{ id: TabId; labelKey: StringKey }> = [\n { id: 'env', labelKey: 'panel.tab.env' },\n { id: 'presets', labelKey: 'panel.tab.presets' },\n { id: 'viewport', labelKey: 'panel.tab.viewport' },\n { id: 'permissions', labelKey: 'panel.tab.permissions' },\n { id: 'notifications', labelKey: 'panel.tab.notifications' },\n { id: 'location', labelKey: 'panel.tab.location' },\n { id: 'device', labelKey: 'panel.tab.device' },\n { id: 'iap', labelKey: 'panel.tab.iap' },\n { id: 'ads', labelKey: 'panel.tab.ads' },\n { id: 'events', labelKey: 'panel.tab.events' },\n { id: 'analytics', labelKey: 'panel.tab.analytics' },\n { id: 'storage', labelKey: 'panel.tab.storage' },\n];\n\nexport function getTabs(): Array<{ id: TabId; label: string }> {\n return TAB_DEFS.map((def) => ({ id: def.id, label: t(def.labelKey) }));\n}\n\n// storage tab receives refreshPanel because its clear button modifies localStorage\n// directly (not aitState), so it must trigger a re-render explicitly.\n// presets tab needs refreshPanel for the same reason (user preset CRUD touches localStorage).\n// device tab uses setDeviceRefreshPanel() for prompt-related local state (pendingPrompt);\n// its aitState mutations are auto-refreshed via the subscription in index.ts.\n// Other tabs only modify aitState or use input controls that reflect changes immediately.\nexport function createTabRenderers(refreshPanel: () => void): Record<TabId, () => HTMLElement> {\n return {\n env: renderEnvironmentTab,\n presets: () => renderPresetsTab(refreshPanel),\n permissions: renderPermissionsTab,\n notifications: renderNotificationsTab,\n location: renderLocationTab,\n device: renderDeviceTab,\n viewport: renderViewportTab,\n iap: renderIapTab,\n ads: renderAdsTab,\n events: renderEventsTab,\n analytics: renderAnalyticsTab,\n storage: () => renderStorageTab(refreshPanel),\n };\n}\n","/**\n * @ait-co/devtools Floating Panel\n *\n * import 하면 자동으로 페이지에 DevTools 패널을 마운트한다.\n * 외부 의존성 없이 vanilla DOM으로 구현.\n */\n\nimport { LOCALE_CHANGE_EVENT, t } from '../i18n/index.js';\nimport { type AitDevtoolsState, aitState } from '../mock/state.js';\nimport { telemetry } from '../telemetry/index.js';\nimport { h } from './helpers.js';\nimport { PANEL_FULLSCREEN_BREAKPOINT, PANEL_HEIGHT, PANEL_STYLES, PANEL_WIDTH } from './styles.js';\nimport { setDeviceRefreshPanel } from './tabs/device.js';\nimport { createTabRenderers, getTabs, type TabId } from './tabs/index.js';\nimport { disposeViewport, initViewport } from './viewport.js';\n\n/** MCP endpoint registered by the unplugin when `mcp: true` is set */\nconst MCP_STATE_PATH = '/api/ait-devtools/state';\n\n/**\n * Push a state snapshot to the Vite dev-server MCP endpoint.\n * No-ops silently when the endpoint is not available (e.g., mcp option not set,\n * or running in production). Fire-and-forget — never throws.\n */\nfunction pushStateToMcpEndpoint(state: AitDevtoolsState): void {\n // Only attempt in a browser context with fetch available\n if (typeof fetch === 'undefined') return;\n fetch(MCP_STATE_PATH, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(state),\n }).catch(() => {\n // Silently ignore — endpoint is not available when mcp option is not set\n });\n}\n\n// --- Draggable toggle button ---\n\nfunction makeDraggable(el: HTMLElement, onClickOnly: () => void) {\n let isDragging = false;\n let startX = 0,\n startY = 0;\n let startLeft = 0,\n startTop = 0;\n let hasMoved = false;\n\n el.addEventListener('pointerdown', (e) => {\n isDragging = true;\n hasMoved = false;\n startX = e.clientX;\n startY = e.clientY;\n const rect = el.getBoundingClientRect();\n startLeft = rect.left;\n startTop = rect.top;\n el.setPointerCapture(e.pointerId);\n e.preventDefault();\n });\n\n el.addEventListener('pointermove', (e) => {\n if (!isDragging) return;\n const dx = e.clientX - startX;\n const dy = e.clientY - startY;\n if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {\n hasMoved = true;\n el.classList.add('dragging');\n }\n if (!hasMoved) return;\n\n el.style.left = `${startLeft + dx}px`;\n el.style.top = `${startTop + dy}px`;\n el.style.right = 'auto';\n el.style.bottom = 'auto';\n });\n\n el.addEventListener('pointerup', (e) => {\n if (!isDragging) return;\n isDragging = false;\n el.classList.remove('dragging');\n el.releasePointerCapture(e.pointerId);\n\n if (hasMoved) {\n snapToEdge(el);\n updatePanelPosition(el);\n saveButtonPosition(el);\n } else {\n onClickOnly();\n }\n });\n\n el.addEventListener('pointercancel', (e) => {\n isDragging = false;\n el.classList.remove('dragging');\n el.releasePointerCapture(e.pointerId);\n if (hasMoved) {\n snapToEdge(el);\n updatePanelPosition(el);\n saveButtonPosition(el);\n }\n });\n}\n\nfunction snapToEdge(el: HTMLElement) {\n const rect = el.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const cx = rect.left + rect.width / 2;\n const margin = 16;\n\n if (cx < vw / 2) {\n el.style.left = `${margin}px`;\n el.style.right = 'auto';\n } else {\n el.style.left = 'auto';\n el.style.right = `${margin}px`;\n }\n\n const top = Math.max(margin, Math.min(vh - rect.height - margin, rect.top));\n el.style.top = `${top}px`;\n el.style.bottom = 'auto';\n}\n\nfunction updatePanelPosition(toggleEl: HTMLElement) {\n if (!panelEl) return;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // On narrow viewports, CSS media query handles fullscreen — clear any inline positioning\n if (vw <= PANEL_FULLSCREEN_BREAKPOINT) {\n panelEl.style.top = '';\n panelEl.style.left = '';\n panelEl.style.right = '';\n panelEl.style.bottom = '';\n return;\n }\n\n const rect = toggleEl.getBoundingClientRect();\n const _panelWidth = PANEL_WIDTH;\n const panelHeight = PANEL_HEIGHT;\n const margin = 16;\n\n // Horizontal: place panel on the same side as the toggle button\n if (rect.left < vw / 2) {\n panelEl.style.left = `${margin}px`;\n panelEl.style.right = 'auto';\n } else {\n panelEl.style.left = 'auto';\n panelEl.style.right = `${margin}px`;\n }\n\n // Vertical: place below button if it's in top half, above if bottom half\n // Clamp so panel stays within viewport\n if (rect.top < vh / 2) {\n const top = Math.min(rect.bottom + 8, vh - panelHeight - margin);\n panelEl.style.top = `${Math.max(margin, top)}px`;\n panelEl.style.bottom = 'auto';\n } else {\n const bottom = Math.min(vh - rect.top + 8, vh - panelHeight - margin);\n panelEl.style.top = 'auto';\n panelEl.style.bottom = `${Math.max(margin, bottom)}px`;\n }\n}\n\nfunction saveButtonPosition(el: HTMLElement) {\n localStorage.setItem(\n '__ait_btn_pos',\n JSON.stringify({\n left: el.style.left,\n top: el.style.top,\n right: el.style.right,\n bottom: el.style.bottom,\n }),\n );\n}\n\n// Uses __ait_btn_pos (not __ait_storage: prefix) — panel-internal state, not mock storage\nfunction restoreButtonPosition(el: HTMLElement) {\n const saved = localStorage.getItem('__ait_btn_pos');\n if (saved) {\n try {\n const pos = JSON.parse(saved);\n if (typeof pos !== 'object' || pos === null) return;\n const allowedKeys = ['left', 'top', 'right', 'bottom'] as const;\n const validCssValue = /^(\\d+px|auto)$/;\n for (const key of allowedKeys) {\n if (key in pos && typeof pos[key] === 'string' && validCssValue.test(pos[key])) {\n el.style[key] = pos[key];\n }\n }\n } catch {\n /* ignore */\n }\n } else {\n el.style.bottom = '16px';\n el.style.right = '16px';\n }\n}\n\n// --- Mount ---\n\nlet currentTab: TabId = 'env';\nlet isOpen = false;\nlet panelEl: HTMLElement | null = null;\nlet bodyEl: HTMLElement | null = null;\nlet tabsEl: HTMLElement | null = null;\nlet toggleEl: HTMLElement | null = null;\nlet injectedStyle: HTMLStyleElement | null = null;\n\n// Saved listener refs so disposePanel() can detach them. Anonymous handlers\n// can't be removed, so mount() now binds these to module-level vars.\nlet panelSwitchTabHandler: ((e: Event) => void) | null = null;\nlet resizeHandler: (() => void) | null = null;\nlet aitStateUnsubscribe: (() => void) | null = null;\nlet localeChangeHandler: (() => void) | null = null;\n\n// Lazy-initialized after refreshPanel is defined\nlet tabRenderers: Record<TabId, () => HTMLElement> | null = null;\n\nfunction refreshPanel() {\n if (!bodyEl || !tabsEl) return;\n if (!tabRenderers) tabRenderers = createTabRenderers(refreshPanel);\n bodyEl.innerHTML = '';\n try {\n bodyEl.appendChild(tabRenderers[currentTab]());\n } catch (err) {\n console.error(`[@ait-co/devtools] Error rendering tab \"${currentTab}\":`, err);\n bodyEl.appendChild(\n h('div', { className: 'ait-panel-tab-error' }, t('panel.tabError', { tab: currentTab })),\n );\n }\n\n tabsEl.querySelectorAll('.ait-panel-tab').forEach((el) => {\n el.classList.toggle('active', el.getAttribute('data-tab') === currentTab);\n });\n}\n\nfunction mount() {\n if (typeof document === 'undefined') return;\n if (document.querySelector('.ait-panel-toggle')) return;\n\n // Wire up device tab's refreshPanel reference\n setDeviceRefreshPanel(refreshPanel);\n\n // Viewport simulation: restore from sessionStorage, apply to DOM, auto-sync.\n initViewport();\n\n // Styles\n injectedStyle = document.createElement('style');\n injectedStyle.textContent = PANEL_STYLES;\n document.head.appendChild(injectedStyle);\n\n // Toggle button\n const toggle = h(\n 'button',\n { className: 'ait-panel-toggle', title: t('panel.toggle.title') },\n 'AIT',\n );\n toggleEl = toggle;\n restoreButtonPosition(toggle);\n\n // Panel\n panelEl = h('div', { className: 'ait-panel' });\n\n const closeBtn = h('button', { className: 'ait-panel-close', title: t('panel.close') }, '\\u00d7');\n closeBtn.addEventListener('click', () => {\n isOpen = false;\n panelEl!.classList.remove('open');\n telemetry.onPanelClose();\n });\n\n const mockBadge = h(\n 'span',\n {\n className: `ait-mock-badge ${aitState.state.panelEditable ? 'ait-mock-badge-on' : 'ait-mock-badge-off'}`,\n title: t('panel.editMode.toggleTitle'),\n },\n aitState.state.panelEditable ? t('panel.editMode.on') : t('panel.editMode.off'),\n );\n\n mockBadge.addEventListener('click', () => {\n aitState.update({ panelEditable: !aitState.state.panelEditable });\n mockBadge.className = `ait-mock-badge ${aitState.state.panelEditable ? 'ait-mock-badge-on' : 'ait-mock-badge-off'}`;\n mockBadge.textContent = aitState.state.panelEditable\n ? t('panel.editMode.on')\n : t('panel.editMode.off');\n refreshPanel();\n });\n\n const headerRight = h(\n 'span',\n { style: 'display:flex;align-items:center;gap:6px' },\n mockBadge,\n h('span', { style: 'font-size:11px;color:#666;font-weight:400' }, `v${__VERSION__}`),\n closeBtn,\n );\n const header = h(\n 'div',\n { className: 'ait-panel-header' },\n h('span', {}, t('panel.title')),\n headerRight,\n );\n\n tabsEl = h('div', { className: 'ait-panel-tabs' });\n for (const tab of getTabs()) {\n const tabEl = h('button', { className: 'ait-panel-tab', 'data-tab': tab.id }, tab.label);\n tabEl.addEventListener('click', () => {\n currentTab = tab.id;\n telemetry.onTabView(tab.id);\n refreshPanel();\n });\n tabsEl.appendChild(tabEl);\n }\n\n bodyEl = h('div', { className: 'ait-panel-body' });\n\n panelEl.append(header, tabsEl, bodyEl);\n document.body.append(panelEl, toggle);\n\n // Re-clamp restored position to current viewport (e.g., saved on wider screen)\n snapToEdge(toggle);\n saveButtonPosition(toggle);\n\n makeDraggable(toggle, () => {\n isOpen = !isOpen;\n panelEl!.classList.toggle('open', isOpen);\n if (isOpen) {\n updatePanelPosition(toggle);\n refreshPanel();\n telemetry.onPanelOpen();\n } else {\n telemetry.onPanelClose();\n }\n });\n\n // Re-clamp button and panel position on window resize (rAF-throttled)\n let resizeRaf = 0;\n resizeHandler = () => {\n if (resizeRaf) return;\n resizeRaf = requestAnimationFrame(() => {\n resizeRaf = 0;\n snapToEdge(toggle);\n saveButtonPosition(toggle);\n if (isOpen) updatePanelPosition(toggle);\n });\n };\n window.addEventListener('resize', resizeHandler);\n\n // 상태 변경 시 자동 갱신 (env, analytics, storage, device, viewport, iap 탭)\n // Defense-in-depth: outer catch complements refreshPanel's inner tab-rendering catch.\n // env 탭은 Navigation 섹션이 setIosSwipeGestureEnabled 같은 SDK no-op API의 호출값을\n // read-only로 비추므로, 앱이 그 API를 호출하면 패널이 실시간 반영해야 한다. 이 탭의\n // 입력 필드(appVersion/locale/safeArea)는 모두 'change'(blur/Enter) 커밋이라\n // 재렌더가 입력 중 데이터를 잃지 않는다 — viewport 탭과 동일한 트레이드오프.\n aitStateUnsubscribe = aitState.subscribe(() => {\n try {\n if (\n isOpen &&\n (currentTab === 'env' ||\n currentTab === 'analytics' ||\n currentTab === 'storage' ||\n currentTab === 'device' ||\n currentTab === 'viewport' ||\n currentTab === 'iap' ||\n currentTab === 'ads' ||\n currentTab === 'presets')\n ) {\n refreshPanel();\n }\n } catch (err) {\n console.error('[@ait-co/devtools] Error in subscribe callback:', err);\n }\n\n // MCP state push: when the unplugin `mcp: true` option is set, the Vite\n // dev server exposes POST /api/ait-devtools/state. Push a snapshot there\n // on every state change so the MCP stdio server can serve it to AI agents.\n // Fire-and-forget — failures are debug-level only; never block the panel.\n pushStateToMcpEndpoint(aitState.state);\n });\n\n // Listen for tab switch requests from device tab (prompt auto-open).\n // Bound here (not at module scope) so disposePanel() can detach it; outside\n // of a mount, switching tabs has no panel to act on anyway.\n panelSwitchTabHandler = (e: Event) => {\n const detail = (e as CustomEvent).detail as { tab: TabId };\n currentTab = detail.tab;\n if (panelEl && !panelEl.classList.contains('open')) {\n isOpen = true;\n panelEl.classList.add('open');\n }\n refreshPanel();\n };\n window.addEventListener('__ait:panel-switch-tab', panelSwitchTabHandler);\n\n // Locale change → tear down the panel and re-mount so every string in the\n // tree re-evaluates against the new catalog. The mount path is already\n // idempotent (`document.querySelector('.ait-panel-toggle')` guard), so\n // dispose+mount is the simplest \"re-render whole panel\" hook.\n localeChangeHandler = () => {\n // disposePanel() resets currentTab to 'env' and isOpen to false. Capture\n // them first so the user stays on the same tab and the panel doesn't\n // close out from under them mid-interaction.\n const savedTab = currentTab;\n const savedOpen = isOpen;\n disposePanel();\n try {\n mount();\n currentTab = savedTab;\n if (savedOpen && panelEl) {\n isOpen = true;\n panelEl.classList.add('open');\n }\n refreshPanel();\n } catch (err) {\n console.error('[@ait-co/devtools] Failed to re-mount after locale change:', err);\n }\n };\n window.addEventListener(LOCALE_CHANGE_EVENT, localeChangeHandler);\n\n refreshPanel();\n\n // Telemetry: check consent state, show toast if needed, fire panel_mount if granted.\n telemetry.init();\n}\n\n/**\n * Pairs with `mount()` (and the existing `disposeViewport()`).\n * Idempotent — safe to call before mount or twice in a row.\n *\n * Removes panel DOM (toggle + panel root), the injected `<style>`, all\n * window/aitState listeners, and resets module-level state. After dispose,\n * `mount()` can be called again to re-mount cleanly.\n */\nfunction disposePanel(): void {\n if (typeof document === 'undefined') return;\n\n if (panelSwitchTabHandler && typeof window !== 'undefined') {\n window.removeEventListener('__ait:panel-switch-tab', panelSwitchTabHandler);\n }\n if (resizeHandler && typeof window !== 'undefined') {\n window.removeEventListener('resize', resizeHandler);\n }\n if (localeChangeHandler && typeof window !== 'undefined') {\n window.removeEventListener(LOCALE_CHANGE_EVENT, localeChangeHandler);\n }\n if (aitStateUnsubscribe) aitStateUnsubscribe();\n\n toggleEl?.remove();\n panelEl?.remove();\n injectedStyle?.remove();\n\n disposeViewport();\n setDeviceRefreshPanel(() => {});\n\n panelSwitchTabHandler = null;\n resizeHandler = null;\n localeChangeHandler = null;\n aitStateUnsubscribe = null;\n toggleEl = null;\n panelEl = null;\n bodyEl = null;\n tabsEl = null;\n injectedStyle = null;\n tabRenderers = null;\n currentTab = 'env';\n isOpen = false;\n}\n\n// DOM ready 시 마운트\nif (typeof document !== 'undefined') {\n const safeMount = () => {\n try {\n mount();\n } catch (err) {\n console.error('[@ait-co/devtools] Failed to mount panel:', err);\n }\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', safeMount);\n } else {\n safeMount();\n }\n}\n\nexport { disposePanel, mount };\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["refreshPanel"],"sources":["../../src/i18n/en.ts","../../src/i18n/ko.ts","../../src/i18n/index.ts","../../src/mock/state.ts","../../src/telemetry/consent-toast.ts","../../src/telemetry/state.ts","../../src/telemetry/send.ts","../../src/telemetry/tier0.ts","../../src/telemetry/index.ts","../../src/panel/helpers.ts","../../src/panel/styles.ts","../../src/mock/device/_helpers.ts","../../src/mock/permissions.ts","../../src/mock/device/camera.ts","../../src/mock/device/clipboard.ts","../../src/mock/device/contacts.ts","../../src/mock/device/haptic.ts","../../src/mock/device/location.ts","../../src/mock/proxy.ts","../../src/mock/device/storage.ts","../../src/panel/tabs/device.ts","../../src/mock/observe.ts","../../src/mock/ads/index.ts","../../src/panel/tabs/ads.ts","../../src/panel/tabs/analytics.ts","../../src/panel/tabs/environment.ts","../../src/panel/tabs/events.ts","../../src/mock/iap/index.ts","../../src/panel/tabs/iap.ts","../../src/panel/tabs/location.ts","../../src/panel/tabs/notifications.ts","../../src/panel/tabs/permissions.ts","../../src/mock/preset-store.ts","../../src/mock/presets.ts","../../src/panel/tabs/presets.ts","../../src/panel/tabs/storage.ts","../../src/mock/navigation/index.ts","../../src/panel/device-emulation.ts","../../src/panel/viewport.ts","../../src/panel/tabs/viewport.ts","../../src/panel/tabs/index.ts","../../src/panel/index.ts"],"sourcesContent":["import type { StringKey } from './ko.js';\n\n// English translations. Mirrors every key in `ko.ts`; missing keys fall back to\n// the key string at runtime (see `t()` in index.ts), but the `Record<StringKey,\n// string>` type below means a missing key will typecheck-fail.\n\nexport const en: Record<StringKey, string> = {\n // Panel chrome\n 'panel.title': 'AIT DevTools',\n 'panel.toggle.title': 'AIT DevTools',\n 'panel.close': 'Close',\n 'panel.editMode.on': 'EDIT',\n 'panel.editMode.off': 'READ-ONLY',\n 'panel.editMode.toggleTitle': 'Toggle panel edit mode',\n 'panel.tabError': 'Error rendering \"{tab}\" tab.',\n\n // Tab names\n 'panel.tab.env': 'Environment',\n 'panel.tab.presets': 'Presets',\n 'panel.tab.viewport': 'Viewport',\n 'panel.tab.permissions': 'Permissions',\n 'panel.tab.notifications': 'Notifications',\n 'panel.tab.location': 'Location',\n 'panel.tab.device': 'Device',\n 'panel.tab.iap': 'IAP',\n 'panel.tab.ads': 'Ads',\n 'panel.tab.events': 'Events',\n 'panel.tab.analytics': 'Analytics',\n 'panel.tab.storage': 'Storage',\n\n // Common\n 'common.readOnly': 'Read-only — mock responses are controlled at build time.',\n\n // Consent toast\n 'toast.consent.title': 'Send anonymous usage stats?',\n 'toast.consent.body':\n 'We collect anonymous events only, to improve the tool. You can turn this off anytime in the Environment tab.',\n 'toast.consent.learnMore': 'Learn more',\n 'toast.consent.accept': 'Yes, send',\n 'toast.consent.deny': 'No, thanks',\n\n // Environment tab\n 'env.section.platform': 'Platform',\n 'env.row.os': 'OS',\n 'env.row.appVersion': 'App Version',\n 'env.row.environment': 'Environment',\n 'env.row.locale': 'Locale',\n 'env.section.network': 'Network',\n 'env.row.networkStatus': 'Status',\n 'env.section.safeArea': 'Safe Area Insets',\n 'env.row.safeArea.top': 'Top',\n 'env.row.safeArea.bottom': 'Bottom',\n 'env.section.navigation': 'Navigation',\n 'env.row.iosSwipeGesture': 'iOS swipe-back',\n 'env.value.iosSwipeGesture.unset': 'not called',\n 'env.value.iosSwipeGesture.enabled': 'enabled',\n 'env.value.iosSwipeGesture.disabled': 'disabled',\n 'env.hint.iosSwipeGesture':\n 'Last value passed to setIosSwipeGestureEnabled. Switching Environment to toss lets a toss-gated guard toggle this.',\n\n // Environment > Telemetry section\n 'env.telemetry.section': 'Telemetry',\n // Tier 0 — opt-out anonymous signal\n 'env.telemetry.t0Row': 'Anonymous usage signal (Tier 0)',\n 'env.telemetry.t0On': 'On',\n 'env.telemetry.t0Off': 'Off',\n 'env.telemetry.t0TurnOn': 'Turn on',\n 'env.telemetry.t0TurnOff': 'Turn off',\n 'env.telemetry.t0Desc': 'Version + date only, no PII. Once per day. Helps improve the package.',\n // Tier 1 — opt-in extended telemetry\n 'env.telemetry.row': 'Extended telemetry (Tier 1)',\n 'env.telemetry.on': 'On',\n 'env.telemetry.off': 'Off',\n 'env.telemetry.turnOn': 'Turn on',\n 'env.telemetry.turnOff': 'Turn off',\n 'env.telemetry.anonIdLabel': 'anon_id: {value}',\n 'env.telemetry.anonIdNotSet': '(not yet set)',\n 'env.telemetry.anonIdCopyTitle': 'Click to copy full anon_id',\n 'env.telemetry.deleteBtn': 'Delete my data',\n 'env.telemetry.deleting': 'Deleting…',\n 'env.telemetry.deleted': 'Deleted',\n 'env.telemetry.deleteFailedRetry': 'Delete failed (please retry)',\n 'env.telemetry.deleteFailed': 'Delete failed',\n 'env.telemetry.privacyLink': 'Privacy policy →',\n\n // Environment > Language toggle (new)\n 'env.section.language': 'Language',\n 'env.language.row': 'Language',\n 'env.language.ko': '한국어',\n 'env.language.en': 'English',\n\n // Permissions tab\n 'permissions.section.device': 'Device Permissions',\n\n // Location tab\n 'location.section.current': 'Current Location',\n 'location.row.latitude': 'Latitude',\n 'location.row.longitude': 'Longitude',\n 'location.row.accuracy': 'Accuracy',\n\n // Device tab\n 'device.section.modes': 'Device API Modes',\n 'device.row.camera': 'Camera',\n 'device.row.photos': 'Photos',\n 'device.row.location': 'Location',\n 'device.row.network': 'Network',\n 'device.row.clipboard': 'Clipboard',\n 'device.section.mockImages': 'Mock Images ({count})',\n 'device.btn.add': '+ Add',\n 'device.btn.useDefaults': 'Use defaults',\n 'device.btn.clear': 'Clear',\n 'device.prompt.camera.title': 'Camera Prompt — Select an image',\n 'device.prompt.photos.title': 'Photos Prompt — Select images',\n 'device.prompt.location.title': 'Location Prompt — Enter coordinates',\n 'device.prompt.locationUpdate.title': 'Location Update — Send coordinates',\n 'device.prompt.fallbackTitle': 'Prompt: {type}',\n 'device.prompt.label.lat': 'Lat',\n 'device.prompt.label.lng': 'Lng',\n 'device.prompt.send': 'Send',\n 'device.prompt.cancel': 'Cancel',\n\n // Device tab — Haptic section\n 'device.section.haptic': 'Haptic',\n 'device.haptic.lastCall': 'Last haptic',\n 'device.haptic.noneYet': '(none yet)',\n 'device.haptic.trigger': 'Trigger haptic',\n\n // Viewport tab\n 'viewport.section.device': 'Device',\n 'viewport.row.preset': 'Preset',\n 'viewport.row.orientation': 'Orientation',\n 'viewport.row.notchSide': 'Notch side',\n 'viewport.section.custom': 'Custom size',\n 'viewport.row.width': 'Width (px)',\n 'viewport.row.height': 'Height (px)',\n 'viewport.section.appearance': 'Appearance',\n 'viewport.row.showFrame': 'Show frame',\n 'viewport.row.showAitNavBar': 'Show Apps in Toss nav bar',\n 'viewport.row.navBarType': 'Nav bar type',\n 'viewport.status.noConstraint': 'No viewport constraint — body fills the window.',\n 'viewport.status.cssPhysical': 'CSS / physical',\n 'viewport.status.safeArea': 'Safe area',\n 'viewport.status.aitNavBar': 'AIT nav bar',\n 'viewport.status.aitNavBarValue': '{height}px → SafeArea top · {type}',\n 'viewport.orientation.autoSuffix': '{orient} (auto)',\n\n // IAP tab\n 'iap.section.simulator': 'IAP Simulator',\n 'iap.row.nextResult': 'Next Purchase Result',\n 'iap.section.tossPay': 'TossPay',\n 'iap.row.tossPayResult': 'Next Payment Result',\n 'iap.section.pending': 'Pending Orders ({count})',\n 'iap.empty.pending': '(no pending orders)',\n 'iap.section.completed': 'Completed Orders ({count})',\n 'iap.empty.completed': '(no completed orders)',\n 'iap.btn.complete': 'Complete',\n 'iap.label.pending': 'PENDING',\n\n // Events tab\n 'events.section.navigation': 'Navigation Events',\n 'events.btn.triggerBack': 'Trigger Back Event',\n 'events.btn.triggerHome': 'Trigger Home Event',\n 'events.section.login': 'Login',\n 'events.row.loggedIn': 'Logged In',\n 'events.row.tossLoginIntegrated': 'Toss Login Integrated',\n\n // Analytics tab\n 'analytics.section.log': 'Analytics Log ({count})',\n 'analytics.btn.clear': 'Clear',\n 'analytics.calls.section': 'SDK Calls ({count})',\n 'analytics.calls.btn.clear': 'Clear',\n 'analytics.calls.empty': '(no SDK calls yet)',\n\n // Storage tab\n 'storage.section.title': 'Storage ({count} items)',\n 'storage.btn.clearAll': 'Clear All',\n 'storage.empty': 'No items in storage',\n\n // Presets tab\n 'presets.section.builtIn': 'Built-in scenarios',\n 'presets.section.saved': 'Saved presets ({count})',\n 'presets.section.save': 'Save',\n 'presets.save.description': 'Capture network / permissions / auth / IAP / ads / payment slices.',\n 'presets.btn.saveCurrent': 'Save current as preset',\n 'presets.btn.apply': 'Apply',\n 'presets.btn.reApply': 'Re-apply',\n 'presets.btn.delete': 'Delete',\n 'presets.empty.saved': 'No saved presets yet.',\n 'presets.empty.builtIn': 'No built-in presets.',\n 'presets.prompt.label': 'Preset label?',\n 'presets.confirm.delete': 'Delete preset \"{label}\"?',\n\n // Ads tab\n 'ads.section.state': 'Ads State',\n 'ads.row.isLoaded': 'isLoaded',\n 'ads.row.forceNoFill': 'Force \"no fill\"',\n 'ads.empty.events': 'No events yet',\n 'ads.section.googleAdMob': 'GoogleAdMob',\n 'ads.section.tossAds': 'TossAds',\n 'ads.section.fullScreenAd': 'FullScreenAd',\n 'ads.btn.load': 'Load',\n 'ads.btn.show': 'Show',\n 'ads.section.tossAdsBanner': 'TossAds Banner',\n 'ads.row.rewardUnitType': 'Reward unit type',\n 'ads.row.rewardAmount': 'Reward amount',\n 'ads.btn.render': 'Render',\n 'ads.btn.noFill': 'No-fill',\n 'ads.btn.click': 'Click',\n 'ads.btn.destroy': 'Destroy',\n\n // Notifications tab\n 'notifications.section.title': 'requestNotificationAgreement',\n 'notifications.option.newAgreement': 'newAgreement (first-time agree)',\n 'notifications.option.alreadyAgreed': 'alreadyAgreed (already opted-in)',\n 'notifications.option.agreementRejected': 'agreementRejected (user declined)',\n};\n","// Korean string catalog (source of truth — keys are typed from this file).\n// Keys follow `<area>.<purpose>` convention. Variable interpolation uses\n// `{name}` placeholders resolved by `t(key, { name: value })`.\n//\n// Some chrome (button labels like \"Load\", \"Show\", \"Clear\", \"Apply\") is left as\n// English in both locales because the panel is an internal devtools surface\n// and these terms are universally recognised by developers in both locales.\n\nexport const ko = {\n // Panel chrome\n 'panel.title': 'AIT DevTools',\n 'panel.toggle.title': 'AIT DevTools',\n 'panel.close': 'Close',\n 'panel.editMode.on': 'EDIT',\n 'panel.editMode.off': 'READ-ONLY',\n 'panel.editMode.toggleTitle': '패널 편집 모드 전환',\n 'panel.tabError': '\"{tab}\" 탭 렌더링 중 오류가 발생했습니다.',\n\n // Tab names\n 'panel.tab.env': 'Environment',\n 'panel.tab.presets': 'Presets',\n 'panel.tab.viewport': 'Viewport',\n 'panel.tab.permissions': 'Permissions',\n 'panel.tab.notifications': 'Notifications',\n 'panel.tab.location': 'Location',\n 'panel.tab.device': 'Device',\n 'panel.tab.iap': 'IAP',\n 'panel.tab.ads': 'Ads',\n 'panel.tab.events': 'Events',\n 'panel.tab.analytics': 'Analytics',\n 'panel.tab.storage': 'Storage',\n\n // Common\n 'common.readOnly': '읽기 전용 — mock 응답은 빌드 타임에 고정됩니다.',\n\n // Consent toast\n 'toast.consent.title': '익명 사용 통계를 보낼까요?',\n 'toast.consent.body': '도구 개선을 위해 익명 이벤트만 수집해요. 언제든 환경 탭에서 끌 수 있어요.',\n 'toast.consent.learnMore': '더 알아보기',\n 'toast.consent.accept': '네, 보낼게요',\n 'toast.consent.deny': '아니요',\n\n // Environment tab\n 'env.section.platform': 'Platform',\n 'env.row.os': 'OS',\n 'env.row.appVersion': 'App Version',\n 'env.row.environment': 'Environment',\n 'env.row.locale': 'Locale',\n 'env.section.network': 'Network',\n 'env.row.networkStatus': 'Status',\n 'env.section.safeArea': 'Safe Area Insets',\n 'env.row.safeArea.top': 'Top',\n 'env.row.safeArea.bottom': 'Bottom',\n 'env.section.navigation': 'Navigation',\n 'env.row.iosSwipeGesture': 'iOS swipe-back',\n 'env.value.iosSwipeGesture.unset': '미호출',\n 'env.value.iosSwipeGesture.enabled': 'enabled',\n 'env.value.iosSwipeGesture.disabled': 'disabled',\n 'env.hint.iosSwipeGesture':\n 'setIosSwipeGestureEnabled의 마지막 호출값. Environment를 toss로 바꾸면 toss-gated 가드가 이 값을 토글합니다.',\n\n // Environment > Telemetry section\n 'env.telemetry.section': 'Telemetry',\n // Tier 0 — opt-out anonymous signal\n 'env.telemetry.t0Row': '익명 사용 신호 (Tier 0)',\n 'env.telemetry.t0On': 'On',\n 'env.telemetry.t0Off': 'Off',\n 'env.telemetry.t0TurnOn': 'Turn on',\n 'env.telemetry.t0TurnOff': 'Turn off',\n 'env.telemetry.t0Desc': '버전·날짜만 수집, PII 없음. 하루 1회. 패키지 개선에 사용됩니다.',\n // Tier 1 — opt-in extended telemetry\n 'env.telemetry.row': '확장 텔레메트리 (Tier 1)',\n 'env.telemetry.on': 'On',\n 'env.telemetry.off': 'Off',\n 'env.telemetry.turnOn': 'Turn on',\n 'env.telemetry.turnOff': 'Turn off',\n 'env.telemetry.anonIdLabel': 'anon_id: {value}',\n 'env.telemetry.anonIdNotSet': '(not yet set)',\n 'env.telemetry.anonIdCopyTitle': '전체 anon_id 복사',\n 'env.telemetry.deleteBtn': '내 데이터 삭제',\n 'env.telemetry.deleting': '삭제 중…',\n 'env.telemetry.deleted': '삭제 완료',\n 'env.telemetry.deleteFailedRetry': '삭제 실패 (다시 시도해주세요)',\n 'env.telemetry.deleteFailed': '삭제 실패',\n 'env.telemetry.privacyLink': '개인정보 처리방침 →',\n\n // Environment > Language toggle (new)\n 'env.section.language': 'Language',\n 'env.language.row': 'Language',\n 'env.language.ko': '한국어',\n 'env.language.en': 'English',\n\n // Permissions tab\n 'permissions.section.device': 'Device Permissions',\n\n // Location tab\n 'location.section.current': 'Current Location',\n 'location.row.latitude': 'Latitude',\n 'location.row.longitude': 'Longitude',\n 'location.row.accuracy': 'Accuracy',\n\n // Device tab\n 'device.section.modes': 'Device API Modes',\n 'device.row.camera': 'Camera',\n 'device.row.photos': 'Photos',\n 'device.row.location': 'Location',\n 'device.row.network': 'Network',\n 'device.row.clipboard': 'Clipboard',\n 'device.section.mockImages': 'Mock Images ({count})',\n 'device.btn.add': '+ Add',\n 'device.btn.useDefaults': 'Use defaults',\n 'device.btn.clear': 'Clear',\n 'device.prompt.camera.title': 'Camera Prompt — 이미지를 선택하세요',\n 'device.prompt.photos.title': 'Photos Prompt — 이미지를 선택하세요',\n 'device.prompt.location.title': 'Location Prompt — 좌표 입력',\n 'device.prompt.locationUpdate.title': 'Location Update — 좌표 전송',\n 'device.prompt.fallbackTitle': 'Prompt: {type}',\n 'device.prompt.label.lat': 'Lat',\n 'device.prompt.label.lng': 'Lng',\n 'device.prompt.send': 'Send',\n 'device.prompt.cancel': 'Cancel',\n\n // Device tab — Haptic section\n 'device.section.haptic': 'Haptic',\n 'device.haptic.lastCall': '마지막 haptic',\n 'device.haptic.noneYet': '(아직 없음)',\n 'device.haptic.trigger': 'Haptic 트리거',\n\n // Viewport tab\n 'viewport.section.device': 'Device',\n 'viewport.row.preset': 'Preset',\n 'viewport.row.orientation': 'Orientation',\n 'viewport.row.notchSide': 'Notch side',\n 'viewport.section.custom': 'Custom size',\n 'viewport.row.width': 'Width (px)',\n 'viewport.row.height': 'Height (px)',\n 'viewport.section.appearance': 'Appearance',\n 'viewport.row.showFrame': 'Show frame',\n 'viewport.row.showAitNavBar': 'Apps in Toss 내비게이션 바 표시',\n 'viewport.row.navBarType': 'Nav bar type',\n 'viewport.status.noConstraint': '뷰포트 제약 없음 — body가 창을 가득 채웁니다.',\n 'viewport.status.cssPhysical': 'CSS / physical',\n 'viewport.status.safeArea': 'Safe area',\n 'viewport.status.aitNavBar': 'AIT nav bar',\n 'viewport.status.aitNavBarValue': '{height}px → SafeArea top · {type}',\n 'viewport.orientation.autoSuffix': '{orient} (auto)',\n\n // IAP tab\n 'iap.section.simulator': 'IAP Simulator',\n 'iap.row.nextResult': 'Next Purchase Result',\n 'iap.section.tossPay': 'TossPay',\n 'iap.row.tossPayResult': 'Next Payment Result',\n 'iap.section.pending': 'Pending Orders ({count})',\n 'iap.empty.pending': '(대기 중인 주문 없음)',\n 'iap.section.completed': 'Completed Orders ({count})',\n 'iap.empty.completed': '(완료된 주문 없음)',\n 'iap.btn.complete': 'Complete',\n 'iap.label.pending': 'PENDING',\n\n // Events tab\n 'events.section.navigation': 'Navigation Events',\n 'events.btn.triggerBack': 'Back 이벤트 발생',\n 'events.btn.triggerHome': 'Home 이벤트 발생',\n 'events.section.login': 'Login',\n 'events.row.loggedIn': 'Logged In',\n 'events.row.tossLoginIntegrated': 'Toss Login Integrated',\n\n // Analytics tab\n 'analytics.section.log': 'Analytics Log ({count})',\n 'analytics.btn.clear': 'Clear',\n 'analytics.calls.section': 'SDK Calls ({count})',\n 'analytics.calls.btn.clear': 'Clear',\n 'analytics.calls.empty': '(아직 SDK 호출 없음)',\n\n // Storage tab\n 'storage.section.title': 'Storage ({count} items)',\n 'storage.btn.clearAll': 'Clear All',\n 'storage.empty': '저장된 항목이 없습니다',\n\n // Presets tab\n 'presets.section.builtIn': 'Built-in scenarios',\n 'presets.section.saved': 'Saved presets ({count})',\n 'presets.section.save': 'Save',\n 'presets.save.description':\n 'network / permissions / auth / IAP / ads / payment 슬라이스를 캡처합니다.',\n 'presets.btn.saveCurrent': '현재 상태를 프리셋으로 저장',\n 'presets.btn.apply': 'Apply',\n 'presets.btn.reApply': 'Re-apply',\n 'presets.btn.delete': 'Delete',\n 'presets.empty.saved': '저장된 프리셋이 아직 없습니다.',\n 'presets.empty.builtIn': '내장 프리셋이 없습니다.',\n 'presets.prompt.label': '프리셋 라벨을 입력하세요',\n 'presets.confirm.delete': '\"{label}\" 프리셋을 삭제할까요?',\n\n // Ads tab\n 'ads.section.state': 'Ads State',\n 'ads.row.isLoaded': 'isLoaded',\n 'ads.row.forceNoFill': '강제 \"no fill\"',\n 'ads.empty.events': '아직 이벤트가 없습니다',\n 'ads.section.googleAdMob': 'GoogleAdMob',\n 'ads.section.tossAds': 'TossAds',\n 'ads.section.fullScreenAd': 'FullScreenAd',\n 'ads.btn.load': 'Load',\n 'ads.btn.show': 'Show',\n 'ads.section.tossAdsBanner': 'TossAds 배너',\n 'ads.row.rewardUnitType': '리워드 단위 타입',\n 'ads.row.rewardAmount': '리워드 수량',\n 'ads.btn.render': 'Render',\n 'ads.btn.noFill': 'No-fill',\n 'ads.btn.click': 'Click',\n 'ads.btn.destroy': 'Destroy',\n\n // Notifications tab\n 'notifications.section.title': 'requestNotificationAgreement',\n 'notifications.option.newAgreement': 'newAgreement (최초 동의)',\n 'notifications.option.alreadyAgreed': 'alreadyAgreed (이미 동의됨)',\n 'notifications.option.agreementRejected': 'agreementRejected (사용자 거절)',\n} as const;\n\nexport type StringKey = keyof typeof ko;\n","/**\n * Vanilla TS i18n for the floating DevTools panel.\n *\n * Public surface:\n * - `t(key, vars?)` — look up a UI string, with `{name}` placeholder\n * interpolation. Falls back to the key itself if a translation is missing.\n * - `getLocale()` / `setLocale(locale)` — read/persist the active locale.\n * `setLocale` dispatches `__ait:localechange` so the panel can remount.\n * - `detectLocale()` — first-run heuristic from `navigator.language`.\n *\n * `ko` is the source of truth (keys are typed from it). `en` is also a full\n * `Record<StringKey, string>` (devtools is developer-facing, en is a real\n * audience). The `Partial` lookup table preserves the runtime `?? key` safety\n * net even though we ship complete catalogs today.\n */\n\nimport { en } from './en.js';\nimport { ko, type StringKey } from './ko.js';\n\nexport type Locale = 'ko' | 'en';\n\nconst LOCALE_STORAGE_KEY = '__ait_locale';\nconst LOCALE_CHANGE_EVENT = '__ait:localechange';\n\nconst tables: Record<Locale, Partial<Record<StringKey, string>>> = { ko, en };\n\nlet currentLocale: Locale | null = null;\n\nfunction safeReadStorage(): Locale | null {\n if (typeof localStorage === 'undefined') return null;\n try {\n const raw = localStorage.getItem(LOCALE_STORAGE_KEY);\n if (raw === 'ko' || raw === 'en') return raw;\n } catch {\n /* localStorage can throw in privacy modes — fall back silently */\n }\n return null;\n}\n\nfunction safeWriteStorage(locale: Locale): void {\n if (typeof localStorage === 'undefined') return;\n try {\n localStorage.setItem(LOCALE_STORAGE_KEY, locale);\n } catch {\n /* ignore quota / privacy errors */\n }\n}\n\n/**\n * Read `navigator.language` and decide a locale. `ko` (and `ko-*`) → `'ko'`,\n * everything else → `'en'`. Pure function; does not touch storage.\n */\nexport function detectLocale(): Locale {\n if (typeof navigator === 'undefined') return 'en';\n const lang = navigator.language ?? '';\n return /^ko\\b/i.test(lang) ? 'ko' : 'en';\n}\n\n/**\n * Resolve the active locale, in order:\n * 1. previously set in-memory value (set by `setLocale`)\n * 2. localStorage `__ait_locale`\n * 3. `detectLocale()` from navigator\n */\nexport function getLocale(): Locale {\n if (currentLocale) return currentLocale;\n const stored = safeReadStorage();\n currentLocale = stored ?? detectLocale();\n return currentLocale;\n}\n\n/**\n * Persist a locale choice and notify listeners. The panel listens for\n * `__ait:localechange` and re-mounts so every string re-evaluates.\n */\nexport function setLocale(locale: Locale): void {\n currentLocale = locale;\n safeWriteStorage(locale);\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(LOCALE_CHANGE_EVENT));\n }\n}\n\n/**\n * Look up a UI string for the current locale. Falls back to the key if missing,\n * so a forgotten key surfaces visibly rather than rendering empty.\n */\nexport function t(key: StringKey, vars?: Record<string, string | number>): string {\n const raw = tables[getLocale()][key] ?? key;\n if (!vars) return raw;\n return raw.replace(/\\{(\\w+)\\}/g, (match, name: string) => {\n const value = vars[name];\n return value === undefined ? match : String(value);\n });\n}\n\nexport type { StringKey };\nexport { LOCALE_CHANGE_EVENT, LOCALE_STORAGE_KEY };\n\n/**\n * Test-only escape hatch — resets the cached in-memory locale so subsequent\n * `getLocale()` calls re-read storage / re-detect. Production code never needs\n * this; tests use it between cases.\n */\nexport function _resetLocaleCacheForTests(): void {\n currentLocale = null;\n}\n","/**\n * @ait-co/devtools 중앙 상태 관리\n * DevTools Panel과 mock 구현체가 이 상태를 공유한다.\n */\n\nimport type { AitSdkCall } from '../mcp/ait-source.js';\nimport type {\n AnalyticsLogEntry,\n DeviceModes,\n IapNextResult,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n NotificationAgreementResult,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n ViewportState,\n} from './types.js';\n\nexport type { AitSdkCall, AitSdkCallFidelity } from '../mcp/ait-source.js';\nexport type {\n AitNavBarType,\n AnalyticsLogEntry,\n AppOrientation,\n DeviceApiMode,\n DeviceModes,\n HapticFeedbackType,\n IapNextResult,\n LandscapeSide,\n LocationCoords,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n NotchType,\n NotificationAgreementResult,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n SafeAreaProvenance,\n ViewportOrientation,\n ViewportPreset,\n ViewportPresetId,\n ViewportState,\n} from './types.js';\n\ntype Listener = () => void;\n\n/** SDK 호출 로그 ring buffer 상한 */\nconst SDK_CALL_LOG_MAX = 200;\n\nexport interface AitDevtoolsState {\n // 환경\n platform: PlatformOS;\n environment: OperationalEnvironment;\n appVersion: string;\n locale: string;\n schemeUri: string;\n groupId: string;\n deploymentId: string;\n deviceId: string;\n\n // 브랜드\n brand: {\n displayName: string;\n icon: string;\n primaryColor: string;\n };\n\n // 네트워크\n networkStatus: NetworkStatus;\n\n // 네비게이션 동작 — real은 native bridge로 발화하는 no-op API들의 마지막 호출값을\n // 관측 가능한 state로 mirror (real ground-truth: devtools#171 on-device relay).\n // null = 앱이 아직 호출 안 함(real 기본 동작 = iOS 엣지 스와이프 뒤로가기 enabled).\n navigation: {\n iosSwipeGestureEnabled: boolean | null;\n };\n\n // 권한\n permissions: Record<PermissionName, PermissionStatus>;\n\n // 위치\n location: MockLocation;\n\n // Safe Area\n safeAreaInsets: SafeAreaInsets;\n\n // 연락처\n contacts: MockContact[];\n\n // IAP\n iap: {\n products: MockIapProduct[];\n nextResult: IapNextResult;\n pendingOrders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }>;\n completedOrders: Array<{\n orderId: string;\n sku: string;\n status: 'COMPLETED' | 'REFUNDED';\n date: string;\n }>;\n };\n\n // 결제 (TossPay)\n payment: {\n nextResult: 'success' | 'fail';\n failReason: string;\n };\n\n // 로그인\n auth: {\n isLoggedIn: boolean;\n isTossLoginIntegrated: boolean;\n userKeyHash: string;\n anonymousKeyHash: string;\n };\n\n // 알림\n notification: {\n nextResult: NotificationAgreementResult;\n };\n\n // 광고\n ads: {\n isLoaded: boolean;\n nextEvent:\n | 'loaded'\n | 'clicked'\n | 'dismissed'\n | 'failedToShow'\n | 'impression'\n | 'userEarnedReward';\n forceNoFill: boolean;\n lastEvent: { type: string; timestamp: number } | null;\n /** AdMob reward 단위 타입 (기본: 'coins') */\n rewardUnitType: string;\n /** AdMob reward 단위 수량 (기본: 10) */\n rewardAmount: number;\n };\n\n // 게임\n game: {\n profile: { nickname: string; profileImageUri: string } | null;\n leaderboardScores: Array<{ score: string; timestamp: number }>;\n };\n\n // 분석 로그\n analyticsLog: AnalyticsLogEntry[];\n\n // SDK 호출 로그 (ring buffer, 상한 SDK_CALL_LOG_MAX)\n sdkCallLog: AitSdkCall[];\n\n // 디바이스 API 모드\n deviceModes: DeviceModes;\n\n // mock 모드용 더미 데이터\n mockData: MockData;\n\n // mock 활성화 상태\n panelEditable: boolean;\n\n // 뷰포트 시뮬레이션 (devtools 전용, SDK와 무관)\n viewport: ViewportState;\n}\n\nconst DEFAULT_STATE: AitDevtoolsState = {\n platform: 'ios',\n environment: 'sandbox',\n appVersion: '5.240.0',\n locale: 'ko-KR',\n schemeUri: '/',\n groupId: 'mock-group-id',\n deploymentId: 'mock-deployment-id',\n deviceId: '',\n\n brand: {\n displayName: 'Mock App',\n icon: '',\n primaryColor: '#3182F6',\n },\n\n networkStatus: 'WIFI',\n\n // null = 앱이 setIosSwipeGestureEnabled를 아직 호출 안 함.\n navigation: {\n iosSwipeGestureEnabled: null,\n },\n\n permissions: {\n clipboard: 'allowed',\n contacts: 'allowed',\n photos: 'allowed',\n geolocation: 'allowed',\n camera: 'allowed',\n microphone: 'notDetermined',\n },\n\n location: {\n coords: {\n latitude: 37.5665,\n longitude: 126.978,\n altitude: 0,\n accuracy: 10,\n altitudeAccuracy: 0,\n heading: 0,\n },\n timestamp: Date.now(),\n accessLocation: 'FINE',\n },\n\n // iPhone 15 Pro relay 실측값(devtools#190)과 정합: partner WebView portrait에서\n // SafeAreaInsets.get()이 반환한 top=54(토스 nav bar 높이), bottom=34(home indicator).\n // env(safe-area-inset-top)는 0이었으므로 OS 노치는 이 top에 들어가지 않는다.\n // preset이 'none'/'custom'이면 syncSafeAreaFromViewport가 건드리지 않으므로 이 값이\n // SafeAreaInsets.get()의 out-of-box 계약값으로 남는다. preset을 고르면 그 값으로 sync됨.\n safeAreaInsets: { top: 54, bottom: 34, left: 0, right: 0 },\n\n contacts: [\n { name: '홍길동', phoneNumber: '010-1234-5678' },\n { name: '김토스', phoneNumber: '010-9876-5432' },\n ],\n\n iap: {\n products: [\n {\n sku: 'mock-gem-100',\n type: 'CONSUMABLE',\n displayName: '보석 100개',\n displayAmount: '1,000원',\n iconUrl: '',\n description: '게임에서 사용할 수 있는 보석 100개',\n },\n ],\n nextResult: 'success',\n pendingOrders: [],\n completedOrders: [],\n },\n\n payment: {\n nextResult: 'success',\n failReason: '',\n },\n\n auth: {\n isLoggedIn: true,\n isTossLoginIntegrated: true,\n userKeyHash: 'mock-user-hash-abc123',\n anonymousKeyHash: 'mock-anon-hash-xyz789',\n },\n\n notification: {\n nextResult: 'newAgreement',\n },\n\n ads: {\n isLoaded: false,\n nextEvent: 'loaded',\n forceNoFill: false,\n lastEvent: null,\n rewardUnitType: 'coins',\n rewardAmount: 10,\n },\n\n game: {\n profile: { nickname: 'MockPlayer', profileImageUri: '' },\n leaderboardScores: [],\n },\n\n analyticsLog: [],\n\n sdkCallLog: [],\n\n deviceModes: {\n camera: 'mock',\n photos: 'mock',\n location: 'mock',\n network: 'mock',\n // 'mock' so the clipboard mock is self-contained. With 'web' the mock\n // calls `navigator.clipboard.readText()` directly, which — when paired\n // with `@ait-co/polyfill` — recurses: polyfill routes `navigator.clipboard`\n // back to the SDK's `getClipboardText`, which is this mock, which calls\n // `navigator.clipboard.readText`, … Users who want true browser\n // clipboard integration can flip this to 'web' from the panel.\n clipboard: 'mock',\n },\n\n mockData: {\n images: [],\n clipboardText: '',\n },\n\n panelEditable: true,\n\n viewport: {\n preset: 'none',\n orientation: 'auto',\n appOrientation: null,\n customWidth: 402,\n customHeight: 874,\n frame: false,\n aitNavBar: true,\n aitNavBarType: 'partner',\n },\n};\n\nfunction generateDeviceId(): string {\n const stored = localStorage.getItem('__ait_device_id');\n if (stored) return stored;\n const id = crypto.randomUUID();\n localStorage.setItem('__ait_device_id', id);\n return id;\n}\n\nexport class AitStateManager {\n private _state: AitDevtoolsState;\n private _listeners = new Set<Listener>();\n private _inTransaction = false;\n\n constructor() {\n this._state = structuredClone(DEFAULT_STATE);\n try {\n this._state.deviceId = generateDeviceId();\n } catch {\n this._state.deviceId = `mock-device-${Math.random().toString(36).slice(2)}`;\n }\n }\n\n get state(): AitDevtoolsState {\n return this._state;\n }\n\n update(partial: Partial<AitDevtoolsState>) {\n this._state = { ...this._state, ...partial };\n this._notify();\n }\n\n /** 중첩 객체 업데이트용 */\n patch<K extends keyof AitDevtoolsState>(key: K, partial: Partial<AitDevtoolsState[K]>) {\n const current = this._state[key];\n if (typeof current === 'object' && current !== null && !Array.isArray(current)) {\n this._state = {\n ...this._state,\n [key]: { ...(current as Record<string, unknown>), ...(partial as Record<string, unknown>) },\n };\n } else {\n this._state = { ...this._state, [key]: partial as AitDevtoolsState[K] };\n }\n this._notify();\n }\n\n subscribe(listener: Listener): () => void {\n this._listeners.add(listener);\n return () => this._listeners.delete(listener);\n }\n\n /**\n * 한 묶음의 update/patch 호출을 묶어 listener notify 1회로 만든다.\n * preset 적용처럼 여러 슬라이스를 동시에 바꿀 때 panel re-render 폭주를\n * 방지한다. 중첩 호출은 outermost transaction이 끝날 때 한 번만 notify\n * (inner도 throw해도 outer finally가 flag를 복구한다).\n *\n * Rollback은 없다 — `fn`이 throw해도 그때까지의 state 변경은 유지된다.\n * 구독자가 partial state를 영원히 못 보는 사고를 막기 위해, throw 여부와\n * 무관하게 항상 한 번 notify한 뒤 throw를 propagate한다. DB transaction이\n * 아니라 \"여러 mutation을 한 notify로 묶는 batch\"라고 생각하면 된다.\n *\n * Listener는 throw해선 안 된다 — finally 안의 `_notify()`가 throw하면 원래\n * `fn`의 throw를 덮어버린다. 우리 구독자는 panel re-render뿐이라 실제\n * 발생 사례는 없지만, 외부에서 listener를 등록할 때 주의.\n */\n transaction(fn: () => void): void {\n if (this._inTransaction) {\n fn();\n return;\n }\n this._inTransaction = true;\n try {\n fn();\n } finally {\n this._inTransaction = false;\n this._notify();\n }\n }\n\n /** 분석 로그 추가 */\n logAnalytics(entry: Omit<AnalyticsLogEntry, 'timestamp'>) {\n this._state = {\n ...this._state,\n analyticsLog: [...this._state.analyticsLog, { ...entry, timestamp: Date.now() }],\n };\n this._notify();\n }\n\n /**\n * SDK 호출 로그 추가 (ring buffer, 상한 SDK_CALL_LOG_MAX).\n * `observe()`가 호출하고, proxy의 KNOWN_UNIMPLEMENTED 경로도 직접 호출한다.\n */\n logSdkCall(entry: AitSdkCall) {\n const log = this._state.sdkCallLog;\n const next = log.length >= SDK_CALL_LOG_MAX ? log.slice(1 - SDK_CALL_LOG_MAX) : log;\n this._state = { ...this._state, sdkCallLog: [...next, entry] };\n this._notify();\n }\n\n /** 이벤트 트리거 (backEvent, homeEvent 등) */\n trigger(event: string) {\n window.dispatchEvent(new CustomEvent(`__ait:${event}`));\n }\n\n reset() {\n const deviceId = this._state.deviceId;\n this._state = { ...structuredClone(DEFAULT_STATE), deviceId };\n this._notify();\n }\n\n private _notify() {\n if (this._inTransaction) return;\n for (const listener of this._listeners) {\n listener();\n }\n }\n}\n\n// `tsdown.config.ts`는 mock/panel/unplugin entry를 별도 config object로 빌드한다\n// (\"every entry is self-contained\"). 그 결과 소비자가 두 entry(예: `@ait-co/devtools` +\n// `@ait-co/devtools/panel`)를 동시에 import하면 `state.ts`가 entry별로 따로 번들되어\n// `AitStateManager` 인스턴스가 entry당 1개씩 만들어진다. panel이 toggle한 state는\n// mock SDK가 보는 state와 다른 인스턴스가 되어 모든 토글이 비기능이 된다.\n//\n// build pipeline을 건드리지 않고 runtime guard로 해결한다: globalThis에 인스턴스를\n// 캐시해 같은 페이지의 모든 entry가 동일 인스턴스를 공유하도록 한다.\nconst SINGLETON_KEY = '__aitDevtoolsStateSingleton__';\ntype GlobalWithSingleton = typeof globalThis & { [SINGLETON_KEY]?: AitStateManager };\nconst globalRef = globalThis as GlobalWithSingleton;\nif (!globalRef[SINGLETON_KEY]) {\n globalRef[SINGLETON_KEY] = new AitStateManager();\n}\nexport const aitState: AitStateManager = globalRef[SINGLETON_KEY]!;\n\n// 브라우저 콘솔에서 접근 가능하도록\nif (typeof window !== 'undefined') {\n window.__ait = aitState;\n}\n","/**\n * Consent toast UI — vanilla DOM, fixed bottom-right.\n *\n * Shows once per \"undecided + reprompt window cleared\" session.\n * Calls onAccept / onDeny callbacks; caller is responsible for persisting state.\n */\n\nimport { t } from '../i18n/index.js';\n\nconst TOAST_ID = '__ait-telemetry-toast';\n\nconst TOAST_STYLES = `\n #${TOAST_ID} {\n position: fixed;\n z-index: 100001;\n bottom: 80px;\n right: 16px;\n width: 280px;\n background: #1a1a2e;\n border: 1px solid #3a3a5a;\n border-radius: 10px;\n padding: 14px 16px;\n box-shadow: 0 4px 24px rgba(0,0,0,0.4);\n font-family: -apple-system, BlinkMacSystemFont, 'Pretendard', sans-serif;\n font-size: 13px;\n color: #e0e0e0;\n box-sizing: border-box;\n }\n #${TOAST_ID} .ait-toast-header {\n font-size: 13px;\n font-weight: 600;\n color: #e0e0e0;\n margin-bottom: 6px;\n }\n #${TOAST_ID} .ait-toast-body {\n font-size: 12px;\n color: #aaa;\n margin-bottom: 12px;\n line-height: 1.5;\n }\n #${TOAST_ID} .ait-toast-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n justify-content: flex-end;\n }\n #${TOAST_ID} .ait-toast-btn-primary {\n background: #3182F6;\n color: white;\n border: none;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 12px;\n cursor: pointer;\n font-family: inherit;\n }\n #${TOAST_ID} .ait-toast-btn-primary:hover { background: #1b6ef3; }\n #${TOAST_ID} .ait-toast-btn-secondary {\n background: #2a2a4a;\n color: #e0e0e0;\n border: 1px solid #3a3a5a;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 12px;\n cursor: pointer;\n font-family: inherit;\n }\n #${TOAST_ID} .ait-toast-btn-secondary:hover { background: #3a3a5a; }\n #${TOAST_ID} .ait-toast-link {\n font-size: 11px;\n color: #666;\n text-decoration: none;\n margin-right: auto;\n }\n #${TOAST_ID} .ait-toast-link:hover { color: #aaa; }\n`;\n\nfunction injectStyles(): void {\n if (document.getElementById(`${TOAST_ID}-style`)) return;\n const style = document.createElement('style');\n style.id = `${TOAST_ID}-style`;\n style.textContent = TOAST_STYLES;\n document.head.appendChild(style);\n}\n\nfunction removeToast(): void {\n document.getElementById(TOAST_ID)?.remove();\n document.getElementById(`${TOAST_ID}-style`)?.remove();\n}\n\nexport interface ConsentToastOptions {\n onAccept: () => void;\n onDeny: () => void;\n}\n\n/**\n * Renders and shows the consent toast.\n * If the toast is already visible, does nothing.\n */\nexport function showConsentToast({ onAccept, onDeny }: ConsentToastOptions): void {\n if (document.getElementById(TOAST_ID)) return;\n\n injectStyles();\n\n const toast = document.createElement('div');\n toast.id = TOAST_ID;\n\n const header = document.createElement('div');\n header.className = 'ait-toast-header';\n header.textContent = t('toast.consent.title');\n\n const body = document.createElement('div');\n body.className = 'ait-toast-body';\n body.textContent = t('toast.consent.body');\n\n const learnMore = document.createElement('a');\n learnMore.className = 'ait-toast-link';\n learnMore.href = 'https://docs.aitc.dev/privacy';\n learnMore.target = '_blank';\n learnMore.rel = 'noopener noreferrer';\n learnMore.textContent = t('toast.consent.learnMore');\n\n const yesBtn = document.createElement('button');\n yesBtn.className = 'ait-toast-btn-primary';\n yesBtn.textContent = t('toast.consent.accept');\n yesBtn.addEventListener('click', () => {\n removeToast();\n onAccept();\n });\n\n const noBtn = document.createElement('button');\n noBtn.className = 'ait-toast-btn-secondary';\n noBtn.textContent = t('toast.consent.deny');\n noBtn.addEventListener('click', () => {\n removeToast();\n onDeny();\n });\n\n const actions = document.createElement('div');\n actions.className = 'ait-toast-actions';\n actions.append(learnMore, noBtn, yesBtn);\n\n toast.append(header, body, actions);\n document.body.appendChild(toast);\n}\n\nexport { removeToast };\n","/**\n * Telemetry consent state machine + localStorage I/O.\n *\n * localStorage keys are LOCKED — do not rename without updating the privacy page.\n */\n\nexport type ConsentState = 'granted' | 'denied' | 'undecided';\n\n// Key names — locked per privacy page spec\nconst KEY_CONSENT = '__ait_telemetry:consent';\nconst KEY_REPROMPT_AFTER = '__ait_telemetry:reprompt_after';\nconst KEY_POLICY_VERSION = '__ait_telemetry:policy_version';\nconst KEY_ANON_ID = '__ait_telemetry:anon_id';\n\n// Tier 0 keys\nexport const KEY_T0_LAST_SENT = '__ait_telemetry:t0_last_sent';\nexport const KEY_T0_OFF = '__ait_telemetry:t0_off';\n\n// ---------------------------------------------------------------------------\n// Tier 0 opt-out helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if Tier 0 ping is enabled.\n * Disabled when `localStorage.__ait_telemetry:t0_off = '1'`\n * or `process.env.AITC_TELEMETRY === 'off'`.\n */\nexport function isTier0Enabled(): boolean {\n if (typeof process !== 'undefined' && process.env.AITC_TELEMETRY === 'off') return false;\n try {\n return localStorage.getItem(KEY_T0_OFF) !== '1';\n } catch {\n return true;\n }\n}\n\n/**\n * Sets or clears the Tier 0 opt-out marker.\n */\nexport function setTier0Enabled(enabled: boolean): void {\n try {\n if (enabled) {\n localStorage.removeItem(KEY_T0_OFF);\n } else {\n localStorage.setItem(KEY_T0_OFF, '1');\n }\n } catch {\n /* storage unavailable */\n }\n}\n\n/**\n * Returns true if Tier 0 has already been sent today (YYYY-MM-DD).\n */\nexport function hasSentTier0Today(): boolean {\n try {\n const stored = localStorage.getItem(KEY_T0_LAST_SENT);\n if (!stored) return false;\n const today = new Date().toISOString().slice(0, 10);\n return stored === today;\n } catch {\n return false;\n }\n}\n\n/**\n * Records that Tier 0 was sent today.\n */\nexport function markTier0Sent(): void {\n try {\n const today = new Date().toISOString().slice(0, 10);\n localStorage.setItem(KEY_T0_LAST_SENT, today);\n } catch {\n /* storage unavailable */\n }\n}\n\n/**\n * Current policy version. Bump this string whenever the privacy policy changes.\n * Users who previously granted on an older version will be re-prompted once.\n */\nexport const CURRENT_POLICY_VERSION = '2026-05-18';\n\n/** 30 days in milliseconds */\nconst THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;\n\n// ---------------------------------------------------------------------------\n// Reads\n// ---------------------------------------------------------------------------\n\nexport function readConsentState(): ConsentState {\n const raw = localStorage.getItem(KEY_CONSENT);\n if (raw === 'granted' || raw === 'denied') return raw;\n return 'undecided';\n}\n\nexport function readRepromptAfter(): number {\n const raw = localStorage.getItem(KEY_REPROMPT_AFTER);\n if (raw === null) return 0;\n const n = Number(raw);\n return Number.isFinite(n) ? n : 0;\n}\n\nexport function readPolicyVersion(): string | null {\n return localStorage.getItem(KEY_POLICY_VERSION);\n}\n\n/**\n * Returns the stored anon_id, or generates + persists a new UUID v4 on first call.\n * Once generated it is never overwritten.\n */\nexport function getOrCreateAnonId(): string {\n const existing = localStorage.getItem(KEY_ANON_ID);\n if (existing) return existing;\n const id = crypto.randomUUID();\n localStorage.setItem(KEY_ANON_ID, id);\n return id;\n}\n\n// ---------------------------------------------------------------------------\n// Writes / transitions\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve effective consent, handling the policy-version bump rule:\n * - If stored = \"granted\" but stored version ≠ CURRENT → revert to undecided\n * - If stored = \"denied\" and version changed → stay denied (no re-prompt)\n *\n * Call this at init time to normalise state before checking whether to show a toast.\n * Returns the effective ConsentState after applying the version-bump rule.\n */\nexport function resolveEffectiveConsent(): ConsentState {\n const raw = localStorage.getItem(KEY_CONSENT);\n if (raw === 'granted') {\n const storedVersion = readPolicyVersion();\n if (storedVersion !== CURRENT_POLICY_VERSION) {\n // Policy changed — treat as undecided so user gets re-prompted once\n localStorage.removeItem(KEY_CONSENT);\n localStorage.removeItem(KEY_POLICY_VERSION);\n return 'undecided';\n }\n return 'granted';\n }\n if (raw === 'denied') return 'denied';\n return 'undecided';\n}\n\n/**\n * User clicked \"Yes, send\".\n * Sets consent = granted, records policy version.\n */\nexport function acceptConsent(): void {\n localStorage.setItem(KEY_CONSENT, 'granted');\n localStorage.setItem(KEY_POLICY_VERSION, CURRENT_POLICY_VERSION);\n // Ensure reprompt_after is cleared (shouldn't matter, but keep state clean)\n localStorage.removeItem(KEY_REPROMPT_AFTER);\n}\n\n/**\n * User clicked \"No, thanks\".\n * First denial: sets reprompt_after = now + 30 days.\n * Second denial (reprompt_after was already set to a past finite value that triggered\n * re-prompt): sets reprompt_after = MAX_SAFE_INTEGER → permanent silence.\n */\nexport function denyConsent(): void {\n localStorage.setItem(KEY_CONSENT, 'denied');\n const existing = readRepromptAfter();\n if (existing > 0 && existing < Number.MAX_SAFE_INTEGER) {\n // This is the second denial — silence permanently\n localStorage.setItem(KEY_REPROMPT_AFTER, String(Number.MAX_SAFE_INTEGER));\n } else {\n // First denial\n localStorage.setItem(KEY_REPROMPT_AFTER, String(Date.now() + THIRTY_DAYS_MS));\n }\n}\n\n/**\n * Environment-tab toggle: free transition between granted/denied.\n * Does NOT touch reprompt_after.\n */\nexport function setConsentViaToggle(granted: boolean): void {\n if (granted) {\n localStorage.setItem(KEY_CONSENT, 'granted');\n localStorage.setItem(KEY_POLICY_VERSION, CURRENT_POLICY_VERSION);\n } else {\n localStorage.setItem(KEY_CONSENT, 'denied');\n }\n}\n\n/**\n * Returns true if the toast should be shown now.\n * Conditions:\n * - undecided (no prior choice or policy bumped to a newer version)\n * - denied + reprompt_after set + reprompt_after < now (one re-prompt after\n * the configured silence window; `denyConsent` flips to permanent silence\n * on the second denial by setting reprompt_after to MAX_SAFE_INTEGER).\n */\nexport function shouldShowToast(): boolean {\n const state = resolveEffectiveConsent();\n if (state === 'undecided') {\n const repromptAfter = readRepromptAfter();\n if (repromptAfter === 0) return true;\n return Date.now() > repromptAfter;\n }\n if (state === 'denied') {\n const repromptAfter = readRepromptAfter();\n if (repromptAfter === 0 || repromptAfter >= Number.MAX_SAFE_INTEGER) return false;\n return Date.now() > repromptAfter;\n }\n return false;\n}\n\n/**\n * Sends the DELETE request to remove the user's data from the server, and\n * rotates the local anon_id on success so any subsequent events are unlinkable\n * from the deleted history.\n */\nexport async function deleteMyData(endpoint: string): Promise<boolean> {\n const anonId = localStorage.getItem(KEY_ANON_ID);\n if (!anonId) return false;\n try {\n const res = await fetch(`${endpoint}/e?anon_id=${encodeURIComponent(anonId)}`, {\n method: 'DELETE',\n });\n if (!res.ok) return false;\n localStorage.setItem(KEY_ANON_ID, crypto.randomUUID());\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Telemetry send + retry.\n *\n * Rules:\n * 1. If consent ≠ \"granted\" — drop silently.\n * 2. POST event as JSON with 5 s timeout.\n * 3. On network error or non-2xx: retry ONCE after 2 s. On second failure: drop.\n * 4. console.debug on retry, development only (NODE_ENV !== \"production\").\n * 5. For \"session_duration\": use sendBeacon if available, fall back to fetch keepalive.\n *\n * Max meta size: 256 bytes (JSON-serialized). Over-size meta is dropped to undefined.\n */\n\nimport { TELEMETRY_ENDPOINT } from './index.js';\nimport { getOrCreateAnonId, readConsentState } from './state.js';\n\nexport type TelemetryEvent = 'panel_mount' | 'panel_open' | 'tab_view' | 'session_duration';\n\nexport interface EventPayload {\n tier: 1;\n source: 'devtools';\n event: TelemetryEvent;\n anon_id: string;\n version: string;\n ts: number;\n meta?: Record<string, unknown>;\n}\n\n/** Meta cap per server contract (JSON bytes). */\nconst META_BYTE_CAP = 256;\n\nfunction sanitizeMeta(\n meta: Record<string, unknown> | undefined,\n): Record<string, unknown> | undefined {\n if (meta === undefined) return undefined;\n const serialized = JSON.stringify(meta);\n if (new TextEncoder().encode(serialized).length > META_BYTE_CAP) {\n // Drop oversized meta rather than sending something the server will reject\n return undefined;\n }\n return meta;\n}\n\nasync function doFetch(payload: EventPayload): Promise<boolean> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 5_000);\n try {\n const res = await fetch(`${TELEMETRY_ENDPOINT}/e`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n return res.ok;\n } catch {\n return false;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Send a telemetry event. Drops silently if consent is not \"granted\".\n */\nexport async function send(\n event: TelemetryEvent,\n version: string,\n meta?: Record<string, unknown>,\n): Promise<void> {\n if (readConsentState() !== 'granted') return;\n\n const payload: EventPayload = {\n tier: 1,\n source: 'devtools',\n event,\n anon_id: getOrCreateAnonId(),\n version,\n ts: Date.now(),\n meta: sanitizeMeta(meta),\n };\n\n const ok = await doFetch(payload);\n if (ok) return;\n\n // Retry once after 2 s\n if (process.env.NODE_ENV !== 'production') {\n console.debug('[@ait-co/devtools] telemetry: retrying after failure', event);\n }\n await delay(2_000);\n await doFetch(payload);\n // Second failure → drop silently (no further action)\n}\n\n/**\n * Send the \"session_duration\" event via sendBeacon (unload-safe).\n * Falls back to fetch with keepalive if sendBeacon is unavailable.\n * No retry during page unload.\n */\nexport function sendBeaconEvent(\n event: 'session_duration',\n version: string,\n meta: Record<string, unknown>,\n): void {\n if (readConsentState() !== 'granted') return;\n\n const payload: EventPayload = {\n tier: 1,\n source: 'devtools',\n event,\n anon_id: getOrCreateAnonId(),\n version,\n ts: Date.now(),\n meta: sanitizeMeta(meta),\n };\n\n const body = JSON.stringify(payload);\n\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n navigator.sendBeacon(`${TELEMETRY_ENDPOINT}/e`, new Blob([body], { type: 'application/json' }));\n return;\n }\n\n // Fallback: fetch with keepalive (no retry — page is unloading)\n fetch(`${TELEMETRY_ENDPOINT}/e`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n keepalive: true,\n }).catch(() => {\n /* unload — nothing we can do */\n });\n}\n","/**\n * Tier 0 telemetry — opt-out, fire-and-forget daily ping.\n *\n * Payload: { tier: 0, source: 'devtools', ts: number, version: string }\n * No anon_id. No event name. No meta.\n *\n * Rules:\n * - Sent once per calendar day (localStorage daily marker).\n * - Skipped when __ait_telemetry:t0_off = '1' or AITC_TELEMETRY=off.\n * - 5 s timeout, no retry. Failure is silently dropped.\n */\n\nimport { TELEMETRY_ENDPOINT } from './index.js';\nimport { hasSentTier0Today, isTier0Enabled, markTier0Sent } from './state.js';\n\nexport interface Tier0Payload {\n tier: 0;\n source: 'devtools';\n ts: number;\n version: string;\n}\n\n/**\n * Sends the Tier 0 daily ping if eligible.\n * Returns true if a ping was sent, false if skipped or failed.\n */\nexport async function sendTier0Ping(version: string): Promise<boolean> {\n if (!isTier0Enabled()) return false;\n if (hasSentTier0Today()) return false;\n\n const payload: Tier0Payload = {\n tier: 0,\n source: 'devtools',\n ts: Date.now(),\n version,\n };\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 5_000);\n\n try {\n await fetch(`${TELEMETRY_ENDPOINT}/e`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n // Mark as sent regardless of server response code — avoids re-spam on 400s too.\n markTier0Sent();\n return true;\n } catch {\n // Network error or timeout — drop silently, do NOT mark as sent (retry tomorrow).\n return false;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n","/**\n * Telemetry client — internal to @ait-co/devtools.\n *\n * NOT exported from src/mock/index.ts — this is panel-internal only.\n *\n * Usage: import { telemetry } from './telemetry/index.js' (from panel code).\n */\n\nimport { showConsentToast } from './consent-toast.js';\nimport { send, sendBeaconEvent } from './send.js';\nimport {\n acceptConsent,\n denyConsent,\n getOrCreateAnonId,\n resolveEffectiveConsent,\n shouldShowToast,\n} from './state.js';\nimport { sendTier0Ping } from './tier0.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Telemetry ingest endpoint.\n * Overridable at build time via define (e.g., for e2e / local dev).\n * Do NOT expose this as a public env-var surface.\n */\nfunction readGlobalString(key: string): string | undefined {\n const val = (globalThis as Record<string, unknown>)[key];\n return typeof val === 'string' ? val : undefined;\n}\n\nexport const TELEMETRY_ENDPOINT: string =\n readGlobalString('__TELEMETRY_ENDPOINT__') ?? 'https://t.aitc.dev';\n\n// Version is injected by tsdown define (__VERSION__) as a compile-time text\n// substitution — same mechanism panel/index.ts uses for its header label.\n// It is NOT a runtime global, so a globalThis lookup would always miss.\nfunction getVersion(): string {\n return __VERSION__;\n}\n\n// ---------------------------------------------------------------------------\n// Session duration tracking\n// ---------------------------------------------------------------------------\n\nlet panelVisibleSince: number | null = null;\nlet accumulatedMs = 0;\nlet pagehideWired = false;\n\nfunction onPanelVisible(): void {\n if (panelVisibleSince === null) {\n panelVisibleSince = Date.now();\n }\n}\n\nfunction onPanelHidden(): void {\n if (panelVisibleSince !== null) {\n accumulatedMs += Date.now() - panelVisibleSince;\n panelVisibleSince = null;\n }\n}\n\nfunction wirePagehide(): void {\n if (pagehideWired) return;\n pagehideWired = true;\n\n // pagehide covers bfcache (Safari) and regular navigation. Preferred over beforeunload.\n window.addEventListener('pagehide', () => {\n onPanelHidden();\n if (accumulatedMs > 0) {\n sendBeaconEvent('session_duration', getVersion(), { ms: accumulatedMs });\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport type TabId = string;\n\n/**\n * Call once after panel mounts.\n * Handles: Tier 0 ping, consent check, optional toast, panel_mount event, pagehide wiring.\n */\nfunction init(): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n\n wirePagehide();\n\n // Tier 0: fire-and-forget daily ping (opt-out, no consent needed).\n void sendTier0Ping(getVersion());\n\n const effectiveConsent = resolveEffectiveConsent();\n\n if (effectiveConsent === 'granted') {\n // Ensure anon_id exists before firing\n getOrCreateAnonId();\n void send('panel_mount', getVersion());\n return;\n }\n\n if (shouldShowToast()) {\n const showToast = () => {\n showConsentToast({\n onAccept: () => {\n acceptConsent();\n getOrCreateAnonId();\n void send('panel_mount', getVersion());\n },\n onDeny: () => {\n denyConsent();\n },\n });\n };\n\n if (typeof requestIdleCallback === 'function') {\n requestIdleCallback(showToast, { timeout: 3_000 });\n } else {\n setTimeout(showToast, 1_500);\n }\n }\n}\n\n/**\n * Call when the panel is opened/toggled visible.\n */\nfunction onPanelOpen(): void {\n void send('panel_open', getVersion());\n onPanelVisible();\n}\n\n/**\n * Call when the panel is closed/hidden.\n */\nfunction onPanelClose(): void {\n onPanelHidden();\n}\n\n/**\n * Call when the user switches tabs.\n */\nfunction onTabView(tabId: TabId): void {\n void send('tab_view', getVersion(), { tab: tabId });\n}\n\nexport const telemetry = {\n init,\n onPanelOpen,\n onPanelClose,\n onTabView,\n} as const;\n","/**\n * 공통 DOM 헬퍼 함수\n */\n\nimport { t } from '../i18n/index.js';\n\nexport function h<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n attrs?: Record<string, string>,\n ...children: (string | Node)[]\n): HTMLElementTagNameMap[K] {\n const el = document.createElement(tag);\n if (attrs) {\n for (const [k, v] of Object.entries(attrs)) {\n if (k === 'className') el.className = v;\n else el.setAttribute(k, v);\n }\n }\n for (const child of children) {\n el.append(typeof child === 'string' ? document.createTextNode(child) : child);\n }\n return el;\n}\n\nexport function selectRow(\n label: string,\n options: string[],\n value: string,\n onChange: (v: string) => void,\n disabled = false,\n): HTMLElement {\n const select = h('select', { className: 'ait-select' });\n if (disabled) select.disabled = true;\n for (const opt of options) {\n const option = h('option', { value: opt }, opt);\n if (opt === value) option.selected = true;\n select.appendChild(option);\n }\n select.addEventListener('change', () => onChange(select.value));\n return h('div', { className: 'ait-row' }, h('label', {}, label), select);\n}\n\nexport function inputRow(\n label: string,\n value: string,\n onChange: (v: string) => void,\n disabled = false,\n): HTMLElement {\n const input = h('input', { className: 'ait-input', value });\n if (disabled) input.disabled = true;\n input.addEventListener('change', () => onChange(input.value));\n return h('div', { className: 'ait-row' }, h('label', {}, label), input);\n}\n\nexport function monitoringNotice(): HTMLElement {\n return h('div', { className: 'ait-monitoring-notice' }, t('common.readOnly'));\n}\n","/**\n * Floating Panel CSS (inline, 외부 의존성 없음)\n */\n\nexport const PANEL_WIDTH = 360;\nexport const PANEL_HEIGHT = 480;\nexport const PANEL_FULLSCREEN_BREAKPOINT = 720;\n\n// Viewport simulation frame styling\nexport const VIEWPORT_FRAME_BORDER_RADIUS = 36;\nexport const VIEWPORT_FRAME_BEZEL_INNER = 10; // first ring (outer device shell)\nexport const VIEWPORT_FRAME_BEZEL_OUTER = 12; // second ring (chrome highlight)\nexport const VIEWPORT_FRAME_BEZEL_COLOR_INNER = '#1a1a2e';\nexport const VIEWPORT_FRAME_BEZEL_COLOR_OUTER = '#3a3a5a';\nexport const VIEWPORT_BG_COLOR = '#0a0a14';\nexport const VIEWPORT_BODY_MARGIN = 24;\n// Status bar strip drawn above the WebView (body) when the frame is on. The OS\n// notch / Dynamic Island lives here, outside the WebView — matching the real\n// device where env(safe-area-inset-top) is 0 and the SDK top inset reports the\n// nav bar, not the notch. Tall enough to seat a Dynamic Island (37px) with margin.\nexport const VIEWPORT_STATUS_BAR_HEIGHT = 50;\n\nexport const PANEL_STYLES = /* css */ `\n .ait-panel-toggle {\n position: fixed;\n z-index: 99999;\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: #3182F6;\n border: none;\n cursor: pointer;\n box-shadow: 0 2px 12px rgba(0,0,0,0.2);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 20px;\n color: white;\n transition: transform 0.15s;\n font-family: -apple-system, BlinkMacSystemFont, sans-serif;\n touch-action: none;\n user-select: none;\n }\n .ait-panel-toggle:hover:not(.dragging) {\n transform: scale(1.1);\n }\n\n .ait-panel {\n position: fixed;\n z-index: 99998;\n width: ${PANEL_WIDTH}px;\n height: ${PANEL_HEIGHT}px;\n background: #1a1a2e;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.4);\n font-family: -apple-system, BlinkMacSystemFont, 'Pretendard', sans-serif;\n font-size: 13px;\n color: #e0e0e0;\n overflow: hidden;\n display: none;\n }\n .ait-panel.open {\n display: flex;\n flex-direction: column;\n }\n\n .ait-panel-header {\n padding: 12px 16px;\n background: #16213e;\n font-weight: 600;\n font-size: 14px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-bottom: 1px solid #2a2a4a;\n }\n .ait-panel-header > span:first-child {\n color: #3182F6;\n }\n\n .ait-panel-tabs {\n display: flex;\n background: #16213e;\n border-bottom: 1px solid #2a2a4a;\n overflow-x: auto;\n scrollbar-width: none;\n }\n .ait-panel-tabs::-webkit-scrollbar { display: none; }\n\n .ait-panel-tab {\n padding: 8px 12px;\n font-size: 12px;\n color: #888;\n cursor: pointer;\n white-space: nowrap;\n border-bottom: 2px solid transparent;\n background: none;\n border-top: none;\n border-left: none;\n border-right: none;\n font-family: inherit;\n }\n .ait-panel-tab:hover {\n color: #bbb;\n }\n .ait-panel-tab.active {\n color: #3182F6;\n border-bottom-color: #3182F6;\n }\n\n .ait-panel-body {\n padding: 12px 16px;\n overflow-y: auto;\n flex: 1;\n min-height: 0;\n }\n\n .ait-section {\n margin-bottom: 16px;\n }\n .ait-section-title {\n font-size: 11px;\n text-transform: uppercase;\n color: #666;\n margin-bottom: 8px;\n letter-spacing: 0.5px;\n }\n\n .ait-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .ait-row label {\n color: #aaa;\n font-size: 12px;\n }\n\n .ait-select {\n background: #2a2a4a;\n color: #e0e0e0;\n border: 1px solid #3a3a5a;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 12px;\n font-family: inherit;\n cursor: pointer;\n }\n\n .ait-input {\n background: #2a2a4a;\n color: #e0e0e0;\n border: 1px solid #3a3a5a;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 12px;\n width: 100px;\n font-family: inherit;\n }\n\n .ait-btn {\n background: #3182F6;\n color: white;\n border: none;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 12px;\n cursor: pointer;\n font-family: inherit;\n }\n .ait-btn:hover {\n background: #1b6ef3;\n }\n .ait-btn-sm {\n padding: 4px 8px;\n font-size: 11px;\n }\n .ait-btn-danger {\n background: #e74c3c;\n }\n .ait-btn-danger:hover {\n background: #c0392b;\n }\n\n .ait-log-entry {\n font-family: 'SF Mono', 'Menlo', monospace;\n font-size: 11px;\n padding: 3px 0;\n border-bottom: 1px solid #2a2a4a;\n color: #aaa;\n }\n .ait-log-entry .ait-log-type {\n color: #3182F6;\n font-weight: 600;\n margin-right: 6px;\n }\n .ait-log-entry .ait-log-time {\n color: #555;\n margin-right: 6px;\n }\n\n .ait-storage-row {\n font-family: 'SF Mono', 'Menlo', monospace;\n font-size: 11px;\n display: flex;\n gap: 8px;\n padding: 4px 0;\n border-bottom: 1px solid #2a2a4a;\n }\n .ait-storage-key {\n color: #e8a87c;\n min-width: 80px;\n word-break: break-all;\n }\n .ait-storage-value {\n color: #95e6cb;\n flex: 1;\n word-break: break-all;\n }\n\n /* Device tab */\n .ait-image-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 8px;\n }\n .ait-image-thumb {\n position: relative;\n width: 64px;\n height: 64px;\n border-radius: 4px;\n overflow: hidden;\n border: 1px solid #3a3a5a;\n }\n .ait-image-thumb img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n .ait-image-thumb .ait-image-remove {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: rgba(231,76,60,0.9);\n color: white;\n border: none;\n cursor: pointer;\n font-size: 10px;\n line-height: 18px;\n text-align: center;\n padding: 0;\n }\n .ait-btn-row {\n display: flex;\n gap: 6px;\n margin-top: 8px;\n }\n .ait-btn-secondary {\n background: #2a2a4a;\n color: #e0e0e0;\n border: 1px solid #3a3a5a;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 11px;\n cursor: pointer;\n font-family: inherit;\n }\n .ait-btn-secondary:hover {\n background: #3a3a5a;\n }\n\n /* Prompt notification */\n .ait-prompt-banner {\n background: #2d1b69;\n border: 1px solid #6c3bd5;\n border-radius: 6px;\n padding: 10px 12px;\n margin-bottom: 12px;\n }\n .ait-prompt-banner .ait-prompt-title {\n color: #b388ff;\n font-size: 12px;\n font-weight: 600;\n margin-bottom: 8px;\n }\n .ait-prompt-input-row {\n display: flex;\n gap: 6px;\n align-items: center;\n margin-top: 6px;\n }\n .ait-prompt-input-row input {\n background: #2a2a4a;\n color: #e0e0e0;\n border: 1px solid #3a3a5a;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 12px;\n width: 80px;\n font-family: inherit;\n }\n .ait-prompt-input-row label {\n color: #aaa;\n font-size: 11px;\n min-width: 30px;\n }\n\n .ait-panel-close {\n display: none;\n background: none;\n border: none;\n color: #888;\n font-size: 18px;\n cursor: pointer;\n padding: 0 4px;\n font-family: inherit;\n }\n .ait-panel-close:hover {\n color: #e0e0e0;\n }\n\n /* Disabled state for monitoring-only mode */\n .ait-select:disabled,\n .ait-input:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .ait-btn:disabled,\n .ait-btn-secondary:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .ait-btn-danger:disabled {\n background: #5a5a5a;\n }\n\n /* Mock status badge */\n .ait-mock-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.3px;\n cursor: pointer;\n }\n .ait-mock-badge-on {\n background: #1a4731;\n color: #4ade80;\n }\n .ait-mock-badge-off {\n background: #4a1a1a;\n color: #f87171;\n }\n\n /* Monitoring-only notice */\n .ait-monitoring-notice {\n background: #2a1a00;\n border: 1px solid #6b4c00;\n border-radius: 4px;\n padding: 6px 10px;\n margin-bottom: 12px;\n font-size: 11px;\n color: #fbbf24;\n }\n\n .ait-panel-tab-error {\n padding: 12px;\n color: #e53e3e; /* readable on both light (#fff) and dark (#1a1a2e) panel backgrounds */\n }\n\n /* Presets tab */\n .ait-preset-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 0;\n border-bottom: 1px solid #2a2a4a;\n }\n .ait-preset-row.ait-preset-active .ait-preset-label {\n color: #4ade80;\n font-weight: 600;\n }\n .ait-preset-label {\n font-size: 12px;\n color: #ddd;\n flex: 1;\n word-break: break-word;\n }\n .ait-preset-actions {\n display: flex;\n gap: 4px;\n flex-shrink: 0;\n }\n .ait-preset-description {\n font-size: 11px;\n color: #777;\n padding: 0 0 6px 4px;\n border-bottom: 1px solid #2a2a4a;\n margin-bottom: 0;\n }\n\n /* Viewport tab status rows */\n .ait-status-row {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n font-size: 11px;\n color: #888;\n padding: 3px 0;\n border-bottom: 1px dashed #2a2a4a;\n gap: 8px;\n }\n .ait-status-row:last-child { border-bottom: none; }\n .ait-status-row .ait-status-value {\n font-family: 'SF Mono', 'Menlo', monospace;\n color: #95e6cb;\n font-size: 11px;\n text-align: right;\n word-break: break-word;\n }\n\n /* === Viewport simulation === */\n /* Static rules. Dynamic per-preset values (width/height, navbar top offset)\n are still injected via a separate <style id=\"__ait-viewport-style\">. */\n html.ait-viewport-active {\n background: ${VIEWPORT_BG_COLOR};\n min-height: 100dvh;\n }\n html.ait-viewport-active body {\n position: relative;\n /* isolation: isolate creates a stacking context so notch/navbar z-index\n cannot escape body and paint over the floating Panel toggle. */\n isolation: isolate;\n margin: ${VIEWPORT_BODY_MARGIN}px auto;\n overflow: auto;\n background: #fff;\n box-sizing: border-box;\n }\n html.ait-viewport-framed body {\n border-radius: ${VIEWPORT_FRAME_BORDER_RADIUS}px;\n /* Reserve the status bar strip above the WebView so the notch sits outside\n the body (OS notch is outside the WebView; env top=0). */\n margin-top: ${VIEWPORT_BODY_MARGIN + VIEWPORT_STATUS_BAR_HEIGHT}px;\n box-shadow:\n 0 0 0 ${VIEWPORT_FRAME_BEZEL_INNER}px ${VIEWPORT_FRAME_BEZEL_COLOR_INNER},\n 0 0 0 ${VIEWPORT_FRAME_BEZEL_OUTER}px ${VIEWPORT_FRAME_BEZEL_COLOR_OUTER},\n 0 24px 48px rgba(0,0,0,0.5);\n }\n\n /* Notch / Dynamic Island / punch-hole — drawn in the status bar strip ABOVE\n the WebView (negative top puts it in the reserved margin), so it never\n overlaps the nav bar (which sits at the body's top edge). */\n .ait-notch {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n background: #000;\n z-index: 10;\n pointer-events: none;\n }\n .ait-notch-dynamic-island {\n top: -${VIEWPORT_STATUS_BAR_HEIGHT - 6}px;\n width: 126px; height: 37px; border-radius: 20px;\n }\n .ait-notch-pill {\n top: -${VIEWPORT_STATUS_BAR_HEIGHT}px;\n width: 160px; height: 30px;\n border-bottom-left-radius: 20px; border-bottom-right-radius: 20px;\n }\n .ait-notch-punch-hole {\n top: -${VIEWPORT_STATUS_BAR_HEIGHT - 10}px;\n width: 12px; height: 12px; border-radius: 50%;\n }\n\n /* Home indicator pill (bottom of body, iPhones with safe-area bottom > 0) */\n .ait-home-indicator {\n position: absolute;\n bottom: 8px;\n left: 50%;\n transform: translateX(-50%);\n width: 134px;\n height: 5px;\n border-radius: 3px;\n background: rgba(0, 0, 0, 0.85);\n z-index: 10;\n pointer-events: none;\n }\n\n /* Apps in Toss host nav bar — sits at the top of the WebView (body). The OS\n notch lives outside the WebView (env top=0), so the nav bar bottom is the\n content's top edge; applyViewport gives body padding-top = nav bar height\n so content starts exactly below it. */\n .ait-navbar {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 54px; /* AIT_NAV_BAR_HEIGHT_PARTNER (relay 실측) */\n background: rgba(255, 255, 255, 0.92);\n backdrop-filter: blur(8px);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 12px;\n box-sizing: border-box;\n font: 500 15px -apple-system, BlinkMacSystemFont, 'Pretendard', sans-serif;\n color: #1a1a1a;\n z-index: 10;\n }\n .ait-navbar-title {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1;\n margin-left: 4px;\n overflow: hidden;\n }\n .ait-navbar-icon {\n width: 22px;\n height: 22px;\n border-radius: 6px;\n background: linear-gradient(135deg, #3182f6, #7c3aed);\n flex-shrink: 0;\n }\n .ait-navbar-name {\n font-size: 15px;\n font-weight: 600;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .ait-navbar-actions {\n display: flex;\n align-items: center;\n background: rgba(0, 0, 0, 0.05);\n border-radius: 999px;\n padding: 4px 8px;\n gap: 4px;\n }\n .ait-navbar-btn {\n background: none;\n border: none;\n padding: 2px 8px;\n font: inherit;\n font-size: 18px;\n color: inherit;\n line-height: 1;\n cursor: pointer;\n }\n .ait-navbar-btn:hover { color: #3182f6; }\n .ait-navbar-back { padding: 0 8px; font-size: 24px; }\n .ait-navbar-divider { width: 1px; height: 16px; background: rgba(0, 0, 0, 0.15); }\n\n /* Game variant: 투명 배경, 우측 actions만 — 풀스크린 게임 캔버스를 가리지 않는다 */\n .ait-navbar.ait-navbar-game {\n background: transparent;\n backdrop-filter: none;\n justify-content: flex-end;\n color: #fff;\n }\n .ait-navbar.ait-navbar-game .ait-navbar-actions {\n background: rgba(0, 0, 0, 0.35);\n color: #fff;\n }\n .ait-navbar.ait-navbar-game .ait-navbar-divider {\n background: rgba(255, 255, 255, 0.3);\n }\n .ait-navbar.ait-navbar-game .ait-navbar-btn:hover { color: #8ab4ff; }\n\n @media (max-width: ${PANEL_FULLSCREEN_BREAKPOINT}px) {\n .ait-panel.open {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n max-height: 100%;\n border-radius: 0;\n }\n .ait-panel-toggle {\n z-index: 100000;\n }\n .ait-panel-close {\n display: block;\n }\n }\n`;\n","/**\n * 디바이스 모듈 내부 공유 헬퍼\n */\n\nimport { aitState } from '../state.js';\n\n// --- Placeholder Image Generator ---\n\nfunction generatePlaceholderImage(\n width: number,\n height: number,\n text: string,\n color: string,\n): string {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n // jsdom 등 Canvas API 미지원 환경에서는 간단한 SVG data URI 반환\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\"><rect fill=\"${color}\" width=\"${width}\" height=\"${height}\"/><text x=\"50%\" y=\"50%\" fill=\"white\" font-size=\"16\" text-anchor=\"middle\" dominant-baseline=\"middle\">${text}</text></svg>`;\n return `data:image/svg+xml;base64,${btoa(svg)}`;\n }\n ctx.fillStyle = color;\n ctx.fillRect(0, 0, width, height);\n ctx.fillStyle = 'white';\n ctx.font = '16px sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, width / 2, height / 2);\n return canvas.toDataURL('image/png');\n}\n\nconst DEFAULT_PLACEHOLDERS = [\n { text: 'Mock Photo 1', color: '#3182F6' },\n { text: 'Mock Photo 2', color: '#27ae60' },\n { text: 'Mock Photo 3', color: '#e67e22' },\n];\n\nlet cachedPlaceholders: string[] | null = null;\n\nexport function getDefaultPlaceholderImages(): string[] {\n if (!cachedPlaceholders) {\n cachedPlaceholders = DEFAULT_PLACEHOLDERS.map((p) =>\n generatePlaceholderImage(320, 240, p.text, p.color),\n );\n }\n return [...cachedPlaceholders];\n}\n\n/** @internal device 모듈 내부 전용 */\nexport function getMockImages(): string[] {\n const images = aitState.state.mockData.images;\n if (images.length > 0) return images;\n return getDefaultPlaceholderImages();\n}\n\n// --- Prompt Mode Helper ---\n\nconst PROMPT_TIMEOUT_MS = 30_000;\n\n/** @internal device 모듈 내부 전용 */\nexport function waitForPromptResponse<T>(type: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const eventName = `__ait:prompt-response:${type}`;\n const cancelName = '__ait:prompt-cancel';\n\n function cleanup() {\n clearTimeout(timer);\n window.removeEventListener(eventName, handler);\n window.removeEventListener(cancelName, cancelHandler);\n }\n\n const timer = setTimeout(() => {\n cleanup();\n const panelMounted = !!document.querySelector('.ait-panel');\n const hint = panelMounted\n ? 'Please provide input via the DevTools panel.'\n : 'Is @ait-co/devtools/panel imported?';\n reject(\n new Error(\n `[@ait-co/devtools] Prompt timeout for \"${type}\" after ${PROMPT_TIMEOUT_MS / 1000}s. ${hint}`,\n ),\n );\n }, PROMPT_TIMEOUT_MS);\n\n const handler = (e: Event) => {\n cleanup();\n resolve((e as CustomEvent).detail as T);\n };\n\n const cancelHandler = () => {\n cleanup();\n reject(new Error(`[@ait-co/devtools] Prompt cancelled for \"${type}\"`));\n };\n\n window.addEventListener(eventName, handler);\n window.addEventListener(cancelName, cancelHandler);\n window.dispatchEvent(new CustomEvent('__ait:prompt-request', { detail: { type } }));\n });\n}\n","/**\n * 권한 시스템 mock\n * 각 디바이스 API (.getPermission, .openPermissionDialog)에 부착된다.\n */\n\nimport { aitState } from './state.js';\nimport type { PermissionName, PermissionStatus } from './types.js';\n\nexport async function getPermission(name: PermissionName): Promise<PermissionStatus> {\n return aitState.state.permissions[name];\n}\n\nexport async function openPermissionDialog(name: PermissionName): Promise<'allowed' | 'denied'> {\n const current = aitState.state.permissions[name];\n if (current === 'allowed') return 'allowed';\n // notDetermined나 denied일 때 — Panel에서 설정된 값을 사용\n // 기본적으로는 allowed로 전환\n aitState.patch('permissions', { [name]: 'allowed' });\n return 'allowed';\n}\n\nexport async function requestPermission(permission: {\n name: PermissionName;\n access: string;\n}): Promise<'allowed' | 'denied'> {\n return openPermissionDialog(permission.name);\n}\n\n/** 권한이 필요한 함수에 .getPermission(), .openPermissionDialog()를 부착 */\nexport function withPermission<T extends (...args: never[]) => unknown>(\n fn: T,\n permissionName: PermissionName,\n): T & {\n getPermission: () => Promise<PermissionStatus>;\n openPermissionDialog: () => Promise<'allowed' | 'denied'>;\n} {\n const enhanced = fn as T & {\n getPermission: () => Promise<PermissionStatus>;\n openPermissionDialog: () => Promise<'allowed' | 'denied'>;\n };\n enhanced.getPermission = () => getPermission(permissionName);\n enhanced.openPermissionDialog = () => openPermissionDialog(permissionName);\n return enhanced;\n}\n\n/** 권한 체크 후 denied면 에러 throw */\nexport function checkPermission(name: PermissionName, fnName: string): void {\n const status = aitState.state.permissions[name];\n if (status === 'denied') {\n throw new Error(\n `[@ait-co/devtools] ${fnName}: Permission \"${name}\" is denied. Change it in the DevTools panel.`,\n );\n }\n}\n","/**\n * Camera & Album Photos & Album Items mock\n * mock/web/prompt 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\nimport { getMockImages, waitForPromptResponse } from './_helpers.js';\n\n// --- 타입 ---\n\nexport type AlbumItemType = 'PHOTO' | 'VIDEO';\n\n// --- Camera ---\n\nasync function openCameraMock(): Promise<{ id: string; dataUri: string }> {\n const images = getMockImages();\n return { id: crypto.randomUUID(), dataUri: images[0] };\n}\n\nasync function openCameraWeb(): Promise<{ id: string; dataUri: string }> {\n return new Promise((resolve, reject) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.capture = 'environment';\n let settled = false;\n input.onchange = () => {\n settled = true;\n const file = input.files?.[0];\n if (!file) {\n reject(new Error('No file selected'));\n return;\n }\n const reader = new FileReader();\n reader.onload = () => resolve({ id: crypto.randomUUID(), dataUri: reader.result as string });\n reader.onerror = () => reject(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n };\n // Detect file picker cancel via focus heuristic.\n // Note: unreliable on some mobile browsers and Safari where focus events differ.\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) reject(new Error('File picker cancelled'));\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function openCameraPrompt(): Promise<{ id: string; dataUri: string }> {\n const dataUri = await waitForPromptResponse<string>('camera');\n return { id: crypto.randomUUID(), dataUri };\n}\n\nconst _openCamera = async (_options?: {\n base64?: boolean;\n maxWidth?: number;\n}): Promise<{ id: string; dataUri: string }> => {\n checkPermission('camera', 'openCamera');\n const mode = aitState.state.deviceModes.camera;\n if (mode === 'web') return openCameraWeb();\n if (mode === 'prompt') return openCameraPrompt();\n return openCameraMock();\n};\nexport const openCamera = withPermission(_openCamera, 'camera');\n\n// --- Album Photos ---\n\nasync function fetchAlbumPhotosMock(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n const images = getMockImages();\n return images.slice(0, maxCount).map((dataUri) => ({ id: crypto.randomUUID(), dataUri }));\n}\n\nasync function fetchAlbumPhotosWeb(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n return new Promise((resolve, reject) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.multiple = true;\n let settled = false;\n input.onchange = async () => {\n settled = true;\n const files = Array.from(input.files ?? []).slice(0, maxCount);\n if (files.length === 0) {\n reject(new Error('No files selected'));\n return;\n }\n const results = await Promise.all(\n files.map(\n (file) =>\n new Promise<{ id: string; dataUri: string }>((res, rej) => {\n const reader = new FileReader();\n reader.onload = () =>\n res({ id: crypto.randomUUID(), dataUri: reader.result as string });\n reader.onerror = () => rej(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n }),\n ),\n );\n resolve(results);\n };\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) reject(new Error('File picker cancelled'));\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function fetchAlbumPhotosPrompt(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n const dataUris = await waitForPromptResponse<string[]>('photos');\n return dataUris.slice(0, maxCount).map((dataUri) => ({ id: crypto.randomUUID(), dataUri }));\n}\n\nconst _fetchAlbumPhotos = async (options?: {\n maxCount?: number;\n maxWidth?: number;\n base64?: boolean;\n}): Promise<Array<{ id: string; dataUri: string }>> => {\n checkPermission('photos', 'fetchAlbumPhotos');\n const maxCount = options?.maxCount ?? 10;\n const mode = aitState.state.deviceModes.photos;\n if (mode === 'web') return fetchAlbumPhotosWeb(maxCount);\n if (mode === 'prompt') return fetchAlbumPhotosPrompt(maxCount);\n return fetchAlbumPhotosMock(maxCount);\n};\nexport const fetchAlbumPhotos = withPermission(_fetchAlbumPhotos, 'photos');\n\n// --- Album Items (사진·동영상 복합 선택) ---\n\nexport interface FetchAlbumItemsOptions {\n types?: AlbumItemType[];\n maxCount?: number;\n maxWidth?: number;\n base64?: boolean;\n}\n\nexport interface AlbumItemResponse {\n id: string;\n dataUri: string;\n type: AlbumItemType;\n}\n\nasync function fetchAlbumItemsMock(\n maxCount: number,\n types: AlbumItemType[],\n): Promise<AlbumItemResponse[]> {\n const images = getMockImages();\n return images\n .slice(0, maxCount)\n .filter(() => types.includes('PHOTO'))\n .map((dataUri) => ({ id: crypto.randomUUID(), dataUri, type: 'PHOTO' as AlbumItemType }));\n}\n\nasync function fetchAlbumItemsWeb(\n maxCount: number,\n types: AlbumItemType[],\n): Promise<AlbumItemResponse[]> {\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = types.includes('VIDEO') ? 'image/*,video/*' : 'image/*';\n input.multiple = true;\n let settled = false;\n input.onchange = async () => {\n settled = true;\n const files = Array.from(input.files ?? []).slice(0, maxCount);\n if (files.length === 0) {\n resolve([]);\n return;\n }\n const results = await Promise.all(\n files.map(\n (file) =>\n new Promise<AlbumItemResponse>((res, rej) => {\n const itemType: AlbumItemType = file.type.startsWith('video/') ? 'VIDEO' : 'PHOTO';\n const reader = new FileReader();\n reader.onload = () =>\n res({ id: crypto.randomUUID(), dataUri: reader.result as string, type: itemType });\n reader.onerror = () => rej(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n }),\n ),\n );\n resolve(results);\n };\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) resolve([]);\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function fetchAlbumItemsPrompt(maxCount: number): Promise<AlbumItemResponse[]> {\n const dataUris = await waitForPromptResponse<string[]>('photos');\n return dataUris\n .slice(0, maxCount)\n .map((dataUri) => ({ id: crypto.randomUUID(), dataUri, type: 'PHOTO' as AlbumItemType }));\n}\n\nconst _fetchAlbumItems = async (options?: FetchAlbumItemsOptions): Promise<AlbumItemResponse[]> => {\n checkPermission('photos', 'fetchAlbumItems');\n const maxCount = options?.maxCount ?? 10;\n const types = options?.types ?? ['PHOTO'];\n const mode = aitState.state.deviceModes.photos;\n if (mode === 'web') return fetchAlbumItemsWeb(maxCount, types);\n if (mode === 'prompt') return fetchAlbumItemsPrompt(maxCount);\n return fetchAlbumItemsMock(maxCount, types);\n};\nexport const fetchAlbumItems = withPermission(_fetchAlbumItems, 'photos');\n","/**\n * Clipboard mock\n * mock/web 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\n\nconst _getClipboardText = async (): Promise<string> => {\n checkPermission('clipboard', 'getClipboardText');\n const mode = aitState.state.deviceModes.clipboard;\n if (mode === 'mock') return aitState.state.mockData.clipboardText;\n // web mode (default)\n try {\n return await navigator.clipboard.readText();\n } catch {\n return '';\n }\n};\nexport const getClipboardText = withPermission(_getClipboardText, 'clipboard');\n\nconst _setClipboardText = async (text: string): Promise<void> => {\n checkPermission('clipboard', 'setClipboardText');\n const mode = aitState.state.deviceModes.clipboard;\n if (mode === 'mock') {\n aitState.patch('mockData', { clipboardText: text });\n return;\n }\n // web mode (default)\n await navigator.clipboard.writeText(text);\n};\nexport const setClipboardText = withPermission(_setClipboardText, 'clipboard');\n","/**\n * Contacts mock\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\n\nconst _fetchContacts = async (options: {\n size: number;\n offset: number;\n query?: { contains?: string };\n}) => {\n checkPermission('contacts', 'fetchContacts');\n let contacts = aitState.state.contacts;\n if (options.query?.contains) {\n const q = options.query.contains.toLowerCase();\n contacts = contacts.filter(\n (c) => c.name.toLowerCase().includes(q) || c.phoneNumber.includes(q),\n );\n }\n const sliced = contacts.slice(options.offset, options.offset + options.size);\n const nextOffset = options.offset + options.size;\n return {\n result: sliced,\n nextOffset: nextOffset < contacts.length ? nextOffset : null,\n done: nextOffset >= contacts.length,\n };\n};\nexport const fetchContacts = withPermission(_fetchContacts, 'contacts');\n","/**\n * Haptic Feedback & saveBase64Data mock\n *\n * generateHapticFeedback — 영역 3 (하드웨어 API 관측):\n * - 10종 HapticFeedbackType을 navigator.vibrate 패턴으로 매핑(근사, best-effort).\n * - `typeof navigator.vibrate === 'function'` 가드 — API 없는 환경에서 throw 없이 skip.\n * - sdkCallLog에 🟡(partial)로 기록. params: { hapticType, vibrated: boolean }.\n * - 시그니처 불변 — __typecheck.ts의 Assert<Mock, Original> 통과.\n */\n\nimport { aitState } from '../state.js';\nimport type { HapticFeedbackType } from '../types.js';\n\n/**\n * HapticFeedbackType 10종 → navigator.vibrate 패턴 매핑.\n * 숫자: 진동 ms. 배열: [진동, 정지, 진동, …] 교대 패턴.\n */\nexport const HAPTIC_VIBRATE_PATTERN: Record<HapticFeedbackType, VibratePattern> = {\n tickWeak: 10,\n tap: 20,\n tickMedium: 30,\n softMedium: 40,\n basicWeak: 15,\n basicMedium: 50,\n success: [10, 40, 10],\n error: [40, 30, 40],\n wiggle: [20, 20, 20, 20, 20],\n confetti: [10, 20, 10, 20, 10, 20, 10],\n};\n\nexport async function generateHapticFeedback(options: { type: HapticFeedbackType }): Promise<void> {\n const timestamp = Date.now();\n aitState.logAnalytics({ type: 'haptic', params: { hapticType: options.type } });\n\n const pattern = HAPTIC_VIBRATE_PATTERN[options.type] ?? 30;\n const vibrated = typeof navigator.vibrate === 'function' ? navigator.vibrate(pattern) : false;\n\n aitState.logSdkCall({\n method: 'generateHapticFeedback',\n args: [{ type: options.type }],\n timestamp,\n status: 'resolved',\n result: { hapticType: options.type, vibrated },\n fidelity: 'partial',\n });\n}\n\nexport async function saveBase64Data(params: {\n data: string;\n fileName: string;\n mimeType: string;\n}): Promise<void> {\n const a = document.createElement('a');\n a.href = `data:${params.mimeType};base64,${params.data}`;\n a.download = params.fileName;\n a.click();\n}\n","/**\n * Location mock (getCurrentLocation, startUpdateLocation)\n * mock/web/prompt 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\nimport type { MockLocation } from '../types.js';\nimport { waitForPromptResponse } from './_helpers.js';\n\nenum Accuracy {\n Lowest = 1,\n Low = 2,\n Balanced = 3,\n High = 4,\n Highest = 5,\n BestForNavigation = 6,\n}\n\nexport { Accuracy };\n\nfunction buildLocation(): MockLocation {\n return {\n coords: { ...aitState.state.location.coords },\n timestamp: Date.now(),\n accessLocation: aitState.state.location.accessLocation,\n };\n}\n\n// -- getCurrentLocation --\n\nasync function getCurrentLocationMock(): Promise<MockLocation> {\n return buildLocation();\n}\n\nasync function getCurrentLocationWeb(): Promise<MockLocation> {\n return new Promise((resolve) => {\n if (!navigator.geolocation) {\n console.warn('[@ait-co/devtools] Geolocation API not available, falling back to mock');\n resolve(buildLocation());\n return;\n }\n navigator.geolocation.getCurrentPosition(\n (pos) => {\n resolve({\n coords: {\n latitude: pos.coords.latitude,\n longitude: pos.coords.longitude,\n altitude: pos.coords.altitude ?? 0,\n accuracy: pos.coords.accuracy,\n altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,\n heading: pos.coords.heading ?? 0,\n },\n timestamp: pos.timestamp,\n accessLocation: 'FINE',\n });\n },\n () => {\n console.warn('[@ait-co/devtools] Geolocation failed, falling back to mock');\n resolve(buildLocation());\n },\n );\n });\n}\n\nasync function getCurrentLocationPrompt(): Promise<MockLocation> {\n return waitForPromptResponse<MockLocation>('location');\n}\n\nconst _getCurrentLocation = async (_options?: { accuracy: Accuracy }): Promise<MockLocation> => {\n checkPermission('geolocation', 'getCurrentLocation');\n const mode = aitState.state.deviceModes.location;\n if (mode === 'web') return getCurrentLocationWeb();\n if (mode === 'prompt') return getCurrentLocationPrompt();\n return getCurrentLocationMock();\n};\nexport const getCurrentLocation = withPermission(_getCurrentLocation, 'geolocation');\n\n// -- startUpdateLocation --\n\ninterface StartUpdateLocationEventParams {\n onEvent: (response: MockLocation) => void;\n onError: (error: unknown) => void;\n options: { accuracy: Accuracy; timeInterval: number; distanceInterval: number };\n}\n\nfunction startUpdateLocationMock(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent, options } = eventParams;\n const interval = Math.max(options.timeInterval, 500);\n const id = setInterval(() => {\n const loc = buildLocation();\n loc.coords.latitude += (Math.random() - 0.5) * 0.0001;\n loc.coords.longitude += (Math.random() - 0.5) * 0.0001;\n onEvent(loc);\n }, interval);\n return () => clearInterval(id);\n}\n\nfunction startUpdateLocationWeb(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent, onError } = eventParams;\n if (!navigator.geolocation) {\n console.warn('[@ait-co/devtools] Geolocation API not available, falling back to mock');\n return startUpdateLocationMock(eventParams);\n }\n const watchId = navigator.geolocation.watchPosition(\n (pos) => {\n onEvent({\n coords: {\n latitude: pos.coords.latitude,\n longitude: pos.coords.longitude,\n altitude: pos.coords.altitude ?? 0,\n accuracy: pos.coords.accuracy,\n altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,\n heading: pos.coords.heading ?? 0,\n },\n timestamp: pos.timestamp,\n accessLocation: 'FINE',\n });\n },\n (err) => onError(err),\n );\n return () => navigator.geolocation.clearWatch(watchId);\n}\n\nfunction startUpdateLocationPrompt(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent } = eventParams;\n const handler = (e: Event) => {\n onEvent((e as CustomEvent).detail as MockLocation);\n };\n window.addEventListener('__ait:prompt-response:location-update', handler);\n window.dispatchEvent(\n new CustomEvent('__ait:prompt-request', { detail: { type: 'location-update' } }),\n );\n return () => window.removeEventListener('__ait:prompt-response:location-update', handler);\n}\n\nconst _startUpdateLocation = (eventParams: StartUpdateLocationEventParams): (() => void) => {\n const mode = aitState.state.deviceModes.location;\n if (mode === 'web') return startUpdateLocationWeb(eventParams);\n if (mode === 'prompt') return startUpdateLocationPrompt(eventParams);\n return startUpdateLocationMock(eventParams);\n};\nexport const startUpdateLocation = withPermission(_startUpdateLocation, 'geolocation');\n","/**\n * 미구현 API용 Proxy 트립와이어.\n *\n * 미구현 프로퍼티에 접근하면 throw한다. 이는 \"devtools에서는 멀쩡히 돌지만\n * 실 SDK에선 실제로 동작하는\" 시나리오를 차단하기 위한 의도적 선택이다.\n * mock이 미구현인 API는 실 SDK에서는 존재할 수 있고, 사용자가 이를 인지하지\n * 못한 채 개발을 이어가면 배포 시점에 놀라게 된다. 에러 메시지에 이슈 URL을\n * 포함해 사용자가 mock 누락을 제보할 수 있게 한다.\n *\n * ## KNOWN_UNIMPLEMENTED 정책\n * SDK에 존재하는 것으로 알려져 있으나 현재 mock이 없는 API 이름만 이 집합에 둔다.\n * 이 경우에만 throw 대신 🔴 inert no-op을 반환하고 sdkCallLog에 기록한다.\n * 완전히 미지의 이름은 여전히 throw — \"잘 되는 척\" 방지.\n */\n\nimport { aitState } from './state.js';\n\nconst ISSUES_URL = 'https://github.com/apps-in-toss-community/devtools/issues';\n\n/**\n * SDK에 존재하나 mock이 아직 없는 것으로 확인된 이름 목록.\n * 새 API가 SDK에 추가되면 여기에 추가하고 별도 PR에서 mock 구현으로 이동한다.\n * 확인되지 않은 이름은 절대 여기에 추가하지 않는다 — throw가 더 안전하다.\n */\nconst KNOWN_UNIMPLEMENTED = new Set<string>([\n // 예: 'someNewSdkApi',\n]);\n\nexport function createMockProxy<T extends Record<string, unknown>>(\n moduleName: string,\n implementations: T,\n): T {\n return new Proxy(implementations, {\n get(target, prop) {\n // 심볼 접근(Symbol.toPrimitive, Symbol.iterator 등)은 프레임워크/런타임이\n // 내부적으로 호출하므로 throw하면 console.log, 구조분해 등이 깨진다.\n if (typeof prop === 'symbol') return undefined;\n if (prop in target) return target[prop];\n\n const name = String(prop);\n\n // SDK에 존재하나 mock 미구현으로 확인된 API — throw 대신 🔴 inert no-op 반환.\n if (KNOWN_UNIMPLEMENTED.has(name)) {\n return (...args: unknown[]): undefined => {\n console.warn(\n `[@ait-co/devtools] ${moduleName}.${name} is known-unimplemented (🔴 inert). ` +\n `Returning undefined. Please file or upvote an issue: ${ISSUES_URL}`,\n );\n aitState.logSdkCall({\n method: `${moduleName}.${name}`,\n args: args,\n timestamp: Date.now(),\n status: 'resolved',\n result: undefined,\n fidelity: 'inert',\n });\n return undefined;\n };\n }\n\n throw new Error(\n `[@ait-co/devtools] ${moduleName}.${prop} is not mocked. ` +\n `This API may exist in @apps-in-toss/web-framework, ` +\n `but devtools' mock does not cover it yet. ` +\n `Please file an issue: ${ISSUES_URL}`,\n );\n },\n }) as T;\n}\n","/**\n * Storage mock\n * localStorage에 `__ait_storage:` prefix로 저장하여 앱 자체 localStorage와 분리\n */\n\nimport { createMockProxy } from '../proxy.js';\n\nexport const Storage = createMockProxy('Storage', {\n getItem: async (key: string): Promise<string | null> => {\n return localStorage.getItem(`__ait_storage:${key}`);\n },\n setItem: async (key: string, value: string): Promise<void> => {\n localStorage.setItem(`__ait_storage:${key}`, value);\n },\n removeItem: async (key: string): Promise<void> => {\n localStorage.removeItem(`__ait_storage:${key}`);\n },\n clearItems: async (): Promise<void> => {\n const keys = Object.keys(localStorage).filter((k) => k.startsWith('__ait_storage:'));\n for (const k of keys) {\n localStorage.removeItem(k);\n }\n },\n});\n","import { type StringKey, t } from '../../i18n/index.js';\nimport {\n generateHapticFeedback,\n getDefaultPlaceholderImages,\n HAPTIC_VIBRATE_PATTERN,\n} from '../../mock/device/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type { HapticFeedbackType } from '../../mock/types.js';\nimport { h, monitoringNotice, selectRow } from '../helpers.js';\n\n// --- Prompt mode state ---\ninterface PendingPrompt {\n type: string;\n}\nlet pendingPrompt: PendingPrompt | null = null;\n\nlet refreshPanel: () => void = () => {};\n\nexport function setDeviceRefreshPanel(fn: () => void) {\n refreshPanel = fn;\n}\n\n// Listen for prompt requests from device APIs\nif (typeof window !== 'undefined') {\n window.addEventListener('__ait:prompt-request', (e: Event) => {\n const detail = (e as CustomEvent).detail as { type: string };\n pendingPrompt = { type: detail.type };\n // Auto-switch to device tab and open panel — handled by index.ts listener which also calls refreshPanel\n window.dispatchEvent(new CustomEvent('__ait:panel-switch-tab', { detail: { tab: 'device' } }));\n });\n}\n\nfunction resolvePrompt(type: string, data: unknown) {\n window.dispatchEvent(new CustomEvent(`__ait:prompt-response:${type}`, { detail: data }));\n pendingPrompt = null;\n refreshPanel();\n}\n\nfunction renderPromptBanner(): HTMLElement | null {\n if (!pendingPrompt) return null;\n\n const banner = h('div', { className: 'ait-prompt-banner' });\n\n if (pendingPrompt.type === 'camera') {\n banner.append(h('div', { className: 'ait-prompt-title' }, t('device.prompt.camera.title')));\n const input = h('input', {\n type: 'file',\n accept: 'image/*',\n style: 'font-size:11px;color:#aaa',\n });\n input.addEventListener('change', () => {\n const file = (input as HTMLInputElement).files?.[0];\n if (!file) return;\n const reader = new FileReader();\n reader.onload = () => resolvePrompt('camera', reader.result as string);\n reader.readAsDataURL(file);\n });\n banner.appendChild(input);\n } else if (pendingPrompt.type === 'photos') {\n banner.append(h('div', { className: 'ait-prompt-title' }, t('device.prompt.photos.title')));\n const input = h('input', {\n type: 'file',\n accept: 'image/*',\n multiple: '',\n style: 'font-size:11px;color:#aaa',\n });\n input.addEventListener('change', () => {\n const files = Array.from((input as HTMLInputElement).files ?? []);\n if (files.length === 0) return;\n Promise.all(\n files.map(\n (file) =>\n new Promise<string>((res) => {\n const reader = new FileReader();\n reader.onload = () => res(reader.result as string);\n reader.readAsDataURL(file);\n }),\n ),\n ).then((dataUris) => resolvePrompt('photos', dataUris));\n });\n banner.appendChild(input);\n } else if (pendingPrompt.type === 'location' || pendingPrompt.type === 'location-update') {\n banner.append(\n h(\n 'div',\n { className: 'ait-prompt-title' },\n pendingPrompt.type === 'location'\n ? t('device.prompt.location.title')\n : t('device.prompt.locationUpdate.title'),\n ),\n );\n const latInput = h('input', {\n className: 'ait-input',\n value: String(aitState.state.location.coords.latitude),\n style: 'width:80px',\n });\n const lngInput = h('input', {\n className: 'ait-input',\n value: String(aitState.state.location.coords.longitude),\n style: 'width:80px',\n });\n const sendBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('device.prompt.send'));\n sendBtn.addEventListener('click', () => {\n const loc = {\n coords: {\n latitude: Number((latInput as HTMLInputElement).value),\n longitude: Number((lngInput as HTMLInputElement).value),\n altitude: 0,\n accuracy: 10,\n altitudeAccuracy: 0,\n heading: 0,\n },\n timestamp: Date.now(),\n accessLocation: 'FINE' as const,\n };\n resolvePrompt(pendingPrompt!.type, loc);\n });\n banner.append(\n h(\n 'div',\n { className: 'ait-prompt-input-row' },\n h('label', {}, t('device.prompt.label.lat')),\n latInput,\n h('label', {}, t('device.prompt.label.lng')),\n lngInput,\n sendBtn,\n ),\n );\n } else {\n // Fallback for unknown prompt types\n banner.append(\n h(\n 'div',\n { className: 'ait-prompt-title' },\n t('device.prompt.fallbackTitle', { type: pendingPrompt.type }),\n ),\n );\n }\n\n // Cancel button for all prompt types\n const cancelBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger', style: 'margin-top:8px' },\n t('device.prompt.cancel'),\n );\n cancelBtn.addEventListener('click', () => {\n pendingPrompt = null;\n window.dispatchEvent(new CustomEvent('__ait:prompt-cancel'));\n refreshPanel();\n });\n banner.appendChild(cancelBtn);\n\n return banner;\n}\n\nexport function renderDeviceTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n // Prompt banner (if active, only when panelEditable)\n if (s.panelEditable) {\n const promptBanner = renderPromptBanner();\n if (promptBanner) container.appendChild(promptBanner);\n }\n\n // Device API Mode selectors\n const modeEntries: Array<{\n labelKey: StringKey;\n key: keyof typeof s.deviceModes;\n options: string[];\n }> = [\n { labelKey: 'device.row.camera', key: 'camera', options: ['mock', 'web', 'prompt'] },\n { labelKey: 'device.row.photos', key: 'photos', options: ['mock', 'web', 'prompt'] },\n { labelKey: 'device.row.location', key: 'location', options: ['mock', 'web', 'prompt'] },\n { labelKey: 'device.row.network', key: 'network', options: ['mock', 'web'] },\n { labelKey: 'device.row.clipboard', key: 'clipboard', options: ['mock', 'web'] },\n ];\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('device.section.modes')),\n ...modeEntries.map((entry) =>\n selectRow(\n t(entry.labelKey),\n entry.options,\n s.deviceModes[entry.key],\n (v) => {\n aitState.patch('deviceModes', { [entry.key]: v } as Partial<typeof s.deviceModes>);\n },\n disabled,\n ),\n ),\n ),\n );\n\n // Mock Images management\n const images = s.mockData.images;\n const imageGrid = h('div', { className: 'ait-image-grid' });\n images.forEach((dataUri, idx) => {\n const thumb = h('div', { className: 'ait-image-thumb' });\n const img = h('img', { src: dataUri });\n const removeBtn = h('button', { className: 'ait-image-remove' }, 'x');\n removeBtn.addEventListener('click', () => {\n const newImages = [...aitState.state.mockData.images];\n newImages.splice(idx, 1);\n aitState.patch('mockData', { images: newImages });\n });\n if (disabled) removeBtn.disabled = true;\n thumb.append(img, removeBtn);\n imageGrid.appendChild(thumb);\n });\n\n const addBtn = h('button', { className: 'ait-btn-secondary' }, t('device.btn.add'));\n addBtn.addEventListener('click', () => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.multiple = true;\n input.onchange = () => {\n const files = Array.from(input.files ?? []);\n Promise.all(\n files.map(\n (file) =>\n new Promise<string>((res) => {\n const reader = new FileReader();\n reader.onload = () => res(reader.result as string);\n reader.readAsDataURL(file);\n }),\n ),\n ).then((dataUris) => {\n aitState.patch('mockData', { images: [...aitState.state.mockData.images, ...dataUris] });\n });\n };\n input.click();\n });\n if (disabled) addBtn.disabled = true;\n\n const defaultsBtn = h('button', { className: 'ait-btn-secondary' }, t('device.btn.useDefaults'));\n defaultsBtn.addEventListener('click', () => {\n aitState.patch('mockData', { images: [...getDefaultPlaceholderImages()] });\n });\n if (disabled) defaultsBtn.disabled = true;\n\n const clearImagesBtn = h('button', { className: 'ait-btn-secondary' }, t('device.btn.clear'));\n clearImagesBtn.addEventListener('click', () => {\n aitState.patch('mockData', { images: [] });\n });\n if (disabled) clearImagesBtn.disabled = true;\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('device.section.mockImages', { count: images.length }),\n ),\n imageGrid,\n h('div', { className: 'ait-btn-row' }, addBtn, defaultsBtn, clearImagesBtn),\n ),\n );\n\n // Haptic section — sdkCallLog에서 마지막 haptic 호출을 읽어 표시한다\n container.appendChild(renderHapticSection());\n\n return container;\n}\n\nfunction renderHapticSection(): HTMLElement {\n // 마지막 generateHapticFeedback 호출을 sdkCallLog에서 찾는다.\n const log = aitState.state.sdkCallLog;\n const lastEntry = [...log].reverse().find((e) => e.method === 'generateHapticFeedback');\n const lastResult = lastEntry?.result as { hapticType: string; vibrated: boolean } | undefined;\n\n const lastCallValue = lastResult\n ? `${lastResult.hapticType} (vibrated: ${String(lastResult.vibrated)})`\n : t('device.haptic.noneYet');\n\n const lastCallRow = h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('device.haptic.lastCall')),\n h('span', { className: 'ait-value' }, lastCallValue),\n );\n\n const hapticTypes = Object.keys(HAPTIC_VIBRATE_PATTERN) as HapticFeedbackType[];\n const triggerSection = h(\n 'div',\n { className: 'ait-section-subtitle' },\n t('device.haptic.trigger'),\n );\n const btnRow = h(\n 'div',\n { className: 'ait-btn-row' },\n ...hapticTypes.map((type) => {\n const btn = h(\n 'button',\n { className: 'ait-btn-secondary', 'data-testid': `haptic-${type}-btn` },\n type,\n );\n btn.addEventListener('click', () => {\n void generateHapticFeedback({ type }).then(() => {\n refreshPanel();\n });\n });\n return btn;\n }),\n );\n\n return h(\n 'div',\n { className: 'ait-section', 'data-testid': 'section-haptic' },\n h('div', { className: 'ait-section-title' }, t('device.section.haptic')),\n lastCallRow,\n triggerSection,\n btnRow,\n );\n}\n","/**\n * SDK 호출 관측 래퍼.\n *\n * `observe(apiName, fidelity, fn)` — fn의 시그니처를 그대로 보존하면서\n * 호출 시 args·resolve·reject 결과를 `aitState.sdkCallLog`에 기록한다.\n *\n * **signature 보존 절대 조건**: 제네릭 pass-through로 `__typecheck.ts`의\n * `Assert<Mock, Original>` 불변을 유지한다. observe()로 감싼 함수는\n * 원본과 동일한 타입을 가진다.\n */\n\nimport type { AitSdkCallFidelity } from '../mcp/ait-source.js';\nimport { aitState } from './state.js';\n\n/**\n * fn을 observe로 감싼다.\n *\n * @param apiName - 로그에 기록할 SDK 메서드 이름 (예: `'setScreenAwakeMode'`)\n * @param fidelity - 이 mock의 fidelity grade ('faithful' | 'partial' | 'inert')\n * @param fn - 실제 mock 구현체. 시그니처를 그대로 통과시킨다.\n * @returns fn과 동일한 타입의 래퍼 함수\n */\nexport function observe<TArgs extends unknown[], TReturn>(\n apiName: string,\n fidelity: AitSdkCallFidelity,\n fn: (...args: TArgs) => TReturn,\n): (...args: TArgs) => TReturn {\n return (...args: TArgs): TReturn => {\n const timestamp = Date.now();\n // args를 JSON-safe하게 직렬화한다. 직렬화할 수 없는 값(함수·순환 참조 등)은\n // 문자열 표현으로 대체해 로그가 깨지지 않도록 한다.\n const safeArgs: unknown[] = args.map((a) => safeSerialize(a));\n\n const result = fn(...args);\n\n if (result instanceof Promise) {\n // pending 상태로 먼저 기록\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'pending',\n fidelity,\n });\n\n // resolve/reject 결과로 업데이트 (ring buffer에서 마지막 pending 항목 덮어쓰기)\n (result as Promise<unknown>).then(\n (value) => {\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'resolved',\n result: safeSerialize(value),\n fidelity,\n });\n },\n (err: unknown) => {\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'rejected',\n error: err instanceof Error ? err.message : String(err),\n fidelity,\n });\n },\n );\n\n return result;\n }\n\n // 동기 반환 — 즉시 resolved로 기록\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'resolved',\n result: safeSerialize(result),\n fidelity,\n });\n\n return result;\n };\n}\n\n/**\n * 값을 JSON-safe한 형태로 변환한다.\n * - null / primitive — 그대로.\n * - 함수 — `'[Function: name]'` 문자열.\n * - 기타 객체 — JSON.stringify 실패 시 `'[unserializable]'`.\n */\nfunction safeSerialize(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value === 'function') return `[Function: ${value.name || 'anonymous'}]`;\n if (typeof value !== 'object') return value;\n try {\n return JSON.parse(JSON.stringify(value));\n } catch {\n return '[unserializable]';\n }\n}\n","/**\n * 광고 mock (GoogleAdMob, TossAds, FullScreenAd)\n *\n * 변경 이력 (#196):\n * - slot 레지스트리로 TossAds destroy/destroyAll 누수 수정 (🟡→🟢)\n * - attachBanner BannerSlotCallbacks 발화 (onAdRendered/onAdImpression/onNoFill 등)\n * - initialize onInitialized/onInitializationFailed 발화\n * - AdMob reward 파라미터화 (state.ads.rewardUnitType/rewardAmount)\n * - 모든 호출 observe()로 sdkCallLog에 기록\n */\n\nimport { observe } from '../observe.js';\nimport { createMockProxy } from '../proxy.js';\nimport { aitState } from '../state.js';\n\nfunction withIsSupported<T extends (...args: never[]) => unknown>(\n fn: T,\n): T & { isSupported: () => boolean } {\n (fn as T & { isSupported: () => boolean }).isSupported = () => true;\n return fn as T & { isSupported: () => boolean };\n}\n\n// --- slot 레지스트리 (TossAds destroy 누수 수정) ---\n// attachBanner가 생성한 placeholder를 slotId로 추적해서\n// destroy/destroyAll이 실제 el.remove()를 수행할 수 있게 한다.\nconst _slotRegistry = new Map<string, HTMLElement>();\n\nlet _slotCounter = 0;\nfunction _nextSlotId(adGroupId: string): string {\n _slotCounter += 1;\n return `mock-slot-${adGroupId}-${_slotCounter}`;\n}\n\n/** 테스트에서 레지스트리를 초기화할 수 있게 export */\nexport function _resetSlotRegistry(): void {\n _slotRegistry.clear();\n _slotCounter = 0;\n}\n\n// --- Google AdMob ---\n\nexport const GoogleAdMob = createMockProxy('GoogleAdMob', {\n loadAppsInTossAdMob: withIsSupported(\n observe(\n 'GoogleAdMob.loadAppsInTossAdMob',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n args.onError(new Error('No fill'));\n return;\n }\n aitState.patch('ads', { isLoaded: true });\n args.onEvent({ type: 'loaded', data: { adGroupId: args.options?.adGroupId } });\n }, 200);\n return () => {};\n },\n ),\n ),\n\n showAppsInTossAdMob: withIsSupported(\n observe(\n 'GoogleAdMob.showAppsInTossAdMob',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'requested' }), 50);\n setTimeout(() => args.onEvent({ type: 'show' }), 100);\n setTimeout(() => args.onEvent({ type: 'impression' }), 150);\n setTimeout(() => {\n const { rewardUnitType, rewardAmount } = aitState.state.ads;\n args.onEvent({\n type: 'userEarnedReward',\n data: { unitType: rewardUnitType, unitAmount: rewardAmount },\n });\n }, 1000);\n setTimeout(() => {\n args.onEvent({ type: 'dismissed' });\n aitState.patch('ads', { isLoaded: false });\n }, 1500);\n return () => {};\n },\n ),\n ),\n\n isAppsInTossAdMobLoaded: withIsSupported(\n observe(\n 'GoogleAdMob.isAppsInTossAdMobLoaded',\n 'faithful',\n async (_options: { adGroupId?: string }): Promise<boolean> => aitState.state.ads.isLoaded,\n ),\n ),\n});\n\n// --- TossAds ---\n\nexport const TossAds = createMockProxy('TossAds', {\n initialize: withIsSupported(\n observe(\n 'TossAds.initialize',\n 'partial',\n (options: {\n callbacks?: { onInitialized?: () => void; onInitializationFailed?: (error: Error) => void };\n }): void => {\n // forceNoFill을 initialization failure로도 활용한다\n if (aitState.state.ads.forceNoFill) {\n options.callbacks?.onInitializationFailed?.(new Error('No fill'));\n return;\n }\n options.callbacks?.onInitialized?.();\n },\n ),\n ),\n\n attach: withIsSupported(\n observe(\n 'TossAds.attach',\n 'partial',\n (_adGroupId: string, target: string | HTMLElement, _options?: unknown): void => {\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n if (el) {\n const placeholder = document.createElement('div');\n placeholder.style.cssText =\n 'background:#f0f0f0;border:1px dashed #999;padding:16px;text-align:center;color:#666;font-size:14px;';\n placeholder.textContent = '[@ait-co/devtools] TossAds Placeholder';\n el.appendChild(placeholder);\n }\n },\n ),\n ),\n\n attachBanner: withIsSupported(\n observe(\n 'TossAds.attachBanner',\n 'faithful',\n (\n adGroupId: string,\n target: string | HTMLElement,\n options?: {\n theme?: 'auto' | 'light' | 'dark';\n tone?: 'blackAndWhite' | 'grey';\n variant?: 'card' | 'expanded';\n callbacks?: {\n onAdRendered?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdViewable?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdClicked?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdImpression?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdFailedToRender?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: Record<string, never>;\n error: { code: number; message: string; domain?: string };\n }) => void;\n onNoFill?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: Record<string, never>;\n }) => void;\n };\n },\n ): { destroy: () => void } => {\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n const slotId = _nextSlotId(adGroupId);\n\n const placeholder = document.createElement('div');\n\n // AttachBannerOptions를 placeholder 스타일에 반영\n const theme = options?.theme ?? 'auto';\n const variant = options?.variant ?? 'card';\n const isDark =\n theme === 'dark' ||\n (theme === 'auto' &&\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-color-scheme: dark)').matches);\n const bg = isDark ? '#1a1a1a' : '#f0f0f0';\n const textColor = isDark ? '#aaa' : '#666';\n const borderColor = isDark ? '#555' : '#999';\n const height = variant === 'expanded' ? '120px' : '60px';\n\n placeholder.dataset.aitSlotId = slotId;\n placeholder.style.cssText = `background:${bg};border:1px dashed ${borderColor};padding:8px 12px;text-align:center;color:${textColor};font-size:12px;min-height:${height};display:flex;align-items:center;justify-content:center;`;\n placeholder.textContent = `[@ait-co/devtools] Banner Ad (${variant})`;\n\n if (el) {\n el.appendChild(placeholder);\n _slotRegistry.set(slotId, placeholder);\n }\n\n const destroySlot = () => {\n const registered = _slotRegistry.get(slotId);\n if (registered) {\n registered.remove();\n _slotRegistry.delete(slotId);\n }\n };\n\n // 콜백 발화 (setTimeout으로 비동기 — 실 SDK와 동일하게 렌더 완료 후)\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n options?.callbacks?.onNoFill?.({\n slotId,\n adGroupId,\n adMetadata: {},\n });\n options?.callbacks?.onAdFailedToRender?.({\n slotId,\n adGroupId,\n adMetadata: {},\n error: { code: 0, message: 'No fill' },\n });\n return;\n }\n\n const eventPayload = {\n slotId,\n adGroupId,\n adMetadata: { creativeId: `mock-creative-${slotId}`, requestId: `mock-req-${slotId}` },\n };\n options?.callbacks?.onAdRendered?.(eventPayload);\n options?.callbacks?.onAdImpression?.(eventPayload);\n }, 100);\n\n return { destroy: destroySlot };\n },\n ),\n ),\n\n destroy: withIsSupported(\n observe('TossAds.destroy', 'faithful', (slotId: string): void => {\n const el = _slotRegistry.get(slotId);\n if (el) {\n el.remove();\n _slotRegistry.delete(slotId);\n }\n }),\n ),\n\n destroyAll: withIsSupported(\n observe('TossAds.destroyAll', 'faithful', (): void => {\n for (const el of _slotRegistry.values()) {\n el.remove();\n }\n _slotRegistry.clear();\n }),\n ),\n});\n\n// --- FullScreen Ad ---\n\nexport const loadFullScreenAd = withIsSupported(\n observe(\n 'loadFullScreenAd',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n args.onError(new Error('No fill'));\n return;\n }\n aitState.patch('ads', { isLoaded: true });\n args.onEvent({ type: 'loaded', data: { adGroupId: args.options?.adGroupId } });\n }, 200);\n return () => {};\n },\n ),\n);\n\nexport const showFullScreenAd = withIsSupported(\n observe(\n 'showFullScreenAd',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'show' }), 100);\n setTimeout(() => args.onEvent({ type: 'dismissed' }), 1500);\n return () => {};\n },\n ),\n);\n","import { t } from '../../i18n/index.js';\nimport { GoogleAdMob, loadFullScreenAd, showFullScreenAd, TossAds } from '../../mock/ads/index.js';\nimport { aitState } from '../../mock/state.js';\nimport { h, inputRow, monitoringNotice } from '../helpers.js';\n\nfunction recordEvent(type: string) {\n aitState.patch('ads', { lastEvent: { type, timestamp: Date.now() } });\n}\n\nfunction recordError(message: string) {\n recordEvent(`error: ${message}`);\n}\n\nfunction statusRow(label: string, value: string): HTMLElement {\n return h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, label),\n h('span', { style: 'font-family:SF Mono,Menlo,monospace;font-size:11px;color:#aaa' }, value),\n );\n}\n\nfunction lastEventLine(): HTMLElement {\n const last = aitState.state.ads.lastEvent;\n if (!last) {\n return h(\n 'div',\n { className: 'ait-log-entry' },\n h('span', { style: 'color:#555' }, t('ads.empty.events')),\n );\n }\n const time = new Date(last.timestamp).toLocaleTimeString();\n const isError = last.type.startsWith('error:');\n return h(\n 'div',\n { className: 'ait-log-entry' },\n h('span', { className: 'ait-log-type', style: isError ? 'color:#e74c3c' : '' }, last.type),\n h('span', { className: 'ait-log-time' }, time),\n );\n}\n\nfunction adSection(\n title: string,\n onLoad: () => void,\n onShow: () => void,\n disabled: boolean,\n): HTMLElement {\n const loadBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.load'));\n const showBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.show'));\n if (disabled) {\n loadBtn.disabled = true;\n showBtn.disabled = true;\n }\n loadBtn.addEventListener('click', onLoad);\n showBtn.addEventListener('click', onShow);\n\n return h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, title),\n h('div', { className: 'ait-btn-row' }, loadBtn, showBtn),\n );\n}\n\n/** TossAds 배너 인터랙티브 섹션 — Render/No-fill/Click/Destroy 버튼 */\nfunction tossAdsBannerSection(disabled: boolean): HTMLElement {\n // 패널 내부 더미 mount 대상\n const mountTarget = h('div', {\n style: 'min-height:60px;background:#111;border-radius:4px;margin-bottom:6px;overflow:hidden;',\n });\n\n const renderBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.render'));\n const noFillBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.noFill'));\n const clickBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.click'));\n const destroyBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('ads.btn.destroy'));\n\n if (disabled) {\n renderBtn.disabled = true;\n noFillBtn.disabled = true;\n clickBtn.disabled = true;\n destroyBtn.disabled = true;\n }\n\n // 가장 최근 attachBanner 반환 handle 보존 (panel 내부 추적)\n let currentHandle: { destroy: () => void } | null = null;\n\n renderBtn.addEventListener('click', () => {\n // 기존 슬롯 정리 후 새로 Render\n currentHandle?.destroy();\n currentHandle = null;\n mountTarget.innerHTML = '';\n\n const handle = TossAds.attachBanner('mock-banner-group', mountTarget, {\n theme: 'auto',\n variant: 'card',\n callbacks: {\n onAdRendered: (p) => recordEvent(`onAdRendered(${p.slotId})`),\n onAdImpression: (p) => recordEvent(`onAdImpression(${p.slotId})`),\n onAdClicked: (p) => recordEvent(`onAdClicked(${p.slotId})`),\n onAdFailedToRender: (p) => recordEvent(`error: onAdFailedToRender(${p.slotId})`),\n onNoFill: (p) => recordEvent(`error: onNoFill(${p.slotId})`),\n },\n });\n currentHandle = handle;\n });\n\n noFillBtn.addEventListener('click', () => {\n // 기존 슬롯 정리\n currentHandle?.destroy();\n currentHandle = null;\n mountTarget.innerHTML = '';\n\n // no-fill 경로: 패널 상의 forceNoFill 상태와 무관하게 즉시 no-fill 콜백을 발화한다.\n // (forceNoFill을 일시 toggle하면 attachBanner 내부 setTimeout이 복원 후 실행돼\n // 실제 forceNoFill 상태를 읽어 기본 경로가 돌아가는 race condition이 생긴다.)\n const slotId = `mock-slot-no-fill-${Date.now()}`;\n recordEvent(`error: onNoFill(${slotId})`);\n recordEvent(`error: onAdFailedToRender(${slotId})`);\n });\n\n clickBtn.addEventListener('click', () => {\n recordEvent('banner:clicked');\n });\n\n destroyBtn.addEventListener('click', () => {\n if (currentHandle) {\n currentHandle.destroy();\n currentHandle = null;\n mountTarget.innerHTML = '';\n recordEvent('banner:destroyed');\n } else {\n recordError('No banner to destroy');\n }\n });\n\n return h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('ads.section.tossAdsBanner')),\n mountTarget,\n h('div', { className: 'ait-btn-row' }, renderBtn, noFillBtn, clickBtn, destroyBtn),\n );\n}\n\nexport function renderAdsTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n const forceNoFillCb = h('input', { type: 'checkbox', className: 'ait-checkbox' });\n forceNoFillCb.checked = s.ads.forceNoFill;\n if (disabled) forceNoFillCb.disabled = true;\n forceNoFillCb.addEventListener('change', () => {\n aitState.patch('ads', { forceNoFill: forceNoFillCb.checked });\n });\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('ads.section.state')),\n statusRow(t('ads.row.isLoaded'), String(s.ads.isLoaded)),\n h('div', { className: 'ait-row' }, h('label', {}, t('ads.row.forceNoFill')), forceNoFillCb),\n inputRow(\n t('ads.row.rewardUnitType'),\n s.ads.rewardUnitType,\n (v) => aitState.patch('ads', { rewardUnitType: v }),\n disabled,\n ),\n inputRow(\n t('ads.row.rewardAmount'),\n String(s.ads.rewardAmount),\n (v) => {\n const n = Number(v);\n if (!Number.isNaN(n)) aitState.patch('ads', { rewardAmount: n });\n },\n disabled,\n ),\n lastEventLine(),\n ),\n adSection(\n t('ads.section.googleAdMob'),\n () => {\n GoogleAdMob.loadAppsInTossAdMob({\n onEvent: (e) => recordEvent(e.type),\n onError: (err) => recordError(err.message),\n });\n },\n () => {\n GoogleAdMob.showAppsInTossAdMob({\n onEvent: (e) => recordEvent(e.type),\n onError: (err) => recordError(err.message),\n });\n },\n disabled,\n ),\n adSection(\n t('ads.section.tossAds'),\n () => {\n // TossAds initialize: live state를 읽어 forceNoFill 분기\n TossAds.initialize({\n callbacks: {\n onInitialized: () => {\n aitState.patch('ads', { isLoaded: true });\n recordEvent('loaded');\n },\n onInitializationFailed: (err) => recordError(err.message),\n },\n });\n },\n () => {\n if (!aitState.state.ads.isLoaded) {\n recordError('Ad not loaded');\n return;\n }\n recordEvent('show');\n setTimeout(() => {\n recordEvent('dismissed');\n aitState.patch('ads', { isLoaded: false });\n }, 1500);\n },\n disabled,\n ),\n tossAdsBannerSection(disabled),\n adSection(\n t('ads.section.fullScreenAd'),\n () => {\n loadFullScreenAd({\n onEvent: (e) => recordEvent(e.type),\n onError: (err) => recordError(err.message),\n });\n },\n () => {\n showFullScreenAd({\n onEvent: (e) => recordEvent(e.type),\n onError: (err) => recordError(err.message),\n });\n },\n disabled,\n ),\n );\n\n return container;\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport { h, monitoringNotice } from '../helpers.js';\n\nconst FIDELITY_BADGE: Record<string, string> = {\n faithful: '🟢',\n partial: '🟡',\n inert: '🔴',\n};\n\nexport function renderAnalyticsTab(): HTMLElement {\n const disabled = !aitState.state.panelEditable;\n const container = h('div');\n if (disabled) container.appendChild(monitoringNotice());\n\n // --- Analytics Log section ---\n const logs = aitState.state.analyticsLog;\n\n const clearAnalyticsBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger' },\n t('analytics.btn.clear'),\n );\n if (disabled) clearAnalyticsBtn.disabled = true;\n clearAnalyticsBtn.addEventListener('click', () => {\n aitState.update({ analyticsLog: [] });\n });\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-row' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('analytics.section.log', { count: logs.length }),\n ),\n clearAnalyticsBtn,\n ),\n ...logs\n .slice(-30)\n .reverse()\n .map((entry) => {\n const time = new Date(entry.timestamp).toLocaleTimeString();\n return h(\n 'div',\n { className: 'ait-log-entry' },\n h('span', { className: 'ait-log-time' }, time),\n h('span', { className: 'ait-log-type' }, entry.type),\n JSON.stringify(entry.params),\n );\n }),\n ),\n );\n\n // --- SDK Calls section ---\n const calls = aitState.state.sdkCallLog;\n\n const clearCallsBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger' },\n t('analytics.calls.btn.clear'),\n );\n if (disabled) clearCallsBtn.disabled = true;\n clearCallsBtn.addEventListener('click', () => {\n aitState.update({ sdkCallLog: [] });\n });\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-row' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('analytics.calls.section', { count: calls.length }),\n ),\n clearCallsBtn,\n ),\n calls.length === 0\n ? h('div', { className: 'ait-log-entry ait-log-empty' }, t('analytics.calls.empty'))\n : h(\n 'div',\n {},\n ...calls\n .slice(-50)\n .reverse()\n .map((call) => {\n const badge = FIDELITY_BADGE[call.fidelity] ?? '⬜';\n const time = new Date(call.timestamp).toLocaleTimeString();\n const argsStr =\n call.args.length > 0\n ? `(${call.args.map((a) => JSON.stringify(a)).join(', ')})`\n : '()';\n const statusSuffix =\n call.status === 'rejected'\n ? ` ✗ ${call.error ?? 'error'}`\n : call.status === 'pending'\n ? ' …'\n : '';\n\n return h(\n 'div',\n { className: `ait-log-entry ait-sdk-call ait-sdk-call-${call.fidelity}` },\n h('span', { className: 'ait-log-badge' }, badge),\n h('span', { className: 'ait-log-method' }, call.method),\n h('span', { className: 'ait-log-args' }, argsStr),\n h('span', { className: 'ait-log-time' }, ` · ${time}`),\n statusSuffix ? h('span', { className: 'ait-log-status' }, statusSuffix) : '',\n );\n }),\n ),\n ),\n );\n\n return container;\n}\n","import { getLocale, type Locale, setLocale, t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type { NetworkStatus, OperationalEnvironment, PlatformOS } from '../../mock/types.js';\nimport { TELEMETRY_ENDPOINT } from '../../telemetry/index.js';\nimport {\n deleteMyData,\n isTier0Enabled,\n readConsentState,\n setConsentViaToggle,\n setTier0Enabled,\n} from '../../telemetry/state.js';\nimport { h, inputRow, monitoringNotice, selectRow } from '../helpers.js';\n\nexport function renderEnvironmentTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.section.platform')),\n selectRow(\n t('env.row.os'),\n ['ios', 'android'],\n s.platform,\n (v) => aitState.update({ platform: v as PlatformOS }),\n disabled,\n ),\n inputRow(\n t('env.row.appVersion'),\n s.appVersion,\n (v) => aitState.update({ appVersion: v }),\n disabled,\n ),\n selectRow(\n t('env.row.environment'),\n ['toss', 'sandbox'],\n s.environment,\n (v) => aitState.update({ environment: v as OperationalEnvironment }),\n disabled,\n ),\n inputRow(t('env.row.locale'), s.locale, (v) => aitState.update({ locale: v }), disabled),\n ),\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.section.network')),\n selectRow(\n t('env.row.networkStatus'),\n ['WIFI', '4G', '5G', '3G', '2G', 'OFFLINE', 'WWAN', 'UNKNOWN'],\n s.networkStatus,\n (v) => aitState.update({ networkStatus: v as NetworkStatus }),\n disabled,\n ),\n ),\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.section.safeArea')),\n inputRow(\n t('env.row.safeArea.top'),\n String(s.safeAreaInsets.top),\n (v) => aitState.patch('safeAreaInsets', { top: Number(v) }),\n disabled,\n ),\n inputRow(\n t('env.row.safeArea.bottom'),\n String(s.safeAreaInsets.bottom),\n (v) => aitState.patch('safeAreaInsets', { bottom: Number(v) }),\n disabled,\n ),\n ),\n buildNavigationSection(),\n buildLanguageSection(),\n buildTelemetrySection(),\n );\n return container;\n}\n\n/**\n * Navigation 동작 관측 (read-only) — real(토스 WebView)에서 native bridge로 발화하는\n * no-op API의 마지막 호출값을 보여준다. Environment를 toss로 바꾸면 toss-gated 가드\n * (예: sdk-example `useDisableIosSwipeGestureInToss`)가 돌면서 이 값이 토글되므로,\n * \"toss 진입 → 가드 실행 → 관측 가능한 state 변화\" 루프를 패널에서 한눈에 확인할 수 있다.\n */\nfunction buildNavigationSection(): HTMLElement {\n const swipe = aitState.state.navigation.iosSwipeGestureEnabled;\n const valueText =\n swipe === null\n ? t('env.value.iosSwipeGesture.unset')\n : swipe\n ? t('env.value.iosSwipeGesture.enabled')\n : t('env.value.iosSwipeGesture.disabled');\n\n return h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.section.navigation')),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('env.row.iosSwipeGesture')),\n h(\n 'span',\n {\n style: `font-family:'SF Mono','Menlo',monospace;font-size:12px;color:${\n swipe === null ? '#888' : '#95e6cb'\n }`,\n },\n valueText,\n ),\n ),\n h('div', { style: 'font-size:11px;color:#666;margin-top:4px' }, t('env.hint.iosSwipeGesture')),\n );\n}\n\nfunction buildLanguageSection(): HTMLElement {\n const current = getLocale();\n const select = h('select', { className: 'ait-select' }) as HTMLSelectElement;\n const options: Array<{ value: Locale; labelKey: 'env.language.ko' | 'env.language.en' }> = [\n { value: 'ko', labelKey: 'env.language.ko' },\n { value: 'en', labelKey: 'env.language.en' },\n ];\n for (const opt of options) {\n const option = h('option', { value: opt.value }, t(opt.labelKey));\n if (opt.value === current) option.selected = true;\n select.appendChild(option);\n }\n select.addEventListener('change', () => {\n setLocale(select.value as Locale);\n });\n\n return h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.section.language')),\n h('div', { className: 'ait-row' }, h('label', {}, t('env.language.row')), select),\n );\n}\n\nfunction buildTelemetrySection(): HTMLElement {\n // --- Tier 0 row ---\n const t0Enabled = isTier0Enabled();\n const t0StatusLabel = h(\n 'span',\n {\n style: `font-size:12px;font-weight:600;color:${t0Enabled ? '#4ade80' : '#888'}`,\n },\n t0Enabled ? t('env.telemetry.t0On') : t('env.telemetry.t0Off'),\n );\n const t0ToggleBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm', style: 'font-size:11px' },\n t0Enabled ? t('env.telemetry.t0TurnOff') : t('env.telemetry.t0TurnOn'),\n );\n t0ToggleBtn.addEventListener('click', () => {\n setTier0Enabled(!t0Enabled);\n window.dispatchEvent(new CustomEvent('__ait:panel-switch-tab', { detail: { tab: 'env' } }));\n });\n const t0Row = h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('env.telemetry.t0Row')),\n h('span', { style: 'display:flex;align-items:center;gap:8px' }, t0StatusLabel, t0ToggleBtn),\n );\n const t0Desc = h(\n 'div',\n { style: 'font-size:11px;color:#666;margin-bottom:6px' },\n t('env.telemetry.t0Desc'),\n );\n\n // --- Tier 1 row ---\n const consent = readConsentState();\n const isGranted = consent === 'granted';\n\n const statusLabel = h(\n 'span',\n {\n style: `font-size:12px;font-weight:600;color:${isGranted ? '#4ade80' : '#888'}`,\n },\n isGranted ? t('env.telemetry.on') : t('env.telemetry.off'),\n );\n\n const toggleBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm', style: 'font-size:11px' },\n isGranted ? t('env.telemetry.turnOff') : t('env.telemetry.turnOn'),\n );\n toggleBtn.addEventListener('click', () => {\n setConsentViaToggle(!isGranted);\n window.dispatchEvent(new CustomEvent('__ait:panel-switch-tab', { detail: { tab: 'env' } }));\n });\n\n const statusRow = h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('env.telemetry.row')),\n h('span', { style: 'display:flex;align-items:center;gap:8px' }, statusLabel, toggleBtn),\n );\n\n // anon_id display (truncated to 8 chars + ellipsis, click-to-copy)\n const rawAnonId = localStorage.getItem('__ait_telemetry:anon_id');\n const displayAnonId = rawAnonId ?? t('env.telemetry.anonIdNotSet');\n const truncatedId = displayAnonId.length > 8 ? `${displayAnonId.slice(0, 8)}…` : displayAnonId;\n\n const anonIdEl = h(\n 'span',\n {\n style: \"font-family:'SF Mono','Menlo',monospace;font-size:11px;color:#95e6cb;cursor:pointer\",\n title: t('env.telemetry.anonIdCopyTitle'),\n },\n t('env.telemetry.anonIdLabel', { value: truncatedId }),\n );\n anonIdEl.addEventListener('click', () => {\n if (!rawAnonId) return;\n navigator.clipboard.writeText(rawAnonId).catch(() => {\n /* clipboard unavailable — silently ignore */\n });\n });\n\n // Delete my data button\n const deleteBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger' },\n t('env.telemetry.deleteBtn'),\n );\n const deleteStatus = h('span', { style: 'font-size:11px;color:#aaa' });\n\n deleteBtn.addEventListener('click', () => {\n deleteBtn.disabled = true;\n deleteStatus.textContent = t('env.telemetry.deleting');\n deleteMyData(TELEMETRY_ENDPOINT)\n .then((ok) => {\n deleteStatus.textContent = ok\n ? t('env.telemetry.deleted')\n : t('env.telemetry.deleteFailedRetry');\n deleteBtn.disabled = false;\n })\n .catch(() => {\n deleteStatus.textContent = t('env.telemetry.deleteFailed');\n deleteBtn.disabled = false;\n });\n });\n\n // Privacy link\n const privacyLink = h('a', {\n href: 'https://docs.aitc.dev/privacy',\n target: '_blank',\n rel: 'noopener noreferrer',\n style: 'font-size:11px;color:#666;text-decoration:none',\n });\n privacyLink.textContent = t('env.telemetry.privacyLink');\n\n return h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('env.telemetry.section')),\n t0Row,\n t0Desc,\n statusRow,\n h('div', { style: 'margin-bottom:6px' }, anonIdEl),\n h(\n 'div',\n { className: 'ait-btn-row', style: 'align-items:center;gap:8px;margin-top:6px' },\n deleteBtn,\n deleteStatus,\n ),\n h('div', { style: 'margin-top:8px' }, privacyLink),\n );\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport { h, monitoringNotice, selectRow } from '../helpers.js';\n\nexport function renderEventsTab(): HTMLElement {\n const disabled = !aitState.state.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n const backBtn = h('button', { className: 'ait-btn' }, t('events.btn.triggerBack'));\n backBtn.addEventListener('click', () => aitState.trigger('backEvent'));\n if (disabled) backBtn.disabled = true;\n\n const homeBtn = h('button', { className: 'ait-btn' }, t('events.btn.triggerHome'));\n homeBtn.addEventListener('click', () => aitState.trigger('homeEvent'));\n if (disabled) homeBtn.disabled = true;\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('events.section.navigation')),\n h('div', { className: 'ait-row' }, backBtn, homeBtn),\n ),\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('events.section.login')),\n selectRow(\n t('events.row.loggedIn'),\n ['true', 'false'],\n String(aitState.state.auth.isLoggedIn),\n (v) => {\n aitState.patch('auth', { isLoggedIn: v === 'true' });\n },\n disabled,\n ),\n selectRow(\n t('events.row.tossLoginIntegrated'),\n ['true', 'false'],\n String(aitState.state.auth.isTossLoginIntegrated),\n (v) => {\n aitState.patch('auth', { isTossLoginIntegrated: v === 'true' });\n },\n disabled,\n ),\n ),\n );\n return container;\n}\n","/**\n * IAP (인앱결제) mock\n */\n\nimport { createMockProxy } from '../proxy.js';\nimport { aitState } from '../state.js';\n\n// orderCounter는 모듈 레벨 상태로 reset()에 의해 초기화되지 않는다.\n// 테스트에서는 orderId를 stringContaining('mock-order-')로 검증하여 카운터 값에 의존하지 않는다.\nlet orderCounter = 0;\n\nfunction generateOrderId(): string {\n return `mock-order-${++orderCounter}-${Date.now()}`;\n}\n\ninterface IapCreateOneTimePurchaseOrderOptions {\n options: {\n sku?: string;\n productId?: string;\n processProductGrant: (params: { orderId: string }) => boolean | Promise<boolean>;\n };\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>;\n onError: (error: unknown) => void | Promise<void>;\n}\n\ninterface CreateSubscriptionPurchaseOrderOptions {\n options: {\n sku: string;\n offerId?: string | null;\n processProductGrant: (params: {\n orderId: string;\n subscriptionId?: string;\n }) => boolean | Promise<boolean>;\n };\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>;\n onError: (error: unknown) => void | Promise<void>;\n}\n\ninterface IapOrderResult {\n orderId: string;\n displayName: string;\n displayAmount: string;\n amount: number;\n currency: string;\n fraction: number;\n miniAppIconUrl: string | null;\n}\n\nfunction buildOrderResult(sku: string): IapOrderResult {\n const product = aitState.state.iap.products.find((p) => p.sku === sku);\n const amountStr = product?.displayAmount?.replace(/[^0-9]/g, '') ?? '1000';\n return {\n orderId: generateOrderId(),\n displayName: product?.displayName ?? 'Mock Product',\n displayAmount: product?.displayAmount ?? '1,000원',\n amount: parseInt(amountStr, 10) || 1000,\n currency: 'KRW',\n fraction: 0,\n miniAppIconUrl: product?.iconUrl || null,\n };\n}\n\nasync function handlePurchase(\n sku: string,\n processProductGrant: (params: {\n orderId: string;\n subscriptionId?: string;\n }) => boolean | Promise<boolean>,\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>,\n onError: (error: unknown) => void | Promise<void>,\n): Promise<void> {\n const nextResult = aitState.state.iap.nextResult;\n\n // 비동기 시뮬레이션 (실제로는 결제 UI가 뜨는 시간)\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult !== 'success') {\n onError({ code: nextResult });\n return;\n }\n\n const result = buildOrderResult(sku);\n\n try {\n const granted = await processProductGrant({ orderId: result.orderId });\n if (!granted) {\n onError({ code: 'PRODUCT_NOT_GRANTED_BY_PARTNER' });\n return;\n }\n } catch (e) {\n onError(e);\n return;\n }\n\n // 주문 완료 기록\n aitState.patch('iap', {\n completedOrders: [\n ...aitState.state.iap.completedOrders,\n {\n orderId: result.orderId,\n sku,\n status: 'COMPLETED' as const,\n date: new Date().toISOString(),\n },\n ],\n });\n\n await onEvent({ type: 'success', data: result });\n}\n\nexport const IAP = createMockProxy('IAP', {\n // 반환되는 cancel 함수는 mock에서는 no-op이다 (실제 SDK는 결제 UI를 닫음)\n createOneTimePurchaseOrder(params: IapCreateOneTimePurchaseOrderOptions): () => void {\n const sku = params.options.sku ?? params.options.productId ?? '';\n handlePurchase(sku, params.options.processProductGrant, params.onEvent, params.onError).catch(\n (e) => console.error('[@ait-co/devtools] IAP unexpected error:', e),\n );\n return () => {};\n },\n\n createSubscriptionPurchaseOrder(params: CreateSubscriptionPurchaseOrderOptions): () => void {\n handlePurchase(\n params.options.sku,\n params.options.processProductGrant,\n params.onEvent,\n params.onError,\n ).catch((e) => console.error('[@ait-co/devtools] IAP unexpected error:', e));\n return () => {};\n },\n\n async getProductItemList(): Promise<{ products: unknown[] }> {\n return {\n products: aitState.state.iap.products.map((p) => ({\n ...p,\n ...(p.type === 'SUBSCRIPTION' ? { renewalCycle: p.renewalCycle ?? 'MONTHLY' } : {}),\n })),\n };\n },\n\n async getPendingOrders(): Promise<{\n orders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }>;\n }> {\n return { orders: [...aitState.state.iap.pendingOrders] };\n },\n\n async getCompletedOrRefundedOrders(): Promise<{\n hasNext: boolean;\n nextKey?: string | null;\n orders: Array<{ orderId: string; sku: string; status: 'COMPLETED' | 'REFUNDED'; date: string }>;\n }> {\n return {\n hasNext: false,\n nextKey: null,\n orders: [...aitState.state.iap.completedOrders],\n };\n },\n\n async completeProductGrant(args: { params: { orderId: string } }): Promise<boolean> {\n // pending → completed 전이\n const idx = aitState.state.iap.pendingOrders.findIndex(\n (o) => o.orderId === args.params.orderId,\n );\n if (idx !== -1) {\n const order = aitState.state.iap.pendingOrders[idx];\n const pendingOrders = aitState.state.iap.pendingOrders.filter((_, i) => i !== idx);\n const completedOrders = [\n ...aitState.state.iap.completedOrders,\n {\n orderId: order.orderId,\n sku: order.sku,\n status: 'COMPLETED' as const,\n date: new Date().toISOString(),\n },\n ];\n aitState.patch('iap', { pendingOrders, completedOrders });\n }\n return true;\n },\n\n async getSubscriptionInfo(_args: { params: { orderId: string } }) {\n return {\n subscription: {\n catalogId: 1,\n status: 'ACTIVE',\n expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),\n isAutoRenew: true,\n gracePeriodExpiresAt: null,\n isAccessible: true,\n },\n };\n },\n});\n\n// --- TossPay ---\n\nexport async function checkoutPayment(options: {\n params: { payToken: string };\n}): Promise<{ success: boolean; reason?: string }> {\n const { nextResult, failReason } = aitState.state.payment;\n console.log('[@ait-co/devtools] checkoutPayment:', options.params.payToken);\n\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult === 'success') {\n return { success: true };\n }\n return { success: false, reason: failReason || 'Mock payment failed' };\n}\n\nexport const requestTossPayPaysBilling = Object.assign(\n async function requestTossPayPaysBilling(options: {\n params: { wrappedToken: string };\n }): Promise<{ success: boolean; reason?: string } | undefined> {\n const { nextResult, failReason } = aitState.state.payment;\n console.log('[@ait-co/devtools] requestTossPayPaysBilling:', options.params.wrappedToken);\n\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult === 'success') {\n return { success: true };\n }\n return { success: false, reason: failReason || 'Mock billing auth failed' };\n },\n { isSupported: () => true },\n);\n","import { t } from '../../i18n/index.js';\nimport { IAP } from '../../mock/iap/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type { IapNextResult } from '../../mock/types.js';\nimport { h, monitoringNotice, selectRow } from '../helpers.js';\n\nfunction formatTimestamp(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return iso;\n return d.toLocaleTimeString();\n}\n\nfunction shortOrderId(orderId: string): string {\n return orderId.length > 12 ? `…${orderId.slice(-10)}` : orderId;\n}\n\nexport function renderIapTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n const results: IapNextResult[] = [\n 'success',\n 'USER_CANCELED',\n 'INVALID_PRODUCT_ID',\n 'PAYMENT_PENDING',\n 'NETWORK_ERROR',\n 'ITEM_ALREADY_OWNED',\n 'INTERNAL_ERROR',\n ];\n\n if (disabled) container.appendChild(monitoringNotice());\n\n const pendingOrders = s.iap.pendingOrders;\n const pendingSection = h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('iap.section.pending', { count: pendingOrders.length }),\n ),\n );\n if (pendingOrders.length === 0) {\n pendingSection.appendChild(h('div', { className: 'ait-log-entry' }, t('iap.empty.pending')));\n } else {\n for (const o of pendingOrders) {\n const completeBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('iap.btn.complete'));\n if (disabled) completeBtn.disabled = true;\n completeBtn.addEventListener('click', () => {\n IAP.completeProductGrant({ params: { orderId: o.orderId } }).catch((err) =>\n console.error('[@ait-co/devtools] completeProductGrant error:', err),\n );\n });\n pendingSection.appendChild(\n h(\n 'div',\n { className: 'ait-log-entry' },\n h('span', { className: 'ait-log-type' }, t('iap.label.pending')),\n `${o.sku} (${shortOrderId(o.orderId)}) · ${formatTimestamp(o.paymentCompletedDate)} `,\n completeBtn,\n ),\n );\n }\n }\n\n const completedOrders = s.iap.completedOrders;\n const completedSection = h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('iap.section.completed', { count: completedOrders.length }),\n ),\n );\n if (completedOrders.length === 0) {\n completedSection.appendChild(\n h('div', { className: 'ait-log-entry' }, t('iap.empty.completed')),\n );\n } else {\n for (const o of completedOrders) {\n completedSection.appendChild(\n h(\n 'div',\n { className: 'ait-log-entry' },\n h('span', { className: 'ait-log-type' }, o.status),\n `${o.sku} (${shortOrderId(o.orderId)}) · ${formatTimestamp(o.date)}`,\n ),\n );\n }\n }\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('iap.section.simulator')),\n selectRow(\n t('iap.row.nextResult'),\n results,\n s.iap.nextResult,\n (v) => {\n aitState.patch('iap', { nextResult: v as IapNextResult });\n },\n disabled,\n ),\n ),\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('iap.section.tossPay')),\n selectRow(\n t('iap.row.tossPayResult'),\n ['success', 'fail'],\n s.payment.nextResult,\n (v) => {\n aitState.patch('payment', { nextResult: v as 'success' | 'fail' });\n },\n disabled,\n ),\n ),\n pendingSection,\n completedSection,\n );\n return container;\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport { h, inputRow, monitoringNotice } from '../helpers.js';\n\nexport function renderLocationTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('location.section.current')),\n inputRow(\n t('location.row.latitude'),\n String(s.location.coords.latitude),\n (v) => {\n const coords = { ...s.location.coords, latitude: Number(v) };\n aitState.patch('location', { coords } as Partial<typeof s.location>);\n },\n disabled,\n ),\n inputRow(\n t('location.row.longitude'),\n String(s.location.coords.longitude),\n (v) => {\n const coords = { ...s.location.coords, longitude: Number(v) };\n aitState.patch('location', { coords } as Partial<typeof s.location>);\n },\n disabled,\n ),\n inputRow(\n t('location.row.accuracy'),\n String(s.location.coords.accuracy),\n (v) => {\n const coords = { ...s.location.coords, accuracy: Number(v) };\n aitState.patch('location', { coords } as Partial<typeof s.location>);\n },\n disabled,\n ),\n ),\n );\n return container;\n}\n","import { type StringKey, t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type { NotificationAgreementResult } from '../../mock/types.js';\nimport { h, monitoringNotice } from '../helpers.js';\n\nconst RESULTS: Array<{ value: NotificationAgreementResult; labelKey: StringKey }> = [\n { value: 'newAgreement', labelKey: 'notifications.option.newAgreement' },\n { value: 'alreadyAgreed', labelKey: 'notifications.option.alreadyAgreed' },\n { value: 'agreementRejected', labelKey: 'notifications.option.agreementRejected' },\n];\n\nfunction radioRow(\n name: string,\n current: NotificationAgreementResult,\n option: { value: NotificationAgreementResult; labelKey: StringKey },\n disabled: boolean,\n): HTMLElement {\n const input = h('input', { type: 'radio', name, value: option.value });\n input.checked = current === option.value;\n if (disabled) input.disabled = true;\n input.addEventListener('change', () => {\n if (input.checked) {\n aitState.patch('notification', { nextResult: option.value });\n }\n });\n return h('label', { className: 'ait-row' }, input, h('span', {}, t(option.labelKey)));\n}\n\nexport function renderNotificationsTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('notifications.section.title')),\n ...RESULTS.map((opt) =>\n radioRow('ait-notification-result', s.notification.nextResult, opt, disabled),\n ),\n ),\n );\n return container;\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type { PermissionName, PermissionStatus } from '../../mock/types.js';\nimport { h, monitoringNotice, selectRow } from '../helpers.js';\n\nexport function renderPermissionsTab(): HTMLElement {\n const s = aitState.state;\n const disabled = !s.panelEditable;\n const container = h('div');\n const names: PermissionName[] = [\n 'camera',\n 'photos',\n 'geolocation',\n 'clipboard',\n 'contacts',\n 'microphone',\n ];\n const statuses: PermissionStatus[] = ['allowed', 'denied', 'notDetermined'];\n\n if (disabled) container.appendChild(monitoringNotice());\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('permissions.section.device')),\n ...names.map((name) =>\n selectRow(\n name,\n statuses,\n s.permissions[name],\n (v) => {\n aitState.patch('permissions', { [name]: v as PermissionStatus });\n },\n disabled,\n ),\n ),\n ),\n );\n return container;\n}\n","/**\n * 사용자 저장 preset CRUD. localStorage `__ait_preset:<id>` 한 키당 하나로 저장한다.\n * 패널-내부 storage(`__ait_btn_pos`, `__ait_device_id`, `__ait_storage:`)와 같은\n * 패턴 — 새 storage 도입 없음.\n *\n * 외부 의존성 0. SSR 환경(Node)에서 import만 되어도 안전하도록 모든 접근은\n * `localStorage` 존재 여부를 확인한다.\n */\n\nimport type { MockPreset, MockPresetState } from './presets.js';\n\nconst PREFIX = '__ait_preset:';\n\nfunction safeLocalStorage(): Storage | null {\n try {\n if (typeof localStorage === 'undefined') return null;\n return localStorage;\n } catch {\n return null;\n }\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/**\n * Storage에서 읽은 임의 JSON을 MockPreset으로 검증. id/label 필수, state는\n * object여야 함. 실패하면 null — caller가 storage entry를 무시하거나 정리하면 된다.\n *\n * `state`의 내부 키/값은 검증하지 않는다. `applyPreset`이 `pickKnownKeys`로\n * 키만 거른 뒤 그대로 state에 패치하므로 잘못된 enum 값이 통과될 수 있지만,\n * mock state라 보안 위협은 없다 — 새 enum 값이 추가됐을 때 저장된 preset을\n * reject하지 않으려는 의도.\n */\nfunction parsePreset(raw: string): MockPreset | null {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!isObject(parsed)) return null;\n const { id, label, description, state } = parsed;\n if (typeof id !== 'string' || id.length === 0) return null;\n if (typeof label !== 'string' || label.length === 0) return null;\n if (!isObject(state)) return null;\n return {\n id,\n label,\n description: typeof description === 'string' ? description : undefined,\n state: state as MockPresetState,\n };\n } catch {\n return null;\n }\n}\n\nexport function listUserPresets(): MockPreset[] {\n const ls = safeLocalStorage();\n if (!ls) return [];\n const out: MockPreset[] = [];\n for (let i = 0; i < ls.length; i++) {\n const key = ls.key(i);\n if (!key?.startsWith(PREFIX)) continue;\n const raw = ls.getItem(key);\n if (!raw) continue;\n const preset = parsePreset(raw);\n if (preset) out.push(preset);\n }\n return out.sort((a, b) => a.label.localeCompare(b.label));\n}\n\n/**\n * Preset을 저장한다. label에서 slug를 derive — 같은 slug가 이미 있으면 `-2`, `-3`\n * suffix를 붙여 새 entry를 만든다 (기존 entry 덮어쓰기 아님). UI는 label만 받으면 된다.\n *\n * Throws:\n * - label trim한 뒤 빈 문자열일 때\n * - localStorage 미가용 환경일 때 (SSR 등)\n * - `setItem` 실패 (`QuotaExceededError` 등) — caller가 처리해야 함\n */\nexport function saveUserPreset(\n label: string,\n state: MockPresetState,\n description?: string,\n): MockPreset {\n const trimmed = label.trim();\n if (trimmed.length === 0) {\n throw new Error('Preset label cannot be empty');\n }\n const ls = safeLocalStorage();\n if (!ls) throw new Error('localStorage not available');\n const id = generateId(trimmed, ls);\n const preset: MockPreset = {\n id,\n label: trimmed,\n state,\n ...(description !== undefined && description.length > 0 ? { description } : {}),\n };\n ls.setItem(PREFIX + id, JSON.stringify(preset));\n return preset;\n}\n\nexport function deleteUserPreset(id: string): void {\n const ls = safeLocalStorage();\n if (!ls) return;\n ls.removeItem(PREFIX + id);\n}\n\n/** 충돌 시 `-2`, `-3` 등 suffix를 붙여 unique한 id 만든다. */\nfunction generateId(label: string, ls: Storage): string {\n const base =\n label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 40) || 'preset';\n let candidate = base;\n let n = 2;\n while (ls.getItem(PREFIX + candidate) !== null) {\n candidate = `${base}-${n}`;\n n += 1;\n }\n return candidate;\n}\n","/**\n * Mock state preset library.\n *\n * 자주 쓰는 QA 시나리오(권한 거부, 오프라인, 미로그인 등)를 한 클릭으로 적용할 수 있는\n * preset 정의 + apply 유틸. 토글 한 번에 여러 mock 키가 동시에 일정 상태가 되어야 하는\n * 경우 매번 손으로 맞추는 번잡함을 제거한다.\n *\n * Preset state는 `aitState`의 일부만 다룬다 — preset이 어떤 키도 건드리지 않으면 해당\n * 키는 현재 값을 유지한다. forward-compat: schema 외 키가 들어와도 무시된다.\n */\n\nimport type { AitDevtoolsState } from './state.js';\nimport { aitState } from './state.js';\n\n/**\n * Preset이 덮어쓸 수 있는 mock state slice. 모든 키는 optional —\n * 한 preset이 모든 분야를 정의할 필요 없다.\n *\n * 일부러 좁게 잡았다: viewport / brand / mockData / analyticsLog 등 QA 시나리오와\n * 직접 관련 없는 영역은 preset 대상에서 제외한다 (preset 적용으로 unrelated state가\n * 흔들리는 사고 방지). 필요해지면 추가.\n *\n * `iap` slice는 일부러 `nextResult`만 노출한다 — products / pendingOrders /\n * completedOrders는 array/object 비교가 까다롭고 QA 시나리오에서 강제할 일이 거의\n * 없다. `captureCurrentState` / `matchesPreset` 의 iap 처리 범위와 동기화.\n */\nexport interface MockPresetState {\n networkStatus?: AitDevtoolsState['networkStatus'];\n permissions?: Partial<AitDevtoolsState['permissions']>;\n auth?: Partial<AitDevtoolsState['auth']>;\n iap?: { nextResult?: AitDevtoolsState['iap']['nextResult'] };\n ads?: Partial<AitDevtoolsState['ads']>;\n payment?: Partial<AitDevtoolsState['payment']>;\n}\n\nexport interface MockPreset {\n id: string;\n label: string;\n description?: string;\n state: MockPresetState;\n}\n\nexport const builtInPresets: readonly MockPreset[] = [\n {\n id: 'all-allowed',\n label: 'All allowed (default-ish)',\n description: '모든 권한 허용, WIFI, 로그인됨, IAP success',\n state: {\n networkStatus: 'WIFI',\n permissions: {\n camera: 'allowed',\n photos: 'allowed',\n geolocation: 'allowed',\n clipboard: 'allowed',\n contacts: 'allowed',\n microphone: 'allowed',\n },\n auth: { isLoggedIn: true },\n iap: { nextResult: 'success' },\n ads: { forceNoFill: false },\n payment: { nextResult: 'success', failReason: '' },\n },\n },\n {\n id: 'permission-denied',\n label: 'Permissions denied',\n description: 'camera / photos / geolocation / contacts 거부',\n state: {\n permissions: {\n camera: 'denied',\n photos: 'denied',\n geolocation: 'denied',\n contacts: 'denied',\n },\n },\n },\n {\n id: 'offline',\n label: 'Offline',\n description: 'getNetworkStatus → OFFLINE, IAP NETWORK_ERROR',\n state: {\n networkStatus: 'OFFLINE',\n iap: { nextResult: 'NETWORK_ERROR' },\n payment: { nextResult: 'fail', failReason: 'NETWORK_ERROR' },\n },\n },\n {\n id: 'logged-out',\n label: 'Logged out',\n description: 'auth.isLoggedIn=false. login flow 검증용',\n state: {\n auth: { isLoggedIn: false },\n },\n },\n {\n id: 'iap-pending',\n label: 'IAP payment pending',\n description: '결제 진행 중 분기 검증',\n state: {\n iap: { nextResult: 'PAYMENT_PENDING' },\n },\n },\n {\n id: 'ads-no-fill',\n label: 'Ads — no fill',\n description: '광고 fill 실패 분기 검증',\n state: {\n networkStatus: 'WIFI',\n ads: { forceNoFill: true },\n },\n },\n];\n\n/**\n * Preset의 nested slice를 검증된 키만 골라서 풀어낸다. Forward-compat 차원에서\n * 알지 못하는 키는 drop, drop된 키 전부를 모아 한 번에 warn한다.\n *\n * Value 단위 검증은 하지 않는다 — `permissions.camera`에 enum 외 값이 들어와도\n * 그대로 통과한다. mock state라 잘못된 값은 mock 함수 분기 결과만 흔든다.\n * 새 enum 값이 추가됐을 때 저장된 preset을 reject하지 않으려는 의도.\n */\nfunction pickKnownKeys<T extends object>(\n input: unknown,\n allowed: readonly (keyof T)[],\n): Partial<T> {\n if (typeof input !== 'object' || input === null) return {};\n const out: Partial<T> = {};\n const dropped: string[] = [];\n for (const [key, value] of Object.entries(input)) {\n if ((allowed as readonly string[]).includes(key)) {\n (out as Record<string, unknown>)[key] = value;\n } else {\n dropped.push(key);\n }\n }\n if (dropped.length > 0) {\n console.warn(`[@ait-co/devtools] Preset dropped unknown keys: ${dropped.join(', ')}`);\n }\n return out;\n}\n\nconst PERMISSION_KEYS = [\n 'camera',\n 'photos',\n 'geolocation',\n 'clipboard',\n 'contacts',\n 'microphone',\n] as const;\nconst AUTH_KEYS = ['isLoggedIn', 'isTossLoginIntegrated', 'userKeyHash'] as const;\nconst IAP_KEYS = ['nextResult'] as const;\nconst ADS_KEYS = ['isLoaded', 'nextEvent', 'forceNoFill', 'lastEvent'] as const;\nconst PAYMENT_KEYS = ['nextResult', 'failReason'] as const;\n\n/**\n * Preset state를 현재 `aitState`에 적용한다. 정의된 키만 덮어쓰고, 알지 못하는 키는\n * 조용히 drop한다 (한 번 warn). 여러 슬라이스를 적용해도 listener notify는 한 번이다\n * (`aitState.transaction` 사용 — panel re-render 폭주 방지).\n */\nexport function applyPreset(state: MockPresetState): void {\n aitState.transaction(() => {\n if (state.networkStatus !== undefined) {\n aitState.update({ networkStatus: state.networkStatus });\n }\n if (state.permissions !== undefined) {\n aitState.patch(\n 'permissions',\n pickKnownKeys<AitDevtoolsState['permissions']>(state.permissions, PERMISSION_KEYS),\n );\n }\n if (state.auth !== undefined) {\n aitState.patch('auth', pickKnownKeys<AitDevtoolsState['auth']>(state.auth, AUTH_KEYS));\n }\n if (state.iap !== undefined) {\n const picked = pickKnownKeys<{ nextResult: AitDevtoolsState['iap']['nextResult'] }>(\n state.iap,\n IAP_KEYS,\n );\n aitState.patch('iap', picked);\n }\n if (state.ads !== undefined) {\n aitState.patch('ads', pickKnownKeys<AitDevtoolsState['ads']>(state.ads, ADS_KEYS));\n }\n if (state.payment !== undefined) {\n aitState.patch(\n 'payment',\n pickKnownKeys<AitDevtoolsState['payment']>(state.payment, PAYMENT_KEYS),\n );\n }\n });\n}\n\n/**\n * Preset의 모든 정의된 슬라이스가 현재 state와 일치하는지 검사. UI에서 dirty\n * indicator를 그릴 때 쓴다.\n *\n * 일치한다 = preset이 정의한 키 전부가 그대로다. preset이 정의하지 않은 키는\n * 비교 대상이 아니다 — preset은 partial이므로 다른 토글이 바뀌어도 dirty가 아니다.\n */\nexport function matchesPreset(snapshot: AitDevtoolsState, preset: MockPresetState): boolean {\n if (preset.networkStatus !== undefined && snapshot.networkStatus !== preset.networkStatus) {\n return false;\n }\n if (preset.permissions !== undefined) {\n for (const k of PERMISSION_KEYS) {\n const want = preset.permissions[k];\n if (want !== undefined && snapshot.permissions[k] !== want) return false;\n }\n }\n if (preset.auth !== undefined) {\n for (const k of AUTH_KEYS) {\n const want = preset.auth[k];\n if (want !== undefined && snapshot.auth[k] !== want) return false;\n }\n }\n if (preset.iap !== undefined) {\n if (preset.iap.nextResult !== undefined && snapshot.iap.nextResult !== preset.iap.nextResult) {\n return false;\n }\n }\n if (preset.ads !== undefined) {\n if (preset.ads.forceNoFill !== undefined && snapshot.ads.forceNoFill !== preset.ads.forceNoFill)\n return false;\n if (preset.ads.isLoaded !== undefined && snapshot.ads.isLoaded !== preset.ads.isLoaded)\n return false;\n if (preset.ads.nextEvent !== undefined && snapshot.ads.nextEvent !== preset.ads.nextEvent)\n return false;\n }\n if (preset.payment !== undefined) {\n for (const k of PAYMENT_KEYS) {\n const want = preset.payment[k];\n if (want !== undefined && snapshot.payment[k] !== want) return false;\n }\n }\n return true;\n}\n\n/**\n * 현재 state에서 preset에 저장할 만한 슬라이스를 추출. \"save current as preset\"에서 쓴다.\n */\nexport function captureCurrentState(snapshot: AitDevtoolsState): MockPresetState {\n return {\n networkStatus: snapshot.networkStatus,\n permissions: { ...snapshot.permissions },\n auth: {\n isLoggedIn: snapshot.auth.isLoggedIn,\n isTossLoginIntegrated: snapshot.auth.isTossLoginIntegrated,\n userKeyHash: snapshot.auth.userKeyHash,\n },\n iap: { nextResult: snapshot.iap.nextResult },\n ads: {\n forceNoFill: snapshot.ads.forceNoFill,\n isLoaded: snapshot.ads.isLoaded,\n nextEvent: snapshot.ads.nextEvent,\n },\n payment: { ...snapshot.payment },\n };\n}\n","import { t } from '../../i18n/index.js';\nimport { deleteUserPreset, listUserPresets, saveUserPreset } from '../../mock/preset-store.js';\nimport {\n applyPreset,\n builtInPresets,\n captureCurrentState,\n type MockPreset,\n matchesPreset,\n} from '../../mock/presets.js';\nimport { type AitDevtoolsState, aitState } from '../../mock/state.js';\nimport { h, monitoringNotice } from '../helpers.js';\n\nexport function renderPresetsTab(refreshPanel: () => void): HTMLElement {\n const disabled = !aitState.state.panelEditable;\n const container = h('div');\n if (disabled) container.appendChild(monitoringNotice());\n\n const userPresets = listUserPresets();\n const snapshot = aitState.state;\n\n container.append(\n renderSection(\n t('presets.section.builtIn'),\n builtInPresets,\n disabled,\n snapshot,\n refreshPanel,\n false,\n ),\n );\n\n container.append(\n renderSection(\n t('presets.section.saved', { count: userPresets.length }),\n userPresets,\n disabled,\n snapshot,\n refreshPanel,\n true,\n ),\n );\n\n // Save current state as preset\n const saveBtn = h('button', { className: 'ait-btn ait-btn-sm' }, t('presets.btn.saveCurrent'));\n if (disabled) saveBtn.disabled = true;\n saveBtn.addEventListener('click', () => {\n const label = window.prompt(t('presets.prompt.label'));\n if (label === null) return;\n try {\n saveUserPreset(label, captureCurrentState(aitState.state));\n } catch (err) {\n window.alert((err as Error).message);\n return;\n }\n refreshPanel();\n });\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('presets.section.save')),\n h(\n 'div',\n { style: 'color:#888;font-size:11px;margin-bottom:6px' },\n t('presets.save.description'),\n ),\n saveBtn,\n ),\n );\n\n return container;\n}\n\nfunction renderSection(\n title: string,\n presets: readonly MockPreset[],\n disabled: boolean,\n snapshot: AitDevtoolsState,\n refreshPanel: () => void,\n deletable: boolean,\n): HTMLElement {\n const section = h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, title),\n );\n\n if (presets.length === 0) {\n section.append(\n h(\n 'div',\n { style: 'color:#555;font-size:12px' },\n deletable ? t('presets.empty.saved') : t('presets.empty.builtIn'),\n ),\n );\n return section;\n }\n\n for (const preset of presets) {\n const isActive = matchesPreset(snapshot, preset.state);\n\n const labelEl = h(\n 'span',\n { className: 'ait-preset-label' },\n isActive ? `✓ ${preset.label}` : preset.label,\n );\n\n const applyBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm' },\n isActive ? t('presets.btn.reApply') : t('presets.btn.apply'),\n );\n if (disabled) applyBtn.disabled = true;\n applyBtn.addEventListener('click', () => {\n applyPreset(preset.state);\n refreshPanel();\n });\n\n const buttons: Node[] = [applyBtn];\n if (deletable) {\n const delBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger' },\n t('presets.btn.delete'),\n );\n if (disabled) delBtn.disabled = true;\n delBtn.addEventListener('click', () => {\n if (!window.confirm(t('presets.confirm.delete', { label: preset.label }))) return;\n deleteUserPreset(preset.id);\n refreshPanel();\n });\n buttons.push(delBtn);\n }\n\n const actions = h('span', { className: 'ait-preset-actions' }, ...buttons);\n\n const row = h(\n 'div',\n { className: `ait-preset-row${isActive ? ' ait-preset-active' : ''}` },\n labelEl,\n actions,\n );\n section.append(row);\n\n if (preset.description) {\n section.append(h('div', { className: 'ait-preset-description' }, preset.description));\n }\n }\n\n return section;\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport { h, monitoringNotice } from '../helpers.js';\n\nexport function renderStorageTab(refreshPanel: () => void): HTMLElement {\n const disabled = !aitState.state.panelEditable;\n const container = h('div');\n if (disabled) container.appendChild(monitoringNotice());\n const prefix = '__ait_storage:';\n const entries: Array<[string, string]> = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(prefix)) {\n entries.push([key.slice(prefix.length), localStorage.getItem(key) ?? '']);\n }\n }\n\n const clearBtn = h(\n 'button',\n { className: 'ait-btn ait-btn-sm ait-btn-danger' },\n t('storage.btn.clearAll'),\n );\n if (disabled) clearBtn.disabled = true;\n clearBtn.addEventListener('click', () => {\n for (const [key] of entries) {\n localStorage.removeItem(prefix + key);\n }\n refreshPanel();\n });\n\n container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-row' },\n h(\n 'div',\n { className: 'ait-section-title' },\n t('storage.section.title', { count: entries.length }),\n ),\n clearBtn,\n ),\n entries.length === 0\n ? h('div', { style: 'color:#555;font-size:12px' }, t('storage.empty'))\n : h(\n 'div',\n {},\n ...entries.map(([key, value]) =>\n h(\n 'div',\n { className: 'ait-storage-row' },\n h('span', { className: 'ait-storage-key' }, key),\n h(\n 'span',\n { className: 'ait-storage-value' },\n value.length > 100 ? `${value.slice(0, 100)}...` : value,\n ),\n ),\n ),\n ),\n ),\n );\n return container;\n}\n","/**\n * 화면/네비게이션/이벤트 mock\n */\n\nimport { getNetworkStatusByMode } from '../device/index.js';\nimport { observe } from '../observe.js';\nimport { aitState } from '../state.js';\nimport type { NetworkStatus } from '../types.js';\n\nexport async function closeView(): Promise<void> {\n console.log('[@ait-co/devtools] closeView called');\n window.history.back();\n}\n\nexport async function openURL(url: string): Promise<void> {\n console.log('[@ait-co/devtools] openURL:', url);\n window.open(url, '_blank');\n}\n\nexport async function share(message: { message: string }): Promise<void> {\n if (navigator.share) {\n await navigator.share({ text: message.message });\n return;\n }\n console.log('[@ait-co/devtools] share:', message.message);\n}\n\nexport async function getTossShareLink(path: string, _ogImageUrl?: string): Promise<string> {\n return `https://toss.im/share/mock${path}`;\n}\n\nexport async function setIosSwipeGestureEnabled(options: { isEnabled: boolean }): Promise<void> {\n console.log('[@ait-co/devtools] setIosSwipeGestureEnabled:', options.isEnabled);\n // real(토스 WebView)에선 이 호출이 native bridge로 발화한다(devtools#171 실측). mock은\n // 그 \"마지막 호출값\"을 관측 가능한 state로 mirror해, toss-gated 가드(예: sdk-example\n // useDisableIosSwipeGestureInToss)가 실제로 돌았는지를 AIT.getMockState로 대조할 수 있게 한다.\n aitState.patch('navigation', { iosSwipeGestureEnabled: options.isEnabled });\n}\n\nexport async function setDeviceOrientation(options: {\n type: 'portrait' | 'landscape';\n}): Promise<void> {\n const current = aitState.state.viewport.orientation;\n if (current === 'auto') {\n console.log('[@ait-co/devtools] setDeviceOrientation:', options.type);\n // appOrientation은 Panel이 'auto'일 때 effective orientation을 결정하는 별도 필드.\n // viewport.orientation은 사용자 의도이므로 SDK가 임의로 덮어쓰지 않는다 — 그래야\n // 앱이 같은 방향으로 여러 번 호출해도 매번 정상 반영된다.\n aitState.patch('viewport', { appOrientation: options.type });\n return;\n }\n console.warn(\n `[@ait-co/devtools] setDeviceOrientation(${options.type}) ignored — Panel is forcing \"${current}\". Change the Viewport tab's orientation to \"auto\" to let the app control rotation.`,\n );\n}\n\nexport const setScreenAwakeMode = observe(\n 'setScreenAwakeMode',\n 'inert',\n async (options: { enabled: boolean }): Promise<{ enabled: boolean }> => {\n console.log('[@ait-co/devtools] setScreenAwakeMode:', options.enabled);\n return { enabled: options.enabled };\n },\n);\n\nexport const setSecureScreen = observe(\n 'setSecureScreen',\n 'inert',\n async (options: { enabled: boolean }): Promise<{ enabled: boolean }> => {\n console.log('[@ait-co/devtools] setSecureScreen:', options.enabled);\n return { enabled: options.enabled };\n },\n);\n\nconst _requestReviewImpl = observe('requestReview', 'inert', async (): Promise<void> => {\n console.log('[@ait-co/devtools] requestReview called');\n});\nexport const requestReview: typeof _requestReviewImpl & { isSupported: () => boolean } =\n _requestReviewImpl as typeof _requestReviewImpl & { isSupported: () => boolean };\nrequestReview.isSupported = () => true;\n\n// --- 환경 정보 ---\n\nexport function getPlatformOS(): 'ios' | 'android' {\n return aitState.state.platform;\n}\n\nexport function getOperationalEnvironment(): 'toss' | 'sandbox' {\n return aitState.state.environment;\n}\n\nexport function getTossAppVersion(): string {\n return aitState.state.appVersion;\n}\n\nexport function isMinVersionSupported(minVersions: { android: string; ios: string }): boolean {\n const platform = aitState.state.platform;\n const required = platform === 'ios' ? minVersions.ios : minVersions.android;\n if (required === 'always') return true;\n if (required === 'never') return false;\n\n const current = aitState.state.appVersion.split('.').map(Number);\n const min = required.split('.').map(Number);\n for (let i = 0; i < 3; i++) {\n if ((current[i] ?? 0) > (min[i] ?? 0)) return true;\n if ((current[i] ?? 0) < (min[i] ?? 0)) return false;\n }\n return true; // equal\n}\n\nexport function getSchemeUri(): string {\n return aitState.state.schemeUri || window.location.pathname;\n}\n\nexport function getLocale(): string {\n return aitState.state.locale;\n}\n\nexport function getDeviceId(): string {\n return aitState.state.deviceId;\n}\n\nexport function getGroupId(): string {\n return aitState.state.groupId;\n}\n\nexport async function getNetworkStatus(): Promise<NetworkStatus> {\n const modeResult = getNetworkStatusByMode();\n if (modeResult) return modeResult;\n return aitState.state.networkStatus;\n}\n\nexport async function getServerTime(): Promise<number | undefined> {\n return Date.now();\n}\n(getServerTime as unknown as { isSupported: () => boolean }).isSupported = () => true;\n\n// --- 이벤트 시스템 ---\n\ninterface GraniteEventMap {\n backEvent: { onEvent: () => void; onError?: (error: Error) => void; options?: undefined };\n homeEvent: { onEvent: () => void; onError?: (error: Error) => void; options?: undefined };\n}\n\nexport const graniteEvent = {\n addEventListener<K extends keyof GraniteEventMap>(\n event: K,\n {\n onEvent,\n onError,\n }: {\n onEvent: GraniteEventMap[K]['onEvent'];\n onError?: GraniteEventMap[K]['onError'];\n options?: GraniteEventMap[K]['options'];\n },\n ): () => void {\n const handler = () => {\n try {\n onEvent();\n } catch (e) {\n onError?.(e instanceof Error ? e : new Error(String(e)));\n }\n };\n window.addEventListener(`__ait:${event}`, handler);\n return () => window.removeEventListener(`__ait:${event}`, handler);\n },\n};\n\nexport const appsInTossEvent = {\n addEventListener<K extends string>(\n _event: K,\n _handlers: {\n onEvent: (...args: unknown[]) => void;\n onError?: (error: Error) => void;\n options?: unknown;\n },\n ): () => void {\n return () => {};\n },\n};\n\ninterface TdsEventMap {\n navigationAccessoryEvent: {\n onEvent: (data: { id: string }) => void;\n onError?: (error: Error) => void;\n options: undefined;\n };\n}\n\nexport const tdsEvent = {\n addEventListener<K extends keyof TdsEventMap>(\n event: K,\n {\n onEvent,\n }: {\n onEvent: TdsEventMap[K]['onEvent'];\n onError?: TdsEventMap[K]['onError'];\n options?: TdsEventMap[K]['options'];\n },\n ): () => void {\n const handler = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n onEvent(detail);\n };\n window.addEventListener(`__ait:${event}`, handler);\n return () => window.removeEventListener(`__ait:${event}`, handler);\n },\n};\n\nexport function onVisibilityChangedByTransparentServiceWeb(eventParams: {\n options: { callbackId: string };\n onEvent: (isVisible: boolean) => void;\n onError: (error: unknown) => void;\n}): () => void {\n const handler = () => eventParams.onEvent(!document.hidden);\n document.addEventListener('visibilitychange', handler);\n return () => document.removeEventListener('visibilitychange', handler);\n}\n\n// --- env / globals ---\n\nexport const env = {\n getDeploymentId: () => aitState.state.deploymentId,\n};\n\nexport function getAppsInTossGlobals() {\n return {\n deploymentId: aitState.state.deploymentId,\n brandDisplayName: aitState.state.brand.displayName,\n brandIcon: aitState.state.brand.icon,\n brandPrimaryColor: aitState.state.brand.primaryColor,\n };\n}\n\n// --- SafeAreaInsets ---\n\ntype SafeAreaInsetsValue = { top: number; bottom: number; left: number; right: number };\ntype SafeAreaInsetsSubscribeHandler = { onEvent: (data: SafeAreaInsetsValue) => void };\n\nexport const SafeAreaInsets = {\n get: (): SafeAreaInsetsValue => ({ ...aitState.state.safeAreaInsets }),\n // NOTE: aitState.subscribe에 위임하므로 safeAreaInsets 외 상태 변경에도 콜백이 호출된다.\n // 실제 SDK는 insets 변경 시에만 호출되지만, mock에서는 간소화를 위해 필터링하지 않는다.\n subscribe: ({ onEvent }: SafeAreaInsetsSubscribeHandler): (() => void) => {\n return aitState.subscribe(() => onEvent({ ...aitState.state.safeAreaInsets }));\n },\n};\n\n/** @deprecated */\nexport function getSafeAreaInsets(): number {\n return aitState.state.safeAreaInsets.top;\n}\n","/**\n * 기기 preset ↔ 브라우저 특성 정합\n *\n * Viewport preset이 active일 때(`none`/`custom` 아님), 그 preset이 주장하는 기기와\n * `navigator.userAgent`·`navigator.platform`·`window.devicePixelRatio`·`screen.*`를\n * 일치시킨다. 특정 기기 frame 가상환경을 제공하는 이상 UA/DPR만 호스트 데스크톱 값으로\n * 남으면 비일관적이기 때문이다 (#190).\n *\n * 한계 — page-JS override는 **JS 읽기값만** 바꾼다. 실 CSS media query(`@media`),\n * 실제 터치 이벤트, 엔진 레벨 레이아웃은 호스트 브라우저 값이 그대로다. 픽셀/입력 단위\n * 완전 emulation이 필요하면 Chrome DevTools device-mode(또는 CDP)를 쓴다. preset이\n * `none`/`custom`이면 override를 걸지 않아 일반 dev의 호스트 환경을 건드리지 않는다.\n *\n * 구현 — 대상 속성은 setter가 없어도 `configurable: true`이므로 `Object.defineProperty`로\n * getter를 덮어쓸 수 있다. 원복을 위해 최초 override 직전의 디스크립터를 저장한다.\n */\n\nimport { aitState } from '../mock/state.js';\nimport type { PlatformOS, ViewportPreset } from '../mock/types.js';\n\n/** preset id → 플랫폼. Apple 계열은 ios, 그 외(Galaxy)는 android. */\nexport function platformForPreset(presetId: string): PlatformOS {\n return presetId.startsWith('iphone') || presetId.startsWith('ipad') ? 'ios' : 'android';\n}\n\nexport interface DeviceProfile {\n platform: PlatformOS;\n userAgent: string;\n navigatorPlatform: string;\n devicePixelRatio: number;\n /** 세로 기준 물리 해상도 (CSS px × dpr). landscape면 swap해서 적용. */\n screenWidth: number;\n screenHeight: number;\n}\n\n/**\n * preset + 토스 앱 버전으로 기기 프로필을 합성한다.\n *\n * UA는 표준 모바일 UA 뒤에 `AppsInToss TossApp/<appVersion>` 토큰을 붙인다 — #171\n * 실측(`AppsInToss TossApp/5.261.0`)에서 확인된 토스 WebView UA 형태.\n *\n * @param preset portrait 기준 width/height/dpr를 가진 device preset\n * @param appVersion `aitState.state.appVersion` (UA suffix의 버전 토큰)\n * @param landscape true면 screen width/height를 swap\n */\nexport function buildDeviceProfile(\n preset: ViewportPreset,\n appVersion: string,\n landscape: boolean,\n): DeviceProfile {\n const platform = platformForPreset(preset.id);\n const tossToken = `AppsInToss TossApp/${appVersion}`;\n\n const baseUa =\n platform === 'ios'\n ? // iOS Safari WebView 형태 (iOS 17 계열 — 토스 WebView가 보고하는 라인과 정합).\n 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) ' +\n 'AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148'\n : // Android Chrome WebView 형태.\n 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) ' +\n 'Chrome/120.0.0.0 Mobile Safari/537.36';\n\n const physWidth = Math.round(preset.width * preset.dpr);\n // screenHeight is the full device screen height in CSS px (= window.screen.height on real device).\n // For partner presets, preset.height = WebView innerHeight (smaller than full screen);\n // preset.screenHeight carries the actual screen.height. Falls back to preset.height when absent.\n const physHeight = Math.round((preset.screenHeight ?? preset.height) * preset.dpr);\n\n return {\n platform,\n userAgent: `${baseUa} ${tossToken}`,\n navigatorPlatform: platform === 'ios' ? 'iPhone' : 'Linux armv8l',\n devicePixelRatio: preset.dpr,\n screenWidth: landscape ? physHeight : physWidth,\n screenHeight: landscape ? physWidth : physHeight,\n };\n}\n\n// --- override apply / revert ---------------------------------------------\n\ninterface SavedDescriptor {\n target: object;\n prop: string;\n descriptor: PropertyDescriptor | undefined;\n}\n\nlet savedDescriptors: SavedDescriptor[] | null = null;\n\nfunction override(target: object, prop: string, value: unknown, saved: SavedDescriptor[]): void {\n // 같은 override 세션에서 이미 저장했으면 다시 저장하지 않는다 (원본 보존).\n if (!saved.some((s) => s.target === target && s.prop === prop)) {\n saved.push({ target, prop, descriptor: Object.getOwnPropertyDescriptor(target, prop) });\n }\n try {\n Object.defineProperty(target, prop, {\n configurable: true,\n get: () => value,\n });\n } catch {\n // 일부 환경(엄격한 jsdom 등)에서 redefine이 막히면 조용히 건너뛴다.\n }\n}\n\n/**\n * 기기 프로필을 현재 페이지의 `navigator`/`window`/`screen`에 적용한다.\n * 호출 시 직전 디스크립터를 저장하므로 `revertDeviceEmulation()`으로 원복 가능.\n */\nexport function applyDeviceEmulation(profile: DeviceProfile): void {\n if (typeof navigator === 'undefined' || typeof window === 'undefined') return;\n // 이전 override가 남아 있으면 먼저 원복하고 새로 적용 (preset 전환 시 누적 방지).\n revertDeviceEmulation();\n\n const saved: SavedDescriptor[] = [];\n override(navigator, 'userAgent', profile.userAgent, saved);\n override(navigator, 'platform', profile.navigatorPlatform, saved);\n override(window, 'devicePixelRatio', profile.devicePixelRatio, saved);\n if (typeof screen !== 'undefined') {\n override(screen, 'width', profile.screenWidth, saved);\n override(screen, 'height', profile.screenHeight, saved);\n }\n savedDescriptors = saved;\n}\n\n/** `applyDeviceEmulation`이 덮어쓴 속성을 원래 디스크립터로 되돌린다. */\nexport function revertDeviceEmulation(): void {\n if (!savedDescriptors) return;\n for (const { target, prop, descriptor } of savedDescriptors) {\n try {\n if (descriptor) {\n Object.defineProperty(target, prop, descriptor);\n } else {\n // 원래 own descriptor가 없었으면 (프로토타입 상속 getter) own override만 제거.\n delete (target as Record<string, unknown>)[prop];\n }\n } catch {\n // redefine/delete 실패는 조용히 무시 — best-effort 원복.\n }\n }\n savedDescriptors = null;\n}\n\n/**\n * Viewport state를 받아 preset이 active면 emulation 적용, 아니면 원복.\n * `applyViewport`가 매 viewport 변경마다 호출한다.\n */\nexport function syncDeviceEmulation(preset: ViewportPreset | null, landscape: boolean): void {\n if (!preset || preset.id === 'none' || preset.id === 'custom') {\n revertDeviceEmulation();\n return;\n }\n const profile = buildDeviceProfile(preset, aitState.state.appVersion, landscape);\n applyDeviceEmulation(profile);\n // SDK 계약값 정합: getPlatformOS()는 aitState.platform을 읽으므로 함께 끌고 간다.\n // 값이 실제로 바뀔 때만 update — applyViewport는 viewport subscriber 안에서 호출되므로\n // 무조건 update하면 재진입 루프가 된다 (syncSafeAreaFromViewport와 같은 idempotent 규약).\n if (aitState.state.platform !== profile.platform) {\n aitState.update({ platform: profile.platform });\n }\n}\n","/**\n * Viewport 시뮬레이션 유틸\n *\n * Panel에서 선택한 디바이스 프리셋을 `document.body`에 적용한다. 정적 CSS는\n * `panel/styles.ts`에 정의되어 있고 (Panel mount 시 head에 주입), 여기서는 프리셋별\n * 동적 값(width/height)만 별도 `<style>` 엘리먼트로 관리한다.\n *\n * body `padding-top`은 주입하지 않는다: 실기기에서 토스 native nav bar는 WebView viewport\n * 밖이라 콘텐츠는 top=0부터 시작한다(devtools#275).\n */\n\nimport { closeView } from '../mock/navigation/index.js';\nimport { aitState } from '../mock/state.js';\nimport type {\n AitNavBarType,\n AppOrientation,\n SafeAreaInsets,\n SafeAreaProvenance,\n ViewportOrientation,\n ViewportPreset,\n ViewportPresetId,\n ViewportState,\n} from '../mock/types.js';\nimport { revertDeviceEmulation, syncDeviceEmulation } from './device-emulation.js';\nimport { h } from './helpers.js';\n\nexport const VIEWPORT_STORAGE_KEY = '__ait_viewport';\n\n/** Custom width/height의 안전 상한 (CSS px). 4K + 여유. */\nexport const VIEWPORT_CUSTOM_MAX = 4096;\n\n/**\n * Apps in Toss host nav bar 높이 (CSS px), `partner` type 기준.\n *\n * iPhone 15 Pro on-device relay 실측값(devtools#275): 토스 native nav bar는 partner\n * WebView **viewport 밖**에 그려진다. `SafeAreaInsets.get().top`이 반환하는 54 px는 호스트\n * nav bar 높이에 대한 **정보용 값**이며, partner 앱이 이 값을 `padding-top`으로 적용하면\n * WebView가 이미 nav bar 아래에서 시작하므로 잉여 공간이 생긴다(double-count).\n *\n * 따라서 mock의 `computeSafeAreaInsets`는 partner portrait에서 top=0을 반환한다 —\n * WebView 좌표계에서 콘텐츠는 top=0부터 시작하고, 소비자가 SDK top을 padding으로 적용해도\n * 실기기와 같은 결과를 보도록 한다. `game` type은 nav bar가 WebView 안쪽 투명 오버레이라\n * 별도 실측 대상.\n *\n * landscape에서는 토스 앱이 partner nav bar를 숨기므로 SDK top=0 (2026-05-28 iPhone 15\n * Pro relay 실측 #232 확인).\n *\n * 이 상수는 프리셋의 `navBarHeight` 필드(host chrome 높이 메타데이터 — 프레임 렌더 등에\n * 사용)와 패널 UI 표시 목적으로만 유지된다.\n */\nexport const AIT_NAV_BAR_HEIGHT_PARTNER = 54;\n\nconst NONE_PRESET: ViewportPreset = {\n id: 'none',\n label: 'None (full window)',\n width: 0,\n height: 0,\n dpr: 1,\n notch: 'none',\n notchInset: 0,\n navBarHeight: 0,\n safeAreaBottom: 0,\n};\n\nconst CUSTOM_PRESET: ViewportPreset = {\n id: 'custom',\n label: 'Custom',\n width: 0,\n height: 0,\n dpr: 1,\n notch: 'none',\n notchInset: 0,\n navBarHeight: 0,\n safeAreaBottom: 0,\n};\n\n/** Shorthands used when building preset provenance entries. */\nconst EXTRAPOLATED: SafeAreaProvenance = { source: 'extrapolated' };\nconst PLACEHOLDER: SafeAreaProvenance = { source: 'placeholder' };\n\n/**\n * Device presets (2026). CSS viewport 크기는 실제 기기의 `window.innerWidth/innerHeight`.\n * iPhone 17 시리즈는 2025-09 출시. iPhone Air는 2026-04 출시.\n * Galaxy S26 시리즈는 2026-03-11 출시 — viewport 값은 phone-simulator.com에서 보고된\n * 측정치를 사용.\n *\n * safe-area 모델 (devtools#190 relay 실측 반영):\n * - `notchInset` = OS 노치/status bar inset. 기기별 물리값(landscape 측면 inset + 시각\n * 노치 오버레이용). iPhone 15 Pro 실측에서 `env(safe-area-inset-top)`은 0이었으므로 이\n * 값은 portrait SDK top에는 들어가지 않는다.\n * - `navBarHeight` = 토스 호스트 nav bar 높이. partner type portrait의 SDK `top`(실측 54).\n * 호스트 chrome이라 기기 무관 — 전 preset이 `AIT_NAV_BAR_HEIGHT_PARTNER` 공유.\n * - `safeAreaBottom` = home-indicator inset. 기기별(노치 iPhone 34, 홈버튼/Android 0).\n * iPhone 15 Pro 실측 bottom 34와 일치.\n *\n * 단, navBarHeight 54는 iOS partner에서만 실측됐다 — Android nav bar 높이와 game type\n * 미세 차이는 후속 실측 대상(현재는 같은 값을 잠정 적용).\n *\n * iPhone 17과 17 Pro는 CSS viewport / DPR / safe area가 동일 — 이는 의도이며 카피-페이스트\n * 실수가 아니다. Apple의 17 lineup은 base와 Pro의 web-relevant 스펙이 같다.\n *\n * safeAreaProvenance: 각 preset의 safe-area 값 신뢰도 출처.\n * - `measured` — relay 실기기 세션(measure_safe_area)으로 직접 확인한 값.\n * 현재 iPhone 15 Pro portrait iOS partner만 해당 (devtools#190).\n * - `extrapolated` — Apple 스펙/같은 시리즈 기기에서 유추한 값.\n * - `placeholder` — 연결 기기 없이 추정한 값. QA ground truth로 쓰지 말 것.\n * `measure_safe_area` MCP 툴로 relay 세션에서 `measured`로 승급 필요.\n */\nexport const VIEWPORT_PRESETS: ViewportPreset[] = [\n NONE_PRESET,\n // Apple\n {\n id: 'iphone-se-3',\n label: 'iPhone SE (3rd gen)',\n width: 375,\n height: 667,\n dpr: 2,\n notch: 'none',\n notchInset: 20,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n // SE 3는 홈버튼 기기 — OS 노치 없고 home indicator도 없음. bottom=0은 확정.\n // navBarHeight는 iOS partner 실측(54)에서 기기 무관 상수. extrapolated.\n safeAreaProvenance: EXTRAPOLATED,\n },\n {\n id: 'iphone-15-pro',\n label: 'iPhone 15 Pro',\n width: 393,\n // devtools#275 실측: partner type innerHeight=754. native chrome(iOS status bar + 토스\n // nav bar = 98 pt)이 WebView 밖에 reserve됨 — WebView는 754 pt. screenHeight=852는\n // window.screen.height(전체 물리 화면 CSS px)를 기기 emulation에 사용.\n height: 754,\n screenHeight: 852,\n dpr: 3,\n notch: 'dynamic-island',\n notchInset: 59,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n safeAreaBottomLandscape: 20,\n // devtools#190/#275 portrait 실측: innerH=754(partner), bottom=34.\n // devtools#198/#232 landscape 실측(2026-05-28): bottom=20, left=right=59(양쪽 대칭).\n safeAreaProvenance: {\n source: 'measured',\n device: 'iPhone 15 Pro',\n date: '2026-05-28',\n orientations: ['portrait', 'landscape'],\n },\n },\n {\n id: 'iphone-16e',\n label: 'iPhone 16e',\n width: 390,\n height: 844,\n dpr: 3,\n notch: 'notch',\n notchInset: 47,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n // 16e는 notch 기기. bottom 34는 Apple 스펙에서 유추. 실측 미진행.\n safeAreaProvenance: EXTRAPOLATED,\n },\n {\n id: 'iphone-17',\n label: 'iPhone 17',\n width: 402,\n height: 874,\n dpr: 3,\n notch: 'dynamic-island',\n notchInset: 59,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n // 17 시리즈 Dynamic Island — bottom 34 Apple 스펙 유추. 실측 미진행.\n safeAreaProvenance: EXTRAPOLATED,\n },\n {\n id: 'iphone-air',\n label: 'iPhone Air',\n width: 420,\n height: 912,\n dpr: 3,\n notch: 'dynamic-island',\n notchInset: 59,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n // iPhone Air (2026-04 출시) — Dynamic Island 유추. 실측 미진행.\n safeAreaProvenance: EXTRAPOLATED,\n },\n {\n id: 'iphone-17-pro',\n label: 'iPhone 17 Pro',\n width: 402,\n height: 874,\n dpr: 3,\n notch: 'dynamic-island',\n notchInset: 59,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n // 17 Pro — 17와 web-relevant 스펙 동일. 유추.\n safeAreaProvenance: EXTRAPOLATED,\n },\n {\n id: 'iphone-17-pro-max',\n label: 'iPhone 17 Pro Max',\n width: 440,\n height: 956,\n dpr: 3,\n notch: 'dynamic-island',\n notchInset: 62,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 34,\n // Pro Max — notchInset 62는 Apple 스펙 유추. 실측 미진행.\n safeAreaProvenance: EXTRAPOLATED,\n },\n // Samsung\n //\n // Galaxy S26 series shipped 2026-03-11. Viewport widths/heights below come\n // from phone-simulator.com's measured values. safe-area top/bottom remain\n // S25-derived placeholders because we have no toss host live-measure yet —\n // do not treat them as ground truth for QA.\n {\n id: 'galaxy-s26',\n label: 'Galaxy S26',\n width: 360,\n height: 773,\n dpr: 3,\n notch: 'punch-hole-center',\n notchInset: 32,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n // Android safe-area는 relay 실측 없음. placeholder.\n safeAreaProvenance: PLACEHOLDER,\n },\n {\n id: 'galaxy-s26-plus',\n label: 'Galaxy S26+',\n width: 480,\n height: 1040,\n dpr: 3,\n notch: 'punch-hole-center',\n notchInset: 32,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n safeAreaProvenance: PLACEHOLDER,\n },\n {\n id: 'galaxy-s26-ultra',\n label: 'Galaxy S26 Ultra',\n width: 480,\n height: 1040,\n dpr: 3,\n notch: 'punch-hole-center',\n notchInset: 40,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n safeAreaProvenance: PLACEHOLDER,\n },\n {\n id: 'galaxy-z-flip7',\n label: 'Galaxy Z Flip7',\n width: 412,\n height: 990,\n dpr: 3,\n notch: 'punch-hole-center',\n notchInset: 36,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n safeAreaProvenance: PLACEHOLDER,\n },\n {\n id: 'galaxy-z-fold7-folded',\n label: 'Galaxy Z Fold7 (folded)',\n width: 384,\n height: 870,\n dpr: 3,\n notch: 'punch-hole-center',\n notchInset: 32,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n safeAreaProvenance: PLACEHOLDER,\n },\n {\n id: 'galaxy-z-fold7-unfolded',\n label: 'Galaxy Z Fold7 (unfolded)',\n width: 768,\n height: 884,\n dpr: 2.625,\n notch: 'punch-hole-center',\n notchInset: 32,\n navBarHeight: AIT_NAV_BAR_HEIGHT_PARTNER,\n safeAreaBottom: 0,\n safeAreaProvenance: PLACEHOLDER,\n },\n CUSTOM_PRESET,\n];\n\nexport function getPreset(id: ViewportPresetId): ViewportPreset {\n return VIEWPORT_PRESETS.find((p) => p.id === id) ?? NONE_PRESET;\n}\n\n/**\n * 실제로 화면에 표시될 orientation을 결정한다.\n *\n * - Panel `orientation === 'auto'`: 앱이 마지막으로 SDK로 요청한 값\n * (`appOrientation`)을 따른다. 호출 전이면 portrait.\n * - Panel `orientation === 'portrait' | 'landscape'`: Panel 값이 우선.\n */\nexport function effectiveOrientation(state: ViewportState): 'portrait' | 'landscape' {\n if (state.orientation === 'auto') {\n return state.appOrientation ?? 'portrait';\n }\n return state.orientation;\n}\n\n/**\n * 선택된 뷰포트의 실제 width/height를 계산한다.\n * preset === 'custom'이면 customWidth/customHeight, 그 외에는 preset의 값.\n * effective orientation이 landscape이면 width/height를 swap한다.\n */\nexport function resolveViewportSize(state: ViewportState): { width: number; height: number } {\n if (state.preset === 'none') return { width: 0, height: 0 };\n const base =\n state.preset === 'custom'\n ? { width: state.customWidth, height: state.customHeight }\n : getPreset(state.preset);\n return effectiveOrientation(state) === 'landscape'\n ? { width: base.height, height: base.width }\n : { width: base.width, height: base.height };\n}\n\n/**\n * 프리셋 + orientation + nav bar 상태로부터 SDK `SafeAreaInsets.get()`이 반환할 insets를\n * 계산한다. iPhone 15 Pro on-device relay 실측(devtools#190, #198, #232, #275)에 맞춘 모델:\n *\n * - **Portrait top = 0** (partner/game 모두). 실측(devtools#275)에서 토스 native nav bar는\n * partner WebView **viewport 밖**에 그려진다. SDK가 반환하는 `top=54`는 호스트 nav bar\n * 높이에 대한 정보용 값이고, WebView 좌표계에서 콘텐츠는 top=0부터 시작한다. 소비자가\n * 이 값을 `padding-top`으로 적용하면 실기기에서 잉여 공간이 생긴다(double-count).\n * mock은 top=0을 반환해 소비자 코드가 실기기와 같은 결과를 내도록 한다.\n * `game` type 측정은 아직 미진행이지만 동일하게 top=0을 반환한다(추후 실측으로 갱신).\n * - **Bottom = `safeAreaBottom`** (portrait home-indicator, 실측 34).\n * landscape는 `safeAreaBottomLandscape`가 정의돼 있으면 그 값을 사용한다\n * (iPhone 15 Pro landscape 실측 20 — portrait 34와 다름).\n * - **Landscape iPhone(notch/Dynamic Island)**: CSS env()와 SDK SafeAreaInsets 모두\n * `left = right = notchInset`(양쪽 대칭)을 반환한다. 물리적 노치는 한쪽으로 가지만\n * OS가 양쪽 모두에 같은 inset을 부여하므로 landscapeSide mental model은 틀렸다\n * (2026-05-28 iPhone 15 Pro relay 실측 #198/#232: left=right=59). top=0(landscape에서\n * 토스 앱이 partner nav bar를 숨김, #232 실측 확인).\n * - **Android punch-hole(status bar)**: landscape에서도 top에 status bar(`notchInset`)가\n * 유지된다.\n */\nexport function computeSafeAreaInsets(preset: ViewportPreset, landscape: boolean): SafeAreaInsets {\n if (preset.id === 'none' || preset.id === 'custom') {\n return { top: 0, bottom: 0, left: 0, right: 0 };\n }\n // Portrait top=0: 토스 native nav bar는 partner WebView viewport 밖 — SDK top은 정보용이라\n // padding 대상이 아님(devtools#275). 소비자가 SDK top을 padding으로 적용해도 실기기와\n // 같은 결과를 보도록 top=0을 반환한다. game type 실측 후 분기 필요 시 여기에 추가.\n if (!landscape) {\n return { top: 0, bottom: preset.safeAreaBottom, left: 0, right: 0 };\n }\n // landscape bottom: 별도 실측값이 있으면 우선 사용 (iPhone 15 Pro portrait 34 vs landscape 20).\n const landscapeBottom =\n preset.safeAreaBottomLandscape !== undefined\n ? preset.safeAreaBottomLandscape\n : preset.safeAreaBottom;\n if (preset.notch === 'notch' || preset.notch === 'dynamic-island') {\n // CSS env()와 SDK SafeAreaInsets 모두 양쪽 대칭으로 반환한다 (relay 실측 #198/#232).\n // top=0: landscape에서 토스 앱이 partner nav bar를 숨김 (#232 실측).\n return {\n top: 0,\n bottom: landscapeBottom,\n left: preset.notchInset,\n right: preset.notchInset,\n };\n }\n // Android status bar stays on the top edge even in landscape.\n return {\n top: preset.notchInset,\n bottom: landscapeBottom,\n left: 0,\n right: 0,\n };\n}\n\n/** viewport preset 또는 orientation이 바뀌면 safe-area insets도 자동 갱신한다. */\nfunction syncSafeAreaFromViewport(state: ViewportState): void {\n if (state.preset === 'none' || state.preset === 'custom') return;\n const preset = getPreset(state.preset);\n const next = computeSafeAreaInsets(preset, effectiveOrientation(state) === 'landscape');\n const current = aitState.state.safeAreaInsets;\n if (\n current.top === next.top &&\n current.bottom === next.bottom &&\n current.left === next.left &&\n current.right === next.right\n ) {\n return;\n }\n aitState.update({ safeAreaInsets: next });\n}\n\nconst STYLE_ELEMENT_ID = '__ait-viewport-style';\nconst NOTCH_ELEMENT_ID = '__ait-viewport-notch';\nconst HOME_INDICATOR_ID = '__ait-viewport-home-indicator';\nconst NAV_BAR_ELEMENT_ID = '__ait-viewport-navbar';\n\nlet bodyScrollHintEmitted = false;\n\nfunction ensureStyleElement(): HTMLStyleElement | null {\n if (typeof document === 'undefined') return null;\n let el = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n if (!el) {\n el = document.createElement('style');\n el.id = STYLE_ELEMENT_ID;\n document.head.appendChild(el);\n }\n return el;\n}\n\nfunction removeById(id: string): void {\n const el = document.getElementById(id);\n if (el) el.remove();\n}\n\nfunction removeNotchElement(): void {\n removeById(NOTCH_ELEMENT_ID);\n}\n\nfunction removeHomeIndicator(): void {\n removeById(HOME_INDICATOR_ID);\n}\n\nfunction removeNavBarElement(): void {\n removeById(NAV_BAR_ELEMENT_ID);\n}\n\n/**\n * Apps in Toss host nav bar 렌더. OS status bar(notch) 아래에 쌓인다.\n *\n * 변형(SDK `webViewProps.type`과 의미 일치):\n * - `partner` (기본): 흰 배경, 좌측 뒤로가기(‹), 앱 아이콘 + 이름(`brand.displayName`),\n * 우측 `⋯` + 구분선 + `×`.\n * - `game`: 투명 배경, 게임 캔버스를 가리지 않도록 우측 `⋯` + 구분선 + `×`만.\n *\n * 이 오버레이는 **시각 참고용 frame 장식**이다. 실기기에서 토스 native nav bar는 WebView\n * viewport 밖에 그려지므로(devtools#275), mock의 nav bar 오버레이가 콘텐츠 위에 overlap\n * 되는 것이 실제 동작과 일치한다 — body에 `padding-top`을 주입하지 않는다.\n * 시각 notch 오버레이는 body 밖 위쪽에 따로 그린다(`renderNotchOverlay`) — body 안이 아니다.\n *\n * 뒤로가기 버튼은 `__ait:backEvent`를 트리거하고, X 버튼은 `closeView()`를 호출한다.\n * 실제 SDK 이벤트 플러밍을 한 곳에서 검증할 수 있다.\n */\nfunction renderNavBar(displayName: string, type: AitNavBarType): void {\n removeNavBarElement();\n const el = h('div', {\n id: NAV_BAR_ELEMENT_ID,\n className: `ait-navbar ait-navbar-${type}`,\n 'aria-hidden': 'true',\n });\n\n const moreBtn = h('button', {\n className: 'ait-navbar-btn',\n type: 'button',\n 'aria-label': 'More',\n });\n moreBtn.textContent = '⋯';\n\n const closeBtn = h('button', {\n className: 'ait-navbar-btn',\n type: 'button',\n 'aria-label': 'Close',\n });\n closeBtn.textContent = '×';\n closeBtn.addEventListener('click', () => {\n closeView().catch((err) => console.error('[@ait-co/devtools] navbar close failed:', err));\n });\n\n const actions = h(\n 'div',\n { className: 'ait-navbar-actions' },\n moreBtn,\n h('span', { className: 'ait-navbar-divider' }),\n closeBtn,\n );\n\n if (type === 'game') {\n // Game: 투명 배경, back/title 없음, 우측 actions만 (실제 토스 host 동작과 일치).\n el.append(actions);\n } else {\n const backBtn = h('button', {\n className: 'ait-navbar-btn ait-navbar-back',\n type: 'button',\n 'aria-label': 'Back',\n });\n backBtn.textContent = '‹';\n backBtn.addEventListener('click', () => {\n aitState.trigger('backEvent');\n });\n\n const nameSpan = h('span', { className: 'ait-navbar-name' });\n nameSpan.textContent = displayName;\n\n el.append(\n backBtn,\n h(\n 'div',\n { className: 'ait-navbar-title' },\n h('span', { className: 'ait-navbar-icon' }),\n nameSpan,\n ),\n actions,\n );\n }\n\n document.body.appendChild(el);\n}\n\n/**\n * 현재 preset의 notch/Dynamic Island/punch-hole을 body 상단에 시각적으로 렌더한다.\n * landscape 시에는 노치가 한쪽 변에 있는 것이 실제 기기 동작이지만, 시뮬레이터에서는\n * landscape에서 오버레이를 그리지 않는다 (safeAreaInsets의 left/right로 이미 반영).\n */\nfunction renderNotchOverlay(preset: ViewportPreset): void {\n removeNotchElement();\n if (preset.notch === 'none') return;\n\n const variant =\n preset.notch === 'dynamic-island'\n ? 'ait-notch-dynamic-island'\n : preset.notch === 'notch'\n ? 'ait-notch-pill'\n : 'ait-notch-punch-hole';\n\n const notch = h('div', {\n id: NOTCH_ELEMENT_ID,\n className: `ait-notch ${variant}`,\n 'aria-hidden': 'true',\n });\n document.body.appendChild(notch);\n}\n\n/** brand 이름만 바뀐 경우 nav bar 전체를 다시 만들지 않고 텍스트 노드만 교체한다. */\nfunction refreshNavBarBrand(displayName: string): void {\n const name = document.querySelector(`#${NAV_BAR_ELEMENT_ID} .ait-navbar-name`);\n if (name) name.textContent = displayName;\n}\n\nfunction renderHomeIndicator(): void {\n removeHomeIndicator();\n const el = h('div', {\n id: HOME_INDICATOR_ID,\n className: 'ait-home-indicator',\n 'aria-hidden': 'true',\n });\n document.body.appendChild(el);\n}\n\n/**\n * 모든 viewport DOM mutation을 원복하고 aitState 구독도 해제한다.\n * 외부 consumer가 패널을 동적으로 제거할 때 호출. 호출 후에는 aitState 변경이\n * DOM에 반영되지 않으므로 안전하게 panel을 떼어낼 수 있다.\n */\nexport function disposeViewport(): void {\n if (typeof document === 'undefined') return;\n if (viewportUnsubscribe) viewportUnsubscribe();\n const html = document.documentElement;\n html.classList.remove('ait-viewport-active');\n html.classList.remove('ait-viewport-framed');\n removeById(STYLE_ELEMENT_ID);\n removeNotchElement();\n removeHomeIndicator();\n removeNavBarElement();\n revertDeviceEmulation();\n bodyScrollHintEmitted = false;\n}\n\n/**\n * DOM에 뷰포트 제약을 적용한다.\n * - `html.ait-viewport-active` 클래스로 정적 CSS(styles.ts) 활성화\n * - body의 width/height는 preset 값으로, navbar top offset은 notchInset으로 인라인 주입\n */\nexport function applyViewport(state: ViewportState): void {\n if (typeof document === 'undefined') return;\n const html = document.documentElement;\n const style = ensureStyleElement();\n if (!style) return;\n\n const size = resolveViewportSize(state);\n\n if (state.preset === 'none' || size.width === 0 || size.height === 0) {\n html.classList.remove('ait-viewport-active');\n html.classList.remove('ait-viewport-framed');\n style.textContent = '';\n removeNotchElement();\n removeHomeIndicator();\n removeNavBarElement();\n syncDeviceEmulation(null, false);\n return;\n }\n\n if (!bodyScrollHintEmitted) {\n bodyScrollHintEmitted = true;\n console.info(\n '[@ait-co/devtools] Viewport simulation active — scroll happens on body, not window. ' +\n 'See README \"Known limitations\" for details.',\n );\n }\n\n html.classList.add('ait-viewport-active');\n html.classList.toggle('ait-viewport-framed', state.frame);\n\n const preset = state.preset === 'custom' ? null : getPreset(state.preset);\n const landscape = effectiveOrientation(state) === 'landscape';\n\n // 기기 preset이면 UA/DPR/screen/platform을 그 기기와 정합 (custom은 치수만 강제).\n syncDeviceEmulation(preset, landscape);\n\n // Dynamic per-preset values only — static rules live in styles.ts.\n // body padding-top을 주입하지 않는다: 실기기에서 토스 native nav bar는 WebView viewport\n // 밖에 그려지므로 콘텐츠는 top=0부터 시작한다(devtools#275). mock의 nav bar 오버레이는\n // 시각 참고용 — body는 padding 없이 top=0에서 콘텐츠를 렌더한다.\n style.textContent = /* css */ `\n html.ait-viewport-active body {\n width: ${size.width}px;\n max-width: ${size.width}px;\n min-height: ${size.height}px;\n max-height: ${size.height}px;\n }\n `;\n\n // Notch / home indicator / nav bar are gated in JS so document.getElementById\n // becomes a reliable \"is overlay present\" predicate.\n if (preset && state.frame && !landscape) renderNotchOverlay(preset);\n else removeNotchElement();\n\n if (preset && state.frame && !landscape && preset.safeAreaBottom > 0) renderHomeIndicator();\n else removeHomeIndicator();\n\n if (preset && state.aitNavBar && !landscape) {\n renderNavBar(aitState.state.brand.displayName, state.aitNavBarType);\n } else {\n removeNavBarElement();\n }\n}\n\nfunction isViewportPresetId(v: unknown): v is ViewportPresetId {\n return typeof v === 'string' && VIEWPORT_PRESETS.some((p) => p.id === v);\n}\n\nfunction isViewportOrientation(v: unknown): v is ViewportOrientation {\n return v === 'auto' || v === 'portrait' || v === 'landscape';\n}\n\nfunction isAppOrientation(v: unknown): v is AppOrientation {\n return v === null || v === 'portrait' || v === 'landscape';\n}\n\n/** 1 이상의 정수 + VIEWPORT_CUSTOM_MAX 이하인지 검사. sessionStorage 보호용. */\nfunction isValidCustomDimension(v: unknown): v is number {\n return typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= VIEWPORT_CUSTOM_MAX;\n}\n\n/** Custom 입력에서 사용. 잘린 정수 + 클램프된 안전한 값 또는 null 반환. */\nexport function clampCustomDimension(raw: number): number | null {\n if (!Number.isFinite(raw)) return null;\n const n = Math.floor(raw);\n if (n < 1) return null;\n return Math.min(n, VIEWPORT_CUSTOM_MAX);\n}\n\n/**\n * sessionStorage에 저장된 뷰포트 상태를 읽어서 현재 state에 merge한다.\n * 값이 없거나 파싱 실패 시 no-op.\n */\nexport function loadViewportFromStorage(): Partial<ViewportState> | null {\n if (typeof sessionStorage === 'undefined') return null;\n const raw = sessionStorage.getItem(VIEWPORT_STORAGE_KEY);\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return null;\n const obj = parsed as Record<string, unknown>;\n const next: Partial<ViewportState> = {};\n if (isViewportPresetId(obj.preset)) next.preset = obj.preset;\n if (isViewportOrientation(obj.orientation)) next.orientation = obj.orientation;\n if (isAppOrientation(obj.appOrientation)) next.appOrientation = obj.appOrientation;\n // landscapeSide는 deprecated — sessionStorage에 저장된 기존 값은 무시한다.\n if (isValidCustomDimension(obj.customWidth)) next.customWidth = obj.customWidth;\n if (isValidCustomDimension(obj.customHeight)) next.customHeight = obj.customHeight;\n if (typeof obj.frame === 'boolean') next.frame = obj.frame;\n if (typeof obj.aitNavBar === 'boolean') next.aitNavBar = obj.aitNavBar;\n if (obj.aitNavBarType === 'partner' || obj.aitNavBarType === 'game') {\n next.aitNavBarType = obj.aitNavBarType;\n }\n return next;\n } catch {\n return null;\n }\n}\n\nexport function saveViewportToStorage(state: ViewportState): void {\n if (typeof sessionStorage === 'undefined') return;\n try {\n sessionStorage.setItem(VIEWPORT_STORAGE_KEY, JSON.stringify(state));\n } catch {\n /* ignore quota errors */\n }\n}\n\nlet viewportInitialized = false;\nlet viewportUnsubscribe: (() => void) | null = null;\n\n/**\n * Panel mount 시 호출. sessionStorage 복원 → aitState에 반영 → DOM 적용.\n * aitState 변경을 구독해서 DOM / storage / safe-area insets를 자동 동기화한다.\n *\n * Idempotent: 두 번째 호출은 기존 unsubscribe를 그대로 반환한다 (HMR / 재mount 안전).\n * 테스트는 반환된 unsubscribe를 afterEach에서 호출해 cleanup해야 한다.\n */\nexport function initViewport(): () => void {\n if (typeof window === 'undefined') return () => {};\n if (viewportInitialized && viewportUnsubscribe) return viewportUnsubscribe;\n\n const restored = loadViewportFromStorage();\n if (restored) {\n aitState.patch('viewport', restored);\n }\n applyViewport(aitState.state.viewport);\n syncSafeAreaFromViewport(aitState.state.viewport);\n\n let lastViewportJson = JSON.stringify(aitState.state.viewport);\n let lastBrandName = aitState.state.brand.displayName;\n\n const unsubscribeFn = aitState.subscribe(() => {\n const vp = aitState.state.viewport;\n const brandName = aitState.state.brand.displayName;\n const json = JSON.stringify(vp);\n\n const viewportChanged = json !== lastViewportJson;\n const brandChanged = brandName !== lastBrandName;\n\n if (!viewportChanged && !brandChanged) return;\n lastViewportJson = json;\n lastBrandName = brandName;\n\n if (viewportChanged) {\n applyViewport(vp);\n saveViewportToStorage(vp);\n syncSafeAreaFromViewport(vp);\n } else {\n // Brand-only change: refresh just the nav bar text instead of rebuilding all overlays.\n refreshNavBarBrand(brandName);\n }\n });\n\n viewportInitialized = true;\n viewportUnsubscribe = () => {\n unsubscribeFn();\n viewportInitialized = false;\n viewportUnsubscribe = null;\n };\n return viewportUnsubscribe;\n}\n\n/**\n * @internal Test helper. Production code never touches this — use `disposeViewport()`.\n */\nexport function _resetViewportInit(): void {\n if (viewportUnsubscribe) viewportUnsubscribe();\n viewportInitialized = false;\n viewportUnsubscribe = null;\n}\n","import { t } from '../../i18n/index.js';\nimport { aitState } from '../../mock/state.js';\nimport type {\n AitNavBarType,\n SafeAreaProvenance,\n ViewportOrientation,\n ViewportPresetId,\n} from '../../mock/types.js';\nimport { h, monitoringNotice } from '../helpers.js';\nimport {\n clampCustomDimension,\n computeSafeAreaInsets,\n effectiveOrientation,\n getPreset,\n resolveViewportSize,\n VIEWPORT_PRESETS,\n} from '../viewport.js';\n\n/**\n * Renders a small inline provenance badge for safe-area values.\n * - `measured` — no badge (confirmed value)\n * - `extrapolated` — \"(추정치)\" in muted gray\n * - `placeholder` — \"(미측정)\" in amber\n */\nfunction provenanceBadge(provenance: SafeAreaProvenance | undefined): HTMLElement | null {\n if (!provenance || provenance.source === 'measured') return null;\n const text = provenance.source === 'placeholder' ? '(미측정)' : '(추정치)';\n const color = provenance.source === 'placeholder' ? '#b45309' : '#888';\n const badge = h('span', {\n className: 'ait-provenance-badge',\n title:\n provenance.source === 'placeholder'\n ? 'safe-area 값이 미실측 추정치입니다. relay 세션에서 measure_safe_area로 실측 후 승급하세요.'\n : 'safe-area 값이 기기 스펙에서 유추한 추정치입니다. relay 세션에서 measure_safe_area로 확인하세요.',\n });\n badge.textContent = text;\n badge.style.cssText = `font-size:10px;color:${color};margin-left:4px`;\n return badge;\n}\n\nexport function renderViewportTab(): HTMLElement {\n const s = aitState.state;\n const vp = s.viewport;\n const disabled = !s.panelEditable;\n const container = h('div');\n\n if (disabled) container.appendChild(monitoringNotice());\n\n // --- Preset selector ---\n const presetSelect = h('select', { className: 'ait-select' });\n if (disabled) presetSelect.disabled = true;\n for (const preset of VIEWPORT_PRESETS) {\n const label =\n preset.id === 'none' || preset.id === 'custom'\n ? preset.label\n : `${preset.label} (${preset.width}×${preset.height})`;\n const option = h('option', { value: preset.id }, label);\n if (preset.id === vp.preset) option.selected = true;\n presetSelect.appendChild(option);\n }\n presetSelect.addEventListener('change', () => {\n const id = presetSelect.value as ViewportPresetId;\n const patch: Partial<typeof vp> = { preset: id };\n // custom으로 전환할 때 현재 선택값을 custom 필드의 시드로 복사해둔다.\n if (id === 'custom') {\n const current = getPreset(vp.preset);\n if (current.width > 0) patch.customWidth = current.width;\n if (current.height > 0) patch.customHeight = current.height;\n }\n aitState.patch('viewport', patch);\n });\n\n // --- Orientation toggle ---\n const orientationSelect = h('select', { className: 'ait-select' });\n if (disabled) orientationSelect.disabled = true;\n for (const opt of ['auto', 'portrait', 'landscape'] as ViewportOrientation[]) {\n const option = h('option', { value: opt }, opt);\n if (opt === vp.orientation) option.selected = true;\n orientationSelect.appendChild(option);\n }\n orientationSelect.addEventListener('change', () => {\n aitState.patch('viewport', {\n orientation: orientationSelect.value as ViewportOrientation,\n });\n });\n\n // --- Custom width/height inputs (custom 모드에서만 활성화) ---\n const customRow = h('div', { className: 'ait-section' });\n if (vp.preset === 'custom') {\n const widthInput = h('input', {\n className: 'ait-input',\n type: 'number',\n min: '1',\n value: String(vp.customWidth),\n }) as HTMLInputElement;\n const heightInput = h('input', {\n className: 'ait-input',\n type: 'number',\n min: '1',\n value: String(vp.customHeight),\n }) as HTMLInputElement;\n if (disabled) {\n widthInput.disabled = true;\n heightInput.disabled = true;\n }\n widthInput.addEventListener('change', () => {\n const clamped = clampCustomDimension(Number(widthInput.value));\n if (clamped !== null) {\n aitState.patch('viewport', { customWidth: clamped });\n widthInput.value = String(clamped);\n }\n });\n heightInput.addEventListener('change', () => {\n const clamped = clampCustomDimension(Number(heightInput.value));\n if (clamped !== null) {\n aitState.patch('viewport', { customHeight: clamped });\n heightInput.value = String(clamped);\n }\n });\n customRow.append(\n h('div', { className: 'ait-section-title' }, t('viewport.section.custom')),\n h('div', { className: 'ait-row' }, h('label', {}, t('viewport.row.width')), widthInput),\n h('div', { className: 'ait-row' }, h('label', {}, t('viewport.row.height')), heightInput),\n );\n }\n\n // --- Frame decoration toggle ---\n const frameCheckbox = h('input', { type: 'checkbox' }) as HTMLInputElement;\n frameCheckbox.checked = vp.frame;\n if (disabled) frameCheckbox.disabled = true;\n frameCheckbox.addEventListener('change', () => {\n aitState.patch('viewport', { frame: frameCheckbox.checked });\n });\n\n // --- Apps in Toss nav bar toggle ---\n const navBarCheckbox = h('input', { type: 'checkbox' }) as HTMLInputElement;\n navBarCheckbox.checked = vp.aitNavBar;\n if (disabled) navBarCheckbox.disabled = true;\n navBarCheckbox.addEventListener('change', () => {\n aitState.patch('viewport', { aitNavBar: navBarCheckbox.checked });\n });\n\n // --- Nav bar type (partner / game) — only meaningful when aitNavBar is on ---\n const navBarTypeSelect = h('select', { className: 'ait-select' });\n if (disabled || !vp.aitNavBar) navBarTypeSelect.disabled = true;\n for (const opt of ['partner', 'game'] as AitNavBarType[]) {\n const option = h('option', { value: opt }, opt);\n if (opt === vp.aitNavBarType) option.selected = true;\n navBarTypeSelect.appendChild(option);\n }\n navBarTypeSelect.addEventListener('change', () => {\n aitState.patch('viewport', { aitNavBarType: navBarTypeSelect.value as AitNavBarType });\n });\n\n // --- Status panel: applied size + HiDPI + safe area ---\n const size = resolveViewportSize(vp);\n const statusEl = h('div', { className: 'ait-section' });\n\n if (vp.preset === 'none' || size.width === 0) {\n statusEl.appendChild(\n h('div', { style: 'color:#888;font-size:11px' }, t('viewport.status.noConstraint')),\n );\n } else {\n const preset = vp.preset === 'custom' ? null : getPreset(vp.preset);\n const effOrient = effectiveOrientation(vp);\n const landscape = effOrient === 'landscape';\n const rows: Array<HTMLElement> = [];\n\n // Viewport: CSS @DPR | physical\n const dpr = preset?.dpr ?? 1;\n const physW = Math.round(size.width * dpr);\n const physH = Math.round(size.height * dpr);\n const orientDisplay =\n vp.orientation === 'auto'\n ? t('viewport.orientation.autoSuffix', { orient: effOrient })\n : effOrient;\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, t('viewport.status.cssPhysical')),\n h(\n 'span',\n { className: 'ait-status-value' },\n `${size.width}×${size.height}@${dpr}x | ${physW}×${physH} ${orientDisplay}`,\n ),\n ),\n );\n\n if (preset) {\n const insets = computeSafeAreaInsets(preset, landscape);\n const safeAreaValueEl = h(\n 'span',\n { className: 'ait-status-value' },\n `T${insets.top} R${insets.right} B${insets.bottom} L${insets.left}`,\n );\n const badge = provenanceBadge(preset.safeAreaProvenance);\n if (badge) safeAreaValueEl.appendChild(badge);\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, t('viewport.status.safeArea')),\n safeAreaValueEl,\n ),\n );\n }\n\n if (vp.aitNavBar && !landscape) {\n // nav bar 높이를 status 패널에 정보용으로 표시한다. 이 값은 실기기에서 WebView 바깥의\n // host nav bar 높이(devtools#275)이며 body padding에는 사용하지 않는다.\n const navBarTop = vp.aitNavBarType === 'partner' ? (preset?.navBarHeight ?? 0) : 0;\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, t('viewport.status.aitNavBar')),\n h(\n 'span',\n { className: 'ait-status-value' },\n t('viewport.status.aitNavBarValue', {\n height: navBarTop,\n type: vp.aitNavBarType,\n }),\n ),\n ),\n );\n }\n\n for (const row of rows) statusEl.appendChild(row);\n }\n\n // --- Compose ---\n const deviceSection = h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('viewport.section.device')),\n h('div', { className: 'ait-row' }, h('label', {}, t('viewport.row.preset')), presetSelect),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('viewport.row.orientation')),\n orientationSelect,\n ),\n );\n\n // Landscape + notch 기기: iOS landscape에서 CSS env()와 SDK SafeAreaInsets 모두\n // left=right=notchInset(양쪽 대칭)을 반환하므로 side select가 더 이상 필요 없다.\n // (2026-05-28 iPhone 15 Pro relay 실측 #198/#232)\n\n container.append(\n deviceSection,\n customRow,\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, t('viewport.section.appearance')),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('viewport.row.showFrame')),\n frameCheckbox,\n ),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('viewport.row.showAitNavBar')),\n navBarCheckbox,\n ),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, t('viewport.row.navBarType')),\n navBarTypeSelect,\n ),\n ),\n statusEl,\n );\n\n return container;\n}\n","import { type StringKey, t } from '../../i18n/index.js';\nimport { renderAdsTab } from './ads.js';\nimport { renderAnalyticsTab } from './analytics.js';\nimport { renderDeviceTab } from './device.js';\nimport { renderEnvironmentTab } from './environment.js';\nimport { renderEventsTab } from './events.js';\nimport { renderIapTab } from './iap.js';\nimport { renderLocationTab } from './location.js';\nimport { renderNotificationsTab } from './notifications.js';\nimport { renderPermissionsTab } from './permissions.js';\nimport { renderPresetsTab } from './presets.js';\nimport { renderStorageTab } from './storage.js';\nimport { renderViewportTab } from './viewport.js';\n\nexport type TabId =\n | 'env'\n | 'presets'\n | 'permissions'\n | 'notifications'\n | 'location'\n | 'iap'\n | 'ads'\n | 'events'\n | 'analytics'\n | 'storage'\n | 'device'\n | 'viewport';\n\n// Tab ordering + label-key map. `label` is re-resolved through `t()` at each\n// mount so locale changes pick up the new translation.\nconst TAB_DEFS: Array<{ id: TabId; labelKey: StringKey }> = [\n { id: 'env', labelKey: 'panel.tab.env' },\n { id: 'presets', labelKey: 'panel.tab.presets' },\n { id: 'viewport', labelKey: 'panel.tab.viewport' },\n { id: 'permissions', labelKey: 'panel.tab.permissions' },\n { id: 'notifications', labelKey: 'panel.tab.notifications' },\n { id: 'location', labelKey: 'panel.tab.location' },\n { id: 'device', labelKey: 'panel.tab.device' },\n { id: 'iap', labelKey: 'panel.tab.iap' },\n { id: 'ads', labelKey: 'panel.tab.ads' },\n { id: 'events', labelKey: 'panel.tab.events' },\n { id: 'analytics', labelKey: 'panel.tab.analytics' },\n { id: 'storage', labelKey: 'panel.tab.storage' },\n];\n\nexport function getTabs(): Array<{ id: TabId; label: string }> {\n return TAB_DEFS.map((def) => ({ id: def.id, label: t(def.labelKey) }));\n}\n\n// storage tab receives refreshPanel because its clear button modifies localStorage\n// directly (not aitState), so it must trigger a re-render explicitly.\n// presets tab needs refreshPanel for the same reason (user preset CRUD touches localStorage).\n// device tab uses setDeviceRefreshPanel() for prompt-related local state (pendingPrompt);\n// its aitState mutations are auto-refreshed via the subscription in index.ts.\n// Other tabs only modify aitState or use input controls that reflect changes immediately.\nexport function createTabRenderers(refreshPanel: () => void): Record<TabId, () => HTMLElement> {\n return {\n env: renderEnvironmentTab,\n presets: () => renderPresetsTab(refreshPanel),\n permissions: renderPermissionsTab,\n notifications: renderNotificationsTab,\n location: renderLocationTab,\n device: renderDeviceTab,\n viewport: renderViewportTab,\n iap: renderIapTab,\n ads: renderAdsTab,\n events: renderEventsTab,\n analytics: renderAnalyticsTab,\n storage: () => renderStorageTab(refreshPanel),\n };\n}\n","/**\n * @ait-co/devtools Floating Panel\n *\n * import 하면 자동으로 페이지에 DevTools 패널을 마운트한다.\n * 외부 의존성 없이 vanilla DOM으로 구현.\n */\n\nimport { LOCALE_CHANGE_EVENT, t } from '../i18n/index.js';\nimport { type AitDevtoolsState, aitState } from '../mock/state.js';\nimport { telemetry } from '../telemetry/index.js';\nimport { h } from './helpers.js';\nimport { PANEL_FULLSCREEN_BREAKPOINT, PANEL_HEIGHT, PANEL_STYLES, PANEL_WIDTH } from './styles.js';\nimport { setDeviceRefreshPanel } from './tabs/device.js';\nimport { createTabRenderers, getTabs, type TabId } from './tabs/index.js';\nimport { disposeViewport, initViewport } from './viewport.js';\n\n/** MCP endpoint registered by the unplugin when `mcp: true` is set */\nconst MCP_STATE_PATH = '/api/ait-devtools/state';\n\n/**\n * Push a state snapshot to the Vite dev-server MCP endpoint.\n * No-ops silently when the endpoint is not available (e.g., mcp option not set,\n * or running in production). Fire-and-forget — never throws.\n */\nfunction pushStateToMcpEndpoint(state: AitDevtoolsState): void {\n // Only attempt in a browser context with fetch available\n if (typeof fetch === 'undefined') return;\n fetch(MCP_STATE_PATH, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(state),\n }).catch(() => {\n // Silently ignore — endpoint is not available when mcp option is not set\n });\n}\n\n// --- Draggable toggle button ---\n\nfunction makeDraggable(el: HTMLElement, onClickOnly: () => void) {\n let isDragging = false;\n let startX = 0,\n startY = 0;\n let startLeft = 0,\n startTop = 0;\n let hasMoved = false;\n\n el.addEventListener('pointerdown', (e) => {\n isDragging = true;\n hasMoved = false;\n startX = e.clientX;\n startY = e.clientY;\n const rect = el.getBoundingClientRect();\n startLeft = rect.left;\n startTop = rect.top;\n el.setPointerCapture(e.pointerId);\n e.preventDefault();\n });\n\n el.addEventListener('pointermove', (e) => {\n if (!isDragging) return;\n const dx = e.clientX - startX;\n const dy = e.clientY - startY;\n if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {\n hasMoved = true;\n el.classList.add('dragging');\n }\n if (!hasMoved) return;\n\n el.style.left = `${startLeft + dx}px`;\n el.style.top = `${startTop + dy}px`;\n el.style.right = 'auto';\n el.style.bottom = 'auto';\n });\n\n el.addEventListener('pointerup', (e) => {\n if (!isDragging) return;\n isDragging = false;\n el.classList.remove('dragging');\n el.releasePointerCapture(e.pointerId);\n\n if (hasMoved) {\n snapToEdge(el);\n updatePanelPosition(el);\n saveButtonPosition(el);\n } else {\n onClickOnly();\n }\n });\n\n el.addEventListener('pointercancel', (e) => {\n isDragging = false;\n el.classList.remove('dragging');\n el.releasePointerCapture(e.pointerId);\n if (hasMoved) {\n snapToEdge(el);\n updatePanelPosition(el);\n saveButtonPosition(el);\n }\n });\n}\n\nfunction snapToEdge(el: HTMLElement) {\n const rect = el.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const cx = rect.left + rect.width / 2;\n const margin = 16;\n\n if (cx < vw / 2) {\n el.style.left = `${margin}px`;\n el.style.right = 'auto';\n } else {\n el.style.left = 'auto';\n el.style.right = `${margin}px`;\n }\n\n const top = Math.max(margin, Math.min(vh - rect.height - margin, rect.top));\n el.style.top = `${top}px`;\n el.style.bottom = 'auto';\n}\n\nfunction updatePanelPosition(toggleEl: HTMLElement) {\n if (!panelEl) return;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // On narrow viewports, CSS media query handles fullscreen — clear any inline positioning\n if (vw <= PANEL_FULLSCREEN_BREAKPOINT) {\n panelEl.style.top = '';\n panelEl.style.left = '';\n panelEl.style.right = '';\n panelEl.style.bottom = '';\n return;\n }\n\n const rect = toggleEl.getBoundingClientRect();\n const _panelWidth = PANEL_WIDTH;\n const panelHeight = PANEL_HEIGHT;\n const margin = 16;\n\n // Horizontal: place panel on the same side as the toggle button\n if (rect.left < vw / 2) {\n panelEl.style.left = `${margin}px`;\n panelEl.style.right = 'auto';\n } else {\n panelEl.style.left = 'auto';\n panelEl.style.right = `${margin}px`;\n }\n\n // Vertical: place below button if it's in top half, above if bottom half\n // Clamp so panel stays within viewport\n if (rect.top < vh / 2) {\n const top = Math.min(rect.bottom + 8, vh - panelHeight - margin);\n panelEl.style.top = `${Math.max(margin, top)}px`;\n panelEl.style.bottom = 'auto';\n } else {\n const bottom = Math.min(vh - rect.top + 8, vh - panelHeight - margin);\n panelEl.style.top = 'auto';\n panelEl.style.bottom = `${Math.max(margin, bottom)}px`;\n }\n}\n\nfunction saveButtonPosition(el: HTMLElement) {\n localStorage.setItem(\n '__ait_btn_pos',\n JSON.stringify({\n left: el.style.left,\n top: el.style.top,\n right: el.style.right,\n bottom: el.style.bottom,\n }),\n );\n}\n\n// Uses __ait_btn_pos (not __ait_storage: prefix) — panel-internal state, not mock storage\nfunction restoreButtonPosition(el: HTMLElement) {\n const saved = localStorage.getItem('__ait_btn_pos');\n if (saved) {\n try {\n const pos = JSON.parse(saved);\n if (typeof pos !== 'object' || pos === null) return;\n const allowedKeys = ['left', 'top', 'right', 'bottom'] as const;\n const validCssValue = /^(\\d+px|auto)$/;\n for (const key of allowedKeys) {\n if (key in pos && typeof pos[key] === 'string' && validCssValue.test(pos[key])) {\n el.style[key] = pos[key];\n }\n }\n } catch {\n /* ignore */\n }\n } else {\n el.style.bottom = '16px';\n el.style.right = '16px';\n }\n}\n\n// --- Mount ---\n\nlet currentTab: TabId = 'env';\nlet isOpen = false;\nlet panelEl: HTMLElement | null = null;\nlet bodyEl: HTMLElement | null = null;\nlet tabsEl: HTMLElement | null = null;\nlet toggleEl: HTMLElement | null = null;\nlet injectedStyle: HTMLStyleElement | null = null;\n\n// Saved listener refs so disposePanel() can detach them. Anonymous handlers\n// can't be removed, so mount() now binds these to module-level vars.\nlet panelSwitchTabHandler: ((e: Event) => void) | null = null;\nlet resizeHandler: (() => void) | null = null;\nlet aitStateUnsubscribe: (() => void) | null = null;\nlet localeChangeHandler: (() => void) | null = null;\n\n// Lazy-initialized after refreshPanel is defined\nlet tabRenderers: Record<TabId, () => HTMLElement> | null = null;\n\nfunction refreshPanel() {\n if (!bodyEl || !tabsEl) return;\n if (!tabRenderers) tabRenderers = createTabRenderers(refreshPanel);\n bodyEl.innerHTML = '';\n try {\n bodyEl.appendChild(tabRenderers[currentTab]());\n } catch (err) {\n console.error(`[@ait-co/devtools] Error rendering tab \"${currentTab}\":`, err);\n bodyEl.appendChild(\n h('div', { className: 'ait-panel-tab-error' }, t('panel.tabError', { tab: currentTab })),\n );\n }\n\n tabsEl.querySelectorAll('.ait-panel-tab').forEach((el) => {\n el.classList.toggle('active', el.getAttribute('data-tab') === currentTab);\n });\n}\n\nfunction mount() {\n if (typeof document === 'undefined') return;\n if (document.querySelector('.ait-panel-toggle')) return;\n\n // Wire up device tab's refreshPanel reference\n setDeviceRefreshPanel(refreshPanel);\n\n // Viewport simulation: restore from sessionStorage, apply to DOM, auto-sync.\n initViewport();\n\n // Styles\n injectedStyle = document.createElement('style');\n injectedStyle.textContent = PANEL_STYLES;\n document.head.appendChild(injectedStyle);\n\n // Toggle button\n const toggle = h(\n 'button',\n { className: 'ait-panel-toggle', title: t('panel.toggle.title') },\n 'AIT',\n );\n toggleEl = toggle;\n restoreButtonPosition(toggle);\n\n // Panel\n panelEl = h('div', { className: 'ait-panel' });\n\n const closeBtn = h('button', { className: 'ait-panel-close', title: t('panel.close') }, '\\u00d7');\n closeBtn.addEventListener('click', () => {\n isOpen = false;\n panelEl!.classList.remove('open');\n telemetry.onPanelClose();\n });\n\n const mockBadge = h(\n 'span',\n {\n className: `ait-mock-badge ${aitState.state.panelEditable ? 'ait-mock-badge-on' : 'ait-mock-badge-off'}`,\n title: t('panel.editMode.toggleTitle'),\n },\n aitState.state.panelEditable ? t('panel.editMode.on') : t('panel.editMode.off'),\n );\n\n mockBadge.addEventListener('click', () => {\n aitState.update({ panelEditable: !aitState.state.panelEditable });\n mockBadge.className = `ait-mock-badge ${aitState.state.panelEditable ? 'ait-mock-badge-on' : 'ait-mock-badge-off'}`;\n mockBadge.textContent = aitState.state.panelEditable\n ? t('panel.editMode.on')\n : t('panel.editMode.off');\n refreshPanel();\n });\n\n const headerRight = h(\n 'span',\n { style: 'display:flex;align-items:center;gap:6px' },\n mockBadge,\n h('span', { style: 'font-size:11px;color:#666;font-weight:400' }, `v${__VERSION__}`),\n closeBtn,\n );\n const header = h(\n 'div',\n { className: 'ait-panel-header' },\n h('span', {}, t('panel.title')),\n headerRight,\n );\n\n tabsEl = h('div', { className: 'ait-panel-tabs' });\n for (const tab of getTabs()) {\n const tabEl = h('button', { className: 'ait-panel-tab', 'data-tab': tab.id }, tab.label);\n tabEl.addEventListener('click', () => {\n currentTab = tab.id;\n telemetry.onTabView(tab.id);\n refreshPanel();\n });\n tabsEl.appendChild(tabEl);\n }\n\n bodyEl = h('div', { className: 'ait-panel-body' });\n\n panelEl.append(header, tabsEl, bodyEl);\n document.body.append(panelEl, toggle);\n\n // Re-clamp restored position to current viewport (e.g., saved on wider screen)\n snapToEdge(toggle);\n saveButtonPosition(toggle);\n\n makeDraggable(toggle, () => {\n isOpen = !isOpen;\n panelEl!.classList.toggle('open', isOpen);\n if (isOpen) {\n updatePanelPosition(toggle);\n refreshPanel();\n telemetry.onPanelOpen();\n } else {\n telemetry.onPanelClose();\n }\n });\n\n // Re-clamp button and panel position on window resize (rAF-throttled)\n let resizeRaf = 0;\n resizeHandler = () => {\n if (resizeRaf) return;\n resizeRaf = requestAnimationFrame(() => {\n resizeRaf = 0;\n snapToEdge(toggle);\n saveButtonPosition(toggle);\n if (isOpen) updatePanelPosition(toggle);\n });\n };\n window.addEventListener('resize', resizeHandler);\n\n // 상태 변경 시 자동 갱신 (env, analytics, storage, device, viewport, iap 탭)\n // Defense-in-depth: outer catch complements refreshPanel's inner tab-rendering catch.\n // env 탭은 Navigation 섹션이 setIosSwipeGestureEnabled 같은 SDK no-op API의 호출값을\n // read-only로 비추므로, 앱이 그 API를 호출하면 패널이 실시간 반영해야 한다. 이 탭의\n // 입력 필드(appVersion/locale/safeArea)는 모두 'change'(blur/Enter) 커밋이라\n // 재렌더가 입력 중 데이터를 잃지 않는다 — viewport 탭과 동일한 트레이드오프.\n aitStateUnsubscribe = aitState.subscribe(() => {\n try {\n if (\n isOpen &&\n (currentTab === 'env' ||\n currentTab === 'analytics' ||\n currentTab === 'storage' ||\n currentTab === 'device' ||\n currentTab === 'viewport' ||\n currentTab === 'iap' ||\n currentTab === 'ads' ||\n currentTab === 'presets')\n ) {\n refreshPanel();\n }\n } catch (err) {\n console.error('[@ait-co/devtools] Error in subscribe callback:', err);\n }\n\n // MCP state push: when the unplugin `mcp: true` option is set, the Vite\n // dev server exposes POST /api/ait-devtools/state. Push a snapshot there\n // on every state change so the MCP stdio server can serve it to AI agents.\n // Fire-and-forget — failures are debug-level only; never block the panel.\n pushStateToMcpEndpoint(aitState.state);\n });\n\n // Listen for tab switch requests from device tab (prompt auto-open).\n // Bound here (not at module scope) so disposePanel() can detach it; outside\n // of a mount, switching tabs has no panel to act on anyway.\n panelSwitchTabHandler = (e: Event) => {\n const detail = (e as CustomEvent).detail as { tab: TabId };\n currentTab = detail.tab;\n if (panelEl && !panelEl.classList.contains('open')) {\n isOpen = true;\n panelEl.classList.add('open');\n }\n refreshPanel();\n };\n window.addEventListener('__ait:panel-switch-tab', panelSwitchTabHandler);\n\n // Locale change → tear down the panel and re-mount so every string in the\n // tree re-evaluates against the new catalog. The mount path is already\n // idempotent (`document.querySelector('.ait-panel-toggle')` guard), so\n // dispose+mount is the simplest \"re-render whole panel\" hook.\n localeChangeHandler = () => {\n // disposePanel() resets currentTab to 'env' and isOpen to false. Capture\n // them first so the user stays on the same tab and the panel doesn't\n // close out from under them mid-interaction.\n const savedTab = currentTab;\n const savedOpen = isOpen;\n disposePanel();\n try {\n mount();\n currentTab = savedTab;\n if (savedOpen && panelEl) {\n isOpen = true;\n panelEl.classList.add('open');\n }\n refreshPanel();\n } catch (err) {\n console.error('[@ait-co/devtools] Failed to re-mount after locale change:', err);\n }\n };\n window.addEventListener(LOCALE_CHANGE_EVENT, localeChangeHandler);\n\n refreshPanel();\n\n // Telemetry: check consent state, show toast if needed, fire panel_mount if granted.\n telemetry.init();\n}\n\n/**\n * Pairs with `mount()` (and the existing `disposeViewport()`).\n * Idempotent — safe to call before mount or twice in a row.\n *\n * Removes panel DOM (toggle + panel root), the injected `<style>`, all\n * window/aitState listeners, and resets module-level state. After dispose,\n * `mount()` can be called again to re-mount cleanly.\n */\nfunction disposePanel(): void {\n if (typeof document === 'undefined') return;\n\n if (panelSwitchTabHandler && typeof window !== 'undefined') {\n window.removeEventListener('__ait:panel-switch-tab', panelSwitchTabHandler);\n }\n if (resizeHandler && typeof window !== 'undefined') {\n window.removeEventListener('resize', resizeHandler);\n }\n if (localeChangeHandler && typeof window !== 'undefined') {\n window.removeEventListener(LOCALE_CHANGE_EVENT, localeChangeHandler);\n }\n if (aitStateUnsubscribe) aitStateUnsubscribe();\n\n toggleEl?.remove();\n panelEl?.remove();\n injectedStyle?.remove();\n\n disposeViewport();\n setDeviceRefreshPanel(() => {});\n\n panelSwitchTabHandler = null;\n resizeHandler = null;\n localeChangeHandler = null;\n aitStateUnsubscribe = null;\n toggleEl = null;\n panelEl = null;\n bodyEl = null;\n tabsEl = null;\n injectedStyle = null;\n tabRenderers = null;\n currentTab = 'env';\n isOpen = false;\n}\n\n// DOM ready 시 마운트\nif (typeof document !== 'undefined') {\n const safeMount = () => {\n try {\n mount();\n } catch (err) {\n console.error('[@ait-co/devtools] Failed to mount panel:', err);\n }\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', safeMount);\n } else {\n safeMount();\n }\n}\n\nexport { disposePanel, mount };\n"],"mappings":";AAMA,MAAa,KAAgC;CAE3C,eAAe;CACf,sBAAsB;CACtB,eAAe;CACf,qBAAqB;CACrB,sBAAsB;CACtB,8BAA8B;CAC9B,kBAAkB;CAGlB,iBAAiB;CACjB,qBAAqB;CACrB,sBAAsB;CACtB,yBAAyB;CACzB,2BAA2B;CAC3B,sBAAsB;CACtB,oBAAoB;CACpB,iBAAiB;CACjB,iBAAiB;CACjB,oBAAoB;CACpB,uBAAuB;CACvB,qBAAqB;CAGrB,mBAAmB;CAGnB,uBAAuB;CACvB,sBACE;CACF,2BAA2B;CAC3B,wBAAwB;CACxB,sBAAsB;CAGtB,wBAAwB;CACxB,cAAc;CACd,sBAAsB;CACtB,uBAAuB;CACvB,kBAAkB;CAClB,uBAAuB;CACvB,yBAAyB;CACzB,wBAAwB;CACxB,wBAAwB;CACxB,2BAA2B;CAC3B,0BAA0B;CAC1B,2BAA2B;CAC3B,mCAAmC;CACnC,qCAAqC;CACrC,sCAAsC;CACtC,4BACE;CAGF,yBAAyB;CAEzB,uBAAuB;CACvB,sBAAsB;CACtB,uBAAuB;CACvB,0BAA0B;CAC1B,2BAA2B;CAC3B,wBAAwB;CAExB,qBAAqB;CACrB,oBAAoB;CACpB,qBAAqB;CACrB,wBAAwB;CACxB,yBAAyB;CACzB,6BAA6B;CAC7B,8BAA8B;CAC9B,iCAAiC;CACjC,2BAA2B;CAC3B,0BAA0B;CAC1B,yBAAyB;CACzB,mCAAmC;CACnC,8BAA8B;CAC9B,6BAA6B;CAG7B,wBAAwB;CACxB,oBAAoB;CACpB,mBAAmB;CACnB,mBAAmB;CAGnB,8BAA8B;CAG9B,4BAA4B;CAC5B,yBAAyB;CACzB,0BAA0B;CAC1B,yBAAyB;CAGzB,wBAAwB;CACxB,qBAAqB;CACrB,qBAAqB;CACrB,uBAAuB;CACvB,sBAAsB;CACtB,wBAAwB;CACxB,6BAA6B;CAC7B,kBAAkB;CAClB,0BAA0B;CAC1B,oBAAoB;CACpB,8BAA8B;CAC9B,8BAA8B;CAC9B,gCAAgC;CAChC,sCAAsC;CACtC,+BAA+B;CAC/B,2BAA2B;CAC3B,2BAA2B;CAC3B,sBAAsB;CACtB,wBAAwB;CAGxB,yBAAyB;CACzB,0BAA0B;CAC1B,yBAAyB;CACzB,yBAAyB;CAGzB,2BAA2B;CAC3B,uBAAuB;CACvB,4BAA4B;CAC5B,0BAA0B;CAC1B,2BAA2B;CAC3B,sBAAsB;CACtB,uBAAuB;CACvB,+BAA+B;CAC/B,0BAA0B;CAC1B,8BAA8B;CAC9B,2BAA2B;CAC3B,gCAAgC;CAChC,+BAA+B;CAC/B,4BAA4B;CAC5B,6BAA6B;CAC7B,kCAAkC;CAClC,mCAAmC;CAGnC,yBAAyB;CACzB,sBAAsB;CACtB,uBAAuB;CACvB,yBAAyB;CACzB,uBAAuB;CACvB,qBAAqB;CACrB,yBAAyB;CACzB,uBAAuB;CACvB,oBAAoB;CACpB,qBAAqB;CAGrB,6BAA6B;CAC7B,0BAA0B;CAC1B,0BAA0B;CAC1B,wBAAwB;CACxB,uBAAuB;CACvB,kCAAkC;CAGlC,yBAAyB;CACzB,uBAAuB;CACvB,2BAA2B;CAC3B,6BAA6B;CAC7B,yBAAyB;CAGzB,yBAAyB;CACzB,wBAAwB;CACxB,iBAAiB;CAGjB,2BAA2B;CAC3B,yBAAyB;CACzB,wBAAwB;CACxB,4BAA4B;CAC5B,2BAA2B;CAC3B,qBAAqB;CACrB,uBAAuB;CACvB,sBAAsB;CACtB,uBAAuB;CACvB,yBAAyB;CACzB,wBAAwB;CACxB,0BAA0B;CAG1B,qBAAqB;CACrB,oBAAoB;CACpB,uBAAuB;CACvB,oBAAoB;CACpB,2BAA2B;CAC3B,uBAAuB;CACvB,4BAA4B;CAC5B,gBAAgB;CAChB,gBAAgB;CAChB,6BAA6B;CAC7B,0BAA0B;CAC1B,wBAAwB;CACxB,kBAAkB;CAClB,kBAAkB;CAClB,iBAAiB;CACjB,mBAAmB;CAGnB,+BAA+B;CAC/B,qCAAqC;CACrC,sCAAsC;CACtC,0CAA0C;CAC3C;;;AC/MD,MAAa,KAAK;CAEhB,eAAe;CACf,sBAAsB;CACtB,eAAe;CACf,qBAAqB;CACrB,sBAAsB;CACtB,8BAA8B;CAC9B,kBAAkB;CAGlB,iBAAiB;CACjB,qBAAqB;CACrB,sBAAsB;CACtB,yBAAyB;CACzB,2BAA2B;CAC3B,sBAAsB;CACtB,oBAAoB;CACpB,iBAAiB;CACjB,iBAAiB;CACjB,oBAAoB;CACpB,uBAAuB;CACvB,qBAAqB;CAGrB,mBAAmB;CAGnB,uBAAuB;CACvB,sBAAsB;CACtB,2BAA2B;CAC3B,wBAAwB;CACxB,sBAAsB;CAGtB,wBAAwB;CACxB,cAAc;CACd,sBAAsB;CACtB,uBAAuB;CACvB,kBAAkB;CAClB,uBAAuB;CACvB,yBAAyB;CACzB,wBAAwB;CACxB,wBAAwB;CACxB,2BAA2B;CAC3B,0BAA0B;CAC1B,2BAA2B;CAC3B,mCAAmC;CACnC,qCAAqC;CACrC,sCAAsC;CACtC,4BACE;CAGF,yBAAyB;CAEzB,uBAAuB;CACvB,sBAAsB;CACtB,uBAAuB;CACvB,0BAA0B;CAC1B,2BAA2B;CAC3B,wBAAwB;CAExB,qBAAqB;CACrB,oBAAoB;CACpB,qBAAqB;CACrB,wBAAwB;CACxB,yBAAyB;CACzB,6BAA6B;CAC7B,8BAA8B;CAC9B,iCAAiC;CACjC,2BAA2B;CAC3B,0BAA0B;CAC1B,yBAAyB;CACzB,mCAAmC;CACnC,8BAA8B;CAC9B,6BAA6B;CAG7B,wBAAwB;CACxB,oBAAoB;CACpB,mBAAmB;CACnB,mBAAmB;CAGnB,8BAA8B;CAG9B,4BAA4B;CAC5B,yBAAyB;CACzB,0BAA0B;CAC1B,yBAAyB;CAGzB,wBAAwB;CACxB,qBAAqB;CACrB,qBAAqB;CACrB,uBAAuB;CACvB,sBAAsB;CACtB,wBAAwB;CACxB,6BAA6B;CAC7B,kBAAkB;CAClB,0BAA0B;CAC1B,oBAAoB;CACpB,8BAA8B;CAC9B,8BAA8B;CAC9B,gCAAgC;CAChC,sCAAsC;CACtC,+BAA+B;CAC/B,2BAA2B;CAC3B,2BAA2B;CAC3B,sBAAsB;CACtB,wBAAwB;CAGxB,yBAAyB;CACzB,0BAA0B;CAC1B,yBAAyB;CACzB,yBAAyB;CAGzB,2BAA2B;CAC3B,uBAAuB;CACvB,4BAA4B;CAC5B,0BAA0B;CAC1B,2BAA2B;CAC3B,sBAAsB;CACtB,uBAAuB;CACvB,+BAA+B;CAC/B,0BAA0B;CAC1B,8BAA8B;CAC9B,2BAA2B;CAC3B,gCAAgC;CAChC,+BAA+B;CAC/B,4BAA4B;CAC5B,6BAA6B;CAC7B,kCAAkC;CAClC,mCAAmC;CAGnC,yBAAyB;CACzB,sBAAsB;CACtB,uBAAuB;CACvB,yBAAyB;CACzB,uBAAuB;CACvB,qBAAqB;CACrB,yBAAyB;CACzB,uBAAuB;CACvB,oBAAoB;CACpB,qBAAqB;CAGrB,6BAA6B;CAC7B,0BAA0B;CAC1B,0BAA0B;CAC1B,wBAAwB;CACxB,uBAAuB;CACvB,kCAAkC;CAGlC,yBAAyB;CACzB,uBAAuB;CACvB,2BAA2B;CAC3B,6BAA6B;CAC7B,yBAAyB;CAGzB,yBAAyB;CACzB,wBAAwB;CACxB,iBAAiB;CAGjB,2BAA2B;CAC3B,yBAAyB;CACzB,wBAAwB;CACxB,4BACE;CACF,2BAA2B;CAC3B,qBAAqB;CACrB,uBAAuB;CACvB,sBAAsB;CACtB,uBAAuB;CACvB,yBAAyB;CACzB,wBAAwB;CACxB,0BAA0B;CAG1B,qBAAqB;CACrB,oBAAoB;CACpB,uBAAuB;CACvB,oBAAoB;CACpB,2BAA2B;CAC3B,uBAAuB;CACvB,4BAA4B;CAC5B,gBAAgB;CAChB,gBAAgB;CAChB,6BAA6B;CAC7B,0BAA0B;CAC1B,wBAAwB;CACxB,kBAAkB;CAClB,kBAAkB;CAClB,iBAAiB;CACjB,mBAAmB;CAGnB,+BAA+B;CAC/B,qCAAqC;CACrC,sCAAsC;CACtC,0CAA0C;CAC3C;;;;;;;;;;;;;;;;;;ACpMD,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAE5B,MAAM,SAA6D;CAAE;CAAI;CAAI;AAE7E,IAAI,gBAA+B;AAEnC,SAAS,kBAAiC;AACxC,KAAI,OAAO,iBAAiB,YAAa,QAAO;AAChD,KAAI;EACF,MAAM,MAAM,aAAa,QAAQ,mBAAmB;AACpD,MAAI,QAAQ,QAAQ,QAAQ,KAAM,QAAO;SACnC;AAGR,QAAO;;AAGT,SAAS,iBAAiB,QAAsB;AAC9C,KAAI,OAAO,iBAAiB,YAAa;AACzC,KAAI;AACF,eAAa,QAAQ,oBAAoB,OAAO;SAC1C;;;;;;AASV,SAAgB,eAAuB;AACrC,KAAI,OAAO,cAAc,YAAa,QAAO;CAC7C,MAAM,OAAO,UAAU,YAAY;AACnC,QAAO,SAAS,KAAK,KAAK,GAAG,OAAO;;;;;;;;AAStC,SAAgB,YAAoB;AAClC,KAAI,cAAe,QAAO;AAE1B,iBADe,iBAAiB,IACN,cAAc;AACxC,QAAO;;;;;;AAOT,SAAgB,UAAU,QAAsB;AAC9C,iBAAgB;AAChB,kBAAiB,OAAO;AACxB,KAAI,OAAO,WAAW,YACpB,QAAO,cAAc,IAAI,YAAY,oBAAoB,CAAC;;;;;;AAQ9D,SAAgB,EAAE,KAAgB,MAAgD;CAChF,MAAM,MAAM,OAAO,WAAW,EAAE,QAAQ;AACxC,KAAI,CAAC,KAAM,QAAO;AAClB,QAAO,IAAI,QAAQ,eAAe,OAAO,SAAiB;EACxD,MAAM,QAAQ,KAAK;AACnB,SAAO,UAAU,KAAA,IAAY,QAAQ,OAAO,MAAM;GAClD;;;;;ACpCJ,MAAM,mBAAmB;AAqHzB,MAAM,gBAAkC;CACtC,UAAU;CACV,aAAa;CACb,YAAY;CACZ,QAAQ;CACR,WAAW;CACX,SAAS;CACT,cAAc;CACd,UAAU;CAEV,OAAO;EACL,aAAa;EACb,MAAM;EACN,cAAc;EACf;CAED,eAAe;CAGf,YAAY,EACV,wBAAwB,MACzB;CAED,aAAa;EACX,WAAW;EACX,UAAU;EACV,QAAQ;EACR,aAAa;EACb,QAAQ;EACR,YAAY;EACb;CAED,UAAU;EACR,QAAQ;GACN,UAAU;GACV,WAAW;GACX,UAAU;GACV,UAAU;GACV,kBAAkB;GAClB,SAAS;GACV;EACD,WAAW,KAAK,KAAK;EACrB,gBAAgB;EACjB;CAOD,gBAAgB;EAAE,KAAK;EAAI,QAAQ;EAAI,MAAM;EAAG,OAAO;EAAG;CAE1D,UAAU,CACR;EAAE,MAAM;EAAO,aAAa;EAAiB,EAC7C;EAAE,MAAM;EAAO,aAAa;EAAiB,CAC9C;CAED,KAAK;EACH,UAAU,CACR;GACE,KAAK;GACL,MAAM;GACN,aAAa;GACb,eAAe;GACf,SAAS;GACT,aAAa;GACd,CACF;EACD,YAAY;EACZ,eAAe,EAAE;EACjB,iBAAiB,EAAE;EACpB;CAED,SAAS;EACP,YAAY;EACZ,YAAY;EACb;CAED,MAAM;EACJ,YAAY;EACZ,uBAAuB;EACvB,aAAa;EACb,kBAAkB;EACnB;CAED,cAAc,EACZ,YAAY,gBACb;CAED,KAAK;EACH,UAAU;EACV,WAAW;EACX,aAAa;EACb,WAAW;EACX,gBAAgB;EAChB,cAAc;EACf;CAED,MAAM;EACJ,SAAS;GAAE,UAAU;GAAc,iBAAiB;GAAI;EACxD,mBAAmB,EAAE;EACtB;CAED,cAAc,EAAE;CAEhB,YAAY,EAAE;CAEd,aAAa;EACX,QAAQ;EACR,QAAQ;EACR,UAAU;EACV,SAAS;EAOT,WAAW;EACZ;CAED,UAAU;EACR,QAAQ,EAAE;EACV,eAAe;EAChB;CAED,eAAe;CAEf,UAAU;EACR,QAAQ;EACR,aAAa;EACb,gBAAgB;EAChB,aAAa;EACb,cAAc;EACd,OAAO;EACP,WAAW;EACX,eAAe;EAChB;CACF;AAED,SAAS,mBAA2B;CAClC,MAAM,SAAS,aAAa,QAAQ,kBAAkB;AACtD,KAAI,OAAQ,QAAO;CACnB,MAAM,KAAK,OAAO,YAAY;AAC9B,cAAa,QAAQ,mBAAmB,GAAG;AAC3C,QAAO;;AAGT,IAAa,kBAAb,MAA6B;CAC3B;CACA,6BAAqB,IAAI,KAAe;CACxC,iBAAyB;CAEzB,cAAc;AACZ,OAAK,SAAS,gBAAgB,cAAc;AAC5C,MAAI;AACF,QAAK,OAAO,WAAW,kBAAkB;UACnC;AACN,QAAK,OAAO,WAAW,eAAe,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE;;;CAI7E,IAAI,QAA0B;AAC5B,SAAO,KAAK;;CAGd,OAAO,SAAoC;AACzC,OAAK,SAAS;GAAE,GAAG,KAAK;GAAQ,GAAG;GAAS;AAC5C,OAAK,SAAS;;;CAIhB,MAAwC,KAAQ,SAAuC;EACrF,MAAM,UAAU,KAAK,OAAO;AAC5B,MAAI,OAAO,YAAY,YAAY,YAAY,QAAQ,CAAC,MAAM,QAAQ,QAAQ,CAC5E,MAAK,SAAS;GACZ,GAAG,KAAK;IACP,MAAM;IAAE,GAAI;IAAqC,GAAI;IAAqC;GAC5F;MAED,MAAK,SAAS;GAAE,GAAG,KAAK;IAAS,MAAM;GAAgC;AAEzE,OAAK,SAAS;;CAGhB,UAAU,UAAgC;AACxC,OAAK,WAAW,IAAI,SAAS;AAC7B,eAAa,KAAK,WAAW,OAAO,SAAS;;;;;;;;;;;;;;;;;CAkB/C,YAAY,IAAsB;AAChC,MAAI,KAAK,gBAAgB;AACvB,OAAI;AACJ;;AAEF,OAAK,iBAAiB;AACtB,MAAI;AACF,OAAI;YACI;AACR,QAAK,iBAAiB;AACtB,QAAK,SAAS;;;;CAKlB,aAAa,OAA6C;AACxD,OAAK,SAAS;GACZ,GAAG,KAAK;GACR,cAAc,CAAC,GAAG,KAAK,OAAO,cAAc;IAAE,GAAG;IAAO,WAAW,KAAK,KAAK;IAAE,CAAC;GACjF;AACD,OAAK,SAAS;;;;;;CAOhB,WAAW,OAAmB;EAC5B,MAAM,MAAM,KAAK,OAAO;EACxB,MAAM,OAAO,IAAI,UAAU,mBAAmB,IAAI,MAAM,IAAI,iBAAiB,GAAG;AAChF,OAAK,SAAS;GAAE,GAAG,KAAK;GAAQ,YAAY,CAAC,GAAG,MAAM,MAAM;GAAE;AAC9D,OAAK,SAAS;;;CAIhB,QAAQ,OAAe;AACrB,SAAO,cAAc,IAAI,YAAY,SAAS,QAAQ,CAAC;;CAGzD,QAAQ;EACN,MAAM,WAAW,KAAK,OAAO;AAC7B,OAAK,SAAS;GAAE,GAAG,gBAAgB,cAAc;GAAE;GAAU;AAC7D,OAAK,SAAS;;CAGhB,UAAkB;AAChB,MAAI,KAAK,eAAgB;AACzB,OAAK,MAAM,YAAY,KAAK,WAC1B,WAAU;;;AAahB,MAAM,gBAAgB;AAEtB,MAAM,YAAY;AAClB,IAAI,CAAC,UAAU,eACb,WAAU,iBAAiB,IAAI,iBAAiB;AAElD,MAAa,WAA4B,UAAU;AAGnD,IAAI,OAAO,WAAW,YACpB,QAAO,QAAQ;;;;;;;;;ACzbjB,MAAM,WAAW;AAEjB,MAAM,eAAe;KAChB,SAAS;;;;;;;;;;;;;;;;KAgBT,SAAS;;;;;;KAMT,SAAS;;;;;;KAMT,SAAS;;;;;;KAMT,SAAS;;;;;;;;;;KAUT,SAAS;KACT,SAAS;;;;;;;;;;KAUT,SAAS;KACT,SAAS;;;;;;KAMT,SAAS;;AAGd,SAAS,eAAqB;AAC5B,KAAI,SAAS,eAAe,GAAG,SAAS,QAAQ,CAAE;CAClD,MAAM,QAAQ,SAAS,cAAc,QAAQ;AAC7C,OAAM,KAAK,GAAG,SAAS;AACvB,OAAM,cAAc;AACpB,UAAS,KAAK,YAAY,MAAM;;AAGlC,SAAS,cAAoB;AAC3B,UAAS,eAAe,SAAS,EAAE,QAAQ;AAC3C,UAAS,eAAe,GAAG,SAAS,QAAQ,EAAE,QAAQ;;;;;;AAYxD,SAAgB,iBAAiB,EAAE,UAAU,UAAqC;AAChF,KAAI,SAAS,eAAe,SAAS,CAAE;AAEvC,eAAc;CAEd,MAAM,QAAQ,SAAS,cAAc,MAAM;AAC3C,OAAM,KAAK;CAEX,MAAM,SAAS,SAAS,cAAc,MAAM;AAC5C,QAAO,YAAY;AACnB,QAAO,cAAc,EAAE,sBAAsB;CAE7C,MAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,MAAK,YAAY;AACjB,MAAK,cAAc,EAAE,qBAAqB;CAE1C,MAAM,YAAY,SAAS,cAAc,IAAI;AAC7C,WAAU,YAAY;AACtB,WAAU,OAAO;AACjB,WAAU,SAAS;AACnB,WAAU,MAAM;AAChB,WAAU,cAAc,EAAE,0BAA0B;CAEpD,MAAM,SAAS,SAAS,cAAc,SAAS;AAC/C,QAAO,YAAY;AACnB,QAAO,cAAc,EAAE,uBAAuB;AAC9C,QAAO,iBAAiB,eAAe;AACrC,eAAa;AACb,YAAU;GACV;CAEF,MAAM,QAAQ,SAAS,cAAc,SAAS;AAC9C,OAAM,YAAY;AAClB,OAAM,cAAc,EAAE,qBAAqB;AAC3C,OAAM,iBAAiB,eAAe;AACpC,eAAa;AACb,UAAQ;GACR;CAEF,MAAM,UAAU,SAAS,cAAc,MAAM;AAC7C,SAAQ,YAAY;AACpB,SAAQ,OAAO,WAAW,OAAO,OAAO;AAExC,OAAM,OAAO,QAAQ,MAAM,QAAQ;AACnC,UAAS,KAAK,YAAY,MAAM;;;;ACtIlC,MAAM,cAAc;AACpB,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,cAAc;AAGpB,MAAa,mBAAmB;AAChC,MAAa,aAAa;;;;;;AAW1B,SAAgB,iBAA0B;AACxC,KAAI,OAAO,YAAY,eAAe,QAAQ,IAAI,mBAAmB,MAAO,QAAO;AACnF,KAAI;AACF,SAAO,aAAa,QAAQ,WAAW,KAAK;SACtC;AACN,SAAO;;;;;;AAOX,SAAgB,gBAAgB,SAAwB;AACtD,KAAI;AACF,MAAI,QACF,cAAa,WAAW,WAAW;MAEnC,cAAa,QAAQ,YAAY,IAAI;SAEjC;;;;;AAQV,SAAgB,oBAA6B;AAC3C,KAAI;EACF,MAAM,SAAS,aAAa,QAAQ,iBAAiB;AACrD,MAAI,CAAC,OAAQ,QAAO;AAEpB,SAAO,4BADO,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;SAE7C;AACN,SAAO;;;;;;AAOX,SAAgB,gBAAsB;AACpC,KAAI;EACF,MAAM,yBAAQ,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;AACnD,eAAa,QAAQ,kBAAkB,MAAM;SACvC;;;;;;AASV,MAAa,yBAAyB;;AAGtC,MAAM,iBAAiB,MAAU,KAAK,KAAK;AAM3C,SAAgB,mBAAiC;CAC/C,MAAM,MAAM,aAAa,QAAQ,YAAY;AAC7C,KAAI,QAAQ,aAAa,QAAQ,SAAU,QAAO;AAClD,QAAO;;AAGT,SAAgB,oBAA4B;CAC1C,MAAM,MAAM,aAAa,QAAQ,mBAAmB;AACpD,KAAI,QAAQ,KAAM,QAAO;CACzB,MAAM,IAAI,OAAO,IAAI;AACrB,QAAO,OAAO,SAAS,EAAE,GAAG,IAAI;;AAGlC,SAAgB,oBAAmC;AACjD,QAAO,aAAa,QAAQ,mBAAmB;;;;;;AAOjD,SAAgB,oBAA4B;CAC1C,MAAM,WAAW,aAAa,QAAQ,YAAY;AAClD,KAAI,SAAU,QAAO;CACrB,MAAM,KAAK,OAAO,YAAY;AAC9B,cAAa,QAAQ,aAAa,GAAG;AACrC,QAAO;;;;;;;;;;AAeT,SAAgB,0BAAwC;CACtD,MAAM,MAAM,aAAa,QAAQ,YAAY;AAC7C,KAAI,QAAQ,WAAW;AAErB,MADsB,mBAAmB,KAAA,cACK;AAE5C,gBAAa,WAAW,YAAY;AACpC,gBAAa,WAAW,mBAAmB;AAC3C,UAAO;;AAET,SAAO;;AAET,KAAI,QAAQ,SAAU,QAAO;AAC7B,QAAO;;;;;;AAOT,SAAgB,gBAAsB;AACpC,cAAa,QAAQ,aAAa,UAAU;AAC5C,cAAa,QAAQ,oBAAoB,uBAAuB;AAEhE,cAAa,WAAW,mBAAmB;;;;;;;;AAS7C,SAAgB,cAAoB;AAClC,cAAa,QAAQ,aAAa,SAAS;CAC3C,MAAM,WAAW,mBAAmB;AACpC,KAAI,WAAW,KAAK,WAAW,OAAO,iBAEpC,cAAa,QAAQ,oBAAoB,OAAO,OAAO,iBAAiB,CAAC;KAGzE,cAAa,QAAQ,oBAAoB,OAAO,KAAK,KAAK,GAAG,eAAe,CAAC;;;;;;AAQjF,SAAgB,oBAAoB,SAAwB;AAC1D,KAAI,SAAS;AACX,eAAa,QAAQ,aAAa,UAAU;AAC5C,eAAa,QAAQ,oBAAoB,uBAAuB;OAEhE,cAAa,QAAQ,aAAa,SAAS;;;;;;;;;;AAY/C,SAAgB,kBAA2B;CACzC,MAAM,QAAQ,yBAAyB;AACvC,KAAI,UAAU,aAAa;EACzB,MAAM,gBAAgB,mBAAmB;AACzC,MAAI,kBAAkB,EAAG,QAAO;AAChC,SAAO,KAAK,KAAK,GAAG;;AAEtB,KAAI,UAAU,UAAU;EACtB,MAAM,gBAAgB,mBAAmB;AACzC,MAAI,kBAAkB,KAAK,iBAAiB,OAAO,iBAAkB,QAAO;AAC5E,SAAO,KAAK,KAAK,GAAG;;AAEtB,QAAO;;;;;;;AAQT,eAAsB,aAAa,UAAoC;CACrE,MAAM,SAAS,aAAa,QAAQ,YAAY;AAChD,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI;AAIF,MAAI,EAHQ,MAAM,MAAM,GAAG,SAAS,aAAa,mBAAmB,OAAO,IAAI,EAC7E,QAAQ,UACT,CAAC,EACO,GAAI,QAAO;AACpB,eAAa,QAAQ,aAAa,OAAO,YAAY,CAAC;AACtD,SAAO;SACD;AACN,SAAO;;;;;;;;;;;;;;;;;;ACvMX,MAAM,gBAAgB;AAEtB,SAAS,aACP,MACqC;AACrC,KAAI,SAAS,KAAA,EAAW,QAAO,KAAA;CAC/B,MAAM,aAAa,KAAK,UAAU,KAAK;AACvC,KAAI,IAAI,aAAa,CAAC,OAAO,WAAW,CAAC,SAAS,cAEhD;AAEF,QAAO;;AAGT,eAAe,QAAQ,SAAyC;CAC9D,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,IAAM;AAC7D,KAAI;AAOF,UANY,MAAM,MAAM,GAAG,mBAAmB,KAAK;GACjD,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UAAU,QAAQ;GAC7B,QAAQ,WAAW;GACpB,CAAC,EACS;SACL;AACN,SAAO;WACC;AACR,eAAa,UAAU;;;AAI3B,SAAS,MAAM,IAA2B;AACxC,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;AAM1D,eAAsB,KACpB,OACA,SACA,MACe;AACf,KAAI,kBAAkB,KAAK,UAAW;CAEtC,MAAM,UAAwB;EAC5B,MAAM;EACN,QAAQ;EACR;EACA,SAAS,mBAAmB;EAC5B;EACA,IAAI,KAAK,KAAK;EACd,MAAM,aAAa,KAAK;EACzB;AAGD,KADW,MAAM,QAAQ,QAAQ,CACzB;AAGR,KAAI,QAAQ,IAAI,aAAa,aAC3B,SAAQ,MAAM,wDAAwD,MAAM;AAE9E,OAAM,MAAM,IAAM;AAClB,OAAM,QAAQ,QAAQ;;;;;;;AASxB,SAAgB,gBACd,OACA,SACA,MACM;AACN,KAAI,kBAAkB,KAAK,UAAW;CAEtC,MAAM,UAAwB;EAC5B,MAAM;EACN,QAAQ;EACR;EACA,SAAS,mBAAmB;EAC5B;EACA,IAAI,KAAK,KAAK;EACd,MAAM,aAAa,KAAK;EACzB;CAED,MAAM,OAAO,KAAK,UAAU,QAAQ;AAEpC,KAAI,OAAO,cAAc,eAAe,OAAO,UAAU,eAAe,YAAY;AAClF,YAAU,WAAW,GAAG,mBAAmB,KAAK,IAAI,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,oBAAoB,CAAC,CAAC;AAC/F;;AAIF,OAAM,GAAG,mBAAmB,KAAK;EAC/B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C;EACA,WAAW;EACZ,CAAC,CAAC,YAAY,GAEb;;;;;;;;;;;;;;;;;;;AC5GJ,eAAsB,cAAc,SAAmC;AACrE,KAAI,CAAC,gBAAgB,CAAE,QAAO;AAC9B,KAAI,mBAAmB,CAAE,QAAO;CAEhC,MAAM,UAAwB;EAC5B,MAAM;EACN,QAAQ;EACR,IAAI,KAAK,KAAK;EACd;EACD;CAED,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,IAAM;AAE7D,KAAI;AACF,QAAM,MAAM,GAAG,mBAAmB,KAAK;GACrC,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UAAU,QAAQ;GAC7B,QAAQ,WAAW;GACpB,CAAC;AAEF,iBAAe;AACf,SAAO;SACD;AAEN,SAAO;WACC;AACR,eAAa,UAAU;;;;;;;;;;;;;;;;;AC1B3B,SAAS,iBAAiB,KAAiC;CACzD,MAAM,MAAO,WAAuC;AACpD,QAAO,OAAO,QAAQ,WAAW,MAAM,KAAA;;AAGzC,MAAa,qBACX,iBAAiB,yBAAyB,IAAI;AAKhD,SAAS,aAAqB;AAC5B,QAAA;;AAOF,IAAI,oBAAmC;AACvC,IAAI,gBAAgB;AACpB,IAAI,gBAAgB;AAEpB,SAAS,iBAAuB;AAC9B,KAAI,sBAAsB,KACxB,qBAAoB,KAAK,KAAK;;AAIlC,SAAS,gBAAsB;AAC7B,KAAI,sBAAsB,MAAM;AAC9B,mBAAiB,KAAK,KAAK,GAAG;AAC9B,sBAAoB;;;AAIxB,SAAS,eAAqB;AAC5B,KAAI,cAAe;AACnB,iBAAgB;AAGhB,QAAO,iBAAiB,kBAAkB;AACxC,iBAAe;AACf,MAAI,gBAAgB,EAClB,iBAAgB,oBAAoB,YAAY,EAAE,EAAE,IAAI,eAAe,CAAC;GAE1E;;;;;;AAaJ,SAAS,OAAa;AACpB,KAAI,OAAO,WAAW,eAAe,OAAO,aAAa,YAAa;AAEtE,eAAc;AAGT,eAAc,YAAY,CAAC;AAIhC,KAFyB,yBAAyB,KAEzB,WAAW;AAElC,qBAAmB;AACd,OAAK,eAAe,YAAY,CAAC;AACtC;;AAGF,KAAI,iBAAiB,EAAE;EACrB,MAAM,kBAAkB;AACtB,oBAAiB;IACf,gBAAgB;AACd,oBAAe;AACf,wBAAmB;AACd,UAAK,eAAe,YAAY,CAAC;;IAExC,cAAc;AACZ,kBAAa;;IAEhB,CAAC;;AAGJ,MAAI,OAAO,wBAAwB,WACjC,qBAAoB,WAAW,EAAE,SAAS,KAAO,CAAC;MAElD,YAAW,WAAW,KAAM;;;;;;AAQlC,SAAS,cAAoB;AACtB,MAAK,cAAc,YAAY,CAAC;AACrC,iBAAgB;;;;;AAMlB,SAAS,eAAqB;AAC5B,gBAAe;;;;;AAMjB,SAAS,UAAU,OAAoB;AAChC,MAAK,YAAY,YAAY,EAAE,EAAE,KAAK,OAAO,CAAC;;AAGrD,MAAa,YAAY;CACvB;CACA;CACA;CACA;CACD;;;;;;ACnJD,SAAgB,EACd,KACA,OACA,GAAG,UACuB;CAC1B,MAAM,KAAK,SAAS,cAAc,IAAI;AACtC,KAAI,MACF,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,CACxC,KAAI,MAAM,YAAa,IAAG,YAAY;KACjC,IAAG,aAAa,GAAG,EAAE;AAG9B,MAAK,MAAM,SAAS,SAClB,IAAG,OAAO,OAAO,UAAU,WAAW,SAAS,eAAe,MAAM,GAAG,MAAM;AAE/E,QAAO;;AAGT,SAAgB,UACd,OACA,SACA,OACA,UACA,WAAW,OACE;CACb,MAAM,SAAS,EAAE,UAAU,EAAE,WAAW,cAAc,CAAC;AACvD,KAAI,SAAU,QAAO,WAAW;AAChC,MAAK,MAAM,OAAO,SAAS;EACzB,MAAM,SAAS,EAAE,UAAU,EAAE,OAAO,KAAK,EAAE,IAAI;AAC/C,MAAI,QAAQ,MAAO,QAAO,WAAW;AACrC,SAAO,YAAY,OAAO;;AAE5B,QAAO,iBAAiB,gBAAgB,SAAS,OAAO,MAAM,CAAC;AAC/D,QAAO,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,OAAO;;AAG1E,SAAgB,SACd,OACA,OACA,UACA,WAAW,OACE;CACb,MAAM,QAAQ,EAAE,SAAS;EAAE,WAAW;EAAa;EAAO,CAAC;AAC3D,KAAI,SAAU,OAAM,WAAW;AAC/B,OAAM,iBAAiB,gBAAgB,SAAS,MAAM,MAAM,CAAC;AAC7D,QAAO,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM;;AAGzE,SAAgB,mBAAgC;AAC9C,QAAO,EAAE,OAAO,EAAE,WAAW,yBAAyB,EAAE,EAAE,kBAAkB,CAAC;;ACjC/E,MAAa,eAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACdtC,SAAS,yBACP,OACA,QACA,MACA,OACQ;CACR,MAAM,SAAS,SAAS,cAAc,SAAS;AAC/C,QAAO,QAAQ;AACf,QAAO,SAAS;CAChB,MAAM,MAAM,OAAO,WAAW,KAAK;AACnC,KAAI,CAAC,KAAK;EAER,MAAM,MAAM,kDAAkD,MAAM,YAAY,OAAO,gBAAgB,MAAM,WAAW,MAAM,YAAY,OAAO,uGAAuG,KAAK;AAC7P,SAAO,6BAA6B,KAAK,IAAI;;AAE/C,KAAI,YAAY;AAChB,KAAI,SAAS,GAAG,GAAG,OAAO,OAAO;AACjC,KAAI,YAAY;AAChB,KAAI,OAAO;AACX,KAAI,YAAY;AAChB,KAAI,eAAe;AACnB,KAAI,SAAS,MAAM,QAAQ,GAAG,SAAS,EAAE;AACzC,QAAO,OAAO,UAAU,YAAY;;AAGtC,MAAM,uBAAuB;CAC3B;EAAE,MAAM;EAAgB,OAAO;EAAW;CAC1C;EAAE,MAAM;EAAgB,OAAO;EAAW;CAC1C;EAAE,MAAM;EAAgB,OAAO;EAAW;CAC3C;AAED,IAAI,qBAAsC;AAE1C,SAAgB,8BAAwC;AACtD,KAAI,CAAC,mBACH,sBAAqB,qBAAqB,KAAK,MAC7C,yBAAyB,KAAK,KAAK,EAAE,MAAM,EAAE,MAAM,CACpD;AAEH,QAAO,CAAC,GAAG,mBAAmB;;;AAIhC,SAAgB,gBAA0B;CACxC,MAAM,SAAS,SAAS,MAAM,SAAS;AACvC,KAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,QAAO,6BAA6B;;AAKtC,MAAM,oBAAoB;;AAG1B,SAAgB,sBAAyB,MAA0B;AACjE,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,YAAY,yBAAyB;EAC3C,MAAM,aAAa;EAEnB,SAAS,UAAU;AACjB,gBAAa,MAAM;AACnB,UAAO,oBAAoB,WAAW,QAAQ;AAC9C,UAAO,oBAAoB,YAAY,cAAc;;EAGvD,MAAM,QAAQ,iBAAiB;AAC7B,YAAS;GAET,MAAM,OADe,CAAC,CAAC,SAAS,cAAc,aAAa,GAEvD,iDACA;AACJ,0BACE,IAAI,MACF,0CAA0C,KAAK,UAAU,oBAAoB,IAAK,KAAK,OACxF,CACF;KACA,kBAAkB;EAErB,MAAM,WAAW,MAAa;AAC5B,YAAS;AACT,WAAS,EAAkB,OAAY;;EAGzC,MAAM,sBAAsB;AAC1B,YAAS;AACT,0BAAO,IAAI,MAAM,4CAA4C,KAAK,GAAG,CAAC;;AAGxE,SAAO,iBAAiB,WAAW,QAAQ;AAC3C,SAAO,iBAAiB,YAAY,cAAc;AAClD,SAAO,cAAc,IAAI,YAAY,wBAAwB,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;GACnF;;;;;;;;AC3FJ,eAAsB,cAAc,MAAiD;AACnF,QAAO,SAAS,MAAM,YAAY;;AAGpC,eAAsB,qBAAqB,MAAqD;AAE9F,KADgB,SAAS,MAAM,YAAY,UAC3B,UAAW,QAAO;AAGlC,UAAS,MAAM,eAAe,GAAG,OAAO,WAAW,CAAC;AACpD,QAAO;;;AAWT,SAAgB,eACd,IACA,gBAIA;CACA,MAAM,WAAW;AAIjB,UAAS,sBAAsB,cAAc,eAAe;AAC5D,UAAS,6BAA6B,qBAAqB,eAAe;AAC1E,QAAO;;;AAIT,SAAgB,gBAAgB,MAAsB,QAAsB;AAE1E,KADe,SAAS,MAAM,YAAY,UAC3B,SACb,OAAM,IAAI,MACR,sBAAsB,OAAO,gBAAgB,KAAK,+CACnD;;;;;;;;ACpCL,eAAe,iBAA2D;CACxE,MAAM,SAAS,eAAe;AAC9B,QAAO;EAAE,IAAI,OAAO,YAAY;EAAE,SAAS,OAAO;EAAI;;AAGxD,eAAe,gBAA0D;AACvE,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,QAAQ,SAAS,cAAc,QAAQ;AAC7C,QAAM,OAAO;AACb,QAAM,SAAS;AACf,QAAM,UAAU;EAChB,IAAI,UAAU;AACd,QAAM,iBAAiB;AACrB,aAAU;GACV,MAAM,OAAO,MAAM,QAAQ;AAC3B,OAAI,CAAC,MAAM;AACT,2BAAO,IAAI,MAAM,mBAAmB,CAAC;AACrC;;GAEF,MAAM,SAAS,IAAI,YAAY;AAC/B,UAAO,eAAe,QAAQ;IAAE,IAAI,OAAO,YAAY;IAAE,SAAS,OAAO;IAAkB,CAAC;AAC5F,UAAO,gBAAgB,uBAAO,IAAI,MAAM,sBAAsB,CAAC;AAC/D,UAAO,cAAc,KAAK;;EAI5B,MAAM,gBAAgB;AACpB,oBAAiB;AACf,QAAI,CAAC,QAAS,wBAAO,IAAI,MAAM,wBAAwB,CAAC;AACxD,WAAO,oBAAoB,SAAS,QAAQ;MAC3C,IAAI;;AAET,SAAO,iBAAiB,SAAS,QAAQ;AACzC,QAAM,OAAO;GACb;;AAGJ,eAAe,mBAA6D;CAC1E,MAAM,UAAU,MAAM,sBAA8B,SAAS;AAC7D,QAAO;EAAE,IAAI,OAAO,YAAY;EAAE;EAAS;;AAG7C,MAAM,cAAc,OAAO,aAGqB;AAC9C,iBAAgB,UAAU,aAAa;CACvC,MAAM,OAAO,SAAS,MAAM,YAAY;AACxC,KAAI,SAAS,MAAO,QAAO,eAAe;AAC1C,KAAI,SAAS,SAAU,QAAO,kBAAkB;AAChD,QAAO,gBAAgB;;AAEC,eAAe,aAAa,SAAS;AAI/D,eAAe,qBACb,UACiD;AAEjD,QADe,eAAe,CAChB,MAAM,GAAG,SAAS,CAAC,KAAK,aAAa;EAAE,IAAI,OAAO,YAAY;EAAE;EAAS,EAAE;;AAG3F,eAAe,oBACb,UACiD;AACjD,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,QAAQ,SAAS,cAAc,QAAQ;AAC7C,QAAM,OAAO;AACb,QAAM,SAAS;AACf,QAAM,WAAW;EACjB,IAAI,UAAU;AACd,QAAM,WAAW,YAAY;AAC3B,aAAU;GACV,MAAM,QAAQ,MAAM,KAAK,MAAM,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,SAAS;AAC9D,OAAI,MAAM,WAAW,GAAG;AACtB,2BAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC;;AAcF,WAZgB,MAAM,QAAQ,IAC5B,MAAM,KACH,SACC,IAAI,SAA0C,KAAK,QAAQ;IACzD,MAAM,SAAS,IAAI,YAAY;AAC/B,WAAO,eACL,IAAI;KAAE,IAAI,OAAO,YAAY;KAAE,SAAS,OAAO;KAAkB,CAAC;AACpE,WAAO,gBAAgB,oBAAI,IAAI,MAAM,sBAAsB,CAAC;AAC5D,WAAO,cAAc,KAAK;KAC1B,CACL,CACF,CACe;;EAElB,MAAM,gBAAgB;AACpB,oBAAiB;AACf,QAAI,CAAC,QAAS,wBAAO,IAAI,MAAM,wBAAwB,CAAC;AACxD,WAAO,oBAAoB,SAAS,QAAQ;MAC3C,IAAI;;AAET,SAAO,iBAAiB,SAAS,QAAQ;AACzC,QAAM,OAAO;GACb;;AAGJ,eAAe,uBACb,UACiD;AAEjD,SADiB,MAAM,sBAAgC,SAAS,EAChD,MAAM,GAAG,SAAS,CAAC,KAAK,aAAa;EAAE,IAAI,OAAO,YAAY;EAAE;EAAS,EAAE;;AAG7F,MAAM,oBAAoB,OAAO,YAIsB;AACrD,iBAAgB,UAAU,mBAAmB;CAC7C,MAAM,WAAW,SAAS,YAAY;CACtC,MAAM,OAAO,SAAS,MAAM,YAAY;AACxC,KAAI,SAAS,MAAO,QAAO,oBAAoB,SAAS;AACxD,KAAI,SAAS,SAAU,QAAO,uBAAuB,SAAS;AAC9D,QAAO,qBAAqB,SAAS;;AAEP,eAAe,mBAAmB,SAAS;AAiB3E,eAAe,oBACb,UACA,OAC8B;AAE9B,QADe,eAAe,CAE3B,MAAM,GAAG,SAAS,CAClB,aAAa,MAAM,SAAS,QAAQ,CAAC,CACrC,KAAK,aAAa;EAAE,IAAI,OAAO,YAAY;EAAE;EAAS,MAAM;EAA0B,EAAE;;AAG7F,eAAe,mBACb,UACA,OAC8B;AAC9B,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,QAAQ,SAAS,cAAc,QAAQ;AAC7C,QAAM,OAAO;AACb,QAAM,SAAS,MAAM,SAAS,QAAQ,GAAG,oBAAoB;AAC7D,QAAM,WAAW;EACjB,IAAI,UAAU;AACd,QAAM,WAAW,YAAY;AAC3B,aAAU;GACV,MAAM,QAAQ,MAAM,KAAK,MAAM,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,SAAS;AAC9D,OAAI,MAAM,WAAW,GAAG;AACtB,YAAQ,EAAE,CAAC;AACX;;AAeF,WAbgB,MAAM,QAAQ,IAC5B,MAAM,KACH,SACC,IAAI,SAA4B,KAAK,QAAQ;IAC3C,MAAM,WAA0B,KAAK,KAAK,WAAW,SAAS,GAAG,UAAU;IAC3E,MAAM,SAAS,IAAI,YAAY;AAC/B,WAAO,eACL,IAAI;KAAE,IAAI,OAAO,YAAY;KAAE,SAAS,OAAO;KAAkB,MAAM;KAAU,CAAC;AACpF,WAAO,gBAAgB,oBAAI,IAAI,MAAM,sBAAsB,CAAC;AAC5D,WAAO,cAAc,KAAK;KAC1B,CACL,CACF,CACe;;EAElB,MAAM,gBAAgB;AACpB,oBAAiB;AACf,QAAI,CAAC,QAAS,SAAQ,EAAE,CAAC;AACzB,WAAO,oBAAoB,SAAS,QAAQ;MAC3C,IAAI;;AAET,SAAO,iBAAiB,SAAS,QAAQ;AACzC,QAAM,OAAO;GACb;;AAGJ,eAAe,sBAAsB,UAAgD;AAEnF,SADiB,MAAM,sBAAgC,SAAS,EAE7D,MAAM,GAAG,SAAS,CAClB,KAAK,aAAa;EAAE,IAAI,OAAO,YAAY;EAAE;EAAS,MAAM;EAA0B,EAAE;;AAG7F,MAAM,mBAAmB,OAAO,YAAmE;AACjG,iBAAgB,UAAU,kBAAkB;CAC5C,MAAM,WAAW,SAAS,YAAY;CACtC,MAAM,QAAQ,SAAS,SAAS,CAAC,QAAQ;CACzC,MAAM,OAAO,SAAS,MAAM,YAAY;AACxC,KAAI,SAAS,MAAO,QAAO,mBAAmB,UAAU,MAAM;AAC9D,KAAI,SAAS,SAAU,QAAO,sBAAsB,SAAS;AAC7D,QAAO,oBAAoB,UAAU,MAAM;;AAEd,eAAe,kBAAkB,SAAS;;;;;;;ACzNzE,MAAM,oBAAoB,YAA6B;AACrD,iBAAgB,aAAa,mBAAmB;AAEhD,KADa,SAAS,MAAM,YAAY,cAC3B,OAAQ,QAAO,SAAS,MAAM,SAAS;AAEpD,KAAI;AACF,SAAO,MAAM,UAAU,UAAU,UAAU;SACrC;AACN,SAAO;;;AAGqB,eAAe,mBAAmB,YAAY;AAE9E,MAAM,oBAAoB,OAAO,SAAgC;AAC/D,iBAAgB,aAAa,mBAAmB;AAEhD,KADa,SAAS,MAAM,YAAY,cAC3B,QAAQ;AACnB,WAAS,MAAM,YAAY,EAAE,eAAe,MAAM,CAAC;AACnD;;AAGF,OAAM,UAAU,UAAU,UAAU,KAAK;;AAEX,eAAe,mBAAmB,YAAY;;;;;;ACxB9E,MAAM,iBAAiB,OAAO,YAIxB;AACJ,iBAAgB,YAAY,gBAAgB;CAC5C,IAAI,WAAW,SAAS,MAAM;AAC9B,KAAI,QAAQ,OAAO,UAAU;EAC3B,MAAM,IAAI,QAAQ,MAAM,SAAS,aAAa;AAC9C,aAAW,SAAS,QACjB,MAAM,EAAE,KAAK,aAAa,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,SAAS,EAAE,CACrE;;CAEH,MAAM,SAAS,SAAS,MAAM,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,KAAK;CAC5E,MAAM,aAAa,QAAQ,SAAS,QAAQ;AAC5C,QAAO;EACL,QAAQ;EACR,YAAY,aAAa,SAAS,SAAS,aAAa;EACxD,MAAM,cAAc,SAAS;EAC9B;;AAE0B,eAAe,gBAAgB,WAAW;;;;;;;;;;;;;;;;ACXvE,MAAa,yBAAqE;CAChF,UAAU;CACV,KAAK;CACL,YAAY;CACZ,YAAY;CACZ,WAAW;CACX,aAAa;CACb,SAAS;EAAC;EAAI;EAAI;EAAG;CACrB,OAAO;EAAC;EAAI;EAAI;EAAG;CACnB,QAAQ;EAAC;EAAI;EAAI;EAAI;EAAI;EAAG;CAC5B,UAAU;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAG;CACvC;AAED,eAAsB,uBAAuB,SAAsD;CACjG,MAAM,YAAY,KAAK,KAAK;AAC5B,UAAS,aAAa;EAAE,MAAM;EAAU,QAAQ,EAAE,YAAY,QAAQ,MAAM;EAAE,CAAC;CAE/E,MAAM,UAAU,uBAAuB,QAAQ,SAAS;CACxD,MAAM,WAAW,OAAO,UAAU,YAAY,aAAa,UAAU,QAAQ,QAAQ,GAAG;AAExF,UAAS,WAAW;EAClB,QAAQ;EACR,MAAM,CAAC,EAAE,MAAM,QAAQ,MAAM,CAAC;EAC9B;EACA,QAAQ;EACR,QAAQ;GAAE,YAAY,QAAQ;GAAM;GAAU;EAC9C,UAAU;EACX,CAAC;;;;;;;;ACvBJ,SAAS,gBAA8B;AACrC,QAAO;EACL,QAAQ,EAAE,GAAG,SAAS,MAAM,SAAS,QAAQ;EAC7C,WAAW,KAAK,KAAK;EACrB,gBAAgB,SAAS,MAAM,SAAS;EACzC;;AAKH,eAAe,yBAAgD;AAC7D,QAAO,eAAe;;AAGxB,eAAe,wBAA+C;AAC5D,QAAO,IAAI,SAAS,YAAY;AAC9B,MAAI,CAAC,UAAU,aAAa;AAC1B,WAAQ,KAAK,yEAAyE;AACtF,WAAQ,eAAe,CAAC;AACxB;;AAEF,YAAU,YAAY,oBACnB,QAAQ;AACP,WAAQ;IACN,QAAQ;KACN,UAAU,IAAI,OAAO;KACrB,WAAW,IAAI,OAAO;KACtB,UAAU,IAAI,OAAO,YAAY;KACjC,UAAU,IAAI,OAAO;KACrB,kBAAkB,IAAI,OAAO,oBAAoB;KACjD,SAAS,IAAI,OAAO,WAAW;KAChC;IACD,WAAW,IAAI;IACf,gBAAgB;IACjB,CAAC;WAEE;AACJ,WAAQ,KAAK,8DAA8D;AAC3E,WAAQ,eAAe,CAAC;IAE3B;GACD;;AAGJ,eAAe,2BAAkD;AAC/D,QAAO,sBAAoC,WAAW;;AAGxD,MAAM,sBAAsB,OAAO,aAA6D;AAC9F,iBAAgB,eAAe,qBAAqB;CACpD,MAAM,OAAO,SAAS,MAAM,YAAY;AACxC,KAAI,SAAS,MAAO,QAAO,uBAAuB;AAClD,KAAI,SAAS,SAAU,QAAO,0BAA0B;AACxD,QAAO,wBAAwB;;AAEC,eAAe,qBAAqB,cAAc;AAUpF,SAAS,wBAAwB,aAAyD;CACxF,MAAM,EAAE,SAAS,YAAY;CAC7B,MAAM,WAAW,KAAK,IAAI,QAAQ,cAAc,IAAI;CACpD,MAAM,KAAK,kBAAkB;EAC3B,MAAM,MAAM,eAAe;AAC3B,MAAI,OAAO,aAAa,KAAK,QAAQ,GAAG,MAAO;AAC/C,MAAI,OAAO,cAAc,KAAK,QAAQ,GAAG,MAAO;AAChD,UAAQ,IAAI;IACX,SAAS;AACZ,cAAa,cAAc,GAAG;;AAGhC,SAAS,uBAAuB,aAAyD;CACvF,MAAM,EAAE,SAAS,YAAY;AAC7B,KAAI,CAAC,UAAU,aAAa;AAC1B,UAAQ,KAAK,yEAAyE;AACtF,SAAO,wBAAwB,YAAY;;CAE7C,MAAM,UAAU,UAAU,YAAY,eACnC,QAAQ;AACP,UAAQ;GACN,QAAQ;IACN,UAAU,IAAI,OAAO;IACrB,WAAW,IAAI,OAAO;IACtB,UAAU,IAAI,OAAO,YAAY;IACjC,UAAU,IAAI,OAAO;IACrB,kBAAkB,IAAI,OAAO,oBAAoB;IACjD,SAAS,IAAI,OAAO,WAAW;IAChC;GACD,WAAW,IAAI;GACf,gBAAgB;GACjB,CAAC;KAEH,QAAQ,QAAQ,IAAI,CACtB;AACD,cAAa,UAAU,YAAY,WAAW,QAAQ;;AAGxD,SAAS,0BAA0B,aAAyD;CAC1F,MAAM,EAAE,YAAY;CACpB,MAAM,WAAW,MAAa;AAC5B,UAAS,EAAkB,OAAuB;;AAEpD,QAAO,iBAAiB,yCAAyC,QAAQ;AACzE,QAAO,cACL,IAAI,YAAY,wBAAwB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,EAAE,CAAC,CACjF;AACD,cAAa,OAAO,oBAAoB,yCAAyC,QAAQ;;AAG3F,MAAM,wBAAwB,gBAA8D;CAC1F,MAAM,OAAO,SAAS,MAAM,YAAY;AACxC,KAAI,SAAS,MAAO,QAAO,uBAAuB,YAAY;AAC9D,KAAI,SAAS,SAAU,QAAO,0BAA0B,YAAY;AACpE,QAAO,wBAAwB,YAAY;;AAEV,eAAe,sBAAsB,cAAc;;;;;;;;;;;;;;;;;AC7HtF,MAAM,aAAa;;;;;;AAOnB,MAAM,sCAAsB,IAAI,IAAY,EAE3C,CAAC;AAEF,SAAgB,gBACd,YACA,iBACG;AACH,QAAO,IAAI,MAAM,iBAAiB,EAChC,IAAI,QAAQ,MAAM;AAGhB,MAAI,OAAO,SAAS,SAAU,QAAO,KAAA;AACrC,MAAI,QAAQ,OAAQ,QAAO,OAAO;EAElC,MAAM,OAAO,OAAO,KAAK;AAGzB,MAAI,oBAAoB,IAAI,KAAK,CAC/B,SAAQ,GAAG,SAA+B;AACxC,WAAQ,KACN,sBAAsB,WAAW,GAAG,KAAK,2FACiB,aAC3D;AACD,YAAS,WAAW;IAClB,QAAQ,GAAG,WAAW,GAAG;IACnB;IACN,WAAW,KAAK,KAAK;IACrB,QAAQ;IACR,QAAQ,KAAA;IACR,UAAU;IACX,CAAC;;AAKN,QAAM,IAAI,MACR,sBAAsB,WAAW,GAAG,KAAK,qIAGd,aAC5B;IAEJ,CAAC;;AC5DmB,gBAAgB,WAAW;CAChD,SAAS,OAAO,QAAwC;AACtD,SAAO,aAAa,QAAQ,iBAAiB,MAAM;;CAErD,SAAS,OAAO,KAAa,UAAiC;AAC5D,eAAa,QAAQ,iBAAiB,OAAO,MAAM;;CAErD,YAAY,OAAO,QAA+B;AAChD,eAAa,WAAW,iBAAiB,MAAM;;CAEjD,YAAY,YAA2B;EACrC,MAAM,OAAO,OAAO,KAAK,aAAa,CAAC,QAAQ,MAAM,EAAE,WAAW,iBAAiB,CAAC;AACpF,OAAK,MAAM,KAAK,KACd,cAAa,WAAW,EAAE;;CAG/B,CAAC;;;ACTF,IAAI,gBAAsC;AAE1C,IAAIA,uBAAiC;AAErC,SAAgB,sBAAsB,IAAgB;AACpD,kBAAe;;AAIjB,IAAI,OAAO,WAAW,YACpB,QAAO,iBAAiB,yBAAyB,MAAa;AAE5D,iBAAgB,EAAE,MADF,EAAkB,OACH,MAAM;AAErC,QAAO,cAAc,IAAI,YAAY,0BAA0B,EAAE,QAAQ,EAAE,KAAK,UAAU,EAAE,CAAC,CAAC;EAC9F;AAGJ,SAAS,cAAc,MAAc,MAAe;AAClD,QAAO,cAAc,IAAI,YAAY,yBAAyB,QAAQ,EAAE,QAAQ,MAAM,CAAC,CAAC;AACxF,iBAAgB;AAChB,iBAAc;;AAGhB,SAAS,qBAAyC;AAChD,KAAI,CAAC,cAAe,QAAO;CAE3B,MAAM,SAAS,EAAE,OAAO,EAAE,WAAW,qBAAqB,CAAC;AAE3D,KAAI,cAAc,SAAS,UAAU;AACnC,SAAO,OAAO,EAAE,OAAO,EAAE,WAAW,oBAAoB,EAAE,EAAE,6BAA6B,CAAC,CAAC;EAC3F,MAAM,QAAQ,EAAE,SAAS;GACvB,MAAM;GACN,QAAQ;GACR,OAAO;GACR,CAAC;AACF,QAAM,iBAAiB,gBAAgB;GACrC,MAAM,OAAQ,MAA2B,QAAQ;AACjD,OAAI,CAAC,KAAM;GACX,MAAM,SAAS,IAAI,YAAY;AAC/B,UAAO,eAAe,cAAc,UAAU,OAAO,OAAiB;AACtE,UAAO,cAAc,KAAK;IAC1B;AACF,SAAO,YAAY,MAAM;YAChB,cAAc,SAAS,UAAU;AAC1C,SAAO,OAAO,EAAE,OAAO,EAAE,WAAW,oBAAoB,EAAE,EAAE,6BAA6B,CAAC,CAAC;EAC3F,MAAM,QAAQ,EAAE,SAAS;GACvB,MAAM;GACN,QAAQ;GACR,UAAU;GACV,OAAO;GACR,CAAC;AACF,QAAM,iBAAiB,gBAAgB;GACrC,MAAM,QAAQ,MAAM,KAAM,MAA2B,SAAS,EAAE,CAAC;AACjE,OAAI,MAAM,WAAW,EAAG;AACxB,WAAQ,IACN,MAAM,KACH,SACC,IAAI,SAAiB,QAAQ;IAC3B,MAAM,SAAS,IAAI,YAAY;AAC/B,WAAO,eAAe,IAAI,OAAO,OAAiB;AAClD,WAAO,cAAc,KAAK;KAC1B,CACL,CACF,CAAC,MAAM,aAAa,cAAc,UAAU,SAAS,CAAC;IACvD;AACF,SAAO,YAAY,MAAM;YAChB,cAAc,SAAS,cAAc,cAAc,SAAS,mBAAmB;AACxF,SAAO,OACL,EACE,OACA,EAAE,WAAW,oBAAoB,EACjC,cAAc,SAAS,aACnB,EAAE,+BAA+B,GACjC,EAAE,qCAAqC,CAC5C,CACF;EACD,MAAM,WAAW,EAAE,SAAS;GAC1B,WAAW;GACX,OAAO,OAAO,SAAS,MAAM,SAAS,OAAO,SAAS;GACtD,OAAO;GACR,CAAC;EACF,MAAM,WAAW,EAAE,SAAS;GAC1B,WAAW;GACX,OAAO,OAAO,SAAS,MAAM,SAAS,OAAO,UAAU;GACvD,OAAO;GACR,CAAC;EACF,MAAM,UAAU,EAAE,UAAU,EAAE,WAAW,sBAAsB,EAAE,EAAE,qBAAqB,CAAC;AACzF,UAAQ,iBAAiB,eAAe;GACtC,MAAM,MAAM;IACV,QAAQ;KACN,UAAU,OAAQ,SAA8B,MAAM;KACtD,WAAW,OAAQ,SAA8B,MAAM;KACvD,UAAU;KACV,UAAU;KACV,kBAAkB;KAClB,SAAS;KACV;IACD,WAAW,KAAK,KAAK;IACrB,gBAAgB;IACjB;AACD,iBAAc,cAAe,MAAM,IAAI;IACvC;AACF,SAAO,OACL,EACE,OACA,EAAE,WAAW,wBAAwB,EACrC,EAAE,SAAS,EAAE,EAAE,EAAE,0BAA0B,CAAC,EAC5C,UACA,EAAE,SAAS,EAAE,EAAE,EAAE,0BAA0B,CAAC,EAC5C,UACA,QACD,CACF;OAGD,QAAO,OACL,EACE,OACA,EAAE,WAAW,oBAAoB,EACjC,EAAE,+BAA+B,EAAE,MAAM,cAAc,MAAM,CAAC,CAC/D,CACF;CAIH,MAAM,YAAY,EAChB,UACA;EAAE,WAAW;EAAqC,OAAO;EAAkB,EAC3E,EAAE,uBAAuB,CAC1B;AACD,WAAU,iBAAiB,eAAe;AACxC,kBAAgB;AAChB,SAAO,cAAc,IAAI,YAAY,sBAAsB,CAAC;AAC5D,kBAAc;GACd;AACF,QAAO,YAAY,UAAU;AAE7B,QAAO;;AAGT,SAAgB,kBAA+B;CAC7C,MAAM,IAAI,SAAS;CACnB,MAAM,WAAW,CAAC,EAAE;CACpB,MAAM,YAAY,EAAE,MAAM;AAE1B,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;AAGvD,KAAI,EAAE,eAAe;EACnB,MAAM,eAAe,oBAAoB;AACzC,MAAI,aAAc,WAAU,YAAY,aAAa;;AAgBvD,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,uBAAuB,CAAC,EACvE,GAbC;EACH;GAAE,UAAU;GAAqB,KAAK;GAAU,SAAS;IAAC;IAAQ;IAAO;IAAS;GAAE;EACpF;GAAE,UAAU;GAAqB,KAAK;GAAU,SAAS;IAAC;IAAQ;IAAO;IAAS;GAAE;EACpF;GAAE,UAAU;GAAuB,KAAK;GAAY,SAAS;IAAC;IAAQ;IAAO;IAAS;GAAE;EACxF;GAAE,UAAU;GAAsB,KAAK;GAAW,SAAS,CAAC,QAAQ,MAAM;GAAE;EAC5E;GAAE,UAAU;GAAwB,KAAK;GAAa,SAAS,CAAC,QAAQ,MAAM;GAAE;EACjF,CAOkB,KAAK,UAClB,UACE,EAAE,MAAM,SAAS,EACjB,MAAM,SACN,EAAE,YAAY,MAAM,OACnB,MAAM;AACL,WAAS,MAAM,eAAe,GAAG,MAAM,MAAM,GAAG,CAAkC;IAEpF,SACD,CACF,CACF,CACF;CAGD,MAAM,SAAS,EAAE,SAAS;CAC1B,MAAM,YAAY,EAAE,OAAO,EAAE,WAAW,kBAAkB,CAAC;AAC3D,QAAO,SAAS,SAAS,QAAQ;EAC/B,MAAM,QAAQ,EAAE,OAAO,EAAE,WAAW,mBAAmB,CAAC;EACxD,MAAM,MAAM,EAAE,OAAO,EAAE,KAAK,SAAS,CAAC;EACtC,MAAM,YAAY,EAAE,UAAU,EAAE,WAAW,oBAAoB,EAAE,IAAI;AACrE,YAAU,iBAAiB,eAAe;GACxC,MAAM,YAAY,CAAC,GAAG,SAAS,MAAM,SAAS,OAAO;AACrD,aAAU,OAAO,KAAK,EAAE;AACxB,YAAS,MAAM,YAAY,EAAE,QAAQ,WAAW,CAAC;IACjD;AACF,MAAI,SAAU,WAAU,WAAW;AACnC,QAAM,OAAO,KAAK,UAAU;AAC5B,YAAU,YAAY,MAAM;GAC5B;CAEF,MAAM,SAAS,EAAE,UAAU,EAAE,WAAW,qBAAqB,EAAE,EAAE,iBAAiB,CAAC;AACnF,QAAO,iBAAiB,eAAe;EACrC,MAAM,QAAQ,SAAS,cAAc,QAAQ;AAC7C,QAAM,OAAO;AACb,QAAM,SAAS;AACf,QAAM,WAAW;AACjB,QAAM,iBAAiB;GACrB,MAAM,QAAQ,MAAM,KAAK,MAAM,SAAS,EAAE,CAAC;AAC3C,WAAQ,IACN,MAAM,KACH,SACC,IAAI,SAAiB,QAAQ;IAC3B,MAAM,SAAS,IAAI,YAAY;AAC/B,WAAO,eAAe,IAAI,OAAO,OAAiB;AAClD,WAAO,cAAc,KAAK;KAC1B,CACL,CACF,CAAC,MAAM,aAAa;AACnB,aAAS,MAAM,YAAY,EAAE,QAAQ,CAAC,GAAG,SAAS,MAAM,SAAS,QAAQ,GAAG,SAAS,EAAE,CAAC;KACxF;;AAEJ,QAAM,OAAO;GACb;AACF,KAAI,SAAU,QAAO,WAAW;CAEhC,MAAM,cAAc,EAAE,UAAU,EAAE,WAAW,qBAAqB,EAAE,EAAE,yBAAyB,CAAC;AAChG,aAAY,iBAAiB,eAAe;AAC1C,WAAS,MAAM,YAAY,EAAE,QAAQ,CAAC,GAAG,6BAA6B,CAAC,EAAE,CAAC;GAC1E;AACF,KAAI,SAAU,aAAY,WAAW;CAErC,MAAM,iBAAiB,EAAE,UAAU,EAAE,WAAW,qBAAqB,EAAE,EAAE,mBAAmB,CAAC;AAC7F,gBAAe,iBAAiB,eAAe;AAC7C,WAAS,MAAM,YAAY,EAAE,QAAQ,EAAE,EAAE,CAAC;GAC1C;AACF,KAAI,SAAU,gBAAe,WAAW;AAExC,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EACE,OACA,EAAE,WAAW,qBAAqB,EAClC,EAAE,6BAA6B,EAAE,OAAO,OAAO,QAAQ,CAAC,CACzD,EACD,WACA,EAAE,OAAO,EAAE,WAAW,eAAe,EAAE,QAAQ,aAAa,eAAe,CAC5E,CACF;AAGD,WAAU,YAAY,qBAAqB,CAAC;AAE5C,QAAO;;AAGT,SAAS,sBAAmC;CAI1C,MAAM,aADY,CAAC,GADP,SAAS,MAAM,WACD,CAAC,SAAS,CAAC,MAAM,MAAM,EAAE,WAAW,yBAAyB,EACzD;CAE9B,MAAM,gBAAgB,aAClB,GAAG,WAAW,WAAW,cAAc,OAAO,WAAW,SAAS,CAAC,KACnE,EAAE,wBAAwB;CAE9B,MAAM,cAAc,EAClB,OACA,EAAE,WAAW,WAAW,EACxB,EAAE,SAAS,EAAE,EAAE,EAAE,yBAAyB,CAAC,EAC3C,EAAE,QAAQ,EAAE,WAAW,aAAa,EAAE,cAAc,CACrD;CAED,MAAM,cAAc,OAAO,KAAK,uBAAuB;CACvD,MAAM,iBAAiB,EACrB,OACA,EAAE,WAAW,wBAAwB,EACrC,EAAE,wBAAwB,CAC3B;CACD,MAAM,SAAS,EACb,OACA,EAAE,WAAW,eAAe,EAC5B,GAAG,YAAY,KAAK,SAAS;EAC3B,MAAM,MAAM,EACV,UACA;GAAE,WAAW;GAAqB,eAAe,UAAU,KAAK;GAAO,EACvE,KACD;AACD,MAAI,iBAAiB,eAAe;AAC7B,0BAAuB,EAAE,MAAM,CAAC,CAAC,WAAW;AAC/C,oBAAc;KACd;IACF;AACF,SAAO;GACP,CACH;AAED,QAAO,EACL,OACA;EAAE,WAAW;EAAe,eAAe;EAAkB,EAC7D,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,wBAAwB,CAAC,EACxE,aACA,gBACA,OACD;;;;;;;;;;;;AC5SH,SAAgB,QACd,SACA,UACA,IAC6B;AAC7B,SAAQ,GAAG,SAAyB;EAClC,MAAM,YAAY,KAAK,KAAK;EAG5B,MAAM,WAAsB,KAAK,KAAK,MAAM,cAAc,EAAE,CAAC;EAE7D,MAAM,SAAS,GAAG,GAAG,KAAK;AAE1B,MAAI,kBAAkB,SAAS;AAE7B,YAAS,WAAW;IAClB,QAAQ;IACR,MAAM;IACN;IACA,QAAQ;IACR;IACD,CAAC;AAGD,UAA4B,MAC1B,UAAU;AACT,aAAS,WAAW;KAClB,QAAQ;KACR,MAAM;KACN;KACA,QAAQ;KACR,QAAQ,cAAc,MAAM;KAC5B;KACD,CAAC;OAEH,QAAiB;AAChB,aAAS,WAAW;KAClB,QAAQ;KACR,MAAM;KACN;KACA,QAAQ;KACR,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;KACvD;KACD,CAAC;KAEL;AAED,UAAO;;AAIT,WAAS,WAAW;GAClB,QAAQ;GACR,MAAM;GACN;GACA,QAAQ;GACR,QAAQ,cAAc,OAAO;GAC7B;GACD,CAAC;AAEF,SAAO;;;;;;;;;AAUX,SAAS,cAAc,OAAyB;AAC9C,KAAI,UAAU,QAAQ,UAAU,KAAA,EAAW,QAAO;AAClD,KAAI,OAAO,UAAU,WAAY,QAAO,cAAc,MAAM,QAAQ,YAAY;AAChF,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI;AACF,SAAO,KAAK,MAAM,KAAK,UAAU,MAAM,CAAC;SAClC;AACN,SAAO;;;;;;;;;;;;;;;ACpFX,SAAS,gBACP,IACoC;AACnC,IAA0C,oBAAoB;AAC/D,QAAO;;AAMT,MAAM,gCAAgB,IAAI,KAA0B;AAEpD,IAAI,eAAe;AACnB,SAAS,YAAY,WAA2B;AAC9C,iBAAgB;AAChB,QAAO,aAAa,UAAU,GAAG;;AAWnC,MAAa,cAAc,gBAAgB,eAAe;CACxD,qBAAqB,gBACnB,QACE,mCACA,aACC,SAImB;AAClB,mBAAiB;AACf,OAAI,SAAS,MAAM,IAAI,aAAa;AAClC,SAAK,wBAAQ,IAAI,MAAM,UAAU,CAAC;AAClC;;AAEF,YAAS,MAAM,OAAO,EAAE,UAAU,MAAM,CAAC;AACzC,QAAK,QAAQ;IAAE,MAAM;IAAU,MAAM,EAAE,WAAW,KAAK,SAAS,WAAW;IAAE,CAAC;KAC7E,IAAI;AACP,eAAa;GAEhB,CACF;CAED,qBAAqB,gBACnB,QACE,mCACA,aACC,SAImB;AAClB,MAAI,CAAC,SAAS,MAAM,IAAI,UAAU;AAChC,QAAK,wBAAQ,IAAI,MAAM,gBAAgB,CAAC;AACxC,gBAAa;;AAEf,mBAAiB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC,EAAE,GAAG;AACzD,mBAAiB,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC,EAAE,IAAI;AACrD,mBAAiB,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC,EAAE,IAAI;AAC3D,mBAAiB;GACf,MAAM,EAAE,gBAAgB,iBAAiB,SAAS,MAAM;AACxD,QAAK,QAAQ;IACX,MAAM;IACN,MAAM;KAAE,UAAU;KAAgB,YAAY;KAAc;IAC7D,CAAC;KACD,IAAK;AACR,mBAAiB;AACf,QAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AACnC,YAAS,MAAM,OAAO,EAAE,UAAU,OAAO,CAAC;KACzC,KAAK;AACR,eAAa;GAEhB,CACF;CAED,yBAAyB,gBACvB,QACE,uCACA,YACA,OAAO,aAAuD,SAAS,MAAM,IAAI,SAClF,CACF;CACF,CAAC;AAIF,MAAa,UAAU,gBAAgB,WAAW;CAChD,YAAY,gBACV,QACE,sBACA,YACC,YAEW;AAEV,MAAI,SAAS,MAAM,IAAI,aAAa;AAClC,WAAQ,WAAW,yCAAyB,IAAI,MAAM,UAAU,CAAC;AACjE;;AAEF,UAAQ,WAAW,iBAAiB;GAEvC,CACF;CAED,QAAQ,gBACN,QACE,kBACA,YACC,YAAoB,QAA8B,aAA6B;EAC9E,MAAM,KAAK,OAAO,WAAW,WAAW,SAAS,cAAc,OAAO,GAAG;AACzE,MAAI,IAAI;GACN,MAAM,cAAc,SAAS,cAAc,MAAM;AACjD,eAAY,MAAM,UAChB;AACF,eAAY,cAAc;AAC1B,MAAG,YAAY,YAAY;;GAGhC,CACF;CAED,cAAc,gBACZ,QACE,wBACA,aAEE,WACA,QACA,YAsC4B;EAC5B,MAAM,KAAK,OAAO,WAAW,WAAW,SAAS,cAAc,OAAO,GAAG;EACzE,MAAM,SAAS,YAAY,UAAU;EAErC,MAAM,cAAc,SAAS,cAAc,MAAM;EAGjD,MAAM,QAAQ,SAAS,SAAS;EAChC,MAAM,UAAU,SAAS,WAAW;EACpC,MAAM,SACJ,UAAU,UACT,UAAU,UACT,OAAO,WAAW,eAClB,OAAO,aAAa,+BAA+B,CAAC;EACxD,MAAM,KAAK,SAAS,YAAY;EAChC,MAAM,YAAY,SAAS,SAAS;EACpC,MAAM,cAAc,SAAS,SAAS;EACtC,MAAM,SAAS,YAAY,aAAa,UAAU;AAElD,cAAY,QAAQ,YAAY;AAChC,cAAY,MAAM,UAAU,cAAc,GAAG,qBAAqB,YAAY,4CAA4C,UAAU,6BAA6B,OAAO;AACxK,cAAY,cAAc,iCAAiC,QAAQ;AAEnE,MAAI,IAAI;AACN,MAAG,YAAY,YAAY;AAC3B,iBAAc,IAAI,QAAQ,YAAY;;EAGxC,MAAM,oBAAoB;GACxB,MAAM,aAAa,cAAc,IAAI,OAAO;AAC5C,OAAI,YAAY;AACd,eAAW,QAAQ;AACnB,kBAAc,OAAO,OAAO;;;AAKhC,mBAAiB;AACf,OAAI,SAAS,MAAM,IAAI,aAAa;AAClC,aAAS,WAAW,WAAW;KAC7B;KACA;KACA,YAAY,EAAE;KACf,CAAC;AACF,aAAS,WAAW,qBAAqB;KACvC;KACA;KACA,YAAY,EAAE;KACd,OAAO;MAAE,MAAM;MAAG,SAAS;MAAW;KACvC,CAAC;AACF;;GAGF,MAAM,eAAe;IACnB;IACA;IACA,YAAY;KAAE,YAAY,iBAAiB;KAAU,WAAW,YAAY;KAAU;IACvF;AACD,YAAS,WAAW,eAAe,aAAa;AAChD,YAAS,WAAW,iBAAiB,aAAa;KACjD,IAAI;AAEP,SAAO,EAAE,SAAS,aAAa;GAElC,CACF;CAED,SAAS,gBACP,QAAQ,mBAAmB,aAAa,WAAyB;EAC/D,MAAM,KAAK,cAAc,IAAI,OAAO;AACpC,MAAI,IAAI;AACN,MAAG,QAAQ;AACX,iBAAc,OAAO,OAAO;;GAE9B,CACH;CAED,YAAY,gBACV,QAAQ,sBAAsB,kBAAwB;AACpD,OAAK,MAAM,MAAM,cAAc,QAAQ,CACrC,IAAG,QAAQ;AAEb,gBAAc,OAAO;GACrB,CACH;CACF,CAAC;AAIF,MAAa,mBAAmB,gBAC9B,QACE,oBACA,aACC,SAImB;AAClB,kBAAiB;AACf,MAAI,SAAS,MAAM,IAAI,aAAa;AAClC,QAAK,wBAAQ,IAAI,MAAM,UAAU,CAAC;AAClC;;AAEF,WAAS,MAAM,OAAO,EAAE,UAAU,MAAM,CAAC;AACzC,OAAK,QAAQ;GAAE,MAAM;GAAU,MAAM,EAAE,WAAW,KAAK,SAAS,WAAW;GAAE,CAAC;IAC7E,IAAI;AACP,cAAa;EAEhB,CACF;AAED,MAAa,mBAAmB,gBAC9B,QACE,oBACA,aACC,SAImB;AAClB,KAAI,CAAC,SAAS,MAAM,IAAI,UAAU;AAChC,OAAK,wBAAQ,IAAI,MAAM,gBAAgB,CAAC;AACxC,eAAa;;AAEf,kBAAiB,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC,EAAE,IAAI;AACrD,kBAAiB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC,EAAE,KAAK;AAC3D,cAAa;EAEhB,CACF;;;ACvTD,SAAS,YAAY,MAAc;AACjC,UAAS,MAAM,OAAO,EAAE,WAAW;EAAE;EAAM,WAAW,KAAK,KAAK;EAAE,EAAE,CAAC;;AAGvE,SAAS,YAAY,SAAiB;AACpC,aAAY,UAAU,UAAU;;AAGlC,SAAS,UAAU,OAAe,OAA4B;AAC5D,QAAO,EACL,OACA,EAAE,WAAW,WAAW,EACxB,EAAE,SAAS,EAAE,EAAE,MAAM,EACrB,EAAE,QAAQ,EAAE,OAAO,iEAAiE,EAAE,MAAM,CAC7F;;AAGH,SAAS,gBAA6B;CACpC,MAAM,OAAO,SAAS,MAAM,IAAI;AAChC,KAAI,CAAC,KACH,QAAO,EACL,OACA,EAAE,WAAW,iBAAiB,EAC9B,EAAE,QAAQ,EAAE,OAAO,cAAc,EAAE,EAAE,mBAAmB,CAAC,CAC1D;CAEH,MAAM,OAAO,IAAI,KAAK,KAAK,UAAU,CAAC,oBAAoB;AAE1D,QAAO,EACL,OACA,EAAE,WAAW,iBAAiB,EAC9B,EAAE,QAAQ;EAAE,WAAW;EAAgB,OAJzB,KAAK,KAAK,WAAW,SAAS,GAIY,kBAAkB;EAAI,EAAE,KAAK,KAAK,EAC1F,EAAE,QAAQ,EAAE,WAAW,gBAAgB,EAAE,KAAK,CAC/C;;AAGH,SAAS,UACP,OACA,QACA,QACA,UACa;CACb,MAAM,UAAU,EAAE,UAAU,EAAE,WAAW,sBAAsB,EAAE,EAAE,eAAe,CAAC;CACnF,MAAM,UAAU,EAAE,UAAU,EAAE,WAAW,sBAAsB,EAAE,EAAE,eAAe,CAAC;AACnF,KAAI,UAAU;AACZ,UAAQ,WAAW;AACnB,UAAQ,WAAW;;AAErB,SAAQ,iBAAiB,SAAS,OAAO;AACzC,SAAQ,iBAAiB,SAAS,OAAO;AAEzC,QAAO,EACL,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,MAAM,EACnD,EAAE,OAAO,EAAE,WAAW,eAAe,EAAE,SAAS,QAAQ,CACzD;;;AAIH,SAAS,qBAAqB,UAAgC;CAE5D,MAAM,cAAc,EAAE,OAAO,EAC3B,OAAO,wFACR,CAAC;CAEF,MAAM,YAAY,EAAE,UAAU,EAAE,WAAW,sBAAsB,EAAE,EAAE,iBAAiB,CAAC;CACvF,MAAM,YAAY,EAAE,UAAU,EAAE,WAAW,sBAAsB,EAAE,EAAE,iBAAiB,CAAC;CACvF,MAAM,WAAW,EAAE,UAAU,EAAE,WAAW,sBAAsB,EAAE,EAAE,gBAAgB,CAAC;CACrF,MAAM,aAAa,EAAE,UAAU,EAAE,WAAW,sBAAsB,EAAE,EAAE,kBAAkB,CAAC;AAEzF,KAAI,UAAU;AACZ,YAAU,WAAW;AACrB,YAAU,WAAW;AACrB,WAAS,WAAW;AACpB,aAAW,WAAW;;CAIxB,IAAI,gBAAgD;AAEpD,WAAU,iBAAiB,eAAe;AAExC,iBAAe,SAAS;AACxB,kBAAgB;AAChB,cAAY,YAAY;AAaxB,kBAXe,QAAQ,aAAa,qBAAqB,aAAa;GACpE,OAAO;GACP,SAAS;GACT,WAAW;IACT,eAAe,MAAM,YAAY,gBAAgB,EAAE,OAAO,GAAG;IAC7D,iBAAiB,MAAM,YAAY,kBAAkB,EAAE,OAAO,GAAG;IACjE,cAAc,MAAM,YAAY,eAAe,EAAE,OAAO,GAAG;IAC3D,qBAAqB,MAAM,YAAY,6BAA6B,EAAE,OAAO,GAAG;IAChF,WAAW,MAAM,YAAY,mBAAmB,EAAE,OAAO,GAAG;IAC7D;GACF,CAAC;GAEF;AAEF,WAAU,iBAAiB,eAAe;AAExC,iBAAe,SAAS;AACxB,kBAAgB;AAChB,cAAY,YAAY;EAKxB,MAAM,SAAS,qBAAqB,KAAK,KAAK;AAC9C,cAAY,mBAAmB,OAAO,GAAG;AACzC,cAAY,6BAA6B,OAAO,GAAG;GACnD;AAEF,UAAS,iBAAiB,eAAe;AACvC,cAAY,iBAAiB;GAC7B;AAEF,YAAW,iBAAiB,eAAe;AACzC,MAAI,eAAe;AACjB,iBAAc,SAAS;AACvB,mBAAgB;AAChB,eAAY,YAAY;AACxB,eAAY,mBAAmB;QAE/B,aAAY,uBAAuB;GAErC;AAEF,QAAO,EACL,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,4BAA4B,CAAC,EAC5E,aACA,EAAE,OAAO,EAAE,WAAW,eAAe,EAAE,WAAW,WAAW,UAAU,WAAW,CACnF;;AAGH,SAAgB,eAA4B;CAC1C,MAAM,IAAI,SAAS;CACnB,MAAM,WAAW,CAAC,EAAE;CACpB,MAAM,YAAY,EAAE,MAAM;AAE1B,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;CAEvD,MAAM,gBAAgB,EAAE,SAAS;EAAE,MAAM;EAAY,WAAW;EAAgB,CAAC;AACjF,eAAc,UAAU,EAAE,IAAI;AAC9B,KAAI,SAAU,eAAc,WAAW;AACvC,eAAc,iBAAiB,gBAAgB;AAC7C,WAAS,MAAM,OAAO,EAAE,aAAa,cAAc,SAAS,CAAC;GAC7D;AAEF,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,oBAAoB,CAAC,EACpE,UAAU,EAAE,mBAAmB,EAAE,OAAO,EAAE,IAAI,SAAS,CAAC,EACxD,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,sBAAsB,CAAC,EAAE,cAAc,EAC3F,SACE,EAAE,yBAAyB,EAC3B,EAAE,IAAI,iBACL,MAAM,SAAS,MAAM,OAAO,EAAE,gBAAgB,GAAG,CAAC,EACnD,SACD,EACD,SACE,EAAE,uBAAuB,EACzB,OAAO,EAAE,IAAI,aAAa,GACzB,MAAM;EACL,MAAM,IAAI,OAAO,EAAE;AACnB,MAAI,CAAC,OAAO,MAAM,EAAE,CAAE,UAAS,MAAM,OAAO,EAAE,cAAc,GAAG,CAAC;IAElE,SACD,EACD,eAAe,CAChB,EACD,UACE,EAAE,0BAA0B,QACtB;AACJ,cAAY,oBAAoB;GAC9B,UAAU,MAAM,YAAY,EAAE,KAAK;GACnC,UAAU,QAAQ,YAAY,IAAI,QAAQ;GAC3C,CAAC;UAEE;AACJ,cAAY,oBAAoB;GAC9B,UAAU,MAAM,YAAY,EAAE,KAAK;GACnC,UAAU,QAAQ,YAAY,IAAI,QAAQ;GAC3C,CAAC;IAEJ,SACD,EACD,UACE,EAAE,sBAAsB,QAClB;AAEJ,UAAQ,WAAW,EACjB,WAAW;GACT,qBAAqB;AACnB,aAAS,MAAM,OAAO,EAAE,UAAU,MAAM,CAAC;AACzC,gBAAY,SAAS;;GAEvB,yBAAyB,QAAQ,YAAY,IAAI,QAAQ;GAC1D,EACF,CAAC;UAEE;AACJ,MAAI,CAAC,SAAS,MAAM,IAAI,UAAU;AAChC,eAAY,gBAAgB;AAC5B;;AAEF,cAAY,OAAO;AACnB,mBAAiB;AACf,eAAY,YAAY;AACxB,YAAS,MAAM,OAAO,EAAE,UAAU,OAAO,CAAC;KACzC,KAAK;IAEV,SACD,EACD,qBAAqB,SAAS,EAC9B,UACE,EAAE,2BAA2B,QACvB;AACJ,mBAAiB;GACf,UAAU,MAAM,YAAY,EAAE,KAAK;GACnC,UAAU,QAAQ,YAAY,IAAI,QAAQ;GAC3C,CAAC;UAEE;AACJ,mBAAiB;GACf,UAAU,MAAM,YAAY,EAAE,KAAK;GACnC,UAAU,QAAQ,YAAY,IAAI,QAAQ;GAC3C,CAAC;IAEJ,SACD,CACF;AAED,QAAO;;;;AChPT,MAAM,iBAAyC;CAC7C,UAAU;CACV,SAAS;CACT,OAAO;CACR;AAED,SAAgB,qBAAkC;CAChD,MAAM,WAAW,CAAC,SAAS,MAAM;CACjC,MAAM,YAAY,EAAE,MAAM;AAC1B,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;CAGvD,MAAM,OAAO,SAAS,MAAM;CAE5B,MAAM,oBAAoB,EACxB,UACA,EAAE,WAAW,qCAAqC,EAClD,EAAE,sBAAsB,CACzB;AACD,KAAI,SAAU,mBAAkB,WAAW;AAC3C,mBAAkB,iBAAiB,eAAe;AAChD,WAAS,OAAO,EAAE,cAAc,EAAE,EAAE,CAAC;GACrC;AAEF,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EACE,OACA,EAAE,WAAW,WAAW,EACxB,EACE,OACA,EAAE,WAAW,qBAAqB,EAClC,EAAE,yBAAyB,EAAE,OAAO,KAAK,QAAQ,CAAC,CACnD,EACD,kBACD,EACD,GAAG,KACA,MAAM,IAAI,CACV,SAAS,CACT,KAAK,UAAU;AAEd,SAAO,EACL,OACA,EAAE,WAAW,iBAAiB,EAC9B,EAAE,QAAQ,EAAE,WAAW,gBAAgB,EAJ5B,IAAI,KAAK,MAAM,UAAU,CAAC,oBAAoB,CAIX,EAC9C,EAAE,QAAQ,EAAE,WAAW,gBAAgB,EAAE,MAAM,KAAK,EACpD,KAAK,UAAU,MAAM,OAAO,CAC7B;GACD,CACL,CACF;CAGD,MAAM,QAAQ,SAAS,MAAM;CAE7B,MAAM,gBAAgB,EACpB,UACA,EAAE,WAAW,qCAAqC,EAClD,EAAE,4BAA4B,CAC/B;AACD,KAAI,SAAU,eAAc,WAAW;AACvC,eAAc,iBAAiB,eAAe;AAC5C,WAAS,OAAO,EAAE,YAAY,EAAE,EAAE,CAAC;GACnC;AAEF,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EACE,OACA,EAAE,WAAW,WAAW,EACxB,EACE,OACA,EAAE,WAAW,qBAAqB,EAClC,EAAE,2BAA2B,EAAE,OAAO,MAAM,QAAQ,CAAC,CACtD,EACD,cACD,EACD,MAAM,WAAW,IACb,EAAE,OAAO,EAAE,WAAW,+BAA+B,EAAE,EAAE,wBAAwB,CAAC,GAClF,EACE,OACA,EAAE,EACF,GAAG,MACA,MAAM,IAAI,CACV,SAAS,CACT,KAAK,SAAS;EACb,MAAM,QAAQ,eAAe,KAAK,aAAa;EAC/C,MAAM,OAAO,IAAI,KAAK,KAAK,UAAU,CAAC,oBAAoB;EAC1D,MAAM,UACJ,KAAK,KAAK,SAAS,IACf,IAAI,KAAK,KAAK,KAAK,MAAM,KAAK,UAAU,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,KACvD;EACN,MAAM,eACJ,KAAK,WAAW,aACZ,MAAM,KAAK,SAAS,YACpB,KAAK,WAAW,YACd,OACA;AAER,SAAO,EACL,OACA,EAAE,WAAW,2CAA2C,KAAK,YAAY,EACzE,EAAE,QAAQ,EAAE,WAAW,iBAAiB,EAAE,MAAM,EAChD,EAAE,QAAQ,EAAE,WAAW,kBAAkB,EAAE,KAAK,OAAO,EACvD,EAAE,QAAQ,EAAE,WAAW,gBAAgB,EAAE,QAAQ,EACjD,EAAE,QAAQ,EAAE,WAAW,gBAAgB,EAAE,MAAM,OAAO,EACtD,eAAe,EAAE,QAAQ,EAAE,WAAW,kBAAkB,EAAE,aAAa,GAAG,GAC3E;GACD,CACL,CACN,CACF;AAED,QAAO;;;;AC5GT,SAAgB,uBAAoC;CAClD,MAAM,IAAI,SAAS;CACnB,MAAM,WAAW,CAAC,EAAE;CACpB,MAAM,YAAY,EAAE,MAAM;AAE1B,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;AAEvD,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,uBAAuB,CAAC,EACvE,UACE,EAAE,aAAa,EACf,CAAC,OAAO,UAAU,EAClB,EAAE,WACD,MAAM,SAAS,OAAO,EAAE,UAAU,GAAiB,CAAC,EACrD,SACD,EACD,SACE,EAAE,qBAAqB,EACvB,EAAE,aACD,MAAM,SAAS,OAAO,EAAE,YAAY,GAAG,CAAC,EACzC,SACD,EACD,UACE,EAAE,sBAAsB,EACxB,CAAC,QAAQ,UAAU,EACnB,EAAE,cACD,MAAM,SAAS,OAAO,EAAE,aAAa,GAA6B,CAAC,EACpE,SACD,EACD,SAAS,EAAE,iBAAiB,EAAE,EAAE,SAAS,MAAM,SAAS,OAAO,EAAE,QAAQ,GAAG,CAAC,EAAE,SAAS,CACzF,EACD,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,sBAAsB,CAAC,EACtE,UACE,EAAE,wBAAwB,EAC1B;EAAC;EAAQ;EAAM;EAAM;EAAM;EAAM;EAAW;EAAQ;EAAU,EAC9D,EAAE,gBACD,MAAM,SAAS,OAAO,EAAE,eAAe,GAAoB,CAAC,EAC7D,SACD,CACF,EACD,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,uBAAuB,CAAC,EACvE,SACE,EAAE,uBAAuB,EACzB,OAAO,EAAE,eAAe,IAAI,GAC3B,MAAM,SAAS,MAAM,kBAAkB,EAAE,KAAK,OAAO,EAAE,EAAE,CAAC,EAC3D,SACD,EACD,SACE,EAAE,0BAA0B,EAC5B,OAAO,EAAE,eAAe,OAAO,GAC9B,MAAM,SAAS,MAAM,kBAAkB,EAAE,QAAQ,OAAO,EAAE,EAAE,CAAC,EAC9D,SACD,CACF,EACD,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,CACxB;AACD,QAAO;;;;;;;;AAST,SAAS,yBAAsC;CAC7C,MAAM,QAAQ,SAAS,MAAM,WAAW;CACxC,MAAM,YACJ,UAAU,OACN,EAAE,kCAAkC,GACpC,QACE,EAAE,oCAAoC,GACtC,EAAE,qCAAqC;AAE/C,QAAO,EACL,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,yBAAyB,CAAC,EACzE,EACE,OACA,EAAE,WAAW,WAAW,EACxB,EAAE,SAAS,EAAE,EAAE,EAAE,0BAA0B,CAAC,EAC5C,EACE,QACA,EACE,OAAO,gEACL,UAAU,OAAO,SAAS,aAE7B,EACD,UACD,CACF,EACD,EAAE,OAAO,EAAE,OAAO,4CAA4C,EAAE,EAAE,2BAA2B,CAAC,CAC/F;;AAGH,SAAS,uBAAoC;CAC3C,MAAM,UAAU,WAAW;CAC3B,MAAM,SAAS,EAAE,UAAU,EAAE,WAAW,cAAc,CAAC;AAKvD,MAAK,MAAM,OAJgF,CACzF;EAAE,OAAO;EAAM,UAAU;EAAmB,EAC5C;EAAE,OAAO;EAAM,UAAU;EAAmB,CAC7C,EAC0B;EACzB,MAAM,SAAS,EAAE,UAAU,EAAE,OAAO,IAAI,OAAO,EAAE,EAAE,IAAI,SAAS,CAAC;AACjE,MAAI,IAAI,UAAU,QAAS,QAAO,WAAW;AAC7C,SAAO,YAAY,OAAO;;AAE5B,QAAO,iBAAiB,gBAAgB;AACtC,YAAU,OAAO,MAAgB;GACjC;AAEF,QAAO,EACL,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,uBAAuB,CAAC,EACvE,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,mBAAmB,CAAC,EAAE,OAAO,CAClF;;AAGH,SAAS,wBAAqC;CAE5C,MAAM,YAAY,gBAAgB;CAClC,MAAM,gBAAgB,EACpB,QACA,EACE,OAAO,wCAAwC,YAAY,YAAY,UACxE,EACD,YAAY,EAAE,qBAAqB,GAAG,EAAE,sBAAsB,CAC/D;CACD,MAAM,cAAc,EAClB,UACA;EAAE,WAAW;EAAsB,OAAO;EAAkB,EAC5D,YAAY,EAAE,0BAA0B,GAAG,EAAE,yBAAyB,CACvE;AACD,aAAY,iBAAiB,eAAe;AAC1C,kBAAgB,CAAC,UAAU;AAC3B,SAAO,cAAc,IAAI,YAAY,0BAA0B,EAAE,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC,CAAC;GAC3F;CACF,MAAM,QAAQ,EACZ,OACA,EAAE,WAAW,WAAW,EACxB,EAAE,SAAS,EAAE,EAAE,EAAE,sBAAsB,CAAC,EACxC,EAAE,QAAQ,EAAE,OAAO,2CAA2C,EAAE,eAAe,YAAY,CAC5F;CACD,MAAM,SAAS,EACb,OACA,EAAE,OAAO,+CAA+C,EACxD,EAAE,uBAAuB,CAC1B;CAID,MAAM,YADU,kBAAkB,KACJ;CAE9B,MAAM,cAAc,EAClB,QACA,EACE,OAAO,wCAAwC,YAAY,YAAY,UACxE,EACD,YAAY,EAAE,mBAAmB,GAAG,EAAE,oBAAoB,CAC3D;CAED,MAAM,YAAY,EAChB,UACA;EAAE,WAAW;EAAsB,OAAO;EAAkB,EAC5D,YAAY,EAAE,wBAAwB,GAAG,EAAE,uBAAuB,CACnE;AACD,WAAU,iBAAiB,eAAe;AACxC,sBAAoB,CAAC,UAAU;AAC/B,SAAO,cAAc,IAAI,YAAY,0BAA0B,EAAE,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC,CAAC;GAC3F;CAEF,MAAM,YAAY,EAChB,OACA,EAAE,WAAW,WAAW,EACxB,EAAE,SAAS,EAAE,EAAE,EAAE,oBAAoB,CAAC,EACtC,EAAE,QAAQ,EAAE,OAAO,2CAA2C,EAAE,aAAa,UAAU,CACxF;CAGD,MAAM,YAAY,aAAa,QAAQ,0BAA0B;CACjE,MAAM,gBAAgB,aAAa,EAAE,6BAA6B;CAClE,MAAM,cAAc,cAAc,SAAS,IAAI,GAAG,cAAc,MAAM,GAAG,EAAE,CAAC,KAAK;CAEjF,MAAM,WAAW,EACf,QACA;EACE,OAAO;EACP,OAAO,EAAE,gCAAgC;EAC1C,EACD,EAAE,6BAA6B,EAAE,OAAO,aAAa,CAAC,CACvD;AACD,UAAS,iBAAiB,eAAe;AACvC,MAAI,CAAC,UAAW;AAChB,YAAU,UAAU,UAAU,UAAU,CAAC,YAAY,GAEnD;GACF;CAGF,MAAM,YAAY,EAChB,UACA,EAAE,WAAW,qCAAqC,EAClD,EAAE,0BAA0B,CAC7B;CACD,MAAM,eAAe,EAAE,QAAQ,EAAE,OAAO,6BAA6B,CAAC;AAEtE,WAAU,iBAAiB,eAAe;AACxC,YAAU,WAAW;AACrB,eAAa,cAAc,EAAE,yBAAyB;AACtD,eAAa,mBAAmB,CAC7B,MAAM,OAAO;AACZ,gBAAa,cAAc,KACvB,EAAE,wBAAwB,GAC1B,EAAE,kCAAkC;AACxC,aAAU,WAAW;IACrB,CACD,YAAY;AACX,gBAAa,cAAc,EAAE,6BAA6B;AAC1D,aAAU,WAAW;IACrB;GACJ;CAGF,MAAM,cAAc,EAAE,KAAK;EACzB,MAAM;EACN,QAAQ;EACR,KAAK;EACL,OAAO;EACR,CAAC;AACF,aAAY,cAAc,EAAE,4BAA4B;AAExD,QAAO,EACL,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,wBAAwB,CAAC,EACxE,OACA,QACA,WACA,EAAE,OAAO,EAAE,OAAO,qBAAqB,EAAE,SAAS,EAClD,EACE,OACA;EAAE,WAAW;EAAe,OAAO;EAA6C,EAChF,WACA,aACD,EACD,EAAE,OAAO,EAAE,OAAO,kBAAkB,EAAE,YAAY,CACnD;;;;AC5QH,SAAgB,kBAA+B;CAC7C,MAAM,WAAW,CAAC,SAAS,MAAM;CACjC,MAAM,YAAY,EAAE,MAAM;AAE1B,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;CAEvD,MAAM,UAAU,EAAE,UAAU,EAAE,WAAW,WAAW,EAAE,EAAE,yBAAyB,CAAC;AAClF,SAAQ,iBAAiB,eAAe,SAAS,QAAQ,YAAY,CAAC;AACtE,KAAI,SAAU,SAAQ,WAAW;CAEjC,MAAM,UAAU,EAAE,UAAU,EAAE,WAAW,WAAW,EAAE,EAAE,yBAAyB,CAAC;AAClF,SAAQ,iBAAiB,eAAe,SAAS,QAAQ,YAAY,CAAC;AACtE,KAAI,SAAU,SAAQ,WAAW;AAEjC,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,4BAA4B,CAAC,EAC5E,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,SAAS,QAAQ,CACrD,EACD,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,uBAAuB,CAAC,EACvE,UACE,EAAE,sBAAsB,EACxB,CAAC,QAAQ,QAAQ,EACjB,OAAO,SAAS,MAAM,KAAK,WAAW,GACrC,MAAM;AACL,WAAS,MAAM,QAAQ,EAAE,YAAY,MAAM,QAAQ,CAAC;IAEtD,SACD,EACD,UACE,EAAE,iCAAiC,EACnC,CAAC,QAAQ,QAAQ,EACjB,OAAO,SAAS,MAAM,KAAK,sBAAsB,GAChD,MAAM;AACL,WAAS,MAAM,QAAQ,EAAE,uBAAuB,MAAM,QAAQ,CAAC;IAEjE,SACD,CACF,CACF;AACD,QAAO;;;;;;;ACxCT,IAAI,eAAe;AAEnB,SAAS,kBAA0B;AACjC,QAAO,cAAc,EAAE,aAAa,GAAG,KAAK,KAAK;;AAoCnD,SAAS,iBAAiB,KAA6B;CACrD,MAAM,UAAU,SAAS,MAAM,IAAI,SAAS,MAAM,MAAM,EAAE,QAAQ,IAAI;CACtE,MAAM,YAAY,SAAS,eAAe,QAAQ,WAAW,GAAG,IAAI;AACpE,QAAO;EACL,SAAS,iBAAiB;EAC1B,aAAa,SAAS,eAAe;EACrC,eAAe,SAAS,iBAAiB;EACzC,QAAQ,SAAS,WAAW,GAAG,IAAI;EACnC,UAAU;EACV,UAAU;EACV,gBAAgB,SAAS,WAAW;EACrC;;AAGH,eAAe,eACb,KACA,qBAIA,SACA,SACe;CACf,MAAM,aAAa,SAAS,MAAM,IAAI;AAGtC,OAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAI,CAAC;AAE5C,KAAI,eAAe,WAAW;AAC5B,UAAQ,EAAE,MAAM,YAAY,CAAC;AAC7B;;CAGF,MAAM,SAAS,iBAAiB,IAAI;AAEpC,KAAI;AAEF,MAAI,CADY,MAAM,oBAAoB,EAAE,SAAS,OAAO,SAAS,CAAC,EACxD;AACZ,WAAQ,EAAE,MAAM,kCAAkC,CAAC;AACnD;;UAEK,GAAG;AACV,UAAQ,EAAE;AACV;;AAIF,UAAS,MAAM,OAAO,EACpB,iBAAiB,CACf,GAAG,SAAS,MAAM,IAAI,iBACtB;EACE,SAAS,OAAO;EAChB;EACA,QAAQ;EACR,uBAAM,IAAI,MAAM,EAAC,aAAa;EAC/B,CACF,EACF,CAAC;AAEF,OAAM,QAAQ;EAAE,MAAM;EAAW,MAAM;EAAQ,CAAC;;AAGlD,MAAa,MAAM,gBAAgB,OAAO;CAExC,2BAA2B,QAA0D;AAEnF,iBADY,OAAO,QAAQ,OAAO,OAAO,QAAQ,aAAa,IAC1C,OAAO,QAAQ,qBAAqB,OAAO,SAAS,OAAO,QAAQ,CAAC,OACrF,MAAM,QAAQ,MAAM,4CAA4C,EAAE,CACpE;AACD,eAAa;;CAGf,gCAAgC,QAA4D;AAC1F,iBACE,OAAO,QAAQ,KACf,OAAO,QAAQ,qBACf,OAAO,SACP,OAAO,QACR,CAAC,OAAO,MAAM,QAAQ,MAAM,4CAA4C,EAAE,CAAC;AAC5E,eAAa;;CAGf,MAAM,qBAAuD;AAC3D,SAAO,EACL,UAAU,SAAS,MAAM,IAAI,SAAS,KAAK,OAAO;GAChD,GAAG;GACH,GAAI,EAAE,SAAS,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,WAAW,GAAG,EAAE;GACnF,EAAE,EACJ;;CAGH,MAAM,mBAEH;AACD,SAAO,EAAE,QAAQ,CAAC,GAAG,SAAS,MAAM,IAAI,cAAc,EAAE;;CAG1D,MAAM,+BAIH;AACD,SAAO;GACL,SAAS;GACT,SAAS;GACT,QAAQ,CAAC,GAAG,SAAS,MAAM,IAAI,gBAAgB;GAChD;;CAGH,MAAM,qBAAqB,MAAyD;EAElF,MAAM,MAAM,SAAS,MAAM,IAAI,cAAc,WAC1C,MAAM,EAAE,YAAY,KAAK,OAAO,QAClC;AACD,MAAI,QAAQ,IAAI;GACd,MAAM,QAAQ,SAAS,MAAM,IAAI,cAAc;GAC/C,MAAM,gBAAgB,SAAS,MAAM,IAAI,cAAc,QAAQ,GAAG,MAAM,MAAM,IAAI;GAClF,MAAM,kBAAkB,CACtB,GAAG,SAAS,MAAM,IAAI,iBACtB;IACE,SAAS,MAAM;IACf,KAAK,MAAM;IACX,QAAQ;IACR,uBAAM,IAAI,MAAM,EAAC,aAAa;IAC/B,CACF;AACD,YAAS,MAAM,OAAO;IAAE;IAAe;IAAiB,CAAC;;AAE3D,SAAO;;CAGT,MAAM,oBAAoB,OAAwC;AAChE,SAAO,EACL,cAAc;GACZ,WAAW;GACX,QAAQ;GACR,WAAW,IAAI,KAAK,KAAK,KAAK,GAAG,MAAU,KAAK,KAAK,IAAK,CAAC,aAAa;GACxE,aAAa;GACb,sBAAsB;GACtB,cAAc;GACf,EACF;;CAEJ,CAAC;AAkBuC,OAAO,OAC9C,eAAe,0BAA0B,SAEsB;CAC7D,MAAM,EAAE,YAAY,eAAe,SAAS,MAAM;AAClD,SAAQ,IAAI,iDAAiD,QAAQ,OAAO,aAAa;AAEzF,OAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAI,CAAC;AAE5C,KAAI,eAAe,UACjB,QAAO,EAAE,SAAS,MAAM;AAE1B,QAAO;EAAE,SAAS;EAAO,QAAQ,cAAc;EAA4B;GAE7E,EAAE,mBAAmB,MAAM,CAC5B;;;AC1ND,SAAS,gBAAgB,KAAqB;CAC5C,MAAM,IAAI,IAAI,KAAK,IAAI;AACvB,KAAI,OAAO,MAAM,EAAE,SAAS,CAAC,CAAE,QAAO;AACtC,QAAO,EAAE,oBAAoB;;AAG/B,SAAS,aAAa,SAAyB;AAC7C,QAAO,QAAQ,SAAS,KAAK,IAAI,QAAQ,MAAM,IAAI,KAAK;;AAG1D,SAAgB,eAA4B;CAC1C,MAAM,IAAI,SAAS;CACnB,MAAM,WAAW,CAAC,EAAE;CACpB,MAAM,YAAY,EAAE,MAAM;CAC1B,MAAM,UAA2B;EAC/B;EACA;EACA;EACA;EACA;EACA;EACA;EACD;AAED,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;CAEvD,MAAM,gBAAgB,EAAE,IAAI;CAC5B,MAAM,iBAAiB,EACrB,OACA,EAAE,WAAW,eAAe,EAC5B,EACE,OACA,EAAE,WAAW,qBAAqB,EAClC,EAAE,uBAAuB,EAAE,OAAO,cAAc,QAAQ,CAAC,CAC1D,CACF;AACD,KAAI,cAAc,WAAW,EAC3B,gBAAe,YAAY,EAAE,OAAO,EAAE,WAAW,iBAAiB,EAAE,EAAE,oBAAoB,CAAC,CAAC;KAE5F,MAAK,MAAM,KAAK,eAAe;EAC7B,MAAM,cAAc,EAAE,UAAU,EAAE,WAAW,sBAAsB,EAAE,EAAE,mBAAmB,CAAC;AAC3F,MAAI,SAAU,aAAY,WAAW;AACrC,cAAY,iBAAiB,eAAe;AAC1C,OAAI,qBAAqB,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,OAAO,QAClE,QAAQ,MAAM,kDAAkD,IAAI,CACrE;IACD;AACF,iBAAe,YACb,EACE,OACA,EAAE,WAAW,iBAAiB,EAC9B,EAAE,QAAQ,EAAE,WAAW,gBAAgB,EAAE,EAAE,oBAAoB,CAAC,EAChE,GAAG,EAAE,IAAI,IAAI,aAAa,EAAE,QAAQ,CAAC,MAAM,gBAAgB,EAAE,qBAAqB,CAAC,IACnF,YACD,CACF;;CAIL,MAAM,kBAAkB,EAAE,IAAI;CAC9B,MAAM,mBAAmB,EACvB,OACA,EAAE,WAAW,eAAe,EAC5B,EACE,OACA,EAAE,WAAW,qBAAqB,EAClC,EAAE,yBAAyB,EAAE,OAAO,gBAAgB,QAAQ,CAAC,CAC9D,CACF;AACD,KAAI,gBAAgB,WAAW,EAC7B,kBAAiB,YACf,EAAE,OAAO,EAAE,WAAW,iBAAiB,EAAE,EAAE,sBAAsB,CAAC,CACnE;KAED,MAAK,MAAM,KAAK,gBACd,kBAAiB,YACf,EACE,OACA,EAAE,WAAW,iBAAiB,EAC9B,EAAE,QAAQ,EAAE,WAAW,gBAAgB,EAAE,EAAE,OAAO,EAClD,GAAG,EAAE,IAAI,IAAI,aAAa,EAAE,QAAQ,CAAC,MAAM,gBAAgB,EAAE,KAAK,GACnE,CACF;AAIL,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,wBAAwB,CAAC,EACxE,UACE,EAAE,qBAAqB,EACvB,SACA,EAAE,IAAI,aACL,MAAM;AACL,WAAS,MAAM,OAAO,EAAE,YAAY,GAAoB,CAAC;IAE3D,SACD,CACF,EACD,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,sBAAsB,CAAC,EACtE,UACE,EAAE,wBAAwB,EAC1B,CAAC,WAAW,OAAO,EACnB,EAAE,QAAQ,aACT,MAAM;AACL,WAAS,MAAM,WAAW,EAAE,YAAY,GAAyB,CAAC;IAEpE,SACD,CACF,EACD,gBACA,iBACD;AACD,QAAO;;;;ACxHT,SAAgB,oBAAiC;CAC/C,MAAM,IAAI,SAAS;CACnB,MAAM,WAAW,CAAC,EAAE;CACpB,MAAM,YAAY,EAAE,MAAM;AAE1B,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;AAEvD,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,2BAA2B,CAAC,EAC3E,SACE,EAAE,wBAAwB,EAC1B,OAAO,EAAE,SAAS,OAAO,SAAS,GACjC,MAAM;EACL,MAAM,SAAS;GAAE,GAAG,EAAE,SAAS;GAAQ,UAAU,OAAO,EAAE;GAAE;AAC5D,WAAS,MAAM,YAAY,EAAE,QAAQ,CAA+B;IAEtE,SACD,EACD,SACE,EAAE,yBAAyB,EAC3B,OAAO,EAAE,SAAS,OAAO,UAAU,GAClC,MAAM;EACL,MAAM,SAAS;GAAE,GAAG,EAAE,SAAS;GAAQ,WAAW,OAAO,EAAE;GAAE;AAC7D,WAAS,MAAM,YAAY,EAAE,QAAQ,CAA+B;IAEtE,SACD,EACD,SACE,EAAE,wBAAwB,EAC1B,OAAO,EAAE,SAAS,OAAO,SAAS,GACjC,MAAM;EACL,MAAM,SAAS;GAAE,GAAG,EAAE,SAAS;GAAQ,UAAU,OAAO,EAAE;GAAE;AAC5D,WAAS,MAAM,YAAY,EAAE,QAAQ,CAA+B;IAEtE,SACD,CACF,CACF;AACD,QAAO;;;;ACxCT,MAAM,UAA8E;CAClF;EAAE,OAAO;EAAgB,UAAU;EAAqC;CACxE;EAAE,OAAO;EAAiB,UAAU;EAAsC;CAC1E;EAAE,OAAO;EAAqB,UAAU;EAA0C;CACnF;AAED,SAAS,SACP,MACA,SACA,QACA,UACa;CACb,MAAM,QAAQ,EAAE,SAAS;EAAE,MAAM;EAAS;EAAM,OAAO,OAAO;EAAO,CAAC;AACtE,OAAM,UAAU,YAAY,OAAO;AACnC,KAAI,SAAU,OAAM,WAAW;AAC/B,OAAM,iBAAiB,gBAAgB;AACrC,MAAI,MAAM,QACR,UAAS,MAAM,gBAAgB,EAAE,YAAY,OAAO,OAAO,CAAC;GAE9D;AACF,QAAO,EAAE,SAAS,EAAE,WAAW,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,SAAS,CAAC,CAAC;;AAGvF,SAAgB,yBAAsC;CACpD,MAAM,IAAI,SAAS;CACnB,MAAM,WAAW,CAAC,EAAE;CACpB,MAAM,YAAY,EAAE,MAAM;AAE1B,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;AAEvD,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,8BAA8B,CAAC,EAC9E,GAAG,QAAQ,KAAK,QACd,SAAS,2BAA2B,EAAE,aAAa,YAAY,KAAK,SAAS,CAC9E,CACF,CACF;AACD,QAAO;;;;ACxCT,SAAgB,uBAAoC;CAClD,MAAM,IAAI,SAAS;CACnB,MAAM,WAAW,CAAC,EAAE;CACpB,MAAM,YAAY,EAAE,MAAM;CAC1B,MAAM,QAA0B;EAC9B;EACA;EACA;EACA;EACA;EACA;EACD;CACD,MAAM,WAA+B;EAAC;EAAW;EAAU;EAAgB;AAE3E,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;AAEvD,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,6BAA6B,CAAC,EAC7E,GAAG,MAAM,KAAK,SACZ,UACE,MACA,UACA,EAAE,YAAY,QACb,MAAM;AACL,WAAS,MAAM,eAAe,GAAG,OAAO,GAAuB,CAAC;IAElE,SACD,CACF,CACF,CACF;AACD,QAAO;;;;AC5BT,MAAM,SAAS;AAEf,SAAS,mBAAmC;AAC1C,KAAI;AACF,MAAI,OAAO,iBAAiB,YAAa,QAAO;AAChD,SAAO;SACD;AACN,SAAO;;;AAIX,SAAS,SAAS,GAA0C;AAC1D,QAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,EAAE;;;;;;;;;;;AAYjE,SAAS,YAAY,KAAgC;AACnD,KAAI;EACF,MAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,MAAI,CAAC,SAAS,OAAO,CAAE,QAAO;EAC9B,MAAM,EAAE,IAAI,OAAO,aAAa,UAAU;AAC1C,MAAI,OAAO,OAAO,YAAY,GAAG,WAAW,EAAG,QAAO;AACtD,MAAI,OAAO,UAAU,YAAY,MAAM,WAAW,EAAG,QAAO;AAC5D,MAAI,CAAC,SAAS,MAAM,CAAE,QAAO;AAC7B,SAAO;GACL;GACA;GACA,aAAa,OAAO,gBAAgB,WAAW,cAAc,KAAA;GACtD;GACR;SACK;AACN,SAAO;;;AAIX,SAAgB,kBAAgC;CAC9C,MAAM,KAAK,kBAAkB;AAC7B,KAAI,CAAC,GAAI,QAAO,EAAE;CAClB,MAAM,MAAoB,EAAE;AAC5B,MAAK,IAAI,IAAI,GAAG,IAAI,GAAG,QAAQ,KAAK;EAClC,MAAM,MAAM,GAAG,IAAI,EAAE;AACrB,MAAI,CAAC,KAAK,WAAW,OAAO,CAAE;EAC9B,MAAM,MAAM,GAAG,QAAQ,IAAI;AAC3B,MAAI,CAAC,IAAK;EACV,MAAM,SAAS,YAAY,IAAI;AAC/B,MAAI,OAAQ,KAAI,KAAK,OAAO;;AAE9B,QAAO,IAAI,MAAM,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,MAAM,CAAC;;;;;;;;;;;AAY3D,SAAgB,eACd,OACA,OACA,aACY;CACZ,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,MAAM,+BAA+B;CAEjD,MAAM,KAAK,kBAAkB;AAC7B,KAAI,CAAC,GAAI,OAAM,IAAI,MAAM,6BAA6B;CACtD,MAAM,KAAK,WAAW,SAAS,GAAG;CAClC,MAAM,SAAqB;EACzB;EACA,OAAO;EACP;EACA,GAAI,gBAAgB,KAAA,KAAa,YAAY,SAAS,IAAI,EAAE,aAAa,GAAG,EAAE;EAC/E;AACD,IAAG,QAAQ,SAAS,IAAI,KAAK,UAAU,OAAO,CAAC;AAC/C,QAAO;;AAGT,SAAgB,iBAAiB,IAAkB;CACjD,MAAM,KAAK,kBAAkB;AAC7B,KAAI,CAAC,GAAI;AACT,IAAG,WAAW,SAAS,GAAG;;;AAI5B,SAAS,WAAW,OAAe,IAAqB;CACtD,MAAM,OACJ,MACG,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG,CACvB,MAAM,GAAG,GAAG,IAAI;CACrB,IAAI,YAAY;CAChB,IAAI,IAAI;AACR,QAAO,GAAG,QAAQ,SAAS,UAAU,KAAK,MAAM;AAC9C,cAAY,GAAG,KAAK,GAAG;AACvB,OAAK;;AAEP,QAAO;;;;AC9ET,MAAa,iBAAwC;CACnD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACb,OAAO;GACL,eAAe;GACf,aAAa;IACX,QAAQ;IACR,QAAQ;IACR,aAAa;IACb,WAAW;IACX,UAAU;IACV,YAAY;IACb;GACD,MAAM,EAAE,YAAY,MAAM;GAC1B,KAAK,EAAE,YAAY,WAAW;GAC9B,KAAK,EAAE,aAAa,OAAO;GAC3B,SAAS;IAAE,YAAY;IAAW,YAAY;IAAI;GACnD;EACF;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACb,OAAO,EACL,aAAa;GACX,QAAQ;GACR,QAAQ;GACR,aAAa;GACb,UAAU;GACX,EACF;EACF;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACb,OAAO;GACL,eAAe;GACf,KAAK,EAAE,YAAY,iBAAiB;GACpC,SAAS;IAAE,YAAY;IAAQ,YAAY;IAAiB;GAC7D;EACF;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACb,OAAO,EACL,MAAM,EAAE,YAAY,OAAO,EAC5B;EACF;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACb,OAAO,EACL,KAAK,EAAE,YAAY,mBAAmB,EACvC;EACF;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACb,OAAO;GACL,eAAe;GACf,KAAK,EAAE,aAAa,MAAM;GAC3B;EACF;CACF;;;;;;;;;AAUD,SAAS,cACP,OACA,SACY;AACZ,KAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO,EAAE;CAC1D,MAAM,MAAkB,EAAE;CAC1B,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,CAC9C,KAAK,QAA8B,SAAS,IAAI,CAC7C,KAAgC,OAAO;KAExC,SAAQ,KAAK,IAAI;AAGrB,KAAI,QAAQ,SAAS,EACnB,SAAQ,KAAK,mDAAmD,QAAQ,KAAK,KAAK,GAAG;AAEvF,QAAO;;AAGT,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACD;AACD,MAAM,YAAY;CAAC;CAAc;CAAyB;CAAc;AACxE,MAAM,WAAW,CAAC,aAAa;AAC/B,MAAM,WAAW;CAAC;CAAY;CAAa;CAAe;CAAY;AACtE,MAAM,eAAe,CAAC,cAAc,aAAa;;;;;;AAOjD,SAAgB,YAAY,OAA8B;AACxD,UAAS,kBAAkB;AACzB,MAAI,MAAM,kBAAkB,KAAA,EAC1B,UAAS,OAAO,EAAE,eAAe,MAAM,eAAe,CAAC;AAEzD,MAAI,MAAM,gBAAgB,KAAA,EACxB,UAAS,MACP,eACA,cAA+C,MAAM,aAAa,gBAAgB,CACnF;AAEH,MAAI,MAAM,SAAS,KAAA,EACjB,UAAS,MAAM,QAAQ,cAAwC,MAAM,MAAM,UAAU,CAAC;AAExF,MAAI,MAAM,QAAQ,KAAA,GAAW;GAC3B,MAAM,SAAS,cACb,MAAM,KACN,SACD;AACD,YAAS,MAAM,OAAO,OAAO;;AAE/B,MAAI,MAAM,QAAQ,KAAA,EAChB,UAAS,MAAM,OAAO,cAAuC,MAAM,KAAK,SAAS,CAAC;AAEpF,MAAI,MAAM,YAAY,KAAA,EACpB,UAAS,MACP,WACA,cAA2C,MAAM,SAAS,aAAa,CACxE;GAEH;;;;;;;;;AAUJ,SAAgB,cAAc,UAA4B,QAAkC;AAC1F,KAAI,OAAO,kBAAkB,KAAA,KAAa,SAAS,kBAAkB,OAAO,cAC1E,QAAO;AAET,KAAI,OAAO,gBAAgB,KAAA,EACzB,MAAK,MAAM,KAAK,iBAAiB;EAC/B,MAAM,OAAO,OAAO,YAAY;AAChC,MAAI,SAAS,KAAA,KAAa,SAAS,YAAY,OAAO,KAAM,QAAO;;AAGvE,KAAI,OAAO,SAAS,KAAA,EAClB,MAAK,MAAM,KAAK,WAAW;EACzB,MAAM,OAAO,OAAO,KAAK;AACzB,MAAI,SAAS,KAAA,KAAa,SAAS,KAAK,OAAO,KAAM,QAAO;;AAGhE,KAAI,OAAO,QAAQ,KAAA;MACb,OAAO,IAAI,eAAe,KAAA,KAAa,SAAS,IAAI,eAAe,OAAO,IAAI,WAChF,QAAO;;AAGX,KAAI,OAAO,QAAQ,KAAA,GAAW;AAC5B,MAAI,OAAO,IAAI,gBAAgB,KAAA,KAAa,SAAS,IAAI,gBAAgB,OAAO,IAAI,YAClF,QAAO;AACT,MAAI,OAAO,IAAI,aAAa,KAAA,KAAa,SAAS,IAAI,aAAa,OAAO,IAAI,SAC5E,QAAO;AACT,MAAI,OAAO,IAAI,cAAc,KAAA,KAAa,SAAS,IAAI,cAAc,OAAO,IAAI,UAC9E,QAAO;;AAEX,KAAI,OAAO,YAAY,KAAA,EACrB,MAAK,MAAM,KAAK,cAAc;EAC5B,MAAM,OAAO,OAAO,QAAQ;AAC5B,MAAI,SAAS,KAAA,KAAa,SAAS,QAAQ,OAAO,KAAM,QAAO;;AAGnE,QAAO;;;;;AAMT,SAAgB,oBAAoB,UAA6C;AAC/E,QAAO;EACL,eAAe,SAAS;EACxB,aAAa,EAAE,GAAG,SAAS,aAAa;EACxC,MAAM;GACJ,YAAY,SAAS,KAAK;GAC1B,uBAAuB,SAAS,KAAK;GACrC,aAAa,SAAS,KAAK;GAC5B;EACD,KAAK,EAAE,YAAY,SAAS,IAAI,YAAY;EAC5C,KAAK;GACH,aAAa,SAAS,IAAI;GAC1B,UAAU,SAAS,IAAI;GACvB,WAAW,SAAS,IAAI;GACzB;EACD,SAAS,EAAE,GAAG,SAAS,SAAS;EACjC;;;;ACpPH,SAAgB,iBAAiB,cAAuC;CACtE,MAAM,WAAW,CAAC,SAAS,MAAM;CACjC,MAAM,YAAY,EAAE,MAAM;AAC1B,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;CAEvD,MAAM,cAAc,iBAAiB;CACrC,MAAM,WAAW,SAAS;AAE1B,WAAU,OACR,cACE,EAAE,0BAA0B,EAC5B,gBACA,UACA,UACA,cACA,MACD,CACF;AAED,WAAU,OACR,cACE,EAAE,yBAAyB,EAAE,OAAO,YAAY,QAAQ,CAAC,EACzD,aACA,UACA,UACA,cACA,KACD,CACF;CAGD,MAAM,UAAU,EAAE,UAAU,EAAE,WAAW,sBAAsB,EAAE,EAAE,0BAA0B,CAAC;AAC9F,KAAI,SAAU,SAAQ,WAAW;AACjC,SAAQ,iBAAiB,eAAe;EACtC,MAAM,QAAQ,OAAO,OAAO,EAAE,uBAAuB,CAAC;AACtD,MAAI,UAAU,KAAM;AACpB,MAAI;AACF,kBAAe,OAAO,oBAAoB,SAAS,MAAM,CAAC;WACnD,KAAK;AACZ,UAAO,MAAO,IAAc,QAAQ;AACpC;;AAEF,gBAAc;GACd;AAEF,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,uBAAuB,CAAC,EACvE,EACE,OACA,EAAE,OAAO,+CAA+C,EACxD,EAAE,2BAA2B,CAC9B,EACD,QACD,CACF;AAED,QAAO;;AAGT,SAAS,cACP,OACA,SACA,UACA,UACA,cACA,WACa;CACb,MAAM,UAAU,EACd,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,MAAM,CACpD;AAED,KAAI,QAAQ,WAAW,GAAG;AACxB,UAAQ,OACN,EACE,OACA,EAAE,OAAO,6BAA6B,EACtC,YAAY,EAAE,sBAAsB,GAAG,EAAE,wBAAwB,CAClE,CACF;AACD,SAAO;;AAGT,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,WAAW,cAAc,UAAU,OAAO,MAAM;EAEtD,MAAM,UAAU,EACd,QACA,EAAE,WAAW,oBAAoB,EACjC,WAAW,KAAK,OAAO,UAAU,OAAO,MACzC;EAED,MAAM,WAAW,EACf,UACA,EAAE,WAAW,sBAAsB,EACnC,WAAW,EAAE,sBAAsB,GAAG,EAAE,oBAAoB,CAC7D;AACD,MAAI,SAAU,UAAS,WAAW;AAClC,WAAS,iBAAiB,eAAe;AACvC,eAAY,OAAO,MAAM;AACzB,iBAAc;IACd;EAEF,MAAM,UAAkB,CAAC,SAAS;AAClC,MAAI,WAAW;GACb,MAAM,SAAS,EACb,UACA,EAAE,WAAW,qCAAqC,EAClD,EAAE,qBAAqB,CACxB;AACD,OAAI,SAAU,QAAO,WAAW;AAChC,UAAO,iBAAiB,eAAe;AACrC,QAAI,CAAC,OAAO,QAAQ,EAAE,0BAA0B,EAAE,OAAO,OAAO,OAAO,CAAC,CAAC,CAAE;AAC3E,qBAAiB,OAAO,GAAG;AAC3B,kBAAc;KACd;AACF,WAAQ,KAAK,OAAO;;EAGtB,MAAM,UAAU,EAAE,QAAQ,EAAE,WAAW,sBAAsB,EAAE,GAAG,QAAQ;EAE1E,MAAM,MAAM,EACV,OACA,EAAE,WAAW,iBAAiB,WAAW,uBAAuB,MAAM,EACtE,SACA,QACD;AACD,UAAQ,OAAO,IAAI;AAEnB,MAAI,OAAO,YACT,SAAQ,OAAO,EAAE,OAAO,EAAE,WAAW,0BAA0B,EAAE,OAAO,YAAY,CAAC;;AAIzF,QAAO;;;;AClJT,SAAgB,iBAAiB,cAAuC;CACtE,MAAM,WAAW,CAAC,SAAS,MAAM;CACjC,MAAM,YAAY,EAAE,MAAM;AAC1B,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;CACvD,MAAM,SAAS;CACf,MAAM,UAAmC,EAAE;AAC3C,MAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;EAC5C,MAAM,MAAM,aAAa,IAAI,EAAE;AAC/B,MAAI,KAAK,WAAW,OAAO,CACzB,SAAQ,KAAK,CAAC,IAAI,MAAM,GAAc,EAAE,aAAa,QAAQ,IAAI,IAAI,GAAG,CAAC;;CAI7E,MAAM,WAAW,EACf,UACA,EAAE,WAAW,qCAAqC,EAClD,EAAE,uBAAuB,CAC1B;AACD,KAAI,SAAU,UAAS,WAAW;AAClC,UAAS,iBAAiB,eAAe;AACvC,OAAK,MAAM,CAAC,QAAQ,QAClB,cAAa,WAAW,SAAS,IAAI;AAEvC,gBAAc;GACd;AAEF,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EACE,OACA,EAAE,WAAW,WAAW,EACxB,EACE,OACA,EAAE,WAAW,qBAAqB,EAClC,EAAE,yBAAyB,EAAE,OAAO,QAAQ,QAAQ,CAAC,CACtD,EACD,SACD,EACD,QAAQ,WAAW,IACf,EAAE,OAAO,EAAE,OAAO,6BAA6B,EAAE,EAAE,gBAAgB,CAAC,GACpE,EACE,OACA,EAAE,EACF,GAAG,QAAQ,KAAK,CAAC,KAAK,WACpB,EACE,OACA,EAAE,WAAW,mBAAmB,EAChC,EAAE,QAAQ,EAAE,WAAW,mBAAmB,EAAE,IAAI,EAChD,EACE,QACA,EAAE,WAAW,qBAAqB,EAClC,MAAM,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,MACpD,CACF,CACF,CACF,CACN,CACF;AACD,QAAO;;;;ACvDT,eAAsB,YAA2B;AAC/C,SAAQ,IAAI,sCAAsC;AAClD,QAAO,QAAQ,MAAM;;AA6CW,QAChC,sBACA,SACA,OAAO,YAAiE;AACtE,SAAQ,IAAI,0CAA0C,QAAQ,QAAQ;AACtE,QAAO,EAAE,SAAS,QAAQ,SAAS;EAEtC;AAE8B,QAC7B,mBACA,SACA,OAAO,YAAiE;AACtE,SAAQ,IAAI,uCAAuC,QAAQ,QAAQ;AACnE,QAAO,EAAE,SAAS,QAAQ,SAAS;EAEtC;AAKD,MAAa,gBAHc,QAAQ,iBAAiB,SAAS,YAA2B;AACtF,SAAQ,IAAI,0CAA0C;EACtD;AAGF,cAAc,oBAAoB;AAqDlC,eAAsB,gBAA6C;AACjE,QAAO,KAAK,KAAK;;AAEnB,cAA6D,oBAAoB;;;;;;;;;;;;;;;;;;;;AClHjF,SAAgB,kBAAkB,UAA8B;AAC9D,QAAO,SAAS,WAAW,SAAS,IAAI,SAAS,WAAW,OAAO,GAAG,QAAQ;;;;;;;;;;;;AAuBhF,SAAgB,mBACd,QACA,YACA,WACe;CACf,MAAM,WAAW,kBAAkB,OAAO,GAAG;CAC7C,MAAM,YAAY,sBAAsB;CAExC,MAAM,SACJ,aAAa,QAET,kHAGA;CAGN,MAAM,YAAY,KAAK,MAAM,OAAO,QAAQ,OAAO,IAAI;CAIvD,MAAM,aAAa,KAAK,OAAO,OAAO,gBAAgB,OAAO,UAAU,OAAO,IAAI;AAElF,QAAO;EACL;EACA,WAAW,GAAG,OAAO,GAAG;EACxB,mBAAmB,aAAa,QAAQ,WAAW;EACnD,kBAAkB,OAAO;EACzB,aAAa,YAAY,aAAa;EACtC,cAAc,YAAY,YAAY;EACvC;;AAWH,IAAI,mBAA6C;AAEjD,SAAS,SAAS,QAAgB,MAAc,OAAgB,OAAgC;AAE9F,KAAI,CAAC,MAAM,MAAM,MAAM,EAAE,WAAW,UAAU,EAAE,SAAS,KAAK,CAC5D,OAAM,KAAK;EAAE;EAAQ;EAAM,YAAY,OAAO,yBAAyB,QAAQ,KAAK;EAAE,CAAC;AAEzF,KAAI;AACF,SAAO,eAAe,QAAQ,MAAM;GAClC,cAAc;GACd,WAAW;GACZ,CAAC;SACI;;;;;;AASV,SAAgB,qBAAqB,SAA8B;AACjE,KAAI,OAAO,cAAc,eAAe,OAAO,WAAW,YAAa;AAEvE,wBAAuB;CAEvB,MAAM,QAA2B,EAAE;AACnC,UAAS,WAAW,aAAa,QAAQ,WAAW,MAAM;AAC1D,UAAS,WAAW,YAAY,QAAQ,mBAAmB,MAAM;AACjE,UAAS,QAAQ,oBAAoB,QAAQ,kBAAkB,MAAM;AACrE,KAAI,OAAO,WAAW,aAAa;AACjC,WAAS,QAAQ,SAAS,QAAQ,aAAa,MAAM;AACrD,WAAS,QAAQ,UAAU,QAAQ,cAAc,MAAM;;AAEzD,oBAAmB;;;AAIrB,SAAgB,wBAA8B;AAC5C,KAAI,CAAC,iBAAkB;AACvB,MAAK,MAAM,EAAE,QAAQ,MAAM,gBAAgB,iBACzC,KAAI;AACF,MAAI,WACF,QAAO,eAAe,QAAQ,MAAM,WAAW;MAG/C,QAAQ,OAAmC;SAEvC;AAIV,oBAAmB;;;;;;AAOrB,SAAgB,oBAAoB,QAA+B,WAA0B;AAC3F,KAAI,CAAC,UAAU,OAAO,OAAO,UAAU,OAAO,OAAO,UAAU;AAC7D,yBAAuB;AACvB;;CAEF,MAAM,UAAU,mBAAmB,QAAQ,SAAS,MAAM,YAAY,UAAU;AAChF,sBAAqB,QAAQ;AAI7B,KAAI,SAAS,MAAM,aAAa,QAAQ,SACtC,UAAS,OAAO,EAAE,UAAU,QAAQ,UAAU,CAAC;;;;;;;;;;;;;;AClInD,MAAa,uBAAuB;;AAGpC,MAAa,sBAAsB;AAuBnC,MAAM,cAA8B;CAClC,IAAI;CACJ,OAAO;CACP,OAAO;CACP,QAAQ;CACR,KAAK;CACL,OAAO;CACP,YAAY;CACZ,cAAc;CACd,gBAAgB;CACjB;AAED,MAAM,gBAAgC;CACpC,IAAI;CACJ,OAAO;CACP,OAAO;CACP,QAAQ;CACR,KAAK;CACL,OAAO;CACP,YAAY;CACZ,cAAc;CACd,gBAAgB;CACjB;;AAGD,MAAM,eAAmC,EAAE,QAAQ,gBAAgB;AACnE,MAAM,cAAkC,EAAE,QAAQ,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BjE,MAAa,mBAAqC;CAChD;CAEA;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAGhB,oBAAoB;EACrB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EAIP,QAAQ;EACR,cAAc;EACd,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAChB,yBAAyB;EAGzB,oBAAoB;GAClB,QAAQ;GACR,QAAQ;GACR,MAAM;GACN,cAAc,CAAC,YAAY,YAAY;GACxC;EACF;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAEhB,oBAAoB;EACrB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAEhB,oBAAoB;EACrB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAEhB,oBAAoB;EACrB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAEhB,oBAAoB;EACrB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAEhB,oBAAoB;EACrB;CAOD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAEhB,oBAAoB;EACrB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAChB,oBAAoB;EACrB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAChB,oBAAoB;EACrB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAChB,oBAAoB;EACrB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAChB,oBAAoB;EACrB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,YAAY;EACZ,cAAA;EACA,gBAAgB;EAChB,oBAAoB;EACrB;CACD;CACD;AAED,SAAgB,UAAU,IAAsC;AAC9D,QAAO,iBAAiB,MAAM,MAAM,EAAE,OAAO,GAAG,IAAI;;;;;;;;;AAUtD,SAAgB,qBAAqB,OAAgD;AACnF,KAAI,MAAM,gBAAgB,OACxB,QAAO,MAAM,kBAAkB;AAEjC,QAAO,MAAM;;;;;;;AAQf,SAAgB,oBAAoB,OAAyD;AAC3F,KAAI,MAAM,WAAW,OAAQ,QAAO;EAAE,OAAO;EAAG,QAAQ;EAAG;CAC3D,MAAM,OACJ,MAAM,WAAW,WACb;EAAE,OAAO,MAAM;EAAa,QAAQ,MAAM;EAAc,GACxD,UAAU,MAAM,OAAO;AAC7B,QAAO,qBAAqB,MAAM,KAAK,cACnC;EAAE,OAAO,KAAK;EAAQ,QAAQ,KAAK;EAAO,GAC1C;EAAE,OAAO,KAAK;EAAO,QAAQ,KAAK;EAAQ;;;;;;;;;;;;;;;;;;;;;;;AAwBhD,SAAgB,sBAAsB,QAAwB,WAAoC;AAChG,KAAI,OAAO,OAAO,UAAU,OAAO,OAAO,SACxC,QAAO;EAAE,KAAK;EAAG,QAAQ;EAAG,MAAM;EAAG,OAAO;EAAG;AAKjD,KAAI,CAAC,UACH,QAAO;EAAE,KAAK;EAAG,QAAQ,OAAO;EAAgB,MAAM;EAAG,OAAO;EAAG;CAGrE,MAAM,kBACJ,OAAO,4BAA4B,KAAA,IAC/B,OAAO,0BACP,OAAO;AACb,KAAI,OAAO,UAAU,WAAW,OAAO,UAAU,iBAG/C,QAAO;EACL,KAAK;EACL,QAAQ;EACR,MAAM,OAAO;EACb,OAAO,OAAO;EACf;AAGH,QAAO;EACL,KAAK,OAAO;EACZ,QAAQ;EACR,MAAM;EACN,OAAO;EACR;;;AAIH,SAAS,yBAAyB,OAA4B;AAC5D,KAAI,MAAM,WAAW,UAAU,MAAM,WAAW,SAAU;CAE1D,MAAM,OAAO,sBADE,UAAU,MAAM,OAAO,EACK,qBAAqB,MAAM,KAAK,YAAY;CACvF,MAAM,UAAU,SAAS,MAAM;AAC/B,KACE,QAAQ,QAAQ,KAAK,OACrB,QAAQ,WAAW,KAAK,UACxB,QAAQ,SAAS,KAAK,QACtB,QAAQ,UAAU,KAAK,MAEvB;AAEF,UAAS,OAAO,EAAE,gBAAgB,MAAM,CAAC;;AAG3C,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AACzB,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;AAE3B,IAAI,wBAAwB;AAE5B,SAAS,qBAA8C;AACrD,KAAI,OAAO,aAAa,YAAa,QAAO;CAC5C,IAAI,KAAK,SAAS,eAAe,iBAAiB;AAClD,KAAI,CAAC,IAAI;AACP,OAAK,SAAS,cAAc,QAAQ;AACpC,KAAG,KAAK;AACR,WAAS,KAAK,YAAY,GAAG;;AAE/B,QAAO;;AAGT,SAAS,WAAW,IAAkB;CACpC,MAAM,KAAK,SAAS,eAAe,GAAG;AACtC,KAAI,GAAI,IAAG,QAAQ;;AAGrB,SAAS,qBAA2B;AAClC,YAAW,iBAAiB;;AAG9B,SAAS,sBAA4B;AACnC,YAAW,kBAAkB;;AAG/B,SAAS,sBAA4B;AACnC,YAAW,mBAAmB;;;;;;;;;;;;;;;;;;AAmBhC,SAAS,aAAa,aAAqB,MAA2B;AACpE,sBAAqB;CACrB,MAAM,KAAK,EAAE,OAAO;EAClB,IAAI;EACJ,WAAW,yBAAyB;EACpC,eAAe;EAChB,CAAC;CAEF,MAAM,UAAU,EAAE,UAAU;EAC1B,WAAW;EACX,MAAM;EACN,cAAc;EACf,CAAC;AACF,SAAQ,cAAc;CAEtB,MAAM,WAAW,EAAE,UAAU;EAC3B,WAAW;EACX,MAAM;EACN,cAAc;EACf,CAAC;AACF,UAAS,cAAc;AACvB,UAAS,iBAAiB,eAAe;AACvC,aAAW,CAAC,OAAO,QAAQ,QAAQ,MAAM,2CAA2C,IAAI,CAAC;GACzF;CAEF,MAAM,UAAU,EACd,OACA,EAAE,WAAW,sBAAsB,EACnC,SACA,EAAE,QAAQ,EAAE,WAAW,sBAAsB,CAAC,EAC9C,SACD;AAED,KAAI,SAAS,OAEX,IAAG,OAAO,QAAQ;MACb;EACL,MAAM,UAAU,EAAE,UAAU;GAC1B,WAAW;GACX,MAAM;GACN,cAAc;GACf,CAAC;AACF,UAAQ,cAAc;AACtB,UAAQ,iBAAiB,eAAe;AACtC,YAAS,QAAQ,YAAY;IAC7B;EAEF,MAAM,WAAW,EAAE,QAAQ,EAAE,WAAW,mBAAmB,CAAC;AAC5D,WAAS,cAAc;AAEvB,KAAG,OACD,SACA,EACE,OACA,EAAE,WAAW,oBAAoB,EACjC,EAAE,QAAQ,EAAE,WAAW,mBAAmB,CAAC,EAC3C,SACD,EACD,QACD;;AAGH,UAAS,KAAK,YAAY,GAAG;;;;;;;AAQ/B,SAAS,mBAAmB,QAA8B;AACxD,qBAAoB;AACpB,KAAI,OAAO,UAAU,OAAQ;CAS7B,MAAM,QAAQ,EAAE,OAAO;EACrB,IAAI;EACJ,WAAW,aARX,OAAO,UAAU,mBACb,6BACA,OAAO,UAAU,UACf,mBACA;EAKN,eAAe;EAChB,CAAC;AACF,UAAS,KAAK,YAAY,MAAM;;;AAIlC,SAAS,mBAAmB,aAA2B;CACrD,MAAM,OAAO,SAAS,cAAc,IAAI,mBAAmB,mBAAmB;AAC9E,KAAI,KAAM,MAAK,cAAc;;AAG/B,SAAS,sBAA4B;AACnC,sBAAqB;CACrB,MAAM,KAAK,EAAE,OAAO;EAClB,IAAI;EACJ,WAAW;EACX,eAAe;EAChB,CAAC;AACF,UAAS,KAAK,YAAY,GAAG;;;;;;;AAQ/B,SAAgB,kBAAwB;AACtC,KAAI,OAAO,aAAa,YAAa;AACrC,KAAI,oBAAqB,sBAAqB;CAC9C,MAAM,OAAO,SAAS;AACtB,MAAK,UAAU,OAAO,sBAAsB;AAC5C,MAAK,UAAU,OAAO,sBAAsB;AAC5C,YAAW,iBAAiB;AAC5B,qBAAoB;AACpB,sBAAqB;AACrB,sBAAqB;AACrB,wBAAuB;AACvB,yBAAwB;;;;;;;AAQ1B,SAAgB,cAAc,OAA4B;AACxD,KAAI,OAAO,aAAa,YAAa;CACrC,MAAM,OAAO,SAAS;CACtB,MAAM,QAAQ,oBAAoB;AAClC,KAAI,CAAC,MAAO;CAEZ,MAAM,OAAO,oBAAoB,MAAM;AAEvC,KAAI,MAAM,WAAW,UAAU,KAAK,UAAU,KAAK,KAAK,WAAW,GAAG;AACpE,OAAK,UAAU,OAAO,sBAAsB;AAC5C,OAAK,UAAU,OAAO,sBAAsB;AAC5C,QAAM,cAAc;AACpB,sBAAoB;AACpB,uBAAqB;AACrB,uBAAqB;AACrB,sBAAoB,MAAM,MAAM;AAChC;;AAGF,KAAI,CAAC,uBAAuB;AAC1B,0BAAwB;AACxB,UAAQ,KACN,oIAED;;AAGH,MAAK,UAAU,IAAI,sBAAsB;AACzC,MAAK,UAAU,OAAO,uBAAuB,MAAM,MAAM;CAEzD,MAAM,SAAS,MAAM,WAAW,WAAW,OAAO,UAAU,MAAM,OAAO;CACzE,MAAM,YAAY,qBAAqB,MAAM,KAAK;AAGlD,qBAAoB,QAAQ,UAAU;AAMtC,OAAM,cAAwB;;eAEjB,KAAK,MAAM;mBACP,KAAK,MAAM;oBACV,KAAK,OAAO;oBACZ,KAAK,OAAO;;;AAM9B,KAAI,UAAU,MAAM,SAAS,CAAC,UAAW,oBAAmB,OAAO;KAC9D,qBAAoB;AAEzB,KAAI,UAAU,MAAM,SAAS,CAAC,aAAa,OAAO,iBAAiB,EAAG,sBAAqB;KACtF,sBAAqB;AAE1B,KAAI,UAAU,MAAM,aAAa,CAAC,UAChC,cAAa,SAAS,MAAM,MAAM,aAAa,MAAM,cAAc;KAEnE,sBAAqB;;AAIzB,SAAS,mBAAmB,GAAmC;AAC7D,QAAO,OAAO,MAAM,YAAY,iBAAiB,MAAM,MAAM,EAAE,OAAO,EAAE;;AAG1E,SAAS,sBAAsB,GAAsC;AACnE,QAAO,MAAM,UAAU,MAAM,cAAc,MAAM;;AAGnD,SAAS,iBAAiB,GAAiC;AACzD,QAAO,MAAM,QAAQ,MAAM,cAAc,MAAM;;;AAIjD,SAAS,uBAAuB,GAAyB;AACvD,QAAO,OAAO,MAAM,YAAY,OAAO,UAAU,EAAE,IAAI,KAAK,KAAK,KAAA;;;AAInE,SAAgB,qBAAqB,KAA4B;AAC/D,KAAI,CAAC,OAAO,SAAS,IAAI,CAAE,QAAO;CAClC,MAAM,IAAI,KAAK,MAAM,IAAI;AACzB,KAAI,IAAI,EAAG,QAAO;AAClB,QAAO,KAAK,IAAI,GAAG,oBAAoB;;;;;;AAOzC,SAAgB,0BAAyD;AACvE,KAAI,OAAO,mBAAmB,YAAa,QAAO;CAClD,MAAM,MAAM,eAAe,QAAQ,qBAAqB;AACxD,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,OAAO,WAAW,YAAY,WAAW,KAAM,QAAO;EAC1D,MAAM,MAAM;EACZ,MAAM,OAA+B,EAAE;AACvC,MAAI,mBAAmB,IAAI,OAAO,CAAE,MAAK,SAAS,IAAI;AACtD,MAAI,sBAAsB,IAAI,YAAY,CAAE,MAAK,cAAc,IAAI;AACnE,MAAI,iBAAiB,IAAI,eAAe,CAAE,MAAK,iBAAiB,IAAI;AAEpE,MAAI,uBAAuB,IAAI,YAAY,CAAE,MAAK,cAAc,IAAI;AACpE,MAAI,uBAAuB,IAAI,aAAa,CAAE,MAAK,eAAe,IAAI;AACtE,MAAI,OAAO,IAAI,UAAU,UAAW,MAAK,QAAQ,IAAI;AACrD,MAAI,OAAO,IAAI,cAAc,UAAW,MAAK,YAAY,IAAI;AAC7D,MAAI,IAAI,kBAAkB,aAAa,IAAI,kBAAkB,OAC3D,MAAK,gBAAgB,IAAI;AAE3B,SAAO;SACD;AACN,SAAO;;;AAIX,SAAgB,sBAAsB,OAA4B;AAChE,KAAI,OAAO,mBAAmB,YAAa;AAC3C,KAAI;AACF,iBAAe,QAAQ,sBAAsB,KAAK,UAAU,MAAM,CAAC;SAC7D;;AAKV,IAAI,sBAAsB;AAC1B,IAAI,sBAA2C;;;;;;;;AAS/C,SAAgB,eAA2B;AACzC,KAAI,OAAO,WAAW,YAAa,cAAa;AAChD,KAAI,uBAAuB,oBAAqB,QAAO;CAEvD,MAAM,WAAW,yBAAyB;AAC1C,KAAI,SACF,UAAS,MAAM,YAAY,SAAS;AAEtC,eAAc,SAAS,MAAM,SAAS;AACtC,0BAAyB,SAAS,MAAM,SAAS;CAEjD,IAAI,mBAAmB,KAAK,UAAU,SAAS,MAAM,SAAS;CAC9D,IAAI,gBAAgB,SAAS,MAAM,MAAM;CAEzC,MAAM,gBAAgB,SAAS,gBAAgB;EAC7C,MAAM,KAAK,SAAS,MAAM;EAC1B,MAAM,YAAY,SAAS,MAAM,MAAM;EACvC,MAAM,OAAO,KAAK,UAAU,GAAG;EAE/B,MAAM,kBAAkB,SAAS;AAGjC,MAAI,CAAC,mBAAmB,EAFH,cAAc,eAEI;AACvC,qBAAmB;AACnB,kBAAgB;AAEhB,MAAI,iBAAiB;AACnB,iBAAc,GAAG;AACjB,yBAAsB,GAAG;AACzB,4BAAyB,GAAG;QAG5B,oBAAmB,UAAU;GAE/B;AAEF,uBAAsB;AACtB,6BAA4B;AAC1B,iBAAe;AACf,wBAAsB;AACtB,wBAAsB;;AAExB,QAAO;;;;;;;;;;ACluBT,SAAS,gBAAgB,YAAgE;AACvF,KAAI,CAAC,cAAc,WAAW,WAAW,WAAY,QAAO;CAC5D,MAAM,OAAO,WAAW,WAAW,gBAAgB,UAAU;CAC7D,MAAM,QAAQ,WAAW,WAAW,gBAAgB,YAAY;CAChE,MAAM,QAAQ,EAAE,QAAQ;EACtB,WAAW;EACX,OACE,WAAW,WAAW,gBAClB,uEACA;EACP,CAAC;AACF,OAAM,cAAc;AACpB,OAAM,MAAM,UAAU,wBAAwB,MAAM;AACpD,QAAO;;AAGT,SAAgB,oBAAiC;CAC/C,MAAM,IAAI,SAAS;CACnB,MAAM,KAAK,EAAE;CACb,MAAM,WAAW,CAAC,EAAE;CACpB,MAAM,YAAY,EAAE,MAAM;AAE1B,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;CAGvD,MAAM,eAAe,EAAE,UAAU,EAAE,WAAW,cAAc,CAAC;AAC7D,KAAI,SAAU,cAAa,WAAW;AACtC,MAAK,MAAM,UAAU,kBAAkB;EACrC,MAAM,QACJ,OAAO,OAAO,UAAU,OAAO,OAAO,WAClC,OAAO,QACP,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,GAAG,OAAO,OAAO;EACxD,MAAM,SAAS,EAAE,UAAU,EAAE,OAAO,OAAO,IAAI,EAAE,MAAM;AACvD,MAAI,OAAO,OAAO,GAAG,OAAQ,QAAO,WAAW;AAC/C,eAAa,YAAY,OAAO;;AAElC,cAAa,iBAAiB,gBAAgB;EAC5C,MAAM,KAAK,aAAa;EACxB,MAAM,QAA4B,EAAE,QAAQ,IAAI;AAEhD,MAAI,OAAO,UAAU;GACnB,MAAM,UAAU,UAAU,GAAG,OAAO;AACpC,OAAI,QAAQ,QAAQ,EAAG,OAAM,cAAc,QAAQ;AACnD,OAAI,QAAQ,SAAS,EAAG,OAAM,eAAe,QAAQ;;AAEvD,WAAS,MAAM,YAAY,MAAM;GACjC;CAGF,MAAM,oBAAoB,EAAE,UAAU,EAAE,WAAW,cAAc,CAAC;AAClE,KAAI,SAAU,mBAAkB,WAAW;AAC3C,MAAK,MAAM,OAAO;EAAC;EAAQ;EAAY;EAAY,EAA2B;EAC5E,MAAM,SAAS,EAAE,UAAU,EAAE,OAAO,KAAK,EAAE,IAAI;AAC/C,MAAI,QAAQ,GAAG,YAAa,QAAO,WAAW;AAC9C,oBAAkB,YAAY,OAAO;;AAEvC,mBAAkB,iBAAiB,gBAAgB;AACjD,WAAS,MAAM,YAAY,EACzB,aAAa,kBAAkB,OAChC,CAAC;GACF;CAGF,MAAM,YAAY,EAAE,OAAO,EAAE,WAAW,eAAe,CAAC;AACxD,KAAI,GAAG,WAAW,UAAU;EAC1B,MAAM,aAAa,EAAE,SAAS;GAC5B,WAAW;GACX,MAAM;GACN,KAAK;GACL,OAAO,OAAO,GAAG,YAAY;GAC9B,CAAC;EACF,MAAM,cAAc,EAAE,SAAS;GAC7B,WAAW;GACX,MAAM;GACN,KAAK;GACL,OAAO,OAAO,GAAG,aAAa;GAC/B,CAAC;AACF,MAAI,UAAU;AACZ,cAAW,WAAW;AACtB,eAAY,WAAW;;AAEzB,aAAW,iBAAiB,gBAAgB;GAC1C,MAAM,UAAU,qBAAqB,OAAO,WAAW,MAAM,CAAC;AAC9D,OAAI,YAAY,MAAM;AACpB,aAAS,MAAM,YAAY,EAAE,aAAa,SAAS,CAAC;AACpD,eAAW,QAAQ,OAAO,QAAQ;;IAEpC;AACF,cAAY,iBAAiB,gBAAgB;GAC3C,MAAM,UAAU,qBAAqB,OAAO,YAAY,MAAM,CAAC;AAC/D,OAAI,YAAY,MAAM;AACpB,aAAS,MAAM,YAAY,EAAE,cAAc,SAAS,CAAC;AACrD,gBAAY,QAAQ,OAAO,QAAQ;;IAErC;AACF,YAAU,OACR,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,0BAA0B,CAAC,EAC1E,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,qBAAqB,CAAC,EAAE,WAAW,EACvF,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,sBAAsB,CAAC,EAAE,YAAY,CAC1F;;CAIH,MAAM,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACtD,eAAc,UAAU,GAAG;AAC3B,KAAI,SAAU,eAAc,WAAW;AACvC,eAAc,iBAAiB,gBAAgB;AAC7C,WAAS,MAAM,YAAY,EAAE,OAAO,cAAc,SAAS,CAAC;GAC5D;CAGF,MAAM,iBAAiB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvD,gBAAe,UAAU,GAAG;AAC5B,KAAI,SAAU,gBAAe,WAAW;AACxC,gBAAe,iBAAiB,gBAAgB;AAC9C,WAAS,MAAM,YAAY,EAAE,WAAW,eAAe,SAAS,CAAC;GACjE;CAGF,MAAM,mBAAmB,EAAE,UAAU,EAAE,WAAW,cAAc,CAAC;AACjE,KAAI,YAAY,CAAC,GAAG,UAAW,kBAAiB,WAAW;AAC3D,MAAK,MAAM,OAAO,CAAC,WAAW,OAAO,EAAqB;EACxD,MAAM,SAAS,EAAE,UAAU,EAAE,OAAO,KAAK,EAAE,IAAI;AAC/C,MAAI,QAAQ,GAAG,cAAe,QAAO,WAAW;AAChD,mBAAiB,YAAY,OAAO;;AAEtC,kBAAiB,iBAAiB,gBAAgB;AAChD,WAAS,MAAM,YAAY,EAAE,eAAe,iBAAiB,OAAwB,CAAC;GACtF;CAGF,MAAM,OAAO,oBAAoB,GAAG;CACpC,MAAM,WAAW,EAAE,OAAO,EAAE,WAAW,eAAe,CAAC;AAEvD,KAAI,GAAG,WAAW,UAAU,KAAK,UAAU,EACzC,UAAS,YACP,EAAE,OAAO,EAAE,OAAO,6BAA6B,EAAE,EAAE,+BAA+B,CAAC,CACpF;MACI;EACL,MAAM,SAAS,GAAG,WAAW,WAAW,OAAO,UAAU,GAAG,OAAO;EACnE,MAAM,YAAY,qBAAqB,GAAG;EAC1C,MAAM,YAAY,cAAc;EAChC,MAAM,OAA2B,EAAE;EAGnC,MAAM,MAAM,QAAQ,OAAO;EAC3B,MAAM,QAAQ,KAAK,MAAM,KAAK,QAAQ,IAAI;EAC1C,MAAM,QAAQ,KAAK,MAAM,KAAK,SAAS,IAAI;EAC3C,MAAM,gBACJ,GAAG,gBAAgB,SACf,EAAE,mCAAmC,EAAE,QAAQ,WAAW,CAAC,GAC3D;AACN,OAAK,KACH,EACE,OACA,EAAE,WAAW,kBAAkB,EAC/B,EAAE,QAAQ,EAAE,EAAE,EAAE,8BAA8B,CAAC,EAC/C,EACE,QACA,EAAE,WAAW,oBAAoB,EACjC,GAAG,KAAK,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI,MAAM,MAAM,GAAG,MAAM,GAAG,gBAC7D,CACF,CACF;AAED,MAAI,QAAQ;GACV,MAAM,SAAS,sBAAsB,QAAQ,UAAU;GACvD,MAAM,kBAAkB,EACtB,QACA,EAAE,WAAW,oBAAoB,EACjC,IAAI,OAAO,IAAI,IAAI,OAAO,MAAM,IAAI,OAAO,OAAO,IAAI,OAAO,OAC9D;GACD,MAAM,QAAQ,gBAAgB,OAAO,mBAAmB;AACxD,OAAI,MAAO,iBAAgB,YAAY,MAAM;AAC7C,QAAK,KACH,EACE,OACA,EAAE,WAAW,kBAAkB,EAC/B,EAAE,QAAQ,EAAE,EAAE,EAAE,2BAA2B,CAAC,EAC5C,gBACD,CACF;;AAGH,MAAI,GAAG,aAAa,CAAC,WAAW;GAG9B,MAAM,YAAY,GAAG,kBAAkB,YAAa,QAAQ,gBAAgB,IAAK;AACjF,QAAK,KACH,EACE,OACA,EAAE,WAAW,kBAAkB,EAC/B,EAAE,QAAQ,EAAE,EAAE,EAAE,4BAA4B,CAAC,EAC7C,EACE,QACA,EAAE,WAAW,oBAAoB,EACjC,EAAE,kCAAkC;IAClC,QAAQ;IACR,MAAM,GAAG;IACV,CAAC,CACH,CACF,CACF;;AAGH,OAAK,MAAM,OAAO,KAAM,UAAS,YAAY,IAAI;;CAInD,MAAM,gBAAgB,EACpB,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,0BAA0B,CAAC,EAC1E,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,sBAAsB,CAAC,EAAE,aAAa,EAC1F,EACE,OACA,EAAE,WAAW,WAAW,EACxB,EAAE,SAAS,EAAE,EAAE,EAAE,2BAA2B,CAAC,EAC7C,kBACD,CACF;AAMD,WAAU,OACR,eACA,WACA,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,EAAE,8BAA8B,CAAC,EAC9E,EACE,OACA,EAAE,WAAW,WAAW,EACxB,EAAE,SAAS,EAAE,EAAE,EAAE,yBAAyB,CAAC,EAC3C,cACD,EACD,EACE,OACA,EAAE,WAAW,WAAW,EACxB,EAAE,SAAS,EAAE,EAAE,EAAE,6BAA6B,CAAC,EAC/C,eACD,EACD,EACE,OACA,EAAE,WAAW,WAAW,EACxB,EAAE,SAAS,EAAE,EAAE,EAAE,0BAA0B,CAAC,EAC5C,iBACD,CACF,EACD,SACD;AAED,QAAO;;;;ACzPT,MAAM,WAAsD;CAC1D;EAAE,IAAI;EAAO,UAAU;EAAiB;CACxC;EAAE,IAAI;EAAW,UAAU;EAAqB;CAChD;EAAE,IAAI;EAAY,UAAU;EAAsB;CAClD;EAAE,IAAI;EAAe,UAAU;EAAyB;CACxD;EAAE,IAAI;EAAiB,UAAU;EAA2B;CAC5D;EAAE,IAAI;EAAY,UAAU;EAAsB;CAClD;EAAE,IAAI;EAAU,UAAU;EAAoB;CAC9C;EAAE,IAAI;EAAO,UAAU;EAAiB;CACxC;EAAE,IAAI;EAAO,UAAU;EAAiB;CACxC;EAAE,IAAI;EAAU,UAAU;EAAoB;CAC9C;EAAE,IAAI;EAAa,UAAU;EAAuB;CACpD;EAAE,IAAI;EAAW,UAAU;EAAqB;CACjD;AAED,SAAgB,UAA+C;AAC7D,QAAO,SAAS,KAAK,SAAS;EAAE,IAAI,IAAI;EAAI,OAAO,EAAE,IAAI,SAAS;EAAE,EAAE;;AASxE,SAAgB,mBAAmB,cAA4D;AAC7F,QAAO;EACL,KAAK;EACL,eAAe,iBAAiB,aAAa;EAC7C,aAAa;EACb,eAAe;EACf,UAAU;EACV,QAAQ;EACR,UAAU;EACV,KAAK;EACL,KAAK;EACL,QAAQ;EACR,WAAW;EACX,eAAe,iBAAiB,aAAa;EAC9C;;;;;;;;;;;ACpDH,MAAM,iBAAiB;;;;;;AAOvB,SAAS,uBAAuB,OAA+B;AAE7D,KAAI,OAAO,UAAU,YAAa;AAClC,OAAM,gBAAgB;EACpB,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,MAAM;EAC5B,CAAC,CAAC,YAAY,GAEb;;AAKJ,SAAS,cAAc,IAAiB,aAAyB;CAC/D,IAAI,aAAa;CACjB,IAAI,SAAS,GACX,SAAS;CACX,IAAI,YAAY,GACd,WAAW;CACb,IAAI,WAAW;AAEf,IAAG,iBAAiB,gBAAgB,MAAM;AACxC,eAAa;AACb,aAAW;AACX,WAAS,EAAE;AACX,WAAS,EAAE;EACX,MAAM,OAAO,GAAG,uBAAuB;AACvC,cAAY,KAAK;AACjB,aAAW,KAAK;AAChB,KAAG,kBAAkB,EAAE,UAAU;AACjC,IAAE,gBAAgB;GAClB;AAEF,IAAG,iBAAiB,gBAAgB,MAAM;AACxC,MAAI,CAAC,WAAY;EACjB,MAAM,KAAK,EAAE,UAAU;EACvB,MAAM,KAAK,EAAE,UAAU;AACvB,MAAI,KAAK,IAAI,GAAG,GAAG,KAAK,KAAK,IAAI,GAAG,GAAG,GAAG;AACxC,cAAW;AACX,MAAG,UAAU,IAAI,WAAW;;AAE9B,MAAI,CAAC,SAAU;AAEf,KAAG,MAAM,OAAO,GAAG,YAAY,GAAG;AAClC,KAAG,MAAM,MAAM,GAAG,WAAW,GAAG;AAChC,KAAG,MAAM,QAAQ;AACjB,KAAG,MAAM,SAAS;GAClB;AAEF,IAAG,iBAAiB,cAAc,MAAM;AACtC,MAAI,CAAC,WAAY;AACjB,eAAa;AACb,KAAG,UAAU,OAAO,WAAW;AAC/B,KAAG,sBAAsB,EAAE,UAAU;AAErC,MAAI,UAAU;AACZ,cAAW,GAAG;AACd,uBAAoB,GAAG;AACvB,sBAAmB,GAAG;QAEtB,cAAa;GAEf;AAEF,IAAG,iBAAiB,kBAAkB,MAAM;AAC1C,eAAa;AACb,KAAG,UAAU,OAAO,WAAW;AAC/B,KAAG,sBAAsB,EAAE,UAAU;AACrC,MAAI,UAAU;AACZ,cAAW,GAAG;AACd,uBAAoB,GAAG;AACvB,sBAAmB,GAAG;;GAExB;;AAGJ,SAAS,WAAW,IAAiB;CACnC,MAAM,OAAO,GAAG,uBAAuB;CACvC,MAAM,KAAK,OAAO;CAClB,MAAM,KAAK,OAAO;CAClB,MAAM,KAAK,KAAK,OAAO,KAAK,QAAQ;CACpC,MAAM,SAAS;AAEf,KAAI,KAAK,KAAK,GAAG;AACf,KAAG,MAAM,OAAO,GAAG,OAAO;AAC1B,KAAG,MAAM,QAAQ;QACZ;AACL,KAAG,MAAM,OAAO;AAChB,KAAG,MAAM,QAAQ,GAAG,OAAO;;CAG7B,MAAM,MAAM,KAAK,IAAI,QAAQ,KAAK,IAAI,KAAK,KAAK,SAAS,QAAQ,KAAK,IAAI,CAAC;AAC3E,IAAG,MAAM,MAAM,GAAG,IAAI;AACtB,IAAG,MAAM,SAAS;;AAGpB,SAAS,oBAAoB,UAAuB;AAClD,KAAI,CAAC,QAAS;CACd,MAAM,KAAK,OAAO;CAClB,MAAM,KAAK,OAAO;AAGlB,KAAI,MAAA,KAAmC;AACrC,UAAQ,MAAM,MAAM;AACpB,UAAQ,MAAM,OAAO;AACrB,UAAQ,MAAM,QAAQ;AACtB,UAAQ,MAAM,SAAS;AACvB;;CAGF,MAAM,OAAO,SAAS,uBAAuB;CAE7C,MAAM,cAAA;CACN,MAAM,SAAS;AAGf,KAAI,KAAK,OAAO,KAAK,GAAG;AACtB,UAAQ,MAAM,OAAO,GAAG,OAAO;AAC/B,UAAQ,MAAM,QAAQ;QACjB;AACL,UAAQ,MAAM,OAAO;AACrB,UAAQ,MAAM,QAAQ,GAAG,OAAO;;AAKlC,KAAI,KAAK,MAAM,KAAK,GAAG;EACrB,MAAM,MAAM,KAAK,IAAI,KAAK,SAAS,GAAG,KAAK,cAAc,OAAO;AAChE,UAAQ,MAAM,MAAM,GAAG,KAAK,IAAI,QAAQ,IAAI,CAAC;AAC7C,UAAQ,MAAM,SAAS;QAClB;EACL,MAAM,SAAS,KAAK,IAAI,KAAK,KAAK,MAAM,GAAG,KAAK,cAAc,OAAO;AACrE,UAAQ,MAAM,MAAM;AACpB,UAAQ,MAAM,SAAS,GAAG,KAAK,IAAI,QAAQ,OAAO,CAAC;;;AAIvD,SAAS,mBAAmB,IAAiB;AAC3C,cAAa,QACX,iBACA,KAAK,UAAU;EACb,MAAM,GAAG,MAAM;EACf,KAAK,GAAG,MAAM;EACd,OAAO,GAAG,MAAM;EAChB,QAAQ,GAAG,MAAM;EAClB,CAAC,CACH;;AAIH,SAAS,sBAAsB,IAAiB;CAC9C,MAAM,QAAQ,aAAa,QAAQ,gBAAgB;AACnD,KAAI,MACF,KAAI;EACF,MAAM,MAAM,KAAK,MAAM,MAAM;AAC7B,MAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM;EAC7C,MAAM,cAAc;GAAC;GAAQ;GAAO;GAAS;GAAS;EACtD,MAAM,gBAAgB;AACtB,OAAK,MAAM,OAAO,YAChB,KAAI,OAAO,OAAO,OAAO,IAAI,SAAS,YAAY,cAAc,KAAK,IAAI,KAAK,CAC5E,IAAG,MAAM,OAAO,IAAI;SAGlB;MAGH;AACL,KAAG,MAAM,SAAS;AAClB,KAAG,MAAM,QAAQ;;;AAMrB,IAAI,aAAoB;AACxB,IAAI,SAAS;AACb,IAAI,UAA8B;AAClC,IAAI,SAA6B;AACjC,IAAI,SAA6B;AACjC,IAAI,WAA+B;AACnC,IAAI,gBAAyC;AAI7C,IAAI,wBAAqD;AACzD,IAAI,gBAAqC;AACzC,IAAI,sBAA2C;AAC/C,IAAI,sBAA2C;AAG/C,IAAI,eAAwD;AAE5D,SAAS,eAAe;AACtB,KAAI,CAAC,UAAU,CAAC,OAAQ;AACxB,KAAI,CAAC,aAAc,gBAAe,mBAAmB,aAAa;AAClE,QAAO,YAAY;AACnB,KAAI;AACF,SAAO,YAAY,aAAa,aAAa,CAAC;UACvC,KAAK;AACZ,UAAQ,MAAM,2CAA2C,WAAW,KAAK,IAAI;AAC7E,SAAO,YACL,EAAE,OAAO,EAAE,WAAW,uBAAuB,EAAE,EAAE,kBAAkB,EAAE,KAAK,YAAY,CAAC,CAAC,CACzF;;AAGH,QAAO,iBAAiB,iBAAiB,CAAC,SAAS,OAAO;AACxD,KAAG,UAAU,OAAO,UAAU,GAAG,aAAa,WAAW,KAAK,WAAW;GACzE;;AAGJ,SAAS,QAAQ;AACf,KAAI,OAAO,aAAa,YAAa;AACrC,KAAI,SAAS,cAAc,oBAAoB,CAAE;AAGjD,uBAAsB,aAAa;AAGnC,eAAc;AAGd,iBAAgB,SAAS,cAAc,QAAQ;AAC/C,eAAc,cAAc;AAC5B,UAAS,KAAK,YAAY,cAAc;CAGxC,MAAM,SAAS,EACb,UACA;EAAE,WAAW;EAAoB,OAAO,EAAE,qBAAqB;EAAE,EACjE,MACD;AACD,YAAW;AACX,uBAAsB,OAAO;AAG7B,WAAU,EAAE,OAAO,EAAE,WAAW,aAAa,CAAC;CAE9C,MAAM,WAAW,EAAE,UAAU;EAAE,WAAW;EAAmB,OAAO,EAAE,cAAc;EAAE,EAAE,IAAS;AACjG,UAAS,iBAAiB,eAAe;AACvC,WAAS;AACT,UAAS,UAAU,OAAO,OAAO;AACjC,YAAU,cAAc;GACxB;CAEF,MAAM,YAAY,EAChB,QACA;EACE,WAAW,kBAAkB,SAAS,MAAM,gBAAgB,sBAAsB;EAClF,OAAO,EAAE,6BAA6B;EACvC,EACD,SAAS,MAAM,gBAAgB,EAAE,oBAAoB,GAAG,EAAE,qBAAqB,CAChF;AAED,WAAU,iBAAiB,eAAe;AACxC,WAAS,OAAO,EAAE,eAAe,CAAC,SAAS,MAAM,eAAe,CAAC;AACjE,YAAU,YAAY,kBAAkB,SAAS,MAAM,gBAAgB,sBAAsB;AAC7F,YAAU,cAAc,SAAS,MAAM,gBACnC,EAAE,oBAAoB,GACtB,EAAE,qBAAqB;AAC3B,gBAAc;GACd;CAEF,MAAM,cAAc,EAClB,QACA,EAAE,OAAO,2CAA2C,EACpD,WACA,EAAE,QAAQ,EAAE,OAAO,6CAA6C,EAAE,UAAkB,EACpF,SACD;CACD,MAAM,SAAS,EACb,OACA,EAAE,WAAW,oBAAoB,EACjC,EAAE,QAAQ,EAAE,EAAE,EAAE,cAAc,CAAC,EAC/B,YACD;AAED,UAAS,EAAE,OAAO,EAAE,WAAW,kBAAkB,CAAC;AAClD,MAAK,MAAM,OAAO,SAAS,EAAE;EAC3B,MAAM,QAAQ,EAAE,UAAU;GAAE,WAAW;GAAiB,YAAY,IAAI;GAAI,EAAE,IAAI,MAAM;AACxF,QAAM,iBAAiB,eAAe;AACpC,gBAAa,IAAI;AACjB,aAAU,UAAU,IAAI,GAAG;AAC3B,iBAAc;IACd;AACF,SAAO,YAAY,MAAM;;AAG3B,UAAS,EAAE,OAAO,EAAE,WAAW,kBAAkB,CAAC;AAElD,SAAQ,OAAO,QAAQ,QAAQ,OAAO;AACtC,UAAS,KAAK,OAAO,SAAS,OAAO;AAGrC,YAAW,OAAO;AAClB,oBAAmB,OAAO;AAE1B,eAAc,cAAc;AAC1B,WAAS,CAAC;AACV,UAAS,UAAU,OAAO,QAAQ,OAAO;AACzC,MAAI,QAAQ;AACV,uBAAoB,OAAO;AAC3B,iBAAc;AACd,aAAU,aAAa;QAEvB,WAAU,cAAc;GAE1B;CAGF,IAAI,YAAY;AAChB,uBAAsB;AACpB,MAAI,UAAW;AACf,cAAY,4BAA4B;AACtC,eAAY;AACZ,cAAW,OAAO;AAClB,sBAAmB,OAAO;AAC1B,OAAI,OAAQ,qBAAoB,OAAO;IACvC;;AAEJ,QAAO,iBAAiB,UAAU,cAAc;AAQhD,uBAAsB,SAAS,gBAAgB;AAC7C,MAAI;AACF,OACE,WACC,eAAe,SACd,eAAe,eACf,eAAe,aACf,eAAe,YACf,eAAe,cACf,eAAe,SACf,eAAe,SACf,eAAe,WAEjB,eAAc;WAET,KAAK;AACZ,WAAQ,MAAM,mDAAmD,IAAI;;AAOvE,yBAAuB,SAAS,MAAM;GACtC;AAKF,0BAAyB,MAAa;AAEpC,eADgB,EAAkB,OACd;AACpB,MAAI,WAAW,CAAC,QAAQ,UAAU,SAAS,OAAO,EAAE;AAClD,YAAS;AACT,WAAQ,UAAU,IAAI,OAAO;;AAE/B,gBAAc;;AAEhB,QAAO,iBAAiB,0BAA0B,sBAAsB;AAMxE,6BAA4B;EAI1B,MAAM,WAAW;EACjB,MAAM,YAAY;AAClB,gBAAc;AACd,MAAI;AACF,UAAO;AACP,gBAAa;AACb,OAAI,aAAa,SAAS;AACxB,aAAS;AACT,YAAQ,UAAU,IAAI,OAAO;;AAE/B,iBAAc;WACP,KAAK;AACZ,WAAQ,MAAM,8DAA8D,IAAI;;;AAGpF,QAAO,iBAAiB,qBAAqB,oBAAoB;AAEjE,eAAc;AAGd,WAAU,MAAM;;;;;;;;;;AAWlB,SAAS,eAAqB;AAC5B,KAAI,OAAO,aAAa,YAAa;AAErC,KAAI,yBAAyB,OAAO,WAAW,YAC7C,QAAO,oBAAoB,0BAA0B,sBAAsB;AAE7E,KAAI,iBAAiB,OAAO,WAAW,YACrC,QAAO,oBAAoB,UAAU,cAAc;AAErD,KAAI,uBAAuB,OAAO,WAAW,YAC3C,QAAO,oBAAoB,qBAAqB,oBAAoB;AAEtE,KAAI,oBAAqB,sBAAqB;AAE9C,WAAU,QAAQ;AAClB,UAAS,QAAQ;AACjB,gBAAe,QAAQ;AAEvB,kBAAiB;AACjB,6BAA4B,GAAG;AAE/B,yBAAwB;AACxB,iBAAgB;AAChB,uBAAsB;AACtB,uBAAsB;AACtB,YAAW;AACX,WAAU;AACV,UAAS;AACT,UAAS;AACT,iBAAgB;AAChB,gBAAe;AACf,cAAa;AACb,UAAS;;AAIX,IAAI,OAAO,aAAa,aAAa;CACnC,MAAM,kBAAkB;AACtB,MAAI;AACF,UAAO;WACA,KAAK;AACZ,WAAQ,MAAM,6CAA6C,IAAI;;;AAGnE,KAAI,SAAS,eAAe,UAC1B,UAAS,iBAAiB,oBAAoB,UAAU;KAExD,YAAW"}