/* 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 , 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 among sibling