Files
initiative/specs/035-statblock-fold-pin/research.md
Lukas 460c65bf49 Implement stat block panel fold/unfold and pin-to-second-panel
Replace the close button and heading with fold/unfold controls that
collapse the panel to a slim right-edge tab showing the creature name
vertically, and add a pin button (xl+ viewports with creature loaded)
that opens the creature in a second left-side panel for simultaneous
reference. Fold state is respected on turn change. 19 acceptance tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:18:15 +01:00

5.2 KiB

Research: Stat Block Panel Fold/Unfold and Pin

Feature: 035-statblock-fold-pin Date: 2026-03-11

R-001: Fold/Unfold Animation Approach

Decision: Use CSS translate with transition property for fold/unfold, matching the existing slide-in-right keyframes pattern.

Rationale: The project already uses translate: 100%translate: 0 for the mobile drawer animation (index.css lines 58-69). The fold/unfold is the same motion in reverse. Using CSS transitions (rather than keyframes) gives us bidirectional animation without separate fold/unfold keyframes — the browser interpolates both directions from a single transition: translate 200ms ease-out declaration.

Alternatives considered:

  • Keyframe animations (animate-slide-out-right + animate-slide-in-right): More verbose, requires managing animation-fill-mode and class toggling. Rejected — transitions are simpler for toggle states.
  • JS-driven animation (Web Animations API): Overkill for a simple translate. Rejected.

R-002: Folded Tab Design

Decision: The folded tab is a narrow strip (~40px wide) fixed to the right edge, containing the creature name rendered vertically using writing-mode: vertical-rl. The tab acts as a clickable button to unfold.

Rationale: writing-mode: vertical-rl is well-supported (96%+ browser support) and produces clean vertical text without transform hacks. The 40px width accommodates typical creature names at ~14px font size. The tab is part of the same component — it renders conditionally based on fold state, not as a separate element.

Alternatives considered:

  • transform: rotate(-90deg): Causes layout issues (element still occupies horizontal space). Rejected.
  • Truncated horizontal text in narrow strip: Unreadable at 40px. Rejected.
  • Icon-only tab (no creature name): Loses context about which creature is loaded. Rejected — spec requires creature name.

R-003: Dual-Panel Breakpoint

Decision: Use 1280px (Tailwind's xl breakpoint) as the minimum viewport width for showing the pin button and supporting dual panels.

Rationale: Two 400px panels + the centered content container (max 672px = max-w-2xl) totals 1472px. At 1280px, the content column compresses slightly but remains usable. Below 1280px, single-panel mode is sufficient. The existing desktop breakpoint is 1024px (lg) for single panel; xl provides a clean step-up for dual panels.

Alternatives considered:

  • 1024px (same as single panel): Not enough room for two 400px panels + content. Rejected.
  • 1440px: Too restrictive — excludes many laptop screens. Rejected.
  • Custom breakpoint (e.g., 1200px): Deviates from Tailwind defaults without strong reason. Rejected.

R-004: Pinned Panel State Management

Decision: Lift pinned creature state (pinnedCreatureId: CreatureId | null) to App.tsx alongside selectedCreatureId. The pinned panel reuses the same StatBlockPanel component with a variant or role prop to control header buttons (unpin vs fold/pin).

Rationale: App.tsx already manages selectedCreatureId and passes creature data down. Adding pinnedCreatureId follows the same pattern. The StatBlockPanel component handles its own desktop/mobile rendering, so reusing it for the pinned panel avoids duplication. The pinned panel only renders on desktop at xl+ breakpoints.

Alternatives considered:

  • Separate PinnedPanel component: Creates duplication of panel chrome, scrolling, and stat block rendering. Rejected — use props to differentiate.
  • Context/store for panel state: Over-engineering for two pieces of state. Rejected — simple useState is sufficient.

R-005: Mobile Behavior Preservation

Decision: On mobile (< 1024px), keep the existing drawer/backdrop pattern. Replace the X close button with a fold toggle that slides the drawer off-screen. The backdrop click still dismisses (sets selectedCreatureId to null). No pin button on mobile.

Rationale: The spec requires mobile behavior preservation (FR-010). The fold toggle on mobile provides consistency with desktop while the backdrop click provides the familiar dismiss pattern. Pinning doesn't make sense on small screens where two panels can't coexist.

Alternatives considered:

  • Keep X close button on mobile, fold only on desktop: Inconsistent UX across breakpoints. Rejected — spec says replace close button.
  • Add close button alongside fold on mobile: Spec explicitly removes close button (FR-001). Rejected.

R-006: Panel Layout Architecture

Decision: Both panels use position: fixed anchored to their respective edges. The pinned panel uses left-0 while the browse panel uses right-0. The main content area does not reflow — panels overlay the edges of the viewport.

Rationale: The current panel already uses fixed positioning (right-0). Adding a left-0 panel follows the same pattern. Fixed positioning avoids complex flex/grid layout changes to App.tsx. The main content is already centered with max-w-2xl mx-auto, so panels on either side naturally overlay the unused viewport margins on wide screens.

Alternatives considered:

  • CSS Grid with named areas: Requires restructuring App.tsx layout. Over-engineering for panels that already work as fixed overlays. Rejected.
  • Flex layout with panel slots: Same restructuring concern. Rejected.