/* --------------------------------------------------------------------------- assistant/assistant.css — the AI assistant's chat surface (owner item 2). The shape is borrowed, reference by reference: ChatGPT's centred column and in-field composer, Glean's source chips under the field, Airtable's "New chat" panel. The PALETTE is Loopable's own — see AssistantPage.tsx's note on why the dark canvas and the vendor logos are the parts not copied. EVERY FONT SIZE IS A `--lp-fs-*` TOKEN AND EVERY COLOUR A `--lp-*` TOKEN. Wave-21 R5 is gated app-wide over every CSS file under src, and a literal here reds `web_ui` for the whole tree. No emojis, no ALL-CAPS chrome (DESIGN.md R6). AND DO NOT WRITE A GLOB IN THIS COMMENT: a star-star followed by a slash is the CSS comment terminator, and `lightningcss` then dies far from the cause. --------------------------------------------------------------------------- */ .as-chat { display: flex; width: 100%; height: 100%; min-width: 0; overflow: hidden; background: var(--lp-surface); } /* ── Airtable's panel: New chat, then the history ─────────────────────────── */ .as-history { flex: 0 0 var(--lp-rail-w); width: var(--lp-rail-w); min-width: var(--lp-rail-w); height: 100%; box-sizing: border-box; display: flex; flex-direction: column; padding: 10px 8px; border-right: 1px solid var(--lp-line); background: var(--lp-surface); overflow: hidden; } /* ── R14 / C2: the Chat / Query toggle, directly above New chat ───────────── */ .as-mode-toggle { flex: 0 0 auto; display: flex; gap: 2px; margin-bottom: 10px; padding: 2px; border-radius: var(--lp-r-md); background: var(--lp-surface-2); } .as-mode { flex: 1 1 0; min-width: 0; min-height: 26px; padding: 4px 8px; border: 0; border-radius: 6px; background: transparent; color: var(--lp-muted); font: inherit; font-size: var(--lp-fs-2xs); font-weight: 600; cursor: pointer; } .as-mode:hover { color: var(--lp-ink); } .as-mode.is-on { background: var(--lp-surface); color: var(--lp-ink); } /* The Query list is the SAME `QueryRail` the standalone page renders — only its host changed. These are the overrides a rail needs when it is nested inside a panel that is already one rail wide: it must FILL that panel rather than claim a second `--lp-rail-w` beside it, and it must not draw a second divider against the one `.as-history` already has. `.qy-rail-ask` goes because it links to the Assistant, which is the surface it is now inside. */ .as-history .qy-rail { flex: 1 1 auto; width: auto; min-width: 0; height: auto; min-height: 0; padding: 0; border-right: 0; } .as-history .qy-rail-ask { display: none; } /* Query fills the conversation column while the toggle is on Query. */ .as-conversation > .shell-db-frame, .as-conversation > .qy-empty { flex: 1 1 auto; min-height: 0; } .as-new { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 7px 10px; border: 1px solid var(--lp-line); border-radius: var(--lp-r-md); background: var(--lp-surface); color: var(--lp-ink); font: inherit; font-size: var(--lp-fs-xs); font-weight: 600; text-align: left; cursor: pointer; } .as-new:hover { background: var(--lp-surface-2); } .as-new svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; } .as-thread-list { flex: 1 1 auto; min-height: 0; margin-top: 10px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; } .as-thread-row { display: flex; align-items: center; min-width: 0; border-radius: var(--lp-r-md); } .as-thread-row:hover { background: var(--lp-surface-2); } .as-thread-row.is-active { background: var(--lp-blue-tint); } .as-thread-row.is-active .as-thread { font-weight: 600; } .as-thread { flex: 1 1 auto; min-width: 0; min-height: 30px; padding: 6px 10px; border: 0; border-radius: var(--lp-r-md); background: transparent; color: var(--lp-ink); font: inherit; font-size: var(--lp-fs-xs); text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; } .as-thread-del { flex: 0 0 auto; width: 24px; height: 24px; margin-right: 4px; display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 6px; background: transparent; color: var(--lp-muted); opacity: 0; cursor: pointer; } .as-thread-del svg { fill: none; stroke: currentColor; stroke-width: 1.3; stroke-linecap: round; stroke-linejoin: round; } .as-thread-row:hover .as-thread-del, .as-thread-row.is-active .as-thread-del, .as-thread-del:focus-visible { opacity: 1; } .as-thread-confirm { flex: 0 0 auto; margin-right: 4px; padding: 3px 8px; border: 0; border-radius: 6px; background: var(--lp-red-deep); color: var(--lp-surface); font: inherit; font-size: var(--lp-fs-2xs); font-weight: 600; cursor: pointer; } .as-thread-empty { margin: 8px 10px 0; color: var(--lp-muted); font-size: var(--lp-fs-2xs); } /* ── ChatGPT's column ─────────────────────────────────────────────────────── */ .as-conversation { flex: 1 1 auto; min-width: 0; height: 100%; display: flex; flex-direction: column; overflow: hidden; } /* One measure, used by the stream, the dock and the opening state, so the composer does not shift sideways when the first answer arrives. */ .as-column { width: 100%; max-width: 760px; margin: 0 auto; padding: 0 24px; box-sizing: border-box; } .as-stream { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 26px 0 8px; } .as-dock { flex: 0 0 auto; padding: 6px 0 18px; background: var(--lp-surface); } .as-opening { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 24px; box-sizing: border-box; text-align: center; } .as-opening-h { margin: 0 0 6px; color: var(--lp-ink); font-size: var(--lp-fs-lg); font-weight: 650; } .as-opening-p { max-width: 52ch; margin: 0 0 20px; color: var(--lp-muted); font-size: var(--lp-fs-sm); line-height: var(--lp-lh); } .as-opening .as-composer { width: 100%; max-width: 700px; text-align: left; } /* ── the messages ─────────────────────────────────────────────────────────── */ .as-message { margin: 0 0 22px; } .as-message--user { display: flex; justify-content: flex-end; } .as-bubble { max-width: 78%; margin: 0; padding: 9px 14px; border-radius: 18px; background: var(--lp-surface-2); color: var(--lp-ink); font-size: var(--lp-fs-sm); line-height: var(--lp-lh); white-space: pre-wrap; overflow-wrap: anywhere; } /* No bubble and no "Assistant" label: the reference prints the answer as the page's own prose, and a role label on every turn is the thing that makes a chat read like a transcript instead of a reply. */ .as-message-text { margin: 0; color: var(--lp-ink); font-size: var(--lp-fs-sm); line-height: var(--lp-lh); white-space: pre-wrap; overflow-wrap: anywhere; } .as-message--assistant.is-refusal .as-message-text { color: var(--lp-muted); } .as-numeric { display: flex; align-items: baseline; gap: 8px; margin: 10px 0 0; } .as-numeric-label { color: var(--lp-muted); font-size: var(--lp-fs-2xs); } .as-numeric-value { color: var(--lp-ink); font-size: var(--lp-fs-md); font-weight: 650; } .as-preview { display: flex; flex-direction: column; gap: 2px; margin-top: 12px; padding: 9px 13px; border: 1px solid var(--lp-line); border-radius: var(--lp-r-md); background: var(--lp-surface); font: inherit; text-align: left; cursor: pointer; } .as-preview:hover { background: var(--lp-surface-2); } .as-preview-name { color: var(--lp-ink); font-size: var(--lp-fs-xs); font-weight: 600; } .as-preview-meta { color: var(--lp-blue-solid); font-size: var(--lp-fs-2xs); font-weight: 600; } /* ── R20: the thumbs, which the server reads back into the next turn ───────── */ .as-rate { display: flex; align-items: center; gap: 4px; margin-top: 10px; } .as-rate-btn { width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 6px; background: transparent; color: var(--lp-muted); cursor: pointer; } .as-rate-btn svg { fill: none; stroke: currentColor; stroke-width: 1.3; stroke-linecap: round; stroke-linejoin: round; } .as-rate-btn:hover { background: var(--lp-surface-2); color: var(--lp-ink); } .as-rate-btn.is-on { background: var(--lp-blue-tint); color: var(--lp-blue-solid); } .as-rate-why-said { margin-left: 4px; color: var(--lp-muted); font-size: var(--lp-fs-2xs); } .as-rate-why { display: flex; gap: 6px; margin-top: 6px; } .as-rate-input { flex: 1 1 auto; min-width: 0; padding: 6px 10px; border: 1px solid var(--lp-line); border-radius: var(--lp-r-md); background: var(--lp-surface); color: var(--lp-ink); font: inherit; font-size: var(--lp-fs-2xs); } .as-rate-input:focus { outline: none; border-color: var(--lp-blue-solid); } .as-rate-send { flex: 0 0 auto; padding: 6px 12px; border: 1px solid var(--lp-line); border-radius: var(--lp-r-md); background: var(--lp-surface); color: var(--lp-ink); font: inherit; font-size: var(--lp-fs-2xs); font-weight: 600; cursor: pointer; } .as-rate-send:hover { background: var(--lp-surface-2); } .as-sources { margin-top: 8px; } .as-sources-btn { padding: 0; border: 0; background: transparent; color: var(--lp-muted); font: inherit; font-size: var(--lp-fs-2xs); font-weight: 600; cursor: pointer; } .as-sources-btn:hover { color: var(--lp-ink); } .as-sources-list { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; } .as-citation { color: var(--lp-muted); font-size: var(--lp-fs-2xs); line-height: var(--lp-lh); text-decoration: none; overflow-wrap: anywhere; } .as-citation:hover { color: var(--lp-blue-solid); text-decoration: underline; } .as-working { margin: 0 0 22px; } .as-problem { margin: 0 0 10px; color: var(--lp-red-deep); font-size: var(--lp-fs-2xs); } /* ── the composer ─────────────────────────────────────────────────────────── */ .as-field { border: 1px solid var(--lp-line); border-radius: 22px; background: var(--lp-surface); padding: 10px 8px 8px 16px; box-shadow: 0 1px 2px rgba(20, 24, 40, 0.05); } .as-field:focus-within { border-color: var(--lp-blue-solid); } .as-prompt { display: block; width: 100%; max-height: 180px; border: 0; outline: none; padding: 0 8px 0 0; background: transparent; color: var(--lp-ink); font: inherit; font-size: var(--lp-fs-sm); line-height: var(--lp-lh); resize: none; box-sizing: border-box; } .as-prompt::placeholder { color: var(--lp-muted); } .as-field-foot { display: flex; align-items: center; gap: 12px; margin-top: 8px; } .as-pick { display: inline-flex; align-items: center; gap: 5px; min-width: 0; } .as-pick-label { color: var(--lp-muted); font-size: var(--lp-fs-2xs); } /* Text, not a boxed control — ChatGPT's "Instant" reads as part of the field. */ .as-pick-select { max-width: 150px; border: 0; background: transparent; color: var(--lp-ink); font: inherit; font-size: var(--lp-fs-2xs); font-weight: 600; cursor: pointer; } .as-send { flex: 0 0 auto; margin-left: auto; width: 30px; height: 30px; display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 999px; background: var(--lp-ink); color: var(--lp-surface); cursor: pointer; } .as-send svg { fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; } .as-send:disabled { background: var(--lp-surface-2); color: var(--lp-muted); cursor: default; } /* ── Glean's chips ────────────────────────────────────────────────────────── */ .as-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; padding: 0 6px; } .as-chip { display: inline-flex; align-items: center; gap: 6px; max-width: 220px; padding: 5px 11px; border: 1px solid transparent; border-radius: 999px; background: var(--lp-surface-2); color: var(--lp-ink); font: inherit; font-size: var(--lp-fs-2xs); font-weight: 500; cursor: pointer; } .as-chip:hover { border-color: var(--lp-line); } .as-chip.is-selected { background: var(--lp-blue-tint); border-color: var(--lp-blue-tint); font-weight: 600; } /* The one the answer will be built on, when more than one is picked (R3). */ .as-chip.is-target { border-color: var(--lp-blue-solid); } .as-chip-mark { flex: 0 0 auto; display: inline-flex; width: 14px; height: 14px; color: var(--lp-muted); } .as-chip-mark svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; } .as-chip.is-selected .as-chip-mark { color: var(--lp-blue-solid); } .as-chip-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* A source this caller holds and cannot ask (see AssistantPage's note): still visible, because an artefact already built on it stays listed — but plainly not a door. */ .as-chip.is-blocked { color: var(--lp-muted); background: transparent; border-color: var(--lp-line); border-style: dashed; cursor: default; } .as-chip.is-blocked .as-chip-mark { opacity: 0.5; } .as-chip.is-blocked:hover { border-color: var(--lp-line); } .as-chips-note { flex: 1 0 100%; margin: 2px 0 0; color: var(--lp-muted); font-size: var(--lp-fs-2xs); } .as-chips-empty { margin: 2px 0 0; color: var(--lp-muted); font-size: var(--lp-fs-2xs); }