@quitting/ui 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"file":"atim-ui.umd.js","names":[],"sources":["../src/directives/draggable.ts","../src/components/Icon.vue","../src/components/Icon.vue","../src/components/CallWindow.vue","../src/components/CallWindow.vue","../src/components/meeting/icons.ts","../src/components/meeting/MeetingHeader.vue","../src/components/meeting/MeetingHeader.vue","../src/components/meeting/MainVideoTile.vue","../src/components/meeting/MainVideoTile.vue","../src/components/meeting/SmallVideoTile.vue","../src/components/meeting/SmallVideoTile.vue","../src/components/meeting/VideoListPanel.vue","../src/components/meeting/VideoListPanel.vue","../src/components/meeting/MemberItem.vue","../src/components/meeting/MemberItem.vue","../src/components/meeting/MemberListPanel.vue","../src/components/meeting/MemberListPanel.vue","../src/components/meeting/SidebarTabs.vue","../src/components/meeting/SidebarTabs.vue","../src/components/ConfirmDialog.vue","../src/components/ConfirmDialog.vue","../src/components/MeetingEndedToast.vue","../src/components/MeetingEndedToast.vue","../src/components/MeetWindow.vue","../src/components/MeetWindow.vue","../src/sounds/ring-callee.mp3","../src/components/IncomingRing.vue","../src/components/IncomingRing.vue","../src/components/MeetingInvite.vue","../src/components/MeetingInvite.vue","../src/components/ChatPanel.vue","../src/components/ChatPanel.vue","../src/global-methods.ts","../src/sounds/ring-caller.mp3","../src/components/CallDialog.vue","../src/components/CallDialog.vue","../src/components/CallOutgoing.vue","../src/components/CallOutgoing.vue","../src/index.ts"],"sourcesContent":["import type { Directive, DirectiveBinding } from 'vue'\n\n// v-draggable:让元素可在视口内拖拽(基于 position: fixed)\n//\n// 用法:\n// <div v-draggable>整块都可拖</div>\n// <div v-draggable=\"'.header'\">只有 .header 子元素是拖拽句柄</div>\n//\n// 与 im-web/src/directives/draggable.ts 的差异:\n// - 基于 viewport(fixed)而非父元素(absolute)—— 通话窗口可挪到屏幕任意位置\n// - 用 addEventListener 而非覆盖 onmousedown,可与其他指令/事件共存\n// - unmounted 时清理 listener,避免泄漏\n// - 支持指定拖拽句柄(header),避免整块内容都触发拖拽(如输入框)\n//\n// 边界约束:元素中心点不超出视口;初始位置由元素 CSS 决定(不强制 left/top)\n\ninterface DragState {\n startX: number\n startY: number\n origLeft: number\n origTop: number\n}\n\nfunction getHandle(el: HTMLElement, binding: DirectiveBinding): HTMLElement {\n const selector = binding.value as string | undefined\n if (!selector) return el\n const handle = el.querySelector<HTMLElement>(selector)\n return handle || el\n}\n\nfunction onStart(e: MouseEvent, el: HTMLElement, state: DragState) {\n // 输入控件不触发拖拽(避免与 textarea/input select 冲突)\n const tag = (e.target as HTMLElement).tagName\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return\n\n const rect = el.getBoundingClientRect()\n state.startX = e.clientX\n state.startY = e.clientY\n state.origLeft = rect.left\n state.origTop = rect.top\n // 切到 fixed 并锁定当前坐标,避免后续 CSS 影响跳变\n // 关键:清掉 transform(如居中用的 translate(-50%, -50%)),否则 left/top 会双重偏移\n el.style.position = 'fixed'\n el.style.transform = 'none'\n el.style.left = `${rect.left}px`\n el.style.top = `${rect.top}px`\n el.style.right = 'auto'\n el.style.bottom = 'auto'\n el.style.margin = '0'\n el.classList.add('v-dragging')\n\n const onMove = (ev: MouseEvent) => {\n const dx = ev.clientX - state.startX\n const dy = ev.clientY - state.startY\n const newLeft = state.origLeft + dx\n const newTop = state.origTop + dy\n // 视口边界约束(保证元素至少有 50px 可见)\n const maxX = window.innerWidth - 50\n const maxY = window.innerHeight - 50\n const minX = 50 - el.offsetWidth\n const minY = 0\n el.style.left = `${Math.min(Math.max(newLeft, minX), maxX)}px`\n el.style.top = `${Math.min(Math.max(newTop, minY), maxY)}px`\n }\n const onUp = () => {\n document.removeEventListener('mousemove', onMove)\n document.removeEventListener('mouseup', onUp)\n el.classList.remove('v-dragging')\n }\n document.addEventListener('mousemove', onMove)\n document.addEventListener('mouseup', onUp)\n}\n\nconst draggable: Directive<HTMLElement> = {\n mounted(el, binding) {\n const handle = getHandle(el, binding)\n handle.style.cursor = 'move'\n const state: DragState = { startX: 0, startY: 0, origLeft: 0, origTop: 0 }\n const handler = (e: MouseEvent) => onStart(e, el, state)\n handle.addEventListener('mousedown', handler)\n // 存到 el 上方便 unmounted 清理\n ;(el as any).__dragHandle__ = handle\n ;(el as any).__dragHandler__ = handler\n },\n unmounted(el) {\n const handle = (el as any).__dragHandle__ as HTMLElement | undefined\n const handler = (el as any).__dragHandler__ as ((e: MouseEvent) => void) | undefined\n if (handle && handler) {\n handle.removeEventListener('mousedown', handler)\n }\n },\n}\n\nexport default draggable\n","<script setup lang=\"ts\">\n// 通用图标组件:SVG 路径内联在 ICON_PATHS 常量里,无运行时文件加载\n// 这样通过 npm 安装到其他项目后无需额外配置即可正常显示\n//\n// 用法:\n// <Icon name=\"mic-on\" :size=\"18\" />\n// <Icon name=\"mic-off\" :size=\"18\" color=\"#ef4444\" />\n//\n// 颜色继承 currentColor(父元素的 color CSS 属性),或显式传 color prop\n\n// 所有图标统一 viewBox=\"0 0 1024 1024\",方便对齐\n// fill 默认移除(继承 currentColor),少数图标有特殊 fill 在 path 上保留\nconst ICON_PATHS = {\n // 麦克风开启\n 'mic-on': `<path d=\"M514.986667 112c80.170667 0 145.109333 62.08 145.109333 138.666667v277.248c0 76.586667-64.938667 138.624-145.066667 138.624-80.213333 0-145.152-62.08-145.152-138.624V250.666667c0-76.586667 64.981333-138.666667 145.109334-138.666667z m236.970666 411.221333c0 118.485333-93.013333 215.893333-212.608 228.992v135.637334a24.149333 24.149333 0 0 1-48.341333 0v-135.04c-122.709333-10.24-219.008-109.013333-219.008-229.546667v-96.426667a24.704 24.704 0 0 1 49.408 0v109.184c0 98.56 83.712 169.088 190.592 169.088 106.837333 0 196.309333-92.8 196.309333-169.088V423.936a21.845333 21.845333 0 0 1 43.690667 0v99.285333z\"/>`,\n // 麦克风关闭\n 'mic-off': `<path d=\"M656.384 559.232L369.877333 272.725333v-22.058666c0-76.586667 64.981333-138.666667 145.109334-138.666667 80.170667 0 145.109333 62.08 145.109333 138.666667v277.248c0 10.794667-1.28 21.248-3.712 31.317333z m-84.693333 96.341333a150.528 150.528 0 0 1-56.704 10.965334c-80.128 0-145.066667-62.08-145.066667-138.624v-74.154667l201.770667 201.813333z m153.898666-27.136l-29.610666-29.568a165.973333 165.973333 0 0 0 12.373333-62.848V423.936a21.845333 21.845333 0 0 1 43.648 0v99.285333c0 37.930667-9.557333 73.685333-26.453333 105.216z m-89.173333 91.904a245.290667 245.290667 0 0 1-97.066667 31.872v135.637334a24.149333 24.149333 0 0 1-48.341333 0v-135.04c-122.709333-10.24-219.008-109.013333-219.008-229.546667v-96.426667a24.704 24.704 0 0 1 49.408 0v109.184c0 98.56 86.613333 178.474667 193.450667 178.474667 33.92 0 65.834667-8.064 93.568-22.186667l28.032 28.032zM145.28 183.893333l45.226667-45.226666 678.826666 678.826666-45.226666 45.226667z\"/>`,\n // 摄像头开启\n 'camera-on': `<path d=\"M873.770667 314.922667c19.797333-12.202667 37.632-2.048 37.632 18.304v335.232c0 24.362667-15.872 32.512-37.632 18.261333l-112.938667-67.029333c-19.797333-12.202667-37.632-26.453333-37.632-46.72V424.618667c0-18.261333 17.834667-30.464 37.632-42.666667l112.938667-67.029333zM207.232 288h391.936c43.562667 0 79.232 32 79.232 71.125333v305.749334c0 39.125333-35.669333 71.125333-79.232 71.125333H207.232C163.669333 736 128 704 128 664.874667V359.125333C128 320 163.669333 288 207.232 288z\"/>`,\n // 摄像头关闭\n 'camera-off': `<path d=\"M873.770667 314.922667c19.797333-12.202667 37.632-2.048 37.632 18.304v335.232c0 24.362667-15.872 32.512-37.632 18.261333l-112.938667-67.029333c-19.797333-12.202667-37.632-26.453333-37.632-46.72V424.618667c0-18.261333 17.834667-30.464 37.632-42.666667l112.938667-67.029333zM385.152 288h214.016c43.562667 0 79.232 32 79.232 71.125333v222.122667L385.152 288z m256.042667 437.077333a85.333333 85.333333 0 0 1-42.026667 10.922667H207.232C163.669333 736 128 704 128 664.874667V359.125333c0-38.186667 34.005333-69.632 76.16-71.082666l437.034667 437.034666zM145.28 183.893333l45.226667-45.226666 678.826666 678.826666-45.226666 45.226667z\"/>`,\n // 声音(扬声器开启)\n 'sound-on': `<path d=\"M767.317333 767.018667a33.237333 33.237333 0 0 1-20.565333 6.656 34.56 34.56 0 0 1-25.386667-10.709334 34.986667 34.986667 0 0 1-10.666666-25.557333 35.2 35.2 0 0 1 13.994666-28.501333 5.76 5.76 0 0 0 1.450667-1.109334l0.725333-0.725333c13.738667-9.386667 26.453333-20.48 38.272-33.28 11.733333-12.842667 22.058667-26.794667 30.848-41.813333 8.832-15.061333 15.701333-31.146667 20.608-48.128a190.293333 190.293333 0 0 0 7.338667-52.906667c0-37.034667-9.301333-70.826667-27.946667-101.418667a250.794667 250.794667 0 0 0-69.845333-75.52 34.730667 34.730667 0 0 1-15.445333-29.568 34.986667 34.986667 0 0 1 10.666666-25.557333 34.56 34.56 0 0 1 25.386667-10.709333c8.32 0 15.658667 2.688 22.058667 8.106666 18.133333 12.842667 35.029333 27.776 50.730666 44.8 15.658667 17.066667 29.141333 35.541333 40.448 55.509334 11.264 19.968 20.053333 41.344 26.453334 64 6.4 22.698667 9.557333 46.165333 9.557333 70.314666 0 24.704-3.2 48.384-9.557333 71.082667a285.653333 285.653333 0 0 1-26.837334 64.384 305.450667 305.450667 0 0 1-92.288 100.693333v-0.042666z m-41.173333-332.330667c10.282667 14.336 18.517333 28.757333 24.661333 43.306667 6.101333 14.549333 9.173333 32.213333 9.173334 52.906666 0 19.754667-3.2 37.546667-9.557334 53.333334-6.4 15.786667-14.464 30.08-24.277333 42.88-3.925333 5.461333-8.106667 10.24-12.501333 14.464a23.552 23.552 0 0 1-16.896 6.272 29.866667 29.866667 0 0 1-22.442667-9.6 32.085333 32.085333 0 0 1-9.173333-22.954667 33.706667 33.706667 0 0 1 2.218666-11.861333h-0.768c2.474667-5.418667 5.418667-10.581333 8.832-15.530667 3.413333-4.949333 6.741333-9.984 9.941334-15.146667a84.48 84.48 0 0 0 8.106666-17.792c2.176-6.656 3.285333-14.677333 3.285334-24.064 0-8.874667-1.109333-16.64-3.328-23.296a94.208 94.208 0 0 0-8.106667-18.133333 240.170667 240.170667 0 0 0-9.898667-15.530667 67.072 67.072 0 0 1-8.106666-15.573333 29.994667 29.994667 0 0 1-2.176-11.818667c0-8.874667 3.072-16.512 9.173333-22.954666a29.866667 29.866667 0 0 1 22.442667-9.6c6.826667 0 12.501333 2.090667 16.896 6.272 4.437333 4.181333 8.576 9.002667 12.501333 14.421333z m-237.482667 439.637333a9078.869333 9078.869333 0 0 0-157.354666-139.136 10554.453333 10554.453333 0 0 0-83.797334-70.314666 177.408 177.408 0 0 1-16.213333 0.725333 125.781333 125.781333 0 0 1-13.952 0.768h-12.501333c-13.226667 0-24.021333-4.096-32.341334-12.202667a39.424 39.424 0 0 1-12.501333-29.269333v-186.453333h0.725333l-0.725333-0.768c0-13.824 4.266667-25.173333 12.885333-34.048a42.666667 42.666667 0 0 1 32-13.312h41.856a13012.906667 13012.906667 0 0 1 122.794667-103.637334c15.701333-13.312 31.146667-26.282667 46.336-38.826666 15.189333-12.586667 28.928-24.064 41.173333-34.432 12.245333-10.368 22.570667-18.986667 30.890667-25.898667 8.32-6.912 13.482667-11.093333 15.445333-12.586667 14.677333-10.88 26.581333-15.786667 35.626667-14.805333 9.088 0.981333 16.213333 5.418667 21.333333 13.312s8.704 17.493333 10.666667 28.885333c1.962667 11.349333 2.944 21.674667 2.944 31.061334v599.466666c0 9.386667-1.706667 19.413333-5.12 29.994667-3.456 10.624-8.746667 19.029333-15.829333 25.173333-7.125333 6.186667-16.042667 8.746667-26.88 7.765334-10.752-0.981333-23.253333-8.106667-37.461334-21.461334z\"/>`,\n // 静音(扬声器关闭)\n 'sound-off': `<path d=\"M767.872 670.72c10.624-11.946667 20.010667-24.917333 28.16-38.784 8.789333-15.018667 15.658667-31.061333 20.565333-48.085333a190.293333 190.293333 0 0 0 7.338667-52.906667c0-37.034667-9.301333-70.826667-27.946667-101.418667a250.794667 250.794667 0 0 0-69.845333-75.52 34.730667 34.730667 0 0 1-15.445333-29.568 34.986667 34.986667 0 0 1 10.666666-25.557333 34.56 34.56 0 0 1 25.386667-10.709333c8.32 0 15.658667 2.688 22.058667 8.106666 18.133333 12.842667 35.029333 27.776 50.730666 44.8 15.658667 17.066667 29.141333 35.541333 40.448 55.509334 11.264 19.968 20.053333 41.344 26.453334 64 6.4 22.698667 9.557333 46.165333 9.557333 70.314666 0 24.704-3.2 48.384-9.557333 71.082667a285.653333 285.653333 0 0 1-26.837334 64.384 304.64 304.64 0 0 1-40.533333 55.552l-51.2-51.2zM725.333333 628.224l-46.08-46.08c2.090667-3.072 4.096-6.229333 6.058667-9.386667a84.48 84.48 0 0 0 8.106667-17.792c2.176-6.656 3.285333-14.677333 3.285333-24.064 0-8.874667-1.109333-16.64-3.328-23.296a94.208 94.208 0 0 0-8.106667-18.133333 240.170667 240.170667 0 0 0-9.898666-15.530667 67.072 67.072 0 0 1-8.106667-15.573333 29.994667 29.994667 0 0 1-2.176-11.818667c0-8.874667 3.072-16.512 9.173333-22.954666a29.866667 29.866667 0 0 1 22.442667-9.6c6.826667 0 12.501333 2.090667 16.896 6.272 4.437333 4.181333 8.576 9.002667 12.501333 14.421333 10.282667 14.336 18.517333 28.757333 24.661334 43.306667 6.101333 14.549333 9.173333 32.213333 9.173333 52.906666 0 19.754667-3.2 37.546667-9.557333 53.333334A183.168 183.168 0 0 1 725.333333 628.181333zM278.997333 362.837333l294.954667 294.997334v175.061333c0 9.386667-1.706667 19.370667-5.12 29.952-3.456 10.624-8.746667 19.029333-15.829333 25.173333-7.125333 6.186667-16.042667 8.746667-26.88 7.765334-10.752-0.981333-23.253333-8.106667-37.461334-21.461334a9078.869333 9078.869333 0 0 0-157.354666-139.136 10554.453333 10554.453333 0 0 0-83.797334-70.314666 177.408 177.408 0 0 1-16.213333 0.725333 125.781333 125.781333 0 0 1-13.952 0.768h-12.501333c-13.226667 0-24.021333-4.096-32.341334-12.202667a39.424 39.424 0 0 1-12.501333-29.269333v-186.453333h0.725333l-0.725333-0.768c0-13.824 4.266667-25.173333 12.885333-34.048a42.666667 42.666667 0 0 1 32-13.312h41.856l32.256-27.434667z m98.261334-82.688c13.056-11.050667 25.898667-21.802667 38.613333-32.341333 15.189333-12.544 28.928-24.021333 41.173333-34.389333 12.245333-10.368 22.570667-18.986667 30.890667-25.898667 8.32-6.912 13.482667-11.093333 15.445333-12.586667 14.677333-10.88 26.581333-15.786667 35.626667-14.805333 9.088 0.981333 16.213333 5.418667 21.333333 13.312s8.704 17.493333 10.666667 28.885333c1.962667 11.349333 2.944 21.674667 2.944 31.061334v243.413333l-196.693333-196.693333zM145.28 183.893333l45.226667-45.226666 678.826666 678.826666-45.226666 45.226667z\"/>`,\n // 接听(电话听筒)\n answer: `<path d=\"M864.597333 647.338667l11.52 11.562666c0.341333 0.682667 0.597333 1.408 1.066667 2.005334 11.52 14.762667 14.848 32.213333 10.325333 52.266666-3.285333 14.421333-9.216 28.16-16.597333 41.386667-6.613333 11.861333-13.994667 23.381333-21.034667 34.986667l-9.045333 9.088c-5.845333 3.541333-11.477333 7.594667-17.493333 10.453333-15.018667 7.04-30.421333 8.533333-45.781334 8.490667-20.906667-0.042667-40.96-3.498667-60.970666-7.210667-32.810667-6.101333-64.896-13.952-94.933334-26.88-42.154667-18.133333-79.658667-42.922667-115.2-70.528a987.818667 987.818667 0 0 1-78.08-68.010667c-0.682667-0.682667-1.536-1.152-2.346666-1.706666l-44.373334-44.373334c-0.682667-0.938667-1.322667-1.962667-2.133333-2.816-19.072-22.058667-38.656-43.690667-57.216-66.218666a620.117333 620.117333 0 0 1-60.928-88.746667C236.245333 396.928 216.32 350.421333 212.608 296.362667c-1.450667-20.693333-4.224-41.088-2.389333-62.336 1.237333-14.677333 4.053333-29.184 11.52-43.008 2.048-3.797333 4.522667-7.381333 6.826666-11.050667l9.045334-9.045333c8.32-5.248 16.554667-10.538667 24.917333-15.616 13.909333-8.490667 27.733333-17.109333 42.965333-22.442667 15.957333-5.632 30.634667-4.778667 43.605334 3.2 4.906667 3.029333 9.429333 6.613333 14.122666 9.941333l8.789334 8.789334c0.768 1.066667 1.493333 2.133333 2.304 3.157333 4.266667 5.12 8.746667 10.026667 12.714666 15.36 23.637333 31.616 42.325333 66.56 58.581334 103.082667 8.448 19.029333 6.186667 38.741333-6.826667 58.154666-6.101333 9.045333-13.226667 17.621333-20.736 25.642667-14.506667 15.488-19.84 32.213333-15.616 50.56 1.536 6.570667 3.754667 12.928 6.570667 18.901333 9.258667 19.754667 22.826667 36.266667 37.76 51.626667 27.178667 27.861333 54.869333 55.338667 82.389333 82.901333 15.488 15.488 31.530667 30.378667 50.133333 42.24 11.52 7.381333 23.68 13.525333 37.973334 15.445334 14.08 1.962667 27.562667-1.365333 39.808-11.264 6.442667-5.205333 12.330667-11.136 18.645333-16.512 22.784-19.285333 46.506667-25.642667 71.850667-15.914667 37.546667 14.506667 71.253333 34.645333 102.826666 58.197333 4.778667 3.584 9.472 7.338667 14.208 10.965334z\"/>`,\n // 挂断\n hangup: `<path d=\"M68.820008 642.540165a22.883151 22.883151 0 0 0 13.903686 21.04864c2.800052 1.158641 5.793203 1.737961 8.689804 1.737961l216.37612-0.38621a22.786601 22.786601 0 0 0 22.786601-22.786601l0-87.670471c11.682955-13.807125 25.779752-25.296981 41.517949-33.986785a274.115036 274.115036 0 0 1 140.002394-34.276445l0.482771 0a270.832225 270.832225 0 0 1 139.616184 34.083335c15.641647 8.786365 29.641883 20.276209 41.228289 34.083346l0 87.1877a22.69004 22.69004 0 0 0 22.59349 22.786601l194.072289-0.28966 22.30383 0a22.59349 22.59349 0 0 0 22.690051-22.786601l0.09655-49.531883a22.207281 22.207281 0 0 0-0.67587-5.310432l-0.965541-3.958692a150.140499 150.140499 0 0 0-11.586405-46.828392c-43.352459-100.898276-222.748643-176.982342-417.303691-176.982342l-0.965541 0a868.01487 868.01487 0 0 0-229.024605 32.152275c-76.566836 21.72451-137.105804 52.138824-170.609819 85.64285-17.765829 17.283058-32.152275 37.752367-42.290379 60.442418-8.207045 18.441688-12.551947 38.428237-12.841607 58.607897l-0.09655 47.021491z\"/>`,\n // 举手\n handup: `<path d=\"M166.997333 665.6H150.613333c-0.725333-0.213333-1.450667-0.597333-2.133333-0.682667-18.602667-2.261333-33.28-12.245333-44.288-29.653333a155.818667 155.818667 0 0 1-17.536-41.002667c-3.712-13.056-6.613333-26.410667-9.898667-39.594666v-12.842667c1.621333-6.613333 2.730667-13.482667 4.992-19.754667 5.632-15.573333 15.488-27.52 26.368-38.4 14.805333-14.72 31.402667-26.453333 48.213334-37.973333 27.477333-18.901333 55.765333-36.010667 86.144-48.128 42.624-16.981333 86.613333-25.941333 131.285333-31.573333 34.346667-4.266667 68.778667-6.4 103.296-7.125334 0.981333 0 1.92-0.298667 2.901333-0.469333h62.72c1.152 0.170667 2.346667 0.426667 3.498667 0.512 29.098667 2.090667 58.24 3.541333 87.296 6.314667 35.712 3.413333 70.997333 10.112 105.856 19.712 48.981333 13.44 95.957333 32.256 136.832 67.84 15.616 13.653333 32 26.069333 45.781333 42.368 9.472 11.264 17.749333 23.466667 22.229334 38.570666 1.28 4.138667 2.005333 8.405333 2.986666 12.629334v12.8c-2.176 9.6-4.266667 19.2-6.570666 28.714666-3.84 15.786667-7.552 31.701333-14.506667 46.208-7.338667 15.274667-18.304 25.002667-33.109333 28.586667-5.632 1.28-11.349333 1.962667-17.066667 2.944h-12.373333c-1.28-0.213333-2.56-0.469333-3.84-0.597333-6.656-0.64-13.269333-0.938667-19.882667-1.877334-39.082667-5.632-77.013333-17.152-114.346667-31.488-19.413333-7.466667-31.701333-22.997333-36.266666-45.952a205.098667 205.098667 0 0 1-3.456-32.810666c-0.682667-21.162667-8.746667-36.778667-24.746667-46.762667a94.72 94.72 0 0 0-17.92-8.704c-20.565333-7.424-41.856-9.514667-63.232-9.813333-38.954667-0.469333-77.952-0.341333-116.906667-0.341334-21.888 0-43.776 0.810667-65.365333 5.546667-13.312 2.944-26.24 7.210667-37.717333 15.914667-11.349333 8.618667-18.517333 20.48-20.181334 36.181333-0.853333 8.192-0.853333 16.554667-1.493333 24.789333-2.474667 29.781333-14.805333 51.072-39.594667 62.08-36.736 16.298667-74.88 25.898667-113.834666 31.573334-5.973333 0.853333-11.861333 1.493333-17.792 2.261333z\"/>`,\n // 关闭(叉)\n close: `<path d=\"M649.179 512l212.839-212.84c37.881-37.881 37.881-99.298 0-137.179s-99.298-37.881-137.179 0L512 374.821l-212.839-212.84c-37.881-37.881-99.298-37.881-137.179 0s-37.881 99.298 0 137.179L374.821 512 161.982 724.84c-37.881 37.881-37.881 99.297 0 137.179 18.94 18.94 43.765 28.41 68.589 28.41 24.825 0 49.649-9.47 68.589-28.41L512 649.179l212.839 212.84c18.94 18.94 43.765 28.41 68.589 28.41s49.649-9.47 68.59-28.41c37.881-37.882 37.881-99.298 0-137.179L649.179 512z\"/>`,\n // 用户\n user: `<path d=\"M0 0h1024v1024H0V0z m614.8608 664.439467c-2.973013-30.685867-1.983147-53.4528-1.983147-82.158934 13.88544-7.918933 63.488-55.432533 67.437227-95.0272 10.922667-0.989867 28.757333-11.8784 33.716907-55.432533 2.976427-22.766933-7.932587-36.625067-14.875307-40.584533 17.851733-54.442667 25.78432-224.703147-99.170987-241.53088C587.093333 127.92832 554.366293 116.053333 511.72352 116.053333c-170.571093 2.9696-221.149867 130.6624-183.463253 276.176214-6.94272 3.959467-17.851733 16.827733-14.875307 40.584533 4.959573 43.554133 22.807893 54.442667 33.716907 55.432533 3.966293 40.584533 53.551787 88.098133 67.437226 95.0272 0 28.706133 0.989867 50.4832-1.98656 82.158934C378.835627 756.497067 157.696 708.983467 146.773333 907.946667h733.866667c-5.94944-201.936213-232.05888-151.453013-265.775787-243.510614z\"/>`,\n // 更多(三个点)\n more: `<circle cx=\"180\" cy=\"512\" r=\"90\"/><circle cx=\"512\" cy=\"512\" r=\"90\"/><circle cx=\"844\" cy=\"512\" r=\"90\"/>`,\n // 加号\n plus: `<path d=\"M480 224v256H224a32 32 0 1 0 0 64h256v256a32 32 0 1 0 64 0V544h256a32 32 0 1 0 0-64H544V224a32 32 0 1 0-64 0z\"/>`,\n // 减号\n minus: `<path d=\"M192 480h640a32 32 0 1 1 0 64H192a32 32 0 1 1 0-64z\"/>`,\n} as const\n\nexport type IconName = keyof typeof ICON_PATHS\n\nconst props = withDefaults(\n defineProps<{\n name: IconName\n size?: number | string\n color?: string\n title?: string\n }>(),\n {\n size: 16,\n color: 'currentColor',\n },\n)\n</script>\n\n<template>\n <svg\n :width=\"props.size\"\n :height=\"props.size\"\n viewBox=\"0 0 1024 1024\"\n xmlns=\"http://www.w3.org/2000/svg\"\n :fill=\"props.color\"\n :aria-label=\"props.title\"\n :aria-hidden=\"props.title ? undefined : 'true'\"\n v-html=\"ICON_PATHS[props.name]\"\n />\n</template>\n","<script setup lang=\"ts\">\n// 通用图标组件:SVG 路径内联在 ICON_PATHS 常量里,无运行时文件加载\n// 这样通过 npm 安装到其他项目后无需额外配置即可正常显示\n//\n// 用法:\n// <Icon name=\"mic-on\" :size=\"18\" />\n// <Icon name=\"mic-off\" :size=\"18\" color=\"#ef4444\" />\n//\n// 颜色继承 currentColor(父元素的 color CSS 属性),或显式传 color prop\n\n// 所有图标统一 viewBox=\"0 0 1024 1024\",方便对齐\n// fill 默认移除(继承 currentColor),少数图标有特殊 fill 在 path 上保留\nconst ICON_PATHS = {\n // 麦克风开启\n 'mic-on': `<path d=\"M514.986667 112c80.170667 0 145.109333 62.08 145.109333 138.666667v277.248c0 76.586667-64.938667 138.624-145.066667 138.624-80.213333 0-145.152-62.08-145.152-138.624V250.666667c0-76.586667 64.981333-138.666667 145.109334-138.666667z m236.970666 411.221333c0 118.485333-93.013333 215.893333-212.608 228.992v135.637334a24.149333 24.149333 0 0 1-48.341333 0v-135.04c-122.709333-10.24-219.008-109.013333-219.008-229.546667v-96.426667a24.704 24.704 0 0 1 49.408 0v109.184c0 98.56 83.712 169.088 190.592 169.088 106.837333 0 196.309333-92.8 196.309333-169.088V423.936a21.845333 21.845333 0 0 1 43.690667 0v99.285333z\"/>`,\n // 麦克风关闭\n 'mic-off': `<path d=\"M656.384 559.232L369.877333 272.725333v-22.058666c0-76.586667 64.981333-138.666667 145.109334-138.666667 80.170667 0 145.109333 62.08 145.109333 138.666667v277.248c0 10.794667-1.28 21.248-3.712 31.317333z m-84.693333 96.341333a150.528 150.528 0 0 1-56.704 10.965334c-80.128 0-145.066667-62.08-145.066667-138.624v-74.154667l201.770667 201.813333z m153.898666-27.136l-29.610666-29.568a165.973333 165.973333 0 0 0 12.373333-62.848V423.936a21.845333 21.845333 0 0 1 43.648 0v99.285333c0 37.930667-9.557333 73.685333-26.453333 105.216z m-89.173333 91.904a245.290667 245.290667 0 0 1-97.066667 31.872v135.637334a24.149333 24.149333 0 0 1-48.341333 0v-135.04c-122.709333-10.24-219.008-109.013333-219.008-229.546667v-96.426667a24.704 24.704 0 0 1 49.408 0v109.184c0 98.56 86.613333 178.474667 193.450667 178.474667 33.92 0 65.834667-8.064 93.568-22.186667l28.032 28.032zM145.28 183.893333l45.226667-45.226666 678.826666 678.826666-45.226666 45.226667z\"/>`,\n // 摄像头开启\n 'camera-on': `<path d=\"M873.770667 314.922667c19.797333-12.202667 37.632-2.048 37.632 18.304v335.232c0 24.362667-15.872 32.512-37.632 18.261333l-112.938667-67.029333c-19.797333-12.202667-37.632-26.453333-37.632-46.72V424.618667c0-18.261333 17.834667-30.464 37.632-42.666667l112.938667-67.029333zM207.232 288h391.936c43.562667 0 79.232 32 79.232 71.125333v305.749334c0 39.125333-35.669333 71.125333-79.232 71.125333H207.232C163.669333 736 128 704 128 664.874667V359.125333C128 320 163.669333 288 207.232 288z\"/>`,\n // 摄像头关闭\n 'camera-off': `<path d=\"M873.770667 314.922667c19.797333-12.202667 37.632-2.048 37.632 18.304v335.232c0 24.362667-15.872 32.512-37.632 18.261333l-112.938667-67.029333c-19.797333-12.202667-37.632-26.453333-37.632-46.72V424.618667c0-18.261333 17.834667-30.464 37.632-42.666667l112.938667-67.029333zM385.152 288h214.016c43.562667 0 79.232 32 79.232 71.125333v222.122667L385.152 288z m256.042667 437.077333a85.333333 85.333333 0 0 1-42.026667 10.922667H207.232C163.669333 736 128 704 128 664.874667V359.125333c0-38.186667 34.005333-69.632 76.16-71.082666l437.034667 437.034666zM145.28 183.893333l45.226667-45.226666 678.826666 678.826666-45.226666 45.226667z\"/>`,\n // 声音(扬声器开启)\n 'sound-on': `<path d=\"M767.317333 767.018667a33.237333 33.237333 0 0 1-20.565333 6.656 34.56 34.56 0 0 1-25.386667-10.709334 34.986667 34.986667 0 0 1-10.666666-25.557333 35.2 35.2 0 0 1 13.994666-28.501333 5.76 5.76 0 0 0 1.450667-1.109334l0.725333-0.725333c13.738667-9.386667 26.453333-20.48 38.272-33.28 11.733333-12.842667 22.058667-26.794667 30.848-41.813333 8.832-15.061333 15.701333-31.146667 20.608-48.128a190.293333 190.293333 0 0 0 7.338667-52.906667c0-37.034667-9.301333-70.826667-27.946667-101.418667a250.794667 250.794667 0 0 0-69.845333-75.52 34.730667 34.730667 0 0 1-15.445333-29.568 34.986667 34.986667 0 0 1 10.666666-25.557333 34.56 34.56 0 0 1 25.386667-10.709333c8.32 0 15.658667 2.688 22.058667 8.106666 18.133333 12.842667 35.029333 27.776 50.730666 44.8 15.658667 17.066667 29.141333 35.541333 40.448 55.509334 11.264 19.968 20.053333 41.344 26.453334 64 6.4 22.698667 9.557333 46.165333 9.557333 70.314666 0 24.704-3.2 48.384-9.557333 71.082667a285.653333 285.653333 0 0 1-26.837334 64.384 305.450667 305.450667 0 0 1-92.288 100.693333v-0.042666z m-41.173333-332.330667c10.282667 14.336 18.517333 28.757333 24.661333 43.306667 6.101333 14.549333 9.173333 32.213333 9.173334 52.906666 0 19.754667-3.2 37.546667-9.557334 53.333334-6.4 15.786667-14.464 30.08-24.277333 42.88-3.925333 5.461333-8.106667 10.24-12.501333 14.464a23.552 23.552 0 0 1-16.896 6.272 29.866667 29.866667 0 0 1-22.442667-9.6 32.085333 32.085333 0 0 1-9.173333-22.954667 33.706667 33.706667 0 0 1 2.218666-11.861333h-0.768c2.474667-5.418667 5.418667-10.581333 8.832-15.530667 3.413333-4.949333 6.741333-9.984 9.941334-15.146667a84.48 84.48 0 0 0 8.106666-17.792c2.176-6.656 3.285333-14.677333 3.285334-24.064 0-8.874667-1.109333-16.64-3.328-23.296a94.208 94.208 0 0 0-8.106667-18.133333 240.170667 240.170667 0 0 0-9.898667-15.530667 67.072 67.072 0 0 1-8.106666-15.573333 29.994667 29.994667 0 0 1-2.176-11.818667c0-8.874667 3.072-16.512 9.173333-22.954666a29.866667 29.866667 0 0 1 22.442667-9.6c6.826667 0 12.501333 2.090667 16.896 6.272 4.437333 4.181333 8.576 9.002667 12.501333 14.421333z m-237.482667 439.637333a9078.869333 9078.869333 0 0 0-157.354666-139.136 10554.453333 10554.453333 0 0 0-83.797334-70.314666 177.408 177.408 0 0 1-16.213333 0.725333 125.781333 125.781333 0 0 1-13.952 0.768h-12.501333c-13.226667 0-24.021333-4.096-32.341334-12.202667a39.424 39.424 0 0 1-12.501333-29.269333v-186.453333h0.725333l-0.725333-0.768c0-13.824 4.266667-25.173333 12.885333-34.048a42.666667 42.666667 0 0 1 32-13.312h41.856a13012.906667 13012.906667 0 0 1 122.794667-103.637334c15.701333-13.312 31.146667-26.282667 46.336-38.826666 15.189333-12.586667 28.928-24.064 41.173333-34.432 12.245333-10.368 22.570667-18.986667 30.890667-25.898667 8.32-6.912 13.482667-11.093333 15.445333-12.586667 14.677333-10.88 26.581333-15.786667 35.626667-14.805333 9.088 0.981333 16.213333 5.418667 21.333333 13.312s8.704 17.493333 10.666667 28.885333c1.962667 11.349333 2.944 21.674667 2.944 31.061334v599.466666c0 9.386667-1.706667 19.413333-5.12 29.994667-3.456 10.624-8.746667 19.029333-15.829333 25.173333-7.125333 6.186667-16.042667 8.746667-26.88 7.765334-10.752-0.981333-23.253333-8.106667-37.461334-21.461334z\"/>`,\n // 静音(扬声器关闭)\n 'sound-off': `<path d=\"M767.872 670.72c10.624-11.946667 20.010667-24.917333 28.16-38.784 8.789333-15.018667 15.658667-31.061333 20.565333-48.085333a190.293333 190.293333 0 0 0 7.338667-52.906667c0-37.034667-9.301333-70.826667-27.946667-101.418667a250.794667 250.794667 0 0 0-69.845333-75.52 34.730667 34.730667 0 0 1-15.445333-29.568 34.986667 34.986667 0 0 1 10.666666-25.557333 34.56 34.56 0 0 1 25.386667-10.709333c8.32 0 15.658667 2.688 22.058667 8.106666 18.133333 12.842667 35.029333 27.776 50.730666 44.8 15.658667 17.066667 29.141333 35.541333 40.448 55.509334 11.264 19.968 20.053333 41.344 26.453334 64 6.4 22.698667 9.557333 46.165333 9.557333 70.314666 0 24.704-3.2 48.384-9.557333 71.082667a285.653333 285.653333 0 0 1-26.837334 64.384 304.64 304.64 0 0 1-40.533333 55.552l-51.2-51.2zM725.333333 628.224l-46.08-46.08c2.090667-3.072 4.096-6.229333 6.058667-9.386667a84.48 84.48 0 0 0 8.106667-17.792c2.176-6.656 3.285333-14.677333 3.285333-24.064 0-8.874667-1.109333-16.64-3.328-23.296a94.208 94.208 0 0 0-8.106667-18.133333 240.170667 240.170667 0 0 0-9.898666-15.530667 67.072 67.072 0 0 1-8.106667-15.573333 29.994667 29.994667 0 0 1-2.176-11.818667c0-8.874667 3.072-16.512 9.173333-22.954666a29.866667 29.866667 0 0 1 22.442667-9.6c6.826667 0 12.501333 2.090667 16.896 6.272 4.437333 4.181333 8.576 9.002667 12.501333 14.421333 10.282667 14.336 18.517333 28.757333 24.661334 43.306667 6.101333 14.549333 9.173333 32.213333 9.173333 52.906666 0 19.754667-3.2 37.546667-9.557333 53.333334A183.168 183.168 0 0 1 725.333333 628.181333zM278.997333 362.837333l294.954667 294.997334v175.061333c0 9.386667-1.706667 19.370667-5.12 29.952-3.456 10.624-8.746667 19.029333-15.829333 25.173333-7.125333 6.186667-16.042667 8.746667-26.88 7.765334-10.752-0.981333-23.253333-8.106667-37.461334-21.461334a9078.869333 9078.869333 0 0 0-157.354666-139.136 10554.453333 10554.453333 0 0 0-83.797334-70.314666 177.408 177.408 0 0 1-16.213333 0.725333 125.781333 125.781333 0 0 1-13.952 0.768h-12.501333c-13.226667 0-24.021333-4.096-32.341334-12.202667a39.424 39.424 0 0 1-12.501333-29.269333v-186.453333h0.725333l-0.725333-0.768c0-13.824 4.266667-25.173333 12.885333-34.048a42.666667 42.666667 0 0 1 32-13.312h41.856l32.256-27.434667z m98.261334-82.688c13.056-11.050667 25.898667-21.802667 38.613333-32.341333 15.189333-12.544 28.928-24.021333 41.173333-34.389333 12.245333-10.368 22.570667-18.986667 30.890667-25.898667 8.32-6.912 13.482667-11.093333 15.445333-12.586667 14.677333-10.88 26.581333-15.786667 35.626667-14.805333 9.088 0.981333 16.213333 5.418667 21.333333 13.312s8.704 17.493333 10.666667 28.885333c1.962667 11.349333 2.944 21.674667 2.944 31.061334v243.413333l-196.693333-196.693333zM145.28 183.893333l45.226667-45.226666 678.826666 678.826666-45.226666 45.226667z\"/>`,\n // 接听(电话听筒)\n answer: `<path d=\"M864.597333 647.338667l11.52 11.562666c0.341333 0.682667 0.597333 1.408 1.066667 2.005334 11.52 14.762667 14.848 32.213333 10.325333 52.266666-3.285333 14.421333-9.216 28.16-16.597333 41.386667-6.613333 11.861333-13.994667 23.381333-21.034667 34.986667l-9.045333 9.088c-5.845333 3.541333-11.477333 7.594667-17.493333 10.453333-15.018667 7.04-30.421333 8.533333-45.781334 8.490667-20.906667-0.042667-40.96-3.498667-60.970666-7.210667-32.810667-6.101333-64.896-13.952-94.933334-26.88-42.154667-18.133333-79.658667-42.922667-115.2-70.528a987.818667 987.818667 0 0 1-78.08-68.010667c-0.682667-0.682667-1.536-1.152-2.346666-1.706666l-44.373334-44.373334c-0.682667-0.938667-1.322667-1.962667-2.133333-2.816-19.072-22.058667-38.656-43.690667-57.216-66.218666a620.117333 620.117333 0 0 1-60.928-88.746667C236.245333 396.928 216.32 350.421333 212.608 296.362667c-1.450667-20.693333-4.224-41.088-2.389333-62.336 1.237333-14.677333 4.053333-29.184 11.52-43.008 2.048-3.797333 4.522667-7.381333 6.826666-11.050667l9.045334-9.045333c8.32-5.248 16.554667-10.538667 24.917333-15.616 13.909333-8.490667 27.733333-17.109333 42.965333-22.442667 15.957333-5.632 30.634667-4.778667 43.605334 3.2 4.906667 3.029333 9.429333 6.613333 14.122666 9.941333l8.789334 8.789334c0.768 1.066667 1.493333 2.133333 2.304 3.157333 4.266667 5.12 8.746667 10.026667 12.714666 15.36 23.637333 31.616 42.325333 66.56 58.581334 103.082667 8.448 19.029333 6.186667 38.741333-6.826667 58.154666-6.101333 9.045333-13.226667 17.621333-20.736 25.642667-14.506667 15.488-19.84 32.213333-15.616 50.56 1.536 6.570667 3.754667 12.928 6.570667 18.901333 9.258667 19.754667 22.826667 36.266667 37.76 51.626667 27.178667 27.861333 54.869333 55.338667 82.389333 82.901333 15.488 15.488 31.530667 30.378667 50.133333 42.24 11.52 7.381333 23.68 13.525333 37.973334 15.445334 14.08 1.962667 27.562667-1.365333 39.808-11.264 6.442667-5.205333 12.330667-11.136 18.645333-16.512 22.784-19.285333 46.506667-25.642667 71.850667-15.914667 37.546667 14.506667 71.253333 34.645333 102.826666 58.197333 4.778667 3.584 9.472 7.338667 14.208 10.965334z\"/>`,\n // 挂断\n hangup: `<path d=\"M68.820008 642.540165a22.883151 22.883151 0 0 0 13.903686 21.04864c2.800052 1.158641 5.793203 1.737961 8.689804 1.737961l216.37612-0.38621a22.786601 22.786601 0 0 0 22.786601-22.786601l0-87.670471c11.682955-13.807125 25.779752-25.296981 41.517949-33.986785a274.115036 274.115036 0 0 1 140.002394-34.276445l0.482771 0a270.832225 270.832225 0 0 1 139.616184 34.083335c15.641647 8.786365 29.641883 20.276209 41.228289 34.083346l0 87.1877a22.69004 22.69004 0 0 0 22.59349 22.786601l194.072289-0.28966 22.30383 0a22.59349 22.59349 0 0 0 22.690051-22.786601l0.09655-49.531883a22.207281 22.207281 0 0 0-0.67587-5.310432l-0.965541-3.958692a150.140499 150.140499 0 0 0-11.586405-46.828392c-43.352459-100.898276-222.748643-176.982342-417.303691-176.982342l-0.965541 0a868.01487 868.01487 0 0 0-229.024605 32.152275c-76.566836 21.72451-137.105804 52.138824-170.609819 85.64285-17.765829 17.283058-32.152275 37.752367-42.290379 60.442418-8.207045 18.441688-12.551947 38.428237-12.841607 58.607897l-0.09655 47.021491z\"/>`,\n // 举手\n handup: `<path d=\"M166.997333 665.6H150.613333c-0.725333-0.213333-1.450667-0.597333-2.133333-0.682667-18.602667-2.261333-33.28-12.245333-44.288-29.653333a155.818667 155.818667 0 0 1-17.536-41.002667c-3.712-13.056-6.613333-26.410667-9.898667-39.594666v-12.842667c1.621333-6.613333 2.730667-13.482667 4.992-19.754667 5.632-15.573333 15.488-27.52 26.368-38.4 14.805333-14.72 31.402667-26.453333 48.213334-37.973333 27.477333-18.901333 55.765333-36.010667 86.144-48.128 42.624-16.981333 86.613333-25.941333 131.285333-31.573333 34.346667-4.266667 68.778667-6.4 103.296-7.125334 0.981333 0 1.92-0.298667 2.901333-0.469333h62.72c1.152 0.170667 2.346667 0.426667 3.498667 0.512 29.098667 2.090667 58.24 3.541333 87.296 6.314667 35.712 3.413333 70.997333 10.112 105.856 19.712 48.981333 13.44 95.957333 32.256 136.832 67.84 15.616 13.653333 32 26.069333 45.781333 42.368 9.472 11.264 17.749333 23.466667 22.229334 38.570666 1.28 4.138667 2.005333 8.405333 2.986666 12.629334v12.8c-2.176 9.6-4.266667 19.2-6.570666 28.714666-3.84 15.786667-7.552 31.701333-14.506667 46.208-7.338667 15.274667-18.304 25.002667-33.109333 28.586667-5.632 1.28-11.349333 1.962667-17.066667 2.944h-12.373333c-1.28-0.213333-2.56-0.469333-3.84-0.597333-6.656-0.64-13.269333-0.938667-19.882667-1.877334-39.082667-5.632-77.013333-17.152-114.346667-31.488-19.413333-7.466667-31.701333-22.997333-36.266666-45.952a205.098667 205.098667 0 0 1-3.456-32.810666c-0.682667-21.162667-8.746667-36.778667-24.746667-46.762667a94.72 94.72 0 0 0-17.92-8.704c-20.565333-7.424-41.856-9.514667-63.232-9.813333-38.954667-0.469333-77.952-0.341333-116.906667-0.341334-21.888 0-43.776 0.810667-65.365333 5.546667-13.312 2.944-26.24 7.210667-37.717333 15.914667-11.349333 8.618667-18.517333 20.48-20.181334 36.181333-0.853333 8.192-0.853333 16.554667-1.493333 24.789333-2.474667 29.781333-14.805333 51.072-39.594667 62.08-36.736 16.298667-74.88 25.898667-113.834666 31.573334-5.973333 0.853333-11.861333 1.493333-17.792 2.261333z\"/>`,\n // 关闭(叉)\n close: `<path d=\"M649.179 512l212.839-212.84c37.881-37.881 37.881-99.298 0-137.179s-99.298-37.881-137.179 0L512 374.821l-212.839-212.84c-37.881-37.881-99.298-37.881-137.179 0s-37.881 99.298 0 137.179L374.821 512 161.982 724.84c-37.881 37.881-37.881 99.297 0 137.179 18.94 18.94 43.765 28.41 68.589 28.41 24.825 0 49.649-9.47 68.589-28.41L512 649.179l212.839 212.84c18.94 18.94 43.765 28.41 68.589 28.41s49.649-9.47 68.59-28.41c37.881-37.882 37.881-99.298 0-137.179L649.179 512z\"/>`,\n // 用户\n user: `<path d=\"M0 0h1024v1024H0V0z m614.8608 664.439467c-2.973013-30.685867-1.983147-53.4528-1.983147-82.158934 13.88544-7.918933 63.488-55.432533 67.437227-95.0272 10.922667-0.989867 28.757333-11.8784 33.716907-55.432533 2.976427-22.766933-7.932587-36.625067-14.875307-40.584533 17.851733-54.442667 25.78432-224.703147-99.170987-241.53088C587.093333 127.92832 554.366293 116.053333 511.72352 116.053333c-170.571093 2.9696-221.149867 130.6624-183.463253 276.176214-6.94272 3.959467-17.851733 16.827733-14.875307 40.584533 4.959573 43.554133 22.807893 54.442667 33.716907 55.432533 3.966293 40.584533 53.551787 88.098133 67.437226 95.0272 0 28.706133 0.989867 50.4832-1.98656 82.158934C378.835627 756.497067 157.696 708.983467 146.773333 907.946667h733.866667c-5.94944-201.936213-232.05888-151.453013-265.775787-243.510614z\"/>`,\n // 更多(三个点)\n more: `<circle cx=\"180\" cy=\"512\" r=\"90\"/><circle cx=\"512\" cy=\"512\" r=\"90\"/><circle cx=\"844\" cy=\"512\" r=\"90\"/>`,\n // 加号\n plus: `<path d=\"M480 224v256H224a32 32 0 1 0 0 64h256v256a32 32 0 1 0 64 0V544h256a32 32 0 1 0 0-64H544V224a32 32 0 1 0-64 0z\"/>`,\n // 减号\n minus: `<path d=\"M192 480h640a32 32 0 1 1 0 64H192a32 32 0 1 1 0-64z\"/>`,\n} as const\n\nexport type IconName = keyof typeof ICON_PATHS\n\nconst props = withDefaults(\n defineProps<{\n name: IconName\n size?: number | string\n color?: string\n title?: string\n }>(),\n {\n size: 16,\n color: 'currentColor',\n },\n)\n</script>\n\n<template>\n <svg\n :width=\"props.size\"\n :height=\"props.size\"\n viewBox=\"0 0 1024 1024\"\n xmlns=\"http://www.w3.org/2000/svg\"\n :fill=\"props.color\"\n :aria-label=\"props.title\"\n :aria-hidden=\"props.title ? undefined : 'true'\"\n v-html=\"ICON_PATHS[props.name]\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, watch } from 'vue'\nimport type { CallType } from '@quitting/contract'\nimport vDraggable from '../directives/draggable'\nimport Icon from './Icon.vue'\n\n// 主叫/被叫统一组件——样式完全一致,行为也一致\n// 大窗默认显示远端(对方),小窗默认显示本地(自己)\n// 点击小窗切换大小窗的视频源(参考 im-web AtVideo 双击切换)\n// 控制栏悬浮于视频底部 30px 处,水平居中 3 个圆形按钮,hover 视频才显示\n\nconst props = defineProps<{\n mode: 'caller' | 'callee'\n calleeUid?: string\n callType: CallType\n onClose?: () => void\n}>()\n\nconst emit = defineEmits<{ (e: 'close'): void }>()\n\nconst status = ref<'connecting' | 'connected' | 'ended'>('connecting')\nconst deviceErrors = ref<string[]>([])\nconst endedReason = ref('')\nconst micEnabled = ref(true)\nconst cameraEnabled = ref(props.callType === 'video')\nconst hasCamera = ref(true)\nconst hasMic = ref(true)\n\n// 大窗 / 小窗 video 元素 ref\nconst mainVideo = ref<HTMLVideoElement | null>(null)\nconst pipVideo = ref<HTMLVideoElement | null>(null)\n// 视频源是否已交换:默认 main=远端、pip=本地;交换后 main=本地、pip=远端\n// 用户点击小窗 toggle\nconst swapped = ref(false)\n\nconst remotePlaying = ref(false)\nconst duration = ref(0)\nlet timer: ReturnType<typeof setInterval> | null = null\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nasync function syncDeviceCapability() {\n try {\n const devices = await navigator.mediaDevices.enumerateDevices()\n hasCamera.value = devices.some((d) => d.kind === 'videoinput')\n hasMic.value = devices.some((d) => d.kind === 'audioinput')\n if (!hasCamera.value) cameraEnabled.value = false\n if (!hasMic.value) micEnabled.value = false\n } catch (e) {\n console.warn('[atim] enumerateDevices 失败,按默认能力继续', e)\n }\n}\n\n// 按 swapped 状态把本地/远端 track attach 到对应 video 元素\n// remoteTrackSubscribed / devices / mount fallback / 交换 都会调此方法\nfunction attachByRole() {\n const session = getClient()?.call?.currentSession\n if (!session) return\n // 先把两个元素上的所有 track detach,避免 LiveKit track 同时挂多元素导致画面重复\n if (mainVideo.value) session.detachAllVideoFromEl(mainVideo.value)\n if (pipVideo.value) session.detachAllVideoFromEl(pipVideo.value)\n if (swapped.value) {\n // 交换后:本地大窗、远端小窗\n if (mainVideo.value && hasCamera.value && props.callType === 'video') {\n session.attachLocalVideo(mainVideo.value)\n }\n if (pipVideo.value) {\n session.attachRemoteVideo(pipVideo.value)\n }\n } else {\n // 默认:远端大窗、本地小窗\n if (mainVideo.value) {\n session.attachRemoteVideo(mainVideo.value)\n }\n if (pipVideo.value && hasCamera.value && props.callType === 'video') {\n session.attachLocalVideo(pipVideo.value)\n }\n }\n}\n\nfunction swapVideo() {\n swapped.value = !swapped.value\n attachByRole()\n}\n\nonMounted(async () => {\n await syncDeviceCapability()\n const client = getClient()\n if (!client) {\n console.error('[atim] client 未注入,无法发起通话')\n return\n }\n const session = client.call.currentSession\n if (!session) return\n\n session.onSession('connected', () => {\n status.value = 'connected'\n attachByRole()\n timer = setInterval(() => duration.value++, 1000)\n })\n session.onSession('remoteConnected', () => {\n remotePlaying.value = true\n attachByRole()\n })\n // 远端 track 异步流入触发——participantConnected 时 track 还没到\n session.onSession('remoteTrackSubscribed', (info: { kind: string }) => {\n if (info.kind === 'video') {\n remotePlaying.value = true\n attachByRole()\n }\n })\n session.onSession('remoteDisconnected', () => {\n remotePlaying.value = false\n })\n session.onSession('devices', (info: { mic: boolean; camera: boolean; errors: string[] }) => {\n if (info.errors && info.errors.length > 0) {\n deviceErrors.value = info.errors\n setTimeout(() => (deviceErrors.value = []), 3000)\n }\n // enableDevices 完成后重新 attach 本地 video\n if (info.camera) attachByRole()\n })\n session.onSession('ended', () => {\n status.value = 'ended'\n if (timer) clearInterval(timer)\n setTimeout(() => emit('close'), 1500)\n })\n\n // race 修复:session 可能已 connected(mount 前)\n if (session.isConnected) {\n status.value = 'connected'\n attachByRole()\n timer = setInterval(() => duration.value++, 1000)\n }\n\n // 远端 track 异步流入兜底\n if (props.callType === 'video') {\n let tries = 0\n const remoteRetry = setInterval(() => {\n tries++\n if (tries > 10 || remotePlaying.value) {\n clearInterval(remoteRetry)\n return\n }\n attachByRole()\n }, 300)\n }\n})\n\nonUnmounted(() => {\n if (timer) clearInterval(timer)\n})\n\nwatch(status, (s) => {\n if (s === 'ended') endedReason.value = '通话已结束'\n})\n\nasync function toggleMic() {\n if (!hasMic.value) return\n const session = getClient()?.call?.currentSession\n if (!session) return\n micEnabled.value = !micEnabled.value\n await session.setMicEnabled(micEnabled.value)\n}\n\nasync function toggleCamera() {\n if (!hasCamera.value) return\n const session = getClient()?.call?.currentSession\n if (!session) return\n cameraEnabled.value = !cameraEnabled.value\n await session.setCameraEnabled(cameraEnabled.value)\n // 摄像头开关后必须重新 attach(pub.track 引用变化)\n attachByRole()\n}\n\nasync function hangup() {\n endedReason.value = '已挂断'\n const client = getClient()\n await client?.call?.end('user_left')\n emit('close')\n}\n\nfunction formatDuration(s: number) {\n const m = Math.floor(s / 60)\n .toString()\n .padStart(2, '0')\n const sec = (s % 60).toString().padStart(2, '0')\n return `${m}:${sec}`\n}\n</script>\n\n<template>\n <div v-draggable=\"'.call-status-bar'\" class=\"call-window\">\n <!-- 视频区域(flex 占满,小窗叠加其上) -->\n <div class=\"video-area\">\n <!-- 大窗:默认远端,swapped 后本地 -->\n <video\n ref=\"mainVideo\"\n autoplay\n playsinline\n :class=\"[\n 'video-main',\n { 'network-error': status === 'connected' && !remotePlaying && !swapped },\n ]\"\n ></video>\n <!-- 小窗:默认本地,swapped 后远端。点击交换大小窗 -->\n <video\n v-if=\"callType === 'video'\"\n ref=\"pipVideo\"\n autoplay\n muted\n playsinline\n class=\"video-pip\"\n :title=\"swapped ? '点击切回对方主画面' : '点击切换自己为主画面'\"\n @click=\"swapVideo\"\n ></video>\n <!-- 连接中蒙层 -->\n <div v-if=\"status === 'connecting'\" class=\"connecting-overlay\">连接中...</div>\n <!-- 通话结束蒙层 -->\n <div v-else-if=\"status === 'ended'\" class=\"ended-overlay\">\n {{ endedReason || '通话已结束' }}\n </div>\n <!-- 设备失败提示(3 秒自动隐藏)-->\n <div v-if=\"deviceErrors.length\" class=\"device-warn\">\n {{ deviceErrors.join('; ') }}\n </div>\n <!-- 顶部状态栏(拖拽句柄) -->\n <div class=\"call-status-bar\">\n <span v-if=\"status === 'connecting'\">连接中</span>\n <span v-else-if=\"status === 'connected'\">{{ formatDuration(duration) }}</span>\n <span v-else>{{ endedReason || '通话已结束' }}</span>\n </div>\n </div>\n <!-- 底部控制栏:方形图标按钮,与会议窗口风格一致 -->\n <div class=\"controls-bar\">\n <button v-if=\"hasMic\" class=\"ctrl-btn\" :class=\"{ off: !micEnabled }\" @click=\"toggleMic\">\n <Icon :name=\"micEnabled ? 'mic-on' : 'mic-off'\" :size=\"22\" />\n </button>\n <button\n v-if=\"callType === 'video' && hasCamera\"\n class=\"ctrl-btn\"\n :class=\"{ off: !cameraEnabled }\"\n @click=\"toggleCamera\"\n >\n <Icon :name=\"cameraEnabled ? 'camera-on' : 'camera-off'\" :size=\"22\" />\n </button>\n <button class=\"ctrl-btn hangup\" @click=\"hangup\">\n <Icon name=\"hangup\" :size=\"22\" />\n </button>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* 通话窗口:与会议窗口风格一致——无圆角、flex 布局、深色面板背景 */\n.call-window {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: min(640px, 90vw);\n aspect-ratio: 16 / 9;\n background: #0f172a;\n border-radius: 0;\n overflow: hidden;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n z-index: 9999;\n display: flex;\n flex-direction: column;\n}\n/* 视频区域:flex 占满,相对定位供小窗叠加 */\n.video-area {\n position: relative;\n flex: 1;\n overflow: hidden;\n}\n/* 大窗:铺满视频区域 */\n.video-main {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: #000;\n}\n.video-main.network-error {\n filter: grayscale(1) blur(2px);\n opacity: 0.6;\n}\n/* 小窗:右下角贴边,16:9 比例,无边框无圆角(与会议小视频一致) */\n.video-pip {\n position: absolute;\n right: 0;\n bottom: 0;\n width: 96px;\n aspect-ratio: 16 / 9;\n object-fit: cover;\n background: #000;\n border-radius: 0;\n cursor: pointer;\n z-index: 2;\n}\n.video-pip:hover {\n outline: 1px solid rgba(255, 255, 255, 0.3);\n}\n/* 顶部状态栏(拖拽句柄) */\n.call-status-bar {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 13px;\n color: rgba(255, 255, 255, 0.9);\n background: linear-gradient(to bottom, rgba(0, 0, 0, 0.4), transparent);\n cursor: move;\n z-index: 3;\n pointer-events: auto;\n}\n/* 底部控制栏:常驻显示,方形图标按钮(与会议窗口一致) */\n.controls-bar {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 6px 0 8px;\n background: #0f172a;\n z-index: 4;\n}\n.ctrl-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n color: #fff;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n opacity: 0.85;\n cursor: pointer;\n transition: opacity 0.15s;\n}\n.ctrl-btn:hover {\n opacity: 1;\n}\n.ctrl-btn:active {\n transform: scale(0.92);\n}\n.ctrl-btn.off {\n color: #ef4444;\n opacity: 1;\n}\n.ctrl-btn.hangup {\n color: #ef4444;\n}\n/* 连接中 / 通话结束蒙层 */\n.connecting-overlay,\n.ended-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.7);\n color: #fff;\n font-size: 15px;\n z-index: 5;\n}\n/* 设备失败提示(顶部居中黄条) */\n.device-warn {\n position: absolute;\n top: 40px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(212, 160, 23, 0.92);\n color: #fff;\n font-size: 12px;\n padding: 4px 12px;\n border-radius: 4px;\n z-index: 6;\n pointer-events: none;\n max-width: 80%;\n text-align: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, watch } from 'vue'\nimport type { CallType } from '@quitting/contract'\nimport vDraggable from '../directives/draggable'\nimport Icon from './Icon.vue'\n\n// 主叫/被叫统一组件——样式完全一致,行为也一致\n// 大窗默认显示远端(对方),小窗默认显示本地(自己)\n// 点击小窗切换大小窗的视频源(参考 im-web AtVideo 双击切换)\n// 控制栏悬浮于视频底部 30px 处,水平居中 3 个圆形按钮,hover 视频才显示\n\nconst props = defineProps<{\n mode: 'caller' | 'callee'\n calleeUid?: string\n callType: CallType\n onClose?: () => void\n}>()\n\nconst emit = defineEmits<{ (e: 'close'): void }>()\n\nconst status = ref<'connecting' | 'connected' | 'ended'>('connecting')\nconst deviceErrors = ref<string[]>([])\nconst endedReason = ref('')\nconst micEnabled = ref(true)\nconst cameraEnabled = ref(props.callType === 'video')\nconst hasCamera = ref(true)\nconst hasMic = ref(true)\n\n// 大窗 / 小窗 video 元素 ref\nconst mainVideo = ref<HTMLVideoElement | null>(null)\nconst pipVideo = ref<HTMLVideoElement | null>(null)\n// 视频源是否已交换:默认 main=远端、pip=本地;交换后 main=本地、pip=远端\n// 用户点击小窗 toggle\nconst swapped = ref(false)\n\nconst remotePlaying = ref(false)\nconst duration = ref(0)\nlet timer: ReturnType<typeof setInterval> | null = null\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nasync function syncDeviceCapability() {\n try {\n const devices = await navigator.mediaDevices.enumerateDevices()\n hasCamera.value = devices.some((d) => d.kind === 'videoinput')\n hasMic.value = devices.some((d) => d.kind === 'audioinput')\n if (!hasCamera.value) cameraEnabled.value = false\n if (!hasMic.value) micEnabled.value = false\n } catch (e) {\n console.warn('[atim] enumerateDevices 失败,按默认能力继续', e)\n }\n}\n\n// 按 swapped 状态把本地/远端 track attach 到对应 video 元素\n// remoteTrackSubscribed / devices / mount fallback / 交换 都会调此方法\nfunction attachByRole() {\n const session = getClient()?.call?.currentSession\n if (!session) return\n // 先把两个元素上的所有 track detach,避免 LiveKit track 同时挂多元素导致画面重复\n if (mainVideo.value) session.detachAllVideoFromEl(mainVideo.value)\n if (pipVideo.value) session.detachAllVideoFromEl(pipVideo.value)\n if (swapped.value) {\n // 交换后:本地大窗、远端小窗\n if (mainVideo.value && hasCamera.value && props.callType === 'video') {\n session.attachLocalVideo(mainVideo.value)\n }\n if (pipVideo.value) {\n session.attachRemoteVideo(pipVideo.value)\n }\n } else {\n // 默认:远端大窗、本地小窗\n if (mainVideo.value) {\n session.attachRemoteVideo(mainVideo.value)\n }\n if (pipVideo.value && hasCamera.value && props.callType === 'video') {\n session.attachLocalVideo(pipVideo.value)\n }\n }\n}\n\nfunction swapVideo() {\n swapped.value = !swapped.value\n attachByRole()\n}\n\nonMounted(async () => {\n await syncDeviceCapability()\n const client = getClient()\n if (!client) {\n console.error('[atim] client 未注入,无法发起通话')\n return\n }\n const session = client.call.currentSession\n if (!session) return\n\n session.onSession('connected', () => {\n status.value = 'connected'\n attachByRole()\n timer = setInterval(() => duration.value++, 1000)\n })\n session.onSession('remoteConnected', () => {\n remotePlaying.value = true\n attachByRole()\n })\n // 远端 track 异步流入触发——participantConnected 时 track 还没到\n session.onSession('remoteTrackSubscribed', (info: { kind: string }) => {\n if (info.kind === 'video') {\n remotePlaying.value = true\n attachByRole()\n }\n })\n session.onSession('remoteDisconnected', () => {\n remotePlaying.value = false\n })\n session.onSession('devices', (info: { mic: boolean; camera: boolean; errors: string[] }) => {\n if (info.errors && info.errors.length > 0) {\n deviceErrors.value = info.errors\n setTimeout(() => (deviceErrors.value = []), 3000)\n }\n // enableDevices 完成后重新 attach 本地 video\n if (info.camera) attachByRole()\n })\n session.onSession('ended', () => {\n status.value = 'ended'\n if (timer) clearInterval(timer)\n setTimeout(() => emit('close'), 1500)\n })\n\n // race 修复:session 可能已 connected(mount 前)\n if (session.isConnected) {\n status.value = 'connected'\n attachByRole()\n timer = setInterval(() => duration.value++, 1000)\n }\n\n // 远端 track 异步流入兜底\n if (props.callType === 'video') {\n let tries = 0\n const remoteRetry = setInterval(() => {\n tries++\n if (tries > 10 || remotePlaying.value) {\n clearInterval(remoteRetry)\n return\n }\n attachByRole()\n }, 300)\n }\n})\n\nonUnmounted(() => {\n if (timer) clearInterval(timer)\n})\n\nwatch(status, (s) => {\n if (s === 'ended') endedReason.value = '通话已结束'\n})\n\nasync function toggleMic() {\n if (!hasMic.value) return\n const session = getClient()?.call?.currentSession\n if (!session) return\n micEnabled.value = !micEnabled.value\n await session.setMicEnabled(micEnabled.value)\n}\n\nasync function toggleCamera() {\n if (!hasCamera.value) return\n const session = getClient()?.call?.currentSession\n if (!session) return\n cameraEnabled.value = !cameraEnabled.value\n await session.setCameraEnabled(cameraEnabled.value)\n // 摄像头开关后必须重新 attach(pub.track 引用变化)\n attachByRole()\n}\n\nasync function hangup() {\n endedReason.value = '已挂断'\n const client = getClient()\n await client?.call?.end('user_left')\n emit('close')\n}\n\nfunction formatDuration(s: number) {\n const m = Math.floor(s / 60)\n .toString()\n .padStart(2, '0')\n const sec = (s % 60).toString().padStart(2, '0')\n return `${m}:${sec}`\n}\n</script>\n\n<template>\n <div v-draggable=\"'.call-status-bar'\" class=\"call-window\">\n <!-- 视频区域(flex 占满,小窗叠加其上) -->\n <div class=\"video-area\">\n <!-- 大窗:默认远端,swapped 后本地 -->\n <video\n ref=\"mainVideo\"\n autoplay\n playsinline\n :class=\"[\n 'video-main',\n { 'network-error': status === 'connected' && !remotePlaying && !swapped },\n ]\"\n ></video>\n <!-- 小窗:默认本地,swapped 后远端。点击交换大小窗 -->\n <video\n v-if=\"callType === 'video'\"\n ref=\"pipVideo\"\n autoplay\n muted\n playsinline\n class=\"video-pip\"\n :title=\"swapped ? '点击切回对方主画面' : '点击切换自己为主画面'\"\n @click=\"swapVideo\"\n ></video>\n <!-- 连接中蒙层 -->\n <div v-if=\"status === 'connecting'\" class=\"connecting-overlay\">连接中...</div>\n <!-- 通话结束蒙层 -->\n <div v-else-if=\"status === 'ended'\" class=\"ended-overlay\">\n {{ endedReason || '通话已结束' }}\n </div>\n <!-- 设备失败提示(3 秒自动隐藏)-->\n <div v-if=\"deviceErrors.length\" class=\"device-warn\">\n {{ deviceErrors.join('; ') }}\n </div>\n <!-- 顶部状态栏(拖拽句柄) -->\n <div class=\"call-status-bar\">\n <span v-if=\"status === 'connecting'\">连接中</span>\n <span v-else-if=\"status === 'connected'\">{{ formatDuration(duration) }}</span>\n <span v-else>{{ endedReason || '通话已结束' }}</span>\n </div>\n </div>\n <!-- 底部控制栏:方形图标按钮,与会议窗口风格一致 -->\n <div class=\"controls-bar\">\n <button v-if=\"hasMic\" class=\"ctrl-btn\" :class=\"{ off: !micEnabled }\" @click=\"toggleMic\">\n <Icon :name=\"micEnabled ? 'mic-on' : 'mic-off'\" :size=\"22\" />\n </button>\n <button\n v-if=\"callType === 'video' && hasCamera\"\n class=\"ctrl-btn\"\n :class=\"{ off: !cameraEnabled }\"\n @click=\"toggleCamera\"\n >\n <Icon :name=\"cameraEnabled ? 'camera-on' : 'camera-off'\" :size=\"22\" />\n </button>\n <button class=\"ctrl-btn hangup\" @click=\"hangup\">\n <Icon name=\"hangup\" :size=\"22\" />\n </button>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* 通话窗口:与会议窗口风格一致——无圆角、flex 布局、深色面板背景 */\n.call-window {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: min(640px, 90vw);\n aspect-ratio: 16 / 9;\n background: #0f172a;\n border-radius: 0;\n overflow: hidden;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n z-index: 9999;\n display: flex;\n flex-direction: column;\n}\n/* 视频区域:flex 占满,相对定位供小窗叠加 */\n.video-area {\n position: relative;\n flex: 1;\n overflow: hidden;\n}\n/* 大窗:铺满视频区域 */\n.video-main {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: #000;\n}\n.video-main.network-error {\n filter: grayscale(1) blur(2px);\n opacity: 0.6;\n}\n/* 小窗:右下角贴边,16:9 比例,无边框无圆角(与会议小视频一致) */\n.video-pip {\n position: absolute;\n right: 0;\n bottom: 0;\n width: 96px;\n aspect-ratio: 16 / 9;\n object-fit: cover;\n background: #000;\n border-radius: 0;\n cursor: pointer;\n z-index: 2;\n}\n.video-pip:hover {\n outline: 1px solid rgba(255, 255, 255, 0.3);\n}\n/* 顶部状态栏(拖拽句柄) */\n.call-status-bar {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 13px;\n color: rgba(255, 255, 255, 0.9);\n background: linear-gradient(to bottom, rgba(0, 0, 0, 0.4), transparent);\n cursor: move;\n z-index: 3;\n pointer-events: auto;\n}\n/* 底部控制栏:常驻显示,方形图标按钮(与会议窗口一致) */\n.controls-bar {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 6px 0 8px;\n background: #0f172a;\n z-index: 4;\n}\n.ctrl-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n color: #fff;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n opacity: 0.85;\n cursor: pointer;\n transition: opacity 0.15s;\n}\n.ctrl-btn:hover {\n opacity: 1;\n}\n.ctrl-btn:active {\n transform: scale(0.92);\n}\n.ctrl-btn.off {\n color: #ef4444;\n opacity: 1;\n}\n.ctrl-btn.hangup {\n color: #ef4444;\n}\n/* 连接中 / 通话结束蒙层 */\n.connecting-overlay,\n.ended-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.7);\n color: #fff;\n font-size: 15px;\n z-index: 5;\n}\n/* 设备失败提示(顶部居中黄条) */\n.device-warn {\n position: absolute;\n top: 40px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(212, 160, 23, 0.92);\n color: #fff;\n font-size: 12px;\n padding: 4px 12px;\n border-radius: 4px;\n z-index: 6;\n pointer-events: none;\n max-width: 80%;\n text-align: center;\n}\n</style>\n","// 共享 SVG 渲染函数——所有图标从 design/video-conference-layout.html 原样搬\n// 函数式组件,避免在每个用到的地方重复粘贴 SVG 字符串\n\n/** 麦克风开启:白色描边 */\nexport const MicOnIcon = `<svg class=\"mic-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M12 2a3 3 0 0 1 3 3v6a3 3 0 0 1-6 0V5a3 3 0 0 1 3-3z\"></path>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"></path>\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"></line>\n <line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\"></line>\n</svg>`\n\n/** 麦克风关闭:红色描边 + 斜杠 */\nexport const MicOffIcon = `<svg class=\"mic-icon muted\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"></line>\n <path d=\"M9 9v5a3 3 0 0 0 5.1 2.1\"></path>\n <path d=\"M15 9.34V5a3 3 0 0 0-5.94-1.1\"></path>\n <path d=\"M17 16.95A7 7 0 0 1 5 12v-2\"></path>\n <path d=\"M19 5v4\"></path>\n <path d=\"M12 19v4\"></path>\n <path d=\"M8 23h8\"></path>\n</svg>`\n\n/** 正在发言:声波柱动效(CSS keyframes 控制) */\nexport const SpeakingIndicator = `<span class=\"speaking-indicator\"><span class=\"wave-bar\"></span><span class=\"wave-bar\"></span><span class=\"wave-bar\"></span><span>正在发言</span></span>`\n\n/** 主持人 badge:用 \"👑\" emoji 太重,这里用简洁的\"主\"字徽标 */\nexport const HostBadge = `<span class=\"host-badge\">主持人</span>`\n\n/** 最小化按钮 SVG */\nexport const MinimizeIcon = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line></svg>`\n\n/** 还原(最大化)按钮 SVG:四角向外箭头 */\nexport const MaximizeIcon = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"15 3 21 3 21 9\"></polyline><polyline points=\"9 21 3 21 3 15\"></polyline><line x1=\"21\" y1=\"3\" x2=\"14\" y2=\"10\"></line><line x1=\"3\" y1=\"21\" x2=\"10\" y2=\"14\"></line></svg>`\n\n/** 关闭按钮 SVG */\nexport const CloseIcon = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line></svg>`\n\n/** 更多操作(⋯)按钮 SVG */\nexport const MoreIcon = `<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><circle cx=\"5\" cy=\"12\" r=\"2\"></circle><circle cx=\"12\" cy=\"12\" r=\"2\"></circle><circle cx=\"19\" cy=\"12\" r=\"2\"></circle></svg>`\n\n/** 渲染麦克风图标——根据 muted 选 MicOnIcon/MicOffIcon */\nexport function renderMicIcon(muted: boolean): string {\n return muted ? MicOffIcon : MicOnIcon\n}\n","<script setup lang=\"ts\">\nimport { MinimizeIcon, CloseIcon } from './icons'\n\ndefineProps<{\n title: string\n duration?: string\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n (e: 'minimize'): void\n}>()\n</script>\n\n<template>\n <header class=\"meeting-header\">\n <div class=\"title-area\">\n <h1 class=\"title\">{{ title }}</h1>\n <span v-if=\"duration\" class=\"duration\">{{ duration }}</span>\n </div>\n <div class=\"window-controls\">\n <button\n class=\"win-btn minimize-btn\"\n type=\"button\"\n aria-label=\"最小化\"\n title=\"最小化\"\n @click=\"emit('minimize')\"\n v-html=\"MinimizeIcon\"\n ></button>\n <button\n class=\"win-btn close-btn\"\n type=\"button\"\n aria-label=\"关闭会议\"\n title=\"关闭\"\n @click=\"emit('close')\"\n v-html=\"CloseIcon\"\n ></button>\n </div>\n </header>\n</template>\n\n<style scoped>\n.meeting-header {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 0;\n border-bottom: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n cursor: move;\n}\n.title-area {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.title {\n font-size: 15px;\n font-weight: 600;\n color: var(--atim-meeting-text-primary, #fff);\n margin: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.duration {\n font-size: 12px;\n color: var(--atim-meeting-text-tertiary, rgba(255, 255, 255, 0.55));\n font-variant-numeric: tabular-nums;\n}\n.window-controls {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.win-btn {\n width: 24px;\n height: 24px;\n border: none;\n background: transparent;\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n background 0.2s,\n color 0.2s;\n}\n.win-btn:hover {\n background: rgba(255, 255, 255, 0.1);\n color: var(--atim-meeting-text-primary, #fff);\n}\n.win-btn :deep(svg) {\n width: 18px;\n height: 18px;\n}\n.win-btn.close-btn:hover {\n background: #e53935;\n color: #fff;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { MinimizeIcon, CloseIcon } from './icons'\n\ndefineProps<{\n title: string\n duration?: string\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n (e: 'minimize'): void\n}>()\n</script>\n\n<template>\n <header class=\"meeting-header\">\n <div class=\"title-area\">\n <h1 class=\"title\">{{ title }}</h1>\n <span v-if=\"duration\" class=\"duration\">{{ duration }}</span>\n </div>\n <div class=\"window-controls\">\n <button\n class=\"win-btn minimize-btn\"\n type=\"button\"\n aria-label=\"最小化\"\n title=\"最小化\"\n @click=\"emit('minimize')\"\n v-html=\"MinimizeIcon\"\n ></button>\n <button\n class=\"win-btn close-btn\"\n type=\"button\"\n aria-label=\"关闭会议\"\n title=\"关闭\"\n @click=\"emit('close')\"\n v-html=\"CloseIcon\"\n ></button>\n </div>\n </header>\n</template>\n\n<style scoped>\n.meeting-header {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 0;\n border-bottom: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n cursor: move;\n}\n.title-area {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.title {\n font-size: 15px;\n font-weight: 600;\n color: var(--atim-meeting-text-primary, #fff);\n margin: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.duration {\n font-size: 12px;\n color: var(--atim-meeting-text-tertiary, rgba(255, 255, 255, 0.55));\n font-variant-numeric: tabular-nums;\n}\n.window-controls {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.win-btn {\n width: 24px;\n height: 24px;\n border: none;\n background: transparent;\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n background 0.2s,\n color 0.2s;\n}\n.win-btn:hover {\n background: rgba(255, 255, 255, 0.1);\n color: var(--atim-meeting-text-primary, #fff);\n}\n.win-btn :deep(svg) {\n width: 18px;\n height: 18px;\n}\n.win-btn.close-btn:hover {\n background: #e53935;\n color: #fff;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'\nimport Icon from '../Icon.vue'\nimport { SpeakingIndicator } from './icons'\n\nconst props = defineProps<{\n identity: string\n name: string\n muted: boolean\n speaking: boolean\n isHost: boolean\n isSelf?: boolean\n onVideoElReady?: (identity: string, el: HTMLVideoElement | null) => void\n}>()\n\nconst videoEl = ref<HTMLVideoElement | null>(null)\n\n// 初始 mount + identity 切换(主持人转让后)都要通知父组件重新 attach\nfunction notify() {\n props.onVideoElReady?.(props.identity, videoEl.value)\n}\n\nonMounted(notify)\nonBeforeUnmount(() => {\n props.onVideoElReady?.(props.identity, null)\n})\nwatch(\n () => props.identity,\n () => {\n nextTick(notify)\n },\n)\n</script>\n\n<template>\n <section class=\"main-video\" aria-label=\"主视频画面\">\n <video ref=\"videoEl\" autoplay muted playsinline class=\"tile-video\"></video>\n <div v-if=\"!name\" class=\"video-placeholder\">主视频画面</div>\n <div class=\"video-overlay\">\n <div class=\"video-meta-grid\">\n <div class=\"corner-tl\">\n <span class=\"participant-name\">{{ name || '等待主持人加入' }}</span>\n </div>\n <div v-if=\"speaking\" class=\"corner-tr\" v-html=\"SpeakingIndicator\"></div>\n <div class=\"corner-br\">\n <Icon\n :name=\"muted ? 'mic-off' : 'mic-on'\"\n :size=\"18\"\n :color=\"muted ? '#ef4444' : '#fff'\"\n :title=\"muted ? '麦克风已关' : '麦克风已开'\"\n />\n </div>\n </div>\n </div>\n </section>\n</template>\n\n<style scoped>\n.main-video {\n position: relative;\n width: 100%;\n height: 100%;\n background: var(--atim-meeting-bg-video, #000);\n border-radius: 0;\n overflow: hidden;\n}\n.tile-video {\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: var(--atim-meeting-bg-video, #000);\n}\n.video-placeholder {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n font-size: 14px;\n}\n.video-overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n padding: var(--atim-meeting-overlay-gap, 5px);\n}\n.video-meta-grid {\n position: absolute;\n inset: var(--atim-meeting-overlay-gap, 5px);\n display: grid;\n grid-template-columns: 1fr auto;\n grid-template-rows: auto 1fr auto;\n pointer-events: none;\n}\n.corner-tl {\n grid-area: 3 / 1 / 4 / 2;\n align-self: end;\n justify-self: start;\n}\n.corner-tr {\n grid-area: 1 / 2 / 2 / 3;\n align-self: start;\n justify-self: end;\n}\n.corner-br {\n grid-area: 3 / 2 / 4 / 3;\n align-self: end;\n justify-self: end;\n}\n.participant-name {\n font-size: 14px;\n font-weight: 500;\n color: var(--atim-meeting-text-primary, #fff);\n text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 60%;\n}\n.corner-br {\n filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));\n}\n.main-video :deep(.speaking-indicator) {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n font-size: 11px;\n font-weight: 500;\n color: var(--atim-meeting-speaking, #22c55e);\n background: rgba(34, 197, 94, 0.15);\n border: 1px solid rgba(34, 197, 94, 0.5);\n border-radius: 0;\n padding: 2px 6px;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(2px);\n}\n.main-video :deep(.wave-bar) {\n width: 2px;\n background: var(--atim-meeting-speaking, #22c55e);\n border-radius: 0;\n animation: wave 1s ease-in-out infinite;\n}\n.main-video :deep(.wave-bar:nth-child(1)) {\n height: 6px;\n animation-delay: 0s;\n}\n.main-video :deep(.wave-bar:nth-child(2)) {\n height: 10px;\n animation-delay: 0.15s;\n}\n.main-video :deep(.wave-bar:nth-child(3)) {\n height: 6px;\n animation-delay: 0.3s;\n}\n@keyframes wave {\n 0%,\n 100% {\n transform: scaleY(1);\n opacity: 0.7;\n }\n 50% {\n transform: scaleY(1.6);\n opacity: 1;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'\nimport Icon from '../Icon.vue'\nimport { SpeakingIndicator } from './icons'\n\nconst props = defineProps<{\n identity: string\n name: string\n muted: boolean\n speaking: boolean\n isHost: boolean\n isSelf?: boolean\n onVideoElReady?: (identity: string, el: HTMLVideoElement | null) => void\n}>()\n\nconst videoEl = ref<HTMLVideoElement | null>(null)\n\n// 初始 mount + identity 切换(主持人转让后)都要通知父组件重新 attach\nfunction notify() {\n props.onVideoElReady?.(props.identity, videoEl.value)\n}\n\nonMounted(notify)\nonBeforeUnmount(() => {\n props.onVideoElReady?.(props.identity, null)\n})\nwatch(\n () => props.identity,\n () => {\n nextTick(notify)\n },\n)\n</script>\n\n<template>\n <section class=\"main-video\" aria-label=\"主视频画面\">\n <video ref=\"videoEl\" autoplay muted playsinline class=\"tile-video\"></video>\n <div v-if=\"!name\" class=\"video-placeholder\">主视频画面</div>\n <div class=\"video-overlay\">\n <div class=\"video-meta-grid\">\n <div class=\"corner-tl\">\n <span class=\"participant-name\">{{ name || '等待主持人加入' }}</span>\n </div>\n <div v-if=\"speaking\" class=\"corner-tr\" v-html=\"SpeakingIndicator\"></div>\n <div class=\"corner-br\">\n <Icon\n :name=\"muted ? 'mic-off' : 'mic-on'\"\n :size=\"18\"\n :color=\"muted ? '#ef4444' : '#fff'\"\n :title=\"muted ? '麦克风已关' : '麦克风已开'\"\n />\n </div>\n </div>\n </div>\n </section>\n</template>\n\n<style scoped>\n.main-video {\n position: relative;\n width: 100%;\n height: 100%;\n background: var(--atim-meeting-bg-video, #000);\n border-radius: 0;\n overflow: hidden;\n}\n.tile-video {\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: var(--atim-meeting-bg-video, #000);\n}\n.video-placeholder {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n font-size: 14px;\n}\n.video-overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n padding: var(--atim-meeting-overlay-gap, 5px);\n}\n.video-meta-grid {\n position: absolute;\n inset: var(--atim-meeting-overlay-gap, 5px);\n display: grid;\n grid-template-columns: 1fr auto;\n grid-template-rows: auto 1fr auto;\n pointer-events: none;\n}\n.corner-tl {\n grid-area: 3 / 1 / 4 / 2;\n align-self: end;\n justify-self: start;\n}\n.corner-tr {\n grid-area: 1 / 2 / 2 / 3;\n align-self: start;\n justify-self: end;\n}\n.corner-br {\n grid-area: 3 / 2 / 4 / 3;\n align-self: end;\n justify-self: end;\n}\n.participant-name {\n font-size: 14px;\n font-weight: 500;\n color: var(--atim-meeting-text-primary, #fff);\n text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 60%;\n}\n.corner-br {\n filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));\n}\n.main-video :deep(.speaking-indicator) {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n font-size: 11px;\n font-weight: 500;\n color: var(--atim-meeting-speaking, #22c55e);\n background: rgba(34, 197, 94, 0.15);\n border: 1px solid rgba(34, 197, 94, 0.5);\n border-radius: 0;\n padding: 2px 6px;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(2px);\n}\n.main-video :deep(.wave-bar) {\n width: 2px;\n background: var(--atim-meeting-speaking, #22c55e);\n border-radius: 0;\n animation: wave 1s ease-in-out infinite;\n}\n.main-video :deep(.wave-bar:nth-child(1)) {\n height: 6px;\n animation-delay: 0s;\n}\n.main-video :deep(.wave-bar:nth-child(2)) {\n height: 10px;\n animation-delay: 0.15s;\n}\n.main-video :deep(.wave-bar:nth-child(3)) {\n height: 6px;\n animation-delay: 0.3s;\n}\n@keyframes wave {\n 0%,\n 100% {\n transform: scaleY(1);\n opacity: 0.7;\n }\n 50% {\n transform: scaleY(1.6);\n opacity: 1;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount } from 'vue'\nimport Icon from '../Icon.vue'\nimport { SpeakingIndicator, HostBadge } from './icons'\n\nconst props = defineProps<{\n identity: string\n name: string\n muted: boolean\n speaking: boolean\n isHost: boolean\n isSelf?: boolean\n // 挂载/卸载时回调,把 video 元素交回父组件——避开 v-for + 函数 ref 的不稳定问题\n onVideoElReady?: (identity: string, el: HTMLVideoElement | null) => void\n}>()\n\nconst emit = defineEmits<{\n (e: 'click', identity: string): void\n}>()\n\nconst videoEl = ref<HTMLVideoElement | null>(null)\n\nonMounted(() => {\n if (videoEl.value) {\n props.onVideoElReady?.(props.identity, videoEl.value)\n }\n})\n\nonBeforeUnmount(() => {\n props.onVideoElReady?.(props.identity, null)\n})\n</script>\n\n<template>\n <div\n class=\"small-video\"\n role=\"img\"\n :aria-label=\"`${name} 的视频画面`\"\n @click=\"emit('click', identity)\"\n >\n <video ref=\"videoEl\" autoplay muted playsinline class=\"tile-video\"></video>\n <div class=\"video-placeholder\">视频画面</div>\n <div class=\"video-overlay\">\n <div class=\"video-meta-grid\">\n <div class=\"corner-tl\">\n <span class=\"participant-name\">{{ name }}</span>\n <span v-if=\"isHost\" class=\"host-badge-inline\" v-html=\"HostBadge\"></span>\n </div>\n <div v-if=\"speaking\" class=\"corner-tr\" v-html=\"SpeakingIndicator\"></div>\n <div class=\"corner-br\">\n <Icon\n :name=\"muted ? 'mic-off' : 'mic-on'\"\n :size=\"18\"\n :color=\"muted ? '#ef4444' : '#fff'\"\n :title=\"muted ? '麦克风已关' : '麦克风已开'\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.small-video {\n position: relative;\n background: var(--atim-meeting-bg-video, #000);\n border-radius: 0;\n overflow: hidden;\n flex: 0 0 auto;\n cursor: pointer;\n}\n.tile-video {\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: var(--atim-meeting-bg-video, #000);\n}\n.video-placeholder {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n color: rgba(255, 255, 255, 0.4);\n font-size: 12px;\n pointer-events: none;\n}\n.video-overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n padding: var(--atim-meeting-overlay-gap, 5px);\n}\n.video-meta-grid {\n position: absolute;\n inset: var(--atim-meeting-overlay-gap, 5px);\n display: grid;\n grid-template-columns: 1fr auto;\n grid-template-rows: auto 1fr auto;\n pointer-events: none;\n}\n.corner-tl {\n grid-area: 3 / 1 / 4 / 2;\n align-self: end;\n justify-self: start;\n display: flex;\n align-items: center;\n gap: 4px;\n max-width: 100%;\n}\n.corner-tr {\n grid-area: 1 / 2 / 2 / 3;\n align-self: start;\n justify-self: end;\n}\n.corner-br {\n grid-area: 3 / 2 / 4 / 3;\n align-self: end;\n justify-self: end;\n}\n.participant-name {\n font-size: 12px;\n font-weight: 500;\n color: var(--atim-meeting-text-primary, #fff);\n text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n.corner-br {\n filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));\n}\n.small-video :deep(.speaking-indicator) {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n font-size: 10px;\n font-weight: 500;\n color: var(--atim-meeting-speaking, #22c55e);\n background: rgba(34, 197, 94, 0.15);\n border: 1px solid rgba(34, 197, 94, 0.5);\n border-radius: 0;\n padding: 1px 4px;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);\n}\n.small-video :deep(.wave-bar) {\n width: 2px;\n background: var(--atim-meeting-speaking, #22c55e);\n border-radius: 0;\n animation: wave-sm 1s ease-in-out infinite;\n}\n.small-video :deep(.wave-bar:nth-child(1)) {\n height: 4px;\n animation-delay: 0s;\n}\n.small-video :deep(.wave-bar:nth-child(2)) {\n height: 7px;\n animation-delay: 0.15s;\n}\n.small-video :deep(.wave-bar:nth-child(3)) {\n height: 4px;\n animation-delay: 0.3s;\n}\n.host-badge-inline :deep(.host-badge) {\n font-size: 9px;\n color: #fbbf24;\n background: rgba(251, 191, 36, 0.15);\n border: 1px solid rgba(251, 191, 36, 0.5);\n padding: 1px 4px;\n white-space: nowrap;\n}\n@keyframes wave-sm {\n 0%,\n 100% {\n transform: scaleY(1);\n opacity: 0.7;\n }\n 50% {\n transform: scaleY(1.6);\n opacity: 1;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount } from 'vue'\nimport Icon from '../Icon.vue'\nimport { SpeakingIndicator, HostBadge } from './icons'\n\nconst props = defineProps<{\n identity: string\n name: string\n muted: boolean\n speaking: boolean\n isHost: boolean\n isSelf?: boolean\n // 挂载/卸载时回调,把 video 元素交回父组件——避开 v-for + 函数 ref 的不稳定问题\n onVideoElReady?: (identity: string, el: HTMLVideoElement | null) => void\n}>()\n\nconst emit = defineEmits<{\n (e: 'click', identity: string): void\n}>()\n\nconst videoEl = ref<HTMLVideoElement | null>(null)\n\nonMounted(() => {\n if (videoEl.value) {\n props.onVideoElReady?.(props.identity, videoEl.value)\n }\n})\n\nonBeforeUnmount(() => {\n props.onVideoElReady?.(props.identity, null)\n})\n</script>\n\n<template>\n <div\n class=\"small-video\"\n role=\"img\"\n :aria-label=\"`${name} 的视频画面`\"\n @click=\"emit('click', identity)\"\n >\n <video ref=\"videoEl\" autoplay muted playsinline class=\"tile-video\"></video>\n <div class=\"video-placeholder\">视频画面</div>\n <div class=\"video-overlay\">\n <div class=\"video-meta-grid\">\n <div class=\"corner-tl\">\n <span class=\"participant-name\">{{ name }}</span>\n <span v-if=\"isHost\" class=\"host-badge-inline\" v-html=\"HostBadge\"></span>\n </div>\n <div v-if=\"speaking\" class=\"corner-tr\" v-html=\"SpeakingIndicator\"></div>\n <div class=\"corner-br\">\n <Icon\n :name=\"muted ? 'mic-off' : 'mic-on'\"\n :size=\"18\"\n :color=\"muted ? '#ef4444' : '#fff'\"\n :title=\"muted ? '麦克风已关' : '麦克风已开'\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.small-video {\n position: relative;\n background: var(--atim-meeting-bg-video, #000);\n border-radius: 0;\n overflow: hidden;\n flex: 0 0 auto;\n cursor: pointer;\n}\n.tile-video {\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: var(--atim-meeting-bg-video, #000);\n}\n.video-placeholder {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n color: rgba(255, 255, 255, 0.4);\n font-size: 12px;\n pointer-events: none;\n}\n.video-overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n padding: var(--atim-meeting-overlay-gap, 5px);\n}\n.video-meta-grid {\n position: absolute;\n inset: var(--atim-meeting-overlay-gap, 5px);\n display: grid;\n grid-template-columns: 1fr auto;\n grid-template-rows: auto 1fr auto;\n pointer-events: none;\n}\n.corner-tl {\n grid-area: 3 / 1 / 4 / 2;\n align-self: end;\n justify-self: start;\n display: flex;\n align-items: center;\n gap: 4px;\n max-width: 100%;\n}\n.corner-tr {\n grid-area: 1 / 2 / 2 / 3;\n align-self: start;\n justify-self: end;\n}\n.corner-br {\n grid-area: 3 / 2 / 4 / 3;\n align-self: end;\n justify-self: end;\n}\n.participant-name {\n font-size: 12px;\n font-weight: 500;\n color: var(--atim-meeting-text-primary, #fff);\n text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n.corner-br {\n filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));\n}\n.small-video :deep(.speaking-indicator) {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n font-size: 10px;\n font-weight: 500;\n color: var(--atim-meeting-speaking, #22c55e);\n background: rgba(34, 197, 94, 0.15);\n border: 1px solid rgba(34, 197, 94, 0.5);\n border-radius: 0;\n padding: 1px 4px;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);\n}\n.small-video :deep(.wave-bar) {\n width: 2px;\n background: var(--atim-meeting-speaking, #22c55e);\n border-radius: 0;\n animation: wave-sm 1s ease-in-out infinite;\n}\n.small-video :deep(.wave-bar:nth-child(1)) {\n height: 4px;\n animation-delay: 0s;\n}\n.small-video :deep(.wave-bar:nth-child(2)) {\n height: 7px;\n animation-delay: 0.15s;\n}\n.small-video :deep(.wave-bar:nth-child(3)) {\n height: 4px;\n animation-delay: 0.3s;\n}\n.host-badge-inline :deep(.host-badge) {\n font-size: 9px;\n color: #fbbf24;\n background: rgba(251, 191, 36, 0.15);\n border: 1px solid rgba(251, 191, 36, 0.5);\n padding: 1px 4px;\n white-space: nowrap;\n}\n@keyframes wave-sm {\n 0%,\n 100% {\n transform: scaleY(1);\n opacity: 0.7;\n }\n 50% {\n transform: scaleY(1.6);\n opacity: 1;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport SmallVideoTile from './SmallVideoTile.vue'\nimport type { MeetingMember } from '@quitting/contract'\n\nconst props = defineProps<{\n members: MeetingMember[]\n // 每个小视频 tile 挂载/卸载时回调(去重,identity 不变不会重复触发)\n onTileMount?: (identity: string, el: HTMLVideoElement | null) => void\n}>()\n\nconst emit = defineEmits<{\n (e: 'tileClick', identity: string): void\n}>()\n\n// 暴露 listEl 给父组件做布局计算(设置 16:9 tile 尺寸)\nconst listEl = ref<HTMLElement | null>(null)\ndefineExpose({ listEl })\n\n// 闭包稳定:不会因 parent re-render 反复触发\nfunction handleTileVideoReady(identity: string, el: HTMLVideoElement | null) {\n props.onTileMount?.(identity, el)\n}\n</script>\n\n<template>\n <div ref=\"listEl\" class=\"sidebar-list\">\n <SmallVideoTile\n v-for=\"m in members\"\n :key=\"m.identity\"\n :identity=\"m.identity\"\n :name=\"m.name\"\n :muted=\"m.muted\"\n :speaking=\"m.speaking\"\n :is-host=\"m.isHost\"\n :on-video-el-ready=\"handleTileVideoReady\"\n @click=\"(id: string) => emit('tileClick', id)\"\n />\n </div>\n</template>\n\n<style scoped>\n.sidebar-list {\n flex: 1 1 0;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n display: flex;\n flex-direction: column;\n gap: 8px;\n /* 隐藏滚动条但保留滚动功能 */\n scrollbar-width: none;\n}\n.sidebar-list::-webkit-scrollbar {\n display: none;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport SmallVideoTile from './SmallVideoTile.vue'\nimport type { MeetingMember } from '@quitting/contract'\n\nconst props = defineProps<{\n members: MeetingMember[]\n // 每个小视频 tile 挂载/卸载时回调(去重,identity 不变不会重复触发)\n onTileMount?: (identity: string, el: HTMLVideoElement | null) => void\n}>()\n\nconst emit = defineEmits<{\n (e: 'tileClick', identity: string): void\n}>()\n\n// 暴露 listEl 给父组件做布局计算(设置 16:9 tile 尺寸)\nconst listEl = ref<HTMLElement | null>(null)\ndefineExpose({ listEl })\n\n// 闭包稳定:不会因 parent re-render 反复触发\nfunction handleTileVideoReady(identity: string, el: HTMLVideoElement | null) {\n props.onTileMount?.(identity, el)\n}\n</script>\n\n<template>\n <div ref=\"listEl\" class=\"sidebar-list\">\n <SmallVideoTile\n v-for=\"m in members\"\n :key=\"m.identity\"\n :identity=\"m.identity\"\n :name=\"m.name\"\n :muted=\"m.muted\"\n :speaking=\"m.speaking\"\n :is-host=\"m.isHost\"\n :on-video-el-ready=\"handleTileVideoReady\"\n @click=\"(id: string) => emit('tileClick', id)\"\n />\n </div>\n</template>\n\n<style scoped>\n.sidebar-list {\n flex: 1 1 0;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n display: flex;\n flex-direction: column;\n gap: 8px;\n /* 隐藏滚动条但保留滚动功能 */\n scrollbar-width: none;\n}\n.sidebar-list::-webkit-scrollbar {\n display: none;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount } from 'vue'\nimport Icon from '../Icon.vue'\nimport type { MeetingMember } from '@quitting/contract'\n\nconst props = defineProps<{\n member: MeetingMember\n // 当前用户是否主持人(决定能否触发转主持人/踢人/再次邀请)\n canPromote: boolean\n canKick: boolean\n canReinvite?: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'transferHost', uid: string): void\n (e: 'kick', uid: string): void\n (e: 'reinvite', uid: string): void\n}>()\n\n// 三点菜单展开状态\nconst menuOpen = ref(false)\n// 鼠标离开关闭定时器\nlet closeTimer: ReturnType<typeof setTimeout> | null = null\nconst CLOSE_DELAY = 500\n\n// 入会(online + joined)才允许设为主持人;离线/等待加入不显示此菜单项\nconst canShowPromote = () =>\n props.canPromote && props.member.online && props.member.joined && !props.member.isHost\n// 入会后(无论在线还是暂时离线)都允许踢出;已离会不需要再踢\nconst canShowKick = () => props.canKick && !props.member.isHost && props.member.status !== 'left'\n// 再次邀请:等待加入(status=invited)或已离会(status=left)允许\nconst canShowReinvite = () =>\n !!props.canReinvite &&\n !props.member.isHost &&\n (props.member.status === 'invited' || props.member.status === 'left')\n\nfunction toggleMenu() {\n if (menuOpen.value) {\n closeMenu()\n } else {\n menuOpen.value = true\n }\n}\nfunction closeMenu() {\n menuOpen.value = false\n if (closeTimer) {\n clearTimeout(closeTimer)\n closeTimer = null\n }\n}\n// 鼠标进入按钮或菜单:取消关闭定时器\nfunction onMouseEnter() {\n if (closeTimer) {\n clearTimeout(closeTimer)\n closeTimer = null\n }\n}\n// 鼠标离开按钮或菜单:500ms 后关闭\nfunction onMouseLeave() {\n if (!menuOpen.value) return\n if (closeTimer) clearTimeout(closeTimer)\n closeTimer = setTimeout(() => {\n menuOpen.value = false\n closeTimer = null\n }, CLOSE_DELAY)\n}\nfunction onPromote() {\n closeMenu()\n emit('transferHost', props.member.identity)\n}\nfunction onKick() {\n closeMenu()\n emit('kick', props.member.identity)\n}\nfunction onReinvite() {\n closeMenu()\n emit('reinvite', props.member.identity)\n}\n\n// 点击外部关闭菜单\nfunction onDocClick(e: MouseEvent) {\n if (!menuOpen.value) return\n const target = e.target as HTMLElement\n if (!target.closest('.member-item-row')) {\n closeMenu()\n }\n}\nonMounted(() => {\n document.addEventListener('click', onDocClick)\n})\nonBeforeUnmount(() => {\n document.removeEventListener('click', onDocClick)\n if (closeTimer) clearTimeout(closeTimer)\n})\n</script>\n\n<template>\n <div\n class=\"member-item\"\n :class=\"{\n offline: !member.online && member.status !== 'left',\n waiting: member.online && !member.joined,\n left: member.status === 'left',\n }\"\n >\n <div class=\"member-row\">\n <div class=\"member-main\">\n <div class=\"member-name\">\n {{ member.name }}\n <span v-if=\"member.isHost\" class=\"host-tag\">主持人</span>\n </div>\n </div>\n <div class=\"member-status\">\n <span v-if=\"member.status === 'left'\" class=\"status-badge\">已离会</span>\n <span v-else-if=\"!member.online\" class=\"status-badge\">离线</span>\n <span v-else-if=\"!member.joined\" class=\"status-badge\">等待加入</span>\n <span v-else-if=\"member.speaking\" class=\"status-badge speaking\">正在发言</span>\n </div>\n </div>\n <div class=\"member-row member-item-row\">\n <div class=\"member-main\">\n <div class=\"member-dept\">{{ member.dept || '—' }}</div>\n </div>\n <div class=\"member-status\">\n <!-- 三点菜单按钮(左侧) -->\n <button\n v-if=\"canShowPromote() || canShowKick() || canShowReinvite()\"\n class=\"member-menu\"\n type=\"button\"\n aria-label=\"更多操作\"\n @click.stop=\"toggleMenu\"\n @mouseenter=\"onMouseEnter\"\n @mouseleave=\"onMouseLeave\"\n >\n <Icon name=\"more\" :size=\"14\" />\n </button>\n <!-- 下拉菜单 -->\n <div\n v-if=\"menuOpen\"\n class=\"member-menu-popover\"\n @mouseenter=\"onMouseEnter\"\n @mouseleave=\"onMouseLeave\"\n >\n <button v-if=\"canShowReinvite()\" class=\"menu-item\" type=\"button\" @click.stop=\"onReinvite\">\n 再次邀请\n </button>\n <button v-if=\"canShowPromote()\" class=\"menu-item\" type=\"button\" @click.stop=\"onPromote\">\n 设为主持人\n </button>\n <button v-if=\"canShowKick()\" class=\"menu-item\" type=\"button\" @click.stop=\"onKick\">\n 离会\n </button>\n </div>\n <!-- 麦克风状态图标(最右侧):仅入会成员显示 -->\n <Icon\n v-if=\"member.joined\"\n :name=\"member.muted ? 'mic-off' : 'mic-on'\"\n :size=\"14\"\n :color=\"member.muted ? '#ef4444' : 'rgba(255, 255, 255, 0.85)'\"\n :title=\"member.muted ? '麦克风已关' : '麦克风已开'\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.member-item {\n position: relative;\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 6px;\n height: 56px;\n padding: 0 10px;\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n border-radius: 0;\n}\n/* 离线/等待加入:仅文字透明度降低,边框/按钮/菜单正常显示 */\n/* 整行不再 opacity,避免影响 ⋯ 按钮 + 悬浮菜单可读性 */\n.member-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-height: 0;\n}\n.member-main {\n flex: 1 1 0;\n min-width: 0;\n}\n.member-name {\n /* 入会成员昵称:从中亮白降到中等亮度的暖灰,避免与背景对比过强 */\n font-size: 13px;\n font-weight: 500;\n color: rgba(255, 255, 255, 0.78);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.member-item.offline .member-name,\n.member-item.waiting .member-name,\n.member-item.left .member-name {\n color: var(--atim-meeting-text-tertiary, rgba(255, 255, 255, 0.55));\n}\n.host-tag {\n font-size: 10px;\n color: #fbbf24;\n background: rgba(251, 191, 36, 0.15);\n border: 1px solid rgba(251, 191, 36, 0.5);\n padding: 1px 4px;\n white-space: nowrap;\n}\n.member-dept {\n /* 部门颜色同样降低亮度 */\n font-size: 11px;\n color: rgba(255, 255, 255, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.member-item.offline .member-dept,\n.member-item.waiting .member-dept,\n.member-item.left .member-dept {\n color: var(--atim-meeting-text-tertiary, rgba(255, 255, 255, 0.55));\n}\n.member-status {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n}\n/* 状态标签:无圆角、无边框(仅靠字色区分) */\n.status-badge {\n font-size: 10px;\n color: var(--atim-meeting-text-tertiary, rgba(255, 255, 255, 0.55));\n padding: 1px 5px;\n white-space: nowrap;\n border-radius: 0;\n background: transparent;\n}\n.status-badge.speaking {\n color: var(--atim-meeting-speaking, #22c55e);\n}\n.member-menu {\n width: 22px;\n height: 22px;\n border: none;\n background: transparent;\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n}\n.member-menu:hover {\n background: rgba(255, 255, 255, 0.08);\n color: var(--atim-meeting-text-primary, #fff);\n}\n/* 下拉菜单:弹在三点菜单按钮左侧(麦克图标现在在最右) */\n.member-menu-popover {\n position: absolute;\n top: calc(50% - 12px);\n right: 56px;\n z-index: 5;\n min-width: 96px;\n background: #1e293b;\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n border-radius: 0;\n display: flex;\n flex-direction: column;\n padding: 2px 0;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);\n}\n.menu-item {\n border: none;\n background: transparent;\n /* 离会与设为主持人颜色一致——不再用红色突出 danger */\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n font-size: 12px;\n padding: 6px 12px;\n text-align: left;\n cursor: pointer;\n border-radius: 0;\n}\n.menu-item:hover {\n background: rgba(255, 255, 255, 0.08);\n color: var(--atim-meeting-text-primary, #fff);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount } from 'vue'\nimport Icon from '../Icon.vue'\nimport type { MeetingMember } from '@quitting/contract'\n\nconst props = defineProps<{\n member: MeetingMember\n // 当前用户是否主持人(决定能否触发转主持人/踢人/再次邀请)\n canPromote: boolean\n canKick: boolean\n canReinvite?: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'transferHost', uid: string): void\n (e: 'kick', uid: string): void\n (e: 'reinvite', uid: string): void\n}>()\n\n// 三点菜单展开状态\nconst menuOpen = ref(false)\n// 鼠标离开关闭定时器\nlet closeTimer: ReturnType<typeof setTimeout> | null = null\nconst CLOSE_DELAY = 500\n\n// 入会(online + joined)才允许设为主持人;离线/等待加入不显示此菜单项\nconst canShowPromote = () =>\n props.canPromote && props.member.online && props.member.joined && !props.member.isHost\n// 入会后(无论在线还是暂时离线)都允许踢出;已离会不需要再踢\nconst canShowKick = () => props.canKick && !props.member.isHost && props.member.status !== 'left'\n// 再次邀请:等待加入(status=invited)或已离会(status=left)允许\nconst canShowReinvite = () =>\n !!props.canReinvite &&\n !props.member.isHost &&\n (props.member.status === 'invited' || props.member.status === 'left')\n\nfunction toggleMenu() {\n if (menuOpen.value) {\n closeMenu()\n } else {\n menuOpen.value = true\n }\n}\nfunction closeMenu() {\n menuOpen.value = false\n if (closeTimer) {\n clearTimeout(closeTimer)\n closeTimer = null\n }\n}\n// 鼠标进入按钮或菜单:取消关闭定时器\nfunction onMouseEnter() {\n if (closeTimer) {\n clearTimeout(closeTimer)\n closeTimer = null\n }\n}\n// 鼠标离开按钮或菜单:500ms 后关闭\nfunction onMouseLeave() {\n if (!menuOpen.value) return\n if (closeTimer) clearTimeout(closeTimer)\n closeTimer = setTimeout(() => {\n menuOpen.value = false\n closeTimer = null\n }, CLOSE_DELAY)\n}\nfunction onPromote() {\n closeMenu()\n emit('transferHost', props.member.identity)\n}\nfunction onKick() {\n closeMenu()\n emit('kick', props.member.identity)\n}\nfunction onReinvite() {\n closeMenu()\n emit('reinvite', props.member.identity)\n}\n\n// 点击外部关闭菜单\nfunction onDocClick(e: MouseEvent) {\n if (!menuOpen.value) return\n const target = e.target as HTMLElement\n if (!target.closest('.member-item-row')) {\n closeMenu()\n }\n}\nonMounted(() => {\n document.addEventListener('click', onDocClick)\n})\nonBeforeUnmount(() => {\n document.removeEventListener('click', onDocClick)\n if (closeTimer) clearTimeout(closeTimer)\n})\n</script>\n\n<template>\n <div\n class=\"member-item\"\n :class=\"{\n offline: !member.online && member.status !== 'left',\n waiting: member.online && !member.joined,\n left: member.status === 'left',\n }\"\n >\n <div class=\"member-row\">\n <div class=\"member-main\">\n <div class=\"member-name\">\n {{ member.name }}\n <span v-if=\"member.isHost\" class=\"host-tag\">主持人</span>\n </div>\n </div>\n <div class=\"member-status\">\n <span v-if=\"member.status === 'left'\" class=\"status-badge\">已离会</span>\n <span v-else-if=\"!member.online\" class=\"status-badge\">离线</span>\n <span v-else-if=\"!member.joined\" class=\"status-badge\">等待加入</span>\n <span v-else-if=\"member.speaking\" class=\"status-badge speaking\">正在发言</span>\n </div>\n </div>\n <div class=\"member-row member-item-row\">\n <div class=\"member-main\">\n <div class=\"member-dept\">{{ member.dept || '—' }}</div>\n </div>\n <div class=\"member-status\">\n <!-- 三点菜单按钮(左侧) -->\n <button\n v-if=\"canShowPromote() || canShowKick() || canShowReinvite()\"\n class=\"member-menu\"\n type=\"button\"\n aria-label=\"更多操作\"\n @click.stop=\"toggleMenu\"\n @mouseenter=\"onMouseEnter\"\n @mouseleave=\"onMouseLeave\"\n >\n <Icon name=\"more\" :size=\"14\" />\n </button>\n <!-- 下拉菜单 -->\n <div\n v-if=\"menuOpen\"\n class=\"member-menu-popover\"\n @mouseenter=\"onMouseEnter\"\n @mouseleave=\"onMouseLeave\"\n >\n <button v-if=\"canShowReinvite()\" class=\"menu-item\" type=\"button\" @click.stop=\"onReinvite\">\n 再次邀请\n </button>\n <button v-if=\"canShowPromote()\" class=\"menu-item\" type=\"button\" @click.stop=\"onPromote\">\n 设为主持人\n </button>\n <button v-if=\"canShowKick()\" class=\"menu-item\" type=\"button\" @click.stop=\"onKick\">\n 离会\n </button>\n </div>\n <!-- 麦克风状态图标(最右侧):仅入会成员显示 -->\n <Icon\n v-if=\"member.joined\"\n :name=\"member.muted ? 'mic-off' : 'mic-on'\"\n :size=\"14\"\n :color=\"member.muted ? '#ef4444' : 'rgba(255, 255, 255, 0.85)'\"\n :title=\"member.muted ? '麦克风已关' : '麦克风已开'\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.member-item {\n position: relative;\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 6px;\n height: 56px;\n padding: 0 10px;\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n border-radius: 0;\n}\n/* 离线/等待加入:仅文字透明度降低,边框/按钮/菜单正常显示 */\n/* 整行不再 opacity,避免影响 ⋯ 按钮 + 悬浮菜单可读性 */\n.member-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-height: 0;\n}\n.member-main {\n flex: 1 1 0;\n min-width: 0;\n}\n.member-name {\n /* 入会成员昵称:从中亮白降到中等亮度的暖灰,避免与背景对比过强 */\n font-size: 13px;\n font-weight: 500;\n color: rgba(255, 255, 255, 0.78);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.member-item.offline .member-name,\n.member-item.waiting .member-name,\n.member-item.left .member-name {\n color: var(--atim-meeting-text-tertiary, rgba(255, 255, 255, 0.55));\n}\n.host-tag {\n font-size: 10px;\n color: #fbbf24;\n background: rgba(251, 191, 36, 0.15);\n border: 1px solid rgba(251, 191, 36, 0.5);\n padding: 1px 4px;\n white-space: nowrap;\n}\n.member-dept {\n /* 部门颜色同样降低亮度 */\n font-size: 11px;\n color: rgba(255, 255, 255, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.member-item.offline .member-dept,\n.member-item.waiting .member-dept,\n.member-item.left .member-dept {\n color: var(--atim-meeting-text-tertiary, rgba(255, 255, 255, 0.55));\n}\n.member-status {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n}\n/* 状态标签:无圆角、无边框(仅靠字色区分) */\n.status-badge {\n font-size: 10px;\n color: var(--atim-meeting-text-tertiary, rgba(255, 255, 255, 0.55));\n padding: 1px 5px;\n white-space: nowrap;\n border-radius: 0;\n background: transparent;\n}\n.status-badge.speaking {\n color: var(--atim-meeting-speaking, #22c55e);\n}\n.member-menu {\n width: 22px;\n height: 22px;\n border: none;\n background: transparent;\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n}\n.member-menu:hover {\n background: rgba(255, 255, 255, 0.08);\n color: var(--atim-meeting-text-primary, #fff);\n}\n/* 下拉菜单:弹在三点菜单按钮左侧(麦克图标现在在最右) */\n.member-menu-popover {\n position: absolute;\n top: calc(50% - 12px);\n right: 56px;\n z-index: 5;\n min-width: 96px;\n background: #1e293b;\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n border-radius: 0;\n display: flex;\n flex-direction: column;\n padding: 2px 0;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);\n}\n.menu-item {\n border: none;\n background: transparent;\n /* 离会与设为主持人颜色一致——不再用红色突出 danger */\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n font-size: 12px;\n padding: 6px 12px;\n text-align: left;\n cursor: pointer;\n border-radius: 0;\n}\n.menu-item:hover {\n background: rgba(255, 255, 255, 0.08);\n color: var(--atim-meeting-text-primary, #fff);\n}\n</style>\n","<script setup lang=\"ts\">\nimport MemberItem from './MemberItem.vue'\nimport type { MeetingMember } from '@quitting/contract'\n\ndefineProps<{\n members: MeetingMember[]\n canPromote: boolean\n canKick: boolean\n // 主持人权限:允许「再次邀请」status=invited/left 的成员\n canReinvite?: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'transferHost', uid: string): void\n (e: 'kick', uid: string): void\n (e: 'reinvite', uid: string): void\n}>()\n</script>\n\n<template>\n <div class=\"member-list\">\n <MemberItem\n v-for=\"m in members\"\n :key=\"m.identity\"\n :member=\"m\"\n :can-promote=\"canPromote\"\n :can-kick=\"canKick\"\n :can-reinvite=\"canReinvite\"\n @transfer-host=\"(uid: string) => emit('transferHost', uid)\"\n @kick=\"(uid: string) => emit('kick', uid)\"\n @reinvite=\"(uid: string) => emit('reinvite', uid)\"\n />\n </div>\n</template>\n\n<style scoped>\n.member-list {\n flex: 1 1 0;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n display: flex;\n flex-direction: column;\n gap: 8px;\n /* 隐藏滚动条但保留滚动功能 */\n scrollbar-width: none;\n}\n.member-list::-webkit-scrollbar {\n display: none;\n}\n</style>\n","<script setup lang=\"ts\">\nimport MemberItem from './MemberItem.vue'\nimport type { MeetingMember } from '@quitting/contract'\n\ndefineProps<{\n members: MeetingMember[]\n canPromote: boolean\n canKick: boolean\n // 主持人权限:允许「再次邀请」status=invited/left 的成员\n canReinvite?: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'transferHost', uid: string): void\n (e: 'kick', uid: string): void\n (e: 'reinvite', uid: string): void\n}>()\n</script>\n\n<template>\n <div class=\"member-list\">\n <MemberItem\n v-for=\"m in members\"\n :key=\"m.identity\"\n :member=\"m\"\n :can-promote=\"canPromote\"\n :can-kick=\"canKick\"\n :can-reinvite=\"canReinvite\"\n @transfer-host=\"(uid: string) => emit('transferHost', uid)\"\n @kick=\"(uid: string) => emit('kick', uid)\"\n @reinvite=\"(uid: string) => emit('reinvite', uid)\"\n />\n </div>\n</template>\n\n<style scoped>\n.member-list {\n flex: 1 1 0;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n display: flex;\n flex-direction: column;\n gap: 8px;\n /* 隐藏滚动条但保留滚动功能 */\n scrollbar-width: none;\n}\n.member-list::-webkit-scrollbar {\n display: none;\n}\n</style>\n","<script setup lang=\"ts\">\nimport Icon from '../Icon.vue'\n\ndefineProps<{\n activeTab: 'video' | 'member'\n}>()\n\nconst emit = defineEmits<{\n (e: 'update:activeTab', tab: 'video' | 'member'): void\n (e: 'addMember'): void\n}>()\n</script>\n\n<template>\n <div class=\"tab-bar\">\n <button\n class=\"tab-btn\"\n :class=\"{ active: activeTab === 'video' }\"\n type=\"button\"\n @click=\"emit('update:activeTab', 'video')\"\n >\n 视频列表\n </button>\n <button\n class=\"tab-btn\"\n :class=\"{ active: activeTab === 'member', 'before-add': true }\"\n type=\"button\"\n @click=\"emit('update:activeTab', 'member')\"\n >\n 成员列表\n </button>\n <span class=\"tab-divider\" aria-hidden=\"true\"></span>\n <button class=\"add-member-btn\" type=\"button\" title=\"添加参会人员\" @click=\"emit('addMember')\">\n <Icon name=\"plus\" :size=\"14\" />\n </button>\n </div>\n</template>\n\n<style scoped>\n.tab-bar {\n flex: 0 0 auto;\n display: flex;\n flex-direction: row;\n align-items: stretch;\n /* 整个 tab-bar 撑满父级宽度,与下方列表对齐 */\n width: 100%;\n gap: 0;\n}\n.tab-btn {\n flex: 1 1 0;\n padding: 0 8px;\n height: 28px;\n line-height: 28px;\n background: var(--atim-meeting-tab-inactive, #111827);\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n border-right: none;\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n font-size: 12px;\n cursor: pointer;\n transition:\n background 0.2s,\n color 0.2s;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-radius: 0;\n min-width: 0;\n}\n/* 选中 tab 与 + 按钮之间需要可见分割线:两个 #1e293b 背景连成一片时 1px 边框看不出来\n 用一个 2px 深色实体分割条嵌在中间,颜色对比足够明显 */\n.tab-btn.before-add {\n border-right: none;\n}\n.tab-divider {\n flex: 0 0 auto;\n width: 2px;\n background: #0b1220;\n border: none;\n align-self: stretch;\n}\n.tab-btn.active {\n background: var(--atim-meeting-tab-active, #1e293b);\n color: var(--atim-meeting-text-primary, #fff);\n}\n.tab-btn:hover:not(.active) {\n background: #1f2937;\n color: var(--atim-meeting-text-primary, #fff);\n}\n/* 添加按钮:始终 28×28 正方形;Icon 由 flex 居中 */\n.add-member-btn {\n flex: 0 0 28px;\n width: 28px;\n height: 28px;\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n border-left: none;\n background: var(--atim-meeting-tab-active, #1e293b);\n color: var(--atim-meeting-text-primary, #fff);\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n margin-left: -1px;\n box-sizing: border-box;\n}\n.add-member-btn:hover {\n background: #334155;\n}\n</style>\n","<script setup lang=\"ts\">\nimport Icon from '../Icon.vue'\n\ndefineProps<{\n activeTab: 'video' | 'member'\n}>()\n\nconst emit = defineEmits<{\n (e: 'update:activeTab', tab: 'video' | 'member'): void\n (e: 'addMember'): void\n}>()\n</script>\n\n<template>\n <div class=\"tab-bar\">\n <button\n class=\"tab-btn\"\n :class=\"{ active: activeTab === 'video' }\"\n type=\"button\"\n @click=\"emit('update:activeTab', 'video')\"\n >\n 视频列表\n </button>\n <button\n class=\"tab-btn\"\n :class=\"{ active: activeTab === 'member', 'before-add': true }\"\n type=\"button\"\n @click=\"emit('update:activeTab', 'member')\"\n >\n 成员列表\n </button>\n <span class=\"tab-divider\" aria-hidden=\"true\"></span>\n <button class=\"add-member-btn\" type=\"button\" title=\"添加参会人员\" @click=\"emit('addMember')\">\n <Icon name=\"plus\" :size=\"14\" />\n </button>\n </div>\n</template>\n\n<style scoped>\n.tab-bar {\n flex: 0 0 auto;\n display: flex;\n flex-direction: row;\n align-items: stretch;\n /* 整个 tab-bar 撑满父级宽度,与下方列表对齐 */\n width: 100%;\n gap: 0;\n}\n.tab-btn {\n flex: 1 1 0;\n padding: 0 8px;\n height: 28px;\n line-height: 28px;\n background: var(--atim-meeting-tab-inactive, #111827);\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n border-right: none;\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n font-size: 12px;\n cursor: pointer;\n transition:\n background 0.2s,\n color 0.2s;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-radius: 0;\n min-width: 0;\n}\n/* 选中 tab 与 + 按钮之间需要可见分割线:两个 #1e293b 背景连成一片时 1px 边框看不出来\n 用一个 2px 深色实体分割条嵌在中间,颜色对比足够明显 */\n.tab-btn.before-add {\n border-right: none;\n}\n.tab-divider {\n flex: 0 0 auto;\n width: 2px;\n background: #0b1220;\n border: none;\n align-self: stretch;\n}\n.tab-btn.active {\n background: var(--atim-meeting-tab-active, #1e293b);\n color: var(--atim-meeting-text-primary, #fff);\n}\n.tab-btn:hover:not(.active) {\n background: #1f2937;\n color: var(--atim-meeting-text-primary, #fff);\n}\n/* 添加按钮:始终 28×28 正方形;Icon 由 flex 居中 */\n.add-member-btn {\n flex: 0 0 28px;\n width: 28px;\n height: 28px;\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n border-left: none;\n background: var(--atim-meeting-tab-active, #1e293b);\n color: var(--atim-meeting-text-primary, #fff);\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n margin-left: -1px;\n box-sizing: border-box;\n}\n.add-member-btn:hover {\n background: #334155;\n}\n</style>\n","<script setup lang=\"ts\">\n// 通用确认弹窗:深色主题、零圆角、水平居中\ndefineProps<{\n title?: string\n message: string\n confirmText?: string\n cancelText?: string\n danger?: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'confirm'): void\n (e: 'cancel'): void\n}>()\n</script>\n\n<template>\n <div class=\"confirm-mask\" @click.self=\"emit('cancel')\">\n <div class=\"confirm-dialog\">\n <div v-if=\"title\" class=\"confirm-title\">{{ title }}</div>\n <div class=\"confirm-message\">{{ message }}</div>\n <div class=\"confirm-actions\">\n <button class=\"confirm-btn cancel\" type=\"button\" @click=\"emit('cancel')\">\n {{ cancelText || '取消' }}\n </button>\n <button class=\"confirm-btn ok\" :class=\"{ danger }\" type=\"button\" @click=\"emit('confirm')\">\n {{ confirmText || '确认' }}\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.confirm-mask {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 99999;\n}\n.confirm-dialog {\n min-width: 320px;\n max-width: 440px;\n background: #0f172a;\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 0;\n padding: 20px;\n color: #fff;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);\n}\n.confirm-title {\n font-size: 15px;\n font-weight: 600;\n margin-bottom: 8px;\n}\n.confirm-message {\n font-size: 13px;\n color: rgba(255, 255, 255, 0.85);\n line-height: 1.5;\n margin-bottom: 16px;\n}\n.confirm-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.confirm-btn {\n padding: 6px 14px;\n font-size: 12px;\n border: 1px solid rgba(255, 255, 255, 0.15);\n background: rgba(255, 255, 255, 0.08);\n color: rgba(255, 255, 255, 0.85);\n cursor: pointer;\n border-radius: 0;\n}\n.confirm-btn:hover {\n background: rgba(255, 255, 255, 0.15);\n color: #fff;\n}\n.confirm-btn.ok {\n background: #1e293b;\n color: #fff;\n}\n.confirm-btn.ok.danger {\n background: #e53935;\n border-color: #e53935;\n}\n.confirm-btn.ok.danger:hover {\n background: #f45f5b;\n}\n</style>\n","<script setup lang=\"ts\">\n// 通用确认弹窗:深色主题、零圆角、水平居中\ndefineProps<{\n title?: string\n message: string\n confirmText?: string\n cancelText?: string\n danger?: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'confirm'): void\n (e: 'cancel'): void\n}>()\n</script>\n\n<template>\n <div class=\"confirm-mask\" @click.self=\"emit('cancel')\">\n <div class=\"confirm-dialog\">\n <div v-if=\"title\" class=\"confirm-title\">{{ title }}</div>\n <div class=\"confirm-message\">{{ message }}</div>\n <div class=\"confirm-actions\">\n <button class=\"confirm-btn cancel\" type=\"button\" @click=\"emit('cancel')\">\n {{ cancelText || '取消' }}\n </button>\n <button class=\"confirm-btn ok\" :class=\"{ danger }\" type=\"button\" @click=\"emit('confirm')\">\n {{ confirmText || '确认' }}\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.confirm-mask {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 99999;\n}\n.confirm-dialog {\n min-width: 320px;\n max-width: 440px;\n background: #0f172a;\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 0;\n padding: 20px;\n color: #fff;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);\n}\n.confirm-title {\n font-size: 15px;\n font-weight: 600;\n margin-bottom: 8px;\n}\n.confirm-message {\n font-size: 13px;\n color: rgba(255, 255, 255, 0.85);\n line-height: 1.5;\n margin-bottom: 16px;\n}\n.confirm-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.confirm-btn {\n padding: 6px 14px;\n font-size: 12px;\n border: 1px solid rgba(255, 255, 255, 0.15);\n background: rgba(255, 255, 255, 0.08);\n color: rgba(255, 255, 255, 0.85);\n cursor: pointer;\n border-radius: 0;\n}\n.confirm-btn:hover {\n background: rgba(255, 255, 255, 0.15);\n color: #fff;\n}\n.confirm-btn.ok {\n background: #1e293b;\n color: #fff;\n}\n.confirm-btn.ok.danger {\n background: #e53935;\n border-color: #e53935;\n}\n.confirm-btn.ok.danger:hover {\n background: #f45f5b;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount, computed } from 'vue'\nimport Icon from './Icon.vue'\n\n// 会议结束独立提示浮窗:固定定位在屏幕右下角,不依赖会议主窗口存在\n// 用于解散/被踢/结束/主动离开等场景,显示原因 + 倒计时 + 进度条\nconst props = withDefaults(\n defineProps<{\n reason?: string\n duration?: number // 倒计时秒数\n }>(),\n {\n reason: '会议已结束',\n duration: 5,\n },\n)\n\nconst emit = defineEmits<{ (e: 'close'): void }>()\n\nconst remaining = ref(props.duration)\nlet timer: ReturnType<typeof setInterval> | null = null\n\nconst displayText = computed(() => `${props.reason}(${remaining.value}s)`)\n\nonMounted(() => {\n timer = setInterval(() => {\n remaining.value--\n if (remaining.value <= 0) {\n if (timer) clearInterval(timer)\n timer = null\n emit('close')\n }\n }, 1000)\n})\n\nonBeforeUnmount(() => {\n if (timer) clearInterval(timer)\n})\n</script>\n\n<template>\n <div class=\"ended-toast\">\n <div class=\"ended-toast-card\">\n <span class=\"ended-toast-text\">{{ displayText }}</span>\n <button class=\"ended-toast-close\" type=\"button\" title=\"立即关闭\" @click=\"emit('close')\">\n <Icon name=\"close\" :size=\"14\" />\n </button>\n </div>\n <!-- 倒计时进度条 -->\n <div class=\"ended-toast-progress\">\n <div\n class=\"ended-toast-progress-bar\"\n :style=\"{ animationDuration: `${props.duration}s` }\"\n ></div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.ended-toast {\n position: fixed;\n right: 20px;\n bottom: 20px;\n z-index: 10001;\n width: 240px;\n background: var(--atim-meeting-bg-panel, #0f172a);\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.12));\n border-radius: 0;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n color: #fff;\n overflow: hidden;\n}\n.ended-toast-card {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 10px 12px;\n}\n.ended-toast-text {\n font-size: 13px;\n font-weight: 500;\n color: #fff;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ended-toast-close {\n width: 22px;\n height: 22px;\n flex: 0 0 auto;\n border: 1px solid rgba(255, 255, 255, 0.2);\n background: rgba(255, 255, 255, 0.08);\n color: #fff;\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n}\n.ended-toast-close:hover {\n background: rgba(255, 255, 255, 0.18);\n}\n.ended-toast-progress {\n height: 2px;\n background: rgba(255, 255, 255, 0.1);\n overflow: hidden;\n}\n.ended-toast-progress-bar {\n height: 100%;\n background: #e53935;\n width: 100%;\n transform-origin: left;\n animation: ended-toast-shrink linear forwards;\n}\n@keyframes ended-toast-shrink {\n from {\n transform: scaleX(1);\n }\n to {\n transform: scaleX(0);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount, computed } from 'vue'\nimport Icon from './Icon.vue'\n\n// 会议结束独立提示浮窗:固定定位在屏幕右下角,不依赖会议主窗口存在\n// 用于解散/被踢/结束/主动离开等场景,显示原因 + 倒计时 + 进度条\nconst props = withDefaults(\n defineProps<{\n reason?: string\n duration?: number // 倒计时秒数\n }>(),\n {\n reason: '会议已结束',\n duration: 5,\n },\n)\n\nconst emit = defineEmits<{ (e: 'close'): void }>()\n\nconst remaining = ref(props.duration)\nlet timer: ReturnType<typeof setInterval> | null = null\n\nconst displayText = computed(() => `${props.reason}(${remaining.value}s)`)\n\nonMounted(() => {\n timer = setInterval(() => {\n remaining.value--\n if (remaining.value <= 0) {\n if (timer) clearInterval(timer)\n timer = null\n emit('close')\n }\n }, 1000)\n})\n\nonBeforeUnmount(() => {\n if (timer) clearInterval(timer)\n})\n</script>\n\n<template>\n <div class=\"ended-toast\">\n <div class=\"ended-toast-card\">\n <span class=\"ended-toast-text\">{{ displayText }}</span>\n <button class=\"ended-toast-close\" type=\"button\" title=\"立即关闭\" @click=\"emit('close')\">\n <Icon name=\"close\" :size=\"14\" />\n </button>\n </div>\n <!-- 倒计时进度条 -->\n <div class=\"ended-toast-progress\">\n <div\n class=\"ended-toast-progress-bar\"\n :style=\"{ animationDuration: `${props.duration}s` }\"\n ></div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.ended-toast {\n position: fixed;\n right: 20px;\n bottom: 20px;\n z-index: 10001;\n width: 240px;\n background: var(--atim-meeting-bg-panel, #0f172a);\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.12));\n border-radius: 0;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n color: #fff;\n overflow: hidden;\n}\n.ended-toast-card {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 10px 12px;\n}\n.ended-toast-text {\n font-size: 13px;\n font-weight: 500;\n color: #fff;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ended-toast-close {\n width: 22px;\n height: 22px;\n flex: 0 0 auto;\n border: 1px solid rgba(255, 255, 255, 0.2);\n background: rgba(255, 255, 255, 0.08);\n color: #fff;\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n}\n.ended-toast-close:hover {\n background: rgba(255, 255, 255, 0.18);\n}\n.ended-toast-progress {\n height: 2px;\n background: rgba(255, 255, 255, 0.1);\n overflow: hidden;\n}\n.ended-toast-progress-bar {\n height: 100%;\n background: #e53935;\n width: 100%;\n transform-origin: left;\n animation: ended-toast-shrink linear forwards;\n}\n@keyframes ended-toast-shrink {\n from {\n transform: scaleX(1);\n }\n to {\n transform: scaleX(0);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, computed, nextTick, watch } from 'vue'\nimport vDraggable from '../directives/draggable'\nimport MeetingHeader from './meeting/MeetingHeader.vue'\nimport MainVideoTile from './meeting/MainVideoTile.vue'\nimport VideoListPanel from './meeting/VideoListPanel.vue'\nimport MemberListPanel from './meeting/MemberListPanel.vue'\nimport SidebarTabs from './meeting/SidebarTabs.vue'\nimport Icon from './Icon.vue'\nimport ConfirmDialog from './ConfirmDialog.vue'\nimport MeetingEndedToast from './MeetingEndedToast.vue'\nimport { MaximizeIcon, CloseIcon } from './meeting/icons'\nimport type { MeetingMember, MeetingHostChangedEvent, MeetingDissolvedEvent } from '@quitting/contract'\n\n// 会议窗口:左主视频(默认主持人) + 右侧栏 Tab(视频列表/成员列表) + 底部成员状态条\n// 布局严格 16:9,所有组件无圆角;样式对齐 design/video-conference-layout.html\n// 视频挂载沿用 CallSession/MeetingSession 的 attach 模式:通过 onVideoElReady 把元素交回 SDK\n\nconst props = defineProps<{\n mode: 'host' | 'participant'\n title: string\n // 窗口高度(CSS 值),由调用方决定。支持 \"80vh\" / \"600px\" / \"100%\" 等\n // position: fixed 下百分比相对 viewport;宽度由 layout() 按 16:9 推算\n height?: string\n onClose?: () => void\n // 用户信息解析器:uid → { name, dept? }\n // 后端 ListMembers 在 user 表无对应记录时会回退为 uid(如 demo mock 用户),\n // 调用方可传此函数让 SDK UI 显示业务侧维护的 name/dept;返回 undefined 则保留后端原值\n resolveUser?: (uid: string) => { name: string; dept?: string } | undefined\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n // 添加成员按钮被点击:SDK UI 仅抛事件,邀请哪些人由调用方决定\n // 保持 SDK UI 不直接发 REST 的边界(业务方监听后弹自己的选人 UI)\n (e: 'addMember'): void\n}>()\n\nconst micEnabled = ref(true)\nconst cameraEnabled = ref(true)\nconst speakerMuted = ref(false)\nconst hasCamera = ref(true)\nconst hasMic = ref(true)\nconst deviceErrors = ref<string[]>([])\n// 通用操作提示(reinvite 失败等),复用 device-warn 样式,3 秒自动消失\nconst actionError = ref('')\nlet actionErrorTimer: ReturnType<typeof setTimeout> | null = null\nfunction pushToast(msg: string) {\n actionError.value = msg\n if (actionErrorTimer) clearTimeout(actionErrorTimer)\n actionErrorTimer = setTimeout(() => (actionError.value = ''), 3000)\n}\nconst duration = ref(0)\nconst endedReason = ref('')\nconst activeTab = ref<'video' | 'member'>('video')\nconst members = ref<MeetingMember[]>([])\nconst hostIdentity = ref<string>('')\n// 最小化状态:true 时隐藏主窗口内容,显示可拖拽的小浮条(LiveKit 会话不中断)\nconst minimized = ref(false)\n\n// 元素引用\nconst contentEl = ref<HTMLElement | null>(null)\nconst mainVideoWrap = ref<HTMLElement | null>(null)\nconst sidebarEl = ref<HTMLElement | null>(null)\nconst tabBarWrap = ref<HTMLElement | null>(null)\nconst videoListPanelRef = ref<InstanceType<typeof VideoListPanel> | null>(null)\nconst mainVideoRef = ref<InstanceType<typeof MainVideoTile> | null>(null)\n\n// 视频 attach:identity → 元素;保留 self/remote 区分\nlet selfVideoEl: HTMLVideoElement | null = null\nlet mainVideoIdentity: string | null = null\nlet mainVideoEl: HTMLVideoElement | null = null\nconst participantVideoEls = new Map<string, HTMLVideoElement>()\n\nlet timer: ReturnType<typeof setInterval> | null = null\nlet resizeObserver: ResizeObserver | null = null\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nfunction getSession() {\n return getClient()?.meeting?.currentSession\n}\n\n// 主视频参与者:当前 hostIdentity 对应的 member(可能未入会)\nconst mainVideoMember = computed<MeetingMember | null>(() => {\n const m = members.value.find((x) => x.identity === hostIdentity.value)\n return m || null\n})\n\n// 当前用户是否是「当前主持人」——动态判断,主持人转让后自动更新\n// mode prop 在创建时固化(host/participant),转让后原主持人 mode 仍是 host 但已无权限,\n// 新主持人 mode 是 participant 却应有权限。改用 identity === hostIdentity 解决菜单错乱。\nconst isCurrentHost = computed(() => {\n const sess = getSession()\n return !!sess && sess.identity === hostIdentity.value\n})\n\n// 正在发言的成员(最小化浮条显示用)——与成员列表 speaking 判定一致\nconst speakingMembers = computed(() =>\n members.value.filter((m) => m.speaking && m.joined && m.online),\n)\n// 发言人展示文本:≤3 个全显示(顿号分隔);>3 个显示第一个 + \"...等N人\"\nconst speakerDisplayText = computed(() => {\n const list = speakingMembers.value\n if (list.length === 0) return ''\n if (list.length <= 3) return list.map((m) => m.name).join('、')\n return `${list[0].name}...等${list.length}人`\n})\n\n// 小视频列表:除了主视频那位,其他在线成员\nconst smallVideoMembers = computed<MeetingMember[]>(() => {\n return members.value.filter((m) => m.identity !== hostIdentity.value && (m.online || m.joined))\n})\n\n// 小视频列表变化后必须重新 layout:\n// mount 时 members 还是空 → small-video 没渲染 → layout() 跳过尺寸应用\n// 之后 refreshMembers 完成、small-video 渲染,需要重新 layout 把 inline style 设上\nwatch(smallVideoMembers, () => {\n nextTick(layout)\n})\n\n// 排序后的成员列表(底部状态条 + 成员列表 tab 用)\n// 排序优先级:自己 → 正在发言 → 在线已加入 → 在线未加入 → 离线 → 已离会\n// 已离会(status='left')排最后,避免占用正常成员位置\nfunction getStatusPriority(m: MeetingMember, selfUid: string): number {\n if (m.identity === selfUid) return 0\n if (m.status === 'left') return 5\n if (m.speaking && m.joined && m.online) return 1\n if (m.online) {\n if (!m.joined) return 3\n return 2\n }\n return 4\n}\nfunction sortMembers(list: MeetingMember[]): MeetingMember[] {\n const selfUid = getSession()?.identity || ''\n return list.slice().sort((a, b) => {\n const pa = getStatusPriority(a, selfUid)\n const pb = getStatusPriority(b, selfUid)\n if (pa !== pb) return pa - pb\n return (a.inviteOrder || 0) - (b.inviteOrder || 0)\n })\n}\nconst sortedMembers = computed(() => sortMembers(members.value))\n\nasync function refreshMembers(force = false) {\n const client = getClient()\n if (!client?.meeting) return\n try {\n const list = await client.meeting.getMembers(force)\n // 若调用方提供 resolveUser,用业务侧 name/dept 覆盖后端返回(demo 模式下后端回退 uid)\n if (props.resolveUser) {\n members.value = list.map((m: MeetingMember) => {\n const u = props.resolveUser?.(m.identity)\n if (!u) return m\n return { ...m, name: u.name, dept: u.dept ?? m.dept }\n })\n } else {\n members.value = list\n }\n } catch (err) {\n console.warn('[atim] refreshMembers 失败', err)\n }\n}\n\nasync function reattachAllVideos() {\n const session = getSession()\n if (!session) return\n // 主视频:self 时走 attachLocalVideo,否则 attachParticipantVideo\n if (mainVideoIdentity && mainVideoEl) {\n if (mainVideoIdentity === session.identity) {\n session.attachLocalVideo(mainVideoEl)\n } else {\n session.attachParticipantVideo(mainVideoIdentity, mainVideoEl)\n }\n }\n}\n\n// 主视频元素就绪\nasync function onMainVideoReady(identity: string, el: HTMLVideoElement | null) {\n const session = getSession()\n if (!session) return\n if (el) {\n mainVideoIdentity = identity\n mainVideoEl = el\n if (identity === session.identity) {\n session.attachLocalVideo(el)\n } else {\n session.attachParticipantVideo(identity, el)\n }\n } else {\n mainVideoIdentity = null\n mainVideoEl = null\n }\n}\n\n// 小视频元素就绪\nfunction onSmallVideoReady(identity: string, el: HTMLVideoElement | null) {\n const session = getSession()\n if (!session) return\n if (el) {\n participantVideoEls.set(identity, el)\n if (identity === session.identity) {\n // 自己的 tile:attach 本地视频(非主持人在小视频列表里看不到自己就是因为这里跳过了)\n session.attachLocalVideo(el)\n } else {\n session.attachParticipantVideo(identity, el)\n }\n } else {\n participantVideoEls.delete(identity)\n }\n}\n\nasync function syncDeviceCapability() {\n try {\n const devices = await navigator.mediaDevices.enumerateDevices()\n hasCamera.value = devices.some((d) => d.kind === 'videoinput')\n hasMic.value = devices.some((d) => d.kind === 'audioinput')\n if (!hasCamera.value) cameraEnabled.value = false\n if (!hasMic.value) micEnabled.value = false\n } catch (e) {\n console.warn('[atim] enumerateDevices 失败', e)\n }\n}\n\nfunction triggerLayout() {\n nextTick(layout)\n}\n\n// 布局计算:从 design/video-conference-layout.html layout() 函数搬\n// 所有尺寸(mwW/sidebarW/mainW/smallW)无条件计算,避免小视频列表为空时窗口宽度塌陷\nfunction layout() {\n const content = contentEl.value\n const mainVideo = mainVideoWrap.value\n const sidebar = sidebarEl.value\n const videoListEl = videoListPanelRef.value?.listEl as HTMLElement | undefined\n const items = videoListEl?.querySelectorAll<HTMLElement>('.small-video') ?? []\n if (!content || !mainVideo || !sidebar) return\n\n // 1. 主视频 16:9,高度 = content 高度\n const contentH = content.clientHeight\n if (contentH <= 0) return\n const mainH = contentH\n const mainW = (mainH * 16) / 9\n\n // 2. 小视频 16:9,3 个 + 3 个 gap 填满 sidebar 可用高度\n const smallGap = 8\n const tabBarH = tabBarWrap.value?.offsetHeight ?? 28\n const smallH = (mainH - tabBarH - smallGap * 3) / 3\n const smallW = (smallH * 16) / 9\n\n // 3. sidebar 宽度 = 小视频宽(不再保留自定义滚动条空间,列表与 tab 等宽)\n const sidebarW = smallW\n\n // 4. 窗口总宽度(padding + main + gap + sidebar + padding)—— 左右 padding 对称\n const padding = 10\n const gap = 10\n const mwW = padding + mainW + gap + sidebarW + padding\n\n // 5. 应用到 DOM(items 为空也照常设,保持窗口宽度稳定)\n const mw = document.getElementById('meetingWindow') as HTMLElement | null\n if (mw) {\n mw.style.width = mwW + 'px'\n }\n\n mainVideo.style.width = mainW + 'px'\n mainVideo.style.height = mainH + 'px'\n\n // sidebar 宽度 = smallW,与 list 完全对齐\n sidebar.style.width = sidebarW + 'px'\n sidebar.style.height = contentH + 'px'\n\n if (videoListEl) {\n // list 撑满 sidebar,与 tab-bar 等宽\n videoListEl.style.width = '100%'\n videoListEl.style.height = contentH - tabBarH - smallGap + 'px'\n }\n\n items.forEach((item) => {\n // 小视频保持 16:9\n item.style.width = smallW + 'px'\n // 不取整:Math.floor 会让 3 个 small 累计损失 ~2px,导致最后 small 底部与主视频底部错位\n item.style.height = smallH + 'px'\n })\n}\n\n// ============ 设备/会议控制 ============\nasync function toggleMic() {\n if (!hasMic.value) return\n const session = getSession()\n if (!session) return\n const next = !micEnabled.value\n try {\n await session.setMicEnabled(next)\n micEnabled.value = next\n } catch (err) {\n console.warn('[atim] setMicEnabled 失败,UI 不切换', err)\n }\n}\n\nasync function toggleCamera() {\n if (!hasCamera.value) return\n const session = getSession()\n if (!session) return\n const next = !cameraEnabled.value\n try {\n await session.setCameraEnabled(next)\n cameraEnabled.value = next\n nextTick(reattachAllVideos)\n } catch (err) {\n console.warn('[atim] setCameraEnabled 失败,UI 不切换', err)\n }\n}\n\nasync function toggleSpeaker() {\n const session = getSession()\n if (!session) return\n speakerMuted.value = !speakerMuted.value\n await session.setSpeakerMuted(speakerMuted.value)\n}\n\n// 是否显示「解散会议」确认弹窗\nconst showDissolveConfirm = ref(false)\nconst dissolvePending = ref(false)\n\nasync function leaveMeeting() {\n const client = getClient()\n if (!client) return\n // 当前主持人才能解散;非主持人(含已转让的前主持人)直接走普通 leave\n if (!isCurrentHost.value) {\n await client.meeting?.leave()\n // 主窗口立即隐藏,独立倒计时 toast 提示后由 toast 触发 close\n endedReason.value = '已离开'\n return\n }\n // host:查房内参与者数量\n // - count > 1:房内还有其他人,弹\"解散会议\"确认(让 host 明确选择 Leave 还是 Dissolve)\n // - count <= 1:只剩 host 自己——直接 dissolve,避免遗留僵尸会议让离线 invitee 上线后收到 stale 邀请\n // 之前的 bug:count=1 时走 leave,只改 host 自己 invite.status=left,meeting.status 仍 active,\n // 其他 invitee 的 status 仍 invited → pending-invites 返回 stale 邀请\n let count = 1\n try {\n count = await client.meeting.getParticipantCount()\n } catch (err) {\n console.warn('[atim] 查参与者数量失败,按 1 处理(自动解散)', err)\n }\n if (count > 1) {\n showDissolveConfirm.value = true\n return\n }\n dissolvePending.value = true\n try {\n await client.meeting.dissolve()\n endedReason.value = '会议已解散'\n } catch (err) {\n // dissolve 失败兜底:后端 Leave 内部也会做同样的自动 dissolve 兜底,但前端先失败说明网络层有问题\n console.error('[atim] host 单独时解散失败,回退到 leave', err)\n endedReason.value = '已离开'\n await client.meeting?.leave().catch(() => {})\n } finally {\n dissolvePending.value = false\n }\n}\n\n// 确认解散\nasync function confirmDissolve() {\n const client = getClient()\n if (!client) return\n showDissolveConfirm.value = false\n dissolvePending.value = true\n try {\n await client.meeting.dissolve()\n // 解散成功:endedReason 让 ended-overlay 显示;broadcast 到达后 SDK 也会触发 ended 事件\n endedReason.value = '会议已解散'\n } catch (err) {\n console.error('[atim] 解散失败', err)\n endedReason.value = '解散失败'\n } finally {\n dissolvePending.value = false\n }\n}\n\nfunction cancelDissolve() {\n showDissolveConfirm.value = false\n}\n\nasync function kickParticipant(uid: string) {\n const session = getSession()\n if (!session) return\n try {\n await session.kick(getClient().transport, uid)\n await refreshMembers(true)\n } catch (err) {\n console.warn('[atim] kickParticipant 失败', err)\n }\n}\n\nasync function transferHost(uid: string) {\n const session = getSession()\n if (!session) return\n try {\n await session.transferHost(getClient().transport, uid)\n hostIdentity.value = uid\n await refreshMembers(true)\n } catch (err) {\n console.warn('[atim] transferHost 失败', err)\n }\n}\n\n// 主持人再次邀请某成员(等待加入/已离会状态)\nasync function reinviteMember(uid: string) {\n const session = getSession()\n if (!session) return\n try {\n await getClient().meeting.reinvite(session.meetingId, uid)\n await refreshMembers(true)\n } catch (err: unknown) {\n // 后端对被踢者(status=removed)返回 target_was_kicked——成员列表已过滤 removed,\n // 但邀请弹窗候选来自全量用户,仍可能选中被踢者。映射成中文提示,避免静默失败。\n const msg = err instanceof Error ? err.message : ''\n if (msg.includes('target_was_kicked')) {\n pushToast('该成员已被踢出会议,无法再次邀请')\n } else if (msg.includes('meeting_ended')) {\n pushToast('会议已结束')\n } else {\n pushToast('再次邀请失败,请重试')\n }\n console.warn('[atim] reinvite 失败', err)\n }\n}\n\n// 监听 hostChanged 广播:切换主视频\nfunction onHostChangedEvent(e: MeetingHostChangedEvent) {\n const session = getSession()\n if (!session || e.meetingId !== session.meetingId) return\n hostIdentity.value = e.hostUid\n // 主视频元素会被 v-if 重新挂载,nextTick 后 reattach\n nextTick(() => {\n refreshMembers(true)\n })\n}\n\n// 监听解散广播:主持人解散后所有参与人收到,立即隐藏视频窗口,只留独立倒计时提示。\n// 不依赖 session 'ended' 事件(它要等 sess.end() 的 async disconnect 完成才触发,有延迟)。\nfunction onDissolvedEvent(e: MeetingDissolvedEvent) {\n const session = getSession()\n if (!session || e.meetingId !== session.meetingId) return\n if (!endedReason.value) endedReason.value = '会议已解散'\n if (timer) clearInterval(timer)\n}\n\n// 监听被踢广播:立即隐藏视频窗口\nfunction onKickedEvent(e: { meetingId: string; by: string }) {\n const session = getSession()\n if (!session || e.meetingId !== session.meetingId) return\n if (!endedReason.value) endedReason.value = '已被移出会议'\n if (timer) clearInterval(timer)\n}\n\nfunction formatDuration(s: number) {\n const h = Math.floor(s / 3600)\n const m = Math.floor((s % 3600) / 60)\n .toString()\n .padStart(2, '0')\n const sec = (s % 60).toString().padStart(2, '0')\n return h > 0 ? `${h}:${m}:${sec}` : `${m}:${sec}`\n}\n\nonMounted(async () => {\n await syncDeviceCapability()\n const client = getClient()\n if (!client) return\n const session = client.meeting?.currentSession\n if (!session) return\n\n hostIdentity.value = session.hostIdentity || session.identity\n\n session.onSession('connected', () => {\n refreshMembers()\n nextTick(layout)\n timer = setInterval(() => {\n duration.value++\n // 每 2 秒同时刷一次成员列表(muted/speaking 实时态)\n if (duration.value % 2 === 0) refreshMembers()\n }, 1000)\n })\n session.onSession('participantJoined', () => {\n refreshMembers(true)\n })\n session.onSession('participantLeft', () => {\n refreshMembers(true)\n })\n session.onSession('participantTrackSubscribed', (info: { kind: string; identity: string }) => {\n if (info.kind !== 'video') return\n // 优先用小视频池的元素\n const el = participantVideoEls.get(info.identity)\n if (el) {\n session.attachParticipantVideo(info.identity, el)\n }\n // 主视频位置的 track 流入:reattach 主视频(mainVideoEl 不在 participantVideoEls 里)\n if (mainVideoIdentity === info.identity) {\n nextTick(reattachAllVideos)\n }\n })\n // 发言态:LiveKit 给全量列表,覆盖式更新 members[].speaking\n // refreshMembers 每 2 秒会用后端 speaking=false 覆盖,下个 activeSpeakersChanged 事件会再次纠正——可接受\n // 就地修改成员字段,仅在状态变化时替换数组引用触发响应式\n session.onSession('activeSpeakersChanged', (info: { identities: string[] }) => {\n const set = new Set(info.identities)\n let changed = false\n for (const m of members.value) {\n const speaking = set.has(m.identity)\n if (m.speaking !== speaking) {\n m.speaking = speaking\n changed = true\n }\n }\n if (changed) members.value = [...members.value]\n })\n session.onSession('devices', (info: { mic: boolean; camera: boolean; errors: string[] }) => {\n if (info.errors && info.errors.length > 0) {\n deviceErrors.value = info.errors\n setTimeout(() => (deviceErrors.value = []), 3000)\n }\n // 同步设备实际开启状态(enableDevices 可能 mic/camera 开启失败,UI 需据此更新按钮)\n if (!info.mic) micEnabled.value = false\n if (!info.camera) cameraEnabled.value = false\n if (info.camera) nextTick(reattachAllVideos)\n })\n session.onSession('ended', () => {\n // 解散/被踢/对端断开都会触发——独立倒计时浮窗提示,5s 后自动关或用户手动关\n if (!endedReason.value) endedReason.value = '会议已结束'\n if (timer) clearInterval(timer)\n })\n\n // race 修复:mount 之前可能已 connected\n if (session.isConnected) {\n refreshMembers()\n nextTick(layout)\n timer = setInterval(() => {\n duration.value++\n if (duration.value % 2 === 0) refreshMembers()\n }, 1000)\n }\n\n // 监听 host 转让广播(所有客户端)\n client.on('meeting:hostChanged', onHostChangedEvent)\n // 监听解散/被踢广播:参与人端立即隐藏视频窗口(不等 sess.end 的 async 完成)\n client.on('meeting:dissolved', onDissolvedEvent)\n client.on('meeting:kicked', onKickedEvent)\n\n // 布局双保险:ResizeObserver + window resize\n if (contentEl.value) {\n resizeObserver = new ResizeObserver(() => layout())\n resizeObserver.observe(contentEl.value)\n }\n window.addEventListener('resize', layout)\n nextTick(layout)\n})\n\nonUnmounted(() => {\n if (timer) clearInterval(timer)\n if (actionErrorTimer) clearTimeout(actionErrorTimer)\n if (resizeObserver) {\n resizeObserver.disconnect()\n resizeObserver = null\n }\n window.removeEventListener('resize', layout)\n const client = getClient()\n client?.off?.('meeting:hostChanged', onHostChangedEvent)\n client?.off?.('meeting:dissolved', onDissolvedEvent)\n client?.off?.('meeting:kicked', onKickedEvent)\n})\n</script>\n\n<template>\n <div\n id=\"meetingWindow\"\n v-show=\"!minimized && !endedReason\"\n v-draggable=\"'.meeting-header'\"\n class=\"meeting-window\"\n :style=\"{ height: height || '80vh' }\"\n >\n <MeetingHeader\n :title=\"title\"\n :duration=\"formatDuration(duration)\"\n @close=\"leaveMeeting\"\n @minimize=\"minimized = true\"\n />\n\n <div v-if=\"deviceErrors.length\" class=\"device-warn\">\n {{ deviceErrors.join('; ') }}\n </div>\n <div v-if=\"actionError\" class=\"device-warn\">\n {{ actionError }}\n </div>\n\n <div ref=\"contentEl\" class=\"content\">\n <!-- 主视频:当前主持人 -->\n <div ref=\"mainVideoWrap\">\n <MainVideoTile\n v-if=\"mainVideoMember\"\n ref=\"mainVideoRef\"\n :identity=\"mainVideoMember.identity\"\n :name=\"mainVideoMember.name\"\n :muted=\"mainVideoMember.muted\"\n :speaking=\"mainVideoMember.speaking\"\n :is-host=\"mainVideoMember.isHost\"\n :is-self=\"mainVideoMember.identity === (getSession()?.identity || '')\"\n :on-video-el-ready=\"onMainVideoReady\"\n />\n </div>\n\n <aside ref=\"sidebarEl\" class=\"sidebar\">\n <div ref=\"tabBarWrap\">\n <SidebarTabs v-model:active-tab=\"activeTab\" @add-member=\"emit('addMember')\" />\n </div>\n <!-- 视频列表 tab -->\n <VideoListPanel\n v-show=\"activeTab === 'video'\"\n ref=\"videoListPanelRef\"\n :members=\"smallVideoMembers\"\n :on-tile-mount=\"onSmallVideoReady\"\n />\n <!-- 成员列表 tab -->\n <MemberListPanel\n v-show=\"activeTab === 'member'\"\n :members=\"sortedMembers\"\n :can-promote=\"isCurrentHost\"\n :can-kick=\"isCurrentHost\"\n :can-reinvite=\"isCurrentHost\"\n @transfer-host=\"(uid: string) => transferHost(uid)\"\n @kick=\"(uid: string) => kickParticipant(uid)\"\n @reinvite=\"(uid: string) => reinviteMember(uid)\"\n />\n </aside>\n </div>\n\n <!-- 底部控制按钮区:水平居中,全图标,hover 显示当前状态 -->\n <div class=\"bottom-controls\">\n <button\n v-if=\"hasMic\"\n class=\"ctrl-btn\"\n :class=\"{ off: !micEnabled }\"\n type=\"button\"\n :title=\"micEnabled ? '麦克风已开' : '麦克风已关'\"\n @click=\"toggleMic\"\n >\n <Icon :name=\"micEnabled ? 'mic-on' : 'mic-off'\" :size=\"22\" />\n </button>\n <button\n v-if=\"hasCamera\"\n class=\"ctrl-btn\"\n :class=\"{ off: !cameraEnabled }\"\n type=\"button\"\n :title=\"cameraEnabled ? '摄像头已开' : '摄像头已关'\"\n @click=\"toggleCamera\"\n >\n <Icon :name=\"cameraEnabled ? 'camera-on' : 'camera-off'\" :size=\"22\" />\n </button>\n <button\n class=\"ctrl-btn\"\n :class=\"{ off: speakerMuted }\"\n type=\"button\"\n :title=\"speakerMuted ? '扬声器已关' : '扬声器已开'\"\n @click=\"toggleSpeaker\"\n >\n <Icon :name=\"speakerMuted ? 'sound-off' : 'sound-on'\" :size=\"22\" />\n </button>\n <button class=\"ctrl-btn hangup\" type=\"button\" title=\"离开会议\" @click=\"leaveMeeting\">\n <Icon name=\"hangup\" :size=\"22\" />\n </button>\n </div>\n </div>\n\n <!-- 解散会议确认弹窗:独立于主窗口,最小化态下也能显示 -->\n <ConfirmDialog\n v-if=\"showDissolveConfirm\"\n title=\"解散会议\"\n message=\"会议中还有其他成员,确认要解散会议吗?所有成员将被强制离会。\"\n confirm-text=\"解散会议\"\n cancel-text=\"取消\"\n :danger=\"true\"\n @confirm=\"confirmDissolve\"\n @cancel=\"cancelDissolve\"\n />\n\n <!-- 会议结束/离开倒计时提示:独立浮窗,不依赖主窗口 -->\n <MeetingEndedToast\n v-if=\"endedReason\"\n :reason=\"endedReason\"\n :duration=\"5\"\n @close=\"emit('close')\"\n />\n\n <!-- 最小化浮条:可拖拽,单行显示发言人/会议名称 + 时长 + 还原/关闭按钮 -->\n <div v-if=\"minimized && !endedReason\" class=\"mini-bar\" v-draggable=\"'.mini-bar-body'\">\n <div class=\"mini-bar-body\">\n <!-- 有人发言时显示声波动效 + 发言人;无人发言显示会议名称(同一位置切换) -->\n <span v-if=\"speakerDisplayText\" class=\"speaking-indicator\">\n <span class=\"wave-bar\"></span>\n <span class=\"wave-bar\"></span>\n <span class=\"wave-bar\"></span>\n </span>\n <span class=\"mini-main-text\" :class=\"{ speaking: !!speakerDisplayText }\">\n {{ speakerDisplayText || title }}\n </span>\n <span class=\"mini-duration\">{{ formatDuration(duration) }}</span>\n </div>\n <div class=\"mini-bar-actions\">\n <button\n class=\"mini-btn\"\n type=\"button\"\n aria-label=\"还原窗口\"\n title=\"还原窗口\"\n v-html=\"MaximizeIcon\"\n @click=\"minimized = false\"\n ></button>\n <button\n class=\"mini-btn mini-close\"\n type=\"button\"\n aria-label=\"离开会议\"\n title=\"离开会议\"\n v-html=\"CloseIcon\"\n @click=\"leaveMeeting\"\n ></button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.meeting-window {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n /* height 由 prop 控制(inline style 注入),未传时 fallback 80vh */\n width: var(--mw-w, min(1200px, 95vw));\n background: var(--atim-meeting-bg-panel, #0f172a);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n margin: 0 auto;\n padding: 10px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n color: var(--atim-meeting-text-primary, #fff);\n z-index: 9999;\n border-radius: 0;\n /* 与 design/video-conference-layout.html :root 变量一一对应 */\n --atim-meeting-bg-body: #0b1220;\n --atim-meeting-bg-panel: #0f172a;\n --atim-meeting-bg-video: #000000;\n --atim-meeting-text-primary: #ffffff;\n --atim-meeting-text-secondary: rgba(255, 255, 255, 0.85);\n --atim-meeting-text-tertiary: rgba(255, 255, 255, 0.55);\n --atim-meeting-border: rgba(255, 255, 255, 0.08);\n --atim-meeting-scrollbar: rgba(255, 255, 255, 0.15);\n --atim-meeting-scrollbar-hover: rgba(255, 255, 255, 0.35);\n --atim-meeting-tab-active: #1e293b;\n --atim-meeting-tab-inactive: #111827;\n --atim-meeting-speaking: #22c55e;\n --atim-meeting-online: #22c55e;\n --atim-meeting-offline: #6b7280;\n --atim-meeting-overlay-gap: 5px;\n}\n.content {\n flex: 1 1 0;\n min-height: 0;\n display: flex;\n flex-direction: row;\n gap: 10px;\n padding: 0;\n overflow: hidden;\n margin-top: 5px;\n}\n.content > div:first-child {\n flex: 0 0 auto;\n}\n.sidebar {\n flex: 0 0 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n /* 比会议窗口背景 #0f172a 稍深一点的暗色,让空列表区域不显得空洞 */\n background: #0b1220;\n /* tab-bar 与 list 之间的 8px 间距由 sidebar flex gap 直接管理,\n 不依赖子组件内部 margin(避免 BFC 包含 margin 导致 offsetHeight 多算 8px) */\n gap: 8px;\n overflow: hidden;\n}\n/* 所有子节点撑满 sidebar 宽度:tab-bar / video list / member list 三者左右对齐 */\n.sidebar > * {\n width: 100%;\n}\n.sidebar > div:first-child {\n flex: 0 0 auto;\n}\n/* 设备失败提示 */\n.device-warn {\n position: absolute;\n top: 44px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(212, 160, 23, 0.92);\n color: var(--atim-meeting-text-primary, #fff);\n font-size: 12px;\n padding: 4px 12px;\n border-radius: 0;\n z-index: 6;\n pointer-events: none;\n max-width: 80%;\n text-align: center;\n}\n/* 底部控制按钮区:水平居中、常驻显示、紧凑高度 */\n.bottom-controls {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 6px 0 2px;\n z-index: 4;\n}\n.ctrl-btn {\n width: 32px;\n height: 32px;\n /* 无边框无背景:仅图标 */\n border: none;\n background: transparent;\n color: var(--atim-meeting-text-primary, #fff);\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n transition:\n transform 0.1s,\n opacity 0.15s;\n opacity: 0.85;\n}\n.ctrl-btn:hover {\n opacity: 1;\n}\n.ctrl-btn:active {\n transform: scale(0.92);\n}\n/* 开关关闭状态:图标变红 */\n.ctrl-btn.off {\n color: #ef4444;\n opacity: 1;\n}\n/* 挂断按钮:图标红色(无红色背景块) */\n.ctrl-btn.hangup {\n color: #ef4444;\n}\n.ctrl-btn.hangup:hover {\n color: #f45f5b;\n}\n\n/* ============ 最小化浮条 ============ */\n.mini-bar {\n position: fixed;\n right: 20px;\n bottom: 20px;\n z-index: 9999;\n display: flex;\n align-items: stretch;\n max-width: 320px;\n min-width: 220px;\n background: var(--atim-meeting-bg-panel, #0f172a);\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.12));\n border-radius: 0;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n color: var(--atim-meeting-text-primary, #fff);\n overflow: hidden;\n}\n/* 拖拽区:单行——发言人/会议名称 + 时长 */\n.mini-bar-body {\n flex: 1 1 auto;\n min-width: 0;\n padding: 8px 10px;\n cursor: move;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n/* 主体文本:有人发言时显示发言人(绿色),无人发言显示会议名称(白色) */\n.mini-main-text {\n font-size: 13px;\n font-weight: 500;\n color: var(--atim-meeting-text-primary, #fff);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n flex: 1 1 auto;\n min-width: 0;\n}\n.mini-main-text.speaking {\n color: var(--atim-meeting-speaking, #22c55e);\n font-weight: 600;\n}\n.mini-duration {\n font-size: 11px;\n color: var(--atim-meeting-text-tertiary, rgba(255, 255, 255, 0.55));\n font-variant-numeric: tabular-nums;\n flex: 0 0 auto;\n}\n/* 声波动效:复用 MainVideoTile 的 wave-bar 三柱动效 + 颜色变量 */\n.mini-bar-body .speaking-indicator {\n display: inline-flex;\n align-items: flex-end;\n gap: 2px;\n flex: 0 0 auto;\n}\n.mini-bar-body .wave-bar {\n width: 2px;\n background: var(--atim-meeting-speaking, #22c55e);\n animation: mini-wave 1s ease-in-out infinite;\n}\n.mini-bar-body .wave-bar:nth-child(1) {\n height: 6px;\n animation-delay: 0s;\n}\n.mini-bar-body .wave-bar:nth-child(2) {\n height: 10px;\n animation-delay: 0.15s;\n}\n.mini-bar-body .wave-bar:nth-child(3) {\n height: 6px;\n animation-delay: 0.3s;\n}\n@keyframes mini-wave {\n 0%,\n 100% {\n transform: scaleY(1);\n opacity: 0.7;\n }\n 50% {\n transform: scaleY(1.6);\n opacity: 1;\n }\n}\n/* 操作按钮区 */\n.mini-bar-actions {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 0 4px;\n border-left: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n}\n.mini-btn {\n width: 26px;\n height: 100%;\n min-height: 36px;\n border: none;\n background: transparent;\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n background 0.2s,\n color 0.2s;\n}\n.mini-btn:hover {\n background: rgba(255, 255, 255, 0.1);\n color: var(--atim-meeting-text-primary, #fff);\n}\n.mini-btn :deep(svg) {\n width: 16px;\n height: 16px;\n}\n.mini-btn.mini-close:hover {\n background: #e53935;\n color: #fff;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, computed, nextTick, watch } from 'vue'\nimport vDraggable from '../directives/draggable'\nimport MeetingHeader from './meeting/MeetingHeader.vue'\nimport MainVideoTile from './meeting/MainVideoTile.vue'\nimport VideoListPanel from './meeting/VideoListPanel.vue'\nimport MemberListPanel from './meeting/MemberListPanel.vue'\nimport SidebarTabs from './meeting/SidebarTabs.vue'\nimport Icon from './Icon.vue'\nimport ConfirmDialog from './ConfirmDialog.vue'\nimport MeetingEndedToast from './MeetingEndedToast.vue'\nimport { MaximizeIcon, CloseIcon } from './meeting/icons'\nimport type { MeetingMember, MeetingHostChangedEvent, MeetingDissolvedEvent } from '@quitting/contract'\n\n// 会议窗口:左主视频(默认主持人) + 右侧栏 Tab(视频列表/成员列表) + 底部成员状态条\n// 布局严格 16:9,所有组件无圆角;样式对齐 design/video-conference-layout.html\n// 视频挂载沿用 CallSession/MeetingSession 的 attach 模式:通过 onVideoElReady 把元素交回 SDK\n\nconst props = defineProps<{\n mode: 'host' | 'participant'\n title: string\n // 窗口高度(CSS 值),由调用方决定。支持 \"80vh\" / \"600px\" / \"100%\" 等\n // position: fixed 下百分比相对 viewport;宽度由 layout() 按 16:9 推算\n height?: string\n onClose?: () => void\n // 用户信息解析器:uid → { name, dept? }\n // 后端 ListMembers 在 user 表无对应记录时会回退为 uid(如 demo mock 用户),\n // 调用方可传此函数让 SDK UI 显示业务侧维护的 name/dept;返回 undefined 则保留后端原值\n resolveUser?: (uid: string) => { name: string; dept?: string } | undefined\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n // 添加成员按钮被点击:SDK UI 仅抛事件,邀请哪些人由调用方决定\n // 保持 SDK UI 不直接发 REST 的边界(业务方监听后弹自己的选人 UI)\n (e: 'addMember'): void\n}>()\n\nconst micEnabled = ref(true)\nconst cameraEnabled = ref(true)\nconst speakerMuted = ref(false)\nconst hasCamera = ref(true)\nconst hasMic = ref(true)\nconst deviceErrors = ref<string[]>([])\n// 通用操作提示(reinvite 失败等),复用 device-warn 样式,3 秒自动消失\nconst actionError = ref('')\nlet actionErrorTimer: ReturnType<typeof setTimeout> | null = null\nfunction pushToast(msg: string) {\n actionError.value = msg\n if (actionErrorTimer) clearTimeout(actionErrorTimer)\n actionErrorTimer = setTimeout(() => (actionError.value = ''), 3000)\n}\nconst duration = ref(0)\nconst endedReason = ref('')\nconst activeTab = ref<'video' | 'member'>('video')\nconst members = ref<MeetingMember[]>([])\nconst hostIdentity = ref<string>('')\n// 最小化状态:true 时隐藏主窗口内容,显示可拖拽的小浮条(LiveKit 会话不中断)\nconst minimized = ref(false)\n\n// 元素引用\nconst contentEl = ref<HTMLElement | null>(null)\nconst mainVideoWrap = ref<HTMLElement | null>(null)\nconst sidebarEl = ref<HTMLElement | null>(null)\nconst tabBarWrap = ref<HTMLElement | null>(null)\nconst videoListPanelRef = ref<InstanceType<typeof VideoListPanel> | null>(null)\nconst mainVideoRef = ref<InstanceType<typeof MainVideoTile> | null>(null)\n\n// 视频 attach:identity → 元素;保留 self/remote 区分\nlet selfVideoEl: HTMLVideoElement | null = null\nlet mainVideoIdentity: string | null = null\nlet mainVideoEl: HTMLVideoElement | null = null\nconst participantVideoEls = new Map<string, HTMLVideoElement>()\n\nlet timer: ReturnType<typeof setInterval> | null = null\nlet resizeObserver: ResizeObserver | null = null\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nfunction getSession() {\n return getClient()?.meeting?.currentSession\n}\n\n// 主视频参与者:当前 hostIdentity 对应的 member(可能未入会)\nconst mainVideoMember = computed<MeetingMember | null>(() => {\n const m = members.value.find((x) => x.identity === hostIdentity.value)\n return m || null\n})\n\n// 当前用户是否是「当前主持人」——动态判断,主持人转让后自动更新\n// mode prop 在创建时固化(host/participant),转让后原主持人 mode 仍是 host 但已无权限,\n// 新主持人 mode 是 participant 却应有权限。改用 identity === hostIdentity 解决菜单错乱。\nconst isCurrentHost = computed(() => {\n const sess = getSession()\n return !!sess && sess.identity === hostIdentity.value\n})\n\n// 正在发言的成员(最小化浮条显示用)——与成员列表 speaking 判定一致\nconst speakingMembers = computed(() =>\n members.value.filter((m) => m.speaking && m.joined && m.online),\n)\n// 发言人展示文本:≤3 个全显示(顿号分隔);>3 个显示第一个 + \"...等N人\"\nconst speakerDisplayText = computed(() => {\n const list = speakingMembers.value\n if (list.length === 0) return ''\n if (list.length <= 3) return list.map((m) => m.name).join('、')\n return `${list[0].name}...等${list.length}人`\n})\n\n// 小视频列表:除了主视频那位,其他在线成员\nconst smallVideoMembers = computed<MeetingMember[]>(() => {\n return members.value.filter((m) => m.identity !== hostIdentity.value && (m.online || m.joined))\n})\n\n// 小视频列表变化后必须重新 layout:\n// mount 时 members 还是空 → small-video 没渲染 → layout() 跳过尺寸应用\n// 之后 refreshMembers 完成、small-video 渲染,需要重新 layout 把 inline style 设上\nwatch(smallVideoMembers, () => {\n nextTick(layout)\n})\n\n// 排序后的成员列表(底部状态条 + 成员列表 tab 用)\n// 排序优先级:自己 → 正在发言 → 在线已加入 → 在线未加入 → 离线 → 已离会\n// 已离会(status='left')排最后,避免占用正常成员位置\nfunction getStatusPriority(m: MeetingMember, selfUid: string): number {\n if (m.identity === selfUid) return 0\n if (m.status === 'left') return 5\n if (m.speaking && m.joined && m.online) return 1\n if (m.online) {\n if (!m.joined) return 3\n return 2\n }\n return 4\n}\nfunction sortMembers(list: MeetingMember[]): MeetingMember[] {\n const selfUid = getSession()?.identity || ''\n return list.slice().sort((a, b) => {\n const pa = getStatusPriority(a, selfUid)\n const pb = getStatusPriority(b, selfUid)\n if (pa !== pb) return pa - pb\n return (a.inviteOrder || 0) - (b.inviteOrder || 0)\n })\n}\nconst sortedMembers = computed(() => sortMembers(members.value))\n\nasync function refreshMembers(force = false) {\n const client = getClient()\n if (!client?.meeting) return\n try {\n const list = await client.meeting.getMembers(force)\n // 若调用方提供 resolveUser,用业务侧 name/dept 覆盖后端返回(demo 模式下后端回退 uid)\n if (props.resolveUser) {\n members.value = list.map((m: MeetingMember) => {\n const u = props.resolveUser?.(m.identity)\n if (!u) return m\n return { ...m, name: u.name, dept: u.dept ?? m.dept }\n })\n } else {\n members.value = list\n }\n } catch (err) {\n console.warn('[atim] refreshMembers 失败', err)\n }\n}\n\nasync function reattachAllVideos() {\n const session = getSession()\n if (!session) return\n // 主视频:self 时走 attachLocalVideo,否则 attachParticipantVideo\n if (mainVideoIdentity && mainVideoEl) {\n if (mainVideoIdentity === session.identity) {\n session.attachLocalVideo(mainVideoEl)\n } else {\n session.attachParticipantVideo(mainVideoIdentity, mainVideoEl)\n }\n }\n}\n\n// 主视频元素就绪\nasync function onMainVideoReady(identity: string, el: HTMLVideoElement | null) {\n const session = getSession()\n if (!session) return\n if (el) {\n mainVideoIdentity = identity\n mainVideoEl = el\n if (identity === session.identity) {\n session.attachLocalVideo(el)\n } else {\n session.attachParticipantVideo(identity, el)\n }\n } else {\n mainVideoIdentity = null\n mainVideoEl = null\n }\n}\n\n// 小视频元素就绪\nfunction onSmallVideoReady(identity: string, el: HTMLVideoElement | null) {\n const session = getSession()\n if (!session) return\n if (el) {\n participantVideoEls.set(identity, el)\n if (identity === session.identity) {\n // 自己的 tile:attach 本地视频(非主持人在小视频列表里看不到自己就是因为这里跳过了)\n session.attachLocalVideo(el)\n } else {\n session.attachParticipantVideo(identity, el)\n }\n } else {\n participantVideoEls.delete(identity)\n }\n}\n\nasync function syncDeviceCapability() {\n try {\n const devices = await navigator.mediaDevices.enumerateDevices()\n hasCamera.value = devices.some((d) => d.kind === 'videoinput')\n hasMic.value = devices.some((d) => d.kind === 'audioinput')\n if (!hasCamera.value) cameraEnabled.value = false\n if (!hasMic.value) micEnabled.value = false\n } catch (e) {\n console.warn('[atim] enumerateDevices 失败', e)\n }\n}\n\nfunction triggerLayout() {\n nextTick(layout)\n}\n\n// 布局计算:从 design/video-conference-layout.html layout() 函数搬\n// 所有尺寸(mwW/sidebarW/mainW/smallW)无条件计算,避免小视频列表为空时窗口宽度塌陷\nfunction layout() {\n const content = contentEl.value\n const mainVideo = mainVideoWrap.value\n const sidebar = sidebarEl.value\n const videoListEl = videoListPanelRef.value?.listEl as HTMLElement | undefined\n const items = videoListEl?.querySelectorAll<HTMLElement>('.small-video') ?? []\n if (!content || !mainVideo || !sidebar) return\n\n // 1. 主视频 16:9,高度 = content 高度\n const contentH = content.clientHeight\n if (contentH <= 0) return\n const mainH = contentH\n const mainW = (mainH * 16) / 9\n\n // 2. 小视频 16:9,3 个 + 3 个 gap 填满 sidebar 可用高度\n const smallGap = 8\n const tabBarH = tabBarWrap.value?.offsetHeight ?? 28\n const smallH = (mainH - tabBarH - smallGap * 3) / 3\n const smallW = (smallH * 16) / 9\n\n // 3. sidebar 宽度 = 小视频宽(不再保留自定义滚动条空间,列表与 tab 等宽)\n const sidebarW = smallW\n\n // 4. 窗口总宽度(padding + main + gap + sidebar + padding)—— 左右 padding 对称\n const padding = 10\n const gap = 10\n const mwW = padding + mainW + gap + sidebarW + padding\n\n // 5. 应用到 DOM(items 为空也照常设,保持窗口宽度稳定)\n const mw = document.getElementById('meetingWindow') as HTMLElement | null\n if (mw) {\n mw.style.width = mwW + 'px'\n }\n\n mainVideo.style.width = mainW + 'px'\n mainVideo.style.height = mainH + 'px'\n\n // sidebar 宽度 = smallW,与 list 完全对齐\n sidebar.style.width = sidebarW + 'px'\n sidebar.style.height = contentH + 'px'\n\n if (videoListEl) {\n // list 撑满 sidebar,与 tab-bar 等宽\n videoListEl.style.width = '100%'\n videoListEl.style.height = contentH - tabBarH - smallGap + 'px'\n }\n\n items.forEach((item) => {\n // 小视频保持 16:9\n item.style.width = smallW + 'px'\n // 不取整:Math.floor 会让 3 个 small 累计损失 ~2px,导致最后 small 底部与主视频底部错位\n item.style.height = smallH + 'px'\n })\n}\n\n// ============ 设备/会议控制 ============\nasync function toggleMic() {\n if (!hasMic.value) return\n const session = getSession()\n if (!session) return\n const next = !micEnabled.value\n try {\n await session.setMicEnabled(next)\n micEnabled.value = next\n } catch (err) {\n console.warn('[atim] setMicEnabled 失败,UI 不切换', err)\n }\n}\n\nasync function toggleCamera() {\n if (!hasCamera.value) return\n const session = getSession()\n if (!session) return\n const next = !cameraEnabled.value\n try {\n await session.setCameraEnabled(next)\n cameraEnabled.value = next\n nextTick(reattachAllVideos)\n } catch (err) {\n console.warn('[atim] setCameraEnabled 失败,UI 不切换', err)\n }\n}\n\nasync function toggleSpeaker() {\n const session = getSession()\n if (!session) return\n speakerMuted.value = !speakerMuted.value\n await session.setSpeakerMuted(speakerMuted.value)\n}\n\n// 是否显示「解散会议」确认弹窗\nconst showDissolveConfirm = ref(false)\nconst dissolvePending = ref(false)\n\nasync function leaveMeeting() {\n const client = getClient()\n if (!client) return\n // 当前主持人才能解散;非主持人(含已转让的前主持人)直接走普通 leave\n if (!isCurrentHost.value) {\n await client.meeting?.leave()\n // 主窗口立即隐藏,独立倒计时 toast 提示后由 toast 触发 close\n endedReason.value = '已离开'\n return\n }\n // host:查房内参与者数量\n // - count > 1:房内还有其他人,弹\"解散会议\"确认(让 host 明确选择 Leave 还是 Dissolve)\n // - count <= 1:只剩 host 自己——直接 dissolve,避免遗留僵尸会议让离线 invitee 上线后收到 stale 邀请\n // 之前的 bug:count=1 时走 leave,只改 host 自己 invite.status=left,meeting.status 仍 active,\n // 其他 invitee 的 status 仍 invited → pending-invites 返回 stale 邀请\n let count = 1\n try {\n count = await client.meeting.getParticipantCount()\n } catch (err) {\n console.warn('[atim] 查参与者数量失败,按 1 处理(自动解散)', err)\n }\n if (count > 1) {\n showDissolveConfirm.value = true\n return\n }\n dissolvePending.value = true\n try {\n await client.meeting.dissolve()\n endedReason.value = '会议已解散'\n } catch (err) {\n // dissolve 失败兜底:后端 Leave 内部也会做同样的自动 dissolve 兜底,但前端先失败说明网络层有问题\n console.error('[atim] host 单独时解散失败,回退到 leave', err)\n endedReason.value = '已离开'\n await client.meeting?.leave().catch(() => {})\n } finally {\n dissolvePending.value = false\n }\n}\n\n// 确认解散\nasync function confirmDissolve() {\n const client = getClient()\n if (!client) return\n showDissolveConfirm.value = false\n dissolvePending.value = true\n try {\n await client.meeting.dissolve()\n // 解散成功:endedReason 让 ended-overlay 显示;broadcast 到达后 SDK 也会触发 ended 事件\n endedReason.value = '会议已解散'\n } catch (err) {\n console.error('[atim] 解散失败', err)\n endedReason.value = '解散失败'\n } finally {\n dissolvePending.value = false\n }\n}\n\nfunction cancelDissolve() {\n showDissolveConfirm.value = false\n}\n\nasync function kickParticipant(uid: string) {\n const session = getSession()\n if (!session) return\n try {\n await session.kick(getClient().transport, uid)\n await refreshMembers(true)\n } catch (err) {\n console.warn('[atim] kickParticipant 失败', err)\n }\n}\n\nasync function transferHost(uid: string) {\n const session = getSession()\n if (!session) return\n try {\n await session.transferHost(getClient().transport, uid)\n hostIdentity.value = uid\n await refreshMembers(true)\n } catch (err) {\n console.warn('[atim] transferHost 失败', err)\n }\n}\n\n// 主持人再次邀请某成员(等待加入/已离会状态)\nasync function reinviteMember(uid: string) {\n const session = getSession()\n if (!session) return\n try {\n await getClient().meeting.reinvite(session.meetingId, uid)\n await refreshMembers(true)\n } catch (err: unknown) {\n // 后端对被踢者(status=removed)返回 target_was_kicked——成员列表已过滤 removed,\n // 但邀请弹窗候选来自全量用户,仍可能选中被踢者。映射成中文提示,避免静默失败。\n const msg = err instanceof Error ? err.message : ''\n if (msg.includes('target_was_kicked')) {\n pushToast('该成员已被踢出会议,无法再次邀请')\n } else if (msg.includes('meeting_ended')) {\n pushToast('会议已结束')\n } else {\n pushToast('再次邀请失败,请重试')\n }\n console.warn('[atim] reinvite 失败', err)\n }\n}\n\n// 监听 hostChanged 广播:切换主视频\nfunction onHostChangedEvent(e: MeetingHostChangedEvent) {\n const session = getSession()\n if (!session || e.meetingId !== session.meetingId) return\n hostIdentity.value = e.hostUid\n // 主视频元素会被 v-if 重新挂载,nextTick 后 reattach\n nextTick(() => {\n refreshMembers(true)\n })\n}\n\n// 监听解散广播:主持人解散后所有参与人收到,立即隐藏视频窗口,只留独立倒计时提示。\n// 不依赖 session 'ended' 事件(它要等 sess.end() 的 async disconnect 完成才触发,有延迟)。\nfunction onDissolvedEvent(e: MeetingDissolvedEvent) {\n const session = getSession()\n if (!session || e.meetingId !== session.meetingId) return\n if (!endedReason.value) endedReason.value = '会议已解散'\n if (timer) clearInterval(timer)\n}\n\n// 监听被踢广播:立即隐藏视频窗口\nfunction onKickedEvent(e: { meetingId: string; by: string }) {\n const session = getSession()\n if (!session || e.meetingId !== session.meetingId) return\n if (!endedReason.value) endedReason.value = '已被移出会议'\n if (timer) clearInterval(timer)\n}\n\nfunction formatDuration(s: number) {\n const h = Math.floor(s / 3600)\n const m = Math.floor((s % 3600) / 60)\n .toString()\n .padStart(2, '0')\n const sec = (s % 60).toString().padStart(2, '0')\n return h > 0 ? `${h}:${m}:${sec}` : `${m}:${sec}`\n}\n\nonMounted(async () => {\n await syncDeviceCapability()\n const client = getClient()\n if (!client) return\n const session = client.meeting?.currentSession\n if (!session) return\n\n hostIdentity.value = session.hostIdentity || session.identity\n\n session.onSession('connected', () => {\n refreshMembers()\n nextTick(layout)\n timer = setInterval(() => {\n duration.value++\n // 每 2 秒同时刷一次成员列表(muted/speaking 实时态)\n if (duration.value % 2 === 0) refreshMembers()\n }, 1000)\n })\n session.onSession('participantJoined', () => {\n refreshMembers(true)\n })\n session.onSession('participantLeft', () => {\n refreshMembers(true)\n })\n session.onSession('participantTrackSubscribed', (info: { kind: string; identity: string }) => {\n if (info.kind !== 'video') return\n // 优先用小视频池的元素\n const el = participantVideoEls.get(info.identity)\n if (el) {\n session.attachParticipantVideo(info.identity, el)\n }\n // 主视频位置的 track 流入:reattach 主视频(mainVideoEl 不在 participantVideoEls 里)\n if (mainVideoIdentity === info.identity) {\n nextTick(reattachAllVideos)\n }\n })\n // 发言态:LiveKit 给全量列表,覆盖式更新 members[].speaking\n // refreshMembers 每 2 秒会用后端 speaking=false 覆盖,下个 activeSpeakersChanged 事件会再次纠正——可接受\n // 就地修改成员字段,仅在状态变化时替换数组引用触发响应式\n session.onSession('activeSpeakersChanged', (info: { identities: string[] }) => {\n const set = new Set(info.identities)\n let changed = false\n for (const m of members.value) {\n const speaking = set.has(m.identity)\n if (m.speaking !== speaking) {\n m.speaking = speaking\n changed = true\n }\n }\n if (changed) members.value = [...members.value]\n })\n session.onSession('devices', (info: { mic: boolean; camera: boolean; errors: string[] }) => {\n if (info.errors && info.errors.length > 0) {\n deviceErrors.value = info.errors\n setTimeout(() => (deviceErrors.value = []), 3000)\n }\n // 同步设备实际开启状态(enableDevices 可能 mic/camera 开启失败,UI 需据此更新按钮)\n if (!info.mic) micEnabled.value = false\n if (!info.camera) cameraEnabled.value = false\n if (info.camera) nextTick(reattachAllVideos)\n })\n session.onSession('ended', () => {\n // 解散/被踢/对端断开都会触发——独立倒计时浮窗提示,5s 后自动关或用户手动关\n if (!endedReason.value) endedReason.value = '会议已结束'\n if (timer) clearInterval(timer)\n })\n\n // race 修复:mount 之前可能已 connected\n if (session.isConnected) {\n refreshMembers()\n nextTick(layout)\n timer = setInterval(() => {\n duration.value++\n if (duration.value % 2 === 0) refreshMembers()\n }, 1000)\n }\n\n // 监听 host 转让广播(所有客户端)\n client.on('meeting:hostChanged', onHostChangedEvent)\n // 监听解散/被踢广播:参与人端立即隐藏视频窗口(不等 sess.end 的 async 完成)\n client.on('meeting:dissolved', onDissolvedEvent)\n client.on('meeting:kicked', onKickedEvent)\n\n // 布局双保险:ResizeObserver + window resize\n if (contentEl.value) {\n resizeObserver = new ResizeObserver(() => layout())\n resizeObserver.observe(contentEl.value)\n }\n window.addEventListener('resize', layout)\n nextTick(layout)\n})\n\nonUnmounted(() => {\n if (timer) clearInterval(timer)\n if (actionErrorTimer) clearTimeout(actionErrorTimer)\n if (resizeObserver) {\n resizeObserver.disconnect()\n resizeObserver = null\n }\n window.removeEventListener('resize', layout)\n const client = getClient()\n client?.off?.('meeting:hostChanged', onHostChangedEvent)\n client?.off?.('meeting:dissolved', onDissolvedEvent)\n client?.off?.('meeting:kicked', onKickedEvent)\n})\n</script>\n\n<template>\n <div\n id=\"meetingWindow\"\n v-show=\"!minimized && !endedReason\"\n v-draggable=\"'.meeting-header'\"\n class=\"meeting-window\"\n :style=\"{ height: height || '80vh' }\"\n >\n <MeetingHeader\n :title=\"title\"\n :duration=\"formatDuration(duration)\"\n @close=\"leaveMeeting\"\n @minimize=\"minimized = true\"\n />\n\n <div v-if=\"deviceErrors.length\" class=\"device-warn\">\n {{ deviceErrors.join('; ') }}\n </div>\n <div v-if=\"actionError\" class=\"device-warn\">\n {{ actionError }}\n </div>\n\n <div ref=\"contentEl\" class=\"content\">\n <!-- 主视频:当前主持人 -->\n <div ref=\"mainVideoWrap\">\n <MainVideoTile\n v-if=\"mainVideoMember\"\n ref=\"mainVideoRef\"\n :identity=\"mainVideoMember.identity\"\n :name=\"mainVideoMember.name\"\n :muted=\"mainVideoMember.muted\"\n :speaking=\"mainVideoMember.speaking\"\n :is-host=\"mainVideoMember.isHost\"\n :is-self=\"mainVideoMember.identity === (getSession()?.identity || '')\"\n :on-video-el-ready=\"onMainVideoReady\"\n />\n </div>\n\n <aside ref=\"sidebarEl\" class=\"sidebar\">\n <div ref=\"tabBarWrap\">\n <SidebarTabs v-model:active-tab=\"activeTab\" @add-member=\"emit('addMember')\" />\n </div>\n <!-- 视频列表 tab -->\n <VideoListPanel\n v-show=\"activeTab === 'video'\"\n ref=\"videoListPanelRef\"\n :members=\"smallVideoMembers\"\n :on-tile-mount=\"onSmallVideoReady\"\n />\n <!-- 成员列表 tab -->\n <MemberListPanel\n v-show=\"activeTab === 'member'\"\n :members=\"sortedMembers\"\n :can-promote=\"isCurrentHost\"\n :can-kick=\"isCurrentHost\"\n :can-reinvite=\"isCurrentHost\"\n @transfer-host=\"(uid: string) => transferHost(uid)\"\n @kick=\"(uid: string) => kickParticipant(uid)\"\n @reinvite=\"(uid: string) => reinviteMember(uid)\"\n />\n </aside>\n </div>\n\n <!-- 底部控制按钮区:水平居中,全图标,hover 显示当前状态 -->\n <div class=\"bottom-controls\">\n <button\n v-if=\"hasMic\"\n class=\"ctrl-btn\"\n :class=\"{ off: !micEnabled }\"\n type=\"button\"\n :title=\"micEnabled ? '麦克风已开' : '麦克风已关'\"\n @click=\"toggleMic\"\n >\n <Icon :name=\"micEnabled ? 'mic-on' : 'mic-off'\" :size=\"22\" />\n </button>\n <button\n v-if=\"hasCamera\"\n class=\"ctrl-btn\"\n :class=\"{ off: !cameraEnabled }\"\n type=\"button\"\n :title=\"cameraEnabled ? '摄像头已开' : '摄像头已关'\"\n @click=\"toggleCamera\"\n >\n <Icon :name=\"cameraEnabled ? 'camera-on' : 'camera-off'\" :size=\"22\" />\n </button>\n <button\n class=\"ctrl-btn\"\n :class=\"{ off: speakerMuted }\"\n type=\"button\"\n :title=\"speakerMuted ? '扬声器已关' : '扬声器已开'\"\n @click=\"toggleSpeaker\"\n >\n <Icon :name=\"speakerMuted ? 'sound-off' : 'sound-on'\" :size=\"22\" />\n </button>\n <button class=\"ctrl-btn hangup\" type=\"button\" title=\"离开会议\" @click=\"leaveMeeting\">\n <Icon name=\"hangup\" :size=\"22\" />\n </button>\n </div>\n </div>\n\n <!-- 解散会议确认弹窗:独立于主窗口,最小化态下也能显示 -->\n <ConfirmDialog\n v-if=\"showDissolveConfirm\"\n title=\"解散会议\"\n message=\"会议中还有其他成员,确认要解散会议吗?所有成员将被强制离会。\"\n confirm-text=\"解散会议\"\n cancel-text=\"取消\"\n :danger=\"true\"\n @confirm=\"confirmDissolve\"\n @cancel=\"cancelDissolve\"\n />\n\n <!-- 会议结束/离开倒计时提示:独立浮窗,不依赖主窗口 -->\n <MeetingEndedToast\n v-if=\"endedReason\"\n :reason=\"endedReason\"\n :duration=\"5\"\n @close=\"emit('close')\"\n />\n\n <!-- 最小化浮条:可拖拽,单行显示发言人/会议名称 + 时长 + 还原/关闭按钮 -->\n <div v-if=\"minimized && !endedReason\" class=\"mini-bar\" v-draggable=\"'.mini-bar-body'\">\n <div class=\"mini-bar-body\">\n <!-- 有人发言时显示声波动效 + 发言人;无人发言显示会议名称(同一位置切换) -->\n <span v-if=\"speakerDisplayText\" class=\"speaking-indicator\">\n <span class=\"wave-bar\"></span>\n <span class=\"wave-bar\"></span>\n <span class=\"wave-bar\"></span>\n </span>\n <span class=\"mini-main-text\" :class=\"{ speaking: !!speakerDisplayText }\">\n {{ speakerDisplayText || title }}\n </span>\n <span class=\"mini-duration\">{{ formatDuration(duration) }}</span>\n </div>\n <div class=\"mini-bar-actions\">\n <button\n class=\"mini-btn\"\n type=\"button\"\n aria-label=\"还原窗口\"\n title=\"还原窗口\"\n v-html=\"MaximizeIcon\"\n @click=\"minimized = false\"\n ></button>\n <button\n class=\"mini-btn mini-close\"\n type=\"button\"\n aria-label=\"离开会议\"\n title=\"离开会议\"\n v-html=\"CloseIcon\"\n @click=\"leaveMeeting\"\n ></button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.meeting-window {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n /* height 由 prop 控制(inline style 注入),未传时 fallback 80vh */\n width: var(--mw-w, min(1200px, 95vw));\n background: var(--atim-meeting-bg-panel, #0f172a);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n margin: 0 auto;\n padding: 10px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n color: var(--atim-meeting-text-primary, #fff);\n z-index: 9999;\n border-radius: 0;\n /* 与 design/video-conference-layout.html :root 变量一一对应 */\n --atim-meeting-bg-body: #0b1220;\n --atim-meeting-bg-panel: #0f172a;\n --atim-meeting-bg-video: #000000;\n --atim-meeting-text-primary: #ffffff;\n --atim-meeting-text-secondary: rgba(255, 255, 255, 0.85);\n --atim-meeting-text-tertiary: rgba(255, 255, 255, 0.55);\n --atim-meeting-border: rgba(255, 255, 255, 0.08);\n --atim-meeting-scrollbar: rgba(255, 255, 255, 0.15);\n --atim-meeting-scrollbar-hover: rgba(255, 255, 255, 0.35);\n --atim-meeting-tab-active: #1e293b;\n --atim-meeting-tab-inactive: #111827;\n --atim-meeting-speaking: #22c55e;\n --atim-meeting-online: #22c55e;\n --atim-meeting-offline: #6b7280;\n --atim-meeting-overlay-gap: 5px;\n}\n.content {\n flex: 1 1 0;\n min-height: 0;\n display: flex;\n flex-direction: row;\n gap: 10px;\n padding: 0;\n overflow: hidden;\n margin-top: 5px;\n}\n.content > div:first-child {\n flex: 0 0 auto;\n}\n.sidebar {\n flex: 0 0 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n /* 比会议窗口背景 #0f172a 稍深一点的暗色,让空列表区域不显得空洞 */\n background: #0b1220;\n /* tab-bar 与 list 之间的 8px 间距由 sidebar flex gap 直接管理,\n 不依赖子组件内部 margin(避免 BFC 包含 margin 导致 offsetHeight 多算 8px) */\n gap: 8px;\n overflow: hidden;\n}\n/* 所有子节点撑满 sidebar 宽度:tab-bar / video list / member list 三者左右对齐 */\n.sidebar > * {\n width: 100%;\n}\n.sidebar > div:first-child {\n flex: 0 0 auto;\n}\n/* 设备失败提示 */\n.device-warn {\n position: absolute;\n top: 44px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(212, 160, 23, 0.92);\n color: var(--atim-meeting-text-primary, #fff);\n font-size: 12px;\n padding: 4px 12px;\n border-radius: 0;\n z-index: 6;\n pointer-events: none;\n max-width: 80%;\n text-align: center;\n}\n/* 底部控制按钮区:水平居中、常驻显示、紧凑高度 */\n.bottom-controls {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 6px 0 2px;\n z-index: 4;\n}\n.ctrl-btn {\n width: 32px;\n height: 32px;\n /* 无边框无背景:仅图标 */\n border: none;\n background: transparent;\n color: var(--atim-meeting-text-primary, #fff);\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n transition:\n transform 0.1s,\n opacity 0.15s;\n opacity: 0.85;\n}\n.ctrl-btn:hover {\n opacity: 1;\n}\n.ctrl-btn:active {\n transform: scale(0.92);\n}\n/* 开关关闭状态:图标变红 */\n.ctrl-btn.off {\n color: #ef4444;\n opacity: 1;\n}\n/* 挂断按钮:图标红色(无红色背景块) */\n.ctrl-btn.hangup {\n color: #ef4444;\n}\n.ctrl-btn.hangup:hover {\n color: #f45f5b;\n}\n\n/* ============ 最小化浮条 ============ */\n.mini-bar {\n position: fixed;\n right: 20px;\n bottom: 20px;\n z-index: 9999;\n display: flex;\n align-items: stretch;\n max-width: 320px;\n min-width: 220px;\n background: var(--atim-meeting-bg-panel, #0f172a);\n border: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.12));\n border-radius: 0;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n color: var(--atim-meeting-text-primary, #fff);\n overflow: hidden;\n}\n/* 拖拽区:单行——发言人/会议名称 + 时长 */\n.mini-bar-body {\n flex: 1 1 auto;\n min-width: 0;\n padding: 8px 10px;\n cursor: move;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n/* 主体文本:有人发言时显示发言人(绿色),无人发言显示会议名称(白色) */\n.mini-main-text {\n font-size: 13px;\n font-weight: 500;\n color: var(--atim-meeting-text-primary, #fff);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n flex: 1 1 auto;\n min-width: 0;\n}\n.mini-main-text.speaking {\n color: var(--atim-meeting-speaking, #22c55e);\n font-weight: 600;\n}\n.mini-duration {\n font-size: 11px;\n color: var(--atim-meeting-text-tertiary, rgba(255, 255, 255, 0.55));\n font-variant-numeric: tabular-nums;\n flex: 0 0 auto;\n}\n/* 声波动效:复用 MainVideoTile 的 wave-bar 三柱动效 + 颜色变量 */\n.mini-bar-body .speaking-indicator {\n display: inline-flex;\n align-items: flex-end;\n gap: 2px;\n flex: 0 0 auto;\n}\n.mini-bar-body .wave-bar {\n width: 2px;\n background: var(--atim-meeting-speaking, #22c55e);\n animation: mini-wave 1s ease-in-out infinite;\n}\n.mini-bar-body .wave-bar:nth-child(1) {\n height: 6px;\n animation-delay: 0s;\n}\n.mini-bar-body .wave-bar:nth-child(2) {\n height: 10px;\n animation-delay: 0.15s;\n}\n.mini-bar-body .wave-bar:nth-child(3) {\n height: 6px;\n animation-delay: 0.3s;\n}\n@keyframes mini-wave {\n 0%,\n 100% {\n transform: scaleY(1);\n opacity: 0.7;\n }\n 50% {\n transform: scaleY(1.6);\n opacity: 1;\n }\n}\n/* 操作按钮区 */\n.mini-bar-actions {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 0 4px;\n border-left: 1px solid var(--atim-meeting-border, rgba(255, 255, 255, 0.08));\n}\n.mini-btn {\n width: 26px;\n height: 100%;\n min-height: 36px;\n border: none;\n background: transparent;\n color: var(--atim-meeting-text-secondary, rgba(255, 255, 255, 0.85));\n cursor: pointer;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n background 0.2s,\n color 0.2s;\n}\n.mini-btn:hover {\n background: rgba(255, 255, 255, 0.1);\n color: var(--atim-meeting-text-primary, #fff);\n}\n.mini-btn :deep(svg) {\n width: 16px;\n height: 16px;\n}\n.mini-btn.mini-close:hover {\n background: #e53935;\n color: #fff;\n}\n</style>\n","export default \"data:audio/mpeg;base64,SUQzBAAAAAAiNlRDT04AAAAHAAADQmx1ZXMAUFJJVgAAIXQAAFhNUAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0MyA3OS4xNjEyMTAsIDIwMTcvMDgvMTEtMTA6Mjg6MzYgICAgICAgICI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICB4bWxuczp4bXBETT0iaHR0cDovL25zLmFkb2JlLmNvbS94bXAvMS4wL0R5bmFtaWNNZWRpYS8iCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1wOkNyZWF0b3JUb29sPSJGaWxlIGNyZWF0ZWQgYnkgR29sZFdhdmUuICBHb2xkV2F2ZSBjb3B5cmlnaHQgKEMpIENocmlzIENyYWlnIgogICB4bXA6TWV0YWRhdGFEYXRlPSIyMDE3LTExLTAxVDE5OjI1OjUyKzA4OjAwIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAxNy0xMS0wMVQxOToyNTo1MiswODowMCIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDplZWRmZWNkMi0xNTYzLWJmNDUtOTQxZS03OWJjMzhjZmU0OGEiCiAgIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ZWVkZmVjZDItMTU2My1iZjQ1LTk0MWUtNzliYzM4Y2ZlNDhhIgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NmI2MmI5ZGEtOThmNC03NjRkLWE2MGItMmZlOGFkYTZkMjYxIgogICBkYzpmb3JtYXQ9ImF1ZGlvL21wZWciPgogICA8eG1wRE06VHJhY2tzPgogICAgPHJkZjpCYWc+CiAgICAgPHJkZjpsaQogICAgICB4bXBETTp0cmFja05hbWU9IkN1ZVBvaW50IE1hcmtlcnMiCiAgICAgIHhtcERNOnRyYWNrVHlwZT0iQ3VlIgogICAgICB4bXBETTpmcmFtZVJhdGU9ImY4MDAwIi8+CiAgICAgPHJkZjpsaQogICAgICB4bXBETTp0cmFja05hbWU9IkNEIFRyYWNrIE1hcmtlcnMiCiAgICAgIHhtcERNOnRyYWNrVHlwZT0iVHJhY2siCiAgICAgIHhtcERNOmZyYW1lUmF0ZT0iZjgwMDAiLz4KICAgICA8cmRmOmxpCiAgICAgIHhtcERNOnRyYWNrTmFtZT0iU3ViY2xpcCBNYXJrZXJzIgogICAgICB4bXBETTp0cmFja1R5cGU9IkluT3V0IgogICAgICB4bXBETTpmcmFtZVJhdGU9ImY4MDAwIi8+CiAgICA8L3JkZjpCYWc+CiAgIDwveG1wRE06VHJhY2tzPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2YjYyYjlkYS05OGY0LTc2NGQtYTYwYi0yZmU4YWRhNmQyNjEiCiAgICAgIHN0RXZ0OndoZW49IjIwMTctMTEtMDFUMTk6MjU6NTIrMDg6MDAiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIEF1ZGl0aW9uIENDIDIwMTguMCAoV2luZG93cykiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii9tZXRhZGF0YSIvPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplZWRmZWNkMi0xNTYzLWJmNDUtOTQxZS03OWJjMzhjZmU0OGEiCiAgICAgIHN0RXZ0OndoZW49IjIwMTctMTEtMDFUMTk6MjU6NTIrMDg6MDAiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIEF1ZGl0aW9uIENDIDIwMTguMCAoV2luZG93cykiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogICA8eG1wTU06RGVyaXZlZEZyb20KICAgIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NmI2MmI5ZGEtOThmNC03NjRkLWE2MGItMmZlOGFkYTZkMjYxIgogICAgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo2YjYyYjlkYS05OGY0LTc2NGQtYTYwYi0yZmU4YWRhNmQyNjEiCiAgICBzdFJlZjpvcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NmI2MmI5ZGEtOThmNC03NjRkLWE2MGItMmZlOGFkYTZkMjYxIi8+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4AVFNTRQAAABMAAANVUFlVTiBUcmFuc2NvZGluZwAAAAAAAAAAAAAA/+PIwAAAAAAAAAAAAEluZm8AAAAPAAAAKwAAZUAAEREWFhwcIiIiJyctLTMzMzg4Pj5ERERJSU9PVVVVW1tgYGZmZmxscXF3d3d9fYKCiIiIjo6T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AExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+OIxAAAAANIAAAAAExBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqTEFNRTMuMTAwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\"","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount } from 'vue'\nimport type { CallInviteEvent } from '@quitting/contract'\n// 被叫响铃中循环播放 callee.mp3\nimport ringCalleeUrl from '../sounds/ring-callee.mp3'\nimport Icon from './Icon.vue'\n\nconst props = defineProps<{\n invite: CallInviteEvent\n onClose?: () => void\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n // 接听成功后携带 session 通知父组件渲染 CallWindow\n // 不能依赖父组件 watch currentSession——AtimClient 不是 reactive,watch 不会触发\n (e: 'accepted', session: unknown): void\n}>()\n\nconst ringAudio = ref<HTMLAudioElement | null>(null)\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nfunction startRing() {\n if (!ringAudio.value) return\n ringAudio.value.currentTime = 0\n ringAudio.value.play().catch(() => {\n // 自动播放策略可能拒绝,不报错\n })\n}\n\nfunction stopRing() {\n if (!ringAudio.value) return\n ringAudio.value.pause()\n ringAudio.value.currentTime = 0\n}\n\nlet unsubCancelled: (() => void) | null = null\n\nonMounted(() => {\n startRing()\n // 主叫取消时被叫端响铃应自动关\n const client = getClient()\n if (client) {\n unsubCancelled = client.on('call:cancelled', () => {\n stopRing()\n emit('close')\n })\n }\n})\n\nonBeforeUnmount(() => {\n stopRing()\n if (unsubCancelled) unsubCancelled()\n})\n\nasync function accept() {\n stopRing()\n const client = getClient()\n try {\n const session = await client?.call?.accept(props.invite.callId, props.invite.callType)\n emit('accepted', session)\n emit('close')\n } catch (err: any) {\n // 规则1:多端并发进入,本端是后进端被拒(另一端已接听)\n const msg = String(err?.message || err || '')\n if (msg.includes('conflict') || msg.includes('entered elsewhere')) {\n alert('已在其他设备接听,本端通话已关闭')\n } else {\n alert('网络异常,请稍后重试')\n }\n emit('close')\n }\n}\n\nasync function reject() {\n stopRing()\n const client = getClient()\n // 被叫拒接:currentSession 为 null(还没 accept),必须显式传 callId 让 SDK 调后端 /call/end\n await client?.call?.end('rejected', props.invite.callId)\n emit('close')\n}\n</script>\n\n<template>\n <div class=\"atim-incoming-ring\">\n <audio ref=\"ringAudio\" :src=\"ringCalleeUrl\" loop preload=\"auto\" />\n <div class=\"atim-ring-content\">\n <div class=\"atim-ring-avatar\"><Icon name=\"answer\" :size=\"24\" color=\"#fff\" /></div>\n <div class=\"atim-ring-info\">\n <div class=\"atim-ring-name\">{{ invite.from }}</div>\n <div class=\"atim-ring-type\">\n {{ invite.callType === 'video' ? '视频通话' : '语音通话' }}\n </div>\n </div>\n <div class=\"atim-ring-actions\">\n <button class=\"atim-reject\" @click=\"reject\">拒接</button>\n <button class=\"atim-accept\" @click=\"accept\">接听</button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.atim-incoming-ring {\n position: fixed;\n top: 24px;\n right: 24px;\n background: #0f172a;\n border-radius: 0;\n padding: 16px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n z-index: 10000;\n animation: slide-in 0.3s ease;\n}\n@keyframes slide-in {\n from {\n transform: translateX(120%);\n }\n to {\n transform: translateX(0);\n }\n}\n.atim-ring-content {\n display: flex;\n align-items: center;\n gap: 12px;\n min-width: 280px;\n}\n.atim-ring-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: #52c41a;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 24px;\n}\n.atim-ring-info {\n flex: 1;\n}\n.atim-ring-name {\n font-weight: 600;\n font-size: 15px;\n color: rgba(255, 255, 255, 0.85);\n}\n.atim-ring-type {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.55);\n margin-top: 2px;\n}\n.atim-ring-actions {\n display: flex;\n gap: 8px;\n}\n.atim-ring-actions button {\n padding: 6px 14px;\n border: none;\n border-radius: 0;\n cursor: pointer;\n font-size: 13px;\n color: #fff;\n}\n.atim-reject {\n background: #ef4444;\n}\n.atim-accept {\n background: #52c41a;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount } from 'vue'\nimport type { CallInviteEvent } from '@quitting/contract'\n// 被叫响铃中循环播放 callee.mp3\nimport ringCalleeUrl from '../sounds/ring-callee.mp3'\nimport Icon from './Icon.vue'\n\nconst props = defineProps<{\n invite: CallInviteEvent\n onClose?: () => void\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n // 接听成功后携带 session 通知父组件渲染 CallWindow\n // 不能依赖父组件 watch currentSession——AtimClient 不是 reactive,watch 不会触发\n (e: 'accepted', session: unknown): void\n}>()\n\nconst ringAudio = ref<HTMLAudioElement | null>(null)\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nfunction startRing() {\n if (!ringAudio.value) return\n ringAudio.value.currentTime = 0\n ringAudio.value.play().catch(() => {\n // 自动播放策略可能拒绝,不报错\n })\n}\n\nfunction stopRing() {\n if (!ringAudio.value) return\n ringAudio.value.pause()\n ringAudio.value.currentTime = 0\n}\n\nlet unsubCancelled: (() => void) | null = null\n\nonMounted(() => {\n startRing()\n // 主叫取消时被叫端响铃应自动关\n const client = getClient()\n if (client) {\n unsubCancelled = client.on('call:cancelled', () => {\n stopRing()\n emit('close')\n })\n }\n})\n\nonBeforeUnmount(() => {\n stopRing()\n if (unsubCancelled) unsubCancelled()\n})\n\nasync function accept() {\n stopRing()\n const client = getClient()\n try {\n const session = await client?.call?.accept(props.invite.callId, props.invite.callType)\n emit('accepted', session)\n emit('close')\n } catch (err: any) {\n // 规则1:多端并发进入,本端是后进端被拒(另一端已接听)\n const msg = String(err?.message || err || '')\n if (msg.includes('conflict') || msg.includes('entered elsewhere')) {\n alert('已在其他设备接听,本端通话已关闭')\n } else {\n alert('网络异常,请稍后重试')\n }\n emit('close')\n }\n}\n\nasync function reject() {\n stopRing()\n const client = getClient()\n // 被叫拒接:currentSession 为 null(还没 accept),必须显式传 callId 让 SDK 调后端 /call/end\n await client?.call?.end('rejected', props.invite.callId)\n emit('close')\n}\n</script>\n\n<template>\n <div class=\"atim-incoming-ring\">\n <audio ref=\"ringAudio\" :src=\"ringCalleeUrl\" loop preload=\"auto\" />\n <div class=\"atim-ring-content\">\n <div class=\"atim-ring-avatar\"><Icon name=\"answer\" :size=\"24\" color=\"#fff\" /></div>\n <div class=\"atim-ring-info\">\n <div class=\"atim-ring-name\">{{ invite.from }}</div>\n <div class=\"atim-ring-type\">\n {{ invite.callType === 'video' ? '视频通话' : '语音通话' }}\n </div>\n </div>\n <div class=\"atim-ring-actions\">\n <button class=\"atim-reject\" @click=\"reject\">拒接</button>\n <button class=\"atim-accept\" @click=\"accept\">接听</button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.atim-incoming-ring {\n position: fixed;\n top: 24px;\n right: 24px;\n background: #0f172a;\n border-radius: 0;\n padding: 16px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n z-index: 10000;\n animation: slide-in 0.3s ease;\n}\n@keyframes slide-in {\n from {\n transform: translateX(120%);\n }\n to {\n transform: translateX(0);\n }\n}\n.atim-ring-content {\n display: flex;\n align-items: center;\n gap: 12px;\n min-width: 280px;\n}\n.atim-ring-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: #52c41a;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 24px;\n}\n.atim-ring-info {\n flex: 1;\n}\n.atim-ring-name {\n font-weight: 600;\n font-size: 15px;\n color: rgba(255, 255, 255, 0.85);\n}\n.atim-ring-type {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.55);\n margin-top: 2px;\n}\n.atim-ring-actions {\n display: flex;\n gap: 8px;\n}\n.atim-ring-actions button {\n padding: 6px 14px;\n border: none;\n border-radius: 0;\n cursor: pointer;\n font-size: 13px;\n color: #fff;\n}\n.atim-reject {\n background: #ef4444;\n}\n.atim-accept {\n background: #52c41a;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onMounted, onBeforeUnmount, ref, computed } from 'vue'\nimport type { MeetingInviteEvent } from '@quitting/contract'\n// 响铃音频必须走 ESM import,Vite 才会处理资源 pipeline,运行时拿到带 hash 的真实 URL\n// 之前用 new Audio(\"./ring-callee.mp3\") 是相对页面 URL,demo public 目录没这个文件 → 404 → NotSupportedError\nimport ringCalleeUrl from '../sounds/ring-callee.mp3'\n\n// 会议邀请弹框:结构 / 样式严格对齐 design/meeting-invite-dialog.html\n// 调用方传 fromName 把 uid 翻译为业务侧用户名;不传则回退显示 invite.from\n\nconst props = defineProps<{\n invite: MeetingInviteEvent\n conflict?: boolean\n // 邀请者展示名:SDK UI 不知道 uid→name 映射,由调用方(如 demo 的 DEMO_USERS)解析后传入\n fromName?: string\n onClose?: () => void\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n (e: 'accepted', session: unknown): void\n}>()\n\n// 响铃控制:默认循环播放,可点击「关闭响铃」静音\nconst ringMuted = ref(false)\nlet audioEl: HTMLAudioElement | null = null\n\n// 顶部标题:根据 variant 描述邀请者的动作\nconst titleText = computed(() => {\n const name = props.fromName || props.invite.from\n const variant = props.invite.variant\n if (variant === 'rejoin') return '重新加入会议'\n if (variant === 'reinvite') return `${name} 再次邀请您入会`\n return `${name}邀请您入会`\n})\n\nfunction startRing() {\n if (audioEl) {\n audioEl.muted = ringMuted.value\n audioEl.currentTime = 0\n audioEl.play().catch((err) => {\n console.warn('[atim] 邀请响铃播放失败(可能是 autoplay 策略)', err)\n })\n }\n}\n\nfunction toggleMute() {\n ringMuted.value = !ringMuted.value\n if (audioEl) {\n audioEl.muted = ringMuted.value\n if (!ringMuted.value) {\n audioEl.play().catch(() => {})\n }\n }\n}\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\n// 把后端错误 message 映射成中文提示;解析失败回退通用文案\n// 后端错误关键字参考 server/internal/signaling/meeting.go 各处 errors.New\nfunction friendlyError(err: any): string {\n const msg = String(err?.message || err || '')\n if (msg.includes('meeting_ended') || msg.includes('meeting_not_found')) {\n return '会议已结束'\n }\n if (msg.includes('invite_status_invalid')) {\n return '邀请已失效'\n }\n if (msg.includes('not_invited')) {\n return '您未被邀请到该会议'\n }\n if (msg.includes('kicked')) {\n return '您已被移出该会议'\n }\n // 规则1:多端并发进入,本端是后进端,已被服务端拒绝\n if (msg.includes('conflict') || msg.includes('entered elsewhere')) {\n return '已在其他设备进入,本端邀请已失效'\n }\n // 规则2:已有待处理邀请或已在会话中\n if (msg.includes('busy') || msg.includes('pending')) {\n return '对方正忙,请稍后再试'\n }\n return '网络异常,请稍后重试'\n}\n\nasync function accept() {\n const client = getClient()\n if (!client) return\n if (props.conflict) {\n if (client.call?.currentSession) {\n await client.call.end('user_left').catch(() => {})\n } else if (client.meeting?.currentSession) {\n await client.meeting.leave().catch(() => {})\n }\n }\n try {\n const session = await client.meeting.respond(\n props.invite.meetingId,\n 'accept',\n props.invite.title,\n )\n if (session) {\n emit('accepted', session)\n }\n emit('close')\n } catch (err: any) {\n console.error('[atim] 会议接听失败', err)\n // 用户场景:邀请弹窗未及时收到 dissolved 事件(如断线期间),\n // 点击\"加入\"时后端拒绝——必须给明确提示,不能静默关闭\n alert(friendlyError(err))\n emit('close')\n }\n}\n\nasync function reject() {\n const client = getClient()\n // reject 在后端等价于主动离会(status=left),与离会行为一致\n // 失败仅记录日志,不阻塞关闭弹窗(reject 失败不影响他人)\n try {\n await client?.meeting?.respond(props.invite.meetingId, 'reject')\n } catch (err: any) {\n console.warn('[atim] 会议拒绝失败', err)\n }\n emit('close')\n}\n\nonMounted(() => {\n // 响铃:复用 ring-callee.mp3(被叫响铃,语义匹配)\n audioEl = new Audio(ringCalleeUrl)\n audioEl.loop = true\n audioEl.preload = 'auto'\n startRing()\n})\n\nonBeforeUnmount(() => {\n if (audioEl) {\n audioEl.pause()\n audioEl = null\n }\n})\n</script>\n\n<template>\n <div class=\"invite-dialog\">\n <div class=\"dialog-title\">{{ titleText }}</div>\n <div class=\"meeting-info\">\n <span class=\"info-label\">会议名:</span>\n <span class=\"info-value\">{{ invite.title }}</span>\n </div>\n <div class=\"dialog-actions\">\n <button class=\"btn btn-ghost\" type=\"button\" @click=\"toggleMute\">\n {{ ringMuted ? '恢复响铃' : '关闭响铃' }}\n </button>\n <button class=\"btn btn-secondary\" type=\"button\" @click=\"reject\">拒绝</button>\n <button class=\"btn btn-primary\" type=\"button\" @click=\"accept\">\n {{ conflict ? '切换' : '加入' }}\n </button>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* 颜色变量:同时兼容深浅背景 */\n.invite-dialog {\n --dialog-bg: rgba(255, 255, 255, 0.92);\n --dialog-bg-dark: rgba(18, 22, 32, 0.92);\n --text-primary: rgba(0, 0, 0, 0.88);\n --text-secondary: rgba(0, 0, 0, 0.55);\n --text-primary-dark: rgba(255, 255, 255, 0.92);\n --text-secondary-dark: rgba(255, 255, 255, 0.55);\n --border: rgba(0, 0, 0, 0.08);\n --border-dark: rgba(255, 255, 255, 0.1);\n --accent: #10b981;\n --accent-hover: #059669;\n --danger: #ef4444;\n --danger-hover: #dc2626;\n\n width: 260px;\n padding: 14px;\n /* 玻璃拟态:背景模糊 + 半透明白色 */\n background: var(--dialog-bg);\n backdrop-filter: blur(20px) saturate(180%);\n -webkit-backdrop-filter: blur(20px) saturate(180%);\n border: 1px solid rgba(255, 255, 255, 0.5);\n border-radius: 16px;\n box-shadow:\n 0 1px 2px rgba(0, 0, 0, 0.02),\n 0 4px 8px rgba(0, 0, 0, 0.04),\n 0 8px 16px rgba(0, 0, 0, 0.06),\n 0 16px 32px rgba(0, 0, 0, 0.08);\n color: var(--text-primary);\n position: relative;\n overflow: hidden;\n}\n\n/* 顶部柔和光条 */\n.invite-dialog::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 1px;\n background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);\n pointer-events: none;\n}\n\n/* 深色背景适配:用 .dark 类切换 */\n.invite-dialog.dark {\n background: var(--dialog-bg-dark);\n border-color: rgba(255, 255, 255, 0.12);\n color: var(--text-primary-dark);\n}\n.invite-dialog.dark .info-label {\n color: var(--text-secondary-dark);\n}\n.invite-dialog.dark .info-value {\n color: var(--text-primary-dark);\n}\n.invite-dialog.dark .btn-secondary {\n border-color: var(--border-dark);\n color: rgba(255, 255, 255, 0.8);\n}\n.invite-dialog.dark .btn-ghost {\n color: rgba(255, 255, 255, 0.5);\n}\n.invite-dialog.dark .btn-secondary:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(255, 255, 255, 0.2);\n}\n.invite-dialog.dark .btn-secondary.danger {\n border-color: rgba(239, 68, 68, 0.5);\n color: rgba(255, 255, 255, 0.9);\n}\n.invite-dialog.dark .btn-secondary.danger:hover {\n background: rgba(239, 68, 68, 0.15);\n border-color: rgba(239, 68, 68, 0.7);\n}\n\n.dialog-title {\n font-size: 13px;\n font-weight: 600;\n line-height: 1.4;\n margin-bottom: 8px;\n}\n\n.meeting-info {\n display: flex;\n align-items: baseline;\n gap: 4px;\n margin-bottom: 12px;\n font-size: 12px;\n}\n\n.info-label {\n color: var(--text-secondary);\n white-space: nowrap;\n}\n\n.info-value {\n color: var(--text-primary);\n font-weight: 600;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dialog-actions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 6px;\n}\n\n.btn {\n height: 26px;\n padding: 0 10px;\n border: 1px solid transparent;\n background: transparent;\n color: var(--text-primary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n}\n\n.btn:hover {\n color: var(--text-primary);\n}\n\n.btn-primary {\n background: var(--accent);\n color: #fff;\n box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);\n}\n.btn-primary:hover {\n background: var(--accent-hover);\n box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);\n transform: translateY(-1px);\n}\n\n.btn-secondary {\n border-color: var(--border);\n color: var(--text-primary);\n}\n.btn-secondary:hover {\n background: rgba(0, 0, 0, 0.04);\n border-color: rgba(0, 0, 0, 0.12);\n}\n\n.btn-ghost {\n padding: 0 6px;\n color: var(--text-secondary);\n}\n.btn-ghost:hover {\n color: var(--text-primary);\n background: rgba(0, 0, 0, 0.04);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onMounted, onBeforeUnmount, ref, computed } from 'vue'\nimport type { MeetingInviteEvent } from '@quitting/contract'\n// 响铃音频必须走 ESM import,Vite 才会处理资源 pipeline,运行时拿到带 hash 的真实 URL\n// 之前用 new Audio(\"./ring-callee.mp3\") 是相对页面 URL,demo public 目录没这个文件 → 404 → NotSupportedError\nimport ringCalleeUrl from '../sounds/ring-callee.mp3'\n\n// 会议邀请弹框:结构 / 样式严格对齐 design/meeting-invite-dialog.html\n// 调用方传 fromName 把 uid 翻译为业务侧用户名;不传则回退显示 invite.from\n\nconst props = defineProps<{\n invite: MeetingInviteEvent\n conflict?: boolean\n // 邀请者展示名:SDK UI 不知道 uid→name 映射,由调用方(如 demo 的 DEMO_USERS)解析后传入\n fromName?: string\n onClose?: () => void\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n (e: 'accepted', session: unknown): void\n}>()\n\n// 响铃控制:默认循环播放,可点击「关闭响铃」静音\nconst ringMuted = ref(false)\nlet audioEl: HTMLAudioElement | null = null\n\n// 顶部标题:根据 variant 描述邀请者的动作\nconst titleText = computed(() => {\n const name = props.fromName || props.invite.from\n const variant = props.invite.variant\n if (variant === 'rejoin') return '重新加入会议'\n if (variant === 'reinvite') return `${name} 再次邀请您入会`\n return `${name}邀请您入会`\n})\n\nfunction startRing() {\n if (audioEl) {\n audioEl.muted = ringMuted.value\n audioEl.currentTime = 0\n audioEl.play().catch((err) => {\n console.warn('[atim] 邀请响铃播放失败(可能是 autoplay 策略)', err)\n })\n }\n}\n\nfunction toggleMute() {\n ringMuted.value = !ringMuted.value\n if (audioEl) {\n audioEl.muted = ringMuted.value\n if (!ringMuted.value) {\n audioEl.play().catch(() => {})\n }\n }\n}\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\n// 把后端错误 message 映射成中文提示;解析失败回退通用文案\n// 后端错误关键字参考 server/internal/signaling/meeting.go 各处 errors.New\nfunction friendlyError(err: any): string {\n const msg = String(err?.message || err || '')\n if (msg.includes('meeting_ended') || msg.includes('meeting_not_found')) {\n return '会议已结束'\n }\n if (msg.includes('invite_status_invalid')) {\n return '邀请已失效'\n }\n if (msg.includes('not_invited')) {\n return '您未被邀请到该会议'\n }\n if (msg.includes('kicked')) {\n return '您已被移出该会议'\n }\n // 规则1:多端并发进入,本端是后进端,已被服务端拒绝\n if (msg.includes('conflict') || msg.includes('entered elsewhere')) {\n return '已在其他设备进入,本端邀请已失效'\n }\n // 规则2:已有待处理邀请或已在会话中\n if (msg.includes('busy') || msg.includes('pending')) {\n return '对方正忙,请稍后再试'\n }\n return '网络异常,请稍后重试'\n}\n\nasync function accept() {\n const client = getClient()\n if (!client) return\n if (props.conflict) {\n if (client.call?.currentSession) {\n await client.call.end('user_left').catch(() => {})\n } else if (client.meeting?.currentSession) {\n await client.meeting.leave().catch(() => {})\n }\n }\n try {\n const session = await client.meeting.respond(\n props.invite.meetingId,\n 'accept',\n props.invite.title,\n )\n if (session) {\n emit('accepted', session)\n }\n emit('close')\n } catch (err: any) {\n console.error('[atim] 会议接听失败', err)\n // 用户场景:邀请弹窗未及时收到 dissolved 事件(如断线期间),\n // 点击\"加入\"时后端拒绝——必须给明确提示,不能静默关闭\n alert(friendlyError(err))\n emit('close')\n }\n}\n\nasync function reject() {\n const client = getClient()\n // reject 在后端等价于主动离会(status=left),与离会行为一致\n // 失败仅记录日志,不阻塞关闭弹窗(reject 失败不影响他人)\n try {\n await client?.meeting?.respond(props.invite.meetingId, 'reject')\n } catch (err: any) {\n console.warn('[atim] 会议拒绝失败', err)\n }\n emit('close')\n}\n\nonMounted(() => {\n // 响铃:复用 ring-callee.mp3(被叫响铃,语义匹配)\n audioEl = new Audio(ringCalleeUrl)\n audioEl.loop = true\n audioEl.preload = 'auto'\n startRing()\n})\n\nonBeforeUnmount(() => {\n if (audioEl) {\n audioEl.pause()\n audioEl = null\n }\n})\n</script>\n\n<template>\n <div class=\"invite-dialog\">\n <div class=\"dialog-title\">{{ titleText }}</div>\n <div class=\"meeting-info\">\n <span class=\"info-label\">会议名:</span>\n <span class=\"info-value\">{{ invite.title }}</span>\n </div>\n <div class=\"dialog-actions\">\n <button class=\"btn btn-ghost\" type=\"button\" @click=\"toggleMute\">\n {{ ringMuted ? '恢复响铃' : '关闭响铃' }}\n </button>\n <button class=\"btn btn-secondary\" type=\"button\" @click=\"reject\">拒绝</button>\n <button class=\"btn btn-primary\" type=\"button\" @click=\"accept\">\n {{ conflict ? '切换' : '加入' }}\n </button>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* 颜色变量:同时兼容深浅背景 */\n.invite-dialog {\n --dialog-bg: rgba(255, 255, 255, 0.92);\n --dialog-bg-dark: rgba(18, 22, 32, 0.92);\n --text-primary: rgba(0, 0, 0, 0.88);\n --text-secondary: rgba(0, 0, 0, 0.55);\n --text-primary-dark: rgba(255, 255, 255, 0.92);\n --text-secondary-dark: rgba(255, 255, 255, 0.55);\n --border: rgba(0, 0, 0, 0.08);\n --border-dark: rgba(255, 255, 255, 0.1);\n --accent: #10b981;\n --accent-hover: #059669;\n --danger: #ef4444;\n --danger-hover: #dc2626;\n\n width: 260px;\n padding: 14px;\n /* 玻璃拟态:背景模糊 + 半透明白色 */\n background: var(--dialog-bg);\n backdrop-filter: blur(20px) saturate(180%);\n -webkit-backdrop-filter: blur(20px) saturate(180%);\n border: 1px solid rgba(255, 255, 255, 0.5);\n border-radius: 16px;\n box-shadow:\n 0 1px 2px rgba(0, 0, 0, 0.02),\n 0 4px 8px rgba(0, 0, 0, 0.04),\n 0 8px 16px rgba(0, 0, 0, 0.06),\n 0 16px 32px rgba(0, 0, 0, 0.08);\n color: var(--text-primary);\n position: relative;\n overflow: hidden;\n}\n\n/* 顶部柔和光条 */\n.invite-dialog::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 1px;\n background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);\n pointer-events: none;\n}\n\n/* 深色背景适配:用 .dark 类切换 */\n.invite-dialog.dark {\n background: var(--dialog-bg-dark);\n border-color: rgba(255, 255, 255, 0.12);\n color: var(--text-primary-dark);\n}\n.invite-dialog.dark .info-label {\n color: var(--text-secondary-dark);\n}\n.invite-dialog.dark .info-value {\n color: var(--text-primary-dark);\n}\n.invite-dialog.dark .btn-secondary {\n border-color: var(--border-dark);\n color: rgba(255, 255, 255, 0.8);\n}\n.invite-dialog.dark .btn-ghost {\n color: rgba(255, 255, 255, 0.5);\n}\n.invite-dialog.dark .btn-secondary:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(255, 255, 255, 0.2);\n}\n.invite-dialog.dark .btn-secondary.danger {\n border-color: rgba(239, 68, 68, 0.5);\n color: rgba(255, 255, 255, 0.9);\n}\n.invite-dialog.dark .btn-secondary.danger:hover {\n background: rgba(239, 68, 68, 0.15);\n border-color: rgba(239, 68, 68, 0.7);\n}\n\n.dialog-title {\n font-size: 13px;\n font-weight: 600;\n line-height: 1.4;\n margin-bottom: 8px;\n}\n\n.meeting-info {\n display: flex;\n align-items: baseline;\n gap: 4px;\n margin-bottom: 12px;\n font-size: 12px;\n}\n\n.info-label {\n color: var(--text-secondary);\n white-space: nowrap;\n}\n\n.info-value {\n color: var(--text-primary);\n font-weight: 600;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dialog-actions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 6px;\n}\n\n.btn {\n height: 26px;\n padding: 0 10px;\n border: 1px solid transparent;\n background: transparent;\n color: var(--text-primary);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n}\n\n.btn:hover {\n color: var(--text-primary);\n}\n\n.btn-primary {\n background: var(--accent);\n color: #fff;\n box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);\n}\n.btn-primary:hover {\n background: var(--accent-hover);\n box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);\n transform: translateY(-1px);\n}\n\n.btn-secondary {\n border-color: var(--border);\n color: var(--text-primary);\n}\n.btn-secondary:hover {\n background: rgba(0, 0, 0, 0.04);\n border-color: rgba(0, 0, 0, 0.12);\n}\n\n.btn-ghost {\n padding: 0 6px;\n color: var(--text-secondary);\n}\n.btn-ghost:hover {\n color: var(--text-primary);\n background: rgba(0, 0, 0, 0.04);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, nextTick } from 'vue'\nimport type { Message } from '@quitting/contract'\n\nconst props = defineProps<{\n to: string\n onClose?: () => void\n}>()\n\nconst emit = defineEmits<{ (e: 'close'): void }>()\n\nconst messages = ref<Message[]>([])\nconst input = ref('')\nconst conversationId = ref('')\nconst lastReadSeq = ref(0)\nconst listEl = ref<HTMLDivElement | null>(null)\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nonMounted(async () => {\n const client = getClient()\n if (!client) return\n\n // 监听新消息\n client.on('message', (msg: Message) => {\n if (msg.conversationId && msg.conversationId === conversationId.value) {\n messages.value.push(msg)\n scrollToBottom()\n // 自动 ACK\n client.message.ack(msg.id).catch(() => {})\n }\n })\n client.on('message:revoked', (e: any) => {\n const m = messages.value.find((x) => x.id === e.messageId)\n if (m) m.status = 'revoked'\n })\n\n // 先发一条占位消息触发会话创建\n // 真实业务应先 GET /conversations 取已存在的会话\n})\n\nasync function send() {\n if (!input.value.trim()) return\n const client = getClient()\n const body = input.value\n input.value = ''\n // 乐观渲染:先本地加一条 status=sending\n const tempId = 'temp-' + Date.now()\n messages.value.push({\n id: tempId,\n appId: '',\n conversationId: conversationId.value,\n seq: 0,\n senderId: client.uid,\n clientMid: tempId,\n type: 'text',\n body,\n status: 'sending',\n createdAt: Date.now(),\n })\n scrollToBottom()\n try {\n const resp = await client.message.send({ to: props.to, type: 'text', body })\n // 更新本地消息为已发送\n const m = messages.value.find((x) => x.id === tempId)\n if (m) {\n m.id = resp.msgId\n m.seq = resp.seq\n m.status = 'sent'\n conversationId.value = conversationId.value || (m as any).conversationId || ''\n }\n lastReadSeq.value = resp.seq\n } catch (err) {\n const m = messages.value.find((x) => x.id === tempId)\n if (m) m.status = 'failed'\n }\n}\n\nasync function loadHistory() {\n const client = getClient()\n if (!conversationId.value) return\n const msgs = await client.message.history(conversationId.value, 0, 50)\n messages.value = msgs\n if (msgs.length > 0) {\n lastReadSeq.value = msgs[msgs.length - 1].seq\n await client.message.markRead(conversationId.value, lastReadSeq.value)\n }\n scrollToBottom()\n}\n\nfunction scrollToBottom() {\n nextTick(() => {\n if (listEl.value) listEl.value.scrollTop = listEl.value.scrollHeight\n })\n}\n</script>\n\n<template>\n <div class=\"atim-chat-panel\">\n <div class=\"atim-chat-header\">\n <span>{{ to }}</span>\n <button class=\"atim-chat-close\" @click=\"emit('close')\">×</button>\n </div>\n <div ref=\"listEl\" class=\"atim-chat-messages\">\n <div\n v-for=\"m in messages\"\n :key=\"m.id\"\n :class=\"['atim-chat-msg', m.senderId === getClient()?.uid ? 'mine' : 'theirs']\"\n >\n <span v-if=\"m.status === 'revoked'\" class=\"atim-chat-revoked\">[已撤回]</span>\n <span v-else>{{ m.body }}</span>\n </div>\n </div>\n <div class=\"atim-chat-input\">\n <input v-model=\"input\" @keyup.enter=\"send\" placeholder=\"输入消息...\" />\n <button @click=\"send\">发送</button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.atim-chat-panel {\n position: fixed;\n bottom: 16px;\n right: 16px;\n width: 360px;\n height: 480px;\n background: #fff;\n border-radius: 8px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n display: flex;\n flex-direction: column;\n z-index: 9999;\n font-family: -apple-system, 'Segoe UI', Roboto, sans-serif;\n}\n.atim-chat-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n border-bottom: 1px solid #eee;\n font-weight: 600;\n}\n.atim-chat-close {\n background: none;\n border: none;\n cursor: pointer;\n font-size: 20px;\n color: #999;\n}\n.atim-chat-messages {\n flex: 1;\n overflow-y: auto;\n padding: 12px;\n}\n.atim-chat-msg {\n max-width: 70%;\n margin-bottom: 8px;\n padding: 8px 12px;\n border-radius: 12px;\n font-size: 14px;\n word-break: break-word;\n}\n.atim-chat-msg.mine {\n background: #95ec69;\n margin-left: auto;\n}\n.atim-chat-msg.theirs {\n background: #f5f5f5;\n}\n.atim-chat-revoked {\n color: #999;\n font-style: italic;\n font-size: 12px;\n}\n.atim-chat-input {\n display: flex;\n padding: 8px;\n border-top: 1px solid #eee;\n}\n.atim-chat-input input {\n flex: 1;\n padding: 8px;\n border: 1px solid #ddd;\n border-radius: 4px;\n}\n.atim-chat-input button {\n margin-left: 8px;\n padding: 8px 16px;\n background: #07c160;\n color: #fff;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, nextTick } from 'vue'\nimport type { Message } from '@quitting/contract'\n\nconst props = defineProps<{\n to: string\n onClose?: () => void\n}>()\n\nconst emit = defineEmits<{ (e: 'close'): void }>()\n\nconst messages = ref<Message[]>([])\nconst input = ref('')\nconst conversationId = ref('')\nconst lastReadSeq = ref(0)\nconst listEl = ref<HTMLDivElement | null>(null)\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nonMounted(async () => {\n const client = getClient()\n if (!client) return\n\n // 监听新消息\n client.on('message', (msg: Message) => {\n if (msg.conversationId && msg.conversationId === conversationId.value) {\n messages.value.push(msg)\n scrollToBottom()\n // 自动 ACK\n client.message.ack(msg.id).catch(() => {})\n }\n })\n client.on('message:revoked', (e: any) => {\n const m = messages.value.find((x) => x.id === e.messageId)\n if (m) m.status = 'revoked'\n })\n\n // 先发一条占位消息触发会话创建\n // 真实业务应先 GET /conversations 取已存在的会话\n})\n\nasync function send() {\n if (!input.value.trim()) return\n const client = getClient()\n const body = input.value\n input.value = ''\n // 乐观渲染:先本地加一条 status=sending\n const tempId = 'temp-' + Date.now()\n messages.value.push({\n id: tempId,\n appId: '',\n conversationId: conversationId.value,\n seq: 0,\n senderId: client.uid,\n clientMid: tempId,\n type: 'text',\n body,\n status: 'sending',\n createdAt: Date.now(),\n })\n scrollToBottom()\n try {\n const resp = await client.message.send({ to: props.to, type: 'text', body })\n // 更新本地消息为已发送\n const m = messages.value.find((x) => x.id === tempId)\n if (m) {\n m.id = resp.msgId\n m.seq = resp.seq\n m.status = 'sent'\n conversationId.value = conversationId.value || (m as any).conversationId || ''\n }\n lastReadSeq.value = resp.seq\n } catch (err) {\n const m = messages.value.find((x) => x.id === tempId)\n if (m) m.status = 'failed'\n }\n}\n\nasync function loadHistory() {\n const client = getClient()\n if (!conversationId.value) return\n const msgs = await client.message.history(conversationId.value, 0, 50)\n messages.value = msgs\n if (msgs.length > 0) {\n lastReadSeq.value = msgs[msgs.length - 1].seq\n await client.message.markRead(conversationId.value, lastReadSeq.value)\n }\n scrollToBottom()\n}\n\nfunction scrollToBottom() {\n nextTick(() => {\n if (listEl.value) listEl.value.scrollTop = listEl.value.scrollHeight\n })\n}\n</script>\n\n<template>\n <div class=\"atim-chat-panel\">\n <div class=\"atim-chat-header\">\n <span>{{ to }}</span>\n <button class=\"atim-chat-close\" @click=\"emit('close')\">×</button>\n </div>\n <div ref=\"listEl\" class=\"atim-chat-messages\">\n <div\n v-for=\"m in messages\"\n :key=\"m.id\"\n :class=\"['atim-chat-msg', m.senderId === getClient()?.uid ? 'mine' : 'theirs']\"\n >\n <span v-if=\"m.status === 'revoked'\" class=\"atim-chat-revoked\">[已撤回]</span>\n <span v-else>{{ m.body }}</span>\n </div>\n </div>\n <div class=\"atim-chat-input\">\n <input v-model=\"input\" @keyup.enter=\"send\" placeholder=\"输入消息...\" />\n <button @click=\"send\">发送</button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.atim-chat-panel {\n position: fixed;\n bottom: 16px;\n right: 16px;\n width: 360px;\n height: 480px;\n background: #fff;\n border-radius: 8px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n display: flex;\n flex-direction: column;\n z-index: 9999;\n font-family: -apple-system, 'Segoe UI', Roboto, sans-serif;\n}\n.atim-chat-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n border-bottom: 1px solid #eee;\n font-weight: 600;\n}\n.atim-chat-close {\n background: none;\n border: none;\n cursor: pointer;\n font-size: 20px;\n color: #999;\n}\n.atim-chat-messages {\n flex: 1;\n overflow-y: auto;\n padding: 12px;\n}\n.atim-chat-msg {\n max-width: 70%;\n margin-bottom: 8px;\n padding: 8px 12px;\n border-radius: 12px;\n font-size: 14px;\n word-break: break-word;\n}\n.atim-chat-msg.mine {\n background: #95ec69;\n margin-left: auto;\n}\n.atim-chat-msg.theirs {\n background: #f5f5f5;\n}\n.atim-chat-revoked {\n color: #999;\n font-style: italic;\n font-size: 12px;\n}\n.atim-chat-input {\n display: flex;\n padding: 8px;\n border-top: 1px solid #eee;\n}\n.atim-chat-input input {\n flex: 1;\n padding: 8px;\n border: 1px solid #ddd;\n border-radius: 4px;\n}\n.atim-chat-input button {\n margin-left: 8px;\n padding: 8px 16px;\n background: #07c160;\n color: #fff;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n}\n</style>\n","import { createApp, type App } from 'vue'\nimport { h } from 'vue'\n\nimport type { AtimClient } from '@quitting/core'\nimport type { CallType } from '@quitting/contract'\n\nimport CallWindow from './components/CallWindow.vue'\nimport MeetWindow from './components/MeetWindow.vue'\nimport IncomingRing from './components/IncomingRing.vue'\nimport MeetingInvite from './components/MeetingInvite.vue'\nimport ChatPanel from './components/ChatPanel.vue'\n\n// 全局方法:业务方零编码接入,调 AtimUI.chat/call/meeting 即弹窗\n// 对方收到邀请自动弹 IncomingRing/MeetingInvite(由本模块监听 client 事件触发)\n\nlet client: AtimClient\nlet app: App\n\n// 会议邀请弹窗引用:按 meetingId 索引,dissolved/kicked 事件来时按 meetingId 清弹窗\ninterface MountedInvite {\n subApp: App\n div: HTMLDivElement\n}\nconst mountedInvites = new Map<string, MountedInvite>()\n\n// 用动态 mount 实现弹窗(不依赖业务方在 template 里放组件)\n// 返回 subApp(Vue 应用实例);div 通过闭包持有,挂载方在 onClose 里清理\nfunction mountComponent(component: any, props: Record<string, any>, mountPoint?: HTMLElement) {\n const div = document.createElement('div')\n ;(mountPoint || document.body).appendChild(div)\n const subApp = (createApp as any)({\n render: () =>\n h(component, {\n ...props,\n onClose: () => {\n // 会议邀请关闭时同步清 Map(避免 dissolved/kicked 找到已关的引用重复 unmount)\n if (component === MeetingInvite && props.invite?.meetingId) {\n mountedInvites.delete(props.invite.meetingId)\n }\n subApp.unmount()\n div.remove()\n },\n }),\n })\n // 继承主 app 的全局组件/插件\n subApp.config = app.config\n subApp.mount(div)\n return { subApp, div }\n}\n\nexport function setupGlobalMethods(atimClient: AtimClient, vueApp: App) {\n client = atimClient\n app = vueApp\n\n // 注入到 globalThis,供 Vue 组件内部 getClient() 拿到\n // 严格 provide/inject 模式可由业务方自行实现,这里用 globalThis 简化跨组件访问\n ;(globalThis as any).__atimClient = atimClient\n\n // ===== 监听邀请事件,自动弹窗 =====\n client.on('call:invite', (e) => {\n if (client.sessionState !== 'idle') {\n // 会话冲突:1:1 邀请自动忙线拒绝\n client.call.end('busy').catch(() => {})\n return\n }\n mountComponent(IncomingRing, {\n invite: e,\n })\n })\n\n client.on('meeting:invite', async (e) => {\n // 异步校验后端 pending:Centrifugo recovery 重放 stale publication 时丢弃\n try {\n const resp = await client.transport.meetingPendingInvites()\n const valid = (resp.invites || []).some((p) => p.meetingId === e.meetingId)\n if (!valid) {\n console.warn('[atim-ui] stale meeting_invite 丢弃(会议已结束)', e.meetingId)\n return\n }\n } catch (err) {\n console.warn('[atim-ui] invite 校验失败,默认透传', err)\n }\n // 同 meetingId 已弹过:先清旧的(避免堆叠),再 mount 新的(覆盖 reinvite 场景)\n const existing = mountedInvites.get(e.meetingId)\n if (existing) {\n existing.subApp.unmount()\n existing.div.remove()\n mountedInvites.delete(e.meetingId)\n }\n const conflict = client.sessionState !== 'idle'\n const { subApp, div } = mountComponent(MeetingInvite, {\n invite: e,\n conflict,\n })\n mountedInvites.set(e.meetingId, { subApp, div })\n })\n\n // 会议解散:清对应邀请弹窗\n client.on('meeting:dissolved', (e: { meetingId: string }) => {\n const ref = mountedInvites.get(e.meetingId)\n if (ref) {\n ref.subApp.unmount()\n ref.div.remove()\n mountedInvites.delete(e.meetingId)\n }\n })\n\n client.on('call:busy', () => {\n // 主叫收到忙线,UI 层应自行监听并提示\n })\n client.on('call:no_answer', () => {\n // 主叫收到无人接听\n })\n\n client.on('session:kicked', (e) => {\n alert(\n `您的账号在其他 ${e.clientType === 4 ? 'Web' : e.clientType === 1 ? 'PC' : '移动端'} 登录,已强制下线`,\n )\n window.location.reload()\n })\n}\n\n// 暴露给业务方的全局方法\nexport const AtimUI = {\n // 弹出与某用户的聊天框\n chat(userId: string) {\n mountComponent(ChatPanel, { to: userId })\n },\n\n // 弹出通话窗口(主叫)\n call(userId: string, type: CallType = 'video') {\n mountComponent(CallWindow, {\n mode: 'caller',\n calleeUid: userId,\n callType: type,\n })\n // 调 core 发起通话\n client.call\n .init(userId, type)\n .then(() => {\n // CallWindow 组件内部会拿 currentSession 渲染\n })\n .catch((err) => {\n console.error('[atim] call init 失败', err)\n })\n },\n\n // 弹出会议窗口(主持人)\n meeting(opts: { invitees: string[]; title?: string }) {\n mountComponent(MeetWindow, {\n mode: 'host',\n title: opts.title || '即时会议',\n })\n client.meeting.create(opts.invitees, opts.title || '即时会议').catch((err) => {\n console.error('[atim] meeting create 失败', err)\n })\n },\n}\n","export default \"data:audio/mpeg;base64,//uQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAB0AAC/BAAEBggLDQ8TFhgaHB4jJScpLC4yNDc5Oz1CREZIS01PU1ZYWlxeY2VnaWxucnR3eXt9goSGiIuNj5OWmJqcnqOlp6msrrK0t7m7vcLExsjLzc/T1tja3N7j5efp7O7y9Pf5+/0AAAA8TEFNRTMuMTAwAQAAAAAAAAAAABSAJAL0ZgAAgAAAvwRQljMCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//uQBAAP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAETEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVdLVEUVJMoAkQIREQYQgmAwGTJkyZMmEECBAgQIECBMmTsLTvwTTvxBAgQL/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARkyad//xnvf/ZAgQQ/e7IECCER3sgQIECEREE73+z0///+0ECZMmTJk073///+Lvf3u7IECBAgQIIQTTvWiIz97s8mTJkCAAAAAAAAIELsmTJk07//u7PJkyBAghGf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VVVVVVVVVVUD8aij///xVixUUFsWFWf+rrFRbFhcV4t//4szigH41FH///irFiooLYsKs/9XWKi2LC4rxb//xZnFFUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5AEXo/w6QC4mAEQDB0gFxMAIgGAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5AEQQ/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kARBD/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSBECP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kgRAj/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5IEQI/wAABpAAAACAAADSAAAAEAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV\"","<script setup lang=\"ts\">\nimport { ref, computed, onMounted, onBeforeUnmount } from 'vue'\nimport type { CallType } from '@quitting/contract'\nimport vDraggable from '../directives/draggable'\nimport ringCallerUrl from '../sounds/ring-caller.mp3'\nimport ringCalleeUrl from '../sounds/ring-callee.mp3'\n\n// 统一通话邀请/等待弹窗\n// mode='outgoing':主叫呼叫等待(响 caller.mp3)\n// - 显示\"呼叫中...\"动画 + 红色\"取消\"按钮\n// - 收到 call:accepted → emit('connected')\n// - 收到 call:rejected/busy/no_answer → emit('close')\n// - 用户点取消 → 调 client.call.end('cancelled', callId) → emit('close')\n// mode='incoming':被叫响铃(响 callee.mp3)\n// - 显示\"邀请你通话\" + 绿色\"接听\" + 红色\"拒接\" 按钮\n// - 用户点接听 → await client.call.accept() → emit('accepted', session) → emit('close')\n// - 用户点拒接 → client.call.end('rejected', callId) → emit('close')\n// - 收到 call:cancelled → emit('close')\n\nconst props = defineProps<{\n mode: 'outgoing' | 'incoming'\n peerUid: string\n peerName?: string\n callType: CallType\n // incoming 必须传 callId(来自 call:invite 事件);outgoing 可省略(取 currentSession.callId)\n callId?: string\n // 主叫超时秒数(默认 30s)\n timeoutSec?: number\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n // outgoing 收到 call:accepted 时触发——父组件切到 CallWindow\n (e: 'connected'): void\n // incoming 接听成功时触发,携带 session——父组件切到 CallWindow\n (e: 'accepted', session: unknown): void\n}>()\n\ntype DialogStatus = 'ringing' | 'connected' | 'rejected' | 'busy' | 'no_answer' | 'cancelled'\nconst status = ref<DialogStatus>('ringing')\nconst statusText = ref('')\nconst ringAudio = ref<HTMLAudioElement | null>(null)\n\nconst unsubscribers: Array<() => void> = []\nlet timeoutTimer: ReturnType<typeof setTimeout> | null = null\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nfunction startRing() {\n if (!ringAudio.value) return\n ringAudio.value.currentTime = 0\n ringAudio.value.play().catch(() => {})\n}\n\nfunction stopRing() {\n if (!ringAudio.value) return\n ringAudio.value.pause()\n ringAudio.value.currentTime = 0\n}\n\nconst avatarText = computed(() => (props.peerName || props.peerUid)[0].toUpperCase())\nconst statusLabel = computed(() => {\n if (status.value === 'ringing') {\n return props.mode === 'outgoing' ? '呼叫中' : '邀请你通话'\n }\n return statusText.value\n})\n\nfunction setStatusAndClose(s: DialogStatus, text: string, delay = 1500) {\n status.value = s\n statusText.value = text\n setTimeout(() => emit('close'), delay)\n}\n\n// outgoing 模式:主叫取消\nasync function cancelOutgoing() {\n stopRing()\n status.value = 'cancelled'\n statusText.value = '已取消'\n const client = getClient()\n const cid = props.callId || client?.call?.currentSession?.callId\n await client?.call?.end('cancelled', cid).catch(() => {})\n setTimeout(() => emit('close'), 800)\n}\n\n// incoming 模式:被叫接听\nasync function acceptIncoming() {\n stopRing()\n const client = getClient()\n if (!client || !props.callId) return\n status.value = 'connected'\n statusText.value = '已接听'\n try {\n const session = await client.call.accept(props.callId, props.callType)\n emit('accepted', session)\n setTimeout(() => emit('close'), 200)\n } catch (err: any) {\n // connect 失败、设备异常等——SDK 已清 currentSession + 调后端兜底\n // UI 显示失败提示后关闭,让用户重试\n console.error('[atim] 接听失败', err)\n status.value = 'rejected'\n statusText.value = err?.message || '接听失败'\n setTimeout(() => emit('close'), 1500)\n }\n}\n\n// incoming 模式:被叫拒接\nasync function rejectIncoming() {\n stopRing()\n status.value = 'rejected'\n statusText.value = '已拒接'\n const client = getClient()\n await client?.call?.end('rejected', props.callId).catch(() => {})\n setTimeout(() => emit('close'), 600)\n}\n\nonMounted(() => {\n const client = getClient()\n if (!client) {\n emit('close')\n return\n }\n startRing()\n\n if (props.mode === 'outgoing') {\n // 主叫监听被叫响应\n unsubscribers.push(\n client.on('call:accepted', () => {\n stopRing()\n status.value = 'connected'\n statusText.value = '对方已接听'\n setTimeout(() => emit('connected'), 300)\n }),\n )\n unsubscribers.push(client.on('call:rejected', () => setStatusAndClose('rejected', '对方拒接')))\n unsubscribers.push(client.on('call:busy', () => setStatusAndClose('busy', '对方繁忙')))\n unsubscribers.push(\n client.on('call:no_answer', () => setStatusAndClose('no_answer', '无人接听')),\n )\n // 客户端超时兜底\n const sec = props.timeoutSec ?? 30\n timeoutTimer = setTimeout(() => {\n if (status.value === 'ringing') setStatusAndClose('no_answer', '无人接听')\n }, sec * 1000)\n } else {\n // incoming 监听主叫取消\n unsubscribers.push(\n client.on('call:cancelled', () => {\n stopRing()\n setStatusAndClose('cancelled', '对方已取消', 600)\n }),\n )\n }\n})\n\nonBeforeUnmount(() => {\n stopRing()\n unsubscribers.forEach((fn) => fn())\n if (timeoutTimer) clearTimeout(timeoutTimer)\n})\n</script>\n\n<template>\n <div v-draggable=\"'.call-dialog-status'\" class=\"call-dialog\">\n <audio\n ref=\"ringAudio\"\n :src=\"mode === 'outgoing' ? ringCallerUrl : ringCalleeUrl\"\n loop\n preload=\"auto\"\n />\n <div class=\"call-dialog-header\">\n <span>{{ callType === 'video' ? '视频通话' : '语音通话' }}</span>\n </div>\n <div class=\"call-dialog-body\">\n <div :class=\"['avatar', status === 'ringing' ? 'ringing' : status]\">\n <span>{{ avatarText }}</span>\n </div>\n <div class=\"peer-name\">{{ peerName || peerUid }}</div>\n <div class=\"status-text\">\n <template v-if=\"status === 'ringing'\">\n {{ statusLabel }}<span class=\"dots\"><i>.</i><i>.</i><i>.</i></span>\n </template>\n <template v-else>\n <span :class=\"status\">{{ statusText }}</span>\n </template>\n </div>\n </div>\n <div class=\"call-dialog-actions\">\n <!-- outgoing 模式:只显示取消 -->\n <template v-if=\"mode === 'outgoing'\">\n <button v-if=\"status === 'ringing'\" class=\"btn cancel\" @click=\"cancelOutgoing\">取消</button>\n </template>\n <!-- incoming 模式:接听 + 拒接 -->\n <template v-else>\n <button v-if=\"status === 'ringing'\" class=\"btn reject\" @click=\"rejectIncoming\">拒接</button>\n <button v-if=\"status === 'ringing'\" class=\"btn accept\" @click=\"acceptIncoming\">接听</button>\n </template>\n </div>\n </div>\n</template>\n\n<style scoped>\n.call-dialog {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 320px;\n background: #0f172a;\n border-radius: 0;\n padding: 20px 20px 28px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n color: #fff;\n z-index: 9999;\n text-align: center;\n}\n.call-dialog-header {\n cursor: move;\n font-size: 12px;\n color: #999;\n padding-bottom: 10px;\n border-bottom: 1px solid #333;\n margin-bottom: 16px;\n}\n.avatar {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: #fff;\n font-size: 36px;\n font-weight: 600;\n line-height: 80px;\n margin: 0 auto 12px;\n position: relative;\n}\n.avatar.ringing::before {\n content: '';\n position: absolute;\n inset: -8px;\n border: 2px solid #667eea;\n border-radius: 50%;\n animation: pulse 1.5s ease-out infinite;\n}\n.avatar.rejected,\n.avatar.cancelled {\n background: #888;\n}\n.avatar.busy,\n.avatar.no_answer {\n background: #d4a017;\n}\n.avatar.connected {\n background: #52c41a;\n}\n@keyframes pulse {\n 0% {\n transform: scale(1);\n opacity: 0.8;\n }\n 100% {\n transform: scale(1.4);\n opacity: 0;\n }\n}\n.peer-name {\n font-size: 16px;\n font-weight: 600;\n margin-bottom: 6px;\n}\n.status-text {\n font-size: 13px;\n color: #bbb;\n min-height: 20px;\n}\n.status-text .rejected,\n.status-text .cancelled {\n color: #888;\n}\n.status-text .busy,\n.status-text .no_answer {\n color: #d4a017;\n}\n.status-text .connected {\n color: #52c41a;\n}\n.dots i {\n display: inline-block;\n font-style: normal;\n animation: dot-bounce 1.4s infinite;\n}\n.dots i:nth-child(2) {\n animation-delay: 0.2s;\n}\n.dots i:nth-child(3) {\n animation-delay: 0.4s;\n}\n@keyframes dot-bounce {\n 0%,\n 60%,\n 100% {\n opacity: 0.3;\n }\n 30% {\n opacity: 1;\n }\n}\n.call-dialog-actions {\n margin-top: 20px;\n display: flex;\n justify-content: center;\n gap: 20px;\n min-height: 44px;\n}\n.btn {\n padding: 8px 24px;\n border: none;\n border-radius: 0;\n cursor: pointer;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n transition:\n transform 0.1s,\n opacity 0.1s;\n}\n.btn:active {\n transform: scale(0.96);\n}\n.btn.accept {\n background: #52c41a;\n}\n.btn.reject,\n.btn.cancel {\n background: #ef4444;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, computed, onMounted, onBeforeUnmount } from 'vue'\nimport type { CallType } from '@quitting/contract'\nimport vDraggable from '../directives/draggable'\nimport ringCallerUrl from '../sounds/ring-caller.mp3'\nimport ringCalleeUrl from '../sounds/ring-callee.mp3'\n\n// 统一通话邀请/等待弹窗\n// mode='outgoing':主叫呼叫等待(响 caller.mp3)\n// - 显示\"呼叫中...\"动画 + 红色\"取消\"按钮\n// - 收到 call:accepted → emit('connected')\n// - 收到 call:rejected/busy/no_answer → emit('close')\n// - 用户点取消 → 调 client.call.end('cancelled', callId) → emit('close')\n// mode='incoming':被叫响铃(响 callee.mp3)\n// - 显示\"邀请你通话\" + 绿色\"接听\" + 红色\"拒接\" 按钮\n// - 用户点接听 → await client.call.accept() → emit('accepted', session) → emit('close')\n// - 用户点拒接 → client.call.end('rejected', callId) → emit('close')\n// - 收到 call:cancelled → emit('close')\n\nconst props = defineProps<{\n mode: 'outgoing' | 'incoming'\n peerUid: string\n peerName?: string\n callType: CallType\n // incoming 必须传 callId(来自 call:invite 事件);outgoing 可省略(取 currentSession.callId)\n callId?: string\n // 主叫超时秒数(默认 30s)\n timeoutSec?: number\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n // outgoing 收到 call:accepted 时触发——父组件切到 CallWindow\n (e: 'connected'): void\n // incoming 接听成功时触发,携带 session——父组件切到 CallWindow\n (e: 'accepted', session: unknown): void\n}>()\n\ntype DialogStatus = 'ringing' | 'connected' | 'rejected' | 'busy' | 'no_answer' | 'cancelled'\nconst status = ref<DialogStatus>('ringing')\nconst statusText = ref('')\nconst ringAudio = ref<HTMLAudioElement | null>(null)\n\nconst unsubscribers: Array<() => void> = []\nlet timeoutTimer: ReturnType<typeof setTimeout> | null = null\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nfunction startRing() {\n if (!ringAudio.value) return\n ringAudio.value.currentTime = 0\n ringAudio.value.play().catch(() => {})\n}\n\nfunction stopRing() {\n if (!ringAudio.value) return\n ringAudio.value.pause()\n ringAudio.value.currentTime = 0\n}\n\nconst avatarText = computed(() => (props.peerName || props.peerUid)[0].toUpperCase())\nconst statusLabel = computed(() => {\n if (status.value === 'ringing') {\n return props.mode === 'outgoing' ? '呼叫中' : '邀请你通话'\n }\n return statusText.value\n})\n\nfunction setStatusAndClose(s: DialogStatus, text: string, delay = 1500) {\n status.value = s\n statusText.value = text\n setTimeout(() => emit('close'), delay)\n}\n\n// outgoing 模式:主叫取消\nasync function cancelOutgoing() {\n stopRing()\n status.value = 'cancelled'\n statusText.value = '已取消'\n const client = getClient()\n const cid = props.callId || client?.call?.currentSession?.callId\n await client?.call?.end('cancelled', cid).catch(() => {})\n setTimeout(() => emit('close'), 800)\n}\n\n// incoming 模式:被叫接听\nasync function acceptIncoming() {\n stopRing()\n const client = getClient()\n if (!client || !props.callId) return\n status.value = 'connected'\n statusText.value = '已接听'\n try {\n const session = await client.call.accept(props.callId, props.callType)\n emit('accepted', session)\n setTimeout(() => emit('close'), 200)\n } catch (err: any) {\n // connect 失败、设备异常等——SDK 已清 currentSession + 调后端兜底\n // UI 显示失败提示后关闭,让用户重试\n console.error('[atim] 接听失败', err)\n status.value = 'rejected'\n statusText.value = err?.message || '接听失败'\n setTimeout(() => emit('close'), 1500)\n }\n}\n\n// incoming 模式:被叫拒接\nasync function rejectIncoming() {\n stopRing()\n status.value = 'rejected'\n statusText.value = '已拒接'\n const client = getClient()\n await client?.call?.end('rejected', props.callId).catch(() => {})\n setTimeout(() => emit('close'), 600)\n}\n\nonMounted(() => {\n const client = getClient()\n if (!client) {\n emit('close')\n return\n }\n startRing()\n\n if (props.mode === 'outgoing') {\n // 主叫监听被叫响应\n unsubscribers.push(\n client.on('call:accepted', () => {\n stopRing()\n status.value = 'connected'\n statusText.value = '对方已接听'\n setTimeout(() => emit('connected'), 300)\n }),\n )\n unsubscribers.push(client.on('call:rejected', () => setStatusAndClose('rejected', '对方拒接')))\n unsubscribers.push(client.on('call:busy', () => setStatusAndClose('busy', '对方繁忙')))\n unsubscribers.push(\n client.on('call:no_answer', () => setStatusAndClose('no_answer', '无人接听')),\n )\n // 客户端超时兜底\n const sec = props.timeoutSec ?? 30\n timeoutTimer = setTimeout(() => {\n if (status.value === 'ringing') setStatusAndClose('no_answer', '无人接听')\n }, sec * 1000)\n } else {\n // incoming 监听主叫取消\n unsubscribers.push(\n client.on('call:cancelled', () => {\n stopRing()\n setStatusAndClose('cancelled', '对方已取消', 600)\n }),\n )\n }\n})\n\nonBeforeUnmount(() => {\n stopRing()\n unsubscribers.forEach((fn) => fn())\n if (timeoutTimer) clearTimeout(timeoutTimer)\n})\n</script>\n\n<template>\n <div v-draggable=\"'.call-dialog-status'\" class=\"call-dialog\">\n <audio\n ref=\"ringAudio\"\n :src=\"mode === 'outgoing' ? ringCallerUrl : ringCalleeUrl\"\n loop\n preload=\"auto\"\n />\n <div class=\"call-dialog-header\">\n <span>{{ callType === 'video' ? '视频通话' : '语音通话' }}</span>\n </div>\n <div class=\"call-dialog-body\">\n <div :class=\"['avatar', status === 'ringing' ? 'ringing' : status]\">\n <span>{{ avatarText }}</span>\n </div>\n <div class=\"peer-name\">{{ peerName || peerUid }}</div>\n <div class=\"status-text\">\n <template v-if=\"status === 'ringing'\">\n {{ statusLabel }}<span class=\"dots\"><i>.</i><i>.</i><i>.</i></span>\n </template>\n <template v-else>\n <span :class=\"status\">{{ statusText }}</span>\n </template>\n </div>\n </div>\n <div class=\"call-dialog-actions\">\n <!-- outgoing 模式:只显示取消 -->\n <template v-if=\"mode === 'outgoing'\">\n <button v-if=\"status === 'ringing'\" class=\"btn cancel\" @click=\"cancelOutgoing\">取消</button>\n </template>\n <!-- incoming 模式:接听 + 拒接 -->\n <template v-else>\n <button v-if=\"status === 'ringing'\" class=\"btn reject\" @click=\"rejectIncoming\">拒接</button>\n <button v-if=\"status === 'ringing'\" class=\"btn accept\" @click=\"acceptIncoming\">接听</button>\n </template>\n </div>\n </div>\n</template>\n\n<style scoped>\n.call-dialog {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 320px;\n background: #0f172a;\n border-radius: 0;\n padding: 20px 20px 28px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n color: #fff;\n z-index: 9999;\n text-align: center;\n}\n.call-dialog-header {\n cursor: move;\n font-size: 12px;\n color: #999;\n padding-bottom: 10px;\n border-bottom: 1px solid #333;\n margin-bottom: 16px;\n}\n.avatar {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: #fff;\n font-size: 36px;\n font-weight: 600;\n line-height: 80px;\n margin: 0 auto 12px;\n position: relative;\n}\n.avatar.ringing::before {\n content: '';\n position: absolute;\n inset: -8px;\n border: 2px solid #667eea;\n border-radius: 50%;\n animation: pulse 1.5s ease-out infinite;\n}\n.avatar.rejected,\n.avatar.cancelled {\n background: #888;\n}\n.avatar.busy,\n.avatar.no_answer {\n background: #d4a017;\n}\n.avatar.connected {\n background: #52c41a;\n}\n@keyframes pulse {\n 0% {\n transform: scale(1);\n opacity: 0.8;\n }\n 100% {\n transform: scale(1.4);\n opacity: 0;\n }\n}\n.peer-name {\n font-size: 16px;\n font-weight: 600;\n margin-bottom: 6px;\n}\n.status-text {\n font-size: 13px;\n color: #bbb;\n min-height: 20px;\n}\n.status-text .rejected,\n.status-text .cancelled {\n color: #888;\n}\n.status-text .busy,\n.status-text .no_answer {\n color: #d4a017;\n}\n.status-text .connected {\n color: #52c41a;\n}\n.dots i {\n display: inline-block;\n font-style: normal;\n animation: dot-bounce 1.4s infinite;\n}\n.dots i:nth-child(2) {\n animation-delay: 0.2s;\n}\n.dots i:nth-child(3) {\n animation-delay: 0.4s;\n}\n@keyframes dot-bounce {\n 0%,\n 60%,\n 100% {\n opacity: 0.3;\n }\n 30% {\n opacity: 1;\n }\n}\n.call-dialog-actions {\n margin-top: 20px;\n display: flex;\n justify-content: center;\n gap: 20px;\n min-height: 44px;\n}\n.btn {\n padding: 8px 24px;\n border: none;\n border-radius: 0;\n cursor: pointer;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n transition:\n transform 0.1s,\n opacity 0.1s;\n}\n.btn:active {\n transform: scale(0.96);\n}\n.btn.accept {\n background: #52c41a;\n}\n.btn.reject,\n.btn.cancel {\n background: #ef4444;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount } from 'vue'\nimport type { CallType } from '@quitting/contract'\nimport vDraggable from '../directives/draggable'\n\n// 主叫呼叫等待弹窗(不显示视频)\n// 在 SDK CallController.init 调用后、被叫接听前显示\n// 主叫收到 call:accepted → emit('connected') → 父组件切换到 CallWindow\n// 主叫收到 call:rejected → 显示\"对方拒接\" 1.5s 后 emit('close')\n// 主叫收到 call:busy → 显示\"对方繁忙\" 1.5s 后 emit('close')\n// 主叫收到 call:no_answer → 显示\"无人接听\" 1.5s 后 emit('close')\n// 主叫点取消 → 调 client.call.end('cancelled') + emit('close')\n\nconst props = defineProps<{\n calleeUid: string\n calleeName?: string\n callType: CallType\n // 可选:超时秒数(默认 30s),到时显示\"无人接听\"\n timeoutSec?: number\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n (e: 'connected'): void\n}>()\n\nconst status = ref<'ringing' | 'rejected' | 'busy' | 'no_answer' | 'cancelled'>('ringing')\nconst statusText = ref('')\nlet unsub: (() => void) | null = null\nlet timeoutTimer: ReturnType<typeof setTimeout> | null = null\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nfunction setStatusAndClose(s: typeof status.value, text: string, delay = 1500) {\n status.value = s\n statusText.value = text\n setTimeout(() => emit('close'), delay)\n}\n\nasync function cancel() {\n // 主叫主动取消:currentSession 存在时 SDK 走 hangup,否则用 callId 兜底\n status.value = 'cancelled'\n statusText.value = '已取消'\n const client = getClient()\n const callId = client?.call?.currentSession?.callId\n await client?.call?.end('cancelled', callId).catch(() => {})\n setTimeout(() => emit('close'), 800)\n}\n\nonMounted(() => {\n const client = getClient()\n if (!client) {\n emit('close')\n return\n }\n\n // 监听被叫响应\n unsub = client.on('call:accepted', () => {\n statusText.value = '对方已接听'\n setTimeout(() => emit('connected'), 300)\n })\n client.on('call:rejected', () => {\n setStatusAndClose('rejected', '对方拒接')\n })\n client.on('call:busy', () => {\n setStatusAndClose('busy', '对方繁忙')\n })\n client.on('call:no_answer', () => {\n setStatusAndClose('no_answer', '无人接听')\n })\n // 极少情况:主叫取消后后端回推 call_cancelled 给自己——直接关\n client.on('call:cancelled', () => {\n setStatusAndClose('cancelled', '已取消', 500)\n })\n\n const sec = props.timeoutSec ?? 30\n timeoutTimer = setTimeout(() => {\n if (status.value === 'ringing') {\n setStatusAndClose('no_answer', '无人接听')\n }\n }, sec * 1000)\n})\n\nonBeforeUnmount(() => {\n if (unsub) unsub()\n if (timeoutTimer) clearTimeout(timeoutTimer)\n})\n</script>\n\n<template>\n <div v-draggable=\"'.call-out-header'\" class=\"call-out-window\">\n <div class=\"call-out-header\">\n <span>{{ callType === 'video' ? '视频通话' : '语音通话' }}</span>\n </div>\n <div class=\"call-out-body\">\n <div :class=\"['call-out-avatar', status === 'ringing' ? 'ringing' : status]\">\n {{ (calleeName || calleeUid)[0].toUpperCase() }}\n </div>\n <div class=\"call-out-name\">{{ calleeName || calleeUid }}</div>\n <div class=\"call-out-status\">\n <span v-if=\"status === 'ringing'\" class=\"ringing-dots\">\n 呼叫中<span class=\"dot\">·</span><span class=\"dot\">·</span><span class=\"dot\">·</span>\n </span>\n <span v-else :class=\"status\">{{ statusText }}</span>\n </div>\n </div>\n <div class=\"call-out-actions\">\n <button v-if=\"status === 'ringing'\" class=\"cancel-btn\" @click=\"cancel\">取消</button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.call-out-window {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 320px;\n background: #0f172a;\n border-radius: 0;\n padding: 24px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n color: #fff;\n z-index: 9999;\n text-align: center;\n}\n.call-out-header {\n cursor: move;\n font-size: 13px;\n color: #999;\n padding-bottom: 12px;\n border-bottom: 1px solid #333;\n margin-bottom: 16px;\n}\n.call-out-avatar {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: #fff;\n font-size: 36px;\n font-weight: 600;\n line-height: 80px;\n margin: 0 auto 12px;\n position: relative;\n}\n.call-out-avatar.ringing::before {\n content: '';\n position: absolute;\n inset: -8px;\n border: 2px solid #667eea;\n border-radius: 50%;\n animation: pulse 1.5s ease-out infinite;\n}\n.call-out-avatar.rejected,\n.call-out-avatar.cancelled {\n background: #888;\n}\n.call-out-avatar.busy,\n.call-out-avatar.no_answer {\n background: #d4a017;\n}\n@keyframes pulse {\n 0% {\n transform: scale(1);\n opacity: 0.8;\n }\n 100% {\n transform: scale(1.4);\n opacity: 0;\n }\n}\n.call-out-name {\n font-size: 16px;\n font-weight: 600;\n margin-bottom: 8px;\n}\n.call-out-status {\n font-size: 13px;\n color: #999;\n min-height: 20px;\n}\n.call-out-status .rejected,\n.call-out-status .cancelled {\n color: #888;\n}\n.call-out-status .busy,\n.call-out-status .no_answer {\n color: #d4a017;\n}\n.ringing-dots .dot {\n display: inline-block;\n animation: dot-bounce 1.4s infinite;\n}\n.ringing-dots .dot:nth-child(2) {\n animation-delay: 0.2s;\n}\n.ringing-dots .dot:nth-child(3) {\n animation-delay: 0.4s;\n}\n@keyframes dot-bounce {\n 0%,\n 60%,\n 100% {\n opacity: 0.3;\n }\n 30% {\n opacity: 1;\n }\n}\n.call-out-actions {\n margin-top: 24px;\n min-height: 40px;\n}\n.cancel-btn {\n padding: 8px 32px;\n background: #ef4444;\n color: #fff;\n border: none;\n border-radius: 0;\n cursor: pointer;\n font-size: 13px;\n transition: opacity 0.1s;\n}\n.cancel-btn:active {\n opacity: 0.8;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount } from 'vue'\nimport type { CallType } from '@quitting/contract'\nimport vDraggable from '../directives/draggable'\n\n// 主叫呼叫等待弹窗(不显示视频)\n// 在 SDK CallController.init 调用后、被叫接听前显示\n// 主叫收到 call:accepted → emit('connected') → 父组件切换到 CallWindow\n// 主叫收到 call:rejected → 显示\"对方拒接\" 1.5s 后 emit('close')\n// 主叫收到 call:busy → 显示\"对方繁忙\" 1.5s 后 emit('close')\n// 主叫收到 call:no_answer → 显示\"无人接听\" 1.5s 后 emit('close')\n// 主叫点取消 → 调 client.call.end('cancelled') + emit('close')\n\nconst props = defineProps<{\n calleeUid: string\n calleeName?: string\n callType: CallType\n // 可选:超时秒数(默认 30s),到时显示\"无人接听\"\n timeoutSec?: number\n}>()\n\nconst emit = defineEmits<{\n (e: 'close'): void\n (e: 'connected'): void\n}>()\n\nconst status = ref<'ringing' | 'rejected' | 'busy' | 'no_answer' | 'cancelled'>('ringing')\nconst statusText = ref('')\nlet unsub: (() => void) | null = null\nlet timeoutTimer: ReturnType<typeof setTimeout> | null = null\n\nfunction getClient() {\n return (window as any).__atimClient\n}\n\nfunction setStatusAndClose(s: typeof status.value, text: string, delay = 1500) {\n status.value = s\n statusText.value = text\n setTimeout(() => emit('close'), delay)\n}\n\nasync function cancel() {\n // 主叫主动取消:currentSession 存在时 SDK 走 hangup,否则用 callId 兜底\n status.value = 'cancelled'\n statusText.value = '已取消'\n const client = getClient()\n const callId = client?.call?.currentSession?.callId\n await client?.call?.end('cancelled', callId).catch(() => {})\n setTimeout(() => emit('close'), 800)\n}\n\nonMounted(() => {\n const client = getClient()\n if (!client) {\n emit('close')\n return\n }\n\n // 监听被叫响应\n unsub = client.on('call:accepted', () => {\n statusText.value = '对方已接听'\n setTimeout(() => emit('connected'), 300)\n })\n client.on('call:rejected', () => {\n setStatusAndClose('rejected', '对方拒接')\n })\n client.on('call:busy', () => {\n setStatusAndClose('busy', '对方繁忙')\n })\n client.on('call:no_answer', () => {\n setStatusAndClose('no_answer', '无人接听')\n })\n // 极少情况:主叫取消后后端回推 call_cancelled 给自己——直接关\n client.on('call:cancelled', () => {\n setStatusAndClose('cancelled', '已取消', 500)\n })\n\n const sec = props.timeoutSec ?? 30\n timeoutTimer = setTimeout(() => {\n if (status.value === 'ringing') {\n setStatusAndClose('no_answer', '无人接听')\n }\n }, sec * 1000)\n})\n\nonBeforeUnmount(() => {\n if (unsub) unsub()\n if (timeoutTimer) clearTimeout(timeoutTimer)\n})\n</script>\n\n<template>\n <div v-draggable=\"'.call-out-header'\" class=\"call-out-window\">\n <div class=\"call-out-header\">\n <span>{{ callType === 'video' ? '视频通话' : '语音通话' }}</span>\n </div>\n <div class=\"call-out-body\">\n <div :class=\"['call-out-avatar', status === 'ringing' ? 'ringing' : status]\">\n {{ (calleeName || calleeUid)[0].toUpperCase() }}\n </div>\n <div class=\"call-out-name\">{{ calleeName || calleeUid }}</div>\n <div class=\"call-out-status\">\n <span v-if=\"status === 'ringing'\" class=\"ringing-dots\">\n 呼叫中<span class=\"dot\">·</span><span class=\"dot\">·</span><span class=\"dot\">·</span>\n </span>\n <span v-else :class=\"status\">{{ statusText }}</span>\n </div>\n </div>\n <div class=\"call-out-actions\">\n <button v-if=\"status === 'ringing'\" class=\"cancel-btn\" @click=\"cancel\">取消</button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.call-out-window {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 320px;\n background: #0f172a;\n border-radius: 0;\n padding: 24px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n color: #fff;\n z-index: 9999;\n text-align: center;\n}\n.call-out-header {\n cursor: move;\n font-size: 13px;\n color: #999;\n padding-bottom: 12px;\n border-bottom: 1px solid #333;\n margin-bottom: 16px;\n}\n.call-out-avatar {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: #fff;\n font-size: 36px;\n font-weight: 600;\n line-height: 80px;\n margin: 0 auto 12px;\n position: relative;\n}\n.call-out-avatar.ringing::before {\n content: '';\n position: absolute;\n inset: -8px;\n border: 2px solid #667eea;\n border-radius: 50%;\n animation: pulse 1.5s ease-out infinite;\n}\n.call-out-avatar.rejected,\n.call-out-avatar.cancelled {\n background: #888;\n}\n.call-out-avatar.busy,\n.call-out-avatar.no_answer {\n background: #d4a017;\n}\n@keyframes pulse {\n 0% {\n transform: scale(1);\n opacity: 0.8;\n }\n 100% {\n transform: scale(1.4);\n opacity: 0;\n }\n}\n.call-out-name {\n font-size: 16px;\n font-weight: 600;\n margin-bottom: 8px;\n}\n.call-out-status {\n font-size: 13px;\n color: #999;\n min-height: 20px;\n}\n.call-out-status .rejected,\n.call-out-status .cancelled {\n color: #888;\n}\n.call-out-status .busy,\n.call-out-status .no_answer {\n color: #d4a017;\n}\n.ringing-dots .dot {\n display: inline-block;\n animation: dot-bounce 1.4s infinite;\n}\n.ringing-dots .dot:nth-child(2) {\n animation-delay: 0.2s;\n}\n.ringing-dots .dot:nth-child(3) {\n animation-delay: 0.4s;\n}\n@keyframes dot-bounce {\n 0%,\n 60%,\n 100% {\n opacity: 0.3;\n }\n 30% {\n opacity: 1;\n }\n}\n.call-out-actions {\n margin-top: 24px;\n min-height: 40px;\n}\n.cancel-btn {\n padding: 8px 32px;\n background: #ef4444;\n color: #fff;\n border: none;\n border-radius: 0;\n cursor: pointer;\n font-size: 13px;\n transition: opacity 0.1s;\n}\n.cancel-btn:active {\n opacity: 0.8;\n}\n</style>\n","import type { App, Plugin } from 'vue'\nimport type { AtimClient } from '@quitting/core'\n\nimport { setupGlobalMethods } from './global-methods'\nimport Icon from './components/Icon.vue'\nimport CallWindow from './components/CallWindow.vue'\nimport CallDialog from './components/CallDialog.vue'\nimport CallOutgoing from './components/CallOutgoing.vue'\nimport MeetWindow from './components/MeetWindow.vue'\nimport IncomingRing from './components/IncomingRing.vue'\nimport MeetingInvite from './components/MeetingInvite.vue'\nimport ChatPanel from './components/ChatPanel.vue'\n\n// 半 UI SDK 插件\n// 用法(业务方 main.ts):\n// import AtimUI from '@quitting/ui'\n// import '@quitting/ui/style.css'\n// app.use(AtimUI, { client: atimClient })\n//\n// 之后任何业务组件都可调:\n// AtimUI.chat('user-1')\n// AtimUI.call('user-1', 'video')\n// AtimUI.meeting({ invitees: ['user-1','user-2'], title: '周会' })\nexport interface AtimUIOptions {\n client: AtimClient\n}\n\nexport const AtimUIPlugin: Plugin = {\n install(app: App, options: AtimUIOptions) {\n if (!options?.client) {\n throw new Error('AtimUI 安装时必须传入 client(已初始化的 AtimClient)')\n }\n // 注册全局组件\n app.component('AtimIcon', Icon)\n app.component('AtimCallWindow', CallWindow)\n app.component('AtimCallDialog', CallDialog)\n app.component('AtimCallOutgoing', CallOutgoing)\n app.component('AtimMeetWindow', MeetWindow)\n app.component('AtimIncomingRing', IncomingRing)\n app.component('AtimMeetingInvite', MeetingInvite)\n app.component('AtimChatPanel', ChatPanel)\n\n // 注入全局方法 + 自动监听邀请事件\n setupGlobalMethods(options.client, app)\n },\n}\n\nexport default AtimUIPlugin\n\n// 重新导出组件供按需引入\nexport {\n Icon,\n CallWindow,\n CallDialog,\n CallOutgoing,\n MeetWindow,\n IncomingRing,\n MeetingInvite,\n ChatPanel,\n}\nexport { AtimUI } from './global-methods'\n"],"mappings":"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"}