7 Commits

Author SHA1 Message Date
6483e3d191 Merge version_2_1776516431905 into main
Merge version_2_1776516431905 into main
2026-04-18 12:56:19 +00:00
kudinDmitriyUp
aaef7799c2 Bob AI: fix build errors (attempt 1) 2026-04-18 12:55:02 +00:00
kudinDmitriyUp
e2bcaa5c81 Bob AI: Add complex animations to interactive elements like hover ef 2026-04-18 12:54:13 +00:00
kudinDmitriyUp
71e5f5a314 Bob AI: Apply subtle textures to background sections or specific con 2026-04-18 12:52:28 +00:00
kudinDmitriyUp
fc4f25f155 Bob AI: fix build errors (attempt 1) 2026-04-18 12:51:19 +00:00
kudinDmitriyUp
383a12433d Bob AI: Introduce gradients to key UI elements such as buttons, head 2026-04-18 12:50:33 +00:00
kudinDmitriyUp
911f3b1ab6 Bob AI: Implement a dynamic, animated background effect across the e 2026-04-18 12:48:43 +00:00
4 changed files with 141 additions and 13 deletions

View File

@@ -1,3 +1,5 @@
import { useRef } from 'react';
import useOnScreen from '@/hooks/useOnScreen';
import AboutMediaOverlay from '@/components/sections/about/AboutMediaOverlay';
import ContactSplitForm from '@/components/sections/contact/ContactSplitForm';
import FaqTwoColumn from '@/components/sections/faq/FaqTwoColumn';
@@ -9,8 +11,29 @@ import NavbarCentered from '@/components/ui/NavbarCentered';
import PricingHighlightedCards from '@/components/sections/pricing/PricingHighlightedCards';
import TestimonialOverlayCards from '@/components/sections/testimonial/TestimonialOverlayCards';
import { Award, CheckCircle, Zap } from "lucide-react";
import './styles/animations.css';
export default function App() {
const heroRef = useRef<HTMLDivElement>(null);
const aboutRef = useRef<HTMLDivElement>(null);
const servicesRef = useRef<HTMLDivElement>(null);
const metricsRef = useRef<HTMLDivElement>(null);
const testimonialsRef = useRef<HTMLDivElement>(null);
const pricingRef = useRef<HTMLDivElement>(null);
const faqRef = useRef<HTMLDivElement>(null);
const contactRef = useRef<HTMLDivElement>(null);
const footerRef = useRef<HTMLDivElement>(null);
const heroOnScreen = useOnScreen(heroRef, "-100px");
const aboutOnScreen = useOnScreen(aboutRef, "-100px");
const servicesOnScreen = useOnScreen(servicesRef, "-100px");
const metricsOnScreen = useOnScreen(metricsRef, "-100px");
const testimonialsOnScreen = useOnScreen(testimonialsRef, "-100px");
const pricingOnScreen = useOnScreen(pricingRef, "-100px");
const faqOnScreen = useOnScreen(faqRef, "-100px");
const contactOnScreen = useOnScreen(contactRef, "-100px");
const footerOnScreen = useOnScreen(footerRef, "-100px");
return (
<>
<div id="nav" data-section="nav">
@@ -41,7 +64,7 @@ export default function App() {
/>
</div>
<div id="hero" data-section="hero">
<div ref={heroRef} id="hero" data-section="hero" className={`animate-on-scroll ${heroOnScreen ? 'is-visible' : ''}`}>
<HeroSplit
tag="Serving Greater LA"
title="Beat the Heat: Expert AC Service, Today"
@@ -58,7 +81,7 @@ export default function App() {
/>
</div>
<div id="about" data-section="about">
<div ref={aboutRef} id="about" data-section="about" className={`animate-on-scroll ${aboutOnScreen ? 'is-visible' : ''}`}>
<AboutMediaOverlay
tag="About Us"
title="Trusted LA Cooling Experts"
@@ -67,7 +90,7 @@ export default function App() {
/>
</div>
<div id="services" data-section="services">
<div ref={servicesRef} id="services" data-section="services" className={`texture-noise animate-on-scroll ${servicesOnScreen ? 'is-visible' : ''}`}>
<FeaturesTimelineCards
tag="Our Expertise"
title="Comprehensive Cooling Solutions"
@@ -92,7 +115,7 @@ export default function App() {
/>
</div>
<div id="metrics" data-section="metrics">
<div ref={metricsRef} id="metrics" data-section="metrics" className={`animate-on-scroll ${metricsOnScreen ? 'is-visible' : ''}`}>
<MetricsIconCards
tag="Reliability Metrics"
title="Why LA Homeowners Trust Us"
@@ -117,7 +140,7 @@ export default function App() {
/>
</div>
<div id="testimonials" data-section="testimonials">
<div ref={testimonialsRef} id="testimonials" data-section="testimonials" className={`animate-on-scroll ${testimonialsOnScreen ? 'is-visible' : ''}`}>
<TestimonialOverlayCards
tag="Customer Reviews"
title="Proven Quality Service"
@@ -162,7 +185,7 @@ export default function App() {
/>
</div>
<div id="pricing" data-section="pricing">
<div ref={pricingRef} id="pricing" data-section="pricing" className={`gradient-card animate-on-scroll ${pricingOnScreen ? 'is-visible' : ''}`}>
<PricingHighlightedCards
tag="Fair Pricing"
title="Simple, Upfront Pricing"
@@ -216,7 +239,7 @@ export default function App() {
/>
</div>
<div id="faq" data-section="faq">
<div ref={faqRef} id="faq" data-section="faq" className={`texture-noise animate-on-scroll ${faqOnScreen ? 'is-visible' : ''}`}>
<FaqTwoColumn
tag="Frequently Asked Questions"
title="Questions Answered"
@@ -242,7 +265,7 @@ export default function App() {
/>
</div>
<div id="contact" data-section="contact">
<div ref={contactRef} id="contact" data-section="contact" className={`animate-on-scroll ${contactOnScreen ? 'is-visible' : ''}`}>
<ContactSplitForm
tag="Contact Us"
title="Schedule Your Service"
@@ -277,7 +300,7 @@ export default function App() {
/>
</div>
<div id="footer" data-section="footer">
<div ref={footerRef} id="footer" data-section="footer" className={`animate-on-scroll ${footerOnScreen ? 'is-visible' : ''}`}>
<FooterBasic
columns={[
{

30
src/hooks/useOnScreen.ts Normal file
View File

@@ -0,0 +1,30 @@
import { useState, useEffect, type RefObject } from 'react';
export default function useOnScreen<T extends HTMLElement>(ref: RefObject<T>, rootMargin = '0px') {
const [isIntersecting, setIntersecting] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIntersecting(true);
observer.unobserve(entry.target);
}
},
{
rootMargin,
}
);
const currentRef = ref.current;
if (currentRef) {
observer.observe(currentRef);
}
return () => {
if (currentRef) {
observer.unobserve(currentRef);
}
};
}, [ref, rootMargin]);
return isIntersecting;
}

View File

@@ -2,18 +2,20 @@
@import "tailwindcss";
@import "./styles/masks.css";
@import "./styles/animations.css";
@import "./styles/textures.css";
:root {
/* @colorThemes/lightTheme/grayBlueAccent */
--background: #ffffff;
--card: #f9f9f9;
--foreground: #000612e6;
--primary-cta: #106EFB;
--primary-cta: linear-gradient(135deg, #106EFB 0%, #053A8C 100%);
--primary-cta-text: #ffffff;
--secondary-cta: #f9f9f9;
--secondary-cta-text: #000612e6;
--accent: #e2e2e2;
--background-accent: #106EFB;
--background-accent: linear-gradient(135deg, #106EFB 0%, #053A8C 100%);
--card-gradient: linear-gradient(145deg, var(--card), color-mix(in srgb, #106EFB 5%, var(--card)));
/* @layout/border-radius/rounded */
--radius: 0.5rem;
@@ -134,13 +136,15 @@ html {
body {
margin: 0;
background-color: var(--background);
color: var(--foreground);
font-family: '${nunito.variable}', sans-serif;
position: relative;
min-height: 100vh;
overscroll-behavior: none;
overscroll-behavior-y: none;
background: linear-gradient(-45deg, var(--background), color-mix(in srgb, var(--primary-cta) 5%, var(--background)), var(--background));
background-size: 400% 400%;
animation: animated-gradient 15s ease infinite;
}
h1,
@@ -159,10 +163,15 @@ h6 {
box-shadow: color-mix(in srgb, var(--color-foreground) 5%, transparent) 0px 4px 32px 0px;
}
.gradient-card .card {
background: var(--card-gradient);
}
/* WEBILD_PRIMARY_BUTTON */
/* @primaryButtons/accent-edge */
.primary-button {
background: linear-gradient(180deg, var(--color-primary-cta) 0%, color-mix(in srgb, var(--color-primary-cta) 90%, var(--color-background)) 100%);
background: var(--color-primary-cta);
background-size: 200% auto;
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-accent) 60%, transparent), 0 4px 12px -2px color-mix(in srgb, var(--color-accent) 35%, transparent), inset 0 1px 0 0 color-mix(in srgb, var(--color-foreground) 20%, transparent);
}

View File

@@ -156,3 +156,69 @@
.animate-marquee-horizontal-reverse {
animation: marquee-horizontal-reverse 15s linear infinite;
}
@keyframes gradient-shift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.animated-gradient-button:hover {
animation: gradient-shift 4s ease infinite;
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slide-in-left {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.animate-on-scroll {
opacity: 0;
}
.animate-on-scroll.is-visible {
animation: fade-in-up 0.8s ease-out forwards;
}
/* Hover effects */
.card, .primary-button, .secondary-button {
transition: transform 0.3s ease-out, box-shadow 0.3s ease-out;
}
.card:hover {
transform: translateY(-5px);
box-shadow: color-mix(in srgb, var(--color-foreground) 8%, transparent) 0px 8px 40px 0px;
}
.primary-button:hover {
transform: translateY(-2px);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-accent) 60%, transparent), 0 8px 16px -4px color-mix(in srgb, var(--color-accent) 45%, transparent), inset 0 1px 0 0 color-mix(in srgb, var(--color-foreground) 20%, transparent);
}
.secondary-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 0.08);
}