@ait-co/devtools 0.1.6 → 0.1.7
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.
- package/dist/panel/index.js +130 -3
- package/dist/panel/index.js.map +1 -1
- package/package.json +1 -1
package/dist/panel/index.js
CHANGED
|
@@ -1390,7 +1390,119 @@ function renderEventsTab() {
|
|
|
1390
1390
|
return container;
|
|
1391
1391
|
}
|
|
1392
1392
|
//#endregion
|
|
1393
|
+
//#region src/mock/iap/index.ts
|
|
1394
|
+
/**
|
|
1395
|
+
* IAP (인앱결제) mock
|
|
1396
|
+
*/
|
|
1397
|
+
let orderCounter = 0;
|
|
1398
|
+
function generateOrderId() {
|
|
1399
|
+
return `mock-order-${++orderCounter}-${Date.now()}`;
|
|
1400
|
+
}
|
|
1401
|
+
function buildOrderResult(sku) {
|
|
1402
|
+
const product = aitState.state.iap.products.find((p) => p.sku === sku);
|
|
1403
|
+
const amountStr = product?.displayAmount?.replace(/[^0-9]/g, "") ?? "1000";
|
|
1404
|
+
return {
|
|
1405
|
+
orderId: generateOrderId(),
|
|
1406
|
+
displayName: product?.displayName ?? "Mock Product",
|
|
1407
|
+
displayAmount: product?.displayAmount ?? "1,000원",
|
|
1408
|
+
amount: parseInt(amountStr, 10) || 1e3,
|
|
1409
|
+
currency: "KRW",
|
|
1410
|
+
fraction: 0,
|
|
1411
|
+
miniAppIconUrl: product?.iconUrl || null
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
async function handlePurchase(sku, processProductGrant, onEvent, onError) {
|
|
1415
|
+
const nextResult = aitState.state.iap.nextResult;
|
|
1416
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
1417
|
+
if (nextResult !== "success") {
|
|
1418
|
+
onError({ code: nextResult });
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
const result = buildOrderResult(sku);
|
|
1422
|
+
try {
|
|
1423
|
+
if (!await processProductGrant({ orderId: result.orderId })) {
|
|
1424
|
+
onError({ code: "PRODUCT_NOT_GRANTED_BY_PARTNER" });
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
} catch (e) {
|
|
1428
|
+
onError(e);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
aitState.patch("iap", { completedOrders: [...aitState.state.iap.completedOrders, {
|
|
1432
|
+
orderId: result.orderId,
|
|
1433
|
+
sku,
|
|
1434
|
+
status: "COMPLETED",
|
|
1435
|
+
date: (/* @__PURE__ */ new Date()).toISOString()
|
|
1436
|
+
}] });
|
|
1437
|
+
await onEvent({
|
|
1438
|
+
type: "success",
|
|
1439
|
+
data: result
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
const IAP = createMockProxy("IAP", {
|
|
1443
|
+
createOneTimePurchaseOrder(params) {
|
|
1444
|
+
handlePurchase(params.options.sku ?? params.options.productId ?? "", params.options.processProductGrant, params.onEvent, params.onError).catch((e) => console.error("[@ait-co/devtools] IAP unexpected error:", e));
|
|
1445
|
+
return () => {};
|
|
1446
|
+
},
|
|
1447
|
+
createSubscriptionPurchaseOrder(params) {
|
|
1448
|
+
handlePurchase(params.options.sku, params.options.processProductGrant, params.onEvent, params.onError).catch((e) => console.error("[@ait-co/devtools] IAP unexpected error:", e));
|
|
1449
|
+
return () => {};
|
|
1450
|
+
},
|
|
1451
|
+
async getProductItemList() {
|
|
1452
|
+
return { products: aitState.state.iap.products.map((p) => ({
|
|
1453
|
+
...p,
|
|
1454
|
+
...p.type === "SUBSCRIPTION" ? { renewalCycle: p.renewalCycle ?? "MONTHLY" } : {}
|
|
1455
|
+
})) };
|
|
1456
|
+
},
|
|
1457
|
+
async getPendingOrders() {
|
|
1458
|
+
return { orders: [...aitState.state.iap.pendingOrders] };
|
|
1459
|
+
},
|
|
1460
|
+
async getCompletedOrRefundedOrders() {
|
|
1461
|
+
return {
|
|
1462
|
+
hasNext: false,
|
|
1463
|
+
nextKey: null,
|
|
1464
|
+
orders: [...aitState.state.iap.completedOrders]
|
|
1465
|
+
};
|
|
1466
|
+
},
|
|
1467
|
+
async completeProductGrant(args) {
|
|
1468
|
+
const idx = aitState.state.iap.pendingOrders.findIndex((o) => o.orderId === args.params.orderId);
|
|
1469
|
+
if (idx !== -1) {
|
|
1470
|
+
const order = aitState.state.iap.pendingOrders[idx];
|
|
1471
|
+
const pendingOrders = aitState.state.iap.pendingOrders.filter((_, i) => i !== idx);
|
|
1472
|
+
const completedOrders = [...aitState.state.iap.completedOrders, {
|
|
1473
|
+
orderId: order.orderId,
|
|
1474
|
+
sku: order.sku,
|
|
1475
|
+
status: "COMPLETED",
|
|
1476
|
+
date: (/* @__PURE__ */ new Date()).toISOString()
|
|
1477
|
+
}];
|
|
1478
|
+
aitState.patch("iap", {
|
|
1479
|
+
pendingOrders,
|
|
1480
|
+
completedOrders
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
return true;
|
|
1484
|
+
},
|
|
1485
|
+
async getSubscriptionInfo(_args) {
|
|
1486
|
+
return { subscription: {
|
|
1487
|
+
catalogId: 1,
|
|
1488
|
+
status: "ACTIVE",
|
|
1489
|
+
expiresAt: new Date(Date.now() + 720 * 60 * 60 * 1e3).toISOString(),
|
|
1490
|
+
isAutoRenew: true,
|
|
1491
|
+
gracePeriodExpiresAt: null,
|
|
1492
|
+
isAccessible: true
|
|
1493
|
+
} };
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
//#endregion
|
|
1393
1497
|
//#region src/panel/tabs/iap.ts
|
|
1498
|
+
function formatTimestamp(iso) {
|
|
1499
|
+
const d = new Date(iso);
|
|
1500
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
1501
|
+
return d.toLocaleTimeString();
|
|
1502
|
+
}
|
|
1503
|
+
function shortOrderId(orderId) {
|
|
1504
|
+
return orderId.length > 12 ? `…${orderId.slice(-10)}` : orderId;
|
|
1505
|
+
}
|
|
1394
1506
|
function renderIapTab() {
|
|
1395
1507
|
const s = aitState.state;
|
|
1396
1508
|
const disabled = !s.panelEditable;
|
|
@@ -1405,11 +1517,26 @@ function renderIapTab() {
|
|
|
1405
1517
|
"INTERNAL_ERROR"
|
|
1406
1518
|
];
|
|
1407
1519
|
if (disabled) container.appendChild(monitoringNotice());
|
|
1520
|
+
const pendingOrders = s.iap.pendingOrders;
|
|
1521
|
+
const pendingSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, `Pending Orders (${pendingOrders.length})`));
|
|
1522
|
+
if (pendingOrders.length === 0) pendingSection.appendChild(h("div", { className: "ait-log-entry" }, "(no pending orders)"));
|
|
1523
|
+
else for (const o of pendingOrders) {
|
|
1524
|
+
const completeBtn = h("button", { className: "ait-btn ait-btn-sm" }, "Complete");
|
|
1525
|
+
if (disabled) completeBtn.disabled = true;
|
|
1526
|
+
completeBtn.addEventListener("click", () => {
|
|
1527
|
+
IAP.completeProductGrant({ params: { orderId: o.orderId } }).catch((err) => console.error("[@ait-co/devtools] completeProductGrant error:", err));
|
|
1528
|
+
});
|
|
1529
|
+
pendingSection.appendChild(h("div", { className: "ait-log-entry" }, h("span", { className: "ait-log-type" }, "PENDING"), `${o.sku} (${shortOrderId(o.orderId)}) · ${formatTimestamp(o.paymentCompletedDate)} `, completeBtn));
|
|
1530
|
+
}
|
|
1531
|
+
const completedOrders = s.iap.completedOrders;
|
|
1532
|
+
const completedSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, `Completed Orders (${completedOrders.length})`));
|
|
1533
|
+
if (completedOrders.length === 0) completedSection.appendChild(h("div", { className: "ait-log-entry" }, "(no completed orders)"));
|
|
1534
|
+
else for (const o of completedOrders) completedSection.appendChild(h("div", { className: "ait-log-entry" }, h("span", { className: "ait-log-type" }, o.status), `${o.sku} (${shortOrderId(o.orderId)}) · ${formatTimestamp(o.date)}`));
|
|
1408
1535
|
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "IAP Simulator"), selectRow("Next Purchase Result", results, s.iap.nextResult, (v) => {
|
|
1409
1536
|
aitState.patch("iap", { nextResult: v });
|
|
1410
1537
|
}, disabled)), h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "TossPay"), selectRow("Next Payment Result", ["success", "fail"], s.payment.nextResult, (v) => {
|
|
1411
1538
|
aitState.patch("payment", { nextResult: v });
|
|
1412
|
-
}, disabled)),
|
|
1539
|
+
}, disabled)), pendingSection, completedSection);
|
|
1413
1540
|
return container;
|
|
1414
1541
|
}
|
|
1415
1542
|
//#endregion
|
|
@@ -2375,7 +2502,7 @@ function mount() {
|
|
|
2375
2502
|
mockBadge.textContent = aitState.state.panelEditable ? "EDIT" : "READ-ONLY";
|
|
2376
2503
|
refreshPanel();
|
|
2377
2504
|
});
|
|
2378
|
-
const headerRight = h("span", { style: "display:flex;align-items:center;gap:6px" }, mockBadge, h("span", { style: "font-size:11px;color:#666;font-weight:400" }, `v0.1.
|
|
2505
|
+
const headerRight = h("span", { style: "display:flex;align-items:center;gap:6px" }, mockBadge, h("span", { style: "font-size:11px;color:#666;font-weight:400" }, `v0.1.7`), closeBtn);
|
|
2379
2506
|
const header = h("div", { className: "ait-panel-header" }, h("span", {}, "AIT DevTools"), headerRight);
|
|
2380
2507
|
tabsEl = h("div", { className: "ait-panel-tabs" });
|
|
2381
2508
|
for (const tab of TABS) {
|
|
@@ -2414,7 +2541,7 @@ function mount() {
|
|
|
2414
2541
|
});
|
|
2415
2542
|
aitState.subscribe(() => {
|
|
2416
2543
|
try {
|
|
2417
|
-
if (isOpen && (currentTab === "analytics" || currentTab === "storage" || currentTab === "device" || currentTab === "viewport")) refreshPanel();
|
|
2544
|
+
if (isOpen && (currentTab === "analytics" || currentTab === "storage" || currentTab === "device" || currentTab === "viewport" || currentTab === "iap")) refreshPanel();
|
|
2418
2545
|
} catch (err) {
|
|
2419
2546
|
console.error("[@ait-co/devtools] Error in subscribe callback:", err);
|
|
2420
2547
|
}
|
package/dist/panel/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["refreshPanel"],"sources":["../../src/mock/state.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/location.ts","../../src/mock/proxy.ts","../../src/mock/device/storage.ts","../../src/panel/tabs/device.ts","../../src/panel/tabs/analytics.ts","../../src/panel/tabs/environment.ts","../../src/panel/tabs/events.ts","../../src/panel/tabs/iap.ts","../../src/panel/tabs/location.ts","../../src/panel/tabs/permissions.ts","../../src/panel/tabs/storage.ts","../../src/mock/navigation/index.ts","../../src/panel/viewport.ts","../../src/panel/tabs/viewport.ts","../../src/panel/tabs/index.ts","../../src/panel/index.ts"],"sourcesContent":["/**\n * @ait-co/devtools 중앙 상태 관리\n * DevTools Panel과 mock 구현체가 이 상태를 공유한다.\n */\n\nimport type {\n AnalyticsLogEntry,\n DeviceModes,\n IapNextResult,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n ViewportState,\n} from './types.js';\n\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 OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n ViewportOrientation,\n ViewportPreset,\n ViewportPresetId,\n ViewportState,\n} from './types.js';\n\ntype Listener = () => void;\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 // 권한\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 };\n\n // 광고\n ads: {\n isLoaded: boolean;\n nextEvent:\n | 'loaded'\n | 'clicked'\n | 'dismissed'\n | 'failedToShow'\n | 'impression'\n | 'userEarnedReward';\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 // 디바이스 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 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 safeAreaInsets: { top: 47, 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 },\n\n ads: {\n isLoaded: false,\n nextEvent: 'loaded',\n },\n\n game: {\n profile: { nickname: 'MockPlayer', profileImageUri: '' },\n leaderboardScores: [],\n },\n\n analyticsLog: [],\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\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 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 /** 이벤트 트리거 (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 for (const listener of this._listeners) {\n listener();\n }\n }\n}\n\nexport const aitState = new AitStateManager();\n\n// 브라우저 콘솔에서 접근 가능하도록\nif (typeof window !== 'undefined') {\n window.__ait = aitState;\n}\n","/**\n * 공통 DOM 헬퍼 함수\n */\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(\n 'div',\n { className: 'ait-monitoring-notice' },\n 'Read-only — mock responses are controlled at build time.',\n );\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\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 /* 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 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 overlay (top of body) */\n .ait-notch {\n position: absolute;\n top: 0;\n left: 50%;\n transform: translateX(-50%);\n background: #000;\n z-index: 10;\n pointer-events: none;\n }\n .ait-notch-dynamic-island { top: 11px; width: 126px; height: 37px; border-radius: 20px; }\n .ait-notch-pill {\n width: 160px; height: 30px;\n border-bottom-left-radius: 20px; border-bottom-right-radius: 20px;\n }\n .ait-notch-punch-hole { top: 10px; width: 12px; height: 12px; border-radius: 50%; }\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 directly below the OS status bar */\n .ait-navbar {\n position: absolute;\n left: 0;\n right: 0;\n height: 48px; /* AIT_NAV_BAR_HEIGHT */\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 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// --- 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 * 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 * 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\nconst ISSUES_URL = 'https://github.com/apps-in-toss-community/devtools/issues';\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 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 { getDefaultPlaceholderImages } from '../../mock/device/index.js';\nimport { aitState } from '../../mock/state.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' }, 'Camera Prompt — Select an image'));\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' }, 'Photos Prompt — Select images'));\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 ? 'Location Prompt — Enter coordinates'\n : 'Location Update — Send coordinates',\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' }, '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', {}, 'Lat'),\n latInput,\n h('label', {}, 'Lng'),\n lngInput,\n sendBtn,\n ),\n );\n } else {\n // Fallback for unknown prompt types\n banner.append(h('div', { className: 'ait-prompt-title' }, `Prompt: ${pendingPrompt.type}`));\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 '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<{ label: string; key: keyof typeof s.deviceModes; options: string[] }> =\n [\n { label: 'Camera', key: 'camera', options: ['mock', 'web', 'prompt'] },\n { label: 'Photos', key: 'photos', options: ['mock', 'web', 'prompt'] },\n { label: 'Location', key: 'location', options: ['mock', 'web', 'prompt'] },\n { label: 'Network', key: 'network', options: ['mock', 'web'] },\n { label: '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' }, 'Device API Modes'),\n ...modeEntries.map((entry) =>\n selectRow(\n entry.label,\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' }, '+ 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' }, 'Use defaults');\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' }, '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('div', { className: 'ait-section-title' }, `Mock Images (${images.length})`),\n imageGrid,\n h('div', { className: 'ait-btn-row' }, addBtn, defaultsBtn, clearImagesBtn),\n ),\n );\n\n return container;\n}\n","import { aitState } from '../../mock/state.js';\nimport { h, monitoringNotice } from '../helpers.js';\n\nexport function renderAnalyticsTab(): HTMLElement {\n const disabled = !aitState.state.panelEditable;\n const container = h('div');\n if (disabled) container.appendChild(monitoringNotice());\n const logs = aitState.state.analyticsLog;\n\n const clearBtn = h('button', { className: 'ait-btn ait-btn-sm ait-btn-danger' }, 'Clear');\n if (disabled) clearBtn.disabled = true;\n clearBtn.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('div', { className: 'ait-section-title' }, `Analytics Log (${logs.length})`),\n clearBtn,\n ),\n ...logs\n .slice(-30)\n .reverse()\n .map((entry) => {\n const time = new Date(entry.timestamp).toLocaleTimeString('ko-KR', { hour12: false });\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 return container;\n}\n","import { aitState } from '../../mock/state.js';\nimport type { NetworkStatus, OperationalEnvironment, PlatformOS } from '../../mock/types.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' }, 'Platform'),\n selectRow(\n 'OS',\n ['ios', 'android'],\n s.platform,\n (v) => aitState.update({ platform: v as PlatformOS }),\n disabled,\n ),\n inputRow('App Version', s.appVersion, (v) => aitState.update({ appVersion: v }), disabled),\n selectRow(\n 'Environment',\n ['toss', 'sandbox'],\n s.environment,\n (v) => aitState.update({ environment: v as OperationalEnvironment }),\n disabled,\n ),\n inputRow('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' }, 'Network'),\n selectRow(\n 'Status',\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' }, 'Safe Area Insets'),\n inputRow(\n 'Top',\n String(s.safeAreaInsets.top),\n (v) => aitState.patch('safeAreaInsets', { top: Number(v) }),\n disabled,\n ),\n inputRow(\n 'Bottom',\n String(s.safeAreaInsets.bottom),\n (v) => aitState.patch('safeAreaInsets', { bottom: Number(v) }),\n disabled,\n ),\n ),\n );\n return container;\n}\n","import { 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' }, 'Trigger Back Event');\n backBtn.addEventListener('click', () => aitState.trigger('backEvent'));\n if (disabled) backBtn.disabled = true;\n\n const homeBtn = h('button', { className: 'ait-btn' }, 'Trigger Home Event');\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' }, 'Navigation Events'),\n h('div', { className: 'ait-row' }, backBtn, homeBtn),\n ),\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, 'Login'),\n selectRow(\n 'Logged In',\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 'Toss Login Integrated',\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","import { aitState } from '../../mock/state.js';\nimport type { IapNextResult } from '../../mock/types.js';\nimport { h, monitoringNotice, selectRow } from '../helpers.js';\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 container.append(\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, 'IAP Simulator'),\n selectRow(\n 'Next Purchase Result',\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' }, 'TossPay'),\n selectRow(\n 'Next Payment Result',\n ['success', 'fail'],\n s.payment.nextResult,\n (v) => {\n aitState.patch('payment', { nextResult: v as 'success' | 'fail' });\n },\n disabled,\n ),\n ),\n h(\n 'div',\n { className: 'ait-section' },\n h(\n 'div',\n { className: 'ait-section-title' },\n `Completed Orders (${s.iap.completedOrders.length})`,\n ),\n ...s.iap.completedOrders\n .slice(-5)\n .map((o) =>\n h(\n 'div',\n { className: 'ait-log-entry' },\n h('span', { className: 'ait-log-type' }, o.status),\n `${o.sku} (${o.orderId.slice(-8)})`,\n ),\n ),\n ),\n );\n return container;\n}\n","import { 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' }, 'Current Location'),\n inputRow(\n '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 '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 '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 { 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' }, 'Device Permissions'),\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","import { 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('button', { className: 'ait-btn ait-btn-sm ait-btn-danger' }, 'Clear All');\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('div', { className: 'ait-section-title' }, `Storage (${entries.length} items)`),\n clearBtn,\n ),\n entries.length === 0\n ? h('div', { style: 'color:#555;font-size:12px' }, 'No items in storage')\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 { 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}\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 async function setScreenAwakeMode(options: {\n enabled: boolean;\n}): Promise<{ enabled: boolean }> {\n console.log('[@ait-co/devtools] setScreenAwakeMode:', options.enabled);\n return { enabled: options.enabled };\n}\n\nexport async function setSecureScreen(options: {\n enabled: boolean;\n}): Promise<{ enabled: boolean }> {\n console.log('[@ait-co/devtools] setSecureScreen:', options.enabled);\n return { enabled: options.enabled };\n}\n\nexport async function requestReview(): Promise<void> {\n console.log('[@ait-co/devtools] requestReview called');\n}\n(requestReview as unknown as { isSupported: () => boolean }).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 * Viewport 시뮬레이션 유틸\n *\n * Panel에서 선택한 디바이스 프리셋을 `document.body`에 적용한다. 정적 CSS는\n * `panel/styles.ts`에 정의되어 있고 (Panel mount 시 head에 주입), 여기서는 프리셋별\n * 동적 값(width/height, navbar top offset)만 별도 `<style>` 엘리먼트로 관리한다.\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 ViewportOrientation,\n ViewportPreset,\n ViewportPresetId,\n ViewportState,\n} from '../mock/types.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). 공식 docs에는 명시되어 있지 않지만\n * Toss 공식 예제(`with-contacts-viral`, `random-balls`)가 safeArea.top에 `+ 48`을\n * 추가하는 패턴을 쓴다. SafeAreaInsets에는 포함되지 않으므로 별도 상수로 관리.\n */\nexport const AIT_NAV_BAR_HEIGHT = 48;\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 safeAreaTop: 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 safeAreaTop: 0,\n safeAreaBottom: 0,\n};\n\n/**\n * Device presets (2026). CSS viewport 크기는 실제 기기의 `window.innerWidth/innerHeight`.\n * iPhone 17 시리즈는 2025-09 출시. iPhone Air와 Galaxy S26 시리즈는 2026-04 기준 미출시라\n * 추정 값(`(est)` 라벨 표기). 실제 출시 후 값을 갱신한다.\n *\n * iPhone 17과 17 Pro는 CSS viewport / DPR / safe area가 동일 — 이는 의도이며 카피-페이스트\n * 실수가 아니다. Apple의 17 lineup은 base와 Pro의 web-relevant 스펙이 같다.\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 safeAreaTop: 20,\n safeAreaBottom: 0,\n },\n {\n id: 'iphone-16e',\n label: 'iPhone 16e',\n width: 390,\n height: 844,\n dpr: 3,\n notch: 'notch',\n safeAreaTop: 47,\n safeAreaBottom: 34,\n },\n {\n id: 'iphone-17',\n label: 'iPhone 17',\n width: 402,\n height: 874,\n dpr: 3,\n notch: 'dynamic-island',\n safeAreaTop: 59,\n safeAreaBottom: 34,\n },\n {\n id: 'iphone-air',\n label: 'iPhone Air (est)',\n width: 420,\n height: 912,\n dpr: 3,\n notch: 'dynamic-island',\n safeAreaTop: 59,\n safeAreaBottom: 34,\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 safeAreaTop: 59,\n safeAreaBottom: 34,\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 safeAreaTop: 62,\n safeAreaBottom: 34,\n },\n // Samsung — S26 series specs are estimated from S25 until release.\n {\n id: 'galaxy-s26',\n label: 'Galaxy S26 (est)',\n width: 384,\n height: 832,\n dpr: 3,\n notch: 'punch-hole-center',\n safeAreaTop: 32,\n safeAreaBottom: 0,\n },\n {\n id: 'galaxy-s26-plus',\n label: 'Galaxy S26+ (est)',\n width: 412,\n height: 915,\n dpr: 3,\n notch: 'punch-hole-center',\n safeAreaTop: 32,\n safeAreaBottom: 0,\n },\n {\n id: 'galaxy-s26-ultra',\n label: 'Galaxy S26 Ultra (est)',\n width: 412,\n height: 915,\n dpr: 3.5,\n notch: 'punch-hole-center',\n safeAreaTop: 40,\n safeAreaBottom: 0,\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 safeAreaTop: 36,\n safeAreaBottom: 0,\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 safeAreaTop: 32,\n safeAreaBottom: 0,\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 safeAreaTop: 32,\n safeAreaBottom: 0,\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 * 프리셋 + landscape 여부 + landscape side로부터 OS-level safe-area insets를 계산한다.\n *\n * - Portrait: preset의 `safeAreaTop`, `safeAreaBottom`을 그대로 사용.\n * - Landscape iPhone(notch/Dynamic Island): 노치가 한쪽으로 가므로 `landscapeSide`에\n * 따라 left 또는 right에만 인셋을 준다 (실 기기 동작과 일치). top은 0,\n * home-indicator는 bottom에 유지.\n * - Android punch-hole(status bar): landscape 시에도 top에 status bar가 유지된다.\n */\nexport function computeSafeAreaInsets(\n preset: ViewportPreset,\n landscape: boolean,\n side: LandscapeSide,\n): SafeAreaInsets {\n if (preset.id === 'none' || preset.id === 'custom') {\n return { top: 0, bottom: 0, left: 0, right: 0 };\n }\n if (!landscape) {\n return { top: preset.safeAreaTop, 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.safeAreaTop : 0,\n right: side === 'right' ? preset.safeAreaTop : 0,\n };\n }\n // Android status bar stays on the top edge even in landscape.\n return {\n top: preset.safeAreaTop,\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 );\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 아래에 48px 높이로 쌓인다.\n *\n * 변형(SDK `webViewProps.type`과 의미 일치):\n * - `partner` (기본): 흰 배경, 좌측 뒤로가기(‹), 앱 아이콘 + 이름(`brand.displayName`),\n * 우측 `⋯` + 구분선 + `×`.\n * - `game`: 투명 배경, 게임 캔버스를 가리지 않도록 우측 `⋯` + 구분선 + `×`만.\n *\n * `env(safe-area-inset-top)`에는 이 높이가 포함되지 않으므로 (공식 SDK 확인),\n * 오버레이는 preset.safeAreaTop만큼 아래로 내려서 그린다.\n *\n * 뒤로가기 버튼은 `__ait:backEvent`를 트리거하고, X 버튼은 `closeView()`를 호출한다.\n * 실제 SDK 이벤트 플러밍을 한 곳에서 검증할 수 있다.\n */\nfunction renderNavBar(preset: ViewportPreset, 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 el.style.top = `${preset.safeAreaTop}px`;\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 bodyScrollHintEmitted = false;\n}\n\n/**\n * DOM에 뷰포트 제약을 적용한다.\n * - `html.ait-viewport-active` 클래스로 정적 CSS(styles.ts) 활성화\n * - body의 width/height는 preset 값으로, navbar top offset은 safeAreaTop으로 인라인 주입\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 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 // 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 }\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(preset, 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 { aitState } from '../../mock/state.js';\nimport type {\n AitNavBarType,\n LandscapeSide,\n ViewportOrientation,\n ViewportPresetId,\n} from '../../mock/types.js';\nimport { h, monitoringNotice } from '../helpers.js';\nimport {\n AIT_NAV_BAR_HEIGHT,\n clampCustomDimension,\n computeSafeAreaInsets,\n effectiveOrientation,\n getPreset,\n resolveViewportSize,\n VIEWPORT_PRESETS,\n} from '../viewport.js';\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' }, 'Custom size'),\n h('div', { className: 'ait-row' }, h('label', {}, 'Width (px)'), widthInput),\n h('div', { className: 'ait-row' }, h('label', {}, 'Height (px)'), 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(\n 'div',\n { style: 'color:#888;font-size:11px' },\n 'No viewport constraint — body fills the window.',\n ),\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 = vp.orientation === 'auto' ? `${effOrient} (auto)` : effOrient;\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, 'CSS / physical'),\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, vp.landscapeSide);\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, 'Safe area'),\n h(\n 'span',\n { className: 'ait-status-value' },\n `T${insets.top} R${insets.right} B${insets.bottom} L${insets.left}`,\n ),\n ),\n );\n }\n\n if (vp.aitNavBar && !landscape) {\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, 'AIT nav bar'),\n h(\n 'span',\n { className: 'ait-status-value' },\n `${AIT_NAV_BAR_HEIGHT}px (excl. SafeArea) · ${vp.aitNavBarType}`,\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' }, 'Device'),\n h('div', { className: 'ait-row' }, h('label', {}, 'Preset'), presetSelect),\n h('div', { className: 'ait-row' }, h('label', {}, 'Orientation'), orientationSelect),\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('div', { className: 'ait-row' }, h('label', {}, 'Notch side'), landscapeSideSelect),\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' }, 'Appearance'),\n h('div', { className: 'ait-row' }, h('label', {}, 'Show frame'), frameCheckbox),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, 'Show Apps in Toss nav bar'),\n navBarCheckbox,\n ),\n h('div', { className: 'ait-row' }, h('label', {}, 'Nav bar type'), navBarTypeSelect),\n ),\n statusEl,\n );\n\n return container;\n}\n","import { 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 { renderPermissionsTab } from './permissions.js';\nimport { renderStorageTab } from './storage.js';\nimport { renderViewportTab } from './viewport.js';\n\nexport type TabId =\n | 'env'\n | 'permissions'\n | 'location'\n | 'iap'\n | 'events'\n | 'analytics'\n | 'storage'\n | 'device'\n | 'viewport';\n\nexport const TABS: Array<{ id: TabId; label: string }> = [\n { id: 'env', label: 'Environment' },\n { id: 'viewport', label: 'Viewport' },\n { id: 'permissions', label: 'Permissions' },\n { id: 'location', label: 'Location' },\n { id: 'device', label: 'Device' },\n { id: 'iap', label: 'IAP' },\n { id: 'events', label: 'Events' },\n { id: 'analytics', label: 'Analytics' },\n { id: 'storage', label: 'Storage' },\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// 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 permissions: renderPermissionsTab,\n location: renderLocationTab,\n device: renderDeviceTab,\n viewport: renderViewportTab,\n iap: renderIapTab,\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 { aitState } from '../mock/state.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, TABS, type TabId } from './tabs/index.js';\nimport { initViewport } from './viewport.js';\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;\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' }, `Error rendering \"${currentTab}\" tab.`),\n );\n }\n\n tabsEl.querySelectorAll('.ait-panel-tab').forEach((el) => {\n el.classList.toggle('active', el.getAttribute('data-tab') === currentTab);\n });\n}\n\n// Listen for tab switch requests from device tab (prompt auto-open)\nif (typeof window !== 'undefined') {\n window.addEventListener('__ait:panel-switch-tab', (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}\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 const style = document.createElement('style');\n style.textContent = PANEL_STYLES;\n document.head.appendChild(style);\n\n // Toggle button\n const toggle = h('button', { className: 'ait-panel-toggle', title: 'AIT DevTools' }, 'AIT');\n restoreButtonPosition(toggle);\n\n // Panel\n panelEl = h('div', { className: 'ait-panel' });\n\n const closeBtn = h('button', { className: 'ait-panel-close', title: 'Close' }, '\\u00d7');\n closeBtn.addEventListener('click', () => {\n isOpen = false;\n panelEl!.classList.remove('open');\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: 'Toggle panel edit mode',\n },\n aitState.state.panelEditable ? 'EDIT' : 'READ-ONLY',\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 ? 'EDIT' : 'READ-ONLY';\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', {}, 'AIT DevTools'),\n headerRight,\n );\n\n tabsEl = h('div', { className: 'ait-panel-tabs' });\n for (const tab of TABS) {\n const tabEl = h('button', { className: 'ait-panel-tab', 'data-tab': tab.id }, tab.label);\n tabEl.addEventListener('click', () => {\n currentTab = 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 }\n });\n\n // Re-clamp button and panel position on window resize (rAF-throttled)\n let resizeRaf = 0;\n window.addEventListener('resize', () => {\n if (resizeRaf) return;\n resizeRaf = requestAnimationFrame(() => {\n resizeRaf = 0;\n snapToEdge(toggle);\n saveButtonPosition(toggle);\n if (isOpen) updatePanelPosition(toggle);\n });\n });\n\n // 상태 변경 시 자동 갱신 (analytics, storage 탭)\n // Defense-in-depth: outer catch complements refreshPanel's inner tab-rendering catch.\n aitState.subscribe(() => {\n try {\n if (\n isOpen &&\n (currentTab === 'analytics' ||\n currentTab === 'storage' ||\n currentTab === 'device' ||\n currentTab === 'viewport')\n ) {\n refreshPanel();\n }\n } catch (err) {\n console.error('[@ait-co/devtools] Error in subscribe callback:', err);\n }\n });\n\n refreshPanel();\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 { mount };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["refreshPanel"],"sources":["../../src/mock/state.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/location.ts","../../src/mock/proxy.ts","../../src/mock/device/storage.ts","../../src/panel/tabs/device.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/permissions.ts","../../src/panel/tabs/storage.ts","../../src/mock/navigation/index.ts","../../src/panel/viewport.ts","../../src/panel/tabs/viewport.ts","../../src/panel/tabs/index.ts","../../src/panel/index.ts"],"sourcesContent":["/**\n * @ait-co/devtools 중앙 상태 관리\n * DevTools Panel과 mock 구현체가 이 상태를 공유한다.\n */\n\nimport type {\n AnalyticsLogEntry,\n DeviceModes,\n IapNextResult,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n ViewportState,\n} from './types.js';\n\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 OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n ViewportOrientation,\n ViewportPreset,\n ViewportPresetId,\n ViewportState,\n} from './types.js';\n\ntype Listener = () => void;\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 // 권한\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 };\n\n // 광고\n ads: {\n isLoaded: boolean;\n nextEvent:\n | 'loaded'\n | 'clicked'\n | 'dismissed'\n | 'failedToShow'\n | 'impression'\n | 'userEarnedReward';\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 // 디바이스 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 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 safeAreaInsets: { top: 47, 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 },\n\n ads: {\n isLoaded: false,\n nextEvent: 'loaded',\n },\n\n game: {\n profile: { nickname: 'MockPlayer', profileImageUri: '' },\n leaderboardScores: [],\n },\n\n analyticsLog: [],\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\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 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 /** 이벤트 트리거 (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 for (const listener of this._listeners) {\n listener();\n }\n }\n}\n\nexport const aitState = new AitStateManager();\n\n// 브라우저 콘솔에서 접근 가능하도록\nif (typeof window !== 'undefined') {\n window.__ait = aitState;\n}\n","/**\n * 공통 DOM 헬퍼 함수\n */\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(\n 'div',\n { className: 'ait-monitoring-notice' },\n 'Read-only — mock responses are controlled at build time.',\n );\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\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 /* 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 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 overlay (top of body) */\n .ait-notch {\n position: absolute;\n top: 0;\n left: 50%;\n transform: translateX(-50%);\n background: #000;\n z-index: 10;\n pointer-events: none;\n }\n .ait-notch-dynamic-island { top: 11px; width: 126px; height: 37px; border-radius: 20px; }\n .ait-notch-pill {\n width: 160px; height: 30px;\n border-bottom-left-radius: 20px; border-bottom-right-radius: 20px;\n }\n .ait-notch-punch-hole { top: 10px; width: 12px; height: 12px; border-radius: 50%; }\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 directly below the OS status bar */\n .ait-navbar {\n position: absolute;\n left: 0;\n right: 0;\n height: 48px; /* AIT_NAV_BAR_HEIGHT */\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 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// --- 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 * 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 * 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\nconst ISSUES_URL = 'https://github.com/apps-in-toss-community/devtools/issues';\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 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 { getDefaultPlaceholderImages } from '../../mock/device/index.js';\nimport { aitState } from '../../mock/state.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' }, 'Camera Prompt — Select an image'));\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' }, 'Photos Prompt — Select images'));\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 ? 'Location Prompt — Enter coordinates'\n : 'Location Update — Send coordinates',\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' }, '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', {}, 'Lat'),\n latInput,\n h('label', {}, 'Lng'),\n lngInput,\n sendBtn,\n ),\n );\n } else {\n // Fallback for unknown prompt types\n banner.append(h('div', { className: 'ait-prompt-title' }, `Prompt: ${pendingPrompt.type}`));\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 '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<{ label: string; key: keyof typeof s.deviceModes; options: string[] }> =\n [\n { label: 'Camera', key: 'camera', options: ['mock', 'web', 'prompt'] },\n { label: 'Photos', key: 'photos', options: ['mock', 'web', 'prompt'] },\n { label: 'Location', key: 'location', options: ['mock', 'web', 'prompt'] },\n { label: 'Network', key: 'network', options: ['mock', 'web'] },\n { label: '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' }, 'Device API Modes'),\n ...modeEntries.map((entry) =>\n selectRow(\n entry.label,\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' }, '+ 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' }, 'Use defaults');\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' }, '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('div', { className: 'ait-section-title' }, `Mock Images (${images.length})`),\n imageGrid,\n h('div', { className: 'ait-btn-row' }, addBtn, defaultsBtn, clearImagesBtn),\n ),\n );\n\n return container;\n}\n","import { aitState } from '../../mock/state.js';\nimport { h, monitoringNotice } from '../helpers.js';\n\nexport function renderAnalyticsTab(): HTMLElement {\n const disabled = !aitState.state.panelEditable;\n const container = h('div');\n if (disabled) container.appendChild(monitoringNotice());\n const logs = aitState.state.analyticsLog;\n\n const clearBtn = h('button', { className: 'ait-btn ait-btn-sm ait-btn-danger' }, 'Clear');\n if (disabled) clearBtn.disabled = true;\n clearBtn.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('div', { className: 'ait-section-title' }, `Analytics Log (${logs.length})`),\n clearBtn,\n ),\n ...logs\n .slice(-30)\n .reverse()\n .map((entry) => {\n const time = new Date(entry.timestamp).toLocaleTimeString('ko-KR', { hour12: false });\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 return container;\n}\n","import { aitState } from '../../mock/state.js';\nimport type { NetworkStatus, OperationalEnvironment, PlatformOS } from '../../mock/types.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' }, 'Platform'),\n selectRow(\n 'OS',\n ['ios', 'android'],\n s.platform,\n (v) => aitState.update({ platform: v as PlatformOS }),\n disabled,\n ),\n inputRow('App Version', s.appVersion, (v) => aitState.update({ appVersion: v }), disabled),\n selectRow(\n 'Environment',\n ['toss', 'sandbox'],\n s.environment,\n (v) => aitState.update({ environment: v as OperationalEnvironment }),\n disabled,\n ),\n inputRow('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' }, 'Network'),\n selectRow(\n 'Status',\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' }, 'Safe Area Insets'),\n inputRow(\n 'Top',\n String(s.safeAreaInsets.top),\n (v) => aitState.patch('safeAreaInsets', { top: Number(v) }),\n disabled,\n ),\n inputRow(\n 'Bottom',\n String(s.safeAreaInsets.bottom),\n (v) => aitState.patch('safeAreaInsets', { bottom: Number(v) }),\n disabled,\n ),\n ),\n );\n return container;\n}\n","import { 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' }, 'Trigger Back Event');\n backBtn.addEventListener('click', () => aitState.trigger('backEvent'));\n if (disabled) backBtn.disabled = true;\n\n const homeBtn = h('button', { className: 'ait-btn' }, 'Trigger Home Event');\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' }, 'Navigation Events'),\n h('div', { className: 'ait-row' }, backBtn, homeBtn),\n ),\n h(\n 'div',\n { className: 'ait-section' },\n h('div', { className: 'ait-section-title' }, 'Login'),\n selectRow(\n 'Logged In',\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 'Toss Login Integrated',\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","import { 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('div', { className: 'ait-section-title' }, `Pending Orders (${pendingOrders.length})`),\n );\n if (pendingOrders.length === 0) {\n pendingSection.appendChild(h('div', { className: 'ait-log-entry' }, '(no pending orders)'));\n } else {\n for (const o of pendingOrders) {\n const completeBtn = h('button', { className: 'ait-btn ait-btn-sm' }, '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' }, '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('div', { className: 'ait-section-title' }, `Completed Orders (${completedOrders.length})`),\n );\n if (completedOrders.length === 0) {\n completedSection.appendChild(h('div', { className: 'ait-log-entry' }, '(no completed orders)'));\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' }, 'IAP Simulator'),\n selectRow(\n 'Next Purchase Result',\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' }, 'TossPay'),\n selectRow(\n 'Next Payment Result',\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 { 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' }, 'Current Location'),\n inputRow(\n '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 '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 '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 { 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' }, 'Device Permissions'),\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","import { 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('button', { className: 'ait-btn ait-btn-sm ait-btn-danger' }, 'Clear All');\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('div', { className: 'ait-section-title' }, `Storage (${entries.length} items)`),\n clearBtn,\n ),\n entries.length === 0\n ? h('div', { style: 'color:#555;font-size:12px' }, 'No items in storage')\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 { 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}\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 async function setScreenAwakeMode(options: {\n enabled: boolean;\n}): Promise<{ enabled: boolean }> {\n console.log('[@ait-co/devtools] setScreenAwakeMode:', options.enabled);\n return { enabled: options.enabled };\n}\n\nexport async function setSecureScreen(options: {\n enabled: boolean;\n}): Promise<{ enabled: boolean }> {\n console.log('[@ait-co/devtools] setSecureScreen:', options.enabled);\n return { enabled: options.enabled };\n}\n\nexport async function requestReview(): Promise<void> {\n console.log('[@ait-co/devtools] requestReview called');\n}\n(requestReview as unknown as { isSupported: () => boolean }).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 * Viewport 시뮬레이션 유틸\n *\n * Panel에서 선택한 디바이스 프리셋을 `document.body`에 적용한다. 정적 CSS는\n * `panel/styles.ts`에 정의되어 있고 (Panel mount 시 head에 주입), 여기서는 프리셋별\n * 동적 값(width/height, navbar top offset)만 별도 `<style>` 엘리먼트로 관리한다.\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 ViewportOrientation,\n ViewportPreset,\n ViewportPresetId,\n ViewportState,\n} from '../mock/types.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). 공식 docs에는 명시되어 있지 않지만\n * Toss 공식 예제(`with-contacts-viral`, `random-balls`)가 safeArea.top에 `+ 48`을\n * 추가하는 패턴을 쓴다. SafeAreaInsets에는 포함되지 않으므로 별도 상수로 관리.\n */\nexport const AIT_NAV_BAR_HEIGHT = 48;\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 safeAreaTop: 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 safeAreaTop: 0,\n safeAreaBottom: 0,\n};\n\n/**\n * Device presets (2026). CSS viewport 크기는 실제 기기의 `window.innerWidth/innerHeight`.\n * iPhone 17 시리즈는 2025-09 출시. iPhone Air와 Galaxy S26 시리즈는 2026-04 기준 미출시라\n * 추정 값(`(est)` 라벨 표기). 실제 출시 후 값을 갱신한다.\n *\n * iPhone 17과 17 Pro는 CSS viewport / DPR / safe area가 동일 — 이는 의도이며 카피-페이스트\n * 실수가 아니다. Apple의 17 lineup은 base와 Pro의 web-relevant 스펙이 같다.\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 safeAreaTop: 20,\n safeAreaBottom: 0,\n },\n {\n id: 'iphone-16e',\n label: 'iPhone 16e',\n width: 390,\n height: 844,\n dpr: 3,\n notch: 'notch',\n safeAreaTop: 47,\n safeAreaBottom: 34,\n },\n {\n id: 'iphone-17',\n label: 'iPhone 17',\n width: 402,\n height: 874,\n dpr: 3,\n notch: 'dynamic-island',\n safeAreaTop: 59,\n safeAreaBottom: 34,\n },\n {\n id: 'iphone-air',\n label: 'iPhone Air (est)',\n width: 420,\n height: 912,\n dpr: 3,\n notch: 'dynamic-island',\n safeAreaTop: 59,\n safeAreaBottom: 34,\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 safeAreaTop: 59,\n safeAreaBottom: 34,\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 safeAreaTop: 62,\n safeAreaBottom: 34,\n },\n // Samsung — S26 series specs are estimated from S25 until release.\n {\n id: 'galaxy-s26',\n label: 'Galaxy S26 (est)',\n width: 384,\n height: 832,\n dpr: 3,\n notch: 'punch-hole-center',\n safeAreaTop: 32,\n safeAreaBottom: 0,\n },\n {\n id: 'galaxy-s26-plus',\n label: 'Galaxy S26+ (est)',\n width: 412,\n height: 915,\n dpr: 3,\n notch: 'punch-hole-center',\n safeAreaTop: 32,\n safeAreaBottom: 0,\n },\n {\n id: 'galaxy-s26-ultra',\n label: 'Galaxy S26 Ultra (est)',\n width: 412,\n height: 915,\n dpr: 3.5,\n notch: 'punch-hole-center',\n safeAreaTop: 40,\n safeAreaBottom: 0,\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 safeAreaTop: 36,\n safeAreaBottom: 0,\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 safeAreaTop: 32,\n safeAreaBottom: 0,\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 safeAreaTop: 32,\n safeAreaBottom: 0,\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 * 프리셋 + landscape 여부 + landscape side로부터 OS-level safe-area insets를 계산한다.\n *\n * - Portrait: preset의 `safeAreaTop`, `safeAreaBottom`을 그대로 사용.\n * - Landscape iPhone(notch/Dynamic Island): 노치가 한쪽으로 가므로 `landscapeSide`에\n * 따라 left 또는 right에만 인셋을 준다 (실 기기 동작과 일치). top은 0,\n * home-indicator는 bottom에 유지.\n * - Android punch-hole(status bar): landscape 시에도 top에 status bar가 유지된다.\n */\nexport function computeSafeAreaInsets(\n preset: ViewportPreset,\n landscape: boolean,\n side: LandscapeSide,\n): SafeAreaInsets {\n if (preset.id === 'none' || preset.id === 'custom') {\n return { top: 0, bottom: 0, left: 0, right: 0 };\n }\n if (!landscape) {\n return { top: preset.safeAreaTop, 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.safeAreaTop : 0,\n right: side === 'right' ? preset.safeAreaTop : 0,\n };\n }\n // Android status bar stays on the top edge even in landscape.\n return {\n top: preset.safeAreaTop,\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 );\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 아래에 48px 높이로 쌓인다.\n *\n * 변형(SDK `webViewProps.type`과 의미 일치):\n * - `partner` (기본): 흰 배경, 좌측 뒤로가기(‹), 앱 아이콘 + 이름(`brand.displayName`),\n * 우측 `⋯` + 구분선 + `×`.\n * - `game`: 투명 배경, 게임 캔버스를 가리지 않도록 우측 `⋯` + 구분선 + `×`만.\n *\n * `env(safe-area-inset-top)`에는 이 높이가 포함되지 않으므로 (공식 SDK 확인),\n * 오버레이는 preset.safeAreaTop만큼 아래로 내려서 그린다.\n *\n * 뒤로가기 버튼은 `__ait:backEvent`를 트리거하고, X 버튼은 `closeView()`를 호출한다.\n * 실제 SDK 이벤트 플러밍을 한 곳에서 검증할 수 있다.\n */\nfunction renderNavBar(preset: ViewportPreset, 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 el.style.top = `${preset.safeAreaTop}px`;\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 bodyScrollHintEmitted = false;\n}\n\n/**\n * DOM에 뷰포트 제약을 적용한다.\n * - `html.ait-viewport-active` 클래스로 정적 CSS(styles.ts) 활성화\n * - body의 width/height는 preset 값으로, navbar top offset은 safeAreaTop으로 인라인 주입\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 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 // 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 }\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(preset, 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 { aitState } from '../../mock/state.js';\nimport type {\n AitNavBarType,\n LandscapeSide,\n ViewportOrientation,\n ViewportPresetId,\n} from '../../mock/types.js';\nimport { h, monitoringNotice } from '../helpers.js';\nimport {\n AIT_NAV_BAR_HEIGHT,\n clampCustomDimension,\n computeSafeAreaInsets,\n effectiveOrientation,\n getPreset,\n resolveViewportSize,\n VIEWPORT_PRESETS,\n} from '../viewport.js';\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' }, 'Custom size'),\n h('div', { className: 'ait-row' }, h('label', {}, 'Width (px)'), widthInput),\n h('div', { className: 'ait-row' }, h('label', {}, 'Height (px)'), 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(\n 'div',\n { style: 'color:#888;font-size:11px' },\n 'No viewport constraint — body fills the window.',\n ),\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 = vp.orientation === 'auto' ? `${effOrient} (auto)` : effOrient;\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, 'CSS / physical'),\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, vp.landscapeSide);\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, 'Safe area'),\n h(\n 'span',\n { className: 'ait-status-value' },\n `T${insets.top} R${insets.right} B${insets.bottom} L${insets.left}`,\n ),\n ),\n );\n }\n\n if (vp.aitNavBar && !landscape) {\n rows.push(\n h(\n 'div',\n { className: 'ait-status-row' },\n h('span', {}, 'AIT nav bar'),\n h(\n 'span',\n { className: 'ait-status-value' },\n `${AIT_NAV_BAR_HEIGHT}px (excl. SafeArea) · ${vp.aitNavBarType}`,\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' }, 'Device'),\n h('div', { className: 'ait-row' }, h('label', {}, 'Preset'), presetSelect),\n h('div', { className: 'ait-row' }, h('label', {}, 'Orientation'), orientationSelect),\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('div', { className: 'ait-row' }, h('label', {}, 'Notch side'), landscapeSideSelect),\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' }, 'Appearance'),\n h('div', { className: 'ait-row' }, h('label', {}, 'Show frame'), frameCheckbox),\n h(\n 'div',\n { className: 'ait-row' },\n h('label', {}, 'Show Apps in Toss nav bar'),\n navBarCheckbox,\n ),\n h('div', { className: 'ait-row' }, h('label', {}, 'Nav bar type'), navBarTypeSelect),\n ),\n statusEl,\n );\n\n return container;\n}\n","import { 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 { renderPermissionsTab } from './permissions.js';\nimport { renderStorageTab } from './storage.js';\nimport { renderViewportTab } from './viewport.js';\n\nexport type TabId =\n | 'env'\n | 'permissions'\n | 'location'\n | 'iap'\n | 'events'\n | 'analytics'\n | 'storage'\n | 'device'\n | 'viewport';\n\nexport const TABS: Array<{ id: TabId; label: string }> = [\n { id: 'env', label: 'Environment' },\n { id: 'viewport', label: 'Viewport' },\n { id: 'permissions', label: 'Permissions' },\n { id: 'location', label: 'Location' },\n { id: 'device', label: 'Device' },\n { id: 'iap', label: 'IAP' },\n { id: 'events', label: 'Events' },\n { id: 'analytics', label: 'Analytics' },\n { id: 'storage', label: 'Storage' },\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// 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 permissions: renderPermissionsTab,\n location: renderLocationTab,\n device: renderDeviceTab,\n viewport: renderViewportTab,\n iap: renderIapTab,\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 { aitState } from '../mock/state.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, TABS, type TabId } from './tabs/index.js';\nimport { initViewport } from './viewport.js';\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;\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' }, `Error rendering \"${currentTab}\" tab.`),\n );\n }\n\n tabsEl.querySelectorAll('.ait-panel-tab').forEach((el) => {\n el.classList.toggle('active', el.getAttribute('data-tab') === currentTab);\n });\n}\n\n// Listen for tab switch requests from device tab (prompt auto-open)\nif (typeof window !== 'undefined') {\n window.addEventListener('__ait:panel-switch-tab', (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}\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 const style = document.createElement('style');\n style.textContent = PANEL_STYLES;\n document.head.appendChild(style);\n\n // Toggle button\n const toggle = h('button', { className: 'ait-panel-toggle', title: 'AIT DevTools' }, 'AIT');\n restoreButtonPosition(toggle);\n\n // Panel\n panelEl = h('div', { className: 'ait-panel' });\n\n const closeBtn = h('button', { className: 'ait-panel-close', title: 'Close' }, '\\u00d7');\n closeBtn.addEventListener('click', () => {\n isOpen = false;\n panelEl!.classList.remove('open');\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: 'Toggle panel edit mode',\n },\n aitState.state.panelEditable ? 'EDIT' : 'READ-ONLY',\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 ? 'EDIT' : 'READ-ONLY';\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', {}, 'AIT DevTools'),\n headerRight,\n );\n\n tabsEl = h('div', { className: 'ait-panel-tabs' });\n for (const tab of TABS) {\n const tabEl = h('button', { className: 'ait-panel-tab', 'data-tab': tab.id }, tab.label);\n tabEl.addEventListener('click', () => {\n currentTab = 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 }\n });\n\n // Re-clamp button and panel position on window resize (rAF-throttled)\n let resizeRaf = 0;\n window.addEventListener('resize', () => {\n if (resizeRaf) return;\n resizeRaf = requestAnimationFrame(() => {\n resizeRaf = 0;\n snapToEdge(toggle);\n saveButtonPosition(toggle);\n if (isOpen) updatePanelPosition(toggle);\n });\n });\n\n // 상태 변경 시 자동 갱신 (analytics, storage, device, viewport, iap 탭)\n // Defense-in-depth: outer catch complements refreshPanel's inner tab-rendering catch.\n aitState.subscribe(() => {\n try {\n if (\n isOpen &&\n (currentTab === 'analytics' ||\n currentTab === 'storage' ||\n currentTab === 'device' ||\n currentTab === 'viewport' ||\n currentTab === 'iap')\n ) {\n refreshPanel();\n }\n } catch (err) {\n console.error('[@ait-co/devtools] Error in subscribe callback:', err);\n }\n });\n\n refreshPanel();\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 { mount };\n"],"mappings":";AAgJA,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;CAEf,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;CAED,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;EACd;CAED,KAAK;EACH,UAAU;EACV,WAAW;EACZ;CAED,MAAM;EACJ,SAAS;GAAE,UAAU;GAAc,iBAAiB;GAAI;EACxD,mBAAmB,EAAE;EACtB;CAED,cAAc,EAAE;CAEhB,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,eAAe;EACf,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;CAExC,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;;;CAI/C,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;;;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,OAAK,MAAM,YAAY,KAAK,WAC1B,WAAU;;;AAKhB,MAAa,WAAW,IAAI,iBAAiB;AAG7C,IAAI,OAAO,WAAW,YACpB,QAAO,QAAQ;;;;;;ACpVjB,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,EACL,OACA,EAAE,WAAW,yBAAyB,EACtC,2DACD;;ACxCH,MAAa,eAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACTtC,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;;;;;;;;ACxCL,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;;;;;;;AC9H3E,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;;;;;;;ACPvE,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;;;;;;;;;;;;ACpItF,MAAM,aAAa;AAEnB,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;AAElC,QAAM,IAAI,MACR,sBAAsB,WAAW,GAAG,KAAK,qIAGd,aAC5B;IAEJ,CAAC;;ACvBmB,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;;;ACfF,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,kCAAkC,CAAC;EAC7F,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,gCAAgC,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,wCACA,qCACL,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,OAAO;AACxE,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,MAAM,EACrB,UACA,EAAE,SAAS,EAAE,EAAE,MAAM,EACrB,UACA,QACD,CACF;OAGD,QAAO,OAAO,EAAE,OAAO,EAAE,WAAW,oBAAoB,EAAE,WAAW,cAAc,OAAO,CAAC;CAI7F,MAAM,YAAY,EAChB,UACA;EAAE,WAAW;EAAqC,OAAO;EAAkB,EAC3E,SACD;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;;AAavD,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,mBAAmB,EAChE,GAbF;EACE;GAAE,OAAO;GAAU,KAAK;GAAU,SAAS;IAAC;IAAQ;IAAO;IAAS;GAAE;EACtE;GAAE,OAAO;GAAU,KAAK;GAAU,SAAS;IAAC;IAAQ;IAAO;IAAS;GAAE;EACtE;GAAE,OAAO;GAAY,KAAK;GAAY,SAAS;IAAC;IAAQ;IAAO;IAAS;GAAE;EAC1E;GAAE,OAAO;GAAW,KAAK;GAAW,SAAS,CAAC,QAAQ,MAAM;GAAE;EAC9D;GAAE,OAAO;GAAa,KAAK;GAAa,SAAS,CAAC,QAAQ,MAAM;GAAE;EACnE,CAOgB,KAAK,UAClB,UACE,MAAM,OACN,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,QAAQ;AACvE,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;AACnF,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,QAAQ;AAC/E,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,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,gBAAgB,OAAO,OAAO,GAAG,EAC9E,WACA,EAAE,OAAO,EAAE,WAAW,eAAe,EAAE,QAAQ,aAAa,eAAe,CAC5E,CACF;AAED,QAAO;;;;ACtPT,SAAgB,qBAAkC;CAChD,MAAM,WAAW,CAAC,SAAS,MAAM;CACjC,MAAM,YAAY,EAAE,MAAM;AAC1B,KAAI,SAAU,WAAU,YAAY,kBAAkB,CAAC;CACvD,MAAM,OAAO,SAAS,MAAM;CAE5B,MAAM,WAAW,EAAE,UAAU,EAAE,WAAW,qCAAqC,EAAE,QAAQ;AACzF,KAAI,SAAU,UAAS,WAAW;AAClC,UAAS,iBAAiB,eAAe;AACvC,WAAS,OAAO,EAAE,cAAc,EAAE,EAAE,CAAC;GACrC;AAEF,WAAU,OACR,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EACE,OACA,EAAE,WAAW,WAAW,EACxB,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,kBAAkB,KAAK,OAAO,GAAG,EAC9E,SACD,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,mBAAmB,SAAS,EAAE,QAAQ,OAAO,CAAC,CAIrC,EAC9C,EAAE,QAAQ,EAAE,WAAW,gBAAgB,EAAE,MAAM,KAAK,EACpD,KAAK,UAAU,MAAM,OAAO,CAC7B;GACD,CACL,CACF;AACD,QAAO;;;;ACpCT,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,WAAW,EACxD,UACE,MACA,CAAC,OAAO,UAAU,EAClB,EAAE,WACD,MAAM,SAAS,OAAO,EAAE,UAAU,GAAiB,CAAC,EACrD,SACD,EACD,SAAS,eAAe,EAAE,aAAa,MAAM,SAAS,OAAO,EAAE,YAAY,GAAG,CAAC,EAAE,SAAS,EAC1F,UACE,eACA,CAAC,QAAQ,UAAU,EACnB,EAAE,cACD,MAAM,SAAS,OAAO,EAAE,aAAa,GAA6B,CAAC,EACpE,SACD,EACD,SAAS,UAAU,EAAE,SAAS,MAAM,SAAS,OAAO,EAAE,QAAQ,GAAG,CAAC,EAAE,SAAS,CAC9E,EACD,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,UAAU,EACvD,UACE,UACA;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,mBAAmB,EAChE,SACE,OACA,OAAO,EAAE,eAAe,IAAI,GAC3B,MAAM,SAAS,MAAM,kBAAkB,EAAE,KAAK,OAAO,EAAE,EAAE,CAAC,EAC3D,SACD,EACD,SACE,UACA,OAAO,EAAE,eAAe,OAAO,GAC9B,MAAM,SAAS,MAAM,kBAAkB,EAAE,QAAQ,OAAO,EAAE,EAAE,CAAC,EAC9D,SACD,CACF,CACF;AACD,QAAO;;;;AC5DT,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,qBAAqB;AAC3E,SAAQ,iBAAiB,eAAe,SAAS,QAAQ,YAAY,CAAC;AACtE,KAAI,SAAU,SAAQ,WAAW;CAEjC,MAAM,UAAU,EAAE,UAAU,EAAE,WAAW,WAAW,EAAE,qBAAqB;AAC3E,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,oBAAoB,EACjE,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,SAAS,QAAQ,CACrD,EACD,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,QAAQ,EACrD,UACE,aACA,CAAC,QAAQ,QAAQ,EACjB,OAAO,SAAS,MAAM,KAAK,WAAW,GACrC,MAAM;AACL,WAAS,MAAM,QAAQ,EAAE,YAAY,MAAM,QAAQ,CAAC;IAEtD,SACD,EACD,UACE,yBACA,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;;;;;;;ACvCT,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;;;AC1LF,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,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,mBAAmB,cAAc,OAAO,GAAG,CACzF;AACD,KAAI,cAAc,WAAW,EAC3B,gBAAe,YAAY,EAAE,OAAO,EAAE,WAAW,iBAAiB,EAAE,sBAAsB,CAAC;KAE3F,MAAK,MAAM,KAAK,eAAe;EAC7B,MAAM,cAAc,EAAE,UAAU,EAAE,WAAW,sBAAsB,EAAE,WAAW;AAChF,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,UAAU,EACnD,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,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,qBAAqB,gBAAgB,OAAO,GAAG,CAC7F;AACD,KAAI,gBAAgB,WAAW,EAC7B,kBAAiB,YAAY,EAAE,OAAO,EAAE,WAAW,iBAAiB,EAAE,wBAAwB,CAAC;KAE/F,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,gBAAgB,EAC7D,UACE,wBACA,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,UAAU,EACvD,UACE,uBACA,CAAC,WAAW,OAAO,EACnB,EAAE,QAAQ,aACT,MAAM;AACL,WAAS,MAAM,WAAW,EAAE,YAAY,GAAyB,CAAC;IAEpE,SACD,CACF,EACD,gBACA,iBACD;AACD,QAAO;;;;AC9GT,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,mBAAmB,EAChE,SACE,YACA,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,aACA,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,YACA,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,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,qBAAqB,EAClE,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;;;;ACnCT,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,EAAE,UAAU,EAAE,WAAW,qCAAqC,EAAE,YAAY;AAC7F,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,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,YAAY,QAAQ,OAAO,SAAS,EACjF,SACD,EACD,QAAQ,WAAW,IACf,EAAE,OAAO,EAAE,OAAO,6BAA6B,EAAE,sBAAsB,GACvE,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;;;;AC/CT,eAAsB,YAA2B;AAC/C,SAAQ,IAAI,sCAAsC;AAClD,QAAO,QAAQ,MAAM;;AAuDvB,eAAsB,gBAA+B;AACnD,SAAQ,IAAI,0CAA0C;;AAExD,cAA6D,oBAAoB;AAqDjF,eAAsB,gBAA6C;AACjE,QAAO,KAAK,KAAK;;AAEnB,cAA6D,oBAAoB;;;;;;;;;;ACtGjF,MAAa,uBAAuB;;AAGpC,MAAa,sBAAsB;AASnC,MAAM,cAA8B;CAClC,IAAI;CACJ,OAAO;CACP,OAAO;CACP,QAAQ;CACR,KAAK;CACL,OAAO;CACP,aAAa;CACb,gBAAgB;CACjB;;;;;;;;;AAqBD,MAAa,mBAAqC;CAChD;CAEA;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CAED;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CACD;EACE,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CA9ImC;EACpC,IAAI;EACJ,OAAO;EACP,OAAO;EACP,QAAQ;EACR,KAAK;EACL,OAAO;EACP,aAAa;EACb,gBAAgB;EACjB;CAuIA;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;;;;;;;;;;;AAYhD,SAAgB,sBACd,QACA,WACA,MACgB;AAChB,KAAI,OAAO,OAAO,UAAU,OAAO,OAAO,SACxC,QAAO;EAAE,KAAK;EAAG,QAAQ;EAAG,MAAM;EAAG,OAAO;EAAG;AAEjD,KAAI,CAAC,UACH,QAAO;EAAE,KAAK,OAAO;EAAa,QAAQ,OAAO;EAAgB,MAAM;EAAG,OAAO;EAAG;AAEtF,KAAI,OAAO,UAAU,WAAW,OAAO,UAAU,iBAC/C,QAAO;EACL,KAAK;EACL,QAAQ,OAAO;EACf,MAAM,SAAS,SAAS,OAAO,cAAc;EAC7C,OAAO,SAAS,UAAU,OAAO,cAAc;EAChD;AAGH,QAAO;EACL,KAAK,OAAO;EACZ,QAAQ,OAAO;EACf,MAAM;EACN,OAAO;EACR;;;AAIH,SAAS,yBAAyB,OAA4B;AAC5D,KAAI,MAAM,WAAW,UAAU,MAAM,WAAW,SAAU;CAE1D,MAAM,OAAO,sBADE,UAAU,MAAM,OAAO,EAGpC,qBAAqB,MAAM,KAAK,aAChC,MAAM,cACP;CACD,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;;;;;;;;;;;;;;;;AAiBhC,SAAS,aAAa,QAAwB,aAAqB,MAA2B;AAC5F,sBAAqB;CACrB,MAAM,KAAK,EAAE,OAAO;EAClB,IAAI;EACJ,WAAW,yBAAyB;EACpC,eAAe;EAChB,CAAC;AACF,IAAG,MAAM,MAAM,GAAG,OAAO,YAAY;CAErC,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;;;;;;;AA0B/B,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;;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,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,QAAQ,SAAS,MAAM,MAAM,aAAa,MAAM,cAAc;KAE3E,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;;AAGjD,SAAS,gBAAgB,GAAgC;AACvD,QAAO,MAAM,UAAU,MAAM;;;AAI/B,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;AACpE,MAAI,gBAAgB,IAAI,cAAc,CAAE,MAAK,gBAAgB,IAAI;AACjE,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;;;;AC5mBT,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,sBAAsB,EAAE,UAAU,EAAE,WAAW,cAAc,CAAC;AACpE,KAAI,SAAU,qBAAoB,WAAW;AAC7C,MAAK,MAAM,OAAO,CAAC,QAAQ,QAAQ,EAAqB;EACtD,MAAM,SAAS,EAAE,UAAU,EAAE,OAAO,KAAK,EAAE,IAAI;AAC/C,MAAI,QAAQ,GAAG,cAAe,QAAO,WAAW;AAChD,sBAAoB,YAAY,OAAO;;AAEzC,qBAAoB,iBAAiB,gBAAgB;AACnD,WAAS,MAAM,YAAY,EAAE,eAAe,oBAAoB,OAAwB,CAAC;GACzF;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,cAAc,EAC3D,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,aAAa,EAAE,WAAW,EAC5E,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,cAAc,EAAE,YAAY,CAC/E;;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,EACE,OACA,EAAE,OAAO,6BAA6B,EACtC,kDACD,CACF;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,gBAAgB,GAAG,gBAAgB,SAAS,GAAG,UAAU,WAAW;AAC1E,OAAK,KACH,EACE,OACA,EAAE,WAAW,kBAAkB,EAC/B,EAAE,QAAQ,EAAE,EAAE,iBAAiB,EAC/B,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,WAAW,GAAG,cAAc;AACzE,QAAK,KACH,EACE,OACA,EAAE,WAAW,kBAAkB,EAC/B,EAAE,QAAQ,EAAE,EAAE,YAAY,EAC1B,EACE,QACA,EAAE,WAAW,oBAAoB,EACjC,IAAI,OAAO,IAAI,IAAI,OAAO,MAAM,IAAI,OAAO,OAAO,IAAI,OAAO,OAC9D,CACF,CACF;;AAGH,MAAI,GAAG,aAAa,CAAC,UACnB,MAAK,KACH,EACE,OACA,EAAE,WAAW,kBAAkB,EAC/B,EAAE,QAAQ,EAAE,EAAE,cAAc,EAC5B,EACE,QACA,EAAE,WAAW,oBAAoB,EACjC,2BAA8C,GAAG,gBAClD,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,SAAS,EACtD,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,aAAa,EAC1E,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,cAAc,EAAE,kBAAkB,CACrF;AAID,KAAI,qBAAqB,GAAG,KAAK,eAAe,GAAG,WAAW,UAAU,GAAG,WAAW,UAAU;EAC9F,MAAM,QAAQ,UAAU,GAAG,OAAO,CAAC;AACnC,MAAI,UAAU,WAAW,UAAU,iBACjC,eAAc,YACZ,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,aAAa,EAAE,oBAAoB,CACtF;;AAIL,WAAU,OACR,eACA,WACA,EACE,OACA,EAAE,WAAW,eAAe,EAC5B,EAAE,OAAO,EAAE,WAAW,qBAAqB,EAAE,aAAa,EAC1D,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,aAAa,EAAE,cAAc,EAC/E,EACE,OACA,EAAE,WAAW,WAAW,EACxB,EAAE,SAAS,EAAE,EAAE,4BAA4B,EAC3C,eACD,EACD,EAAE,OAAO,EAAE,WAAW,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,eAAe,EAAE,iBAAiB,CACrF,EACD,SACD;AAED,QAAO;;;;ACxOT,MAAa,OAA4C;CACvD;EAAE,IAAI;EAAO,OAAO;EAAe;CACnC;EAAE,IAAI;EAAY,OAAO;EAAY;CACrC;EAAE,IAAI;EAAe,OAAO;EAAe;CAC3C;EAAE,IAAI;EAAY,OAAO;EAAY;CACrC;EAAE,IAAI;EAAU,OAAO;EAAU;CACjC;EAAE,IAAI;EAAO,OAAO;EAAO;CAC3B;EAAE,IAAI;EAAU,OAAO;EAAU;CACjC;EAAE,IAAI;EAAa,OAAO;EAAa;CACvC;EAAE,IAAI;EAAW,OAAO;EAAW;CACpC;AAOD,SAAgB,mBAAmB,cAA4D;AAC7F,QAAO;EACL,KAAK;EACL,aAAa;EACb,UAAU;EACV,QAAQ;EACR,UAAU;EACV,KAAK;EACL,QAAQ;EACR,WAAW;EACX,eAAe,iBAAiB,aAAa;EAC9C;;;;;;;;;;ACjCH,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;AAGjC,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,oBAAoB,WAAW,QAAQ,CACvF;;AAGH,QAAO,iBAAiB,iBAAiB,CAAC,SAAS,OAAO;AACxD,KAAG,UAAU,OAAO,UAAU,GAAG,aAAa,WAAW,KAAK,WAAW;GACzE;;AAIJ,IAAI,OAAO,WAAW,YACpB,QAAO,iBAAiB,2BAA2B,MAAa;AAE9D,cADgB,EAAkB,OACd;AACpB,KAAI,WAAW,CAAC,QAAQ,UAAU,SAAS,OAAO,EAAE;AAClD,WAAS;AACT,UAAQ,UAAU,IAAI,OAAO;;AAE/B,eAAc;EACd;AAGJ,SAAS,QAAQ;AACf,KAAI,OAAO,aAAa,YAAa;AACrC,KAAI,SAAS,cAAc,oBAAoB,CAAE;AAGjD,uBAAsB,aAAa;AAGnC,eAAc;CAGd,MAAM,QAAQ,SAAS,cAAc,QAAQ;AAC7C,OAAM,cAAc;AACpB,UAAS,KAAK,YAAY,MAAM;CAGhC,MAAM,SAAS,EAAE,UAAU;EAAE,WAAW;EAAoB,OAAO;EAAgB,EAAE,MAAM;AAC3F,uBAAsB,OAAO;AAG7B,WAAU,EAAE,OAAO,EAAE,WAAW,aAAa,CAAC;CAE9C,MAAM,WAAW,EAAE,UAAU;EAAE,WAAW;EAAmB,OAAO;EAAS,EAAE,IAAS;AACxF,UAAS,iBAAiB,eAAe;AACvC,WAAS;AACT,UAAS,UAAU,OAAO,OAAO;GACjC;CAEF,MAAM,YAAY,EAChB,QACA;EACE,WAAW,kBAAkB,SAAS,MAAM,gBAAgB,sBAAsB;EAClF,OAAO;EACR,EACD,SAAS,MAAM,gBAAgB,SAAS,YACzC;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,gBAAgB,SAAS;AAChE,gBAAc;GACd;CAEF,MAAM,cAAc,EAClB,QACA,EAAE,OAAO,2CAA2C,EACpD,WACA,EAAE,QAAQ,EAAE,OAAO,6CAA6C,EAAE,SAAkB,EACpF,SACD;CACD,MAAM,SAAS,EACb,OACA,EAAE,WAAW,oBAAoB,EACjC,EAAE,QAAQ,EAAE,EAAE,eAAe,EAC7B,YACD;AAED,UAAS,EAAE,OAAO,EAAE,WAAW,kBAAkB,CAAC;AAClD,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,EAAE,UAAU;GAAE,WAAW;GAAiB,YAAY,IAAI;GAAI,EAAE,IAAI,MAAM;AACxF,QAAM,iBAAiB,eAAe;AACpC,gBAAa,IAAI;AACjB,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;;GAEhB;CAGF,IAAI,YAAY;AAChB,QAAO,iBAAiB,gBAAgB;AACtC,MAAI,UAAW;AACf,cAAY,4BAA4B;AACtC,eAAY;AACZ,cAAW,OAAO;AAClB,sBAAmB,OAAO;AAC1B,OAAI,OAAQ,qBAAoB,OAAO;IACvC;GACF;AAIF,UAAS,gBAAgB;AACvB,MAAI;AACF,OACE,WACC,eAAe,eACd,eAAe,aACf,eAAe,YACf,eAAe,cACf,eAAe,OAEjB,eAAc;WAET,KAAK;AACZ,WAAQ,MAAM,mDAAmD,IAAI;;GAEvE;AAEF,eAAc;;AAIhB,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"}
|
package/package.json
CHANGED