Implement the 027-ui-polish feature that adds six combatant row improvements (inline conditions, row-click stat block, hover-only remove button, AC shield shape, expanded concentration click target, larger d20 icon) plus top bar redesign with icon-only StepBack/StepForward navigation buttons, dark-themed scrollbars, and multiple UX fixes including stat block panel stability during initiative rolls and mobile touch safety for hidden buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-10 19:00:49 +01:00
parent d5f7b6ee36
commit f029c1a85b
14 changed files with 811 additions and 120 deletions

View File

@@ -0,0 +1,66 @@
# Research: Combatant Row UI Polish
**Feature**: 027-ui-polish | **Date**: 2026-03-10
## R1: CSS Shield Shape for AC
**Decision**: Use an inline SVG background with the AC number centered inside, rendered as a dedicated `AcShield` component.
**Rationale**: CSS `clip-path` clips the element itself (including borders and backgrounds) but makes it hard to get a clean outlined shield. An inline SVG shield path with the number as a `<text>` or overlaid HTML gives full control over stroke, fill, and sizing. This matches how D&D Beyond and character sheet PDFs render AC.
**Alternatives considered**:
- `clip-path: polygon(...)` — clips the element shape but can't produce a stroke outline easily. Would need a pseudo-element hack.
- Background image SVG — works but harder to make the stroke color respond to CSS custom properties (theme-aware).
- Lucide Shield icon with number overlaid via absolute positioning — fragile alignment, icon stroke competes with the number visually.
**Approach**: Create a small SVG shield outline (viewBox-based) as a React component. The AC number is rendered as a centered `<span>` overlaid on the SVG using relative/absolute positioning. The SVG uses `currentColor` for the stroke so it inherits theme colors. Size: approximately 28×32px to comfortably fit 1-3 digit numbers.
## R2: Row Click Stat Block — Event Delegation
**Decision**: Attach `onClick` handler to the row container, use `event.stopPropagation()` on all interactive child elements to prevent bubbling.
**Rationale**: This is the standard pattern for "click anywhere except interactive elements." Each interactive element (initiative, HP, AC, conditions, "+", "×", concentration) already has its own click handler — adding `stopPropagation()` to each ensures the row-level handler only fires for non-interactive areas.
**Alternatives considered**:
- Checking `event.target` against a list of interactive selectors — fragile and hard to maintain.
- Wrapping non-interactive areas in a separate clickable element — would complicate the grid layout.
## R3: Hover-Only Elements — Touch Device Accessibility
**Decision**: Use CSS `opacity-0 group-hover:opacity-100` for hide/show, combined with `focus-within:opacity-100` for keyboard accessibility. On touch devices, the elements are accessible via tap (the first tap reveals, second tap activates — standard mobile pattern with opacity transitions).
**Rationale**: The concentration button already uses this exact pattern (`opacity-0 group-hover:opacity-50`). Extending it to the remove button and "+" condition button is consistent.
**Alternatives considered**:
- `display: none` / `display: block` — causes layout shifts (violates FR-004).
- `visibility: hidden` / `visible` — works but doesn't allow opacity transitions.
## R4: Inline Conditions Layout
**Decision**: Move `ConditionTags` and the "+" button into the name column's flex container (the `1fr` column), after the name text. The conditions and "+" sit inline with the name, wrapping if needed.
**Rationale**: The name column is already a flex container (`flex items-center gap-1`). Adding condition icons here is natural. The `truncate` class on the name will need adjustment — the name should shrink (`min-w-0 truncate` on just the name text) while conditions fill remaining space.
**Alternatives considered**:
- Dedicated column for conditions — adds complexity to the grid and uses horizontal space poorly.
- Conditions as a second flex row within the name cell — still uses `flex-wrap` but explicitly creates a two-row layout when many conditions exist.
## R5: D20 Icon Sizing
**Decision**: Increase from `h-5 w-5` (20px) to `h-7 w-7` (28px). The initiative column is 3rem (48px) wide, so 28px fits comfortably with padding.
**Rationale**: At 20px the internal geometry lines of the d20 are too dense to read as a die shape. At 28px the icosahedron silhouette becomes recognizable. The column has room — the d20 button is already `h-7 w-full` so only the icon within needs to grow.
**Alternatives considered**:
- `h-6 w-6` (24px) — marginal improvement, still a bit small.
- `h-8 w-8` (32px) — might feel oversized relative to the initiative numbers in adjacent rows.
## R6: Concentration Click Target
**Decision**: Expand the concentration button from `1.25rem` width to fill the full gutter. Change the grid column from `1.25rem` to `2rem` (or use padding on the button to extend its hit area to the row edge). The brain icon stays centered visually.
**Rationale**: The left border of the row already has `px-3` padding. The concentration column at `1.25rem` (20px) with a 16px icon leaves very little extra hit area. Widening the column or extending the button's padding makes tapping much easier.
**Alternatives considered**:
- Negative margin on the button — hacky, could affect layout.
- Separate invisible overlay — unnecessary complexity.