feat(home): add T3 Airbnb CTA in hero (FR/EN)

feat(analytics): add apt prop to all Airbnb CTAs for clearer tracking (FR/EN, rates, detail, thank-you).

style(home): increase apartment card image height on desktop (md:h-64).
This commit is contained in:
Ruidy 2025-09-08 09:43:50 -04:00
parent bb76e40893
commit d9dcd6fd5f
13 changed files with 2341 additions and 1131 deletions

View file

@ -3,9 +3,12 @@
## Project Structure & Modules
- `src/`: Astro app — `pages/` (locale folders: `fr/`, `en/`), `layouts/`, `styles/`.
- `src/i18n/routes.ts`: central route manifest (`hrefFor`, `siblingPath`) for FR/EN slugs.
- `public/`: static assets and redirects (`_redirects`, `assets/images`, `webfonts`, etc.).
- `docs/spec/website-revamp-spec.md`: product/UX spec and release plan (source of truth).
- `src/i18n/routes.ts`: central route manifest (`hrefFor`, `siblingPath`) for FR/EN
slugs.
- `public/`: static assets and redirects (`_redirects`, `assets/images`, `webfonts`,
etc.).
- `docs/spec/website-revamp-spec.md`: product/UX spec and release plan (source of
truth).
- Legacy: `lib/`, `pages/`, `data/` (Python generator) — deprecated; do not modify.
## Build, Test, and Development
@ -20,7 +23,8 @@
- UI: Tailwind CSS v4 (brand tokens: `--color-brand`, `--color-brand-600`).
- Icons: `lucide-astro` inline SVGs; use `text-brand` for accent.
- JS: vanilla only (no jQuery). Prefer lightweight patterns (e.g., scrollBy carousels).
- i18n: prefer `getRelativeLocaleUrl()` from `astro:i18n` for links; use `siblingPath()` for toggles when slugs differ.
- i18n: prefer `getRelativeLocaleUrl()` from `astro:i18n` for links; use `siblingPath()`
for toggles when slugs differ.
- Content slugs differ by locale (e.g., FR `avis` ↔ EN `reviews`) — never hardcode.
## Testing Guidelines
@ -31,7 +35,8 @@
## Commit & Pull Requests
- Conventional Commits (examples): `feat(home): hero CTA`, `fix(i18n): toggle sibling path`.
- Conventional Commits (examples): `feat(home): hero CTA`, `fix(i18n): toggle
sibling path`.
- PRs should include: build passing, screenshots (FR/EN), and spec updates when needed.
- Keep focused and small; avoid mixing refactors with features.

View file

@ -1,6 +1,6 @@
# VillaFleurie Website (Astro + Tailwind)
# VillaFleurie Website
This site is built with Astro and Tailwind, with FR/EN locales. The legacy Python generator remains for reference but is deprecated — new work happens in `src/`.
This site is built with Astro and Tailwind, with FR/EN locales.
## Quick Start
@ -12,7 +12,8 @@ This site is built with Astro and Tailwind, with FR/EN locales. The legacy Pytho
## Structure
- `src/pages/fr|en/`: localized pages (Home, Apartments, Reviews, Rates, Contact, Policies).
- `src/pages/fr|en/`: localized pages (Home, Apartments, Reviews, Rates, Contact,
Policies).
- `src/layouts/`: shared layout with sticky header, language toggle, footer.
- `src/styles/global.css`: Tailwind v4 with brand tokens (`--color-brand`, `--color-brand-600`).
- `src/i18n/routes.ts`: route manifest (`siblingPath`, CTA label helper).
@ -22,19 +23,17 @@ This site is built with Astro and Tailwind, with FR/EN locales. The legacy Pytho
## i18n & Navigation
- Folder-based locales with different slugs (e.g., FR `avis` ↔ EN `reviews`).
- Always use the route helpers for links and the language toggle; dont hardcode crosslocale paths.
- Always use the route helpers for links and the language toggle; dont hardcode
crosslocale paths.
## Forms & Analytics
- Netlify Forms on `/fr/contact/` and `/en/contact/`, with localized thankyou pages.
- Plausible events: `booking_request_submitted` (primary), `click_airbnb`, `click_booking`, etc.
- Plausible events: `booking_request_submitted` (primary), `click_airbnb`,
`click_booking`, etc.
## Contributing
- Use Conventional Commits. Include screenshots (FR/EN) for UI changes.
- Keep the spec updated when requirements change.
- See `AGENTS.md` for contributor guidelines and the current phase plan.
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,7 @@ const contactHref = getRelativeLocaleUrl('en','/contact/');
</ul>
<div class="mt-6 flex gap-3">
<a href={contactHref} class="inline-flex items-center rounded-lg bg-brand px-4 py-2 text-white hover:bg-brand-600">Send a Request</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-slate-200 px-4 py-2">Book on Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-slate-200 px-4 py-2" onclick="plausible('click_airbnb',{props:{locale:'en',page:'apartment',position:'body',apt:'t2'}})">Book on Airbnb (T2)</a>
</div>
</div>
</BaseLayout>

