From 238b1c9a871557278a6a962d85b1e59cbbe101c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Mora?= Date: Tue, 23 Dec 2025 19:22:50 -0500 Subject: [PATCH] feat(design): enhance UI with animations, gradients, and hover effects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add AnimatedBackground component with canvas-based floating orbs - Add reusable Button component with shimmer effects and variants - Update globals.css with animation keyframes and utility classes - Enhance Header with glass effect and animated nav underlines - Enhance Footer with gradient overlays and hover effects - Update all pages with animated gradient backgrounds - Add hover effects on cards, icons, and buttons - Implement gradient text effects throughout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- app/about/page.tsx | 219 +++-- app/components/AnimatedBackground.tsx | 122 +++ app/components/Button.tsx | 89 ++ app/components/Footer.tsx | 53 +- app/components/Header.tsx | 77 +- app/contact/page.tsx | 145 +-- app/globals.css | 208 +++++ app/page.tsx | 161 ++-- app/services/page.tsx | 93 +- app/solutions/page.tsx | 145 +-- yarn.lock | 1177 +++++++++---------------- 11 files changed, 1348 insertions(+), 1141 deletions(-) create mode 100644 app/components/AnimatedBackground.tsx create mode 100644 app/components/Button.tsx diff --git a/app/about/page.tsx b/app/about/page.tsx index 8f687c1..d53b067 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -1,4 +1,5 @@ -import Link from "next/link"; +"use client"; + import { Award, CheckCircle, @@ -11,12 +12,8 @@ import { GraduationCap, BadgeCheck, } from "lucide-react"; - -export const metadata = { - title: "About Us | AB Group LLC", - description: - "Learn about AB Group LLC, Florida's trusted QuickBooks consulting firm since 2000. Meet our team and discover our certifications.", -}; +import AnimatedBackground from "../components/AnimatedBackground"; +import Button from "../components/Button"; const timeline = [ { @@ -91,21 +88,28 @@ export default function AboutPage() { return ( <> {/* Hero Section */} -
-
+
+ +
-

About Us

-

- Your Trusted QuickBooks{" "} +

+ About Us +

+

+ Your Trusted{" "} + + QuickBooks + {" "} Partner Since 2000

-

+

We are a Florida-based consulting group dedicated to supporting small and mid-sized businesses with expert accounting and business solutions.

+
{/* Mission Section */} @@ -126,30 +130,39 @@ export default function AboutPage() { consultants, our primary task is to give you an array of tools that will help you grow your business.

-
-
- +
+
+
+ +
Florida, USA
-
- +
+
+ +
Est. 2000
-
-
- “There is a right way to do business. To succeed - you'll need a solid idea, a great team, and ultimately the - right execution.” -
-
-
- NA -
-
-

Nicolle Alcazar, MSF

-

President, AB Group LLC

+
+
+
+
+ “There is a right way to do business. To succeed + you'll need a solid idea, a great team, and ultimately + the right execution.” +
+
+
+ NA +
+
+

Nicolle Alcazar, MSF

+

+ President, AB Group LLC +

+
@@ -170,15 +183,16 @@ export default function AboutPage() {
- {values.map((value) => ( + {values.map((value, index) => (
-
- +
+
-

+

{value.title}

{value.description}

@@ -202,17 +216,17 @@ export default function AboutPage() {
{timeline.map((item, index) => ( -
+
-
+
{item.year}
{index < timeline.length - 1 && ( -
+
)}
-

+

{item.title}

{item.description}

@@ -224,10 +238,13 @@ export default function AboutPage() {
{/* Credentials Section */} -
-
+
+ +
- +
+ +

Professional Credentials

@@ -238,25 +255,35 @@ export default function AboutPage() {
- {credentials.map((group) => ( -
-

+ {credentials.map((group, groupIndex) => ( +
+

{group.category === "Professional Degrees" && ( - + )} {group.category === "Intuit Certifications" && ( - + )} {group.category === "Other Certifications" && ( - + )} {group.category}

    - {group.items.map((item) => ( -
  • - - {item} + {group.items.map((item, idx) => ( +
  • + + + {item} +
  • ))}
@@ -276,36 +303,40 @@ export default function AboutPage() {
-
-
- NA -
-

- Nicolle Alcazar, MSF -

-

- President & Lead Consultant -

-

- With a Master of Science in Finance and extensive certifications - as an Intuit Solution Provider and Advanced ProAdvisor, Nicolle - brings over 20 years of experience helping businesses optimize - their accounting and financial systems. -

-
- {[ - "MSF", - "ISP", - "Advanced ProAdvisor", - "Enterprise Certified", - ].map((badge) => ( - - {badge} - - ))} +
+
+
+
+ NA +
+

+ Nicolle Alcazar, MSF +

+

+ President & Lead Consultant +

+

+ With a Master of Science in Finance and extensive + certifications as an Intuit Solution Provider and Advanced + ProAdvisor, Nicolle brings over 20 years of experience helping + businesses optimize their accounting and financial systems. +

+
+ {[ + "MSF", + "ISP", + "Advanced ProAdvisor", + "Enterprise Certified", + ].map((badge, index) => ( + + {badge} + + ))} +
@@ -313,9 +344,13 @@ export default function AboutPage() {

{/* Se Habla Espanol */} -
-
-

+

+
+
+
+
+
+

Se Habla Espanol

@@ -326,8 +361,9 @@ export default function AboutPage() {

{/* CTA Section */} -
-
+
+ +

@@ -337,13 +373,14 @@ export default function AboutPage() { Let's discuss how we can help your business succeed.

- Contact Us - +
diff --git a/app/components/AnimatedBackground.tsx b/app/components/AnimatedBackground.tsx new file mode 100644 index 0000000..2f9001c --- /dev/null +++ b/app/components/AnimatedBackground.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { useEffect, useRef } from "react"; + +interface Orb { + x: number; + y: number; + size: number; + speedX: number; + speedY: number; + opacity: number; +} + +interface AnimatedBackgroundProps { + variant?: "dark" | "light" | "red"; + orbCount?: number; + className?: string; +} + +export default function AnimatedBackground({ + variant = "dark", + orbCount = 5, + className = "", +}: AnimatedBackgroundProps) { + const canvasRef = useRef(null); + const orbsRef = useRef([]); + const animationRef = useRef(undefined); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + const resizeCanvas = () => { + const parent = canvas.parentElement; + if (parent) { + canvas.width = parent.offsetWidth; + canvas.height = parent.offsetHeight; + } + }; + + resizeCanvas(); + window.addEventListener("resize", resizeCanvas); + + // Initialize orbs + orbsRef.current = Array.from({ length: orbCount }, () => ({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + size: Math.random() * 200 + 100, + speedX: (Math.random() - 0.5) * 0.5, + speedY: (Math.random() - 0.5) * 0.5, + opacity: Math.random() * 0.3 + 0.1, + })); + + const getOrbColor = (opacity: number) => { + switch (variant) { + case "dark": + return `rgba(220, 38, 38, ${opacity})`; + case "light": + return `rgba(220, 38, 38, ${opacity * 0.5})`; + case "red": + return `rgba(255, 255, 255, ${opacity * 0.3})`; + default: + return `rgba(220, 38, 38, ${opacity})`; + } + }; + + const animate = () => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + orbsRef.current.forEach((orb) => { + // Update position + orb.x += orb.speedX; + orb.y += orb.speedY; + + // Bounce off edges + if (orb.x < -orb.size) orb.x = canvas.width + orb.size; + if (orb.x > canvas.width + orb.size) orb.x = -orb.size; + if (orb.y < -orb.size) orb.y = canvas.height + orb.size; + if (orb.y > canvas.height + orb.size) orb.y = -orb.size; + + // Draw orb with gradient + const gradient = ctx.createRadialGradient( + orb.x, + orb.y, + 0, + orb.x, + orb.y, + orb.size + ); + gradient.addColorStop(0, getOrbColor(orb.opacity)); + gradient.addColorStop(1, getOrbColor(0)); + + ctx.beginPath(); + ctx.arc(orb.x, orb.y, orb.size, 0, Math.PI * 2); + ctx.fillStyle = gradient; + ctx.fill(); + }); + + animationRef.current = requestAnimationFrame(animate); + }; + + animate(); + + return () => { + window.removeEventListener("resize", resizeCanvas); + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + }; + }, [variant, orbCount]); + + return ( + + ); +} diff --git a/app/components/Button.tsx b/app/components/Button.tsx new file mode 100644 index 0000000..c9b2092 --- /dev/null +++ b/app/components/Button.tsx @@ -0,0 +1,89 @@ +import Link from "next/link"; +import { ReactNode } from "react"; + +interface ButtonProps { + href?: string; + onClick?: () => void; + children: ReactNode; + variant?: "primary" | "secondary" | "ghost" | "outline"; + size?: "sm" | "md" | "lg"; + className?: string; + external?: boolean; + type?: "button" | "submit"; + disabled?: boolean; +} + +export default function Button({ + href, + onClick, + children, + variant = "primary", + size = "md", + className = "", + external = false, + type = "button", + disabled = false, +}: ButtonProps) { + const baseStyles = + "relative inline-flex items-center justify-center font-medium rounded-lg transition-all duration-300 overflow-hidden group"; + + const sizeStyles = { + sm: "px-4 py-2 text-sm", + md: "px-6 py-3 text-base", + lg: "px-8 py-4 text-lg", + }; + + const variantStyles = { + primary: + "bg-gradient-to-r from-red-600 to-red-500 text-white hover:from-red-700 hover:to-red-600 hover:shadow-lg hover:shadow-red-500/25 hover:-translate-y-0.5 active:translate-y-0", + secondary: + "bg-white text-zinc-900 border border-zinc-200 hover:border-zinc-300 hover:bg-zinc-50 hover:shadow-lg hover:-translate-y-0.5 active:translate-y-0", + ghost: + "text-red-600 hover:bg-red-50 hover:text-red-700", + outline: + "border-2 border-red-600 text-red-600 hover:bg-red-600 hover:text-white hover:shadow-lg hover:shadow-red-500/25 hover:-translate-y-0.5 active:translate-y-0", + }; + + const disabledStyles = "opacity-50 cursor-not-allowed hover:transform-none hover:shadow-none"; + + const combinedClassName = `${baseStyles} ${sizeStyles[size]} ${variantStyles[variant]} ${disabled ? disabledStyles : ""} ${className}`; + + // Shimmer effect overlay + const shimmer = variant === "primary" && ( + + ); + + if (href) { + if (external) { + return ( + + {shimmer} + {children} + + ); + } + return ( + + {shimmer} + {children} + + ); + } + + return ( + + ); +} diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx index 3b8ce7a..ad14a4b 100644 --- a/app/components/Footer.tsx +++ b/app/components/Footer.tsx @@ -30,22 +30,32 @@ const navigation = { export default function Footer() { return ( -