diff --git a/public/icon.svg b/public/icon.svg index d0af5c0..64255cd 100644 --- a/public/icon.svg +++ b/public/icon.svg @@ -1,6 +1,16 @@ - - + + + + + + + + + + + + diff --git a/public/index.html b/public/index.html index df675fe..88b8d67 100644 --- a/public/index.html +++ b/public/index.html @@ -5,7 +5,7 @@ Web Terminal - + diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest index d84b8c0..606efe7 100644 --- a/public/manifest.webmanifest +++ b/public/manifest.webmanifest @@ -6,8 +6,8 @@ "scope": "./", "display": "standalone", "orientation": "any", - "background_color": "#1a1a1a", - "theme_color": "#1a1a1a", + "background_color": "#0e0f13", + "theme_color": "#0e0f13", "icons": [ { "src": "./icon.svg", diff --git a/public/settings.ts b/public/settings.ts index 7337b17..0c6efac 100644 --- a/public/settings.ts +++ b/public/settings.ts @@ -13,14 +13,19 @@ export interface Settings { } export const THEMES: Record = { - dark: { background: '#1a1a1a', foreground: '#e8e8e8', cursor: '#e8e8e8' }, - light: { - background: '#f5f5f5', - foreground: '#1a1a1a', - cursor: '#1a1a1a', - selectionBackground: '#bcd3f5', + dark: { + background: '#0e0f13', + foreground: '#e7e8ec', + cursor: '#7c8cff', + selectionBackground: 'rgba(124,140,255,0.28)', }, - solarized: { background: '#002b36', foreground: '#93a1a1', cursor: '#93a1a1' }, + light: { + background: '#f6f7f9', + foreground: '#1a1d24', + cursor: '#5b6cff', + selectionBackground: '#cdd6ff', + }, + solarized: { background: '#002b36', foreground: '#93a1a1', cursor: '#268bd2' }, } const KEY = 'web-terminal:settings' diff --git a/public/style.css b/public/style.css index 7ac30df..7237cc4 100644 --- a/public/style.css +++ b/public/style.css @@ -1,113 +1,357 @@ -/* Web Terminal Styling */ +/* Web Terminal — modern dark UI */ :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; + /* palette (design tokens) */ + --bg: #0e0f13; /* terminal / deepest surface */ + --surface-1: #15161c; /* tab bar, key bar */ + --surface-2: #1c1e26; /* tabs, buttons, cards */ + --surface-3: #252834; /* hover / active */ + --border: rgba(255, 255, 255, 0.07); + --border-strong: rgba(255, 255, 255, 0.13); + --text: #e7e8ec; + --text-dim: #9aa0ad; + --text-faint: #6b7080; + --accent: #7c8cff; /* indigo */ + --accent-2: #9d7cff; + --accent-soft: rgba(124, 140, 255, 0.16); + --green: #46d07f; + --amber: #f5b14c; + --red: #ff6b6b; + --shadow: 0 10px 40px rgba(0, 0, 0, 0.55); + --radius: 9px; + --radius-lg: 14px; + + --tabbar-h: 40px; + --keybar-h: 46px; + + --ui-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; } -/* Touch devices: bigger tap targets for tabs and the key bar. */ @media (pointer: coarse) { :root { - --tabbar-h: 44px; - --keybar-h: 58px; + --tabbar-h: 48px; + --keybar-h: 60px; } } -html, body { +html, +body { margin: 0; padding: 0; width: 100%; height: 100%; - background-color: #1a1a1a; - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + background-color: var(--bg); + color: var(--text); + font-family: var(--ui-font); overflow: hidden; } -/* ── Tab bar (top) ───────────────────────────────────────────────── */ +* { + scrollbar-width: thin; + scrollbar-color: var(--surface-3) transparent; +} + +/* ── Tab bar ─────────────────────────────────────────────────────── */ #tabbar { position: fixed; - top: 0; - left: 0; - right: 0; + inset: 0 0 auto 0; height: var(--tabbar-h); - background-color: #222; - border-bottom: 1px solid #3a3a3a; + background: linear-gradient(180deg, #181a22, var(--surface-1)); + border-bottom: 1px solid var(--border); 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; + align-items: center; + gap: 2px; + padding: 0 4px; overflow-x: auto; overflow-y: hidden; - scrollbar-width: thin; +} +#tabs::-webkit-scrollbar { + height: 0; } +/* tabs as floating rounded chips */ +.tab { + display: flex; + align-items: center; + gap: 2px; + height: calc(var(--tabbar-h) - 12px); + padding: 0 4px 0 10px; + border-radius: 8px; + color: var(--text-dim); + background: transparent; + cursor: pointer; + white-space: nowrap; + font-size: 13px; + user-select: none; + max-width: 220px; + transition: + background 0.12s ease, + color 0.12s ease; +} +.tab:hover { + background: var(--surface-2); + color: var(--text); +} +.tab.active { + background: var(--surface-3); + color: #fff; + box-shadow: inset 0 0 0 1px var(--border-strong); +} + +.tab-dot { + width: 7px; + height: 7px; + border-radius: 50%; + margin-right: 8px; + flex: none; + background: var(--text-faint); +} +.dot-connected { + background: var(--green); + box-shadow: 0 0 6px rgba(70, 208, 127, 0.5); +} +.dot-connecting, +.dot-reconnecting { + background: var(--amber); +} +.dot-exited { + background: var(--red); +} + +.tab-label { + padding-right: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.tab-rename { + width: 120px; + margin: 0 4px; + padding: 3px 6px; + font: inherit; + font-size: 13px; + color: #fff; + background: var(--bg); + border: 1px solid var(--accent); + border-radius: 6px; + outline: none; +} + +/* unread / Claude-waiting accents */ +.tab.unread .tab-dot { + box-shadow: 0 0 0 3px var(--accent-soft); +} +.tab.unread .tab-label { + color: #fff; + font-weight: 600; +} +.tab-claude { + margin-right: 4px; + font-size: 12px; + flex: none; +} +.tab.claude-waiting { + background: rgba(245, 177, 76, 0.14); + box-shadow: inset 0 0 0 1px rgba(245, 177, 76, 0.5); +} + +.tab.dragging { + opacity: 0.4; +} +.tab.drag-over { + box-shadow: inset 3px 0 0 var(--accent); +} + +.tab-close { + background: none; + border: none; + color: var(--text-faint); + cursor: pointer; + font-size: 16px; + line-height: 1; + padding: 3px 6px; + border-radius: 6px; + flex: none; +} +.tab-close:hover { + background: rgba(255, 255, 255, 0.1); + color: #fff; +} +@media (pointer: fine) { + .tab:not(.active):not(:hover) .tab-close { + opacity: 0; + } +} + +.tab-add { + background: none; + border: none; + color: var(--text-dim); + cursor: pointer; + font-size: 18px; + padding: 0 10px; + flex: none; + border-radius: 7px; +} +.tab-add:hover { + color: #fff; + background: var(--surface-2); +} + +/* toolbar */ #toolbar { flex: 0 0 auto; display: flex; align-items: center; - border-left: 1px solid #3a3a3a; + gap: 2px; + padding: 0 6px; + border-left: 1px solid var(--border); } - .toolbtn { background: none; border: none; - color: #aaa; + color: var(--text-dim); cursor: pointer; font-size: 15px; - padding: 0 12px; - height: 100%; + padding: 7px 9px; + border-radius: 8px; + line-height: 1; + transition: background 0.12s ease; } .toolbtn:hover { color: #fff; - background-color: #2a2a2a; + background: var(--surface-2); +} + +/* ── Terminal area ───────────────────────────────────────────────── */ +#term { + position: absolute; + inset: var(--tabbar-h) 0 var(--keybar-h) 0; + background: var(--bg); + overflow: hidden; +} +.term-pane { + position: absolute; + inset: 0; + padding: 6px 8px 0; + box-sizing: border-box; + overflow: hidden; +} + +/* ── Key bar (rounded chips) ─────────────────────────────────────── */ +#keybar { + position: fixed; + inset: auto 0 0 0; + height: var(--keybar-h); + background: var(--surface-1); + border-top: 1px solid var(--border); + display: flex; + align-items: center; + gap: 5px; + padding: 0 6px; + overflow-x: auto; + overflow-y: hidden; + z-index: 1000; +} +#keybar::-webkit-scrollbar { + height: 0; +} +#keybar button { + flex: 1 0 auto; + min-width: 46px; + height: calc(var(--keybar-h) - 14px); + padding: 0 12px; + border: 1px solid var(--border); + background: var(--surface-2); + color: var(--text); + font-family: var(--ui-font); + font-size: 13px; + cursor: pointer; + border-radius: 8px; + transition: + background 0.1s ease, + transform 0.05s ease; + user-select: none; + -webkit-user-select: none; + -webkit-touch-callout: none; +} +#keybar button:hover { + background: var(--surface-3); +} +#keybar button:active { + transform: translateY(1px); + background: var(--surface-3); +} +#keybar button.keybar-btn-primary { + background: var(--accent-soft); + color: #cdd4ff; + border-color: rgba(124, 140, 255, 0.4); + font-weight: 600; + min-width: 54px; +} +#keybar button.keybar-btn-primary:hover { + background: rgba(124, 140, 255, 0.26); +} + +/* ── Shared: overlays, cards, inputs, buttons ────────────────────── */ +.overlay { + position: fixed; + inset: 0; + z-index: 1200; + display: flex; + background: rgba(8, 9, 12, 0.6); + -webkit-backdrop-filter: blur(6px); + backdrop-filter: blur(6px); } -/* Search box overlay (M1) */ #searchbox { position: fixed; - top: calc(var(--tabbar-h) + 6px); - right: 8px; + top: calc(var(--tabbar-h) + 8px); + right: 10px; 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); + gap: 5px; + padding: 7px; + background: var(--surface-2); + border: 1px solid var(--border-strong); + border-radius: 10px; + box-shadow: var(--shadow); } .search-input { - width: 180px; - padding: 4px 6px; + width: 190px; + padding: 5px 8px; font: inherit; font-size: 13px; color: #fff; - background: #111; - border: 1px solid #444; - border-radius: 3px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 7px; outline: none; } +.search-input:focus { + border-color: var(--accent); +} #searchbox button { - background: #333; - border: 1px solid #444; - color: #ddd; + background: var(--surface-3); + border: 1px solid var(--border); + color: var(--text); cursor: pointer; - border-radius: 3px; - padding: 0 8px; + border-radius: 7px; + padding: 0 10px; } #searchbox button:hover { - background: #555; + background: var(--accent-soft); } -/* QR connect modal (M5) */ +/* QR modal */ #qrmodal { position: fixed; inset: 0; @@ -115,145 +359,106 @@ html, body { display: flex; align-items: center; justify-content: center; - background: rgba(0, 0, 0, 0.6); + background: rgba(8, 9, 12, 0.6); + -webkit-backdrop-filter: blur(6px); + backdrop-filter: blur(6px); } .qr-card { - background: #fff; - color: #111; - border-radius: 12px; - padding: 20px; + background: var(--surface-2); + color: var(--text); + border: 1px solid var(--border-strong); + border-radius: var(--radius-lg); + padding: 22px; text-align: center; max-width: 90vw; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + box-shadow: var(--shadow); +} +.qr-card canvas { + border-radius: 10px; + background: #fff; + padding: 8px; } .qr-title { font-weight: 600; - margin-bottom: 12px; + margin-bottom: 14px; } .qr-url { - margin-top: 10px; + margin-top: 12px; font-size: 13px; - color: #333; + color: var(--accent); word-break: break-all; } .qr-note { margin-top: 8px; font-size: 12px; - color: #666; + color: var(--text-dim); max-width: 240px; } .qr-close { - margin-top: 14px; - padding: 6px 18px; - border: none; - border-radius: 6px; - background: #1a1a1a; + margin-top: 16px; + padding: 8px 20px; + border: 1px solid var(--border-strong); + border-radius: 8px; + background: var(--surface-3); color: #fff; cursor: pointer; font: inherit; } +.qr-close:hover { + background: var(--accent-soft); +} -/* Settings panel (M3) */ +/* Settings panel */ #settingspanel { position: fixed; - top: calc(var(--tabbar-h) + 6px); - right: 8px; + top: calc(var(--tabbar-h) + 8px); + right: 10px; 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; + background: var(--surface-2); + border: 1px solid var(--border-strong); + border-radius: 12px; + padding: 12px; + min-width: 220px; + box-shadow: var(--shadow); + color: var(--text); } .settings-row { display: flex; align-items: center; gap: 6px; - margin: 6px 0; + margin: 8px 0; } .settings-label { width: 48px; - color: #aaa; + color: var(--text-dim); font-size: 13px; } .settings-opt { - background: #333; - border: 1px solid #444; - color: #ddd; - border-radius: 4px; - padding: 4px 9px; + background: var(--surface-3); + border: 1px solid var(--border); + color: var(--text); + border-radius: 7px; + padding: 5px 10px; cursor: pointer; font: inherit; font-size: 12px; + transition: background 0.1s ease; +} +.settings-opt:hover { + background: var(--surface-1); } .settings-opt.active { - background: #4a9eff; + background: var(--accent); color: #fff; - border-color: #4a9eff; + border-color: var(--accent); } .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) */ +/* Dashboard + history share the centered-card look */ +#dashboard, #historymodal { position: fixed; inset: 0; @@ -261,35 +466,65 @@ html, body { display: flex; align-items: flex-start; justify-content: center; - padding-top: 56px; - background: rgba(0, 0, 0, 0.6); + padding-top: 60px; + background: rgba(8, 9, 12, 0.6); + -webkit-backdrop-filter: blur(6px); + backdrop-filter: blur(6px); } +.dash-card, .hist-card { - background: #222; - border: 1px solid #3a3a3a; - border-radius: 10px; - min-width: 360px; + background: var(--surface-2); + border: 1px solid var(--border-strong); + border-radius: var(--radius-lg); + min-width: 340px; max-width: 92vw; max-height: 72vh; overflow-y: auto; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + box-shadow: var(--shadow); } +.dash-title, .hist-title { - padding: 12px 16px; + padding: 14px 18px; font-weight: 600; color: #fff; - border-bottom: 1px solid #3a3a3a; -} -.hist-empty { - padding: 16px; - color: #aaa; + border-bottom: 1px solid var(--border); } +.dash-row, .hist-row { display: flex; align-items: center; gap: 10px; - padding: 10px 16px; - border-bottom: 1px solid #2a2a2a; + padding: 11px 18px; + cursor: pointer; + color: var(--text); + border-bottom: 1px solid var(--border); + transition: background 0.1s ease; +} +.dash-row:hover, +.hist-row:hover { + background: var(--surface-3); +} +.dash-row.active { + box-shadow: inset 3px 0 0 var(--accent); +} +.dash-name { + flex: 1; +} +.dash-claude { + color: var(--text-dim); + font-size: 13px; +} +.dash-claude.pending { + color: var(--amber); + font-weight: 600; +} + +.hist-empty { + padding: 18px; + color: var(--text-dim); +} +.hist-row { + cursor: default; } .hist-main { flex: 1 1 auto; @@ -300,7 +535,7 @@ html, body { font-size: 13px; } .hist-prev { - color: #aaa; + color: var(--text-dim); font-size: 12px; overflow: hidden; text-overflow: ellipsis; @@ -308,93 +543,32 @@ html, body { } .hist-resume { flex: none; - background: #2ea043; + background: var(--accent); color: #fff; border: none; - border-radius: 5px; - padding: 6px 14px; + border-radius: 8px; + padding: 7px 15px; cursor: pointer; font: inherit; + font-weight: 500; + transition: background 0.1s ease; } .hist-resume:hover { - background: #3fb950; + background: var(--accent-2); } -.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. */ +/* Approval banner (H3) */ #approvalbar { position: fixed; - left: 0; - right: 0; - bottom: var(--keybar-h); + inset: auto 0 var(--keybar-h) 0; z-index: 1050; display: flex; align-items: center; gap: 10px; - padding: 8px 12px; - background: #3a2a10; - border-top: 1px solid #d29922; - color: #ffcf8f; + padding: 10px 14px; + background: linear-gradient(180deg, rgba(245, 177, 76, 0.16), rgba(245, 177, 76, 0.08)); + border-top: 1px solid rgba(245, 177, 76, 0.5); + color: #ffe0ad; font-size: 14px; } .approval-label { @@ -403,157 +577,17 @@ html, body { #approvalbar button { flex: none; border: none; - border-radius: 5px; - padding: 7px 16px; + border-radius: 8px; + padding: 8px 16px; cursor: pointer; font: inherit; font-weight: 600; } .approval-yes { - background: #2ea043; - color: #fff; + background: var(--green); + color: #06210f; } .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; + background: var(--red); + color: #2a0808; } diff --git a/public/terminal-session.ts b/public/terminal-session.ts index 591a57c..35b36b4 100644 --- a/public/terminal-session.ts +++ b/public/terminal-session.ts @@ -107,7 +107,7 @@ export class TerminalSession { this.term = new Terminal({ scrollback: 5000, fontFamily: 'Menlo, Consolas, monospace', - theme: { background: '#1a1a1a', foreground: '#e8e8e8', cursor: '#e8e8e8' }, + theme: { background: '#0e0f13', foreground: '#e7e8ec', cursor: '#7c8cff' }, }) this.fitAddon = new FitAddon() this.term.loadAddon(this.fitAddon)