feat: US-004 - Rebrand to CVMIS and integrate animated logo

This commit is contained in:
2026-02-15 01:57:13 +00:00
parent 38fdb6fa27
commit 0ffacf8a0a
4 changed files with 82 additions and 33 deletions
+4 -4
View File
@@ -14,7 +14,7 @@
"Typecheck passes" "Typecheck passes"
], ],
"priority": 1, "priority": 1,
"passes": false, "passes": true,
"notes": "Temporary — final story reverts this. Phase state is on line 47 of App.tsx." "notes": "Temporary — final story reverts this. Phase state is on line 47 of App.tsx."
}, },
{ {
@@ -34,7 +34,7 @@
"Typecheck passes" "Typecheck passes"
], ],
"priority": 2, "priority": 2,
"passes": false, "passes": true,
"notes": "The SVG uses a transform with scale(0.05, -0.05) and translate — you'll need to simplify the viewBox and transforms for React. The three <g> IDs are capsule-rx, capsule-terminal, capsule-data. Framer Motion is already installed (11.15.0). Look at LogoReveal/frame 1-5.jpg for the animation sequence. The fan-out in frames 4-5 shows: teal Rx tilts left, amber terminal stays center, green data tilts right." "notes": "The SVG uses a transform with scale(0.05, -0.05) and translate — you'll need to simplify the viewBox and transforms for React. The three <g> IDs are capsule-rx, capsule-terminal, capsule-data. Framer Motion is already installed (11.15.0). Look at LogoReveal/frame 1-5.jpg for the animation sequence. The fan-out in frames 4-5 shows: teal Rx tilts left, amber terminal stays center, green data tilts right."
}, },
{ {
@@ -56,7 +56,7 @@
"Verify in browser using dev-browser skill" "Verify in browser using dev-browser skill"
], ],
"priority": 3, "priority": 3,
"passes": false, "passes": true,
"notes": "LoginScreen.tsx currently uses inline styles with hardcoded colors (#E5E7EB borders, #64748B text, etc). Replace these with the dashboard CSS custom properties defined in index.css (--surface, --accent, --border, --text-primary, --text-secondary, --text-tertiary). Font family vars: var(--font-ui) for labels/buttons, var(--font-geist-mono) for input monospace." "notes": "LoginScreen.tsx currently uses inline styles with hardcoded colors (#E5E7EB borders, #64748B text, etc). Replace these with the dashboard CSS custom properties defined in index.css (--surface, --accent, --border, --text-primary, --text-secondary, --text-tertiary). Font family vars: var(--font-ui) for labels/buttons, var(--font-geist-mono) for input monospace."
}, },
{ {
@@ -76,7 +76,7 @@
"Verify in browser using dev-browser skill" "Verify in browser using dev-browser skill"
], ],
"priority": 4, "priority": 4,
"passes": false, "passes": true,
"notes": "The Shield icon is at LoginScreen.tsx lines 213-218. The logo animation is ~1000ms (500ms rise + 500ms fan-out). Increase the startLoginSequence delay from 400ms to ~1500ms (400ms card entrance + 1000ms logo + 100ms pause). CvmisLogo component from US-002." "notes": "The Shield icon is at LoginScreen.tsx lines 213-218. The logo animation is ~1000ms (500ms rise + 500ms fan-out). Increase the startLoginSequence delay from 400ms to ~1500ms (400ms card entrance + 1000ms logo + 100ms pause). CvmisLogo component from US-002."
}, },
{ {
+61 -7
View File
@@ -50,16 +50,70 @@
- cvmis-logo.svg — source SVG with 3 capsule groups - cvmis-logo.svg — source SVG with 3 capsule groups
- LogoReveal/frame 1-5.jpg — animation reference frames - LogoReveal/frame 1-5.jpg — animation reference frames
### LoginScreen.tsx Key Lines ### CvmisLogo Component
- `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
### LoginScreen.tsx Key Lines (post US-004)
- Line 20: connectionState useState - Line 20: connectionState useState
- Line 43: canLogin derived state - Line 43: canLogin derived state
- Line 60-101: startLoginSequence (typing animation) - Line 60-101: startLoginSequence (typing animation)
- Line 110-137: useEffect with connectionTimeout (2000ms) and startLoginSequence delay (400ms) - Line 110-139: useEffect with connectionTimeout (2000ms) and startLoginSequence delay (1500ms / 400ms reduced motion)
- Line 145-415: JSX render (card, form, button, status indicator) - Line 145-409: JSX render (card, form, button, status indicator)
- Line 213-218: Shield icon (to be replaced) - Line 208-211: CvmisLogo with cssHeight="clamp(48px, 4vw, 64px)" animated=true
- Line 229: "CareerRecord PMR" title - Line 221: "CVMIS" title
- Line 240: "Clinical Information System" subtitle - Line 232: "CV Management Information System" subtitle
- Line 367-388: Connection status indicator (6px dot, 10px text) - Line 350-382: Connection status indicator (6px dot, 10px text)
--- ---
## 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'
---
+5 -4
View File
@@ -2,12 +2,13 @@ import { useEffect, useState } from 'react'
import { motion, useReducedMotion } from 'framer-motion' import { motion, useReducedMotion } from 'framer-motion'
interface CvmisLogoProps { interface CvmisLogoProps {
size: number size?: number
cssHeight?: string
animated?: boolean animated?: boolean
className?: string className?: string
} }
export function CvmisLogo({ size, animated = false, className }: CvmisLogoProps) { export function CvmisLogo({ size, cssHeight, animated = false, className }: CvmisLogoProps) {
const prefersReducedMotion = useReducedMotion() const prefersReducedMotion = useReducedMotion()
const [animationPhase, setAnimationPhase] = useState<'rise' | 'fan' | 'done'>( const [animationPhase, setAnimationPhase] = useState<'rise' | 'fan' | 'done'>(
animated && !prefersReducedMotion ? 'rise' : 'done' animated && !prefersReducedMotion ? 'rise' : 'done'
@@ -34,11 +35,11 @@ export function CvmisLogo({ size, animated = false, className }: CvmisLogoProps)
return ( return (
<svg <svg
viewBox="0 0 600 506" viewBox="0 0 600 506"
height={size} height={cssHeight ? undefined : size}
className={className} className={className}
role="img" role="img"
aria-label="CVMIS logo" aria-label="CVMIS logo"
style={{ overflow: 'visible' }} style={{ overflow: 'visible', ...(cssHeight ? { height: cssHeight, width: 'auto' } : {}) }}
> >
<g transform="translate(0,506) scale(0.05,-0.05)" stroke="none"> <g transform="translate(0,506) scale(0.05,-0.05)" stroke="none">
{/* Capsule: Rx (Pharmacy) - Left — teal, tilts left in fan */} {/* Capsule: Rx (Pharmacy) - Left — teal, tilts left in fan */}
+12 -18
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback, useRef } from 'react' import { useState, useEffect, useCallback, useRef } from 'react'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { Shield } from 'lucide-react' import { CvmisLogo } from './CvmisLogo'
import { useAccessibility } from '../contexts/AccessibilityContext' import { useAccessibility } from '../contexts/AccessibilityContext'
interface LoginScreenProps { interface LoginScreenProps {
@@ -118,10 +118,12 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
setConnectionState('connected') setConnectionState('connected')
}, 2000) }, 2000)
// Delay start slightly for card entrance animation // Delay start to allow card entrance + logo animation to complete
// Reduced motion: logo shows instantly, so use original 400ms delay
// Full motion: 400ms card entrance + 1000ms logo animation + 100ms pause = 1500ms
const startTimeout = addTimeout(() => { const startTimeout = addTimeout(() => {
startLoginSequence() startLoginSequence()
}, 400) }, prefersReducedMotion ? 400 : 1500)
// Capture ref value for cleanup // Capture ref value for cleanup
const pendingTimeouts = timeoutRefs.current const pendingTimeouts = timeoutRefs.current
@@ -134,7 +136,7 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
clearTimeout(connectionTimeout) clearTimeout(connectionTimeout)
pendingTimeouts.forEach(id => clearTimeout(id)) pendingTimeouts.forEach(id => clearTimeout(id))
} }
}, [startLoginSequence, addTimeout]) }, [startLoginSequence, addTimeout, prefersReducedMotion])
const buttonBg = buttonPressed const buttonBg = buttonPressed
? '#085858' ? '#085858'
@@ -204,18 +206,10 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
className="flex flex-col items-center" className="flex flex-col items-center"
style={{ marginBottom: '28px' }} style={{ marginBottom: '28px' }}
> >
<div <div style={{ marginBottom: '10px' }}>
style={{ <CvmisLogo
padding: '10px', cssHeight="clamp(48px, 4vw, 64px)"
borderRadius: '8px', animated={true}
backgroundColor: 'rgba(13, 110, 110, 0.08)',
marginBottom: '10px',
}}
>
<Shield
size={26}
style={{ color: '#0D6E6E' }}
strokeWidth={2.5}
/> />
</div> </div>
<span <span
@@ -227,7 +221,7 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
letterSpacing: '0.01em', letterSpacing: '0.01em',
}} }}
> >
CareerRecord PMR CVMIS
</span> </span>
<span <span
style={{ style={{
@@ -238,7 +232,7 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
marginTop: '2px', marginTop: '2px',
}} }}
> >
Clinical Information System CV Management Information System
</span> </span>
</div> </div>