Create two new legal pages (FR/EN/MG) with LAATEL Corporation
company details (STAT, RCS, NIF). Add footer links to all pages
and translation keys for the three languages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously the Forms API created the contact at form submission time —
which meant unverified signups (bots that pass Turnstile, typos, fake
emails) polluted HubSpot. Now:
- Form submit → Worker stores all data in KV (24h TTL) + sends Brevo
verification email (no HubSpot write)
- User clicks email link → Worker generates ref + creates HubSpot
contact via CRM API + sends welcome email with ref + Paris address
Plus this commit:
- Email header gets the MVA logo on the left of the dark blue banner
- Welcome email's first address line auto-injects (MVA-XXX) so the
customer can copy it directly onto their package
Also handles idempotency — clicking the verification link a second time
returns the existing ref without creating a duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resend requires a verified domain to send to arbitrary recipients —
mvaglobalfret.com isn't registered. Brevo accepts single-sender
verification on a free email address, so we can send from
mvaglobalfret@gmail.com without owning a domain.
- Worker: replace resendSend() with brevoSend() (api.brevo.com/v3/smtp/email)
- Env vars: BREVO_API_KEY, BREVO_SENDER_EMAIL, BREVO_SENDER_NAME
- Update comments in confirmation.js and form-handler.js
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Architecture finale :
1. User remplit formulaire + passe Turnstile CAPTCHA → form-handler.js
2. form-handler.js POST au Worker avec action 'requestVerification'
3. Worker valide Turnstile, génère un token UUID, le stocke en KV (TTL 24h)
avec firstname/email/reference_client, puis envoie un email via Resend
avec un lien : confirmation.html?token=XXX
4. User reçoit email, clique 'Confirmer mon email'
5. confirmation.html lit le token de l'URL, POST au Worker avec action
'verifyToken'
6. Worker valide le token, envoie le welcome email via Resend (avec ref +
adresse Paris depuis env var), marque le token comme utilisé
7. confirmation.html affiche 'Inscription confirmée !'
Ainsi : ref + adresse Paris ne sortent JAMAIS avant validation email,
et les bots sont bloqués à l'étape 1 par Turnstile.
Setup Cloudflare requis (côté user) :
- RESEND_API_KEY : clé API Resend (re_...)
- RESEND_FROM : adresse expéditrice ('onboarding@resend.dev' pour test,
ou domain vérifié pour prod)
- SITE_URL : optionnel, défaut https://mva-global-fret.github.io/site-mva-global-fret
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- contact.html: ajout du widget Turnstile (site key: 0x4AAAAAADKDuc7Rmlb1svIL)
- form-handler.js: blocage de la soumission si pas de token Turnstile valide
- Worker: validation server-side du token via /turnstile/v0/siteverify
avant chaque appel sendWelcomeNow → bloque les bots qui n'auraient
pas passé le challenge côté client.
Le secret Turnstile est en env var Cloudflare (TURNSTILE_SECRET).
Limite humain : Turnstile détecte les bots avec très peu d'interaction
côté utilisateur (mode 'Managed', le plus souvent invisible).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug 1 — Ref MVA-001 dupliquée :
Le filtre HubSpot 'HAS_PROPERTY' avec value:'' retournait 0 résultats.
Suppression du value:'' → maintenant le worker liste correctement les
contacts avec reference_client et incrémente bien (testé : MVA-004).
Bug 2 — Email post-inscription jamais reçu :
Le double opt-in HubSpot ne se déclenche pas via Forms API sans
subscription consent (impossible à configurer sans nouveaux scopes
Private App). Pivot vers une approche plus simple :
- L'email de bienvenue est désormais envoyé directement après
soumission du formulaire (pas de DOI HubSpot)
- L'envoi passe par le Cloudflare Worker (action sendWelcomeNow)
pour que l'adresse Paris reste dans les env vars Cloudflare et
ne soit JAMAIS dans le JS public
- Worker appelle EmailJS REST avec firstname + reference + paris_address
Cleanup : message de succès reverti à 'Inscription réussie' (FR/EN/MG).
Anti-spam : protection légère via filtre email/téléphone côté formulaire.
La cron-based welcome (post-DOI) reste en place mais sera inerte tant
que aucun contact n'a le statut CONFIRMED côté HubSpot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flux complet du double opt-in :
1. User soumet le formulaire → contact créé en HubSpot avec sa référence
2. HubSpot envoie un email 'Confirmez votre inscription'
3. User clique 'Confirmer' → HubSpot le marque 'subscribed'
4. HubSpot redirige vers confirmation.html?email=...
5. La page lit l'email, appelle le Worker Cloudflare pour récupérer la
référence du contact, et déclenche l'envoi de l'email de bienvenue
via EmailJS (avec la référence dedans)
6. Affiche succès + référence à l'écran
Idempotence via localStorage pour éviter de spammer l'email à chaque
rechargement de la page.
À configurer dans HubSpot Settings > Marketing > Email > Confirmation
d'inscription : URL de redirection après confirmation =
https://mva-global-fret.github.io/site-mva-global-fret/confirmation.html?email={{contact.email}}
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Retire le bloc 'Numéro de référence client' de la page de succès
- Met à jour le message en FR/EN/MG : 'Vous recevrez ensuite votre numéro
de référence client' après confirmation
- Désactive l'envoi immédiat de l'email EmailJS de bienvenue (qui
contenait déjà la référence). HubSpot envoie son email de
double opt-in qui sera customisé pour inclure la référence
via le token {{contact.reference_client}}.
Résultat : la référence n'est jamais visible avant que l'email ne soit
vérifié (puisque seuls les emails valides reçoivent le double opt-in).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HubSpot double opt-in is now enabled at the account level. After
submitting the form, contacts must click the confirmation link in
their email to be added to the marketing list.
The success message now explicitly tells the user to check their
inbox and click the confirmation link, instead of just saying
'inscription enregistrée'.
- title: 'Vérifiez votre boîte mail !' (FR), 'Check your inbox!' (EN), 'Jereo ny boaty mailaka!' (MG)
- main msg: focus on confirmation step
- icon: enveloppe-circle-check (gold) instead of generic green check
- note: nuance that the reference number is for tracking parcels
- emailSent: kept as is (informative footer)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Retire l'état caché (scale 0.05 / opacity 0 / offset -27vh)
- Retire les transitions de révélation (transform/opacity 2.2s)
- Retire l'animation de pulse halo (.cta-btn::after)
- Retire le déclenchement JS via la classe .revealed (l'avion qui croise le centre n'a plus d'effet sur le bouton)
Le bouton est désormais centré au milieu du viewport dès le chargement,
toujours cliquable. Hover conserve le scale 1.04 + shine effect.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Antananarivo home delivery is no longer a flat 6,000 Ar:
- 6,000 Ar ≤ 5 kg
- 10,000 Ar ≤ 10 kg
- 20,000 Ar ≤ 20 kg
- > 20 kg: contact us
tarifs.html: replace the single price line with a 3-row tiered list
inside the Antananarivo card, keep the headline "À partir de 6 000 Ar"
on top, and turn the green "home delivery available" note into an
amber "over 20 kg: contact us" info banner.
translations.js: add delivery1Tier1/2/3 keys for FR/EN/MG, refresh
delivery1Title (now mentions "à domicile"), delivery1Price (now "À
partir de…"), delivery1Desc, delivery1Note. Province card unchanged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remplace le prix unique de 6 000 Ar par 3 paliers (5/10/20 kg) plus
une ligne « Nous contacter » pour les colis plus lourds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mobile screens are smaller and users glance at them for less time,
so the 28-second full-traversal on desktop felt sluggish there.
BASE_SPEED is now 1/16 on mobile vs 1/28 on desktop. The touch
boost (×6) still stacks on top, giving a sub-3-second sprint when
the user holds a finger down.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detects mobile via UA + 768 px media query (IS_MOBILE constant) and
flips three knobs:
- Renderer: antialias off, pixelRatio capped at 1.5 (vs 2 on desktop)
- Parcels: spawn every 2.4 s (vs 1.4 s on desktop) to keep clone+
draw cost down
Adds touch handlers so users without a mouse can speed the plane up:
- touchstart bumps `touchBoost` from 1 to 6 → BASE_SPEED is multiplied
by 6 each tick while a finger is on the screen
- touchmove also feeds Math.hypot(dx, dy) * MOUSE_BOOST into
targetProgress, so swiping advances the plane the same way mouse
motion does on desktop
- touchend / touchcancel reset touchBoost to 1 and clear last-touch
coordinates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
translations.js declared the dict as `const translations = {...}`,
which scopes it to the script but does NOT attach it to window. The
inline applyLang() in index.html reads `window.translations?.[l]` and
was always getting undefined → early-return → no DOM updates → the
button text stayed in French regardless of the lang switcher.
One-line fix: append `window.translations = translations;` so
classic-script inline code can pick it up. All page-level i18n keys
(intro.ctaBtn included) start translating again.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hide the gold pill button at page load (opacity 0, scale 0.05,
translated 27 vh up — roughly where the plane is when it crosses
the viewport center). When the plane's progress reaches 0.5, the
tick loop adds a `.revealed` class to .cta-btn; CSS variables flip
and a 1.2 s spring transition lands the button at viewport center
at full size.
Pulse halo (::after) is dormant until the .revealed class lands,
so it doesn't waste cycles on a hidden element. Hover scale (1.04)
re-introduced on `.cta-btn.revealed:hover` with the original 0.32 s
transition so it doesn't fight the slow emerge tween.
Plane and parcel logic untouched.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parcels were appearing right at the plane's center line, looking like
they came out of a window. Move the spawn offset from y-0.4 to y-1.1
so they emerge from below the belly of the airliner, where a real
cargo bay would be.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previous +0.55·height pushed the cargo up into the canopy itself —
user's annotation showed the cargo belongs immediately under the
strings' end, not inside the parachute. With the wrapper fix from
the previous commit, parachuteBottom now correctly points at the
harness end, so cargo center = parachuteBottom - cargoH/2 + 0.06
puts the cargo's top right at the harness with a tiny overlap.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
User's annotation showed the cargo belongs right at the canopy's
base — where the strings converge — not at the very bottom of the
strings. New position: parachuteBottom + 0.55·parachuteHeight,
i.e. about 55% up the parachute's vertical extent. Cargo also
shrunk to 0.36×0.30×0.36 to better match the small rectangle the
user drew.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The black rectangles the user circled in the screenshot were the
strap meshes (strapH/strapV, 0.04-thick black boxes) — they were
meant to wrap the cardboard box but ended up rendering as detached
rectangles in some viewing angles. They're gone.
Cargo positioning is now computed from the actual scaled parachute
bbox: parachuteBottom = -(size.y · baseScale)/2, cargo center sits
just inside that line for a slight overlap so the parachute strings
visually terminate on the box top. The cargo is now a child of the
parachute mesh (para.add(cargo)), so any transform applied to the
parachute — scale, rotation, position — carries the box along with
it. To keep the visible box size consistent regardless of the
parachute's baseScale, the cargo's local position and scale are
divided by baseScale.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Drops the parcel.png sprite (which baked the parachute and the box
into a single image) for a real 3D model: assets/parachute.glb is
the CC-BY 3.0 « Parachute » by Poly by Google, decompressed from
poly.pizza's static.poly.pizza CDN.
The parcel template now stacks four meshes inside one Group so they
move as a unit:
- the loaded parachute (centered + scaled to ~1.6 world units max)
- a 0.46×0.36×0.46 brown box with metal-low MeshStandardMaterial,
positioned at y = -0.96 — right below the parachute's harness
point — so the model's strings appear to terminate on it
- two thin black straps wrapping the box (0.04-thick boxes, one
horizontal one vertical) for visual reinforcement that the cargo
is tied down
spawnParcel clones the template (deep), per-instance clones every
material so opacity can be modulated independently per parcel, and
adds a slight pendulum sway + slow Y spin. Falling/scale-up/fade
logic adapted from the sprite version. Cleanup disposes the cloned
materials when a parcel exits.
HTML credit comment extended to attribute both the airplane and the
new parachute under CC-BY 3.0.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a sprite-based parcel system. The plane spawns a new
parcel-on-parachute (assets/parcel.png, 236 KB transparent) every
~1.4 s while it's visible (progress 0.04-0.92). Each parcel:
- Spawns at the plane's current position with a tiny random offset.
- Falls at 1.4 world units/s, with a small horizontal drift and a
parachute sway sinusoid for character.
- Scales 0.35 → 2.6 over its lifetime, simulating the perspective
of falling toward the camera.
- Fades in over 6% of life, fades out over the last 15%.
- Cleaned up (removed from scene + material disposed) when its
lifetime expires or it drops below y = -10.
Implemented as THREE.Sprite so it always faces the camera, no need
to track per-parcel orientation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
User sketched a near-horizontal red line in the upper third of the
viewport, slightly higher on the left than on the right. Mapping
that to world coords with the camera at z=22 and 40° vfov:
- right end (entrance): y ≈ +3.5
- left end (exit): y ≈ +5
So the plane keeps cruising in the upper portion of the frame and
climbs ~1.5 world units across 40 horizontal units — about 2° of
slope. Pitch follows: rotation.z = -0.06 - p·0.01 (3.5–4° nose-up,
matching the slope). Roll softened to 0.04 ± 0.02 since the path is
nearly straight.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reroute the plane: it now enters from the upper-right (y=+6) and
exits at the lower-left (y=-10), so the path slopes downward as a
~22° descent (slope = -16/40 in world units). Pitch reversed to
match — rotation.z = +0.32 + p·0.05 (≈18-21° nose-down) so the
plane's body aligns with the descent line. Roll and yaw unchanged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The labels in the previous commit were swapped. With the wrapper
rotated -π/2 around Y so the nose points -X, the plane's longitudinal
axis is world X (so rotation.x is roll) and its lateral axis is
world Z (so rotation.z is pitch). Earlier code applied "roll"
(positive 0.18) to .z, which was actually pitching the nose down —
no amount of tweaking rotation.x could compensate, hence the user
seeing the plane go forward+down even after sign flips.
Now:
- rotation.z = -0.30 - p·0.05 (nose up ~17–20°, climb attitude)
- rotation.x = 0.12 + small variation (subtle roll)
- rotation.y = 0 (no yaw, plane already heading the right way)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After the wrapper's -π/2 yaw, applying positive rotation.x to the
planeHolder rotates the plane around world X with the nose dropping,
not lifting (visible in the user-supplied screenshot). Flipping to
-0.18 (≈10°) puts the nose where the trajectory says it should go.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The +0.20 (≈11°) climb angle from last commit looked cartoonishly
steep. Real airliners climb at maybe 3–5° once they've cleaned up
after takeoff. Cut targetPitch to 0.06 + p·0.02 (3.5° → 4.6°).
Roll/yaw untouched.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
py was descending +7→+2, now ascending -1→+12 so the plane gains
altitude across the traversal. px stretched 18→-22 (was 16→-16) so
both endpoints sit clearly outside the visible frustum (camera at
z=22 + 40° vfov gives ~28 world-units of visible width on a 16:9
viewport). Pitch flipped from negative to +0.20 + p·0.10 to read
as nose-up for the climb. Roll/yaw unchanged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Poly by Google airplane's default orientation in the GLB is the
opposite of what I assumed: at wrapper.rotation.y = +π/2 the nose
ended up pointing into the direction the plane was moving away from
(it looked like it was flying tail-first). Switch to -π/2 so the
nose actually leads the trajectory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two adjustments to the intro plane:
1. Autonomous cruise speed. Adds BASE_SPEED (1/28 per second of
wallclock) to targetProgress on every frame, so the plane crosses
the screen on its own in ~28 s without any input. Mouse motion
still adds a boost (one full traversal per ~4500 px of cursor
travel), which feels like the plane "speeding up" when the user
interacts. Time delta clamped to 0.1 s so the plane doesn't jump
forward after the tab returns from background.
2. Reverse direction. Plane now enters from upper-right (x = +16),
traverses to upper-left (x = -16), nose pointing -X. Wrapper rot.y
flipped to +π/2; px formula flipped; banking angles inverted so
the plane still rolls "into the turn" along its new direction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Old behaviour: plane.x mapped 1:1 to cursor.x — moving the mouse left
made the plane reverse. New behaviour: every pixel of cursor travel
(any direction) increments a progress counter from 0 to 1, the plane
position is derived from progress, and progress saturates at 1 — so
once the plane has exited stage right, it stays gone.
FULL_DISTANCE = 3500 px of cursor travel for a full traversal. The
existing lerp (0.06) still smooths the rendered position. Background
parallax still uses raw cursor X/Y (independent of plane progress).
deviceorientation handler updated symmetrically — gamma+beta deltas
push progress forward on mobile.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Background was inadvertently the wrong Gemini export (an unrelated
airliner-over-mountains photo) — sorted out and replaced with the
intended aerial illustration of Antananarivo (Lake Anosy + Rova
hill + city, 487 KB, 1920px wide).
Also lifted the plane's trajectory: y goes from +7 (offscreen
upper-left) to +2 (still upper half, exiting right) instead of
+5..-2. The plane now stays clearly in the upper third of the
viewport, leaving room for the centered CTA button and the city
detail at the bottom.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
User-supplied aerial illustration of Antananarivo (Lake Anosy + Rova
hill + surrounding city, 1920px wide JPG, 241 KB) replaces the
parachute-drop video as the static intro backdrop. The video files
are deleted from /videos.
The plane no longer orbits a scroll timeline. Now:
- Page is a single viewport, no scroll, no act labels, no scroll hint.
- Mouse X (0..1) drives plane.position.x from -16 (offscreen left) to
+16 (offscreen right), with plane.position.y descending from +5 to
-2 — so the plane enters from the upper-left and exits lower-right.
- Pitch/roll/yaw lerp toward small targets that depend on mouse X, so
the plane banks naturally as it crosses.
- Background image gets a softer mouse parallax (-16/-10px) via the
existing --mx/--my CSS vars, now updated from intro-scene.js.
- Three.js cloud spheres are gone; the photo is the entire backdrop.
- ScrollTrigger + the GSAP timeline are removed; the page no longer
needs gsap at all (the script tag stayed for now in case it comes
back, but the dependency could be dropped on a future pass).
- CTA button is back to plain visible/centered, no reveal animation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Drops the cylinder+box airplane built last commit in favor of a CC-BY
3D commercial airliner from Poly by Google (188 KB GLB, 11.3k tris,
hosted in assets/airplane.glb). Loaded at runtime via three/addons
GLTFLoader; importmap extended to expose the addons subpath.
Bug worth noting: a naive setFromObject + position.sub(center) +
scale.setScalar pipeline leaves the model offset by -center after
scaling because position is in pre-scale units. Fix is to wrap the
model in a Group, apply the centering offset to the inner model,
then scale the outer Group — the whole transform stays consistent.
Attribution added in two places per CC-BY 3.0:
- HTML header comment with creator + source URL + license link
- JS file header in intro-scene.js
Tone-mapping bumped to ACES filmic for a slightly nicer render.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restructure the page so the first 4 viewports of scroll drive a
Three.js scene composited on top of the Antananarivo parachute video.
What's there:
- Three.js (ESM, r158 via importmap) renders a low-poly cargo airliner
built from primitives: cylinder fuselage, cone nose, sphere cockpit
(dark glass + emissive), box wings/tail/fin, cylinder engines with
torus intakes, gold trim band, navy fin with gold logo box. No
external model file.
- Hemisphere + directional + ambient lights tuned for golden-hour fill.
- 14 cloud spheres scattered around the plane, slowly rotating.
- GSAP + ScrollTrigger drive a single progress value scrubbed against
scroll position. Inside the rAF loop, the camera arcs from rear-left
(-0.6 rad) to front-right (+1.1 rad), radius dipping mid-flight, and
the plane rolls slightly with scroll.
- Three act labels (Paris CDG / Vol cargo / Antananarivo) cross-fade at
20%/40%-60%/72% scroll positions via a chained gsap timeline.
- Gold CTA button stays opacity:0 + pointer-events:none until the last
~10% of scroll, then fades and scales in. Hover transform rebuilt
without the old mouse-parallax tilt (fights the scroll animation).
- Scroll hint pill (chevron + "Faites défiler") at the bottom of the
first viewport, fades out on first scroll event.
- prefers-reduced-motion shortcut: scroll stage hidden, CTA visible,
no animation. Page reverts to a static screen with the video bg.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The new index.html is a 3-act scroll-driven storytelling intro:
Act 1 (Tarmac at sunset)
Sunset gradient sky, mountain silhouettes, hangar, runway with
centerline lights. Inline SVG cargo plane (MVA-branded gold tail)
sits on the ground while box emojis cycle up a loading ramp.
Act 2 (Take-off at dusk)
Dusk sky, two parallax cloud layers and distant mountains. The
plane translates diagonally up-and-right with a slight tilt and
a glowing contrail, sized down progressively.
Act 3 (Arrival at night)
Deep navy night sky with twinkling stars, a glowing moon, and
the Madagascar coastline silhouette. A small plane fades in
descending toward the island, then a centered CTA block reveals:
"Bienvenue à bord" with the gold "Accéder au site" button that
routes to accueil.html (the real homepage).
Implementation:
- Pure CSS layered scenes; no library
- Scroll progress driven by a single CSS custom property --scroll
(0→1) updated via rAF, layers transform off it
- Mouse-move parallax on layers via --mx/--my (skipped on touch)
- Inline SVG plane reused across the 3 scenes
- All text is i18n-driven with a new `intro` section in FR/EN/MG
Architecture changes:
- index.html → new parallax intro
- accueil.html → former index.html content (full homepage)
- All nav/logo/footer links updated index.html → accueil.html across
the 8 existing pages
- Mobile-nav on accueil.html now also includes Service Commande
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Footer copyright bumped to 2026 across all 8 HTML pages and in the
three translation strings (FR/EN/MG) in translations.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
"Dépasser les dimensions autorisées" now has an asterisk pointing to
a small italic note below the dos/donts grid: "Nous contacter pour
tout colis volumineux." (FR/EN/MG).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New page service-commande.html explaining the order-on-behalf service:
- 10% commission on total order amount
- 50% deposit + 50% on order validation
- Right to request 100% upfront for large orders
- Standard 70,000 Ar/kg shipping fee on top, paid on receipt
- 5-question FAQ
- CTA to contact / Messenger
Trilingual content (FR / EN / MG) added to translations.js with full
serviceCommande section + nav.serviceCommande key.
Menu link inserted right after "Tarifs" in main nav, mobile nav and
footer links across all pages: index, about, tarifs, contact,
guide-envoi, application, cgv, service-commande.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a client tries to re-register with an existing email:
- The form still shows the on-screen "already registered" message
- HubSpot data is still untouched (no duplicate, ref number preserved)
- NEW: a welcome-back email is now sent via EmailJS reminding them
of their existing client reference number
- The internal MVA notification (Formspree) is preserved
Requires creating a new EmailJS template (id: template_welcome_back)
with variables: firstname, email, reference_client.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace the cramped map-container layout with a polished map-card
- Centered section header (h2 + gold-line + subtitle) above the card
- Navy gradient header inside the card with gold location icon
- Address displayed in the header alongside an "open in Google Maps" CTA
- Increased map height to 420px (320px on mobile)
- Soft shadow and gold-tinted border for premium feel
- Added mapSubtitle translation key (FR/EN/MG)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ajout de data-i18n sur tous les éléments non traduits (tarifs, contact, guide,
application, accueil) et ajout des clés correspondantes en FR/EN/MG dans
translations.js : détails tarifaires, cartes livraison, stats accueil, sections
CTA, récapitulatif contact, footer CGV.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
"Sarany" est le terme correct pour désigner les tarifs/prix dans ce contexte.
Corrige : heroTitle tarifs, heroSubtitle tarifs, nav pricing, heroCtaSecondary,
et value3Desc (about).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>