View file

@ -20,7 +20,7 @@ const contactHref = getRelativeLocaleUrl('en','/contact/');
</ul>
<div class="mt-6 flex gap-3">
<a href={contactHref} class="inline-flex items-center rounded-lg bg-brand px-4 py-2 text-white hover:bg-brand-600">Send a Request</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-slate-200 px-4 py-2">Book on Airbnb (T3)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-slate-200 px-4 py-2" onclick="plausible('click_airbnb',{props:{locale:'en',page:'apartment',position:'body',apt:'t3'}})">Book on Airbnb (T3)</a>
</div>
</div>
</BaseLayout>

View file

@ -18,7 +18,8 @@ const t3Href = getRelativeLocaleUrl('en','/apartments/t3-azur/');
<div class="mt-8 flex flex-wrap gap-3">
<a href={contactHref} class="inline-flex items-center rounded-lg bg-brand px-5 py-3 text-white hover:bg-brand-600">Send a Request</a>
<a href="https://www.booking.com/hotel/gp/villafleurie.fr.html" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-white/70 bg-white/90 px-5 py-3 text-slate-900 hover:bg-white" onclick="plausible('click_booking',{props:{locale:'en',page:'home',position:'hero'}})">Book on Booking.com</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-white/70 bg-white/90 px-5 py-3 text-slate-900 hover:bg-white" onclick="plausible('click_airbnb',{props:{locale:'en',page:'home',position:'hero'}})">Book on Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-white/70 bg-white/90 px-5 py-3 text-slate-900 hover:bg-white" onclick="plausible('click_airbnb',{props:{locale:'en',page:'home',position:'hero',apt:'t2'}})">Book on Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-white/70 bg-white/90 px-5 py-3 text-slate-900 hover:bg-white" onclick="plausible('click_airbnb',{props:{locale:'en',page:'home',position:'hero',apt:'t3'}})">Book on Airbnb (T3)</a>
</div>
</div>
</div>
@ -74,7 +75,7 @@ const t3Href = getRelativeLocaleUrl('en','/apartments/t3-azur/');
<div class="mt-2 h-1 w-16 bg-brand rounded"></div>
<div class="mt-6 grid md:grid-cols-2 gap-6">
<article class="rounded-xl overflow-hidden border border-slate-200 bg-white shadow-sm">
<img src="/assets/images/villafleurie_t2_salon_2.jpg" alt="T2 Corail" class="h-48 w-full object-cover" />
<img src="/assets/images/villafleurie_t2_salon_2.jpg" alt="T2 Corail" class="h-48 md:h-64 w-full object-cover" />
<div class="p-4">
<h3 class="text-lg font-semibold">T2 Corail</h3>
<p class="mt-1 text-sm text-slate-600">45 m² • 23 guests • 1 queen + sofabed • €59/night</p>
@ -82,7 +83,7 @@ const t3Href = getRelativeLocaleUrl('en','/apartments/t3-azur/');
</div>
</article>
<article class="rounded-xl overflow-hidden border border-slate-200 bg-white shadow-sm">
<img src="/assets/images/villafleurie_t3_salon.jpg" alt="T3 Azur" class="h-48 w-full object-cover" />
<img src="/assets/images/villafleurie_t3_salon.jpg" alt="T3 Azur" class="h-48 md:h-64 w-full object-cover" />
<div class="p-4">
<h3 class="text-lg font-semibold">T3 Azur</h3>
<p class="mt-1 text-sm text-slate-600">55 m² • up to 4 guests • 2 queen beds • €79/night</p>

