Compare commits

..

No commits in common. "lighthouse" and "main" have entirely different histories.

55 changed files with 645 additions and 3104 deletions

View File

@ -1,12 +0,0 @@
{
"permissions": {
"allow": [
"Bash(npm run build:*)",
"Bash(npm install:*)",
"Bash(ls:*)",
"Bash(node scripts/optimize-images.js:*)",
"Bash(node scripts/optimize-images.cjs:*)",
"Skill(frontend-design)"
]
}
}

View File

@ -3,9 +3,9 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>IT Support Corpus Christi ⚡ 24/7 Managed Services | Bay Area Affiliates</title> <title>Managed IT Services Corpus Christi | Bay Area Affiliates</title>
<meta name="description" content="Stop IT headaches! 25+ years experience, 2-hour response time, 24/7 monitoring. Managed IT services in Corpus Christi & Coastal Bend. Free assessment. Call (361) 765-8400" /> <meta name="description" content="Secure, tailored IT support—Corpus Christi's trusted experts for 25+ years. Call today for a free assessment." />
<meta name="keywords" content="managed IT services corpus christi, IT support coastal bend, 24/7 IT monitoring, business computer solutions, Windows 11 migration, VPN setup, network security corpus christi, same-day IT support" /> <meta name="keywords" content="managed IT services corpus christi, IT support coastal bend, business computer solutions, Portland IT services, computer repair corpus christi" />
<meta name="author" content="Bay Area Affiliates, Inc." /> <meta name="author" content="Bay Area Affiliates, Inc." />
<meta property="og:title" content="Corpus Christi Managed IT Experts. Reliable, Secure, Local." /> <meta property="og:title" content="Corpus Christi Managed IT Experts. Reliable, Secure, Local." />
@ -211,33 +211,10 @@
} }
</script> </script>
<!-- Performance Optimization -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="dns-prefetch" href="https://fonts.gstatic.com" />
<!-- Non-blocking font loading with font-display: swap -->
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@600;700;800&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@600;700;800&display=swap" media="print" onload="this.media='all'" />
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@600;700;800&display=swap" /></noscript>
<!-- Critical asset preloads -->
<link rel="preload" href="/logo_bayarea.svg" as="image" />
<link rel="preload" href="/serverroom.webp" as="image" type="image/webp" />
<!-- Canonical URL -->
<link rel="canonical" href="https://bayarea-cc.com/" />
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/logo_bayarea.svg" /> <link rel="icon" type="image/svg+xml" href="/logo_bayarea.svg" />
<link rel="apple-touch-icon" sizes="180x180" href="/logo_bayarea.svg" /> <link rel="icon" type="image/png" href="/logo_bayarea.svg" />
<!-- Note: For production, consider converting logo_bayarea.svg to favicon.ico for better browser support -->
<!-- PWA / Mobile -->
<meta name="theme-color" content="#3366ff" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="manifest" href="/manifest.json" />
</head> </head>
<body> <body>

682
package-lock.json generated
View File

@ -42,7 +42,6 @@
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"framer-motion": "^12.24.12",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
@ -74,13 +73,10 @@
"globals": "^15.15.0", "globals": "^15.15.0",
"lovable-tagger": "^1.1.9", "lovable-tagger": "^1.1.9",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"sharp": "^0.34.5",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"terser": "^5.44.1",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.38.0", "typescript-eslint": "^8.38.0",
"vite": "^5.4.19", "vite": "^5.4.19"
"web-vitals": "^5.1.0"
} }
}, },
"node_modules/@alloc/quick-lru": { "node_modules/@alloc/quick-lru": {
@ -154,17 +150,6 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@emnapi/runtime": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
"integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.9", "version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
@ -874,496 +859,6 @@
"url": "https://github.com/sponsors/nzakas" "url": "https://github.com/sponsors/nzakas"
} }
}, },
"node_modules/@img/colour": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [
"x64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [
"arm"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
"cpu": [
"s390x"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [
"x64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [
"x64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
"cpu": [
"arm"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.4"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.7.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@isaacs/cliui": { "node_modules/@isaacs/cliui": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -1413,17 +908,6 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@jridgewell/source-map": {
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
}
},
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
@ -4027,13 +3511,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
} }
}, },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true,
"license": "MIT"
},
"node_modules/callsites": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -4391,16 +3868,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/detect-node-es": { "node_modules/detect-node-es": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@ -4895,33 +4362,6 @@
"url": "https://github.com/sponsors/rawify" "url": "https://github.com/sponsors/rawify"
} }
}, },
"node_modules/framer-motion": {
"version": "12.24.12",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.24.12.tgz",
"integrity": "sha512-W+tBOI1SDGNMH4D4mADY95qYd16Drke2Tj9zlGlwTGSCi6yy8wbMmPY1mvirfcTK8HBeuuCd2PflHdN/zbL4ew==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.24.11",
"motion-utils": "^12.24.10",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -5434,21 +4874,6 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/motion-dom": {
"version": "12.24.11",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.24.11.tgz",
"integrity": "sha512-DlWOmsXMJrV8lzZyd+LKjG2CXULUs++bkq8GZ2Sr0R0RRhs30K2wtY+LKiTjhmJU3W61HK+rB0GLz6XmPvTA1A==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.24.10"
}
},
"node_modules/motion-utils": {
"version": "12.24.10",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.24.10.tgz",
"integrity": "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -6258,9 +5683,9 @@
} }
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "7.7.3", "version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
@ -6270,51 +5695,6 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/sharp": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.2",
"semver": "^7.7.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.5",
"@img/sharp-darwin-x64": "0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4",
"@img/sharp-libvips-darwin-x64": "1.2.4",
"@img/sharp-libvips-linux-arm": "1.2.4",
"@img/sharp-libvips-linux-arm64": "1.2.4",
"@img/sharp-libvips-linux-ppc64": "1.2.4",
"@img/sharp-libvips-linux-riscv64": "1.2.4",
"@img/sharp-libvips-linux-s390x": "1.2.4",
"@img/sharp-libvips-linux-x64": "1.2.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
"@img/sharp-linux-arm": "0.34.5",
"@img/sharp-linux-arm64": "0.34.5",
"@img/sharp-linux-ppc64": "0.34.5",
"@img/sharp-linux-riscv64": "0.34.5",
"@img/sharp-linux-s390x": "0.34.5",
"@img/sharp-linux-x64": "0.34.5",
"@img/sharp-linuxmusl-arm64": "0.34.5",
"@img/sharp-linuxmusl-x64": "0.34.5",
"@img/sharp-wasm32": "0.34.5",
"@img/sharp-win32-arm64": "0.34.5",
"@img/sharp-win32-ia32": "0.34.5",
"@img/sharp-win32-x64": "0.34.5"
}
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -6358,16 +5738,6 @@
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
} }
}, },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -6377,17 +5747,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/string-width": { "node_modules/string-width": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@ -6600,32 +5959,6 @@
"tailwindcss": ">=3.0.0 || insiders" "tailwindcss": ">=3.0.0 || insiders"
} }
}, },
"node_modules/terser": {
"version": "5.44.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz",
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
}
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true,
"license": "MIT"
},
"node_modules/thenify": { "node_modules/thenify": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@ -7372,13 +6705,6 @@
"@esbuild/win32-x64": "0.21.5" "@esbuild/win32-x64": "0.21.5"
} }
}, },
"node_modules/web-vitals": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.1.0.tgz",
"integrity": "sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -45,7 +45,6 @@
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"framer-motion": "^12.24.12",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
@ -77,12 +76,9 @@
"globals": "^15.15.0", "globals": "^15.15.0",
"lovable-tagger": "^1.1.9", "lovable-tagger": "^1.1.9",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"sharp": "^0.34.5",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"terser": "^5.44.1",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.38.0", "typescript-eslint": "^8.38.0",
"vite": "^5.4.19", "vite": "^5.4.19"
"web-vitals": "^5.1.0"
} }
} }

View File

