From 8164051e85988cd61ca98ef072be4f40aaa9591b Mon Sep 17 00:00:00 2001 From: VinnyNC Date: Mon, 29 Sep 2025 19:47:45 -0400 Subject: [PATCH] Complete Phase 4: Interactive Enhancements - Implemented transitions system, animations, loading states, loading indicators, hover states, form validation, notification variants, micro-interactions, accessibility improvements --- static/css/components.css | 195 ++++++++++++++++++++++++++++++++++-- static/css/utilities.css | 206 +++++++++++++++++++++++++++++++++++++- 2 files changed, 390 insertions(+), 11 deletions(-) diff --git a/static/css/components.css b/static/css/components.css index cb5eebd..922b05e 100644 --- a/static/css/components.css +++ b/static/css/components.css @@ -354,6 +354,11 @@ 100% { opacity: 1; } } +@keyframes skeleton-shimmer { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } +} + /* Base skeleton styles */ .skeleton { background: linear-gradient(90deg, var(--color-surface-variant) 25%, var(--color-surface) 50%, var(--color-surface-variant) 75%); @@ -362,11 +367,6 @@ border-radius: var(--border-radius-sm); } -@keyframes skeleton-shimmer { - 0% { background-position: -200% 0; } - 100% { background-position: 200% 0; } -} - /* Specific skeleton types */ .skeleton-text { height: var(--font-size-base); @@ -437,7 +437,7 @@ pointer-events: auto; } -/* Loading spinner */ +/* Loading spinner variants */ .loading-spinner { width: 40px; height: 40px; @@ -447,7 +447,59 @@ animation: spin 1s linear infinite; } -/* Loading bar */ +.loading-spinner--small { + width: 24px; + height: 24px; + border-width: 2px; +} + +.loading-spinner--large { + width: 56px; + height: 56px; + border-width: 4px; +} + +.loading-spinner--secondary { + border-top-color: var(--color-secondary); +} + +.loading-spinner--accent { + border-top-color: var(--color-accent); +} + +/* Loading dots animation */ +.loading-dots { + display: inline-flex; + gap: var(--spacing-1); + align-items: center; +} + +.loading-dots::before, +.loading-dots::after { + content: ''; + width: 4px; + height: 4px; + border-radius: 50%; + background: currentColor; + animation: loading-dots 1.4s infinite ease-in-out; +} + +.loading-dots::after { + animation-delay: -0.16s; +} + +@keyframes loading-dots { + 0%, 80%, 100% { + transform: scale(0); + opacity: 0.5; + } + 40% { + transform: scale(1); + opacity: 1; + } +} + +/* Loading bar variants */ .loading-bar { width: 100%; height: 3px; @@ -471,6 +523,78 @@ 100% { transform: translateX(100%); } } +.loading-bar--primary { + background: linear-gradient(90deg, transparent, var(--color-primary), transparent); +} + +.loading-bar--secondary { + background: linear-gradient(90deg, transparent, var(--color-secondary), transparent); +} + +/* Progress indicators */ +.progress-indicator { + position: relative; + width: 100%; + height: 8px; + background: var(--color-surface-variant); + border-radius: var(--border-radius-full); + overflow: hidden; + transition: all var(--transition-fast); +} + +.progress-indicator .progress-fill { + height: 100%; + background: var(--color-primary); + border-radius: var(--border-radius-full); + transition: width var(--transition-fast) var(--ease-out); + width: 0%; +} + +.progress-indicator.complete .progress-fill { + background: var(--color-success); +} + +/* Ring loader */ +.ring-loader { + width: 48px; + height: 48px; + border: 3px solid var(--color-surface-variant); + border-top: 3px solid var(--color-primary); + border-right: 3px solid var(--color-primary); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +/* Bounce loader */ +.bounce-loader { + display: inline-flex; + gap: var(--spacing-1); + align-items: center; +} + +.bounce-loader div { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--color-primary); + animation: bounce-loader-bounce 1.4s ease-in-out infinite; +} + +.bounce-loader div:nth-child(1) { animation-delay: -0.32s; } +.bounce-loader div:nth-child(2) { animation-delay: -0.16s; } +.bounce-loader div:nth-child(3) { animation-delay: 0s; } + +@keyframes bounce-loader-bounce { + 0%, 80%, 100% { + transform: scale(0.8); + opacity: 0.5; + } + 40% { + transform: scale(1); + opacity: 1; + } +} + /* ================================================================= STATEFUL COMPONENTS - Interactive States ================================================================= */ @@ -1051,12 +1175,69 @@ z-index: var(--z-toast); opacity: 0; transition: opacity var(--transition-fast); + box-shadow: var(--shadow-lg); + border-left: 4px solid transparent; } .toast.show { opacity: 1; } +/* Toast variants */ +.toast--success { + background: rgba(76, 175, 80, 0.9); + border-left-color: #4caf50; +} + +.toast--error { + background: rgba(244, 67, 54, 0.9); + border-left-color: #f44336; +} + +.toast--warning { + background: rgba(255, 152, 0, 0.9); + border-left-color: #ff9800; +} + +.toast--info { + background: rgba(33, 150, 243, 0.9); + border-left-color: #2196f3; +} + +/* Toast queue states */ +.toast--queued { + transform: translateX(-50%) translateY(20px); +} + +.toast--queued.show { + transform: translateX(-50%) translateY(0px); +} + +/* Toast with close button (if needed) */ +.toast--closable { + padding-right: var(--spacing-8); + position: relative; +} + +.toast-close-btn { + position: absolute; + right: var(--spacing-3); + top: 50%; + transform: translateY(-50%); + background: transparent; + border: none; + color: currentColor; + cursor: pointer; + padding: var(--spacing-1); + border-radius: var(--border-radius-sm); + opacity: 0.7; + transition: opacity var(--transition-fast); +} + +.toast-close-btn:hover { + opacity: 1; +} + .video-player__notification-badge { position: absolute; top: calc(var(--spacing-2) * -1); diff --git a/static/css/utilities.css b/static/css/utilities.css index 8a985b8..b2d5a4f 100644 --- a/static/css/utilities.css +++ b/static/css/utilities.css @@ -15,6 +15,61 @@ } } +@keyframes slideOut { + to { + opacity: 0; + transform: translateX(100%); + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes fadeOut { + to { + opacity: 0; + } +} + +@keyframes slideInFromBottom { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideInFromTop { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} + @keyframes pulse { 0%, 100% { opacity: 1; @@ -42,11 +97,154 @@ } } -@keyframes slideOut { - to { - opacity: 0; - transform: translateX(100%); +@keyframes spin { + from { + transform: rotate(0deg); } + to { + transform: rotate(360deg); + } +} + +@keyframes shimmer { + 0% { + background-position: -468px 0; + } + 100% { + background-position: 468px 0; + } +} + +/* ================================================================= + TRANSITIONS UTILITY SYSTEM - Consistent transitions + ================================================================= */ + +/* Transition duration constants */ +.transition-none { + transition: none; +} + +.transition-fast { + transition-duration: var(--transition-fast); +} + +.transition-base { + transition-duration: var(--transition-base); +} + +.transition-slow { + transition-duration: var(--transition-slow); +} + +/* Transition properties - individual */ +.transition-all { + transition-property: all; + transition-duration: var(--transition-base); + transition-timing-function: var(--ease-in-out); +} + +.transition-colors { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-duration: var(--transition-base); + transition-timing-function: var(--ease-in-out); +} + +.transition-transform { + transition-property: transform; + transition-duration: var(--transition-base); + transition-timing-function: var(--ease-in-out); +} + +.transition-opacity { + transition-property: opacity; + transition-duration: var(--transition-base); + transition-timing-function: var(--ease-in-out); +} + +.transition-shadow { + transition-property: box-shadow; + transition-duration: var(--transition-base); + transition-timing-function: var(--ease-in-out); +} + +.transition-hover { + transition: all var(--transition-base) var(--ease-in-out); +} + +/* Micro-interaction feedback */ +.hover-lift { + transition: transform var(--transition-fast) var(--ease-out); +} + +.hover-lift:hover { + transform: translateY(-2px); +} + +.interaction-press { + transition: transform var(--transition-fast) var(--ease-out); +} + +.interaction-press:active { + transform: translateY(0) scale(0.98); +} + +/* ================================================================= + ANIMATION UTILITY CLASSES - Enter/exit animations + ================================================================= */ + +/* Slide animations */ +.animate-slide-in { + animation: slideIn var(--transition-slow) var(--ease-in-out); +} + +.animate-slide-out { + animation: slideOut var(--transition-fast) var(--ease-in-out); +} + +.animate-fade-in { + animation: fadeIn var(--transition-base) var(--ease-in-out); +} + +.animate-fade-out { + animation: fadeOut var(--transition-fast) var(--ease-in-out); +} + +.animate-slide-in-bottom { + animation: slideInFromBottom var(--transition-base) var(--ease-out); +} + +.animate-slide-in-top { + animation: slideInFromTop var(--transition-base) var(--ease-out); +} + +.animate-scale-in { + animation: scaleIn var(--transition-base) var(--ease-out); +} + +/* Continuous animations */ +.animate-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +.animate-bounce { + animation: bounce 1s infinite; +} + +.animate-spin { + animation: spin 1s linear infinite; +} + +/* Delayed animations for staggered effects */ +.animate-delay-1 { + animation-delay: 0.1s; +} + +.animate-delay-2 { + animation-delay: 0.2s; +} + +.animate-delay-3 { + animation-delay: 0.3s; } /* =================================================================