feat: make the web UI usable on phones (360-768px)

Responsive pass limited to CSS plus small markup hooks, so desktop (>900px) is unchanged.

- shell: static header with 2-row controls, sticky scrollable tab bar with fade + snap, 40px touch targets, 16px inputs, full-width toasts with safe-area
- grids collapse to one column; dashboard counters become 3 compact tiles and analyst stats 2x2
- equipment: full-width subtabs/filters, 3-up tiles, inspector becomes a bottom sheet on phones
- inventory: 2 columns fit without side scrolling, full-width quantity inputs
- characters: full-width search, 2-column avatar grid with stacked portrait
- dialogs: dvh-capped bottom sheets, two-column light cone and avatar pickers
- json/monster/settings/console form and scroll fixes
- compact one-row header for landscape phones (844x390 174px -> 108px)
- i18n: localize inventory view and relic-set search aria labels/placeholder
- bump styles.css 2.33, item-catalog.css 3, app.js 3.27, i18n.js 2.22
This commit is contained in:
CAtlas0784 committed 2026-11-02 10:43:07 +07:00
1 parent 45d8414e60
commit 992875348d
9 files changed
+497 -14

No files matched your search

+4
View File
@@ -107,6 +107,10 @@
@media (max-width: 540px) {
.item-catalog-grid { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.item-catalog-toolbar .form-select { flex: 1 1 100%; }
/* Keep supporting text legible on phones. */
.item-catalog-name { font-size: .88rem; }
.item-catalog-details { font-size: .72rem; }
.item-catalog-support { font-size: .68rem; }
}
.inventory-mode-switch {
display: flex;
+471
View File
@@ -4499,3 +4499,474 @@ input:checked + .slider:before {
.substat-option:hover:not(:disabled), .substat-option:focus-visible { background: #263d59; }
.substat-option:disabled { opacity: .4; cursor: not-allowed; }
@media (max-width: 600px) { .add-relic-set-grid { grid-template-columns: repeat(auto-fill, minmax(105px, 1fr)); max-height: 230px; } }
/* ==========================================================================
Mobile / responsive pass
Breakpoints: 900 (tablet), 768 (large phone), 640 (phone), 480 (small
phone), 360 (compact). Desktop layout above 900px is left untouched.
========================================================================== */
/* --- Shell: header, tab bar, page padding ------------------------------- */
@media (max-width: 768px) {
/* The tab bar becomes the sticky element so phones keep vertical space
for content instead of pinning a tall header. */
.app-header {
position: static;
flex-wrap: wrap;
gap: 0.6rem;
padding: 0.6rem 0.85rem;
}
.brand { min-width: 0; }
.brand-logo-img { width: 36px; height: 36px; }
.brand-title { font-size: 1.1rem; }
.brand-subtitle { font-size: 0.62rem; }
.header-controls {
width: 100%;
align-items: center;
gap: 0.45rem;
}
.server-mode-badge,
.connection-pill {
min-height: 36px;
padding: 0.32rem 0.6rem;
font-size: 0.75rem;
}
/* Two tidy rows on phones: [server mode] [status] / [language] [refresh] */
.server-mode-badge { order: 1; }
.connection-pill { order: 2; }
#lang-select {
order: 3;
width: auto;
max-width: 46vw;
min-height: 36px;
padding: 0.3rem 0.6rem;
}
#btn-quick-ping { order: 4; }
.nav-tabs {
top: 0;
padding: 0.4rem 0.55rem;
gap: 0.25rem;
scroll-snap-type: x proximity;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
mask-image: linear-gradient(90deg, transparent 0, #000 12px, #000 calc(100% - 12px), transparent 100%);
-webkit-mask-image: linear-gradient(90deg, transparent 0, #000 12px, #000 calc(100% - 12px), transparent 100%);
}
.nav-tabs::-webkit-scrollbar { display: none; }
.tab-btn {
min-height: 40px;
gap: 0.35rem;
padding: 0.5rem 0.75rem;
font-size: 0.8rem;
scroll-snap-align: start;
}
.main-container { padding: 0.9rem 0.8rem 1.4rem; }
/* Touch targets */
.btn { min-height: 40px; }
.btn-sm { min-height: 36px; }
/* iOS zooms the page when a focused field is smaller than 16px. */
input,
select,
textarea { font-size: 16px; }
.form-input,
.form-select {
min-height: 44px;
padding: 0.6rem 0.75rem;
font-size: 16px;
}
.form-label { font-size: 0.72rem; }
/* Toasts: keep clear of screen edges and the home indicator. */
.toast-container {
left: 0.75rem;
right: 0.75rem;
bottom: calc(0.9rem + env(safe-area-inset-bottom, 0px));
align-items: stretch;
}
.toast {
width: 100%;
min-width: 0;
max-width: none;
}
}
@media (max-width: 480px) {
.brand-subtitle { display: none; }
.header-controls { gap: 0.4rem; }
.tab-btn {
padding: 0.45rem 0.65rem;
font-size: 0.76rem;
}
}
/* --- Layout grids ------------------------------------------------------- */
/* The desktop grids use fixed minmax floors (320/280/250px) which no longer
fit phone content boxes; allow them to collapse instead of overflowing.
Page-level grids collapse to one column, while field grids inside dialogs
may still show two compact columns. */
@media (max-width: 640px) {
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr)); }
.modal-content .grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr)); }
/* Dashboard counters: three compact tiles instead of three full-width cards. */
#tab-dashboard > .grid-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.5rem;
}
#tab-dashboard > .grid-3 .stat-box {
padding: 0.65rem 0.6rem;
gap: 0.2rem;
}
#tab-dashboard > .grid-3 .stat-number { font-size: 1.55rem; }
#tab-dashboard > .grid-3 .stat-label {
font-size: 0.66rem;
line-height: 1.25;
letter-spacing: 0.03em;
}
}
/* --- Dialogs as phone bottom sheets ------------------------------------- */
@media (max-width: 640px) {
.modal-overlay {
padding: 0;
align-items: flex-end;
}
.modal-content {
width: 100%;
max-width: 100%;
max-height: calc(100dvh - 0.5rem);
padding: 1rem 0.9rem calc(1rem + env(safe-area-inset-bottom, 0px));
border-width: 1px 0 0;
border-radius: 18px 18px 0 0;
}
.modal-content .modal-header {
margin-bottom: 0.9rem;
padding-bottom: 0.6rem;
}
.assign-picker-modal {
width: 100% !important;
max-width: 100% !important;
max-height: calc(100dvh - 0.5rem) !important;
border-radius: 18px 18px 0 0 !important;
}
.modal-content .grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr)); }
}
/* --- Equipment: tiles, subtabs, inspector ------------------------------- */
@media (max-width: 900px) {
/* The side inspector was a 320px sticky column; on narrow screens it moves
under the tile grid instead of squeezing it to nothing. */
.eq-layout { flex-wrap: wrap; }
#eq-inspector-card {
width: 100% !important;
position: static !important;
}
.eq-tiles-grid { max-height: 50vh; }
}
@media (max-width: 640px) {
/* The server-mode <select> shrinks to the text height; give it a real
touch target inside its pill. */
#server-mode-select {
min-height: 32px;
padding-block: 0.2rem;
}
/* Subtabs and filters each take their own row. */
.eq-toolbar-subtabs { width: 100%; }
.eq-toolbar-subtabs .eq-subtab-btn {
flex: 1 1 0;
min-width: 0;
justify-content: center;
white-space: nowrap;
overflow: hidden;
}
.eq-toolbar-subtabs .eq-subtab-btn > span:first-child {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
#eq-search {
min-width: 0 !important;
flex: 0 0 100% !important;
}
.eq-toolbar-filters {
flex: 0 0 100% !important;
max-width: none !important;
}
#eq-filter-rarity,
#eq-filter-equipped,
#eq-filter-extra {
max-width: none !important;
min-width: 0;
flex: 1 1 44%;
}
.eq-tiles-grid {
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
gap: 0.55rem;
}
.eq-tile-name-snippet { max-width: none; }
/* Phones: the inspector becomes a bottom sheet so details stay in reach
right after tapping a tile (it already ships its own close button). */
#eq-inspector-card {
position: fixed !important;
top: auto !important;
left: 0;
right: 0;
bottom: 0;
width: 100% !important;
max-height: 78dvh;
overflow-y: auto;
overscroll-behavior: contain;
z-index: 900;
border-width: 1px 0 0;
border-radius: 16px 16px 0 0;
padding-bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px));
box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.7);
}
}
/* --- Inventory: keep the two columns readable without side scrolling ---- */
@media (max-width: 640px) {
#inventory-manage-panel table { min-width: 100% !important; }
#inventory-manage-panel th,
#inventory-manage-panel td { padding-inline: 0.7rem !important; }
.inventory-count-input {
width: 100% !important;
max-width: 100% !important;
}
#inventory-search {
min-width: 0 !important;
flex: 0 0 100% !important;
}
}
/* --- Characters: filters and avatar grid -------------------------------- */
@media (max-width: 640px) {
#avatar-search {
min-width: 0 !important;
flex: 0 0 100% !important;
}
#avatar-filter-rarity,
#avatar-filter-element,
#avatar-filter-path {
max-width: none !important;
flex: 1 1 44%;
min-width: 0;
}
#avatars-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.65rem;
}
/* Two columns are only ~150px wide, so stack the portrait above the name
instead of squeezing the text next to it. */
#avatars-grid .avatar-card {
padding: 0.7rem;
min-height: 0;
}
#avatars-grid .avatar-header-info {
flex-direction: column;
align-items: flex-start;
gap: 0.45rem;
margin-right: 0;
padding-top: 0.3rem;
}
#avatars-grid .avatar-image-container {
width: 52px;
height: 52px;
}
#avatars-grid .avatar-name {
font-size: 0.82rem;
line-height: 1.3;
overflow-wrap: anywhere;
}
#avatars-grid .avatar-subinfo { margin-top: 0.2rem; }
#avatars-grid .avatar-subinfo .icon-badge { width: 22px; height: 22px; }
#avatars-grid .avatar-card-footer {
margin-top: 0.55rem;
padding-top: 0.5rem;
}
}
/* --- Modals: phone tuning ---------------------------------------------- */
@media (max-width: 640px) {
/* Several modals carry an inline 92vh cap; dvh keeps them above the
mobile URL bar / keyboard. */
.modal-content { max-height: calc(100dvh - 0.5rem) !important; }
/* The avatar detail modal is wider/taller by an id-scoped desktop rule. */
#modal-avatar-detail .modal-content {
width: 100% !important;
max-width: 100% !important;
max-height: calc(100dvh - 0.5rem) !important;
}
/* Its stat grid has an inline 320px floor that is wider than the sheet. */
#modal-avatar-detail .grid-2 {
grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)) !important;
}
/* Light-cone pickers: two artwork columns instead of one. */
#add-lc-visual-grid {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 140px), 1fr)) !important;
max-height: none !important;
}
#lc-picker-list {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 150px), 1fr)) !important;
}
/* Avatar / relic-slot pickers: two compact tiles per row. */
.picker-grid-container {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 160px), 1fr));
}
}
/* --- JSON editor -------------------------------------------------------- */
@media (max-width: 640px) {
#tab-json .card-title > div { flex-wrap: wrap; }
.json-textarea {
height: 60vh;
min-height: 240px;
font-size: 16px;
line-height: 1.5;
}
.json-editor-wrapper > div:last-child {
flex-wrap: wrap;
gap: 0.35rem;
}
}
/* --- Monster settings / analyst / console ------------------------------ */
@media (max-width: 640px) {
.monster-aa-round-control { grid-template-columns: 1fr; gap: 0.85rem; }
.monster-settings-tabs {
scroll-snap-type: x proximity;
scrollbar-width: none;
mask-image: linear-gradient(90deg, transparent 0, #000 12px, #000 calc(100% - 12px), transparent 100%);
-webkit-mask-image: linear-gradient(90deg, transparent 0, #000 12px, #000 calc(100% - 12px), transparent 100%);
}
.monster-settings-tabs::-webkit-scrollbar { display: none; }
.monster-settings-tab { scroll-snap-align: start; }
/* Analyst: stat tiles read as 2x2; the character cards already stack. */
#tab-damage-analyst > .grid-4 {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.65rem;
}
#tab-damage-analyst > .grid-4 .card { padding: 0.85rem !important; }
#tab-damage-analyst > .grid-4 .card > div:nth-child(2) {
font-size: 1.4rem !important;
overflow-wrap: anywhere;
}
.console-terminal-log {
height: 190px;
max-height: 45vh;
}
}
/* --- Settings rows ------------------------------------------------------ */
@media (max-width: 768px) {
/* Long server paths must break, and toggle rows must keep a full switch. */
#tab-settings .card div { overflow-wrap: anywhere; }
#tab-settings .switch { flex: 0 0 auto; }
}
/* --- Landscape phones --------------------------------------------------- */
/* Wide but short screens would otherwise stack a 100px header and a 73px tab
bar, leaving little room for the page. */
@media (max-height: 500px) and (orientation: landscape) and (min-width: 700px) {
.app-header {
position: static;
padding: 0.4rem 0.9rem;
flex-wrap: nowrap;
gap: 0.75rem;
}
.brand-logo-img { width: 30px; height: 30px; }
.brand-title { font-size: 0.95rem; }
.brand-subtitle { display: none; }
.header-controls {
width: auto;
flex-wrap: nowrap;
gap: 0.4rem;
}
.server-mode-badge,
.connection-pill {
min-height: 32px;
padding: 0.22rem 0.5rem;
font-size: 0.72rem;
}
#server-mode-select { min-height: 28px; }
#lang-select {
width: auto;
min-height: 32px;
padding: 0.2rem 0.4rem;
font-size: 0.72rem;
}
#btn-quick-ping {
min-height: 32px;
padding: 0.25rem 0.5rem;
font-size: 0.72rem;
}
.nav-tabs {
top: 0;
padding: 0.25rem 0.6rem;
}
.tab-btn {
min-height: 34px;
padding: 0.3rem 0.65rem;
font-size: 0.75rem;
}
.main-container { padding: 0.7rem 0.8rem 1.2rem; }
}