ouroboros/web/style.css
Ouroboros eda1181f46 fix(chat): the pointer row is complete, live wait facts win, one status word leads
Roast findings (codex gpt-6-astra) folded in:

- The pointer row carries the question, option labels, recorded answer and the
  wait facts from history, the live delivery and the activity census
  (`project_question_pointer`, `owner_wait_projection`; both contract mirrors),
  so Main paints it from the row alone. The IntersectionObserver hydration, the
  settled-source cache and the Retry state are gone: freshness is the ordinary
  history reconciliation plus the `quiz_state` frame, and task detail is read
  only to open the original form.
- Lifecycle observation precedence: once a live frame closed a wait, an older
  history row or a detail read begun before it cannot reopen «Waiting»; an
  unavailable row keeps what is known; a settled question never reopens. The
  production timeout frame (`wait_for_answer:false`, no `wait_ended_at`) is the
  shape the tests use.
- Parity: history attaches the task's wait record to the Project room's quiz rows
  (a wait the owner resumed by ordinary input leaves no frame behind), the quiz
  card reads those facts, and the parity fixture now pins the rows Python emits
  against what the browser reads from them.
- Wording leads with one word — «Waiting for your answer» / «Unanswered · …» /
  «You answered» / «Replaced by a newer question» / «Status unavailable» — with
  three action labels; status and source lines use meta ink (DESIGN: the owner
  reads them to act). The wait-ended line names the default path the task took
  and that silence was not consent; a late answer's toast says where it went.
- Previews bound the option and the comment separately and never cut for less
  than the marker costs.
- Module map row for `question_presentation.js`; DEVELOPMENT 11 points at the
  ARCHITECTURE data flow instead of restating it; a stale comment in
  `owner_quiz.py`; `contracts.py` pays its 1600-line gate down by compacting
  five comment blocks.
2026-09-17 19:30:17 +03:00

7360 lines
183 KiB
CSS

