2 Commits

Author SHA1 Message Date
Lukas
30e7ed4121 Stabilize turn navigation bar layout with CSS grid
All checks were successful
CI / check (push) Successful in 2m18s
CI / build-image (push) Successful in 17s
Use a three-column grid (1fr / auto / 1fr) so the active combatant
name stays centered while round badge and difficulty indicator are
anchored in the left and right zones. Prevents layout jumps when
the name changes between turns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 02:15:16 +02:00
Lukas
5540baf14c Show concentration icon on mobile as grey affordance
On touch devices, the Brain icon was fully hidden (opacity-0) unlike
the edit and condition buttons. Add pointer-coarse:opacity-50 so it
appears as a discoverable grey icon, matching the other action buttons.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 01:02:16 +02:00
3 changed files with 26 additions and 25 deletions

View File

@@ -80,7 +80,7 @@ describe("TurnNavigation", () => {
expect(container.textContent).not.toContain("\u2014"); expect(container.textContent).not.toContain("\u2014");
}); });
it("round badge and combatant name are siblings in the center area", () => { it("round badge is in the left zone and name is in the center zone", () => {
renderNav( renderNav(
buildEncounter({ buildEncounter({
combatants: [buildCombatant({ name: "Goblin" })], combatants: [buildCombatant({ name: "Goblin" })],
@@ -88,7 +88,8 @@ describe("TurnNavigation", () => {
); );
const badge = screen.getByText("R1"); const badge = screen.getByText("R1");
const name = screen.getByText("Goblin"); const name = screen.getByText("Goblin");
expect(badge.closest(".flex")).toBe(name.parentElement); // Badge and name are in separate grid cells to prevent layout shifts
expect(badge.parentElement).not.toBe(name.parentElement);
}); });
}); });

View File

@@ -430,7 +430,7 @@ function concentrationIconClass(
dimmed: boolean, dimmed: boolean,
): string { ): string {
if (!isConcentrating) if (!isConcentrating)
return "opacity-0 group-hover:opacity-50 text-muted-foreground"; return "opacity-0 pointer-coarse:opacity-50 group-hover:opacity-50 text-muted-foreground";
return dimmed ? "opacity-50 text-purple-400" : "opacity-100 text-purple-400"; return dimmed ? "opacity-50 text-purple-400" : "opacity-100 text-purple-400";
} }

View File

@@ -26,19 +26,19 @@ export function TurnNavigation() {
const activeCombatant = encounter.combatants[encounter.activeIndex]; const activeCombatant = encounter.combatants[encounter.activeIndex];
return ( return (
<div className="card-glow flex items-center gap-3 border-border border-b bg-card px-4 py-3 sm:rounded-lg sm:border"> <div className="card-glow grid grid-cols-[1fr_minmax(0,auto)_1fr] items-center border-border border-b bg-card px-2 py-3 sm:rounded-lg sm:border sm:px-4">
<Button {/* Left zone: navigation + history + round */}
variant="ghost"
size="icon"
onClick={retreatTurn}
disabled={!hasCombatants || isAtStart}
title="Previous turn"
aria-label="Previous turn"
>
<StepBack className="h-5 w-5" />
</Button>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
onClick={retreatTurn}
disabled={!hasCombatants || isAtStart}
title="Previous turn"
aria-label="Previous turn"
>
<StepBack className="h-5 w-5" />
</Button>
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
@@ -59,21 +59,24 @@ export function TurnNavigation() {
> >
<Redo2 className="h-4 w-4" /> <Redo2 className="h-4 w-4" />
</Button> </Button>
<span className="ml-1 rounded-md bg-muted px-2 py-0.5 font-semibold text-foreground text-sm tabular-nums">
R{encounter.roundNumber}
</span>
</div> </div>
<div className="flex min-w-0 flex-1 items-center justify-center gap-2 text-sm"> {/* Center zone: active combatant name */}
<span className="shrink-0 rounded-md bg-muted px-2 py-0.5 font-semibold text-foreground text-sm"> <div className="min-w-0 px-2 text-center text-sm">
<span className="-mt-[3px] inline-block">
R{encounter.roundNumber}
</span>
</span>
{activeCombatant ? ( {activeCombatant ? (
<span className="truncate font-medium">{activeCombatant.name}</span> <span className="truncate font-medium">{activeCombatant.name}</span>
) : ( ) : (
<span className="text-muted-foreground">No combatants</span> <span className="text-muted-foreground">No combatants</span>
)} )}
</div>
{/* Right zone: difficulty + destructive + forward */}
<div className="flex items-center justify-end gap-1">
{difficulty && ( {difficulty && (
<div className="relative"> <div className="relative mr-1">
<DifficultyIndicator <DifficultyIndicator
result={difficulty} result={difficulty}
onClick={() => setShowBreakdown((prev) => !prev)} onClick={() => setShowBreakdown((prev) => !prev)}
@@ -85,9 +88,6 @@ export function TurnNavigation() {
) : null} ) : null}
</div> </div>
)} )}
</div>
<div className="flex flex-shrink-0 items-center gap-3">
<ConfirmButton <ConfirmButton
icon={<Trash2 className="h-5 w-5" />} icon={<Trash2 className="h-5 w-5" />}
label="Clear encounter" label="Clear encounter"