This commit is contained in:
2026-03-24 21:08:57 -04:00
parent 9d21f06d51
commit 5be7d43446
20 changed files with 968 additions and 625 deletions

View File

@@ -0,0 +1,165 @@
import { BuildingOffice2Icon, EnvelopeIcon, PhoneIcon } from '@heroicons/react/24/outline'
interface Props {
phoneLabel: string
phoneValue: string
emailLabel: string
emailValue: string
addressLabel: string
addressValue: string
serviceAreaLabel: string
serviceArea: string
firstNameLabel?: string
lastNameLabel?: string
emailFieldLabel?: string
phoneFieldLabel?: string
messageLabel?: string
submitLabel?: string
}
export default function Contact({
phoneLabel,
phoneValue,
emailLabel,
emailValue,
addressLabel,
addressValue,
serviceAreaLabel,
serviceArea,
firstNameLabel = 'Prénom',
lastNameLabel = 'Nom',
emailFieldLabel = 'Courriel',
phoneFieldLabel = 'Téléphone',
messageLabel = 'Message',
submitLabel = 'Envoyer',
}: Props) {
const inputClass =
'block w-full rounded-md bg-white px-3.5 py-2 text-base text-brand-dark outline-1 -outline-offset-1 outline-brand-200 placeholder:text-brand-400 focus:outline-2 focus:-outline-offset-2 focus:outline-brand-600 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-brand-400 dark:focus:outline-brand-500'
const labelClass = 'block text-sm/6 font-semibold text-brand-dark dark:text-white'
return (
<div className="relative isolate bg-white dark:bg-brand-900">
<div className="mx-auto grid max-w-7xl grid-cols-1 lg:grid-cols-2">
<div className="relative px-6 pt-12 pb-20 lg:static">
<div className="mx-auto max-w-xl lg:mx-0 lg:max-w-lg">
<div className="absolute inset-y-0 left-0 -z-10 w-full overflow-hidden bg-brand-50 ring-1 ring-brand-900/10 lg:w-1/2 dark:bg-brand-900 dark:ring-white/10">
<svg
aria-hidden="true"
className="absolute inset-0 size-full mask-[radial-gradient(100%_100%_at_top_right,white,transparent)] stroke-brand-200 dark:stroke-white/10"
>
<defs>
<pattern
x="100%"
y={-1}
id="83fd4e5a-9d52-42fc-97b6-718e5d7ee527"
width={200}
height={200}
patternUnits="userSpaceOnUse"
>
<path d="M130 200V.5M.5 .5H200" fill="none" />
</pattern>
</defs>
<rect width="100%" height="100%" strokeWidth={0} className="fill-white dark:fill-brand-900" />
<svg x="100%" y={-1} className="overflow-visible fill-brand-50 dark:fill-brand-800/20">
<path d="M-470.5 0h201v201h-201Z" strokeWidth={0} />
</svg>
<rect fill="url(#83fd4e5a-9d52-42fc-97b6-718e5d7ee527)" width="100%" height="100%" strokeWidth={0} />
</svg>
<div
aria-hidden="true"
className="absolute top-[calc(100%-13rem)] -left-56 hidden transform-gpu blur-3xl lg:top-[calc(50%-7rem)] lg:left-[max(-14rem,calc(100%-59rem))] dark:block"
>
<div
style={{
clipPath:
'polygon(74.1% 56.1%, 100% 38.6%, 97.5% 73.3%, 85.5% 100%, 80.7% 98.2%, 72.5% 67.7%, 60.2% 37.8%, 52.4% 32.2%, 47.5% 41.9%, 45.2% 65.8%, 27.5% 23.5%, 0.1% 35.4%, 17.9% 0.1%, 27.6% 23.5%, 76.1% 2.6%, 74.1% 56.1%)',
}}
className="aspect-1155/678 w-288.75 bg-linear-to-br from-brand-300 to-brand-600 opacity-10 dark:opacity-20"
/>
</div>
</div>
<dl className="mt-10 space-y-4 text-base/7 text-brand-700 dark:text-brand-100">
<div className="flex gap-x-4">
<dt className="flex-none">
<span className="sr-only">{addressLabel}</span>
<BuildingOffice2Icon aria-hidden="true" className="h-7 w-6 text-brand-400" />
</dt>
<dd>
{addressValue}
<br />
<span className="text-sm text-brand-500">{serviceAreaLabel} {serviceArea}</span>
</dd>
</div>
<div className="flex gap-x-4">
<dt className="flex-none">
<span className="sr-only">{phoneLabel}</span>
<PhoneIcon aria-hidden="true" className="h-7 w-6 text-brand-400" />
</dt>
<dd>
<a href={`tel:${phoneValue.replace(/\D/g, '')}`} className="hover:text-brand-dark dark:hover:text-white">
{phoneValue}
</a>
</dd>
</div>
<div className="flex gap-x-4">
<dt className="flex-none">
<span className="sr-only">{emailLabel}</span>
<EnvelopeIcon aria-hidden="true" className="h-7 w-6 text-brand-400" />
</dt>
<dd>
<a href={`mailto:${emailValue}`} className="hover:text-brand-dark dark:hover:text-white">
{emailValue}
</a>
</dd>
</div>
</dl>
</div>
</div>
<form action="#" method="POST" className="px-6 pt-12 pb-20">
<div className="mx-auto max-w-xl lg:mr-0 lg:max-w-lg">
<div className="grid grid-cols-1 gap-x-8 gap-y-6 sm:grid-cols-2">
<div>
<label htmlFor="first-name" className={labelClass}>{firstNameLabel}</label>
<div className="mt-2.5">
<input id="first-name" name="first-name" type="text" autoComplete="given-name" className={inputClass} />
</div>
</div>
<div>
<label htmlFor="last-name" className={labelClass}>{lastNameLabel}</label>
<div className="mt-2.5">
<input id="last-name" name="last-name" type="text" autoComplete="family-name" className={inputClass} />
</div>
</div>
<div className="sm:col-span-2">
<label htmlFor="email" className={labelClass}>{emailFieldLabel}</label>
<div className="mt-2.5">
<input id="email" name="email" type="email" autoComplete="email" className={inputClass} />
</div>
</div>
<div className="sm:col-span-2">
<label htmlFor="phone-number" className={labelClass}>{phoneFieldLabel}</label>
<div className="mt-2.5">
<input id="phone-number" name="phone-number" type="tel" autoComplete="tel" className={inputClass} />
</div>
</div>
<div className="sm:col-span-2">
<label htmlFor="message" className={labelClass}>{messageLabel}</label>
<div className="mt-2.5">
<textarea id="message" name="message" rows={4} className={inputClass} defaultValue={''} />
</div>
</div>
</div>
<div className="mt-8 flex justify-end">
<button
type="submit"
className="rounded-md bg-brand-600 px-3.5 py-2.5 text-center text-sm font-semibold text-white shadow-xs hover:bg-brand-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-600 dark:bg-brand-500 dark:hover:bg-brand-400 dark:focus-visible:outline-brand-500"
>
{submitLabel}
</button>
</div>
</div>
</form>
</div>
</div>
)
}

