Repository navigation
feat: add orchestrated page animations and micro-interactions #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ef86c51
0908d96
79435dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| /* ===== Layout ===== */ | ||
| .container { | ||
| display: grid; | ||
| grid-template-columns: 1fr 1fr; | ||
| /* Asymmetrical grid for visual interest */ | ||
| grid-template-columns: 0.9fr 1fr; | ||
|
Comment on lines
+4
to
+5
|
||
| min-height: 100vh; | ||
| gap: 0; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,16 +1,104 @@ | ||||||||||||||||||||||||||
| /* ===== Animations ===== */ | ||||||||||||||||||||||||||
| @keyframes fadeInUp { | ||||||||||||||||||||||||||
| /* ===== Keyframe Animations ===== */ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /* Slide in from left (form section) */ | ||||||||||||||||||||||||||
| @keyframes slideInFromLeft { | ||||||||||||||||||||||||||
| from { | ||||||||||||||||||||||||||
| opacity: 0; | ||||||||||||||||||||||||||
| transform: translateY(8px); | ||||||||||||||||||||||||||
| transform: translateX(-24px); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| to { | ||||||||||||||||||||||||||
| opacity: 1; | ||||||||||||||||||||||||||
| transform: translateX(0); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /* Slide in from right (invoice preview) */ | ||||||||||||||||||||||||||
| @keyframes slideInFromRight { | ||||||||||||||||||||||||||
| from { | ||||||||||||||||||||||||||
| opacity: 0; | ||||||||||||||||||||||||||
| transform: translateX(24px); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| to { | ||||||||||||||||||||||||||
| opacity: 1; | ||||||||||||||||||||||||||
| transform: translateX(0); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /* Fade up for content blocks */ | ||||||||||||||||||||||||||
| @keyframes fadeInUp { | ||||||||||||||||||||||||||
| from { | ||||||||||||||||||||||||||
| opacity: 0; | ||||||||||||||||||||||||||
| transform: translateY(12px); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| to { | ||||||||||||||||||||||||||
| opacity: 1; | ||||||||||||||||||||||||||
| transform: translateY(0); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /* Scale in for panels/modals */ | ||||||||||||||||||||||||||
| @keyframes scaleIn { | ||||||||||||||||||||||||||
| from { | ||||||||||||||||||||||||||
| opacity: 0; | ||||||||||||||||||||||||||
| transform: scale(0.96); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| to { | ||||||||||||||||||||||||||
| opacity: 1; | ||||||||||||||||||||||||||
| transform: scale(1); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /* Focus ring pulse */ | ||||||||||||||||||||||||||
| @keyframes focusRingPulse { | ||||||||||||||||||||||||||
| 0% { | ||||||||||||||||||||||||||
| box-shadow: 0 0 0 0 rgba(var(--primary-rgb), 0.4); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| 100% { | ||||||||||||||||||||||||||
| box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.15); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /* ===== Page Entry Orchestration ===== */ | ||||||||||||||||||||||||||
| .form-section { | ||||||||||||||||||||||||||
| animation: slideInFromLeft 0.5s var(--ease-out-expo) forwards; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| .invoice-section, | ||||||||||||||||||||||||||
| .invoice-placeholder { | ||||||||||||||||||||||||||
| animation: slideInFromRight 0.5s var(--ease-out-expo) 0.08s backwards; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /* ===== Staggered Form Block Entry ===== */ | ||||||||||||||||||||||||||
| .form-block { | ||||||||||||||||||||||||||
| animation: fadeInUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| animation: fadeInUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; | |
| animation: fadeInUp 0.4s var(--ease-out-expo) both; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep form blocks visible after entry animation
Because main.css imports utilities before components, the later .form-block rule in components/forms.css overrides the animation here and drops the forwards fill mode. With opacity: 0 still coming from this rule, once the entry animation finishes (normal motion, non-reduced), the blocks revert to opacity 0 and effectively disappear. Consider removing the base opacity or ensuring the final animation rule keeps forwards/sets opacity: 1.
Useful? React with 👍 / 👎.
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The animation delays are hardcoded here, but CSS variables for stagger delays (--stagger-1 through --stagger-6) are defined in variables.css. Consider using these variables instead to improve maintainability. For example: animation-delay: var(--stagger-1); for the first child, animation-delay: var(--stagger-2); for the second, etc.
| .form-block:nth-child(1) { animation-delay: 0.1s; } | |
| .form-block:nth-child(2) { animation-delay: 0.15s; } | |
| .form-block:nth-child(3) { animation-delay: 0.2s; } | |
| .form-block:nth-child(4) { animation-delay: 0.25s; } | |
| .form-block:nth-child(5) { animation-delay: 0.3s; } | |
| .form-block:nth-child(6) { animation-delay: 0.35s; } | |
| .form-block:nth-child(1) { animation-delay: var(--stagger-1); } | |
| .form-block:nth-child(2) { animation-delay: var(--stagger-2); } | |
| .form-block:nth-child(3) { animation-delay: var(--stagger-3); } | |
| .form-block:nth-child(4) { animation-delay: var(--stagger-4); } | |
| .form-block:nth-child(5) { animation-delay: var(--stagger-5); } | |
| .form-block:nth-child(6) { animation-delay: var(--stagger-6); } |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The focusRingPulse animation sets box-shadow values that will override the static box-shadow defined in forms.css line 146 (.form-group input:focus has box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.12);). The animation starts with box-shadow: 0 0 0 0 rgba(var(--primary-rgb), 0.4) and ends with box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.15). This means after the animation completes, the box-shadow will be 0 0 0 3px rgba(99, 102, 241, 0.15) instead of the expected 0 0 0 3px rgba(99, 102, 241, 0.12) from the :focus rule. Consider adjusting the animation's final state to match the static focus style, or adjust the static style to use the animation's end state.
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reduced motion media query only explicitly sets opacity for .form-block elements, but other animated elements (.form-section, .invoice-section, .invoice-placeholder, .item-row, .history-panel) also start with opacity: 0 in their animations. When animations are disabled with animation-duration: 0.01ms, these elements might flash briefly at opacity 0 or may have visibility issues. Consider adding explicit opacity rules for all animated elements within the reduced motion media query to ensure they're fully visible, similar to what's done for .form-block.
| .form-block { | |
| .form-block, | |
| .form-section, | |
| .invoice-section, | |
| .invoice-placeholder, | |
| .item-row, | |
| .history-panel { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The stagger delay variables defined in this file are not being used. In animations.css, the animation delays are hardcoded (0.1s, 0.15s, 0.2s, etc.) instead of using these CSS variables. Consider either removing these unused variables or updating the animation delays in animations.css to use them (e.g.,
animation-delay: var(--stagger-1);). This would improve maintainability by centralizing the delay values.