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

67 lines
5.2 KiB
Markdown

# 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.