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