Files
odysseus/static/css/02-compare.css
T
Léo a4715b77a5 refactor(css): name the fragments by area instead of by number
style-part-01..08 were arbitrary 6,000-line cuts, so the names said nothing
and a reader had no way to guess which file held a rule.

The original stylesheet turns out to be roughly area-grouped already, so
cutting where the content changes rather than every 6,000 lines produces
boundaries worth naming. Classifying every top-level rule by selector prefix
and smoothing over a 40-rule window gives 17 stable runs: agent-chat, compare,
memory, documents, admin-settings, skills, gallery, cookbook, tasks,
image-editor, email, notes, calendar, research.

The numeric prefix stays because load order is load-bearing, and it also
disambiguates the areas that appear twice: the original interleaves, and
merging non-adjacent blocks of the same area would reorder the cascade.

The names describe where a file sits, not a claim that it holds every rule for
that area or only rules for it. The header in each file says so, because that
is the misreading this naming invites.

Reconstruction was checked against lab's style.css before rewriting: identical
after whitespace normalisation, all 47,528 lines accounted for. The computed
style baseline still reproduces exactly.
2026-09-29 17:50:04 +02:00

1806 lines
68 KiB
CSS

/* 02-compare - part of the former static/style.css.
*
* The stylesheet is split by area, but the split is mechanical: rules were
* moved verbatim and NOT regrouped. The numeric prefix is the load order, and
* that order is what preserves the cascade. A rule sits in the file its
* original position fell into, so an area's rules are not all in its file and
* a file is not only that area's rules.
*
* Do not reorder the <link> tags in static/index.html, and do not move a rule
* between files: with 1,151 redeclarations and 1,886 !important in this
* cascade, either can change which declaration wins.
*
* static/index.html and the PRECACHE list in static/sw.js must agree on this
* order. tests/test_static_stylesheet_manifest.py and
* tests/test_css_computed_style_snapshot.py fail if that drifts.
*/
.harmony-generate-btn:hover { background: color-mix(in srgb, var(--red) 11%, transparent); }
.harmony-preview {
display: flex; height: 20px; border-radius: 6px; overflow: hidden;
margin-top: 8px; border: 1px solid var(--border);
}
.harmony-preview:empty { display: none; border: none; }
.harmony-preview span { flex: 1; }
#theme-reset-btn {
margin-top: 6px; width: 100%; padding: 6px; border: 1px solid var(--border);
border-radius: 6px; background: var(--bg); color: var(--fg); cursor: pointer;
font-size: 12px; font-family: inherit; opacity: 0.7; transition: opacity 0.15s;
}
#theme-reset-btn:hover { opacity: 1; }
.theme-adv-toggle {
margin-top: 10px; margin-bottom: 10px;
padding: 6px 8px; cursor: pointer;
font-size: 11px; color: var(--red); opacity: 0.8;
border: 1px solid var(--border); border-radius: 6px;
transition: opacity 0.15s, background 0.15s;
user-select: none;
}
.theme-adv-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 4%, transparent); }
.theme-adv-toggle .theme-adv-arrow {
display: inline-block; transition: transform 0.2s; font-size: 10px;
margin-right: 4px;
}
.theme-adv-toggle.open .theme-adv-arrow { transform: rotate(90deg); }
.theme-adv-section { margin-top: 8px; margin-bottom: 10px; }
.theme-adv-section.hidden { display: none; }
.theme-adv-group { margin-bottom: 8px; }
.theme-adv-group-label {
font-size: 10px; color: var(--fg); opacity: 0.5;
text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px;
}
.theme-adv-clear-btn {
margin-top: 6px; width: 100%; padding: 5px; border: 1px solid var(--border);
border-radius: 6px; background: transparent; color: var(--fg); opacity: 0.5;
cursor: pointer; font-size: 12px; font-family: inherit; transition: opacity 0.15s;
}
.theme-adv-clear-btn:hover { opacity: 1; }
/* Customization is an editor, so keep its sections visually related while
preserving the existing controls and their order. */
#theme-tab-customize {
display: flex;
flex-direction: column;
gap: 10px;
}
#theme-tab-customize > .admin-card,
#theme-tab-customize > .theme-adv-toggle,
#theme-tab-customize > .theme-adv-section {
margin-top: 0;
margin-bottom: 0;
}
#theme-tab-customize > .admin-card {
padding: 12px;
}
#theme-tab-customize > .admin-card > h2 {
display: flex;
align-items: center;
min-height: 20px;
margin: 0 0 10px;
padding-bottom: 7px;
border-bottom: 1px solid color-mix(in srgb, var(--border) 65%, transparent);
font-size: 13px;
}
#theme-tab-customize .theme-custom {
gap: 6px 14px;
}
#theme-tab-customize .color-row {
min-height: 30px;
padding: 3px 6px;
box-sizing: border-box;
border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
background: color-mix(in srgb, var(--fg) 2%, transparent);
}
#theme-tab-customize .color-row label {
font-size: 12px;
}
#theme-tab-customize .theme-adv-toggle {
flex-shrink: 0;
margin: 0;
background: color-mix(in srgb, var(--fg) 2%, transparent);
}
#theme-tab-customize .theme-adv-section {
padding: 10px 12px 2px;
border: 1px solid color-mix(in srgb, var(--border) 75%, transparent);
border-radius: 7px;
background: color-mix(in srgb, var(--panel) 55%, transparent);
}
#theme-tab-customize .theme-adv-group {
margin: 0;
padding: 0 0 9px;
border-bottom: 1px solid color-mix(in srgb, var(--border) 48%, transparent);
}
#theme-tab-customize .theme-adv-group + .theme-adv-group {
padding-top: 9px;
}
#theme-tab-customize .theme-adv-group:last-of-type {
border-bottom: 0;
}
#theme-tab-customize .theme-adv-group-label {
margin-bottom: 6px;
color: var(--red);
opacity: 0.8;
font-weight: 600;
}
#theme-tab-customize .theme-fd-row,
#theme-tab-customize .theme-harmony-row {
gap: 12px;
margin-bottom: 10px;
}
#theme-tab-customize .theme-fd-row:last-child,
#theme-tab-customize .theme-harmony-row:last-child {
margin-bottom: 0;
}
#theme-tab-customize .theme-fd-group {
min-width: 0;
}
#theme-tab-customize .theme-fd-select {
min-height: 30px;
}
#theme-tab-customize #theme-harmony-card .theme-fd-label {
font-size: 11px;
}
#theme-tab-customize .theme-save-row,
#theme-tab-customize .theme-io-row {
gap: 8px;
}
#theme-tab-customize #theme-reset-btn {
margin-top: 8px;
}
@media (max-width: 560px) {
#theme-tab-customize .theme-custom {
grid-template-columns: 1fr;
}
#theme-tab-customize .theme-fd-row,
#theme-tab-customize .theme-harmony-row {
flex-direction: column;
align-items: stretch;
}
#theme-tab-customize .theme-fd-group,
#theme-tab-customize .theme-harmony-row .theme-fd-group {
width: 100%;
}
}
/* Mobile: bottom sheet modals — slide up from bottom */
@media (max-width: 768px) {
.modal {
align-items: flex-end;
background: rgba(0,0,0,0.4);
pointer-events: auto;
/* Anchor the bottom sheet to the DYNAMIC viewport. The base overlay is
position:fixed; height:100% = the layout viewport, whose bottom sits
UNDER Firefox Android's bottom URL bar — so flex-end parked the sheet
(and its footer / Start button) beneath the bar. dvh excludes the bar
so the footer lands above it. Extra safe-area pad for iOS home bar. */
height: 100dvh;
padding-bottom: env(safe-area-inset-bottom, 0px);
}
/* Confirm dialog stays centered, not a bottom sheet */
#styled-confirm-overlay {
align-items: center;
}
.styled-confirm-box {
border-radius: 12px !important;
border: 1px solid var(--border) !important;
animation: modal-enter 0.25s ease-out both !important;
max-height: none !important;
}
.styled-confirm-box.modal-closing {
animation: modal-exit 0.18s ease-in both !important;
}
.styled-confirm-box::before {
display: none !important;
}
.styled-confirm-box .close-btn {
display: none !important;
}
#theme-popup {
width: 100% !important;
height: 65vh !important;
max-height: 65vh !important;
top: auto !important; left: 0 !important; right: 0 !important; bottom: 0 !important;
position: fixed !important;
border-radius: 14px 14px 0 0;
border: none;
border-top: 1px solid var(--border);
padding: 6px 12px 12px;
animation: sheet-enter 0.2s ease-out forwards;
overflow-y: hidden !important;
}
#theme-popup .theme-tab-panel {
touch-action: pan-y;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
#theme-popup.sheet-ready {
animation: none;
}
#theme-popup.modal-closing {
animation: sheet-exit 0.15s ease-in both !important;
}
.modal-content,
.memory-modal-content,
.settings-modal-content,
#compare-model-overlay .modal-content {
width: 100% !important;
/* 85dvh leaves comfortable headroom above the sheet so the user can
still see the chat behind it and the drag handle has breathing
room. Was 65vh (too short, content clipped) → 90vh (too tall, top
hugged the status bar). */
max-height: 85dvh !important;
max-height: 85vh !important; /* fallback for browsers without dvh */
height: auto !important;
border-radius: 14px 14px 0 0;
border: none;
border-top: 1px solid var(--border);
padding-top: 6px !important;
/* Clip children to the rounded top corners — otherwise the sticky
modal-header's var(--panel) background paints a darker
rectangle past the radius and the corners look square again. */
overflow: hidden;
animation: sheet-enter 0.2s ease-out forwards;
}
/* Keep the tall tool drawers on a shared mobile height ceiling. The
final mobile drawer rule below makes this content-sized so shorter
tools remain compact while tall ones leave a visible top gap. */
#cookbook-modal .modal-content,
#tasks-modal .modal-content,
#calendar-modal .modal-content,
#memory-modal .memory-modal-content,
#settings-modal .settings-modal-content,
#email-lib-modal .modal-content,
#doclib-modal .doclib-modal-content {
max-height: 90vh !important;
max-height: 90dvh !important;
height: auto !important;
/* Reserve the iOS home-indicator / bottom-bar strip so an expanded
skill card's footer (Delete / Edit / Run) isn't hidden under it.
dvh already excludes the URL bar; this handles the safe area. */
padding-bottom: env(safe-area-inset-bottom, 0px) !important;
}
/* Grab handle pill on the non-standard content classes too. Memory's
desktop rule defines a radial-glow ::before later in the file —
these body-prefixed selectors win the specificity battle on mobile. */
body .memory-modal-content::before,
body .settings-modal-content::before {
content: '';
display: block;
position: static;
inset: auto;
width: 36px; height: 4px;
background: var(--fg);
opacity: 0.25;
border-radius: 2px;
margin: 0 auto 4px;
flex-shrink: 0;
padding: 0;
border-top: 10px solid transparent;
border-bottom: 6px solid transparent;
background-clip: padding-box;
animation: none;
}
.memory-modal-content .close-btn,
.memory-modal-content .modal-close,
.settings-modal-content .close-btn,
.settings-modal-content .modal-close {
display: none !important;
}
.memory-modal-content,
.settings-modal-content {
touch-action: pan-y;
overscroll-behavior: contain;
}
/* Library modals use the same bottom-sheet sizing as the other tools.
The shared mobile drawer rule below provides the top breathing room. */
#cookbook-modal .modal-content,
#calendar-modal .modal-content,
#email-lib-modal .modal-content,
#doclib-modal .modal-content,
#gallery-modal .modal-content,
#tasks-modal .modal-content {
/* vh-first as fallback for very old browsers, then dvh wins so
the modal shrinks/grows with the mobile URL bar. Wrong order
previously made the expanded chat preview extend below the
visible viewport on Chrome/Safari mobile (URL bar covered the
action buttons row). */
max-height: 90vh !important;
max-height: 90dvh !important;
height: auto !important;
}
/* Anchor those modals to the top so the full height is usable */
#cookbook-modal,
#calendar-modal,
#email-lib-modal,
#doclib-modal,
#gallery-modal,
#tasks-modal {
padding-top: 0 !important;
align-items: stretch !important;
}
/* Deep Research already gets the swipe grab-handle pill from the
shared `.modal-content::before` rule (the pane carries that class).
The previous header-level pill was redundant and rendered as a
SECOND pill stacked above the real one — removed. */
/* The inner body must flex to fill the new full height, and the
tasks list inside it must scroll independently — otherwise the
list crops at whatever pre-mobile height the desktop layout had. */
#tasks-modal .modal-content {
display: flex !important;
flex-direction: column !important;
}
#tasks-modal .modal-body {
flex: 1 1 auto !important;
min-height: 0 !important;
}
/* Memory/Skills: the body lacks flex:1, so on the fixed-height mobile
sheet (overflow:hidden) it grew past the viewport and clipped the
bottom of the skills list + its row action buttons with no way to
scroll there. Bound the body so the inner list scrolls internally. */
#memory-modal .memory-modal-content {
display: flex !important;
flex-direction: column !important;
}
/* flex-basis MUST be 0 (not auto) — matches the working #doclib-modal
.modal-body. With basis:auto, Firefox (incl. mobile) sizes the body
to its content (~half height) instead of filling, while Chromium
fills it regardless — which is exactly why the skill expand worked
on desktop/Chromium but stuck at ~50% on Firefox mobile. */
#memory-modal .memory-modal-body {
flex: 1 1 0 !important;
min-height: 0 !important;
overflow: hidden !important;
}
/* Same basis:0 fix down the rest of the skills chain so every layer
fills instead of sizing to content under Firefox. */
#memory-modal .memory-tab-panel[data-memory-panel="skills"] {
flex: 1 1 0 !important;
min-height: 0 !important;
}
#memory-modal .memory-tab-panel[data-memory-panel="skills"] > .admin-card {
flex: 1 1 0 !important;
min-height: 0 !important;
}
/* The skills modal carries an extra toolbar row (search/sort/select +
bulk bar) the doc/email libraries don't, so the shared 82dvh preview
min-height overflowed here and pushed the expanded footer (Delete /
Edit / Run) off the bottom of the screen. Let flexbox size it from
the resolved card height instead, so the footer always pins inside
the visible area. */
#memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview {
min-height: 0 !important;
}
/* Once enter animation finishes, clear it so inline transform works for dragging */
.modal-content.sheet-ready {
animation: none;
}
.modal-content.modal-closing {
animation: sheet-exit 0.15s ease-in both !important;
}
/* Grab handle — large touch target, visible pill */
#theme-popup::before,
.modal-content::before {
content: '';
display: block;
width: 36px; height: 4px;
background: var(--fg); opacity: 0.25;
border-radius: 2px;
margin: 0 auto 4px;
flex-shrink: 0;
padding: 0;
/* Expand touch target without changing visual size */
border-top: 10px solid transparent;
border-bottom: 6px solid transparent;
background-clip: padding-box;
}
/* Hide close X on mobile — swipe down to dismiss */
.modal-content .close-btn,
.modal-content .modal-close,
#theme-popup .close-btn {
display: none !important;
}
/* Hide the auto-injected minimize (_) button on mobile — the dock
chip already represents the minimized state, and the swipe-down
gesture is the canonical minimize action. */
.modal-minimize-btn,
.minimize-btn,
[data-minimize] {
display: none !important;
}
/* Lock modals to vertical touch only, prevent horizontal dragging */
.modal-content {
touch-action: pan-y;
overscroll-behavior: contain;
}
.modal-content .cookbook-body,
.modal-content .modal-body {
touch-action: pan-y;
overscroll-behavior: contain;
}
.modal-header {
cursor: default;
touch-action: none;
}
@keyframes sheet-enter {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
@keyframes sheet-exit {
from { transform: translateY(0); }
to { transform: translateY(100%); }
}
}
/* ── Model A/B Comparison ── */
/* -- Extracted inline-style classes -- */
.cmp-header-action-btn {
background: none; border: 1px solid var(--border); color: var(--fg);
cursor: pointer; padding: 3px 10px; font-size: 11px; font-weight: 600;
opacity: 0.7; transition: all 0.15s; line-height: 1; border-radius: 4px;
display: inline-flex; align-items: center; font-family: inherit;
}
.cmp-form-control {
padding: 8px; background: var(--bg); color: var(--fg);
border: 1px solid var(--border); border-radius: 6px; font-size: 0.85em;
}
.cmp-btn-secondary {
background: transparent; color: var(--fg); border: 1px solid var(--border);
border-radius: 6px; cursor: pointer;
}
.cmp-btn-primary {
background: var(--fg); color: var(--bg); border: none;
border-radius: 6px; cursor: pointer; font-weight: 600;
transition: filter 0.12s, background 0.12s, color 0.12s;
}
/* Override global button:hover (which switches bg to var(--panel) =
very dark) — keep the bright primary look and just brighten slightly. */
.cmp-btn-primary:hover:not(:disabled) {
background: var(--fg); color: var(--bg);
filter: brightness(1.1);
}
.cmp-btn-primary:active:not(:disabled) { filter: brightness(0.95); }
.cmp-btn-secondary:hover:not(:disabled) {
background: color-mix(in srgb, var(--fg) 8%, transparent);
border-color: var(--fg); color: var(--fg);
}
.cmp-model-row {
display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
flex-wrap: wrap;
padding: 2px 0;
transition: margin-left 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.15s, border-color 0.15s;
}
.cmp-model-row-duplicate {
margin-left: -4px;
margin-right: -4px;
padding: 4px;
border-radius: 6px;
background: color-mix(in srgb, var(--color-error) 8%, transparent);
border: 1px solid color-mix(in srgb, var(--color-error) 20%, transparent);
}
.cmp-model-row-swap-target {
margin-left: -4px;
margin-right: -4px;
padding: 4px;
border-radius: 6px;
background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 42%, transparent);
}
.cmp-model-row-swap-target .cmp-row-label {
color: var(--accent, var(--red));
opacity: 1;
}
.cmp-row-label {
color: var(--fg); font-size: 0.82em; font-weight: 600; min-width: 20px;
opacity: 0.4; text-align: center; flex-shrink: 0;
}
.cmp-model-meta {
order: 3;
flex: 1 0 calc(100% - 28px);
margin-left: 28px;
margin-top: -4px;
display: flex;
align-items: center;
gap: 5px;
min-width: 0;
font-size: 10px;
line-height: 1.2;
}
.cmp-model-meta-chip,
.cmp-model-meta-warning,
.cmp-model-meta-muted {
display: inline-flex;
align-items: center;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-radius: 999px;
padding: 1px 6px;
letter-spacing: 0;
}
.cmp-model-meta-chip {
color: color-mix(in srgb, var(--fg) 60%, transparent);
background: color-mix(in srgb, var(--fg) 5%, transparent);
border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
}
.cmp-model-meta-warning {
color: var(--color-error);
background: color-mix(in srgb, var(--color-error) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--color-error) 24%, transparent);
font-weight: 600;
}
.cmp-model-meta-muted {
color: color-mix(in srgb, var(--fg) 36%, transparent);
}
.cmp-rm-btn {
background: none; border: none; color: var(--accent, var(--red)); cursor: pointer;
min-width: 20px; font-size: 16px; font-weight: 600; opacity: 0.3;
transition: all 0.15s; padding: 0; line-height: 1; text-align: center;
position: relative; top: -2px;
order: 2;
margin-left: auto;
align-self: center;
}
.cmp-prov-select {
flex: 0 0 auto; width: 120px; font-size: 0.8em;
}
/* Eval-prompts picker — only shown during compare; absolute top-right
inside .chat-input-top, matching .model-picker-wrap's slot. */
.chat-input-top > .cmp-eval-wrap {
position: relative;
grid-column: 2;
grid-row: 1;
top: -2px; right: 0;
z-index: 2;
}
.cmp-eval-btn {
display: inline-flex; align-items: center; gap: 4px;
padding: 4px 8px;
background: transparent;
border: 1px solid var(--border);
border-radius: 6px;
color: var(--fg);
font-size: 11px; font-weight: 500;
font-family: inherit;
cursor: pointer; opacity: 0.75;
transition: opacity 0.15s, border-color 0.15s;
}
.cmp-eval-btn:hover { opacity: 1; border-color: var(--fg); }
.cmp-eval-clear {
display: inline-flex; align-items: center; gap: 4px; margin-left: 4px;
border: 0; background: transparent; color: var(--fg); opacity: .55;
padding: 3px 5px; font: inherit; font-size: 10px; cursor: pointer;
}
.cmp-eval-clear:hover { opacity: 1; }
.cmp-eval-caret { opacity: 0.7; transform: rotate(180deg); }
.cmp-eval-menu {
position: absolute; bottom: calc(100% + 4px); right: 0;
min-width: 220px; max-width: 280px;
max-height: 360px; overflow-y: auto;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 6px;
box-shadow: 0 -4px 16px rgba(0,0,0,0.3);
padding: 4px;
z-index: 1000;
}
.cmp-eval-menu.hidden { display: none; }
.cmp-eval-group-label {
font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px;
opacity: 0.45; font-weight: 600;
padding: 6px 8px 2px;
}
.cmp-eval-item {
display: block; width: 100%;
text-align: left;
padding: 5px 8px;
background: none; border: none;
color: var(--fg); font-size: 11px;
font-family: inherit;
border-radius: 4px;
cursor: pointer;
}
.cmp-eval-item:hover {
background: color-mix(in srgb, var(--fg) 8%, transparent);
}
.cmp-eval-empty {
padding: 10px; text-align: center;
font-size: 11px; opacity: 0.5;
}
/* Tick on items that ship a known expected answer */
.cmp-eval-item-tick {
float: right;
margin-left: 6px;
font-size: 10px;
color: var(--color-success, #4caf50);
opacity: 0.8;
}
/* Expected-answer panel — floats as its own little window above the
chat-input-bar. Distinct surface, padded, drop-shadow, slightly
lifted so it reads as a separate UI element, not part of the input. */
.chat-input-bar:has(.cmp-eval-expected) { position: relative; }
.cmp-eval-expected {
position: absolute;
bottom: calc(100% + 8px);
right: 0;
display: inline-flex; align-items: center; gap: 8px;
padding: 8px 12px;
font-size: 11px;
background: var(--panel);
border: 1px solid color-mix(in srgb, var(--color-success, #4caf50) 50%, transparent);
border-radius: 8px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2);
color: var(--fg);
width: fit-content;
z-index: 5;
pointer-events: auto;
}
.cmp-eval-expected.hidden { display: none; }
.cmp-eval-expected-label {
opacity: 0.6;
text-transform: uppercase;
font-size: 9px;
letter-spacing: 0.5px;
font-weight: 600;
}
.cmp-eval-expected-value {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 11px;
}
.cmp-eval-expected-close {
background: none; border: none; color: var(--fg);
font-size: 14px; line-height: 1; padding: 0 0 0 4px;
opacity: 0.5; cursor: pointer; font-family: inherit;
}
.cmp-eval-expected-close:hover { opacity: 1; }
/* Auto-grade badge — stamped on a pane after stream completes when an
eval prompt with a known expected answer was used. */
.pane-grade-badge {
display: inline-flex; align-items: center; justify-content: center;
width: 18px; height: 18px;
margin: 0 4px;
font-size: 12px; font-weight: 700;
border-radius: 50%;
border: 1px solid currentColor;
flex-shrink: 0;
}
.pane-grade-badge.pass {
color: var(--color-success, #4caf50);
background: color-mix(in srgb, var(--color-success, #4caf50) 12%, transparent);
}
.pane-grade-badge.fail {
color: var(--color-error, #e55);
background: color-mix(in srgb, var(--color-error, #e55) 12%, transparent);
}
/* Compare probe overlay */
.compare-probe-overlay {
position: fixed; inset: 0; z-index: 300;
background: rgba(0,0,0,0.5); display: flex;
align-items: center; justify-content: center;
padding: 12px;
box-sizing: border-box;
}
.compare-probe-card {
width: min(460px, calc(100% - 16px));
max-height: min(620px, calc(100dvh - 24px));
box-sizing: border-box;
background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px;
display: flex; flex-direction: column; align-items: stretch; gap: 10px;
min-width: 280px; box-shadow: 0 8px 28px rgba(0,0,0,0.28);
overflow: hidden;
font-family: inherit;
}
.compare-probe-title {
font-size: 13px; font-weight: 400; color: var(--fg);
}
.compare-probe-title-label { font-weight: 600; }
.compare-probe-title-status { font-weight: 400; }
.compare-probe-list {
display: grid; grid-template-columns: 1fr 1fr; gap: 6px; min-width: 0; width: 100%;
overflow-y: auto;
}
.compare-probe-row {
display: flex; align-items: center; gap: 6px; padding: 6px 10px;
border-radius: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent);
font-size: 12px; transition: background 0.2s; overflow: hidden;
}
.compare-probe-row.fail {
background: color-mix(in srgb, var(--color-error, #f44) 8%, transparent);
}
.compare-probe-spinner {
width: 24px; text-align: center; font-size: 10px; flex-shrink: 0;
letter-spacing: -1px; opacity: 0.6;
}
.compare-probe-spinner.ok { color: var(--color-success); animation: none; }
.compare-probe-spinner.fail { color: var(--color-error, #f44); animation: none; }
.compare-probe-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.compare-probe-status { font-size: 11px; opacity: 0.5; flex-shrink: 0; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.compare-probe-status.ok { color: var(--color-success); opacity: 1; }
.compare-probe-status.fail { color: var(--color-error, #f44); opacity: 1; }
.compare-probe-action-btn {
display: inline-flex; align-items: center; gap: 5px;
min-height: 25px; padding: 3px 8px; background: transparent; color: var(--fg); border: 1px solid var(--border);
border-radius: 5px; cursor: pointer; font-size: 10px; font-family: inherit;
opacity: 0.78; transition: opacity 0.15s, border-color 0.15s, background 0.15s; white-space: nowrap; flex-shrink: 0;
}
.compare-probe-action-btn svg,
.compare-probe-footer-btn svg { width: 12px; height: 12px; flex: 0 0 auto; color: var(--accent, var(--red)); }
.compare-probe-action-btn:hover { opacity: 1; border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--fg) 5%, transparent); }
.compare-probe-feedback { display: flex; flex-direction: column; gap: 5px; }
.compare-probe-feedback:empty { display: none; }
.compare-probe-detail {
display: flex; align-items: center; gap: 6px; min-width: 0;
padding: 6px 8px; font-size: 10px;
background: color-mix(in srgb, var(--color-error, #f44) 6%, transparent);
border-left: 2px solid color-mix(in srgb, var(--color-error, #f44) 72%, transparent);
border-radius: 4px;
}
.compare-probe-detail-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 14px;
width: 14px;
height: 14px;
color: var(--accent, var(--red));
font-family: inherit;
font-size: 11px;
font-weight: 700;
line-height: 1;
}
.compare-probe-detail-message { flex: 1 1 auto; min-width: 0; line-height: 1.35; color: color-mix(in srgb, var(--fg) 72%, transparent); overflow-wrap: anywhere; }
.compare-probe-footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; margin-top: 2px; }
.compare-probe-footer-btn {
display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 5px 10px;
font-family: inherit; font-size: 11px; font-weight: 600; line-height: 1;
}
.compare-probe-start-anyway,
.compare-probe-start-anyway:hover:not(:disabled) {
background: var(--accent, var(--red));
border: 1px solid var(--accent, var(--red));
color: var(--bg);
}
.compare-probe-start-anyway svg { color: currentColor; }
.compare-probe-start-anyway span { font-weight: 700; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes pane-shake {
0%, 100% { transform: translateX(0); }
15% { transform: translateX(-3px) rotate(-0.5deg); }
30% { transform: translateX(3px) rotate(0.5deg); }
45% { transform: translateX(-2px); }
60% { transform: translateX(2px); }
75% { transform: translateX(-1px); }
}
.chat-container.compare-active {
display: flex;
flex-direction: column;
padding: 0;
overflow: hidden;
animation: compare-enter 0.3s ease-out;
}
@keyframes compare-enter {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.chat-container.compare-active .chat-input-bar {
margin-bottom: 0;
}
.chat-container.compare-active #model-picker-wrap {
display: none !important;
}
.compare-grid {
display: grid;
gap: 4px;
flex: 1 1 0;
min-height: 0;
overflow: hidden;
position: relative;
}
.compare-mobile-tabs { display: none; }
.compare-grid[data-cols="2"] { grid-template-columns: 1fr 1fr; }
.compare-grid[data-cols="3"] { grid-template-columns: 1fr 1fr 1fr; }
.compare-grid[data-cols="4"] { grid-template-columns: repeat(4, 1fr); }
.compare-grid[data-cols="5"] { grid-template-columns: repeat(4, 1fr); }
.compare-grid[data-cols="6"] { grid-template-columns: repeat(4, 1fr); }
.compare-grid[data-cols="7"] { grid-template-columns: repeat(4, 1fr); }
.compare-grid[data-cols="8"] { grid-template-columns: repeat(4, 1fr); }
.compare-grid { grid-auto-rows: 1fr; }
.compare-shuffle-notice {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) scale(0.94);
z-index: 30;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
border: 1px solid color-mix(in srgb, var(--red) 28%, var(--border));
border-radius: 8px;
background: color-mix(in srgb, var(--panel, var(--bg)) 92%, transparent);
color: var(--fg);
box-shadow: 0 10px 30px color-mix(in srgb, #000 24%, transparent);
font-size: 12px;
font-weight: 700;
line-height: 1;
opacity: 0;
pointer-events: none;
transition: opacity 0.14s ease, transform 0.14s ease;
}
.compare-shuffle-notice.show {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
.compare-shuffle-notice-icon {
display: inline-flex;
color: var(--red);
animation: compare-shuffle-dice 0.56s ease both;
}
.compare-shuffle-notice-icon svg {
display: block;
width: 18px;
height: 18px;
}
@keyframes compare-shuffle-dice {
0% { transform: rotate(-18deg) scale(0.9); }
55% { transform: rotate(380deg) scale(1.08); }
100% { transform: rotate(360deg) scale(1); }
}
/* Sequential waterfall layout — stacked rows, staggered left, flush right */
.compare-grid.sequential-layout {
display: flex !important;
flex-direction: column !important;
grid-template-columns: none !important;
gap: 4px;
overflow-y: auto;
}
.compare-grid.sequential-layout .compare-pane {
flex-shrink: 0;
min-height: 200px;
transition: margin-left 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease;
}
/* Herringbone diagonal cut on left side of sequential pane headers */
.compare-grid.sequential-layout .compare-pane .pane-header {
clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%);
padding-left: 26px;
}
.compare-pane {
display: flex;
flex-direction: column;
border: 1px solid var(--border);
border-radius: 6px;
overflow: hidden;
min-height: 0;
min-width: 0;
position: relative;
transition: border-color 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease;
}
.compare-pane::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
z-index: 2;
pointer-events: none;
background: transparent;
transform-origin: left center;
}
.compare-pane.is-streaming {
border-color: color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border));
box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent, var(--red)) 12%, transparent);
}
.compare-pane.is-streaming::before {
background: linear-gradient(90deg, transparent 0%, var(--accent, var(--red)) 35%, var(--red) 50%, var(--accent, var(--red)) 65%, transparent 100%);
animation: compare-pane-progress 1.25s linear infinite;
}
.compare-pane.is-awaiting-input {
border-color: color-mix(in srgb, var(--red) 42%, var(--border));
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--red) 12%, transparent);
}
.compare-pane.is-awaiting-input::before {
background: var(--red);
animation: none;
}
.compare-pane.is-done {
border-color: color-mix(in srgb, var(--green, #50fa7b) 20%, var(--border));
}
.compare-pane.is-failed {
border-color: color-mix(in srgb, var(--color-error) 42%, var(--border));
}
.compare-pane.is-failed::before {
background: var(--color-error);
}
@keyframes compare-pane-progress {
from { transform: translateX(-100%); }
to { transform: translateX(100%); }
}
.compare-pane .pane-header {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
padding: 4px 10px;
background: color-mix(in srgb, var(--fg) 4%, transparent);
border-bottom: 1px solid var(--border);
font-size: 0.82em;
font-weight: 600;
color: var(--fg);
transition: background 0.4s;
flex-shrink: 0;
overflow: visible;
min-width: 0;
flex-wrap: nowrap;
}
.pane-header-row { display: flex; align-items: center; width: 100%; min-width: 0; }
.pane-header-primary { min-height: 24px; }
.pane-header-secondary { min-height: 20px; }
.pane-primary-actions, .pane-stats { display: flex; align-items: center; gap: 4px; min-width: 0; }
.pane-primary-actions { margin-left: auto; flex: 0 0 auto; }
.pane-stats { flex: 1 1 auto; overflow: hidden; }
.compare-header-bar button:hover,
.compare-header-bar button:focus-visible {
background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important;
border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)) !important;
color: var(--fg) !important;
opacity: 1 !important;
outline: none;
}
.compare-header-bar button:active {
background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent) !important;
transform: scale(0.96);
}
.compare-more-wrap { position: relative; display: inline-flex; }
.compare-more-btn {
display: inline-flex; align-items: center; justify-content: center;
width: 28px; height: 24px; padding: 0 !important;
border: 1px solid transparent; border-radius: 5px;
background: none; color: var(--fg); cursor: pointer;
opacity: 0.5; transition: opacity .15s, background .15s;
}
.compare-header-bar .compare-close-btn {
width: 24px; height: 24px; min-width: 24px; padding: 0;
border-radius: 999px; font-size: 0; display: inline-flex;
align-items: center; justify-content: center;
}
.compare-header-bar .compare-close-btn:hover,
.compare-header-bar .compare-close-btn:focus-visible {
background: color-mix(in srgb, var(--fg) 10%, transparent) !important;
border-color: transparent !important;
color: var(--fg) !important;
opacity: 1 !important;
outline: none;
}
.compare-header-bar .compare-more-btn:hover,
.compare-header-bar .compare-more-btn:focus-visible,
.compare-header-bar .compare-more-btn[aria-expanded="true"] {
background: color-mix(in srgb, var(--fg) 10%, transparent) !important;
border-color: transparent !important;
color: var(--fg) !important;
opacity: 1 !important;
outline: none;
}
.compare-more-menu {
position: absolute; top: calc(100% + 5px); right: 0; z-index: 10002;
display: none; flex-direction: column; gap: 3px; min-width: 132px;
padding: 5px; border: 1px solid var(--border); border-radius: 8px;
background: var(--panel, var(--bg)); box-shadow: 0 8px 24px rgba(0,0,0,.3);
}
.compare-more-menu.is-open { display: flex; }
.compare-more-menu > .compare-export-wrap,
.compare-more-menu > button {
width: 100%; display: flex; align-items: center;
}
.compare-more-menu #compare-export-btn,
.compare-more-menu #compare-shuffle-btn,
.compare-more-menu #compare-check-btn,
.compare-more-menu #compare-add-menu-btn {
width: 100%; min-height: 32px; height: 32px;
box-sizing: border-box; justify-content: flex-start; gap: 7px;
padding: 7px 9px !important;
border: 1px solid transparent !important; border-radius: 6px;
background: transparent !important; color: var(--fg) !important;
font-size: 11px; font-weight: 500; opacity: .72;
transition: background .12s ease, opacity .12s ease, color .12s ease;
}
.compare-more-menu #compare-export-btn:hover,
.compare-more-menu #compare-shuffle-btn:hover,
.compare-more-menu #compare-check-btn:hover,
.compare-more-menu #compare-add-menu-btn:hover,
.compare-more-menu #compare-export-btn:focus-visible,
.compare-more-menu #compare-shuffle-btn:focus-visible,
.compare-more-menu #compare-check-btn:focus-visible,
.compare-more-menu #compare-add-menu-btn:focus-visible {
opacity: 1; background: color-mix(in srgb, var(--red) 10%, transparent) !important;
border-color: transparent !important; color: var(--fg) !important; outline: none;
}
.compare-more-menu #compare-export-btn svg,
.compare-more-menu #compare-shuffle-btn svg,
.compare-more-menu #compare-check-btn svg,
.compare-more-menu #compare-add-menu-btn svg { flex: 0 0 auto; opacity: .65; }
.compare-more-menu #compare-export-btn span,
.compare-more-menu #compare-shuffle-btn span,
.compare-more-menu #compare-check-btn span,
.compare-more-menu #compare-add-menu-btn span {
font-size: 11px !important; margin-left: 0 !important;
color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important;
}
.compare-more-menu #compare-export-btn,
.compare-more-menu #compare-shuffle-btn,
.compare-more-menu #compare-check-btn,
.compare-more-menu #compare-add-menu-btn {
color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important;
}
.compare-more-menu #compare-export-btn svg,
.compare-more-menu #compare-shuffle-btn svg,
.compare-more-menu #compare-check-btn svg,
.compare-more-menu #compare-add-menu-btn svg { color: var(--fg) !important; opacity: .65 !important; }
.compare-export-item:hover,
.compare-export-item:focus-visible {
background: color-mix(in srgb, var(--red) 10%, transparent) !important;
border-color: transparent !important; outline: none;
}
.compare-export-item { display: flex; align-items: center; gap: 8px; }
.compare-export-item > svg { flex: 0 0 auto; opacity: .65; }
.compare-export-menu .compare-export-item,
.compare-export-menu .compare-export-item > span {
color: var(--fg) !important;
opacity: 1 !important;
}
.compare-add-flap {
position: absolute; z-index: 20; right: -8px; top: 50%;
width: 34px; min-width: 34px; height: 30px; box-sizing: border-box;
padding: 0 !important;
display: inline-flex; flex-direction: row; align-items: center; justify-content: center;
gap: 0; border: 1px solid var(--border);
border-radius: 0 7px 7px 0; background: var(--panel, var(--bg));
color: var(--fg); cursor: pointer; opacity: .7;
box-shadow: 3px 2px 9px color-mix(in srgb, var(--fg) 12%, transparent);
transform: translate(0, -50%); transition: width .18s ease, opacity .15s, background .15s, box-shadow .15s;
}
.compare-add-flap:hover, .compare-add-flap:focus-visible {
width: 70px; opacity: 1; background: color-mix(in srgb, var(--fg) 8%, var(--panel));
box-shadow: 4px 3px 12px color-mix(in srgb, var(--fg) 18%, transparent);
outline: none;
}
.compare-add-flap span {
max-width: 0; overflow: hidden; white-space: nowrap; margin-left: 0 !important;
font-size: 10px; opacity: 0; transition: max-width .18s ease, margin-left .18s ease, opacity .12s;
}
.compare-add-flap:hover span, .compare-add-flap:focus-visible span {
max-width: 32px; margin-left: 3px !important; opacity: 1;
}
.compare-add-flap svg { flex-shrink: 0; position: relative; left: -3px; }
@media (max-width: 768px) {
.compare-add-flap {
top: 8px; right: 8px; width: auto; min-width: 32px; height: 32px;
padding: 5px 8px !important; flex-direction: row; transform: none;
border-right: 1px solid var(--border); border-radius: 6px;
}
.compare-add-flap span { opacity: 1; }
}
.pane-actions {
display: flex; gap: 4px; align-items: center; margin-left: auto; flex-shrink: 0;
}
.compare-pane-footer {
font-size: 0.72em; opacity: 0.4; padding: 4px 10px;
border-top: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
text-align: center; flex-shrink: 0;
}
/* Per-pane vote button. Sits at the bottom of each compare pane so
the action lives right under the response it judges. */
.pane-vote-footer {
padding: 6px 8px;
border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
background: color-mix(in srgb, var(--fg) 3%, transparent);
flex-shrink: 0;
}
.pane-vote-btn {
width: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
background: var(--bg);
color: var(--fg);
border: 1px solid var(--border);
border-radius: 6px;
padding: 6px 10px;
font-family: inherit;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: background 0.15s, border-color 0.15s, opacity 0.15s;
}
.pane-vote-btn:hover:not(:disabled) {
background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg));
border-color: var(--accent, var(--fg));
}
.pane-vote-btn:disabled { cursor: not-allowed; }
.pane-vote-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.pane-action-btn {
background: none; border: none; color: var(--fg); cursor: pointer;
opacity: 0.3; padding: 2px; border-radius: 4px;
display: flex; align-items: center; transition: all 0.15s;
}
.pane-action-btn:hover { opacity: 0.8; background: color-mix(in srgb, var(--fg) 6%, transparent); }
/* Pane title as clickable model-swap button */
.pane-title-btn {
background: none; border: none; cursor: pointer;
font-size: 10px; font-weight: 400; font-family: inherit;
color: var(--fg); padding: 0;
text-align: left; display: flex; align-items: center; gap: 4px;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
transition: opacity 0.15s;
min-width: 0; flex: 1 1 0;
}
.pane-title-btn:hover { opacity: 0.7; }
.pane-title-caret { font-size: 0.6em; opacity: 0.35; flex-shrink: 0; position: relative; top: 2px; }
.pane-title-btn:hover .pane-title-caret { opacity: 0.7; }
.pane-mode-badge {
display: inline-flex;
align-items: center;
gap: 4px;
flex: 0 0 auto;
height: 18px;
padding: 0 6px;
border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
border-radius: 999px;
background: color-mix(in srgb, var(--fg) 3%, var(--bg));
color: color-mix(in srgb, var(--fg) 76%, transparent);
font-size: 9px;
font-weight: 600;
line-height: 1;
letter-spacing: 0;
white-space: nowrap;
}
.pane-mode-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: currentColor;
opacity: 0.75;
flex: 0 0 auto;
}
.pane-mode-detail {
opacity: 0.52;
font-weight: 500;
}
.pane-mode-badge.pane-mode-icon-only {
width: 20px;
padding: 0;
justify-content: center;
}
.pane-mode-settings {
opacity: 0.65;
cursor: pointer;
}
.pane-settings-btn { width: 20px; height: 20px; justify-content: center; }
.pane-settings-btn[aria-expanded="true"] { opacity: 1; background: color-mix(in srgb, var(--fg) 9%, transparent); }
.response-metrics[data-action="settings"] { cursor: pointer; }
.response-thinking-state { color: color-mix(in srgb, var(--fg) 55%, transparent); }
.pane-mode-settings:hover,
.pane-mode-settings:focus-visible,
.pane-mode-settings[aria-expanded="true"] {
opacity: 1;
color: var(--fg);
background: color-mix(in srgb, var(--fg) 9%, var(--bg));
border-color: color-mix(in srgb, var(--accent, var(--fg)) 45%, var(--border));
outline: none;
}
.pane-mode-icon,
.pane-mode-icon svg {
display: block;
width: 12px;
height: 12px;
}
.pane-mode-agent {
color: color-mix(in srgb, var(--fg) 76%, transparent);
background: color-mix(in srgb, var(--fg) 3%, var(--bg));
border-color: color-mix(in srgb, var(--border) 70%, transparent);
}
.pane-mode-search {
color: color-mix(in srgb, var(--accent, var(--red)) 82%, var(--fg));
background: color-mix(in srgb, var(--accent, var(--red)) 10%, var(--bg));
border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, var(--border));
}
.pane-mode-research {
color: color-mix(in srgb, #36c48f 82%, var(--fg));
background: color-mix(in srgb, #36c48f 10%, var(--bg));
border-color: color-mix(in srgb, #36c48f 28%, var(--border));
}
.compare-check-label {
position: relative;
top: -2px;
font-size: 11px;
margin-left: 3px;
}
.compare-check-icon {
position: relative;
top: 1px;
}
#compare-check-btn > .spinner-whirlpool {
position: relative;
top: -2px;
transform: none;
}
.compare-pane .pane-close-btn { opacity: 0.3; }
.compare-pane .pane-close-btn:hover { opacity: 1; color: var(--color-error); }
/* Model swap dropdown under pane title */
.pane-model-dropdown {
position: absolute;
z-index: 1000;
min-width: 220px;
max-height: 300px;
overflow-y: auto;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
padding: 4px;
}
.pane-model-item {
display: block; width: 100%;
box-sizing: border-box;
padding: 8px 10px; font-size: 11px; line-height: 1.3;
text-align: left; background: none; border: none; border-radius: 6px;
color: var(--fg); cursor: pointer; transition: background 0.12s ease;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pane-model-item:hover,
.pane-model-item:focus-visible { background: color-mix(in srgb, var(--red) 10%, transparent); outline: none; }
.pane-model-item.current { color: var(--red); font-weight: 600; }
.pane-timer {
font-size: 10px; font-weight: 400; opacity: 0.45; font-variant-numeric: tabular-nums;
white-space: nowrap; padding-right: 4px;
}
.pane-summary {
display: inline-flex;
align-items: center;
position: relative;
top: 1px;
min-height: 16px;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 5px;
border-radius: 999px;
background: color-mix(in srgb, var(--fg) 4%, transparent);
color: color-mix(in srgb, var(--fg) 58%, transparent);
font-size: 9px;
font-weight: 500;
line-height: 1;
font-variant-numeric: tabular-nums;
cursor: pointer;
transition: max-width .15s ease, white-space .15s ease;
}
.pane-summary:empty { display: none; }
.pane-stats-popup {
position: fixed; z-index: 10040; width: 210px; padding: 6px;
background: var(--panel, var(--bg)); color: var(--fg);
border: 1px solid var(--border); border-radius: 7px;
box-shadow: 0 8px 24px rgba(0,0,0,.3); font-size: 11px;
}
.pane-stats-popup-title { padding: 4px 6px 7px; font-weight: 600; border-bottom: 1px solid var(--border); }
.pane-stats-popup-row { display: flex; justify-content: space-between; gap: 12px; padding: 6px; }
.pane-stats-popup-row span { color: color-mix(in srgb, var(--fg) 55%, transparent); }
.pane-stats-popup-row strong { font-weight: 500; font-variant-numeric: tabular-nums; text-align: right; }
/* Metrics stay on the lower row even in dense comparisons. */
.compare-grid[data-cols="4"] .pane-timer,
.compare-grid[data-cols="5"] .pane-timer,
.compare-grid[data-cols="6"] .pane-timer {
width: auto; order: initial; margin-top: 0; padding-bottom: 0; padding-left: 0;
}
.pane-finish-badge {
font-weight: 600; color: var(--green, #50fa7b);
position: relative;
top: 2px;
}
.compare-pane.winner .pane-header {
background: color-mix(in srgb, var(--green, #50fa7b) 12%, transparent);
border-bottom-color: color-mix(in srgb, var(--green, #50fa7b) 30%, var(--border));
}
.compare-pane.winner .pane-title {
color: var(--green, #50fa7b);
}
.compare-pane.loser .pane-header {
opacity: 0.5;
}
.confetti-piece {
position: fixed;
pointer-events: none;
z-index: 1000000;
}
.compare-pane.expanded { grid-column: 1 / -1; }
.compare-pane .chat-history {
flex: 1 1 0;
min-height: 0;
overflow-y: auto !important;
overflow-x: hidden;
padding: 8px;
display: flex;
flex-direction: column;
}
.compare-pane .chat-history .msg {
flex-shrink: 0;
}
.compare-gen-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
border-radius: 4px;
cursor: pointer;
}
.compare-section {
margin-bottom: 14px;
}
.compare-section:last-child {
margin-bottom: 0;
}
.compare-section-label {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.6px;
opacity: 0.5;
margin-bottom: 5px;
font-weight: 600;
}
/* Active-type/mode readout next to "Type:"/"Mode:" — only needed on mobile
(where the tab/toggle text labels are hidden); hidden on desktop. */
.compare-type-current,
.compare-mode-current { display: none; }
/* Contextual one-liner under the mode toggles describing what you just
changed — empty until the first toggle, then a subtle hint. */
.compare-mode-hint {
font-size: 11px;
opacity: 0.55;
margin-top: 6px;
min-height: 0;
line-height: 1.3;
}
.compare-mode-hint:empty { display: none; }
.compare-type-tabs .compare-mode-tab > svg,
.compare-type-current > svg {
color: var(--accent, var(--red));
}
.compare-mode-tabs {
display: flex;
gap: 4px;
flex-wrap: wrap;
min-width: 0;
}
/* Type tabs match Mode toggles 1:1 (same flex column layout, same metrics) */
.compare-mode-tab {
display: flex; flex-direction: column; align-items: center; justify-content: center;
width: 56px; height: 38px; flex: 1 1 0;
padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px;
cursor: pointer; user-select: none; transition: all 0.15s;
background: none; color: var(--fg); opacity: 0.35;
flex-shrink: 0; gap: 0;
font-family: inherit;
}
.compare-mode-tab > svg {
width: 13px; height: 13px; flex: 0 0 13px;
}
.compare-mode-tab:hover {
opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent);
}
.compare-mode-tab.active {
opacity: 1; border-color: var(--fg);
background: color-mix(in srgb, var(--fg) 10%, transparent);
}
.compare-sources-box {
display: flex; align-items: center; gap: 6px;
padding: 6px 10px; margin-bottom: 8px;
border-radius: 6px; font-size: 0.78em;
background: color-mix(in srgb, var(--red) 8%, transparent);
border: 1px solid color-mix(in srgb, var(--red) 20%, transparent);
color: color-mix(in srgb, var(--fg) 70%, transparent);
cursor: default;
}
.compare-sources-box .sources-label { font-weight: 600; }
/* Compact tool blocks inside compare panes */
.compare-pane .agent-thread-node {
margin: 4px 0; font-size: 0.85em;
}
.compare-pane .agent-thread-cmd {
max-height: 80px; overflow-y: auto;
}
.compare-pane .agent-tool-output pre {
max-height: 120px; overflow-y: auto;
}
.compare-vote-bar {
display: flex; justify-content: center; gap: 8px;
padding: 8px; border-top: 1px solid var(--border);
flex-shrink: 0; flex-wrap: wrap;
}
.compare-vote-bar.hidden { display: none; }
.compare-vote-btn {
padding: 6px 13px; border: 1px solid var(--border); border-radius: 6px;
background: var(--panel); color: var(--fg); cursor: pointer; font-size: 0.8em;
transition: all 0.15s; white-space: nowrap;
}
.compare-score-label {
position: relative;
top: -2px;
display: inline-block;
}
.compare-vote-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); }
.compare-vote-tie { opacity: 0.7; }
.compare-rematch-btn {
display: flex; align-items: center; gap: 6px; margin-left: 8px;
border-color: color-mix(in srgb, var(--accent, var(--red)) 60%, var(--border));
background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent);
color: var(--fg); opacity: 1; font-weight: 600;
}
.compare-rematch-btn:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent); }
/* Preview button accent in pane header */
.pane-preview-btn.active { color: var(--red); }
/* Full-pane iframe for HTML preview */
.compare-pane-iframe {
flex: 1;
width: 100%;
border: none;
border-radius: 0 0 8px 8px;
background: #fff;
}
/* ---- Add-pane "+" button in pane header (last pane only) ---- */
.pane-add-btn { display: none !important; font-size: 16px; font-weight: 600; }
.compare-pane:last-child .pane-add-btn { display: flex !important; }
/* Dropdown for adding a pane */
.add-pane-dropdown {
position: absolute;
right: 0;
z-index: 100;
background: var(--panel, var(--bg));
border: 1px solid var(--border);
border-radius: 10px;
max-height: 300px;
overflow-y: auto;
min-width: 220px;
box-shadow: 0 8px 24px rgba(0,0,0,0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent);
padding: 6px;
backdrop-filter: blur(12px);
animation: dropdown-in 0.15s ease-out;
}
.add-pane-search {
width: 100%;
padding: 6px 8px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--fg);
font-size: 0.85em;
box-sizing: border-box;
margin-bottom: 4px;
position: sticky;
top: 0;
z-index: 1;
}
/* Compare toggle buttons — icon + label stacked */
.compare-toggle-label {
display: block;
font-size: 9px;
line-height: 1;
margin-top: 2px;
font-weight: 500;
}
.compare-blind-toggle,
.compare-save-toggle,
.compare-dice-toggle,
.compare-parallel-toggle,
.compare-reset-toggle {
display: flex; flex-direction: column; align-items: center; justify-content: center;
width: 56px; height: auto; flex: 1 1 0;
padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px;
cursor: pointer; user-select: none; transition: all 0.15s;
background: none; color: var(--fg); opacity: 0.35;
flex-shrink: 0; gap: 0;
}
.compare-blind-toggle:hover,
.compare-save-toggle:hover,
.compare-dice-toggle:hover,
.compare-parallel-toggle:hover,
.compare-reset-toggle:hover {
opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent);
}
.compare-blind-toggle.active {
opacity: 1; color: var(--accent, var(--red)); border-color: var(--accent, var(--red));
background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent);
}
.compare-blind-toggle.active > svg {
color: var(--accent, var(--red));
}
.compare-save-toggle.active {
opacity: 1; color: var(--color-save-green); border-color: var(--color-save-green);
background: color-mix(in srgb, var(--red) 10%, transparent);
}
.compare-dice-toggle.active {
opacity: 1; color: var(--red); border-color: var(--red);
background: color-mix(in srgb, var(--red) 10%, transparent);
}
.compare-parallel-toggle {
opacity: 1; color: #e0a050; border-color: #e0a050;
background: rgba(224, 160, 80, 0.1);
}
.compare-parallel-toggle.active {
color: var(--accent, var(--red)); border-color: var(--accent, var(--red));
background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent);
}
@media (max-width: 520px) {
.compare-toggle-label { display: none; }
/* Tab text labels are hidden on mobile, so spell out the active type next
to "Type:" with its icon. */
.compare-type-current {
display: inline-flex;
align-items: center;
gap: 4px;
margin-left: 5px;
font-weight: 600;
vertical-align: -3px;
}
.compare-type-current svg { width: 14px; height: 14px; }
/* Mode list — comma-separated, each name already coloured inline. */
.compare-mode-current {
display: inline;
margin-left: 5px;
font-weight: 600;
}
.compare-blind-toggle,
.compare-save-toggle,
.compare-dice-toggle,
.compare-parallel-toggle,
.compare-reset-toggle {
width: 32px; height: 32px; min-width: 32px; padding: 0;
}
/* The Group tab's seq/parallel toggle is a single full-width button (not
a row of compact icons like the Compare header), so keep its label and
make it a comfortable touch target with the text beside the icon. */
#group-mode-btn {
flex-direction: row !important;
width: auto !important;
height: auto !important;
min-height: 44px;
padding: 8px 14px !important;
gap: 8px !important;
font-size: 13px;
}
#group-mode-btn .compare-toggle-label {
display: inline !important;
font-size: 13px;
margin-top: 0;
}
#group-mode-btn svg { width: 18px; height: 18px; }
/* Compare header: hide labels + close button, show icons only */
#compare-shuffle-btn span {
display: none;
}
#compare-shuffle-btn,
#compare-check-btn,
#compare-add-btn {
padding: 3px 6px;
}
/* Save space so the header buttons fit on one row: tighter padding +
smaller labels (icons kept full size). (Score now lives in the vote bar.) */
.compare-header-bar button { padding: 3px 5px !important; }
.compare-header-bar #compare-check-btn > span,
.compare-header-bar #compare-add-btn > span {
font-size: 10px; margin-left: 2px;
}
/* Compare mobile: keep the close X visible — it's the only way out
now that the hamburger is hidden during compare mode. */
.compare-header-bar {
padding: 14px 8px 10px 8px !important;
min-height: 44px;
}
.compare-pane .pane-header {
align-items: stretch;
row-gap: 3px;
}
.compare-pane .pane-title-btn {
flex: 1 1 auto;
width: auto;
padding-right: 2px;
}
/* Compare is already visibly in chat mode; the Chat speech-bubble
badge adds a redundant second row on narrow screens. */
.compare-pane .pane-mode-chat { display: none; }
.compare-pane .pane-timer,
.compare-pane .pane-summary,
.compare-pane .pane-finish-badge,
.compare-pane .pane-mode-badge,
.compare-pane .pane-actions {
order: 1;
align-self: center;
}
.compare-pane .pane-finish-badge { top: 0; }
.compare-pane .pane-summary {
max-width: 110px;
}
.compare-pane .pane-mode-detail {
display: none;
}
.compare-grid[data-cols="4"] .compare-pane .pane-timer,
.compare-grid[data-cols="5"] .compare-pane .pane-timer,
.compare-grid[data-cols="6"] .compare-pane .pane-timer {
width: auto;
order: 1;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
}
.compare-pane .pane-actions {
margin-left: auto;
}
/* Mode tabs: icons only, centered */
.compare-mode-tab span { display: none; }
.compare-mode-tabs { justify-content: center; }
/* Header action buttons: hide text labels on Export / Shuffle /
Model on mobile so the close X fits on the right. Score keeps
its "Score" label because its icon (4-square grid) reads too
similar to Shuffle's icon without text. */
.compare-header-bar #compare-export-btn > span,
.compare-header-bar #compare-shuffle-btn > span {
display: none;
}
/* Override the desktop override: on mobile the model picker overlay
MUST cap its height to the viewport so the dropdown doesn't run
past the bottom of the screen. */
#compare-model-overlay .modal-content {
max-height: 85dvh !important;
max-height: 85vh !important;
overflow: hidden !important;
}
#compare-model-overlay .modal-body {
overflow: auto !important;
flex: 1 1 auto !important;
min-height: 0 !important;
}
}
/* Hide number input spinners */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none; margin: 0;
}
/* ── Sensitive info censor ── */
.censored-item {
filter: blur(5px);
cursor: pointer;
transition: filter 0.2s;
border-radius: 2px;
padding: 0 2px;
background: rgba(255,100,100,0.08);
user-select: none;
}
.censored-item:hover { filter: blur(3px); background: rgba(255,100,100,0.15); }
.censored-item.revealed {
filter: none;
background: rgba(100,255,100,0.08);
user-select: auto;
cursor: text;
}
#settings-menu-list .list-item.active {
background: color-mix(in srgb, var(--accent) 10%, transparent);
border-color: var(--accent);
}
/* ── Print / PDF Export ── */
@media print {
body { background: #fff !important; color: #000 !important; }
#sidebar, .sidebar, #icon-rail, .hamburger-btn, #sidebar-backdrop, .chat-input-bar, .input-bar-wrapper,
#welcome-screen, .chat-top-bar, .chat-meta-overlay, .msg-footer,
.modal, .toast, .overflow-wrapper, .mode-toggle, .incognito-btn,
button, .dropdown, .session-dropdown,
.agent-tool-spinner, .agent-thread-node.running { display: none !important; }
main.chat-container { width: 100% !important; margin: 0 !important; padding: 0 !important; max-height: none !important; overflow: visible !important; }
#chat-history { max-height: none !important; overflow: visible !important; height: auto !important; padding: 0 !important; }
.msg { break-inside: avoid; page-break-inside: avoid; border: none !important; box-shadow: none !important; }
.msg-ai { background: #f5f5f5 !important; color: #000 !important; }
.msg-user { background: #e8e8e8 !important; color: #000 !important; }
.msg .role { color: #333 !important; font-weight: bold; }
.msg .body { color: #000 !important; }
pre, code { background: #f0f0f0 !important; color: #000 !important; border: 1px solid #ccc !important; }
details { display: block !important; }
details[open] summary ~ * { display: block !important; }
details > summary { list-style: none; }
details > summary::before { content: "" !important; }
#chat-history::before { content: attr(data-print-title); display: block; font-size: 1.3em; font-weight: bold; margin-bottom: 1em; color: #000; }
a { color: #000 !important; text-decoration: underline; }
}
/* ── Components (from style.css) ── */
/* Self-hosted Fira Code font */
@font-face { font-family: 'Fira Code'; font-weight: 300; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Light.woff2') format('woff2'); }
@font-face { font-family: 'Fira Code'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Regular.woff2') format('woff2'); }
@font-face { font-family: 'Fira Code'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-SemiBold.woff2') format('woff2'); }
/* Scrollbar styling */
/* Code block styling */
pre, code, .hljs {
font-size: 11px;
line-height: 1.5;
}
/* WebKit (Chrome, Edge, Safari) */
/* Utility class for red text */
.red-text {
color: var(--red);
}
/* Internal chat links (search results, session references) */
a.chat-link {
color: var(--hl-function);
text-decoration: none;
border-bottom: 1px dotted var(--hl-function);
cursor: pointer;
}
a.chat-link:hover {
opacity: 0.8;
border-bottom-style: solid;
}
a.chat-link[href^="#email-"] {
display: inline;
padding: 1px 5px;
border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, transparent);
border-radius: 5px;
background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent);
color: var(--fg);
font-weight: 600;
border-bottom-style: solid;
}
a.chat-link[href^="#email-"]:hover {
opacity: 1;
background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent);
border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent);
}
a.chat-link[href^="#notes-more-"],
a.chat-link[href^="#skills-more-"],
a.chat-link[href^="#memories-more-"],
a.chat-link[href^="#events-more-"],
a.chat-link[href^="#sessions-more-"] {
color: var(--muted);
border-bottom: 1px dotted color-mix(in srgb, var(--muted) 55%, transparent);
font-size: 11px;
font-weight: 500;
}
a.chat-link[href^="#notes-more-"]:hover,
a.chat-link[href^="#skills-more-"]:hover,
a.chat-link[href^="#memories-more-"]:hover,
a.chat-link[href^="#events-more-"]:hover,
a.chat-link[href^="#sessions-more-"]:hover {
color: var(--fg);
opacity: 1;
border-bottom-style: solid;
}
.more-list-expanded {
display: block;
max-height: 0;
overflow: hidden;
opacity: 0;
color: inherit;
font: inherit;
line-height: inherit;
transition: max-height 220ms ease, opacity 160ms ease;
}
.more-list-expanded.is-open {
opacity: 1;
}