This commit is contained in:
2026-03-12 20:16:40 -04:00
commit 184cdcad78
71 changed files with 10018 additions and 0 deletions

64
src/pages/404.astro Normal file
View File

@@ -0,0 +1,64 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import { getLangFromUrl, t } from '../i18n/utils';
const lang = getLangFromUrl(Astro.url);
---
<BaseLayout
title={t(lang, '404.title')}
noindex={true}
>
<section class="not-found-section">
<div class="container-site not-found-inner">
<div class="not-found-number" aria-hidden="true">404</div>
<h1 class="not-found-title">{t(lang, '404.title')}</h1>
<p class="not-found-message">{t(lang, '404.message')}</p>
<a href="/" class="btn-primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
<line x1="19" y1="12" x2="5" y2="12"/>
<polyline points="12,19 5,12 12,5"/>
</svg>
{t(lang, '404.home')}
</a>
</div>
</section>
</BaseLayout>
<style>
.not-found-section {
padding: 6rem 0;
min-height: 60vh;
display: flex;
align-items: center;
}
.not-found-inner {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 1.25rem;
}
.not-found-number {
font-family: var(--font-display);
font-size: clamp(5rem, 15vw, 10rem);
font-weight: 500;
color: var(--color-brand-100);
line-height: 1;
letter-spacing: -0.02em;
}
.not-found-title {
font-size: clamp(1.5rem, 3vw, 2rem);
color: var(--color-brand-600);
}
.not-found-message {
font-size: 1.0625rem;
color: var(--color-gray-dark);
opacity: 0.7;
max-width: 420px;
}
</style>

454
src/pages/contact.astro Normal file
View File

