@bakapiano/ccsm 0.22.6 → 0.22.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +538 -538
- package/README.md +189 -189
- package/bin/ccsm.js +235 -235
- package/lib/cliActivity.js +139 -139
- package/lib/codexSeed.js +183 -183
- package/lib/config.js +279 -274
- package/lib/devices.js +229 -229
- package/lib/folders.js +124 -124
- package/lib/localCliSessions.js +519 -519
- package/lib/persistedSessions.js +129 -129
- package/lib/tunnel.js +621 -621
- package/lib/webTerminal.js +225 -225
- package/lib/workspace.js +233 -233
- package/package.json +57 -57
- package/public/css/base.css +99 -99
- package/public/css/cards.css +183 -183
- package/public/css/feedback.css +504 -504
- package/public/css/forms.css +453 -453
- package/public/css/layout.css +154 -154
- package/public/css/modal.css +190 -190
- package/public/css/responsive.css +176 -176
- package/public/css/sidebar.css +707 -707
- package/public/css/terminals.css +546 -546
- package/public/css/tokens.css +81 -81
- package/public/css/wco.css +196 -196
- package/public/css/widgets.css +2725 -2725
- package/public/index.html +152 -152
- package/public/js/api.js +371 -371
- package/public/js/backend.js +149 -149
- package/public/js/components/App.js +73 -73
- package/public/js/components/DirectoryPicker.js +203 -203
- package/public/js/components/EntityFormModal.js +153 -153
- package/public/js/components/Modal.js +57 -57
- package/public/js/components/OfflineBanner.js +67 -67
- package/public/js/components/PageTitleBar.js +13 -13
- package/public/js/components/PendingApprovalOverlay.js +128 -128
- package/public/js/components/Picker.js +179 -179
- package/public/js/components/Popover.js +55 -55
- package/public/js/components/RestartOverlay.js +36 -36
- package/public/js/components/Sidebar.js +380 -380
- package/public/js/components/TerminalInstance.js +28 -0
- package/public/js/components/useDragSort.js +67 -67
- package/public/js/dialog.js +67 -67
- package/public/js/icons.js +212 -212
- package/public/js/main.js +296 -296
- package/public/js/pages/AboutPage.js +90 -90
- package/public/js/pages/ConfigurePage.js +728 -713
- package/public/js/pages/LaunchPage.js +421 -421
- package/public/js/pages/RemotePage.js +743 -743
- package/public/js/pages/SessionsPage.js +53 -53
- package/public/js/state.js +335 -335
- package/scripts/dev.js +149 -149
- package/scripts/install.js +153 -153
- package/scripts/restart-helper.js +96 -96
- package/scripts/upgrade-helper.js +687 -687
- package/server.js +1820 -1807
- package/public/manifest.webmanifest +0 -25
- package/public/setup/index.html +0 -567
|
@@ -1,176 +1,176 @@
|
|
|
1
|
-
/* Narrow viewports: force-collapse sidebar, single-col config grid */
|
|
2
|
-
|
|
3
|
-
/* Narrow desktop / tablet viewports: stack the page-head, single-column
|
|
4
|
-
config grids. Sidebar stays at full width — user can still toggle it
|
|
5
|
-
manually via the collapse button. Phone-sized (≤ 640px) gets the FAB
|
|
6
|
-
drawer treatment below. */
|
|
7
|
-
@media (max-width: 900px) {
|
|
8
|
-
.main { padding: 0 var(--s-4); }
|
|
9
|
-
.page-head { flex-direction: column; gap: var(--s-3); }
|
|
10
|
-
.config-grid { grid-template-columns: 1fr; }
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/* Phone viewports (≤ 640px): sidebar disappears entirely from the grid
|
|
14
|
-
layout; a circular floating button bottom-left toggles a full-screen
|
|
15
|
-
drawer that re-mounts the sidebar over everything else. */
|
|
16
|
-
@media (max-width: 640px) {
|
|
17
|
-
/* Shrink the whole app to the visible area ABOVE the soft keyboard.
|
|
18
|
-
--app-vh is the visualViewport height (main.js); the layout-viewport
|
|
19
|
-
100vh never shrinks for the keyboard, which left the terminal's bottom
|
|
20
|
-
rows hidden behind it. 100dvh is the fallback before the JS runs. */
|
|
21
|
-
.app.is-mobile { grid-template-columns: 1fr !important; height: var(--app-vh, 100dvh); }
|
|
22
|
-
/* Keyboard up: keep the terminal's content above the floating key bar
|
|
23
|
-
(TerminalKeyBar, ~50px). Only the terminal pane needs this — other
|
|
24
|
-
pages have their own scroll padding. */
|
|
25
|
-
body.kb-open .app.is-mobile .session-pane-body { padding-bottom: 50px; }
|
|
26
|
-
.app.is-mobile .sidebar {
|
|
27
|
-
/* Collapsed (drawer closed): out of the flow + invisible. */
|
|
28
|
-
position: fixed;
|
|
29
|
-
inset: 0;
|
|
30
|
-
z-index: 200;
|
|
31
|
-
width: 100%;
|
|
32
|
-
height: 100%;
|
|
33
|
-
transform: translateX(-100%);
|
|
34
|
-
transition: transform .22s cubic-bezier(.4, 0, .2, 1);
|
|
35
|
-
padding: var(--s-3);
|
|
36
|
-
background: var(--bg);
|
|
37
|
-
border-right: 0;
|
|
38
|
-
overflow-y: auto;
|
|
39
|
-
}
|
|
40
|
-
.app.is-mobile.drawer-open .sidebar { transform: translateX(0); }
|
|
41
|
-
/* Brand + nav labels come BACK on mobile — they were hidden by the
|
|
42
|
-
900px collapse rule above. */
|
|
43
|
-
.app.is-mobile .brand-name,
|
|
44
|
-
.app.is-mobile .nav-label,
|
|
45
|
-
.app.is-mobile .nav-badge { opacity: 1; pointer-events: auto; }
|
|
46
|
-
/* Sidebar drag handle isn't useful at full-screen; close button isn't
|
|
47
|
-
either since the FAB doubles as a toggle. */
|
|
48
|
-
.app.is-mobile .sidebar [aria-label="resize sidebar"] { display: none; }
|
|
49
|
-
.app.is-mobile .collapse-toggle { display: none; }
|
|
50
|
-
|
|
51
|
-
/* Main pane occupies full width — no sidebar gutter. */
|
|
52
|
-
.app.is-mobile .main { padding: 0 var(--s-3); }
|
|
53
|
-
/* Session pane edge-to-edge margin needs to match the new padding. */
|
|
54
|
-
.app.is-mobile .session-pane { margin: 0 calc(-1 * var(--s-3)); }
|
|
55
|
-
|
|
56
|
-
/* The bottom-left FAB (52px circle + 16px margin) overlaps content
|
|
57
|
-
near the bottom-left corner. Reserve room inside scroll containers
|
|
58
|
-
so the last button / row isn't trapped under it. */
|
|
59
|
-
.app.is-mobile .settings-scroll { padding-bottom: 80px; }
|
|
60
|
-
.app.is-mobile .remote-page { padding-bottom: 80px; }
|
|
61
|
-
/* Launch page is its own block (no scroll wrapper) — push it up too
|
|
62
|
-
so the Launch button doesn't end up under the FAB. */
|
|
63
|
-
.app.is-mobile [data-panel="launch"] { padding-bottom: 80px; }
|
|
64
|
-
.app.is-mobile [data-panel="about"] { padding-bottom: 80px; }
|
|
65
|
-
/* Sessions terminal: the .session-actions footer / tabs at top means
|
|
66
|
-
the FAB sits over the terminal corner — leave it; the user can
|
|
67
|
-
scroll the terminal independently. */
|
|
68
|
-
|
|
69
|
-
/* Touch scrolling: drive xterm's OWN scrollable .xterm-viewport natively
|
|
70
|
-
so finger drags get real momentum and never "drop" mid-flick. A
|
|
71
|
-
JS-intercepted scroll can't: the moment Chrome's compositor decides the
|
|
72
|
-
drag is a vertical pan it cancels the touch sequence, so the handler
|
|
73
|
-
only ever sees the opening frames — the "滑一点就断触" symptom. The
|
|
74
|
-
catch is that the .xterm-screen layer (DOM rows on mobile) sits ON TOP
|
|
75
|
-
of the viewport and swallows every touch; only the thin scrollbar strip
|
|
76
|
-
at the right edge reached the viewport, which is exactly the
|
|
77
|
-
"中间滑不动、侧边能滑" report. Make the screen transparent to pointers so
|
|
78
|
-
the drag lands on the viewport underneath; pan-y marks it a vertical
|
|
79
|
-
scroller so the compositor scrolls it directly. (Tap-to-focus is then
|
|
80
|
-
re-added in JS — see TerminalView — since taps no longer reach xterm's
|
|
81
|
-
own focus path.) */
|
|
82
|
-
.app.is-mobile .terminal-host .xterm-screen { pointer-events: none; }
|
|
83
|
-
.app.is-mobile .terminal-host .xterm-viewport { touch-action: pan-y; }
|
|
84
|
-
|
|
85
|
-
/* Long inline code (URLs, paths) in the About / Remote bodies break
|
|
86
|
-
out of card edges on a narrow viewport. Let them wrap on any
|
|
87
|
-
character instead of stretching the line. */
|
|
88
|
-
.settings-section code,
|
|
89
|
-
.card code,
|
|
90
|
-
.remote-fact code {
|
|
91
|
-
word-break: break-all;
|
|
92
|
-
white-space: normal;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/* iOS Safari + Edge auto-zoom the viewport when the user taps an
|
|
96
|
-
<input>/<textarea> whose font-size is < 16px. Defensive bump on
|
|
97
|
-
real form inputs. NOTE: we deliberately do NOT touch
|
|
98
|
-
.xterm-helper-textarea here — xterm.js measures cell dimensions
|
|
99
|
-
off that element's computed font, so forcing it to 16px makes the
|
|
100
|
-
terminal render every glyph at ~16px regardless of the fontSize
|
|
101
|
-
option we passed in. (TerminalView intentionally picks 11px on
|
|
102
|
-
phones for ~50 cols.) On real iPhone the helper textarea anti-
|
|
103
|
-
zoom is handled by the viewport meta tag instead — see
|
|
104
|
-
public/index.html's `maximum-scale=1.0`. */
|
|
105
|
-
.input,
|
|
106
|
-
input[type="text"],
|
|
107
|
-
input[type="search"],
|
|
108
|
-
input[type="email"],
|
|
109
|
-
input[type="number"],
|
|
110
|
-
input[type="password"],
|
|
111
|
-
input[type="url"],
|
|
112
|
-
textarea,
|
|
113
|
-
select { font-size: 16px !important; }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/* FAB + backdrop · sit above page content but BELOW dialogs. The
|
|
117
|
-
`left` and `bottom` inline styles come from MobileNavFab.js (drag-
|
|
118
|
-
persisted), so we don't set defaults here — the component seeds
|
|
119
|
-
them on mount. `touch-action: none` stops the browser from also
|
|
120
|
-
scrolling the page while the user is dragging the FAB. */
|
|
121
|
-
.mobile-nav-fab {
|
|
122
|
-
position: fixed;
|
|
123
|
-
z-index: 220; /* above the terminal key bar (215) */
|
|
124
|
-
width: 52px;
|
|
125
|
-
height: 52px;
|
|
126
|
-
border-radius: 50%;
|
|
127
|
-
border: 1px solid var(--border-strong);
|
|
128
|
-
background: var(--bg-elev);
|
|
129
|
-
color: var(--ink);
|
|
130
|
-
display: inline-flex;
|
|
131
|
-
align-items: center;
|
|
132
|
-
justify-content: center;
|
|
133
|
-
box-shadow:
|
|
134
|
-
0 10px 24px -6px rgba(0,0,0,.28),
|
|
135
|
-
0 2px 4px rgba(0,0,0,.10);
|
|
136
|
-
cursor: grab;
|
|
137
|
-
touch-action: none;
|
|
138
|
-
user-select: none;
|
|
139
|
-
transition: box-shadow .15s, background .15s, transform .18s ease;
|
|
140
|
-
}
|
|
141
|
-
/* When the soft keyboard (and the terminal key bar that floats above it)
|
|
142
|
-
is up, lift the FAB clear of the key bar so they don't overlap. The
|
|
143
|
-
key bar is ~50px tall; nudge up a bit more for breathing room. */
|
|
144
|
-
body.kb-open .mobile-nav-fab { transform: translateY(-60px); }
|
|
145
|
-
/* No translateY on hover — would fight the inline left/bottom we set
|
|
146
|
-
on every pointermove during drag, making the FAB jitter under the
|
|
147
|
-
finger as :hover toggles on/off. Background-only hover for desktop
|
|
148
|
-
pointers; touch never matches :hover. */
|
|
149
|
-
.mobile-nav-fab:hover { background: var(--bg); }
|
|
150
|
-
.mobile-nav-fab:active { cursor: grabbing; }
|
|
151
|
-
/* Suppress the browser's default focus ring + tap highlight — on touch
|
|
152
|
-
the ring lingers after every tap and reads as a stuck "selected"
|
|
153
|
-
state on the FAB. We're not removing all affordance: the icon swap
|
|
154
|
-
(hamburger ↔ X) + the is-open border change still communicate state.
|
|
155
|
-
:focus-visible is also dropped here since the FAB is touch-first. */
|
|
156
|
-
.mobile-nav-fab:focus,
|
|
157
|
-
.mobile-nav-fab:focus-visible { outline: none; }
|
|
158
|
-
.mobile-nav-fab { -webkit-tap-highlight-color: transparent; }
|
|
159
|
-
.mobile-nav-fab svg { width: 22px; height: 22px; stroke-width: 2; pointer-events: none; }
|
|
160
|
-
/* Open state stays the same paper white — only the icon swaps to X.
|
|
161
|
-
Keeping the colour consistent reads as "same button, different
|
|
162
|
-
mode" instead of two different controls. */
|
|
163
|
-
.mobile-nav-fab.is-open {
|
|
164
|
-
background: var(--bg-elev);
|
|
165
|
-
color: var(--ink);
|
|
166
|
-
border-color: var(--border-strong);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.mobile-nav-backdrop {
|
|
170
|
-
position: fixed;
|
|
171
|
-
inset: 0;
|
|
172
|
-
z-index: 199; /* below sidebar (200) + fab (220), above content */
|
|
173
|
-
background: rgba(26, 24, 21, 0.45);
|
|
174
|
-
backdrop-filter: blur(2px);
|
|
175
|
-
animation: panel-in .15s ease-out;
|
|
176
|
-
}
|
|
1
|
+
/* Narrow viewports: force-collapse sidebar, single-col config grid */
|
|
2
|
+
|
|
3
|
+
/* Narrow desktop / tablet viewports: stack the page-head, single-column
|
|
4
|
+
config grids. Sidebar stays at full width — user can still toggle it
|
|
5
|
+
manually via the collapse button. Phone-sized (≤ 640px) gets the FAB
|
|
6
|
+
drawer treatment below. */
|
|
7
|
+
@media (max-width: 900px) {
|
|
8
|
+
.main { padding: 0 var(--s-4); }
|
|
9
|
+
.page-head { flex-direction: column; gap: var(--s-3); }
|
|
10
|
+
.config-grid { grid-template-columns: 1fr; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Phone viewports (≤ 640px): sidebar disappears entirely from the grid
|
|
14
|
+
layout; a circular floating button bottom-left toggles a full-screen
|
|
15
|
+
drawer that re-mounts the sidebar over everything else. */
|
|
16
|
+
@media (max-width: 640px) {
|
|
17
|
+
/* Shrink the whole app to the visible area ABOVE the soft keyboard.
|
|
18
|
+
--app-vh is the visualViewport height (main.js); the layout-viewport
|
|
19
|
+
100vh never shrinks for the keyboard, which left the terminal's bottom
|
|
20
|
+
rows hidden behind it. 100dvh is the fallback before the JS runs. */
|
|
21
|
+
.app.is-mobile { grid-template-columns: 1fr !important; height: var(--app-vh, 100dvh); }
|
|
22
|
+
/* Keyboard up: keep the terminal's content above the floating key bar
|
|
23
|
+
(TerminalKeyBar, ~50px). Only the terminal pane needs this — other
|
|
24
|
+
pages have their own scroll padding. */
|
|
25
|
+
body.kb-open .app.is-mobile .session-pane-body { padding-bottom: 50px; }
|
|
26
|
+
.app.is-mobile .sidebar {
|
|
27
|
+
/* Collapsed (drawer closed): out of the flow + invisible. */
|
|
28
|
+
position: fixed;
|
|
29
|
+
inset: 0;
|
|
30
|
+
z-index: 200;
|
|
31
|
+
width: 100%;
|
|
32
|
+
height: 100%;
|
|
33
|
+
transform: translateX(-100%);
|
|
34
|
+
transition: transform .22s cubic-bezier(.4, 0, .2, 1);
|
|
35
|
+
padding: var(--s-3);
|
|
36
|
+
background: var(--bg);
|
|
37
|
+
border-right: 0;
|
|
38
|
+
overflow-y: auto;
|
|
39
|
+
}
|
|
40
|
+
.app.is-mobile.drawer-open .sidebar { transform: translateX(0); }
|
|
41
|
+
/* Brand + nav labels come BACK on mobile — they were hidden by the
|
|
42
|
+
900px collapse rule above. */
|
|
43
|
+
.app.is-mobile .brand-name,
|
|
44
|
+
.app.is-mobile .nav-label,
|
|
45
|
+
.app.is-mobile .nav-badge { opacity: 1; pointer-events: auto; }
|
|
46
|
+
/* Sidebar drag handle isn't useful at full-screen; close button isn't
|
|
47
|
+
either since the FAB doubles as a toggle. */
|
|
48
|
+
.app.is-mobile .sidebar [aria-label="resize sidebar"] { display: none; }
|
|
49
|
+
.app.is-mobile .collapse-toggle { display: none; }
|
|
50
|
+
|
|
51
|
+
/* Main pane occupies full width — no sidebar gutter. */
|
|
52
|
+
.app.is-mobile .main { padding: 0 var(--s-3); }
|
|
53
|
+
/* Session pane edge-to-edge margin needs to match the new padding. */
|
|
54
|
+
.app.is-mobile .session-pane { margin: 0 calc(-1 * var(--s-3)); }
|
|
55
|
+
|
|
56
|
+
/* The bottom-left FAB (52px circle + 16px margin) overlaps content
|
|
57
|
+
near the bottom-left corner. Reserve room inside scroll containers
|
|
58
|
+
so the last button / row isn't trapped under it. */
|
|
59
|
+
.app.is-mobile .settings-scroll { padding-bottom: 80px; }
|
|
60
|
+
.app.is-mobile .remote-page { padding-bottom: 80px; }
|
|
61
|
+
/* Launch page is its own block (no scroll wrapper) — push it up too
|
|
62
|
+
so the Launch button doesn't end up under the FAB. */
|
|
63
|
+
.app.is-mobile [data-panel="launch"] { padding-bottom: 80px; }
|
|
64
|
+
.app.is-mobile [data-panel="about"] { padding-bottom: 80px; }
|
|
65
|
+
/* Sessions terminal: the .session-actions footer / tabs at top means
|
|
66
|
+
the FAB sits over the terminal corner — leave it; the user can
|
|
67
|
+
scroll the terminal independently. */
|
|
68
|
+
|
|
69
|
+
/* Touch scrolling: drive xterm's OWN scrollable .xterm-viewport natively
|
|
70
|
+
so finger drags get real momentum and never "drop" mid-flick. A
|
|
71
|
+
JS-intercepted scroll can't: the moment Chrome's compositor decides the
|
|
72
|
+
drag is a vertical pan it cancels the touch sequence, so the handler
|
|
73
|
+
only ever sees the opening frames — the "滑一点就断触" symptom. The
|
|
74
|
+
catch is that the .xterm-screen layer (DOM rows on mobile) sits ON TOP
|
|
75
|
+
of the viewport and swallows every touch; only the thin scrollbar strip
|
|
76
|
+
at the right edge reached the viewport, which is exactly the
|
|
77
|
+
"中间滑不动、侧边能滑" report. Make the screen transparent to pointers so
|
|
78
|
+
the drag lands on the viewport underneath; pan-y marks it a vertical
|
|
79
|
+
scroller so the compositor scrolls it directly. (Tap-to-focus is then
|
|
80
|
+
re-added in JS — see TerminalView — since taps no longer reach xterm's
|
|
81
|
+
own focus path.) */
|
|
82
|
+
.app.is-mobile .terminal-host .xterm-screen { pointer-events: none; }
|
|
83
|
+
.app.is-mobile .terminal-host .xterm-viewport { touch-action: pan-y; }
|
|
84
|
+
|
|
85
|
+
/* Long inline code (URLs, paths) in the About / Remote bodies break
|
|
86
|
+
out of card edges on a narrow viewport. Let them wrap on any
|
|
87
|
+
character instead of stretching the line. */
|
|
88
|
+
.settings-section code,
|
|
89
|
+
.card code,
|
|
90
|
+
.remote-fact code {
|
|
91
|
+
word-break: break-all;
|
|
92
|
+
white-space: normal;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* iOS Safari + Edge auto-zoom the viewport when the user taps an
|
|
96
|
+
<input>/<textarea> whose font-size is < 16px. Defensive bump on
|
|
97
|
+
real form inputs. NOTE: we deliberately do NOT touch
|
|
98
|
+
.xterm-helper-textarea here — xterm.js measures cell dimensions
|
|
99
|
+
off that element's computed font, so forcing it to 16px makes the
|
|
100
|
+
terminal render every glyph at ~16px regardless of the fontSize
|
|
101
|
+
option we passed in. (TerminalView intentionally picks 11px on
|
|
102
|
+
phones for ~50 cols.) On real iPhone the helper textarea anti-
|
|
103
|
+
zoom is handled by the viewport meta tag instead — see
|
|
104
|
+
public/index.html's `maximum-scale=1.0`. */
|
|
105
|
+
.input,
|
|
106
|
+
input[type="text"],
|
|
107
|
+
input[type="search"],
|
|
108
|
+
input[type="email"],
|
|
109
|
+
input[type="number"],
|
|
110
|
+
input[type="password"],
|
|
111
|
+
input[type="url"],
|
|
112
|
+
textarea,
|
|
113
|
+
select { font-size: 16px !important; }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* FAB + backdrop · sit above page content but BELOW dialogs. The
|
|
117
|
+
`left` and `bottom` inline styles come from MobileNavFab.js (drag-
|
|
118
|
+
persisted), so we don't set defaults here — the component seeds
|
|
119
|
+
them on mount. `touch-action: none` stops the browser from also
|
|
120
|
+
scrolling the page while the user is dragging the FAB. */
|
|
121
|
+
.mobile-nav-fab {
|
|
122
|
+
position: fixed;
|
|
123
|
+
z-index: 220; /* above the terminal key bar (215) */
|
|
124
|
+
width: 52px;
|
|
125
|
+
height: 52px;
|
|
126
|
+
border-radius: 50%;
|
|
127
|
+
border: 1px solid var(--border-strong);
|
|
128
|
+
background: var(--bg-elev);
|
|
129
|
+
color: var(--ink);
|
|
130
|
+
display: inline-flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
justify-content: center;
|
|
133
|
+
box-shadow:
|
|
134
|
+
0 10px 24px -6px rgba(0,0,0,.28),
|
|
135
|
+
0 2px 4px rgba(0,0,0,.10);
|
|
136
|
+
cursor: grab;
|
|
137
|
+
touch-action: none;
|
|
138
|
+
user-select: none;
|
|
139
|
+
transition: box-shadow .15s, background .15s, transform .18s ease;
|
|
140
|
+
}
|
|
141
|
+
/* When the soft keyboard (and the terminal key bar that floats above it)
|
|
142
|
+
is up, lift the FAB clear of the key bar so they don't overlap. The
|
|
143
|
+
key bar is ~50px tall; nudge up a bit more for breathing room. */
|
|
144
|
+
body.kb-open .mobile-nav-fab { transform: translateY(-60px); }
|
|
145
|
+
/* No translateY on hover — would fight the inline left/bottom we set
|
|
146
|
+
on every pointermove during drag, making the FAB jitter under the
|
|
147
|
+
finger as :hover toggles on/off. Background-only hover for desktop
|
|
148
|
+
pointers; touch never matches :hover. */
|
|
149
|
+
.mobile-nav-fab:hover { background: var(--bg); }
|
|
150
|
+
.mobile-nav-fab:active { cursor: grabbing; }
|
|
151
|
+
/* Suppress the browser's default focus ring + tap highlight — on touch
|
|
152
|
+
the ring lingers after every tap and reads as a stuck "selected"
|
|
153
|
+
state on the FAB. We're not removing all affordance: the icon swap
|
|
154
|
+
(hamburger ↔ X) + the is-open border change still communicate state.
|
|
155
|
+
:focus-visible is also dropped here since the FAB is touch-first. */
|
|
156
|
+
.mobile-nav-fab:focus,
|
|
157
|
+
.mobile-nav-fab:focus-visible { outline: none; }
|
|
158
|
+
.mobile-nav-fab { -webkit-tap-highlight-color: transparent; }
|
|
159
|
+
.mobile-nav-fab svg { width: 22px; height: 22px; stroke-width: 2; pointer-events: none; }
|
|
160
|
+
/* Open state stays the same paper white — only the icon swaps to X.
|
|
161
|
+
Keeping the colour consistent reads as "same button, different
|
|
162
|
+
mode" instead of two different controls. */
|
|
163
|
+
.mobile-nav-fab.is-open {
|
|
164
|
+
background: var(--bg-elev);
|
|
165
|
+
color: var(--ink);
|
|
166
|
+
border-color: var(--border-strong);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.mobile-nav-backdrop {
|
|
170
|
+
position: fixed;
|
|
171
|
+
inset: 0;
|
|
172
|
+
z-index: 199; /* below sidebar (200) + fab (220), above content */
|
|
173
|
+
background: rgba(26, 24, 21, 0.45);
|
|
174
|
+
backdrop-filter: blur(2px);
|
|
175
|
+
animation: panel-in .15s ease-out;
|
|
176
|
+
}
|