View File

@@ -11,8 +11,8 @@ import photo3 from "@/assets/photos/silver-and-white-apartment-interior-2023-11-
import photo4 from "@/assets/photos/a-bedroom-with-white-walls-and-a-bed-and-a-2023-11-27-05-25-30-utc.webp";
import photo5 from "@/assets/photos/empty-room-with-window-2023-11-27-05-08-12-utc.webp";
import logo from "@/assets/cachet-hero-logo.svg";
export type HeroContent = {
pillCta: string;
title: string;
title2: string;
subtitle: string;
@@ -35,7 +35,7 @@ const autoplayPlugin = Autoplay({
});
export default function Hero({ content }: { content: HeroContent }) {
const { pillCta, title, title2, subtitle, cta, ctaHref } = content;
const { subtitle, cta, ctaHref } = content;
return (
<div className="bg-white dark:bg-gray-900">
@@ -61,7 +61,7 @@ export default function Hero({ content }: { content: HeroContent }) {
<div className="relative z-10 mx-auto max-w-7xl px-6 lg:px-8 h-full flex items-center justify-center pt-14">
<div className="mx-auto max-w-4xl text-center">
<div className="hidden sm:mb-8 sm:flex sm:justify-center">
{/* <div className="hidden sm:mb-8 sm:flex sm:justify-center">
<div className="flex flex-wrap items-center justify-center gap-2 gap-x-3.5 rounded-full px-4 py-2 text-sm/6 text-gray-300 ring-1 ring-white/20">
<span>RBQ {general.rbq}</span>
<span className="text-white/30" aria-hidden>
@@ -73,7 +73,7 @@ export default function Hero({ content }: { content: HeroContent }) {
</span>
<span>{pillCta}</span>
</div>
</div>
</div> */}
{/* <h1
className="text-5xl font-semibold tracking-tight text-balance text-white sm:text-7xl"
style={{ fontFamily: "var(--font-display)" }}

View File

@@ -0,0 +1,44 @@
interface ContactHeroContent {
imageSrc: string;
imageAlt: string;
title: string;
subtitle: string;
}
export default function ContactHero({ content }: { content: ContactHeroContent }) {
const { imageSrc, imageAlt, title, subtitle } = content;
return (
<div className="relative flex items-center justify-center overflow-hidden bg-brand-600 min-h-[50vh]">
{/* Decorative background image and gradient */}
<div aria-hidden="true" className="absolute inset-0">
<div className="absolute inset-0 mx-auto overflow-hidden">
<img
alt={imageAlt}
src="/images/empty-room-blue-wall-with-moulding-and-parquet-fl-2023-11-27-04-52-31-utc.webp"
className="size-full object-cover"
/>
</div>
<div className="absolute inset-0" />
<div className="absolute inset-0 bg-linear-to-t from-white via-white opacity-40" />
</div>
{/* Callout */}
<section
aria-labelledby="sale-heading"
className="relative mx-auto flex max-w-7xl flex-col items-center justify-center text-center sm:px-6 lg:px-8"
>
<div className="mx-auto max-w-2xl lg:max-w-none">
<h2 id="sale-heading" className="text-4xl font-bold tracking-tight text-white sm:text-5xl lg:text-6xl">
{title}
</h2>
<p className="mx-auto mt-4 max-w-xl text-xl text-white">
{subtitle}
</p>
</div>
</section>
</div>
)
}

View File

@@ -1,46 +1,29 @@
const featuredTestimonial = {
body: 'Integer id nunc sit semper purus. Bibendum at lacus ut arcu blandit montes vitae auctor libero. Hac condimentum dignissim nibh vulputate ut nunc. Amet nibh orci mi venenatis blandit vel et proin. Non hendrerit in vel ac diam.',
author: {
name: 'Brenna Goyette',
handle: 'brennagoyette',
imageUrl:
'https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=1024&h=1024&q=80',
logoUrl: 'https://tailwindcss.com/plus-assets/img/logos/savvycal-logo-gray-900.svg',
},
}
const testimonials = [
[
[
{
iframe: <iframe className="w-full h-full rounded-2xl " src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Fjean.christophe.ali.2025%2Fposts%2Fpfbid02qsqq2Rn4vRx1gCG3q5e5M2ByPXH6zMux59fWvotfsQxpuWyUh46TZuwCND9iKpwnl&show_text=true&width=500" width="500" height="194" style={{ border: 'none', overflow: 'hidden' }} scrolling="no" frameBorder={0} allowFullScreen allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"></iframe>,
key: 'jean-christophe-ali',
}
],
[{
iframe: <iframe className="w-full h-full rounded-2xl " src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Fcharlotte.lafrance.CL%2Fposts%2Fpfbid026Jw7aer2r84q27kgcZ1PKg8q2sqKgZc6nJspP42CTEAU6ac8FF4Ptj8ap8XGh6Wl&show_text=true&width=500" width="500" height="166" style={{ border: 'none', overflow: 'hidden' }} scrolling="no" frameBorder={0} allowFullScreen allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"></iframe>,
key: 'charlotte-lafrance',
}
],
],
[
[{
iframe: <iframe className="w-full h-full rounded-2xl " src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Fcolin.griffin.165%2Fposts%2Fpfbid0iCA6hoF5X32gUJSBcDUiXP2hxhxzMmEiC5Att3CDDwFckrHa7yxyGiLBmXQhQBUFl&show_text=true&width=500" width="500" height="222" style={{ border: 'none', overflow: 'hidden' }} scrolling="no" frameBorder={0} allowFullScreen allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"></iframe> ,
key: 'colin-griffin',
}
],
[ {
iframe: <iframe className="w-full h-full rounded-2xl" src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Fhollowman.la.rock%2Fposts%2Fpfbid0hPR3RpaSLZtZkgpaEzKsNKAF4hUeKbDzkicAZ9jsbCpXKRRsYD63C32rw78uvRoml&show_text=true&width=500" width="500" height="170" style={{ border: 'none', overflow: 'hidden' }} scrolling="no" frameBorder={0} allowFullScreen allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"></iframe>,
key: 'hollowman-la-rock',
}
],
],
]
function classNames(...classes: string[]) {
return classes.filter(Boolean).join(' ')
interface TestimonialItem {
key: string;
src: string;
height: number;
}
export default function Grid() {
interface Props {
sectionLabel: string;
title: string;
items: TestimonialItem[];
}
const iframeProps = {
width: 500,
scrolling: 'no',
frameBorder: 0,
allowFullScreen: true,
allow: 'autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share',
style: { border: 'none', overflow: 'hidden' } as React.CSSProperties,
className: 'w-full rounded-2xl',
}
export default function Grid({ sectionLabel, title, items }: Props) {
const [featured, ...rest] = items
const groups = [rest.slice(0, 2), rest.slice(2)]
return (
<div className="relative isolate bg-brand-50 pt-24 pb-32 sm:pt-32 dark:bg-brand-50">
<div
@@ -69,52 +52,31 @@ export default function Grid() {
</div>
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<h2 className="text-base/7 font-semibold text-brand-400 dark:text-brand-400">Témoignages</h2>
<h2 className="text-base/7 font-semibold text-brand-400 dark:text-brand-400">{sectionLabel}</h2>
<p className="mt-2 text-4xl font-semibold tracking-tight text-balance text-brand-600 sm:text-5xl dark:text-white">
Ce que nos clients disent de nous
{title}
</p>
</div>
<div className="mx-auto mt-16 grid max-w-2xl grid-cols-1 grid-rows-1 gap-8 text-sm/6 text-gray-900 sm:mt-20 sm:grid-cols-2 xl:mx-0 xl:max-w-none xl:grid-flow-col xl:grid-cols-4 dark:text-gray-100">
<figure className="rounded-2xl bg-white shadow-lg ring-1 ring-gray-900/5 sm:col-span-2 xl:col-start-2 xl:row-end-1 dark:bg-gray-800/75 dark:shadow-none dark:ring-white/10">
<iframe className="w-full h-full rounded-2xl " src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Fsylvie.gauthier.73594%2Fposts%2Fpfbid0DHM1833TLKru4cbkTnZq3KLNHrSR8B8rQAgGosGzAbpGMccvLffoNmCLgEo8GUNPl&show_text=true&width=500" width="500" height="208" style={{ border: 'none', overflow: 'hidden' }} scrolling="no" frameBorder={0} allowFullScreen allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"></iframe>
<iframe {...iframeProps} src={featured.src} height={featured.height} />
</figure>
{testimonials.map((columnGroup, columnGroupIdx) => (
<div key={columnGroupIdx} className="space-y-8 xl:contents xl:space-y-0">
{columnGroup.map((column, columnIdx) => (
<div
key={columnIdx}
className={classNames(
(columnGroupIdx === 0 && columnIdx === 0) ||
(columnGroupIdx === testimonials.length - 1 && columnIdx === columnGroup.length - 1)
? 'xl:row-span-2'
: 'xl:row-start-1',
'space-y-8',
)}
>
{column.map((testimonial) => (
<figure
key={testimonial.key}
className="rounded-2xl bg-white shadow-lg ring-1 ring-gray-900/5 dark:bg-gray-800/75 dark:shadow-none dark:ring-white/10"
>
{testimonial.iframe}
{/* <blockquote className="text-gray-900 dark:text-white">
<p>{`“${testimonial.body}”`}</p>
</blockquote>
<figcaption className="mt-6 flex items-center gap-x-4">
<img
alt=""
src={testimonial.author.imageUrl}
className="size-10 rounded-full bg-gray-50 dark:bg-gray-700"
/>
<div>
<div className="font-semibold text-gray-900 dark:text-white">{testimonial.author.name}</div>
<div className="text-gray-600 dark:text-gray-400">{`@${testimonial.author.handle}`}</div>
</div>
</figcaption> */}
{groups.map((group, groupIdx) => (
<div key={groupIdx} className="space-y-8 xl:contents xl:space-y-0">
{group.map((t, colIdx) => {
const isFirstCol = groupIdx === 0 && colIdx === 0
const isLastCol = groupIdx === groups.length - 1 && colIdx === group.length - 1
return (
<div
key={t.key}
className={(isFirstCol || isLastCol) ? 'xl:row-span-2 space-y-8' : 'xl:row-start-1 space-y-8'}
>
<figure className="rounded-2xl bg-white shadow-lg ring-1 ring-gray-900/5 dark:bg-gray-800/75 dark:shadow-none dark:ring-white/10">
<iframe {...iframeProps} src={t.src} height={t.height} />
</figure>
))}
</div>
))}
</div>
)
})}
</div>
))}
</div>