initial commit

This commit is contained in:
2026-03-15 23:38:36 -04:00
parent b7f8423b75
commit 861fb44ece
126 changed files with 5682 additions and 136 deletions

View File

@@ -28,8 +28,8 @@ const lang = getLangFromUrl(Astro.url);
<style>
.cta-banner-section {
position: relative;
background-color: var(--color-brand-600);
color: #ffffff;
background-color: var(--color-brand-50);
color: var(--color-brand-600);
padding: 4rem 0;
overflow: hidden;
}
@@ -37,7 +37,8 @@ const lang = getLangFromUrl(Astro.url);
.cta-banner-bg {
position: absolute;
inset: 0;
background: linear-gradient(135deg, var(--color-brand-700) 0%, var(--color-brand-600) 50%, var(--color-brand-500) 100%);
/* background: linear-gradient(135deg, var(--color-brand-700) 0%, var(--color-brand-600) 50%, var(--color-brand-500) 100%); */
background-color: var(--color-brand-50);
opacity: 0.6;
pointer-events: none;
}
@@ -55,19 +56,19 @@ const lang = getLangFromUrl(Astro.url);
font-family: var(--font-display);
font-size: clamp(1.5rem, 3vw, 2rem);
font-weight: 500;
color: #ffffff;
color: var(--color-brand-600);
margin-bottom: 0.5rem;
}
.cta-banner-subtitle {
font-size: 1.0625rem;
color: rgba(255, 255, 255, 0.8);
color: var(--color-brand-500);
max-width: 520px;
}
.cta-banner-btn {
background-color: #ffffff;
color: var(--color-brand-600);
background-color: var(--color-brand-600);
color: #ffffff;
font-size: 1rem;
padding: 0.875rem 2rem;
white-space: nowrap;
@@ -75,7 +76,7 @@ const lang = getLangFromUrl(Astro.url);
}
.cta-banner-btn:hover {
background-color: var(--color-brand-50);
background-color: var(--color-brand-700);
}
@media (max-width: 640px) {

View File

@@ -5,9 +5,9 @@ const lang = getLangFromUrl(Astro.url);
const currentPath = Astro.url.pathname;
const navLinks = [
{ href: '/', label: t(lang, 'nav.accueil') },
{ href: '/services', label: t(lang, 'nav.services') },
{ href: '/contact', label: t(lang, 'nav.contact') },
{ href: '/landing-1', label: 'Landing 1' },
{ href: '/landing-2', label: 'Landing 2' },
{ href: '/landing-3', label: 'Landing 3' },
];
function isActive(href: string): boolean {

View File

@@ -6,7 +6,7 @@ const lang = getLangFromUrl(Astro.url);
<section class="hero-section">
<div class="hero-bg" aria-hidden="true"></div>
<div class="container-site hero-content">
<div class="container-site hero-content min-h-[60vh]">
<div class="hero-text">
<p class="hero-eyebrow">{general.tagline}</p>
<h1 class="hero-title">{t(lang, 'hero.title')}</h1>

View File

@@ -0,0 +1,139 @@
import imgInterieure from '@/assets/photos/empty-room-blue-wall-with-moulding-and-parquet-fl-2023-11-27-04-52-31-utc.webp'
import imgExterieure from '@/assets/Website Content/Photos/wooden-deck-with-sky-2023-11-27-05-29-44-utc.webp'
import imgDecoration from '@/assets/Website Content/Photos/Mural_Jerkspot_02.jpg'
import imgCommerciale from '@/assets/Website Content/Photos/minimalist-bright-office-room-interior-design-mee-2023-11-27-05-17-04-utc.webp'
import imgRenovation from '@/assets/Website Content/Photos/amazing-design-of-a-room-with-doors-and-parquet-fl-2023-11-27-05-33-04-utc.webp'
export default function TwoRowBentoGridWithThreeColumnSecondRow() {
return (
<div className="bg-[var(--color-brand-600)] py-24 sm:py-32">
<div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-8">
<h2
className="text-base/7 font-semibold text-white"
style={{ fontFamily: 'var(--font-body)' }}
>
Nos Services
</h2>
<p
className="mt-2 max-w-lg text-4xl font-semibold tracking-tight text-pretty text-white sm:text-5xl"
style={{ fontFamily: 'var(--font-display)' }}
>
Des solutions complètes en peinture et décoration
</p>
<div className="mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-6 lg:grid-rows-2">
{/* Peinture intérieure — top left */}
<div className="relative lg:col-span-3">
<div className="absolute inset-0 rounded-xl bg-white max-lg:rounded-t-xl lg:rounded-tl-xl" />
<div className="relative flex h-full flex-col overflow-hidden rounded-xl max-lg:rounded-t-xl lg:rounded-tl-xl">
<img
alt="Peinture intérieure résidentielle"
src={imgInterieure.src}
className="h-80 object-cover object-left"
/>
<div className="p-10 pt-4">
<h3 className="text-sm/4 font-semibold text-[var(--color-brand-500)]">Résidentiel</h3>
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
Peinture intérieure
</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
Transformation complète de vos espaces intérieurs. Préparation des surfaces, application soignée et finitions impeccables pour tous types de pièces.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-xl shadow-sm outline outline-black/5 max-lg:rounded-t-xl lg:rounded-tl-xl" />
</div>
{/* Peinture extérieure — top right */}
<div className="relative lg:col-span-3">
<div className="absolute inset-0 rounded-xl bg-white lg:rounded-tr-xl" />
<div className="relative flex h-full flex-col overflow-hidden rounded-xl lg:rounded-tr-xl">
<img
alt="Peinture extérieure résidentielle"
src={imgExterieure.src}
className="h-80 object-cover object-left lg:object-right"
/>
<div className="p-10 pt-4">
<h3 className="text-sm/4 font-semibold text-[var(--color-brand-500)]">Résidentiel</h3>
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
Peinture extérieure
</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
Protection et embellissement de la façade de votre maison. Travaux résistants aux conditions climatiques du Québec pour une durabilité maximale.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-xl shadow-sm outline outline-black/5 lg:rounded-tr-xl" />
</div>
{/* Décoration intérieure — bottom left */}
<div className="relative lg:col-span-2">
<div className="absolute inset-0 rounded-xl bg-white lg:rounded-bl-xl" />
<div className="relative flex h-full flex-col overflow-hidden rounded-xl lg:rounded-bl-xl">
<img
alt="Décoration intérieure"
src={imgDecoration.src}
className="h-80 object-cover object-left"
/>
<div className="p-10 pt-4">
<h3 className="text-sm/4 font-semibold text-[var(--color-brand-500)]">Résidentiel</h3>
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
Décoration intérieure
</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
Conseils personnalisés en couleur et décoration. Effets décoratifs, murales, textures et finitions spécialisées.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-xl shadow-sm outline outline-black/5 lg:rounded-bl-xl" />
</div>
{/* Peinture commerciale — bottom center */}
<div className="relative lg:col-span-2">
<div className="absolute inset-0 rounded-xl bg-white" />
<div className="relative flex h-full flex-col overflow-hidden rounded-xl">
<img
alt="Peinture commerciale"
src={imgCommerciale.src}
className="h-80 object-cover"
/>
<div className="p-10 pt-4">
<h3 className="text-sm/4 font-semibold text-[var(--color-brand-500)]">Commercial</h3>
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
Peinture commerciale
</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
Bureaux, commerces et espaces institutionnels. Travaux planifiés selon votre calendrier pour minimiser les perturbations.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-xl shadow-sm outline outline-black/5" />
</div>
{/* Rénovation complète — bottom right */}
<div className="relative lg:col-span-2">
<div className="absolute inset-0 rounded-xl bg-white max-lg:rounded-b-xl lg:rounded-br-xl" />
<div className="relative flex h-full flex-col overflow-hidden rounded-xl max-lg:rounded-b-xl lg:rounded-br-xl">
<img
alt="Rénovation complète"
src={imgRenovation.src}
className="h-80 object-cover"
/>
<div className="p-10 pt-4">
<h3 className="text-sm/4 font-semibold text-[var(--color-brand-500)]">Résidentiel</h3>
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
Rénovation complète
</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
Coordination complète de vos travaux de finition. De la peinture aux moulures, un seul entrepreneur pour tous vos projets.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-xl shadow-sm outline outline-black/5 max-lg:rounded-b-xl lg:rounded-br-xl" />
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,240 @@
import * as React from "react"
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
}
return context
}
function Carousel({
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
}: React.ComponentProps<"div"> & CarouselProps) {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) return
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext]
)
React.useEffect(() => {
if (!api || !setApi) return
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) return
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
data-slot="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
)
}
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
const { carouselRef, orientation } = useCarousel()
return (
<div
ref={carouselRef}
className="overflow-hidden"
data-slot="carousel-content"
>
<div
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props}
/>
</div>
)
}
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
const { orientation } = useCarousel()
return (
<div
role="group"
aria-roledescription="slide"
data-slot="carousel-item"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props}
/>
)
}
function CarouselPrevious({
className,
variant = "outline",
size = "icon-sm",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
data-slot="carousel-previous"
variant={variant}
size={size}
className={cn(
"absolute touch-manipulation rounded-full",
orientation === "horizontal"
? "top-1/2 -left-12 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ChevronLeftIcon />
<span className="sr-only">Previous slide</span>
</Button>
)
}
function CarouselNext({
className,
variant = "outline",
size = "icon-sm",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
data-slot="carousel-next"
variant={variant}
size={size}
className={cn(
"absolute touch-manipulation rounded-full",
orientation === "horizontal"
? "top-1/2 -right-12 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ChevronRightIcon />
<span className="sr-only">Next slide</span>
</Button>
)
}
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
useCarousel,
}

View File

@@ -0,0 +1,64 @@
export default function WithImageTiles() {
return (
<div className="overflow-hidden py-32" style={{ backgroundColor: 'var(--color-brand-600)' }}>
<div className="mx-auto max-w-7xl px-6 lg:flex lg:px-8">
<div className="mx-auto grid max-w-2xl grid-cols-1 gap-x-12 gap-y-16 lg:mx-0 lg:max-w-none lg:min-w-full lg:flex-none lg:gap-y-8">
<div className="lg:col-end-1 lg:w-full lg:max-w-lg lg:pb-8">
<h2 className="text-4xl font-semibold tracking-tight text-white sm:text-5xl">
Prêt à transformer votre espace?
</h2>
<p className="mt-6 text-xl/8 text-green-100">
Obtenez une soumission gratuite et sans engagement dès aujourd'hui.
</p>
<p className="mt-6 text-base/7 text-green-200">
Peintres décorateurs professionnels à Laval et sur la Rive-Nord. Peinture résidentielle et commerciale,
décoration intérieure et extérieure.
</p>
<div className="mt-10 flex">
<a
href="/soumission"
className="rounded-md bg-white px-3.5 py-2.5 text-sm font-semibold shadow-xs focus-visible:outline-2 focus-visible:outline-offset-2"
style={{ color: 'var(--color-brand-600)' }}
>
Demandez votre soumission
<span aria-hidden="true">&rarr;</span>
</a>
</div>
</div>
<div className="flex flex-wrap items-start justify-end gap-6 sm:gap-8 lg:contents">
<div className="w-0 flex-auto lg:ml-auto lg:w-auto lg:flex-none lg:self-end">
<img
alt=""
src="https://images.unsplash.com/photo-1670272502246-768d249768ca?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1152&q=80"
className="aspect-7/5 w-148 max-w-none rounded-2xl object-cover max-sm:w-120" style={{ backgroundColor: 'var(--color-brand-700)' }}
/>
</div>
<div className="contents lg:col-span-2 lg:col-end-2 lg:ml-auto lg:flex lg:w-148 lg:items-start lg:justify-end lg:gap-x-8">
<div className="order-first flex w-64 flex-none justify-end self-end max-sm:w-40 lg:w-auto">
<img
alt=""
src="https://images.unsplash.com/photo-1605656816944-971cd5c1407f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=768&h=604&q=80"
className="aspect-4/3 w-[24rem] max-w-none flex-none rounded-2xl object-cover" style={{ backgroundColor: 'var(--color-brand-700)' }}
/>
</div>
<div className="flex w-96 flex-auto justify-end lg:w-auto lg:flex-none">
<img
alt=""
src="https://images.unsplash.com/photo-1568992687947-868a62a9f521?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1152&h=842&q=80"
className="aspect-7/5 w-148 max-w-none flex-none rounded-2xl object-cover max-sm:w-120" style={{ backgroundColor: 'var(--color-brand-700)' }}
/>
</div>
<div className="hidden sm:block sm:w-0 sm:flex-auto lg:w-auto lg:flex-none">
<img
alt=""
src="https://images.unsplash.com/photo-1612872087720-bb876e2e67d1?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=768&h=604&q=80"
className="aspect-4/3 w-[24rem] max-w-none rounded-2xl object-cover" style={{ backgroundColor: 'var(--color-brand-700)' }}
/>
</div>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,193 @@
import { useState } from 'react'
import { Dialog, DialogPanel } from '@headlessui/react'
import heroVideoMp4 from '@/assets/videos/cachet-deco.mp4'
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
import { defaultLocale } from '../../../i18n/config'
import { t } from '../../../i18n/utils'
import general from '../../../content/settings/general.json'
const navigation = [
{ name: 'Landing 1', href: '/landing-1' },
{ name: 'Landing 2', href: '/landing-2' },
{ name: 'Landing 3', href: '/landing-3' },
]
export default function SimpleCenteredWithBackgroundImage() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
return (
<div className="bg-white dark:bg-gray-900">
<header className="absolute inset-x-0 top-0 z-50 bg-[var(--color-brand-600)]">
<nav aria-label="Global" className="flex items-center justify-between p-2 lg:px-8">
<div className="flex lg:flex-1">
<a href="/" className="-m-1.5 p-1 m-1 bg-[white] rounded-full">
<span className="sr-only">Cachet Peintres Décorateurs</span>
<img
alt="Cachet Peintres Décorateurs"
src="/images/logo.png"
className="h-10 w-auto"
/>
</a>
</div>
<div className="flex lg:hidden">
<button
type="button"
onClick={() => setMobileMenuOpen(true)}
className="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-white"
>
<span className="sr-only">Open main menu</span>
<Bars3Icon aria-hidden="true" className="size-6" />
</button>
</div>
<div className="hidden lg:flex lg:gap-x-12">
{navigation.map((item) => (
<a key={item.name} href={item.href} className="text-sm/6 font-semibold text-white hover:underline underline-offset-4 decoration-2">
{item.name}
</a>
))}
</div>
<div className="hidden lg:flex lg:flex-1 lg:justify-end">
<a
href="/soumission"
className="rounded-md bg-white px-4 py-2 text-sm font-semibold text-[var(--color-brand-600)] shadow-sm ring-1 ring-inset ring-white/30 hover:bg-white/90 transition-colors"
>
Soumission gratuite
</a>
</div>
</nav>
<Dialog open={mobileMenuOpen} onClose={setMobileMenuOpen} className="lg:hidden">
<div className="fixed inset-0 z-50" />
<DialogPanel className="fixed inset-y-0 right-0 z-50 w-full overflow-y-auto bg-white p-6 sm:max-w-sm sm:ring-1 sm:ring-gray-900/10 dark:bg-gray-900 dark:sm:ring-gray-100/10">
<div className="flex items-center justify-between">
<a href="/" className="-m-1.5 p-1.5">
<span className="sr-only">Cachet Peintres Décorateurs</span>
<img
alt="Cachet Peintres Décorateurs"
src="/images/logo.png"
className="h-10 w-auto"
/>
</a>
<button
type="button"
onClick={() => setMobileMenuOpen(false)}
className="-m-2.5 rounded-md p-2.5 text-gray-700 dark:text-gray-400"
>
<span className="sr-only">Close menu</span>
<XMarkIcon aria-hidden="true" className="size-6" />
</button>
</div>
<div className="mt-6 flow-root">
<div className="-my-6 divide-y divide-gray-500/10 dark:divide-gray-500/25">
<div className="space-y-2 py-6">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className="-mx-3 block rounded-lg px-3 py-2 text-base/7 font-semibold text-gray-900 hover:bg-gray-50 dark:text-white dark:hover:bg-white/5"
>
{item.name}
</a>
))}
</div>
<div className="py-6">
<a
href="/soumission"
className="-mx-3 block rounded-lg px-3 py-2.5 text-base/7 font-semibold text-center bg-[var(--color-brand-600)] text-white ring-1 ring-inset ring-[var(--color-brand-600)] hover:bg-[var(--color-brand-700)]"
>
Soumission gratuite
</a>
</div>
</div>
</div>
</DialogPanel>
</Dialog>
</header>
<div className="relative isolate min-h-screen overflow-hidden pt-14">
<video
autoPlay
muted
loop
playsInline
className="absolute inset-0 -z-10 size-full object-cover"
>
<source src={heroVideoMp4} type="video/mp4" />
</video>
<div className="absolute inset-0 -z-10 bg-black/50" />
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="grid min-h-[calc(100vh-3.5rem)] grid-cols-1 items-center gap-12 py-20 lg:grid-cols-[1fr_auto] lg:gap-12 lg:py-24">
<div>
<p className="mb-4 inline-block text-sm font-bold uppercase tracking-wider text-white/65">
{general.tagline}
</p>
<h1 className="mb-5 text-4xl font-medium leading-tight text-white sm:text-5xl lg:text-[2.5rem]" style={{ fontFamily: 'var(--font-display)' }}>
{t(defaultLocale, 'hero.title')}
</h1>
<p className="mb-9 max-w-[540px] text-xl leading-relaxed text-white/82">
{t(defaultLocale, 'hero.subtitle')}
</p>
<div className="mb-8 flex flex-wrap gap-4">
<a
href="/soumission"
className="inline-flex items-center gap-2 rounded-md bg-white px-8 py-3.5 text-lg font-semibold text-[var(--color-brand-600)] transition-colors hover:bg-[var(--color-brand-50)]"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden>
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14,2 14,8 20,8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
<polyline points="10,9 9,9 8,9" />
</svg>
{t(defaultLocale, 'hero.cta')}
</a>
<a
href="/services"
className="inline-flex items-center gap-2 rounded-md border-2 border-white px-6 py-3 text-base font-semibold text-white transition-colors hover:bg-white/10"
>
{t(defaultLocale, 'cta.learn_more')}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden>
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12,5 19,12 12,19" />
</svg>
</a>
</div>
<div className="flex flex-wrap items-center gap-2 gap-x-3.5 text-sm text-white/65">
<div className="flex items-center gap-1.5">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
</svg>
<span>RBQ {general.rbq}</span>
</div>
<span className="text-white/30" aria-hidden>·</span>
<div className="flex items-center gap-1.5">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
<circle cx="12" cy="10" r="3" />
</svg>
<span>{general.serviceArea}</span>
</div>
<span className="text-white/30" aria-hidden>·</span>
<div className="flex items-center gap-1.5">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
<polyline points="20 6 9 17 4 12" />
</svg>
<span>Soumission gratuite</span>
</div>
</div>
</div>
<div className="hidden items-center justify-center lg:flex" aria-hidden>
<img
src="/images/logo.png"
alt=""
width={320}
height={320}
loading="eager"
className="h-[280px] w-[280px] object-contain opacity-[0.18] brightness-0 invert"
/>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,215 @@
import { useState } from "react";
import { Dialog, DialogPanel } from "@headlessui/react";
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
import Autoplay from "embla-carousel-autoplay";
import {
Carousel,
CarouselContent,
CarouselItem,
} from "@/components/ui/carousel";
import { defaultLocale } from "../../../i18n/config";
import { t } from "../../../i18n/utils";
import general from "../../../content/settings/general.json";
import photo1 from "@/assets/photos/empty-room-blue-wall-with-moulding-and-parquet-fl-2023-11-27-04-52-31-utc.webp";
import photo2 from "@/assets/photos/the-attic-of-a-home-with-white-ceilings-and-white-2023-11-27-05-27-44-utc.webp";
import photo3 from "@/assets/photos/silver-and-white-apartment-interior-2023-11-27-05-32-03-utc.webp";
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";
const navigation = [
{ name: "Landing 1", href: "/landing-1" },
{ name: "Landing 2", href: "/landing-2" },
{ name: "Landing 3", href: "/landing-3" },
];
const slides = [
{ src: photo1.src, alt: "Peinture intérieure" },
{ src: photo2.src, alt: "Décoration murale" },
{ src: photo3.src, alt: "Finition de qualité" },
{ src: photo4.src, alt: "Chambre" },
{ src: photo5.src, alt: "Pièce lumineuse" },
];
const autoplayPlugin = Autoplay({
delay: 5000,
stopOnInteraction: false,
stopOnMouseEnter: false,
});
export default function SimpleCenteredWithImageSlider() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<div className="bg-white dark:bg-gray-900">
<header className="absolute inset-x-0 top-0 z-50">
<nav
aria-label="Global"
className="flex items-center justify-between p-6 lg:px-8"
>
<div className="flex lg:flex-1">
<a href="/" className="flex items-center bg-[white] rounded-3xl shadow-md
ps-0.5 pe-2 pt-1 pb-0.5
md:ps-1 md:pe-3 md:pt-2 md:pb-1
">
<div className="p-1 bg-[white] rounded-full ">
<span className="sr-only">Cachet Peintres Décorateurs</span>
<img
alt="Cachet Peintres Décorateurs"
src="/images/logo.png"
className="h-10 w-auto"
/>
</div>
<p className="text-xl sm:text-2xl md:text-3xl font-semibold text-brand-600 uppercase">{general.brandName}</p>
</a>
</div>
<div className="flex lg:hidden">
<button
type="button"
onClick={() => setMobileMenuOpen(true)}
className="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-[var(--color-brand-100)] hover:text-[var(--color-brand-300)]"
>
<span className="sr-only">Open main menu</span>
<Bars3Icon aria-hidden="true" className="size-6" />
</button>
</div>
<div className="hidden lg:flex lg:gap-x-12">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className="text-sm/6 font-semibold text-white hover:underline underline-offset-4 decoration-2 drop-shadow"
>
{item.name}
</a>
))}
</div>
<div className="hidden lg:flex lg:flex-1 lg:justify-end">
<a
href="/soumission"
className="rounded-md bg-[var(--color-brand-600)] px-4 py-2 text-sm font-semibold text-white shadow-sm ring-1 ring-inset ring-[var(--color-brand-600)] hover:bg-[var(--color-brand-700)] transition-colors"
>
Soumission gratuite
</a>
</div>
</nav>
<Dialog
open={mobileMenuOpen}
onClose={setMobileMenuOpen}
className="lg:hidden"
>
<div className="fixed inset-0 z-50" />
<DialogPanel className="fixed inset-y-0 right-0 z-50 w-full overflow-y-auto bg-white p-6 sm:max-w-sm sm:ring-1 sm:ring-gray-900/10 dark:bg-gray-900 dark:sm:ring-gray-100/10">
<div className="flex items-center justify-between sm:justify-end lg:justify-between">
<a href="/" className="flex sm:hidden lg:flex items-center bg-[white] rounded-3xl
ps-0.5 pe-2 pt-1 pb-0.5
md:ps-1 md:pe-3 md:pt-2 md:pb-1
">
<div className="p-1 bg-[white] rounded-full ">
<span className="sr-only">Cachet Peintres Décorateurs</span>
<img
alt="Cachet Peintres Décorateurs"
src="/images/logo.png"
className="h-10 w-auto"
/>
</div>
<p className="text-xl sm:text-2xl md:text-3xl font-semibold text-brand-600 uppercase">{general.brandName}</p>
</a>
<button
type="button"
onClick={() => setMobileMenuOpen(false)}
className="-m-2.5 rounded-md p-2.5 text-gray-700 dark:text-gray-400"
>
<span className="sr-only">Close menu</span>
<XMarkIcon aria-hidden="true" className="size-6" />
</button>
</div>
<div className="mt-6 flow-root">
<div className="-my-6 divide-y divide-gray-500/10 dark:divide-gray-500/25">
<div className="space-y-2 py-6">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className="-mx-3 block rounded-lg px-3 py-2 text-base/7 font-semibold text-gray-900 hover:bg-gray-50 dark:text-white dark:hover:bg-white/5"
>
{item.name}
</a>
))}
</div>
<div className="py-6">
<a
href="/soumission"
className="-mx-3 block rounded-lg px-3 py-2.5 text-base/7 font-semibold text-center bg-[var(--color-brand-600)] text-white ring-1 ring-inset ring-[var(--color-brand-600)] hover:bg-[var(--color-brand-700)]"
>
Soumission gratuite
</a>
</div>
</div>
</div>
</DialogPanel>
</Dialog>
</header>
{/* Full-screen hero with background carousel */}
<div className="relative isolate overflow-hidden h-screen min-h-[600px]">
{/* Background carousel */}
<Carousel
className="absolute inset-0 -z-10 size-full [&_[data-slot='carousel-content']]:h-full"
opts={{ loop: true, dragFree: false }}
plugins={[autoplayPlugin]}
>
<CarouselContent className="-ml-0 h-full">
{slides.map((slide, index) => (
<CarouselItem key={index} className="pl-0 h-full relative">
<img
src={slide.src}
alt={slide.alt}
className="size-full object-cover"
/>
{/* Dark overlay for text legibility */}
<div className="absolute inset-0 bg-black/50" />
</CarouselItem>
))}
</CarouselContent>
</Carousel>
{/* Hero text content */}
<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-2xl text-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>·</span>
<span>{general.serviceArea}</span>
<span className="text-white/30" aria-hidden>·</span>
<span>Soumission gratuite</span>
</div>
</div>
<h1
className="text-5xl font-semibold tracking-tight text-balance text-white sm:text-7xl"
style={{ fontFamily: "var(--font-display)" }}
>
{t(defaultLocale, "hero.title")}
</h1>
<p className="mt-8 text-lg font-medium text-pretty text-gray-300 sm:text-xl/8">
{t(defaultLocale, "hero.subtitle")}
</p>
<div className="mt-10 flex items-center justify-center">
<a
href="/soumission"
className="rounded-md bg-[var(--color-brand-600)] px-3.5 py-2.5 text-sm font-semibold text-white shadow-xs hover:bg-[var(--color-brand-700)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-brand-600)]"
>
{t(defaultLocale, "hero.cta")}
</a>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,64 @@
export interface Feature {
name: string
description: string
imageSrc: string
imageAlt: string
}
interface WithAlternatingSectionsProps {
features: Feature[]
title?: string
subtitle?: string
}
function classNames(...classes: (string | boolean | undefined)[]) {
return classes.filter(Boolean).join(' ')
}
export default function WithAlternatingSections({
features,
title = 'Nos services',
subtitle = 'Des solutions complètes en peinture et décoration pour transformer vos espaces.',
}: WithAlternatingSectionsProps) {
return (
<div className="bg-brand-light text-brand-600">
<div className="mx-auto max-w-2xl px-4 py-24 sm:px-6 sm:py-32 lg:max-w-7xl lg:px-8">
<div className="mx-auto max-w-3xl text-center">
<h2 className="text-4xl font-bold tracking-tight text-brand-600 sm:text-5xl">{title}</h2>
<p className="mt-4 text-lg text-brand-500">{subtitle}</p>
</div>
<div className="mt-16 space-y-16">
{features.map((feature, featureIdx) => (
<div
key={feature.name}
className="flex flex-col-reverse lg:grid lg:grid-cols-12 lg:items-center lg:gap-x-8"
>
<div
className={classNames(
featureIdx % 2 === 0 ? 'lg:col-start-1' : 'lg:col-start-8 xl:col-start-9',
'mt-6 lg:col-span-5 lg:row-start-1 lg:mt-0 xl:col-span-4',
)}
>
<h3 className="text-4xl font-semibold tracking-tight text-brand-600">{feature.name}</h3>
<p className="mt-2 text-base text-brand-800">{feature.description}</p>
</div>
<div
className={classNames(
featureIdx % 2 === 0 ? 'lg:col-start-6 xl:col-start-5' : 'lg:col-start-1',
'flex-auto lg:col-span-7 lg:row-start-1 xl:col-span-8',
)}
>
<img
alt={feature.imageAlt}
src={feature.imageSrc}
className="aspect-3/2 w-full rounded-lg bg-gray-100 object-cover"
/>
</div>
</div>
))}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,63 @@
import imgBg from '@/assets/Website Content/Photos/choosing-wall-paints-2023-11-27-05-09-01-utc.webp'
const features = [
{
name: 'Certifié RBQ',
description: 'Entrepreneur certifié par la Régie du Bâtiment du Québec (RBQ : 5839 8736 01). Travaux assurés et conformes aux normes.',
},
{
name: 'Ponctualité et fiabilité',
description: 'Nous respectons nos engagements. Les délais sont tenus et votre projet est livré à temps, selon les standards convenus.',
},
{
name: 'Matériaux de qualité',
description: 'Nous utilisons exclusivement des peintures et produits de première qualité pour un résultat durable et esthétique.',
},
{
name: 'Équipe professionnelle',
description: 'Nos peintres décorateurs sont formés et expérimentés. Chaque détail compte pour nous, de la préparation à la finition.',
},
]
export default function WithFadingImage() {
return (
<div className="bg-[linear-gradient(to_bottom,white_0%,white_75%,var(--color-brand-50)_100%)]">
<div aria-hidden="true" className="relative">
<img
alt="Peintres décorateurs au travail"
src={imgBg.src}
className="h-96 w-full object-cover object-center "
/>
<div className="absolute inset-0 bg-linear-to-t from-white" />
</div>
<div className="relative mx-auto -mt-12 max-w-7xl px-4 pb-16 sm:px-6 sm:pb-24 lg:px-8">
<div className="mx-auto max-w-2xl text-center lg:max-w-4xl">
<h2
className="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl"
style={{ fontFamily: 'var(--font-display)' }}
>
Pourquoi choisir Cachet?
</h2>
<p className="mt-4 text-gray-500" style={{ fontFamily: 'var(--font-body)' }}>
Une expertise reconnue au service de votre satisfaction
</p>
</div>
<dl className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 sm:gap-y-16 lg:max-w-none lg:grid-cols-4 lg:gap-x-8">
{features.map((feature) => (
<div key={feature.name} className="border-t-2 border-[var(--color-brand-500)] pt-4">
<dt
className="font-semibold text-[var(--color-gray-600)]"
style={{ fontFamily: 'var(--font-body)' }}
>
{feature.name}
</dt>
<dd className="mt-2 text-sm text-gray-500 leading-relaxed">{feature.description}</dd>
</div>
))}
</dl>
</div>
</div>
)
}

View File

@@ -0,0 +1,122 @@
const iconPaths: Record<string, string> = {
shield: 'M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z',
clock: 'M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z M12 6v6l4 2',
check: 'M20 6L9 17l-5-5',
team:
'M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2 M9 7a4 4 0 1 0 0-8 4 4 0 0 0 0 8z M23 21v-2a4 4 0 0 0-3-3.87 M16 3.13a4 4 0 0 1 0 7.75',
star: 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',
location: 'M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z M15 10a3 3 0 1 1-6 0 3 3 0 0 1 6 0z',
}
function getIconPath(icon: string): string {
return iconPaths[icon] ?? iconPaths.check
}
export interface PorquoiCard {
icon: string
title: string
description: string
}
export interface PorquoiImages {
main: string
mainAlt: string
detail1: string
detail1Alt: string
detail2: string
detail2Alt: string
}
interface WithTieredImagesProps {
title: string
subtitle: string
cards: PorquoiCard[]
images: PorquoiImages
}
export default function WithTieredImages({
title,
subtitle,
cards,
images,
}: WithTieredImagesProps) {
return (
<div className="bg-[var(--color-brand-600)]">
<div className="mx-auto max-w-2xl px-4 py-24 sm:px-6 sm:py-32 lg:max-w-7xl lg:px-8">
<div className="grid grid-cols-1 items-center gap-x-8 gap-y-16 lg:grid-cols-2">
<div>
<div className="border-b border-white/30 pb-10">
<h2 className="text-lg font-medium text-white">{title}</h2>
<p className="mt-2 text-4xl font-bold tracking-tight text-white sm:text-5xl">
{subtitle}
</p>
</div>
<dl className="mt-10 space-y-10">
{cards.map((card) => (
<div key={card.title} className="flex gap-4">
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-white text-[var(--color-brand-600)]">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
aria-hidden="true"
>
{card.icon === 'clock' ? (
<>
<circle cx="12" cy="12" r="10" />
<polyline points="12,6 12,12 16,14" />
</>
) : card.icon === 'team' ? (
<>
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</>
) : card.icon === 'location' ? (
<>
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
<circle cx="12" cy="10" r="3" />
</>
) : (
<path d={getIconPath(card.icon)} />
)}
</svg>
</div>
<div>
<dt className="text-base font-medium text-white">{card.title}</dt>
<dd className="mt-3 text-base text-white/90">{card.description}</dd>
</div>
</div>
))}
</dl>
</div>
<div>
<img
alt={images.mainAlt}
src={images.main}
className="aspect-square w-full rounded-lg bg-[var(--color-brand-100)] object-cover"
/>
<div className="mt-4 grid grid-cols-2 gap-4 sm:mt-6 sm:gap-6 lg:mt-8 lg:gap-8">
<img
alt={images.detail1Alt}
src={images.detail1}
className="aspect-square w-full rounded-lg bg-[var(--color-brand-100)] object-cover"
/>
<img
alt={images.detail2Alt}
src={images.detail2}
className="aspect-square w-full rounded-lg bg-[var(--color-brand-100)] object-cover"
/>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,48 @@
interface FullWidthWithBackgroundImageAndLargeContentProps {
title: string;
subtitle: string;
buttonText: string;
buttonHref: string;
imageSrc: string;
imageAlt?: string;
}
export default function FullWidthWithBackgroundImageAndLargeContent({
title,
subtitle,
buttonText,
buttonHref,
imageSrc,
imageAlt = '',
}: FullWidthWithBackgroundImageAndLargeContentProps) {
return (
<section className="bg-white" aria-labelledby="cta-heading">
<div className="relative bg-gray-900 min-h-[40rem] flex items-center justify-center">
{/* Decorative image and overlay */}
<div aria-hidden="true" className="absolute inset-0 overflow-hidden">
<img
alt={imageAlt}
src={imageSrc}
className="size-full object-cover"
/>
</div>
<div aria-hidden="true" className="absolute inset-0 bg-gray-900 opacity-50" />
<div className="relative mx-auto flex max-w-3xl flex-col items-center px-6 py-24 text-center sm:py-32 lg:px-0">
<h2 id="cta-heading" className="text-3xl font-bold tracking-tight text-brand-50 sm:text-4xl lg:text-5xl">
{title}
</h2>
<p className="mt-4 text-lg text-brand-50/95 sm:text-xl">
{subtitle}
</p>
<a
href={buttonHref}
className="mt-8 inline-block rounded-md border border-transparent bg-[var(--color-brand-600)] px-8 py-3 text-base font-medium text-white shadow-sm transition-colors hover:bg-[var(--color-brand-700)]"
>
{buttonText}
</a>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,124 @@
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(' ')
}
export default function Grid() {
return (
<div className="relative isolate bg-brand-50 pt-24 pb-32 sm:pt-32 dark:bg-gray-900">
<div
aria-hidden="true"
className="absolute inset-x-0 top-1/2 -z-10 -translate-y-1/2 transform-gpu overflow-hidden opacity-30 blur-3xl"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="ml-[max(50%,38rem)] aspect-1313/771 w-328.25 bg-linear-to-tr from-[var(--color-brand-50)] to-[var(--color-brand-200)]"
/>
</div>
<div
aria-hidden="true"
className="absolute inset-x-0 top-0 -z-10 flex transform-gpu overflow-hidden pt-32 opacity-25 blur-3xl sm:pt-40 xl:justify-end"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="-ml-88 aspect-1313/771 w-328.25 flex-none origin-top-right rotate-30 bg-linear-to-tr from-[var(--color-brand-200)] to-[var(--color-brand-50)] xl:mr-[calc(50%-12rem)] xl:ml-0"
/>
</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>
<p className="mt-2 text-4xl font-semibold tracking-tight text-balance text-brand-600 sm:text-5xl dark:text-white">
Se que nos clients disent de nous
</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>
</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> */}
</figure>
))}
</div>
))}
</div>
))}
</div>
</div>
</div>
)
}