diff --git a/web/src/components/BattlePhase.tsx b/web/src/components/BattlePhase.tsx index ace7d34..00cf400 100644 --- a/web/src/components/BattlePhase.tsx +++ b/web/src/components/BattlePhase.tsx @@ -592,24 +592,17 @@ export function BattlePhase({ view, send, step, setStep }: Props) { > Skip ⏭ -
- - ⏱ - - {SPEED_OPTIONS.map((s) => ( - - ))} -
+ diff --git a/web/src/styles.css b/web/src/styles.css index 7e57cf3..6a44ff5 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -1895,32 +1895,13 @@ h3 { gap: 18px; } -/* Speed picker in the playback controls. */ -.battle-speed { - display: flex; - align-items: center; - gap: 2px; +/* Playback-speed toggle: one button that cycles through the speed options on + each press. A fixed min-width keeps the controls row from reflowing as the + label swaps between 0.5× / 1× / 1.5× / 2× / 3×. */ +.battle-speed-toggle { margin-left: 4px; - padding-left: 6px; - border-left: 1px solid rgba(253, 243, 220, 0.18); -} - -.battle-speed-label { - font-size: 0.9rem; - opacity: 0.7; - margin-right: 2px; -} - -.battle-speed-btn { - min-width: 2.6em; - padding: 3px 5px; - font-size: 0.78rem; -} - -.battle-speed-btn.is-active { - background: var(--gold); - color: #2a1d0c; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25); + min-width: 3.6em; + font-variant-numeric: tabular-nums; } .battle-header { @@ -2817,18 +2798,314 @@ h3 { background: rgba(217, 74, 56, 0.92); } +/* ============================================================ + Phones, portrait — fit the whole game on one screen. + + A modern smartphone held upright (≤ 600px wide) locks the app to the + viewport height so the page itself never scrolls: the topbar is a slim + fixed rail and the play area fills the rest. Cards, trays, and type all + shrink so a round of shopping or a battle reads at a glance. Card rows + scroll sideways when a full six-pet deck outgrows the width, so the + vertical layout stays put. The one screen allowed to grow past the + viewport is Arrange — you can field a lot of pets, so it scrolls. + ============================================================ */ @media (max-width: 600px) { + /* --- app shell: lock to the viewport; only the play area scrolls --- */ + html, + body, + #root { + height: 100%; + min-height: 0; + } + body { + overflow: hidden; + } + .table { + height: 100vh; /* fallback for browsers without dvh */ + height: 100dvh; + min-height: 0; + overflow: hidden; + } + .topbar { + flex-shrink: 0; + } + .table-body { + flex: 1; + min-height: 0; + overflow: hidden; + /* Stretch the play field to the body's full height (overriding the base + flex-start) so its own overflow-y can scroll — otherwise the column is + only as tall as its content and anything past the fold (e.g. the + end-of-battle "Next round" button) gets clipped instead of scrolled. */ + align-items: stretch; + } + /* The play field fills the space under the rail and scrolls internally when a + phase runs long — the end-of-battle result, or an Arrange line with lots of + pets. Everything else is sized to fit, so the scrollbar rarely appears. */ + .table-main { + flex: 1; + min-height: 0; + max-width: none; + padding: 10px 10px 14px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } + + /* --- topbar: a slim rail --- */ + .topbar { + gap: 5px 8px; + padding: 5px 9px; + } + .topbar-brand { + font-size: 1.05rem; + } + /* The wordmark is redundant next to the paw on a phone — drop it to buy width + so the rail packs onto fewer rows. */ + .topbar-brand span { + display: none; + } + /* Let the tags pack tight instead of stretching across a whole row. */ + .topbar-players { + flex: 0 1 auto; + gap: 6px; + } + .topbar-round { + font-size: 0.82rem; + } + .topbar-player { + padding: 3px 9px; + font-size: 0.8rem; + gap: 5px; + } + .topbar-code { + font-size: 0.8rem; + letter-spacing: 0.18em; + padding: 3px 7px 3px calc(7px + 0.18em); + } + .chip { + font-size: 0.76rem; + } + + /* --- cards: printed small --- */ .card { - width: 92px; - height: 128px; + width: 78px; + height: 108px; + padding: 4px; + gap: 3px; } .card-lg { - width: 104px; - height: 146px; + width: 82px; + height: 114px; + } + .card-sm { + width: 60px; + height: 82px; + } + .card-power { + width: 23px; + height: 23px; + font-size: 0.72rem; + } + .card-lg .card-power { + width: 25px; + height: 25px; + font-size: 0.78rem; + } + .card-name { + font-size: 0.62rem; + } + .card-lg .card-name { + font-size: 0.66rem; + } + .card-effect { + font-size: 0.48rem; + } + .card-lg .card-effect { + font-size: 0.5rem; + } + .card-art { + font-size: 1.7rem; + } + .card-lg .card-art { + font-size: 1.85rem; + } + /* A lift on tap would poke out of a sideways-scrolling strip and trip its + scrollbar; keep the feedback small on touch. */ + .card.is-clickable:hover { + transform: translateY(-3px); + } + + /* --- shop: compact trays, one horizontal row each --- */ + .shop { + gap: 10px; + } + .shop-status { + font-size: 0.95rem; + min-height: 1.3em; + } + .shop-coins { + min-height: 26px; + margin-bottom: 0; + gap: 4px; + } + .coin-disc { + font-size: 1.35rem; + } + .section-label { + font-size: 0.82rem; + margin-bottom: 6px; + } + .section-label .muted { + font-size: 0.72rem; + } + .shop-row-wrap, + .deck-wrap { + padding: 9px 10px 11px; + border-radius: 14px; + } + /* A full six-pet deck (or the shop row on a very narrow phone) scrolls + sideways; each tray keeps its height so nothing else shifts. A row that + isn't wide enough to scroll stays centered. */ + .shop-row, + .deck-row { + flex-wrap: nowrap; + justify-content: flex-start; + gap: 8px; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + padding-bottom: 2px; + scrollbar-width: thin; + } + .shop-row { + justify-content: center; + } + .hint { + font-size: 0.76rem; + } + .actions { + gap: 8px; + } + .actions .btn { + padding: 9px 13px; + font-size: 0.86rem; + } + + /* --- avocado / trumpet / mana trays --- */ + .avocado-zone { + padding: 8px 10px 10px; + } + .trumpet-indicator { + margin-top: 8px; + font-size: 0.9rem; + padding: 4px 12px; + } + + /* --- arrange: the one screen that may scroll past the viewport --- */ + .arrange { + gap: 10px; + } + .arrange-col { + min-height: 0; + padding: 12px 12px 14px; + gap: 10px; + } + .arrange-marker { + font-size: 0.85rem; + } + .arrange-controls { + margin-left: 8px; + } + .arrange-handle { + min-width: 34px; + min-height: 34px; + font-size: 1.25rem; + } + + /* --- battle: fit both sides and the clash between the rails --- */ + .battle { + gap: 8px; + } + .battle-header { + gap: 8px; + } + .battle-header h2 { + font-size: 1.1rem; + } + .battle-controls { + flex-wrap: wrap; + justify-content: center; + gap: 5px; + padding: 4px 6px; + } + .battle-speed-toggle { + margin-left: 2px; + } + .battle-names { + font-size: 0.9rem; + gap: 10px; + } + .battlefield { + padding: 8px 8px; + min-height: 0; + gap: 3px; + border-radius: 16px; + } + .battle-center { + font-size: 1.2rem; + min-height: 1.5rem; + } + .battle-side { + gap: 5px; } .battle-unit .card { - width: 78px; - height: 134px; + width: 66px; + height: 112px; + } + .battle-unit-zone { + min-width: 72px; + min-height: 112px; + } + .card-back { + width: 56px; + height: 78px; + } + .card-back-count { + width: 28px; + height: 28px; + font-size: 0.95rem; + } + /* Apples fan tighter so a big stack doesn't shove the pet off-centre. */ + .food-fan-card:not(:first-child) { + margin-left: -50px; + } + .battle-result { + gap: 8px; + } + .battle-result-title { + font-size: 1.7rem; + } + .battle-result .btn-big { + font-size: 1.05rem; + padding: 11px 22px; + } + + /* --- modals & game over --- */ + .modal { + padding: 18px 16px; + } + .gameover { + gap: 14px; + min-height: 0; + padding: 16px 0; + } + .gameover-emoji { + font-size: 3rem; + } + .gameover-title { + font-size: 2.2rem; + } + .gameover-scores { + padding: 14px 18px; } }