@ -1,33 +0,0 @@
# Security Headers for all routes
/*
# XSS Protection
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
# Referrer Policy
Referrer-Policy: strict-origin-when-cross-origin
# Permissions Policy
Permissions-Policy: geolocation=(self), microphone=(), camera=(), payment=()
# Strict Transport Security (HSTS)
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
# Content Security Policy
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https: blob:; connect-src 'self' https://www.google-analytics.com https://*.googletagmanager.com; frame-ancestors 'none';
# Cache Control for static assets
Cache-Control: public, max-age=31536000, immutable
# Cache Control for HTML
/*.html
Cache-Control: public, max-age=0, must-revalidate
# Cache Control for service worker
/sw.js
Cache-Control: public, max-age=0, must-revalidate
# Cache Control for manifest
/manifest.json
Cache-Control: public, max-age=86400, must-revalidate

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

View File

@ -1,22 +0,0 @@
{
"name": "Bay Area Affiliates - Managed IT Services",
"short_name": "Bay Area IT",
"description": "Professional managed IT services in Corpus Christi & Coastal Bend",
"start_url": "/",
"display": "standalone",
"background_color": "#030303",
"theme_color": "#3366ff",
"orientation": "portrait-primary",
"icons": [
{
"src": "/logo_bayarea.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any maskable"
}
],
"categories": ["business", "productivity", "utilities"],
"scope": "/",
"lang": "en-US",
"dir": "ltr"
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

View File

@ -1,113 +0,0 @@
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
const PUBLIC_DIR = path.join(__dirname, '../public');
const WEBP_QUALITY = 80;
const AVIF_QUALITY = 80;
const PNG_QUALITY = 80;
async function optimizeImages() {
console.log('Starting image optimization...\n');
// Get all PNG files in public directory
const files = fs.readdirSync(PUBLIC_DIR)
.filter(file => file.endsWith('.png'))
.map(file => path.join(PUBLIC_DIR, file));
if (files.length === 0) {
console.log('No PNG files found in public directory');
return;
}
console.log(`Found ${files.length} PNG files to optimize\n`);
let totalOriginalSize = 0;
let totalWebpSize = 0;
let totalAvifSize = 0;
let totalCompressedPngSize = 0;
for (const filePath of files) {
const filename = path.basename(filePath);
const filenameWithoutExt = path.basename(filePath, '.png');
const originalSize = fs.statSync(filePath).size;
totalOriginalSize += originalSize;
console.log(`Processing: ${filename} (${(originalSize / 1024 / 1024).toFixed(2)} MB)`);
try {
// Generate WebP version
const webpPath = path.join(PUBLIC_DIR, `${filenameWithoutExt}.webp`);
await sharp(filePath)
.resize(1920, null, { withoutEnlargement: true, fit: 'inside' })
.webp({ quality: WEBP_QUALITY })
.toFile(webpPath);
const webpSize = fs.statSync(webpPath).size;
totalWebpSize += webpSize;
console.log(` ✓ WebP: ${(webpSize / 1024).toFixed(2)} KB (${((1 - webpSize / originalSize) * 100).toFixed(1)}% reduction)`);
// Generate AVIF version
const avifPath = path.join(PUBLIC_DIR, `${filenameWithoutExt}.avif`);
await sharp(filePath)
.resize(1920, null, { withoutEnlargement: true, fit: 'inside' })
.avif({ quality: AVIF_QUALITY })
.toFile(avifPath);
const avifSize = fs.statSync(avifPath).size;
totalAvifSize += avifSize;
console.log(` ✓ AVIF: ${(avifSize / 1024).toFixed(2)} KB (${((1 - avifSize / originalSize) * 100).toFixed(1)}% reduction)`);
// Compress original PNG
const compressedPngPath = path.join(PUBLIC_DIR, `${filenameWithoutExt}_compressed.png`);
await sharp(filePath)
.resize(1920, null, { withoutEnlargement: true, fit: 'inside' })
.png({
quality: PNG_QUALITY,
compressionLevel: 9,
palette: true
})
.toFile(compressedPngPath);
const compressedPngSize = fs.statSync(compressedPngPath).size;
totalCompressedPngSize += compressedPngSize;
console.log(` ✓ Compressed PNG: ${(compressedPngSize / 1024).toFixed(2)} KB (${((1 - compressedPngSize / originalSize) * 100).toFixed(1)}% reduction)`);
// Replace original with compressed version
fs.unlinkSync(filePath);
fs.renameSync(compressedPngPath, filePath);
console.log(` ✓ Original PNG replaced with compressed version\n`);
} catch (error) {
console.error(` ✗ Error processing ${filename}:`, error.message);
}
}
// Summary
console.log('\n' + '='.repeat(60));
console.log('OPTIMIZATION SUMMARY');
console.log('='.repeat(60));
console.log(`Total original PNG size: ${(totalOriginalSize / 1024 / 1024).toFixed(2)} MB`);
console.log(`Total WebP size: ${(totalWebpSize / 1024 / 1024).toFixed(2)} MB (${((1 - totalWebpSize / totalOriginalSize) * 100).toFixed(1)}% reduction)`);
console.log(`Total AVIF size: ${(totalAvifSize / 1024 / 1024).toFixed(2)} MB (${((1 - totalAvifSize / totalOriginalSize) * 100).toFixed(1)}% reduction)`);
console.log(`Total compressed PNG size: ${(totalCompressedPngSize / 1024 / 1024).toFixed(2)} MB (${((1 - totalCompressedPngSize / totalOriginalSize) * 100).toFixed(1)}% reduction)`);
console.log('\nAverage file sizes:');
console.log(` WebP: ${(totalWebpSize / files.length / 1024).toFixed(2)} KB`);
console.log(` AVIF: ${(totalAvifSize / files.length / 1024).toFixed(2)} KB`);
console.log(` Compressed PNG: ${(totalCompressedPngSize / files.length / 1024).toFixed(2)} KB`);
console.log('='.repeat(60));
// Check if targets are met
const avgWebpSize = totalWebpSize / files.length / 1024;
const avgAvifSize = totalAvifSize / files.length / 1024;
const totalSizeAfter = (totalWebpSize + totalAvifSize + totalCompressedPngSize) / 1024 / 1024;
console.log('\nTarget Achievement:');
console.log(` WebP avg < 200KB: ${avgWebpSize < 200 ? '✓ PASS' : '✗ FAIL'} (${avgWebpSize.toFixed(2)} KB)`);
console.log(` AVIF avg < 150KB: ${avgAvifSize < 150 ? '✓ PASS' : '✗ FAIL'} (${avgAvifSize.toFixed(2)} KB)`);
console.log(` Total size < 2MB: ${totalSizeAfter < 2 ? '✓ PASS' : '✗ FAIL'} (${totalSizeAfter.toFixed(2)} MB)`);
}
optimizeImages()
.then(() => console.log('\nOptimization complete!'))
.catch(error => console.error('Optimization failed:', error));

View File

@ -2,12 +2,8 @@ import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner"; import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom"; import { BrowserRouter, Routes, Route } from "react-router-dom";
import { lazy, Suspense, useEffect } from "react"; import { lazy, Suspense } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { reportWebVitals, observePerformance } from "@/utils/reportWebVitals";
import { pageTransition } from "@/utils/animations";
import ExitIntentPopup from "@/components/ExitIntentPopup";
// Eager load critical pages for better initial performance // Eager load critical pages for better initial performance
import Index from "./pages/Index"; import Index from "./pages/Index";
@ -30,101 +26,42 @@ const NetworkAttachedStorage = lazy(() => import("./pages/NetworkAttachedStorage
const queryClient = new QueryClient(); const queryClient = new QueryClient();
// Loading fallback component with animation // Loading fallback component
const PageLoader = () => ( const PageLoader = () => (
<motion.div <div className="min-h-screen flex items-center justify-center bg-background-deep">
className="min-h-screen flex items-center justify-center bg-background-deep"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<div className="text-center"> <div className="text-center">
<motion.div <div className="w-16 h-16 border-4 border-neon/30 border-t-neon rounded-full animate-spin mx-auto mb-4"></div>
className="w-16 h-16 border-4 border-neon/30 border-t-neon rounded-full mx-auto mb-4" <p className="text-foreground-muted">Loading...</p>
animate={{ rotate: 360 }} </div>
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
/>
<motion.p
className="text-foreground-muted"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
Loading...
</motion.p>
</div> </div>
</motion.div>
); );
// Animated page wrapper
const AnimatedPage = ({ children }: { children: React.ReactNode }) => {
return (
<motion.div
initial="initial"
animate="animate"
exit="exit"
variants={{
initial: pageTransition.initial,
animate: pageTransition.animate,
exit: pageTransition.exit
}}
transition={pageTransition.transition}
>
{children}
</motion.div>
);
};
const AppContent = () => {
const location = useLocation();
useEffect(() => {
// Initialize Web Vitals monitoring
reportWebVitals();
observePerformance();
}, []);
// Scroll to top on route change
useEffect(() => {
window.scrollTo(0, 0);
}, [location.pathname]);
return (
<>
<ExitIntentPopup />
<AnimatePresence mode="wait">
<Suspense fallback={<PageLoader />}>
<Routes location={location} key={location.pathname}>
<Route path="/" element={<AnimatedPage><Index /></AnimatedPage>} />
<Route path="/services" element={<AnimatedPage><Services /></AnimatedPage>} />
<Route path="/about" element={<AnimatedPage><About /></AnimatedPage>} />
<Route path="/blog" element={<AnimatedPage><Blog /></AnimatedPage>} />
<Route path="/blog/:slug" element={<AnimatedPage><BlogPost /></AnimatedPage>} />
<Route path="/contact" element={<AnimatedPage><Contact /></AnimatedPage>} />
<Route path="/windows-11-transition" element={<AnimatedPage><Windows11Transition /></AnimatedPage>} />
<Route path="/vpn-setup" element={<AnimatedPage><VpnSetup /></AnimatedPage>} />
<Route path="/web-services" element={<AnimatedPage><WebServices /></AnimatedPage>} />
<Route path="/performance-upgrades" element={<AnimatedPage><PerformanceUpgrades /></AnimatedPage>} />
<Route path="/printer-scanner-installation" element={<AnimatedPage><PrinterScannerInstallation /></AnimatedPage>} />
<Route path="/desktop-hardware" element={<AnimatedPage><DesktopHardware /></AnimatedPage>} />
<Route path="/network-infrastructure" element={<AnimatedPage><NetworkInfrastructure /></AnimatedPage>} />
<Route path="/network-attached-storage" element={<AnimatedPage><NetworkAttachedStorage /></AnimatedPage>} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<AnimatedPage><NotFound /></AnimatedPage>} />
</Routes>
</Suspense>
</AnimatePresence>
</>
);
};
const App = () => ( const App = () => (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<TooltipProvider> <TooltipProvider>
<Toaster /> <Toaster />
<Sonner /> <Sonner />
<BrowserRouter> <BrowserRouter>
<AppContent /> <Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/services" element={<Services />} />
<Route path="/about" element={<About />} />
<Route path="/blog" element={<Blog />} />
<Route path="/blog/:slug" element={<BlogPost />} />
<Route path="/contact" element={<Contact />} />
<Route path="/windows-11-transition" element={<Windows11Transition />} />
<Route path="/vpn-setup" element={<VpnSetup />} />
<Route path="/web-services" element={<WebServices />} />
<Route path="/performance-upgrades" element={<PerformanceUpgrades />} />
<Route path="/printer-scanner-installation" element={<PrinterScannerInstallation />} />
<Route path="/desktop-hardware" element={<DesktopHardware />} />
<Route path="/network-infrastructure" element={<NetworkInfrastructure />} />
<Route path="/network-attached-storage" element={<NetworkAttachedStorage />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</BrowserRouter> </BrowserRouter>
</TooltipProvider> </TooltipProvider>
</QueryClientProvider> </QueryClientProvider>

View File

@ -1,134 +0,0 @@
import { useEffect, useState } from 'react';
import { X, Download } from 'lucide-react';
import { Link } from 'react-router-dom';
const ExitIntentPopup = () => {
const [isVisible, setIsVisible] = useState(false);
const [hasShown, setHasShown] = useState(false);
useEffect(() => {
// Check if popup was already shown in this session
const shown = sessionStorage.getItem('exitIntentShown');
if (shown) {
setHasShown(true);
return;
}
const handleMouseLeave = (e: MouseEvent) => {
// Only trigger if mouse is leaving from top of viewport
if (e.clientY <= 0 && !hasShown) {
setIsVisible(true);
setHasShown(true);
sessionStorage.setItem('exitIntentShown', 'true');
}
};
// Add delay before activating to avoid false triggers
const timer = setTimeout(() => {
document.addEventListener('mouseleave', handleMouseLeave);
}, 3000);
return () => {
clearTimeout(timer);
document.removeEventListener('mouseleave', handleMouseLeave);
};
}, [hasShown]);
const handleClose = () => {
setIsVisible(false);
};
if (!isVisible) return null;
return (
<>
{/* Backdrop */}
<div
className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 animate-in fade-in duration-300"
onClick={handleClose}
/>
{/* Popup */}
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-50 animate-in zoom-in duration-300">
<div className="card-dark p-8 max-w-lg w-[90vw] relative">
{/* Close button */}
<button
onClick={handleClose}
className="absolute top-4 right-4 text-foreground-muted hover:text-foreground transition-colors"
aria-label="Close popup"
>
<X className="w-6 h-6" />
</button>
{/* Content */}
<div className="text-center">
<div className="w-16 h-16 bg-neon/20 rounded-full flex items-center justify-center mx-auto mb-4">
<Download className="w-8 h-8 text-neon" />
</div>
<h2 className="font-heading font-bold text-2xl sm:text-3xl text-foreground mb-4">
Wait! Don't leave empty-handed
</h2>
<p className="text-foreground-muted mb-6 text-lg">
Download our <span className="text-neon font-semibold">free Windows 11 Migration Checklist</span>
a $299 value guide to help you prepare for the upgrade.
</p>
{/* Key benefits */}
<ul className="text-left space-y-2 mb-6 text-sm">
<li className="flex items-start gap-2">
<svg className="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
<span>Hardware compatibility check (avoid costly mistakes)</span>
</li>
<li className="flex items-start gap-2">
<svg className="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
<span>Step-by-step migration timeline</span>
</li>
<li className="flex items-start gap-2">
<svg className="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
<span>Security & compliance considerations</span>
</li>
<li className="flex items-start gap-2">
<svg className="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
<span>Rollback plan (just in case)</span>
</li>
</ul>
{/* CTA */}
<Link
to="/contact?lead=windows11-checklist"
onClick={handleClose}
className="btn-neon w-full flex items-center justify-center space-x-2 mb-3"
>
<Download className="w-5 h-5" />
<span>Download Free Checklist</span>
</Link>
<p className="text-xs text-foreground-muted">
No spam. No credit card required. Instant access.
</p>
{/* Close link */}
<button
onClick={handleClose}
className="text-sm text-foreground-muted hover:text-foreground mt-4 underline"
>
No thanks, I'll figure it out myself
</button>
</div>
</div>
</div>
</>
);
};
export default ExitIntentPopup;

View File

@ -20,7 +20,7 @@ const Footer = () => {
]; ];
return ( return (
<footer role="contentinfo" className="bg-background-deep border-t border-border"> <footer className="bg-background-deep border-t border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-12"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-12">
{/* Company info */} {/* Company info */}

View File

@ -1,8 +1,6 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { Menu, X } from 'lucide-react'; import { Menu, X } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { navVariants, staggerContainer, staggerItem, buttonHover, buttonTap } from '@/utils/animations';
const Navigation = () => { const Navigation = () => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -29,146 +27,65 @@ const Navigation = () => {
const isActive = (path: string) => location.pathname === path; const isActive = (path: string) => location.pathname === path;
return ( return (
<> <nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${isScrolled
{/* Skip link for accessibility */} ? 'bg-white/5 backdrop-blur-2xl border-b border-white/20 shadow-2xl shadow-black/20'
<a href="#main-content" className="skip-link"> : 'bg-transparent'
Skip to main content }`}>
</a>
<motion.nav
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${isScrolled
? 'bg-background-deep/80 backdrop-blur-md border-b border-neon/30 shadow-[0_0_20px_rgba(0,0,0,0.5)]'
: 'bg-transparent border-b border-transparent'
}`}
initial="hidden"
animate="visible"
variants={navVariants}
role="navigation"
aria-label="Main navigation"
>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-14 md:h-16"> <div className="flex items-center justify-between h-16">
{/* Logo with animation */} {/* Logo */}
<Link to="/" className="flex items-center space-x-3"> <Link to="/" className="flex items-center space-x-3">
<motion.div <div className="w-12 h-12 flex items-center justify-center overflow-visible">
className="w-12 h-12 flex items-center justify-center overflow-visible"
whileHover={{ rotate: 360 }}
transition={{ duration: 0.6, ease: "easeInOut" }}
>
<img <img
src="/logo_bayarea.svg" src="/logo_bayarea.svg"
alt="Bay Area Affiliates Logo" alt="Bay Area Affiliates Logo"
className="w-10 h-10 opacity-90" className="w-10 h-10 opacity-90 hover:opacity-100 transition-opacity duration-300"
/> />
</motion.div> </div>
<span className="font-heading font-bold text-lg text-white"> <span className="font-heading font-bold text-lg text-white">
Bay Area Affiliates Bay Area Affiliates
</span> </span>
</Link> </Link>
{/* Desktop Navigation with animations */} {/* Desktop Navigation */}
<div className="hidden md:flex items-center space-x-8"> <div className="hidden md:flex items-center space-x-8">
{navItems.map((item, index) => ( {navItems.map((item) => (
<motion.div
key={item.name}
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1, duration: 0.5 }}
>
<Link <Link
key={item.name}
to={item.path} to={item.path}
className={`font-medium transition-all duration-200 relative group px-2 py-1 ${isActive(item.path) className={`font-medium transition-colors duration-200 ${isActive(item.path)
? 'text-neon' ? 'text-neon'
: 'text-white hover:text-neon' : 'text-white hover:text-neon'
}`} }`}
> >
{item.name} {item.name}
{/* Animated underline */}
<motion.span
className="absolute -bottom-1 left-0 h-0.5 bg-neon rounded-full"
initial={{ width: isActive(item.path) ? '100%' : 0 }}
whileHover={{ width: '100%', boxShadow: '0 0 8px rgba(51, 102, 255, 0.6)' }}
transition={{ duration: 0.3, ease: "easeOut" }}
/>
{/* Glow effect on hover */}
<motion.span
className="absolute inset-0 bg-neon/5 rounded-lg -z-10"
initial={{ opacity: 0, scale: 0.8 }}
whileHover={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.2 }}
/>
</Link> </Link>
</motion.div>
))} ))}
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.5, duration: 0.5 }}
whileHover={buttonHover}
whileTap={buttonTap}
>
<Link <Link
to="/contact" to="/contact"
className="btn-neon ml-4" className="btn-neon ml-4"
> >
Get Started Get Started
</Link> </Link>
</motion.div>
</div> </div>
{/* Mobile menu button with animation */} {/* Mobile menu button */}
<motion.button <button
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
className="md:hidden text-white hover:text-neon transition-colors" className="md:hidden text-white hover:text-neon transition-colors"
aria-label="Toggle navigation menu" aria-label="Toggle navigation menu"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
> >
<AnimatePresence mode="wait"> {isOpen ? <X size={24} /> : <Menu size={24} />}
{isOpen ? ( </button>
<motion.div
key="close"
initial={{ rotate: -90, opacity: 0 }}
animate={{ rotate: 0, opacity: 1 }}
exit={{ rotate: 90, opacity: 0 }}
transition={{ duration: 0.2 }}
>
<X size={24} />
</motion.div>
) : (
<motion.div
key="menu"
initial={{ rotate: 90, opacity: 0 }}
animate={{ rotate: 0, opacity: 1 }}
exit={{ rotate: -90, opacity: 0 }}
transition={{ duration: 0.2 }}
>
<Menu size={24} />
</motion.div>
)}
</AnimatePresence>
</motion.button>
</div> </div>
{/* Mobile Navigation with smooth animations */} {/* Mobile Navigation */}
<AnimatePresence>
{isOpen && ( {isOpen && (
<motion.div <div className="md:hidden bg-white/5 backdrop-blur-2xl border-t border-white/20">
initial={{ height: 0, opacity: 0 }} <div className="px-2 pt-2 pb-3 space-y-1">
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="md:hidden bg-white/5 backdrop-blur-2xl border-t border-white/20 overflow-hidden"
>
<motion.div
className="px-2 pt-2 pb-3 space-y-1"
variants={staggerContainer}
initial="hidden"
animate="visible"
>
{navItems.map((item) => ( {navItems.map((item) => (
<motion.div key={item.name} variants={staggerItem}>
<Link <Link
key={item.name}
to={item.path} to={item.path}
onClick={() => setIsOpen(false)} onClick={() => setIsOpen(false)}
className={`block px-3 py-2 rounded-md text-base font-medium transition-colors ${isActive(item.path) className={`block px-3 py-2 rounded-md text-base font-medium transition-colors ${isActive(item.path)
@ -178,9 +95,7 @@ const Navigation = () => {
> >
{item.name} {item.name}
</Link> </Link>
</motion.div>
))} ))}
<motion.div variants={staggerItem}>
<Link <Link
to="/contact" to="/contact"
onClick={() => setIsOpen(false)} onClick={() => setIsOpen(false)}
@ -188,14 +103,11 @@ const Navigation = () => {
> >
Get Started Get Started
</Link> </Link>
</motion.div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div> </div>
</motion.nav> </div>
</> )}
</div>
</nav>
); );
}; };

View File

@ -1,50 +1,47 @@
import { ReactNode } from 'react'; import { ReactNode, useLayoutEffect, useRef } from 'react';
import { motion, useInView } from 'framer-motion'; import gsap from 'gsap';
import React, { useRef } from 'react'; import { ScrollTrigger } from 'gsap/ScrollTrigger';
interface ScrollRevealProps { gsap.registerPlugin(ScrollTrigger);
children: React.ReactNode;
width?: 'fit-content' | '100%'; type ScrollRevealProps = {
children: ReactNode;
delay?: number; delay?: number;
duration?: number;
direction?: 'up' | 'down' | 'left' | 'right';
className?: string; className?: string;
} };
const ScrollReveal: React.FC<ScrollRevealProps> = ({ const ScrollReveal = ({ children, delay = 0, className = '' }: ScrollRevealProps) => {
children, const elementRef = useRef<HTMLDivElement>(null);
width = 'fit-content',
delay = 0,
duration = 0.5,
direction = 'up',
className = ""
}) => {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: "-50px 0px" }); // Trigger slightly before element is fully in view
const getDirectionOffset = () => { useLayoutEffect(() => {
switch (direction) { const element = elementRef.current;
case 'up': return { y: 75 }; if (!element) return;
case 'down': return { y: -75 };
case 'left': return { x: 75 }; const ctx = gsap.context(() => {
case 'right': return { x: -75 }; gsap.fromTo(
default: return { y: 75 }; element,
{ autoAlpha: 0, y: 32 },
{
autoAlpha: 1,
y: 0,
duration: 0.8,
ease: 'power3.out',
delay: delay / 1000,
scrollTrigger: {
trigger: element,
start: 'top 85%',
once: true,
},
} }
}; );
}, element);
return () => ctx.revert();
}, [delay]);
return ( return (
<div ref={ref} style={{ position: 'relative', width, overflow: 'hidden' }} className={className}> <div ref={elementRef} className={className}>
<motion.div
variants={{
hidden: { opacity: 0, ...getDirectionOffset() },
visible: { opacity: 1, x: 0, y: 0 },
}}
initial="hidden"
animate={isInView ? "visible" : "hidden"}
transition={{ duration, delay, ease: "easeOut" }}
>
{children} {children}
</motion.div>
</div> </div>
); );
}; };

