Hold the battle arena at a fixed height.
The play area grew and shrank as cards entered and left play. Two slots were sized independently of the cards that fill them: the pet zone reserved 150px for a 162px battle card, and the empty-deck placeholder kept its desktop 84x116 on phones where the face-down card is 56x78. So each faint/reveal moved the arena 12px per side on desktop, and a deck running dry moved it 38px per side on a phone. Every arena slot now derives from one set of --unit-*/--deck-* metrics on .battle, and holds that size whether or not a card occupies it. Measured across a battle's states (no pets, both revealed, one fainted, fans of apples, decks dry), the arena height spread goes from 24px desktop / 76px phone to 0. Also hide the arena's horizontal scrollbar: a wide apple fan made it appear and vanish mid-battle, and with a content-driven height that alone jumped the arena by the scrollbar's thickness. Touch, trackpad and shift+wheel still scroll it.
This commit is contained in:
@@ -295,10 +295,10 @@ export function BattlePhase({ view, send, step, setStep }: Props) {
|
||||
// The result dialog pops when the replay ends. "Not yet" dismisses it so the
|
||||
// battle can be rewatched; a toolbar button then remains to advance the round.
|
||||
const [resultDismissed, setResultDismissed] = useState(false)
|
||||
// The deck-peek popover lives inside .battlefield, which sets overflow-x
|
||||
// (and thus overflow-y) to auto — so an absolutely-positioned popover gets
|
||||
// clipped to the battlefield. We anchor it to the hovered stack's viewport
|
||||
// rect and portal it to <body> so it floats over the whole window instead.
|
||||
// The deck-peek popover lives inside .battlefield, which scrolls on one axis
|
||||
// and hides the other — so an absolutely-positioned popover gets clipped to
|
||||
// the battlefield. We anchor it to the hovered stack's viewport rect and
|
||||
// portal it to <body> so it floats over the whole window instead.
|
||||
const [peek, setPeek] = useState<{ seat: number; pos: 'top' | 'bottom'; rect: DOMRect } | null>(
|
||||
null,
|
||||
)
|
||||
|
||||
+45
-25
@@ -1990,6 +1990,15 @@ h3 {
|
||||
animation divides its base duration by this, so the visuals quicken in
|
||||
lockstep with the JS step timers. Fallback keeps calc() valid if unset. */
|
||||
--battle-speed: 1;
|
||||
/* Arena slot metrics. Every slot in the battlefield — the pet in play and the
|
||||
deck beneath/above it — is sized to these exactly, whether or not a card
|
||||
currently occupies it. That keeps the arena a fixed reserved area so its
|
||||
height never shifts as cards enter and leave play. Phones override them in
|
||||
the media query below; nothing else should hardcode these sizes. */
|
||||
--unit-w: 96px;
|
||||
--unit-h: 162px;
|
||||
--deck-w: 84px;
|
||||
--deck-h: 116px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
@@ -2067,13 +2076,24 @@ h3 {
|
||||
(right) to fan out beside each pet; a big apple stack (Manatee, Fire Ant)
|
||||
scrolls horizontally rather than clipping. */
|
||||
padding: 18px 14px;
|
||||
min-height: 150px;
|
||||
/* That sideways scroll keeps its scrollbar hidden: on classic-scrollbar
|
||||
platforms it would appear and vanish as fans grow and shrink, and since the
|
||||
arena's height is content-driven, that alone made it jump by the
|
||||
scrollbar's thickness mid-battle. Touch, trackpad and shift+wheel still
|
||||
scroll it. Vertical is hidden outright — pops that reach past the felt
|
||||
(spawns, ailment badges) were never scrollable to anyway. */
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: none;
|
||||
box-shadow:
|
||||
var(--tray-inset),
|
||||
inset 0 0 60px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.battlefield::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.battle-center {
|
||||
position: relative;
|
||||
font-size: 1.7rem;
|
||||
@@ -2109,10 +2129,12 @@ h3 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Centres the deck stack under (you) / over (opponent) the pet. */
|
||||
/* Centres the deck stack under (you) / over (opponent) the pet. Its height is
|
||||
the deck slot's, held whether the deck still has cards or has run dry. */
|
||||
.battle-deck {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: var(--deck-h);
|
||||
}
|
||||
|
||||
/* --- deck stacks --- */
|
||||
@@ -2125,8 +2147,8 @@ h3 {
|
||||
/* The face-down deck: a walnut-backed card with a woven diamond pattern and a
|
||||
little stacked-depth shadow beneath it. */
|
||||
.card-back {
|
||||
width: 84px;
|
||||
height: 116px;
|
||||
width: var(--deck-w);
|
||||
height: var(--deck-h);
|
||||
border-radius: var(--card-radius);
|
||||
border: 2px solid var(--cocoa);
|
||||
background:
|
||||
@@ -2156,9 +2178,11 @@ h3 {
|
||||
box-shadow: inset 0 0 0 2px rgba(255, 230, 200, 0.25);
|
||||
}
|
||||
|
||||
/* The empty deck slot holds the face-down card's footprint exactly, so a deck
|
||||
running dry doesn't resize the arena. */
|
||||
.stack-empty {
|
||||
width: 84px;
|
||||
height: 116px;
|
||||
width: var(--deck-w);
|
||||
height: var(--deck-h);
|
||||
}
|
||||
|
||||
@keyframes summon-pop {
|
||||
@@ -2303,10 +2327,14 @@ h3 {
|
||||
|
||||
/* --- the pet in play --- */
|
||||
|
||||
/* The pet's slot in the arena: a fixed reserved box the size of a battle card,
|
||||
held even while it's empty (between a faint and the next reveal) so the
|
||||
arena's height stays put as cards enter and leave play. Set-aside pets and the
|
||||
food fan hang off it absolutely, so they never resize it either. */
|
||||
.battle-unit-zone {
|
||||
position: relative;
|
||||
min-width: 100px;
|
||||
min-height: 150px;
|
||||
width: var(--unit-w);
|
||||
height: var(--unit-h);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
@@ -2419,11 +2447,11 @@ h3 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Fills its reserved slot. The slot is taller than a shop card so the full
|
||||
ability text fits without shrinking to an unreadable size during battle. */
|
||||
.battle-unit .card {
|
||||
width: 96px;
|
||||
/* Taller than a shop card so the full ability text fits without shrinking to
|
||||
an unreadable size during the battle. */
|
||||
height: 162px;
|
||||
width: var(--unit-w);
|
||||
height: var(--unit-h);
|
||||
}
|
||||
|
||||
/* Battle cards are short, so give the ability text more of the card by
|
||||
@@ -3205,6 +3233,11 @@ h3 {
|
||||
/* --- battle: fit both sides and the clash between the rails --- */
|
||||
.battle {
|
||||
gap: 8px;
|
||||
/* Smaller arena slots; the pet and deck boxes follow automatically. */
|
||||
--unit-w: 66px;
|
||||
--unit-h: 112px;
|
||||
--deck-w: 56px;
|
||||
--deck-h: 78px;
|
||||
}
|
||||
.battle-header {
|
||||
gap: 8px;
|
||||
@@ -3227,7 +3260,6 @@ h3 {
|
||||
}
|
||||
.battlefield {
|
||||
padding: 8px 8px;
|
||||
min-height: 0;
|
||||
gap: 3px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
@@ -3238,14 +3270,6 @@ h3 {
|
||||
.battle-side {
|
||||
gap: 5px;
|
||||
}
|
||||
.battle-unit .card {
|
||||
width: 66px;
|
||||
height: 112px;
|
||||
}
|
||||
.battle-unit-zone {
|
||||
min-width: 72px;
|
||||
min-height: 112px;
|
||||
}
|
||||
/* A battle card is small and glanced at, not read — so on a phone strip it to
|
||||
the essentials: big art, the power badge and any damage marker, and the
|
||||
name. No suit dot, tier die, or ability text (the magnified view still shows
|
||||
@@ -3270,10 +3294,6 @@ h3 {
|
||||
.battle-unit .card-art {
|
||||
font-size: 2.1rem;
|
||||
}
|
||||
.card-back {
|
||||
width: 56px;
|
||||
height: 78px;
|
||||
}
|
||||
.card-back-count {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
Reference in New Issue
Block a user