View file

@ -22,8 +22,8 @@ import BaseLayout from '../../layouts/BaseLayout.astro';
</div>
<div class="mt-6 flex gap-3">
<a href="https://www.booking.com/hotel/gp/villafleurie.fr.html" target="_blank" rel="noopener" class="underline">Book on Booking.com</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="underline">Book on Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="underline">Book on Airbnb (T3)</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="underline" onclick="plausible('click_airbnb',{props:{locale:'en',page:'rates',position:'body',apt:'t2'}})">Book on Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="underline" onclick="plausible('click_airbnb',{props:{locale:'en',page:'rates',position:'body',apt:'t3'}})">Book on Airbnb (T3)</a>
</div>
</div>
</BaseLayout>

View file

@ -8,8 +8,8 @@ import BaseLayout from '../../layouts/BaseLayout.astro';
<p class="mt-2 text-slate-700">Well get back to you shortly. For instant booking:</p>
<div class="mt-4 flex gap-3">
<a href="https://www.booking.com/hotel/gp/villafleurie.fr.html" target="_blank" rel="noopener" class="underline">Booking.com</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="underline">Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="underline">Airbnb (T3)</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="underline" onclick="plausible('click_airbnb',{props:{locale:'en',page:'thank-you',position:'body',apt:'t2'}})">Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="underline" onclick="plausible('click_airbnb',{props:{locale:'en',page:'thank-you',position:'body',apt:'t3'}})">Airbnb (T3)</a>
</div>
</div>
</BaseLayout>

View file

@ -20,7 +20,7 @@ const contactHref = getRelativeLocaleUrl('fr','/contact/');
</ul>
<div class="mt-6 flex gap-3">
<a href={contactHref} class="inline-flex items-center rounded-lg bg-brand px-4 py-2 text-white hover:bg-brand-600">Envoyer une demande</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-slate-200 px-4 py-2">Réserver sur Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-slate-200 px-4 py-2" on:click={() => plausible('click_airbnb', {props:{locale:'fr',page:'apartment',position:'body',apt:'t2'}})}>Réserver sur Airbnb (T2)</a>
</div>
</div>
</BaseLayout>

View file

@ -20,7 +20,7 @@ const contactHref = getRelativeLocaleUrl('fr','/contact/');
</ul>
<div class="mt-6 flex gap-3">
<a href={contactHref} class="inline-flex items-center rounded-lg bg-brand px-4 py-2 text-white hover:bg-brand-600">Envoyer une demande</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-slate-200 px-4 py-2">Réserver sur Airbnb (T3)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-slate-200 px-4 py-2" on:click={() => plausible('click_airbnb', {props:{locale:'fr',page:'apartment',position:'body',apt:'t3'}})}>Réserver sur Airbnb (T3)</a>
</div>
</div>
</BaseLayout>

View file