/* Shared values and controls live in ui.css; this sheet owns page composition. */
* { margin: 0; padding: 0; box-sizing: border-box; }
/* The universal reset above also clears the UA's `dialog { margin: auto }`,
which is what centres a native modal dialog in its viewport — without it
every `showModal()` surface lands in the top-left corner. Restore it for
dialogs as a class, not per dialog. */
dialog { margin: auto; }
[hidden] {
display: none !important;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
height: var(--vvh);
overflow: hidden;
}
#app {
display: grid;
grid-template-columns: var(--sidebar-width) minmax(0, 1fr) auto;
height: var(--vvh);
position: relative;
}
#matrix-rain {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.12;
pointer-events: none;
z-index: 0;
}
/* App navigation shell: desktop persistent sidebar, mobile drawer below. */
.mobile-nav-toggle {
display: none;
}
.nav-drawer-backdrop {
display: none;
}
#primary-sidebar {
height: var(--vvh);
display: flex;
flex-direction: column;
min-width: 0;
padding: var(--space-4) var(--space-3) var(--space-3);
background:
linear-gradient(180deg, rgba(13, 11, 15, 0.62), rgba(13, 11, 15, 0.32)),
radial-gradient(circle at 8% 0%, var(--accent-08), transparent 260px);
border-right: 1px solid var(--divider);
z-index: 10;
position: relative; /* positioning context for the resize handle */
}
/* Resizable side-section drag handles (v6.33.0). Thin edge strips that set
--sidebar-width / --project-panel-width; hidden under the mobile drawer. */
/* CW10 (v6.34.0): suppress text selection while dragging via a class, not a JS
inline style (DEVELOPMENT.md: no element.style.* property mutations in UI JS). */
body.resizing-panels { user-select: none; }
.resize-handle {
position: absolute;
top: 0;
bottom: 0;
width: 8px;
cursor: col-resize;
z-index: 20;
touch-action: none;
}
.resize-handle-right { right: -4px; }
.resize-handle-left { left: -4px; }
.resize-handle:hover { background: var(--accent-10); }
@media (max-width: 980px) {
.resize-handle { display: none; }
}
.sidebar-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: var(--space-1);
scrollbar-width: thin;
}
.sidebar-scroll > * { flex-shrink: 0; }
#nav-version {
display: block;
max-width: 100%;
text-align: left;
line-height: 1.15;
white-space: normal;
font-size: 8px;
color: var(--text-muted);
padding: var(--space-2) var(--space-2) 0;
opacity: 0.6;
}
.nav-row,
.nav-section-toggle {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: var(--space-3);
min-height: 44px;
padding: 0 var(--space-3);
width: 100%;
border: 1px solid transparent;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
border-radius: var(--nav-item-radius);
transition: background 0.15s ease, color 0.15s ease;
font-size: 13px;
font-family: inherit;
text-align: left;
position: relative;
}
.nav-row svg,
.nav-section-toggle svg {
flex: 0 0 auto;
width: 18px;
height: 18px;
}
.nav-row-icon {
flex: 0 0 18px;
width: 18px;
height: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.nav-row-label {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nav-row-meta {
flex: 0 0 auto;
color: var(--text-muted);
font-size: 11px;
}
/* Clean left active indicator (Codex-style), not a heavy glowing tile. */
.nav-row.active::before,
.nav-section-toggle.active::before {
content: '';
position: absolute;
left: 3px;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 17px;
border-radius: 0 3px 3px 0;
background: var(--accent);
}
.unread-badge {
position: absolute;
top: 4px;
right: 8px;
min-width: 18px;
height: 18px;
padding: 0 5px;
border-radius: var(--radius-sm);
background: var(--red);
color: #fff;
font-size: var(--type-meta);
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
/* Per-project "new activity" indicator: a standard small red dot at the row's end. */
.nav-project-row { position: relative; }
.nav-unread-dot {
flex: 0 0 auto;
margin-left: auto;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--red);
box-shadow: 0 0 0 2px var(--accent-18);
}
.nav-row:hover,
.nav-section-toggle:hover {
background: var(--accent-08);
border-color: transparent;
color: var(--text-primary);
}
.nav-row.active,
.nav-section-toggle.active {
background: var(--accent-12);
border-color: transparent;
color: var(--accent);
}
.nav-row:focus,
.nav-section-toggle:focus,
.app-tab:focus,
.chat-header-btn:focus,
.chat-header-more > summary:focus {
outline: none;
}
/* Inset: these sit flush inside the sidebar / header strip, which clips an
outer ring (docs/DESIGN.md "Focus"). */
.nav-row:focus-visible,
.nav-section-toggle:focus-visible,
.app-tab:focus-visible,
.chat-header-btn:focus-visible,
.chat-header-more > summary:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: -2px;
}
.chat-live-actions {
display: flex;
justify-content: flex-end;
gap: var(--space-2);
padding: 0 14px var(--space-2);
}
/* S3 (Q2/HQ1): the three-action task stop/hurry dropdown (Chat + Activity). */
.task-control-menu {
position: fixed;
top: var(--tcm-top, 0);
left: var(--tcm-left, 0);
z-index: 120;
width: min(220px, calc(100vw - 16px));
max-height: var(--tcm-max-height, calc(100vh - 16px));
overflow-y: auto;
padding: var(--space-2);
border: 1px solid var(--surface-border);
border-radius: var(--radius-md);
background: rgba(13, 11, 15, 0.94);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.34);
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.task-control-item {
border: none;
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-secondary);
font: inherit;
font-size: 13px;
text-align: left;
padding: var(--space-2) var(--space-3);
cursor: pointer;
}
.task-control-item:hover:not(:disabled) {
color: var(--text-primary);
background: var(--menu-item-hover);
}
.task-control-item.danger {
color: rgba(248, 113, 113, 0.95);
}
.task-control-item:disabled {
opacity: 0.6;
cursor: default;
}
/* HQ1 compact card status: hurry applied — a phase chip, never a chat bubble. */
.chat-live-card[data-owner-hurry] [data-live-phase]::after {
content: ' ⏩';
}
/* Layout-only composition for question, Project lifecycle and routing
actions, with room for keyboard focus above and below. Buttons use the shared
`.btn.btn-default.btn-sm` role — no new colors, radii, or variants. */
.system-message-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
padding-block: var(--space-3);
}
.system-message-action {
flex: 0 1 auto;
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
}
.project-question-pointer {
display: flex;
flex-direction: column;
gap: var(--space-1);
min-width: 0;
overflow-wrap: anywhere;
}
.project-question-preview { font-weight: 600; }
.project-question-answer:empty { display: none; }
.project-question-preview, .project-question-answer {
color: var(--text-primary); font-size: var(--type-body);
}
/* The owner reads the status to act on it: meta ink, never the quieter secondary step. */
.project-question-status, .project-question-source {
color: var(--text-meta); font-size: var(--type-meta);
}
/* A block that exists only for open attention (a model wait, a pending or
host-offered Stop) or a bare non-Done ending carries no title placeholder
(docs/DESIGN.md "Conversation activity block"); its chip says the state it is
in (Waiting…, Cancelling…, Failed). The first row of work — a tool call,
narration, a child, a review, an error — gives it a title, whatever lane ran it. */
.chat-live-card > .chat-live-summary-button .chat-live-title:empty {
display: none;
}
.chat-live-actions:empty {
display: none;
}
/* P3 — a converted card becomes a calm "project identity" chip. The whole card
recolors from the red task accent to the project fuchsia over ~250ms; the live
timeline is gone (the project panel owns it now), so the main chat is freed. */
.chat-live-card.is-project {
border-color: var(--project-18);
background: var(--project-08);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18), inset 0 1px 0 rgba(240, 171, 252, 0.05);
}
.chat-live-project-card-btn {
display: flex;
align-items: center;
gap: var(--space-2);
width: 100%;
padding: 10px 14px;
border: 0;
background: transparent;
color: var(--text-secondary);
font: inherit;
font-size: var(--type-body);
text-align: left;
cursor: pointer;
border-radius: inherit;
}
.chat-live-project-card-btn:hover {
background: var(--project-12);
}
/* The card clips its children, so the ring sits inside (the sanctioned variant). */
.chat-live-project-card-btn:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: -2px;
}
.chat-live-project-icon {
display: inline-flex;
color: var(--project);
font-size: var(--type-section);
line-height: 1;
}
.chat-live-project-icon > svg {
width: 1em;
height: 1em;
}
.chat-live-project-name {
font-weight: 600;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-live-project-status {
margin-inline-start: auto;
flex: 0 0 auto;
color: var(--project);
font-size: var(--type-meta);
}
/* F4 — a bound (project-chat) card keeps its live timeline above and gets the
project pointer as a calm fuchsia footer, so a divider sets it apart. */
.chat-live-bound-pointer {
border-top: 1px solid var(--project-18);
background: var(--project-08);
}
/* On a task card the task title stays the one primary thing; the pointer's
project name steps down to meta ink. */
.chat-live-bound-pointer .chat-live-project-name {
font-weight: 500;
color: var(--text-meta);
}
.nav-section {
display: flex;
flex-direction: column;
gap: var(--space-1);
margin-top: var(--space-2);
}
.nav-section-label {
padding: var(--space-3) var(--space-3) var(--space-1);
color: var(--text-muted);
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.nav-chevron {
flex: 0 0 auto;
color: var(--text-muted);
transition: transform 0.15s ease;
}
.nav-section-toggle[aria-expanded="false"] .nav-chevron {
transform: rotate(-90deg);
}
#divider {
display: none;
}
/* Main content in the shell grid. */
#content {
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
position: relative;
z-index: 1;
}
.page {
display: none;
flex-direction: column;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
position: relative;
}
.page.active { display: flex; }
.app-page-glass {
background:
linear-gradient(180deg, rgba(13, 11, 15, 0.76) 0%, rgba(13, 11, 15, 0.34) 170px, rgba(13, 11, 15, 0.08) 100%),
radial-gradient(circle at 18% 0%, var(--accent-10), transparent 300px);
}
/* Containment for narrow screens and unbroken runtime text. */
#content,
#content .page,
#chat-messages,
.chat-messages,
#log-entries,
.log-entry,
.log-main,
.log-body,
.log-headline,
.log-task-summary,
.log-task-timeline,
.chat-bubble,
.chat-bubble .message,
.marketplace-shell,
.marketplace-card,
.skills-card,
.widgets-list,
.widgets-card {
min-width: 0;
}
.log-body,
.log-headline,
.log-pill,
.log-raw,
.chat-bubble .message,
.marketplace-card-body,
.marketplace-card-meta,
.skills-card,
.widgets-card {
overflow-wrap: anywhere;
}
.files-layout {
position: relative;
display: grid;
grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
grid-template-areas: "sidebar preview";
flex: 1;
height: auto;
min-height: 0;
overflow: hidden;
}
.files-sidebar {
grid-area: sidebar;
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
overflow: hidden;
border-right: 1px solid var(--divider);
background: rgba(255, 255, 255, 0.02);
}
.files-toolbar {
display: flex;
gap: 8px;
padding: 12px 16px 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
flex-shrink: 0;
}
.files-toolbar input { flex: 1; min-width: 0; }
.files-browser-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
padding: 12px 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
flex-shrink: 0;
}
.files-breadcrumb {
display: flex;
flex-wrap: wrap;
gap: 6px;
flex: 1 1 280px;
min-width: 0;
overflow: hidden;
}
.files-browser-actions {
display: inline-flex;
gap: 8px;
flex-shrink: 0;
margin-left: auto;
}
.files-crumb {
border: none;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
font-size: 12px;
max-width: min(360px, 70vw);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.files-crumb:hover {
color: var(--text-primary);
}
.files-crumb-sep {
color: var(--text-muted);
font-size: 12px;
}
.files-list {
flex: 1;
min-height: 0;
overflow: auto;
overscroll-behavior: contain;
padding: 8px;
}
.files-entry {
width: 100%;
display: grid;
grid-template-columns: 20px minmax(0, 1fr) auto;
gap: 10px;
align-items: center;
border: none;
background: transparent;
color: var(--text-primary);
border-radius: var(--radius-md);
padding: 10px 12px;
cursor: pointer;
text-align: left;
}
.files-entry:hover {
background: rgba(255, 255, 255, 0.04);
}
.files-entry.parent-link {
opacity: 0.88;
}
.files-entry.selected {
background: var(--accent-10);
box-shadow: inset 0 0 0 1px var(--accent-22);
}
.files-entry-icon {
color: var(--accent);
font-size: 12px;
}
.files-entry-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.files-entry-meta {
color: var(--text-muted);
font-size: 11px;
}
.files-empty {
padding: 18px 12px;
color: var(--text-muted);
font-size: 13px;
}
.files-preview {
grid-area: preview;
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
background:
linear-gradient(180deg, var(--accent-05), transparent 120px),
rgba(255, 255, 255, 0.015);
}
.files-preview-header {
flex-wrap: wrap;
padding: 16px 20px 12px;
border-bottom: 1px solid var(--divider);
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
flex-shrink: 0;
}
.files-preview-header > :first-child { flex: 1 1 220px; min-width: 0; }
.files-preview-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
min-width: 0;
}
.files-preview-status { padding: var(--space-2) var(--space-4); overflow-wrap: anywhere; }
.files-preview-path {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
word-break: break-all;
}
.files-preview-meta {
margin-top: 4px;
font-size: 12px;
color: var(--text-secondary);
}
.files-preview-content {
flex: 1;
min-height: 0;
overflow: auto;
overflow-wrap: anywhere;
overscroll-behavior: contain;
margin: 0;
padding: 20px;
color: #d9dee8;
font-size: 12px;
line-height: 1.55;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
white-space: pre-wrap;
word-break: break-word;
}
.files-editor-shell {
display: flex;
flex-direction: column;
min-height: 100%;
gap: 12px;
}
.files-editor {
flex: 1;
min-height: 320px;
resize: none;
margin: 0;
}
.files-preview-image {
display: block;
max-width: 100%;
max-height: 100%;
margin: 0 auto;
border-radius: var(--radius);
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.28);
}
.files-preview-frame {
width: 100%;
min-height: 70vh;
border: 1px solid var(--divider);
border-radius: var(--radius);
background: #111827;
}
.files-drop-overlay {
position: absolute;
inset: 0;
display: none;
align-items: center;
justify-content: center;
background: rgba(12, 14, 18, 0.72);
backdrop-filter: blur(3px);
z-index: 5;
pointer-events: none;
}
.files-layout.drag-active .files-drop-overlay {
display: flex;
}
.files-drop-card {
padding: 22px 28px;
border-radius: var(--radius-lg);
border: 1px dashed var(--accent-55);
background: var(--accent-10);
color: var(--text-primary);
font-size: 14px;
font-weight: 600;
letter-spacing: 0.01em;
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.24);
}
.files-modal-input { margin-top: var(--space-3); }
.page-header,
.app-page-header {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
grid-template-areas:
"leading title toolbar"
"tabs tabs tabs";
align-items: start;
gap: var(--space-2) var(--space-3);
padding: var(--page-header-padding-y) var(--page-header-padding-x) var(--page-header-padding-bottom);
background: transparent;
backdrop-filter: none;
flex-shrink: 0;
border-bottom: none;
}
.app-page-leading {
grid-area: leading;
display: flex;
align-items: center;
align-self: start;
min-width: 0;
}
.app-page-title-block {
grid-area: title;
min-width: 0;
}
.app-page-title-row {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.app-page-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
color: var(--accent);
}
.app-page-icon svg {
display: block;
}
.page-header h2,
.app-page-title {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.app-page-description {
margin: 2px 0 0;
color: var(--text-secondary);
font-size: 13px;
line-height: 1.35;
max-width: 760px;
}
.app-page-toolbar,
.app-page-actions,
.app-page-trailing {
grid-area: toolbar;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
min-width: 0;
justify-self: end;
align-self: start;
}
.app-page-toolbar {
max-width: 100%;
}
.app-page-trailing {
max-width: 100%;
}
.app-page-tabs {
grid-area: tabs;
min-width: 0;
}
.app-tab-strip {
display: flex;
flex-wrap: nowrap;
gap: 10px;
overflow-x: auto;
padding: var(--pill-strip-padding);
scrollbar-width: none;
}
.app-tab-strip::-webkit-scrollbar {
display: none;
}
.app-tab {
flex: 0 0 auto;
white-space: nowrap;
border: 1px solid var(--surface-border);
background: var(--surface-card);
color: var(--text-secondary);
border-radius: var(--pill-radius);
padding: var(--pill-padding-y) var(--pill-padding-x);
font: inherit;
font-size: var(--pill-font-size);
font-weight: 600;
cursor: pointer;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.app-tab:hover { background: var(--menu-item-hover); }
.app-tab.active,
.app-tab.is-active {
color: var(--text-primary);
border-color: var(--focus-accent-border);
background: var(--accent-12);
}
.app-tab-pill {
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 6px;
padding: 0 6px;
min-width: 18px;
height: 18px;
border-radius: 9px;
background: rgba(245, 158, 11, 0.15);
color: var(--amber);
font-size: 11px;
font-family: 'JetBrains Mono', monospace;
}
.scroll-fade-y {
--scroll-fade-edge: 32px;
--scroll-fade-top: 0px;
--scroll-fade-bottom: 0px;
-webkit-mask-image: linear-gradient(180deg, transparent 0,
black var(--scroll-fade-top), black calc(100% - var(--scroll-fade-bottom)), transparent 100%);
mask-image: linear-gradient(180deg, transparent 0,
black var(--scroll-fade-top), black calc(100% - var(--scroll-fade-bottom)), transparent 100%);
}
.scroll-fade-y[data-scroll-above] { --scroll-fade-top: var(--scroll-fade-edge); }
.scroll-fade-y[data-scroll-below] { --scroll-fade-bottom: var(--scroll-fade-edge); }
.page-header .spacer { flex: 1; }
.app-page-header-overlay {
align-items: center;
}
/* === design-system:migrated-begin (chat: page header, budget pill, load-older) === */
.chat-page-header {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 5;
row-gap: 10px;
pointer-events: none;
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
-webkit-mask-image: none;
mask-image: none;
overflow: visible;
}
/* Blur is decoration, so fade its own layer without masking header actions. */
.chat-page-header::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
pointer-events: none;
background: linear-gradient(180deg, var(--bg-primary), transparent);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
-webkit-mask-image: linear-gradient(180deg, black 45%, transparent 100%);
mask-image: linear-gradient(180deg, black 45%, transparent 100%);
}
.chat-page-header > * {
pointer-events: auto;
}
.chat-header-actions {
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
gap: 6px;
min-width: 0;
max-width: min(100%, 640px);
}
.chat-header-btn {
border: 1px solid rgba(255,255,255,0.12);
background: rgba(13, 11, 15, 0.55);
backdrop-filter: blur(8px);
color: var(--text-secondary);
border-radius: 999px;
padding: 7px 14px;
font-size: var(--type-meta);
line-height: 1.2;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.chat-header-btn:hover {
border-color: rgba(255,255,255,0.16);
color: var(--text-primary);
}
.chat-header-btn.on {
border-color: var(--accent-35);
background: var(--accent-dim);
color: var(--accent-light);
}
.chat-header-btn.danger {
border-color: rgba(239, 68, 68, 0.22);
color: var(--status-error-fg);
}
.chat-header-btn.danger:hover {
border-color: rgba(239, 68, 68, 0.40);
background: rgba(239, 68, 68, 0.08);
}
.chat-header-more {
position: relative;
flex: 0 0 auto;
}
.chat-header-more > summary {
list-style: none;
}
.chat-header-more > summary::-webkit-details-marker {
display: none;
}
.chat-header-menu {
position: absolute;
top: calc(100% + var(--space-2));
right: 0;
z-index: 90;
min-width: 150px;
padding: var(--space-2);
border: 1px solid var(--surface-border);
border-radius: var(--radius-md);
background: rgba(13, 11, 15, 0.92);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.34);
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.chat-header-menu-item {
border: none;
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-secondary);
font: inherit;
font-size: var(--type-body);
text-align: left;
padding: var(--space-2) var(--space-3);
cursor: pointer;
}
.chat-header-menu-item:hover {
color: var(--text-primary);
background: var(--menu-item-hover);
}
/* Evolve/Consciousness live in this menu now; show their on-state in place. */
.chat-header-menu-item.on {
color: var(--accent-light);
font-weight: 700;
}
.chat-header-menu-item.danger {
color: var(--status-error-fg);
}
/* When a mode inside More is active, mark the collapsed More pill with a dot. */
.chat-header-more > summary.has-active {
position: relative;
border-color: var(--accent-35);
background: var(--accent-dim);
color: var(--accent-light);
}
.chat-header-more > summary.has-active::after {
content: '';
position: absolute;
top: 5px;
right: 7px;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
}
.chat-budget-pill {
appearance: none;
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
padding: 4px 12px;
border-radius: 999px;
border: 1px solid rgba(245, 158, 11, 0.18);
background: rgba(13, 11, 15, 0.55);
backdrop-filter: blur(8px);
cursor: pointer;
min-width: 100px;
font: inherit;
transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.chat-budget-pill:hover {
border-color: rgba(245, 158, 11, 0.34);
background: rgba(245, 158, 11, 0.08);
transform: scale(1.02);
}
.chat-budget-pill:active {
transform: scale(0.98);
}
.chat-budget-text {
font-size: var(--type-meta);
font-weight: 600;
color: var(--amber);
white-space: nowrap;
letter-spacing: 0.02em;
}
.chat-budget-bar {
width: 100%;
height: 2px;
border-radius: 1px;
background: rgba(245, 158, 11, 0.12);
overflow: hidden;
}
.chat-budget-bar-fill {
height: 100%;
border-radius: 1px;
background: var(--amber);
transition: width 0.4s ease;
width: 0;
}
.status-badge {
padding: 4px 10px;
font-size: var(--type-meta);
font-weight: 500;
background: rgba(26, 21, 32, 0.78);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.status-badge.online { color: var(--green); }
.status-badge.starting { color: var(--amber); }
.status-badge.error { color: var(--red); }
.status-badge.offline { color: var(--text-muted); }
.status-badge.thinking {
color: var(--accent);
animation: thinking-pulse 1.5s ease-in-out infinite;
}
@keyframes thinking-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* History navigation at the top edge of the rendered window. */
.chat-load-older {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
gap: var(--space-2);
padding: 6px 12px 10px;
}
.chat-load-older[hidden] { display: none; }
.chat-load-older-btn {
appearance: none;
border: 1px solid var(--divider);
border-radius: 999px;
background: var(--bg-secondary);
color: var(--text-primary);
font-size: var(--type-meta);
padding: 5px 14px;
cursor: pointer;
transition: color 0.15s ease, border-color 0.15s ease;
}
.chat-load-older-btn:hover:not(:disabled) {
color: var(--text-primary);
border-color: var(--accent);
}
.chat-load-older-btn:disabled { opacity: 0.6; cursor: default; }
.chat-load-older-btn:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: 2px;
}
.chat-load-older-note {
font-size: var(--type-meta);
color: var(--text-meta);
text-align: center;
max-width: 460px;
}
.typing-bubble { opacity: 0.7; }
.typing-dots {
display: inline-flex;
gap: 4px;
align-items: center;
height: 20px;
}
.typing-dots span {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--text-secondary);
animation: typing-bounce 1.4s ease-in-out infinite;
}
.typing-dots span:nth-child(2) { animation-delay: 0.2s; }
.typing-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing-bounce {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-6px); opacity: 1; }
}
/* === design-system:migrated-end === */
.onboarding-overlay {
position: fixed;
inset: 0;
z-index: 50;
display: flex;
align-items: stretch;
justify-content: center;
}
.onboarding-overlay-backdrop {
position: absolute;
inset: 0;
background: rgba(6, 7, 10, 0.78);
backdrop-filter: blur(10px);
}
.onboarding-frame {
position: relative;
z-index: 1;
width: 100%;
height: 100%;
border: 0;
background: transparent;
}
.onboarding-restart-card {
position: relative;
z-index: 1;
align-self: center;
width: min(460px, calc(100vw - 32px));
padding: 24px;
border: 1px solid var(--divider);
border-radius: var(--radius-sm);
background: var(--bg-secondary);
color: var(--text-primary);
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.38);
}
.onboarding-restart-card h2 {
margin: 0 0 10px;
font-size: 20px;
}
.onboarding-restart-card p {
margin: 0 0 18px;
color: var(--text-secondary);
line-height: 1.5;
}
@media (max-width: 980px) {
.files-layout {
grid-template-columns: 1fr;
grid-template-rows: minmax(0, 30%) minmax(0, 1fr);
grid-template-areas:
"sidebar"
"preview";
}
.files-sidebar {
border-right: none;
border-bottom: 1px solid var(--divider);
max-height: none;
overflow-y: auto;
}
.files-sidebar > * { flex-shrink: 0; }
.files-sidebar .files-list { overflow: visible; }
}
/* === design-system:migrated-begin (chat: transcript, bubbles, live cards, composer) === */
#chat-messages,
.chat-messages {
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
padding: 20px;
padding-top: var(--chat-header-reserve, 56px);
padding-bottom: var(--chat-input-reserve, 108px);
display: flex;
flex-direction: column;
gap: 8px;
/* Container for the late live-card width override. A narrow project panel
must respond to this column's width, not to the outer viewport. */
container-type: inline-size;
container-name: chatcol;
}
/* The transcript is a scrolling column, so rows must keep their natural
height. Children with overflow:hidden (live cards, skill_review bubbles)
otherwise get an automatic flex min-height of 0 and are shrunk to a 1px
strip when the transcript overflows instead of letting the list scroll. */
#chat-messages > *,
.chat-messages > * {
flex-shrink: 0;
}
/* Quiet transcript navigation, centered above the complete two-row composer. */
.chat-scroll-bottom-btn {
position: absolute;
left: 50%;
bottom: calc(100% + var(--space-2));
z-index: 6;
width: var(--composer-control-height);
height: var(--composer-control-height);
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
color: var(--text-secondary);
background: rgba(26, 21, 32, 0.84);
border: 1px solid var(--surface-border);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
box-shadow: none;
cursor: pointer;
opacity: 0;
transform: translate(-50%, 8px);
pointer-events: none;
transition: opacity 0.18s ease, transform 0.18s ease, border-color 0.18s ease, background 0.18s ease, color 0.18s ease;
}
.chat-scroll-bottom-btn.visible {
opacity: 1;
transform: translate(-50%, 0);
pointer-events: auto;
}
.chat-scroll-bottom-btn:hover {
color: var(--text-primary);
background: var(--bg-hover);
border-color: rgba(255, 255, 255, 0.16);
transform: translate(-50%, 0);
}
.chat-scroll-bottom-btn:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: 2px;
}
.chat-scroll-bottom-btn svg { display: block; }
.chat-scroll-activity-dot {
position: absolute;
top: -2px;
right: -2px;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 2px var(--bg-primary);
}
.chat-scroll-activity-dot[hidden] { display: none; }
.chat-bubble {
padding: 10px 14px;
border-radius: var(--radius-lg);
max-width: 80%;
word-wrap: break-word;
overflow-wrap: anywhere;
min-width: 0;
}
.chat-bubble.user {
background: linear-gradient(135deg, var(--user-bubble-from), var(--user-bubble-to));
border: 1px solid var(--user-border);
box-shadow: inset 0 1px 0 var(--user-glow);
align-self: flex-end;
margin-left: 20%;
}
.chat-bubble.assistant {
background: linear-gradient(135deg, var(--accent-18), rgba(120, 24, 36, 0.18));
border: 1px solid rgba(240, 122, 134, 0.24);
box-shadow: inset 0 1px 0 rgba(255, 214, 220, 0.05);
align-self: flex-start;
margin-right: 20%;
}
.chat-bubble.system {
background: var(--system-bubble-bg);
border-left: 2px solid var(--system-border);
align-self: flex-start;
margin-right: 20%;
}
.chat-bubble .sender {
font-size: var(--type-meta);
font-weight: 600;
margin-bottom: 4px;
}
.chat-bubble.user .sender { color: var(--user-sender); }
.chat-bubble.assistant .sender { color: var(--assistant-sender); }
.chat-bubble.system .sender { color: var(--system-sender); }
.chat-bubble .message {
font-size: var(--type-section);
line-height: var(--line-body);
color: var(--text-primary);
white-space: pre-wrap;
}
.chat-bubble[data-chat-markdown-enhanced] .message {
white-space: normal;
}
/* Same opt-out idiom, applied to the rest of the class: a surface whose body is
RENDERED markdown (block elements) must not also honour the bubble's
`pre-wrap`, or every source newline becomes an extra blank line. The marker
attribute is authored by the renderer that writes the markdown, so a surface
that still shows plain multi-line text (an un-hydrated attempt detail) keeps
pre-wrap and its authored line breaks. */
.chat-live-line-body,
[data-chat-markdown-enhanced].chat-live-line-title,
[data-chat-markdown-enhanced].skill-review-full,
[data-chat-markdown-enhanced].chat-review-attempt-detail {
white-space: normal;
}
.chat-photo {
max-width: 100%;
border-radius: var(--radius-sm);
cursor: pointer;
}
.chat-video {
max-width: 100%;
border-radius: var(--radius-sm);
}
.chat-bubble.user .message { color: var(--user-text); }
.chat-bubble.assistant .message { color: var(--assistant-text); }
.chat-bubble.system .message { color: var(--system-text); }
/* Shared rich-content contract (v6.71.0): rendered markdown/lists inside
cards and review disclosures. NOT applied to chat-bubble .message. */
.ui-rich-content {
overflow-wrap: anywhere;
min-inline-size: 0;
}
.ui-rich-content ul,
.ui-rich-content ol {
padding-inline-start: var(--space-5, 24px);
margin-block: 6px;
}
.ui-rich-content li {
margin-block: 3px;
}
/* Rendered markdown outside a chat bubble had no paragraph rhythm at all: the
global reset zeroes <p> margins and nothing put them back, so a multi-
paragraph card body read as one wall of text. */
.ui-rich-content p {
margin-block: var(--space-2);
}
.ui-rich-content p:first-child { margin-block-start: 0; }
.ui-rich-content p:last-child { margin-block-end: 0; }
.chat-bubble.system[data-system-type="skill_review"] {
padding: 0;
overflow: hidden;
}
.chat-bubble.system[data-system-type="skill_review"] .sender,
.chat-bubble.system[data-system-type="skill_review"] .msg-time {
margin-left: 14px;
margin-right: 14px;
}
.chat-bubble.system[data-system-type="skill_review"] .sender {
margin-top: 10px;
}
.chat-bubble.system[data-system-type="skill_review"] .msg-time {
margin-bottom: 10px;
}
.chat-bubble.system[data-system-type="skill_review"] .message {
white-space: normal;
}
.skill-review-disclosure {
border-top: 1px solid rgba(255, 255, 255, 0.05);
}
.skill-review-summary-button {
width: 100%;
border: 0;
background: transparent;
color: inherit;
cursor: pointer;
padding: 11px 14px;
display: flex;
flex-direction: column;
gap: 7px;
text-align: left;
}
.skill-review-summary-button:hover {
background: rgba(245, 158, 11, 0.08);
}
.skill-review-summary-main {
color: rgba(255, 244, 220, 0.96);
font-weight: 650;
}
.skill-review-summary-side {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
color: rgba(255, 240, 210, 0.66);
font-size: var(--type-meta);
}
.skill-review-meta {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.skill-review-toggle-label {
flex-shrink: 0;
color: var(--system-sender);
font-weight: 650;
}
.skill-review-full {
padding: 0 14px 14px;
color: var(--system-text);
white-space: pre-wrap;
/* Full review bodies can run to hundreds of lines: scroll inside the
card instead of re-bloating the chat column (owner-picked 60vh). */
max-height: 60vh;
overflow-y: auto;
}
.skill-review-error {
color: var(--status-error-fg);
}
.skill-review-retry {
margin-left: 6px;
padding: 2px 10px;
border: 1px solid rgba(245, 158, 11, 0.55);
border-radius: 6px;
background: transparent;
color: var(--system-sender);
font: inherit;
font-size: var(--type-meta);
cursor: pointer;
}
.skill-review-retry:hover {
background: rgba(245, 158, 11, 0.12);
}
.chat-bubble.pending {
opacity: 0.78;
border-style: dashed;
}
.msg-pending {
margin-top: 6px;
font-size: var(--type-meta);
color: var(--text-muted);
}
.msg-routing-annotation {
margin-top: 6px;
color: var(--text-secondary);
font-size: var(--type-meta);
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.msg-routing-annotation[data-annotation-status="pending"] {
color: var(--text-disabled);
}
.msg-routing-annotation[data-annotation-status="needs_manual_target"],
.msg-routing-annotation[data-annotation-status="project_unavailable"] {
color: var(--status-error-fg);
}
.msg-time {
font-size: var(--type-meta);
color: var(--text-secondary);
opacity: 0.4;
margin-top: 6px;
text-align: right;
transition: opacity 0.3s ease;
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
user-select: none;
}
.chat-bubble:hover .msg-time {
opacity: 0.8;
}
.chat-bubble.user .msg-time {
text-align: right;
}
.chat-bubble.assistant .msg-time {
text-align: left;
}
.chat-bubble.system .msg-time {
text-align: left;
}
/* Subdued progress messages. */
.chat-bubble.progress {
border-left: 2px solid rgba(220, 38, 38, 0.2);
padding-left: 14px;
max-width: min(100%, 760px);
margin-right: 0;
opacity: 0.72;
}
.chat-bubble.progress .sender {
font-size: var(--type-meta);
color: var(--assistant-sender);
}
.chat-bubble.progress .message {
font-size: var(--type-body);
line-height: 1.45;
color: var(--text-secondary);
}
.chat-live-card {
align-self: flex-start;
width: min(100%, 760px);
/* The 80% gutter holds only while 80% is still >= 620px; below that the card
takes up to 620px, the chatcol breakpoint, so the width stays monotonic
across it and the internal narrow regime (<= 560px) starts only when the
COLUMN itself is narrow. */
max-width: max(80%, min(100%, 620px));
padding: 0;
border-radius: var(--radius-lg);
border: 1px solid var(--accent-dim);
background: var(--accent-04);
backdrop-filter: blur(12px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18), inset 0 1px 0 rgba(255, 200, 210, 0.03);
overflow: hidden;
container-type: inline-size;
container-name: livecard;
/* So the convert-to-project recolor (.is-project) fades in calmly (P3). */
transition: border-color 250ms ease, background-color 250ms ease, box-shadow 250ms ease;
}
.chat-live-card[data-finished="1"] {
border-color: var(--accent-08);
background: var(--accent-04);
}
.chat-live-card[hidden] {
display: none;
}
.chat-live-summary-button {
width: 100%;
border: 0;
background: transparent;
color: inherit;
font: inherit;
cursor: pointer;
padding: 11px 14px;
display: flex;
flex-wrap: wrap;
gap: 8px;
user-select: none;
text-align: left;
}
/* The card clips overflow, so the ring is the sanctioned inset variant. */
.chat-live-summary-button:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: -2px;
}
/* Content is copyable (DESIGN.md §5): the summary suppresses selection only on
its own chrome; a drag that selects text does not toggle the card. The summary
is a div[role=button] (keyboard-activated in chat.js) because WebKit never
lets text inside a real <button> be selected. */
.chat-live-summary-button .chat-live-title,
.chat-live-summary-button .chat-live-activity,
.chat-live-summary-button .chat-live-meta {
-webkit-user-select: text;
user-select: text;
}
.chat-live-summary-button:hover {
background: var(--accent-05);
}
.chat-live-summary {
display: flex;
align-items: flex-start;
/* A flex item shrinks below its text only with min-width: 0; the nested
one-line title relies on that to ellipsize instead of pushing the side
controls past the card edge. */
min-width: 0;
justify-content: space-between;
gap: 10px;
flex: 1 1 100%;
}
/* Chip + typing dots only; the title is their sibling so a narrow container can
reorder it under the side controls. */
.chat-live-summary-main {
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
}
.chat-live-summary-side {
display: inline-flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
/* One title line (title type size, not the inherited body size). */
min-height: calc(1.45 * var(--type-body));
}
.chat-live-phase,
.chat-live-line-phase {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 999px;
border: 1px solid var(--surface-border);
background: var(--surface-control);
color: var(--text-secondary);
font-size: var(--type-meta);
font-weight: 600;
text-transform: none;
}
.chat-live-phase:is(.start, .calling, .context, .progress, .working, .thought, .thinking, .boot),
.chat-live-line-phase:is(.start, .calling, .context, .progress, .working, .thought, .thinking, .boot) {
color: var(--accent-light);
border-color: var(--accent-25);
background: var(--accent-10);
}
.chat-live-card:not([data-finished="1"]) .chat-live-phase:is(.working, .thinking, .progress, .calling, .context) {
animation: thinking-pulse 1.5s ease-in-out infinite;
}
/* Phase pills are status chips with class-named tones; the triple is the
canonical one. The saturated --green/--amber/--red they used to carry are
dot-and-indicator hues, not small-text hues (docs/DESIGN.md "Status and
chips"). */
.chat-live-phase:is(.done, .ready, .ok, .result, .usage, .metrics),
.chat-live-line-phase:is(.done, .ready, .ok, .result, .usage, .metrics),
.log-entry .log-phase:is(.done, .ready, .ok, .result, .usage, .metrics),
.evo-runtime-pill.online {
color: var(--status-ok-fg);
border-color: var(--status-ok-border);
background: var(--status-ok-bg);
}
.chat-live-phase:is(.warn, .alive, .empty, .cancelled),
.chat-live-line-phase:is(.warn, .alive, .empty, .cancelled),
.log-entry .log-phase:is(.warn, .alive, .empty, .cancelled),
.evo-runtime-pill.starting {
color: var(--status-warn-fg);
border-color: var(--status-warn-border);
background: var(--status-warn-bg);
}
.chat-live-phase:is(.error, .timeout, .lifecycle_error),
.chat-live-line-phase:is(.error, .timeout, .lifecycle_error),
.log-entry .log-phase:is(.error, .timeout, .lifecycle_error),
.evo-runtime-pill.error {
color: var(--status-error-fg);
border-color: var(--status-error-border);
background: var(--status-error-bg);
}
/* Live-card typing dots. */
.chat-live-typing {
display: inline-flex;
gap: 3px;
align-items: center;
flex-shrink: 0;
}
.chat-live-typing span {
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--accent-55);
animation: typing-bounce 1.4s ease-in-out infinite;
}
.chat-live-typing span:nth-child(2) { animation-delay: 0.2s; }
.chat-live-typing span:nth-child(3) { animation-delay: 0.4s; }
.chat-live-card[data-finished="1"] > .chat-live-summary-button .chat-live-typing {
display: none;
}
.chat-live-title {
font-size: var(--type-body);
font-weight: 500;
color: var(--text-primary);
flex: 1;
min-width: 0;
line-height: 1.45;
white-space: pre-wrap;
/* A derived name is the request's own first line, so it can be one long
unbroken token (a URL or a path). Without a wrap escape it overflows the
card on every narrow surface, not just the phone container query. */
overflow-wrap: anywhere;
}
/* Collapsed summaries have a stable compact geometry. Expanded cards keep the
ordinary wrapping behavior; complete activity/review/result text lives in
their existing timeline disclosure rather than in a mouse-only tooltip. */
.chat-live-card:not([data-expanded="1"]) > .chat-live-summary-button .chat-live-title,
.chat-live-card:not([data-expanded="1"]) > .chat-live-summary-button .chat-live-activity {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
}
/* One reserved title line (a coined two-line name still clamps to two; the
stable-viewport seam absorbs that one-line growth). */
.chat-live-card:not([data-expanded="1"]) > .chat-live-summary-button .chat-live-title {
min-height: 1.45em;
}
/* A block without work carries no title placeholder: the collapsed geometry
reserves nothing for an empty title (review round 1: the clamp and the
reserved line above outranked the general empty-title rule). */
.chat-live-card:not([data-expanded="1"]) > .chat-live-summary-button .chat-live-title:empty {
display: none;
min-height: 0;
}
/* Activity grows with useful content. Details retain the complete source. */
.chat-live-activity {
color: var(--text-meta);
font-size: var(--type-body);
line-height: var(--line-body);
flex: 1 1 100%;
min-width: 0;
overflow-wrap: anywhere;
}
.chat-live-card:not([data-expanded="1"]) > .chat-live-summary-button .chat-live-activity {
-webkit-line-clamp: 3;
line-clamp: 3;
}
.chat-live-card > .chat-live-summary-button .chat-live-activity:empty { display: none; }
.chat-live-count {
color: var(--text-secondary);
font-size: var(--type-meta);
white-space: nowrap;
}
.chat-live-toggle {
color: var(--text-secondary);
font-size: var(--type-meta);
white-space: nowrap;
}
.chat-live-chevron {
color: var(--text-disabled);
transition: transform 0.18s ease;
}
.chat-live-card[data-expanded="1"] > .chat-live-summary-button .chat-live-chevron {
transform: rotate(180deg);
}
.chat-live-meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
flex: 1 1 auto;
min-width: 0;
color: var(--text-disabled);
font-size: var(--type-meta);
line-height: var(--line-meta);
}
.chat-live-card:not([data-expanded="1"]) > .chat-live-summary-button .chat-live-meta {
min-height: 1.35em;
}
.chat-live-meta-text {
display: inline-flex;
align-items: center;
color: var(--text-secondary);
font-size: var(--type-meta);
}
.chat-live-review-summary {
color: var(--text-meta);
font-size: var(--type-meta);
line-height: var(--line-meta);
flex: 0 0 auto;
margin-inline-start: auto;
align-self: baseline;
white-space: nowrap;
}
[data-live-reviews-host] > .chat-live-reviews {
display: none;
}
.chat-live-card[data-expanded="1"] > [data-live-reviews-host] > .chat-live-reviews {
display: block;
margin: 0 14px 12px;
border-top: 1px solid var(--divider);
}
.chat-review-section-toggle,
.chat-review-group-toggle,
.chat-review-detail-toggle {
border: 0;
background: transparent;
color: inherit;
cursor: pointer;
font: inherit;
}
.chat-review-section-toggle {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
padding: var(--space-2) 0;
color: var(--text-primary);
font-size: var(--type-body);
line-height: var(--line-title);
font-weight: 600;
text-align: left;
}
.chat-review-section-count,
.chat-review-group-meta,
.chat-review-attempt-meta {
color: var(--text-meta);
font-size: var(--type-meta);
line-height: var(--line-meta);
font-weight: 400;
}
.chat-review-groups,
.chat-review-attempts {
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.chat-review-group {
border-radius: var(--radius);
background: var(--ui-card-bg-soft);
}
.chat-review-group-toggle {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
padding: var(--space-2);
text-align: left;
}
.chat-review-group-main,
.chat-review-attempt-main {
display: flex;
align-items: baseline;
gap: var(--space-1);
min-width: 0;
flex-wrap: wrap;
}
.chat-review-group-label,
.chat-review-attempt-label {
color: var(--text-primary);
font-size: var(--type-body);
line-height: var(--line-body);
}
.chat-review-subject {
color: var(--text-meta);
font-size: var(--type-meta);
overflow-wrap: anywhere;
}
.chat-review-execution {
display: inline-flex;
align-items: center;
gap: var(--space-1);
min-width: 0;
color: var(--text-meta);
font-size: var(--type-meta);
line-height: var(--line-meta);
}
.chat-review-execution-model {
min-width: 0;
overflow-wrap: anywhere;
}
.chat-review-attempts {
padding: 0 var(--space-2) var(--space-2);
}
.chat-review-attempt {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: start;
gap: var(--space-1) var(--space-2);
padding: var(--space-2);
border-inline-start: 2px solid var(--status-neutral-fg);
border-radius: var(--radius-sm);
background: var(--surface-control);
}
.chat-review-attempt.working { border-inline-start-color: var(--status-neutral-fg); }
.chat-review-attempt.done { border-inline-start-color: var(--status-ok-fg); }
.chat-review-attempt.warn { border-inline-start-color: var(--status-warn-fg); }
.chat-review-attempt.error { border-inline-start-color: var(--status-error-fg); }
.chat-review-attempt.superseded { opacity: 0.74; }
.chat-review-detail-toggle {
color: var(--text-primary);
font-size: var(--type-meta);
line-height: var(--line-meta);
white-space: nowrap;
}
.chat-review-attempt-detail {
grid-column: 1 / -1;
color: var(--text-meta);
font-size: var(--type-meta);
line-height: var(--line-body);
white-space: pre-wrap;
overflow-wrap: anywhere;
max-height: 60vh;
overflow-y: auto;
}
.chat-review-group-cost,
.chat-review-initiator {
color: var(--text-meta);
font-size: var(--type-meta);
line-height: var(--line-meta);
padding: var(--space-1) var(--space-2);
}
.chat-review-section-toggle:focus-visible,
.chat-review-group-toggle:focus-visible,
.chat-review-detail-toggle:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: 2px;
}
.chat-live-timeline {
padding: 0 14px 12px;
display: none;
flex-direction: column;
gap: 8px;
}
.chat-live-card[data-expanded="1"] > .chat-live-timeline {
display: flex;
border-top: 1px solid rgba(255,255,255,0.05);
max-height: 420px;
overflow-y: auto;
}
.chat-subagents {
display: flex;
flex-direction: column;
gap: 8px;
margin-block: 2px 12px;
margin-inline: 24px 14px;
padding-inline-start: 14px;
border-inline-start: 1px solid var(--accent-25);
}
.chat-subagents:empty {
display: none;
}
/* The root card width is owned by the CHAT COLUMN and therefore stays late
enough to beat the base 80% rule above. Ordinary bubble gutters are separate. */
@container chatcol (max-width: 620px) {
.chat-bubble {
max-width: 100%;
}
.chat-bubble.user {
margin-inline-start: 0;
}
.chat-bubble.assistant,
.chat-bubble.system {
margin-inline-end: 0;
}
.chat-live-card {
width: 100%;
max-width: 100%;
}
}
/* Internal card geometry is owned by the CARD'S actual width. This catches a
deeply nested child even when the outer chat column is wide. The main row
takes a real 100% flex basis; flex-wrap alone leaves flex:1 at a zero basis
and recreates the one-letter-wide title. This component threshold stays
below the separate chat-column gutter breakpoint so the launcher-default
root and first child keep regular geometry. Nested indentation is paid once,
then flattened so deeper cards cannot consume the text column. */
@container livecard (max-width: 560px) {
.chat-live-summary {
flex-wrap: wrap;
}
.chat-live-summary-main {
flex: 0 0 auto;
}
.chat-live-title {
order: 3;
flex: 1 1 100%;
overflow-wrap: anywhere;
}
.chat-live-summary-side {
order: 2;
margin-inline-start: auto;
max-width: 100%;
flex-wrap: wrap;
justify-content: flex-end;
}
.chat-live-line-toggle {
flex-wrap: wrap;
}
.chat-live-line-head {
flex: 1 1 100%;
}
.chat-live-line-title {
overflow-wrap: anywhere;
}
.chat-live-line-expand-label {
margin-inline-start: auto;
}
.chat-review-group-toggle {
flex-wrap: wrap;
}
.chat-review-attempt { grid-template-columns: minmax(0, 1fr); }
.chat-review-group-meta,
.chat-review-detail-toggle {
margin-inline-start: 0;
}
.chat-subagents {
margin-inline: 0;
padding-inline-start: 8px;
}
.chat-subagents .chat-subagents {
padding-inline-start: 0;
}
/* A COLLAPSED nested child keeps its one-line identity row — status chip ·
role · `N notes` · chevron — over its model/executor metadata (DESIGN.md
§5) instead of dropping the title to a third band once its own inline
width crosses this threshold (#623). The title is already nowrap/ellipsis
here; it keeps the same 160px demand the mobile readability floor uses,
and only when even that cannot share the row do the side controls wrap
under it. Higher specificity than the .chat-live-title rule above, so the
cascade decides, not source order. */
.chat-live-card.subagent:not([data-expanded="1"]) > .chat-live-summary-button .chat-live-title {
order: 0;
flex: 1 1 160px;
min-width: 0;
}
}
.chat-live-card.subagent {
width: 100%;
max-width: 100%;
border-color: var(--accent-18);
background: var(--bg-secondary);
box-shadow: inset 0 1px 0 rgba(255, 200, 210, 0.03);
}
/* An opaque secondary surface prevents translucent tints accumulating at depth.
Nested frames and parent-child rails remain the hierarchy cue. */
.chat-live-card.subagent .chat-live-summary-button {
padding: 9px 12px;
}
/* A nested child is subordinate to its root: quieter ink, and collapsed it is
an identity row (chip · role · notes + chevron; the `Show details`
label is the root's) over its metadata row (model, harness chip with the run count,
cost, last update, docked Reviews count). Only its narration line waits for
expansion. The title stays on one line and stays IN the identity row at every
width (the narrow-regime rule above gives it a 160px demand and lets the side
controls wrap under it instead). Activity now stays visible beneath it. */
.chat-live-card.subagent .chat-live-title {
font-weight: 400;
color: var(--text-secondary);
}
.chat-live-card.subagent .chat-live-activity {
color: var(--text-secondary);
}
.chat-live-card.subagent:not([data-expanded="1"]) > .chat-live-summary-button .chat-live-title {
display: block;
min-height: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-live-card.subagent:not([data-expanded="1"]) > .chat-live-summary-button .chat-live-activity {
-webkit-line-clamp: 1;
line-clamp: 1;
}
.chat-live-card.subagent .chat-live-toggle,
.chat-live-card.subagent .chat-live-meta:empty {
display: none;
}
.chat-live-card.subagent:not([data-expanded="1"]) > .chat-live-summary-button .chat-live-meta {
min-height: 0;
}
.chat-live-line {
border-top: 1px solid rgba(255,255,255,0.05);
padding-top: 10px;
display: flex;
flex-direction: column;
gap: 6px;
}
.chat-live-line:first-child {
border-top: none;
padding-top: 0;
}
.chat-live-line.expandable {
/* The whole non-interactive row surface toggles the line (v6.71.0). */
cursor: pointer;
border-radius: var(--radius);
transition: background 0.18s ease;
}
.chat-live-line.expandable:hover {
background: rgba(255,255,255,0.02);
}
.chat-live-line.expandable[data-expanded="1"] {
background: rgba(59, 130, 246, 0.08);
}
.chat-live-line-toggle {
user-select: text;
-webkit-user-select: text;
width: 100%;
padding: 0;
border: 0;
background: transparent;
color: inherit;
cursor: pointer;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
text-align: left;
}
.chat-live-line-toggle:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: -2px;
}
.chat-live-line-head {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
flex: 1;
min-width: 0;
}
/* Collapsed timeline rows are a dense log under the card's summary: meta size
in the quieter ink. An expanded row is reading text again (below). */
.chat-live-line-title {
color: var(--text-secondary);
font-size: var(--type-meta);
font-weight: 400;
line-height: var(--line-meta);
white-space: pre-wrap;
}
.chat-live-line-repeat {
color: var(--text-muted);
font-size: var(--type-meta);
}
.chat-live-line-time {
margin-left: auto;
color: var(--text-muted);
font-size: var(--type-meta);
}
.chat-live-line-expand-label {
color: var(--text-secondary);
font-size: var(--type-meta);
white-space: nowrap;
flex-shrink: 0;
padding-top: 1px;
}
.chat-live-line-body {
color: var(--text-secondary);
/* white-space is set with the other rendered-markdown surfaces, next to the
chat bubble's pre-wrap default this body has to opt out of. */
word-break: break-word;
font-size: var(--type-meta);
line-height: var(--line-meta);
}
/* A markdown heading inside a timeline line is a subsection label (DESIGN.md
§2), never a page title: the row's own size, semibold, inline, no block
margins. Bold spans take the same semibold weight. */
.chat-live-line-title .md-h1,
.chat-live-line-title .md-h2,
.chat-live-line-title .md-h3,
.chat-live-line-body .md-h1,
.chat-live-line-body .md-h2,
.chat-live-line-body .md-h3 {
display: inline;
font-size: inherit;
font-weight: 600;
line-height: inherit;
margin: 0;
}
.chat-live-line-title strong,
.chat-live-line-body strong {
font-weight: 600;
}
/* P3: the genuinely-full fetched output is shown in a bounded, scrollable box so a
huge subagent/research result expands in place without growing the chat endlessly. */
.chat-live-line-body.chat-live-line-body-full {
max-height: 420px;
overflow-y: auto;
}
.chat-live-line-loading {
margin-top: 6px;
font-size: var(--type-meta);
font-style: italic;
color: var(--text-meta);
}
.chat-live-line[data-expanded="1"] .chat-live-line-title {
color: var(--text-primary);
font-size: var(--type-body);
line-height: var(--line-body);
}
.chat-live-line[data-expanded="1"] .chat-live-line-body {
color: var(--text-primary);
font-size: var(--type-body);
line-height: var(--line-body);
}
.chat-live-line.error .chat-live-line-title,
.chat-live-line.timeout .chat-live-line-title {
color: var(--status-error-fg);
}
/* The evidence row says its phase in ink, not in words: the counts-only label
stays the same while a call is still in flight (calling: the row is what the
turn is doing right now, so primary ink, never an alarm hue) and once one
failed (warn). Colour only, like the error rule above. */
.chat-live-line.calling .chat-live-line-title {
color: var(--text-primary);
}
.chat-live-line.warn .chat-live-line-title {
color: var(--status-warn-fg);
}
.chat-bubble .message pre {
background: rgba(0, 0, 0, 0.3);
border-radius: 6px;
padding: 8px 10px;
margin: 6px 0;
overflow-x: auto;
font-size: var(--type-body);
line-height: var(--line-body);
}
.chat-bubble .message code {
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
font-size: var(--type-body);
}
.chat-bubble .message .inline-code {
background: rgba(255, 255, 255, 0.08);
padding: 1px 5px;
border: 1px solid var(--surface-border);
border-radius: var(--radius-xs);
color: var(--accent-light);
font-size: var(--type-body);
}
.chat-bubble .message del {
opacity: 0.5;
text-decoration: line-through;
}
.chat-bubble .message p {
margin: 0 0 var(--space-3);
}
.chat-bubble .message > :first-child { margin-top: 0; }
.chat-bubble .message > :last-child { margin-bottom: 0; }
.chat-bubble .message ul,
.chat-bubble .message ol {
margin: var(--space-2) 0 var(--space-3);
padding-left: var(--space-5);
}
.chat-bubble .message li { margin: var(--space-1) 0; }
.chat-bubble .message li.task-list-item {
display: flex;
align-items: flex-start;
gap: var(--space-2);
margin-left: calc(-1 * var(--space-5));
list-style: none;
}
.chat-bubble .message .task-list { list-style: none; }
.md-checkbox {
display: inline-flex;
width: 18px;
height: 18px;
flex: 0 0 18px;
align-items: center;
justify-content: center;
margin-top: 1px;
border: 1px solid var(--surface-border);
border-radius: var(--radius-xs);
background: var(--surface-control);
color: var(--status-ok-fg);
font-size: var(--type-body);
font-weight: 700;
line-height: 1;
}
.md-checkbox.is-checked {
border-color: rgba(34, 197, 94, 0.38);
background: var(--status-ok-bg);
}
.md-quote {
display: block;
margin: var(--space-3) 0;
padding: var(--space-2) var(--space-3);
border-left: 3px solid var(--accent);
background: var(--surface-card-soft);
color: var(--text-meta);
}
/* The markdown renderer emits .md-table everywhere it runs — live activity
lines, quiz cards, skill review bodies, review attempt details — but the
table styling was scoped to the chat bubble, so every other surface showed
an unseparated run of cells. The class travels with the renderer, like
.md-h1 / .md-quote / .md-link above. */
.md-table {
width: max-content;
min-width: 100%;
border-collapse: collapse;
color: var(--text-primary);
font-size: var(--type-body);
}
.md-table th,
.md-table td {
min-width: 140px;
padding: var(--space-2) var(--space-3);
border-bottom: 1px solid var(--surface-border);
text-align: left;
white-space: nowrap;
}
.md-table th {
font-weight: 600;
color: var(--text-primary);
}
/* Global markdown rendering classes. */
.md-h1 {
font-size: var(--type-page);
color: var(--text-primary);
display: block;
margin: var(--space-5) 0 var(--space-3);
line-height: var(--line-title);
}
.md-h2 {
font-size: var(--type-section);
color: var(--text-primary);
display: block;
margin: var(--space-4) 0 var(--space-2);
line-height: var(--line-title);
}
.md-h3 {
font-size: var(--type-body);
color: var(--text-primary);
display: block;
margin: var(--space-3) 0 var(--space-2);
line-height: var(--line-title);
}
/* Chat-scoped heading clamp: a markdown heading inside a chat bubble is a
section label, never a page title (--type-page stays for real page titles). */
.chat-bubble .message .md-h1,
.chat-bubble .message .md-h2,
.chat-bubble .message .md-h3 {
font-size: var(--type-body);
font-weight: 600;
}
.md-li {
display: block;
padding-left: 12px;
}
.md-link {
color: var(--accent-light);
text-decoration: underline;
text-underline-offset: 2px;
}
/* Prevent wide tables from breaking bubble layout. */
.md-table-wrap {
display: block;
width: 100%;
margin: var(--space-4) 0;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
max-width: 100%;
scrollbar-color: var(--text-disabled) var(--surface-card-soft);
}
.md-code-block {
position: relative;
width: 100%;
min-width: 0;
margin: var(--space-4) 0;
overflow: hidden;
border: 1px solid var(--surface-border);
border-radius: var(--radius);
background: #121016;
color: var(--text-primary);
}
.chat-bubble .message .md-code-block pre {
margin: 0;
padding: var(--space-4);
overflow: auto;
border: 0;
border-radius: 0;
background: transparent;
color: inherit;
font-size: var(--type-body);
line-height: var(--line-body);
white-space: pre;
tab-size: 2;
}
.md-code-language {
display: block;
min-height: 34px;
padding: 9px 76px 0 var(--space-4);
border-bottom: 1px solid var(--surface-border-soft);
color: var(--text-meta);
font: var(--type-meta)/var(--line-meta) 'SF Mono', 'Fira Code', Consolas, monospace;
}
.md-code-copy {
position: absolute;
z-index: 1;
top: 5px;
right: var(--space-2);
min-height: 26px;
padding: 0 var(--space-2);
border: 1px solid transparent;
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-meta);
font: inherit;
font-size: var(--type-meta);
cursor: pointer;
}
.md-code-copy:hover,
.md-code-copy:focus-visible {
border-color: var(--surface-border);
background: var(--bg-hover);
color: var(--text-primary);
}
.md-code-copy:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: 2px;
}
.md-code-copy.is-copied { color: var(--status-ok-fg); }
.md-code-block .hljs-comment,
.md-code-block .hljs-quote { color: #8b8294; font-style: italic; }
.md-code-block .hljs-keyword,
.md-code-block .hljs-selector-tag,
.md-code-block .hljs-literal { color: #d29ce8; }
.md-code-block .hljs-string,
.md-code-block .hljs-regexp,
.md-code-block .hljs-addition { color: #8dd7c7; }
.md-code-block .hljs-number,
.md-code-block .hljs-built_in { color: #ee967a; }
.md-code-block .hljs-title,
.md-code-block .hljs-function { color: #92b7f4; }
.md-code-block .hljs-type,
.md-code-block .hljs-class .hljs-title { color: #e5c66d; }
.md-code-block .hljs-attr,
.md-code-block .hljs-variable,
.md-code-block .hljs-template-variable { color: #add782; }
.chat-bubble .message .katex { color: var(--text-primary); }
.chat-bubble .message .katex-display {
max-width: 100%;
margin: var(--space-4) 0;
padding: var(--space-1) 0;
overflow-x: auto;
overflow-y: hidden;
}
.md-mermaid,
.md-chart {
box-sizing: border-box;
width: 100%;
margin: var(--space-4) 0;
padding: var(--space-4);
overflow: auto;
border: 1px solid var(--surface-border);
border-radius: var(--radius);
background: var(--diagram-bg);
color: var(--text-primary);
}
.md-mermaid { text-align: center; }
.md-mermaid-stage {
position: fixed;
top: 0;
left: -10000px;
width: var(--md-mermaid-stage-width, 320px);
opacity: 0;
pointer-events: none;
}
.md-mermaid svg {
display: block;
width: auto;
min-width: min-content;
max-width: 100%;
height: auto;
margin: 0 auto;
}
.md-chart {
height: clamp(280px, 42vw, 480px);
overflow: hidden;
}
.md-chart canvas {
display: block;
width: 100% !important;
height: 100% !important;
}
.md-chart-error {
height: auto;
overflow-x: auto;
border-color: var(--status-error-fg);
color: var(--text-meta);
font-family: 'SF Mono', 'Fira Code', Consolas, monospace;
font-size: var(--type-meta);
line-height: var(--line-body);
text-align: left;
white-space: pre-wrap;
}
.md-mermaid-error { border-color: var(--status-error-fg); }
.md-diagram-error-note {
padding: var(--space-2) var(--space-4);
border-bottom: 1px solid var(--surface-border);
color: var(--status-error-fg);
font-size: var(--type-meta);
line-height: var(--line-meta);
}
.nav-spacer {
flex: 1;
}
/* Early mobile overrides; late-defined selectors are handled at file end. */
@media (max-width: 640px) {
/* Prevent iOS zoom on input focus. */
textarea, input[type="text"], input[type="password"], input[type="email"], select {
font-size: 16px !important;
}
.chat-bubble {
max-width: 92%;
}
.chat-bubble.user {
margin-left: 8%;
}
.chat-bubble.assistant,
.chat-bubble.system {
margin-right: 8%;
}
.chat-bubble.progress {
max-width: 100%;
margin-right: 0;
}
#app {
grid-template-columns: minmax(0, 1fr);
}
.mobile-nav-toggle {
position: static;
flex: 0 0 44px;
width: 44px;
height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid var(--surface-border);
border-radius: var(--radius-md);
background: rgba(13, 11, 15, 0.76);
color: var(--text-primary);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
}
.nav-drawer-backdrop {
position: fixed;
inset: 0;
z-index: 68;
display: block;
background: rgba(0, 0, 0, 0.48);
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
}
.nav-drawer-backdrop[hidden] {
display: none;
}
#primary-sidebar {
position: fixed;
inset: 0 auto 0 0;
z-index: 70;
width: min(86vw, 320px);
max-width: 320px;
transform: translateX(-105%);
transition: transform 0.18s ease;
box-shadow: 24px 0 60px rgba(0, 0, 0, 0.36);
padding-top: calc(64px + env(safe-area-inset-top, 0px));
background:
linear-gradient(180deg, rgba(13, 11, 15, 0.94), rgba(13, 11, 15, 0.86)),
radial-gradient(circle at 8% 0%, var(--accent-10), transparent 260px);
}
#primary-sidebar.open {
transform: translateX(0);
}
.unread-badge {
top: 2px;
right: 4px;
min-width: 14px;
height: 14px;
}
#nav-version,
.nav-spacer {
display: none;
}
#content {
padding-left: 0;
padding-bottom: env(safe-area-inset-bottom, 0px);
}
.chat-page-header {
/* Keep the desktop overlay header (position:absolute + fade-to-transparent
gradient + blur): the transcript scrolls under it. Only restack the grid
for narrow widths — never switch to position:static / a flat background,
which is what removed the gradient and made mobile look heavy. */
row-gap: 6px;
grid-template-columns: auto minmax(0, 1fr);
grid-template-areas:
"leading title"
"toolbar toolbar"
"tabs tabs";
}
body.project-panel-open .mobile-nav-toggle {
display: none;
}
/* Restart / Panic / More is a short control set — let it wrap instead of
trapping the absolutely-positioned More menu inside an overflow-clip
(overflow:auto/hidden here is what made the dropdown invisible on mobile). */
.chat-header-actions {
display: flex;
max-width: 100%;
flex-wrap: wrap;
}
.chat-page-header .app-page-actions {
width: 100%;
margin-left: 0;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap;
gap: 6px;
}
.chat-page-header .chat-budget-pill,
.chat-page-header .status-badge {
order: 0;
}
.chat-budget-pill {
min-width: 0;
padding: 3px 10px;
}
.page-header,
.app-page-header {
grid-template-columns: auto minmax(0, 1fr);
grid-template-areas:
"leading title"
"toolbar toolbar"
"tabs tabs";
padding: 10px 12px;
}
}
#chat-input-area,
.chat-input-area {
/* Bottom fade darkens transcript before the frosted input without trapping scroll. */
display: flex;
flex-direction: column;
gap: 4px;
padding: 32px 16px 16px;
align-items: stretch;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 5;
pointer-events: none;
background: linear-gradient(
0deg,
rgba(13, 11, 15, 0.82) 0%,
rgba(13, 11, 15, 0.58) 48%,
rgba(13, 11, 15, 0.22) 78%,
rgba(13, 11, 15, 0.00) 100%
);
}
#chat-input-area > *,
.chat-input-area > * {
pointer-events: auto;
}
.chat-input-wrap {
--composer-control-height: 32px;
--composer-control-font-size: 12px;
--composer-control-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.055), 0 8px 22px rgba(0, 0, 0, 0.12);
flex: 1;
position: relative;
display: flex;
flex-direction: column;
align-items: stretch;
gap: var(--space-2);
min-width: 0;
}
.chat-text-row {
order: 2;
display: flex;
align-items: center;
gap: var(--space-2);
min-width: 0;
padding: var(--space-2);
/* Oval (pill) composer: the send/attach controls are vertically CENTERED so they
never bulge out of the rounded corners as the textarea grows multi-line (the
earlier flex-end + 999px pushed them past the curve; centering fixes that). */
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.10);
background: rgba(26, 21, 32, 0.68);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
}
.chat-text-row:focus-within {
border-color: var(--focus-accent-border);
background: rgba(26, 21, 32, 0.86);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 0 0 3px var(--accent-10);
}
/* "Chat freed" cue (P3): a brief fuchsia brighten when a task moves out to a
project, signalling the main chat is free again. Self-clears after ~900ms. */
.chat-text-row.chat-freed {
border-color: var(--project);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 0 0 3px var(--project-12);
}
.chat-toolbar-row {
order: 1;
display: flex;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
gap: var(--space-2);
min-width: 0;
padding: 0 var(--space-2);
}
#chat-input-area.drag-active .chat-input-wrap,
.chat-input-area.drag-active .chat-input-wrap {
border-radius: 24px;
box-shadow: 0 0 0 1px var(--accent-45), 0 0 0 4px var(--accent-10);
}
/* Attach button: a flow item at the left of the text row (no absolute float). */
.chat-attach-btn {
flex: 0 0 auto;
align-self: center;
width: 32px;
height: 32px;
border-radius: var(--radius-sm);
border: none;
background: transparent;
color: var(--text-secondary);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: color 0.15s, background 0.15s;
padding: 0;
}
.chat-attach-btn:hover {
color: var(--text-primary);
background: rgba(255, 255, 255, 0.06);
}
.chat-file-input-hidden {
display: none !important;
}
.chat-attach-btn.uploading {
opacity: 0.5;
pointer-events: none;
}
.chat-attachment-preview {
display: none;
padding: 4px 8px 2px;
max-height: 112px;
overflow-y: auto;
}
.chat-attachment-preview.visible {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
}
.attach-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 8px 4px 6px;
background: rgba(26, 21, 32, 0.78);
border: 1px solid var(--accent-35);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border-radius: var(--radius-sm);
font-size: var(--type-meta);
color: var(--text-primary);
max-width: 100%;
}
.attach-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: min(220px, 56vw);
}
.attach-remove {
flex-shrink: 0;
border: none;
background: none;
color: var(--text-secondary);
cursor: pointer;
font-size: var(--type-section);
line-height: 1;
padding: 0 2px;
}
.attach-remove:hover {
color: var(--text-primary);
}
#chat-input,
textarea.chat-input {
/* Bare text surface inside the .chat-input-wrap card (the card owns the
border / background / focus ring), so no fixed side paddings to reserve
room for floated controls — the row flexes at any width. */
flex: 1 1 auto;
width: 100%;
min-width: 0;
padding: 5px 4px;
border: none;
background: transparent;
box-shadow: none;
color: var(--text-primary);
font-size: var(--type-section);
font-family: inherit;
outline: none;
resize: none;
min-height: 32px;
max-height: 140px;
line-height: 1.45;
}
#chat-input::placeholder,
textarea.chat-input::placeholder { color: var(--text-muted); }
/* Composer pills (Swarm + Low|Max), left of the absolute send button. */
.chat-composer-pills {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
/* One-shot Swarm arm pill: dim when idle, accent when armed. */
.chat-swarm {
height: var(--composer-control-height);
padding: 0 12px;
border: 1px solid rgba(226, 232, 240, 0.12);
border-radius: 999px;
background: rgba(26, 21, 32, 0.38);
color: var(--text-meta);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
box-shadow: var(--composer-control-shadow);
font-size: var(--composer-control-font-size);
font-weight: 700;
letter-spacing: 0;
font-family: inherit;
cursor: pointer;
white-space: nowrap;
transition: color 0.18s, background 0.18s, border-color 0.18s, box-shadow 0.18s;
}
.chat-swarm:hover {
color: var(--accent);
border-color: var(--accent-35);
background: rgba(38, 25, 34, 0.58);
}
.chat-swarm[data-armed="true"] {
color: var(--accent);
border-color: var(--accent-45);
background: var(--accent-dim);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 0 0 3px var(--accent-10);
}
/* Low|Max segmented control: both options visible, the active one highlighted. */
.chat-context-mode {
display: inline-flex;
align-items: center;
height: var(--composer-control-height);
border: 1px solid rgba(226, 232, 240, 0.12);
border-radius: 999px;
overflow: hidden;
background: rgba(26, 21, 32, 0.38);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
box-shadow: var(--composer-control-shadow);
font-size: var(--composer-control-font-size);
font-weight: 700;
letter-spacing: 0;
}
.chat-context-mode[data-disabled="true"] {
opacity: 0.5;
pointer-events: none;
}
.chat-context-mode .chat-seg {
display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
height: var(--composer-control-height);
padding: 0 12px;
border: none;
background: transparent;
color: var(--text-secondary);
font: inherit;
cursor: pointer;
transition: color 0.15s, background 0.15s;
}
.chat-context-mode .chat-seg:hover {
color: var(--text-primary);
}
/* Active segment: Max glows accent (full context); Low glows amber (reduced). */
.chat-context-mode[data-context-mode="max"] .chat-seg[data-mode="max"] {
background: var(--accent-dim);
color: var(--accent);
}
.chat-context-mode[data-context-mode="low"] .chat-seg[data-mode="low"] {
background: rgba(245, 158, 11, 0.16);
color: var(--amber);
}
.chat-context-mode[data-context-mode="nano"] .chat-seg[data-mode="nano"] {
background: var(--project-18);
color: var(--project);
}
.chat-send-group {
flex: 0 0 auto;
align-self: center;
display: flex;
align-items: center;
gap: 0;
height: var(--composer-control-height);
}
.chat-send-group[data-busy="1"] {
opacity: 0.72;
}
.chat-send-inline {
border: 1px solid var(--accent-25);
background: var(--accent-08);
color: var(--accent);
font-size: var(--composer-control-font-size);
font-weight: 700;
font-family: inherit;
letter-spacing: 0;
cursor: pointer;
padding: 0 13px;
height: 100%;
border-radius: 999px;
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
box-shadow: var(--composer-control-shadow);
transition: color 0.18s, background 0.18s, border-color 0.18s, opacity 0.18s;
opacity: 0.88;
display: flex;
align-items: center;
}
.chat-send-inline:hover {
opacity: 1;
background: var(--accent-18);
border-color: var(--accent-45);
}
.chat-send-inline:active {
background: var(--accent-18);
opacity: 1;
}
/* === design-system:migrated-end === */
/* Shared UI components. */
.ui-card,
:where(.settings-shell .form-section, .settings-card, .settings-provider-card, .settings-extension-section, .mcp-server-card, .settings-model-card, .settings-effort-card, .settings-custom-secret-row, .skills-card, .marketplace-card, .widgets-card, .updates-card, .costs-budget-card) {
border: 1px solid var(--ui-card-border);
border-radius: var(--radius);
background: var(--ui-card-bg);
}
.ui-card-soft,
:where(.settings-model-card, .settings-effort-card, .settings-custom-secret-row, .skills-card, .marketplace-card, .widgets-card) {
background: var(--ui-card-bg-soft);
}
.ui-modal-backdrop,
:where(.marketplace-modal-backdrop) {
position: fixed;
inset: 0;
z-index: 90;
display: flex;
justify-content: center;
overflow-y: auto;
background: rgba(0, 0, 0, 0.55);
}
.ui-modal,
:where(.marketplace-modal) {
width: 100%;
display: flex;
flex-direction: column;
border: 1px solid var(--surface-border);
background: var(--ui-modal-bg);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.stat-card {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
border-radius: var(--radius);
padding: 16px 20px;
border: 1px solid var(--accent-08);
backdrop-filter: blur(14px);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
transition: border-color 0.3s;
}
.stat-card:hover {
border-color: var(--accent-18);
}
.stat-card .label {
font-size: 12px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.stat-card .value {
font-size: 20px;
font-weight: 700;
}
.section-title {
font-size: 14px;
font-weight: 600;
color: var(--text-secondary);
margin: 16px 0 12px;
}
.form-section { margin-bottom: 24px; }
.form-section h3 {
font-size: var(--type-section);
line-height: var(--line-title);
font-weight: 600;
margin-bottom: 12px;
}
.form-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 12px;
}
.form-field {
display: flex;
flex-direction: column;
gap: 4px;
}
.form-field label {
font-size: 12px;
color: var(--text-secondary);
}
.local-toggle {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: var(--text-secondary);
cursor: pointer;
padding-bottom: 8px;
white-space: nowrap;
}
.local-toggle input[type="checkbox"] {
accent-color: var(--accent);
width: 14px;
height: 14px;
cursor: pointer;
}
.local-toggle[title] {
transition: color 0.2s;
}
.form-field input[type="range"] {
-webkit-appearance: none;
height: 4px;
background: rgba(255, 255, 255, 0.12);
border-radius: 2px;
border: none;
padding: 0;
}
.logs-filters {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 8px 20px;
align-items: center;
}
.logs-inline-clear {
margin-left: auto;
}
.filter-chip {
padding: 4px 12px;
border-radius: var(--radius-lg);
border: 1px solid var(--divider);
background: transparent;
color: var(--text-secondary);
font-size: 12px;
font-family: inherit;
cursor: pointer;
transition: all 0.15s;
}
.filter-chip.active {
border-color: var(--accent);
color: var(--accent);
background: var(--accent-dim);
}
#log-entries {
flex: 1;
overflow-y: auto;
padding: 12px;
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
font-size: 12px;
line-height: 1.55;
background: rgba(255, 255, 255, 0.02);
margin: 0 12px 12px;
border-radius: var(--radius-sm);
}
.log-entry {
padding: 10px 12px;
border-radius: var(--radius-sm);
display: flex;
flex-direction: column;
gap: 6px;
border: 1px solid rgba(255,255,255,0.04);
background: rgba(255,255,255,0.015);
margin-bottom: 8px;
}
.log-entry:hover {
background: rgba(255, 255, 255, 0.03);
border-color: rgba(255,255,255,0.08);
}
.log-main {
display: flex;
gap: 8px;
align-items: baseline;
flex-wrap: wrap;
}
.log-entry .log-ts {
color: var(--text-muted);
flex-shrink: 0;
}
.log-entry .log-type {
font-weight: 700;
flex-shrink: 0;
min-width: 78px;
text-transform: uppercase;
letter-spacing: 0.03em;
font-size: 11px;
}
.log-entry .log-phase {
padding: 1px 8px;
border-radius: 999px;
font-size: 11px;
border: 1px solid rgba(255,255,255,0.08);
color: var(--text-secondary);
background: rgba(255,255,255,0.04);
}
.log-entry .log-phase:is(.start, .calling, .context, .progress, .thought, .boot, .working, .thinking) {
color: rgba(248, 130, 140, 0.95);
border-color: var(--accent-25);
background: var(--accent-10);
}
.log-entry .log-headline {
color: var(--text-primary);
font-weight: 600;
word-break: break-word;
}
.log-entry .log-repeat {
margin-left: auto;
color: var(--text-muted);
font-size: 11px;
}
.log-meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.log-pill {
display: inline-flex;
align-items: center;
padding: 1px 8px;
border-radius: 999px;
background: rgba(255,255,255,0.05);
color: var(--text-secondary);
border: 1px solid rgba(255,255,255,0.05);
font-size: 11px;
}
.log-body {
color: var(--text-secondary);
word-break: break-word;
white-space: pre-wrap;
}
.log-actions {
display: flex;
justify-content: flex-end;
}
.log-raw-toggle {
border: 1px solid rgba(255,255,255,0.08);
background: transparent;
color: var(--text-muted);
border-radius: 6px;
padding: 3px 8px;
font-size: 11px;
cursor: pointer;
}
.log-raw-toggle:hover {
color: var(--text-primary);
border-color: rgba(255,255,255,0.14);
}
.log-raw {
margin: 0;
padding: 10px;
border-radius: var(--radius-sm);
background: rgba(0, 0, 0, 0.25);
color: var(--text-secondary);
overflow-x: auto;
white-space: pre-wrap;
word-break: break-word;
}
.log-type.tools { color: var(--blue); }
.log-type.llm { color: var(--accent); }
.log-type.errors { color: var(--red); }
.log-type.tasks { color: var(--amber); }
.log-type.system { color: var(--text-muted); }
.log-type.consciousness { color: #d68cff; }
.log-task-card {
border-color: var(--accent-12);
background:
linear-gradient(135deg, var(--accent-08), rgba(255,255,255,0.01)),
rgba(255,255,255,0.015);
}
.log-task-summary {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.log-task-review {
margin-top: 8px;
padding: 8px 10px;
border: 1px solid rgba(255,255,255,0.06);
border-radius: var(--radius-sm);
background: rgba(0,0,0,0.14);
}
.log-task-details {
border-top: 1px solid rgba(255,255,255,0.05);
padding-top: 8px;
}
.log-task-details summary {
cursor: pointer;
color: var(--text-secondary);
font-size: 11px;
user-select: none;
}
.log-task-timeline {
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 8px;
}
.log-task-event {
padding: 8px 10px;
border-radius: var(--radius-sm);
background: rgba(0, 0, 0, 0.16);
border: 1px solid rgba(255,255,255,0.04);
display: flex;
flex-direction: column;
gap: 6px;
}
/* `width` is the vertical bar, `height` the horizontal one: without the height
a horizontal bar falls back to the 16-17px UA default and paints thick. */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--accent-18); border-radius: var(--radius-xs); }
::-webkit-scrollbar-thumb:hover { background: var(--accent-35); }
#reconnect-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
z-index: 1000;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 12px;
color: var(--text-primary);
font-size: 14px;
font-weight: 500;
}
#reconnect-overlay.visible { display: flex; }
#reconnect-overlay .spinner {
width: 32px;
height: 32px;
border: 3px solid var(--divider);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.cost-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.cost-table th {
text-align: left;
font-weight: 500;
color: var(--text-muted);
font-size: 11px;
padding: 4px 8px;
border-bottom: 1px solid var(--divider);
}
.cost-table td {
padding: 5px 8px;
border-bottom: 1px solid rgba(255,255,255,0.04);
color: var(--text-secondary);
}
.cost-table tbody tr:hover td {
background: rgba(255,255,255,0.03);
}
/* Settings LAN reachability hint. */
.settings-lan-hint {
margin-top: 8px;
padding: 8px 12px;
border-radius: var(--radius-sm);
font-size: 0.84em;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
color: var(--text-secondary, rgba(255,255,255,0.65));
}
.settings-lan-hint[data-tone="ok"] { border-color: var(--status-ok-border); }
.settings-lan-hint[data-tone="warn"] { border-color: var(--status-warn-border); }
.settings-lan-hint[data-tone="info"] { border-color: var(--status-neutral-border); }
.settings-lan-hint a { color: inherit; text-decoration: underline; opacity: 0.9; }
.btn-sm {
padding: calc(var(--button-padding-y) - 4px) calc(var(--button-padding-x) - 4px);
font-size: calc(var(--button-font-size) - 1px);
min-height: 28px;
}
.evo-refresh-btn {
min-width: 92px;
}
.evolution-container {
display: flex;
flex-direction: column;
gap: 20px;
padding: 0 4px;
height: calc(var(--vvh) - 120px);
overflow-y: auto;
}
.evo-runtime-card,
.evo-chart-wrap {
background:
linear-gradient(180deg, var(--accent-05), rgba(255, 255, 255, 0.02)),
rgba(255, 255, 255, 0.02);
border: 1px solid var(--accent-12);
border-radius: var(--radius-lg);
box-shadow: 0 14px 36px rgba(0, 0, 0, 0.18);
backdrop-filter: blur(14px);
}
.evo-runtime-card {
padding: 16px 18px;
display: flex;
flex-direction: column;
gap: 12px;
}
.evo-runtime-head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 14px;
flex-wrap: wrap;
}
.evo-runtime-detail {
color: var(--text-secondary);
line-height: 1.6;
max-width: 880px;
}
.evo-runtime-pills,
.evo-runtime-meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
/* Runtime-card head hosts embedded Evolution controls. */
.evo-runtime-controls {
align-items: center;
}
.evo-runtime-pill,
.evo-runtime-chip {
padding: 6px 10px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
color: var(--text-secondary);
font-size: 12px;
}
.evo-runtime-chip strong {
font-weight: 600;
color: var(--text-primary);
}
.evo-chart-wrap {
position: relative;
width: 100%;
min-height: 400px;
max-height: 50vh;
padding: 16px;
}
.evo-tags-list {
overflow-x: auto;
}
.evo-tags-list table {
width: 100%;
}
.evo-empty {
padding: 18px;
border-radius: 14px;
border: 1px dashed rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.03);
color: var(--text-secondary);
line-height: 1.6;
}
@media (max-width: 900px) {
.evo-runtime-head {
flex-direction: column;
}
.evo-chart-wrap {
min-height: 320px;
max-height: none;
}
}
.about-body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
gap: 24px;
padding: 40px 20px;
text-align: center;
}
.about-logo {
width: 96px;
height: 96px;
border-radius: var(--radius-xl);
object-fit: cover;
}
.about-title { font-size: 28px; font-weight: 700; margin: 0; }
.about-version { color: var(--text-muted); font-size: 13px; margin: 4px 0 0; }
.about-desc { max-width: 480px; color: var(--text-secondary); font-size: 14px; line-height: 1.6; }
.about-credits { display: flex; flex-direction: column; gap: 8px; font-size: 14px; }
.about-links { display: flex; gap: 16px; justify-content: center; margin-top: 4px; }
.about-links a { color: var(--accent); text-decoration: none; }
.costs-scroll { overflow-y: auto; flex: 1; padding: 16px 20px; }
.costs-budget-card {
backdrop-filter: blur(12px);
padding: 20px 24px;
margin-bottom: 20px;
}
.costs-budget-title { font-size: 15px; font-weight: 600; margin: 0; color: var(--text-primary); }
/* Budget card head row hosts title plus embedded Refresh. */
.costs-budget-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.costs-budget-refresh { flex: 0 0 auto; }
.costs-budget-fields { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px 20px; margin-bottom: 16px; }
.costs-budget-fields .form-field { min-width: 0; }
.costs-budget-fields input { width: 100%; min-width: 0; }
.costs-budget-save { margin-top: 4px; }
.costs-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.costs-tables-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
gap: 20px;
}
.costs-stats-grid > *, .costs-tables-grid > * { min-width: 0; }
.costs-tables-grid > * { overflow-x: auto; }
.costs-stats-grid .stat-value { overflow-wrap: anywhere; }
.costs-table-label { font-size: 14px; color: var(--text-secondary); margin: 0 0 8px; }
.cost-cell-name { font-size: 12px; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cost-cell-right { text-align: right; }
.cost-top-model { font-size: 12px; }
.cost-bar-cell { width: 60px; }
.cost-bar { width: 60px; height: 6px; border: 0; border-radius: var(--radius-xs); overflow: hidden; opacity: 0.7; }
.cost-bar::-webkit-progress-bar { background: rgba(255, 255, 255, 0.08); }
.cost-bar::-webkit-progress-value { background: var(--accent); }
.cost-bar::-moz-progress-bar { background: var(--accent); }
.cost-empty-cell { color: var(--text-muted); text-align: center; }
#page-dashboard {
min-height: 0;
}
.dashboard-shell {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
padding: 0 20px 18px;
overflow: hidden;
}
/* Dashboard tabs inherit shared .app-tab* styling. */
.dashboard-panels,
.dashboard-panel {
flex: 1;
min-height: 0;
overflow: hidden;
}
/* Dashboard panels own the scroll-chain for embedded sub-panels. */
.dashboard-panels {
display: flex;
flex-direction: column;
}
.dashboard-panel {
display: none;
}
.dashboard-panel.active {
display: flex;
flex-direction: column;
}
/* P4: Activity subtab — cron schedules, running/queued tasks, background status. */
.activity-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 16px;
}
.activity-section {
margin-bottom: 18px;
}
.activity-h {
margin: 0 0 8px;
font-size: 13px;
font-weight: 600;
color: rgba(226, 232, 240, 0.78);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.activity-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 9px 12px;
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 8px;
margin-bottom: 6px;
background: rgba(255, 255, 255, 0.02);
}
.activity-row.off {
opacity: 0.55;
}
.activity-row-main {
display: flex;
flex-direction: column;
min-width: 0;
}
.activity-name {
font-size: 13px;
color: rgba(239, 246, 255, 0.9);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.activity-sub {
font-size: 12px;
color: rgba(148, 163, 184, 0.85);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.activity-row-actions {
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.activity-tag {
font-size: 11px;
color: rgba(148, 163, 184, 0.8);
font-style: italic;
}
.activity-empty,
.activity-loading {
font-size: 13px;
color: rgba(148, 163, 184, 0.75);
padding: 8px 2px;
}
/* P2: main-screen Update pill — a pointer to Dashboard -> Updates. */
/* "An update is available" is a classification, not an alarm and not a
success: it is a pointer to Dashboard -> Updates and it reads NEUTRAL
(docs/DESIGN.md "Status and chips"). The sky blue it used to carry belonged
to no role in the system and was the only place that hue appeared.
Uncommitted local changes ARE a caveat about what an update would do, so
that variant keeps the warn triple. */
.update-pill {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 6px 8px;
padding: 5px 11px;
border-radius: 999px;
border: 1px solid var(--status-neutral-border);
background: var(--status-neutral-bg);
color: var(--status-neutral-fg);
font-size: 12px;
font-weight: 600;
cursor: pointer;
}
.update-pill:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.update-pill.has-local {
border-color: var(--status-warn-border);
background: var(--status-warn-bg);
color: var(--status-warn-fg);
}
.settings-embedded-content {
display: flex;
flex: 1;
min-height: 0;
flex-direction: column;
overflow: hidden;
}
.btn-xs {
padding: 2px 8px;
font-size: 11px;
min-height: 22px;
}
.evo-empty-error {
color: var(--red);
}
/* Skills page. */
#page-skills,
#page-widgets {
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.skills-scroll,
.widgets-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: var(--space-4) var(--space-5) var(--space-5);
}
.skills-search-chrome {
flex: 0 0 auto;
margin: 2px 0 12px;
padding: 0 var(--space-5);
}
.skills-search-chrome[hidden] {
display: none !important;
}
.skills-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.skills-card {
container: skillcard / inline-size;
padding: 16px 18px;
display: flex;
flex-direction: column;
gap: 12px;
}
/* Older WebViews isolate query containers; keep an open menu above siblings. */
.skills-card:has(.skills-card-menu-dialog[open]) {
position: relative;
z-index: 1;
}
.skills-card-head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
}
.skills-card-title {
flex: 1 1 auto;
min-width: 0;
}
.skills-card-title h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
letter-spacing: -0.01em;
}
.skills-card-desc {
margin: 4px 0 0;
color: rgba(250, 250, 250, 0.65);
font-size: 13px;
line-height: 1.45;
}
.skills-card-installed {
margin-top: 5px;
font-size: 11.5px;
}
.skills-card-toggle {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
flex-wrap: wrap;
justify-content: flex-end;
min-width: 0;
}
/* A saved wide sidebar can make the card narrow even in a wide window. */
@container skillcard (max-width: 520px) {
.skills-card-head {
flex-direction: column;
align-items: stretch;
}
.skills-card-title,
.skills-card-toggle {
width: 100%;
}
.skills-card-toggle {
justify-content: flex-start;
}
}
.skills-status-chip {
padding: 4px 10px;
font-size: 12px;
font-weight: 500;
letter-spacing: 0.01em;
border: 1px solid transparent;
}
.skills-status-chip.is-clickable,
.skills-lock-hint.is-clickable {
cursor: pointer;
}
.skills-status-chip.is-clickable:hover,
.skills-lock-hint.is-clickable:hover {
filter: brightness(1.12);
}
/* Canonical status triples (docs/DESIGN.md "Status and chips"). */
.skills-status-ok {
background: var(--status-ok-bg);
color: var(--status-ok-fg);
border-color: var(--status-ok-border);
}
.skills-status-warn {
background: var(--status-warn-bg);
color: var(--status-warn-fg);
border-color: var(--status-warn-border);
}
.skills-status-danger {
background: var(--status-error-bg);
color: var(--status-error-fg);
border-color: var(--status-error-border);
}
.skills-status-muted {
background: var(--status-neutral-bg);
color: var(--status-neutral-fg);
border-color: var(--status-neutral-border);
}
.skills-switch {
position: relative;
display: inline-flex;
align-items: center;
cursor: pointer;
user-select: none;
padding: 11px 4px;
margin: -11px -4px;
}
.skills-switch input {
position: absolute;
opacity: 0;
inset: 0;
margin: 0;
cursor: pointer;
}
.skills-switch-track {
position: relative;
display: inline-block;
width: 40px;
height: 22px;
border-radius: 999px;
background: rgba(250, 250, 250, 0.12);
transition: background 120ms ease;
}
.skills-switch-thumb {
position: absolute;
top: 2px;
left: 2px;
width: 18px;
height: 18px;
border-radius: 50%;
background: #f8fafc;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
transition: transform 140ms ease, background 120ms ease;
}
.skills-switch input:checked + .skills-switch-track {
background: var(--green);
}
.skills-switch input:checked + .skills-switch-track .skills-switch-thumb {
transform: translateX(18px);
}
.skills-switch input:focus-visible + .skills-switch-track {
outline: 2px solid var(--focus-accent-border);
outline-offset: 2px;
}
.skills-switch.is-locked,
.skills-switch input:disabled {
cursor: not-allowed;
}
.skills-switch.is-locked .skills-switch-track,
.skills-switch input:disabled + .skills-switch-track {
background: rgba(245, 158, 11, 0.18);
opacity: 0.7;
}
.skills-switch.is-locked .skills-switch-thumb,
.skills-switch input:disabled + .skills-switch-track .skills-switch-thumb {
background: rgba(245, 158, 11, 0.85);
}
/* Inline hint for switches locked by review/grants. */
.skills-lock-hint {
font-size: 12px;
color: var(--status-warn-fg);
margin-left: 2px;
display: flex;
align-items: center;
gap: 6px;
}
.skills-lock-hint[role="button"] {
outline: none;
}
.skills-lock-hint[role="button"]:focus-visible,
.skills-status-chip[role="button"]:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: 2px;
}
.skills-lock-hint::before {
content: '\1F512';
font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
font-size: 11px;
}
.skills-review-progress {
display: inline-flex;
align-items: center;
gap: 8px;
align-self: flex-start;
padding: 6px 10px;
border-radius: 999px;
border: 1px solid var(--accent-25);
background: var(--accent-08);
color: rgba(255, 220, 226, 0.86);
font-size: 12px;
}
.skills-review-spinner {
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid rgba(255, 220, 226, 0.24);
border-top-color: rgba(255, 220, 226, 0.9);
animation: skills-review-spin 900ms linear infinite;
}
@keyframes skills-review-spin {
to { transform: rotate(360deg); }
}
#skills-refresh.is-loading::after {
content: '';
display: inline-block;
width: 10px;
height: 10px;
margin-left: 8px;
border-radius: 50%;
border: 2px solid rgba(250, 250, 250, 0.25);
border-top-color: rgba(250, 250, 250, 0.9);
animation: skills-review-spin 900ms linear infinite;
vertical-align: -1px;
}
.skills-access {
border: 1px solid rgba(250, 250, 250, 0.1);
border-radius: var(--radius-md);
padding: 10px 12px;
background: rgba(255, 255, 255, 0.02);
display: flex;
flex-direction: column;
gap: 6px;
font-size: 13px;
}
.skills-access-warn {
border-color: rgba(245, 158, 11, 0.35);
background: rgba(245, 158, 11, 0.06);
}
.skills-access-ok {
border-color: rgba(34, 197, 94, 0.3);
background: rgba(34, 197, 94, 0.05);
}
.skills-access-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: baseline;
}
.skills-access-label {
color: rgba(250, 250, 250, 0.5);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-right: 4px;
}
.skills-access-status {
color: rgba(250, 250, 250, 0.85);
}
.skills-access-actions {
margin-top: 4px;
}
.skills-access code {
padding: 1px 6px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.06);
font-size: 11.5px;
}
.skills-details {
margin-top: 4px;
border-top: 1px solid rgba(250, 250, 250, 0.06);
padding-top: 10px;
}
.skills-details > summary {
cursor: pointer;
user-select: none;
color: rgba(250, 250, 250, 0.55);
font-size: 12px;
list-style: none;
display: inline-flex;
align-items: center;
gap: 4px;
}
.skills-details > summary::-webkit-details-marker {
display: none;
}
.skills-details > summary::before {
content: '\25B8';
display: inline-block;
transition: transform 120ms ease;
font-size: 10px;
}
.skills-details[open] > summary::before {
transform: rotate(90deg);
}
.skills-detail-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: baseline;
font-size: 12.5px;
color: rgba(250, 250, 250, 0.7);
padding: 4px 0;
}
.skills-detail-label {
color: rgba(250, 250, 250, 0.5);
font-size: 11.5px;
text-transform: uppercase;
letter-spacing: 0.05em;
min-width: 90px;
}
.skills-detail-row code {
padding: 1px 6px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.05);
font-size: 11.5px;
}
.skills-presence-runtime {
width: min(100%, 620px);
margin-top: 8px;
padding: 10px 12px;
border: 1px solid rgba(250, 250, 250, 0.08);
border-radius: var(--radius-md);
background: rgba(255, 255, 255, 0.025);
display: flex;
flex-wrap: wrap;
align-items: end;
gap: 8px 12px;
}
.skills-presence-runtime-title {
flex-basis: 100%;
font-size: 12px;
font-weight: 600;
}
.skills-presence-runtime label {
display: grid;
gap: 4px;
color: rgba(250, 250, 250, 0.6);
font-size: 11.5px;
}
.skills-presence-runtime input {
width: 110px;
}
.skills-presence-runtime .skills-presence-workspace {
flex: 1 1 100%;
min-width: 0;
}
.skills-presence-workspace input {
width: 100%;
}
.skills-presence-runtime-actions {
display: flex;
gap: 6px;
}
.skills-presence-runtime > .muted {
flex-basis: 100%;
font-size: 11.5px;
}
.skills-card-actions {
display: flex;
gap: 8px;
align-items: flex-start;
flex-wrap: wrap;
}
.skills-primary-action {
min-width: 130px;
}
.skills-card-actions .skills-details {
margin-top: 0;
border: none;
padding: 0;
margin-left: auto;
}
.skills-card-actions .skills-details > summary {
/* Match action-button height so closed summaries align. */
padding: 6px 4px;
}
.skills-card-menu {
position: relative;
display: inline-flex;
}
.skills-card-menu-trigger {
width: 32px;
min-height: 32px;
border-radius: var(--radius-sm);
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
color: var(--text-secondary);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-weight: 700;
font-size: 18px;
line-height: 1;
font-family: inherit;
padding: 0;
}
.skills-card-menu-trigger:hover {
border-color: var(--accent-25);
color: var(--text-primary);
}
/* Source/provenance chip for non-built-in skills. */
.skills-source-chip {
display: inline-block;
margin-left: 6px;
padding: 1px 8px;
border-radius: 999px;
font-size: 10.5px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
vertical-align: middle;
border: 1px solid transparent;
}
.skills-source-warn {
background: var(--status-warn-bg);
color: var(--status-warn-fg);
border-color: var(--status-warn-border);
}
.skills-source-muted {
background: rgba(250, 250, 250, 0.05);
color: rgba(250, 250, 250, 0.55);
border-color: rgba(250, 250, 250, 0.1);
}
.skills-load-error {
color: var(--red);
font-size: 12px;
margin-bottom: 8px;
}
.skills-badge {
padding: 2px 8px;
border-radius: var(--radius-xs);
font-size: 11px;
font-family: 'JetBrains Mono', monospace;
text-transform: uppercase;
letter-spacing: 0.5px;
white-space: nowrap;
}
.skills-badge-ok { background: var(--status-ok-bg); color: var(--status-ok-fg); }
.skills-badge-danger { background: var(--status-error-bg); color: var(--status-error-fg); }
.skills-badge-warn { background: var(--status-warn-bg); color: var(--status-warn-fg); }
.skills-badge-muted { background: var(--status-neutral-bg); color: var(--status-neutral-fg); }
.toast-stack {
position: fixed;
right: 18px;
top: calc(76px + env(safe-area-inset-top));
z-index: 120;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 10px;
pointer-events: none;
}
.toast {
max-width: min(420px, calc(100vw - 112px));
padding: 11px 14px;
border-radius: var(--radius);
border: 1px solid rgba(255, 255, 255, 0.10);
background: rgba(18, 20, 26, 0.94);
color: var(--text-primary);
box-shadow: 0 18px 42px rgba(0, 0, 0, 0.34);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
font-size: 13px;
line-height: 1.45;
/* Error text can be a long unbroken token (a path, a URL, a serialized
body); without this it overflows the toast instead of wrapping. */
overflow-wrap: anywhere;
pointer-events: auto;
cursor: pointer;
animation: toast-in 160ms ease-out;
}
.toast.is-hiding {
animation: toast-out 160ms ease-in forwards;
}
/* A toast keeps its own glass background (a translucent status fill over the
page would cost the text its contrast); the tone paints edge + foreground. */
.toast-ok { border-color: var(--status-ok-border); color: var(--status-ok-fg); }
.toast-danger { border-color: var(--status-error-border); color: var(--status-error-fg); }
.toast-warn { border-color: var(--status-warn-border); color: var(--status-warn-fg); }
.toast-muted,
.toast-info { color: var(--status-neutral-fg); }
@keyframes toast-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes toast-out {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(8px); }
}
.skills-tabs {
margin-bottom: 14px;
}
.skills-tab-panel {
margin-top: 4px;
}
.marketplace-shell {
display: flex;
flex-direction: column;
gap: 12px;
}
.marketplace-controls {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.marketplace-search { flex: 1 1 240px; min-width: min(200px, 100%); }
.marketplace-checkbox {
display: inline-flex;
align-items: center;
gap: 6px;
color: rgba(250, 250, 250, 0.7);
font-size: 13px;
}
.marketplace-status {
min-height: 18px;
font-size: 12px;
}
.marketplace-results {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 12px;
}
.marketplace-card {
padding: 14px;
display: flex;
flex-direction: column;
gap: 10px;
}
.marketplace-card.is-working {
border-color: rgba(245, 158, 11, 0.35);
background: linear-gradient(135deg, rgba(245, 158, 11, 0.08), rgba(255, 255, 255, 0.02));
/* Pulse during long install/adapt/review/deps work. */
animation: marketplace-card-pulse 2.4s ease-in-out infinite;
}
@keyframes marketplace-card-pulse {
0% { background: linear-gradient(135deg, rgba(245, 158, 11, 0.06), rgba(255, 255, 255, 0.02)); }
50% { background: linear-gradient(135deg, rgba(245, 158, 11, 0.14), rgba(255, 255, 255, 0.04)); }
100% { background: linear-gradient(135deg, rgba(245, 158, 11, 0.06), rgba(255, 255, 255, 0.02)); }
}
/* Marketplace filter row; real input preserves keyboard/a11y. */
.marketplace-filters {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
padding: 6px 0 8px;
}
.marketplace-filter-toggle {
display: inline-flex;
align-items: center;
gap: 8px;
cursor: pointer;
color: var(--text-secondary);
font-size: 13px;
user-select: none;
}
.marketplace-filter-toggle input[type="checkbox"] {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
overflow: hidden;
clip: rect(0 0 0 0);
}
.marketplace-filter-track {
width: 36px;
height: 20px;
border-radius: var(--radius-md);
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.06);
position: relative;
flex-shrink: 0;
transition: background 0.18s ease;
}
.marketplace-filter-track::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 14px;
height: 14px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.85);
transition: transform 0.18s ease;
}
.marketplace-filter-toggle input[type="checkbox"]:checked + .marketplace-filter-track {
background: var(--accent-55);
border-color: var(--accent-65);
}
.marketplace-filter-toggle input[type="checkbox"]:checked + .marketplace-filter-track::after {
transform: translateX(16px);
background: #fff;
}
.marketplace-filter-toggle input[type="checkbox"]:focus-visible + .marketplace-filter-track {
outline: 2px solid var(--focus-accent-border);
outline-offset: 2px;
}
.marketplace-card-head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 10px;
}
.marketplace-card-title strong {
font-size: 14px;
margin-right: 8px;
display: block;
}
.marketplace-card-title .muted {
font-size: 12px;
color: rgba(250, 250, 250, 0.55);
word-break: break-all;
}
.marketplace-card-badges {
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: flex-end;
align-items: flex-start;
min-width: 0;
max-width: 100%;
}
.marketplace-card-body {
color: rgba(250, 250, 250, 0.8);
font-size: 13px;
line-height: 1.45;
min-height: 38px;
}
.marketplace-card-state {
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius);
padding: 10px 12px;
font-size: 12px;
background: rgba(255, 255, 255, 0.035);
}
.marketplace-card-state strong {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.marketplace-working-spinner {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid rgba(245, 158, 11, 0.22);
border-top-color: rgba(245, 158, 11, 0.95);
animation: skills-review-spin 900ms linear infinite;
}
.marketplace-card-state-hint {
color: rgba(250, 250, 250, 0.68);
line-height: 1.45;
}
.marketplace-state-ok {
border-color: var(--status-ok-border);
}
.marketplace-state-warn {
border-color: var(--status-warn-border);
}
.marketplace-state-danger {
border-color: var(--status-error-border);
}
.marketplace-card-meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
font-size: 12px;
color: rgba(250, 250, 250, 0.55);
}
.marketplace-card-meta a {
color: rgba(250, 250, 250, 0.75);
}
.marketplace-card-actions {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: auto;
}
.marketplace-primary-action .btn {
width: 100%;
}
.marketplace-secondary-actions {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.marketplace-secondary-actions > .muted {
font-size: var(--type-meta);
color: var(--text-meta);
line-height: var(--line-meta);
}
.marketplace-pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin-top: 8px;
font-size: 12px;
}
/* Detail modal. */
.marketplace-modal-backdrop {
align-items: flex-start;
padding: 4vh 16px;
}
.marketplace-modal {
max-height: calc(var(--vvh) - 8vh);
min-height: 0;
border-radius: var(--radius-md);
padding: 16px;
max-width: 900px;
gap: 12px;
}
/* Confirm/input/alert dialogs must stay usable over EVERY overlay — the
reconnect shade sits at 1000 (the Panic confirm is the canonical case: it
must be clickable while the socket is down). Content modals
(:where(.marketplace-modal-backdrop), 90) deliberately stay under the
shade — their actions need a live connection anyway. Overlay scale:
modals 90 · toasts/menus 120 · reconnect 1000 · dialogs 1100. */
.confirm-dialog-backdrop {
z-index: 1100;
}
.confirm-dialog {
max-width: 520px;
}
.confirm-dialog .marketplace-modal-body p {
margin: 0;
white-space: pre-line;
}
.confirm-dialog-details {
border-top: 1px solid var(--surface-border);
padding-top: 10px;
}
.confirm-dialog-details > summary {
cursor: pointer;
user-select: none;
}
.confirm-dialog-detail-list {
display: grid;
gap: 8px;
margin: 10px 0 0;
}
.confirm-dialog-detail-row {
display: grid;
grid-template-columns: minmax(110px, 0.35fr) minmax(0, 1fr);
gap: 10px;
}
.confirm-dialog-detail-row dt {
font-weight: 600;
}
.confirm-dialog-detail-row dd {
margin: 0;
overflow-wrap: anywhere;
white-space: pre-wrap;
}
.marketplace-modal-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.marketplace-modal-head, .marketplace-modal-actions { flex-shrink: 0; }
.marketplace-modal-body > * { flex-shrink: 0; }
.marketplace-modal-body {
min-height: 0;
flex: 1 1 auto;
display: flex;
flex-direction: column;
gap: 14px;
overflow-y: auto;
}
.marketplace-modal-body section h3 {
margin: 0 0 4px 0;
font-size: 16px;
}
.marketplace-modal-body section h4 {
margin: 0 0 4px 0;
font-size: 13px;
color: rgba(250, 250, 250, 0.75);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.marketplace-modal-actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
}
.marketplace-block-list h4 { color: var(--red); }
.marketplace-block-list ul { margin: 4px 0 0 0; padding-left: 18px; color: var(--red); font-size: 12px; }
.marketplace-warn-list h4 { color: var(--amber); }
.marketplace-warn-list ul { margin: 4px 0 0 0; padding-left: 18px; color: var(--amber); font-size: 12px; }
/* Installed-card provenance and adapter warnings. */
.skills-card-provenance {
display: flex;
flex-wrap: wrap;
gap: 8px 14px;
align-items: center;
margin-top: 4px;
font-size: 12px;
}
.skills-card-warnings {
margin-top: 4px;
font-size: 12px;
}
.skills-card-warnings summary {
cursor: pointer;
user-select: none;
}
.skills-card-warnings ul {
margin: 6px 0 4px 16px;
padding: 0;
}
/* Widgets page. Masonry (web/modules/masonry.js) measures and packs the cards
and writes the result ONLY as custom properties — `--masonry-h` on the list,
`--masonry-w/-x/-y` on each card; these static rules apply them. Cards are
positioned by transform in the owner's key order, so a reorder never moves a
DOM node (a moved <iframe> would reload). */
.widgets-list {
position: relative;
margin-top: 16px;
height: var(--masonry-h, auto);
}
.widgets-card {
position: absolute;
left: 0;
top: 0;
width: var(--masonry-w, auto);
transform: translate(var(--masonry-x, 0px), var(--masonry-y, 0px));
padding: 14px;
backdrop-filter: blur(12px);
}
.widgets-card.dragging {
opacity: 0.68;
}
.widgets-card.drag-over {
border-color: var(--accent-45);
box-shadow: 0 0 0 3px var(--accent-10);
}
/* Each card is its own stacking context (transform + backdrop-filter), so an
open launch-policy menu would otherwise paint under a later sibling card. */
.widgets-card:has(.skills-card-menu-dialog[open]) {
z-index: 1;
}
.widgets-card-head {
display: flex;
justify-content: space-between;
gap: 12px;
align-items: center;
margin-bottom: 12px;
}
.widgets-card-title {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 8px;
min-width: 0;
}
.widgets-card-drag {
flex: 0 0 auto;
width: 28px;
height: 28px;
border-radius: var(--radius-xs);
border: 1px solid rgba(226, 232, 240, 0.14);
background: rgba(26, 21, 32, 0.48);
color: rgba(226, 232, 240, 0.54);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
cursor: grab;
font: inherit;
font-size: 14px;
line-height: 1;
}
.widgets-card-drag:hover {
border-color: var(--accent-45);
color: var(--text-primary);
}
.widgets-card-drag:active {
cursor: grabbing;
}
/* Framed-card head controls (widgets lifecycle phase 2): status as dot + text
(docs/DESIGN.md section 4 — the status pair is the semantic contract, not a
token migration of this surface), the one primary Start/Stop button, the
launch-policy menu on the Skills card menu primitive, and the drag handle.
Widgets is not a migrated surface (DESIGN.md section 8): these rules keep
the literal sizes and inks the surface already uses, not the type tokens. */
.widgets-card-controls {
display: flex;
align-items: center;
gap: 8px;
flex: 1 1 auto;
flex-wrap: wrap;
justify-content: flex-end;
min-width: 0;
}
.widgets-card-controls > :not(.ui-status) {
flex: 0 0 auto;
}
.widgets-card-controls .ui-status:not([hidden]) {
display: inline-flex;
align-items: center;
min-width: 0;
max-width: var(--widget-fault-status-max-width);
white-space: normal;
overflow-wrap: anywhere;
}
.widgets-card-controls .ui-status[data-tone]::before {
content: "";
display: inline-block;
flex: 0 0 auto;
width: 6px;
height: 6px;
margin-right: 6px;
border-radius: 999px;
background: currentColor;
}
.widgets-menu-heading {
padding: 4px 10px 6px;
font-size: 12px;
line-height: 1.35;
color: rgba(250, 250, 250, 0.5);
white-space: nowrap;
}
.widgets-menu-item {
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}
.widgets-menu-item[aria-checked="false"] .widgets-menu-check {
visibility: hidden;
}
/* A stopped framed card stands in for its frame: same box, same declared
height (the `--widget-frame-height` custom property the frame itself uses),
so Start does not move the layout. */
.widgets-facade {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
width: 100%;
height: var(--widget-frame-height, 320px);
min-height: 320px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius);
background: rgba(13, 11, 15, 0.88);
color: var(--text-primary);
text-align: center;
}
.widgets-facade-icon {
display: inline-flex;
font-size: 24px;
line-height: 1;
color: rgba(250, 250, 250, 0.5);
}
.widgets-facade-icon svg {
width: 1em;
height: 1em;
}
.widgets-facade-title {
font-size: 14px;
line-height: 1.5;
font-weight: 600;
}
.widgets-card-source {
color: rgba(250, 250, 250, 0.5);
font-size: 12px;
}
.widgets-card-body > :first-child {
margin-top: 0;
}
.widgets-frame {
width: 100%;
height: var(--widget-frame-height, 320px);
min-height: 320px;
display: block;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius);
background: rgba(13, 11, 15, 0.88);
}
.widget-form,
.widget-group,
.widget-metric,
.widget-callout,
.widget-kv,
.widget-markdown,
.widget-json,
.widget-code,
.widget-chart,
.widget-tabs,
.widget-stream,
.widget-media,
.widget-gallery,
.widget-progress,
.widget-map,
.widget-calendar,
.widget-kanban,
.widget-subscription-render {
margin-top: 10px;
}
/* Publisher markdown in a widget is DOMPurify-sanitized HTML, not the chat
renderer's classed output, and the renderer stays unchanged (ARCHITECTURE,
"Declarative widgets"). Its block elements therefore need element-level
rhythm inside this one scope; paragraphs come from .ui-rich-content, which
the widget host already applies alongside .widget-markdown. */
.widget-markdown h1,
.widget-markdown h2,
.widget-markdown h3 {
margin: var(--space-4) 0 var(--space-2);
color: var(--text-primary);
line-height: var(--line-title);
}
.widget-markdown h1 { font-size: var(--type-section); }
.widget-markdown h2,
.widget-markdown h3 { font-size: var(--type-body); }
.widget-markdown blockquote {
margin: var(--space-3) 0;
padding: var(--space-2) var(--space-3);
border-left: 3px solid var(--accent);
background: var(--surface-card-soft);
color: var(--text-meta);
}
.widget-markdown table {
width: 100%;
margin: var(--space-4) 0;
border-collapse: collapse;
color: var(--text-primary);
font-size: var(--type-body);
}
.widget-markdown th,
.widget-markdown td {
padding: var(--space-2) var(--space-3);
border-bottom: 1px solid var(--surface-border);
color: var(--text-primary);
text-align: left;
}
.widget-markdown th { font-weight: 600; }
.widget-json {
min-width: 0;
max-width: 100%;
}
.widget-json pre {
max-width: 100%;
max-height: min(360px, 50vh);
overflow: auto;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.widget-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.widget-form-fields {
display: grid;
gap: 10px;
min-width: 0;
}
.widget-form-fields.widget-grid-cols-1 { grid-template-columns: minmax(0, 1fr); }
.widget-form-fields.widget-grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.widget-form-fields.widget-grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.widget-form-fields.widget-grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.widget-field-span-2 { grid-column: span 2; }
.widget-field-span-3 { grid-column: span 3; }
.widget-field-span-4 { grid-column: span 4; }
.widget-field {
min-width: 0;
}
.widget-field-inline {
flex-direction: row;
align-items: center;
}
.widget-group {
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: var(--radius-sm);
padding: 10px;
}
.widget-group > h4,
.widget-group > p { margin: 0 0 8px; }
.widget-group > p { color: var(--text-secondary); font-size: 12px; }
.widget-group-components { min-width: 0; }
.widget-group-components > * { margin-top: 0; }
.widget-group-stack > .widget-group-components { display: flex; flex-direction: column; gap: 10px; }
.widget-group-cluster > .widget-group-components { display: flex; flex-wrap: wrap; align-items: start; gap: 10px; }
.widget-group-grid > .widget-group-components { display: grid; gap: 10px; }
.widget-group-grid.widget-grid-cols-1 > .widget-group-components { grid-template-columns: minmax(0, 1fr); }
.widget-group-grid.widget-grid-cols-2 > .widget-group-components { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.widget-group-grid.widget-grid-cols-3 > .widget-group-components { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.widget-group-grid.widget-grid-cols-4 > .widget-group-components { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.widget-group-grid > .widget-group-components > :is(
.widget-group,
.widget-form,
.widget-table-wrap,
.widget-chart,
.widget-tabs,
.widget-kanban,
.widget-code,
.widget-json,
.widget-subscription-render
) {
grid-column: 1 / -1;
}
.widget-metric,
.widget-callout {
border: 1px solid rgba(255, 255, 255, 0.08);
border-left-width: 3px;
border-radius: var(--radius-sm);
padding: 9px 10px;
}
.widget-metric { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.widget-metric > span { color: var(--text-secondary); font-size: 12px; }
.widget-metric > strong { font-size: 18px; }
.widget-metric small { color: var(--text-secondary); font-size: 11px; font-weight: 500; }
.widget-callout { font-size: 13px; line-height: 1.45; background: rgba(96, 165, 250, 0.06); }
.widget-metric[data-tone="ok"], .widget-callout[data-tone="ok"] { border-left-color: var(--status-ok-fg); }
.widget-metric[data-tone="warn"], .widget-callout[data-tone="warn"] { border-left-color: var(--status-warn-fg); }
.widget-metric[data-tone="danger"], .widget-callout[data-tone="danger"] { border-left-color: var(--status-error-fg); }
/* `info` is a widget-only tone with no status role; it stays the plain blue. */
.widget-metric[data-tone="info"], .widget-callout[data-tone="info"] { border-left-color: var(--blue); }
.widget-kv-row {
display: flex;
justify-content: space-between;
gap: 12px;
padding: 6px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.widget-table-wrap {
overflow-x: auto;
}
.widget-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.widget-table th,
.widget-table td {
padding: 7px 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
text-align: left;
overflow-wrap: break-word;
word-break: normal;
}
.widget-table th,
.widget-table-status { white-space: nowrap; }
.widget-table a { color: #93c5fd; overflow-wrap: anywhere; }
.widget-table-status { font-weight: 600; }
.widget-table-status[data-tone="ok"] { color: var(--status-ok-fg); }
.widget-table-status[data-tone="warn"] { color: var(--status-warn-fg); }
.widget-table-status[data-tone="danger"] { color: var(--status-error-fg); }
.widget-table-status[data-tone="neutral"],
.widget-table-status[data-tone="muted"] { color: var(--status-neutral-fg); }
.widget-media img,
.widget-media video {
max-width: 100%;
border-radius: var(--radius);
}
.widget-media audio {
width: 100%;
}
.widget-gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 10px;
}
/* Host-owned map/calendar/kanban defaults without extra dependencies. */
.widget-map {
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: var(--radius-sm);
padding: 10px;
background: rgba(255, 255, 255, 0.02);
}
.widget-map-list { margin: 0; padding-left: 18px; font-size: 13px; }
.widget-map-list li { padding: 2px 0; }
.widget-calendar { font-size: 13px; }
.widget-calendar-list { list-style: none; margin: 0; padding: 0; }
.widget-calendar-row {
display: flex;
gap: 8px;
align-items: baseline;
padding: 6px 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.widget-kanban {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
gap: 10px;
margin-top: 8px;
}
.widget-kanban-col {
align-self: start;
background: rgba(255, 255, 255, 0.03);
border-radius: var(--radius-sm);
padding: 10px;
min-height: 120px;
}
.widget-kanban-col.is-empty { min-height: 72px; }
.widget-kanban-col-head { margin-bottom: 8px; font-size: 12px; color: var(--text-secondary); }
.widget-kanban-empty { color: var(--text-muted); font-size: 11px; }
.widget-kanban-card {
display: flex;
flex-direction: column;
gap: 7px;
background: var(--accent-12);
border: 1px solid var(--accent-25);
border-radius: 6px;
padding: 6px 8px;
margin: 4px 0;
cursor: grab;
font-size: 12px;
}
.widget-kanban-card:active { cursor: grabbing; }
.widget-kanban-move {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
color: var(--text-secondary);
font-size: 10px;
}
.widget-kanban-move select { min-width: 0; max-width: 112px; }
.widget-progress {
display: flex;
align-items: center;
gap: 10px;
}
.widget-code pre {
margin: 0;
padding: 10px;
border-radius: var(--radius-sm);
background: rgba(0, 0, 0, 0.24);
overflow-x: auto;
}
.widget-code-label {
color: rgba(250, 250, 250, 0.65);
font-size: 12px;
margin-bottom: 6px;
}
.widget-chart {
min-height: 240px;
}
/* Bounded canvas (v6.71.0): a fixed-height positioned box for Chart.js
responsive sizing — charts stop growing the card unbounded, and the data
table below stays reachable. */
.widget-chart-canvas { position: relative; height: clamp(260px, 32vh, 360px); }
.widget-chart-canvas canvas { display: block; width: 100%; }
.widget-chart-fallback { min-height: 0; }
.widget-chart-data { margin-top: 8px; }
.widget-chart-data > summary { color: var(--text-secondary); cursor: pointer; font-size: 12px; }
/* SWR refresh (v6.71.0): content stays; a thin pulsing indicator marks the
background refetch instead of a loading swap. */
.widget-status[data-state="refreshing"] {
position: relative;
}
.widget-status[data-state="refreshing"]::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -2px;
height: 2px;
border-radius: 1px;
background: var(--accent, #7c5cff);
opacity: 0.5;
animation: widget-refresh-pulse 1.2s ease-in-out infinite;
}
@keyframes widget-refresh-pulse {
0%, 100% { opacity: 0.2; }
50% { opacity: 0.7; }
}
.widget-tab-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 10px;
}
.widget-tab-btn {
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 999px;
background: rgba(255, 255, 255, 0.03);
color: var(--text-secondary);
cursor: pointer;
padding: 5px 10px;
}
.widget-tab-btn.active {
border-color: var(--accent-35);
background: var(--accent-12);
color: var(--text-primary);
}
.widget-tab-body > :first-child {
margin-top: 0;
}
/* Late mobile overrides; placed after base rules so they win without !important. */
@media (max-width: 640px) {
.widget-kanban-col.is-empty {
min-height: 0;
padding-block: 8px;
}
.widget-kanban-col.is-empty .widget-kanban-col-head {
margin-bottom: 3px;
}
.widget-form-fields.widget-grid-cols-1,
.widget-form-fields.widget-grid-cols-2,
.widget-form-fields.widget-grid-cols-3,
.widget-form-fields.widget-grid-cols-4,
.widget-group-grid.widget-grid-cols-1 > .widget-group-components,
.widget-group-grid.widget-grid-cols-2 > .widget-group-components,
.widget-group-grid.widget-grid-cols-3 > .widget-group-components,
.widget-group-grid.widget-grid-cols-4 > .widget-group-components {
grid-template-columns: minmax(0, 1fr);
}
.widget-field-span-2,
.widget-field-span-3,
.widget-field-span-4 { grid-column: span 1; }
.widget-table-wrap { overflow-x: visible; }
.widget-table,
.widget-table tbody,
.widget-table tr,
.widget-table td { display: block; width: 100%; }
.widget-table thead { display: none; }
.widget-table tr {
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: var(--radius-sm);
margin-bottom: 8px;
padding: 5px 8px;
}
.widget-table td {
display: grid;
grid-template-columns: minmax(72px, 38%) minmax(0, 1fr);
gap: 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
padding: 6px 0;
}
.widget-table td::before {
content: attr(data-label);
color: var(--text-secondary);
font-size: 11px;
}
.widget-table th[scope="row"] { display: block; padding: 6px 0; }
/* iOS safe-area inset. */
#chat-input-area,
.chat-input-area {
padding-bottom: max(16px, env(safe-area-inset-bottom, 0px));
}
.chat-input-wrap,
.chat-text-row,
.chat-toolbar-row,
#chat-input,
textarea.chat-input {
min-width: 0;
}
.chat-input-wrap {
--composer-control-height: 32px;
}
.chat-toolbar-row {
position: static;
right: auto;
bottom: auto;
z-index: auto;
justify-content: flex-start;
gap: 8px;
padding: 0 6px;
}
.chat-composer-pills {
flex: 1;
overflow-x: auto;
scrollbar-width: none;
}
.chat-composer-pills::-webkit-scrollbar {
display: none;
}
.chat-swarm,
.chat-context-mode .chat-seg {
padding-left: 10px;
padding-right: 10px;
}
.chat-send-inline {
padding-left: 11px;
padding-right: 11px;
}
/* Transcript clears taller mobile header and absolute composer. */
#chat-messages,
.chat-messages {
padding-top: var(--chat-header-reserve, 80px);
padding-bottom: calc(var(--chat-input-reserve, 108px) + env(safe-area-inset-bottom, 0px));
overflow-x: hidden;
}
/* Keyboard-open layout: only #chat-messages scrolls within visual viewport. */
html.keyboard-open,
body.keyboard-open {
position: fixed;
inset: 0;
width: 100%;
height: var(--vvh);
max-height: var(--vvh);
overflow: hidden;
overscroll-behavior: none;
}
body.keyboard-open #content {
position: fixed;
inset: 0;
height: var(--vvh);
max-height: var(--vvh);
padding-left: 0;
padding-bottom: 0;
overflow: hidden;
overscroll-behavior: none;
}
body.keyboard-open #page-chat.active {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--vvh);
max-height: var(--vvh);
display: flex;
flex-direction: column;
z-index: 20;
overflow: hidden;
overscroll-behavior: none;
}
body.keyboard-open #page-chat.active .chat-page-header {
flex-shrink: 0;
position: relative;
}
body.keyboard-open #page-chat.active #chat-messages,
body.keyboard-open .chat-instance-panel .chat-messages {
flex: 1;
min-height: 0;
position: relative;
height: auto;
max-height: none;
padding-top: 0;
padding-bottom: 0;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
body.keyboard-open #page-chat.active #chat-input-area,
body.keyboard-open .chat-instance-panel .chat-input-area {
flex-shrink: 0;
position: relative;
bottom: auto;
padding-top: 8px;
padding-bottom: 8px;
}
.logs-filters {
padding: 8px 12px;
}
#log-entries {
margin: 0 8px 8px;
min-width: 0;
overflow-x: hidden;
}
.evo-chart-wrap {
min-height: 260px;
max-height: none;
padding: 10px;
}
.costs-budget-fields,
.costs-stats-grid,
.costs-tables-grid,
.marketplace-results {
grid-template-columns: 1fr;
}
.costs-scroll {
padding: 12px 12px;
}
.skills-card-head,
.marketplace-controls {
flex-direction: column;
align-items: stretch;
}
.skills-card-title {
width: 100%;
}
.skills-card-title h3,
.skills-card-desc {
overflow-wrap: anywhere;
word-break: normal;
}
.skills-card-toggle {
justify-content: flex-start;
width: 100%;
}
.marketplace-search {
flex: 0 0 auto;
width: 100%;
}
}
/* ===========================================================================
Multi-project sidebar + right thread panel (v6.32.0)
=========================================================================== */
.nav-projects {
display: flex;
flex-direction: column;
gap: 1px;
width: 100%;
align-items: stretch;
margin: var(--space-1) 0 var(--space-2);
flex-shrink: 0;
}
/* The sidebar's one variable-length collection: a bounded window that scrolls
on its own, so expanding it never pushes the Utilities rows past the fold. */
.nav-projects-list {
display: flex;
flex-direction: column;
gap: 1px;
width: 100%;
align-items: stretch;
flex-shrink: 0;
max-height: var(--nav-projects-list-max-height);
min-height: 0;
overflow-y: auto;
}
.nav-projects-list[hidden] {
display: none;
}
/* Project rows read as a compact list nested under the "Projects" header (no
glyph/dot, label indented to align under the parent label, shorter rows and
no left active bar) — a dropdown list, not standalone tabs. */
.nav-project-item {
display: grid;
grid-template-columns: minmax(0, 1fr) 28px;
align-items: center;
width: 100%;
border-radius: var(--radius-sm);
}
.nav-project-row {
min-height: 30px;
font-size: 13px;
color: var(--text-muted);
padding-left: calc(var(--space-3) + 18px + var(--space-3));
padding-right: var(--space-1);
border-radius: var(--radius-sm);
}
.nav-project-row:hover {
color: var(--text-primary);
}
.nav-project-item.is-deleting .nav-project-row,
.nav-project-item.is-deleting .nav-project-row:disabled {
cursor: not-allowed;
color: var(--text-muted);
opacity: 0.62;
}
.nav-project-item.is-deleting {
grid-template-columns: minmax(0, 1fr) auto;
}
.nav-project-deleting-status {
justify-self: end;
padding-right: 3px;
color: var(--text-muted);
font-size: 10px;
white-space: nowrap;
}
.nav-project-row.active {
color: var(--project);
}
.nav-project-row.active::before {
display: none;
}
/* v6.59.0 — New Project "+" in the Projects header + per-row kebab actions. */
.nav-projects-header {
display: grid;
grid-template-columns: minmax(0, 1fr) 32px;
align-items: center;
width: 100%;
}
.nav-projects-header > .nav-section-toggle {
min-width: 0;
}
.nav-projects-header .nav-chevron {
width: 16px;
height: 16px;
transform: rotate(90deg);
}
.nav-projects-header .nav-section-toggle[aria-expanded="false"] .nav-chevron {
transform: rotate(0deg);
}
.nav-projects-header .nav-row-meta:not(:empty) {
min-width: 18px;
height: 18px;
padding: 0 5px;
border-radius: 9px;
background: var(--red);
color: #fff;
font-size: var(--type-meta);
font-weight: 700;
line-height: 18px;
text-align: center;
}
.nav-projects-add {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-muted);
cursor: pointer;
}
.nav-projects-add:hover,
.nav-projects-add:focus-visible {
color: var(--text-primary);
background: var(--bg-hover);
}
.nav-projects-add:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: -2px;
}
.nav-project-kebab {
width: 26px;
height: 26px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
border: 0;
background: transparent;
color: var(--text-muted);
opacity: 0;
cursor: pointer;
border-radius: var(--radius-sm);
}
.nav-project-item:hover .nav-project-kebab,
.nav-project-item:focus-within .nav-project-kebab {
opacity: 1;
}
.nav-project-kebab:hover,
.nav-project-kebab:focus-visible {
color: var(--text-primary);
background: var(--bg-hover);
}
/* Hover paint is not a focus ring: the kebab had only the paint, so a keyboard
user could not tell it was focused (docs/DESIGN.md "Focus"). */
.nav-project-kebab:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: -2px;
}
/* New Project dialog */
.new-project-dialog {
max-width: 560px;
width: min(560px, 94vw);
}
.new-project-field {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: var(--space-3);
font-size: 13px;
}
.new-project-sources {
display: flex;
flex-direction: column;
gap: 6px;
margin: 0 0 var(--space-3);
padding: var(--space-2) var(--space-3);
border: 1px solid var(--divider);
border-radius: var(--radius-md, 8px);
font-size: 13px;
}
.new-project-sources legend {
padding: 0 6px;
color: var(--text-muted);
font-size: 12px;
}
.new-project-sources label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.new-project-selected-target { overflow-wrap: anywhere; }
.new-project-source-detail {
margin-bottom: var(--space-3);
}
.new-project-browser {
border: 1px solid var(--divider);
border-radius: var(--radius-md, 8px);
margin-bottom: var(--space-2);
overflow: hidden;
}
.new-project-browser-path {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 6px 10px;
border-bottom: 1px solid var(--divider);
font-size: 12px;
}
.new-project-browser-path > strong { min-width: 0; overflow-wrap: anywhere; }
.new-project-browser-path > button { flex: 0 0 auto; white-space: nowrap; }
.new-project-browser-list {
display: flex;
flex-direction: column;
}
.new-project-dir-row {
text-align: left;
padding: 6px 10px;
border: none;
background: transparent;
color: var(--text-primary);
font-size: 13px;
cursor: pointer;
}
.new-project-dir-row:hover {
background: var(--menu-item-hover);
}
.new-project-dir-up {
color: var(--text-muted);
}
.new-project-dir-empty {
padding: 8px 10px;
color: var(--text-muted);
font-size: 12px;
}
.new-project-trust-note {
padding: 8px 10px;
border: 1px solid var(--status-warn-border);
border-radius: var(--radius-md, 8px);
color: var(--text-primary);
font-size: 12.5px;
margin: 0 0 var(--space-2);
}
.new-project-error {
color: var(--status-error-fg);
font-size: 12.5px;
margin: 0 0 var(--space-2);
white-space: pre-wrap;
}
.project-panel {
display: flex;
flex-direction: column;
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: var(--project-panel-width);
z-index: 58;
border-left: 1px solid var(--divider);
background:
linear-gradient(180deg, rgba(13, 11, 15, 0.74), rgba(13, 11, 15, 0.64)),
radial-gradient(circle at 15% 0%, var(--project-12), transparent 300px);
backdrop-filter: blur(22px) saturate(130%);
-webkit-backdrop-filter: blur(22px) saturate(130%);
box-shadow: -24px 0 64px rgba(0, 0, 0, 0.34);
transform: translateX(18px);
opacity: 0;
pointer-events: none;
transition: transform 180ms ease, opacity 180ms ease;
}
.project-panel.open {
transform: translateX(0);
opacity: 1;
pointer-events: auto;
}
.project-panel[hidden] {
display: none;
}
.project-panel-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 10px 14px;
border-bottom: 1px solid var(--project-18);
}
.project-panel-title {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 1 auto;
min-width: 0;
}
.project-panel-close {
flex: 0 0 auto;
}
.project-panel-close {
border: 1px solid var(--accent-08);
background: var(--accent-04);
color: var(--text-secondary);
border-radius: var(--radius-md, 8px);
width: 28px;
height: 28px;
font-size: 16px;
line-height: 1;
cursor: pointer;
}
.project-panel-close:hover {
color: var(--text-primary);
border-color: var(--accent);
}
.project-panel-body {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
.project-panel-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: var(--sidebar-width);
z-index: 55;
cursor: default;
background: rgba(0, 0, 0, 0.18);
backdrop-filter: blur(1.5px);
-webkit-backdrop-filter: blur(1.5px);
opacity: 0;
pointer-events: none;
transition: opacity 180ms ease;
}
.project-panel-backdrop.open {
opacity: 1;
pointer-events: auto;
}
.project-panel-backdrop[hidden] {
display: none;
}
.chat-instance-panel {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
/* Own the (now flow) composer + any absolute children so they can never
escape into the surrounding panel/app the way the main chat's absolute
overlay header/composer assume a positioned `.page`. */
position: relative;
}
.chat-instance-panel[hidden] {
display: none;
}
/* Lean status-only header: the project title + close live in the
.project-panel-bar above, so the instance only needs its live status. */
.chat-panel-statusbar {
display: flex;
justify-content: flex-end;
align-items: center;
flex: 0 0 auto;
padding: 8px 14px 0;
}
.chat-instance-panel .chat-messages {
flex: 1;
overflow-y: auto;
min-height: 0;
/* No absolute overlay HEADER here: drop the main chat's phantom 56px
top reserve; the composer reserve matches the shared dock contract. */
padding: 8px 14px;
padding-bottom: var(--chat-input-reserve, 108px);
}
/* Shared dock contract (v6.71.0): the panel composer docks exactly like the
main chat one — the base .chat-input-area rule supplies position:absolute
(anchored to the position:relative panel), the SAME bottom fade gradient,
and the pointer-events pass-through; no second fade layer. */
.chat-instance-panel .chat-input-area {
padding: 24px 14px 12px;
padding-bottom: max(12px, env(safe-area-inset-bottom, 0px));
}
/* Project threads hide the GLOBAL controls (evolution/consciousness/restart/
panic/budget belong to the one agent, not to a project thread). */
.chat-instance-panel .chat-header-actions,
.chat-instance-panel .chat-budget-pill {
display: none;
}
@media (max-width: 900px) {
/* Mobile/narrow: the project thread overlays the content area. */
.project-panel {
position: fixed;
inset: 0;
width: 100%;
z-index: 60;
border-left: none;
transform: translateY(14px);
}
.project-panel-backdrop {
left: 0;
}
.project-panel.open {
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.project-panel,
.project-panel-backdrop,
.chat-scroll-bottom-btn {
transition: none;
}
}
/* Review lanes (6.2) + agent accounts (D30).
Borders read the theme's --divider like every other panel surface. They used
to name --border, which this dark-only :root never declares, so all three
rules silently took their literal grey fallback instead of the token. */
/* === design-system:migrated-begin (harness accounts + reviewer slots + Dashboard Updates tab) ===
Down to the matching end marker: docs/DESIGN.md tokens only, guarded by
tests/test_web_typography_static.py. The rest of this file is a later pass. */
/* Subsection rhythm: a group is its head (heading + that group's own Add
action, docked right — docs/DESIGN.md "List editors") followed by its rows,
one space token apart inside the group; the section's own flex gap keeps
the groups apart, so a heading always sits nearer its rows than its
neighbour — structure, not negative margins. */
/* ONE service banner for the Agents tab: the single place a daemon/runtime
problem is explained, so no row has to carry "(not in discovery)". */
.agents-service-banner {
display: flex;
align-items: baseline;
padding: 10px 12px;
border: 1px solid var(--ui-card-border);
border-radius: var(--radius);
background: var(--ui-card-bg-soft);
}
/* Accounts are grouped by FAMILY, one card each (owner: "все акки клод кода
должны быть эквивалентны", "в каждой секции добавить кнопку"). The card
header carries the family name, its aggregate status and its OWN Add
button — the affordance used to hang off one privileged row. */
.agent-family-list { display: flex; flex-direction: column; gap: 12px; }
.agent-family-card {
border: 1px solid var(--ui-card-border);
border-radius: var(--radius);
background: var(--ui-card-bg);
padding: 14px;
display: flex;
flex-direction: column;
gap: 10px;
}
.agent-family-head {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.agent-family-id { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; min-width: 0; }
.agent-family-id h4 {
margin: 0;
font-size: var(--type-section);
line-height: var(--line-title);
font-weight: 600;
color: var(--text-primary);
}
/* Harness identity is one shared mark + visible-label primitive. Geometry
stays in harness_presentation.js; CSS owns only layout, inherited ink and
long-label behavior. The mark never steals width from the readable name. */
.harness-identity {
display: inline-flex;
align-items: center;
gap: var(--space-1);
min-width: 0;
max-width: 100%;
vertical-align: middle;
}
.harness-identity-mark {
inline-size: 1em;
block-size: 1em;
flex: 0 0 1em;
color: currentColor;
fill: currentColor;
}
.harness-identity-label {
min-width: 0;
overflow-wrap: anywhere;
}
.available-subagent-route-identity-wrap {
display: flex;
align-items: center;
min-width: 0;
color: var(--text-meta);
font-size: var(--type-meta);
line-height: var(--line-meta);
}
.agent-family-rows { display: flex; flex-direction: column; gap: 8px; }
/* Account rows are purpose-built (identity left, actions right), not the
settings key/value grid — .settings-custom-secret-row's 0.65fr/1.35fr
columns inverted the proportions and its align-items:end sank the text.
Two lines: the account and its status on top, muted metadata below, actions
docked right across both (docs/DESIGN.md "Account group / row anatomy"). No
row is privileged: the default CLI login uses this exact layout. */
.harness-account-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
grid-template-areas: "main actions" "meta actions";
align-items: center;
column-gap: 12px;
row-gap: 2px;
padding: 10px 12px;
border: 1px solid var(--divider);
border-radius: 8px;
background: var(--ui-card-bg-soft);
}
.harness-account-main {
grid-area: main;
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: baseline;
min-width: 0;
}
.harness-account-meta {
grid-area: meta;
font-size: var(--type-meta);
line-height: var(--line-meta);
min-width: 0;
}
.harness-account-actions { grid-area: actions; display: flex; gap: 6px; flex-wrap: wrap; }
/* Row anatomy: name = the one primary thing (docs/DESIGN.md). */
.harness-account-main strong {
font-size: var(--type-body);
line-height: var(--line-body);
font-weight: 600;
color: var(--text-primary);
}
/* Status inside a family card or an account row is a meta-sized dot + text, on
the tab's own scale — the global .ui-status chrome size belongs to surfaces
that have not migrated yet. */
.agent-family-id .ui-status,
.harness-account-main .ui-status {
display: inline-flex;
align-items: center;
font-size: var(--type-meta);
line-height: var(--line-meta);
}
.agent-family-id .ui-status[data-tone]::before,
.harness-account-main .ui-status[data-tone]::before {
content: "";
display: inline-block;
flex: 0 0 auto;
width: 6px;
height: 6px;
margin-right: 6px;
border-radius: 999px;
background: currentColor;
}
/* Narrow viewports: the actions column would squeeze the account name to a
couple of characters, so the row becomes one column and the buttons drop
under the metadata they act on. */
@media (max-width: 980px) {
.harness-account-row {
grid-template-columns: minmax(0, 1fr);
grid-template-areas: "main" "meta" "actions";
row-gap: 8px;
}
.agent-family-head { align-items: flex-start; }
}
/* Classification chip: neutral pair, never inherited ink (docs/DESIGN.md).
Also reused by Chat's live executor chip, which keeps its own 10px sizing. */
.harness-chip {
display: inline-block;
padding: 1px 8px;
border-radius: 999px;
border: 1px solid var(--divider);
background: var(--status-neutral-bg);
color: var(--status-neutral-fg);
font-size: var(--type-meta);
line-height: var(--line-meta);
letter-spacing: 0.04em;
}
.log-executor-chip {
width: fit-content;
max-width: 100%;
}
/* A spent window is EMPHASIS, not de-emphasis. The row used to be dimmed to
0.62 opacity, which greyed out the very sentence that reports the limit and
made its still-clickable buttons read as disabled (docs/DESIGN.md: "a control
rendered at secondary ink reads as disabled"). A warn tint says the same
thing without hiding it. */
.harness-exhausted { background: var(--status-warn-bg); }
/* The login card frames itself: .panel-card is an onboarding.css class the
settings pages never load, so leaning on it rendered a bare, frameless
card (finding #4). Same ui-card tokens as every other panel surface. */
.harness-login-card {
margin-top: 8px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
padding: 16px 18px;
border: 1px solid var(--ui-card-border);
border-radius: var(--radius);
background: var(--ui-card-bg);
}
.harness-login-card h4 {
margin: 0;
font-size: var(--type-body);
line-height: var(--line-title);
font-weight: 600;
color: var(--text-primary);
}
.harness-login-card p { margin: 0; color: var(--text-meta); font-size: var(--type-body); line-height: var(--line-body); }
.harness-device-code p { margin: 4px 0; }
/* Device code: display size is the point; nearest token, not a one-off 22px. */
.harness-code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: var(--type-page);
letter-spacing: 0.18em;
padding: 8px 12px;
border: 1px dashed var(--divider);
border-radius: 8px;
display: inline-block;
user-select: all;
}
.harness-attach-command {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: var(--type-meta);
padding: 8px 10px;
border-radius: 8px;
background: var(--surface-control);
overflow-x: auto;
user-select: all;
white-space: pre-wrap;
word-break: break-all;
}
/* Link-first login card (no-terminal UX): the sign-in link is the prominent
action; copy stays an explicit secondary affordance. */
.harness-signin-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.harness-signin-actions a.btn { text-decoration: none; }
.harness-code-entry { display: flex; flex-direction: column; gap: 6px; }
.harness-code-entry label { font-size: var(--type-meta); line-height: var(--line-meta); color: var(--text-meta); }
.harness-code-entry-row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.harness-code-entry-row input { width: auto; min-width: min(220px, 100%); flex: 1 1 220px; }
/* The demoted attach fallback: collapsed by default, visually secondary. */
.harness-advanced {
align-self: stretch;
border-top: 1px solid var(--divider);
padding-top: 8px;
}
.harness-advanced summary {
cursor: pointer;
font-size: var(--type-meta);
line-height: var(--line-meta);
color: var(--text-meta);
}
.harness-advanced[open] summary { margin-bottom: 6px; }
.harness-advanced p { margin: 4px 0; }
/* --- Dashboard -> Updates tab (migrated surface, DESIGN.md section 8) ---
One card: status line (dot + headline), meta hint, chips, a single
action row (status left, one primary action right), and a collapsed
Recovery section holding the destructive/rare controls. */
.updates-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.updates-card {
backdrop-filter: blur(12px);
padding: var(--space-5);
}
.updates-card-title {
font-size: var(--type-section);
line-height: var(--line-title);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--space-3);
}
.updates-status {
display: flex;
align-items: center;
gap: var(--space-2);
min-width: 0;
}
.updates-status-dot {
width: 6px;
height: 6px;
border-radius: 999px;
background: currentColor;
flex: none;
}
.updates-status-dot[data-tone="neutral"] { color: var(--status-neutral-fg); }
.updates-status-dot[data-tone="ok"] { color: var(--status-ok-fg); }
.updates-status-dot[data-tone="warn"] { color: var(--status-warn-fg); }
.updates-status-dot[data-tone="error"] { color: var(--status-error-fg); }
.updates-status-dot[data-tone="disabled"] { color: var(--text-disabled); }
.updates-headline {
font-size: var(--type-body);
line-height: var(--line-body);
font-weight: 600;
color: var(--text-primary);
overflow-wrap: break-word;
min-width: 0;
}
.updates-hint {
font-size: var(--type-meta);
line-height: var(--line-meta);
color: var(--text-meta);
margin-top: var(--space-1);
}
.updates-meta {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
margin-top: var(--space-3);
}
/* Local chip/dot primitives on purpose: the shared .ui-chip alias group
carries page palettes (skills/marketplace) and .ui-status recolors the
sentence itself — neither covers a label:value fact chip with a dot-status
sibling (checklist item 30 one-line statement). DESIGN section 2: label at
meta ink, the value is the primary thing. */
.updates-chip {
font-size: var(--type-meta);
line-height: var(--line-meta);
color: var(--text-primary);
background: var(--ui-card-bg-soft);
border: 1px solid var(--surface-border-soft);
border-radius: var(--radius-sm);
padding: 2px var(--space-2);
}
.updates-chip strong {
color: var(--text-meta);
font-weight: 500;
}
.updates-chip-link {
cursor: pointer;
font-family: inherit;
text-align: left;
}
.updates-chip-link:hover {
background: var(--bg-hover);
}
.updates-action-row {
margin-top: var(--space-4);
}
.updates-action-note {
font-size: var(--type-meta);
color: var(--text-meta);
}
.updates-recovery {
margin-top: var(--space-5);
border-top: 1px solid var(--divider);
padding-top: var(--space-3);
}
.updates-recovery summary {
font-size: var(--type-body);
font-weight: 600;
color: var(--text-primary);
cursor: pointer;
}
.updates-recovery-copy {
font-size: var(--type-meta);
line-height: var(--line-meta);
color: var(--text-meta);
margin: var(--space-2) 0 var(--space-3);
}
.updates-recovery-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
margin-bottom: var(--space-3);
}
.updates-branch {
font-size: var(--type-meta);
color: var(--text-meta);
margin-bottom: var(--space-2);
}
.updates-subhead {
font-size: var(--type-body);
font-weight: 600;
color: var(--text-primary);
margin: var(--space-4) 0 var(--space-2);
}
.updates-restore-list {
display: flex;
flex-direction: column;
gap: var(--space-1);
max-height: var(--updates-list-max-height);
overflow-y: auto;
}
.updates-restore-row {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--type-meta);
line-height: var(--line-meta);
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-sm);
min-width: 0;
}
.updates-restore-row:hover {
background: var(--bg-hover);
}
.updates-restore-label {
color: var(--text-primary);
font-weight: 600;
flex: none;
}
.updates-restore-date {
color: var(--text-meta);
flex: none;
}
.updates-restore-msg {
color: var(--text-meta);
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.updates-restore-row .btn {
margin-left: auto;
flex: none;
}
.updates-empty {
font-size: var(--type-meta);
color: var(--text-meta);
padding: var(--space-2);
}
.updates-empty-error {
color: var(--status-error-fg);
}
/* The update letter: Ouroboros's own short paragraph about the update this
panel offers, kept after it lands as "What changed in this version". A fact
block, so it is separated from the action row the way Recovery is — a rule,
not a card — and it contributes no control of its own. */
.updates-letter {
display: flex;
flex-direction: column;
gap: var(--space-1);
margin-top: var(--space-4);
border-top: 1px solid var(--divider);
padding-top: var(--space-3);
}
.updates-letter-head {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: var(--space-2);
}
.updates-letter-label {
margin: 0;
font-size: var(--type-body);
line-height: var(--line-title);
font-weight: 600;
color: var(--text-primary);
}
/* Provenance sits at meta ink beside the label and WRAPS at narrow widths
instead of truncating: the versions it names are the whole point of it. */
.updates-letter-meta {
font-size: var(--type-meta);
line-height: var(--line-meta);
color: var(--text-meta);
overflow-wrap: anywhere;
min-width: 0;
}
/* Two note tones, each complete: a stale letter is a warning (the text still
holds, it was written for another range), a failed rewrite is an error. */
.updates-letter-note {
font-size: var(--type-meta);
line-height: var(--line-meta);
color: var(--status-warn-fg);
}
.updates-letter-note-failed {
font-size: var(--type-meta);
line-height: var(--line-meta);
color: var(--status-error-fg);
}
.updates-letter-body {
font-size: var(--type-body);
line-height: var(--line-body);
color: var(--text-primary);
margin-top: var(--space-2);
}
/* A markdown heading inside the letter is a subsection label, never a page
title — same clamp the chat bubble applies to rendered markdown, declared
after the global .md-h* block it narrows. */
.updates-letter-body .md-h1,
.updates-letter-body .md-h2,
.updates-letter-body .md-h3 {
font-size: var(--type-body);
line-height: var(--line-body);
color: var(--text-primary);
font-weight: 600;
margin: var(--space-2) 0 var(--space-1);
}
/* === design-system:migrated-end === */
/* === design-system:migrated-begin (chat: executor chip, structured delivery, quiz card) === */
/* Phase 6: per-bubble/subagent executor chip (owner directive: a small chip
like Claudexor's, never a promotional badge). Reuses .harness-chip, sized on
the shared meta token like the rest of the migrated chat surface. */
.chat-live-executor-chip {
font-size: var(--type-meta);
padding: 0 6px;
margin-right: 6px;
vertical-align: middle;
opacity: 0.85;
}
/* Structured chat delivery surfaces. All controls inherit the dark application
palette so media stays legible in both Main and Project chat instances. */
.chat-bubble { position: relative; }
.chat-gallery-grid,
.chat-file-grid,
.chat-links-list {
display: grid;
gap: 8px;
min-width: 0;
/* Structural markup, not owner prose: the bubble's `pre-wrap` would turn
every newline of the card template into a rendered blank line. Captions
below opt back in, because those line breaks are the owner's. */
white-space: normal;
}
.chat-bubble.is-multiple .chat-gallery-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
/* The links bubble's own .message is a container (title + list), so the same
structural rule applies to it directly — at the specificity of the
`.chat-bubble .message` pre-wrap default it has to beat. */
.chat-bubble .message.chat-links-message {
white-space: normal;
}
.chat-group-title,
.chat-links-title {
margin-bottom: 8px;
color: var(--text-meta);
font-size: var(--type-meta);
font-weight: 600;
}
.chat-gallery-item {
position: relative;
min-width: 0;
margin: 0;
overflow: hidden;
border: 1px solid var(--ui-card-border);
border-radius: var(--radius-sm);
background: var(--ui-card-bg);
}
.chat-gallery-item .chat-photo {
display: block;
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: 0;
}
.chat-gallery-item figcaption,
.chat-file-caption {
padding: 7px 9px;
color: var(--text-meta);
font-size: var(--type-meta);
line-height: var(--line-meta);
overflow-wrap: anywhere;
/* Owner-authored text inside a structural container: keep its line breaks. */
white-space: pre-wrap;
}
.chat-photo-actions {
position: absolute;
top: 7px;
right: 7px;
z-index: 2;
}
.chat-photo-actions summary {
display: grid;
width: 30px;
height: 28px;
place-items: center;
border: 1px solid var(--ui-card-border);
border-radius: var(--radius-sm);
background: var(--ui-modal-bg);
color: var(--text-primary);
cursor: pointer;
list-style: none;
}
.chat-photo-actions summary::-webkit-details-marker { display: none; }
.chat-file-card {
display: grid;
grid-template-columns: 48px minmax(0, 1fr) auto;
gap: 10px;
align-items: center;
width: 100%;
min-width: 230px;
padding: 9px;
border: 1px solid var(--ui-card-border);
border-radius: var(--radius);
background: var(--ui-card-bg);
color: var(--text-primary);
font: inherit;
text-align: left;
cursor: pointer;
}
.chat-file-card:hover:not(:disabled) {
border-color: var(--focus-accent-border);
background: var(--bg-hover);
}
.chat-file-card:disabled { cursor: default; opacity: 0.58; }
.chat-file-thumb,
.chat-file-glyph {
width: 48px;
height: 48px;
border-radius: var(--radius-sm);
}
.chat-file-thumb { object-fit: cover; }
.chat-file-glyph {
display: grid;
place-items: center;
background: var(--accent-12);
color: var(--accent-light);
font-size: var(--type-page);
}
.chat-file-copy { min-width: 0; }
.chat-file-name,
.chat-file-meta { display: block; }
.chat-file-name {
overflow: hidden;
color: var(--text-primary);
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-file-meta {
margin-top: 2px;
color: var(--text-meta);
font-size: var(--type-meta);
}
.chat-file-more { color: var(--text-meta); }
.chat-file-dialog {
width: min(420px, calc(100vw - 32px));
padding: 0;
border: 1px solid var(--ui-card-border);
border-radius: var(--radius-lg);
background: var(--ui-modal-bg);
color: var(--text-primary);
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.34);
}
.chat-file-dialog::backdrop { background: rgba(0, 0, 0, 0.64); }
.chat-file-dialog-panel { padding: 18px; }
.chat-file-dialog-title {
margin-bottom: 16px;
overflow-wrap: anywhere;
font-weight: 600;
}
.chat-file-dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.chat-file-dialog-actions button,
.chat-media-controls button,
.chat-media-rate-menu {
min-height: 32px;
border: 1px solid var(--ui-card-border);
border-radius: var(--radius-sm);
background: var(--surface-control);
color: var(--text-primary);
cursor: pointer;
}
.chat-file-dialog-actions button { padding: 6px 12px; }
.chat-file-dialog-actions button:disabled { cursor: default; opacity: 0.45; }
.chat-media-player {
overflow: hidden;
min-width: 250px;
/* Structural markup (see .chat-gallery-grid): no phantom lines between the
stage and the control row. */
white-space: normal;
border: 1px solid var(--ui-card-border);
border-radius: var(--radius);
background: var(--ui-modal-bg);
}
.chat-media-stage video {
display: block;
width: 100%;
max-height: 430px;
background: var(--bg-primary);
cursor: pointer;
}
.chat-media-stage audio { display: none; }
.chat-media-title {
padding: 10px 12px 0;
overflow: hidden;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-media-player.is-audio { padding-top: 6px; }
.chat-media-controls {
display: flex;
gap: 6px;
align-items: center;
padding: 8px;
}
.chat-media-controls button {
flex: 0 0 auto;
min-width: 32px;
padding: 4px 7px;
}
.chat-media-progress {
--media-progress: 0%;
flex: 1 1 120px;
min-width: 70px;
height: 4px;
appearance: none;
border-radius: var(--pill-radius);
background: linear-gradient(to right, var(--accent-light) var(--media-progress), var(--surface-border) var(--media-progress));
cursor: pointer;
}
.chat-media-progress::-webkit-slider-thumb {
width: 13px;
height: 13px;
appearance: none;
border-radius: 50%;
background: var(--accent-light);
}
.chat-media-time {
flex: 0 0 auto;
color: var(--text-meta);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: var(--type-meta);
white-space: nowrap;
}
.chat-media-rate-menu { width: 58px; padding: 4px; }
.chat-link-button {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
min-width: 220px;
padding: 10px 12px;
border: 1px solid var(--focus-accent-border);
border-radius: var(--radius-sm);
background: var(--accent-08);
color: var(--accent-light);
font-weight: 600;
text-decoration: none;
}
.chat-link-button:hover { background: var(--accent-18); }
/* Owner quiz card (design system: born on tokens; DESIGN.md "Quiz card"). */
.chat-quiz-card {
display: flex;
flex-direction: column;
gap: var(--space-2, 8px);
min-width: 260px;
max-width: 560px;
padding: var(--space-3, 12px);
border: 1px solid var(--ui-card-border);
border-radius: var(--radius);
background: var(--ui-card-bg);
}
.chat-quiz-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2, 8px);
}
.chat-quiz-chip {
padding: 1px 8px;
border-radius: var(--pill-radius);
font-size: var(--type-meta);
color: var(--status-neutral-fg);
background: var(--status-neutral-bg);
}
.chat-quiz-status {
display: inline-flex;
align-items: center;
gap: var(--space-2, 8px);
font-size: var(--type-meta);
color: var(--text-meta);
}
.chat-quiz-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--status-neutral-fg);
}
.chat-quiz-card[data-state="answered"] .chat-quiz-dot { background: var(--status-ok-fg); }
.chat-quiz-card[data-state="expired_terminal"] .chat-quiz-dot,
.chat-quiz-card[data-state="superseded"] .chat-quiz-dot { background: var(--text-disabled); }
.chat-quiz-question {
font-size: var(--type-body);
line-height: var(--line-body, 1.5);
font-weight: 600;
color: var(--text-primary);
}
.chat-quiz-stake {
font-size: var(--type-meta);
line-height: var(--line-meta, 1.35);
color: var(--text-meta);
}
.chat-quiz-options {
display: flex;
flex-direction: column;
gap: var(--space-1, 4px);
}
.chat-quiz-option {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--space-1, 4px);
padding: var(--space-2, 8px) var(--space-3, 12px);
border: 1px solid var(--ui-card-border);
border-radius: var(--radius-sm);
background: var(--ui-card-bg-soft);
color: var(--text-primary);
font-size: var(--type-body);
text-align: left;
cursor: pointer;
}
.chat-quiz-option:hover:not(:disabled) {
border-color: var(--focus-accent-border);
background: var(--accent-08);
}
.chat-quiz-option:disabled { color: var(--text-disabled); cursor: default; }
.chat-quiz-option.chosen {
border-color: var(--status-ok-fg);
color: var(--status-ok-fg);
background: var(--status-ok-bg);
}
.chat-quiz-option-detail {
font-size: var(--type-meta);
line-height: var(--line-meta, 1.35);
color: var(--text-meta);
}
.chat-quiz-option:disabled .chat-quiz-option-detail { color: var(--text-disabled); }
.chat-quiz-option-recommended {
margin-left: var(--space-2, 8px);
font-size: var(--type-meta);
font-weight: 600;
color: var(--status-ok-fg);
}
/* Free answer: the owner's own words when no offered option fits. */
.chat-quiz-comment-box {
display: flex;
flex-direction: column;
gap: var(--space-1, 4px);
}
.chat-quiz-comment {
width: 100%;
box-sizing: border-box;
padding: var(--space-2, 8px);
border: 1px solid var(--ui-card-border);
border-radius: var(--radius-sm);
background: var(--ui-card-bg-soft);
color: var(--text-primary);
font-family: inherit;
font-size: var(--type-body);
line-height: var(--line-body, 1.5);
resize: vertical;
}
.chat-quiz-comment::placeholder { color: var(--text-meta); }
.chat-quiz-send {
align-self: flex-start;
padding: var(--space-1, 4px) var(--space-3, 12px);
border: 1px solid var(--ui-card-border);
border-radius: var(--radius-sm);
background: var(--ui-card-bg-soft);
color: var(--text-primary);
font-size: var(--type-meta);
cursor: pointer;
}
.chat-quiz-send:hover:not(:disabled) {
border-color: var(--focus-accent-border);
background: var(--accent-08);
}
.chat-quiz-send:disabled { color: var(--text-disabled); cursor: default; }
.chat-quiz-option:focus-visible,
.chat-quiz-comment:focus-visible,
.chat-quiz-send:focus-visible,
.chat-quiz-more:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: 2px;
}
/* The settled record of what the owner answered: a second primary line. */
.chat-quiz-answer {
font-size: var(--type-body);
line-height: var(--line-body, 1.5);
color: var(--text-primary);
}
.chat-quiz-more {
align-self: flex-start;
padding: var(--space-1, 4px) 0;
border: none;
background: none;
color: var(--accent-light);
font-size: var(--type-meta);
cursor: pointer;
}
.chat-quiz-assumption {
padding-top: var(--space-1, 4px);
border-top: 1px solid var(--ui-card-border);
font-size: var(--type-meta);
line-height: var(--line-meta, 1.35);
color: var(--text-meta);
}
/* D12: always-visible compact copy icon in the bubble's bottom-right corner. */
.chat-message-copy {
position: absolute;
right: 8px;
bottom: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
padding: 0;
border: 1px solid transparent;
border-radius: var(--radius-xs);
background: transparent;
color: var(--text-meta);
font-size: var(--type-meta);
line-height: 1;
cursor: pointer;
opacity: 0.45;
transition: opacity 0.15s ease, background 0.15s ease;
}
.chat-message-copy:hover,
.chat-message-copy:focus-visible { opacity: 1; }
.chat-message-copy:hover { background: var(--surface-control); }
.chat-message-copy:focus-visible {
outline: 2px solid var(--focus-accent-border);
outline-offset: 2px;
}
/* Reserve a gutter so the timestamp never sits under the always-visible icon
(the button spans ~30px from the bubble's right edge). */
.chat-bubble.has-copy .msg-time { margin-right: 28px; }
.chat-copy-fallback {
position: fixed;
left: -10000px;
top: 0;
width: 1px;
height: 1px;
opacity: 0;
}
@media (max-width: 620px) {
.chat-bubble.is-multiple .chat-gallery-grid { grid-template-columns: 1fr; }
.chat-media-controls { flex-wrap: wrap; }
.chat-media-progress { order: 2; flex-basis: 100%; }
.chat-media-rate-menu { display: none; }
}
/* === design-system:migrated-end === */
/* One line, never a paragraph: the pointer names the card and ellipsizes, so
the status bar cannot grow into the reading area (a 400-character plan_task
verdict used to wrap it to twelve lines). The 180px basis is the bar's
original row policy: a default desktop panel keeps one row while the
status pill is short; a longer pill, a narrower panel or a phone wraps the
bar to a second row, never a third. The auto margin keeps the pointer left
of the status pill. */
.project-work-pointer {
flex: 1 1 180px;
min-width: 0;
max-width: 40ch;
margin-right: auto;
justify-content: flex-start;
text-align: left;
overflow: hidden;
}
.project-work-pointer-label {
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.project-work-coverage { font-size: var(--type-meta); color: var(--text-meta); }
.chat-panel-statusbar { flex-wrap: wrap; gap: var(--space-2); }
.chat-live-executor-chip { max-width: 100%; white-space: normal; }
.chat-live-meta-text, .chat-live-executor-chip .harness-identity-label { overflow-wrap: anywhere; }
.chat-bubble .message:not(:has(> *)),
.chat-bubble .message > :is(p:not(:has(img, video)), ul, ol, blockquote, .md-h1, .md-h2, .md-h3) {
max-inline-size: 72ch;
}
/* track-migration:begin — Baseline recipes only for consumers not yet adopted
in this parallel track. Aggregate removes this block after owner adapters land. */
.skills-card-menu-dialog:not(.ui-popup) {
/* Anchored non-modal popover aligned to the trigger's right edge. */
position: absolute;
top: calc(100% + 6px);
right: 0;
left: auto;
margin: 0;
min-width: 160px;
padding: 6px;
border-radius: var(--radius);
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(18, 20, 26, 0.96);
color: var(--text-primary);
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.32);
z-index: 50;
}
.skills-card-menu-dialog:not(.ui-popup) .skills-menu-item {
width: 100%;
border: 0;
background: transparent;
color: var(--text-secondary);
border-radius: var(--radius-sm);
padding: 9px 10px;
text-align: left;
cursor: pointer;
font: inherit;
font-size: 12px;
}
.skills-card-menu-dialog:not(.ui-popup) .skills-menu-item:disabled,
.skills-card-menu-dialog:not(.ui-popup) .skills-menu-item.is-disabled {
opacity: 0.55;
cursor: not-allowed;
}
.skills-card-menu-dialog:not(.ui-popup) .skills-menu-item:disabled:hover,
.skills-card-menu-dialog:not(.ui-popup) .skills-menu-item.is-disabled:hover {
background: transparent;
color: var(--text-secondary);
}
.skills-card-menu-dialog:not(.ui-popup) .skills-menu-item:hover {
background: var(--menu-item-hover);
color: var(--text-primary);
}
.chat-photo-menu:not(.ui-popup) {
position: absolute;
top: 34px;
right: 0;
display: grid;
width: max-content;
min-width: 160px;
overflow: hidden;
border: 1px solid var(--ui-card-border);
border-radius: var(--radius-sm);
background: var(--ui-modal-bg);
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.34);
}
.chat-photo-menu:not(.ui-popup) button {
padding: 8px 10px;
border: 0;
border-bottom: 1px solid var(--divider);
background: transparent;
color: var(--text-primary);
font: inherit;
text-align: left;
cursor: pointer;
}
.chat-photo-menu:not(.ui-popup) button:last-child { border-bottom: 0; }
.chat-photo-menu:not(.ui-popup) button:hover { background: var(--menu-item-hover); }
.form-field input:not(.ui-control):not(.ui-checkbox), .form-field select:not(.ui-control):not(.ui-checkbox),
.reviewer-slot-controls input:not(.ui-control):not(.ui-checkbox), .reviewer-slot-controls select:not(.ui-control):not(.ui-checkbox) {
padding: 8px 12px;
border-radius: var(--radius-sm);
border: 1px solid var(--divider);
background: var(--bg-secondary);
color: var(--text-primary);
font-size: 14px;
font-family: inherit;
outline: none;
width: 320px;
max-width: 100%;
transition: border-color 0.2s, box-shadow 0.2s;
}
.form-field input:not(.ui-control):not(.ui-checkbox):focus {
border-color: var(--focus-accent-border);
box-shadow: 0 0 0 3px var(--focus-accent-ring);
}
.marketplace-search:not(.ui-control) {
height: 36px;
min-height: 36px;
padding: 6px 10px;
border: 1px solid rgba(250, 250, 250, 0.1);
background: rgba(255, 255, 255, 0.03);
color: inherit;
border-radius: 6px;
font: inherit;
font-size: 13px;
}
.marketplace-search:not(.ui-control):focus {
outline: none;
border-color: var(--focus-accent-border);
box-shadow: 0 0 0 3px var(--focus-accent-ring);
background: rgba(255, 255, 255, 0.05);
}
.skills-presence-runtime select:not(.ui-control),
.skills-presence-runtime input:not(.ui-control) {
min-height: 32px;
border: 1px solid rgba(250, 250, 250, 0.12);
border-radius: var(--radius-sm);
background: rgba(12, 14, 18, 0.8);
color: var(--text-primary);
padding: 5px 8px;
}
.skills-presence-runtime input:not(.ui-control) {
width: 110px;
}
.widget-kanban-move select:not(.ui-control) {
min-width: 0;
max-width: 112px;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 5px;
background: rgba(13, 11, 15, 0.82);
color: var(--text-primary);
font: inherit;
padding: 3px 5px;
}
/* track-migration:end */