feat: US-002 - Extract animation timing into named constants
This commit is contained in:
+93
-108
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"project": "Portfolio — Login Screen Rework",
|
"project": "Portfolio — Login Logo & Blur Refinements",
|
||||||
"branchName": "ralph/login-screen-rework",
|
"branchName": "ralph/login-logo-refinements",
|
||||||
"description": "Rework the login screen: responsive sizing, dashboard style alignment, CVMIS rebrand, animated capsule logo, live blurred dashboard background, connection status indicator UX, button pulse, and dissolve transition.",
|
"description": "Refine the login screen's CVMIS logo animation, backdrop blur coverage/intensity, and align visual details (border radius, shadows, colors, typography) with the dashboard design system.",
|
||||||
"userStories": [
|
"userStories": [
|
||||||
{
|
{
|
||||||
"id": "US-001",
|
"id": "US-001",
|
||||||
@@ -19,181 +19,166 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-002",
|
"id": "US-002",
|
||||||
"title": "Create CvmisLogo React SVG component",
|
"title": "Extract animation timing into named constants",
|
||||||
"description": "As a developer, I need a reusable CvmisLogo component that renders the CVMIS capsule logo from cvmis-logo.svg, supporting both static and animated modes.",
|
"description": "As a developer, I want all animation timing values in CvmisLogo.tsx exposed as named constants at the top of the file so I can quickly tune rise speed, fan speed, fan delay, and easing.",
|
||||||
"acceptanceCriteria": [
|
"acceptanceCriteria": [
|
||||||
"Create src/components/CvmisLogo.tsx as a React component",
|
"Named constants at the top of CvmisLogo.tsx for: rise duration (currently 500ms), fan delay after rise (currently 500ms), fan duration (currently 600ms), fan easing curve, fan rotation angle (currently ±50°), fan horizontal spacing (currently ±16px), right pill stagger delay (currently 30ms)",
|
||||||
"Component accepts props: size (number, controls height in px), animated (boolean, default false), className (optional string)",
|
"Additional named constants for overlap blend: OVERLAY_BLEND_START_PROGRESS (target 0.5), OVERLAP_BLEND_MAX_OPACITY (target 0.2), OVERLAP_BLEND_TRANSITION_DURATION",
|
||||||
"SVG paths are inlined from cvmis-logo.svg — three <g> groups: capsule-rx (teal #0b7979), capsule-terminal (amber #d97706), capsule-data (green #059669)",
|
"Component behaviour unchanged when constants retain current values",
|
||||||
"The SVG viewBox is preserved so the logo scales correctly at any size",
|
"Constants are clearly named and grouped with a brief comment block",
|
||||||
"When animated=false, all three capsules render in their final fanned-out positions (matching the original SVG layout)",
|
|
||||||
"When animated=true, the component plays a two-phase reveal using framer-motion: Phase 1 (Rise ~500ms): green data capsule scales from 0 to 1 and translates upward into center position, other capsules hidden. Phase 2 (Fan-out ~500ms): all three capsules appear and rotate/translate to their final fanned-out positions with staggered easing",
|
|
||||||
"Animation reference: LogoReveal/frame 1-5.jpg — frame 1-3 show green capsule rising, frame 4-5 show all three fanning out",
|
|
||||||
"Each capsule group uses transform-origin at its base/bottom so fan-out looks like cards spreading from a hand",
|
|
||||||
"prefers-reduced-motion: skip animation, render final state immediately",
|
|
||||||
"Typecheck passes"
|
"Typecheck passes"
|
||||||
],
|
],
|
||||||
"priority": 2,
|
"priority": 2,
|
||||||
"passes": true,
|
"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": "Read CvmisLogo.tsx carefully first — some timing values are inline in useEffect/motion props. Extract them ALL to top-level constants. The blend constants are new (for US-004) but should be defined now with sensible defaults."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-003",
|
"id": "US-003",
|
||||||
"title": "Responsive login card sizing and dashboard style alignment",
|
"title": "Scale logo and branding block to ~50% of login card height",
|
||||||
"description": "As a visitor on a 1440p or 4K display, I want the login card to be proportionate to my screen and styled consistently with the GP dashboard.",
|
"description": "As a visitor, I want the CVMIS logo and branding text to be larger and more prominent, occupying roughly half the login card's height.",
|
||||||
"acceptanceCriteria": [
|
"acceptanceCriteria": [
|
||||||
"Login card width changes from fixed 320px to responsive: clamp(320px, 28vw, 480px)",
|
"Logo cssHeight scaled up from current clamp(48px, 4vw, 64px) — target approximately clamp(160px, 18vw, 280px), tune visually for balance",
|
||||||
"Card padding scales from fixed 32px to clamp(24px, 2.5vw, 40px)",
|
"Width scales proportionally (SVG viewBox preserves aspect ratio)",
|
||||||
"Input field font size scales proportionally (minimum 13px, up to 15px on large viewports)",
|
"The branding block (logo + CVMIS title + subtitle + spacing) occupies approximately 50% of the total login card height",
|
||||||
"Button font size scales proportionally (minimum 14px, up to 16px)",
|
"Logo does not overflow or clip on mobile viewports (>=375px wide)",
|
||||||
"Label font size scales proportionally (minimum 12px, up to 14px)",
|
|
||||||
"Card uses dashboard color tokens via CSS variables: background var(--surface), border color var(--border-card) or #E4EDEB, text colors var(--text-primary) and var(--text-secondary)",
|
|
||||||
"Input fields use var(--accent) (#0D6E6E) for focus border, #E4EDEB for default border, var(--bg-dashboard) for inactive background",
|
|
||||||
"Card shadow uses the project shadow tokens: 0 1px 2px rgba(26,43,42,0.05) resting, 0 2px 8px rgba(26,43,42,0.08) elevated",
|
|
||||||
"Card border radius remains 12px",
|
|
||||||
"Card still looks good on mobile (≤480px) — should not exceed viewport width minus 32px margin",
|
|
||||||
"Typecheck passes",
|
"Typecheck passes",
|
||||||
"Verify in browser using dev-browser skill"
|
"Verify in browser using dev-browser skill"
|
||||||
],
|
],
|
||||||
"priority": 3,
|
"priority": 3,
|
||||||
"passes": true,
|
"passes": false,
|
||||||
"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": "CvmisLogo accepts cssHeight prop (string) for CSS clamp values. The branding block is in LoginScreen.tsx — the logo, title, and subtitle are in a flex column container. Adjust the cssHeight prop on the CvmisLogo component and check the ratio visually."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-004",
|
"id": "US-004",
|
||||||
"title": "Rebrand to CVMIS and integrate animated logo",
|
"title": "Increase branding text to match dashboard typography scale",
|
||||||
"description": "As the portfolio owner, I want the login to say CVMIS with the capsule logo replacing the Shield icon.",
|
"description": "As a visitor, I want the CVMIS title and subtitle on the login screen to be larger and more in line with the dashboard's typography scale.",
|
||||||
"acceptanceCriteria": [
|
"acceptanceCriteria": [
|
||||||
"Title text changed from 'CareerRecord PMR' to 'CVMIS'",
|
"CVMIS title font size increased from 13px — target approximately 18-20px to match dashboard heading scale",
|
||||||
"Subtitle changed from 'Clinical Information System' to 'CV Management Information System'",
|
"CV Management Information System subtitle font size increased from 11px — target approximately 13-14px",
|
||||||
"The Shield icon import and its teal background container are removed from the branding section",
|
"Both remain in font-ui (Elvaro Grotesque) with appropriate weight hierarchy",
|
||||||
"CvmisLogo component is imported and rendered in the branding section with animated=true",
|
"Text remains visually balanced with the larger logo above and the login form below",
|
||||||
"Logo height is proportional to the responsive card size (roughly 48-64px depending on viewport, use clamp)",
|
|
||||||
"Logo animation completes before the typing animation starts — adjust the startLoginSequence delay (currently 400ms) to account for logo animation duration (~1000ms total)",
|
|
||||||
"Footer text 'Secure clinical system login' remains unchanged",
|
|
||||||
"prefers-reduced-motion: logo shows instantly in final state, typing starts after original 400ms delay",
|
|
||||||
"Typecheck passes",
|
"Typecheck passes",
|
||||||
"Verify in browser using dev-browser skill"
|
"Verify in browser using dev-browser skill"
|
||||||
],
|
],
|
||||||
"priority": 4,
|
"priority": 4,
|
||||||
"passes": true,
|
"passes": false,
|
||||||
"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 title and subtitle are in LoginScreen.tsx in the branding section. Look for the CVMIS text and its fontSize style. Use clamp() for responsive sizing consistent with the card's responsive approach."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-005",
|
"id": "US-005",
|
||||||
"title": "Replace Home icon with CVMIS logo on TopBar",
|
"title": "Add overlap blend effect on fanning capsules",
|
||||||
"description": "As a visitor on the dashboard, I want to see the CVMIS brand logo in the top-left corner instead of the generic Home icon.",
|
"description": "As a visitor, I want to see a subtle color blend where the fanning capsules overlap, matching the multiply-blend effect from the Remotion animation.",
|
||||||
"acceptanceCriteria": [
|
"acceptanceCriteria": [
|
||||||
"In src/components/TopBar.tsx, remove the Home import from lucide-react",
|
"CSS mix-blend-mode: multiply applied to the fanning pill elements in CvmisLogo.tsx",
|
||||||
"Import CvmisLogo from ./CvmisLogo",
|
"Blend effect is not visible at the start of the fan animation",
|
||||||
"Replace the <Home> element with <CvmisLogo size={24} /> (static, no animation)",
|
"Blend fades in starting at ~50% of fan animation progress (using OVERLAY_BLEND_START_PROGRESS constant from US-002)",
|
||||||
"Logo colors match SVG source: teal #0b7979, amber #d97706, green #059669",
|
"Blend reaches max intensity by end of fan (using OVERLAP_BLEND_MAX_OPACITY constant from US-002)",
|
||||||
"Logo maintains aspect ratio and fits within the TopBar height",
|
"Max blend opacity approximately 0.2 (20%)",
|
||||||
"The 'Headhunt Medical Center' brand text and all other TopBar elements remain unchanged",
|
"Blend is only perceptible where capsules actually overlap on light backgrounds",
|
||||||
|
"Blend transition feels smooth, not abrupt",
|
||||||
|
"Respects prefers-reduced-motion (no animation, show final state)",
|
||||||
"Typecheck passes",
|
"Typecheck passes",
|
||||||
"Verify in browser using dev-browser skill"
|
"Verify in browser using dev-browser skill"
|
||||||
],
|
],
|
||||||
"priority": 5,
|
"priority": 5,
|
||||||
"passes": true,
|
"passes": false,
|
||||||
"notes": "TopBar.tsx line 57-61 has the Home icon. Simple swap — CvmisLogo with animated=false (the default). If Home is the only lucide icon used in the import, clean up the import. Check: Search is also imported from lucide-react on line 2."
|
"notes": "Use framer-motion's useTransform or a progress-based approach to derive blend opacity from fan animation progress. The pill elements are <g> groups inside the SVG. Apply mixBlendMode: 'multiply' as a style and animate the group's opacity using the timing constants from US-002. The blend should only be visible during/after the fan phase, not during the rise phase."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-006",
|
"id": "US-006",
|
||||||
"title": "Render live dashboard behind login with blur overlay",
|
"title": "Extend backdrop blur to cover full dashboard including TopBar",
|
||||||
"description": "As a visitor, I want to see the GP dashboard blurred behind the login card, creating visual continuity.",
|
"description": "As a visitor, I want the frosted-glass blur behind the login card to cover the entire dashboard including the TopBar, so nothing behind the overlay is sharp.",
|
||||||
"acceptanceCriteria": [
|
"acceptanceCriteria": [
|
||||||
"In App.tsx, during the 'login' phase, render DashboardLayout underneath the login overlay (both visible simultaneously)",
|
"Blur overlay z-index raised above TopBar z-index (TopBar is zIndex: 100, overlay is currently z-50). Overlay must be >= zIndex: 110 or similar",
|
||||||
"DashboardLayout is wrapped in DetailPanelProvider (as it is in the 'pmr' phase)",
|
"TopBar, Sidebar, and all dashboard content are uniformly blurred behind the overlay",
|
||||||
"DashboardLayout renders at scroll position 0 (showing patient summary header area)",
|
"Login card itself remains crisp and unblurred (card z-index above overlay)",
|
||||||
"LoginScreen becomes an overlay: fixed position, full viewport, semi-transparent background rgba(240, 245, 244, 0.7) with backdrop-filter: blur(20px)",
|
"Blur still fades out during the dissolve/exit transition",
|
||||||
"Dashboard content is non-interactive while login overlay is present (the overlay captures all pointer events)",
|
|
||||||
"The login card remains centered on top of the blurred overlay",
|
|
||||||
"backdrop-filter blur is constant from the moment login appears (no ease-in)",
|
|
||||||
"prefers-reduced-motion: blur still applies (static visual treatment), only entrance animations are skipped",
|
|
||||||
"Typecheck passes",
|
"Typecheck passes",
|
||||||
"Verify in browser using dev-browser skill"
|
"Verify in browser using dev-browser skill"
|
||||||
],
|
],
|
||||||
"priority": 6,
|
"priority": 6,
|
||||||
"passes": true,
|
"passes": false,
|
||||||
"notes": "Currently App.tsx renders phases exclusively (only one at a time). Change so that login phase renders: <DetailPanelProvider><DashboardLayout /></DetailPanelProvider> + <LoginScreen overlay on top>. LoginScreen.tsx already has 'fixed inset-0 z-50' — just change its backgroundColor from solid #1A2B2A to the semi-transparent value with backdrop-filter. Consider adding will-change: backdrop-filter for performance."
|
"notes": "LoginScreen outer overlay currently has 'fixed inset-0 z-50'. TopBar is zIndex: 100. The overlay needs z-index > 100 to cover it. The login card inside the overlay doesn't need its own z-index since it's a child of the overlay. Check that the dissolve exit animation (isExiting) still works after the z-index change."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-007",
|
"id": "US-007",
|
||||||
"title": "Connection status indicator with animated dots and typing-linked timing",
|
"title": "Reduce backdrop blur intensity by ~50%",
|
||||||
"description": "As a visitor, I want to see a clear red-to-green status transition tied to the typing sequence, not an arbitrary timer.",
|
"description": "As a visitor, I want the backdrop blur to be softer so the dashboard behind is slightly more visible while still providing contrast for the login card.",
|
||||||
"acceptanceCriteria": [
|
"acceptanceCriteria": [
|
||||||
"Status indicator LED dot size increased from 6px to 10px",
|
"Blur value reduced from blur(20px) to approximately blur(10px)",
|
||||||
"LED dot has a subtle glow effect: box-shadow 0 0 6px 1px in the LED color (red or green)",
|
"The blur value is a named constant co-located with other LoginScreen timing constants for easy adjustment",
|
||||||
"Status text size increased from 10px to 12px",
|
"Login card remains clearly readable against the softened backdrop",
|
||||||
"Initial state: RED LED + 'Awaiting secure connection' in red (#DC2626) with animated trailing dots",
|
"The dissolve exit animation still animates blur from 10px to 0px",
|
||||||
"The trailing dots animate: dots cycle through '.', '..', '...' repeating every ~1.5 seconds",
|
|
||||||
"Remove the existing independent 2000ms connectionTimeout timer",
|
|
||||||
"Instead, connection transitions to green exactly 500ms after typingComplete becomes true",
|
|
||||||
"Green state: GREEN LED (#059669) + 'Secure connection established, awaiting login' in green",
|
|
||||||
"Transition between red and green states has a smooth 300ms color/shadow transition",
|
|
||||||
"prefers-reduced-motion: no dot cycling animation, state changes happen instantly",
|
|
||||||
"Typecheck passes",
|
"Typecheck passes",
|
||||||
"Verify in browser using dev-browser skill"
|
"Verify in browser using dev-browser skill"
|
||||||
],
|
],
|
||||||
"priority": 7,
|
"priority": 7,
|
||||||
"passes": true,
|
"passes": false,
|
||||||
"notes": "The connectionTimeout is set on line 117 of LoginScreen.tsx (2000ms independent timer). Remove it and add a useEffect that watches typingComplete — when true, setTimeout 500ms then setConnectionState('connected'). The dot animation can use a simple interval cycling dotCount 0→1→2→0. The LED glow box-shadow: '0 0 6px 1px rgba(220,38,38,0.4)' for red, '0 0 6px 1px rgba(5,150,105,0.4)' for green."
|
"notes": "The blur is in two places in LoginScreen.tsx: the initial style (backdropFilter: blur(20px)) and the exit animation (animates from blur(20px) to blur(0px)). Extract the blur value to a constant like BACKDROP_BLUR_PX = 10, then reference it in both places."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-008",
|
"id": "US-008",
|
||||||
"title": "Login button pulse animation on activation",
|
"title": "Align login card border radius and shadow with dashboard design system",
|
||||||
"description": "As a visitor, I want the login button to pulse subtly when it becomes clickable so I know to click it.",
|
"description": "As a visitor, I want the login card to feel like it belongs to the same design system as the dashboard by matching border radius and shadow tokens.",
|
||||||
"acceptanceCriteria": [
|
"acceptanceCriteria": [
|
||||||
"Add a CSS @keyframes animation 'login-pulse' in index.css: scale 1 → 1.03 → 1, ease-in-out, duration 1.5s",
|
"Login card border radius changed from 12px to 8px (matching var(--radius-card) / dashboard cards)",
|
||||||
"When canLogin becomes true (button enabled), apply the pulse animation repeating every 3 seconds (1.5s animation + 1.5s pause via animation-delay or longer duration with keyframe percentages)",
|
"Login input fields and button border radius changed from 4px to 6px (matching var(--radius-sm) / dashboard inner elements)",
|
||||||
"Pulse animation stops when button is hovered (animation: none on hover)",
|
"Login card shadow upgraded from shadow-sm to shadow-lg (0 8px 32px rgba(26,43,42,0.12)) — appropriate for a floating modal over blurred backdrop",
|
||||||
"Pulse animation stops immediately on click (remove animation class on buttonPressed)",
|
"Use CSS custom property references (var(--radius-card), var(--radius-sm)) where available rather than hardcoded values",
|
||||||
"Button opacity transitions from 0.6 to 1.0 when enabled (existing behavior, preserve)",
|
|
||||||
"prefers-reduced-motion: no pulse animation, button just becomes enabled with opacity 1",
|
|
||||||
"Button still receives keyboard focus when it becomes enabled (existing behavior)",
|
|
||||||
"Typecheck passes",
|
"Typecheck passes",
|
||||||
"Verify in browser using dev-browser skill"
|
"Verify in browser using dev-browser skill"
|
||||||
],
|
],
|
||||||
"priority": 8,
|
"priority": 8,
|
||||||
"passes": true,
|
"passes": false,
|
||||||
"notes": "The canLogin variable is on line 43 of LoginScreen.tsx. Add a CSS class 'login-pulse-active' that applies the animation, and conditionally apply it when canLogin && !buttonPressed && !buttonHovered. The @keyframes could use: 0%,100% { transform: scale(1) } 50% { transform: scale(1.03) } with animation: login-pulse 1.5s ease-in-out infinite and a wrapper that adds 1.5s gaps (or use 0%,35%,65%,100% keyframe percentages to build in the pause)."
|
"notes": "Check index.css for whether --radius-card and --radius-sm exist as CSS custom properties. If not, use the hardcoded values (8px and 6px) directly. The card shadow is currently set via inline style — update to the shadow-lg value. The login card borderRadius is in the card's inline style object."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-009",
|
"id": "US-009",
|
||||||
"title": "Login dissolve transition to reveal dashboard",
|
"title": "Replace hardcoded colors with design tokens",
|
||||||
"description": "As a visitor, I want the login card and blur overlay to dissolve smoothly on login, revealing the dashboard underneath.",
|
"description": "As a developer, I want the login screen to reference the same CSS custom properties as the dashboard so palette changes propagate consistently.",
|
||||||
"acceptanceCriteria": [
|
"acceptanceCriteria": [
|
||||||
"On login click: existing pressed state + loading spinner behavior is preserved",
|
"Input text color changed from hardcoded #111827 to var(--text-primary, #1A2B2A)",
|
||||||
"After loading spinner phase, the login card fades out (opacity 0) with slight scale up (1.03)",
|
"Cursor/caret color changed from hardcoded #0D6E6E to var(--accent, #0D6E6E)",
|
||||||
"Simultaneously, the overlay backdrop-filter blur animates from 20px to 0px",
|
"Button background colors changed from hardcoded #0D6E6E / #0A8080 / #085858 to var(--accent) / var(--accent-hover) / appropriate pressed variant using token references",
|
||||||
"Overlay background opacity fades from 0.7 to 0",
|
"Any other hardcoded color values in LoginScreen.tsx that have corresponding CSS custom properties use the token instead",
|
||||||
"Total dissolve duration: ~600ms from card exit to fully revealed dashboard",
|
"No visual change (token values resolve to same colors currently)",
|
||||||
"After dissolve completes, the login overlay is removed from DOM and dashboard becomes interactive",
|
"Typecheck passes"
|
||||||
"In App.tsx, transition from login to pmr phase after the overlay dissolve completes (use a callback from LoginScreen)",
|
|
||||||
"prefers-reduced-motion: instant transition, no dissolve animation",
|
|
||||||
"Typecheck passes",
|
|
||||||
"Verify in browser using dev-browser skill"
|
|
||||||
],
|
],
|
||||||
"priority": 9,
|
"priority": 9,
|
||||||
"passes": true,
|
"passes": false,
|
||||||
"notes": "Currently LoginScreen has isExiting state that scales card to 1.03 and fades to opacity 0 (line 163). Extend this to also animate the overlay container. The overlay is the outer div with 'fixed inset-0' — animate its backdrop-filter and background-color. Use framer-motion animate for coordinated exit. The onComplete callback should fire after the full dissolve, not after the card fade."
|
"notes": "Search LoginScreen.tsx for all hex color values (#xxxxxx) and check whether a corresponding CSS custom property exists in index.css. Some colors were already tokenized in the previous login rework (US-003 of previous run) — verify which ones are still hardcoded. The button has multiple color states (default, hover, pressed) — check all three."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-010",
|
"id": "US-010",
|
||||||
|
"title": "Fix minor typography inconsistencies",
|
||||||
|
"description": "As a visitor, I want the login screen's typography weight and sizing to feel consistent with the dashboard's conventions.",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Form label font weight increased from 500 to 600 (matching dashboard card header weight convention)",
|
||||||
|
"Input text mid-value aligned to ~14px to match dashboard body text",
|
||||||
|
"Button text mid-value aligned to ~15px",
|
||||||
|
"Connection status indicator gap increased from 6px to 8px (matching dashboard CardHeader gap)",
|
||||||
|
"No dramatic visual change — these are subtle alignment fixes",
|
||||||
|
"Typecheck passes"
|
||||||
|
],
|
||||||
|
"priority": 10,
|
||||||
|
"passes": false,
|
||||||
|
"notes": "These are small inline style tweaks in LoginScreen.tsx. The labels, inputs, and button already use clamp() for responsive sizing — just adjust the mid-values. The connection indicator gap is in the flex container styling near the bottom of the component."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "US-011",
|
||||||
"title": "Re-enable boot sequence",
|
"title": "Re-enable boot sequence",
|
||||||
"description": "As a user, I want the full boot → ECG → login → dashboard experience restored.",
|
"description": "As a user, I want the full boot → ECG → login → dashboard experience restored.",
|
||||||
"acceptanceCriteria": [
|
"acceptanceCriteria": [
|
||||||
"In src/App.tsx, change the initial Phase state back from 'login' to 'boot'",
|
"In src/App.tsx, change the initial Phase state back from 'login' to 'boot'",
|
||||||
"Boot → ECG → Login → Dashboard sequence works end to end",
|
"Boot → ECG → Login → Dashboard sequence works end to end",
|
||||||
"Login screen shows blurred dashboard behind it",
|
"Login screen shows blurred dashboard behind it with reduced blur and full TopBar coverage",
|
||||||
"Logo animation plays, typing animation follows, connection indicator transitions, button pulses",
|
"Logo animation plays with blend effect, typing animation follows, connection indicator transitions, button pulses",
|
||||||
"Clicking login dissolves the overlay to reveal the dashboard",
|
"Clicking login dissolves the overlay to reveal the dashboard",
|
||||||
"No other changes to App.tsx beyond reverting the initial state",
|
|
||||||
"Typecheck passes",
|
"Typecheck passes",
|
||||||
"Verify in browser using dev-browser skill: app starts at boot, progresses through ECG, login with blur background and logo animation, arrives at dashboard"
|
"Verify in browser using dev-browser skill"
|
||||||
],
|
],
|
||||||
"priority": 10,
|
"priority": 11,
|
||||||
"passes": true,
|
"passes": false,
|
||||||
"notes": "Simple revert of US-001. Phase state is on line 47 of App.tsx."
|
"notes": "Simple revert of US-001. Phase state is on line 47 of App.tsx."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+29
-152
@@ -1,5 +1,5 @@
|
|||||||
# Progress Log — Login Screen Rework
|
# Progress Log — Login Logo & Blur Refinements
|
||||||
# Branch: ralph/login-screen-rework
|
# Branch: ralph/login-logo-refinements
|
||||||
# Started: 2026-02-15
|
# Started: 2026-02-15
|
||||||
|
|
||||||
## Codebase Patterns
|
## Codebase Patterns
|
||||||
@@ -41,168 +41,45 @@
|
|||||||
- React 18.3.1, TypeScript, Vite, Tailwind CSS
|
- React 18.3.1, TypeScript, Vite, Tailwind CSS
|
||||||
- Framer Motion 11.15.0, Lucide React 0.468.0, fuse.js 7.0.0
|
- 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
|
### Key Files for This Feature
|
||||||
- src/App.tsx — phase management, will need restructuring for blur overlay
|
- src/components/CvmisLogo.tsx — logo component with animation (timing constants to extract)
|
||||||
- src/components/LoginScreen.tsx — main login screen (416 lines)
|
- src/components/LoginScreen.tsx — main login screen (overlay, blur, card styling)
|
||||||
- src/components/TopBar.tsx — Home icon replacement target (line 57)
|
- src/App.tsx — phase management (skip/restore boot sequence)
|
||||||
- src/components/DashboardLayout.tsx — rendered behind login blur
|
- src/index.css — CSS custom properties, design tokens
|
||||||
- src/contexts/DetailPanelContext.tsx — wraps DashboardLayout
|
|
||||||
- cvmis-logo.svg — source SVG with 3 capsule groups
|
|
||||||
- LogoReveal/frame 1-5.jpg — animation reference frames
|
|
||||||
|
|
||||||
### CvmisLogo Component
|
### CvmisLogo Component (from previous run)
|
||||||
- `size` prop: numeric, sets SVG height attribute directly
|
- `size` prop: numeric, sets SVG height attribute directly
|
||||||
- `cssHeight` prop: string, sets height via CSS style (use for clamp/responsive values)
|
- `cssHeight` prop: string, sets height via CSS style (use for clamp/responsive values)
|
||||||
- `animated` prop: boolean, enables framer-motion reveal animation (1000ms total)
|
- `animated` prop: boolean, enables framer-motion reveal animation (1000ms total)
|
||||||
- Logo animation: 500ms rise (green capsule) + 500ms fan-out (all three) = 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)
|
### LoginScreen.tsx State (from previous run)
|
||||||
- Line 20: connectionState useState
|
- Overlay: fixed inset-0 z-50, rgba(240, 245, 244, 0.7) + backdrop-filter: blur(20px)
|
||||||
- Line 21: dotCount useState (for animated trailing dots)
|
- TopBar is zIndex: 100 — currently renders ABOVE the z-50 overlay (bug)
|
||||||
- Line 43: canLogin derived state
|
- Card borderRadius: 12px, inputs/button borderRadius: 4px
|
||||||
- Line 60-101: startLoginSequence (typing animation)
|
- Some colors already tokenized (--surface, --accent, --bg-dashboard) from previous run
|
||||||
- Line 110-115: useEffect — connection transitions to green 500ms after typingComplete
|
- Some colors still hardcoded (#111827 input text, button bg states, caret color)
|
||||||
- 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)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2026-02-15 - US-010
|
## 2026-02-15 - US-001: Skip to login phase for dev iteration
|
||||||
- Reverted initial Phase state from 'login' back to 'boot' in App.tsx line 47
|
- Changed initial Phase state from `'boot'` to `'login'` in `src/App.tsx` line 47
|
||||||
- Full flow verified: boot → ECG → login (with blur, logo, typing, connection indicator, pulse) → dissolve → dashboard
|
- Files changed: `src/App.tsx`
|
||||||
- Files changed: src/App.tsx
|
|
||||||
- **Learnings for future iterations:**
|
- **Learnings for future iterations:**
|
||||||
- Simple one-line revert as planned in US-001
|
- Phase state is a simple `useState<Phase>` on line 47 of App.tsx
|
||||||
- The full boot→ECG→login sequence takes ~20 seconds before login screen appears
|
- 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
|
## 2026-02-15 - US-002: Extract animation timing into named constants
|
||||||
- Changed outer overlay container from plain `<div>` to `<motion.div>` for animated exit
|
- Extracted all inline timing values in CvmisLogo.tsx to named constants at top of file
|
||||||
- On isExiting: overlay animates backgroundColor to transparent, backdropFilter from blur(20px) to blur(0px) over 600ms
|
- 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
|
||||||
- Card exit animation extended from 200ms to 400ms for smoother dissolve feel
|
- Added overlap blend constants for US-005: OVERLAY_BLEND_START_PROGRESS, OVERLAP_BLEND_MAX_OPACITY, OVERLAP_BLEND_TRANSITION_DURATION_S (exported)
|
||||||
- onComplete callback fires after 600ms dissolve (previously 200ms card exit)
|
- Files changed: `src/components/CvmisLogo.tsx`
|
||||||
- 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
|
|
||||||
- **Learnings for future iterations:**
|
- **Learnings for future iterations:**
|
||||||
- framer-motion can animate backdropFilter and backgroundColor on a motion.div via the animate prop
|
- 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
|
||||||
- The onComplete timeout (600ms) must match the overlay dissolve duration, not the card fade duration
|
- 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
|
||||||
- Card fade (400ms) finishes before overlay dissolve (600ms), creating a layered reveal effect
|
- FAN_EASING was already a named constant before this story; it was left in place and grouped with the new fan constants
|
||||||
- WebkitBackdropFilter needs to be animated alongside backdropFilter for Safari
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 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'
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,30 @@ interface CvmisLogoProps {
|
|||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Animation timing constants ──────────────────────────────────────
|
||||||
|
// Rise phase: all pills rise together from below
|
||||||
|
const RISE_DURATION_MS = 500 // duration of the upward rise (ms)
|
||||||
|
const RISE_DURATION_S = RISE_DURATION_MS / 1000
|
||||||
|
const RISE_OPACITY_DURATION_S = 0.25 // opacity fade-in during rise (s)
|
||||||
|
const RISE_EASING: [number, number, number, number] = [0.33, 1, 0.68, 1]
|
||||||
|
const RISE_START_Y = 350 // initial Y offset (viewBox units)
|
||||||
|
|
||||||
|
// Fan phase: left and right pills fan outward
|
||||||
|
const FAN_DELAY_AFTER_RISE_MS = 500 // delay before fan begins (ms from mount)
|
||||||
|
const FAN_DURATION_S = 0.6 // duration of fan-out (s)
|
||||||
|
const FAN_EASING = 'cubic-bezier(0.34, 1.56, 0.64, 1)'
|
||||||
|
const FAN_ROTATION_DEG = 50 // rotation angle for fanned pills (±degrees)
|
||||||
|
const FAN_HORIZONTAL_PX = 16 // horizontal offset for fanned pills (±px)
|
||||||
|
const FAN_RIGHT_STAGGER_S = 0.03 // stagger delay for right pill (s)
|
||||||
|
|
||||||
|
// Total animation = rise delay + fan duration
|
||||||
|
const TOTAL_ANIMATION_MS = FAN_DELAY_AFTER_RISE_MS + FAN_DURATION_S * 1000
|
||||||
|
|
||||||
|
// Overlap blend: multiply blend on fanning capsules (used by US-005)
|
||||||
|
export const OVERLAY_BLEND_START_PROGRESS = 0.5 // fan progress at which blend fades in
|
||||||
|
export const OVERLAP_BLEND_MAX_OPACITY = 0.2 // max blend opacity (20%)
|
||||||
|
export const OVERLAP_BLEND_TRANSITION_DURATION_S = FAN_DURATION_S * (1 - OVERLAY_BLEND_START_PROGRESS)
|
||||||
|
|
||||||
// Pivot point: bottom-center of the pill stack (in viewBox coords)
|
// Pivot point: bottom-center of the pill stack (in viewBox coords)
|
||||||
const PX = 300
|
const PX = 300
|
||||||
const PY = 275
|
const PY = 275
|
||||||
@@ -23,7 +47,6 @@ function fanTransform(rotation: number, dx: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const IDENTITY_TRANSFORM = fanTransform(0, 0)
|
const IDENTITY_TRANSFORM = fanTransform(0, 0)
|
||||||
const FAN_EASING = 'cubic-bezier(0.34, 1.56, 0.64, 1)'
|
|
||||||
|
|
||||||
export function CvmisLogo({ size, cssHeight, animated = false, className }: CvmisLogoProps) {
|
export function CvmisLogo({ size, cssHeight, animated = false, className }: CvmisLogoProps) {
|
||||||
const prefersReducedMotion = useReducedMotion()
|
const prefersReducedMotion = useReducedMotion()
|
||||||
@@ -34,8 +57,8 @@ export function CvmisLogo({ size, cssHeight, animated = false, className }: Cvmi
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!animated || prefersReducedMotion) return
|
if (!animated || prefersReducedMotion) return
|
||||||
|
|
||||||
const fanTimer = setTimeout(() => setPhase('fanning'), 500)
|
const fanTimer = setTimeout(() => setPhase('fanning'), FAN_DELAY_AFTER_RISE_MS)
|
||||||
const doneTimer = setTimeout(() => setPhase('done'), 1000)
|
const doneTimer = setTimeout(() => setPhase('done'), TOTAL_ANIMATION_MS)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(fanTimer)
|
clearTimeout(fanTimer)
|
||||||
@@ -47,10 +70,10 @@ export function CvmisLogo({ size, cssHeight, animated = false, className }: Cvmi
|
|||||||
const isFanned = phase === 'fanning' || phase === 'done'
|
const isFanned = phase === 'fanning' || phase === 'done'
|
||||||
const fanTarget = isFanned || skip
|
const fanTarget = isFanned || skip
|
||||||
|
|
||||||
const leftTransform = fanTarget ? fanTransform(-50, -16) : IDENTITY_TRANSFORM
|
const leftTransform = fanTarget ? fanTransform(-FAN_ROTATION_DEG, -FAN_HORIZONTAL_PX) : IDENTITY_TRANSFORM
|
||||||
const rightTransform = fanTarget ? fanTransform(50, 16) : IDENTITY_TRANSFORM
|
const rightTransform = fanTarget ? fanTransform(FAN_ROTATION_DEG, FAN_HORIZONTAL_PX) : IDENTITY_TRANSFORM
|
||||||
const fanTransition = skip ? 'none' : `transform 0.6s ${FAN_EASING}`
|
const fanTransition = skip ? 'none' : `transform ${FAN_DURATION_S}s ${FAN_EASING}`
|
||||||
const fanTransitionDelayed = skip ? 'none' : `transform 0.6s ${FAN_EASING} 0.03s`
|
const fanTransitionDelayed = skip ? 'none' : `transform ${FAN_DURATION_S}s ${FAN_EASING} ${FAN_RIGHT_STAGGER_S}s`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
@@ -66,11 +89,11 @@ export function CvmisLogo({ size, cssHeight, animated = false, className }: Cvmi
|
|||||||
>
|
>
|
||||||
{/* Rise group — all pills rise together from below */}
|
{/* Rise group — all pills rise together from below */}
|
||||||
<motion.g
|
<motion.g
|
||||||
initial={skip ? false : { y: 350, opacity: 0 }}
|
initial={skip ? false : { y: RISE_START_Y, opacity: 0 }}
|
||||||
animate={{ y: 0, opacity: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
transition={{
|
transition={{
|
||||||
y: { duration: 0.5, ease: [0.33, 1, 0.68, 1] },
|
y: { duration: RISE_DURATION_S, ease: RISE_EASING },
|
||||||
opacity: { duration: 0.25 },
|
opacity: { duration: RISE_OPACITY_DURATION_S },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Rx pill — teal, fans left (bottom layer) */}
|
{/* Rx pill — teal, fans left (bottom layer) */}
|
||||||
|
|||||||
Reference in New Issue
Block a user