Files
web-terminal/public/style.css
Yaojia Wang e36ca272ed feat(v0.3): O2 — resume past Claude sessions
- src/http/history.ts: listSessions() reads ~/.claude/projects/*/*.jsonl
  (mtime-sorted, top 50), parseSessionMeta() extracts cwd + first user prompt
  (pure, 4 unit tests); GET /sessions
- terminal-session: opts.initialInput (typed ~700ms after the shell is ready)
- tabs.newTabForResume(cwd,id): new tab in the project dir running
  'claude --resume <id>'
- public/history.ts: 🕘 panel listing sessions (project · time · preview) with
  Resume buttons
- Verified: /sessions returns 44 real sessions; panel lists project/time/preview.
  216 tests green.
2026-06-18 08:04:04 +02:00

560 lines
10 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Web Terminal Styling */
:root {
/* Key bar is shown on ALL devices (clickable shortcut buttons). Touch
devices get a taller bar with bigger tap targets. */
--tabbar-h: 36px;
--keybar-h: 42px;
}
/* Touch devices: bigger tap targets for tabs and the key bar. */
@media (pointer: coarse) {
:root {
--tabbar-h: 44px;
--keybar-h: 58px;
}
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: #1a1a1a;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
overflow: hidden;
}
/* ── Tab bar (top) ───────────────────────────────────────────────── */
#tabbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--tabbar-h);
background-color: #222;
border-bottom: 1px solid #3a3a3a;
display: flex;
align-items: stretch;
z-index: 1001;
}
/* tabs: scrollable; toolbar: fixed cluster on the right */
#tabs {
flex: 1 1 auto;
display: flex;
align-items: stretch;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
}
#toolbar {
flex: 0 0 auto;
display: flex;
align-items: center;
border-left: 1px solid #3a3a3a;
}
.toolbtn {
background: none;
border: none;
color: #aaa;
cursor: pointer;
font-size: 15px;
padding: 0 12px;
height: 100%;
}
.toolbtn:hover {
color: #fff;
background-color: #2a2a2a;
}
/* Search box overlay (M1) */
#searchbox {
position: fixed;
top: calc(var(--tabbar-h) + 6px);
right: 8px;
z-index: 1100;
display: flex;
gap: 4px;
padding: 6px;
background-color: #2a2a2a;
border: 1px solid #3a3a3a;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
.search-input {
width: 180px;
padding: 4px 6px;
font: inherit;
font-size: 13px;
color: #fff;
background: #111;
border: 1px solid #444;
border-radius: 3px;
outline: none;
}
#searchbox button {
background: #333;
border: 1px solid #444;
color: #ddd;
cursor: pointer;
border-radius: 3px;
padding: 0 8px;
}
#searchbox button:hover {
background: #555;
}
/* QR connect modal (M5) */
#qrmodal {
position: fixed;
inset: 0;
z-index: 1200;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.6);
}
.qr-card {
background: #fff;
color: #111;
border-radius: 12px;
padding: 20px;
text-align: center;
max-width: 90vw;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
.qr-title {
font-weight: 600;
margin-bottom: 12px;
}
.qr-url {
margin-top: 10px;
font-size: 13px;
color: #333;
word-break: break-all;
}
.qr-note {
margin-top: 8px;
font-size: 12px;
color: #666;
max-width: 240px;
}
.qr-close {
margin-top: 14px;
padding: 6px 18px;
border: none;
border-radius: 6px;
background: #1a1a1a;
color: #fff;
cursor: pointer;
font: inherit;
}
/* Settings panel (M3) */
#settingspanel {
position: fixed;
top: calc(var(--tabbar-h) + 6px);
right: 8px;
z-index: 1100;
background: #2a2a2a;
border: 1px solid #3a3a3a;
border-radius: 6px;
padding: 10px;
min-width: 210px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
color: #ddd;
}
.settings-row {
display: flex;
align-items: center;
gap: 6px;
margin: 6px 0;
}
.settings-label {
width: 48px;
color: #aaa;
font-size: 13px;
}
.settings-opt {
background: #333;
border: 1px solid #444;
color: #ddd;
border-radius: 4px;
padding: 4px 9px;
cursor: pointer;
font: inherit;
font-size: 12px;
}
.settings-opt.active {
background: #4a9eff;
color: #fff;
border-color: #4a9eff;
}
.settings-size {
min-width: 22px;
text-align: center;
}
/* Dashboard (M7) */
#dashboard {
position: fixed;
inset: 0;
z-index: 1200;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 56px;
background: rgba(0, 0, 0, 0.6);
}
.dash-card {
background: #222;
border: 1px solid #3a3a3a;
border-radius: 10px;
min-width: 320px;
max-width: 90vw;
max-height: 70vh;
overflow-y: auto;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
.dash-title {
padding: 12px 16px;
font-weight: 600;
color: #fff;
border-bottom: 1px solid #3a3a3a;
}
.dash-row {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
cursor: pointer;
color: #ddd;
border-bottom: 1px solid #2a2a2a;
}
.dash-row:hover {
background: #2a2a2a;
}
.dash-row.active {
background: #1a1a1a;
box-shadow: inset 3px 0 0 #4a9eff;
}
.dash-name {
flex: 1;
}
.dash-claude {
color: #aaa;
font-size: 13px;
}
.dash-claude.pending {
color: #ffcf8f;
font-weight: 600;
}
/* History / resume modal (O2) */
#historymodal {
position: fixed;
inset: 0;
z-index: 1200;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 56px;
background: rgba(0, 0, 0, 0.6);
}
.hist-card {
background: #222;
border: 1px solid #3a3a3a;
border-radius: 10px;
min-width: 360px;
max-width: 92vw;
max-height: 72vh;
overflow-y: auto;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
.hist-title {
padding: 12px 16px;
font-weight: 600;
color: #fff;
border-bottom: 1px solid #3a3a3a;
}
.hist-empty {
padding: 16px;
color: #aaa;
}
.hist-row {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
border-bottom: 1px solid #2a2a2a;
}
.hist-main {
flex: 1 1 auto;
min-width: 0;
}
.hist-proj {
color: #fff;
font-size: 13px;
}
.hist-prev {
color: #aaa;
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hist-resume {
flex: none;
background: #2ea043;
color: #fff;
border: none;
border-radius: 5px;
padding: 6px 14px;
cursor: pointer;
font: inherit;
}
.hist-resume:hover {
background: #3fb950;
}
.tab {
display: flex;
align-items: center;
padding: 0 4px 0 12px;
color: #aaa;
background-color: #2a2a2a;
border-right: 1px solid #1a1a1a;
cursor: pointer;
white-space: nowrap;
font-size: 13px;
user-select: none;
max-width: 200px;
}
.tab.active {
background-color: #1a1a1a;
color: #fff;
box-shadow: inset 0 -2px 0 #4a9eff; /* active underline accent */
}
/* Status dot — color reflects connection state. */
.tab-dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
flex: none;
background-color: #666;
}
.dot-connected {
background-color: #3fb950; /* green */
}
.dot-connecting,
.dot-reconnecting {
background-color: #d29922; /* amber */
}
.dot-exited {
background-color: #f85149; /* red */
}
/* Unread: an inactive tab got new output — blue ring + brighter label. */
.tab.unread .tab-dot {
box-shadow: 0 0 0 2px rgba(74, 158, 255, 0.6);
}
.tab.unread .tab-label {
color: #fff;
font-weight: 600;
}
/* Claude Code activity glyph (H2/H4) */
.tab-claude {
margin-right: 6px;
font-size: 12px;
flex: none;
}
/* A background tab that needs approval gets an amber outline. */
.tab.claude-waiting {
background-color: #3a2a10;
box-shadow: inset 0 -2px 0 #d29922;
}
/* Approve/reject banner (H3) — sits just above the key bar. */
#approvalbar {
position: fixed;
left: 0;
right: 0;
bottom: var(--keybar-h);
z-index: 1050;
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
background: #3a2a10;
border-top: 1px solid #d29922;
color: #ffcf8f;
font-size: 14px;
}
.approval-label {
flex: 1 1 auto;
}
#approvalbar button {
flex: none;
border: none;
border-radius: 5px;
padding: 7px 16px;
cursor: pointer;
font: inherit;
font-weight: 600;
}
.approval-yes {
background: #2ea043;
color: #fff;
}
.approval-no {
background: #c93c37;
color: #fff;
}
/* Drag-to-reorder feedback. */
.tab.dragging {
opacity: 0.4;
}
.tab.drag-over {
box-shadow: inset 3px 0 0 #4a9eff; /* drop-position indicator */
}
.tab-label {
padding-right: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Inline rename input (double-click a tab) */
.tab-rename {
width: 110px;
margin: 0 4px;
padding: 2px 4px;
font: inherit;
font-size: 13px;
color: #fff;
background: #111;
border: 1px solid #4a9eff;
border-radius: 3px;
outline: none;
}
.tab-close {
background: none;
border: none;
color: #888;
cursor: pointer;
font-size: 15px;
line-height: 1;
padding: 2px 6px;
border-radius: 3px;
flex: none;
}
.tab-close:hover {
background-color: #444;
color: #fff;
}
/* Desktop (mouse): reveal × on hover/active to reduce clutter.
Touch devices keep × always visible (no hover). */
@media (pointer: fine) {
.tab:not(.active):not(:hover) .tab-close {
opacity: 0;
}
}
.tab-add {
background: none;
border: none;
color: #aaa;
cursor: pointer;
font-size: 18px;
padding: 0 14px;
user-select: none;
flex: none;
}
.tab-add:hover {
color: #fff;
}
/* ── Terminal area (holds one .term-pane per tab) ────────────────── */
#term {
position: absolute;
top: var(--tabbar-h);
left: 0;
right: 0;
bottom: var(--keybar-h);
background-color: #1a1a1a;
overflow: hidden;
}
.term-pane {
position: absolute;
inset: 0;
overflow: hidden;
}
/* ── Shortcut key bar (bottom) — shown on ALL devices ────────────── */
#keybar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: var(--keybar-h);
background-color: #2a2a2a;
border-top: 1px solid #3a3a3a;
display: flex;
flex-direction: row;
overflow-x: auto;
overflow-y: hidden;
z-index: 1000;
gap: 0;
scrollbar-width: thin;
}
#keybar button {
flex: 1 0 auto;
min-width: 48px;
height: var(--keybar-h);
padding: 0 8px;
border: 1px solid #444;
border-top: none;
border-bottom: none;
background-color: #333;
color: #ddd;
font-family: inherit;
font-size: 14px;
cursor: pointer;
border-radius: 0;
transition: background-color 0.12s;
-webkit-user-select: none;
user-select: none;
-webkit-touch-callout: none;
}
#keybar button:active {
background-color: #555;
}
/* Esc is the most-used (interrupt) — make it prominent. */
#keybar button.keybar-btn-primary {
background-color: #5a3a12;
color: #ffcf8f;
font-weight: 600;
min-width: 56px;
}
#keybar button.keybar-btn-primary:active {
background-color: #7a4f18;
}