mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
SessionMode gains 'pi', a first-class backend alongside Claude Code, OpenCode, Codex, Gemini and Antigravity: its own PTY, tmux session, rose tab identity, welcome button, run-mode entry, cron agentType, Docker and remote-SSH command defaults, and clone-repo Brain option. Pi is a different shape of CLI from the other four, and three decisions follow from that: - It has NO permission prompts and no sandbox, so there is no --dangerously-skip-permissions analog and none was invented. The privilege-shaped knob is the tri-state approveProjectTrust, which makes pi load and EXECUTE repo-local .pi/extensions TypeScript and install missing project packages. clampExternalCliBypassForOwner() therefore puts pi in the MATERIALIZE branch: a non-granted multi-user owner gets --no-approve even when no config was sent, because pi's own default is a prompt the session user could answer themselves. That helper had zero test coverage; it now has coverage for all four CLIs. - Only the PI_ prefix joins the env allowlist. Pi's ~34 provider key vars share no prefix and ALLOWED_ENV_PREFIXES is one global list with no mode context, so admitting them would widen the allowlist for every mode at once. Auth goes through pi's /login or the server's own environment. --api-key is deliberately never wired: it would put a provider secret on the spawn command line. - pi stays OUT of isAltScreenStripMode(). Its default TUI renders into the main screen with terminal-owned scrollback, and its 0.84.0 fullscreen mode is runtime-switchable via /settings; that flip was measured to put the pane into the alt screen, which the strip would have corrupted. pi-cli-resolver.ts additionally sanity-probes `pi --version` and requires semver-shaped output, because `pi` is a short generic name a stray binary can shadow; GET /api/pi/status surfaces path and version so a misresolution is diagnosable rather than presenting as a broken mode. Docker installs pi in its own --ignore-scripts step so that flag cannot affect the other four CLIs, and seeds its credentials per-file rather than whole-dir (~/.pi/agent also holds sessions, extensions and package trees). Verified end to end against pi 0.84.1 on an isolated instance: resolver search-dir fallback, flag construction, piConfig persistence across a full server restart, the trust prompt and its --no-approve suppression, the rose Run button on the default daylight-blue skin (the nested skin block eats per-mode gradients unless the rule lives inside it), and the buffer local-echo policy, which pi tolerates where codex did not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3607 lines
95 KiB
CSS
3607 lines
95 KiB
CSS
/* ============================================================================
|
||
Mobile & Touch Styles
|
||
Extracted from styles.css for cleaner separation.
|
||
Loaded only on small/medium screens via media attribute in index.html.
|
||
============================================================================ */
|
||
|
||
/* ============================================================================
|
||
Mobile Init Flash Prevention
|
||
Hides elements that are off by default on mobile BEFORE JS runs.
|
||
The inline <script> in <head> adds .mobile-init to <html> synchronously.
|
||
app.init() removes .mobile-init after applying visibility settings.
|
||
============================================================================ */
|
||
html.mobile-init .header-font-controls,
|
||
html.mobile-init .header-system-stats,
|
||
html.mobile-init .header-tokens,
|
||
html.mobile-init .monitor-panel,
|
||
html.mobile-init .subagents-panel,
|
||
html.mobile-init .project-insights-panel,
|
||
html.mobile-init .file-browser-panel {
|
||
display: none !important;
|
||
}
|
||
|
||
/* ============================================================================
|
||
iOS Zoom Prevention (all mobile/tablet sizes)
|
||
iOS Safari ignores user-scalable=no since iOS 10.
|
||
font-size >= 16px on inputs prevents auto-zoom on focus.
|
||
touch-action: manipulation prevents double-tap zoom.
|
||
============================================================================ */
|
||
@media (max-width: 768px) {
|
||
/* Prevent iOS auto-zoom on input focus (triggers at font-size < 16px) */
|
||
input, textarea, select {
|
||
font-size: 16px !important;
|
||
}
|
||
|
||
/* Prevent double-tap zoom globally */
|
||
html {
|
||
touch-action: manipulation;
|
||
}
|
||
|
||
/* No "open in new window" (detach) on phones/tablets — popped-out browser
|
||
windows aren't usable there. !important beats the hover/detached reveal
|
||
rules in styles.css */
|
||
.session-tab .tab-detach {
|
||
display: none !important;
|
||
}
|
||
}
|
||
|
||
/* ============================================================================
|
||
Tablet Breakpoint (430px - 768px)
|
||
============================================================================ */
|
||
@media (max-width: 768px) and (min-width: 430px) {
|
||
/* Compact header for tablet - fixed at top, includes safe area padding */
|
||
.header {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
min-height: 48px;
|
||
max-height: 48px;
|
||
padding: 0.35rem 0.5rem;
|
||
padding-left: calc(0.5rem + var(--safe-area-left));
|
||
padding-right: calc(0.5rem + var(--safe-area-right));
|
||
background: #0a0a0a;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||
contain: none;
|
||
overflow: visible;
|
||
z-index: 1200;
|
||
}
|
||
|
||
/* iOS safe area adjustment for fixed header - header extends into notch area */
|
||
.ios-device .header {
|
||
padding-top: calc(0.35rem + var(--safe-area-top));
|
||
min-height: calc(48px + var(--safe-area-top));
|
||
max-height: calc(48px + var(--safe-area-top));
|
||
}
|
||
|
||
/* Push ALL content below fixed header (not just .main) so banners
|
||
(respawn, timer, orchestrator) between header and main are visible */
|
||
.app {
|
||
padding-top: 54px;
|
||
}
|
||
|
||
.ios-device .app {
|
||
padding-top: calc(54px + var(--safe-area-top));
|
||
}
|
||
|
||
/* Font controls - smaller on tablet, visibility controlled by JS */
|
||
.header-font-controls {
|
||
gap: 0.15rem;
|
||
padding: 0.15rem 0.25rem;
|
||
}
|
||
|
||
.header-font-controls .btn-icon-sm {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
|
||
/* System stats - compact on tablet, visibility controlled by JS */
|
||
.header-system-stats {
|
||
font-size: 0.6rem;
|
||
gap: 0.25rem;
|
||
}
|
||
|
||
/* Inline, always-visible header utilities (eye / multimonitor). The
|
||
position:fixed collapsible tray (02fa3f3) had its expand toggle reverted on
|
||
master but left this container permanently `mobile-collapsed` → the response
|
||
viewer eye became unreachable on phones. Restored to the simple inline flow. */
|
||
.header-right {
|
||
gap: 0.35rem;
|
||
}
|
||
|
||
.btn-icon-header:not(.btn-sm) {
|
||
width: 44px;
|
||
height: 44px;
|
||
}
|
||
|
||
/* Compact session tabs — .tabs-two-rows override needed to match
|
||
specificity of .session-tabs.tabs-two-rows in styles.css (0,2,0).
|
||
overscroll-behavior-x keeps a swipe that runs past the last tab inside the
|
||
strip: chained to the page it becomes the browser's back gesture, which is
|
||
exactly the swipe someone makes reaching for the rightmost tabs (#257). */
|
||
.session-tabs,
|
||
.session-tabs.tabs-two-rows {
|
||
flex-wrap: nowrap;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
-webkit-overflow-scrolling: touch;
|
||
overscroll-behavior-x: contain;
|
||
scrollbar-width: none;
|
||
max-height: 52px;
|
||
gap: 3px;
|
||
}
|
||
|
||
.session-tabs::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
.session-tab {
|
||
padding: 0.4rem 0.6rem;
|
||
font-size: 0.75rem;
|
||
min-height: 40px;
|
||
}
|
||
|
||
.session-tab .tab-name {
|
||
max-width: 80px;
|
||
}
|
||
|
||
/* Compact toolbar - includes safe area padding */
|
||
.toolbar {
|
||
padding: 0.4rem 0.5rem;
|
||
padding-left: calc(0.5rem + var(--safe-area-left));
|
||
padding-right: calc(0.5rem + var(--safe-area-right));
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
/* Hide instance count buttons on tablet too */
|
||
.tab-count-group {
|
||
display: none;
|
||
}
|
||
|
||
/* Compact toolbar buttons */
|
||
.btn-toolbar {
|
||
padding: 0.4rem 0.8rem;
|
||
font-size: 0.8rem;
|
||
min-height: 40px;
|
||
}
|
||
|
||
.case-select-group {
|
||
max-width: none;
|
||
}
|
||
|
||
.case-combobox {
|
||
width: 160px;
|
||
}
|
||
|
||
.toolbar-select {
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
/* Stack panels vertically */
|
||
.monitor-panel,
|
||
.subagents-panel {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
left: 0;
|
||
right: 0;
|
||
border-radius: 8px 8px 0 0;
|
||
max-height: 40vh;
|
||
}
|
||
|
||
/* Project insights - compact on tablet, visibility controlled by JS */
|
||
.project-insights-panel {
|
||
max-width: 280px;
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
/* ---- Settings Modal: Tablet Optimizations ---- */
|
||
|
||
/* Modals must stack above the fixed tablet header (z-index 1200) so the
|
||
modal header with the close button stays visible */
|
||
.modal {
|
||
z-index: 1300;
|
||
}
|
||
|
||
.command-palette-modal {
|
||
padding: 10vh 0.75rem 0;
|
||
}
|
||
|
||
.command-palette-shell {
|
||
width: 100%;
|
||
max-height: 74vh;
|
||
}
|
||
|
||
.command-palette-input-row {
|
||
grid-template-columns: 20px minmax(0, 1fr);
|
||
}
|
||
|
||
.command-palette-input-row kbd {
|
||
display: none;
|
||
}
|
||
|
||
.command-palette-item {
|
||
min-height: 56px;
|
||
}
|
||
|
||
/* Settings grid stays 2-col on tablet but tighter */
|
||
.settings-grid {
|
||
gap: 0.4rem 0.75rem;
|
||
}
|
||
|
||
.settings-item {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
/* Event type grid - slightly tighter */
|
||
.event-type-grid {
|
||
grid-template-columns: 1fr 40px 40px 40px;
|
||
gap: 5px 6px;
|
||
}
|
||
|
||
.event-label {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
/* ---- Subagent Windows: Tablet Optimizations ---- */
|
||
|
||
.subagent-window {
|
||
width: 320px;
|
||
height: 280px;
|
||
min-width: 240px;
|
||
min-height: 160px;
|
||
}
|
||
|
||
.subagent-window-header {
|
||
padding: 0.35rem 0.5rem;
|
||
}
|
||
|
||
.subagent-window-title .id {
|
||
font-size: 0.7rem;
|
||
max-width: 180px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.subagent-window-title .status {
|
||
font-size: 0.55rem;
|
||
}
|
||
|
||
.subagent-window-body {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
/* ---- Respawn Banner: Tablet Optimizations ---- */
|
||
|
||
.respawn-banner {
|
||
padding: 0.25rem 0.5rem;
|
||
padding-left: calc(0.5rem + var(--safe-area-left));
|
||
padding-right: calc(0.5rem + var(--safe-area-right));
|
||
font-size: 0.65rem;
|
||
}
|
||
|
||
.respawn-compact-layout {
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.respawn-status-row1 {
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
.respawn-action-log {
|
||
max-height: 2.6em;
|
||
font-size: 0.6rem;
|
||
}
|
||
|
||
.respawn-countdown-timer {
|
||
font-size: 0.55rem;
|
||
}
|
||
|
||
/* Respawn timer bar — slimmer on tablet */
|
||
.respawn-timer-bar {
|
||
width: 24px;
|
||
}
|
||
|
||
/* Show desktop voice button on tablet (hidden by max-width:1023px in styles.css,
|
||
mobile .btn-voice-mobile only shows at <430px) */
|
||
.toolbar-center .btn-toolbar.btn-voice {
|
||
display: flex !important;
|
||
}
|
||
|
||
/* Toolbar — use desktop-style sizing on tablet (plenty of room at 430-768px) */
|
||
.toolbar {
|
||
padding: 0 0.5rem;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.toolbar-left,
|
||
.toolbar-right {
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
/* Instance count controls are hidden on tablet, so toolbar-group needs gap
|
||
to space out Run / Stop / Run Shell (desktop uses gap:0 because -1+ separates them) */
|
||
.toolbar-group {
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.btn-toolbar {
|
||
padding: 0.4rem 0.75rem;
|
||
font-size: 0.75rem;
|
||
min-height: unset;
|
||
}
|
||
}
|
||
|
||
/* ============================================================================
|
||
Phone Breakpoint (<430px)
|
||
============================================================================ */
|
||
@media (max-width: 430px) {
|
||
/* Phones get a 44px header, up from 36px. Every header control is a touch
|
||
target and 44px is the floor for one; the brand "C" that gets you home is
|
||
the one that matters most. Redefined as the TOKEN rather than a literal so
|
||
the panels positioned off `var(--header-height)` (file browser, insights,
|
||
plan overlays in styles.css) follow it instead of drifting 8px under the
|
||
header. Costs 8px of terminal height on a phone. */
|
||
:root {
|
||
--header-height: 44px;
|
||
}
|
||
|
||
/* Phone brand collapses to a single "C" home button: hide the wordmark,
|
||
keep the tap target */
|
||
.header-brand {
|
||
padding-right: 0;
|
||
margin-right: 0.1rem;
|
||
border-right: none;
|
||
/* styles.css sizes this to the FULL header height, which is taller than the
|
||
header's padding box; centred by the header's `align-items: center` above,
|
||
that overflow is symmetric and the button lands flush with both edges.
|
||
Do not "fix" it with `height: 100%`: the header sets min/max-height and no
|
||
height, so the percentage has no definite containing block to resolve
|
||
against and silently falls back to auto. */
|
||
}
|
||
|
||
/* The "C" was a 0.85rem inline span — about a 12x13px hit area, far under the
|
||
44px minimum, on the one control that gets you back to the home screen.
|
||
It is now a real 44x44 button: 44px wide, and the full height of the phone
|
||
header, which is itself 44px for exactly this reason. The negative margin
|
||
spends the header's OWN left padding on the target instead of pushing the
|
||
tab strip right. */
|
||
.header-brand .logo {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 44px;
|
||
/* Taller than its parent on purpose: centred in the padded brand box, this
|
||
makes the button fill all 36 header pixels edge to edge. */
|
||
height: var(--header-height);
|
||
margin-left: -0.3rem;
|
||
font-size: 1.15rem;
|
||
line-height: 1;
|
||
border-radius: 8px;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.header-brand .logo:active {
|
||
background: rgba(96, 165, 250, 0.16);
|
||
}
|
||
|
||
.header-brand .logo .logo-text {
|
||
display: none;
|
||
}
|
||
|
||
.header-brand .logo .logo-compact {
|
||
display: inline;
|
||
}
|
||
|
||
/* Font controls - compact on phones, visibility controlled by JS */
|
||
.header-font-controls {
|
||
gap: 0.1rem;
|
||
padding: 0.1rem 0.2rem;
|
||
}
|
||
|
||
.header-font-controls .btn-icon-sm {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
|
||
.header-font-controls .font-size-display {
|
||
font-size: 0.6rem;
|
||
min-width: 14px;
|
||
}
|
||
|
||
/* System stats - very compact on phones, visibility controlled by JS */
|
||
.header-system-stats {
|
||
font-size: 0.55rem;
|
||
gap: 0.15rem;
|
||
}
|
||
|
||
/* Token count - compact on phones, visibility controlled by JS */
|
||
.header-tokens {
|
||
font-size: 0.6rem;
|
||
padding: 0.1rem 0.3rem;
|
||
}
|
||
|
||
/* Ultra-compact header for mobile - fixed at top */
|
||
.header {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
min-height: var(--header-height);
|
||
max-height: var(--header-height);
|
||
/* styles.css top-aligns header children. That read as centred while the bar
|
||
was 36px and its contents ~31px; in a 44px bar it leaves a visible gap
|
||
under everything. */
|
||
align-items: center;
|
||
padding: 0.15rem 0.3rem;
|
||
padding-left: calc(0.3rem + var(--safe-area-left));
|
||
padding-right: calc(0.3rem + var(--safe-area-right));
|
||
gap: 0.15rem;
|
||
overflow: hidden;
|
||
background: #0a0a0a;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||
contain: none;
|
||
z-index: 1200;
|
||
}
|
||
|
||
/* iOS safe area adjustment for fixed header - header extends into notch area */
|
||
.ios-device .header {
|
||
padding-top: calc(0.15rem + var(--safe-area-top));
|
||
min-height: calc(var(--header-height) + var(--safe-area-top));
|
||
max-height: calc(var(--header-height) + var(--safe-area-top));
|
||
}
|
||
|
||
/* Push ALL content below fixed header (not just .main) so banners
|
||
(respawn, timer, orchestrator) between header and main are visible.
|
||
Bottom padding for the fixed toolbar (40px) so terminal content
|
||
doesn't extend behind it. JS overrides paddingBottom on .main
|
||
when keyboard is visible, and resetLayout() clears the inline
|
||
style to re-expose this CSS value. */
|
||
.app {
|
||
/* Header height plus its 1px border and a little slack. Derived from the
|
||
token so the offset cannot fall out of step with the bar it clears. */
|
||
padding-top: calc(var(--header-height) + 6px);
|
||
}
|
||
|
||
.ios-device .app {
|
||
padding-top: calc(var(--header-height) + 6px + var(--safe-area-top));
|
||
}
|
||
|
||
.main {
|
||
padding-bottom: calc(40px + var(--safe-area-bottom));
|
||
}
|
||
|
||
/* iOS Safari: toolbar is pushed up by (100vh - --app-height) to clear the
|
||
browser's bottom bar. Match that offset in main's padding so the terminal
|
||
doesn't extend behind the toolbar. */
|
||
.ios-device.safari-browser .main {
|
||
padding-bottom: calc(40px + var(--safe-area-bottom) + (100vh - var(--app-height, 100vh)));
|
||
}
|
||
|
||
.header-right {
|
||
padding-left: 0.2rem;
|
||
gap: 0.1rem;
|
||
flex-shrink: 0;
|
||
border-left: none;
|
||
}
|
||
|
||
/* Smaller header buttons on mobile */
|
||
.btn-icon-header {
|
||
width: 26px;
|
||
height: 26px;
|
||
padding: 0;
|
||
}
|
||
|
||
.btn-icon-header svg {
|
||
width: 12px;
|
||
height: 12px;
|
||
}
|
||
|
||
/* Hide header settings gear, lifecycle log, away digest, session manager, and
|
||
file viewer on mobile - settings moved to toolbar; the others are secondary /
|
||
desktop-oriented controls that don't belong on the cramped phone header (the
|
||
session manager stays reachable via the Ctrl+K palette's "Browse all sessions"
|
||
item; the file viewer button is opt-in but its panel is desktop-sized).
|
||
(The attachments button is opt-in / default-hidden everywhere via its own
|
||
--hidden marker, so it needs no mobile-specific rule here.) */
|
||
.btn-icon-header.btn-settings,
|
||
.btn-icon-header.btn-lifecycle-log,
|
||
.btn-icon-header.btn-away-digest,
|
||
.btn-icon-header.btn-session-manager,
|
||
.btn-icon-header.btn-file-viewer,
|
||
/* Approvals bell: phones answer from the overview's NEEDS YOU rows instead
|
||
(inline approve/deny in mobile-overview.js); the bell would only crowd the
|
||
header it was designed to stay out of. */
|
||
.btn-icon-header.btn-approvals {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Read My Mind 🧠 header button: never in the phone header; the phone
|
||
surface is the keyboard-accessory 🧠 key (same `readMyMindEnabled` gate,
|
||
see keyboard-accessory.js + the rmm-enabled rules in styles.css). */
|
||
.btn-icon-header.btn-readmymind {
|
||
display: none !important;
|
||
}
|
||
|
||
/* The big labeled Admin Panel button is desktop-only (admin-gated, revealed by
|
||
admin-ui.js). On phones admins still reach user management via App Settings →
|
||
Users, so the cramped header stays minimal. */
|
||
.btn-admin-panel {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Mobile voice input button in toolbar — initially hidden via inline style,
|
||
shown by VoiceInput._showButtons() clearing the inline display */
|
||
.btn-voice-mobile {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 26px;
|
||
height: 26px;
|
||
background: #1a2a3f;
|
||
border: 1px solid rgba(59, 130, 246, 0.3);
|
||
border-radius: 4px;
|
||
color: #93c5fd;
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
order: 5; /* Between Run Shell and settings gear */
|
||
}
|
||
|
||
.btn-voice-mobile svg {
|
||
width: 13px;
|
||
height: 13px;
|
||
}
|
||
|
||
.btn-voice-mobile:active {
|
||
background: #2a3a5f;
|
||
}
|
||
|
||
.btn-voice-mobile.recording {
|
||
background: rgba(239, 68, 68, 0.25);
|
||
border-color: rgba(239, 68, 68, 0.6);
|
||
color: #ef4444;
|
||
animation: mobile-voice-pulse 1.2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes mobile-voice-pulse {
|
||
0%, 100% {
|
||
box-shadow: inset 0 0 0 1px rgba(239, 68, 68, 0.35);
|
||
background: rgba(239, 68, 68, 0.2);
|
||
}
|
||
50% {
|
||
box-shadow: inset 0 0 0 2px rgba(239, 68, 68, 0.75);
|
||
background: rgba(239, 68, 68, 0.35);
|
||
}
|
||
}
|
||
|
||
/* Mobile app settings gear in toolbar - far right */
|
||
.btn-settings-mobile {
|
||
display: inline-flex !important;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 26px;
|
||
height: 26px;
|
||
background: transparent;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
border-radius: 4px;
|
||
color: #9ca3af;
|
||
font-size: 0.85rem;
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
order: 6; /* Far right */
|
||
position: relative;
|
||
}
|
||
|
||
.btn-settings-mobile svg {
|
||
width: 13px;
|
||
height: 13px;
|
||
}
|
||
|
||
.btn-settings-mobile:active {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
color: #fff;
|
||
}
|
||
|
||
/* App container - prevent scrolling, strict layout */
|
||
.app {
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
/* When keyboard is visible, pin the app container to the viewport.
|
||
iOS Safari scrolls the page to bring the focused input (xterm's hidden
|
||
textarea) into view, even with overflow:hidden. position:fixed prevents
|
||
the browser from scrolling the document under the app. */
|
||
.keyboard-visible .app {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: auto;
|
||
height: var(--app-height, 100dvh);
|
||
}
|
||
|
||
/* Ultra-compact session tabs — .tabs-two-rows override needed to match
|
||
specificity of .session-tabs.tabs-two-rows in styles.css (0,2,0) */
|
||
.session-tabs,
|
||
.session-tabs.tabs-two-rows {
|
||
flex: 1;
|
||
flex-wrap: nowrap;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
-webkit-overflow-scrolling: touch;
|
||
overscroll-behavior-x: contain;
|
||
scrollbar-width: none;
|
||
max-height: 36px;
|
||
gap: 2px;
|
||
padding: 0;
|
||
}
|
||
|
||
.session-tabs::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
/* Smaller tabs for mobile */
|
||
.session-tab {
|
||
flex-shrink: 0;
|
||
min-height: 32px;
|
||
max-height: 32px;
|
||
padding: 0.35rem 0.5rem;
|
||
font-size: 0.7rem;
|
||
gap: 0.25rem;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* Smaller status indicator on mobile */
|
||
.session-tab .tab-status {
|
||
width: 4px;
|
||
height: 4px;
|
||
}
|
||
|
||
/* The working dot is the one glance-state a phone needs: keep idle tiny, but
|
||
let the pulsing green "working" dot read from arm's length. !important on
|
||
the glow because the skin block's no-halo rule (styles.css, nested under
|
||
html:not([data-skin="og"])) outranks any plain class rule here. */
|
||
.session-tab .tab-status.busy {
|
||
width: 9px;
|
||
height: 9px;
|
||
box-shadow: 0 0 8px 2px color-mix(in srgb, var(--green) 55%, transparent) !important;
|
||
}
|
||
|
||
/* No orbiting ring on phone tabs (styles.css draws one on desktop/tablet):
|
||
the dot is already enlarged to 9px with a glow here, and a 15px ring in a
|
||
32px tab would sit on top of the tab name. The glow is the phone's tell. */
|
||
.session-tab .tab-status.busy::after {
|
||
display: none;
|
||
}
|
||
|
||
/* Truncate tab names more aggressively on mobile */
|
||
.session-tab .tab-name {
|
||
max-width: 50px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* Hide close/gear buttons on non-active tabs on mobile */
|
||
.session-tab .tab-close,
|
||
.session-tab .tab-gear {
|
||
display: none;
|
||
}
|
||
|
||
/* Gear icon on active tab - tiny, subtle */
|
||
.session-tab.active .tab-gear {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 0.65rem;
|
||
line-height: 1;
|
||
width: 32px;
|
||
height: 32px;
|
||
margin-left: auto;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
/* Close button on active tab - large for easy tapping */
|
||
.session-tab.active .tab-close {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 1.1rem;
|
||
line-height: 1;
|
||
width: 20px;
|
||
height: 18px;
|
||
margin-left: -2px;
|
||
}
|
||
|
||
/* Clean dark toolbar - positioned near bottom */
|
||
.toolbar {
|
||
position: fixed;
|
||
bottom: var(--safe-area-bottom);
|
||
left: 0;
|
||
right: 0;
|
||
height: 40px;
|
||
min-height: 40px;
|
||
max-height: 40px;
|
||
padding: 0.3rem 0.4rem;
|
||
padding-left: calc(0.4rem + var(--safe-area-left));
|
||
padding-right: calc(0.4rem + var(--safe-area-right));
|
||
flex-wrap: nowrap;
|
||
gap: 0.3rem;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
background: #111111;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||
z-index: 50;
|
||
/* Smooth transition for keyboard show/hide */
|
||
transition: transform 0.15s ease-out;
|
||
/* Ensure transform works properly */
|
||
will-change: transform;
|
||
}
|
||
|
||
/* iOS Safari with tab bar: position: fixed uses the layout viewport which
|
||
extends behind the browser chrome. Offset the toolbar upward by the delta
|
||
between 100vh (layout) and --app-height (visual). */
|
||
.ios-device.safari-browser .toolbar {
|
||
bottom: calc(var(--safe-area-bottom) + (100vh - var(--app-height, 100vh)));
|
||
}
|
||
|
||
/* When keyboard is visible the JS translateY already accounts for the full
|
||
distance from the visual-viewport bottom to the layout-viewport bottom
|
||
(keyboard + Safari bar). Remove the CSS Safari-bar offset to avoid
|
||
double-counting, which otherwise creates a visible gap above the keyboard. */
|
||
.keyboard-visible.ios-device.safari-browser .toolbar {
|
||
bottom: var(--safe-area-bottom);
|
||
}
|
||
.keyboard-visible.ios-device.safari-browser .keyboard-accessory-bar {
|
||
bottom: calc(var(--safe-area-bottom) + 40px);
|
||
}
|
||
|
||
/* Show case selector in center */
|
||
.toolbar-center {
|
||
display: flex !important;
|
||
align-items: center;
|
||
}
|
||
|
||
.toolbar-center .btn-toolbar.btn-voice {
|
||
display: none !important;
|
||
}
|
||
|
||
.toolbar-right {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Hide version display on mobile */
|
||
.version-display {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Toolbar layout */
|
||
.toolbar-left {
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
}
|
||
|
||
.toolbar-left .toolbar-group {
|
||
display: flex;
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
}
|
||
|
||
.toolbar-left .toolbar-group:first-child {
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
justify-content: space-between;
|
||
gap: 0.3rem;
|
||
flex-wrap: nowrap;
|
||
align-items: center;
|
||
}
|
||
|
||
/* Hide instance count buttons (±) on mobile - cleaner UI */
|
||
.tab-count-group {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Compact toolbar buttons for mobile - white text on dark */
|
||
.btn-toolbar {
|
||
min-height: 26px !important;
|
||
max-height: 26px !important;
|
||
height: 26px !important;
|
||
padding: 0 0.5rem !important;
|
||
font-size: 0.65rem !important;
|
||
border-radius: 4px;
|
||
line-height: 26px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 500;
|
||
letter-spacing: 0.01em;
|
||
color: #fff;
|
||
}
|
||
|
||
/* Run button group — split button with dropdown */
|
||
.run-btn-group {
|
||
display: flex;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.btn-toolbar.btn-run {
|
||
flex: 0 0 auto;
|
||
padding: 0 0.6rem !important;
|
||
font-weight: 500;
|
||
border-radius: 4px 0 0 4px !important;
|
||
border-right: none !important;
|
||
}
|
||
|
||
.btn-toolbar.btn-run svg {
|
||
width: 10px;
|
||
height: 10px;
|
||
margin-right: 3px;
|
||
}
|
||
|
||
.btn-toolbar.btn-run-gear {
|
||
flex: 0 0 auto;
|
||
padding: 0 0.35rem !important;
|
||
min-width: unset !important;
|
||
border-radius: 0 4px 4px 0 !important;
|
||
border-left: 1px solid rgba(255,255,255,0.15) !important;
|
||
}
|
||
|
||
.btn-toolbar.btn-run-gear svg {
|
||
width: 10px;
|
||
height: 10px;
|
||
}
|
||
|
||
/* Claude mode colors on mobile */
|
||
.btn-toolbar.btn-run.mode-claude,
|
||
.btn-toolbar.btn-run-gear.mode-claude {
|
||
background: #1e3a5f;
|
||
border-color: rgba(59, 130, 246, 0.3);
|
||
color: #93c5fd;
|
||
}
|
||
|
||
.btn-toolbar.btn-run.mode-claude:active,
|
||
.btn-toolbar.btn-run-gear.mode-claude:active {
|
||
background: #2563eb;
|
||
border-color: rgba(59, 130, 246, 0.5);
|
||
}
|
||
|
||
/* OpenCode mode colors on mobile */
|
||
.btn-toolbar.btn-run.mode-opencode,
|
||
.btn-toolbar.btn-run-gear.mode-opencode {
|
||
background: #0a2e2a;
|
||
border-color: rgba(16, 185, 129, 0.3);
|
||
color: #6ee7b7;
|
||
}
|
||
|
||
.btn-toolbar.btn-run.mode-opencode:active,
|
||
.btn-toolbar.btn-run-gear.mode-opencode:active {
|
||
background: #0d4a40;
|
||
border-color: rgba(16, 185, 129, 0.5);
|
||
}
|
||
|
||
/* Gemini mode colors on mobile */
|
||
.btn-toolbar.btn-run.mode-gemini,
|
||
.btn-toolbar.btn-run-gear.mode-gemini {
|
||
background: #10243f;
|
||
border-color: rgba(96, 165, 250, 0.3);
|
||
color: #dbeafe;
|
||
}
|
||
|
||
.btn-toolbar.btn-run.mode-gemini:active,
|
||
.btn-toolbar.btn-run-gear.mode-gemini:active {
|
||
background: #174ea6;
|
||
border-color: rgba(96, 165, 250, 0.5);
|
||
}
|
||
|
||
/* Antigravity mode colors on mobile */
|
||
.btn-toolbar.btn-run.mode-antigravity,
|
||
.btn-toolbar.btn-run-gear.mode-antigravity {
|
||
background: #0b2b33;
|
||
border-color: rgba(34, 211, 238, 0.3);
|
||
color: #cffafe;
|
||
}
|
||
|
||
.btn-toolbar.btn-run.mode-antigravity:active,
|
||
.btn-toolbar.btn-run-gear.mode-antigravity:active {
|
||
background: #0e7490;
|
||
border-color: rgba(34, 211, 238, 0.5);
|
||
}
|
||
|
||
/* Pi mode colors on mobile.
|
||
`!important` is load-bearing here, not noise: styles.css nests its skin rules
|
||
inside `html:not([data-skin="og"])`, so a bare `.btn-toolbar.btn-run` in there
|
||
resolves to (0,2,1) and outranks this (0,2,0) `.mode-pi` pair regardless of
|
||
load order. The antigravity block right above omits it and is consequently
|
||
dead on every non-og skin (i.e. on the default) — do not copy that. */
|
||
.btn-toolbar.btn-run.mode-pi,
|
||
.btn-toolbar.btn-run-gear.mode-pi {
|
||
background: #33121f !important;
|
||
border-color: rgba(244, 114, 182, 0.3) !important;
|
||
color: #fce7f3 !important;
|
||
}
|
||
|
||
.btn-toolbar.btn-run.mode-pi:active,
|
||
.btn-toolbar.btn-run-gear.mode-pi:active {
|
||
background: #9d174d !important;
|
||
border-color: rgba(244, 114, 182, 0.5) !important;
|
||
}
|
||
|
||
/* Run mode dropdown menu — positioned above toolbar on mobile */
|
||
.run-mode-menu {
|
||
bottom: 100%;
|
||
left: 0;
|
||
margin-bottom: 6px;
|
||
min-width: 160px;
|
||
max-width: 80vw;
|
||
}
|
||
|
||
.run-mode-option {
|
||
padding: 10px 12px;
|
||
font-size: 0.8rem;
|
||
cursor: pointer;
|
||
-webkit-tap-highlight-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
/* Per-URL edit/delete in the Web / URL list need a real touch target, and they
|
||
sit next to the row's own tap area, so they get sized up rather than relying
|
||
on the 24px desktop hit box. */
|
||
.run-mode-row-btn {
|
||
width: 34px;
|
||
height: 34px;
|
||
font-size: 1rem;
|
||
-webkit-tap-highlight-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
.run-mode-webview-delete { font-size: 1.2rem; }
|
||
|
||
.run-mode-history {
|
||
-webkit-overflow-scrolling: touch;
|
||
touch-action: manipulation;
|
||
}
|
||
|
||
/* Stop button - visible on mobile, icon-only */
|
||
.btn-toolbar.btn-stop {
|
||
display: flex !important;
|
||
flex: 0 0 auto;
|
||
padding: 0 8px !important;
|
||
min-width: unset;
|
||
order: 3; /* Left of Run Shell */
|
||
}
|
||
|
||
.btn-toolbar.btn-stop svg {
|
||
width: 10px;
|
||
height: 10px;
|
||
margin-right: 0;
|
||
}
|
||
|
||
/* Shell is NOT a toolbar button on phones — it moved into the Run dropdown
|
||
(Terminal / Shell), freeing this slot for Enter. Starting a shell is a rare,
|
||
deliberate act; sending Enter is a constant one, so the scarce phone real
|
||
estate goes to Enter. */
|
||
.btn-toolbar.btn-shell {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Secondary action - Enter - right side. Takes the slot (and the order) the
|
||
Shell button used to hold, so the toolbar rhythm is unchanged. */
|
||
.btn-toolbar.btn-enter {
|
||
display: flex !important;
|
||
flex: 0 0 auto;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 54px;
|
||
width: 54px;
|
||
white-space: nowrap;
|
||
padding: 0 8px !important;
|
||
overflow: hidden;
|
||
font-size: 0.65rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.01em;
|
||
/* !important is REQUIRED here, not defensive habit: styles.css nests its skin
|
||
overrides inside `html:not([data-skin="og"]) { … }`, so a plain `.btn-toolbar`
|
||
in that block resolves to (0,2,1) and outranks this (0,2,0) rule. Without
|
||
!important the button silently renders in generic toolbar grey. */
|
||
background: rgba(30, 58, 95, 0.85) !important;
|
||
border: 1px solid rgba(59, 130, 246, 0.45) !important;
|
||
color: #dbeafe !important;
|
||
order: 4; /* Right position — same slot Shell used to occupy */
|
||
}
|
||
|
||
.btn-toolbar.btn-enter:hover,
|
||
.btn-toolbar.btn-enter:active {
|
||
background: rgba(37, 74, 122, 0.95) !important;
|
||
border-color: rgba(59, 130, 246, 0.7) !important;
|
||
color: #fff !important;
|
||
}
|
||
|
||
/* Hide case selector on mobile - simplified toolbar */
|
||
.case-select-group {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Simplified toolbar layout — Run, Enter, and Case */
|
||
.toolbar-left .toolbar-group:first-child {
|
||
width: 100%;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* Mobile case button - visible on mobile */
|
||
.btn-toolbar.btn-case-mobile {
|
||
display: flex !important;
|
||
flex: 1 1 0 !important;
|
||
min-width: 0;
|
||
padding: 0 8px !important;
|
||
order: 2; /* Between Run and Run Shell */
|
||
overflow: hidden;
|
||
justify-content: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.btn-toolbar.btn-case-mobile #mobileCaseName {
|
||
max-width: none;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Mobile case settings gear button - next to Run */
|
||
.btn-case-settings-mobile {
|
||
display: inline-flex !important;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 26px;
|
||
height: 26px;
|
||
background: transparent;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
border-radius: 4px;
|
||
color: #9ca3af;
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
order: 1; /* Next to Run */
|
||
}
|
||
|
||
.btn-case-settings-mobile:active {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
color: #fff;
|
||
}
|
||
|
||
@media (max-width: 374px) {
|
||
.toolbar {
|
||
padding: 0 2px;
|
||
}
|
||
|
||
.toolbar-left,
|
||
.toolbar-left .toolbar-group,
|
||
.toolbar-left .toolbar-group:first-child {
|
||
gap: 2px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 430px) {
|
||
.btn-case-settings-mobile {
|
||
display: none !important;
|
||
}
|
||
}
|
||
|
||
/* Mobile case settings popover */
|
||
.case-settings-popover-mobile {
|
||
position: fixed;
|
||
bottom: calc(var(--safe-area-bottom) + 46px);
|
||
left: 8px;
|
||
right: 8px;
|
||
background: #1e1e1e;
|
||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||
border-radius: 8px;
|
||
padding: 0.6rem 0.75rem;
|
||
z-index: 1000;
|
||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
|
||
display: block;
|
||
}
|
||
|
||
.case-settings-popover-mobile.hidden {
|
||
display: none !important;
|
||
}
|
||
|
||
.case-settings-popover-mobile .checkbox-inline {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 0.75rem;
|
||
color: #e5e7eb;
|
||
}
|
||
|
||
.case-settings-popover-mobile .form-hint {
|
||
display: block;
|
||
margin-top: 0.2rem;
|
||
font-size: 0.6rem;
|
||
color: #6b7280;
|
||
}
|
||
|
||
/* Keyboard Accessory Bar - quick actions above keyboard */
|
||
.keyboard-accessory-bar {
|
||
display: none;
|
||
position: fixed;
|
||
bottom: calc(var(--safe-area-bottom) + 40px); /* Above toolbar */
|
||
left: 0;
|
||
right: 0;
|
||
height: 44px;
|
||
background: #1a1a1a;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||
padding: 6px 8px;
|
||
padding-left: calc(8px + var(--safe-area-left));
|
||
padding-right: calc(8px + var(--safe-area-right));
|
||
gap: 8px;
|
||
align-items: center;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
-webkit-overflow-scrolling: touch;
|
||
z-index: 51;
|
||
transition: transform 0.15s ease-out;
|
||
will-change: transform;
|
||
}
|
||
|
||
.keyboard-accessory-bar.visible {
|
||
display: flex;
|
||
}
|
||
|
||
/* 隐藏滚动条但保留滑动能力 */
|
||
.keyboard-accessory-bar::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
.accessory-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
gap: 4px;
|
||
padding: 6px 12px;
|
||
background: #2a2a2a;
|
||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||
border-radius: 6px;
|
||
color: #e5e5e5;
|
||
font-size: 0.65rem;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: background 0.15s, border-color 0.15s;
|
||
}
|
||
|
||
/* Confirm state — button turns amber and shows "Tap again" */
|
||
.accessory-btn.confirming {
|
||
background: #6b4f00;
|
||
border-color: #b8860b;
|
||
color: #ffd54f;
|
||
}
|
||
|
||
/* Armed one-shot Ctrl (shell bar, issue #262). Phone palette is hardcoded in
|
||
this block, so the state needs its own entry here. Three classes beat the
|
||
plain .accessory-btn rules; the light-skin rule at the bottom of this file
|
||
is higher still at (0,3,1) and is excluded by hand there, not outranked. */
|
||
.accessory-btn.accessory-btn-ctrl.armed {
|
||
background: #2563eb;
|
||
border-color: rgba(59, 130, 246, 0.9);
|
||
color: #fff;
|
||
font-weight: 700;
|
||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.45);
|
||
}
|
||
|
||
.accessory-btn:active {
|
||
background: #3a3a3a;
|
||
}
|
||
|
||
.accessory-btn svg {
|
||
width: 14px;
|
||
height: 14px;
|
||
}
|
||
|
||
.accessory-btn-arrow {
|
||
padding: 6px 10px;
|
||
background: #2563eb;
|
||
border-color: rgba(59, 130, 246, 0.5);
|
||
color: #fff;
|
||
}
|
||
|
||
.accessory-btn-arrow:active {
|
||
background: #1d4ed8;
|
||
}
|
||
|
||
.accessory-btn-dismiss {
|
||
margin-left: auto;
|
||
flex: 1 1 0;
|
||
max-width: 100px;
|
||
padding: 10px 8px;
|
||
background: #2563eb;
|
||
border-color: rgba(59, 130, 246, 0.5);
|
||
color: #fff;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.accessory-btn-dismiss svg {
|
||
width: 22px;
|
||
height: 22px;
|
||
}
|
||
|
||
.accessory-btn-dismiss:active {
|
||
background: #1d4ed8;
|
||
}
|
||
|
||
/* Voice preview — positioned above accessory bar on mobile */
|
||
.voice-preview {
|
||
bottom: calc(var(--safe-area-bottom) + 94px);
|
||
font-size: 0.8rem;
|
||
max-width: 90%;
|
||
padding: 6px 14px;
|
||
}
|
||
|
||
/* Add case button - compact (keeping for when shown via menu) */
|
||
.btn-case-add {
|
||
display: none !important;
|
||
}
|
||
|
||
.btn-case-settings {
|
||
display: none !important;
|
||
}
|
||
|
||
/* When keyboard is visible, also move accessory bar up */
|
||
.keyboard-visible .keyboard-accessory-bar.visible {
|
||
/* Position is handled by JS transform along with toolbar */
|
||
}
|
||
|
||
/* Paste overlay for iOS clipboard access */
|
||
/* Paste overlay styles extracted to universal section below (line ~2293+) */
|
||
|
||
/* LEGACY: Hide old toolbar select (no longer used on mobile) */
|
||
.toolbar-select {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Keep btn-case-add styling for tablet/future use */
|
||
.toolbar .btn-case-add {
|
||
min-width: 26px !important;
|
||
max-width: 26px !important;
|
||
width: 26px !important;
|
||
min-height: 26px !important;
|
||
max-height: 26px !important;
|
||
height: 26px !important;
|
||
padding: 0 !important;
|
||
font-size: 0.9rem;
|
||
font-weight: bold;
|
||
display: inline-flex !important;
|
||
align-items: center;
|
||
justify-content: center;
|
||
line-height: 1;
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.btn-case-add:hover,
|
||
.btn-case-add:active {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
color: #fff;
|
||
}
|
||
|
||
/* Panels on mobile - full width, positioned above toolbar, visibility controlled by JS */
|
||
.monitor-panel,
|
||
.subagents-panel {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
left: 0;
|
||
right: 0;
|
||
border-radius: 8px 8px 0 0;
|
||
bottom: calc(44px + 2rem + var(--safe-area-bottom));
|
||
max-height: 35vh;
|
||
}
|
||
|
||
/* Modals must stack above the fixed mobile header (z-index 1200), or the
|
||
modal header with the close button is buried underneath it and the
|
||
full-screen modal cannot be dismissed */
|
||
.modal {
|
||
z-index: 1300;
|
||
}
|
||
|
||
/* Full-screen modals on phones */
|
||
.modal-content {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
height: 100%;
|
||
max-height: 100%;
|
||
border-radius: 0;
|
||
margin: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* Small modals (confirmations, dialogs) — auto-height, not full-screen */
|
||
.modal-content.modal-sm {
|
||
height: auto;
|
||
max-height: 85vh;
|
||
border-radius: 12px;
|
||
margin: 1rem;
|
||
width: calc(100% - 2rem);
|
||
}
|
||
|
||
/* Read My Mind: a small dialog (mirrors modal-sm), not a full-screen
|
||
takeover — it opens over the keyboard from the accessory 🧠 key and
|
||
should read as a quick suggestion sheet. Not modal-sm itself because
|
||
that caps desktop width at 340px; this modal wants 560px there. */
|
||
.modal-content.readmymind-modal {
|
||
height: auto;
|
||
max-height: 85vh;
|
||
border-radius: 12px;
|
||
margin: 1rem;
|
||
width: calc(100% - 2rem);
|
||
}
|
||
/* Four footer buttons on a narrow phone: let them wrap instead of clipping,
|
||
and give buttons + alternate rows finger-sized targets. The flex row
|
||
itself comes from the base rule in styles.css. */
|
||
.readmymind-modal .modal-footer {
|
||
flex-wrap: wrap;
|
||
}
|
||
.readmymind-modal .modal-footer .btn-toolbar {
|
||
flex: 1 1 auto;
|
||
justify-content: center;
|
||
min-height: 38px;
|
||
}
|
||
.readmymind-alt {
|
||
min-height: 38px;
|
||
}
|
||
.readmymind-steer-input {
|
||
min-height: 38px;
|
||
}
|
||
|
||
/* Modal safe area padding - all sides for full-screen modals */
|
||
.ios-device .modal-content {
|
||
padding-top: var(--safe-area-top);
|
||
padding-bottom: var(--safe-area-bottom);
|
||
padding-left: var(--safe-area-left);
|
||
padding-right: var(--safe-area-right);
|
||
}
|
||
|
||
.modal-header {
|
||
padding: 0.75rem 1rem;
|
||
}
|
||
|
||
.modal-header h3 {
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 0.75rem 1rem;
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
.away-digest-modal {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
height: 100%;
|
||
max-height: 100%;
|
||
}
|
||
|
||
.away-digest-ranges {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
.away-digest-ranges .filter-btn {
|
||
min-height: 38px;
|
||
padding: 0.4rem 0.5rem;
|
||
}
|
||
|
||
.away-digest-custom-range,
|
||
.away-digest-custom-range.active {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.away-digest-summary {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.away-digest-item {
|
||
grid-template-columns: 1fr;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.away-digest-action {
|
||
width: 100%;
|
||
min-height: 38px;
|
||
}
|
||
|
||
.modal-footer,
|
||
.form-actions {
|
||
padding: 0.75rem 1rem;
|
||
padding-bottom: calc(0.75rem + var(--safe-area-bottom));
|
||
}
|
||
|
||
/* Terminal container safe area */
|
||
.ios-device .terminal-container {
|
||
padding-bottom: var(--safe-area-bottom);
|
||
}
|
||
|
||
/* Main area must fill available space */
|
||
.main {
|
||
flex: 1;
|
||
min-height: 0; /* Allow flex item to shrink below content size */
|
||
}
|
||
|
||
/* Show CLI info bar on mobile (Claude version/model) */
|
||
.cli-info-bar {
|
||
display: block;
|
||
}
|
||
|
||
/* Mobile terminal — JS touch handler (terminal.scrollLines()) drives
|
||
scrollback because xterm.js DOM renderer doesn't populate xterm-viewport's
|
||
scroll area. touch-action:none lets our JS handler own the gesture. */
|
||
.terminal-container {
|
||
height: 100%;
|
||
min-height: 0;
|
||
position: relative;
|
||
overflow: visible;
|
||
touch-action: none;
|
||
}
|
||
|
||
.terminal-container .xterm,
|
||
.terminal-container .xterm-viewport,
|
||
.terminal-container .xterm-screen {
|
||
touch-action: none;
|
||
}
|
||
|
||
.response-viewer {
|
||
padding-bottom: var(--safe-area-bottom, 0px);
|
||
/* dvh tracks the visible viewport on iOS Safari (vh = large viewport and would clip
|
||
the header/close button off-screen); the vh line is the old-engine fallback */
|
||
max-height: 88vh;
|
||
max-height: 92dvh;
|
||
}
|
||
|
||
.response-viewer-body {
|
||
font-size: 14.5px;
|
||
line-height: 1.65;
|
||
padding: 16px 16px 24px;
|
||
--rv-content-max: 100%;
|
||
}
|
||
|
||
.response-viewer-body .rv-text pre,
|
||
.response-viewer-body pre {
|
||
/* Slightly smaller on mobile so diagrams fit better before scrolling */
|
||
padding: 12px 14px;
|
||
margin-left: -4px;
|
||
margin-right: -4px;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.response-viewer-body .rv-text pre code,
|
||
.response-viewer-body pre code {
|
||
font-size: 11.5px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.response-viewer-body .rv-text h1,
|
||
.response-viewer-body > h1 { font-size: 1.35em; }
|
||
.response-viewer-body .rv-text h2,
|
||
.response-viewer-body > h2 { font-size: 1.2em; }
|
||
.response-viewer-body .rv-text h3,
|
||
.response-viewer-body > h3 { font-size: 1.08em; }
|
||
|
||
/* Compact welcome overlay for mobile */
|
||
.welcome-content {
|
||
max-width: calc(100vw - 1.5rem);
|
||
padding: 1rem 0.75rem;
|
||
}
|
||
|
||
.welcome-title {
|
||
font-size: 1.2rem;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.welcome-desc {
|
||
font-size: 0.8rem;
|
||
margin-bottom: 0.4rem;
|
||
}
|
||
|
||
.welcome-actions {
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.welcome-btn {
|
||
width: 100%;
|
||
justify-content: center;
|
||
min-height: 44px;
|
||
padding: 0.75rem 1rem;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.history-show-more {
|
||
display: block;
|
||
margin-bottom: 0.75rem;
|
||
}
|
||
|
||
.welcome-ralph-link {
|
||
display: block;
|
||
margin: 0.75rem auto 0;
|
||
}
|
||
|
||
.welcome-hint {
|
||
font-size: 0.7rem;
|
||
margin-top: 0.75rem;
|
||
}
|
||
|
||
/* Ralph wizard responsive */
|
||
.modal-wizard {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.wizard-progress {
|
||
padding: 0.5rem;
|
||
}
|
||
|
||
.wizard-step {
|
||
padding: 0.3rem;
|
||
}
|
||
|
||
.wizard-step-number {
|
||
width: 24px;
|
||
height: 24px;
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.wizard-step-label {
|
||
display: none;
|
||
}
|
||
|
||
/* Timer banner - includes safe area padding */
|
||
.timer-banner {
|
||
padding-left: calc(0.5rem + var(--safe-area-left));
|
||
padding-right: calc(0.5rem + var(--safe-area-right));
|
||
}
|
||
|
||
/* ---- Respawn Banner: Mobile Optimizations ---- */
|
||
|
||
.respawn-banner {
|
||
padding: 0.3rem 0.5rem;
|
||
padding-left: calc(0.5rem + var(--safe-area-left));
|
||
padding-right: calc(0.5rem + var(--safe-area-right));
|
||
font-size: 0.65rem;
|
||
}
|
||
|
||
/* Stack banner vertically on mobile — status on top, action log below */
|
||
.respawn-compact-layout {
|
||
flex-direction: column;
|
||
gap: 0.15rem;
|
||
}
|
||
|
||
/* Let status column shrink to fit */
|
||
.respawn-status-col {
|
||
flex-shrink: 1;
|
||
min-width: 0;
|
||
gap: 0.1rem;
|
||
}
|
||
|
||
/* Row1: wrap allowed, key info prioritized via order */
|
||
.respawn-status-row1 {
|
||
flex-wrap: wrap;
|
||
gap: 0.25rem;
|
||
row-gap: 0.1rem;
|
||
}
|
||
|
||
/* State label — smaller on phone, stays first */
|
||
.respawn-state {
|
||
font-size: 0.6rem;
|
||
padding: 0.05rem 0.3rem;
|
||
}
|
||
|
||
/* Confidence badge — compact */
|
||
.respawn-banner .detection-confidence {
|
||
font-size: 0.5rem;
|
||
padding: 0.02rem 0.2rem;
|
||
}
|
||
|
||
/* Cycles — compact */
|
||
.respawn-cycles {
|
||
font-size: 0.55rem;
|
||
}
|
||
|
||
/* Run timer — compact */
|
||
.respawn-timer {
|
||
font-size: 0.55rem;
|
||
padding: 0 0.25rem;
|
||
}
|
||
|
||
/* Tokens — compact, truncate if long */
|
||
.respawn-tokens {
|
||
font-size: 0.55rem;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
max-width: 120px;
|
||
}
|
||
|
||
/* Spinner icon — smaller on phone */
|
||
.respawn-indicator {
|
||
font-size: 0.65rem;
|
||
}
|
||
|
||
/* Stop button — 36px touch target, pushed right */
|
||
.respawn-banner .btn-icon-only {
|
||
min-width: 36px;
|
||
min-height: 36px;
|
||
font-size: 1rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-left: auto;
|
||
border-radius: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Row2: tighter spacing, smaller text */
|
||
.respawn-status-row2 {
|
||
font-size: 0.55rem;
|
||
gap: 0.3rem;
|
||
min-height: 0;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
/* Detection badges — smaller on phone */
|
||
.respawn-banner .detection-hook,
|
||
.respawn-banner .detection-ai-check,
|
||
.respawn-banner .detection-status {
|
||
font-size: 0.55rem;
|
||
padding: 0.02rem 0.2rem;
|
||
}
|
||
|
||
/* Countdown timers — inline, no progress bars */
|
||
.respawn-countdown-timers {
|
||
gap: 0.2rem;
|
||
}
|
||
|
||
.respawn-countdown-timer {
|
||
font-size: 0.5rem;
|
||
padding: 0.02rem 0.2rem;
|
||
gap: 0.15rem;
|
||
}
|
||
|
||
/* Hide progress bars on phone — values are enough */
|
||
.respawn-countdown-timer .respawn-timer-bar {
|
||
display: none;
|
||
}
|
||
|
||
/* Action log — single-line strip below status, show latest entry only */
|
||
.respawn-action-log {
|
||
border-left: none;
|
||
border-top: 1px solid rgba(34, 197, 94, 0.15);
|
||
padding-left: 0;
|
||
padding-top: 0.15rem;
|
||
max-height: 1.4em;
|
||
font-size: 0.55rem;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ---- Session Options Modal: Respawn Tab Mobile Optimizations ---- */
|
||
|
||
/* Respawn header — keep horizontal, compact */
|
||
.respawn-header {
|
||
flex-direction: row;
|
||
gap: 0.4rem;
|
||
align-items: center;
|
||
}
|
||
|
||
/* Enable/Stop buttons — compact */
|
||
.respawn-header .respawn-actions {
|
||
display: flex;
|
||
gap: 0.3rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.respawn-header .respawn-actions .btn-toolbar {
|
||
min-height: 28px !important;
|
||
font-size: 0.7rem !important;
|
||
padding: 0.2rem 0.5rem !important;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
/* Duration preset buttons — one compact row */
|
||
.duration-presets {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.2rem;
|
||
}
|
||
|
||
.duration-preset-btn {
|
||
min-height: 24px;
|
||
padding: 0.1rem 0.4rem;
|
||
font-size: 0.6rem;
|
||
border-radius: 4px;
|
||
text-align: center;
|
||
}
|
||
|
||
.duration-custom {
|
||
display: flex;
|
||
gap: 0.2rem;
|
||
align-items: center;
|
||
}
|
||
|
||
.duration-custom-input.visible {
|
||
flex: 0 1 auto;
|
||
}
|
||
|
||
.duration-custom-input input {
|
||
width: 64px;
|
||
min-height: 24px;
|
||
font-size: 16px; /* Prevents iOS zoom */
|
||
padding: 0.1rem 0.3rem;
|
||
}
|
||
|
||
/* Preset selector row — full-width dropdown, buttons below */
|
||
.preset-selector {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 0.25rem;
|
||
}
|
||
|
||
.preset-selector select {
|
||
grid-column: 1 / -1;
|
||
min-height: 32px;
|
||
font-size: 16px; /* Prevents iOS zoom */
|
||
border-radius: 5px;
|
||
padding: 0.2rem 0.4rem;
|
||
}
|
||
|
||
.preset-selector .btn {
|
||
min-height: 32px;
|
||
font-size: 0.65rem;
|
||
border-radius: 5px;
|
||
padding: 0.2rem 0.4rem;
|
||
}
|
||
|
||
/* Textareas — prevent iOS zoom with 16px font, compact height */
|
||
#sessionOptionsModal textarea {
|
||
font-size: 16px;
|
||
min-height: 32px !important;
|
||
max-height: 56px;
|
||
height: auto !important;
|
||
border-radius: 6px;
|
||
padding: 0.3rem 0.5rem;
|
||
}
|
||
|
||
/* Override inline min-height styles on respawn textareas */
|
||
#modalRespawnPrompt {
|
||
min-height: 32px !important;
|
||
max-height: 56px;
|
||
}
|
||
|
||
#modalRespawnKickstart {
|
||
min-height: 32px !important;
|
||
max-height: 48px;
|
||
}
|
||
|
||
/* Checkboxes — compact touch area */
|
||
#sessionOptionsModal .checkbox-inline {
|
||
min-height: 30px;
|
||
font-size: 0.75rem;
|
||
padding: 0.15rem 0;
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
#sessionOptionsModal .checkbox-inline input[type="checkbox"] {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
|
||
/* Respawn-loop box title matches the auto-resume title; its cycle-step
|
||
checkboxes match the step labels */
|
||
#sessionOptionsModal .respawn-loop-title {
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
#sessionOptionsModal .respawn-loop-box .checkbox-inline {
|
||
font-size: 0.65rem;
|
||
}
|
||
|
||
/* Respawn options row — stack if needed */
|
||
#sessionOptionsModal .respawn-options-row {
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
/* Form section headers — compact on mobile */
|
||
#sessionOptionsModal .form-section-header {
|
||
margin-top: 0.75rem;
|
||
padding-top: 0.5rem;
|
||
font-size: 0.65rem;
|
||
}
|
||
|
||
/* Form rows — tighter spacing */
|
||
#sessionOptionsModal .form-row {
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
#sessionOptionsModal .form-row label {
|
||
font-size: 0.65rem;
|
||
margin-bottom: 0.2rem;
|
||
}
|
||
|
||
/* Form hints — compact */
|
||
#sessionOptionsModal .form-hint {
|
||
font-size: 0.6rem;
|
||
margin-top: 0.2rem;
|
||
}
|
||
|
||
/* ---- Context Tab: Mobile Optimizations ---- */
|
||
|
||
/* Context settings cards — tighter padding */
|
||
.context-setting {
|
||
padding: 0.625rem;
|
||
}
|
||
|
||
/* Stack context setting header vertically on narrow screens */
|
||
.context-setting-header {
|
||
flex-wrap: wrap;
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
.context-setting-header .input-suffix-sm input {
|
||
width: 60px;
|
||
font-size: 16px; /* Prevents iOS zoom */
|
||
min-height: 36px;
|
||
}
|
||
|
||
.context-setting input[type="text"] {
|
||
font-size: 16px; /* Prevents iOS zoom */
|
||
min-height: 36px;
|
||
padding: 0.4rem 0.6rem;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
/* Color picker — larger swatches for touch */
|
||
.color-picker {
|
||
gap: 8px;
|
||
}
|
||
|
||
.color-swatch {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
/* Toggle switches — larger touch area */
|
||
.form-row-switch {
|
||
min-height: 40px;
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
/* Number inputs in context tab — prevent iOS zoom */
|
||
.context-settings-grid input[type="number"] {
|
||
font-size: 16px;
|
||
min-height: 36px;
|
||
}
|
||
|
||
/* ---- Ralph Tab: Mobile Optimizations ---- */
|
||
|
||
/* Ralph limits grid — 2 columns on mobile */
|
||
.ralph-limits-grid {
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.ralph-limits-grid .form-col input[type="number"] {
|
||
font-size: 16px; /* Prevents iOS zoom */
|
||
min-height: 36px;
|
||
padding: 0.4rem 0.5rem;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
/* Ralph phrase input — prevent iOS zoom */
|
||
#modalRalphPhrase {
|
||
font-size: 16px;
|
||
min-height: 40px;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
/* Save button — full width, proper touch target */
|
||
.ralph-config-actions {
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.ralph-config-actions .btn-toolbar {
|
||
width: 100%;
|
||
min-height: 44px;
|
||
font-size: 0.85rem;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
/* ---- Summary Tab: Mobile Optimizations ---- */
|
||
|
||
/* Filter buttons — larger touch targets */
|
||
.run-summary-filters {
|
||
gap: 0.3rem;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.filter-btn {
|
||
padding: 0.35rem 0.75rem;
|
||
font-size: 0.7rem;
|
||
min-height: 32px;
|
||
}
|
||
|
||
/* Timeline events — tighter on mobile */
|
||
.timeline-event {
|
||
padding: 0.4rem 0.5rem;
|
||
margin-bottom: 0.25rem;
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
/* Summary footer — stack vertically */
|
||
.run-summary-footer {
|
||
flex-direction: column;
|
||
gap: 0.4rem;
|
||
padding: 0.5rem 0;
|
||
}
|
||
|
||
.run-summary-actions {
|
||
flex-wrap: wrap;
|
||
gap: 0.3rem;
|
||
}
|
||
|
||
.run-summary-actions .btn-toolbar {
|
||
flex: 1;
|
||
min-height: 36px;
|
||
font-size: 0.7rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.auto-refresh-label {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.ralph-panel {
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.ralph-summary {
|
||
padding: 0.4rem 0.5rem;
|
||
padding-left: calc(0.5rem + var(--safe-area-left));
|
||
padding-right: calc(0.5rem + var(--safe-area-right));
|
||
}
|
||
|
||
/* Project insights panel - compact on mobile, visibility controlled by JS */
|
||
.project-insights-panel {
|
||
max-width: 100%;
|
||
font-size: 0.65rem;
|
||
bottom: calc(44px + 2rem + var(--safe-area-bottom));
|
||
}
|
||
|
||
/* File browser panel - compact on mobile, visibility controlled by JS */
|
||
.file-browser-panel {
|
||
max-width: 100%;
|
||
max-height: 50vh;
|
||
bottom: calc(44px + 2rem + var(--safe-area-bottom));
|
||
}
|
||
|
||
/* File preview window: full screen on phones. --app-height tracks the visual
|
||
viewport (KeyboardHandler), so the edit textarea + Save bar stay above the
|
||
OS keyboard instead of hiding behind it. Footer/edit bar pad for the home
|
||
indicator. */
|
||
.file-preview-window {
|
||
width: 100vw;
|
||
max-width: 100vw;
|
||
height: var(--app-height, 100vh);
|
||
max-height: var(--app-height, 100vh);
|
||
border-radius: 0;
|
||
border-left: none;
|
||
border-right: none;
|
||
}
|
||
|
||
.file-preview-editbar {
|
||
padding-bottom: calc(0.4rem + var(--safe-area-bottom));
|
||
}
|
||
|
||
.file-preview-overlay .file-preview-footer {
|
||
padding-bottom: calc(0.35rem + var(--safe-area-bottom));
|
||
}
|
||
|
||
/* >=16px or iOS Safari auto-zooms the page on focus */
|
||
.file-preview-body textarea.file-preview-editor {
|
||
font-size: 16px;
|
||
}
|
||
|
||
/* Notification drawer - full width on mobile, includes safe area padding */
|
||
.notification-drawer {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
right: 0;
|
||
border-radius: 0;
|
||
padding-left: var(--safe-area-left);
|
||
padding-right: var(--safe-area-right);
|
||
padding-bottom: var(--safe-area-bottom);
|
||
}
|
||
|
||
/* Scroll margin for inputs - helps with keyboard visibility */
|
||
input, textarea, [contenteditable] {
|
||
scroll-margin-bottom: 200px;
|
||
scroll-margin-top: 80px;
|
||
}
|
||
|
||
/* When keyboard is visible, the JS paddingBottom on .main already reserves
|
||
space for toolbar + accessory bar. No extra padding needed here — it was
|
||
double-counting and creating dead space between terminal and the bars. */
|
||
|
||
/* Ensure modals scroll properly when keyboard is visible */
|
||
.keyboard-visible .modal-body {
|
||
max-height: 40vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* When keyboard opens in case modal, let body grow taller */
|
||
.keyboard-visible #createCaseModal .modal-body {
|
||
max-height: 60vh;
|
||
}
|
||
|
||
/* Mobile Case Picker - Mobile-specific overrides */
|
||
.mobile-case-picker .modal-backdrop {
|
||
background: rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
.mobile-case-picker-sheet {
|
||
max-height: 60vh;
|
||
padding-bottom: var(--safe-area-bottom);
|
||
animation: slideUp 0.2s ease-out;
|
||
}
|
||
|
||
@keyframes slideUp {
|
||
from {
|
||
transform: translateY(100%);
|
||
}
|
||
to {
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.mobile-case-picker-header .modal-close {
|
||
width: 32px;
|
||
height: 32px;
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.mobile-case-picker-footer {
|
||
padding-bottom: calc(12px + var(--safe-area-bottom));
|
||
}
|
||
|
||
/* ---- Settings Modal: Mobile Optimizations ---- */
|
||
|
||
/* ---- Case Modal: Mobile Touch Optimizations ---- */
|
||
|
||
|
||
/* Touch-friendly form inputs in case modal */
|
||
#createCaseModal .form-row {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
#createCaseModal .form-row label {
|
||
font-size: 0.8rem;
|
||
margin-bottom: 0.4rem;
|
||
font-weight: 500;
|
||
color: #e5e7eb;
|
||
}
|
||
|
||
#createCaseModal .form-row input[type="text"] {
|
||
min-height: 44px;
|
||
font-size: 16px; /* Prevents iOS zoom on focus */
|
||
padding: 0.5rem 0.75rem;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
#createCaseModal .form-row .form-hint {
|
||
font-size: 0.65rem;
|
||
margin-top: 0.35rem;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* Touch-friendly action buttons in case modal */
|
||
#createCaseModal .form-actions {
|
||
padding: 0.75rem 1rem;
|
||
padding-bottom: calc(0.75rem + var(--safe-area-bottom));
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
#createCaseModal .form-actions .btn-toolbar {
|
||
min-height: 44px !important;
|
||
max-height: 44px !important;
|
||
height: 44px !important;
|
||
font-size: 0.85rem !important;
|
||
font-weight: 500;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
/* Loading state for submit button */
|
||
#caseModalSubmit.loading {
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* Smooth open animation for case modal from mobile picker */
|
||
#createCaseModal.from-mobile .modal-content {
|
||
animation: caseModalSlideUp 0.25s ease-out;
|
||
}
|
||
|
||
@keyframes caseModalSlideUp {
|
||
from {
|
||
transform: translateY(30px);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateY(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* Mobile case picker - larger create button (override btn-toolbar !important) */
|
||
.btn-case-create-mobile {
|
||
min-height: 48px !important;
|
||
max-height: 48px !important;
|
||
height: 48px !important;
|
||
font-size: 0.85rem !important;
|
||
font-weight: 500;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
/* Settings grid - single column on phones */
|
||
.settings-grid {
|
||
grid-template-columns: 1fr;
|
||
gap: 0.35rem;
|
||
}
|
||
|
||
.settings-grid-3col {
|
||
grid-template-columns: 1fr 1fr 1fr;
|
||
}
|
||
|
||
.settings-item {
|
||
padding: 0.35rem 0.5rem;
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.settings-item-label {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.settings-section-header {
|
||
font-size: 0.6rem;
|
||
padding: 0.35rem 0 0.2rem 0;
|
||
margin-top: 0.5rem;
|
||
}
|
||
|
||
/* Form rows - compact on mobile */
|
||
.form-row {
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.form-row label {
|
||
font-size: 0.7rem;
|
||
margin-bottom: 0.2rem;
|
||
}
|
||
|
||
.form-row .form-hint {
|
||
font-size: 0.6rem;
|
||
}
|
||
|
||
.form-row input[type="text"],
|
||
.form-row input[type="number"],
|
||
.form-row textarea,
|
||
.form-row .form-select,
|
||
.form-row select {
|
||
font-size: 0.75rem;
|
||
padding: 0.35rem 0.5rem;
|
||
min-height: 32px;
|
||
}
|
||
|
||
.form-row-switch {
|
||
gap: 0.3rem;
|
||
}
|
||
|
||
.form-row-switch > label:first-child {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.form-section-header {
|
||
font-size: 0.6rem;
|
||
margin-top: 0.75rem;
|
||
}
|
||
|
||
/* Event type grid - tighter on mobile */
|
||
.event-type-grid {
|
||
grid-template-columns: 1fr 36px 36px 36px;
|
||
gap: 4px 4px;
|
||
padding: 6px;
|
||
margin-top: 6px;
|
||
}
|
||
|
||
.event-header {
|
||
font-size: 0.55rem;
|
||
}
|
||
|
||
.event-label {
|
||
font-size: 0.65rem;
|
||
}
|
||
|
||
.event-type-grid input[type="checkbox"] {
|
||
width: 12px;
|
||
height: 12px;
|
||
}
|
||
|
||
/* Modal footer buttons - full width on mobile */
|
||
.form-actions {
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.form-actions .btn-toolbar {
|
||
flex: 1;
|
||
min-height: 36px !important;
|
||
max-height: 36px !important;
|
||
height: 36px !important;
|
||
font-size: 0.75rem !important;
|
||
}
|
||
|
||
/* ---- Subagent Windows: Mobile Optimizations ---- */
|
||
|
||
/* Compact stacked cards — multiple visible at once, draggable via touch */
|
||
.subagent-window {
|
||
position: fixed;
|
||
width: calc(100% - 8px);
|
||
max-width: calc(100% - 8px);
|
||
height: 110px;
|
||
min-height: 80px;
|
||
max-height: 110px;
|
||
min-width: 200px;
|
||
border-radius: 6px;
|
||
resize: none;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||
}
|
||
|
||
/* Hide resize handle on mobile */
|
||
.subagent-window::after {
|
||
display: none;
|
||
}
|
||
|
||
/* Compact subagent window header */
|
||
.subagent-window-header {
|
||
padding: 0.2rem 0.4rem;
|
||
min-height: 24px;
|
||
}
|
||
|
||
.subagent-window-title {
|
||
gap: 0.2rem;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.subagent-window-title .icon {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.subagent-window-title .id {
|
||
font-size: 0.6rem;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
max-width: 45vw;
|
||
}
|
||
|
||
.subagent-window-title .status {
|
||
font-size: 0.45rem;
|
||
padding: 0.05rem 0.15rem;
|
||
}
|
||
|
||
.subagent-model-badge {
|
||
font-size: 0.45rem !important;
|
||
padding: 0.05rem 0.15rem !important;
|
||
}
|
||
|
||
.subagent-window-actions button {
|
||
font-size: 0.7rem;
|
||
padding: 0.15rem 0.35rem;
|
||
min-width: 26px;
|
||
min-height: 26px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.subagent-window-body {
|
||
font-size: 0.6rem;
|
||
padding: 0.2rem 0.35rem;
|
||
}
|
||
|
||
.subagent-window-body .activity-line {
|
||
gap: 0.15rem;
|
||
padding: 0.05rem 0;
|
||
}
|
||
|
||
.subagent-window-body .activity-line .time {
|
||
font-size: 0.5rem;
|
||
}
|
||
|
||
.subagent-window-body .activity-line .tool-name {
|
||
font-size: 0.55rem;
|
||
}
|
||
|
||
.subagent-window-body .activity-line .tool-detail {
|
||
font-size: 0.55rem;
|
||
}
|
||
|
||
/* Hide parent label on mobile to save space */
|
||
.subagent-window-parent {
|
||
display: none;
|
||
}
|
||
|
||
/* ---- Tab Subagent Badges: Smaller on Mobile ---- */
|
||
|
||
.session-tab .tab-subagent-badge {
|
||
height: 14px;
|
||
padding: 0 3px;
|
||
border-radius: 7px;
|
||
margin-left: 2px;
|
||
}
|
||
|
||
.session-tab .tab-subagent-badge .subagent-label {
|
||
font-size: 0.45rem;
|
||
}
|
||
|
||
/* Subagent dropdown - full width near badge on mobile (JS sets top from badge position) */
|
||
.subagent-dropdown {
|
||
position: fixed !important;
|
||
left: 8px !important;
|
||
right: 8px !important;
|
||
bottom: auto !important;
|
||
min-width: auto !important;
|
||
max-width: none !important;
|
||
border-radius: 8px;
|
||
transform: none !important;
|
||
}
|
||
|
||
/* ---- Mobile Overview (phone home screen) ----
|
||
Shown in place of the welcome overlay when the C logo is tapped. Styled only
|
||
with :root tokens so every skin, including the four light ones, works with
|
||
no override block. Never give .mobile-overview itself a display value: the
|
||
element ships with [hidden] and only .visible may turn it on. */
|
||
|
||
.mobile-overview.visible {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 10;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.75rem;
|
||
padding: 0.75rem 0.6rem calc(1rem + var(--safe-area-bottom));
|
||
background: var(--bg-dark);
|
||
overflow-y: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
overscroll-behavior: contain;
|
||
}
|
||
|
||
/* No side padding: the wordmark's left edge lines up with the session cards
|
||
below it, and the Run button's right edge with theirs. */
|
||
.mobile-overview-top {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
padding: 0;
|
||
}
|
||
|
||
/* Same accent as the "C" in the header (`.logo` uses --accent-hover), and the
|
||
same 48px box as the Run button opposite it, so the two are centered on one
|
||
line by construction rather than by eye. */
|
||
.mobile-overview-brand {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
min-height: 48px;
|
||
font-size: 1.5rem;
|
||
font-weight: 800;
|
||
line-height: 1;
|
||
color: var(--accent-hover);
|
||
letter-spacing: -0.02em;
|
||
}
|
||
|
||
/* Split Run button. Colors come from the toolbar's own
|
||
`.btn-toolbar.btn-run.mode-<backend>` rules in styles.css (the element
|
||
carries those classes), so this button and the one in the toolbar are the
|
||
same control in two places. Only size/shape is set here, and NOTHING that
|
||
would override the mode gradient. */
|
||
.mobile-overview-header {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.mobile-overview-run-group {
|
||
display: flex;
|
||
align-items: stretch;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Sized well past the 44px touch minimum: this is the primary action on the
|
||
home screen, and the caret is a separate target right next to it.
|
||
⚠️ The !important is required, not decorative: the phone block clamps every
|
||
.btn-toolbar to `height/min-height/max-height: 26px !important`, and this
|
||
button deliberately carries .btn-toolbar to inherit the run-mode gradient.
|
||
Without matching !important (max-height included) it renders 26px tall. */
|
||
.mobile-overview-run-group .mobile-overview-run {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
min-height: 48px !important;
|
||
height: 48px !important;
|
||
max-height: 48px !important;
|
||
padding: 0 1.15rem !important;
|
||
font-size: 0.95rem !important;
|
||
line-height: 1;
|
||
font-weight: 700;
|
||
font-family: inherit;
|
||
border-radius: 10px 0 0 10px !important;
|
||
}
|
||
|
||
.mobile-overview-run-mode {
|
||
font-weight: 600;
|
||
font-size: 0.85rem;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.mobile-overview-run-group .mobile-overview-run-caret {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 48px !important;
|
||
height: 48px !important;
|
||
max-height: 48px !important;
|
||
min-width: 48px !important;
|
||
width: 48px !important;
|
||
padding: 0 !important;
|
||
line-height: 1;
|
||
font-family: inherit;
|
||
border-radius: 0 10px 10px 0 !important;
|
||
}
|
||
|
||
/* The phone block shrinks every run-gear glyph to 10px; this one is a 48px
|
||
tap target, so it gets a proportionate chevron. `display:block` drops the
|
||
inline-baseline gap that would otherwise push it a pixel low. */
|
||
.mobile-overview-run-group .mobile-overview-run-caret svg {
|
||
display: block;
|
||
width: 20px !important;
|
||
height: 20px !important;
|
||
margin: 0 !important;
|
||
}
|
||
|
||
.mobile-overview-run-menu {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.15rem;
|
||
margin: 0.5rem 0 0;
|
||
padding: 0.35rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
background: var(--floating-bg);
|
||
box-shadow: var(--elevated-shadow);
|
||
}
|
||
|
||
.mobile-overview-run-option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
min-height: 44px;
|
||
padding: 0 0.6rem;
|
||
border: none;
|
||
border-radius: 8px;
|
||
background: transparent;
|
||
color: var(--text);
|
||
font-family: inherit;
|
||
font-size: 0.82rem;
|
||
text-align: left;
|
||
}
|
||
|
||
.mobile-overview-run-option.selected {
|
||
background: var(--control-bg-hover);
|
||
}
|
||
|
||
.mobile-overview-run-option:active {
|
||
background: var(--bg-hover);
|
||
}
|
||
|
||
.mobile-overview-run-option--add {
|
||
color: var(--accent);
|
||
}
|
||
|
||
.mobile-overview-run-header {
|
||
padding: 0.4rem 0.6rem 0.2rem;
|
||
font-size: 0.6rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.09em;
|
||
text-transform: uppercase;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.mobile-overview-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
/* Flush left with the wordmark and the cards: one left edge down the screen. */
|
||
.mobile-overview-heading {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.35rem;
|
||
margin: 0.35rem 0 0;
|
||
padding: 0;
|
||
font-size: 0.65rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.09em;
|
||
text-transform: uppercase;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.mobile-overview-heading-count {
|
||
color: var(--text-dim);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mobile-overview-empty {
|
||
margin: 0;
|
||
padding: 0.5rem 0;
|
||
font-size: 0.75rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Row: 56px tap target, dot + two-line body + pill + chevron */
|
||
.mobile-overview-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.6rem;
|
||
width: 100%;
|
||
min-height: 56px;
|
||
padding: 0.5rem 0.7rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
background: var(--bg-card);
|
||
color: var(--text);
|
||
font-family: inherit;
|
||
text-align: left;
|
||
}
|
||
|
||
.mobile-overview-row:active {
|
||
background: var(--bg-hover);
|
||
}
|
||
|
||
/* Approvals Inbox answer strip: sits under a NEEDS YOU row (sibling of the
|
||
row <button>, see _buildMobileOverviewApprovalStrip). Buttons inherit no
|
||
toolbar styling on purpose; they are one-tap dialog answers, not runs. */
|
||
.mobile-overview-row-wrap {
|
||
width: 100%;
|
||
}
|
||
.mobile-overview-approval-strip {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.4rem;
|
||
padding: 0.4rem 0.2rem 0.1rem;
|
||
}
|
||
.mobile-overview-approval-btn {
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
background: var(--bg-card);
|
||
color: var(--text);
|
||
font-family: inherit;
|
||
font-size: 0.72rem;
|
||
padding: 0.35rem 0.6rem;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.mobile-overview-approval-btn.primary {
|
||
background: var(--accent);
|
||
border-color: var(--accent);
|
||
color: white;
|
||
}
|
||
.mobile-overview-approval-btn.danger {
|
||
border-color: var(--error);
|
||
color: var(--error);
|
||
}
|
||
|
||
/* Attention states mirror the session tabs exactly: red blink when the agent
|
||
asked something (permission / question), yellow blink when it is waiting for
|
||
a prompt. Same hues and same cadence as tab-blink-red / tab-blink-yellow in
|
||
styles.css; the keyframes are re-declared here only because a tab's resting
|
||
background is transparent while a row's is the card color. */
|
||
.mobile-overview-row--needs {
|
||
border-color: var(--red);
|
||
animation: mobile-overview-blink-red 2.5s ease-in-out infinite;
|
||
}
|
||
|
||
.mobile-overview-row--waiting {
|
||
border-color: var(--yellow);
|
||
animation: mobile-overview-blink-yellow 3.5s ease-in-out infinite;
|
||
}
|
||
|
||
.mobile-overview-row--error {
|
||
border-color: var(--red);
|
||
}
|
||
|
||
/* Working is not an alert, so it gets a calm green breathing edge rather than a
|
||
blink: at a glance the row reads "this one is moving", without competing with
|
||
the two states that actually want you. Slower than both of them on purpose. */
|
||
.mobile-overview-row--working {
|
||
border-color: var(--green);
|
||
animation: mobile-overview-breathe-green 2.2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes mobile-overview-breathe-green {
|
||
0%,
|
||
100% {
|
||
background: var(--bg-card);
|
||
border-color: var(--border);
|
||
}
|
||
50% {
|
||
background: rgba(34, 197, 94, 0.1);
|
||
border-color: var(--green);
|
||
}
|
||
}
|
||
|
||
/* The pill picks up a three-dot ellipsis that fills in and empties, so the row
|
||
still reads as active on a skin where the border tint is subtle. */
|
||
.mobile-overview-pill--working::after {
|
||
content: '';
|
||
display: inline-block;
|
||
width: 0.75em;
|
||
text-align: left;
|
||
animation: mobile-overview-pill-dots 1.5s steps(1, end) infinite;
|
||
}
|
||
|
||
@keyframes mobile-overview-pill-dots {
|
||
0% {
|
||
content: '';
|
||
}
|
||
25% {
|
||
content: '.';
|
||
}
|
||
50% {
|
||
content: '..';
|
||
}
|
||
75% {
|
||
content: '...';
|
||
}
|
||
}
|
||
|
||
@keyframes mobile-overview-blink-red {
|
||
0%,
|
||
100% {
|
||
background: var(--bg-card);
|
||
border-color: var(--border);
|
||
}
|
||
50% {
|
||
background: rgba(239, 68, 68, 0.14);
|
||
border-color: var(--red);
|
||
}
|
||
}
|
||
|
||
@keyframes mobile-overview-blink-yellow {
|
||
0%,
|
||
100% {
|
||
background: var(--bg-card);
|
||
border-color: var(--border);
|
||
}
|
||
50% {
|
||
background: rgba(234, 179, 8, 0.12);
|
||
border-color: var(--yellow);
|
||
}
|
||
}
|
||
|
||
.mobile-overview-row-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.15rem;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.mobile-overview-row-title {
|
||
display: block;
|
||
font-size: 0.9rem;
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.mobile-overview-row-case {
|
||
color: var(--text-dim);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.mobile-overview-row-sub {
|
||
display: block;
|
||
font-size: 0.68rem;
|
||
color: var(--text-muted);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* Third line of the row body: "STARTED 3d ago · IDLE 12m". Monospace so the
|
||
numbers stay put as the clock rewrites them every 20s, and dimmer than the
|
||
path above it: it answers a question you only ask on purpose. */
|
||
.mobile-overview-row-meta {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
min-width: 0;
|
||
font-family: var(--font-mono, monospace);
|
||
font-size: 0.63rem;
|
||
color: var(--text-muted);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.mobile-overview-meta-item {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.mobile-overview-meta-key {
|
||
margin-right: 0.3rem;
|
||
opacity: 0.6;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
|
||
.mobile-overview-meta-sep {
|
||
opacity: 0.45;
|
||
}
|
||
|
||
/* The freshest signal on the row: while a session is actually running, how
|
||
long the current turn has been going is the number the eye should land on.
|
||
Same treatment the desktop rail gives its "active" stamp. */
|
||
.mobile-overview-row--working .mobile-overview-meta-since {
|
||
color: var(--green);
|
||
opacity: 0.95;
|
||
}
|
||
|
||
.mobile-overview-dot {
|
||
flex-shrink: 0;
|
||
width: 9px;
|
||
height: 9px;
|
||
border-radius: 50%;
|
||
background: var(--text-muted);
|
||
}
|
||
|
||
.mobile-overview-dot--needs,
|
||
.mobile-overview-dot--error {
|
||
background: var(--red);
|
||
}
|
||
|
||
.mobile-overview-dot--waiting {
|
||
background: var(--yellow);
|
||
}
|
||
|
||
/* Same as .session-tab .tab-status: green when the session is fine, and the
|
||
shared `pulse` keyframes while it is working. The halo matches the busy tab
|
||
dot and the desktop home column (.home-sessions-dot--working, styles.css):
|
||
working reads identically on every surface or it reads as three features. */
|
||
.mobile-overview-dot--working {
|
||
background: var(--green);
|
||
animation: pulse 1.5s infinite;
|
||
box-shadow: 0 0 8px 2px color-mix(in srgb, var(--green) 55%, transparent);
|
||
will-change: opacity;
|
||
}
|
||
|
||
/* Ring the pulsing dot with the SAME spinner a tab shows while it loads: same
|
||
2px ring, same bright leading edge, same `tab-load-spin` keyframes from
|
||
styles.css (reused, not re-declared, so the two can never drift). Green
|
||
rather than the tab's blue because here it means "running", not "loading":
|
||
the motion is the shared part, the color still belongs to the state. */
|
||
.mobile-overview-dot {
|
||
position: relative;
|
||
}
|
||
|
||
.mobile-overview-dot--working::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset: -4px;
|
||
border: 2px solid rgba(34, 197, 94, 0.25);
|
||
border-top-color: var(--green);
|
||
border-radius: 50%;
|
||
animation: tab-load-spin 0.7s linear infinite;
|
||
}
|
||
|
||
.mobile-overview-dot--idle {
|
||
background: var(--green);
|
||
}
|
||
|
||
.mobile-overview-dot--done {
|
||
background: var(--text-muted);
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.mobile-overview-pill {
|
||
flex-shrink: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
padding: 0.2rem 0.45rem;
|
||
border: 1px solid var(--border-light);
|
||
border-radius: 999px;
|
||
font-size: 0.62rem;
|
||
font-weight: 600;
|
||
color: var(--text-dim);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mobile-overview-pill--needs,
|
||
.mobile-overview-pill--error {
|
||
border-color: var(--red);
|
||
color: var(--red);
|
||
}
|
||
|
||
.mobile-overview-pill--waiting {
|
||
border-color: var(--yellow);
|
||
color: var(--yellow);
|
||
}
|
||
|
||
.mobile-overview-pill--idle,
|
||
.mobile-overview-pill--working {
|
||
border-color: var(--green);
|
||
color: var(--green);
|
||
}
|
||
|
||
.mobile-overview-chevron {
|
||
flex-shrink: 0;
|
||
color: var(--text-muted);
|
||
font-size: 1rem;
|
||
line-height: 1;
|
||
}
|
||
|
||
/* Past conversations: quieter than a live row, since tapping one starts work */
|
||
.mobile-overview-row--past {
|
||
background: transparent;
|
||
border-style: dashed;
|
||
}
|
||
|
||
.mobile-overview-row--past .mobile-overview-row-title {
|
||
font-weight: 500;
|
||
color: var(--text-dim);
|
||
}
|
||
|
||
.mobile-overview-dot--past {
|
||
background: transparent;
|
||
border: 1px solid var(--border-light);
|
||
}
|
||
|
||
.mobile-overview-pill--past {
|
||
border-color: var(--border-light);
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.mobile-overview-more {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.35rem;
|
||
min-height: 40px;
|
||
border: none;
|
||
border-radius: 10px;
|
||
background: var(--control-bg);
|
||
color: var(--text-dim);
|
||
font-family: inherit;
|
||
font-size: 0.72rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mobile-overview-more-count {
|
||
padding: 0.05rem 0.35rem;
|
||
border-radius: 999px;
|
||
background: var(--control-bg-hover);
|
||
color: var(--text-muted);
|
||
}
|
||
}
|
||
|
||
/* The overview's attention blink is an alert, so it stays visible without
|
||
motion: hold the alert color instead of animating to it. */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.mobile-overview-row--needs,
|
||
.mobile-overview-row--waiting {
|
||
animation: none;
|
||
}
|
||
|
||
.mobile-overview-row--needs {
|
||
background: rgba(239, 68, 68, 0.14);
|
||
}
|
||
|
||
.mobile-overview-row--waiting {
|
||
background: rgba(234, 179, 8, 0.12);
|
||
}
|
||
|
||
.mobile-overview-dot--working {
|
||
animation: none;
|
||
}
|
||
|
||
/* The ring stays as a static full circle: it still marks the row, it just
|
||
stops turning. */
|
||
.mobile-overview-dot--working::after {
|
||
border-color: var(--green);
|
||
animation: none;
|
||
}
|
||
|
||
/* Working is only informational, so it drops to a static green edge and a
|
||
static ellipsis rather than holding a tint the way the alerts do. */
|
||
.mobile-overview-row--working {
|
||
animation: none;
|
||
}
|
||
|
||
.mobile-overview-pill--working::after {
|
||
content: '...';
|
||
animation: none;
|
||
}
|
||
}
|
||
|
||
/* Light-skin compatibility for mobile-only chrome. These components predate
|
||
the shared skin system and intentionally retain their original dark values
|
||
for the three dark skins above. */
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is(.header, .toolbar, .keyboard-accessory-bar) {
|
||
background: var(--glass-bg);
|
||
border-color: var(--glass-border);
|
||
color: var(--text);
|
||
}
|
||
|
||
/* `.accessory-btn:not(.armed)` on purpose: this selector is (0,3,1) — `:is()`
|
||
takes the specificity of its most specific argument, and `.btn-toolbar
|
||
.btn-shell` is two classes — so it OUTRANKS the (0,3,0) armed-Ctrl rules in
|
||
both stylesheets and repainted the armed modifier back to a resting button on
|
||
all four light skins. Excluding the state here fixes phone and tablet at once;
|
||
adding a class to the armed rules would only have moved the tie. */
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is(.btn-voice-mobile, .btn-settings-mobile, .btn-toolbar.btn-shell, .toolbar .btn-case-add, .accessory-btn:not(.armed)) {
|
||
background: var(--control-bg);
|
||
border-color: var(--control-border);
|
||
color: var(--text-dim);
|
||
}
|
||
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is(.btn-voice-mobile:active, .btn-settings-mobile:active, .btn-toolbar.btn-shell:hover, .btn-toolbar.btn-shell:active, .btn-case-add:hover, .btn-case-add:active, .accessory-btn:active) {
|
||
background: var(--control-bg-hover);
|
||
border-color: var(--control-border-hover);
|
||
color: var(--text);
|
||
}
|
||
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is(.btn-toolbar.btn-run.mode-claude, .btn-toolbar.btn-run-gear.mode-claude) {
|
||
background: linear-gradient(135deg, var(--accent-grad-a), var(--accent-grad-b));
|
||
border-color: var(--accent);
|
||
color: var(--accent-ink);
|
||
}
|
||
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is(.btn-toolbar.btn-run.mode-opencode, .btn-toolbar.btn-run-gear.mode-opencode) {
|
||
background: linear-gradient(135deg, var(--accent-d), var(--accent-grad-b));
|
||
border-color: var(--accent);
|
||
color: var(--accent-ink);
|
||
}
|
||
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is(.btn-toolbar.btn-run.mode-gemini, .btn-toolbar.btn-run-gear.mode-gemini) {
|
||
background: linear-gradient(135deg, #174ea6, #4f46e5);
|
||
border-color: #315fc3;
|
||
color: #ffffff;
|
||
}
|
||
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is(.btn-toolbar.btn-run.mode-antigravity, .btn-toolbar.btn-run-gear.mode-antigravity) {
|
||
background: linear-gradient(135deg, #0e7490, #0891b2);
|
||
border-color: #155e75;
|
||
color: #ffffff;
|
||
}
|
||
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is(.btn-toolbar.btn-run.mode-pi, .btn-toolbar.btn-run-gear.mode-pi) {
|
||
background: linear-gradient(135deg, #be185d, #db2777);
|
||
border-color: #9d174d;
|
||
color: #ffffff;
|
||
}
|
||
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) .btn-toolbar.btn-run-gear {
|
||
border-left-color: var(--control-border-hover) !important;
|
||
}
|
||
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is(.case-settings-popover-mobile, .mobile-case-picker-sheet) {
|
||
background: var(--floating-bg);
|
||
border-color: var(--control-border);
|
||
color: var(--text);
|
||
box-shadow: var(--elevated-shadow);
|
||
}
|
||
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is(.case-settings-popover-mobile .checkbox-inline, #createCaseModal .form-row label) {
|
||
color: var(--text);
|
||
}
|
||
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) .case-settings-popover-mobile .form-hint {
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) .mobile-case-picker .modal-backdrop {
|
||
background: var(--modal-backdrop);
|
||
}
|
||
|
||
|
||
/* Keyboard accessory bar + paste overlay base styles moved to styles.css
|
||
(always loaded — covers iPad landscape where mobile.css doesn't load).
|
||
Phone-specific overrides remain in @media (max-width: 430px) above. */
|
||
|
||
/* ============================================================================
|
||
iOS Safari Specific Fixes
|
||
============================================================================ */
|
||
.ios-device.safari-browser {
|
||
/* Prevent pull-to-refresh */
|
||
overscroll-behavior: none;
|
||
}
|
||
|
||
/* JS touch handler now drives terminal scrollback — no native scroll needed.
|
||
-webkit-overflow-scrolling and overflow-y:scroll on xterm-viewport removed. */
|
||
|
||
/* ============================================================================
|
||
Hover State Fallbacks for Touch
|
||
============================================================================ */
|
||
@media (hover: none) and (pointer: coarse) {
|
||
/* Remove hover-dependent UI patterns */
|
||
.session-tab .tab-close,
|
||
.session-tab .tab-gear {
|
||
opacity: 1;
|
||
width: auto;
|
||
padding: 0.15rem 0.25rem;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* Simplify button hover states */
|
||
.btn-toolbar:hover {
|
||
transform: none;
|
||
}
|
||
|
||
.btn-icon-header:hover {
|
||
transform: none;
|
||
}
|
||
|
||
/* Show dropdown on tap instead of hover */
|
||
.subagent-dropdown-trigger:active + .subagent-dropdown-menu,
|
||
.subagent-dropdown-trigger:focus + .subagent-dropdown-menu {
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 430px) {
|
||
/* Attachment history (COD-18): full-screen sheet on phones */
|
||
.attachment-history-drawer {
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
max-width: 100%;
|
||
height: 100vh;
|
||
height: 100dvh;
|
||
border-left: none;
|
||
border-radius: 0;
|
||
padding-top: var(--safe-area-top);
|
||
padding-left: var(--safe-area-left);
|
||
padding-right: var(--safe-area-right);
|
||
padding-bottom: var(--safe-area-bottom);
|
||
}
|
||
|
||
.attachment-history-header {
|
||
padding: 12px;
|
||
}
|
||
|
||
.attachment-history-list {
|
||
padding: 6px;
|
||
}
|
||
|
||
.attachment-history-item {
|
||
grid-template-columns: 104px minmax(0, 1fr);
|
||
gap: 8px;
|
||
padding: 8px 6px;
|
||
}
|
||
|
||
.attachment-history-thumb {
|
||
width: 104px;
|
||
}
|
||
|
||
.attachment-history-actions {
|
||
gap: 4px;
|
||
}
|
||
|
||
.attachment-history-actions button {
|
||
padding: 4px 7px;
|
||
}
|
||
}
|
||
|
||
/* ============================================================================
|
||
Connection loss: phone sizing
|
||
The banner sits in normal flow directly under the fixed header (the container
|
||
already reserves that space), so it needs the same safe-area padding as the
|
||
other banners. The overlay is fixed and handles its own insets.
|
||
============================================================================ */
|
||
@media (max-width: 430px) {
|
||
.offline-banner {
|
||
padding: 0.4rem 0.5rem;
|
||
padding-left: calc(0.5rem + var(--safe-area-left));
|
||
padding-right: calc(0.5rem + var(--safe-area-right));
|
||
font-size: 0.7rem;
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
/* The countdown is the first thing to go when the bar gets tight. The
|
||
wording plus the Retry button carry the message on their own. */
|
||
.offline-banner-detail {
|
||
display: none;
|
||
}
|
||
|
||
.offline-banner-retry {
|
||
padding: 0.25rem 0.5rem;
|
||
margin-left: auto;
|
||
}
|
||
|
||
.offline-overlay-card {
|
||
padding: 22px 18px 18px;
|
||
}
|
||
|
||
.offline-overlay-title {
|
||
font-size: 1.05rem;
|
||
}
|
||
|
||
.offline-overlay-actions {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.offline-overlay-btn {
|
||
width: 100%;
|
||
padding: 0.65rem 1rem;
|
||
}
|
||
}
|
||
|
||
/* ============================================================================
|
||
App Settings, compact layout (<= 860px)
|
||
|
||
Same single scrolling document as the desktop rail layout; only the
|
||
navigation changes. The rail collapses to its search field and #appSettingsJump
|
||
takes over as the sticky "where am I / jump elsewhere" control, so a phone
|
||
spends its vertical budget on settings instead of on chrome.
|
||
|
||
Groups render as one inset rounded list with hairline dividers rather than a
|
||
stack of separate cards: at 390px the per-card borders were most of the pixels.
|
||
============================================================================ */
|
||
@media (max-width: 860px) {
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .modal-content.modal-lg {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
height: 100%;
|
||
max-height: 100%;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
}
|
||
|
||
/* Rail keeps only its search field, laid out as a bar */
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-rail {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
border-right: 0;
|
||
border-bottom: 1px solid var(--border);
|
||
background: transparent;
|
||
padding: 10px 14px;
|
||
overflow: visible;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-rail-items,
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-rail-foot {
|
||
display: none;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-search {
|
||
margin: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-search input {
|
||
padding: 9px 10px 9px 30px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
/* Save + Close are the two ways out of the sheet (save-and-close vs
|
||
discard-and-close), hit in the same corner with the same thumb, so here —
|
||
and only here, since Save is header-only below 860px — they share a
|
||
recessed tray and matching pill geometry instead of reading as a fat
|
||
accent pill parked beside a stray × glyph. Tray colors come from skin
|
||
tokens, never a hardcoded black alpha, or the light skins get a grey slab.
|
||
`:has()` keeps the tray off the two sheets that carry a lone × (Session
|
||
Options and Add Case save from inside their own forms). */
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-head-actions:has(.set-head-save) {
|
||
padding: 3px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
background: var(--bg-input);
|
||
}
|
||
|
||
/* Save moves into the header; the bottom action bar would cost 60px. Both
|
||
buttons grow to a thumb-sized target and keep identical heights so the
|
||
pair reads as one cluster — 36 + the tray's 3px padding and 1px border on
|
||
each side is a 44px block, the same height as the phone header. */
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-head-save {
|
||
display: inline-flex;
|
||
height: 36px;
|
||
padding: 0 16px;
|
||
font-size: 0.86rem;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-head-actions .modal-close {
|
||
width: 36px;
|
||
height: 36px;
|
||
font-size: 1.35rem;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-foot {
|
||
display: none;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-doc {
|
||
padding: 0 14px 34px;
|
||
flex: 1;
|
||
}
|
||
|
||
/* ── jump control ──────────────────────────────────────────────────── */
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump {
|
||
display: flex;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 4;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
margin: 10px 0 2px;
|
||
padding: 10px 12px;
|
||
border-radius: 11px;
|
||
font: inherit;
|
||
font-size: 0.82rem;
|
||
color: var(--text);
|
||
background: rgba(var(--accent-rgb), 0.13);
|
||
border: 1px solid rgba(var(--accent-rgb), 0.3);
|
||
-webkit-backdrop-filter: blur(14px);
|
||
backdrop-filter: blur(14px);
|
||
cursor: pointer;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-ico {
|
||
color: var(--accent);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-label {
|
||
font-weight: 580;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-chev {
|
||
margin-left: auto;
|
||
color: var(--text-muted);
|
||
flex-shrink: 0;
|
||
transition: transform 0.18s;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump[aria-expanded='true'] .set-jump-chev {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-veil {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 8;
|
||
background: rgba(4, 8, 13, 0.62);
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-menu {
|
||
display: none;
|
||
position: absolute;
|
||
left: 14px;
|
||
right: 14px;
|
||
z-index: 9;
|
||
padding: 7px;
|
||
border-radius: 16px;
|
||
/* Opaque on purpose: --floating-bg is translucent and the settings rows
|
||
behind the menu bleed through it. */
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--control-border);
|
||
box-shadow: var(--elevated-shadow);
|
||
max-height: 70vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
#appSettingsModal.jump-open .set-jump-veil,
|
||
#appSettingsModal.jump-open .set-jump-menu {
|
||
display: block;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 11px;
|
||
width: 100%;
|
||
padding: 11px 12px;
|
||
border: 0;
|
||
border-radius: 11px;
|
||
background: transparent;
|
||
color: var(--text-dim);
|
||
font: inherit;
|
||
font-size: 0.82rem;
|
||
text-align: left;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-row svg {
|
||
color: var(--text-muted);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-row .set-jump-count {
|
||
margin-left: auto;
|
||
font-size: 0.62rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-row.active {
|
||
background: rgba(var(--accent-rgb), 0.14);
|
||
color: var(--text);
|
||
font-weight: 570;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-row.active svg {
|
||
color: var(--accent);
|
||
}
|
||
|
||
/* ── sections step down: the jump pill already names the current one ── */
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section {
|
||
padding-top: 0;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section + .set-section {
|
||
border-top: 0;
|
||
margin-top: 0;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section-head {
|
||
gap: 7px;
|
||
margin: 18px 0 2px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section-head svg {
|
||
padding: 0;
|
||
border: 0;
|
||
background: none;
|
||
color: var(--text-muted);
|
||
width: 12px;
|
||
height: 12px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section-head h2 {
|
||
font-size: 0.6rem;
|
||
font-weight: 640;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section-head::after {
|
||
content: '';
|
||
flex: 1;
|
||
height: 1px;
|
||
background: linear-gradient(90deg, var(--border), transparent);
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section-blurb {
|
||
display: none;
|
||
}
|
||
|
||
/* ── live layout preview ───────────────────────────────────────────── */
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-preview {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-preview-stage {
|
||
min-height: 62px;
|
||
}
|
||
|
||
/* ── inset grouped list ────────────────────────────────────────────── */
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group {
|
||
margin-top: 14px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group + .set-group {
|
||
margin-top: 16px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-head {
|
||
margin-bottom: 7px;
|
||
padding: 0 3px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-hint {
|
||
padding: 0 3px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body {
|
||
gap: 0;
|
||
background: rgba(255, 255, 255, 0.035);
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
border-radius: 13px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .set-row {
|
||
background: transparent;
|
||
border: 0;
|
||
border-radius: 0;
|
||
padding: 12px 13px;
|
||
gap: 12px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .set-row + .set-row {
|
||
border-top: 1px solid rgba(255, 255, 255, 0.055);
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .set-chips,
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .set-modelgrid,
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .set-minigrid,
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > #appSettingsShortcutsList {
|
||
padding: 12px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .event-type-grid {
|
||
padding: 12px;
|
||
margin: 0;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row-label {
|
||
font-size: 0.84rem;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row-desc {
|
||
font-size: 0.69rem;
|
||
max-width: none;
|
||
}
|
||
|
||
/* Fields go full width under their label instead of fighting for the row */
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row.has-field {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 9px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row.has-field .set-select,
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row.has-field .set-input {
|
||
width: 100%;
|
||
min-width: 0;
|
||
max-width: none;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row-actions-wide {
|
||
width: 100%;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row-actions-wide .set-input {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-num {
|
||
width: 76px;
|
||
}
|
||
|
||
/* Bigger touch targets for the toggles and chips */
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .switch-sm {
|
||
width: 40px;
|
||
height: 24px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .switch-sm .slider:before {
|
||
height: 18px;
|
||
width: 18px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .switch-sm input:checked + .slider:before {
|
||
transform: translateX(16px);
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-chip {
|
||
font-size: 0.78rem;
|
||
padding: 9px 14px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-modelgrid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 8px;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-minigrid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-mini .set-select {
|
||
width: 148px;
|
||
}
|
||
|
||
/* One scrollable line beats a ragged two-row wrap for 7 effort levels */
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-segment {
|
||
overflow-x: auto;
|
||
scrollbar-width: none;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-segment::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-segment button {
|
||
flex: 0 0 auto;
|
||
padding: 8px 12px;
|
||
}
|
||
}
|
||
|
||
/* ============================================================================
|
||
Session Options, compact layout (<= 860px)
|
||
|
||
App Settings collapses its rail and hands navigation to the sticky
|
||
#appSettingsJump pill. Session Options has no such pill (and no search), so
|
||
its rail stays put and becomes a horizontal, scrollable strip — which is
|
||
what its tab bar was before the two modals started sharing a surface.
|
||
============================================================================ */
|
||
@media (max-width: 860px) {
|
||
:is(#sessionOptionsModal, #createCaseModal) .set-rail {
|
||
padding: 8px 10px;
|
||
}
|
||
|
||
:is(#sessionOptionsModal, #createCaseModal) .set-rail-items {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex: 1;
|
||
min-width: 0;
|
||
gap: 4px;
|
||
overflow-x: auto;
|
||
scrollbar-width: none;
|
||
}
|
||
|
||
:is(#sessionOptionsModal, #createCaseModal) .set-rail-items::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
:is(#sessionOptionsModal, #createCaseModal) .set-rail-item {
|
||
white-space: nowrap;
|
||
padding: 8px 12px;
|
||
}
|
||
|
||
/* The active marker is a left bar in the vertical rail; horizontally that
|
||
reads as a stray tick, so the strip uses a filled pill instead. */
|
||
:is(#sessionOptionsModal, #createCaseModal) .set-rail-item.active::before {
|
||
display: none;
|
||
}
|
||
|
||
:is(#sessionOptionsModal, #createCaseModal) .set-rail-item.active {
|
||
background: rgba(var(--accent-rgb), 0.13);
|
||
}
|
||
}
|