12 Commits

Author SHA1 Message Date
68e5f92dde Update src/components/button/useButtonClick.ts 2026-03-25 23:39:02 +00:00
b76d1546b5 Update src/components/button/ButtonBounceEffect/ButtonBounceEffect.tsx 2026-03-25 23:38:13 +00:00
2eb4be8f1c Update src/components/button/ButtonBounceEffect/ButtonBounceEffect.tsx 2026-03-25 23:37:26 +00:00
99daeff82c Update src/components/button/useButtonClick.ts 2026-03-25 23:36:52 +00:00
c0c6b67710 Update src/components/button/ButtonBounceEffect/ButtonBounceEffect.tsx 2026-03-25 23:36:52 +00:00
a6f99ff072 Update src/components/button/useButtonClick.ts 2026-03-25 23:34:03 +00:00
b842d2ba67 Update src/app/page.tsx 2026-03-25 23:34:03 +00:00
05a3691716 Merge version_6 into main
Merge version_6 into main
2026-03-25 23:30:09 +00:00
498aafdfb6 Update src/app/page.tsx 2026-03-25 23:30:06 +00:00
b67d96e954 Switch to version 4: modified src/app/page.tsx 2026-03-25 23:27:07 +00:00
5e6075de8c Merge version_5 into main
Merge version_5 into main
2026-03-25 20:35:41 +00:00
3d9c52f1c4 Merge version_5 into main
Merge version_5 into main
2026-03-25 20:34:59 +00:00
3 changed files with 84 additions and 235 deletions

View File

