feat: redesign portrait PWA as mobile app UI with thumb-zone transport, bottom-sheet modals, and floating mini-player

This commit is contained in:
Jonathan Sykes
2026-07-02 22:38:14 +08:00
parent 722457312c
commit 2af2eebacc

View File

@@ -1878,6 +1878,253 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); }
.hero-tagline { font-size: 13px; }
}
/* ============================================================================
* PORTRAIT PWA — mobile app design refresh
*
* Same media query as the structural block above; being later in the cascade,
* these rules win ties. Scope is intentionally identical so desktop, browser
* tabs, and landscape are never affected. Design goals (mobile-app UI):
* • 44px minimum tap targets, thumb-zone transport controls
* • music-app control deck: centered shuffle/prev/play/next/repeat row,
* secondary tools on a second row; volume slider hidden (hardware keys)
* • bottom-sheet modals, floating mini-player card, nav active pill
* • 16px input font to stop iOS focus-zoom
* ========================================================================== */
@media (display-mode: standalone) and (orientation: portrait) {
/* --- Global touch polish ---------------------------------------------- */
::-webkit-scrollbar { width: 0; height: 0; }
button, .card, .nav-item, .playlist-item { -webkit-tap-highlight-color: transparent; }
/* --- Topbar: app-bar with pill search --------------------------------- */
.sidebar-toggle {
width: 44px;
height: 44px;
border-radius: 14px;
}
.search-form { min-width: 0; } /* flex item: allow shrinking to the viewport */
#searchInput {
height: 44px;
min-width: 0; /* let the pill shrink below intrinsic input width */
border-radius: 22px;
padding: 0 18px;
font-size: 16px; /* ≥16px prevents iOS focus-zoom */
}
.search-btn {
height: 44px;
border-radius: 22px;
padding: 0 18px;
}
/* --- Player pane ------------------------------------------------------- */
.player-pane {
padding: 12px calc(12px + env(safe-area-inset-right)) 12px
calc(12px + env(safe-area-inset-left));
}
.player-stage { border-radius: 16px; }
/* --- Control deck: two-row music-app transport ------------------------- */
.controls {
margin-top: 12px;
border-radius: 18px;
padding: 14px 14px 16px;
}
.seek-row { gap: 10px; }
/* Fatter seek track + thumb for touch */
.seek { height: 6px; }
.seek::-webkit-slider-thumb { width: 18px; height: 18px; }
.btn-row {
justify-content: center;
gap: 10px 8px;
margin-top: 14px;
}
/* Forced line break between transport row (order ≤ 5) and tool row (≥ 7) */
.btn-row::before { content: ""; width: 100%; order: 6; height: 0; }
/* Row A — transport, centered in the thumb zone */
#shuffleBtn { order: 1; }
#prevBtn { order: 2; }
#playBtn { order: 3; }
#nextBtn { order: 4; }
#repeatBtn { order: 5; }
#prevBtn, #nextBtn, #shuffleBtn, #repeatBtn {
background: transparent;
border-color: transparent;
width: 48px;
height: 48px;
border-radius: 50%;
}
#prevBtn, #nextBtn { font-size: 20px; }
#shuffleBtn, #repeatBtn { font-size: 17px; color: var(--text-2); }
#shuffleBtn.active, #repeatBtn.active {
color: var(--accent-bright);
background: rgba(255, 75, 50, 0.16);
border-color: transparent;
box-shadow: none;
}
#playBtn {
width: 64px;
height: 64px;
border-radius: 50%;
font-size: 24px;
margin: 0 6px;
}
/* Row B — secondary tools, uniform 44px targets (sizes only: colors and
.active/.sleep-active states keep their class-based styling) */
#abABtn, #abBBtn, #abClearBtn, #muteBtn, #loopBtn, #sleepTimerBtn, #fsBtn {
order: 7;
min-width: 44px;
height: 44px;
border-radius: 13px;
}
.vol { order: 7; }
.vol input { display: none; } /* hardware volume keys on mobile */
.btn-row .spacer { display: none; }
.btn-row .sel { order: 8; }
.sel select {
height: 44px;
border-radius: 13px;
padding: 0 10px;
}
/* --- Now playing meta: full-width action grid --------------------------- */
.now-meta { margin-top: 16px; }
.np-title { font-size: 18px; }
.np-actions {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
width: 100%;
}
.np-btn {
padding: 12px 8px;
text-align: center;
border-radius: 13px;
}
/* --- Up next / related: comfier touch rows ------------------------------ */
.upnext { border-radius: 18px; }
.upnext-item { padding: 8px; border-radius: 12px; }
.upnext-item img { width: 96px; border-radius: 8px; }
.related-panel { border-radius: 18px; }
.related-item { padding: 8px; border-radius: 12px; }
.related-item img { width: 96px; border-radius: 8px; }
/* --- List pane: large title, touch-sized rows --------------------------- */
.list-header { padding: 18px 16px 10px; }
.list-header h2 { font-size: 24px; letter-spacing: -0.02em; }
.list-actions button { padding: 9px 13px; border-radius: 10px; }
.list-filter-bar input {
padding: 11px 14px;
border-radius: 13px;
font-size: 16px;
}
.batch-btn { padding: 10px 14px; border-radius: 10px; }
.cards { gap: 8px; padding-left: 12px; padding-right: 12px; }
.card { padding: 10px; gap: 12px; border-radius: 16px; }
.thumb { border-radius: 12px; }
.card-title { font-size: 14px; }
.card-channel { margin-top: 6px; }
/* No hover on touch — keep row actions always visible and finger-sized */
.card-menu { width: 38px; height: 38px; border-radius: 12px; opacity: 1; }
.card-del { opacity: 1; width: 36px; height: 36px; border-radius: 11px; }
/* --- Settings: roomier rows --------------------------------------------- */
.set-group { border-radius: 18px; }
.set-row { padding: 14px 0; }
.set-row input[type="checkbox"] { width: 22px; height: 22px; }
.set-select { padding: 10px 12px; border-radius: 11px; }
/* --- Sidebar drawer: rounded sheet edge --------------------------------- */
.sidebar {
border-right: none;
border-radius: 0 22px 22px 0;
}
.nav-item { padding: 13px 14px; font-size: 15px; }
.playlist-item { padding: 12px 14px; }
.switch { padding: 12px 10px; }
/* --- Bottom nav: active pill indicator ---------------------------------- */
.bottom-nav-btn { padding: 8px 4px 10px; gap: 4px; }
.bottom-nav-icon {
font-size: 21px;
padding: 4px 16px;
border-radius: 16px;
transition: background 0.2s var(--ease), transform 0.18s var(--ease);
}
.bottom-nav-btn.active .bottom-nav-icon {
background: rgba(255, 75, 50, 0.16);
transform: none;
}
.bottom-nav-badge { top: 4px; margin-left: 10px; }
/* --- Mini player: floating card above the nav --------------------------- */
.mini-bar {
left: 10px;
right: 10px;
bottom: calc(72px + env(safe-area-inset-bottom));
border-radius: 16px;
overflow: hidden;
border: 1px solid var(--line);
box-shadow: 0 12px 34px -10px rgba(0, 0, 0, 0.65);
}
.mini-bar-inner {
border-top: none;
padding: 10px 6px 10px 14px;
}
.mini-btn { width: 40px; height: 40px; border-radius: 12px; }
.mini-close { width: 36px; height: 36px; }
/* List pane clearance for taller nav + floating mini-bar */
.list-pane { padding-bottom: calc(152px + env(safe-area-inset-bottom)); }
/* --- Toasts: clear the floating mini-bar -------------------------------- */
.toast-container { bottom: calc(148px + env(safe-area-inset-bottom)); }
.toast { white-space: normal; max-width: calc(100vw - 32px); text-align: center; }
/* --- Modals: bottom sheet ----------------------------------------------- */
.modal-backdrop {
align-items: flex-end;
padding: 0;
z-index: 400; /* above mini-bar (300) and bottom-nav (310) */
}
.modal {
width: 100%;
max-width: 100%;
border-radius: 22px 22px 0 0;
border-left: none;
border-right: none;
border-bottom: none;
padding: 12px 20px calc(24px + env(safe-area-inset-bottom));
animation: sheetUp 0.3s var(--ease);
}
.modal::before {
content: "";
display: block;
width: 40px;
height: 4px;
border-radius: 2px;
background: var(--line);
margin: 0 auto 16px;
}
.modal input[type="text"] { font-size: 16px; padding: 13px 15px; border-radius: 13px; }
.modal-list button { padding: 14px 15px; border-radius: 13px; }
.modal-actions { margin-top: 22px; }
.modal-actions .btn { padding: 13px 20px; border-radius: 13px; }
/* --- Shortcut overlay: fit narrow screens ------------------------------- */
.shortcut-panel { min-width: 0; width: 100%; max-width: 480px; }
}
@keyframes sheetUp {
from { opacity: 0; transform: translateY(40%); }
to { opacity: 1; transform: none; }
}
/* ============================================================================
* PORTRAIT PWA — JS-class mirror (.portrait-pwa on .app)
*