View File

@ -1,244 +0,0 @@
import { motion } from 'framer-motion';
import { useMemo } from 'react';
const BackgroundAnimations = () => {
// Particles with better visibility (increased opacity and size)
const particles = useMemo(() => {
return Array.from({ length: 18 }, (_, i) => ({
id: i,
x: Math.random() * 100,
y: Math.random() * 100,
size: Math.random() * 4 + 2, // Larger particles (2-6px instead of 1-3px)
duration: Math.random() * 10 + 6,
delay: Math.random() * 4,
opacity: Math.random() * 0.4 + 0.5, // Much higher opacity (0.5-0.9)
}));
}, []);
// Circuit nodes for tech effect
const circuitNodes = useMemo(() => {
return Array.from({ length: 8 }, (_, i) => ({
id: i,
x: Math.random() * 80 + 10,
y: Math.random() * 80 + 10,
pulseDelay: Math.random() * 2,
}));
}, []);
// Connection lines between nodes
const connectionLines = useMemo(() => {
const lines = [];
for (let i = 0; i < circuitNodes.length - 1; i++) {
if (i + 1 < circuitNodes.length) {
lines.push({
id: i,
x1: circuitNodes[i].x,
y1: circuitNodes[i].y,
x2: circuitNodes[i + 1].x,
y2: circuitNodes[i + 1].y,
});
}
}
return lines;
}, [circuitNodes]);
return (
<div className="absolute inset-0 overflow-hidden pointer-events-none" style={{ willChange: 'transform' }}>
{/* Animated Grid Background */}
<motion.div
className="absolute inset-0"
style={{
backgroundImage: `
linear-gradient(to right, rgba(51, 102, 255, 0.08) 1px, transparent 1px),
linear-gradient(to bottom, rgba(51, 102, 255, 0.08) 1px, transparent 1px)
`,
backgroundSize: '60px 60px',
willChange: 'transform',
}}
animate={{
opacity: [0.3, 0.5, 0.3],
}}
transition={{
duration: 4,
repeat: Infinity,
ease: "easeInOut",
}}
/>
{/* SVG Container for Lines and Nodes */}
<svg className="absolute inset-0 w-full h-full" style={{ willChange: 'transform' }}>
{/* Animated Connection Lines */}
{connectionLines.map((line) => (
<motion.line
key={line.id}
x1={`${line.x1}%`}
y1={`${line.y1}%`}
x2={`${line.x2}%`}
y2={`${line.y2}%`}
stroke="rgba(51, 102, 255, 0.4)"
strokeWidth="1.5"
initial={{ pathLength: 0, opacity: 0 }}
animate={{ pathLength: 1, opacity: [0.3, 0.7, 0.3] }}
transition={{
pathLength: { duration: 2, delay: line.id * 0.3 },
opacity: { duration: 3, repeat: Infinity, ease: "easeInOut" },
}}
/>
))}
{/* Vertical Data Streams */}
{[...Array(6)].map((_, i) => (
<motion.line
key={`vertical-${i}`}
x1={`${10 + i * 18}%`}
y1="0%"
x2={`${10 + i * 18}%`}
y2="100%"
stroke="rgba(51, 102, 255, 0.3)"
strokeWidth="1.5"
strokeDasharray="8 16"
initial={{ strokeDashoffset: 0 }}
animate={{ strokeDashoffset: 100 }}
transition={{
duration: 3,
repeat: Infinity,
ease: "linear",
delay: i * 0.4,
}}
/>
))}
{/* Circuit Nodes with pulse ring */}
{circuitNodes.map((node) => (
<g key={node.id}>
{/* Pulse ring */}
<motion.circle
cx={`${node.x}%`}
cy={`${node.y}%`}
r="6"
fill="none"
stroke="#3366ff"
strokeWidth="1"
animate={{
r: [6, 12, 6],
opacity: [0.8, 0, 0.8],
}}
transition={{
duration: 2.5,
repeat: Infinity,
delay: node.pulseDelay,
ease: "easeOut",
}}
/>
{/* Core node */}
<motion.circle
cx={`${node.x}%`}
cy={`${node.y}%`}
r="4"
fill="#3366ff"
animate={{
opacity: [0.7, 1, 0.7],
r: [3, 4.5, 3],
}}
transition={{
duration: 2,
repeat: Infinity,
delay: node.pulseDelay,
ease: "easeInOut",
}}
/>
</g>
))}
</svg>
{/* Floating Data Particles - highly visible */}
{particles.map((particle) => (
<motion.div
key={particle.id}
className="absolute rounded-full bg-neon"
style={{
width: `${particle.size}px`,
height: `${particle.size}px`,
left: `${particle.x}%`,
top: `${particle.y}%`,
boxShadow: '0 0 12px rgba(51, 102, 255, 0.9), 0 0 24px rgba(51, 102, 255, 0.5)',
willChange: 'transform, opacity',
}}
animate={{
y: [0, -500],
opacity: [0, particle.opacity, particle.opacity, 0],
}}
transition={{
duration: particle.duration,
repeat: Infinity,
delay: particle.delay,
ease: "linear",
}}
/>
))}
{/* Scanline Effect */}
<motion.div
className="absolute inset-0 pointer-events-none"
style={{
background: `repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(51, 102, 255, 0.04) 2px,
rgba(51, 102, 255, 0.04) 4px
)`,
}}
animate={{
opacity: [0.3, 0.5, 0.3],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut",
}}
/>
{/* Radial Glows */}
{[
{ x: 20, y: 30, size: 300 },
{ x: 80, y: 70, size: 350 },
{ x: 50, y: 50, size: 400 },
].map((pos, i) => (
<motion.div
key={`glow-${i}`}
className="absolute rounded-full pointer-events-none"
style={{
left: `${pos.x}%`,
top: `${pos.y}%`,
width: `${pos.size}px`,
height: `${pos.size}px`,
background: 'radial-gradient(circle, rgba(51, 102, 255, 0.25) 0%, transparent 70%)',
transform: 'translate(-50%, -50%)',
willChange: 'transform',
}}
animate={{
scale: [1, 1.3, 1],
opacity: [0.5, 0.8, 0.5],
}}
transition={{
duration: 5,
repeat: Infinity,
delay: i * 1.5,
ease: "easeInOut",
}}
/>
))}
{/* Corner Accent Glows */}
<div className="absolute top-0 left-0 w-96 h-96 bg-gradient-to-br from-neon/20 to-transparent rounded-full opacity-60 pointer-events-none" />
<div className="absolute bottom-0 right-0 w-[500px] h-[500px] bg-gradient-to-tl from-neon/20 to-transparent rounded-full opacity-60 pointer-events-none" />
</div>
);
};
export default BackgroundAnimations;

View File

