Files
orca/previews/mobile-driver-overlay-preview.html
T
Jinjing bd546b47d2 Redesign mobile driver overlay layout and clarify action copy (#5647)
Clarify the mobile-driving experience by updating outdated terminology and
making actions more precise. Generic "Mobile" references are replaced with
"Phone", and action buttons now explicitly distinguish between "this"
terminal and "all" terminals. Also introduces a modern card layout featuring
a smartphone icon badge and a live status indicator, complete with updated
locales, tests, and a standalone HTML preview.
2026-06-17 19:21:42 -07:00

523 lines
16 KiB
HTML

<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mobile Driver Overlay — Orca Preview</title>
<style>
/* ── Orca dark tokens (from main.css .dark) ── */
:root {
--background: #0a0a0a;
--foreground: #fafafa;
--card: #171717;
--card-foreground: #fafafa;
--primary: #e5e5e5;
--primary-foreground: #171717;
--muted: #262626;
--muted-foreground: #a1a1a1;
--accent: #404040;
--accent-foreground: #fafafa;
--border: rgb(255 255 255 / 0.07);
--input: rgb(255 255 255 / 0.15);
--ring: #737373;
--radius: 0.625rem;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
'Courier New', monospace;
--orca-pane-title-height: 24px;
--orca-pane-title-fg: rgb(255 255 255 / 0.52);
--orca-pane-title-separator: rgb(255 255 255 / 0.06);
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--xterm-bg: #000000;
--xterm-fg: #cccccc;
--xterm-green: #23d18b;
--xterm-blue: #3b8eea;
--xterm-dim: #6a6a6a;
}
@font-face {
font-family: 'Geist';
src: url('../src/renderer/src/assets/fonts/Geist-Variable.woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: 'Geist', ui-sans-serif, system-ui, sans-serif;
background: var(--background);
color: var(--foreground);
letter-spacing: 0.01em;
}
/* ── Preview chrome (switcher only — not part of Orca UI) ── */
.preview-chrome {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.625rem 1rem;
border-bottom: 1px solid var(--border);
background: color-mix(in srgb, var(--card) 92%, transparent);
backdrop-filter: blur(8px);
}
.preview-chrome h1 {
margin: 0;
font-size: 0.8125rem;
font-weight: 600;
color: var(--muted-foreground);
}
.state-tabs {
display: flex;
gap: 0.375rem;
}
.state-tab {
height: 1.75rem;
padding: 0 0.75rem;
border: 1px solid var(--border);
border-radius: calc(var(--radius) - 2px);
background: transparent;
color: var(--muted-foreground);
font: inherit;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
}
.state-tab[aria-selected='true'] {
border-color: var(--input);
background: color-mix(in srgb, var(--input) 30%, transparent);
color: var(--foreground);
}
/* ── Orca workspace frame ── */
.workspace {
height: 100%;
padding-top: 3rem;
display: flex;
flex-direction: column;
}
.workspace-main {
flex: 1;
min-height: 0;
padding: 0.5rem;
}
/* Terminal pane — matches .pane + .terminal-container */
.pane {
position: relative;
height: 100%;
min-height: 28rem;
overflow: hidden;
background: var(--xterm-bg);
}
.pane[data-has-title]::after {
content: '';
position: absolute;
top: var(--orca-pane-title-height);
left: -5px;
right: -5px;
height: 1px;
background: var(--orca-pane-title-separator);
z-index: 6;
pointer-events: none;
}
.pane-title-bar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: var(--orca-pane-title-height);
z-index: 5;
display: flex;
align-items: center;
padding: 0 8px;
font-size: 13px;
font-family: var(--font-mono);
color: var(--orca-pane-title-fg);
background: transparent;
user-select: none;
}
.xterm-container {
position: relative;
width: calc(100% - 4px);
height: calc(100% - var(--orca-pane-title-height));
margin-top: var(--orca-pane-title-height);
margin-left: 4px;
}
.xterm-screen {
position: absolute;
inset: 0;
padding: 8px 10px;
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.35;
color: var(--xterm-fg);
white-space: pre;
overflow: hidden;
}
.xterm-screen .green {
color: var(--xterm-green);
}
.xterm-screen .blue {
color: var(--xterm-blue);
}
.xterm-screen .dim {
color: var(--xterm-dim);
}
/* ── MobileDriverOverlay (from MobileDriverOverlay.tsx) ── */
.mobile-driver-banner {
pointer-events: none;
position: absolute;
inset: 0;
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
}
.mobile-driver-banner.is-chip {
align-items: flex-start;
justify-content: flex-end;
padding: 0;
}
.overlay-card {
pointer-events: auto;
display: flex;
width: 100%;
max-width: 30rem;
flex-direction: column;
gap: 0.75rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--card);
color: var(--card-foreground);
padding: 1.5rem 1.5rem 1.25rem;
box-shadow: var(--shadow-xs);
}
.overlay-header {
display: flex;
align-items: flex-start;
gap: 0.75rem;
}
.phone-icon-badge {
display: flex;
width: 2.5rem;
height: 2.5rem;
flex-shrink: 0;
align-items: center;
justify-content: center;
border: 1px solid var(--border);
border-radius: 9999px;
}
.phone-icon-badge.tone-driving {
background: var(--muted);
}
.phone-icon-badge.tone-held {
background: color-mix(in srgb, var(--muted) 60%, transparent);
}
.phone-icon-badge svg {
width: 1.25rem;
height: 1.25rem;
color: var(--foreground);
}
.overlay-copy {
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
gap: 0.25rem;
}
.overlay-eyebrow {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.025em;
text-transform: uppercase;
}
.overlay-eyebrow.tone-driving {
color: var(--foreground);
}
.overlay-eyebrow.tone-held {
color: var(--muted-foreground);
}
.live-dot {
width: 0.375rem;
height: 0.375rem;
border-radius: 9999px;
background: var(--foreground);
}
.overlay-title {
font-size: 1rem;
font-weight: 600;
line-height: 1.25;
}
.overlay-body {
font-size: 0.875rem;
line-height: 1.625;
color: var(--muted-foreground);
}
.overlay-actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 0.25rem;
}
/* Button primitive (button.tsx, dark theme) */
[data-slot='button'] {
display: inline-flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
gap: 0.375rem;
border-radius: calc(var(--radius) - 2px);
border: 1px solid transparent;
font-size: 0.875rem;
font-weight: 500;
white-space: nowrap;
cursor: default;
outline: none;
transition: all 150ms ease;
}
[data-slot='button'][data-size='sm'] {
height: 2rem;
padding: 0 0.75rem;
}
[data-slot='button'][data-size='xs'] {
height: 1.5rem;
gap: 0.25rem;
padding: 0 0.5rem;
font-size: 0.75rem;
}
[data-slot='button'][data-variant='default'] {
background: var(--primary);
color: var(--primary-foreground);
}
[data-slot='button'][data-variant='default'].is-focused {
border-color: var(--ring);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
}
[data-slot='button'][data-variant='outline'] {
border-color: var(--input);
background: color-mix(in srgb, var(--input) 30%, transparent);
color: var(--foreground);
box-shadow: var(--shadow-xs);
}
[data-slot='button'][data-variant='ghost'] {
background: transparent;
color: var(--foreground);
padding-left: 0.25rem;
padding-right: 0.25rem;
}
/* LockChip */
.mobile-driver-banner .lock-chip {
pointer-events: auto;
position: absolute;
right: 0.5rem;
top: 0.5rem;
z-index: 50;
display: flex;
align-items: center;
gap: 0.375rem;
border: 1px solid var(--border);
border-radius: 9999px;
background: var(--card);
color: var(--card-foreground);
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
font-weight: 500;
box-shadow: var(--shadow-xs);
}
.lock-chip svg {
width: 0.75rem;
height: 0.75rem;
color: var(--foreground);
}
.state-panel[hidden] {
display: none !important;
}
</style>
</head>
<body>
<div class="preview-chrome">
<h1>Mobile Driver Overlay — production layout preview</h1>
<div class="state-tabs" role="tablist" aria-label="Overlay state">
<button class="state-tab" type="button" role="tab" data-state="driving" aria-selected="false">
Phone in control
</button>
<button class="state-tab" type="button" role="tab" data-state="held" aria-selected="true">
Phone session ended
</button>
<button class="state-tab" type="button" role="tab" data-state="chip" aria-selected="false">
Collapsed chip
</button>
</div>
</div>
<div class="workspace">
<div class="workspace-main">
<div class="pane" data-has-title>
<div class="pane-title-bar">
<span class="pane-title-text">bash — fix-phone-resizing</span>
</div>
<div class="xterm-container">
<div class="xterm-screen" aria-hidden="true">
<span class="green"></span> fix-phone-resizing <span class="dim">git:(</span><span class="blue">fix-phone-resizing</span><span class="dim">) ✗</span>
<span class="green"></span> fix-phone-resizing <span class="dim">git status</span>
On branch fix-phone-resizing
Changes not staged for commit:
<span class="dim">modified:</span> src/renderer/src/components/terminal-pane/MobileDriverOverlay.tsx
<span class="dim">modified:</span> src/renderer/src/i18n/locales/en.json
<span class="green"></span> fix-phone-resizing <span class="dim">_</span></div>
<!-- Driving -->
<div class="state-panel mobile-driver-banner" id="panel-driving" role="dialog" hidden>
<div class="overlay-card">
<div class="overlay-header">
<div class="phone-icon-badge tone-driving">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
<path d="M12 18h.01" />
</svg>
</div>
<div class="overlay-copy">
<div class="overlay-eyebrow tone-driving">
<span class="live-dot" aria-hidden="true"></span>
<span>From your phone</span>
</div>
<div class="overlay-title">Your phone is in control</div>
</div>
</div>
<div class="overlay-body">
Desktop keyboard is paused. Take back this terminal to type here, take back all terminals your phone controls, or collapse to keep watching.
</div>
<div class="overlay-actions">
<button data-slot="button" data-variant="outline" data-size="sm" type="button">Collapse</button>
<button data-slot="button" data-variant="outline" data-size="sm" type="button">Take back all terminals</button>
<button data-slot="button" data-variant="default" data-size="sm" type="button" class="is-focused">Take back this terminal</button>
</div>
</div>
</div>
<!-- Held -->
<div class="state-panel mobile-driver-banner" id="panel-held" role="dialog">
<div class="overlay-card">
<div class="overlay-header">
<div class="phone-icon-badge tone-held">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
<path d="M12 18h.01" />
</svg>
</div>
<div class="overlay-copy">
<div class="overlay-eyebrow tone-held">
<span>From your phone</span>
</div>
<div class="overlay-title">Your phone left this at phone size</div>
</div>
</div>
<div class="overlay-body">
Your phone session ended. Restore to desktop size for this terminal, or for all terminals your phone left at phone size.
</div>
<div class="overlay-actions">
<button data-slot="button" data-variant="outline" data-size="sm" type="button">Restore all terminals</button>
<button data-slot="button" data-variant="default" data-size="sm" type="button" class="is-focused">Restore this terminal</button>
</div>
</div>
</div>
<!-- Chip -->
<div class="state-panel mobile-driver-banner is-chip" id="panel-chip" hidden>
<div class="lock-chip mobile-driver-banner">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
<path d="M12 18h.01" />
</svg>
<button data-slot="button" data-variant="ghost" data-size="xs" type="button">Phone driving</button>
<button data-slot="button" data-variant="default" data-size="xs" type="button">Take back</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const tabs = document.querySelectorAll('.state-tab')
const panels = {
driving: document.getElementById('panel-driving'),
held: document.getElementById('panel-held'),
chip: document.getElementById('panel-chip')
}
function showState(state) {
for (const [key, panel] of Object.entries(panels)) {
panel.hidden = key !== state
}
tabs.forEach((tab) => {
tab.setAttribute('aria-selected', tab.dataset.state === state ? 'true' : 'false')
})
}
tabs.forEach((tab) => {
tab.addEventListener('click', () => showState(tab.dataset.state))
})
</script>
</body>
</html>