feat: US-002 - Extract animation timing into named constants

This commit is contained in:
2026-02-15 14:10:47 +00:00
parent e5be969308
commit a56a4dd848
3 changed files with 156 additions and 271 deletions
+29 -152
View File
@@ -1,5 +1,5 @@
# Progress Log — Login Screen Rework
# Branch: ralph/login-screen-rework
# Progress Log — Login Logo & Blur Refinements
# Branch: ralph/login-logo-refinements
# Started: 2026-02-15
## Codebase Patterns
@@ -41,168 +41,45 @@
- React 18.3.1, TypeScript, Vite, Tailwind CSS
- Framer Motion 11.15.0, Lucide React 0.468.0, fuse.js 7.0.0
### Phase Rendering (post US-006)
- Login phase now renders BOTH DashboardLayout and LoginScreen overlay simultaneously
- DashboardLayout is wrapped in DetailPanelProvider for both 'login' and 'pmr' phases
- LoginScreen overlay: `fixed inset-0 z-50` with `rgba(240, 245, 244, 0.7)` + `backdrop-filter: blur(20px)`
### Key Files for This Feature
- src/App.tsx — phase management, will need restructuring for blur overlay
- src/components/LoginScreen.tsx — main login screen (416 lines)
- src/components/TopBar.tsx — Home icon replacement target (line 57)
- src/components/DashboardLayout.tsx — rendered behind login blur
- src/contexts/DetailPanelContext.tsx — wraps DashboardLayout
- cvmis-logo.svg — source SVG with 3 capsule groups
- LogoReveal/frame 1-5.jpg — animation reference frames
- src/components/CvmisLogo.tsx — logo component with animation (timing constants to extract)
- src/components/LoginScreen.tsx — main login screen (overlay, blur, card styling)
- src/App.tsx — phase management (skip/restore boot sequence)
- src/index.css — CSS custom properties, design tokens
### CvmisLogo Component
### CvmisLogo Component (from previous run)
- `size` prop: numeric, sets SVG height attribute directly
- `cssHeight` prop: string, sets height via CSS style (use for clamp/responsive values)
- `animated` prop: boolean, enables framer-motion reveal animation (1000ms total)
- Logo animation: 500ms rise (green capsule) + 500ms fan-out (all three) = 1000ms total
- All timing values are named constants at top of file — tune there, not inline
- Blend constants (OVERLAY_BLEND_*) are exported for use by other components (US-005)
### LoginScreen.tsx Key Lines (post US-007)
- Line 20: connectionState useState
- Line 21: dotCount useState (for animated trailing dots)
- Line 43: canLogin derived state
- Line 60-101: startLoginSequence (typing animation)
- Line 110-115: useEffect — connection transitions to green 500ms after typingComplete
- Line 118-126: useEffect — animated dot cycling (500ms interval) while connecting
- Line 128-150: useEffect — cursor blink + startLoginSequence delay (no more connectionTimeout)
- Line 370-405: Connection status indicator (10px LED dot with glow, 12px text)
### LoginScreen.tsx State (from previous run)
- Overlay: fixed inset-0 z-50, rgba(240, 245, 244, 0.7) + backdrop-filter: blur(20px)
- TopBar is zIndex: 100 — currently renders ABOVE the z-50 overlay (bug)
- Card borderRadius: 12px, inputs/button borderRadius: 4px
- Some colors already tokenized (--surface, --accent, --bg-dashboard) from previous run
- Some colors still hardcoded (#111827 input text, button bg states, caret color)
---
## 2026-02-15 - US-010
- Reverted initial Phase state from 'login' back to 'boot' in App.tsx line 47
- Full flow verified: boot → ECG → login (with blur, logo, typing, connection indicator, pulse) → dissolve → dashboard
- Files changed: src/App.tsx
## 2026-02-15 - US-001: Skip to login phase for dev iteration
- Changed initial Phase state from `'boot'` to `'login'` in `src/App.tsx` line 47
- Files changed: `src/App.tsx`
- **Learnings for future iterations:**
- Simple one-line revert as planned in US-001
- The full boot→ECG→login sequence takes ~20 seconds before login screen appears
- Phase state is a simple `useState<Phase>` on line 47 of App.tsx
- All phase rendering logic (`boot`, `ecg`, `login`, `pmr`) remains intact — only initial value changes
- US-011 will revert this exact change back to `'boot'`
---
## 2026-02-15 - US-009
- Changed outer overlay container from plain `<div>` to `<motion.div>` for animated exit
- On isExiting: overlay animates backgroundColor to transparent, backdropFilter from blur(20px) to blur(0px) over 600ms
- Card exit animation extended from 200ms to 400ms for smoother dissolve feel
- onComplete callback fires after 600ms dissolve (previously 200ms card exit)
- After dissolve completes, overlay removed from DOM and dashboard becomes interactive
- prefers-reduced-motion: instant transition (0ms for all timers)
- Files changed: src/components/LoginScreen.tsx
- Verified in browser: clicked login → spinner → card fades + overlay blur dissolves → dashboard revealed
## 2026-02-15 - US-002: Extract animation timing into named constants
- Extracted all inline timing values in CvmisLogo.tsx to named constants at top of file
- Constants added: RISE_DURATION_MS, RISE_DURATION_S, RISE_OPACITY_DURATION_S, RISE_EASING, RISE_START_Y, FAN_DELAY_AFTER_RISE_MS, FAN_DURATION_S, FAN_ROTATION_DEG, FAN_HORIZONTAL_PX, FAN_RIGHT_STAGGER_S, TOTAL_ANIMATION_MS
- Added overlap blend constants for US-005: OVERLAY_BLEND_START_PROGRESS, OVERLAP_BLEND_MAX_OPACITY, OVERLAP_BLEND_TRANSITION_DURATION_S (exported)
- Files changed: `src/components/CvmisLogo.tsx`
- **Learnings for future iterations:**
- framer-motion can animate backdropFilter and backgroundColor on a motion.div via the animate prop
- The onComplete timeout (600ms) must match the overlay dissolve duration, not the card fade duration
- Card fade (400ms) finishes before overlay dissolve (600ms), creating a layered reveal effect
- WebkitBackdropFilter needs to be animated alongside backdropFilter for Safari
- Blend constants are `export`ed because TypeScript strict mode flags unused `const` declarations — exporting avoids the TS6133 error while making them available for US-005
- TOTAL_ANIMATION_MS is computed from FAN_DELAY_AFTER_RISE_MS + FAN_DURATION_S * 1000, so changing rise or fan timing automatically updates the done-timer
- FAN_EASING was already a named constant before this story; it was left in place and grouped with the new fan constants
---
## 2026-02-15 - US-008
- Added @keyframes login-pulse in index.css: scale 1→1.03→1 over 3s cycle (1.5s animation built into keyframe percentages with 1.5s pause)
- Added .login-pulse-active class that applies the animation infinitely
- Hover removes animation via CSS rule (.login-pulse-active:hover { animation: none })
- Button gets login-pulse-active class when canLogin && !buttonPressed
- prefers-reduced-motion: .login-pulse-active { animation: none } in reduced motion media query
- Button opacity 0.6→1.0 transition preserved (existing behavior)
- Button still receives keyboard focus when enabled (existing behavior)
- Files changed: src/index.css, src/components/LoginScreen.tsx
- Verified in browser: button has login-pulse animation running (3s ease-in-out infinite), class applied correctly
- **Learnings for future iterations:**
- Used keyframe percentages (0%,60%,100% at scale(1), 30% at scale(1.03)) to build pause into a single animation rather than animation-delay
- CSS handles hover removal — no need for buttonHovered state in the class condition
- buttonPressed removes the class entirely (not just pauses), which is cleaner
---
## 2026-02-15 - US-007
- Reworked connection status indicator: LED dot 6px→10px with glow box-shadow, text 10px→12px
- Removed independent 2000ms connectionTimeout timer
- Added useEffect that transitions to green 500ms after typingComplete becomes true
- Added animated trailing dots cycling '.', '..', '...' every 500ms while connecting
- Initial state: red LED + red text "Awaiting secure connection" with animated dots
- Connected state: green LED + green text "Secure connection established, awaiting login"
- 300ms smooth transition for color and box-shadow between states
- prefers-reduced-motion: no dot cycling, instant state changes
- Files changed: src/components/LoginScreen.tsx
- Verified in browser: red indicator with cycling dots visible during typing, transitions to green after typing completes
- **Learnings for future iterations:**
- dotCount state cycles 0→1→2→3→0 (4 states: no dots, '.', '..', '...') via modulo arithmetic
- Connection transition is now tied to typingComplete state, not an arbitrary timer
- The dot interval cleanup needs to happen in both the dedicated useEffect and the main cleanup
- LED glow uses rgba with 0.4 alpha for subtle effect matching project shadow conventions
---
## 2026-02-15 - US-006
- Rendered DashboardLayout (wrapped in DetailPanelProvider) behind LoginScreen during login phase in App.tsx
- Changed LoginScreen overlay from solid #1A2B2A background to semi-transparent rgba(240, 245, 244, 0.7) with backdrop-filter: blur(20px)
- Dashboard is non-interactive during login (overlay captures pointer events via fixed inset-0 z-50)
- After login click, phase transitions to 'pmr' and overlay is removed from DOM, dashboard becomes interactive
- Files changed: src/App.tsx, src/components/LoginScreen.tsx
- Verified in browser: blur overlay shows dashboard content behind login card, login click transitions to interactive dashboard
- **Learnings for future iterations:**
- App.tsx phase rendering changed from exclusive (one phase at a time) to overlapping (login + pmr render DashboardLayout)
- DetailPanelProvider now wraps DashboardLayout for both 'login' and 'pmr' phases — condition is `(phase === 'login' || phase === 'pmr')`
- LoginScreen already had `fixed inset-0 z-50` which makes it a full-viewport overlay — just needed background/blur changes
- WebkitBackdropFilter needed for Safari compatibility alongside backdropFilter
---
## 2026-02-15 - US-005
- Replaced Home icon with CvmisLogo (size={24}, static/no animation) in TopBar.tsx
- Removed Home from lucide-react import (Search still used)
- Imported CvmisLogo component
- Files changed: src/components/TopBar.tsx
- Verified in browser: logo renders correctly with teal/amber/green capsule colors, fits TopBar height
- **Learnings for future iterations:**
- TopBar uses inline styles throughout, consistent with LoginScreen pattern
- Search is the only remaining lucide-react icon in TopBar.tsx
- CvmisLogo default `animated=false` means no animation prop needed for static usage
---
## 2026-02-15 - US-004
- Rebranded login from "CareerRecord PMR" to "CVMIS" with subtitle "CV Management Information System"
- Replaced Shield icon with CvmisLogo component (animated=true, responsive cssHeight)
- Added `cssHeight` prop to CvmisLogo for CSS clamp-based responsive sizing: clamp(48px, 4vw, 64px)
- Increased startLoginSequence delay from 400ms to 1500ms to let logo animation complete before typing begins
- prefers-reduced-motion: keeps original 400ms delay since logo renders instantly
- Fixed lint warning: added prefersReducedMotion to useEffect dependency array
- Files changed: src/components/LoginScreen.tsx, src/components/CvmisLogo.tsx
- **Learnings for future iterations:**
- CvmisLogo `size` prop is numeric (SVG height attribute) — use `cssHeight` string prop for CSS clamp values
- Logo animation is 1000ms total (500ms rise + 500ms fan-out) — typing delay must account for this
- The committed LoginScreen from US-003 still had Shield icon — US-003 only committed responsive sizing, not branding changes
---
## 2026-02-15 - US-003
- Responsive card: width clamp(320px,28vw,480px), maxWidth calc(100vw-32px), padding clamp(24px,2.5vw,40px)
- Replaced hardcoded colors with CSS variables: --surface, --bg-dashboard, --accent, --text-secondary, --text-tertiary
- Input fields: #E4EDEB default border, var(--accent) focus border, var(--bg-dashboard) inactive bg
- Font sizes: labels clamp(12px,1vw,14px), inputs clamp(13px,1.1vw,15px), button clamp(14px,1.1vw,16px)
- Card shadow: 0 1px 2px rgba(26,43,42,0.05) matching project shadow tokens
- Files changed: src/components/LoginScreen.tsx
- **Learnings for future iterations:**
- No --border-card CSS variable exists in index.css — use #E4EDEB directly
- LoginScreen uses inline styles throughout, not Tailwind classes (except for focus-visible ring on button)
- The card used className="bg-white" which needed to be replaced with inline style for consistency
---
## 2026-02-15 - US-002
- Created CvmisLogo.tsx component with inlined SVG paths from cvmis-logo.svg
- Three capsule groups: capsule-rx (teal #0b7979), capsule-terminal (amber #d97706), capsule-data (green #059669)
- Props: size (height px), animated (boolean, default false), className (optional)
- Framer Motion animation: Phase 1 (rise 500ms) — green data capsule scales from 0, Phase 2 (fan-out 500ms) — all three appear
- prefers-reduced-motion: skips animation, renders final state immediately
- Files changed: src/components/CvmisLogo.tsx (new)
- **Learnings for future iterations:**
- The SVG uses viewBox="0 0 600 506" with internal g transform scale(0.05,-0.05) — keep this coordinate system intact
- framer-motion's useReducedMotion() hook is the simplest way to handle reduced motion
- transform-origin in SVG needs px units when using framer-motion on g elements
---
## 2026-02-15 - US-001
- Changed initial Phase state from 'boot' to 'login' in App.tsx line 47
- Files changed: src/App.tsx
- **Learnings for future iterations:**
- Phase state is a simple string union type on line 47 of App.tsx
- US-010 will revert this exact change back to 'boot'
---