@ -18,7 +18,8 @@ const t3Href = getRelativeLocaleUrl('fr','/appartements/t3-azur/');
<div class="mt-8 flex flex-wrap gap-3">
<a href={contactHref} class="inline-flex items-center rounded-lg bg-brand px-5 py-3 text-white hover:bg-brand-600">Envoyer une demande</a>
<a href="https://www.booking.com/hotel/gp/villafleurie.fr.html" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-white/70 bg-white/90 px-5 py-3 text-slate-900 hover:bg-white" on:click={() => plausible('click_booking', {props:{locale:'fr',page:'home',position:'hero'}})}>Réserver sur Booking.com</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-white/70 bg-white/90 px-5 py-3 text-slate-900 hover:bg-white" on:click={() => plausible('click_airbnb', {props:{locale:'fr',page:'home',position:'hero'}})}>Réserver sur Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-white/70 bg-white/90 px-5 py-3 text-slate-900 hover:bg-white" on:click={() => plausible('click_airbnb', {props:{locale:'fr',page:'home',position:'hero',apt:'t2'}})}>Réserver sur Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="inline-flex items-center rounded-lg border border-white/70 bg-white/90 px-5 py-3 text-slate-900 hover:bg-white" on:click={() => plausible('click_airbnb', {props:{locale:'fr',page:'home',position:'hero',apt:'t3'}})}>Réserver sur Airbnb (T3)</a>
</div>
</div>
</div>
@ -75,7 +76,7 @@ const t3Href = getRelativeLocaleUrl('fr','/appartements/t3-azur/');
<div class="mt-2 h-1 w-16 bg-brand rounded"></div>
<div class="mt-6 grid md:grid-cols-2 gap-6">
<article class="rounded-xl overflow-hidden border border-slate-200 bg-white shadow-sm">
<img src="/assets/images/villafleurie_t2_salon_2.jpg" alt="T2 Corail" class="h-48 w-full object-cover" />
<img src="/assets/images/villafleurie_t2_salon_2.jpg" alt="T2 Corail" class="h-48 md:h-64 w-full object-cover" />
<div class="p-4">
<h3 class="text-lg font-semibold">T2 Corail</h3>
<p class="mt-1 text-sm text-slate-600">45 m² • 23 pers. • 1 lit queen + canapélit • 59 €/nuit</p>
@ -83,7 +84,7 @@ const t3Href = getRelativeLocaleUrl('fr','/appartements/t3-azur/');
</div>
</article>
<article class="rounded-xl overflow-hidden border border-slate-200 bg-white shadow-sm">
<img src="/assets/images/villafleurie_t3_salon.jpg" alt="T3 Azur" class="h-48 w-full object-cover" />
<img src="/assets/images/villafleurie_t3_salon.jpg" alt="T3 Azur" class="h-48 md:h-64 w-full object-cover" />
<div class="p-4">
<h3 class="text-lg font-semibold">T3 Azur</h3>
<p class="mt-1 text-sm text-slate-600">55 m² • jusquà 4 pers. • 2 lits queen • 79 €/nuit</p>

View file

@ -8,8 +8,8 @@ import BaseLayout from '../../layouts/BaseLayout.astro';
<p class="mt-2 text-slate-700">Nous vous répondrons rapidement. Pour une réservation instantanée :</p>
<div class="mt-4 flex gap-3">
<a href="https://www.booking.com/hotel/gp/villafleurie.fr.html" target="_blank" rel="noopener" class="underline">Booking.com</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="underline">Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="underline">Airbnb (T3)</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="underline" on:click={() => plausible('click_airbnb', {props:{locale:'fr',page:'thank-you',position:'body',apt:'t2'}})}>Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="underline" on:click={() => plausible('click_airbnb', {props:{locale:'fr',page:'thank-you',position:'body',apt:'t3'}})}>Airbnb (T3)</a>
</div>
</div>
</BaseLayout>

View file

@ -22,8 +22,8 @@ import BaseLayout from '../../layouts/BaseLayout.astro';
</div>
<div class="mt-6 flex gap-3">
<a href="https://www.booking.com/hotel/gp/villafleurie.fr.html" target="_blank" rel="noopener" class="underline">Réserver sur Booking.com</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="underline">Réserver sur Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="underline">Réserver sur Airbnb (T3)</a>
<a href="https://airbnb.fr/h/villafleurie-t2" target="_blank" rel="noopener" class="underline" on:click={() => plausible('click_airbnb', {props:{locale:'fr',page:'rates',position:'body',apt:'t2'}})}>Réserver sur Airbnb (T2)</a>
<a href="https://airbnb.fr/h/villafleurie-t3" target="_blank" rel="noopener" class="underline" on:click={() => plausible('click_airbnb', {props:{locale:'fr',page:'rates',position:'body',apt:'t3'}})}>Réserver sur Airbnb (T3)</a>
</div>
</div>
</BaseLayout>