@@ -0,0 +1,454 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import CTABanner from '../components/CTABanner.astro';
import { getLangFromUrl, t } from '../i18n/utils';
import general from '../content/settings/general.json';
const lang = getLangFromUrl(Astro.url);
---
<BaseLayout
title={t(lang, 'contact.title')}
description={`Contactez Cachet Peintres Décorateurs — ${general.phone} — ${general.email}. Nous servons Laval, la Rive-Nord et les environs.`}
canonical="/contact"
>
<!-- Page header -->
<section class="page-header">
<div class="container-site">
<nav class="breadcrumb" aria-label="Fil d'Ariane">
<a href="/">{t(lang, 'nav.accueil')}</a>
<span aria-hidden="true"></span>
<span aria-current="page">{t(lang, 'contact.title')}</span>
</nav>
<h1 class="page-title">{t(lang, 'contact.title')}</h1>
<p class="page-subtitle">{t(lang, 'contact.subtitle')}</p>
</div>
</section>
<section class="contact-section">
<div class="container-site contact-grid">
<!-- Contact info -->
<div class="contact-info">
<div class="contact-card">
<div class="contact-card-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 13a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 2.18h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L7.91 9.91a16 16 0 0 0 6.18 6.18l.94-.94a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/>
</svg>
</div>
<div>
<h3>{t(lang, 'contact.phone')}</h3>
<a href={`tel:${general.phone.replace(/\D/g, '')}`} class="contact-value contact-link">
{general.phone}
</a>
</div>
</div>
<div class="contact-card">
<div class="contact-card-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>
</div>
<div>
<h3>{t(lang, 'contact.email')}</h3>
<a href={`mailto:${general.email}`} class="contact-value contact-link">
{general.email}
</a>
</div>
</div>
<div class="contact-card">
<div class="contact-card-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<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>
</div>
<div>
<h3>{t(lang, 'contact.address')}</h3>
<p class="contact-value">{general.address}</p>
<p class="contact-service-area">Zone desservie : {general.serviceArea}</p>
</div>
</div>
<!-- Hours -->
<div class="contact-hours">
<h3 class="contact-hours-title">{t(lang, 'contact.hours')}</h3>
<ul class="hours-list">
<li>
<span class="hours-day">Lundi Vendredi</span>
<span class="hours-time">7h00 18h00</span>
</li>
<li>
<span class="hours-day">Samedi</span>
<span class="hours-time">8h00 16h00</span>
</li>
<li>
<span class="hours-day">Dimanche</span>
<span class="hours-time closed">Fermé</span>
</li>
</ul>
</div>
<!-- Social -->
{general.facebook && (
<div class="contact-social">
<h3 class="contact-social-title">{t(lang, 'contact.follow')}</h3>
<a
href={general.facebook}
class="social-link"
target="_blank"
rel="noopener noreferrer"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"/>
</svg>
Suivez-nous sur Facebook
</a>
</div>
)}
<!-- Legal -->
<div class="contact-legal">
<p>RBQ : {general.rbq}</p>
<p>NEQ : {general.neq} ({general.legalName})</p>
</div>
</div>
<!-- CTA -->
<div class="contact-action-panel">
<div class="contact-cta-card">
<div class="contact-cta-icon">
<svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
<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"/>
</svg>
</div>
<h2>Demandez votre soumission</h2>
<p>Obtenez une estimation personnalisée et gratuite pour votre projet de peinture ou décoration.</p>
<a href="/soumission" class="btn-primary contact-soumission-btn">
{t(lang, 'cta.soumission')}
</a>
</div>
<!-- Map placeholder -->
<div class="contact-map">
<h3 class="contact-map-title">{t(lang, 'contact.map.title')}</h3>
<div class="map-placeholder">
<div class="map-placeholder-inner">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
<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>
<p>Laval & Rive-Nord, Québec</p>
<span class="map-service-area">{general.serviceArea}</span>
<a
href={`https://maps.google.com/?q=Laval+Quebec`}
target="_blank"
rel="noopener noreferrer"
class="btn-outline map-btn"
>
Voir sur Google Maps
</a>
</div>
</div>
</div>
</div>
</div>
</section>
<CTABanner />
</BaseLayout>
<style>
.page-header {
background-color: var(--color-brand-50);
border-bottom: 1.5px solid var(--color-brand-100);
padding: 3rem 0 2.5rem;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: var(--color-brand-400);
margin-bottom: 1rem;
}
.breadcrumb a {
color: var(--color-brand-600);
transition: opacity 0.2s ease;
}
.breadcrumb a:hover { opacity: 0.7; }
.breadcrumb span[aria-current] {
color: var(--color-gray-dark);
opacity: 0.7;
}
.page-title {
font-size: clamp(1.75rem, 4vw, 2.75rem);
color: var(--color-brand-600);
margin-bottom: 0.75rem;
}
.page-subtitle {
font-size: 1.125rem;
color: var(--color-gray-dark);
opacity: 0.75;
}
.contact-section {
padding: 4rem 0 5rem;
}
.contact-grid {
display: grid;
grid-template-columns: 1fr 1.1fr;
gap: 4rem;
align-items: start;
}
.contact-info {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.contact-card {
display: flex;
align-items: flex-start;
gap: 1rem;
padding: 1.375rem;
background-color: var(--color-brand-50);
border: 1.5px solid var(--color-brand-100);
border-radius: 8px;
}
.contact-card-icon {
width: 44px;
height: 44px;
background-color: var(--color-brand-600);
color: #ffffff;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.contact-card h3 {
font-family: var(--font-body);
font-size: 0.8125rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--color-brand-400);
margin-bottom: 0.375rem;
}
.contact-value {
font-size: 1.0625rem;
color: var(--color-gray-dark);
font-weight: 400;
}
.contact-link {
color: var(--color-brand-600);
font-weight: 700;
transition: opacity 0.2s ease;
}
.contact-link:hover { opacity: 0.75; }
.contact-service-area {
font-size: 0.875rem;
color: var(--color-brand-400);
margin-top: 0.25rem;
}
.contact-hours {
padding: 1.375rem;
background-color: var(--color-brand-50);
border: 1.5px solid var(--color-brand-100);
border-radius: 8px;
}
.contact-hours-title {
font-family: var(--font-body);
font-size: 0.8125rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--color-brand-400);
margin-bottom: 0.875rem;
}
.hours-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.hours-list li {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.9375rem;
}
.hours-day {
color: var(--color-gray-dark);
opacity: 0.8;
}
.hours-time {
font-weight: 700;
color: var(--color-brand-600);
}
.hours-time.closed {
color: var(--color-gray-light);
font-weight: 400;
}
.contact-social { margin-top: 0.25rem; }
.contact-social-title {
font-size: 0.875rem;
font-weight: 700;
color: var(--color-brand-600);
margin-bottom: 0.625rem;
}
.social-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9375rem;
color: #1877f2;
font-weight: 700;
transition: opacity 0.2s ease;
}
.social-link:hover { opacity: 0.8; }
.contact-legal {
font-size: 0.75rem;
color: var(--color-gray-dark);
opacity: 0.45;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
/* Action panel */
.contact-action-panel {
display: flex;
flex-direction: column;
gap: 2rem;
}
.contact-cta-card {
background-color: var(--color-brand-600);
color: #ffffff;
padding: 2.5rem;
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.contact-cta-icon {
width: 64px;
height: 64px;
background-color: rgba(255, 255, 255, 0.15);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.contact-cta-card h2 {
font-size: clamp(1.25rem, 2.5vw, 1.625rem);
color: #ffffff;
font-family: var(--font-display);
}
.contact-cta-card p {
font-size: 1rem;
color: rgba(255, 255, 255, 0.8);
line-height: 1.6;
}
.contact-soumission-btn {
background-color: #ffffff;
color: var(--color-brand-600);
margin-top: 0.5rem;
}
.contact-soumission-btn:hover {
background-color: var(--color-brand-50);
}
.map-placeholder {
background-color: var(--color-brand-50);
border: 1.5px solid var(--color-brand-100);
border-radius: 10px;
overflow: hidden;
}
.map-placeholder-inner {
padding: 3rem 2rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
text-align: center;
color: var(--color-brand-400);
}
.map-placeholder-inner p {
font-size: 1.0625rem;
font-weight: 700;
color: var(--color-brand-600);
}
.map-service-area {
font-size: 0.875rem;
color: var(--color-gray-dark);
opacity: 0.65;
}
.map-btn {
margin-top: 0.5rem;
font-size: 0.875rem;
padding: 0.5rem 1.25rem;
}
.contact-map-title {
font-family: var(--font-body);
font-size: 0.8125rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--color-brand-400);
margin-bottom: 0.875rem;
}
@media (max-width: 900px) {
.contact-grid {
grid-template-columns: 1fr;
gap: 2.5rem;
}
}
</style>

139
src/pages/index.astro Normal file
View File

@@ -0,0 +1,139 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Hero from '../components/Hero.astro';
import ServicesPreview from '../components/ServicesPreview.astro';
import CTABanner from '../components/CTABanner.astro';
import { getLangFromUrl, t } from '../i18n/utils';
import general from '../content/settings/general.json';
const lang = getLangFromUrl(Astro.url);
---
<BaseLayout
title="Peintres Décorateurs | Laval & Rive-Nord"
description={`${general.siteName} — Peintres décorateurs professionnels à Laval et sur la Rive-Nord. Peinture résidentielle et commerciale, décoration intérieure et extérieure. Soumission gratuite.`}
canonical="/"
>
<Hero />
<ServicesPreview />
<!-- Why choose us section -->
<section class="why-section">
<div class="container-site">
<div class="section-header">
<h2 class="section-title">Pourquoi choisir Cachet?</h2>
<p class="section-subtitle">Une expertise reconnue au service de votre satisfaction</p>
</div>
<div class="why-grid">
<div class="why-card">
<div class="why-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
</svg>
</div>
<h3>Certifié RBQ</h3>
<p>Entrepreneur certifié par la Régie du Bâtiment du Québec (RBQ : 5839 8736 01). Travaux assurés et conformes aux normes.</p>
</div>
<div class="why-card">
<div class="why-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="12" cy="12" r="10"/>
<polyline points="12,6 12,12 16,14"/>
</svg>
</div>
<h3>Ponctualité et fiabilité</h3>
<p>Nous respectons nos engagements. Les délais sont tenus et votre projet est livré à temps, selon les standards convenus.</p>
</div>
<div class="why-card">
<div class="why-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<polyline points="20,6 9,17 4,12"/>
</svg>
</div>
<h3>Matériaux de qualité</h3>
<p>Nous utilisons exclusivement des peintures et produits de première qualité pour un résultat durable et esthétique.</p>
</div>
<div class="why-card">
<div class="why-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<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"/>
</svg>
</div>
<h3>Équipe professionnelle</h3>
<p>Nos peintres décorateurs sont formés et expérimentés. Chaque détail compte pour nous, de la préparation à la finition.</p>
</div>
</div>
</div>
</section>
<CTABanner />
</BaseLayout>
<style>
.why-section {
padding: 5rem 0;
background-color: var(--color-brand-50);
}
.section-header {
text-align: center;
margin-bottom: 3rem;
}
.why-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1.5rem;
}
.why-card {
background-color: #ffffff;
border: 1.5px solid var(--color-brand-100);
border-radius: 8px;
padding: 1.75rem 1.5rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.why-icon {
width: 48px;
height: 48px;
background-color: var(--color-brand-600);
color: #ffffff;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.why-card h3 {
font-family: var(--font-body);
font-size: 1rem;
font-weight: 700;
color: var(--color-brand-600);
}
.why-card p {
font-size: 0.9rem;
color: var(--color-gray-dark);
opacity: 0.8;
line-height: 1.6;
}
@media (max-width: 900px) {
.why-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 540px) {
.why-grid {
grid-template-columns: 1fr;
}
}
</style>

233
src/pages/services.astro Normal file
View File

@@ -0,0 +1,233 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import CTABanner from '../components/CTABanner.astro';
import { getCollection, render } from 'astro:content';
import { getLangFromUrl, t } from '../i18n/utils';
const lang = getLangFromUrl(Astro.url);
const allServices = await getCollection('services');
const services = allServices
.filter(s => s.id.startsWith('fr/'))
.sort((a, b) => a.data.order - b.data.order);
const icons: Record<string, string> = {
interior: `<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9,22 9,12 15,12 15,22"/></svg>`,
exterior: `<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 22V12l10-10 10 10v10"/><path d="M15 22v-4a3 3 0 0 0-6 0v4"/><path d="M22 22H2"/></svg>`,
commercial: `<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg>`,
decoration: `<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>`,
renovation: `<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>`,
};
---
<BaseLayout
title={t(lang, 'nav.services')}
description="Découvrez tous nos services de peinture et décoration à Laval et sur la Rive-Nord : peinture intérieure et extérieure, commerciale, décoration d'intérieur et rénovation."
canonical="/services"
>
<!-- Page header -->
<section class="page-header">
<div class="container-site">
<nav class="breadcrumb" aria-label="Fil d'Ariane">
<a href="/">{t(lang, 'nav.accueil')}</a>
<span aria-hidden="true"></span>
<span aria-current="page">{t(lang, 'nav.services')}</span>
</nav>
<h1 class="page-title">{t(lang, 'services.title')}</h1>
<p class="page-subtitle">{t(lang, 'services.subtitle')}</p>
</div>
</section>
<!-- Services listing -->
<section class="services-listing">
<div class="container-site">
{services.length > 0 ? (
services.map(async (service) => {
const { Content } = await render(service);
const iconSvg = service.data.icon ? (icons[service.data.icon] ?? icons['interior']) : icons['interior'];
const serviceId = service.id.replace('fr/', '');
return (
<article class="service-detail" id={serviceId}>
<div class="service-detail-header">
<div class="service-detail-icon" set:html={iconSvg} />
<div>
<h2 class="service-detail-title">{service.data.title}</h2>
<p class="service-detail-lead">{service.data.description}</p>
</div>
</div>
<div class="service-detail-content prose">
<Content />
</div>
<div class="service-detail-cta">
<a href="/soumission" class="btn-primary">
{t(lang, 'cta.soumission.short')}
</a>
</div>
</article>
);
})
) : (
<p class="services-empty">{t(lang, 'services.empty')}</p>
)}
</div>
</section>
<CTABanner />
</BaseLayout>
<style>
.page-header {
background-color: var(--color-brand-50);
border-bottom: 1.5px solid var(--color-brand-100);
padding: 3rem 0 2.5rem;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: var(--color-brand-400);
margin-bottom: 1rem;
}
.breadcrumb a {
color: var(--color-brand-600);
transition: opacity 0.2s ease;
}
.breadcrumb a:hover {
opacity: 0.7;
}
.breadcrumb span[aria-current] {
color: var(--color-gray-dark);
opacity: 0.7;
}
.page-title {
font-size: clamp(1.75rem, 4vw, 2.75rem);
color: var(--color-brand-600);
margin-bottom: 0.75rem;
}
.page-subtitle {
font-size: 1.125rem;
color: var(--color-gray-dark);
opacity: 0.75;
max-width: 560px;
}
.services-listing {
padding: 4rem 0 5rem;
}
.service-detail {
padding: 3rem 0;
border-bottom: 1.5px solid var(--color-brand-100);
}
.service-detail:last-of-type {
border-bottom: none;
}
.service-detail-header {
display: flex;
align-items: flex-start;
gap: 1.5rem;
margin-bottom: 2rem;
}
.service-detail-icon {
width: 64px;
height: 64px;
background-color: var(--color-brand-600);
color: #ffffff;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.service-detail-title {
font-size: clamp(1.375rem, 3vw, 1.75rem);
color: var(--color-brand-600);
margin-bottom: 0.5rem;
}
.service-detail-lead {
font-size: 1.0625rem;
color: var(--color-gray-dark);
opacity: 0.8;
line-height: 1.6;
max-width: 640px;
}
.service-detail-content {
max-width: 720px;
margin-bottom: 2rem;
}
/* Prose styles */
.prose :global(h2) {
font-size: 1.25rem;
color: var(--color-brand-600);
font-family: var(--font-display);
margin: 1.5rem 0 0.75rem;
}
.prose :global(ul) {
list-style: none;
padding: 0;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.5rem;
}
.prose :global(li) {
display: flex;
align-items: baseline;
gap: 0.5rem;
font-size: 0.9375rem;
color: var(--color-gray-dark);
opacity: 0.85;
}
.prose :global(li)::before {
content: '—';
color: var(--color-brand-400);
flex-shrink: 0;
font-weight: 700;
}
.prose :global(p) {
font-size: 1rem;
color: var(--color-gray-dark);
opacity: 0.8;
line-height: 1.7;
margin-bottom: 1rem;
}
.service-detail-cta {
margin-top: 1rem;
}
.services-empty {
text-align: center;
padding: 5rem 0;
color: var(--color-gray-dark);
opacity: 0.6;
}
@media (max-width: 640px) {
.service-detail-header {
flex-direction: column;
gap: 1rem;
}
.prose :global(ul) {
grid-template-columns: 1fr;
}
}
</style>

258
src/pages/soumission.astro Normal file
View File

@@ -0,0 +1,258 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import SoumissionForm from '../components/SoumissionForm.astro';
import { getLangFromUrl, t } from '../i18n/utils';
import general from '../content/settings/general.json';
const lang = getLangFromUrl(Astro.url);
---
<BaseLayout
title={t(lang, 'nav.soumission')}
description="Demandez votre soumission gratuite pour vos travaux de peinture ou décoration à Laval et sur la Rive-Nord. Cachet Peintres Décorateurs vous répond rapidement."
canonical="/soumission"
>
<!-- Page header -->
<section class="page-header">
<div class="container-site">
<nav class="breadcrumb" aria-label="Fil d'Ariane">
<a href="/">{t(lang, 'nav.accueil')}</a>
<span aria-hidden="true"></span>
<span aria-current="page">{t(lang, 'nav.soumission')}</span>
</nav>
<h1 class="page-title">{t(lang, 'soumission.title')}</h1>
<p class="page-subtitle">{t(lang, 'soumission.subtitle')}</p>
</div>
</section>
<section class="soumission-section">
<div class="container-site soumission-grid">
<!-- Form -->
<div class="soumission-form-col">
<SoumissionForm />
</div>
<!-- Side info -->
<aside class="soumission-sidebar">
<div class="sidebar-card">
<h2 class="sidebar-title">Comment ça fonctionne</h2>
<ol class="process-list">
<li class="process-step">
<div class="process-num">1</div>
<div>
<h3>Remplissez le formulaire</h3>
<p>Décrivez votre projet en quelques mots — type de travaux, superficie et délai souhaité.</p>
</div>
</li>
<li class="process-step">
<div class="process-num">2</div>
<div>
<h3>Nous vous contactons</h3>
<p>Un de nos experts vous rappelle dans les 2448 heures pour planifier une visite gratuite.</p>
</div>
</li>
<li class="process-step">
<div class="process-num">3</div>
<div>
<h3>Soumission détaillée</h3>
<p>Vous recevez une soumission claire et sans engagement, avec tous les détails du projet.</p>
</div>
</li>
</ol>
</div>
<div class="sidebar-contact">
<h3>Ou contactez-nous directement</h3>
<a href={`tel:${general.phone.replace(/\D/g, '')}`} class="sidebar-phone">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 13a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 2.18h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L7.91 9.91a16 16 0 0 0 6.18 6.18l.94-.94a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/>
</svg>
{general.phone}
</a>
<a href={`mailto:${general.email}`} class="sidebar-email">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>
{general.email}
</a>
<p class="sidebar-legal">
Numéro RBQ : {general.rbq}<br />
{general.legalName}
</p>
</div>
</aside>
</div>
</section>
</BaseLayout>
<style>
.page-header {
background-color: var(--color-brand-50);
border-bottom: 1.5px solid var(--color-brand-100);
padding: 3rem 0 2.5rem;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: var(--color-brand-400);
margin-bottom: 1rem;
}
.breadcrumb a {
color: var(--color-brand-600);
transition: opacity 0.2s ease;
}
.breadcrumb a:hover { opacity: 0.7; }
.breadcrumb span[aria-current] {
color: var(--color-gray-dark);
opacity: 0.7;
}
.page-title {
font-size: clamp(1.75rem, 4vw, 2.75rem);
color: var(--color-brand-600);
margin-bottom: 0.75rem;
}
.page-subtitle {
font-size: 1.125rem;
color: var(--color-gray-dark);
opacity: 0.75;
max-width: 560px;
}
.soumission-section {
padding: 4rem 0 5rem;
}
.soumission-grid {
display: grid;
grid-template-columns: 1fr 380px;
gap: 4rem;
align-items: start;
}
/* Sidebar */
.soumission-sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.sidebar-card {
background-color: var(--color-brand-50);
border: 1.5px solid var(--color-brand-100);
border-radius: 10px;
padding: 2rem;
}
.sidebar-title {
font-family: var(--font-display);
font-size: 1.25rem;
color: var(--color-brand-600);
margin-bottom: 1.5rem;
}
.process-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.process-step {
display: flex;
align-items: flex-start;
gap: 1rem;
}
.process-num {
width: 36px;
height: 36px;
background-color: var(--color-brand-600);
color: #ffffff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-display);
font-size: 0.9375rem;
font-weight: 500;
flex-shrink: 0;
}
.process-step h3 {
font-family: var(--font-body);
font-size: 0.9375rem;
font-weight: 700;
color: var(--color-brand-600);
margin-bottom: 0.25rem;
}
.process-step p {
font-size: 0.875rem;
color: var(--color-gray-dark);
opacity: 0.75;
line-height: 1.55;
}
.sidebar-contact {
background-color: var(--color-brand-600);
color: #ffffff;
border-radius: 10px;
padding: 1.75rem;
display: flex;
flex-direction: column;
gap: 0.875rem;
}
.sidebar-contact h3 {
font-family: var(--font-body);
font-size: 0.8125rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 0.25rem;
}
.sidebar-phone,
.sidebar-email {
display: flex;
align-items: center;
gap: 0.625rem;
font-size: 1rem;
font-weight: 700;
color: #ffffff;
transition: opacity 0.2s ease;
}
.sidebar-phone:hover,
.sidebar-email:hover { opacity: 0.8; }
.sidebar-legal {
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.4);
margin-top: 0.5rem;
line-height: 1.6;
}
@media (max-width: 900px) {
.soumission-grid {
grid-template-columns: 1fr;
gap: 2.5rem;
}
.soumission-sidebar {
order: -1;
}
}
</style>