diff --git a/public/dashboard.html b/public/dashboard.html index daac31d..87e74d7 100644 --- a/public/dashboard.html +++ b/public/dashboard.html @@ -108,15 +108,928 @@ font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 11px; color: var(--fg-dim); } + .alert-banner { + background: rgba(248, 113, 113, 0.12); + border: 1px solid rgba(248, 113, 113, 0.4); + color: var(--bad); + border-radius: 10px; + padding: 14px 18px; + margin: 16px 0 0; + font-size: 13px; + font-weight: 600; + } + .alert-banner .alert-sub { + font-weight: 400; color: var(--bad); opacity: 0.85; + margin-top: 4px; font-size: 12px; + } + .csv-link { + background: var(--panel-2); border: 1px solid var(--line-2); + color: var(--fg-dim); padding: 6px 12px; border-radius: 8px; + font-size: 12px; font-weight: 600; margin-left: auto; + text-decoration: none; display: inline-block; + } + .csv-link:hover { color: var(--fg); border-color: var(--fg-faint); } + .status-success { color: var(--good); } + .status-error { color: var(--bad); } + .status-partial { color: var(--warn); } + .err-msg { + font-family: ui-monospace, SFMono-Regular, Consolas, monospace; + font-size: 11px; color: var(--bad); + max-width: 480px; overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; + } + td.tight, th.tight { padding: 6px 10px; } + .margin-pos { color: var(--money); } + .margin-neg { color: var(--bad); } + + /* ── Tab navigation ─────────────────────────────────────── */ + .tabs { + display: flex; gap: 4px; border-bottom: 1px solid var(--line); + margin: 8px 0 20px; align-items: flex-end; + } + .tab { + background: transparent; border: 1px solid transparent; + border-bottom: none; color: var(--fg-dim); + padding: 10px 16px; border-radius: 8px 8px 0 0; + cursor: pointer; font-size: 13px; font-weight: 600; + margin-bottom: -1px; + } + .tab:hover { color: var(--fg); } + .tab.active { + background: var(--panel); color: var(--fg); + border-color: var(--line); border-bottom: 1px solid var(--panel); + } + + /* ── Jobs tab ─────────────────────────────────────────────── */ + .jobs-summary { + display: grid; gap: 12px; margin: 16px 0 24px; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + } + .jobs-filters { + display: flex; flex-wrap: wrap; gap: 8px; align-items: center; + margin: 16px 0; padding: 12px; background: var(--panel); + border: 1px solid var(--line); border-radius: 10px; + } + .jobs-filters label { + font-size: 11px; color: var(--fg-faint); + text-transform: uppercase; letter-spacing: 0.04em; font-weight: 600; + } + .jobs-filters select, .jobs-filters input { + background: var(--bg); border: 1px solid var(--line-2); + color: var(--fg); padding: 6px 10px; border-radius: 6px; + font-size: 12px; font-family: inherit; min-width: 100px; + } + .jobs-filters input.q { flex: 1; min-width: 180px; } + .jobs-filters button { + background: var(--panel-2); border: 1px solid var(--line-2); + color: var(--fg-dim); padding: 6px 12px; border-radius: 6px; + cursor: pointer; font-size: 12px; font-weight: 600; + } + .jobs-filters button:hover { color: var(--fg); } + .jobs-table-wrap { + /* Internal horizontal scroll for the wide table. overflow-y + is "clip" instead of "visible" because the CSS spec forces + "visible" to be implicitly "auto" when paired with a + scrolling axis — and "auto" creates a vertical scroll + CONTAINER inside the wrap, which can trap rows inside a + shorter-than-content box. "clip" doesn't establish a + scroll context; the page scrolls naturally and the wrap + grows tall to fit all rows. Per-browser fallback to + overflow-y: visible (some older Safari). + max-height: none guards against any cascaded constraint. */ + overflow-x: auto; overflow-y: clip; + max-height: none; + background: var(--panel); + border: 1px solid var(--line); border-radius: 10px; + scrollbar-width: none; /* Firefox: hide native bar */ + } + .jobs-table-wrap::-webkit-scrollbar { display: none; } + + /* Fixed-position horizontal scrollbar that hugs the viewport + bottom whenever the Jobs tab is open AND the table actually + overflows horizontally. position: fixed (not sticky) ensures + it's always at viewport bottom regardless of vertical page + scroll position — the operator can scroll the page down to + see more rows, and the bar stays glued to the bottom of the + browser window the whole time. JS positions its left+width + to match the table's visible area and syncs scrollLeft with + the wrap. */ + /* Raised off the very bottom of the viewport so the operator has + a few px of click leeway above the OS chrome / dock. Made + noticeably thicker so the scrollbar thumb is an easier target — + the old 12px height made horizontal scrolling fiddly on a + trackpad. Visually the wider band reads as a UI element rather + than a stray window edge. */ + .jobs-sticky-scroll { + position: fixed; bottom: 16px; left: 0; z-index: 50; + overflow-x: scroll; overflow-y: hidden; + height: 22px; + background: var(--bg); + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); + display: none; /* JS toggles to "block" when relevant */ + } + .jobs-sticky-scroll::-webkit-scrollbar { height: 20px; } + .jobs-sticky-scroll::-webkit-scrollbar-track { background: var(--bg); } + .jobs-sticky-scroll::-webkit-scrollbar-thumb { + background: var(--line-2); border-radius: 10px; + border: 4px solid var(--bg); + } + .jobs-sticky-scroll::-webkit-scrollbar-thumb:hover { background: var(--fg-faint); } + .jobs-sticky-scroll-inner { height: 1px; } + + /* Global in-flight job indicator. Fixed at top-right of the + viewport so it persists across tab changes (each tab rewrites + #root, so this lives outside the root). Compact pill with a + pulsing dot — the operator gets a peripheral "something is + running" signal without it stealing focus from the current + tab's content. Hidden via display:none when state.activeJobs is + empty. */ + #global-inflight-bar { + position: fixed; + top: 16px; + right: 18px; + z-index: 1000; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 14px 8px 11px; + background: rgba(15, 23, 42, 0.92); + border: 1px solid var(--line-2); + border-radius: 999px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35); + font-size: 11.5px; + color: var(--fg); + cursor: pointer; + backdrop-filter: blur(6px); + transition: border-color 0.15s ease, background 0.15s ease; + } + #global-inflight-bar:hover { + border-color: var(--accent); + background: rgba(15, 23, 42, 0.98); + } + #global-inflight-bar .gb-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 6px var(--accent); + animation: breadcrumb-pulse 1.2s infinite; + } + #global-inflight-bar .gb-stage { + color: var(--fg-dim); + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.04em; + margin-left: 4px; + } + #global-inflight-bar .gb-jobcount { + font-weight: 700; + color: var(--fg); + } + + /* "Active jobs" callout at the top of the Jobs tab. Sits ABOVE the + test-run panel as a dedicated section so it's the first thing + the operator sees on Jobs-tab entry — regardless of whether the + in-flight job came from the test-run panel or a Recap-submitted + summarize. */ + .inflight-callout { + margin-bottom: 14px; + padding: 14px 16px; + background: var(--panel); + border: 1px solid var(--line-2); + border-left: 3px solid var(--accent); + border-radius: 6px; + } + .inflight-callout .ic-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--fg-dim); + font-weight: 700; + } + .inflight-callout .ic-header .ic-count { + color: var(--accent); + font-weight: 700; + } + .inflight-callout .ic-jobrow { + padding: 10px 12px; + background: var(--panel-2); + border: 1px solid var(--line-2); + border-radius: 6px; + margin-bottom: 8px; + } + .inflight-callout .ic-jobrow:last-child { + margin-bottom: 0; + } + .inflight-callout .ic-jobrow-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + font-size: 11px; + color: var(--fg-dim); + } + .inflight-callout .ic-jobrow-header .ic-jobid { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + color: var(--fg-faint); + } + .inflight-callout .ic-jobrow-header .ic-source { + padding: 2px 8px; + border-radius: 999px; + background: rgba(165,180,252,0.12); + color: var(--accent); + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + } + .inflight-callout .ic-jobrow-header .ic-source.ic-source-testrun { + background: rgba(74,222,128,0.10); + color: var(--good); + } + .inflight-callout .ic-jobrow-header .ic-elapsed { + font-variant-numeric: tabular-nums; + color: var(--fg-faint); + } + + /* Pizza-tracker breadcrumb pulse for the currently-active stage. */ + @keyframes breadcrumb-pulse { + 0% { opacity: 1; } + 50% { opacity: 0.4; } + 100% { opacity: 1; } + } + /* Generic pulse used by the hardware-queue chip's active dot. */ + @keyframes pulse { + 0% { opacity: 1; } + 50% { opacity: 0.45; } + 100% { opacity: 1; } + } + .jobs-table { + width: 100%; min-width: 1600px; border-collapse: collapse; + font-size: 11px; border: none; border-radius: 0; + /* table-layout: auto so columns size naturally on first paint; + user-set explicit widths via the resize handles override. + Cells use overflow:hidden + ellipsis so a narrowed column + truncates instead of forcing the column wider. */ + } + .jobs-table th { + cursor: pointer; user-select: none; + position: sticky; top: 0; background: var(--panel-2); z-index: 1; + position: relative; /* anchor for the resize handle */ + vertical-align: middle; + padding: 6px 14px 6px 8px; /* 14px right = handle (8px) + 6px buffer */ + /* TH itself stays display: table-cell so the table layout works. + The actual wrap+clamp lives on the inner .th-label span below + (which uses display: -webkit-box for line-clamp support — + that display value is incompatible with table-cell). */ + white-space: nowrap; + } + .jobs-table th .th-label { + /* Wrap header text at word boundaries (spaces, slashes, dashes + when natural break-points exist). NEVER break inside a word + like "TX" or "CHUNKS" — that produced the letter-per-line + vertical stack of doom we just fixed. Capped at 2 lines via + -webkit-line-clamp so a too-narrow column truncates with + ellipsis instead of growing the header row arbitrarily tall. */ + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + white-space: normal; + word-break: normal; + overflow-wrap: break-word; /* fallback ONLY if a single word can't fit */ + line-height: 1.2; + max-height: calc(2 * 1.2em); + } + .jobs-table th:hover { color: var(--fg); } + .jobs-table th.sort-active { color: var(--accent); } + .jobs-table th .sort-ind { font-size: 9px; margin-left: 4px; opacity: 0.7; } + /* Vertical hit-strip at the right edge of each header for column + width resizing. Wider hit-strip (8px) for easier mouse target + — narrow columns still leave the strip reachable since the TH + has 14px right-padding. The visual is only 2px wide (centered) + so it doesn't intrude on the header label; the rest of the + hit-strip is transparent. */ + .col-resize-handle { + position: absolute; right: 0; top: 0; bottom: 0; width: 8px; + cursor: col-resize; user-select: none; + } + .col-resize-handle::after { + content: ""; position: absolute; + right: 3px; top: 6px; bottom: 6px; width: 2px; + background: transparent; + } + .col-resize-handle:hover::after { background: var(--accent); } + body.col-resizing { cursor: col-resize !important; } + body.col-resizing * { user-select: none !important; pointer-events: none; } + body.col-resizing .col-resize-handle { pointer-events: auto; } + .jobs-table td { + white-space: nowrap; + overflow: hidden; text-overflow: ellipsis; + vertical-align: top; + /* min-width: 0 lets auto-layout columns shrink BELOW their + natural content width when the user resizes them narrower. + Without this, cell content forces a hard floor on the + column width even with overflow: hidden — so resizing the + YouTube URL column smaller does nothing until the user-set + width exceeds the cell's intrinsic width. The actual + max-width per cell is stamped inline by renderJobsBody when + the column has a defaultWidth or user-set width. */ + min-width: 0; + } + .jobs-table tr:hover td { background: rgba(165,180,252,0.04); } + .jobs-table td.title-cell a { color: var(--accent); text-decoration: none; } + .jobs-table td.title-cell a:hover { text-decoration: underline; } + /* Errors column: single-line by default with a chevron that + expands JUST that one cell to wrap. Other cells in the same row + are unaffected, so the row collapses back to single-line as soon + as the expand chevron is toggled off. The TD's max-width is + applied INLINE per-cell (see the errors cellRenderer below) so + it tracks the column's effective width — catalog default OR + user resize. Without an inline max-width, auto-layout tables + grow the column to fit the unwrapped error string, blowing out + the table's horizontal scroll. */ + .jobs-table td.errors-cell-td { + position: relative; padding-right: 28px; + overflow: hidden; text-overflow: ellipsis; + } + .jobs-table td.errors-cell-td.expanded { white-space: normal; vertical-align: top; } + .jobs-table td.errors-cell-td .err-text { + display: inline-block; max-width: 100%; + overflow: hidden; text-overflow: ellipsis; vertical-align: middle; + } + .jobs-table td.errors-cell-td.expanded .err-text { + overflow: visible; text-overflow: clip; white-space: normal; + } + .jobs-table td.errors-cell-td .err-expand { + position: absolute; right: 8px; top: 50%; + transform: translateY(-50%); + width: 18px; height: 18px; border-radius: 4px; + background: var(--panel-2); border: 1px solid var(--line-2); + color: var(--fg-dim); font-size: 10px; cursor: pointer; + display: flex; align-items: center; justify-content: center; + padding: 0; line-height: 1; + } + .jobs-table td.errors-cell-td .err-expand:hover { color: var(--accent); border-color: var(--accent); } + .jobs-table td.errors-cell-td.expanded .err-expand { + top: 4px; transform: none; + } + /* Copy-to-clipboard button. Sits just left of .err-expand so both + are reachable without overlapping the wrapped/expanded text. + Hidden by default, fades in on cell hover (operator-only + affordance — keeps the table dense for normal scanning). */ + .jobs-table td.errors-cell-td .err-copy { + position: absolute; right: 30px; top: 50%; + transform: translateY(-50%); + width: 18px; height: 18px; border-radius: 4px; + background: var(--panel-2); border: 1px solid var(--line-2); + color: var(--fg-dim); font-size: 9px; cursor: pointer; + display: flex; align-items: center; justify-content: center; + padding: 0; line-height: 1; + opacity: 0; transition: opacity 0.12s ease; + } + .jobs-table td.errors-cell-td:hover .err-copy { opacity: 1; } + .jobs-table td.errors-cell-td .err-copy:hover { color: var(--accent); border-color: var(--accent); } + .jobs-table td.errors-cell-td .err-copy.copied { + color: var(--good); border-color: var(--good); opacity: 1; + } + .jobs-table td.errors-cell-td.expanded .err-copy { + top: 4px; transform: none; + } + .status-pill { + display: inline-block; padding: 2px 8px; border-radius: 999px; + font-size: 10px; font-weight: 700; text-transform: uppercase; + } + .status-success { background: rgba(74,222,128,0.12); color: var(--good); } + .status-partial { background: rgba(251,191,36,0.14); color: var(--warn); } + .status-failed { background: rgba(252,165,165,0.14); color: var(--bad); } + /* "Inspect" button + per-job detail sub-row that drops in beneath + the main Jobs-table row when the operator clicks 🔍. The detail + row visually nests inside the main row (slight indent, subdued + background) and shows a compact per-audit-entry table for + diagnosing partial/failed runs without shell access. */ + .job-detail-btn { + background: transparent; border: 0; cursor: pointer; + color: var(--fg-dim); font-size: 14px; padding: 2px 4px; + border-radius: 4px; + } + .job-detail-btn:hover { color: var(--accent); background: rgba(165,180,252,0.08); } + .jobs-table tr.job-detail-row td.job-detail-cell { + background: rgba(165,180,252,0.04); + border-top: 1px solid var(--line-2); + padding: 12px 18px 14px 32px; + white-space: normal; + vertical-align: top; + } + .job-detail-summary { + font-size: 11.5px; color: var(--fg-dim); margin-bottom: 8px; + } + .job-detail-summary b { color: var(--fg); font-weight: 700; } + .job-detail-warn { color: var(--warn); } + .job-detail-bad { color: var(--bad); font-weight: 600; } + .job-detail-table { + width: 100%; max-width: 1100px; + font-size: 11.5px; + border-collapse: collapse; + } + .job-detail-table th { + text-align: left; padding: 4px 8px; + color: var(--fg-faint); font-weight: 600; + font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; + border-bottom: 1px solid var(--line-2); + } + .job-detail-table td { + padding: 5px 8px; + border-bottom: 1px solid rgba(255,255,255,0.04); + vertical-align: top; + } + .job-detail-table td.num { text-align: right; font-variant-numeric: tabular-nums; } + .job-detail-table td.dim { color: var(--fg-faint); } + .job-detail-table td.job-detail-err { + max-width: 480px; + color: var(--fg-dim); + word-break: break-word; + } + .job-detail-loading, .job-detail-empty { + color: var(--fg-dim); font-size: 12px; font-style: italic; + } + .job-detail-error { + color: var(--bad); font-size: 12px; + } + .jobs-pagination { + display: flex; gap: 8px; align-items: center; justify-content: center; + margin: 16px 0; color: var(--fg-dim); font-size: 12px; + } + .jobs-pagination button { + background: var(--panel-2); border: 1px solid var(--line-2); + color: var(--fg-dim); padding: 6px 12px; border-radius: 6px; + cursor: pointer; font-size: 12px; font-weight: 600; + } + .jobs-pagination button:disabled { opacity: 0.4; cursor: not-allowed; } + .jobs-pagination button:not(:disabled):hover { color: var(--fg); } + .jobs-empty { + padding: 40px 20px; text-align: center; color: var(--fg-faint); font-size: 13px; + } + .errors-cell { color: var(--bad); font-size: 10px; max-width: 360px; white-space: normal; } + + /* Column drag visual feedback */ + .jobs-table th[draggable="true"] { cursor: grab; } + .jobs-table th[draggable="true"]:active { cursor: grabbing; } + .jobs-table th.col-drag-over { + border-left: 2px solid var(--accent); + } + + /* Floating right-click context menu */ + .col-context-menu { + position: fixed; z-index: 1000; + background: var(--panel); border: 1px solid var(--line-2); + border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); + min-width: 180px; padding: 4px; font-size: 12px; + } + .col-context-menu .menu-label { + padding: 8px 12px 4px; font-size: 10px; color: var(--fg-faint); + text-transform: uppercase; letter-spacing: 0.04em; font-weight: 600; + } + .col-context-menu button { + display: block; width: 100%; text-align: left; + background: transparent; border: none; color: var(--fg); + padding: 8px 12px; cursor: pointer; border-radius: 6px; + font-size: 12px; font-family: inherit; + } + .col-context-menu button:hover:not(:disabled) { background: var(--panel-2); color: var(--accent); } + .col-context-menu button:disabled { color: var(--fg-faint); cursor: not-allowed; } + .col-context-menu button.has-submenu { display: flex; justify-content: space-between; align-items: center; } + .col-context-menu button.back-btn { color: var(--fg-dim); font-size: 11px; } + .col-context-menu hr { + border: none; border-top: 1px solid var(--line); margin: 4px 0; + } + .col-context-overlay { + position: fixed; inset: 0; z-index: 999; background: transparent; + } + + /* ── Test-run panel ───────────────────────────────────────── */ + .tr-label { + display: block; font-size: 11px; color: var(--fg-faint); + text-transform: uppercase; letter-spacing: 0.04em; + font-weight: 600; margin-bottom: 4px; + } + .tr-input { + width: 100%; padding: 7px 10px; + background: var(--bg); border: 1px solid var(--line-2); + color: var(--fg); border-radius: 6px; font-size: 12px; + font-family: inherit; box-sizing: border-box; + } + .tr-input:focus { border-color: var(--accent); outline: none; } + .tr-input:disabled { opacity: 0.5; cursor: not-allowed; } + .tr-toggle { + display: inline-flex; gap: 0; + background: var(--bg); border: 1px solid var(--line-2); + border-radius: 6px; overflow: hidden; + } + .tr-toggle button { + background: transparent; border: none; + color: var(--fg-dim); padding: 7px 14px; + cursor: pointer; font-size: 12px; font-weight: 600; + font-family: inherit; + } + .tr-toggle button:hover:not(.active):not(:disabled) { color: var(--fg); } + .tr-toggle button.active { background: var(--accent); color: var(--bg); } + .tr-toggle button:disabled { opacity: 0.5; cursor: not-allowed; } + .tr-btn { + background: var(--panel-2); border: 1px solid var(--line-2); + color: var(--fg-dim); padding: 8px 16px; border-radius: 6px; + cursor: pointer; font-size: 12px; font-weight: 600; + font-family: inherit; + } + .tr-btn:hover:not(:disabled) { color: var(--fg); border-color: var(--fg-faint); } + .tr-btn:disabled { opacity: 0.5; cursor: not-allowed; } + .tr-btn-primary { + background: var(--accent); color: var(--bg); border-color: var(--accent); + } + .tr-btn-primary:hover:not(:disabled) { background: #c4b5fd; color: var(--bg); } + + /* ─── Settings tab ─────────────────────────────────────────── */ + /* Visual goal: feel like an extension of the Overview + Jobs + tabs — same panel/border/spacing language, same accent for + interactive state, same compact information density. The old + layout used native dropdowns + number spinners which looked + like a generic form; this rewrite uses pills (toggle-buttons) + and slider+number combos that read as "control panel" instead. */ + .settings-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-bottom: 16px; + } + @media (max-width: 900px) { + .settings-grid { grid-template-columns: 1fr; } + } + .settings-panel { + background: var(--panel); + border: 1px solid var(--line); + border-radius: 10px; + padding: 14px 16px; + } + .settings-panel-title { + font-size: 11px; font-weight: 700; + letter-spacing: 0.06em; text-transform: uppercase; + color: var(--fg-dim); + margin-bottom: 12px; + display: flex; align-items: center; gap: 8px; + } + .settings-panel-title .dot { + width: 6px; height: 6px; border-radius: 50%; + background: var(--accent); + } + .settings-panel-title.gemini .dot { background: #4a9eff; } + .settings-panel-title.hardware .dot { background: #ff9b3a; } + .settings-panel-title.routing .dot { background: #c084fc; } + .settings-panel-title.shared .dot { background: var(--fg-faint); } + .settings-panel-title.output .dot { background: var(--good); } + .settings-panel-title.prompts .dot { background: #7dd3fc; } + + /* Collapsible settings panels (Grant's compact view). + Click a panel title to collapse its body; chevron rotates. + State persists to localStorage under recaps:settings:collapsed + so a reload doesn't pop everything back open. Applies to the + top-level settings panels AND each individual LLM-prompt + block inside the prompts panel. */ + .settings-panel-title, + .prompt-block-header { + cursor: pointer; user-select: none; + } + .settings-panel .panel-chevron, + .prompt-block-header .panel-chevron { + margin-left: auto; + font-size: 16px; + line-height: 1; + color: var(--fg-dim); + transition: transform 0.15s ease; + padding: 0 2px; + } + .settings-panel.collapsed .panel-chevron, + .prompt-block.collapsed .panel-chevron { + transform: rotate(-90deg); + } + .settings-panel.collapsed .settings-panel-body { display: none; } + .settings-panel.collapsed .settings-panel-title { margin-bottom: 0; } + .prompt-block.collapsed .prompt-block-body { display: none; } + .prompt-block.collapsed .prompt-block-header { margin-bottom: 0; } + .prompt-block { + margin-top: 12px; + } + .prompt-block-header { + display: flex; align-items: center; gap: 8px; + font-size: 11px; font-weight: 600; + color: var(--fg); + margin-bottom: 6px; + padding: 2px 0; + } + .prompt-block-header:hover { color: var(--accent); } + .prompt-block-header:hover .panel-chevron, + .settings-panel-title:hover .panel-chevron { color: var(--fg-dim); } + + /* Settings row: label on left, control on right, compact spacing. */ + .settings-row { + display: flex; align-items: center; gap: 10px; + padding: 6px 0; + border-bottom: 1px solid rgba(255,255,255,0.04); + font-size: 12px; + } + .settings-row:last-child { border-bottom: none; } + + /* Compact single-line text input row used in Endpoints & credentials. + Three columns: status-dot + label (fixed width), input (flex), + and a small help-tooltip icon. One row = one field, no inline + help text — the description is hidden behind the ? icon. */ + .settings-text-row { + display: flex; align-items: center; gap: 10px; + padding: 5px 0; + border-bottom: 1px solid rgba(255,255,255,0.04); + font-size: 12px; + } + .settings-text-row:last-child { border-bottom: none; } + .settings-text-row .settings-text-label { + flex: 0 0 auto; width: 190px; + color: var(--fg-dim); font-weight: 500; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + .settings-text-row input[type="text"], + .settings-text-row input[type="password"] { + flex: 1 1 auto; min-width: 0; + padding: 5px 9px; + background: var(--bg); border: 1px solid var(--line-2); + border-radius: 5px; color: var(--fg); + font-family: ui-monospace, Menlo, Consolas, monospace; + font-size: 11.5px; + } + .settings-text-row input:focus { border-color: var(--accent); outline: none; } + + /* Tooltip ? icon next to text inputs. Reveals the full help text + on hover/focus. Positioned via CSS so the tooltip pops above the + row and doesn't push siblings around. */ + .settings-help-anchor { + position: relative; + flex: 0 0 auto; + width: 18px; height: 18px; + display: inline-flex; align-items: center; justify-content: center; + background: rgba(165,180,252,0.12); + color: var(--accent); + border-radius: 50%; + font-size: 11px; font-weight: 700; + cursor: help; user-select: none; + } + .settings-help-anchor:hover, + .settings-help-anchor:focus { background: rgba(165,180,252,0.22); outline: none; } + .settings-help-tooltip { + position: absolute; + bottom: calc(100% + 8px); + right: -4px; + width: 320px; + padding: 10px 12px; + background: #0b1220; + color: var(--fg); + border: 1px solid var(--line-2); + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.4); + font-size: 11.5px; font-weight: 400; + line-height: 1.5; + white-space: normal; + opacity: 0; + pointer-events: none; + transform: translateY(4px); + transition: opacity 0.12s ease, transform 0.12s ease; + z-index: 50; + } + .settings-help-tooltip::after { + content: ""; + position: absolute; + top: 100%; + right: 7px; + border: 6px solid transparent; + border-top-color: var(--line-2); + } + .settings-help-anchor:hover .settings-help-tooltip, + .settings-help-anchor:focus .settings-help-tooltip { + opacity: 1; + transform: translateY(0); + pointer-events: auto; + } + + /* Discovery health line — small status row under the Service + Discovery URL field. Shows whether the last fetch succeeded + and what services were returned (or the error message). */ + .discovery-status { + margin: 4px 0 6px 200px; /* aligns under the input column */ + font-size: 10.5px; line-height: 1.5; + color: var(--fg-dim); + font-family: ui-monospace, Menlo, Consolas, monospace; + } + .discovery-status.ok { color: #86efac; } + .discovery-status.err { color: #fca5a5; } + .discovery-status.dim { color: var(--fg-faint); } + + /* Collapsible "Advanced: manual overrides" accordion inside the + Endpoints & credentials panel. Closed by default. */ + details.settings-advanced { + margin-top: 6px; + border-top: 1px dashed rgba(255,255,255,0.08); + padding-top: 8px; + } + details.settings-advanced > summary { + cursor: pointer; + list-style: none; + font-size: 11px; + font-weight: 600; + color: var(--fg-dim); + padding: 4px 0; + user-select: none; + } + details.settings-advanced > summary:hover { color: var(--fg); } + details.settings-advanced > summary::before { + content: "▸ "; + display: inline-block; + transition: transform 0.12s ease; + } + details.settings-advanced[open] > summary::before { + content: "▾ "; + } + details.settings-advanced > summary::-webkit-details-marker { display: none; } + details.settings-advanced .advanced-note { + font-size: 10.5px; color: var(--fg-faint); + font-weight: 400; margin-left: 6px; + } + .settings-row > label.row-label { + flex: 0 0 auto; + width: 130px; + color: var(--fg-dim); + font-weight: 500; + } + .settings-row > .row-control { flex: 1 1 auto; min-width: 0; } + + /* Pill group — reuses the tr-toggle language but adds wrap so + long lists (5 model options, 4 routing modes) don't overflow + on narrow panels. */ + .settings-pills { + display: inline-flex; flex-wrap: wrap; gap: 4px; + background: var(--bg); border: 1px solid var(--line-2); + border-radius: 6px; padding: 3px; + } + .settings-pills button { + background: transparent; border: none; + color: var(--fg-dim); padding: 5px 10px; + cursor: pointer; font-size: 11px; font-weight: 600; + font-family: inherit; border-radius: 4px; + white-space: nowrap; + } + .settings-pills button:hover:not(.active) { color: var(--fg); background: rgba(255,255,255,0.04); } + .settings-pills button.active { background: var(--accent); color: var(--bg); } + + /* Slider + number-input pair. Number stays editable (operator can + type a precise value); slider gives a fast visual sweep with a + fat accent thumb. Number reflects slider live and vice-versa. */ + .settings-slider { + display: flex; align-items: center; gap: 10px; + flex: 1 1 auto; + } + .settings-slider input[type="number"] { + width: 56px; padding: 4px 6px; + background: var(--bg); border: 1px solid var(--line-2); + color: var(--fg); border-radius: 5px; font-size: 12px; + font-family: inherit; text-align: right; font-variant-numeric: tabular-nums; + -moz-appearance: textfield; + } + .settings-slider input[type="number"]::-webkit-outer-spin-button, + .settings-slider input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; margin: 0; + } + .settings-slider input[type="number"]:focus { border-color: var(--accent); outline: none; } + .settings-slider input[type="range"] { + -webkit-appearance: none; appearance: none; + flex: 1 1 auto; height: 4px; + background: var(--line-2); border-radius: 2px; + outline: none; cursor: pointer; min-width: 80px; + } + .settings-slider input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; appearance: none; + width: 16px; height: 16px; + background: var(--accent); border-radius: 50%; + cursor: grab; border: 2px solid var(--panel); + } + .settings-slider input[type="range"]::-webkit-slider-thumb:active { cursor: grabbing; } + .settings-slider input[type="range"]::-moz-range-thumb { + width: 16px; height: 16px; + background: var(--accent); border-radius: 50%; + cursor: grab; border: 2px solid var(--panel); + } + .settings-slider .unit { color: var(--fg-faint); font-size: 11px; font-weight: 500; } + .settings-slider .default-hint { color: var(--fg-faint); font-size: 10px; font-style: italic; } + + /* Toggle switch (boolean inputs) — visual on/off pill */ + .settings-toggle { + display: inline-flex; align-items: center; gap: 8px; + cursor: pointer; + } + .settings-toggle input[type="checkbox"] { + appearance: none; -webkit-appearance: none; + width: 36px; height: 20px; border-radius: 999px; + background: var(--line-2); position: relative; + cursor: pointer; transition: background 0.15s; + } + .settings-toggle input[type="checkbox"]::after { + content: ""; position: absolute; + left: 2px; top: 2px; width: 16px; height: 16px; + background: var(--fg-dim); border-radius: 50%; + transition: left 0.15s, background 0.15s; + } + .settings-toggle input[type="checkbox"]:checked { background: rgba(165,180,252,0.4); } + .settings-toggle input[type="checkbox"]:checked::after { left: 18px; background: var(--accent); } + .settings-toggle .state-label { + font-size: 11px; color: var(--fg-dim); font-weight: 600; + min-width: 56px; + } + + /* Prompt textarea cluster */ + .settings-prompt { + background: var(--bg); border: 1px solid var(--line-2); + border-radius: 6px; padding: 8px; + } + .settings-prompt textarea { + width: 100%; box-sizing: border-box; + font-family: 'SF Mono', Menlo, Consolas, monospace; + font-size: 11px; line-height: 1.5; + background: transparent; color: var(--fg); + border: none; outline: none; resize: vertical; + padding: 4px; + } + .settings-prompt-default { + display: none; + margin: 8px 0 0; padding: 10px 12px; + background: var(--panel-2); border: 1px solid var(--line-2); + border-radius: 4px; + font-family: 'SF Mono', Menlo, Consolas, monospace; + font-size: 10px; line-height: 1.5; + white-space: pre-wrap; color: var(--fg-dim); + max-height: 240px; overflow-y: auto; + } + .settings-prompt-controls { + display: flex; align-items: center; gap: 8px; margin-top: 8px; + flex-wrap: wrap; + } + .settings-actions { + display: flex; align-items: center; gap: 10px; + padding: 16px 0 8px; + border-top: 1px solid var(--line); + margin-top: 8px; + } + +
Loading…