feat(desktop): 统一聊天与 Conductor 对话布局并复用 composer

引入 page--chat-ui 共用壳层与 bindComposerInRoot;Conductor 提示词气泡独立于输入框;修正侧栏拖拽热区对齐缝隙。
This commit is contained in:
lyx committed 2026-06-05 02:37:22 +08:00
1 parent 598a869cb3
commit a8da958d34
3 files changed
+153 -209

No files matched your search

+88 -144
View File
@@ -3069,7 +3069,7 @@ function openModelMenu(chipEl, menuEl) {
renderModelMenu(menuEl);
menuEl.hidden = false;
const chipRect = chipEl.getBoundingClientRect();
const composer = chipEl.closest('.cdb-composer') || chipEl.closest('.composer');
const composer = chipEl.closest('.composer');
if (composer) {
const composerRect = composer.getBoundingClientRect();
menuEl.style.left = (chipRect.left - composerRect.left) + 'px';
@@ -3204,22 +3204,22 @@ let activeFileComposer = 'chat';
function fileCtx(f) { return f.ctx || 'chat'; }
function filesForCtx(ctx) { return state.pendingFiles.filter(f => fileCtx(f) === ctx); }
function composerPageEl(ctx) {
const page = ctx === 'collab' ? 'collab' : 'chat';
return document.querySelector(`.page--chat-ui[data-page="${page}"]`);
}
function composerRootEl(ctx) {
return composerPageEl(ctx)?.querySelector('.composer');
}
function composerCfg(ctx = activeFileComposer) {
if (ctx === 'collab') {
return {
input: document.getElementById('cdb-input'),
strip: document.getElementById('cdb-thumb-strip'),
uploadBtn: null,
imgInput: document.getElementById('cdb-file-input'),
dropZone: document.querySelector('.page[data-page="collab"]'),
};
}
const root = composerRootEl(ctx);
const page = composerPageEl(ctx);
return {
input: inputEl,
strip: thumbStrip,
input: root?.querySelector('.composer-inset .input') || null,
strip: root?.querySelector('.thumb-strip') || null,
uploadBtn: null,
imgInput: document.getElementById('chat-file-input'),
dropZone: chatPanel,
imgInput: root?.querySelector('input[type="file"]') || null,
dropZone: ctx === 'collab' ? page : chatPanel,
};
}
@@ -4692,28 +4692,28 @@ chatStatus.setConnecting();
window.ga.startBridge && window.ga.startBridge();
})();
/* 聊天输入框 — 与 Conductor collabComposer 同构(inset + 加号菜单 + Enter 发送) */
(function () {
'use strict';
const root = document.getElementById('chat-composer');
if (!root) return;
const input = document.getElementById('chat-input');
const fileInput = document.getElementById('chat-file-input');
const plusBtn = document.getElementById('chat-plus-btn');
const menu = document.getElementById('chat-menu');
const sendBtnEl = document.getElementById('send-btn');
/* 聊天 / Conductor 共用 composer 绑定(结构:.composer > .composer-slot > .composer-inset) */
function bindComposerInRoot(root, opts) {
if (!root || root.dataset.composerBound) return null;
root.dataset.composerBound = '1';
const ctx = opts.ctx || root.dataset.composerCtx || 'chat';
const input = root.querySelector('.composer-inset .input');
const fileInput = root.querySelector('input[type="file"]');
const plusBtn = root.querySelector('.composer-plus');
const menu = root.querySelector('.composer-menu');
const sendBtn = root.querySelector('.send');
function closeMenu() {
if (!menu) return;
menu.hidden = true;
if (plusBtn) plusBtn.setAttribute('aria-expanded', 'false');
plusBtn?.setAttribute('aria-expanded', 'false');
}
function openMenu() {
if (!menu || !plusBtn) return;
closeAllModelMenus?.();
window.collabComposer?.closeMenu?.();
if (ctx === 'chat') window.collabComposer?.closeMenu?.();
else window.chatComposer?.closeMenu?.();
menu.hidden = false;
plusBtn.setAttribute('aria-expanded', 'true');
}
@@ -4724,155 +4724,99 @@ window.ga.startBridge && window.ga.startBridge();
else closeMenu();
}
function doSend() {
const sess = activeSess();
if (sess && rt(sess).busy) { cancelPrompt(); return; }
submitInput();
}
function doSend() { opts.onSend?.(); }
function bindOnce() {
if (root.dataset.bound) return;
root.dataset.bound = '1';
plusBtn?.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
toggleMenu();
});
plusBtn?.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
toggleMenu();
});
menu?.addEventListener('click', (e) => {
const item = e.target.closest('[data-composer-action]');
if (!item) return;
e.stopPropagation();
closeMenu();
const act = item.dataset.composerAction;
if (act === 'upload') {
window.gaSetActiveFileComposer?.(ctx);
fileInput?.click();
return;
}
if (act === 'preset') openModal('preset-modal');
});
menu?.addEventListener('click', (e) => {
const item = e.target.closest('.ga-menu-item');
if (!item) return;
e.stopPropagation();
closeMenu();
if (item.id === 'chat-menu-upload') {
window.gaSetActiveFileComposer?.('chat');
fileInput?.click();
return;
}
if (item.id === 'chat-menu-preset') {
openModal('preset-modal');
}
});
input?.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey && !e.isComposing && e.keyCode !== 229) {
e.preventDefault();
doSend();
}
});
sendBtnEl?.addEventListener('click', (e) => {
input?.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey && !e.isComposing && e.keyCode !== 229) {
e.preventDefault();
doSend();
});
}
}
});
bindOnce();
window.chatComposer = { closeMenu, focus: () => input?.focus() };
sendBtn?.addEventListener('click', (e) => {
e.preventDefault();
doSend();
});
opts.afterBind?.(root, { input, closeMenu, doSend });
return { ctx, input, closeMenu, focus: () => input?.focus() };
}
(function () {
'use strict';
const root = document.getElementById('chat-composer');
const bound = bindComposerInRoot(root, {
ctx: 'chat',
onSend() {
const sess = activeSess();
if (sess && rt(sess).busy) { cancelPrompt(); return; }
submitInput();
},
});
if (bound) window.chatComposer = { closeMenu: bound.closeMenu, focus: bound.focus };
})();
/* Conductor 输入框 — 与主聊天 composer 解耦(cdb-* DOM,便于日后抽离) */
(function () {
'use strict';
const CTX = 'collab';
const root = document.getElementById('cdb-composer');
if (!root) return;
const input = document.getElementById('cdb-input');
const fileInput = document.getElementById('cdb-file-input');
const plusBtn = document.getElementById('cdb-plus-btn');
const menu = document.getElementById('cdb-menu');
const sendBtn = document.getElementById('cdb-send');
let onSend = null;
const input = root.querySelector('.composer-inset .input');
const sendBtn = root.querySelector('.send');
function text() { return window.gaComposerText?.('collab') ?? ''; }
function clearIfMatch(raw) {
if (input && text().trim() === String(raw || '').trim()) input.innerHTML = '';
}
function setEnabled(on) {
if (input) input.contentEditable = on ? 'true' : 'false';
if (sendBtn) sendBtn.disabled = !on;
}
function closeMenu() {
if (!menu) return;
menu.hidden = true;
if (plusBtn) plusBtn.setAttribute('aria-expanded', 'false');
}
function openMenu() {
if (!menu || !plusBtn) return;
closeAllModelMenus?.();
menu.hidden = false;
plusBtn.setAttribute('aria-expanded', 'true');
}
function toggleMenu() {
if (!menu) return;
if (menu.hidden) openMenu();
else closeMenu();
}
function doSend() {
if (!onSend) return;
onSend(text());
}
function bindOnce() {
if (root.dataset.bound) return;
root.dataset.bound = '1';
plusBtn?.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
toggleMenu();
});
menu?.addEventListener('click', (e) => {
const item = e.target.closest('.ga-menu-item');
if (!item) return;
e.stopPropagation();
closeMenu();
if (item.id === 'cdb-menu-upload') {
window.gaSetActiveFileComposer?.(CTX);
fileInput?.click();
return;
}
});
document.querySelectorAll('#collab-quick [data-prompt-key]').forEach((btn) => {
btn.addEventListener('click', () => {
if (!onSend) return;
const key = btn.dataset.promptKey;
onSend((window.gaT && window.gaT(key)) || key);
const bound = bindComposerInRoot(root, {
ctx: 'collab',
onSend() { if (onSend) onSend(text()); },
afterBind() {
document.querySelectorAll('#collab-quick [data-prompt-key]').forEach((btn) => {
btn.addEventListener('click', () => {
if (!onSend) return;
const key = btn.dataset.promptKey;
onSend((window.gaT && window.gaT(key)) || key);
});
});
});
input?.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey && !e.isComposing && e.keyCode !== 229) {
e.preventDefault();
doSend();
}
});
sendBtn?.addEventListener('click', (e) => {
e.preventDefault();
doSend();
});
}
},
});
if (!bound) return;
function init(handler) {
onSend = handler;
bindOnce();
}
window.collabComposer = {
init, text, clearIfMatch, setEnabled,
focus: () => input?.focus(),
closeMenu,
focus: bound.focus,
closeMenu: bound.closeMenu,
};
})();
+27 -20
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<title>GenericAgent</title>
<link rel="stylesheet" href="assets/fonts/fonts.css">
<link rel="stylesheet" href="styles.css?v=124">
<link rel="stylesheet" href="styles.css?v=127">
<!-- KaTeX: LaTeX 公式渲染 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" crossorigin="anonymous">
<!-- highlight.js: 代码高亮 -->
@@ -98,8 +98,7 @@
<div id="pages">
<!-- 聊天 -->
<section class="page active" data-page="chat"
>
<section class="page active page--chat-ui" data-page="chat">
<div class="page-top">
<button type="button" class="pt-icon pt-sb-toggle" aria-label="toggle sidebar">
<span data-ga-icon="caretLeft"></span>
@@ -131,7 +130,7 @@
</div>
</div>
<div class="composer-anchor" id="chat-composer-anchor">
<div class="composer" id="chat-composer">
<div class="composer" id="chat-composer" data-composer-ctx="chat">
<div class="composer-slot">
<div class="plan-card" id="plan-bar" hidden aria-live="polite"></div>
<div class="composer-inset">
@@ -143,11 +142,11 @@
<span data-ga-icon="plus"></span>
</button>
<div class="ga-menu composer-menu" id="chat-menu" hidden role="menu">
<button type="button" class="ga-menu-item" id="chat-menu-upload" role="menuitem">
<button type="button" class="ga-menu-item" data-composer-action="upload" role="menuitem">
<span data-ga-icon="paperclip"></span>
<span data-i18n="upload.button"></span>
</button>
<button type="button" class="ga-menu-item" id="chat-menu-preset" role="menuitem">
<button type="button" class="ga-menu-item" data-composer-action="preset" role="menuitem">
<span data-ga-icon="lightning"></span>
<span data-i18n="modal.preset"></span>
</button>
@@ -197,8 +196,8 @@
</div>
</section>
<!-- 指挥家 Conductor -->
<section class="page" data-page="collab">
<!-- 指挥家 Conductor(对话区结构复用 page--chat-ui / chat-shell) -->
<section class="page page--chat-ui" data-page="collab">
<div class="collab-offline" id="collab-offline" hidden>
<span data-i18n="collab.offline"></span>
<button type="button" class="chip sm" id="collab-retry" data-i18n="collab.retry"></button>
@@ -215,6 +214,8 @@
<span data-ga-icon="caretRight"></span>
</button>
</div>
<div class="chat-shell">
<div class="chat-panel">
<div class="msg-area collab-msgs" id="collab-msgs">
<div class="collab-scroll">
<div class="collab-guide" id="collab-welcome" hidden>
@@ -275,22 +276,24 @@
</aside>
</div>
</div>
<div class="composer cdb-composer" id="cdb-composer">
<div class="collab-quick" id="collab-quick">
<button type="button" class="pill" data-prompt-key="collab.chipProgress" data-i18n="collab.chipProgress"></button>
<button type="button" class="pill" data-prompt-key="collab.chipPause" data-i18n="collab.chipPause"></button>
<button type="button" class="pill" data-prompt-key="collab.chipSummary" data-i18n="collab.chipSummary"></button>
</div>
<div class="composer-inset cdb-inset">
<div class="collab-quick" id="collab-quick">
<button type="button" class="pill" data-prompt-key="collab.chipProgress" data-i18n="collab.chipProgress"></button>
<button type="button" class="pill" data-prompt-key="collab.chipPause" data-i18n="collab.chipPause"></button>
<button type="button" class="pill" data-prompt-key="collab.chipSummary" data-i18n="collab.chipSummary"></button>
</div>
<div class="composer-anchor" id="cdb-composer-anchor">
<div class="composer" id="cdb-composer" data-composer-ctx="collab">
<div class="composer-slot">
<div class="composer-inset">
<div class="thumb-strip" id="cdb-thumb-strip" hidden></div>
<div class="input" id="cdb-input" contenteditable="true" role="textbox" aria-multiline="true" data-i18n-ph="collab.placeholder"></div>
<div class="composer-bot">
<div class="composer-plus-wrap cdb-plus-wrap">
<button type="button" class="ic-btn composer-plus cdb-plus" id="cdb-plus-btn" aria-haspopup="menu" aria-expanded="false" data-i18n-title="collab.plusMenu" title="">
<div class="composer-plus-wrap">
<button type="button" class="ic-btn composer-plus" id="cdb-plus-btn" aria-haspopup="menu" aria-expanded="false" data-i18n-title="collab.plusMenu" title="">
<span data-ga-icon="plus"></span>
</button>
<div class="ga-menu cdb-menu" id="cdb-menu" hidden role="menu">
<button type="button" class="ga-menu-item" id="cdb-menu-upload" role="menuitem">
<div class="ga-menu composer-menu" id="cdb-menu" hidden role="menu">
<button type="button" class="ga-menu-item" data-composer-action="upload" role="menuitem">
<span data-ga-icon="paperclip"></span>
<span data-i18n="upload.button"></span>
</button>
@@ -306,8 +309,12 @@
</button>
</div>
</div>
</div>
<div class="ga-menu" id="cdb-model-menu" hidden></div>
<input type="file" id="cdb-file-input" multiple hidden>
</div>
</div>
</div>
</div>
</section>
@@ -598,6 +605,6 @@
<!-- highlight.js -->
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js" crossorigin="anonymous"></script>
<script src="phosphor-icons.js"></script>
<script src="app.js?v=161"></script>
<script src="app.js?v=162"></script>
</body>
</html>
+38 -45
View File
@@ -685,28 +685,23 @@ html[data-appearance="dark"] .swatch[data-theme="8"].sel::after{
.page-title{ font-size:18px; font-weight:700; color:var(--txt-strong); font-family:var(--font-title-en); }
.page-sub{ font-size:12px; color:var(--muted); margin-top:5px; margin-bottom:16px; }
/* 聊天页:撑满主栏;消息区顶 = 新对话顶;输入框与消息区同宽 */
.page.active[data-page="chat"]{ min-height:0; width:100%; }
.page.active[data-page="chat"] > .page-top{ flex:0 0 var(--shell-head-h); }
.chat-shell{
/* 聊天 / Conductor 共用对话布局(page--chat-ui) */
.page.page--chat-ui{ min-height:0; width:100%; }
.page.page--chat-ui > .page-top{ flex:0 0 var(--shell-head-h); }
.page.page--chat-ui > .chat-shell{
flex:1; min-height:0; width:100%;
margin-top:var(--shell-gap);
padding:0 16px 16px;
display:flex; flex-direction:column;
--chat-gutter:0;
box-sizing:border-box;
}
.chat-panel{
.page.page--chat-ui > .chat-shell > .chat-panel{
flex:1; min-height:0; width:100%;
display:flex; flex-direction:column;
gap:var(--chat-stack-gap);
border:none;
border-radius:0;
overflow:visible;
box-sizing:border-box;
background:transparent;
}
.page.active[data-page="chat"] .msg-area{
.page.page--chat-ui .msg-area{
position:relative;
flex:1; min-height:0;
width:100%;
@@ -723,31 +718,31 @@ html[data-appearance="dark"] .swatch[data-theme="8"].sel::after{
background:var(--card);
box-sizing:border-box;
}
.page.active[data-page="chat"] .composer-anchor,
.page.active[data-page="chat"] .composer,
.page.active[data-page="chat"] .composer-slot,
.page.active[data-page="chat"] .composer-inset{
.page.page--chat-ui .composer-anchor,
.page.page--chat-ui .composer,
.page.page--chat-ui .composer-slot,
.page.page--chat-ui .composer-inset{
width:100%;
min-width:0;
box-sizing:border-box;
}
.page.active[data-page="chat"] .composer-anchor{
.page.page--chat-ui .composer-anchor{
flex:0 0 auto;
padding:0;
margin:0;
background:transparent;
}
.page.active[data-page="chat"] .composer-inset{
.page.page--chat-ui .composer-inset{
border:1px solid var(--line);
border-radius:var(--radius);
background:var(--card);
}
.page.active[data-page="chat"] .msgs{
.page.page--chat-ui .msgs{
width:100%;
max-width:var(--chat-col-w);
margin:0 auto;
}
.page.active[data-page="chat"] .chat-start{
.page.page--chat-ui .chat-start{
width:100%;
max-width:var(--chat-col-w);
margin:0 auto;
@@ -1164,8 +1159,7 @@ span.flatpickr-weekday{ color:var(--muted) !important; font-size:11px; }
}
.composer-inset .composer-bot .spacer{ flex:1; }
.composer-plus-wrap{ position:relative; flex:0 0 auto; }
.composer-plus-wrap .composer-menu,
.composer-plus-wrap .cdb-menu{
.composer-plus-wrap .composer-menu{
left:0; right:auto; top:auto; bottom:calc(100% + 6px); min-width:180px;
}
.composer-plus{ width:34px; height:34px; }
@@ -1240,12 +1234,12 @@ span.flatpickr-weekday{ color:var(--muted) !important; font-size:11px; }
.body.sb-collapsed .sb-toggle:hover{ opacity:1; background:var(--panel); border-color:var(--line-strong); }
.rp-resize, .sb-resize{
width:4px; flex:0 0 4px; cursor:col-resize; border-radius:2px;
background:transparent; transition:background .15s; align-self:stretch;
position:relative; z-index:5; /* gap 缩小后会与面板重叠,提到面板之上保证可拖 */
width:100%; min-width:0; cursor:col-resize; border-radius:2px;
background:transparent; transition:background .15s;
align-self:stretch; justify-self:stretch;
position:relative; z-index:5;
margin:0;
}
.rp-resize{ margin-right:-10px; }
.sb-resize{ margin-left:-10px; }
.rp-resize:hover, .rp-resize.dragging,
.sb-resize:hover, .sb-resize.dragging{ background:var(--accent); }
.body.rp-collapsed .rp-resize,
@@ -1606,12 +1600,10 @@ html[data-appearance="dark"] .plan-item--warn .plan-item-text{ color:#f0a020; }
}
.ga-menu-item:hover{ background:var(--hover-2); }
.ga-menu-item.active{ color:var(--accent); }
.composer-menu .ga-menu-item,
.cdb-menu .ga-menu-item{
.composer-menu .ga-menu-item{
display:flex; align-items:center; gap:8px;
}
.composer-menu .ga-menu-item svg,
.cdb-menu .ga-menu-item svg{ width:16px; height:16px; flex:0 0 auto; color:var(--icon); }
.composer-menu .ga-menu-item svg{ width:16px; height:16px; flex:0 0 auto; color:var(--icon); }
/* ─── chat composer: image thumb strip ─── */
.thumb-strip{
@@ -2137,7 +2129,7 @@ html[data-appearance="dark"] .bubble.md pre code{ color:var(--txt-2); }
border-radius:var(--radius-sm);
}
/* ---- Conductor(drawer/composer 工具栏除外,对话区复用主聊天 .page-top / .msg-area / .composer) ---- */
/* ---- Conductor(仅保留与主聊天不同的部分,布局见 .page--chat-ui) ---- */
.collab-offline,.collab-reconnect{
flex:0 0 auto; padding:10px 16px; font-size:13px; text-align:center;
background:color-mix(in srgb, var(--amber) 12%, var(--panel));
@@ -2148,21 +2140,21 @@ html[data-appearance="dark"] .bubble.md pre code{ color:var(--txt-2); }
.collab-rail-run[hidden], .collab-rail-done[hidden], .collab-rail-issue[hidden]{ display:none !important; }
.collab-offline:not([hidden]), .collab-reconnect:not([hidden]){ display:flex; }
.collab-reconnect{ background:color-mix(in srgb, var(--accent) 10%, var(--panel)); }
.page[data-page="collab"] .msg-area.collab-msgs{ position:relative; overflow:hidden; padding:0; }
.page[data-page="collab"] .collab-scroll{
.page.page--chat-ui[data-page="collab"] .msg-area.collab-msgs{ overflow:hidden; padding:0; }
.page.page--chat-ui[data-page="collab"] .collab-scroll{
flex:1 1 0; min-height:0; overflow:auto;
display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px;
padding:24px;
padding:16px;
}
.page[data-page="collab"] .collab-msgs.has-msgs .collab-scroll{
.page.page--chat-ui[data-page="collab"] .collab-msgs.has-msgs .collab-scroll{
align-items:stretch; justify-content:flex-start;
}
.page[data-page="collab"] .collab-overlay{
.page.page--chat-ui[data-page="collab"] .collab-overlay{
position:absolute; inset:0; z-index:10; pointer-events:none;
}
.page[data-page="collab"] .collab-overlay > .collab-rail,
.page[data-page="collab"] .collab-overlay > .collab-prog-panel{ pointer-events:auto; }
.page[data-page="collab"].dragover .collab-msgs::after{
.page.page--chat-ui[data-page="collab"] .collab-overlay > .collab-rail,
.page.page--chat-ui[data-page="collab"] .collab-overlay > .collab-prog-panel{ pointer-events:auto; }
.page.page--chat-ui[data-page="collab"].dragover .collab-msgs::after{
content:attr(data-drop-hint); position:absolute; inset:8px; z-index:20; pointer-events:none;
display:flex; align-items:center; justify-content:center; border-radius:var(--radius);
border:2px dashed var(--accent); background:color-mix(in srgb, var(--accent) 8%, var(--panel));
@@ -2173,7 +2165,7 @@ html[data-appearance="dark"] .bubble.md pre code{ color:var(--txt-2); }
transform:translateY(-50%);
transition:left .22s ease-out;
}
.page[data-page="collab"].collab-prog-open .collab-rail{ left:232px; }
.page.page--chat-ui[data-page="collab"].collab-prog-open .collab-rail{ left:232px; }
.collab-rail-handle{
display:flex; flex-direction:column; align-items:center; justify-content:center; gap:5px;
width:22px; padding:10px 3px; cursor:pointer;
@@ -2190,7 +2182,7 @@ html[data-appearance="dark"] .bubble.md pre code{ color:var(--txt-2); }
opacity:.55;
}
.collab-rail-chev{ width:12px; height:12px; flex:0 0 auto; transition:transform .22s ease-out; }
.page[data-page="collab"].collab-prog-open .collab-rail-chev{ transform:rotate(180deg); }
.page.page--chat-ui[data-page="collab"].collab-prog-open .collab-rail-chev{ transform:rotate(180deg); }
.collab-rail-badge{
display:flex; flex-direction:column; align-items:center; gap:2px;
font-size:10px; font-weight:700; line-height:1;
@@ -2228,7 +2220,7 @@ html[data-appearance="dark"] .bubble.md pre code{ color:var(--txt-2); }
transform:translateX(-100%); transition:transform .22s ease-out;
pointer-events:none;
}
.page[data-page="collab"].collab-prog-open .collab-prog-panel{
.page.page--chat-ui[data-page="collab"].collab-prog-open .collab-prog-panel{
transform:translateX(0); pointer-events:auto;
}
.collab-prog-panel-head{
@@ -2348,12 +2340,13 @@ html[data-appearance="dark"] .bubble.md pre code{ color:var(--txt-2); }
margin-left:auto; min-width:18px; height:18px; padding:0 5px; border-radius:999px;
background:var(--ok); color:#fff; font-size:10px; font-weight:700; line-height:18px; text-align:center;
}
.page[data-page="collab"] .collab-quick{
display:flex; flex-wrap:wrap; gap:8px; margin-bottom:10px;
.page.page--chat-ui[data-page="collab"] > .chat-shell > .chat-panel > .collab-quick{
flex:0 0 auto;
display:flex; flex-wrap:wrap; gap:8px;
}
@media (max-width:900px){
.collab-guide-steps{ grid-template-columns:repeat(2, minmax(0, 1fr)); gap:40px 32px; }
.collab-guide-step{ gap:20px; }
.page[data-page="collab"].collab-prog-open .collab-rail{ left:min(232px, 72vw); }
.page.page--chat-ui[data-page="collab"].collab-prog-open .collab-rail{ left:min(232px, 72vw); }
.collab-prog-panel{ width:min(232px, 72vw); }
}