@@ -1,8 +1,6 @@
"use client"; "use client";
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider"; import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
import ReactLenis, { useLenis } from "lenis/react";
import { useState } from 'react';
import ContactCTA from '@/components/sections/contact/ContactCTA'; import ContactCTA from '@/components/sections/contact/ContactCTA';
import FaqDouble from '@/components/sections/faq/FaqDouble'; import FaqDouble from '@/components/sections/faq/FaqDouble';
import FeatureCardTwentyThree from '@/components/sections/feature/FeatureCardTwentyThree'; import FeatureCardTwentyThree from '@/components/sections/feature/FeatureCardTwentyThree';
@@ -11,38 +9,9 @@ import HeroLogoBillboardSplit from '@/components/sections/hero/HeroLogoBillboard
import MetricCardTwo from '@/components/sections/metrics/MetricCardTwo'; import MetricCardTwo from '@/components/sections/metrics/MetricCardTwo';
import NavbarLayoutFloatingInline from '@/components/navbar/NavbarLayoutFloatingInline'; import NavbarLayoutFloatingInline from '@/components/navbar/NavbarLayoutFloatingInline';
import TestimonialCardTwo from '@/components/sections/testimonial/TestimonialCardTwo'; import TestimonialCardTwo from '@/components/sections/testimonial/TestimonialCardTwo';
import Input from '@/components/form/Input';
import { MessageCircle, Sparkles, Star, Wrench } from "lucide-react"; import { MessageCircle, Sparkles, Star, Wrench } from "lucide-react";
export default function LandingPage() { export default function LandingPage() {
const lenis = useLenis();
const scrollToSection = (sectionId) => {
const cleanId = String(sectionId).replace(/^#/, "");
if (lenis) {
lenis.scrollTo(`#${cleanId}`, { offset: 0 });
} else {
const element = document.getElementById(cleanId);
if (!element) return;
element.scrollIntoView({ behavior: "smooth", block: "start" });
}
};
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [message, setMessage] = useState("");
const handleSubmitQuote = (e) => {
e.preventDefault();
console.log({ name, email, phone, message });
alert("Quote request submitted! We'll be in touch soon.");
setName("");
setEmail("");
setPhone("");
setMessage("");
};
return ( return (
<ThemeProvider <ThemeProvider
defaultButtonVariant="icon-arrow" defaultButtonVariant="icon-arrow"
@@ -56,7 +25,7 @@ export default function LandingPage() {
secondaryButtonStyle="layered" secondaryButtonStyle="layered"
headingFontWeight="bold" headingFontWeight="bold"
> >
<ReactLenis root>
<div id="nav" data-section="nav"> <div id="nav" data-section="nav">
<NavbarLayoutFloatingInline <NavbarLayoutFloatingInline
navItems={[ navItems={[
@@ -71,7 +40,7 @@ export default function LandingPage() {
]} ]}
brandName="Results Roofing" brandName="Results Roofing"
button={{ button={{
text: "Free Estimate", onClick: () => scrollToSection("quote")}} text: "Free Estimate", href: "#quote"}}
animateOnLoad={true} animateOnLoad={true}
/> />
</div> </div>
@@ -84,9 +53,9 @@ export default function LandingPage() {
description="Roofing that feels premium before the first shingle goes on. Results Roofing helps Dallas homeowners with inspections, repairs, replacements, and insurance guidance through a polished, high-trust experience from start to finish." description="Roofing that feels premium before the first shingle goes on. Results Roofing helps Dallas homeowners with inspections, repairs, replacements, and insurance guidance through a polished, high-trust experience from start to finish."
buttons={[ buttons={[
{ {
text: "Get My Free Roof Inspection", onClick: () => scrollToSection("quote")}, text: "Get My Free Roof Inspection", href: "#quote"},
{ {
text: "See Why Dallas Trusts Us", onClick: () => scrollToSection("reviews")}, text: "See Why Dallas Trusts Us", href: "#reviews"},
]} ]}
layoutOrder="default" layoutOrder="default"
imageSrc="http://img.b2bpic.net/free-photo/chisinau-arena-sunset-moldova_1268-16015.jpg" imageSrc="http://img.b2bpic.net/free-photo/chisinau-arena-sunset-moldova_1268-16015.jpg"
@@ -209,11 +178,11 @@ export default function LandingPage() {
background={{ background={{
variant: "radial-gradient"}} variant: "radial-gradient"}}
tag="Ready when you are" tag="Ready when you are"
title="Protect your home with a roof that looks better and performs better." title="Get Your Free Roofing Estimate"
description="Whether you need an inspection, a fast repair, or a full replacement, Results Roofing is positioned here as the premium choice that still feels approachable." description="Need an inspection, repair, or full roof replacement? Tell us what is going on and our team will reach out to schedule your free estimate."
buttons={[ buttons={[
{ {
text: "Get a Free Estimate", onClick: () => scrollToSection("quote")}, text: "Request Free Estimate", href: "#quote"},
{ {
text: "Call Results Roofing", href: "tel:+12145550199"}, text: "Call Results Roofing", href: "tel:+12145550199"},
]} ]}
@@ -223,68 +192,6 @@ export default function LandingPage() {
/> />
</div> </div>
<div id="quote" data-section="quote" className="py-20 bg-card rounded-lg mx-auto max-w-content-width px-4 md:px-8">
<div className="text-center mb-12">
<h2 className="text-4xl md:text-5xl font-bold leading-tight text-foreground">Get Your Free Estimate</h2>
<p className="mt-4 text-lg text-foreground/80">Fill out the form below and we'll get back to you shortly.</p>
</div>
<form onSubmit={handleSubmitQuote} className="max-w-xl mx-auto space-y-6">
<div>
<label htmlFor="name" className="block text-sm font-medium text-foreground">Name</label>
<Input
id="name"
type="text"
placeholder="Your Name"
value={name}
onChange={setName}
required
className="mt-1 block w-full text-foreground"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-foreground">Email</label>
<Input
id="email"
type="email"
placeholder="your@email.com"
value={email}
onChange={setEmail}
required
className="mt-1 block w-full text-foreground"
/>
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-foreground">Phone</label>
<Input
id="phone"
type="tel"
placeholder="(___) ___-____"
value={phone}
onChange={setPhone}
className="mt-1 block w-full text-foreground"
/>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-foreground">Message</label>
<textarea
id="message"
rows={4}
placeholder="Tell us about your roofing needs..."
value={message}
onChange={(e) => setMessage(e.target.value)}
className="mt-1 block w-full p-3 border border-border rounded-md shadow-sm bg-background text-foreground focus:ring-primary-cta focus:border-primary-cta sm:text-sm"
style={{ '--border': 'var(--accent)', '--background': 'var(--card)' } as any}
/>
</div>
<button
type="submit"
className="w-full py-3 px-4 rounded-md shadow-sm text-base font-medium text-primary-cta-text bg-primary-cta hover:bg-primary-cta-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-cta-hover"
>
Submit Request
</button>
</form>
</div>
<div id="footer" data-section="footer"> <div id="footer" data-section="footer">
<FooterBaseCard <FooterBaseCard
logoText="Results Roofing" logoText="Results Roofing"
@@ -292,19 +199,19 @@ export default function LandingPage() {
{ {
title: "Quick Links", items: [ title: "Quick Links", items: [
{ {
label: "Services", onClick: () => scrollToSection("services")}, label: "Services", href: "#services"},
{ {
label: "Process", onClick: () => scrollToSection("process")}, label: "Process", href: "#process"},
{ {
label: "Reviews", onClick: () => scrollToSection("reviews")}, label: "Reviews", href: "#reviews"},
{ {
label: "FAQ", onClick: () => scrollToSection("faq")}, label: "FAQ", href: "#faq"},
], ],
}, },
{ {
title: "Contact", items: [ title: "Contact", items: [
{ {
label: "Get an Estimate", onClick: () => scrollToSection("quote")}, label: "Get an Estimate", href: "#quote"},
{ {
label: "Call Us: (214) 555-0199", href: "tel:+12145550199"}, label: "Call Us: (214) 555-0199", href: "tel:+12145550199"},
{ {
@@ -323,7 +230,7 @@ export default function LandingPage() {
copyrightText="© 2024 | Results Roofing. All rights reserved." copyrightText="© 2024 | Results Roofing. All rights reserved."
/> />
</div> </div>
</ReactLenis>
</ThemeProvider> </ThemeProvider>
); );
} }

View File

@@ -1,74 +1,57 @@
"use client"; import React, { forwardRef } from "react";
import { cn } from "@/lib/utils";
import { useButtonClick } from "@/components/button/useButtonClick";
import { useRef, memo } from "react"; interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
import { useCharAnimation } from "../useCharAnimation";
import { useButtonClick } from "../useButtonClick";
import { cls } from "@/lib/utils";
import "./BounceButton.css";
interface ButtonBounceEffectProps {
text: string; text: string;
onClick?: () => void;
href?: string; href?: string;
className?: string;
bgClassName?: string; bgClassName?: string;
textClassName?: string; textClassName?: string;
disabled?: boolean; iconClassName?: string;
ariaLabel?: string; newTab?: boolean;
type?: "button" | "submit" | "reset";
scrollToSection?: boolean;
} }
const ButtonBounceEffect = ({ export const ButtonBounceEffect = forwardRef<HTMLButtonElement, ButtonProps>(
text, (
onClick, {
href, text,
className = "", onClick,
bgClassName = "", href,
textClassName = "", className,
disabled = false, bgClassName,
ariaLabel, textClassName,
type = "button", iconClassName,
scrollToSection, disabled = false,
}: ButtonBounceEffectProps) => { ariaLabel,
const buttonRef = useRef<HTMLButtonElement>(null); type = "button", newTab,
const handleClick = useButtonClick(href, onClick, scrollToSection); ...props
},
ref
) => {
const clickHandler = useButtonClick(href, onClick, newTab);
useCharAnimation(buttonRef, text); return (
<button
return ( className={cn(
<button "group relative flex h-12 w-full items-center justify-center rounded-lg bg-primary-cta p-3 text-sm font-medium text-primary-cta-foreground transition-all duration-300 ease-out active:scale-95", className
ref={buttonRef}
type={type}
onClick={handleClick}
data-href={href}
disabled={disabled}
aria-label={ariaLabel || text}
className={cls(
"bounce-button relative cursor-pointer flex items-center justify-center bg-transparent border-none leading-none no-underline h-9 px-6 min-w-0 w-fit max-w-full rounded-theme text-primary-cta-text",
"disabled:cursor-not-allowed disabled:opacity-50",
className
)}
>
<div
className={cls(
"bounce-button-bg absolute! inset-0 rounded-theme primary-button",
bgClassName
)}
></div>
<span
data-button-animate-chars=""
className={cls(
"bounce-button-text relative text-sm inline-block overflow-hidden truncate whitespace-nowrap",
textClassName
)} )}
onClick={clickHandler}
aria-label={ariaLabel}
disabled={disabled}
type={type}
ref={ref}
{...props}
> >
{text} <span
</span> className={cn(
</button> "relative flex items-center gap-2 translate-y-0 group-hover:-translate-y-1 group-active:translate-y-0 transition-transform duration-300 ease-out", textClassName
); )}
}; >
{text}
</span>
</button>
);
}
);
ButtonBounceEffect.displayName = "ButtonBounceEffect"; ButtonBounceEffect.displayName = "ButtonBounceEffect";
export default memo(ButtonBounceEffect);

View File

@@ -1,74 +1,33 @@
"use client"; "use client";
import { useLenis } from "lenis/react"; import { useCallback } from "react";
import { useRouter, usePathname } from "next/navigation"; // No import for 'lenis' or 'lenis/react'
import { useEffect } from "react";
export const useButtonClick = ( /**
href?: string, * A hook to provide a consistent click handler for buttons, especially for internal hash navigation.
onClick?: () => void, * It ensures smooth scrolling without relying on external libraries like Lenis or problematic querySelector patterns.
scrollToSection?: boolean */
) => { export function useButtonClick() {
const lenis = useLenis(); const handleButtonClick = useCallback((event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>, href?: string) => {
const router = useRouter(); if (href && href.startsWith("#")) {
const pathname = usePathname(); const id = href.substring(1); // Remove the '#'
// Use getElementById directly to avoid querySelector issues with complex IDs (like '##quote')
const targetElement = document.getElementById(id);
const scrollToElement = (sectionId: string, delay: number = 100) => { if (targetElement) {
setTimeout(() => { event.preventDefault(); // Prevent default browser jump
if (lenis) { targetElement.scrollIntoView({ behavior: "smooth" });
lenis.scrollTo(`#${sectionId}`, { offset: 0 }); // Optionally update the URL hash without a full page reload or additional scroll
// history.pushState(null, '', href);
} else { } else {
const element = document.getElementById(sectionId); console.warn(`Attempted to scroll to element with ID '${id}', but it was not found on the page.`);
if (element) { // If the element is not found, we let the default behavior happen (which might lead to a page refresh to the root)
element.scrollIntoView({ behavior: "smooth", block: "start" }); // or prevent it and do nothing. Given it's a "fix" for a bug, a silent failure with a console warning is safer than unexpected navigation.
}
}
}, delay);
};
const handleClick = () => {
if (href) {
const isExternalLink = /^(https?:\/\/|www\.)/.test(href);
if (isExternalLink) {
window.open(
href.startsWith("www.") ? `https://${href}` : href,
"_blank",
"noopener,noreferrer"
);
} else if (href.startsWith("/")) {
const [path, hash] = href.split("#");
if (path !== pathname) {
router.push(path);
if (hash) {
setTimeout(() => {
window.location.hash = hash;
scrollToElement(hash, 100);
}, 100);
}
} else {
if (hash) {
window.location.hash = hash;
scrollToElement(hash, 50);
} else if (scrollToSection) {
const sectionId = path.replace(/^\//, "").replace(/\//g, "-");
scrollToElement(sectionId, 50);
}
}
} else {
scrollToElement(href, 50);
} }
} }
onClick?.(); // For non-hash hrefs (external links, full paths), let the browser handle it
}; // unless there's a specific programmatic override needed (which isn't requested here).
}, []);
useEffect(() => { return { handleButtonClick };
if (typeof window !== "undefined" && window.location.hash) { }
const hash = window.location.hash.replace("#", "");
scrollToElement(hash, 300);
}
}, [pathname]);
return handleClick;
};