@ -1,8 +1,6 @@
import { ArrowRight, Clock, DollarSign, Phone } from 'lucide-react'; import { ArrowRight, Clock, DollarSign, Phone } from 'lucide-react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { motion, useInView } from 'framer-motion'; import ScrollReveal from '../ScrollReveal';
import { useRef } from 'react';
import { fadeInUp, staggerContainer, staggerItem, buttonHover, buttonTap } from '@/utils/animations';
const CTASection = () => { const CTASection = () => {
const faqs = [ const faqs = [
@ -23,13 +21,6 @@ const CTASection = () => {
} }
]; ];
const headerRef = useRef(null);
const ctaRef = useRef(null);
const faqRef = useRef(null);
const isHeaderInView = useInView(headerRef, { once: true, margin: "-100px" });
const isCtaInView = useInView(ctaRef, { once: true, margin: "-100px" });
const isFaqInView = useInView(faqRef, { once: true, margin: "-100px" });
return ( return (
<section className="py-24 bg-background-deep relative overflow-hidden"> <section className="py-24 bg-background-deep relative overflow-hidden">
{/* Background decoration */} {/* Background decoration */}
@ -38,96 +29,49 @@ const CTASection = () => {
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="max-w-4xl mx-auto text-center"> <div className="max-w-4xl mx-auto text-center">
<motion.div <ScrollReveal>
ref={headerRef}
initial="hidden"
animate={isHeaderInView ? "visible" : "hidden"}
variants={fadeInUp}
>
<h2 className="font-heading font-bold text-4xl sm:text-5xl lg:text-6xl text-foreground mb-6"> <h2 className="font-heading font-bold text-4xl sm:text-5xl lg:text-6xl text-foreground mb-6">
Ready for <motion.span Ready for <span className="text-neon text-glow">reliable IT?</span>
className="text-neon text-glow"
animate={{
textShadow: [
"0 0 20px rgba(51, 102, 255, 0.5)",
"0 0 40px rgba(51, 102, 255, 0.8)",
"0 0 20px rgba(51, 102, 255, 0.5)"
]
}}
transition={{ duration: 3, repeat: Infinity, ease: "easeInOut" }}
>
reliable IT?
</motion.span>
</h2> </h2>
<p className="text-xl text-foreground-muted mb-12 leading-relaxed"> <p className="text-xl text-foreground-muted mb-12 leading-relaxed">
Join 150+ Coastal Bend businesses that trust us with their technology. Join 150+ Coastal Bend businesses that trust us with their technology.
Get started with a free 20-minute assessment. Get started with a free 20-minute assessment.
</p> </p>
</motion.div> </ScrollReveal>
{/* Primary CTAs */} {/* Primary CTAs */}
<motion.div <ScrollReveal delay={200}>
ref={ctaRef} <div className="flex flex-col sm:flex-row gap-4 justify-center items-center mb-16">
initial={{ opacity: 0, y: 30 }}
animate={isCtaInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 30 }}
transition={{ delay: 0.2, duration: 0.6 }}
className="flex flex-col sm:flex-row gap-4 justify-center items-center mb-16"
>
<motion.div whileHover={buttonHover} whileTap={buttonTap}>
<Link <Link
to="/contact" to="/contact"
className="btn-neon group flex items-center space-x-2 text-lg px-8 py-4" className="btn-neon group flex items-center space-x-2 text-lg px-8 py-4"
> >
<span>Book a 20-minute assessment</span> <span>Book a 20-minute assessment</span>
<motion.div <ArrowRight className="w-5 h-5 transition-transform group-hover:translate-x-1" />
animate={{ x: [0, 3, 0] }}
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
>
<ArrowRight className="w-5 h-5" />
</motion.div>
</Link> </Link>
</motion.div>
<motion.div whileHover={buttonHover} whileTap={buttonTap}>
<Link <Link
to="/contact" to="/contact"
className="btn-ghost group flex items-center space-x-2 text-lg px-8 py-4" className="btn-ghost group flex items-center space-x-2 text-lg px-8 py-4"
> >
<span>Send a message</span> <span>Send a message</span>
</Link> </Link>
</motion.div> </div>
</motion.div> </ScrollReveal>
{/* Micro FAQ */} {/* Micro FAQ */}
<motion.div <ScrollReveal delay={400}>
ref={faqRef} <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
initial="hidden"
animate={isFaqInView ? "visible" : "hidden"}
variants={staggerContainer}
className="grid grid-cols-1 md:grid-cols-3 gap-8"
>
{faqs.map((faq, index) => { {faqs.map((faq, index) => {
const Icon = faq.icon; const Icon = faq.icon;
return ( return (
<motion.div <div key={faq.question} className="text-left">
key={faq.question} <div className="flex items-start space-x-3">
variants={staggerItem} <div className="w-8 h-8 bg-neon/20 rounded-lg flex items-center justify-center flex-shrink-0 mt-1">
className="text-left"
>
<motion.div
className="flex items-start space-x-3"
whileHover={{ x: 5 }}
transition={{ duration: 0.2 }}
>
<motion.div
className="w-8 h-8 bg-neon/20 rounded-lg flex items-center justify-center flex-shrink-0 mt-1"
whileHover={{ rotate: 360, scale: 1.1 }}
transition={{ duration: 0.5 }}
>
<Icon className="w-4 h-4 text-neon" /> <Icon className="w-4 h-4 text-neon" />
</motion.div> </div>
<div> <div>
<h3 className="font-semibold text-foreground mb-2"> <h3 className="font-semibold text-foreground mb-2">
{faq.question} {faq.question}
@ -136,43 +80,37 @@ const CTASection = () => {
{faq.answer} {faq.answer}
</p> </p>
</div> </div>
</motion.div> </div>
</motion.div> </div>
); );
})} })}
</motion.div> </div>
</ScrollReveal>
{/* Contact info */} {/* Contact info */}
<motion.div <ScrollReveal delay={600}>
initial={{ opacity: 0, y: 20 }} <div className="mt-16 pt-8 border-t border-border/30">
animate={isFaqInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
transition={{ delay: 0.6, duration: 0.6 }}
className="mt-16 pt-8 border-t border-border/30"
>
<p className="text-sm text-foreground-muted mb-4"> <p className="text-sm text-foreground-muted mb-4">
Ready to talk? We're here to help. Ready to talk? We're here to help.
</p> </p>
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center text-sm"> <div className="flex flex-col sm:flex-row gap-4 justify-center items-center text-sm">
<motion.a <a
href="tel:+1-361-555-0123" href="tel:+1-361-555-0123"
className="text-neon hover:text-neon/80 transition-colors flex items-center" className="text-neon hover:text-neon/80 transition-colors flex items-center"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
> >
<Phone className="w-4 h-4 mr-2" /> <Phone className="w-4 h-4 mr-2" />
(361) 555-0123 (361) 555-0123
</motion.a> </a>
<span className="hidden sm:block text-border">|</span> <span className="hidden sm:block text-border">|</span>
<motion.a <a
href="mailto:info@bayareaaffiliates.com" href="mailto:info@bayareaaffiliates.com"
className="text-neon hover:text-neon/80 transition-colors" className="text-neon hover:text-neon/80 transition-colors"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
> >
info@bayareaaffiliates.com info@bayareaaffiliates.com
</motion.a> </a>
</div> </div>
</motion.div> </div>
</ScrollReveal>
</div> </div>
</div> </div>
</section> </section>

View File

@ -1,165 +1,87 @@
import { ArrowRight, Play } from 'lucide-react'; import { ArrowRight, Play } from 'lucide-react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { motion, useScroll, useTransform } from 'framer-motion'; import { useEffect, useRef } from 'react';
import { heroVariants, heroItemVariants, buttonHover, buttonTap, easing } from '@/utils/animations'; import heroNetwork from '@/assets/hero-network.jpg';
import BackgroundAnimations from './BackgroundAnimations';
const HeroSection = () => { const HeroSection = () => {
const { scrollY } = useScroll(); const imageRef = useRef<HTMLImageElement>(null);
// Smooth parallax effect with Framer Motion useEffect(() => {
const y = useTransform(scrollY, [0, 500], [0, 150]); const handleScroll = () => {
const opacity = useTransform(scrollY, [0, 300], [1, 0]); if (imageRef.current) {
const scrolled = window.pageYOffset;
const parallax = scrolled * 0.5;
imageRef.current.style.transform = `translateY(${parallax}px) scale(1.1)`;
}
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return ( return (
<section className="section-pin"> <section className="section-pin">
<div className="relative h-full flex items-center justify-center overflow-hidden"> <div className="relative h-full flex items-center justify-center overflow-hidden">
{/* Background image with smooth parallax */} {/* Background image with parallax */}
<motion.div <div className="absolute inset-0 overflow-hidden">
className="absolute inset-0 overflow-hidden" <img
style={{ y }} ref={imageRef}
>
<picture>
<source type="image/avif" srcSet="/serverroom.avif" />
<source type="image/webp" srcSet="/serverroom.webp" />
<motion.img
src="/serverroom.png" src="/serverroom.png"
alt="Modern IT infrastructure with network connections and server equipment" alt="Modern IT infrastructure with network connections"
className="w-full h-[110%] object-cover" className="w-full h-[110%] object-cover will-change-transform"
initial={{ scale: 1.1, opacity: 0 }} style={{ transform: 'translateY(0px) scale(1.1)' }}
animate={{ scale: 1.1, opacity: 1 }}
transition={{ duration: 1.2, ease: easing.elegant }}
loading="eager"
fetchPriority="high"
/> />
</picture> {/* Dark overlay */}
<div className="absolute inset-0 bg-black/35"></div>
</div>
{/* Animated background effects */} {/* Main content */}
<BackgroundAnimations /> <div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
{/* Darker overlay for better text contrast */}
<div className="absolute inset-0 bg-gradient-to-b from-black/60 via-black/50 to-black/60"></div>
</motion.div>
{/* Main content with staggered animations */}
<motion.div
className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center"
variants={heroVariants}
initial="hidden"
animate="visible"
style={{ opacity }}
>
<div className="max-w-4xl mx-auto"> <div className="max-w-4xl mx-auto">
{/* Badge */} {/* Badge */}
<motion.div <div className="inline-flex items-center px-4 py-2 rounded-full bg-neon/20 border border-neon/40 text-neon text-sm font-medium mb-8 drop-shadow-[0_0_10px_rgba(51,102,255,0.5)]">
variants={heroItemVariants} <span className="w-2 h-2 bg-neon rounded-full mr-2 animate-glow-pulse"></span>
className="inline-flex items-center px-4 py-2 rounded-full bg-neon/20 border border-neon/40 text-neon text-sm font-medium mb-8 drop-shadow-[0_0_10px_rgba(51,102,255,0.5)]"
>
<motion.span
className="w-2 h-2 bg-neon rounded-full mr-2"
animate={{
scale: [1, 1.2, 1],
opacity: [1, 0.7, 1],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut",
}}
/>
Serving the Coastal Bend since 2010 Serving the Coastal Bend since 2010
</motion.div> </div>
{/* Main headline */} {/* Main headline */}
<motion.h1 <h1 className="font-heading font-bold text-5xl sm:text-6xl lg:text-7xl text-white mb-6 text-balance drop-shadow-[0_0_20px_rgba(255,255,255,0.3)]">
variants={heroItemVariants}
className="font-heading font-bold text-5xl sm:text-6xl lg:text-7xl text-white mb-6 text-balance drop-shadow-[0_0_20px_rgba(255,255,255,0.3)]"
>
Modern IT that keeps your{' '} Modern IT that keeps your{' '}
<motion.span <span className="text-neon text-glow drop-shadow-[0_0_30px_rgba(51,102,255,0.8)]">business moving</span>
className="text-neon text-glow drop-shadow-[0_0_30px_rgba(51,102,255,0.8)]" </h1>
animate={{
textShadow: [
'0 0 20px rgba(51,102,255,0.5)',
'0 0 30px rgba(51,102,255,0.8)',
'0 0 20px rgba(51,102,255,0.5)',
],
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "easeInOut",
}}
>
business moving
</motion.span>
</motion.h1>
{/* Subheadline */} {/* Subheadline */}
<motion.p <p className="text-xl sm:text-2xl text-white/95 mb-12 max-w-3xl mx-auto leading-relaxed drop-shadow-[0_0_15px_rgba(255,255,255,0.2)]">
variants={heroItemVariants}
className="text-xl sm:text-2xl text-white/95 mb-12 max-w-3xl mx-auto leading-relaxed drop-shadow-[0_0_15px_rgba(255,255,255,0.2)]"
>
From fast devices to secure remote access and resilient networks we design, From fast devices to secure remote access and resilient networks we design,
run and protect your tech so you can focus on growth. run and protect your tech so you can focus on growth.
</motion.p> </p>
{/* CTA buttons with hover animations */} {/* CTA buttons */}
<motion.div <div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
variants={heroItemVariants}
className="flex flex-col sm:flex-row gap-4 justify-center items-center"
>
<motion.div
whileHover={buttonHover}
whileTap={buttonTap}
>
<Link <Link
to="/contact" to="/contact"
className="btn-neon group flex items-center space-x-2" className="btn-neon group flex items-center space-x-2"
> >
<span>Get in touch</span> <span>Get in touch</span>
<motion.div <ArrowRight className="w-5 h-5 transition-transform group-hover:translate-x-1" />
animate={{ x: [0, 3, 0] }}
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
>
<ArrowRight className="w-5 h-5" />
</motion.div>
</Link> </Link>
</motion.div>
<motion.div <button className="btn-ghost group flex items-center space-x-2">
whileHover={buttonHover} <Play className="w-5 h-5 transition-transform group-hover:scale-110" />
whileTap={buttonTap} <span>See our system</span>
> </button>
<Link </div>
to="/services"
className="btn-ghost group flex items-center space-x-2"
>
<Play className="w-5 h-5" />
<span>See our services</span>
</Link>
</motion.div>
</motion.div>
</div> </div>
</motion.div> </div>
{/* Animated scroll indicator */} {/* Scroll indicator */}
<motion.div <div className="absolute bottom-8 left-1/2 transform -translate-x-1/2">
className="absolute bottom-8 left-1/2 transform -translate-x-1/2"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 1.5, duration: 0.8 }}
>
<div className="w-6 h-10 border-2 border-neon/60 rounded-full flex justify-center drop-shadow-[0_0_10px_rgba(51,102,255,0.3)]"> <div className="w-6 h-10 border-2 border-neon/60 rounded-full flex justify-center drop-shadow-[0_0_10px_rgba(51,102,255,0.3)]">
<motion.div <div className="w-1 h-3 bg-neon rounded-full mt-2 animate-bounce"></div>
className="w-1 h-3 bg-neon rounded-full mt-2" </div>
animate={{ y: [0, 12, 0] }}
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
/>
</div> </div>
</motion.div>
</div> </div>
</section> </section>
); );

View File

@ -1,20 +1,10 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Search, Shield, Cog, Zap } from 'lucide-react'; import { Search, Shield, Cog, Zap } from 'lucide-react';
import { motion, useInView, useScroll, useTransform } from 'framer-motion'; import ScrollReveal from '../ScrollReveal';
import { fadeInUp, scaleIn } from '@/utils/animations';
const ProcessTimeline = () => { const ProcessTimeline = () => {
const [activeStep, setActiveStep] = useState(0); const [activeStep, setActiveStep] = useState(0);
const sectionRef = useRef<HTMLDivElement>(null); const sectionRef = useRef<HTMLDivElement>(null);
const headerRef = useRef(null);
const isHeaderInView = useInView(headerRef, { once: true, margin: "-100px" });
// Smooth scroll-based timeline progress
const { scrollYProgress } = useScroll({
target: sectionRef,
offset: ["start end", "end start"]
});
const timelineProgress = useTransform(scrollYProgress, [0.2, 0.8], [0, 100]);
const steps = [ const steps = [
{ {
@ -73,12 +63,7 @@ const ProcessTimeline = () => {
</div> </div>
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<motion.div <ScrollReveal>
ref={headerRef}
initial="hidden"
animate={isHeaderInView ? "visible" : "hidden"}
variants={fadeInUp}
>
<div className="text-center mb-20"> <div className="text-center mb-20">
<h2 className="font-heading font-bold text-4xl sm:text-5xl text-foreground mb-6"> <h2 className="font-heading font-bold text-4xl sm:text-5xl text-foreground mb-6">
How we <span className="text-neon">transform</span> your IT How we <span className="text-neon">transform</span> your IT
@ -87,14 +72,14 @@ const ProcessTimeline = () => {
Our proven four-phase methodology ensures systematic improvement and lasting results. Our proven four-phase methodology ensures systematic improvement and lasting results.
</p> </p>
</div> </div>
</motion.div> </ScrollReveal>
<div className="relative"> <div className="relative">
{/* Timeline line */} {/* Timeline line */}
<div className="absolute left-8 lg:left-1/2 lg:transform lg:-translate-x-1/2 top-0 bottom-0 w-px bg-border"> <div className="absolute left-8 lg:left-1/2 lg:transform lg:-translate-x-1/2 top-0 bottom-0 w-px bg-border">
<motion.div <div
className="absolute top-0 left-0 w-full bg-neon" className="absolute top-0 left-0 w-full bg-neon transition-all duration-500 ease-out"
style={{ height: useTransform(timelineProgress, (value) => `${value}%`) }} style={{ height: `${(activeStep + 1) * 25}%` }}
/> />
</div> </div>
@ -104,35 +89,17 @@ const ProcessTimeline = () => {
const Icon = step.icon; const Icon = step.icon;
const isActive = index <= activeStep; const isActive = index <= activeStep;
const isEven = index % 2 === 0; const isEven = index % 2 === 0;
const stepRef = useRef(null);
const isInView = useInView(stepRef, { once: true, margin: "-150px" });
return ( return (
<motion.div <ScrollReveal key={step.title} delay={index * 100}>
key={step.title}
ref={stepRef}
initial="hidden"
animate={isInView ? "visible" : "hidden"}
variants={fadeInUp}
transition={{ delay: index * 0.1 }}
>
<div className={`relative flex flex-col lg:flex-row items-center ${ <div className={`relative flex flex-col lg:flex-row items-center ${
isEven ? '' : 'lg:flex-row-reverse' isEven ? '' : 'lg:flex-row-reverse'
}`}> }`}>
{/* Step content */} {/* Step content */}
<motion.div <div className={`flex-1 ${isEven ? 'lg:pr-16' : 'lg:pl-16'} ${
className={`flex-1 ${isEven ? 'lg:pr-16' : 'lg:pl-16'} ${
isEven ? 'lg:text-right' : 'lg:text-left' isEven ? 'lg:text-right' : 'lg:text-left'
} text-center lg:text-left`} } text-center lg:text-left`}>
initial={{ opacity: 0, x: isEven ? -40 : 40 }} <div className="card-dark p-8 max-w-lg mx-auto lg:mx-0">
animate={isInView ? { opacity: 1, x: 0 } : { opacity: 0, x: isEven ? -40 : 40 }}
transition={{ delay: index * 0.1 + 0.2, duration: 0.6 }}
>
<motion.div
className="card-dark p-8 max-w-lg mx-auto lg:mx-0"
whileHover={{ y: -5, boxShadow: "0 0 30px rgba(51, 102, 255, 0.3)" }}
transition={{ duration: 0.3 }}
>
<div className="mb-4"> <div className="mb-4">
<span className="text-sm font-medium text-neon uppercase tracking-wider"> <span className="text-sm font-medium text-neon uppercase tracking-wider">
Step {index + 1} Step {index + 1}
@ -147,34 +114,24 @@ const ProcessTimeline = () => {
<p className="text-sm text-foreground-muted"> <p className="text-sm text-foreground-muted">
{step.details} {step.details}
</p> </p>
</motion.div> </div>
</motion.div> </div>
{/* Timeline dot */} {/* Timeline dot */}
<div className="relative z-10 my-8 lg:my-0"> <div className="relative z-10 my-8 lg:my-0">
<motion.div <div className={`w-16 h-16 rounded-full border-4 flex items-center justify-center transition-all duration-500 ${
className={`w-16 h-16 rounded-full border-4 flex items-center justify-center ${
isActive isActive
? 'border-neon bg-neon text-neon-foreground shadow-neon' ? 'border-neon bg-neon text-neon-foreground shadow-neon'
: 'border-border bg-background text-foreground-muted' : 'border-border bg-background text-foreground-muted'
}`} }`}>
initial={{ scale: 0, rotate: -180 }}
animate={isInView ? { scale: 1, rotate: 0 } : { scale: 0, rotate: -180 }}
transition={{
delay: index * 0.1 + 0.3,
duration: 0.6,
type: "spring",
stiffness: 200
}}
>
<Icon className="w-6 h-6" /> <Icon className="w-6 h-6" />
</motion.div> </div>
</div> </div>
{/* Spacer for layout */} {/* Spacer for layout */}
<div className="flex-1 hidden lg:block"></div> <div className="flex-1 hidden lg:block"></div>
</div> </div>
</motion.div> </ScrollReveal>
); );
})} })}
</div> </div>

View File

@ -1,8 +1,6 @@
import { MapPin, Star, Users } from 'lucide-react'; import { MapPin, Star, Users } from 'lucide-react';
import { motion, useInView } from 'framer-motion'; import ScrollReveal from '../ScrollReveal';
import { useRef } from 'react';
import CountUpNumber from '../CountUpNumber'; import CountUpNumber from '../CountUpNumber';
import { fadeInUp, scaleIn, staggerContainer, staggerItem } from '@/utils/animations';
const ProofSection = () => { const ProofSection = () => {
const stats = [ const stats = [
@ -19,13 +17,6 @@ const ProofSection = () => {
company: "Coastal Medical Group" company: "Coastal Medical Group"
}; };
const headerRef = useRef(null);
const statsRef = useRef(null);
const testimonialRef = useRef(null);
const isHeaderInView = useInView(headerRef, { once: true, margin: "-100px" });
const isStatsInView = useInView(statsRef, { once: true, margin: "-100px" });
const isTestimonialInView = useInView(testimonialRef, { once: true, margin: "-100px" });
return ( return (
<section className="py-24 bg-background relative overflow-hidden"> <section className="py-24 bg-background relative overflow-hidden">
{/* Background decoration */} {/* Background decoration */}
@ -33,27 +24,12 @@ const ProofSection = () => {
<div className="absolute top-0 left-1/2 transform -translate-x-1/2 w-96 h-96 bg-neon/5 rounded-full blur-3xl"></div> <div className="absolute top-0 left-1/2 transform -translate-x-1/2 w-96 h-96 bg-neon/5 rounded-full blur-3xl"></div>
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<motion.div <ScrollReveal>
ref={headerRef}
initial="hidden"
animate={isHeaderInView ? "visible" : "hidden"}
variants={fadeInUp}
>
<div className="text-center mb-16"> <div className="text-center mb-16">
<motion.div <div className="inline-flex items-center px-4 py-2 rounded-full bg-neon/10 border border-neon/30 text-neon text-sm font-medium mb-8">
className="inline-flex items-center px-4 py-2 rounded-full bg-neon/10 border border-neon/30 text-neon text-sm font-medium mb-8"
animate={{
boxShadow: [
"0 0 20px rgba(51, 102, 255, 0.2)",
"0 0 30px rgba(51, 102, 255, 0.4)",
"0 0 20px rgba(51, 102, 255, 0.2)"
]
}}
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
>
<MapPin className="w-4 h-4 mr-2" /> <MapPin className="w-4 h-4 mr-2" />
Proudly serving the Coastal Bend Proudly serving the Coastal Bend
</motion.div> </div>
<h2 className="font-heading font-bold text-4xl sm:text-5xl text-foreground mb-6"> <h2 className="font-heading font-bold text-4xl sm:text-5xl text-foreground mb-6">
Local expertise for{' '} Local expertise for{' '}
@ -66,22 +42,13 @@ const ProofSection = () => {
tailored solutions that work in the real world. tailored solutions that work in the real world.
</p> </p>
</div> </div>
</motion.div> </ScrollReveal>
{/* Stats */} {/* Stats */}
<motion.div <ScrollReveal delay={200}>
ref={statsRef} <div className="grid grid-cols-2 lg:grid-cols-4 gap-8 mb-20">
initial="hidden"
animate={isStatsInView ? "visible" : "hidden"}
variants={staggerContainer}
className="grid grid-cols-2 lg:grid-cols-4 gap-8 mb-20"
>
{stats.map((stat, index) => ( {stats.map((stat, index) => (
<motion.div <div key={stat.label} className="text-center">
key={stat.label}
variants={staggerItem}
className="text-center"
>
<div className="font-heading font-bold text-4xl lg:text-5xl text-neon mb-2"> <div className="font-heading font-bold text-4xl lg:text-5xl text-neon mb-2">
<CountUpNumber <CountUpNumber
value={stat.value} value={stat.value}
@ -89,69 +56,34 @@ const ProofSection = () => {
className="inline-block" className="inline-block"
/> />
</div> </div>
<motion.div <div className="text-foreground-muted text-sm lg:text-base">
className="text-foreground-muted text-sm lg:text-base"
initial={{ opacity: 0, y: 10 }}
animate={isStatsInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 10 }}
transition={{ delay: index * 0.1 + 0.3, duration: 0.5 }}
>
{stat.label} {stat.label}
</motion.div> </div>
</motion.div> </div>
))} ))}
</motion.div> </div>
</ScrollReveal>
{/* Testimonial */} {/* Testimonial */}
<motion.div <ScrollReveal delay={400}>
ref={testimonialRef} <div className="card-dark p-8 lg:p-12 max-w-4xl mx-auto">
initial="hidden"
animate={isTestimonialInView ? "visible" : "hidden"}
variants={scaleIn}
>
<motion.div
className="card-dark p-8 lg:p-12 max-w-4xl mx-auto"
whileHover={{ y: -5, boxShadow: "0 0 40px rgba(51, 102, 255, 0.3)" }}
transition={{ duration: 0.3 }}
>
<div className="flex flex-col lg:flex-row items-start gap-8"> <div className="flex flex-col lg:flex-row items-start gap-8">
{/* Quote */} {/* Quote */}
<div className="flex-1"> <div className="flex-1">
<motion.div <div className="flex items-center mb-6">
className="flex items-center mb-6"
initial="hidden"
animate={isTestimonialInView ? "visible" : "hidden"}
variants={staggerContainer}
>
{[...Array(5)].map((_, i) => ( {[...Array(5)].map((_, i) => (
<motion.div <Star key={i} className="w-5 h-5 text-neon fill-current" />
key={i}
variants={staggerItem}
whileHover={{ scale: 1.2, rotate: 360 }}
transition={{ duration: 0.4 }}
>
<Star className="w-5 h-5 text-neon fill-current" />
</motion.div>
))} ))}
</motion.div> </div>
<blockquote className="text-lg lg:text-xl text-foreground leading-relaxed mb-6"> <blockquote className="text-lg lg:text-xl text-foreground leading-relaxed mb-6">
"{testimonial.quote}" "{testimonial.quote}"
</blockquote> </blockquote>
<div className="flex items-center"> <div className="flex items-center">
<motion.div <div className="w-12 h-12 bg-neon/20 rounded-full flex items-center justify-center mr-4">
className="w-12 h-12 bg-neon/20 rounded-full flex items-center justify-center mr-4"
animate={{
boxShadow: [
"0 0 10px rgba(51, 102, 255, 0.3)",
"0 0 20px rgba(51, 102, 255, 0.5)",
"0 0 10px rgba(51, 102, 255, 0.3)"
]
}}
transition={{ duration: 2, repeat: Infinity }}
>
<Users className="w-6 h-6 text-neon" /> <Users className="w-6 h-6 text-neon" />
</motion.div> </div>
<div> <div>
<div className="font-semibold text-foreground">{testimonial.author}</div> <div className="font-semibold text-foreground">{testimonial.author}</div>
<div className="text-foreground-muted text-sm"> <div className="text-foreground-muted text-sm">
@ -161,43 +93,29 @@ const ProofSection = () => {
</div> </div>
</div> </div>
</div> </div>
</motion.div> </div>
</motion.div> </ScrollReveal>
{/* Service area */} {/* Service area */}
<motion.div <ScrollReveal delay={600}>
initial={{ opacity: 0, y: 30 }} <div className="mt-16 text-center">
animate={isTestimonialInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 30 }}
transition={{ delay: 0.4, duration: 0.6 }}
className="mt-16 text-center"
>
<h3 className="font-heading font-semibold text-xl text-foreground mb-6"> <h3 className="font-heading font-semibold text-xl text-foreground mb-6">
Serving businesses throughout the Coastal Bend Serving businesses throughout the Coastal Bend
</h3> </h3>
<motion.div <div className="flex flex-wrap justify-center items-center gap-6 text-foreground-muted">
className="flex flex-wrap justify-center items-center gap-6 text-foreground-muted"
initial="hidden"
animate={isTestimonialInView ? "visible" : "hidden"}
variants={staggerContainer}
>
{[ {[
'Corpus Christi', 'Portland', 'Ingleside', 'Aransas Pass', 'Corpus Christi', 'Portland', 'Ingleside', 'Aransas Pass',
'Rockport', 'Fulton', 'Sinton', 'Mathis' 'Rockport', 'Fulton', 'Sinton', 'Mathis'
].map((city) => ( ].map((city) => (
<motion.span <span key={city} className="flex items-center text-sm">
key={city}
className="flex items-center text-sm"
variants={staggerItem}
whileHover={{ scale: 1.1, color: "rgba(51, 102, 255, 1)" }}
transition={{ duration: 0.2 }}
>
<MapPin className="w-3 h-3 mr-1 text-neon" /> <MapPin className="w-3 h-3 mr-1 text-neon" />
{city} {city}
</motion.span> </span>
))} ))}
</motion.div> </div>
</motion.div> </div>
</ScrollReveal>
</div> </div>
</section> </section>
); );

View File

@ -1,8 +1,6 @@
import { Monitor, Wifi, Cloud, Shield, Database, Settings } from 'lucide-react'; import { Monitor, Wifi, Cloud, Shield, Database, Settings } from 'lucide-react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { motion, useInView } from 'framer-motion'; import ScrollReveal from '../ScrollReveal';
import { useRef } from 'react';
import { fadeInUp, staggerContainer, staggerItem, cardHover, glowHover } from '@/utils/animations';
const ServicesOverview = () => { const ServicesOverview = () => {
const services = [ const services = [
@ -44,11 +42,6 @@ const ServicesOverview = () => {
} }
]; ];
const headerRef = useRef(null);
const gridRef = useRef(null);
const isHeaderInView = useInView(headerRef, { once: true, margin: "-100px" });
const isGridInView = useInView(gridRef, { once: true, margin: "-100px" });
return ( return (
<section className="py-24 bg-background-deep relative overflow-hidden"> <section className="py-24 bg-background-deep relative overflow-hidden">
{/* Background decoration */} {/* Background decoration */}
@ -57,12 +50,7 @@ const ServicesOverview = () => {
<div className="absolute bottom-1/4 left-0 w-96 h-96 bg-neon/5 rounded-full blur-3xl"></div> <div className="absolute bottom-1/4 left-0 w-96 h-96 bg-neon/5 rounded-full blur-3xl"></div>
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<motion.div <ScrollReveal>
ref={headerRef}
initial="hidden"
animate={isHeaderInView ? "visible" : "hidden"}
variants={fadeInUp}
>
<div className="text-center mb-16"> <div className="text-center mb-16">
<h2 className="font-heading font-bold text-4xl sm:text-5xl text-foreground mb-6"> <h2 className="font-heading font-bold text-4xl sm:text-5xl text-foreground mb-6">
Complete IT solutions for{' '} Complete IT solutions for{' '}
@ -72,44 +60,22 @@ const ServicesOverview = () => {
From desktop support to enterprise infrastructure we've got your technology covered. From desktop support to enterprise infrastructure we've got your technology covered.
</p> </p>
</div> </div>
</motion.div> </ScrollReveal>
<motion.div <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
ref={gridRef}
initial="hidden"
animate={isGridInView ? "visible" : "hidden"}
variants={staggerContainer}
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
>
{services.map((service, index) => { {services.map((service, index) => {
const Icon = service.icon; const Icon = service.icon;
return ( return (
<motion.div key={service.title} variants={staggerItem} className="h-full"> <ScrollReveal key={service.title} delay={index * 100}>
<motion.div <div className="card-dark p-8 group hover:shadow-neon transition-all duration-500 hover:-translate-y-1">
className="card-dark p-8 h-full relative overflow-hidden group border border-white/5 hover:border-neon/50 transition-colors duration-500"
whileHover={{
y: -10,
scale: 1.02,
boxShadow: "0 20px 40px -10px rgba(51, 102, 255, 0.3)"
}}
transition={{ duration: 0.4, ease: "easeOut" }}
>
{/* Subtle gradient background nice touch */}
<div className="absolute inset-0 bg-gradient-to-br from-neon/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
{/* Icon */} {/* Icon */}
<motion.div <div className="w-12 h-12 bg-neon/20 rounded-xl flex items-center justify-center mb-6 group-hover:bg-neon/30 transition-colors">
className="w-12 h-12 bg-neon/10 rounded-xl flex items-center justify-center mb-6 relative z-10 group-hover:bg-neon/20 transition-colors duration-300" <Icon className="w-6 h-6 text-neon" />
whileHover={{ rotate: 360, scale: 1.1 }} </div>
transition={{ duration: 0.6 }}
>
<Icon className="w-6 h-6 text-neon drop-shadow-[0_0_8px_rgba(51,102,255,0.6)]" />
</motion.div>
{/* Content */} {/* Content */}
<div className="relative z-10"> <h3 className="font-heading font-bold text-xl text-foreground mb-4">
<h3 className="font-heading font-bold text-xl text-foreground mb-4 group-hover:text-neon transition-colors duration-300">
{service.title} {service.title}
</h3> </h3>
@ -121,18 +87,7 @@ const ServicesOverview = () => {
<ul className="space-y-2 mb-6"> <ul className="space-y-2 mb-6">
{service.features.map((feature) => ( {service.features.map((feature) => (
<li key={feature} className="flex items-center text-sm text-foreground-muted"> <li key={feature} className="flex items-center text-sm text-foreground-muted">
<motion.div <div className="w-1.5 h-1.5 bg-neon rounded-full mr-3"></div>
className="w-1.5 h-1.5 bg-neon rounded-full mr-3 shadow-[0_0_5px_rgba(51,102,255,0.8)]"
animate={{
scale: [1, 1.5, 1],
opacity: [0.7, 1, 0.7]
}}
transition={{
duration: 2,
repeat: Infinity,
delay: index * 0.2
}}
/>
{feature} {feature}
</li> </li>
))} ))}
@ -141,41 +96,30 @@ const ServicesOverview = () => {
{/* CTA */} {/* CTA */}
<Link <Link
to="/services" to="/services"
className="inline-flex items-center text-neon font-medium group-hover:translate-x-2 transition-transform duration-300" className="inline-flex items-center text-neon font-medium hover:text-neon/80 transition-colors group-hover:underline"
> >
Learn more Learn more
<motion.svg <svg className="w-4 h-4 ml-1 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
className="w-4 h-4 ml-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
animate={{ x: [0, 3, 0] }}
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7" />
</motion.svg> </svg>
</Link> </Link>
</div> </div>
</motion.div> </ScrollReveal>
</motion.div>
); );
})} })}
</motion.div> </div>
{/* Bottom CTA */} {/* Bottom CTA */}
<motion.div <ScrollReveal delay={600}>
initial={{ opacity: 0, y: 30 }} <div className="text-center mt-16">
animate={isGridInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 30 }}
transition={{ delay: 0.8, duration: 0.6 }}
className="text-center mt-16"
>
<Link <Link
to="/services" to="/services"
className="btn-ghost text-lg px-12 py-4" className="btn-ghost text-lg px-12 py-4"
> >
View all services View all services
</Link> </Link>
</motion.div> </div>
</ScrollReveal>
</div> </div>
</section> </section>
); );

View File

@ -1,54 +0,0 @@
import { motion } from 'framer-motion';
const TrustedBy = () => {
// Placeholder logos - in a real app these would be SVGs
const companies = [
{ name: "TechCorp", color: "bg-blue-400" },
{ name: "SecureNet", color: "bg-indigo-400" },
{ name: "CloudSystems", color: "bg-cyan-400" },
{ name: "FutureData", color: "bg-sky-400" },
{ name: "CyberGuard", color: "bg-teal-400" },
{ name: "NetWorks", color: "bg-emerald-400" },
];
// duplicate for seamless loop
const logos = [...companies, ...companies, ...companies];
return (
<section className="py-10 border-b border-white/5 bg-background-deep relative overflow-hidden">
<p className="text-center text-sm font-medium text-foreground-muted mb-8 tracking-wider uppercase relative z-10 px-4">
Trusted by innovative businesses in Corpus Christi
</p>
<div className="relative flex overflow-hidden mask-linear-fade">
{/* Gradient Masks for smooth fade out at edges */}
<div className="absolute left-0 top-0 bottom-0 w-24 bg-gradient-to-r from-background-deep to-transparent z-10"></div>
<div className="absolute right-0 top-0 bottom-0 w-24 bg-gradient-to-l from-background-deep to-transparent z-10"></div>
<motion.div
className="flex space-x-16 items-center"
animate={{ x: ["0%", "-50%"] }}
transition={{
duration: 30,
repeat: Infinity,
ease: "linear",
repeatType: "loop"
}}
>
{logos.map((company, index) => (
<div key={index} className="flex items-center space-x-2 opacity-50 grayscale hover:grayscale-0 hover:opacity-100 transition-all duration-300">
{/* Fake Logo Icon */}
<div className={`w-8 h-8 rounded-lg ${company.color}/20 flex items-center justify-center`}>
<div className={`w-4 h-4 rounded-full ${company.color}`}></div>
</div>
{/* Fake Logo Text */}
<span className="text-xl font-bold text-white tracking-tight">{company.name}</span>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TrustedBy;

View File

@ -1,58 +1,6 @@
import { Shield, Zap, Users, ArrowRight } from 'lucide-react'; import { Shield, Zap, Users, ArrowRight } from 'lucide-react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { motion, useInView, useMotionValue, useSpring, useTransform } from 'framer-motion'; import ScrollReveal from '../ScrollReveal';
import { useRef, MouseEvent } from 'react';
import { fadeInUp, slideInLeft, slideInRight, buttonHover, buttonTap, cardHover } from '@/utils/animations';
// Optimized 3D Tilt Card Component with reduced effect
const TiltCard = ({ children, className = '' }: { children: React.ReactNode; className?: string }) => {
const cardRef = useRef<HTMLDivElement>(null);
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
// Reduced rotation range for subtler effect (10 -> 5 degrees)
const rotateX = useSpring(useTransform(mouseY, [-0.5, 0.5], [5, -5]), {
stiffness: 200,
damping: 25,
});
const rotateY = useSpring(useTransform(mouseX, [-0.5, 0.5], [-5, 5]), {
stiffness: 200,
damping: 25,
});
const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
if (!cardRef.current) return;
const rect = cardRef.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
mouseX.set((e.clientX - centerX) / (rect.width / 2));
mouseY.set((e.clientY - centerY) / (rect.height / 2));
};
const handleMouseLeave = () => {
mouseX.set(0);
mouseY.set(0);
};
return (
<motion.div
ref={cardRef}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
style={{
rotateX,
rotateY,
transformStyle: 'preserve-3d',
willChange: 'transform',
}}
className={className}
whileHover={{ scale: 1.01 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
);
};
const ValuePillars = () => { const ValuePillars = () => {
const pillars = [ const pillars = [
@ -79,21 +27,13 @@ const ValuePillars = () => {
}, },
]; ];
const headerRef = useRef(null);
const isHeaderInView = useInView(headerRef, { once: true, margin: "-100px" });
return ( return (
<section className="py-24 bg-background-deep relative overflow-hidden"> <section className="py-24 bg-background-deep relative overflow-hidden">
{/* Background decoration */} {/* Background decoration */}
<div className="absolute inset-0 grid-overlay opacity-20"></div> <div className="absolute inset-0 grid-overlay opacity-20"></div>
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<motion.div <ScrollReveal>
ref={headerRef}
initial="hidden"
animate={isHeaderInView ? "visible" : "hidden"}
variants={fadeInUp}
>
<div className="text-center mb-16"> <div className="text-center mb-16">
<h2 className="font-heading font-bold text-4xl sm:text-5xl text-foreground mb-6"> <h2 className="font-heading font-bold text-4xl sm:text-5xl text-foreground mb-6">
Why teams choose us for{' '} Why teams choose us for{' '}
@ -103,44 +43,25 @@ const ValuePillars = () => {
We handle the complexity so you can focus on what you do best. We handle the complexity so you can focus on what you do best.
</p> </p>
</div> </div>
</motion.div> </ScrollReveal>
<div className="space-y-24"> <div className="space-y-24">
{pillars.map((pillar, index) => { {pillars.map((pillar, index) => {
const Icon = pillar.icon; const Icon = pillar.icon;
const isReverse = index % 2 === 1; const isReverse = index % 2 === 1;
const itemRef = useRef(null);
const isInView = useInView(itemRef, { once: true, margin: "-100px" });
return ( return (
<motion.div <ScrollReveal key={pillar.number} delay={index * 200}>
key={pillar.number}
ref={itemRef}
initial="hidden"
animate={isInView ? "visible" : "hidden"}
variants={isReverse ? slideInRight : slideInLeft}
transition={{ delay: 0.2 }}
>
<div className={`flex flex-col ${isReverse ? 'lg:flex-row-reverse' : 'lg:flex-row'} items-center gap-12 lg:gap-16`}> <div className={`flex flex-col ${isReverse ? 'lg:flex-row-reverse' : 'lg:flex-row'} items-center gap-12 lg:gap-16`}>
{/* Content */} {/* Content */}
<div className="flex-1 space-y-6"> <div className="flex-1 space-y-6">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<motion.span <span className="text-6xl font-heading font-bold text-neon/30">
className="text-6xl font-heading font-bold text-neon/30"
initial={{ opacity: 0, scale: 0.5 }}
animate={isInView ? { opacity: 1, scale: 1 } : { opacity: 0, scale: 0.5 }}
transition={{ delay: 0.4, duration: 0.6, type: "spring" }}
>
{pillar.number} {pillar.number}
</motion.span> </span>
<motion.div <div className="w-12 h-12 bg-neon/20 rounded-xl flex items-center justify-center">
className="w-12 h-12 bg-neon/20 rounded-xl flex items-center justify-center"
initial={{ opacity: 0, rotate: -180 }}
animate={isInView ? { opacity: 1, rotate: 0 } : { opacity: 0, rotate: -180 }}
transition={{ delay: 0.5, duration: 0.6 }}
>
<Icon className="w-6 h-6 text-neon" /> <Icon className="w-6 h-6 text-neon" />
</motion.div> </div>
</div> </div>
<h3 className="font-heading font-bold text-3xl text-foreground"> <h3 className="font-heading font-bold text-3xl text-foreground">
@ -151,48 +72,29 @@ const ValuePillars = () => {
{pillar.description} {pillar.description}
</p> </p>
<motion.div whileHover={buttonHover} whileTap={buttonTap}>
<Link <Link
to="/services" to="/services"
className="btn-ghost group flex items-center space-x-2 w-fit" className="btn-ghost group flex items-center space-x-2 w-fit"
onClick={() => window.scrollTo(0, 0)} onClick={() => window.scrollTo(0, 0)}
> >
<span>Learn more</span> <span>Learn more</span>
<motion.div <ArrowRight className="w-4 h-4 transition-transform group-hover:translate-x-1" />
animate={{ x: [0, 3, 0] }}
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
>
<ArrowRight className="w-4 h-4" />
</motion.div>
</Link> </Link>
</motion.div>
</div> </div>
{/* Image with 3D Tilt Effect */} {/* Image */}
<TiltCard className="flex-1"> <div className="flex-1 parallax">
<div className="card-dark p-2 group hover:shadow-neon transition-all duration-500"> <div className="card-dark p-2 group hover:shadow-neon transition-all duration-500">
<div className="relative overflow-hidden rounded-xl"> <img
<motion.img
src={pillar.image} src={pillar.image}
alt={pillar.title} alt={pillar.title}
className="w-full h-64 lg:h-80 object-cover" className="w-full h-64 lg:h-80 object-cover rounded-xl transition-transform duration-500 group-hover:scale-105"
whileHover={{ scale: 1.05 }}
transition={{ duration: 0.4, ease: "easeOut" }}
loading="lazy" loading="lazy"
style={{ willChange: 'transform' }}
/>
{/* Simplified shine effect on hover */}
<motion.div
className="absolute inset-0 bg-gradient-to-tr from-transparent via-white/8 to-transparent pointer-events-none"
initial={{ x: '-100%', y: '-100%' }}
whileHover={{ x: '100%', y: '100%' }}
transition={{ duration: 0.5, ease: "easeOut" }}
/> />
</div> </div>
</div> </div>
</TiltCard>
</div> </div>
</motion.div> </ScrollReveal>
); );
})} })}
</div> </div>

View File

@ -1,3 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@600;700;800&display=swap');
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@ -86,31 +88,6 @@
scroll-behavior: smooth; scroll-behavior: smooth;
} }
/* Accessibility: Focus visible styles */
*:focus-visible {
outline: 2px solid hsl(var(--neon));
outline-offset: 3px;
border-radius: 4px;
}
/* Skip link for keyboard navigation */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: hsl(var(--neon));
color: hsl(var(--background));
padding: 8px 16px;
text-decoration: none;
z-index: 100;
font-weight: 600;
border-radius: 0 0 8px 0;
}
.skip-link:focus {
top: 0;
}
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
html { html {
scroll-behavior: auto; scroll-behavior: auto;
@ -124,29 +101,6 @@
transition-duration: 0.01ms !important; transition-duration: 0.01ms !important;
} }
} }
/* Screen reader only class */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.sr-only:focus,
.sr-only:active {
position: static;
width: auto;
height: auto;
overflow: visible;
clip: auto;
white-space: normal;
}
} }
@layer components { @layer components {
@ -194,26 +148,6 @@
@apply rounded-[var(--radius)] px-8 py-4 font-semibold; @apply rounded-[var(--radius)] px-8 py-4 font-semibold;
@apply transition-all duration-300 ease-out; @apply transition-all duration-300 ease-out;
@apply shadow-[0_0_0_1px_hsl(var(--neon))] hover:shadow-[0_0_20px_hsl(var(--neon)/0.5)]; @apply shadow-[0_0_0_1px_hsl(var(--neon))] hover:shadow-[0_0_20px_hsl(var(--neon)/0.5)];
position: relative;
overflow: hidden;
}
.btn-neon::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.btn-neon:hover::before {
width: 300px;
height: 300px;
} }
.btn-ghost { .btn-ghost {
@ -221,116 +155,16 @@
@apply rounded-[var(--radius)] px-8 py-4 font-semibold; @apply rounded-[var(--radius)] px-8 py-4 font-semibold;
@apply transition-all duration-300 ease-out; @apply transition-all duration-300 ease-out;
@apply hover:shadow-[0_0_15px_hsl(var(--neon)/0.3)]; @apply hover:shadow-[0_0_15px_hsl(var(--neon)/0.3)];
position: relative;
overflow: hidden;
}
.btn-ghost::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(to right, transparent, hsl(var(--neon)), transparent);
transform: translateX(-100%);
transition: transform 0.5s ease-out;
}
.btn-ghost:hover::after {
transform: translateX(100%);
} }
/* Card styles */ /* Card styles */
.card-dark { .card-dark {
@apply bg-card border border-card-border rounded-[var(--radius-lg)]; @apply bg-card border border-card-border rounded-[var(--radius-lg)];
@apply backdrop-blur-sm shadow-[var(--shadow-card)]; @apply backdrop-blur-sm shadow-[var(--shadow-card)];
position: relative;
overflow: hidden;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.card-dark::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg,
transparent 30%,
hsl(var(--neon) / 0.1) 50%,
transparent 70%);
border-radius: inherit;
opacity: 0;
transition: opacity 0.4s ease;
z-index: -1;
}
.card-dark:hover::before {
opacity: 1;
}
.card-dark:hover {
transform: translateY(-4px);
border-color: hsl(var(--neon) / 0.3);
} }
/* Typography helpers */ /* Typography helpers */
.text-balance { .text-balance {
text-wrap: balance; text-wrap: balance;
} }
/* Smooth focus ring for better UX */
.focus-ring {
@apply focus:outline-none focus:ring-2 focus:ring-neon focus:ring-offset-2 focus:ring-offset-background;
transition: all 0.2s ease;
}
/* Magnetic hover effect for interactive elements */
.magnetic-hover {
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.magnetic-hover:hover {
transform: translateY(-2px);
}
/* Glassmorphism effect */
.glass {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* Shimmer effect for loading states */
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.shimmer {
position: relative;
overflow: hidden;
}
.shimmer::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(51, 102, 255, 0.1),
transparent
);
animation: shimmer 2s infinite;
}
} }

View File

@ -4,19 +4,10 @@ import { Shield, Users, Zap, MapPin } from 'lucide-react';
import ScrollReveal from '@/components/ScrollReveal'; import ScrollReveal from '@/components/ScrollReveal';
import CountUpNumber from '@/components/CountUpNumber'; import CountUpNumber from '@/components/CountUpNumber';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { useSEO } from '@/hooks/useSEO';
const About = () => { const About = () => {
const imageRef = useRef<HTMLImageElement>(null); const imageRef = useRef<HTMLImageElement>(null);
useSEO({
title: 'About Bay Area Affiliates | IT Experts Corpus Christi Since 2010',
description: 'Local IT expertise for the Coastal Bend since 2010. 150+ businesses served, 99.9% uptime. Security-first approach with clear communication. Meet the team.',
keywords: 'about Bay Area Affiliates, IT company Corpus Christi, managed services provider Texas, local IT support Coastal Bend',
canonical: 'https://bayarea-cc.com/about',
ogImage: 'https://bayarea-cc.com/og-image.png',
});
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
if (imageRef.current) { if (imageRef.current) {
@ -75,7 +66,7 @@ const About = () => {
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero section with image background */} {/* Hero section with image background */}
<section className="relative h-screen flex items-center justify-center overflow-hidden"> <section className="relative h-screen flex items-center justify-center overflow-hidden">
{/* Background image with parallax */} {/* Background image with parallax */}
@ -87,7 +78,7 @@ const About = () => {
className="w-full h-[110%] object-cover will-change-transform" className="w-full h-[110%] object-cover will-change-transform"
style={{ transform: 'translateY(0px) scale(1.1)' }} style={{ transform: 'translateY(0px) scale(1.1)' }}
loading="eager" loading="eager"
fetchPriority="high" fetchpriority="high"
/> />
</div> </div>

View File

@ -81,7 +81,7 @@ const Blog = () => {
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero section with image background */} {/* Hero section with image background */}
<section className="relative h-screen flex items-center justify-center overflow-hidden"> <section className="relative h-screen flex items-center justify-center overflow-hidden">
{/* Background image with parallax */} {/* Background image with parallax */}
@ -93,7 +93,7 @@ const Blog = () => {
className="w-full h-[110%] object-cover will-change-transform" className="w-full h-[110%] object-cover will-change-transform"
style={{ transform: 'translateY(0px) scale(1.1)' }} style={{ transform: 'translateY(0px) scale(1.1)' }}
loading="eager" loading="eager"
fetchPriority="high" fetchpriority="high"
/> />
</div> </div>
@ -211,7 +211,8 @@ const Blog = () => {
{/* Scroll to Top Button */} {/* Scroll to Top Button */}
<button <button
onClick={scrollToTop} onClick={scrollToTop}
className={`fixed bottom-8 right-8 z-50 p-4 bg-neon text-neon-foreground rounded-full shadow-lg shadow-neon/50 transition-all duration-300 hover:scale-110 hover:shadow-neon ${showScrollTop ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-16 pointer-events-none' className={`fixed bottom-8 right-8 z-50 p-4 bg-neon text-neon-foreground rounded-full shadow-lg shadow-neon/50 transition-all duration-300 hover:scale-110 hover:shadow-neon ${
showScrollTop ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-16 pointer-events-none'
}`} }`}
aria-label="Scroll to top" aria-label="Scroll to top"
> >

View File

@ -112,7 +112,7 @@ const BlogPost = () => {
<div className="min-h-screen bg-background-deep"> <div className="min-h-screen bg-background-deep">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero section with featured image */} {/* Hero section with featured image */}
<section className="relative h-[60vh] min-h-[400px] flex items-center justify-center overflow-hidden"> <section className="relative h-[60vh] min-h-[400px] flex items-center justify-center overflow-hidden">
{/* Background image */} {/* Background image */}
@ -122,7 +122,7 @@ const BlogPost = () => {
alt={post.title} alt={post.title}
className="w-full h-full object-cover" className="w-full h-full object-cover"
loading="eager" loading="eager"
fetchPriority="high" fetchpriority="high"
/> />
<div className="absolute inset-0 bg-gradient-to-t from-background-deep via-background-deep/60 to-transparent"></div> <div className="absolute inset-0 bg-gradient-to-t from-background-deep via-background-deep/60 to-transparent"></div>
</div> </div>

View File

@ -5,17 +5,8 @@ import { Mail, Phone, MapPin, Clock, DollarSign, Headphones } from 'lucide-react
import ScrollReveal from '@/components/ScrollReveal'; import ScrollReveal from '@/components/ScrollReveal';
import { useToast } from '@/hooks/use-toast'; import { useToast } from '@/hooks/use-toast';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { useSEO } from '@/hooks/useSEO';
const Contact = () => { const Contact = () => {
useSEO({
title: 'Contact Bay Area Affiliates | Free IT Consultation Corpus Christi',
description: 'Get in touch for a free IT consultation. 24-hour response, free assessment, no obligation. Serving Corpus Christi, Portland, Rockport & the Coastal Bend.',
keywords: 'contact IT support Corpus Christi, free IT consultation, Bay Area Affiliates phone, IT services quote Texas',
canonical: 'https://bayarea-cc.com/contact',
ogImage: 'https://bayarea-cc.com/og-image.png',
});
const { toast } = useToast(); const { toast } = useToast();
const imageRef = useRef<HTMLImageElement>(null); const imageRef = useRef<HTMLImageElement>(null);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
@ -90,7 +81,7 @@ const Contact = () => {
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero section with image background */} {/* Hero section with image background */}
<section className="relative h-screen flex items-center justify-center overflow-hidden"> <section className="relative h-screen flex items-center justify-center overflow-hidden">
{/* Background image with parallax */} {/* Background image with parallax */}
@ -167,7 +158,7 @@ const Contact = () => {
required required
value={formData.name} value={formData.name}
onChange={handleChange} onChange={handleChange}
className="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-neon focus:border-neon focus:bg-white/10 focus:shadow-[0_0_15px_rgba(51,102,255,0.3)] transition-all duration-300 text-foreground placeholder-foreground-muted/50" className="w-full px-4 py-3 bg-input border border-input-border rounded-lg focus:outline-none focus:ring-2 focus:ring-neon focus:border-transparent text-foreground"
placeholder="Your full name" placeholder="Your full name"
/> />
</div> </div>
@ -183,7 +174,7 @@ const Contact = () => {
required required
value={formData.email} value={formData.email}
onChange={handleChange} onChange={handleChange}
className="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-neon focus:border-neon focus:bg-white/10 focus:shadow-[0_0_15px_rgba(51,102,255,0.3)] transition-all duration-300 text-foreground placeholder-foreground-muted/50" className="w-full px-4 py-3 bg-input border border-input-border rounded-lg focus:outline-none focus:ring-2 focus:ring-neon focus:border-transparent text-foreground"
placeholder="your@email.com" placeholder="your@email.com"
/> />
</div> </div>
@ -200,7 +191,7 @@ const Contact = () => {
name="company" name="company"
value={formData.company} value={formData.company}
onChange={handleChange} onChange={handleChange}
className="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-neon focus:border-neon focus:bg-white/10 focus:shadow-[0_0_15px_rgba(51,102,255,0.3)] transition-all duration-300 text-foreground placeholder-foreground-muted/50" className="w-full px-4 py-3 bg-input border border-input-border rounded-lg focus:outline-none focus:ring-2 focus:ring-neon focus:border-transparent text-foreground"
placeholder="Your company name" placeholder="Your company name"
/> />
</div> </div>
@ -215,7 +206,7 @@ const Contact = () => {
name="phone" name="phone"
value={formData.phone} value={formData.phone}
onChange={handleChange} onChange={handleChange}
className="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-neon focus:border-neon focus:bg-white/10 focus:shadow-[0_0_15px_rgba(51,102,255,0.3)] transition-all duration-300 text-foreground placeholder-foreground-muted/50" className="w-full px-4 py-3 bg-input border border-input-border rounded-lg focus:outline-none focus:ring-2 focus:ring-neon focus:border-transparent text-foreground"
placeholder="(361) 555-0123" placeholder="(361) 555-0123"
/> />
</div> </div>
@ -232,7 +223,7 @@ const Contact = () => {
rows={5} rows={5}
value={formData.message} value={formData.message}
onChange={handleChange} onChange={handleChange}
className="w-full px-4 py-3 bg-white/5 backdrop-blur-sm border border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-neon focus:border-neon focus:bg-white/10 focus:shadow-[0_0_15px_rgba(51,102,255,0.3)] transition-all duration-300 text-foreground placeholder-foreground-muted/50 resize-none" className="w-full px-4 py-3 bg-input border border-input-border rounded-lg focus:outline-none focus:ring-2 focus:ring-neon focus:border-transparent text-foreground resize-none"
placeholder="Tell us about your IT needs or challenges..." placeholder="Tell us about your IT needs or challenges..."
/> />
</div> </div>
@ -381,47 +372,6 @@ const Contact = () => {
</ScrollReveal> </ScrollReveal>
</div> </div>
</section> </section>
{/* Service Area Map */}
<section className="py-16 bg-background-deep">
<div className="w-full max-w-[95%] mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal width="100%">
<div className="text-center mb-8">
<h2 className="font-heading font-bold text-3xl text-foreground mb-4">
Our Service Area
</h2>
<p className="text-foreground-muted max-w-2xl mx-auto">
Proudly serving Corpus Christi, Portland, Rockport, Aransas Pass, Kingsville, Port Aransas, and the entire Coastal Bend region.
</p>
</div>
<div className="card-dark p-2 overflow-hidden">
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d113726.84791849895!2d-97.48659164550781!3d27.800587899999997!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x8668fa3c40818e93%3A0x4e3c0a1c2bef9c65!2sCorpus%20Christi%2C%20TX!5e0!3m2!1sen!2sus!4v1736364000000!5m2!1sen!2sus"
width="100%"
height="450"
style={{ border: 0, borderRadius: '1rem' }}
allowFullScreen
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
title="Bay Area Affiliates Service Area - Corpus Christi and Coastal Bend"
aria-label="Google Maps showing our service area in Corpus Christi and the Coastal Bend region"
/>
</div>
<div className="mt-8 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
{['Corpus Christi', 'Portland', 'Rockport', 'Aransas Pass', 'Kingsville', 'Port Aransas'].map((city) => (
<div key={city} className="text-center">
<div className="card-dark p-3">
<MapPin className="w-5 h-5 text-neon mx-auto mb-1" />
<p className="text-xs text-foreground font-medium">{city}</p>
</div>
</div>
))}
</div>
</ScrollReveal>
</div>
</section>
</main> </main>
<Footer /> <Footer />

View File

@ -39,7 +39,7 @@ const DesktopHardware = () => {
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero Section */} {/* Hero Section */}
<section className="bg-gradient-to-br from-gray-900 via-gray-800 to-gray-600 text-white py-20"> <section className="bg-gradient-to-br from-gray-900 via-gray-800 to-gray-600 text-white py-20">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">

View File

@ -6,48 +6,18 @@ import ProcessTimeline from '@/components/home/ProcessTimeline';
import ServicesOverview from '@/components/home/ServicesOverview'; import ServicesOverview from '@/components/home/ServicesOverview';
import ProofSection from '@/components/home/ProofSection'; import ProofSection from '@/components/home/ProofSection';
import CTASection from '@/components/home/CTASection'; import CTASection from '@/components/home/CTASection';
import TrustedBy from '@/components/home/TrustedBy';
import ScrollReveal from '@/components/ScrollReveal';
import { useSEO } from '@/hooks/useSEO';
const Index = () => { const Index = () => {
useSEO({
title: 'IT Support Corpus Christi ⚡ 24/7 Managed Services | Bay Area Affiliates',
description: 'Stop IT headaches! 25+ years experience, 2-hour response time, 24/7 monitoring. Managed IT services in Corpus Christi & Coastal Bend. Free assessment. Call (361) 765-8400',
keywords: 'managed IT services corpus christi, IT support coastal bend, 24/7 IT monitoring, business computer solutions, Windows 11 migration, VPN setup, network security corpus christi',
canonical: 'https://bayarea-cc.com/',
ogImage: 'https://bayarea-cc.com/og-image.png',
});
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main id="main-content" role="main"> <main>
<HeroSection /> <HeroSection />
<ScrollReveal delay={0.2} width="100%">
<TrustedBy />
</ScrollReveal>
<ScrollReveal width="100%">
<ValuePillars /> <ValuePillars />
</ScrollReveal>
<ScrollReveal width="100%">
<ProcessTimeline /> <ProcessTimeline />
</ScrollReveal>
<ScrollReveal width="100%">
<ServicesOverview /> <ServicesOverview />
</ScrollReveal>
<ScrollReveal width="100%">
<ProofSection /> <ProofSection />
</ScrollReveal>
<ScrollReveal width="100%">
<CTASection /> <CTASection />
</ScrollReveal>
</main> </main>
<Footer /> <Footer />
</div> </div>

View File

@ -39,7 +39,7 @@ const NetworkAttachedStorage = () => {
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero Section */} {/* Hero Section */}
<section className="bg-gradient-to-br from-blue-900 via-blue-800 to-blue-600 text-white py-20"> <section className="bg-gradient-to-br from-blue-900 via-blue-800 to-blue-600 text-white py-20">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">

View File

@ -39,7 +39,7 @@ const NetworkInfrastructure = () => {
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero Section */} {/* Hero Section */}
<section className="bg-gradient-to-br from-teal-900 via-teal-800 to-teal-600 text-white py-20"> <section className="bg-gradient-to-br from-teal-900 via-teal-800 to-teal-600 text-white py-20">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">

View File

@ -39,7 +39,7 @@ const PerformanceUpgrades = () => {
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero Section */} {/* Hero Section */}
<section className="bg-gradient-to-br from-purple-900 via-purple-800 to-purple-600 text-white py-20"> <section className="bg-gradient-to-br from-purple-900 via-purple-800 to-purple-600 text-white py-20">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">

View File

@ -39,7 +39,7 @@ const PrinterScannerInstallation = () => {
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero Section */} {/* Hero Section */}
<section className="bg-gradient-to-br from-indigo-900 via-indigo-800 to-indigo-600 text-white py-20"> <section className="bg-gradient-to-br from-indigo-900 via-indigo-800 to-indigo-600 text-white py-20">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">

View File

@ -3,20 +3,11 @@ import Footer from '@/components/Footer';
import { Monitor, Wifi, Cloud, Shield, Database, Settings, CheckCircle } from 'lucide-react'; import { Monitor, Wifi, Cloud, Shield, Database, Settings, CheckCircle } from 'lucide-react';
import ScrollReveal from '@/components/ScrollReveal'; import ScrollReveal from '@/components/ScrollReveal';
import { useLayoutEffect, useRef } from 'react'; import { useLayoutEffect, useRef } from 'react';
import { useSEO } from '@/hooks/useSEO';
const Services = () => { const Services = () => {
const pageRef = useRef<HTMLDivElement>(null); const pageRef = useRef<HTMLDivElement>(null);
const heroImageRef = useRef<HTMLImageElement>(null); const heroImageRef = useRef<HTMLImageElement>(null);
useSEO({
title: 'IT Services Corpus Christi | Managed IT, Network, Cloud | Bay Area Affiliates',
description: 'Complete IT solutions for Corpus Christi businesses. Hardware support, network infrastructure, cloud services, secure remote access, backup & Microsoft 365. Free consultation.',
keywords: 'IT services Corpus Christi, managed IT Coastal Bend, network infrastructure Texas, cloud services, VPN setup, Microsoft 365',
canonical: 'https://bayarea-cc.com/services',
ogImage: 'https://bayarea-cc.com/og-image.png',
});
useLayoutEffect(() => { useLayoutEffect(() => {
// Dynamically import GSAP only when needed to reduce initial bundle size // Dynamically import GSAP only when needed to reduce initial bundle size
let ctx: any; let ctx: any;
@ -172,7 +163,7 @@ const Services = () => {
<div ref={pageRef} className="min-h-screen"> <div ref={pageRef} className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero section with image background */} {/* Hero section with image background */}
<section className="relative h-screen flex items-center justify-center overflow-hidden"> <section className="relative h-screen flex items-center justify-center overflow-hidden">
{/* Background image with parallax */} {/* Background image with parallax */}
@ -183,7 +174,7 @@ const Services = () => {
alt="Corpus Christi IT services data center with enterprise networking equipment and server infrastructure" alt="Corpus Christi IT services data center with enterprise networking equipment and server infrastructure"
className="w-full h-[110%] object-cover will-change-transform" className="w-full h-[110%] object-cover will-change-transform"
loading="eager" loading="eager"
fetchPriority="high" fetchpriority="high"
/> />
</div> </div>

View File

@ -39,7 +39,7 @@ const VpnSetup = () => {
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero Section */} {/* Hero Section */}
<section className="bg-gradient-to-br from-green-900 via-green-800 to-green-600 text-white py-20"> <section className="bg-gradient-to-br from-green-900 via-green-800 to-green-600 text-white py-20">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">

View File

@ -39,7 +39,7 @@ const WebServices = () => {
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero Section */} {/* Hero Section */}
<section className="bg-gradient-to-br from-emerald-900 via-emerald-800 to-emerald-600 text-white py-20"> <section className="bg-gradient-to-br from-emerald-900 via-emerald-800 to-emerald-600 text-white py-20">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">

View File

@ -39,7 +39,7 @@ const Windows11Transition = () => {
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<Navigation /> <Navigation />
<main role="main"> <main>
{/* Hero Section */} {/* Hero Section */}
<section className="bg-gradient-to-br from-blue-900 via-blue-800 to-blue-600 text-white py-20"> <section className="bg-gradient-to-br from-blue-900 via-blue-800 to-blue-600 text-white py-20">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">

View File

@ -1,210 +0,0 @@
import { Variants } from 'framer-motion';
// Smooth easing curves
export const easing = {
smooth: [0.6, 0.01, 0.05, 0.95],
snappy: [0.25, 0.46, 0.45, 0.94],
bouncy: [0.68, -0.55, 0.265, 1.55],
elegant: [0.43, 0.13, 0.23, 0.96],
};
// Hero section animations
export const heroVariants: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
delayChildren: 0.3,
},
},
};
export const heroItemVariants: Variants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.8,
ease: easing.elegant,
},
},
};
// Fade in up animation
export const fadeInUp: Variants = {
hidden: {
opacity: 0,
y: 60,
},
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.7,
ease: easing.elegant,
},
},
};
// Scale in animation
export const scaleIn: Variants = {
hidden: {
opacity: 0,
scale: 0.8,
},
visible: {
opacity: 1,
scale: 1,
transition: {
duration: 0.6,
ease: easing.smooth,
},
},
};
// Slide in from left
export const slideInLeft: Variants = {
hidden: {
opacity: 0,
x: -60,
},
visible: {
opacity: 1,
x: 0,
transition: {
duration: 0.7,
ease: easing.elegant,
},
},
};
// Slide in from right
export const slideInRight: Variants = {
hidden: {
opacity: 0,
x: 60,
},
visible: {
opacity: 1,
x: 0,
transition: {
duration: 0.7,
ease: easing.elegant,
},
},
};
// Stagger container
export const staggerContainer: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.12,
delayChildren: 0.1,
},
},
};
// Stagger item
export const staggerItem: Variants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
ease: easing.elegant,
},
},
};
// Button hover animation
export const buttonHover = {
scale: 1.02,
transition: {
duration: 0.2,
ease: easing.snappy,
},
};
export const buttonTap = {
scale: 0.98,
};
// Card hover animation
export const cardHover = {
y: -8,
transition: {
duration: 0.3,
ease: easing.smooth,
},
};
// Glow effect
export const glowHover = {
boxShadow: '0 0 30px rgba(51, 102, 255, 0.6)',
transition: {
duration: 0.3,
},
};
// Navigation animation
export const navVariants: Variants = {
hidden: {
y: -100,
opacity: 0,
},
visible: {
y: 0,
opacity: 1,
transition: {
duration: 0.6,
ease: easing.elegant,
},
},
};
// Page transition
export const pageTransition = {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -20 },
transition: { duration: 0.4, ease: easing.elegant },
};
// Parallax scroll effect
export const parallaxScroll = (scrollY: number, factor: number = 0.5) => ({
y: scrollY * factor,
transition: { type: 'tween', ease: 'linear', duration: 0 },
});
// Scroll reveal with intersection observer
export const scrollRevealVariants: Variants = {
hidden: {
opacity: 0,
y: 50,
},
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.7,
ease: easing.elegant,
},
},
};
// Magnetic button effect (advanced)
export const magneticEffect = (x: number, y: number, strength: number = 0.3) => ({
x: x * strength,
y: y * strength,
transition: {
type: 'spring',
stiffness: 150,
damping: 15,
mass: 0.1,
},
});

View File

@ -1,100 +0,0 @@
import { onCLS, onFCP, onLCP, onTTFB, onINP, type Metric } from 'web-vitals';
function sendToAnalytics(metric: Metric) {
// Log to console in development
if (import.meta.env.DEV) {
console.log('Web Vitals:', {
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
id: metric.id,
});
}
// Send to Google Analytics if available
if (typeof window !== 'undefined' && (window as any).gtag) {
(window as any).gtag('event', metric.name, {
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
event_category: 'Web Vitals',
event_label: metric.id,
non_interaction: true,
});
}
// Send to custom analytics endpoint if needed
if (import.meta.env.PROD && import.meta.env.VITE_ANALYTICS_ENDPOINT) {
fetch(import.meta.env.VITE_ANALYTICS_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
metric: metric.name,
value: metric.value,
rating: metric.rating,
id: metric.id,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
}),
keepalive: true,
}).catch((error) => {
console.error('Failed to send web vitals:', error);
});
}
}
export function reportWebVitals() {
// Core Web Vitals
onCLS(sendToAnalytics);
onLCP(sendToAnalytics);
onINP(sendToAnalytics); // Replaces deprecated FID
// Other important metrics
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
}
// Performance observer for additional metrics
export function observePerformance() {
if (typeof window === 'undefined' || !('PerformanceObserver' in window)) {
return;
}
// Observe long tasks (blocking the main thread)
try {
const longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
console.warn('Long Task detected:', {
duration: entry.duration,
startTime: entry.startTime,
name: entry.name,
});
}
}
});
longTaskObserver.observe({ entryTypes: ['longtask'] });
} catch (e) {
// Long task API not supported
}
// Observe layout shifts
try {
const layoutShiftObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if ((entry as any).hadRecentInput) continue;
const value = (entry as any).value;
if (value > 0.1) {
console.warn('Large Layout Shift:', {
value,
startTime: entry.startTime,
sources: (entry as any).sources,
});
}
}
});
layoutShiftObserver.observe({ entryTypes: ['layout-shift'] });
} catch (e) {
// Layout shift API not supported
}
}