mirror of
https://github.com/alphaXiv/OpenResearch.git
synced 2026-11-11 09:59:34 +08:00
* Show a loading state instead of the empty state while chat history loads (v0.1.77) Switching between agent conversations briefly flashed the empty state (OpenResearch logo + 'Ask the agent to explore your codebase...') before the conversation's messages finished fetching. The empty vs transcript branch was gated purely on messages.length, which can't distinguish a not-yet-loaded session from a genuinely empty one. Add a distinct 'Loading conversation…' state, driven by key presence in messagesBySession (absent = still fetching). A failed fetch now seeds an empty transcript so it recovers to the usable empty state instead of a stuck spinner, and drops the loadedSessions guard so a switch-back retries. * Enlarge the chat loading spinner and text for legibility The 13px muted text + 13px wheel read as too small and hidden. Bump the wheel to 22px (thicker stroke), the label to 16px, and use --subtext for better contrast. * Guard failed-fetch recovery against clobbering streamed messages Review nit: the .catch seeded [] unconditionally, which could overwrite a message that streamed in via SSE during a failed history fetch (self-heals on the next re-broadcast, but briefly drops it). Add an onlyIfAbsent flag to the seed action so recovery no-ops when the transcript key already exists.
4403 lines
100 KiB
CSS
4403 lines
100 KiB
CSS
/* OpenResearch design system, mirrored from openresearch.sh www/src/styles.css.
|
||
Light theme by default, dark via prefers-color-scheme. Quiet idiom:
|
||
hairline borders, soft radii, flat surfaces that tint on hover. */
|
||
|
||
:root {
|
||
--base: #ffffff;
|
||
--canvas: #faf8f4;
|
||
--panel: #f3f0ea;
|
||
--surface: #faf7f2;
|
||
--surface-bright: #fdfbfb;
|
||
--highlight: #fdf3f1;
|
||
--text: #1d1b1a;
|
||
--subtext: #737373;
|
||
--muted: #a1a1a1;
|
||
--primary: #9a2036;
|
||
--primary-subtle: #f7e9ec;
|
||
--secondary: #4a90e2;
|
||
--error: #e2a100;
|
||
--border: #d4d4d4;
|
||
--border-variant: #e5e5e5;
|
||
|
||
--accent-orange: #da642c;
|
||
--accent-red: #d94654;
|
||
--accent-teal: #209a84;
|
||
--accent-blue: #3a8dff;
|
||
--accent-pink: #ff2b90;
|
||
--accent-amber: #da9100;
|
||
--accent-green: #5eb64c;
|
||
--accent-purple: #9c5cff;
|
||
|
||
--accent-green-subtle: #e7f4e5;
|
||
--accent-amber-subtle: #fff3e1;
|
||
--accent-teal-subtle: #e1f3f0;
|
||
--accent-red-subtle: #fbe9ea;
|
||
--accent-blue-subtle: #e5f0ff;
|
||
--accent-purple-subtle: #f1e8ff;
|
||
|
||
--dots-muted: #e3ded5;
|
||
--dots-strong: #bdb6a8;
|
||
|
||
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
|
||
|
||
/* Type scale. Sizes are px steps; anything smaller than --fs-2xs is banned. */
|
||
--fs-2xs: 10px;
|
||
--fs-xs: 11px;
|
||
--fs-sm: 12px;
|
||
--fs-md: 13px;
|
||
--fs-base: 14px;
|
||
--fs-lg: 15px;
|
||
--fs-xl: 16px;
|
||
--fs-2xl: 18px;
|
||
--fs-3xl: 22px;
|
||
--fs-4xl: 28px;
|
||
--fs-5xl: 34px;
|
||
|
||
/* Weight scale. Display weight is for hero headings only; the brand
|
||
wordmark keeps a literal 800. */
|
||
--fw-regular: 400;
|
||
--fw-medium: 500;
|
||
--fw-semibold: 600;
|
||
--fw-display: 700;
|
||
|
||
/* Radius scale. True circles stay literal 50%. */
|
||
--radius-xs: 4px;
|
||
--radius-sm: 6px;
|
||
--radius-md: 8px;
|
||
--radius-lg: 10px;
|
||
--radius-xl: 12px;
|
||
--radius-full: 999px;
|
||
|
||
/* xterm surface — deliberately dark in both themes; keep in sync with the
|
||
terminal theme in LogTerminal.tsx. */
|
||
--term-bg: #1a1a1a;
|
||
/* Readable column cap shared by the chat header/thread/composer (which must
|
||
stay in lockstep or the chat column misaligns) and the settings/doc views. */
|
||
--readable-col: 760px;
|
||
|
||
/* Legacy aliases (pre-restyle class/inline usage). New code uses the tokens above. */
|
||
--bg: var(--base);
|
||
--bg-canvas: var(--canvas);
|
||
--surface-2: var(--panel);
|
||
--border-strong: var(--border);
|
||
--accent: var(--primary);
|
||
--teal: var(--accent-teal);
|
||
--green: var(--accent-green);
|
||
--red: var(--accent-red);
|
||
--amber: var(--accent-amber);
|
||
--purple: var(--accent-purple);
|
||
|
||
color-scheme: light;
|
||
}
|
||
|
||
@media (prefers-color-scheme: dark) {
|
||
:root {
|
||
--base: #0e0c0c;
|
||
--canvas: #141110;
|
||
--panel: #221f1e;
|
||
--surface: #1d1b1a;
|
||
--surface-bright: #130f0f;
|
||
--highlight: #393433;
|
||
--text: #e6e1e0;
|
||
--subtext: #a68e8b;
|
||
--muted: #737373;
|
||
--primary: #ffb3ad;
|
||
--primary-subtle: #33191b;
|
||
--secondary: #3e7fcf;
|
||
--error: #ffb951;
|
||
--border: #525252;
|
||
--border-variant: #404040;
|
||
|
||
--accent-amber: #e67e22;
|
||
|
||
--accent-green-subtle: #1c2b18;
|
||
--accent-amber-subtle: #33260f;
|
||
--accent-teal-subtle: #12332d;
|
||
--accent-red-subtle: #331418;
|
||
--accent-blue-subtle: #10233a;
|
||
--accent-purple-subtle: #251933;
|
||
|
||
--dots-muted: #2a2523;
|
||
--dots-strong: #555555;
|
||
|
||
color-scheme: dark;
|
||
}
|
||
}
|
||
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
html,
|
||
body,
|
||
#root {
|
||
height: 100%;
|
||
margin: 0;
|
||
}
|
||
|
||
body {
|
||
background: var(--base);
|
||
color: var(--text);
|
||
font-family: var(--sans);
|
||
font-size: var(--fs-base);
|
||
line-height: 1.45;
|
||
overflow: hidden;
|
||
}
|
||
|
||
::selection {
|
||
background: var(--highlight);
|
||
}
|
||
|
||
button {
|
||
font: inherit;
|
||
color: inherit;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
}
|
||
|
||
input,
|
||
textarea,
|
||
select {
|
||
font: inherit;
|
||
color: var(--text);
|
||
background: var(--base);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
padding: 6px 10px;
|
||
outline: none;
|
||
}
|
||
|
||
input:focus,
|
||
textarea:focus,
|
||
select:focus {
|
||
border-color: var(--text);
|
||
}
|
||
|
||
input::placeholder,
|
||
textarea::placeholder {
|
||
color: var(--muted);
|
||
opacity: 1; /* Firefox dims placeholders on its own */
|
||
}
|
||
|
||
::-webkit-scrollbar {
|
||
width: 10px;
|
||
height: 10px;
|
||
}
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--border);
|
||
border-radius: var(--radius-sm);
|
||
border: 2px solid transparent;
|
||
background-clip: padding-box;
|
||
}
|
||
::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
50% {
|
||
opacity: 0.35;
|
||
}
|
||
}
|
||
|
||
@keyframes spin {
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
/* ---- app shell ---- */
|
||
|
||
.app {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
}
|
||
|
||
/* Horizontal padding gives the chat column a constant gutter to the screen
|
||
edge; it stays even when a side panel collapses (a flex gap would not).
|
||
The panels' inner margins add the matching gutter when a panel is present. */
|
||
.app-body {
|
||
display: flex;
|
||
flex: 1;
|
||
min-height: 0;
|
||
padding: 0 14px;
|
||
}
|
||
|
||
/* ---- projects home ---- */
|
||
|
||
.home {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
scrollbar-gutter: stable both-edges;
|
||
background: var(--canvas);
|
||
}
|
||
.home-inner {
|
||
max-width: 620px;
|
||
margin: 0 auto;
|
||
padding: 48px 24px 64px;
|
||
}
|
||
.wordmark {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.4em;
|
||
color: var(--text);
|
||
}
|
||
.wordmark svg {
|
||
width: 1em;
|
||
height: 1em;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.home-brand {
|
||
/* Matches the openresearch.sh landing wordmark (font-bold = 700). */
|
||
font-weight: var(--fw-display);
|
||
font-size: var(--fs-4xl);
|
||
letter-spacing: -0.02em;
|
||
margin-bottom: 36px;
|
||
}
|
||
.home-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-bottom: 18px;
|
||
}
|
||
.home-head h2 {
|
||
margin: 0;
|
||
font-size: var(--fs-2xl);
|
||
letter-spacing: -0.01em;
|
||
}
|
||
.home-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.project-card {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
text-align: left;
|
||
padding: 14px 16px;
|
||
background: var(--base);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
cursor: pointer;
|
||
transition:
|
||
border-color 120ms ease,
|
||
box-shadow 120ms ease;
|
||
}
|
||
.project-card:hover {
|
||
border-color: var(--text);
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
|
||
}
|
||
.project-delete {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 10px;
|
||
display: none;
|
||
padding: 5px;
|
||
border-radius: var(--radius-sm);
|
||
color: var(--muted);
|
||
line-height: 0;
|
||
}
|
||
.project-card:hover .project-delete {
|
||
display: block;
|
||
}
|
||
.project-delete:hover {
|
||
color: var(--danger, #d33);
|
||
background: var(--overlay, rgba(0, 0, 0, 0.05));
|
||
}
|
||
.project-card .name {
|
||
font-weight: var(--fw-semibold);
|
||
font-size: var(--fs-base);
|
||
}
|
||
.project-card .repo {
|
||
color: var(--text);
|
||
}
|
||
.project-card .paper {
|
||
font-size: var(--fs-xs);
|
||
color: var(--muted);
|
||
}
|
||
.project-card .time {
|
||
font-size: var(--fs-xs);
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* ---- rail header (brand + project switcher atop the agents rail) ---- */
|
||
|
||
.rail-brand {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
height: 48px;
|
||
/* 12px left text inset + ~14px icon right edge — keep in step with
|
||
.rail-nav below. */
|
||
padding: 0 8px 0 12px;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
/* Back arrow + current project name; truncates, arrow stays visible. */
|
||
.rail-brand .brand {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-weight: var(--fw-semibold);
|
||
font-size: var(--fs-md);
|
||
color: var(--text);
|
||
padding: 4px 6px 4px 0;
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.rail-brand .brand:hover {
|
||
color: var(--subtext);
|
||
}
|
||
.rail-brand .brand svg {
|
||
flex-shrink: 0;
|
||
}
|
||
.rail-brand .brand .brand-project {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.icon-btn {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 28px;
|
||
height: 28px;
|
||
color: var(--subtext);
|
||
}
|
||
.icon-btn:hover {
|
||
color: var(--text);
|
||
background: var(--surface);
|
||
}
|
||
|
||
/* CSS-only tooltip for icon-only controls: `data-tip` holds the label.
|
||
Inverted bubble (text-on-base swap) so it reads in both themes. Default
|
||
below the control; `.tip-up` flips above (for footer rows). */
|
||
[data-tip] {
|
||
position: relative;
|
||
}
|
||
[data-tip]::after {
|
||
content: attr(data-tip);
|
||
position: absolute;
|
||
top: calc(100% + 6px);
|
||
right: -4px;
|
||
background: var(--text);
|
||
color: var(--base);
|
||
font-size: var(--fs-xs);
|
||
font-weight: var(--fw-medium);
|
||
line-height: 1;
|
||
padding: 5px 8px;
|
||
border-radius: var(--radius-sm);
|
||
white-space: nowrap;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
pointer-events: none;
|
||
transition: opacity 0.1s ease 0.3s, visibility 0s linear 0.4s;
|
||
z-index: 60;
|
||
}
|
||
[data-tip]:hover::after {
|
||
opacity: 1;
|
||
visibility: visible;
|
||
}
|
||
.tip-up[data-tip]::after {
|
||
top: auto;
|
||
bottom: calc(100% + 6px);
|
||
}
|
||
|
||
/* ---- buttons (quiet: 1px border, rounded, flat) ---- */
|
||
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
padding: 6px 14px;
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-semibold);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--base);
|
||
color: var(--text);
|
||
white-space: nowrap;
|
||
transition:
|
||
background 120ms ease,
|
||
border-color 120ms ease,
|
||
color 120ms ease;
|
||
}
|
||
.btn:hover:not(:disabled) {
|
||
background: var(--surface);
|
||
}
|
||
.btn:active:not(:disabled) {
|
||
background: var(--highlight);
|
||
}
|
||
.btn:disabled {
|
||
opacity: 0.45;
|
||
cursor: default;
|
||
}
|
||
.btn.primary {
|
||
background: var(--primary);
|
||
border-color: var(--primary);
|
||
color: var(--base);
|
||
}
|
||
.btn.primary:hover:not(:disabled) {
|
||
background: color-mix(in oklab, var(--primary) 88%, var(--text));
|
||
border-color: color-mix(in oklab, var(--primary) 88%, var(--text));
|
||
}
|
||
.btn.primary:active:not(:disabled) {
|
||
background: color-mix(in oklab, var(--primary) 80%, var(--text));
|
||
border-color: color-mix(in oklab, var(--primary) 80%, var(--text));
|
||
}
|
||
.btn.danger {
|
||
color: var(--accent-red);
|
||
}
|
||
.btn.danger:hover:not(:disabled) {
|
||
background: color-mix(in oklab, var(--accent-red) 8%, transparent);
|
||
}
|
||
.btn.danger:active:not(:disabled) {
|
||
background: color-mix(in oklab, var(--accent-red) 14%, transparent);
|
||
}
|
||
.btn.ghost {
|
||
border-color: transparent;
|
||
color: var(--text);
|
||
}
|
||
.btn.ghost:hover:not(:disabled) {
|
||
color: var(--text);
|
||
background: var(--surface);
|
||
}
|
||
.btn.sm {
|
||
padding: 3px 9px;
|
||
font-size: var(--fs-xs);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
|
||
/* ---- status badge (colored dot + quiet label; live statuses pulse) ---- */
|
||
|
||
.status-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-medium);
|
||
color: var(--text);
|
||
white-space: nowrap;
|
||
}
|
||
.status-badge .dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 50%;
|
||
background: currentColor;
|
||
flex-shrink: 0;
|
||
}
|
||
.status-badge.live .dot {
|
||
animation: pulse 1.2s ease-in-out infinite;
|
||
}
|
||
/* The status color lives on the dot; the label stays neutral. */
|
||
.st-done .dot {
|
||
color: var(--accent-green);
|
||
}
|
||
.st-failed .dot {
|
||
color: var(--accent-red);
|
||
}
|
||
.st-running .dot {
|
||
color: var(--accent-teal);
|
||
}
|
||
.st-starting .dot {
|
||
color: var(--accent-amber);
|
||
}
|
||
.st-cancelled .dot {
|
||
color: var(--accent-orange);
|
||
}
|
||
.st-editing .dot {
|
||
color: var(--accent-purple);
|
||
}
|
||
.st-idle .dot {
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* ---- panes ---- */
|
||
|
||
/* Shared chrome for the floating side panels (rail + right pane): border,
|
||
rounded corners, soft two-layer shadow. Background and margins stay
|
||
per-panel. */
|
||
.floating-panel {
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
box-shadow:
|
||
0 6px 24px color-mix(in oklab, var(--text) 5%, transparent),
|
||
0 1px 4px color-mix(in oklab, var(--text) 4%, transparent);
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Floating agents rail: fixed-width, collapsible (not resizable). */
|
||
.session-rail {
|
||
width: 232px;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
/* right margin = the gutter to the chat column; screen-edge gutter comes
|
||
from .app-body's left padding. */
|
||
margin: 10px 14px 10px 0;
|
||
background: var(--base);
|
||
min-height: 0;
|
||
}
|
||
/* Rail nav: new session + settings sections, above the Recents list. */
|
||
.rail-nav {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
padding: 8px;
|
||
flex-shrink: 0;
|
||
}
|
||
.rail-nav-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 7px 10px;
|
||
font-size: var(--fs-md);
|
||
color: var(--text);
|
||
border-radius: var(--radius-md);
|
||
text-align: left;
|
||
}
|
||
.rail-nav-item:hover {
|
||
background: var(--surface);
|
||
}
|
||
.rail-nav-item.active {
|
||
background: var(--panel);
|
||
font-weight: var(--fw-semibold);
|
||
}
|
||
|
||
.rail-section-label {
|
||
padding: 10px 10px 6px;
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-medium);
|
||
color: var(--subtext);
|
||
}
|
||
/* "Recents" label + the session filter button on one line. */
|
||
.rail-section-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
.rail-filter {
|
||
position: relative;
|
||
display: inline-flex;
|
||
margin-right: 2px;
|
||
}
|
||
/* A non-default filter keeps the button lit via the shared .icon-btn.active. */
|
||
.rail-filter-btn {
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.rail-empty {
|
||
padding: 6px 10px;
|
||
font-size: var(--fs-sm);
|
||
color: var(--muted);
|
||
}
|
||
.session-rail .rail-body {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
padding: 4px 8px;
|
||
}
|
||
|
||
/* The pane fills the leftover space so the thread's scrollbar reaches its
|
||
right edge; the readable-col cap lives on the header, the composer, and
|
||
.chat-thread-inner (inside the scroller), which center themselves. */
|
||
.chat-pane {
|
||
flex: 1;
|
||
/* min-width:0 (not 360) so a wide panel can never force the flex row past
|
||
the viewport; the panel's own clamp keeps the usable minimum. */
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: var(--base);
|
||
min-height: 0;
|
||
}
|
||
|
||
/* Floating right panel: a closable, resizable card. `.max` lifts it out of
|
||
the flex row to cover (nearly) the whole window, still floating. */
|
||
.right-pane {
|
||
position: relative;
|
||
flex-shrink: 0;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
/* left margin = the gutter to the chat column; screen-edge gutter comes
|
||
from .app-body's right padding. */
|
||
margin: 10px 0 10px 14px;
|
||
background: var(--canvas);
|
||
}
|
||
.right-pane.max {
|
||
position: fixed;
|
||
inset: 10px;
|
||
margin: 0;
|
||
z-index: 60;
|
||
box-shadow: 0 12px 40px color-mix(in oklab, var(--text) 22%, transparent);
|
||
}
|
||
|
||
/* Invisible grab strip on the panel's left edge. Sits above the detail
|
||
overlays (.drawer/.term-view are z-index 20, inset 0) so resizing works in
|
||
every view, not just the tree. */
|
||
.panel-resizer {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 6px;
|
||
cursor: col-resize;
|
||
z-index: 30;
|
||
}
|
||
.panel-resizer:hover,
|
||
.panel-resizer:active {
|
||
background: color-mix(in oklab, var(--text) 12%, transparent);
|
||
}
|
||
|
||
/* Expand/close cluster at the right end of the panel's tab bar. */
|
||
.panel-controls {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Panel tab bar: pinned Log/Artifacts + closable experiment/file tabs. */
|
||
.tabs {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 0 10px;
|
||
height: 40px;
|
||
border-bottom: 1px solid var(--border);
|
||
background: var(--base);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Scrollable strip of tabs. */
|
||
.tab-strip {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow-x: auto;
|
||
scrollbar-width: none;
|
||
}
|
||
.tab-strip::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
/* Dynamic (closable) tabs: truncated label, inline close "x". */
|
||
.tab.closable {
|
||
max-width: 176px;
|
||
padding-right: 4px;
|
||
}
|
||
.tab .tab-label {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.tab .tab-close {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 16px;
|
||
height: 16px;
|
||
border-radius: var(--radius-xs);
|
||
color: var(--muted);
|
||
flex-shrink: 0;
|
||
}
|
||
.tab .tab-close:hover {
|
||
background: color-mix(in oklab, var(--text) 15%, transparent);
|
||
color: var(--text);
|
||
}
|
||
|
||
/* One tab style for the whole strip — pinned tabs simply have no close "x". */
|
||
.tab {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 5px 12px;
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
border-radius: var(--radius-sm);
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
.tab.active {
|
||
background: var(--surface);
|
||
}
|
||
.tab-body {
|
||
flex: 1;
|
||
min-height: 0;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* Toolbar row above a tab's content (e.g. the tree/table switch). */
|
||
.pane-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
padding: 10px 12px 0;
|
||
flex-shrink: 0;
|
||
}
|
||
.pane-content {
|
||
flex: 1;
|
||
min-height: 0;
|
||
position: relative;
|
||
}
|
||
|
||
/* Right-pane plan tab: a plain, padded document surface — the plan is a
|
||
markdown file, not a card, so no tinted background. */
|
||
.plan-tab-content {
|
||
overflow-y: auto;
|
||
background: var(--base);
|
||
padding: 18px 24px;
|
||
}
|
||
.plan-tab-content .md {
|
||
max-width: var(--readable-col);
|
||
}
|
||
|
||
/* Segmented control: a clearly-darker track (the theme's creams all blend
|
||
into each other) with a raised white pill on the active segment. */
|
||
.seg {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
padding: 3px;
|
||
border-radius: var(--radius-md);
|
||
background: color-mix(in oklab, var(--text) 10%, transparent);
|
||
}
|
||
.seg button {
|
||
padding: 3px 12px;
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.seg button:hover {
|
||
color: var(--text);
|
||
}
|
||
.seg button.active {
|
||
background: var(--base);
|
||
box-shadow: 0 1px 3px color-mix(in oklab, var(--text) 25%, transparent);
|
||
}
|
||
|
||
/* ---- chat ---- */
|
||
|
||
/* Borderless header that fades into the chat: the thread scrolls under a
|
||
gradient hanging below the bar instead of hitting a hard rule. */
|
||
.chat-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 0 16px;
|
||
background: var(--base);
|
||
flex-shrink: 0;
|
||
height: 48px;
|
||
position: relative;
|
||
z-index: 4;
|
||
width: 100%;
|
||
max-width: var(--readable-col);
|
||
margin: 0 auto;
|
||
}
|
||
/* Rail hidden: the header spans the full pane (Claude-desktop style) so the
|
||
reopen toggle lands in the window's top-left corner with the title beside
|
||
it; the tighter padding puts the icon on the screen-edge gutter. */
|
||
.chat-header.rail-hidden {
|
||
max-width: none;
|
||
padding: 0 2px;
|
||
}
|
||
/* Breathing room between the corner toggle and the title. */
|
||
.chat-header.rail-hidden > .icon-btn:first-child {
|
||
margin-right: 12px;
|
||
}
|
||
.chat-header::after {
|
||
content: "";
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 0;
|
||
right: 0;
|
||
height: 24px;
|
||
background: linear-gradient(to bottom, var(--base), transparent);
|
||
pointer-events: none;
|
||
}
|
||
|
||
|
||
.chat-header .title {
|
||
font-size: var(--fs-md);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.session-row {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
text-align: left;
|
||
padding: 7px 10px;
|
||
border-radius: var(--radius-md);
|
||
font-size: var(--fs-md);
|
||
color: var(--text);
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
.session-row:hover {
|
||
background: var(--surface);
|
||
}
|
||
.session-row.active {
|
||
background: var(--surface);
|
||
font-weight: var(--fw-medium);
|
||
}
|
||
/* Reserved status column so titles align whether or not a busy dot shows. */
|
||
.session-row .session-dot {
|
||
width: 14px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
.session-row .session-title {
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.session-row .session-time {
|
||
font-size: var(--fs-2xs);
|
||
color: var(--muted);
|
||
flex-shrink: 0;
|
||
}
|
||
/* Hover (or an open menu) swaps the timestamp for the three-dot menu button. */
|
||
.session-row .session-menu-btn {
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 18px;
|
||
height: 18px;
|
||
margin: -2px 0;
|
||
border-radius: var(--radius-sm);
|
||
color: var(--muted);
|
||
flex-shrink: 0;
|
||
}
|
||
.session-row .session-menu-btn:hover {
|
||
color: var(--text);
|
||
background: var(--panel);
|
||
}
|
||
/* :focus-within (not :focus-visible): it holds while the kebab itself is
|
||
focused — a row-only match would display:none the button mid-Tab and dump
|
||
focus to <body>, making the menu keyboard-unreachable. */
|
||
.session-row:hover .session-menu-btn,
|
||
.session-row:focus-within .session-menu-btn,
|
||
.session-row.menu-open .session-menu-btn {
|
||
display: inline-flex;
|
||
}
|
||
.session-row:hover .session-time,
|
||
.session-row:focus-within .session-time,
|
||
.session-row.menu-open .session-time {
|
||
display: none;
|
||
}
|
||
.session-row .busy-dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 50%;
|
||
background: var(--primary);
|
||
animation: pulse 1.2s infinite;
|
||
flex-shrink: 0;
|
||
}
|
||
/* Turn held on an unanswered card: ready for the user — steady, no pulse. */
|
||
.session-row .busy-dot.waiting {
|
||
animation: none;
|
||
}
|
||
/* Inline rename: the title span is replaced by an input sharing its box so the
|
||
text doesn't jump. Take the row's full width and hide the kebab/time. */
|
||
.session-row .session-title-input {
|
||
flex: 1;
|
||
min-width: 0;
|
||
padding: 1px 5px;
|
||
margin: -2px 0;
|
||
font: inherit;
|
||
color: var(--text);
|
||
background: var(--base);
|
||
border: 1px solid var(--primary);
|
||
border-radius: var(--radius-sm);
|
||
outline: none;
|
||
}
|
||
.session-row.editing {
|
||
background: var(--surface);
|
||
cursor: default;
|
||
}
|
||
.session-row.editing .session-menu-btn,
|
||
.session-row.editing .session-time {
|
||
display: none;
|
||
}
|
||
.chat-thread {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
/* stable: no shift when the bar appears; both-edges: keeps the column centered */
|
||
scrollbar-gutter: stable both-edges;
|
||
}
|
||
.chat-thread-inner {
|
||
max-width: var(--readable-col);
|
||
margin: 0 auto;
|
||
/* Bottom padding clears the composer's 24px fade so the last message stays
|
||
fully legible when scrolled to the very bottom. */
|
||
padding: 16px 16px 32px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.chat-empty {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 12px;
|
||
color: var(--text);
|
||
padding: 20px;
|
||
text-align: center;
|
||
}
|
||
.chat-empty h2 {
|
||
margin: 0;
|
||
font-size: var(--fs-5xl);
|
||
font-weight: var(--fw-display);
|
||
letter-spacing: -0.02em;
|
||
color: var(--text);
|
||
}
|
||
.chat-empty .chat-empty-hint {
|
||
font-size: var(--fs-sm);
|
||
color: var(--muted);
|
||
max-width: 340px;
|
||
text-align: center;
|
||
}
|
||
.chat-empty p {
|
||
margin: 0;
|
||
font-size: var(--fs-md);
|
||
max-width: 320px;
|
||
}
|
||
/* one-click /reproduce-paper prefill for paper-born projects */
|
||
.chat-empty .chat-suggest {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
color: var(--text);
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-full);
|
||
padding: 6px 14px;
|
||
cursor: pointer;
|
||
transition: border-color 120ms ease;
|
||
}
|
||
.chat-empty .chat-suggest:hover {
|
||
border-color: var(--text);
|
||
}
|
||
|
||
/* Shown while an existing session's transcript is fetched, so switching
|
||
sessions never flashes the empty state before its messages seed in. */
|
||
.chat-loading {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 12px;
|
||
color: var(--subtext);
|
||
font-size: var(--fs-xl);
|
||
padding: 20px;
|
||
}
|
||
/* Larger, more visible wheel than the inline .spinner used in the thread. */
|
||
.chat-loading .spinner {
|
||
width: 22px;
|
||
height: 22px;
|
||
border-width: 3px;
|
||
}
|
||
|
||
/* user message: a right-aligned rounded bubble, no border, no label */
|
||
.msg-user {
|
||
align-self: flex-end;
|
||
max-width: 88%;
|
||
background: var(--surface);
|
||
border-radius: 16px; /* chat bubble — deliberate outsized radius */
|
||
padding: 10px 15px;
|
||
font-size: var(--fs-md);
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
.msg-images {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
margin-top: 8px;
|
||
}
|
||
.msg-images img {
|
||
max-width: 220px;
|
||
max-height: 160px;
|
||
border: 1px solid var(--border-variant);
|
||
border-radius: var(--radius-xs);
|
||
display: block;
|
||
}
|
||
|
||
.msg-assistant {
|
||
font-size: var(--fs-base);
|
||
line-height: 1.62;
|
||
color: var(--text);
|
||
min-width: 0;
|
||
}
|
||
|
||
.reasoning {
|
||
color: var(--muted);
|
||
font-size: var(--fs-sm);
|
||
margin: 2px 0;
|
||
}
|
||
.reasoning summary {
|
||
cursor: pointer;
|
||
list-style: none;
|
||
user-select: none;
|
||
font-weight: var(--fw-semibold);
|
||
}
|
||
.reasoning[open] {
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
/* tool calls: bordered surface card, one row per call */
|
||
/* ---- tool calls: compact, coalesced, gray (Claude-desktop style) ---- */
|
||
.tool-group {
|
||
margin: 2px 0;
|
||
}
|
||
.tool-group-summary {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
padding: 3px 2px;
|
||
cursor: pointer;
|
||
color: var(--muted);
|
||
font-size: var(--fs-md);
|
||
text-align: left;
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.tool-group-summary:hover {
|
||
color: var(--subtext);
|
||
background: var(--surface);
|
||
}
|
||
.tool-group.has-error .tool-group-summary {
|
||
color: var(--accent-red);
|
||
}
|
||
.tool-chevron {
|
||
flex-shrink: 0;
|
||
color: var(--muted);
|
||
transition: transform 0.12s ease;
|
||
}
|
||
.tool-chevron.open {
|
||
transform: rotate(90deg);
|
||
}
|
||
.tool-group-rows {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1px;
|
||
margin: 2px 0 4px 7px;
|
||
padding-left: 10px;
|
||
border-left: 1px solid var(--border-variant);
|
||
}
|
||
.tool-row {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.tool-row summary {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 3px 4px;
|
||
cursor: pointer;
|
||
list-style: none;
|
||
user-select: none;
|
||
min-width: 0;
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.tool-row summary:hover {
|
||
background: var(--surface);
|
||
}
|
||
.tool-row summary::-webkit-details-marker {
|
||
display: none;
|
||
}
|
||
.tool-line {
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
font-size: var(--fs-md);
|
||
color: var(--subtext);
|
||
}
|
||
.tool-open {
|
||
flex-shrink: 0;
|
||
font-size: var(--fs-xs);
|
||
color: var(--primary);
|
||
}
|
||
.tool-open:hover {
|
||
text-decoration: underline;
|
||
}
|
||
.tool-status {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
background: var(--muted);
|
||
}
|
||
.tool-status.running {
|
||
background: var(--accent-amber);
|
||
animation: pulse 1.2s ease-in-out infinite;
|
||
}
|
||
.tool-status.error {
|
||
background: var(--accent-red);
|
||
}
|
||
.tool-detail {
|
||
margin: 2px 0 4px 14px;
|
||
}
|
||
.tool-cmd-full {
|
||
padding: 6px 10px;
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-xs);
|
||
color: var(--text);
|
||
background: var(--surface);
|
||
border-radius: var(--radius-sm);
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
.tool-output {
|
||
margin-top: 3px;
|
||
padding: 6px 10px;
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-xs);
|
||
color: var(--subtext);
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
max-height: 260px;
|
||
overflow-y: auto;
|
||
background: var(--base);
|
||
border: 1px solid var(--border-variant);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.working {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: var(--subtext);
|
||
font-size: var(--fs-md);
|
||
padding: 2px 0 8px;
|
||
}
|
||
/* Turn held open on an unanswered card: waiting on the user, not the model. */
|
||
.working.awaiting {
|
||
font-style: italic;
|
||
}
|
||
|
||
.spinner {
|
||
width: 13px;
|
||
height: 13px;
|
||
border: 2px solid var(--border);
|
||
border-top-color: var(--primary);
|
||
border-radius: 50%;
|
||
animation: spin 0.8s linear infinite;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* composer: rounded box and send button. Borderless like the header —
|
||
the thread fades into the box through a gradient hanging above it. */
|
||
.composer {
|
||
/* No top padding: the fade hanging above leads straight into the box. */
|
||
padding: 0 12px 12px;
|
||
flex-shrink: 0;
|
||
position: relative;
|
||
z-index: 4;
|
||
background: var(--base);
|
||
width: 100%;
|
||
max-width: var(--readable-col);
|
||
margin: 0 auto;
|
||
}
|
||
.composer::before {
|
||
content: "";
|
||
position: absolute;
|
||
bottom: 100%;
|
||
left: 0;
|
||
right: 0;
|
||
height: 24px;
|
||
background: linear-gradient(to top, var(--base), transparent);
|
||
pointer-events: none;
|
||
}
|
||
.composer-box {
|
||
position: relative; /* anchors the slash-skill menu */
|
||
display: flex;
|
||
flex-direction: column;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--base);
|
||
}
|
||
.composer textarea {
|
||
border: none;
|
||
background: none;
|
||
resize: none;
|
||
padding: 10px 12px 4px;
|
||
font-size: var(--fs-md);
|
||
min-height: 42px;
|
||
max-height: 180px;
|
||
}
|
||
/* pasted images waiting to be sent */
|
||
.composer-attachments {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
padding: 8px 12px 0;
|
||
}
|
||
.attachment-thumb {
|
||
position: relative;
|
||
}
|
||
.attachment-thumb img {
|
||
width: 52px;
|
||
height: 52px;
|
||
object-fit: cover;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-sm);
|
||
display: block;
|
||
}
|
||
.attachment-thumb button {
|
||
position: absolute;
|
||
top: -5px;
|
||
right: -5px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 16px;
|
||
height: 16px;
|
||
padding: 0;
|
||
border: 1px solid var(--border);
|
||
border-radius: 50%;
|
||
background: var(--surface);
|
||
color: var(--text);
|
||
cursor: pointer;
|
||
}
|
||
.attachment-thumb button:hover {
|
||
background: var(--text);
|
||
color: var(--base);
|
||
}
|
||
.composer-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 8px 8px;
|
||
}
|
||
.send-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: var(--radius-md);
|
||
background: var(--primary);
|
||
color: var(--base);
|
||
transition:
|
||
background 100ms ease,
|
||
opacity 100ms ease;
|
||
}
|
||
.send-btn:hover:not(:disabled) {
|
||
background: color-mix(in oklab, var(--primary) 88%, var(--text));
|
||
}
|
||
.send-btn:disabled {
|
||
opacity: 0.4;
|
||
cursor: default;
|
||
}
|
||
.send-btn.stop {
|
||
background: var(--surface);
|
||
color: var(--text);
|
||
}
|
||
.send-btn.stop:hover:not(:disabled) {
|
||
background: color-mix(in oklab, var(--surface) 88%, var(--text));
|
||
}
|
||
|
||
/* ---- markdown ---- */
|
||
|
||
.md {
|
||
min-width: 0;
|
||
word-break: break-word;
|
||
color: var(--text);
|
||
line-height: 1.62;
|
||
}
|
||
.md > *:first-child {
|
||
margin-top: 0;
|
||
}
|
||
.md > *:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
.md p {
|
||
margin: 10px 0;
|
||
}
|
||
.md strong {
|
||
color: var(--text);
|
||
font-weight: var(--fw-semibold);
|
||
}
|
||
.md pre {
|
||
background: var(--surface);
|
||
border: 1px solid color-mix(in oklab, var(--border) 50%, transparent);
|
||
border-radius: var(--radius-md);
|
||
padding: 8px 12px;
|
||
overflow-x: auto;
|
||
font-size: var(--fs-sm);
|
||
color: var(--text);
|
||
}
|
||
.md code {
|
||
font-family: var(--mono);
|
||
font-size: 0.9em;
|
||
font-weight: var(--fw-medium);
|
||
color: var(--primary);
|
||
background: var(--panel);
|
||
border: 1px solid var(--border-variant);
|
||
border-radius: var(--radius-xs);
|
||
padding: 1px 5px;
|
||
}
|
||
/* KaTeX math — tame the library's 1.21em default so formulas sit at body
|
||
size, and let wide display equations scroll instead of overflowing. */
|
||
.md .katex {
|
||
font-size: 1.05em;
|
||
}
|
||
.md .katex-display {
|
||
margin: 12px 0;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
padding: 2px 0;
|
||
}
|
||
/* File mention chip — reads as clickable inline code (thin border, panel
|
||
fill, mono), matching openresearch.sh's FileMention. */
|
||
.md .file-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
max-width: 100%;
|
||
margin: 0 1px;
|
||
padding: 0 6px;
|
||
vertical-align: baseline;
|
||
font-family: var(--mono);
|
||
font-size: 0.9em;
|
||
font-weight: var(--fw-medium);
|
||
color: var(--text);
|
||
background: var(--panel);
|
||
border: 1px solid var(--border-variant);
|
||
border-radius: var(--radius-xs);
|
||
cursor: pointer;
|
||
}
|
||
.md .file-chip:hover:not(:disabled) {
|
||
background: var(--surface);
|
||
color: var(--primary);
|
||
}
|
||
.md .file-chip svg {
|
||
flex: none;
|
||
opacity: 0.6;
|
||
}
|
||
.md .file-chip-label {
|
||
max-width: 260px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.md pre code {
|
||
background: none;
|
||
border: none;
|
||
color: inherit;
|
||
padding: 0;
|
||
font-weight: var(--fw-regular);
|
||
}
|
||
|
||
.md-code {
|
||
position: relative;
|
||
margin: 10px 0;
|
||
}
|
||
.md-code pre {
|
||
margin: 0;
|
||
}
|
||
.md-code-copy {
|
||
position: absolute;
|
||
top: 6px;
|
||
right: 6px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 26px;
|
||
height: 26px;
|
||
color: var(--muted);
|
||
background: var(--base);
|
||
border: 1px solid var(--border-variant);
|
||
border-radius: var(--radius-sm);
|
||
opacity: 0;
|
||
transition: opacity 0.12s ease, color 0.12s ease;
|
||
}
|
||
.md-code:hover .md-code-copy {
|
||
opacity: 1;
|
||
}
|
||
.md-code-copy:hover {
|
||
color: var(--text);
|
||
border-color: var(--muted);
|
||
}
|
||
|
||
.md h1,
|
||
.md h2,
|
||
.md h3,
|
||
.md h4 {
|
||
color: var(--text);
|
||
font-size: 1.05em;
|
||
font-weight: var(--fw-semibold);
|
||
margin: 12px 0 6px;
|
||
}
|
||
.md ul,
|
||
.md ol {
|
||
margin: 6px 0;
|
||
padding-left: 22px;
|
||
}
|
||
.md li::marker {
|
||
color: var(--primary);
|
||
}
|
||
.md a {
|
||
color: var(--primary);
|
||
}
|
||
/* Same wide-table treatment as .report-md table: cells keep whole-word
|
||
min-content widths (see .md td below), so an over-wide table scrolls in
|
||
place instead of clipping at the pane edge. */
|
||
.md table {
|
||
border-collapse: collapse;
|
||
display: block;
|
||
width: max-content;
|
||
max-width: 100%;
|
||
font-size: var(--fs-md);
|
||
margin: 10px 0;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
overflow-x: auto;
|
||
}
|
||
.md th,
|
||
.md td {
|
||
border-bottom: 1px solid var(--border-variant);
|
||
padding: 8px 14px;
|
||
text-align: left;
|
||
color: var(--text);
|
||
/* .md's word-break: break-word shrinks cell min-content width to one
|
||
character, letting auto table layout split short words mid-word;
|
||
overflow-wrap keeps full-word min-content and only breaks overflow. */
|
||
word-break: normal;
|
||
overflow-wrap: break-word;
|
||
}
|
||
.md tr:last-child td {
|
||
border-bottom: none;
|
||
}
|
||
.md thead th {
|
||
background: var(--surface);
|
||
font-weight: var(--fw-medium);
|
||
color: var(--text);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.md tbody tr:hover td {
|
||
background: var(--surface-bright);
|
||
}
|
||
.md blockquote {
|
||
margin: 6px 0;
|
||
padding: 2px 0 2px 10px;
|
||
border-left: 3px solid var(--border);
|
||
color: var(--subtext);
|
||
}
|
||
|
||
/* ---- tree nodes (graph cards on the dotted canvas) ---- */
|
||
|
||
/* Cards must stay `selectable` (ReactFlow sets pointer-events:none on nodes
|
||
* that are neither draggable nor selectable, which would kill the action
|
||
* buttons) — but selection itself means nothing here, so suppress its
|
||
* pointer cursor. Specificity beats RF's stylesheet. Focus/selected visuals
|
||
* need no override: nodesFocusable={false} removes the tab stop, RF's own
|
||
* CSS zeroes node focus outlines, and its .selected box-shadow only targets
|
||
* built-in node types, not custom ones. */
|
||
.react-flow__node.react-flow__node-exp.selectable {
|
||
cursor: default;
|
||
}
|
||
.exp-node {
|
||
/* Keep in sync with NODE_W in TreeView.tsx (layout math uses it). */
|
||
width: 264px;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--base);
|
||
padding: 10px 12px;
|
||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||
font-size: var(--fs-md);
|
||
transition: box-shadow 120ms ease;
|
||
}
|
||
.exp-node:hover {
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||
}
|
||
.exp-node.live {
|
||
border-color: var(--accent-teal);
|
||
box-shadow: 0 2px 12px rgba(32, 154, 132, 0.2);
|
||
}
|
||
.exp-node .node-eyebrow {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
margin-bottom: 6px;
|
||
font-size: var(--fs-2xs);
|
||
font-weight: var(--fw-medium);
|
||
color: var(--muted);
|
||
}
|
||
.exp-node .node-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
min-width: 0;
|
||
}
|
||
.exp-node .node-status {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
}
|
||
.exp-node .node-slug {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.exp-node .node-title {
|
||
margin-top: 4px;
|
||
color: var(--text);
|
||
font-size: var(--fs-sm);
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
.exp-node .node-meta {
|
||
margin-top: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: var(--fs-2xs);
|
||
color: var(--muted);
|
||
}
|
||
/* Direct view shortcuts (Changes / Logs) at the card's foot. */
|
||
.exp-node .node-actions {
|
||
margin-top: 8px;
|
||
padding-top: 6px;
|
||
border-top: 1px solid var(--border-variant);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
}
|
||
.exp-node .node-action {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
/* Tight: Changes + Logs + Code + the GitHub icon share one fixed-width row. */
|
||
padding: 3px 6px;
|
||
font-size: var(--fs-xs);
|
||
font-weight: var(--fw-medium);
|
||
color: var(--text);
|
||
border-radius: var(--radius-sm);
|
||
/* The GitHub shortcut is an <a> among sibling <button>s. */
|
||
text-decoration: none;
|
||
}
|
||
.exp-node .node-action:hover {
|
||
color: var(--text);
|
||
background: var(--surface);
|
||
}
|
||
/* External GitHub shortcut: icon-only, pinned to the card's far edge. */
|
||
.exp-node .node-action-ext {
|
||
margin-left: auto;
|
||
padding: 3px 5px;
|
||
}
|
||
|
||
/* run-history squares (RUNS strip on node cards) */
|
||
.run-squares {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
}
|
||
.run-sq {
|
||
width: 9px;
|
||
height: 9px;
|
||
flex-shrink: 0;
|
||
}
|
||
.run-sq.pass {
|
||
background: var(--accent-green);
|
||
}
|
||
.run-sq.fail {
|
||
border: 1.5px solid color-mix(in oklab, var(--accent-red) 55%, transparent);
|
||
}
|
||
.run-sq.live {
|
||
background: var(--accent-teal);
|
||
animation: pulse 1.2s ease-in-out infinite;
|
||
}
|
||
.run-sq.other {
|
||
border: 1.5px solid var(--border);
|
||
}
|
||
|
||
.react-flow__handle {
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
}
|
||
.react-flow__attribution {
|
||
display: none !important;
|
||
}
|
||
|
||
/* ---- runs table ---- */
|
||
|
||
/* Runs table: a white card on the panel canvas, quiet separators. */
|
||
.runs-table-wrap {
|
||
position: absolute;
|
||
inset: 0;
|
||
overflow: auto;
|
||
padding: 12px;
|
||
}
|
||
.runs-table {
|
||
width: 100%;
|
||
border-collapse: separate;
|
||
border-spacing: 0;
|
||
font-size: var(--fs-md);
|
||
background: var(--base);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
overflow: hidden;
|
||
}
|
||
.runs-table th {
|
||
text-align: left;
|
||
color: var(--text);
|
||
font-size: var(--fs-xs);
|
||
font-weight: var(--fw-semibold);
|
||
padding: 8px 12px;
|
||
border-bottom: 1px solid var(--border);
|
||
position: sticky;
|
||
top: 0;
|
||
background: var(--base);
|
||
z-index: 1;
|
||
}
|
||
.runs-table td {
|
||
padding: 8px 12px;
|
||
border-bottom: 1px solid color-mix(in oklab, var(--text) 6%, transparent);
|
||
white-space: nowrap;
|
||
}
|
||
.runs-table tr:last-child td {
|
||
border-bottom: none;
|
||
}
|
||
.runs-table tr.clickable {
|
||
cursor: pointer;
|
||
}
|
||
.runs-table tr.clickable:hover td {
|
||
background: var(--canvas);
|
||
}
|
||
.mono {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
}
|
||
|
||
/* Instances tab: Running / Past section headers. */
|
||
.instances-section-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin: 22px 0 10px;
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
}
|
||
.instances-section-title .count-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 18px;
|
||
height: 18px;
|
||
padding: 0 5px;
|
||
border-radius: var(--radius-md);
|
||
background: var(--canvas);
|
||
border: 1px solid var(--border);
|
||
font-size: var(--fs-xs);
|
||
font-weight: var(--fw-medium);
|
||
color: var(--text);
|
||
}
|
||
/* Normal-flow wrapper: the shared .runs-table-wrap is position:absolute (it
|
||
fills the experiments panel); in the settings pane the table sits in flow and
|
||
just needs horizontal-scroll safety on narrow viewports. */
|
||
.instances-table-wrap {
|
||
overflow-x: auto;
|
||
}
|
||
/* In-flow empty state (the shared .empty-state is position:absolute and would
|
||
cover the whole pane, overlapping the other section). */
|
||
.instances-empty {
|
||
margin: 0;
|
||
padding: 14px 16px;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
background: var(--base);
|
||
color: var(--subtext);
|
||
font-size: var(--fs-md);
|
||
}
|
||
|
||
/* Badge plus the optional job-page link (HF only — Modal stores no URL). */
|
||
.backend-cell {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
}
|
||
.backend-cell .icon-btn {
|
||
width: 22px;
|
||
height: 22px;
|
||
}
|
||
|
||
/* Backend cell: brand logo + name + flavor detail. */
|
||
.backend-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
}
|
||
.backend-badge svg {
|
||
flex: none;
|
||
display: block;
|
||
}
|
||
.backend-badge .backend-name {
|
||
font-weight: var(--fw-medium);
|
||
}
|
||
.backend-badge .backend-detail {
|
||
color: var(--muted);
|
||
}
|
||
.backend-badge.muted {
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* ---- detail drawer ---- */
|
||
|
||
.drawer {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: var(--base);
|
||
display: flex;
|
||
flex-direction: column;
|
||
z-index: 20;
|
||
}
|
||
|
||
/* No padding-top: it would hold sticky diff-file headers 18px below the top
|
||
with scrolled content peeking through the gap. The first child carries the
|
||
spacing as margin instead, which scrolls away with the content. */
|
||
.drawer-body {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
padding: 0 16px 24px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 22px;
|
||
}
|
||
.drawer-body > :first-child {
|
||
margin-top: 18px;
|
||
}
|
||
|
||
.drawer-section h3 {
|
||
margin: 0 0 10px;
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
}
|
||
|
||
.kv {
|
||
display: grid;
|
||
grid-template-columns: auto 1fr;
|
||
gap: 3px 14px;
|
||
font-size: var(--fs-md);
|
||
}
|
||
.kv .k {
|
||
color: var(--subtext);
|
||
}
|
||
.kv .v {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
word-break: break-all;
|
||
}
|
||
|
||
/* full-pane terminal view (mirror of openresearch.sh ExperimentFullView) */
|
||
.term-view {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: var(--base);
|
||
z-index: 20;
|
||
}
|
||
.term-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
height: 40px;
|
||
padding: 0 10px;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
.term-title {
|
||
min-width: 0;
|
||
font-size: var(--fs-md);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.term-bar .error {
|
||
font-size: var(--fs-sm);
|
||
color: var(--accent-red);
|
||
}
|
||
.term-bar .btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
.term-fill {
|
||
flex: 1;
|
||
min-height: 0;
|
||
background: var(--term-bg);
|
||
padding: 4px 0 4px 6px;
|
||
}
|
||
.term-empty {
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24px;
|
||
text-align: center;
|
||
font-size: var(--fs-md);
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* run history dropdown */
|
||
.run-history {
|
||
position: relative;
|
||
flex-shrink: 0;
|
||
}
|
||
.run-picker {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 4px 6px 4px 10px;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--base);
|
||
color: var(--text);
|
||
}
|
||
.run-picker:hover {
|
||
background: var(--surface);
|
||
}
|
||
.run-picker .run-label {
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-semibold);
|
||
}
|
||
.run-picker-chev {
|
||
color: var(--muted);
|
||
flex-shrink: 0;
|
||
}
|
||
.history-menu {
|
||
position: absolute;
|
||
top: calc(100% + 6px);
|
||
right: 0;
|
||
min-width: 230px;
|
||
max-height: 320px;
|
||
overflow-y: auto;
|
||
background: var(--base);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
|
||
padding: 5px;
|
||
z-index: 50;
|
||
}
|
||
.history-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
text-align: left;
|
||
padding: 6px 8px;
|
||
font-size: var(--fs-sm);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.history-item:hover {
|
||
background: var(--surface);
|
||
}
|
||
.history-item.active {
|
||
background: var(--surface);
|
||
}
|
||
.history-item .run-label {
|
||
font-weight: var(--fw-semibold);
|
||
}
|
||
.history-item .when {
|
||
margin-left: auto;
|
||
font-size: var(--fs-xs);
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* commit picker (Changes tab) */
|
||
.changes-branch {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 10px;
|
||
}
|
||
.commit-picker {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
/* Tiny caption naming what a header control is (Branch / Commit / Source) —
|
||
* two adjacent unlabeled monospace pickers are indistinguishable. */
|
||
.ctl-label {
|
||
flex-shrink: 0;
|
||
font-size: var(--fs-2xs);
|
||
font-weight: var(--fw-medium);
|
||
color: var(--subtext);
|
||
}
|
||
.commit-picker select {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
}
|
||
.uncommitted-dot {
|
||
display: inline-block;
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--accent-purple);
|
||
flex-shrink: 0;
|
||
}
|
||
.changes-note {
|
||
font-size: var(--fs-sm);
|
||
color: var(--muted);
|
||
}
|
||
|
||
.empty-state {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10px;
|
||
padding: 24px;
|
||
text-align: center;
|
||
color: var(--subtext);
|
||
}
|
||
.empty-state p {
|
||
max-width: 46ch;
|
||
margin: 0;
|
||
font-size: var(--fs-md);
|
||
line-height: 1.5;
|
||
text-wrap: balance;
|
||
}
|
||
.empty-state-cta {
|
||
gap: 6px;
|
||
}
|
||
.empty-state p.empty-state-title {
|
||
font-size: var(--fs-2xl);
|
||
font-weight: var(--fw-regular);
|
||
color: var(--text);
|
||
}
|
||
.empty-state p.empty-state-hint {
|
||
font-size: var(--fs-lg);
|
||
color: var(--subtext);
|
||
}
|
||
|
||
/* ---- first-run onboarding (rides the .home canvas) ---- */
|
||
|
||
.onboarding .home-inner {
|
||
max-width: 560px;
|
||
padding-top: 96px;
|
||
}
|
||
.onb-eyebrow {
|
||
font-size: var(--fs-2xs);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--muted);
|
||
margin-bottom: 14px;
|
||
}
|
||
.onb-title {
|
||
margin: 0 0 6px;
|
||
font-size: var(--fs-3xl);
|
||
letter-spacing: -0.01em;
|
||
}
|
||
.onb-sub {
|
||
color: var(--text);
|
||
font-size: var(--fs-base);
|
||
line-height: 1.55;
|
||
margin: 0 0 22px;
|
||
max-width: 480px;
|
||
}
|
||
.onb-cards {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.onb-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 5px;
|
||
background: var(--base);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
padding: 18px 20px;
|
||
}
|
||
.onb-card-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
.onb-card-name {
|
||
font-weight: var(--fw-semibold);
|
||
font-size: var(--fs-base);
|
||
}
|
||
.onb-card-detail {
|
||
font-size: var(--fs-sm);
|
||
}
|
||
.onb-card-meta {
|
||
font-size: var(--fs-sm);
|
||
color: var(--subtext);
|
||
}
|
||
.onb-card-meta code {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-xs);
|
||
}
|
||
.onb-card-row {
|
||
display: grid;
|
||
grid-template-columns: 56px 1fr auto;
|
||
align-items: baseline;
|
||
gap: 12px;
|
||
padding: 3px 0;
|
||
}
|
||
.onb-loading {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: var(--subtext);
|
||
font-size: var(--fs-md);
|
||
padding: 8px 0;
|
||
}
|
||
.onb-gh-options {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
margin-top: 12px;
|
||
padding-top: 14px;
|
||
border-top: 1px solid var(--border-variant);
|
||
}
|
||
.onb-gh-option {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
.onb-gh-option-label {
|
||
font-size: var(--fs-xs);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
}
|
||
.onb-gh-option-body {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
.onb-gh-cmd {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
background: var(--panel);
|
||
border: 1px solid var(--border-variant);
|
||
border-radius: var(--radius-sm);
|
||
padding: 3px 8px;
|
||
}
|
||
.onb-gh-hint {
|
||
font-size: var(--fs-sm);
|
||
color: var(--subtext);
|
||
}
|
||
.onb-gh-hint code {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-xs);
|
||
}
|
||
.onb-gh-or {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
color: var(--muted);
|
||
font-size: var(--fs-xs);
|
||
}
|
||
.onb-gh-or::before,
|
||
.onb-gh-or::after {
|
||
content: "";
|
||
flex: 1;
|
||
height: 1px;
|
||
background: var(--border-variant);
|
||
}
|
||
.onb-gh-option .onb-token-form {
|
||
flex: 1;
|
||
min-width: 0;
|
||
margin-top: 0;
|
||
}
|
||
.onb-token-form {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-top: 8px;
|
||
}
|
||
.onb-token-form input {
|
||
flex: 1;
|
||
min-width: 220px;
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
}
|
||
.onb-token-form a {
|
||
font-size: var(--fs-sm);
|
||
color: var(--subtext);
|
||
white-space: nowrap;
|
||
}
|
||
.onb-token-form .error {
|
||
flex-basis: 100%;
|
||
color: var(--accent-red);
|
||
font-size: var(--fs-md);
|
||
white-space: pre-wrap;
|
||
}
|
||
.onb-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-top: 22px;
|
||
}
|
||
|
||
/* new-project mode switch (existing repo vs blank repo) */
|
||
.form .form-seg {
|
||
align-self: flex-start;
|
||
margin-bottom: 2px;
|
||
}
|
||
.form .form-seg button {
|
||
padding: 5px 12px;
|
||
}
|
||
|
||
/* repo parse feedback under the new-project repository input */
|
||
.form .repo-hint {
|
||
font-family: var(--mono);
|
||
font-weight: var(--fw-regular);
|
||
font-size: var(--fs-xs);
|
||
color: var(--muted);
|
||
}
|
||
.form .repo-hint.ok {
|
||
color: var(--accent-teal);
|
||
}
|
||
|
||
/* new-project "from a paper": search results + selected-paper card */
|
||
.form .paper-results {
|
||
display: flex;
|
||
flex-direction: column;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
max-height: 240px;
|
||
overflow-y: auto;
|
||
}
|
||
.form .paper-results button {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 2px;
|
||
padding: 8px 10px;
|
||
background: none;
|
||
border: none;
|
||
border-bottom: 1px solid var(--border-variant);
|
||
text-align: left;
|
||
font: inherit;
|
||
color: var(--text);
|
||
cursor: pointer;
|
||
}
|
||
.form .paper-results button:last-child {
|
||
border-bottom: none;
|
||
}
|
||
.form .paper-results button:hover {
|
||
background: var(--surface);
|
||
}
|
||
.form .paper-results .title {
|
||
font-size: var(--fs-md);
|
||
font-weight: var(--fw-medium);
|
||
}
|
||
.form .paper-results .id,
|
||
.form .paper-pick .id {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-xs);
|
||
color: var(--muted);
|
||
}
|
||
.form .paper-pick {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
padding: 10px 12px;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--surface);
|
||
}
|
||
.form .paper-pick .meta {
|
||
min-width: 0;
|
||
}
|
||
.form .paper-pick .title {
|
||
font-size: var(--fs-md);
|
||
font-weight: var(--fw-semibold);
|
||
}
|
||
|
||
/* ---- forms / modal ---- */
|
||
|
||
.form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.form label {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
font-size: var(--fs-xs);
|
||
color: var(--text);
|
||
font-weight: var(--fw-medium);
|
||
}
|
||
.form .row2 {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 10px;
|
||
}
|
||
.form .actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
margin-top: 6px;
|
||
}
|
||
.form .error,
|
||
.settings-card > .error {
|
||
color: var(--accent-red);
|
||
font-size: var(--fs-md);
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
.modal-backdrop {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(29, 27, 26, 0.4);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 100;
|
||
}
|
||
.modal {
|
||
width: 480px;
|
||
max-width: 94vw;
|
||
max-height: 88vh;
|
||
overflow-y: auto;
|
||
background: var(--base);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-xl);
|
||
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.22);
|
||
padding: 24px;
|
||
}
|
||
.modal h2 {
|
||
margin: 0 0 14px;
|
||
font-size: var(--fs-xl);
|
||
}
|
||
|
||
/* ---- settings ---- */
|
||
|
||
.settings-section + .settings-section {
|
||
margin-top: 18px;
|
||
padding-top: 16px;
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
|
||
.settings-section h3 {
|
||
margin: 0 0 8px;
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
}
|
||
|
||
.settings-loading {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: var(--subtext);
|
||
font-size: var(--fs-md);
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.settings-note {
|
||
margin: 10px 0 0;
|
||
font-size: var(--fs-sm);
|
||
padding: 8px 10px;
|
||
border: 1px solid var(--accent-amber);
|
||
border-radius: var(--radius-md);
|
||
background: var(--accent-amber-subtle);
|
||
color: var(--accent-amber);
|
||
font-weight: var(--fw-medium);
|
||
}
|
||
|
||
/* Determinate progress bar (data-dir move). */
|
||
.progress {
|
||
margin: 12px 0 4px;
|
||
}
|
||
.progress-track {
|
||
height: 8px;
|
||
border-radius: var(--radius-full);
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
overflow: hidden;
|
||
}
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: var(--accent);
|
||
border-radius: var(--radius-full);
|
||
transition: width 0.2s ease;
|
||
}
|
||
.progress-caption {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 6px;
|
||
font-size: var(--fs-sm);
|
||
color: var(--muted);
|
||
}
|
||
|
||
.settings-form {
|
||
margin-top: 14px;
|
||
padding-top: 14px;
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
|
||
.badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
/* Explicit sans so badges nested in mono contexts (.kv .v) don't inherit. */
|
||
font-family: var(--sans);
|
||
font-size: var(--fs-xs);
|
||
font-weight: var(--fw-medium);
|
||
padding: 1px 7px;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-sm);
|
||
color: var(--text);
|
||
}
|
||
.badge.ok {
|
||
color: var(--accent-green);
|
||
border-color: var(--accent-green);
|
||
background: var(--accent-green-subtle);
|
||
}
|
||
.badge.err {
|
||
color: var(--accent-red);
|
||
border-color: var(--accent-red);
|
||
background: var(--accent-red-subtle);
|
||
}
|
||
.badge.warn {
|
||
color: var(--accent-amber);
|
||
border-color: var(--accent-amber);
|
||
background: var(--accent-amber-subtle);
|
||
}
|
||
|
||
/* ---- files tab ---- */
|
||
|
||
/* ---- Files tab: split explorer — tree on the left, inline preview right ---- */
|
||
.files-tab {
|
||
height: 100%;
|
||
min-height: 0;
|
||
display: flex;
|
||
background: var(--base);
|
||
}
|
||
.files-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
padding: 7px 11px;
|
||
background: var(--base);
|
||
color: var(--text);
|
||
text-decoration: none;
|
||
}
|
||
.files-pill code {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
/* Direct children only — the copy .icon-btn manages its own icon color. */
|
||
.files-pill > svg {
|
||
flex-shrink: 0;
|
||
color: var(--muted);
|
||
}
|
||
a.files-pill:hover {
|
||
border-color: var(--muted);
|
||
}
|
||
|
||
/* Left pane: the file tree, with the dir-path footer pinned under it.
|
||
Shares the preview's base background — one surface, split by a hairline —
|
||
so it reads as a file browser, not a stray colored strip. */
|
||
.ftree-pane {
|
||
position: relative;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
border-left: 1px solid var(--border-variant);
|
||
border-right: 1px solid var(--border-variant);
|
||
background: var(--base);
|
||
}
|
||
/* Invisible grab strip straddling the tree/preview divider. */
|
||
.ftree-resizer {
|
||
position: absolute;
|
||
right: -3px;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 6px;
|
||
cursor: col-resize;
|
||
z-index: 30;
|
||
}
|
||
.ftree-resizer:hover,
|
||
.ftree-resizer:active {
|
||
background: color-mix(in oklab, var(--text) 12%, transparent);
|
||
}
|
||
.ftree-scroll {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
padding: 10px 8px;
|
||
}
|
||
.ft-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 5px 8px;
|
||
border-radius: var(--radius-sm);
|
||
cursor: pointer;
|
||
}
|
||
.ft-row:hover {
|
||
background: var(--surface);
|
||
}
|
||
.ft-row.selected,
|
||
.ft-row.selected:hover {
|
||
background: var(--panel);
|
||
}
|
||
.ft-chevron {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 14px;
|
||
height: 14px;
|
||
flex-shrink: 0;
|
||
color: var(--muted);
|
||
transition: transform 0.12s ease;
|
||
}
|
||
.ft-chevron.open {
|
||
transform: rotate(90deg);
|
||
}
|
||
.ft-chevron.spacer {
|
||
visibility: hidden;
|
||
}
|
||
.ft-title {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-size: var(--fs-md);
|
||
font-weight: var(--fw-medium);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.ft-dirname,
|
||
.ft-name {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.ft-dirname {
|
||
color: var(--text);
|
||
}
|
||
.ft-thumb {
|
||
width: 18px;
|
||
height: 18px;
|
||
object-fit: cover;
|
||
border-radius: var(--radius-xs);
|
||
border: 1px solid var(--border-variant);
|
||
flex-shrink: 0;
|
||
}
|
||
.ft-divider {
|
||
padding: 12px 8px 4px;
|
||
font-size: var(--fs-xs);
|
||
font-weight: var(--fw-medium);
|
||
color: var(--muted);
|
||
}
|
||
/* The files-dir path — demoted from header pills to a quiet footer. */
|
||
.ftree-footer {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
padding: 5px 8px;
|
||
border-top: 1px solid var(--border-variant);
|
||
}
|
||
.ftree-footer code {
|
||
flex: 1;
|
||
min-width: 0;
|
||
/* RTL ellipsis trick: keep the tail of the path visible, elide the front. */
|
||
direction: rtl;
|
||
text-align: left;
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-xs);
|
||
color: var(--muted);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.ftree-footer .icon-btn {
|
||
width: 22px;
|
||
height: 22px;
|
||
}
|
||
|
||
/* Right pane: the selected entry, rendered inline. */
|
||
.fpreview {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
background: var(--base);
|
||
}
|
||
.fpreview-none {
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
font-size: var(--fs-md);
|
||
color: var(--muted);
|
||
}
|
||
.fpreview-head {
|
||
height: 40px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 0 14px;
|
||
border-bottom: 1px solid var(--border-variant);
|
||
color: var(--subtext);
|
||
flex-shrink: 0;
|
||
}
|
||
.fpreview-path {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
color: var(--text);
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.fpreview-date,
|
||
.fpreview-size {
|
||
font-size: var(--fs-xs);
|
||
color: var(--muted);
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
.fpreview-body {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: auto;
|
||
}
|
||
/* Documents (reports, markdown) read as a centered readable column. */
|
||
.fpreview-body.doc {
|
||
padding: 18px 28px 48px;
|
||
}
|
||
.fpreview-body.doc .report-md {
|
||
max-width: var(--readable-col);
|
||
margin: 0 auto;
|
||
}
|
||
.fpreview-image {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: center;
|
||
padding: 24px;
|
||
}
|
||
.fpreview-image img {
|
||
max-width: 100%;
|
||
height: auto;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.fpreview-pdf {
|
||
display: block;
|
||
width: 100%;
|
||
height: 100%;
|
||
border: 0;
|
||
}
|
||
|
||
/* Whole-tab empty state: what this tab is for, plus the drop-in path. */
|
||
.files-empty-state {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
padding: 24px;
|
||
text-align: center;
|
||
color: var(--muted);
|
||
}
|
||
.files-empty-state h3 {
|
||
margin: 6px 0 0;
|
||
font-size: var(--fs-base);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
}
|
||
.files-empty-state p {
|
||
margin: 0;
|
||
max-width: 420px;
|
||
font-size: var(--fs-md);
|
||
line-height: 1.55;
|
||
color: var(--subtext);
|
||
}
|
||
.files-empty-state .ftree-footer {
|
||
margin-top: 10px;
|
||
max-width: 100%;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
padding: 6px 10px;
|
||
background: var(--base);
|
||
}
|
||
.files-empty-state .ftree-footer code {
|
||
max-width: 380px;
|
||
}
|
||
.files-truncated {
|
||
margin: 0;
|
||
padding: 8px 14px;
|
||
font-size: var(--fs-xs);
|
||
color: var(--muted);
|
||
}
|
||
/* Reports are long-form documents, so preserve their Markdown hierarchy.
|
||
The shared .md rules intentionally keep chat headings compact. */
|
||
.report-md {
|
||
font-size: var(--fs-lg);
|
||
}
|
||
.report-md h1 {
|
||
font-size: 2em;
|
||
line-height: 1.18;
|
||
margin: 28px 0 14px;
|
||
}
|
||
.report-md h2 {
|
||
font-size: 1.5em;
|
||
line-height: 1.25;
|
||
margin: 28px 0 10px;
|
||
}
|
||
.report-md h3 {
|
||
font-size: 1.2em;
|
||
line-height: 1.35;
|
||
margin: 22px 0 8px;
|
||
}
|
||
.report-md h4 {
|
||
font-size: 1em;
|
||
line-height: 1.4;
|
||
margin: 18px 0 6px;
|
||
}
|
||
/* Wide GFM tables scroll in place instead of widening the report pane. */
|
||
.report-md table {
|
||
display: block;
|
||
width: max-content;
|
||
max-width: 100%;
|
||
overflow-x: auto;
|
||
}
|
||
.report-md .report-img {
|
||
display: block;
|
||
margin: 12px 0;
|
||
}
|
||
.report-md .report-img img {
|
||
max-width: 100%;
|
||
height: auto;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.report-md .report-img-caption {
|
||
display: block;
|
||
margin-top: 4px;
|
||
text-align: center;
|
||
font-size: var(--fs-sm);
|
||
color: var(--subtext);
|
||
}
|
||
|
||
/* ---- diff view (react-diff-view; mirrors .openresearch-diff) ---- */
|
||
|
||
.openresearch-diff {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.diff-file-card {
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--base);
|
||
}
|
||
/* The header owns the card's rounded top outline (overlaid on the card
|
||
border via -1px margins) so it still looks like a rounded card top while
|
||
stuck — the card's own border scrolls away with the card. Painted via two
|
||
layered pseudo-elements because negative-z children cover the element's own
|
||
background/border: a square white mask below (hides code scrolling past the
|
||
corner notches), the rounded bordered surface above it, text on top. */
|
||
.diff-file-header {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin: -1px -1px 0;
|
||
width: calc(100% + 2px);
|
||
text-align: left;
|
||
padding: 8px 12px;
|
||
cursor: pointer;
|
||
}
|
||
.diff-file-header::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: -2;
|
||
background: var(--base);
|
||
}
|
||
.diff-file-header::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: -1;
|
||
background: var(--canvas);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md) var(--radius-md) 0 0;
|
||
}
|
||
.diff-file-header .chev {
|
||
color: var(--muted);
|
||
font-size: var(--fs-2xs);
|
||
flex-shrink: 0;
|
||
width: 12px;
|
||
}
|
||
.diff-file-header .path {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
.diff-file-header .path code {
|
||
min-width: 0;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-xs);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
}
|
||
.diff-file-header .stats {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-shrink: 0;
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-2xs);
|
||
font-weight: var(--fw-medium);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.diff-stat-add {
|
||
color: var(--accent-green);
|
||
}
|
||
.diff-stat-del {
|
||
color: var(--accent-red);
|
||
}
|
||
.diff-empty {
|
||
padding: 8px 12px;
|
||
color: var(--muted);
|
||
font-size: var(--fs-md);
|
||
}
|
||
|
||
.truncated-notice {
|
||
border: 1px solid var(--accent-amber);
|
||
border-radius: var(--radius-md);
|
||
background: var(--accent-amber-subtle);
|
||
padding: 12px 14px;
|
||
font-size: var(--fs-md);
|
||
}
|
||
.truncated-notice h4 {
|
||
margin: 0 0 4px;
|
||
font-size: var(--fs-md);
|
||
color: var(--accent-amber);
|
||
}
|
||
.truncated-notice p {
|
||
margin: 0;
|
||
color: var(--subtext);
|
||
}
|
||
|
||
.openresearch-diff .openresearch-diff-file {
|
||
--openresearch-diff-selection-background-color: color-mix(
|
||
in oklab,
|
||
var(--surface) 76%,
|
||
var(--primary)
|
||
);
|
||
--openresearch-diff-gutter-selection-background-color: color-mix(
|
||
in oklab,
|
||
var(--surface) 68%,
|
||
var(--primary)
|
||
);
|
||
/* GitHub-strength washes: the tint should read as a highlight, not paint. */
|
||
--openresearch-diff-insert-gutter-background-color: color-mix(
|
||
in oklab,
|
||
var(--base) 84%,
|
||
var(--accent-green)
|
||
);
|
||
--openresearch-diff-delete-gutter-background-color: color-mix(
|
||
in oklab,
|
||
var(--base) 86%,
|
||
var(--accent-red)
|
||
);
|
||
--openresearch-diff-insert-code-background-color: color-mix(
|
||
in oklab,
|
||
var(--base) 91%,
|
||
var(--accent-green)
|
||
);
|
||
--openresearch-diff-delete-code-background-color: color-mix(
|
||
in oklab,
|
||
var(--base) 92%,
|
||
var(--accent-red)
|
||
);
|
||
--openresearch-diff-insert-edit-background-color: color-mix(
|
||
in oklab,
|
||
var(--base) 72%,
|
||
var(--accent-green)
|
||
);
|
||
--openresearch-diff-delete-edit-background-color: color-mix(
|
||
in oklab,
|
||
var(--base) 78%,
|
||
var(--accent-red)
|
||
);
|
||
--openresearch-diff-divider-color: var(--border);
|
||
--openresearch-diff-omit-gutter-line-color: color-mix(in oklab, var(--base) 86%, var(--text));
|
||
--openresearch-diff-unified-gutter-text-color: color-mix(in oklab, var(--text) 45%, var(--base));
|
||
|
||
--diff-background-color: var(--base);
|
||
--diff-text-color: var(--text);
|
||
--diff-font-family: var(--mono);
|
||
--diff-selection-text-color: var(--primary);
|
||
--diff-selection-background-color: var(--openresearch-diff-selection-background-color);
|
||
--diff-gutter-selected-text-color: var(--diff-selection-text-color);
|
||
--diff-gutter-selected-background-color: var(
|
||
--openresearch-diff-gutter-selection-background-color
|
||
);
|
||
--diff-code-selected-text-color: var(--diff-selection-text-color);
|
||
--diff-code-selected-background-color: var(--diff-selection-background-color);
|
||
--diff-gutter-insert-text-color: var(--accent-green);
|
||
--diff-gutter-insert-background-color: var(--openresearch-diff-insert-gutter-background-color);
|
||
--diff-gutter-delete-text-color: var(--accent-red);
|
||
--diff-gutter-delete-background-color: var(--openresearch-diff-delete-gutter-background-color);
|
||
--diff-code-insert-text-color: var(--diff-text-color);
|
||
--diff-code-insert-background-color: var(--openresearch-diff-insert-code-background-color);
|
||
--diff-code-delete-text-color: var(--diff-text-color);
|
||
--diff-code-delete-background-color: var(--openresearch-diff-delete-code-background-color);
|
||
--diff-code-insert-edit-text-color: var(--diff-text-color);
|
||
--diff-code-insert-edit-background-color: var(--openresearch-diff-insert-edit-background-color);
|
||
--diff-code-delete-edit-text-color: var(--diff-text-color);
|
||
--diff-code-delete-edit-background-color: var(--openresearch-diff-delete-edit-background-color);
|
||
--diff-omit-gutter-line-color: var(--openresearch-diff-omit-gutter-line-color);
|
||
|
||
width: 100%;
|
||
font-size: var(--fs-sm);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.openresearch-diff .openresearch-diff-file.diff-unified {
|
||
table-layout: auto;
|
||
}
|
||
.openresearch-diff .openresearch-diff-file.diff-unified .diff-line > td:first-child {
|
||
display: none;
|
||
}
|
||
.openresearch-diff .openresearch-diff-file.diff-unified .diff-line > td:nth-child(2) {
|
||
width: 0%;
|
||
padding: 0 0.5rem 0 0.625rem;
|
||
white-space: nowrap;
|
||
text-align: right;
|
||
color: var(--openresearch-diff-unified-gutter-text-color);
|
||
border-right: 1px solid var(--openresearch-diff-divider-color);
|
||
user-select: none;
|
||
cursor: default;
|
||
}
|
||
.openresearch-diff .openresearch-diff-file .diff-line {
|
||
line-height: 1.5;
|
||
}
|
||
.openresearch-diff .openresearch-diff-file .diff-hunk + .diff-hunk .diff-line:first-child > td {
|
||
border-top: 1px solid var(--openresearch-diff-divider-color);
|
||
}
|
||
|
||
/* prism token colors (One Light / One Dark, same hexes as openresearch.sh) */
|
||
/* prettier-ignore */
|
||
:is(.md, .openresearch-diff, .file-view) .token.comment,
|
||
:is(.md, .openresearch-diff, .file-view) .token.prolog,
|
||
:is(.md, .openresearch-diff, .file-view) .token.cdata { font-style: italic; color: #a0a1a7; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.punctuation { color: #383a42; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.property,
|
||
:is(.md, .openresearch-diff, .file-view) .token.tag,
|
||
:is(.md, .openresearch-diff, .file-view) .token.deleted { color: #e45649; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.constant,
|
||
:is(.md, .openresearch-diff, .file-view) .token.symbol,
|
||
:is(.md, .openresearch-diff, .file-view) .token.boolean,
|
||
:is(.md, .openresearch-diff, .file-view) .token.number { color: #986801; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.selector,
|
||
:is(.md, .openresearch-diff, .file-view) .token.attr-name,
|
||
:is(.md, .openresearch-diff, .file-view) .token.char,
|
||
:is(.md, .openresearch-diff, .file-view) .token.inserted,
|
||
:is(.md, .openresearch-diff, .file-view) .token.string { color: #50a14f; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.builtin { color: #c18401; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.operator,
|
||
:is(.md, .openresearch-diff, .file-view) .token.entity,
|
||
:is(.md, .openresearch-diff, .file-view) .token.url { color: #56b6c2; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.atrule,
|
||
:is(.md, .openresearch-diff, .file-view) .token.attr-value { color: #986801; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.keyword { color: #a626a4; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.function,
|
||
:is(.md, .openresearch-diff, .file-view) .token.decorator,
|
||
:is(.md, .openresearch-diff, .file-view) .token.def { color: #4078f2; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.class-name,
|
||
:is(.md, .openresearch-diff, .file-view) .token.namespace { color: #c18401; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.regex { color: #50a14f; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.important,
|
||
:is(.md, .openresearch-diff, .file-view) .token.variable { color: #e45649; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.parameter { color: #383a42; }
|
||
|
||
@media (prefers-color-scheme: dark) {
|
||
:is(.md, .openresearch-diff, .file-view) .token.comment,
|
||
:is(.md, .openresearch-diff, .file-view) .token.prolog,
|
||
:is(.md, .openresearch-diff, .file-view) .token.cdata { font-style: italic; color: #7f848e; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.punctuation { color: #abb2bf; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.property,
|
||
:is(.md, .openresearch-diff, .file-view) .token.tag,
|
||
:is(.md, .openresearch-diff, .file-view) .token.deleted { color: #e06c75; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.constant,
|
||
:is(.md, .openresearch-diff, .file-view) .token.symbol,
|
||
:is(.md, .openresearch-diff, .file-view) .token.boolean,
|
||
:is(.md, .openresearch-diff, .file-view) .token.number { color: #d19a66; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.selector,
|
||
:is(.md, .openresearch-diff, .file-view) .token.attr-name,
|
||
:is(.md, .openresearch-diff, .file-view) .token.char,
|
||
:is(.md, .openresearch-diff, .file-view) .token.inserted,
|
||
:is(.md, .openresearch-diff, .file-view) .token.string { color: #98c379; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.builtin { color: #e5c07b; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.operator,
|
||
:is(.md, .openresearch-diff, .file-view) .token.entity,
|
||
:is(.md, .openresearch-diff, .file-view) .token.url { color: #56b6c2; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.atrule,
|
||
:is(.md, .openresearch-diff, .file-view) .token.attr-value { color: #d19a66; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.keyword { color: #c678dd; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.function,
|
||
:is(.md, .openresearch-diff, .file-view) .token.decorator,
|
||
:is(.md, .openresearch-diff, .file-view) .token.def { color: #61afef; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.class-name,
|
||
:is(.md, .openresearch-diff, .file-view) .token.namespace { color: #e5c07b; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.regex { color: #98c379; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.important,
|
||
:is(.md, .openresearch-diff, .file-view) .token.variable { color: #e06c75; }
|
||
:is(.md, .openresearch-diff, .file-view) .token.parameter { color: #abb2bf; }
|
||
}
|
||
|
||
/* ---- settings sections (shown in the middle pane, picked from the rail) ---- */
|
||
|
||
/* Like .chat-thread: the scroller spans the pane so its scrollbar sits at the
|
||
pane's right edge, and .settings-view re-applies the readable width. */
|
||
.settings-view-scroll {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
scrollbar-gutter: stable both-edges;
|
||
}
|
||
.settings-view {
|
||
max-width: var(--readable-col);
|
||
margin: 0 auto;
|
||
padding: 24px 32px 60px;
|
||
}
|
||
.settings-view h1 {
|
||
margin: 0 0 6px;
|
||
font-size: var(--fs-3xl);
|
||
}
|
||
.settings-head-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
}
|
||
.settings-head-row h1 {
|
||
margin: 0;
|
||
}
|
||
/* Errors shown directly in a settings tab (outside a .form wrapper), e.g. the
|
||
Instances load error. Mirrors `.form .error`. */
|
||
.settings-view > .error {
|
||
color: var(--accent-red);
|
||
font-size: var(--fs-md);
|
||
white-space: pre-wrap;
|
||
margin: 0 0 12px;
|
||
}
|
||
.settings-sub {
|
||
margin: 0 0 18px;
|
||
color: var(--text);
|
||
font-size: var(--fs-md);
|
||
}
|
||
.settings-card {
|
||
background: var(--base);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
padding: 16px 18px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.settings-card h3 {
|
||
margin: 0 0 10px;
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
}
|
||
.settings-card .settings-sub {
|
||
margin-bottom: 12px;
|
||
}
|
||
.settings-card-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.settings-card .kv {
|
||
gap: 6px 18px;
|
||
}
|
||
.settings-empty {
|
||
color: var(--muted);
|
||
font-size: var(--fs-md);
|
||
margin: 4px 0 0;
|
||
}
|
||
.muted {
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* compute target list (Settings → Compute) */
|
||
.compute-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
margin-bottom: 14px;
|
||
}
|
||
.compute-row {
|
||
background: var(--base);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
}
|
||
.compute-row-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 12px 14px;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
.compute-row-head:hover {
|
||
background: var(--surface);
|
||
border-radius: var(--radius-lg);
|
||
}
|
||
.compute-row.open .compute-row-head:hover {
|
||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||
}
|
||
.compute-row-logo {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
flex: none;
|
||
}
|
||
.compute-row-name {
|
||
font-size: var(--fs-md);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
flex: none;
|
||
}
|
||
.compute-row-summary {
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
color: var(--muted);
|
||
font-size: var(--fs-sm);
|
||
}
|
||
.compute-row-head .badge,
|
||
.compute-make-default,
|
||
.compute-default-pill {
|
||
flex: none;
|
||
}
|
||
/* Rendered as `badge compute-default-pill` — only the accent color differs. */
|
||
.compute-default-pill {
|
||
color: var(--primary);
|
||
border-color: var(--primary);
|
||
}
|
||
.compute-chevron-btn {
|
||
flex: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 2px;
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.compute-chevron-btn:hover {
|
||
background: var(--panel);
|
||
}
|
||
.compute-chevron {
|
||
color: var(--muted);
|
||
transition: transform 120ms ease;
|
||
}
|
||
.compute-row.open .compute-chevron {
|
||
transform: rotate(180deg);
|
||
}
|
||
.compute-row-body {
|
||
border-top: 1px solid var(--border);
|
||
padding: 14px;
|
||
}
|
||
.compute-row-body .settings-card {
|
||
margin-bottom: 0;
|
||
margin-top: 14px;
|
||
}
|
||
.compute-default-note {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.compute-flavor-form {
|
||
margin-bottom: 14px;
|
||
}
|
||
.compute-flavor-form label {
|
||
max-width: 320px;
|
||
}
|
||
.compute-flavor-hint {
|
||
font-size: var(--fs-sm);
|
||
}
|
||
/* Standing disclosure, not a warning — quiet muted text, no amber callout. */
|
||
.compute-footnote {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 6px;
|
||
margin: 2px 0 0;
|
||
font-size: var(--fs-sm);
|
||
color: var(--muted);
|
||
}
|
||
.compute-footnote svg {
|
||
flex: none;
|
||
margin-top: 1px; /* optical align with the first text line */
|
||
}
|
||
|
||
/* harness sub-tabs */
|
||
.harness-tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
margin-bottom: 14px;
|
||
border-bottom: 1px solid var(--border-variant);
|
||
}
|
||
.harness-tabs button {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
padding: 7px 12px;
|
||
font-size: var(--fs-md);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
border-bottom: 2px solid transparent;
|
||
margin-bottom: -1px;
|
||
}
|
||
.harness-tabs button:hover {
|
||
color: var(--text);
|
||
}
|
||
.harness-tabs button.active {
|
||
border-bottom-color: var(--primary);
|
||
}
|
||
.harness-dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 50%;
|
||
background: var(--muted);
|
||
}
|
||
.harness-dot.ok {
|
||
background: var(--accent-green);
|
||
}
|
||
.harness-dot.err {
|
||
background: var(--accent-red);
|
||
}
|
||
.harness-dot.warn {
|
||
background: var(--accent-amber);
|
||
}
|
||
|
||
/* env var table — fixed layout so buttons appearing never reflow columns */
|
||
.env-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: var(--fs-md);
|
||
table-layout: fixed;
|
||
}
|
||
.env-table td:first-child {
|
||
width: 32%;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
.env-table .badge {
|
||
margin-left: 8px;
|
||
}
|
||
/* inline edit/add inputs read as table cells, not boxes; the underline is the
|
||
focus indicator the removed border would otherwise provide */
|
||
.env-table input {
|
||
width: 100%;
|
||
border: none;
|
||
background: transparent;
|
||
padding: 0;
|
||
}
|
||
.env-table input:focus {
|
||
box-shadow: 0 1px 0 0 var(--text);
|
||
}
|
||
/* fixed row height: inline inputs and buttons appear without shifting rows */
|
||
.env-table td {
|
||
height: 36px;
|
||
padding: 0 10px 0 0;
|
||
vertical-align: middle;
|
||
border-bottom: 1px solid var(--border-variant);
|
||
}
|
||
.env-table td:last-child {
|
||
width: 116px;
|
||
white-space: nowrap;
|
||
text-align: right;
|
||
}
|
||
/* a full-width colspan cell (hint row) is also :last-child — undo the
|
||
action-column styling so the note wraps and aligns normally */
|
||
.env-table td[colspan] {
|
||
white-space: normal;
|
||
text-align: left;
|
||
}
|
||
.env-table .icon-btn {
|
||
margin-left: 8px;
|
||
vertical-align: middle;
|
||
}
|
||
.env-table .icon-btn:hover {
|
||
color: var(--accent-red);
|
||
}
|
||
|
||
/* k8s flavor table (Settings → Compute) */
|
||
.flavor-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: var(--fs-md);
|
||
}
|
||
.flavor-table th {
|
||
padding: 5px 10px 5px 0;
|
||
border-bottom: 1px solid var(--border);
|
||
text-align: left;
|
||
font-weight: var(--fw-medium);
|
||
color: var(--text);
|
||
}
|
||
.flavor-table td {
|
||
padding: 5px 10px 5px 0;
|
||
border-bottom: 1px solid var(--border-variant);
|
||
}
|
||
|
||
/* ssh hosts table — fixed layout with anywhere-wrapping so long aliases and
|
||
key paths can never push the action column outside the card. Widths track
|
||
the column order in SshSection: host, address, identity (flex), status,
|
||
test button. */
|
||
.ssh-table {
|
||
table-layout: fixed;
|
||
}
|
||
.ssh-table th:nth-child(1) {
|
||
width: 20%; /* host */
|
||
}
|
||
.ssh-table th:nth-child(2) {
|
||
width: 26%; /* address */
|
||
}
|
||
.ssh-table th:nth-child(4) {
|
||
width: 108px; /* status */
|
||
}
|
||
.ssh-table th:nth-child(5) {
|
||
width: 52px; /* test button */
|
||
}
|
||
.ssh-table td {
|
||
overflow-wrap: anywhere;
|
||
}
|
||
.ssh-table td:last-child {
|
||
padding-right: 0;
|
||
text-align: right;
|
||
}
|
||
.ssh-tested-at {
|
||
display: block;
|
||
margin-top: 2px;
|
||
color: var(--muted);
|
||
font-size: var(--fs-xs);
|
||
}
|
||
|
||
@keyframes settings-spin {
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
.spin {
|
||
animation: settings-spin 0.9s linear infinite;
|
||
}
|
||
|
||
/* ---- composer model picker ---- */
|
||
|
||
.model-picker,
|
||
.option-picker {
|
||
position: relative;
|
||
display: inline-flex;
|
||
}
|
||
/* Boxed composer control (permission mode, model). */
|
||
.composer-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
font-size: var(--fs-sm);
|
||
color: var(--text);
|
||
padding: 5px 8px;
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.composer-pill:hover {
|
||
background: var(--surface);
|
||
color: var(--text);
|
||
}
|
||
/* Text-only composer control (reasoning level) — sits beside the model label. */
|
||
.composer-bare {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
font-size: var(--fs-sm);
|
||
color: var(--text);
|
||
padding: 5px 4px;
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.composer-bare:hover {
|
||
color: var(--text);
|
||
}
|
||
.model-menu,
|
||
.option-menu {
|
||
position: absolute;
|
||
bottom: calc(100% + 8px);
|
||
left: 0;
|
||
max-height: 380px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: var(--base);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
|
||
z-index: 50;
|
||
overflow: hidden;
|
||
}
|
||
.model-menu {
|
||
width: 320px;
|
||
}
|
||
.option-menu {
|
||
min-width: 190px;
|
||
padding: 6px;
|
||
}
|
||
/* Context-window meter: ring trigger + title/value head over a thin bar. */
|
||
.composer-bare.context-ring {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
margin-right: 8px;
|
||
}
|
||
.context-meter-menu {
|
||
width: 280px;
|
||
padding: 10px 12px 12px;
|
||
}
|
||
.context-meter-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: baseline;
|
||
gap: 12px;
|
||
font-size: var(--fs-sm);
|
||
color: var(--muted);
|
||
}
|
||
.context-meter-value {
|
||
color: var(--text);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.context-meter-menu .progress {
|
||
margin: 8px 0 0;
|
||
}
|
||
.context-meter-menu .progress-track {
|
||
height: 5px;
|
||
border: none;
|
||
background: var(--border);
|
||
}
|
||
/* Right-anchored menus (bottom-right controls) open toward the left edge. */
|
||
.model-menu.align-right,
|
||
.option-menu.align-right {
|
||
left: auto;
|
||
right: 0;
|
||
}
|
||
/* Rail menus open downward (composer menus open upward, above the box). */
|
||
.option-menu.drop-down {
|
||
bottom: auto;
|
||
top: calc(100% + 4px);
|
||
}
|
||
/* Session kebab menu: anchored to the row, hugging its right edge. Absolute
|
||
inside the scrolling rail, so near the list's bottom it extends the scroll
|
||
area instead of overlaying — acceptable while lists stay short; a portal or
|
||
upward flip is the fix if that changes. */
|
||
.option-menu.session-menu {
|
||
left: auto;
|
||
right: 6px;
|
||
top: calc(100% - 2px);
|
||
min-width: 140px;
|
||
}
|
||
.model-item.danger,
|
||
.model-item.danger:hover {
|
||
color: var(--accent-red);
|
||
}
|
||
/* "· Default" note on the pinned default row. */
|
||
.option-default {
|
||
color: var(--muted);
|
||
font-weight: var(--fw-regular);
|
||
}
|
||
/* Separator under the default row. */
|
||
.option-sep {
|
||
height: 1px;
|
||
margin: 5px 4px;
|
||
background: var(--border-variant);
|
||
}
|
||
/* Right-aligned 1-based number hints in the mode menu. */
|
||
.option-num {
|
||
color: var(--muted);
|
||
font-size: var(--fs-xs);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.model-menu input {
|
||
border: none;
|
||
border-bottom: 1px solid var(--border-variant);
|
||
background: none;
|
||
padding: 9px 12px;
|
||
font-size: var(--fs-md);
|
||
outline: none;
|
||
}
|
||
.model-menu-list {
|
||
overflow-y: auto;
|
||
padding: 6px;
|
||
}
|
||
.model-group {
|
||
font-size: var(--fs-xs);
|
||
font-weight: var(--fw-medium);
|
||
color: var(--muted);
|
||
padding: 8px 8px 4px;
|
||
}
|
||
.model-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
width: 100%;
|
||
text-align: left;
|
||
padding: 6px 8px;
|
||
font-size: var(--fs-md);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.model-item:hover {
|
||
background: var(--surface);
|
||
}
|
||
.model-item .model-id {
|
||
display: block;
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-2xs);
|
||
color: var(--muted);
|
||
}
|
||
.model-more {
|
||
padding: 4px 8px 8px;
|
||
font-size: var(--fs-xs);
|
||
color: var(--muted);
|
||
}
|
||
/* Mid-session footer: the harness is fixed, so only its models are offered. */
|
||
.model-locked-note {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 7px 12px;
|
||
font-size: var(--fs-xs);
|
||
color: var(--muted);
|
||
border-top: 1px solid var(--border-variant);
|
||
}
|
||
.model-locked-note svg {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* ---- composer slash-skill menu ---- */
|
||
|
||
.skill-menu {
|
||
position: absolute;
|
||
bottom: calc(100% + 8px);
|
||
left: 0;
|
||
min-width: 340px;
|
||
max-width: 100%;
|
||
padding: 6px;
|
||
background: var(--base);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
|
||
z-index: 50;
|
||
overflow: hidden;
|
||
}
|
||
.skill-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
width: 100%;
|
||
text-align: left;
|
||
padding: 7px 8px;
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
.skill-item.active {
|
||
background: var(--surface);
|
||
}
|
||
.skill-item .skill-name {
|
||
font-size: var(--fs-sm);
|
||
}
|
||
.skill-item .skill-hint {
|
||
color: var(--muted);
|
||
}
|
||
.skill-item .skill-desc {
|
||
font-size: var(--fs-xs);
|
||
color: var(--subtext);
|
||
}
|
||
|
||
/* Command chip — the picked slash-skill, rendered as a unit in the composer
|
||
(removable) and echoed in sent user bubbles (inert). */
|
||
.skill-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 1px 7px;
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-medium);
|
||
color: var(--primary);
|
||
background: var(--primary-subtle);
|
||
border: 1px solid var(--border-variant);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
/* The chip overlays the textarea's first line, Claude-desktop style; the
|
||
textarea's first line is indented past it (inline text-indent, measured
|
||
from the chip's width), so wrapped args flow full-width beneath the chip. */
|
||
.composer-input {
|
||
position: relative;
|
||
display: flex;
|
||
/* Clips the chip once the textarea scrolls it out of the first line. */
|
||
overflow: hidden;
|
||
}
|
||
.composer-input textarea {
|
||
flex: 1;
|
||
}
|
||
.composer-chip {
|
||
position: absolute;
|
||
/* Sit on the textarea's first text line (10px top padding, --fs-md text)
|
||
despite the chip's smaller --fs-sm body. */
|
||
top: 9px;
|
||
left: 12px;
|
||
z-index: 1;
|
||
/* Inert like inline text — clicks land in the textarea underneath. */
|
||
pointer-events: none;
|
||
}
|
||
.msg-user .skill-chip {
|
||
margin-right: 2px;
|
||
vertical-align: baseline;
|
||
}
|
||
|
||
/* --- file viewer (right-pane tab opened from chat tool rows) ------------- */
|
||
|
||
|
||
.file-view {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
min-height: 0;
|
||
}
|
||
.file-view-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 4px 12px;
|
||
border-bottom: 1px solid var(--border-variant);
|
||
color: var(--text);
|
||
flex-shrink: 0;
|
||
}
|
||
.file-view-path {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
color: var(--text);
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
/* Branch badge on committed-state file views (code browser in branch mode) —
|
||
a plain source label, not a BranchPill: the header shouldn't grow a second
|
||
navigation affordance next to the path. */
|
||
.file-view-ref {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-xs);
|
||
color: var(--muted);
|
||
border: 1px solid var(--border-variant);
|
||
border-radius: var(--radius-sm);
|
||
padding: 1px 6px;
|
||
max-width: 180px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
.file-view-body {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: auto;
|
||
background: var(--base);
|
||
}
|
||
/* Gutter + code share one row so line numbers track content rows 1:1 — the
|
||
code never soft-wraps (min-width: max-content), so line N is row N. */
|
||
.file-view-codewrap {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
min-width: max-content;
|
||
}
|
||
/* Identical font metrics + vertical padding are the 1:1 row-alignment
|
||
invariant — one rule so they can't drift apart. */
|
||
.file-view-gutter,
|
||
.file-view-code {
|
||
margin: 0;
|
||
padding-top: 14px;
|
||
padding-bottom: 14px;
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
line-height: 1.55;
|
||
}
|
||
.file-view-gutter {
|
||
padding-left: 14px;
|
||
padding-right: 10px;
|
||
text-align: right;
|
||
color: var(--muted);
|
||
user-select: none;
|
||
/* Stay visible while the code scrolls horizontally. */
|
||
position: sticky;
|
||
left: 0;
|
||
background: var(--base);
|
||
border-right: 1px solid var(--border-variant);
|
||
flex-shrink: 0;
|
||
}
|
||
.file-view-code {
|
||
padding-left: 16px;
|
||
padding-right: 16px;
|
||
tab-size: 4;
|
||
min-width: max-content;
|
||
}
|
||
.file-view-note {
|
||
padding: 10px 16px;
|
||
font-size: var(--fs-sm);
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* Rendered markdown files (.md tabs) — doc-sized type over the chat .md base. */
|
||
.file-view-md {
|
||
max-width: var(--readable-col);
|
||
padding: 18px 20px 32px;
|
||
}
|
||
.file-view-md .md {
|
||
font-size: var(--fs-base);
|
||
}
|
||
.file-view-md .md h1 {
|
||
font-size: 1.5em;
|
||
margin: 18px 0 8px;
|
||
}
|
||
.file-view-md .md h2 {
|
||
font-size: 1.25em;
|
||
margin: 16px 0 8px;
|
||
}
|
||
.file-view-md .md h3 {
|
||
font-size: 1.1em;
|
||
}
|
||
|
||
.icon-btn.active {
|
||
color: var(--primary);
|
||
background: var(--surface);
|
||
}
|
||
|
||
/* --- interactive prompt cards (plan / permission / question) ------------------
|
||
Emitted by the harness when the agent asks for a plan approval, a permission,
|
||
or a choice. Kept quiet: a hairline border, a thin per-kind left accent, no
|
||
shadow — it should read as an inline aside, not a callout that shouts. */
|
||
.prompt-card {
|
||
margin: 8px 0;
|
||
padding: 12px 14px;
|
||
border: 1px solid var(--border);
|
||
border-left: 3px solid var(--border);
|
||
border-radius: var(--radius-sm);
|
||
background: var(--surface);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 9px;
|
||
}
|
||
.prompt-card.plan {
|
||
border-left-color: var(--accent-blue);
|
||
}
|
||
.prompt-card.permission {
|
||
border-left-color: var(--accent-amber);
|
||
}
|
||
.prompt-card.question {
|
||
border-left-color: var(--accent-purple);
|
||
}
|
||
/* A card in a read-only host (no respond handler): muted, buttons disabled.
|
||
Resolved cards never render as .prompt-card at all — permission cards
|
||
vanish, plan/question cards collapse to .prompt-collapsed rows. */
|
||
.prompt-card.readonly {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.prompt-head {
|
||
font-size: var(--fs-xs);
|
||
font-weight: var(--fw-semibold);
|
||
color: var(--text);
|
||
}
|
||
.prompt-head code {
|
||
font-family: var(--mono);
|
||
font-size: var(--fs-sm);
|
||
color: var(--text);
|
||
}
|
||
.prompt-sub {
|
||
font-size: var(--fs-sm);
|
||
color: var(--subtext);
|
||
word-break: break-word;
|
||
}
|
||
.prompt-q {
|
||
font-size: var(--fs-base);
|
||
font-weight: var(--fw-semibold);
|
||
line-height: 1.5;
|
||
color: var(--text);
|
||
}
|
||
.prompt-plan {
|
||
font-size: var(--fs-base);
|
||
line-height: 1.6;
|
||
color: var(--text);
|
||
max-height: 340px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* Question options: each is a full-width button with the label on its own line
|
||
and the (optional) description block beneath it — never inline, or the two
|
||
run together ("labelDescription…"). */
|
||
.prompt-options {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
.prompt-option {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 2px;
|
||
width: 100%;
|
||
padding: 8px 11px;
|
||
text-align: left;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-sm);
|
||
background: var(--base);
|
||
color: var(--text);
|
||
cursor: pointer;
|
||
transition:
|
||
border-color 80ms ease,
|
||
background 80ms ease;
|
||
}
|
||
.prompt-option:hover:not(:disabled) {
|
||
border-color: var(--border-strong);
|
||
background: var(--surface);
|
||
}
|
||
.prompt-option.sel {
|
||
border-color: var(--primary);
|
||
background: var(--primary-subtle);
|
||
}
|
||
.prompt-option:disabled {
|
||
cursor: default;
|
||
}
|
||
.prompt-option-label {
|
||
display: block;
|
||
font-size: var(--fs-md);
|
||
font-weight: var(--fw-semibold);
|
||
}
|
||
.prompt-option-desc {
|
||
display: block;
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-regular);
|
||
line-height: 1.45;
|
||
color: var(--subtext);
|
||
}
|
||
|
||
/* Action row (plan approve / permission allow-deny / multi-select submit). */
|
||
.prompt-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
.prompt-actions .btn-primary,
|
||
.prompt-actions .btn-ghost {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 13px;
|
||
font-family: inherit;
|
||
font-size: var(--fs-sm);
|
||
font-weight: var(--fw-semibold);
|
||
border: 1px solid transparent;
|
||
border-radius: var(--radius-sm);
|
||
cursor: pointer;
|
||
transition:
|
||
background 80ms ease,
|
||
border-color 80ms ease;
|
||
}
|
||
.prompt-actions .btn-primary {
|
||
background: var(--primary);
|
||
color: var(--base);
|
||
}
|
||
.prompt-actions .btn-primary:hover:not(:disabled) {
|
||
opacity: 0.9;
|
||
}
|
||
.prompt-actions .btn-ghost {
|
||
background: transparent;
|
||
border-color: var(--border);
|
||
color: var(--subtext);
|
||
}
|
||
.prompt-actions .btn-ghost:hover:not(:disabled) {
|
||
border-color: var(--border-strong);
|
||
color: var(--text);
|
||
background: var(--surface);
|
||
}
|
||
.prompt-actions button:disabled {
|
||
opacity: 0.5;
|
||
cursor: default;
|
||
}
|
||
|
||
/* Resolved plan/question cards collapse to a one-line expandable row at their
|
||
chronological position (Claude-desktop style); resolved permission cards
|
||
render nothing at all. Same visual family as `.reasoning`. */
|
||
.prompt-collapsed {
|
||
color: var(--muted);
|
||
font-size: var(--fs-md);
|
||
margin: 4px 0;
|
||
}
|
||
.prompt-collapsed summary {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 8px;
|
||
cursor: pointer;
|
||
list-style: none;
|
||
user-select: none;
|
||
}
|
||
.prompt-collapsed summary::-webkit-details-marker {
|
||
display: none;
|
||
}
|
||
.prompt-collapsed summary::after {
|
||
content: "›";
|
||
color: var(--muted);
|
||
transition: transform 80ms ease;
|
||
}
|
||
.prompt-collapsed[open] summary::after {
|
||
transform: rotate(90deg);
|
||
}
|
||
.prompt-collapsed-title {
|
||
font-weight: var(--fw-semibold);
|
||
word-break: break-word;
|
||
}
|
||
.prompt-outcome {
|
||
font-size: var(--fs-sm);
|
||
color: var(--subtext);
|
||
word-break: break-word;
|
||
}
|
||
.prompt-outcome.approved,
|
||
.prompt-outcome.chosen {
|
||
color: var(--accent-green);
|
||
}
|
||
.prompt-outcome.approved::before,
|
||
.prompt-outcome.chosen::before {
|
||
content: "✓ ";
|
||
}
|
||
.prompt-outcome.revised,
|
||
.prompt-outcome.rejected {
|
||
color: var(--accent-amber);
|
||
}
|
||
.prompt-collapsed-body {
|
||
margin-top: 6px;
|
||
padding-left: 12px;
|
||
border-left: 2px solid var(--border);
|
||
font-size: var(--fs-md);
|
||
color: var(--subtext);
|
||
}
|
||
.prompt-collapsed-options {
|
||
margin: 6px 0 0;
|
||
padding-left: 18px;
|
||
}
|
||
.prompt-collapsed-options .sel {
|
||
color: var(--text);
|
||
font-weight: var(--fw-semibold);
|
||
}
|
||
.prompt-collapsed-note {
|
||
margin-top: 6px;
|
||
font-style: italic;
|
||
}
|
||
|
||
/* --- plan strip (docked above the composer while a plan awaits a decision) --- */
|
||
|
||
.plan-strip {
|
||
/* Lives INSIDE .composer (so the composer's picker popovers stack above
|
||
it). Claude-desktop shape: title line on top, a row of buttons below. */
|
||
position: relative;
|
||
width: 100%;
|
||
margin: 0 0 10px;
|
||
padding: 11px 13px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 10px;
|
||
border: 1px solid var(--border);
|
||
border-left: 3px solid var(--accent-blue);
|
||
border-radius: var(--radius-md);
|
||
background: var(--surface);
|
||
box-shadow: 0 2px 10px rgb(0 0 0 / 6%);
|
||
}
|
||
.plan-strip-info {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
}
|
||
.plan-strip-open {
|
||
margin-left: auto;
|
||
padding: 0;
|
||
border: none;
|
||
background: none;
|
||
color: var(--accent-blue);
|
||
font-size: var(--fs-md);
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
.plan-strip-open:hover {
|
||
text-decoration: underline;
|
||
}
|
||
.plan-strip-spacer {
|
||
flex: 1;
|
||
}
|
||
/* Revise mode: a standalone bordered textarea (the strip itself has none —
|
||
unlike the borderless composer textarea, this needs to read as its own
|
||
input), focus ring on :focus like any other text field. */
|
||
.plan-strip-revise-input {
|
||
width: 100%;
|
||
resize: none;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
padding: 9px 11px;
|
||
font-size: var(--fs-md);
|
||
font-family: inherit;
|
||
background: var(--base);
|
||
color: var(--text);
|
||
}
|
||
.plan-strip-revise-input:focus {
|
||
border-color: var(--accent-blue);
|
||
}
|
||
.plan-strip-icon {
|
||
color: var(--accent-blue);
|
||
flex-shrink: 0;
|
||
align-self: center;
|
||
}
|
||
.plan-strip-title {
|
||
font-size: var(--fs-md);
|
||
font-weight: var(--fw-semibold);
|
||
white-space: nowrap;
|
||
}
|
||
/* Button styling comes from the shared `.prompt-actions` rules, but the
|
||
strip is monochrome (no brand red): black text, black borders, quiet
|
||
hover. `--text` keeps it theme-correct in dark mode. The spacer splits
|
||
reject/revise (left) from the accepts (right); when the pane is too
|
||
narrow the accept group wraps to its own right-aligned line. */
|
||
.plan-strip-actions {
|
||
gap: 6px 8px;
|
||
justify-content: flex-end;
|
||
}
|
||
.plan-strip-actions .btn-primary,
|
||
.plan-strip-actions .btn-ghost {
|
||
background: transparent;
|
||
border: 1px solid var(--text);
|
||
color: var(--text);
|
||
}
|
||
.plan-strip-actions .btn-primary:hover:not(:disabled),
|
||
.plan-strip-actions .btn-ghost:hover:not(:disabled) {
|
||
background: var(--surface-2, rgb(0 0 0 / 5%));
|
||
border-color: var(--text);
|
||
color: var(--text);
|
||
opacity: 1;
|
||
}
|
||
/* The accept primary: solid dark (desktop's black button), not brand red.
|
||
`--text`-on-`--base` inverts correctly in dark mode. */
|
||
.plan-strip-actions .plan-strip-primary {
|
||
background: var(--text);
|
||
border-color: var(--text);
|
||
color: var(--base);
|
||
}
|
||
.plan-strip-actions .plan-strip-primary:hover:not(:disabled) {
|
||
background: color-mix(in oklab, var(--text) 85%, var(--base));
|
||
border-color: var(--text);
|
||
color: var(--base);
|
||
}
|
||
.plan-strip-approve {
|
||
position: relative;
|
||
display: flex;
|
||
}
|
||
/* Split button: flat inner corners; the caret drops its own left border and
|
||
draws a faint base-colored seam instead. (0,3,0)+ selectors so the
|
||
`.prompt-actions`/`.plan-strip-actions` shorthands can't re-round or
|
||
re-border the seam. */
|
||
.plan-strip-approve .btn-primary:first-child {
|
||
border-top-right-radius: 0;
|
||
border-bottom-right-radius: 0;
|
||
}
|
||
.plan-strip-actions .plan-strip-caret {
|
||
border-top-left-radius: 0;
|
||
border-bottom-left-radius: 0;
|
||
padding: 0 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
border-left: 1px solid color-mix(in oklab, var(--base) 35%, var(--text));
|
||
}
|
||
.plan-strip-menu {
|
||
position: absolute;
|
||
right: 0;
|
||
bottom: calc(100% + 4px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 190px;
|
||
padding: 4px;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--surface);
|
||
box-shadow: 0 6px 20px rgb(0 0 0 / 12%);
|
||
z-index: 6;
|
||
}
|
||
.plan-strip-menu button {
|
||
text-align: left;
|
||
padding: 7px 9px;
|
||
border: none;
|
||
border-radius: var(--radius-sm);
|
||
background: transparent;
|
||
color: var(--text);
|
||
font-size: var(--fs-md);
|
||
cursor: pointer;
|
||
}
|
||
.plan-strip-menu button:hover {
|
||
background: var(--surface-2, rgb(0 0 0 / 5%));
|
||
}
|
||
|
||
/* Compact in-transcript plan card while the strip owns the actions. */
|
||
.prompt-plan.clamped {
|
||
max-height: 9.5em;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
.prompt-plan.clamped::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: auto 0 0 0;
|
||
height: 34px;
|
||
background: linear-gradient(to bottom, transparent, var(--surface));
|
||
pointer-events: none;
|
||
}
|
||
.prompt-plan-open {
|
||
align-self: flex-start;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--accent-blue);
|
||
font-size: var(--fs-sm);
|
||
padding: 0;
|
||
cursor: pointer;
|
||
}
|
||
.prompt-plan-open:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
/* Code browser tab — project-clone / branch file tree in flow layout (NOT
|
||
.runs-table-wrap, which is position:absolute and would jump to (0,0)). */
|
||
.code-tab {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
min-height: 0;
|
||
}
|
||
.code-tab-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
/* Match .file-view-header so Code ↔ file tab switches don't jump chrome. */
|
||
padding: 4px 12px;
|
||
border-bottom: 1px solid var(--border-variant);
|
||
flex-shrink: 0;
|
||
}
|
||
.code-tab-select {
|
||
max-width: 280px;
|
||
min-width: 0;
|
||
/* Compact metrics so the header's height matches .file-view-header's
|
||
* (whose tallest child is a 28px icon-btn) — the base select rule's
|
||
* 6px/14px would make Code <-> file tab switches jump the divider. */
|
||
padding: 3px 8px;
|
||
font-size: var(--fs-sm);
|
||
}
|
||
.code-tab-note {
|
||
padding: 8px 16px;
|
||
font-size: var(--fs-sm);
|
||
color: var(--muted);
|
||
border-bottom: 1px solid var(--border-variant);
|
||
flex-shrink: 0;
|
||
}
|
||
.code-tab-body {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: auto;
|
||
background: var(--base);
|
||
}
|
||
.code-tree {
|
||
padding: 6px 0;
|
||
font-size: var(--fs-md);
|
||
}
|
||
.code-tree-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
width: 100%;
|
||
padding: 3px 10px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text);
|
||
text-align: left;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
font-size: inherit;
|
||
}
|
||
.code-tree-row:hover {
|
||
background: var(--panel);
|
||
}
|
||
.code-tree-row > svg {
|
||
flex-shrink: 0;
|
||
color: var(--subtext);
|
||
}
|
||
.code-tree-row > svg.code-tree-chev {
|
||
color: var(--muted);
|
||
}
|
||
.code-tree-name {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* ---- live worktree tab (WorktreeTab.tsx) ----
|
||
The Changes/Files viewer for a chat session's private worktree. Reuses the
|
||
.code-tab shell; adds a branch chip, a status-badged change list, and a diff
|
||
region below it. */
|
||
.wt-branch-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
min-width: 0;
|
||
padding: 2px 8px;
|
||
border-radius: var(--radius-full);
|
||
background: color-mix(in oklab, var(--text) 8%, transparent);
|
||
color: var(--subtext);
|
||
font-size: var(--fs-xs);
|
||
}
|
||
.wt-branch-chip > svg {
|
||
flex-shrink: 0;
|
||
}
|
||
.wt-branch-name {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
font-family: var(--mono);
|
||
}
|
||
.wt-count {
|
||
padding: 0;
|
||
border: none;
|
||
}
|
||
/* Same horizontal gutter as .drawer-body so the diff cards match the
|
||
* experiment Changes view; no padding-top for the same reason there — sticky
|
||
* diff-file headers would sit below the top with content peeking through.
|
||
* The first child carries the spacing as margin instead. */
|
||
.wt-changes {
|
||
padding: 0 16px 24px;
|
||
}
|
||
.wt-changes > :first-child {
|
||
margin-top: 14px;
|
||
}
|
||
.wt-empty {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 48px 24px;
|
||
text-align: center;
|
||
color: var(--muted);
|
||
}
|
||
.wt-empty > svg {
|
||
color: var(--subtext);
|
||
}
|
||
.wt-empty p {
|
||
margin: 0;
|
||
max-width: 320px;
|
||
font-size: var(--fs-sm);
|
||
}
|
||
|
||
/* ---- onboarding tour ----
|
||
Spotlight walkthrough of the workspace (Tour.tsx). The overlay sits above
|
||
the z-100 modal ladder; the dim is a single oversized box-shadow around the
|
||
spotlight box so there's only one element to morph between steps. */
|
||
.tour-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 200;
|
||
}
|
||
.tour-dim {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.55);
|
||
}
|
||
.tour-spotlight {
|
||
position: absolute;
|
||
border-radius: var(--radius-lg);
|
||
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
|
||
transition: all 300ms ease;
|
||
}
|
||
.tour-ring {
|
||
position: absolute;
|
||
border-radius: var(--radius-lg);
|
||
border: 2px solid var(--primary);
|
||
transition: all 300ms ease;
|
||
}
|
||
.tour-card {
|
||
position: absolute;
|
||
width: 390px;
|
||
max-width: calc(100vw - 32px);
|
||
background: var(--base);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-xl);
|
||
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.22);
|
||
padding: 21px 23px 0;
|
||
transition:
|
||
left 200ms ease,
|
||
top 200ms ease;
|
||
}
|
||
.tour-card.centered {
|
||
left: 50%;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
transition: none;
|
||
}
|
||
.tour-card h3 {
|
||
margin: 0 26px 7px 0;
|
||
font-size: var(--fs-lg);
|
||
font-weight: var(--fw-semibold);
|
||
}
|
||
.tour-card p {
|
||
margin: 0;
|
||
font-size: var(--fs-base);
|
||
line-height: 1.55;
|
||
color: var(--text);
|
||
}
|
||
.tour-card .tour-close {
|
||
position: absolute;
|
||
top: 12px;
|
||
right: 12px;
|
||
}
|
||
.tour-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
border-top: 1px solid var(--border-variant);
|
||
margin-top: 16px;
|
||
padding: 11px 0;
|
||
}
|
||
.tour-footer-side {
|
||
flex: 1;
|
||
display: flex;
|
||
}
|
||
.tour-footer-side.end {
|
||
justify-content: flex-end;
|
||
}
|
||
.tour-count {
|
||
font-size: var(--fs-sm);
|
||
color: var(--muted);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
/* Rotated-square pointer on the card edge nearest the spotlight; the two
|
||
outward borders draw the triangle. Class names follow the card's anchor
|
||
("above" = card above the target, so the arrow is on the bottom edge). */
|
||
.tour-arrow {
|
||
position: absolute;
|
||
width: 12px;
|
||
height: 12px;
|
||
background: var(--base);
|
||
rotate: 45deg;
|
||
pointer-events: none;
|
||
}
|
||
.tour-arrow.above {
|
||
bottom: -6.5px;
|
||
left: calc(50% - 6px);
|
||
border-right: 1px solid var(--border);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.tour-arrow.below {
|
||
top: -6.5px;
|
||
left: calc(50% - 6px);
|
||
border-left: 1px solid var(--border);
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
.tour-arrow.left {
|
||
right: -6.5px;
|
||
top: calc(50% - 6px);
|
||
border-top: 1px solid var(--border);
|
||
border-right: 1px solid var(--border);
|
||
}
|
||
.tour-arrow.right {
|
||
left: -6.5px;
|
||
top: calc(50% - 6px);
|
||
border-bottom: 1px solid var(--border);
|
||
border-left: 1px solid var(--border);
|
||
}
|