feat(ui): mobile mesh tabs, AIUI-style audio player, cloud grid + map fixes

UI (this session):
- Global audio player now scales the whole interface into the space above it
  on desktop (sidebar + main) and docks directly above the tab bar on mobile;
  it stays visible while navigating.
- Mesh mobile redesign: floating Chat / BTC / Dead Man / AI / Map tab strip
  with a single fixed, internally-scrolling pane (page no longer scrolls);
  tabs hide while a conversation is open; floating back button; collapsible
  Device panel (starts collapsed); keyboard-aware conversation sizing via
  VisualViewport so the chat sits just above the keyboard.
- Cloud file grid: uniform 4/3 card heights (folders + images match).
- Swipe left/right switches tabs on the Apps and Web5 screens.
- Map tool fills its pane (no bottom gap); fix skewed Share Location toggle
  on mobile (global min-height rule was deforming the switch).
- Trim redundant helper copy from the mesh AI tab.

Also bundles pre-existing in-progress work that was already in the tree:
mesh listener/session + wallet + container + bitcoin-status backend changes,
docker UI updates, and assorted other UI tweaks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-19 09:52:26 -04:00
co-authored by Claude Opus 4.8
parent c4855526fe
commit 1bce694ebb
37 changed files with 1260 additions and 208 deletions
+38 -10
View File
@@ -130,19 +130,45 @@ select:focus-visible {
}
}
/* Scroll container bottom padding — desktop breathing room */
/* Height of the global audio player bar — 0 unless it is visible. Set on
<html> by GlobalAudioPlayer.vue. Scroll containers add it to their bottom
padding so the fixed player pushes content up instead of covering it. */
:root {
--audio-player-height: 0px;
}
/* Scroll container bottom padding — desktop breathing room. (On desktop the
audio player instead shrinks the whole #main-content area — see the
html.audio-active rule below — so no player offset is added here.) */
.mobile-scroll-pad,
.mobile-scroll-pad-back {
padding-bottom: 6rem;
}
/* Audio player docked: shrink the whole interface into the space above it so
the entire view scales up (like the AIUI iframe) instead of just gaining
scroll padding. On desktop the player spans full width and BOTH the sidebar
and the main content scale into the reduced height above it. Mobile keeps the
tab-bar + player handled via .mobile-scroll-pad padding. */
@media (min-width: 768px) {
html.audio-active .dashboard-view {
height: calc(100dvh - var(--audio-player-height, 0px));
min-height: 0;
overflow: hidden;
}
/* Sidebar uses h-screen (100vh) — pin it to the reduced container height. */
html.audio-active .dashboard-view [data-controller-zone="sidebar"] {
height: 100%;
}
}
/* Mobile: override with tab bar clearance */
@media (max-width: 767px) {
.mobile-scroll-pad {
padding-bottom: calc(var(--mobile-tab-bar-height, 88px) + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + 16px);
padding-bottom: calc(var(--mobile-tab-bar-height, 88px) + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + var(--audio-player-height, 0px) + 16px);
}
.mobile-scroll-pad-back {
padding-bottom: calc(var(--mobile-tab-bar-height, 88px) + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + 64px);
padding-bottom: calc(var(--mobile-tab-bar-height, 88px) + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + var(--audio-player-height, 0px) + 64px);
}
/* Safe area top padding for all mobile content views.
@@ -174,11 +200,11 @@ select:focus-visible {
}
.mobile-scroll-pad {
padding-bottom: calc(var(--mobile-tab-bar-height, 88px) + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + 16px);
padding-bottom: calc(var(--mobile-tab-bar-height, 88px) + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + var(--audio-player-height, 0px) + 16px);
}
.mobile-scroll-pad-back {
padding-bottom: calc(var(--mobile-tab-bar-height, 88px) + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + 64px);
padding-bottom: calc(var(--mobile-tab-bar-height, 88px) + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + var(--audio-player-height, 0px) + 64px);
}
.mobile-safe-top {
@@ -525,6 +551,7 @@ input[type="radio"]:active + * {
position: relative;
}
/* On mobile browsers, cap chat height to the dynamic viewport to prevent
content extending behind browser chrome (address bar / toolbar). */
@media (max-width: 767px) {
@@ -555,8 +582,8 @@ input[type="radio"]:active + * {
context) stay above the tab bar instead of sliding underneath it. */
@media (max-width: 767px) {
.chat-iframe-mobile {
height: calc(100vh - var(--mobile-tab-bar-height, 72px) - var(--safe-area-top, env(safe-area-inset-top, 0px)) - 16px) !important;
height: calc(100dvh - var(--mobile-tab-bar-height, 72px) - var(--safe-area-top, env(safe-area-inset-top, 0px)) - 16px) !important;
height: calc(100vh - var(--mobile-tab-bar-height, 72px) - var(--safe-area-top, env(safe-area-inset-top, 0px)) - var(--audio-player-height, 0px) - 16px) !important;
height: calc(100dvh - var(--mobile-tab-bar-height, 72px) - var(--safe-area-top, env(safe-area-inset-top, 0px)) - var(--audio-player-height, 0px) - 16px) !important;
flex: none;
}
}
@@ -1926,18 +1953,19 @@ html.modal-scroll-locked .dashboard-scroll-panel {
background: rgba(0, 0, 0, 0.4);
}
/* ── Mobile floating back/close button (always 8px above tab bar) ──── */
/* ── Mobile floating back/close button (always 8px above tab bar — and above
the audio player too when it is showing, so it never hides behind it) ── */
.mobile-back-btn {
position: fixed;
left: 1rem;
right: 1rem;
bottom: calc(var(--mobile-tab-bar-height, 72px) + 8px);
bottom: calc(var(--mobile-tab-bar-height, 72px) + var(--audio-player-height, 0px) + 8px);
z-index: 40;
filter: drop-shadow(0 10px 25px rgba(0, 0, 0, 0.5));
}
.mobile-filter-btn {
bottom: calc(var(--mobile-tab-bar-height, 72px) + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + 12px);
bottom: calc(var(--mobile-tab-bar-height, 72px) + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + var(--audio-player-height, 0px) + 12px);
filter: drop-shadow(0 10px 25px rgba(0, 0, 0, 0.5));
}