Merge version_2_1776516431905 into main #1
41
src/App.tsx
41
src/App.tsx
@@ -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
30
src/hooks/useOnScreen.ts
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user