fix: mobile toolbar button sizing and accessory bar commands
- Fix Run Claude button being squeezed on mobile (use fit-content width) - Remove Send button from accessory bar - Add /init, /clear, /compact buttons with confirmation dialogs - Center toolbar buttons instead of stretching Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@@ -16,7 +16,7 @@ When user says "COM":
|
|||||||
1. Increment version in BOTH `package.json` AND `CLAUDE.md`
|
1. Increment version in BOTH `package.json` AND `CLAUDE.md`
|
||||||
2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web`
|
2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web`
|
||||||
|
|
||||||
**Version**: 0.1461 (must match `package.json` for npm publish)
|
**Version**: 0.1462 (must match `package.json` for npm publish)
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ To change defaults, edit the `??` fallback values in `openAppSettings()` and `ap
|
|||||||
|
|
||||||
**Port allocation**: E2E tests use centralized ports in `test/e2e/e2e.config.ts`. Unit/integration tests pick unique ports manually. Search `const PORT =` or `TEST_PORT` in test files to find used ports before adding new tests.
|
**Port allocation**: E2E tests use centralized ports in `test/e2e/e2e.config.ts`. Unit/integration tests pick unique ports manually. Search `const PORT =` or `TEST_PORT` in test files to find used ports before adding new tests.
|
||||||
|
|
||||||
**E2E tests**: Use Playwright. Run `npx playwright install chromium` first. See `test/e2e/fixtures/` for helpers. E2E config (`test/e2e/e2e.config.ts`) provides ports (3183-3190), timeouts, and helpers.
|
**E2E tests**: Use Playwright. Run `npx playwright install chromium` first. See `test/e2e/fixtures/` for helpers. E2E config (`test/e2e/e2e.config.ts`) provides ports (3183-3193), timeouts, and helpers.
|
||||||
|
|
||||||
**Test config**: Vitest runs with `globals: true` (no imports needed for `describe`/`it`/`expect`/`vi`) and `fileParallelism: false` (files run sequentially to respect screen limits). Unit test timeout is 30s, teardown timeout is 60s. E2E tests have longer timeouts defined in `test/e2e/e2e.config.ts` (90s test, 30s session creation).
|
**Test config**: Vitest runs with `globals: true` (no imports needed for `describe`/`it`/`expect`/`vi`) and `fileParallelism: false` (files run sequentially to respect screen limits). Unit test timeout is 30s, teardown timeout is 60s. E2E tests have longer timeouts defined in `test/e2e/e2e.config.ts` (90s test, 30s session creation).
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 388 KiB After Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 214 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1461",
|
"version": "0.1462",
|
||||||
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -58,13 +58,7 @@ async function takeScreenshots() {
|
|||||||
// Screenshot 2: Simulate keyboard appearing
|
// Screenshot 2: Simulate keyboard appearing
|
||||||
console.log('Simulating virtual keyboard...');
|
console.log('Simulating virtual keyboard...');
|
||||||
|
|
||||||
// Resize viewport to simulate keyboard taking up space
|
// Trigger the keyboard handler manually - simulate what happens when keyboard shows
|
||||||
await page.setViewportSize({
|
|
||||||
width: VIEWPORT_WIDTH,
|
|
||||||
height: VIEWPORT_HEIGHT - KEYBOARD_HEIGHT
|
|
||||||
});
|
|
||||||
|
|
||||||
// Trigger the keyboard handler manually
|
|
||||||
await page.evaluate((keyboardHeight) => {
|
await page.evaluate((keyboardHeight) => {
|
||||||
// Add keyboard-visible class
|
// Add keyboard-visible class
|
||||||
document.body.classList.add('keyboard-visible');
|
document.body.classList.add('keyboard-visible');
|
||||||
@@ -74,6 +68,20 @@ async function takeScreenshots() {
|
|||||||
if (toolbar) {
|
if (toolbar) {
|
||||||
toolbar.style.transform = `translateY(${-keyboardHeight}px)`;
|
toolbar.style.transform = `translateY(${-keyboardHeight}px)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show and move accessory bar
|
||||||
|
const accessoryBar = document.querySelector('.keyboard-accessory-bar');
|
||||||
|
if (accessoryBar) {
|
||||||
|
accessoryBar.classList.add('visible');
|
||||||
|
accessoryBar.style.transform = `translateY(${-keyboardHeight}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shrink main content area so terminal doesn't extend behind keyboard
|
||||||
|
// Account for toolbar (40px) + accessory bar (44px)
|
||||||
|
const main = document.querySelector('.main');
|
||||||
|
if (main) {
|
||||||
|
main.style.paddingBottom = `${keyboardHeight + 94}px`;
|
||||||
|
}
|
||||||
}, KEYBOARD_HEIGHT);
|
}, KEYBOARD_HEIGHT);
|
||||||
|
|
||||||
await page.waitForTimeout(300);
|
await page.waitForTimeout(300);
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ const KeyboardHandler = {
|
|||||||
});
|
});
|
||||||
// Also handle scroll (iOS scrolls viewport when keyboard appears)
|
// Also handle scroll (iOS scrolls viewport when keyboard appears)
|
||||||
window.visualViewport.addEventListener('scroll', () => {
|
window.visualViewport.addEventListener('scroll', () => {
|
||||||
this.updateToolbarPosition();
|
this.updateLayoutForKeyboard();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -241,40 +241,74 @@ const KeyboardHandler = {
|
|||||||
this.onKeyboardHide();
|
this.onKeyboardHide();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateToolbarPosition();
|
this.updateLayoutForKeyboard();
|
||||||
this.lastViewportHeight = currentHeight;
|
this.lastViewportHeight = currentHeight;
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Update toolbar position based on visual viewport */
|
/** Update layout when keyboard shows/hides */
|
||||||
updateToolbarPosition() {
|
updateLayoutForKeyboard() {
|
||||||
if (!window.visualViewport) return;
|
if (!window.visualViewport) return;
|
||||||
|
|
||||||
const toolbar = document.querySelector('.toolbar');
|
// Only adjust on mobile
|
||||||
if (!toolbar) return;
|
|
||||||
|
|
||||||
// Only reposition on mobile
|
|
||||||
if (!MobileDetection.isSmallScreen() && !MobileDetection.isMediumScreen()) {
|
if (!MobileDetection.isSmallScreen() && !MobileDetection.isMediumScreen()) {
|
||||||
toolbar.style.transform = '';
|
this.resetLayout();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toolbar = document.querySelector('.toolbar');
|
||||||
|
const accessoryBar = document.querySelector('.keyboard-accessory-bar');
|
||||||
|
const main = document.querySelector('.main');
|
||||||
|
|
||||||
if (this.keyboardVisible) {
|
if (this.keyboardVisible) {
|
||||||
// The toolbar is fixed at bottom: 0 (relative to layout viewport)
|
// Calculate keyboard offset
|
||||||
// We need to move it up to be at the bottom of the visual viewport
|
|
||||||
// Distance from visual viewport bottom to layout viewport bottom:
|
|
||||||
const layoutHeight = window.innerHeight;
|
const layoutHeight = window.innerHeight;
|
||||||
const visualBottom = window.visualViewport.offsetTop + window.visualViewport.height;
|
const visualBottom = window.visualViewport.offsetTop + window.visualViewport.height;
|
||||||
const offsetFromBottom = layoutHeight - visualBottom;
|
const keyboardOffset = layoutHeight - visualBottom;
|
||||||
|
|
||||||
// Move toolbar up by this offset (negative translateY moves up)
|
// Move toolbar up above keyboard
|
||||||
toolbar.style.transform = `translateY(${-offsetFromBottom}px)`;
|
if (toolbar) {
|
||||||
|
toolbar.style.transform = `translateY(${-keyboardOffset}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move accessory bar up (it sits above toolbar)
|
||||||
|
if (accessoryBar) {
|
||||||
|
accessoryBar.style.transform = `translateY(${-keyboardOffset}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shrink main content area so terminal doesn't extend behind keyboard
|
||||||
|
// Account for keyboard height + toolbar height (40px) + accessory bar (44px)
|
||||||
|
if (main) {
|
||||||
|
main.style.paddingBottom = `${keyboardOffset + 94}px`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
this.resetLayout();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Reset layout to normal (no keyboard) */
|
||||||
|
resetLayout() {
|
||||||
|
const toolbar = document.querySelector('.toolbar');
|
||||||
|
const accessoryBar = document.querySelector('.keyboard-accessory-bar');
|
||||||
|
const main = document.querySelector('.main');
|
||||||
|
|
||||||
|
if (toolbar) {
|
||||||
toolbar.style.transform = '';
|
toolbar.style.transform = '';
|
||||||
}
|
}
|
||||||
|
if (accessoryBar) {
|
||||||
|
accessoryBar.style.transform = '';
|
||||||
|
}
|
||||||
|
if (main) {
|
||||||
|
main.style.paddingBottom = '';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Called when keyboard appears */
|
/** Called when keyboard appears */
|
||||||
onKeyboardShow() {
|
onKeyboardShow() {
|
||||||
|
// Show keyboard accessory bar
|
||||||
|
if (typeof KeyboardAccessoryBar !== 'undefined') {
|
||||||
|
KeyboardAccessoryBar.show();
|
||||||
|
}
|
||||||
|
|
||||||
// Scroll active terminal to bottom so input line is visible
|
// Scroll active terminal to bottom so input line is visible
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (typeof app !== 'undefined' && app.activeSessionId) {
|
if (typeof app !== 'undefined' && app.activeSessionId) {
|
||||||
@@ -288,11 +322,12 @@ const KeyboardHandler = {
|
|||||||
|
|
||||||
/** Called when keyboard hides */
|
/** Called when keyboard hides */
|
||||||
onKeyboardHide() {
|
onKeyboardHide() {
|
||||||
// Reset toolbar position
|
// Hide keyboard accessory bar
|
||||||
const toolbar = document.querySelector('.toolbar');
|
if (typeof KeyboardAccessoryBar !== 'undefined') {
|
||||||
if (toolbar) {
|
KeyboardAccessoryBar.hide();
|
||||||
toolbar.style.transform = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.resetLayout();
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Check if element is an input that triggers keyboard (excludes terminal) */
|
/** Check if element is an input that triggers keyboard (excludes terminal) */
|
||||||
@@ -345,6 +380,155 @@ const KeyboardHandler = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Mobile Swipe Handler
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SwipeHandler - Detects horizontal swipes on terminal to switch sessions.
|
||||||
|
* Only active on mobile/touch devices.
|
||||||
|
*/
|
||||||
|
const SwipeHandler = {
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
startTime: 0,
|
||||||
|
minSwipeDistance: 80, // Minimum pixels for a valid swipe
|
||||||
|
maxSwipeTime: 300, // Maximum ms for a swipe gesture
|
||||||
|
maxVerticalDrift: 100, // Max vertical movement allowed
|
||||||
|
|
||||||
|
/** Initialize swipe handling */
|
||||||
|
init() {
|
||||||
|
// Only on touch devices
|
||||||
|
if (!MobileDetection.isTouchDevice()) return;
|
||||||
|
|
||||||
|
const terminal = document.querySelector('.main');
|
||||||
|
if (!terminal) return;
|
||||||
|
|
||||||
|
terminal.addEventListener('touchstart', (e) => this.onTouchStart(e), { passive: true });
|
||||||
|
terminal.addEventListener('touchend', (e) => this.onTouchEnd(e), { passive: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
onTouchStart(e) {
|
||||||
|
if (!e.touches || e.touches.length !== 1) return;
|
||||||
|
this.startX = e.touches[0].clientX;
|
||||||
|
this.startY = e.touches[0].clientY;
|
||||||
|
this.startTime = Date.now();
|
||||||
|
},
|
||||||
|
|
||||||
|
onTouchEnd(e) {
|
||||||
|
if (!e.changedTouches || e.changedTouches.length !== 1) return;
|
||||||
|
|
||||||
|
const endX = e.changedTouches[0].clientX;
|
||||||
|
const endY = e.changedTouches[0].clientY;
|
||||||
|
const elapsed = Date.now() - this.startTime;
|
||||||
|
|
||||||
|
// Check if it's a valid swipe
|
||||||
|
const deltaX = endX - this.startX;
|
||||||
|
const deltaY = Math.abs(endY - this.startY);
|
||||||
|
|
||||||
|
if (elapsed > this.maxSwipeTime) return; // Too slow
|
||||||
|
if (deltaY > this.maxVerticalDrift) return; // Too much vertical movement
|
||||||
|
if (Math.abs(deltaX) < this.minSwipeDistance) return; // Too short
|
||||||
|
|
||||||
|
// Valid swipe detected
|
||||||
|
if (deltaX > 0) {
|
||||||
|
// Swipe right -> previous session
|
||||||
|
if (typeof app !== 'undefined') app.prevSession();
|
||||||
|
} else {
|
||||||
|
// Swipe left -> next session
|
||||||
|
if (typeof app !== 'undefined') app.nextSession();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Mobile Keyboard Accessory Bar
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KeyboardAccessoryBar - Quick action buttons shown above keyboard when typing.
|
||||||
|
*/
|
||||||
|
const KeyboardAccessoryBar = {
|
||||||
|
element: null,
|
||||||
|
|
||||||
|
/** Create and inject the accessory bar */
|
||||||
|
init() {
|
||||||
|
// Only on mobile
|
||||||
|
if (!MobileDetection.isTouchDevice()) return;
|
||||||
|
|
||||||
|
// Create accessory bar element
|
||||||
|
this.element = document.createElement('div');
|
||||||
|
this.element.className = 'keyboard-accessory-bar';
|
||||||
|
this.element.innerHTML = `
|
||||||
|
<button class="accessory-btn" data-action="init" title="/init">/init</button>
|
||||||
|
<button class="accessory-btn" data-action="clear" title="/clear">/clear</button>
|
||||||
|
<button class="accessory-btn" data-action="compact" title="/compact">/compact</button>
|
||||||
|
<button class="accessory-btn accessory-btn-dismiss" data-action="dismiss" title="Dismiss keyboard">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M19 9l-7 7-7-7"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add click handlers
|
||||||
|
this.element.addEventListener('click', (e) => {
|
||||||
|
const btn = e.target.closest('.accessory-btn');
|
||||||
|
if (!btn) return;
|
||||||
|
|
||||||
|
const action = btn.dataset.action;
|
||||||
|
this.handleAction(action);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Insert before toolbar
|
||||||
|
const toolbar = document.querySelector('.toolbar');
|
||||||
|
if (toolbar && toolbar.parentNode) {
|
||||||
|
toolbar.parentNode.insertBefore(this.element, toolbar);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Handle accessory button actions */
|
||||||
|
handleAction(action) {
|
||||||
|
if (typeof app === 'undefined' || !app.activeSessionId) return;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'init':
|
||||||
|
this.confirmAndSend('/init', 'Run /init command?');
|
||||||
|
break;
|
||||||
|
case 'clear':
|
||||||
|
this.confirmAndSend('/clear', 'Clear conversation history?');
|
||||||
|
break;
|
||||||
|
case 'compact':
|
||||||
|
this.confirmAndSend('/compact', 'Compact context?');
|
||||||
|
break;
|
||||||
|
case 'dismiss':
|
||||||
|
// Blur active element to dismiss keyboard
|
||||||
|
document.activeElement?.blur();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Show confirmation dialog before sending command */
|
||||||
|
confirmAndSend(command, message) {
|
||||||
|
if (confirm(message)) {
|
||||||
|
app.sendInput(command + '\r');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Show the accessory bar */
|
||||||
|
show() {
|
||||||
|
if (this.element) {
|
||||||
|
this.element.classList.add('visible');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Hide the accessory bar */
|
||||||
|
hide() {
|
||||||
|
if (this.element) {
|
||||||
|
this.element.classList.remove('visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Accessibility: Focus Trap for Modals
|
// Accessibility: Focus Trap for Modals
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1051,8 +1235,10 @@ class ClaudemanApp {
|
|||||||
init() {
|
init() {
|
||||||
// Initialize mobile detection first (adds device classes to body)
|
// Initialize mobile detection first (adds device classes to body)
|
||||||
MobileDetection.init();
|
MobileDetection.init();
|
||||||
// Initialize keyboard handler for mobile input visibility
|
// Initialize mobile handlers
|
||||||
KeyboardHandler.init();
|
KeyboardHandler.init();
|
||||||
|
SwipeHandler.init();
|
||||||
|
KeyboardAccessoryBar.init();
|
||||||
this.initTerminal();
|
this.initTerminal();
|
||||||
this.loadFontSize();
|
this.loadFontSize();
|
||||||
this.applyHeaderVisibilitySettings();
|
this.applyHeaderVisibilitySettings();
|
||||||
@@ -3515,6 +3701,14 @@ class ClaudemanApp {
|
|||||||
this.selectSession(this.sessionOrder[nextIndex]);
|
this.selectSession(this.sessionOrder[nextIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevSession() {
|
||||||
|
if (this.sessionOrder.length <= 1) return;
|
||||||
|
|
||||||
|
const currentIndex = this.sessionOrder.indexOf(this.activeSessionId);
|
||||||
|
const prevIndex = (currentIndex - 1 + this.sessionOrder.length) % this.sessionOrder.length;
|
||||||
|
this.selectSession(this.sessionOrder[prevIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Navigation ==========
|
// ========== Navigation ==========
|
||||||
|
|
||||||
goHome() {
|
goHome() {
|
||||||
@@ -7420,6 +7614,20 @@ class ClaudemanApp {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send input to the active session.
|
||||||
|
* @param {string} input - Text to send (include \r for Enter)
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async sendInput(input) {
|
||||||
|
if (!this.activeSessionId) return;
|
||||||
|
await fetch(`/api/sessions/${this.activeSessionId}/input`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ input, useScreen: true })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Timer ==========
|
// ========== Timer ==========
|
||||||
|
|
||||||
showTimer() {
|
showTimer() {
|
||||||
|
|||||||
@@ -8174,35 +8174,107 @@ kbd {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Case selector - fills available space on left */
|
/* Hide case selector on mobile - simplified toolbar */
|
||||||
.case-select-group {
|
.case-select-group {
|
||||||
flex: 1;
|
display: none !important;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
order: 1; /* Left position */
|
|
||||||
min-width: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-select {
|
/* Simplified toolbar layout - just Run Claude and Run Shell */
|
||||||
font-size: 0.65rem !important;
|
.toolbar-left .toolbar-group:first-child {
|
||||||
padding: 0 1.4rem 0 0.4rem !important;
|
width: 100%;
|
||||||
min-height: 26px !important;
|
justify-content: center;
|
||||||
max-height: 26px !important;
|
gap: 12px;
|
||||||
height: 26px !important;
|
}
|
||||||
line-height: 26px;
|
|
||||||
border-radius: 4px;
|
.btn-toolbar.btn-claude {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-width: fit-content;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-toolbar.btn-shell {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-width: fit-content;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
background: #1a1a1a;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
color: #e5e5e5;
|
padding: 6px 8px;
|
||||||
flex: 1;
|
padding-left: calc(8px + var(--safe-area-left));
|
||||||
min-width: 0;
|
padding-right: calc(8px + var(--safe-area-right));
|
||||||
text-overflow: ellipsis;
|
gap: 8px;
|
||||||
overflow: hidden;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 51;
|
||||||
|
transition: transform 0.15s ease-out;
|
||||||
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add case button - compact */
|
.keyboard-accessory-bar.visible {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessory-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #2a2a2a;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #e5e5e5;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessory-btn:active {
|
||||||
|
background: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessory-btn svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accessory-btn-dismiss {
|
||||||
|
margin-left: auto;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add case button - compact (keeping for when shown via menu) */
|
||||||
.btn-case-add {
|
.btn-case-add {
|
||||||
|
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 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
min-width: 26px !important;
|
||||||
max-width: 26px !important;
|
max-width: 26px !important;
|
||||||
width: 26px !important;
|
width: 26px !important;
|
||||||
|
|||||||