diff --git a/src/app/globals.css b/src/app/globals.css index fd1152d..133b992 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -418,6 +418,7 @@ html { .app-item { opacity: 0; transform: translateY(-20px); + will-change: transform, opacity; } .header-animate { @@ -475,6 +476,18 @@ html { } } +@keyframes footerSlideUp { + 0% { + opacity: 0; + transform: translateY(100%); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +} + @keyframes slideUp { 0% { opacity: 0; @@ -487,6 +500,40 @@ html { } } +@keyframes modalSlideIn { + 0% { + opacity: 0; + transform: translate(-50%, -48%) scale(0.96); + } + + 100% { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } +} + +@keyframes modalSlideOut { + 0% { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + + 100% { + opacity: 0; + transform: translate(-50%, -48%) scale(0.96); + } +} + +@keyframes fadeOut { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + @keyframes slideDown { 0% { opacity: 1; @@ -554,6 +601,51 @@ html { } } +/* Slide in from right edge */ +@keyframes slideInFromRight { + 0% { + opacity: 0; + transform: translateX(100px); + } + + 70% { + transform: translateX(-5px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slideInFromRightSecond { + 0% { + opacity: 0; + transform: translateX(80px); + } + + 70% { + transform: translateX(-3px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slideOutToRight { + 0% { + opacity: 1; + transform: translateX(0); + } + + 100% { + opacity: 0; + transform: translateX(50px); + } +} + @keyframes tooltipSlideUp { 0% { opacity: 0; @@ -618,4 +710,40 @@ body.theme-flash::after { pointer-events: none; background: var(--text-primary); animation: themeFlash 0.15s ease-out forwards; +} + +/* ===== SKELETON LOADING ANIMATION ===== */ + +@keyframes skeletonPulse { + + 0%, + 100% { + opacity: 0.4; + } + + 50% { + opacity: 0.7; + } +} + +.skeleton-pulse { + animation: skeletonPulse 1.5s ease-in-out infinite; + will-change: opacity; +} + +/* Staggered skeleton items for wave effect */ +.skeleton-item { + animation: skeletonPulse 1.5s ease-in-out infinite; + will-change: opacity; +} + +/* Force GPU acceleration for animated elements */ +.category-header, +.app-item, +.header-animate, +.header-controls { + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; } \ No newline at end of file diff --git a/src/components/app/AppItem.tsx b/src/components/app/AppItem.tsx index 2615640..8a72237 100644 --- a/src/components/app/AppItem.tsx +++ b/src/components/app/AppItem.tsx @@ -64,11 +64,10 @@ export const AppItem = memo(function AppItem({ onTooltipLeave, onFocus, }: AppItemProps) { - // Build unavailable tooltip text + // Build unavailable tooltip text (just the reason, no description) const getUnavailableText = () => { - if (app.unavailableReason) return app.unavailableReason; const distroName = distros.find(d => d.id === selectedDistro)?.name || ''; - return `Not available in ${distroName} repos`; + return app.unavailableReason || `Not available in ${distroName} repos`; }; const isAur = selectedDistro === 'arch' && app.targets?.arch && isAurPackage(app.targets.arch); @@ -98,10 +97,11 @@ export const AppItem = memo(function AppItem({ } }} onMouseEnter={(e) => { - if (isAvailable) onTooltipEnter(app.description, e); + // Show description tooltip for all apps (available and unavailable) + onTooltipEnter(app.description, e); }} onMouseLeave={() => { - if (isAvailable) onTooltipLeave(); + onTooltipLeave(); }} >
{ if (!sectionRef.current || hasAnimated.current) return; hasAnimated.current = true; @@ -67,27 +67,33 @@ function CategorySectionComponent({ const header = section.querySelector('.category-header'); const items = section.querySelectorAll('.app-item'); - // Initial state - gsap.set(header, { clipPath: 'inset(0 100% 0 0)' }); - gsap.set(items, { y: -20, opacity: 0 }); + // Use requestAnimationFrame for smoother initial setup + requestAnimationFrame(() => { + // Initial state with GPU-accelerated transforms + gsap.set(header, { clipPath: 'inset(0 100% 0 0)' }); + gsap.set(items, { y: -15, opacity: 0, force3D: true }); - // Animate with staggered delay based on category index - const delay = categoryIndex * 0.08; + // Staggered delay based on category index (reduced for faster feel) + const delay = categoryIndex * 0.05; - gsap.to(header, { - clipPath: 'inset(0 0% 0 0)', - duration: 0.9, - ease: 'power3.out', - delay: delay + 0.1 - }); + // Animate header with clip-path reveal + gsap.to(header, { + clipPath: 'inset(0 0% 0 0)', + duration: 0.6, + ease: 'power2.out', + delay: delay + 0.05 + }); - gsap.to(items, { - y: 0, - opacity: 1, - duration: 0.8, - stagger: 0.04, - ease: 'expo.out', - delay: delay + 0.2 + // Animate items with GPU-accelerated transforms + gsap.to(items, { + y: 0, + opacity: 1, + duration: 0.5, + stagger: 0.025, + ease: 'power2.out', + delay: delay + 0.1, + force3D: true + }); }); }, [categoryIndex]); diff --git a/src/components/command/AurDrawerSettings.tsx b/src/components/command/AurDrawerSettings.tsx index 66adc7d..f29f1c1 100644 --- a/src/components/command/AurDrawerSettings.tsx +++ b/src/components/command/AurDrawerSettings.tsx @@ -1,7 +1,5 @@ 'use client'; -import { Package, Download, Terminal } from 'lucide-react'; - interface AurDrawerSettingsProps { aurAppNames: string[]; hasYayInstalled: boolean; @@ -11,7 +9,7 @@ interface AurDrawerSettingsProps { } /** - * AurDrawerSettings - Settings panel for AUR configuration inside the drawer + * AurDrawerSettings - Compact UI with smooth button animations */ export function AurDrawerSettings({ aurAppNames, @@ -21,95 +19,81 @@ export function AurDrawerSettings({ setSelectedHelper, }: AurDrawerSettingsProps) { return ( -
- {/* Header */} -
-
- -
-
-

- AUR Packages Detected -

-

- These apps require an AUR helper: {aurAppNames.join(', ')} -

-
+
+ {/* Header with all apps listed */} +
+

+ AUR packages: + {aurAppNames.join(', ')} +

- {/* Controls Grid */} -
- - {/* 1. Installation Mode */} -
- -
- - -
-

- {hasYayInstalled - ? "Script will use your existing helper" - : "Script will install the helper first"} -

-
- - {/* 2. Helper Selection */} -
- -
+ {/* Controls with animated buttons */} +
+ {/* Helper selection */} +
+ AUR helper: +
-
+ {/* Divider */} +
+ + {/* Installation mode */} +
+ Already installed? +
+ + +
+
); diff --git a/src/components/command/AurFloatingCard.tsx b/src/components/command/AurFloatingCard.tsx index 7774063..1316e9b 100644 --- a/src/components/command/AurFloatingCard.tsx +++ b/src/components/command/AurFloatingCard.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { X } from 'lucide-react'; interface AurFloatingCardProps { @@ -31,19 +31,19 @@ export function AurFloatingCard({ const [hasAnswered, setHasAnswered] = useState(null); // Track if user has selected a helper (completed flow) const [helperChosen, setHelperChosen] = useState(false); - // Track if user has interacted (dismissed or selected) to prevent nagging - const [userInteracted, setUserInteracted] = useState(false); + // Track if user has interacted (dismissed or selected) to prevent nagging - use ref to persist + const userInteractedRef = useRef(false); // Reset when new AUR packages appear, BUT ONLY if user hasn't interacted yet useEffect(() => { - if (show && aurAppNames.length > 0 && !userInteracted) { + if (show && aurAppNames.length > 0 && !userInteractedRef.current) { setDismissed(false); setIsExiting(false); setShowConfirmation(false); setHelperChosen(false); setHasAnswered(null); } - }, [aurAppNames.length, show, userInteracted]); + }, [aurAppNames.length, show]); if (!show || dismissed) return null; @@ -55,7 +55,7 @@ export function AurFloatingCard({ const handleHelperSelect = (helper: 'yay' | 'paru') => { setSelectedHelper(helper); setHelperChosen(true); - setUserInteracted(true); // Don't ask again + userInteractedRef.current = true; // Don't ask again // Start exit animation after a brief moment setTimeout(() => { @@ -67,7 +67,7 @@ export function AurFloatingCard({ }; const handleDismiss = () => { - setUserInteracted(true); // Don't ask again + userInteractedRef.current = true; // Don't ask again setIsExiting(true); setTimeout(() => { setDismissed(true); @@ -87,11 +87,11 @@ export function AurFloatingCard({ }, 3000); return ( -
+

You can change this later in preview tab @@ -103,17 +103,17 @@ export function AurFloatingCard({ // Hide cards while exiting if (isExiting && helperChosen) { return ( -

+
{hasAnswered !== null && (
@@ -123,7 +123,7 @@ export function AurFloatingCard({ } return ( -
+
{/* Card 1: Do you have an AUR helper? */}
{/* Header */} @@ -207,8 +207,8 @@ export function AurFloatingCard({ `} style={{ animation: isExiting - ? 'cardSlideOut 0.15s ease-out forwards' - : 'cardSlideInSecond 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) 0.05s forwards', + ? 'slideOutToRight 0.15s ease-out forwards' + : 'slideInFromRightSecond 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) 0.05s forwards', opacity: 0 }} > diff --git a/src/components/command/CommandFooter.tsx b/src/components/command/CommandFooter.tsx index d5f311e..d6c4a9d 100644 --- a/src/components/command/CommandFooter.tsx +++ b/src/components/command/CommandFooter.tsx @@ -166,8 +166,8 @@ export function CommandFooter({ }; return ( -
- {/* AUR Floating Card - appears when AUR packages selected */} + <> + {/* AUR Floating Card - appears when AUR packages selected (outside animated container) */} - {/* Footer container with strong outer glow */} -
- {/* Outer glow - large spread */} -
- {/* Middle glow layer */} -
- {/* Inner glow - sharp */} -
- - {/* Bars container */} -
- {/* Shortcuts Bar with Search (nvim-style) */} - + {/* Backdrop */} +