Ce que c’est
Un site d’agence de voyage, complet : accueil sur photo avec son anneau de vignettes, présentation, six circuits en zigzag, quatre escapades, un bloc film, les avis en carrousel, une FAQ et un pied de page. Une seule page, un seul fichier HTML.
Le design reprend celui d’un template Webflow dont seules les photos ont été conservées. Tout le code a été réécrit : l’original tenait dans 112 Ko de CSS minifié, jQuery, deux bundles Webflow de 237 Ko, un moteur d’animation propriétaire (IX3) et quatre fichiers GSAP chargés depuis deux domaines.
C’est le point important : ce template n’est pas là pour être admiré, il est là pour être lu, cassé et remonté.
Ce que tu changes en premier
Les couleurs — huit lignes
Tout en haut de assets/css/style.css :
--color-ink: #0f0a01; /* le texte sur fond clair */
--color-paper: #f8f8f8; /* le fond de page */
--color-surface: #ffffff; /* les cartes, la section du film */
--color-deep: #08070c; /* les deux sections sombres */
--color-gold: #febd1a; /* les étoiles et la flèche du formulaire */
--color-star: #fa9200; /* les étoiles des avis */
--color-star-off: #d9d9d9; /* la cinquième étoile, éteinte */
--on-dark-soft: rgba(255,255,255,.7); /* le texte secondaire sur noir */
Une chose à comprendre avant de toucher au reste : le doré tombe à 1,7:1 sur blanc. Il ne porte donc jamais de texte ici, seulement des étoiles et une flèche de 18 px, sur fond noir — où il remonte à 12,0:1. Si tu le remplaces par une couleur qui doit porter du texte, il t’en faudra trois nuances : la pleine, une assombrie pour le survol, une éclaircie pour les photos. En changer une oblige à changer les trois. Le reste de la palette est large : 18,6:1 pour l’encre sur le papier, 5,2:1 pour le texte secondaire, 20,1:1 pour le blanc sur noir.
Le nom
Cherche « Duneva » dans index.html. Aucune classe CSS ne
contient la marque : un rechercher-remplacer ne peut rien casser. Restent
deux images à remplacer, le logo de la barre
(assets/img/logo-wordmark.webp) et le lettrage géant du pied
(assets/img/footer-wordmark.svg).
Les tailles
L’échelle typographique est déclarée une fois, puis redéclarée à chaque
point de rupture (991, 767 et 479 px). Un titre n’a donc jamais sa propre
media query : tu changes --display-xl-size et toute la page
suit.
Les dix-huit effets, et comment ils sont faits
Tous relevés dans les données du moteur d’animation de l’original — 36 interactions et 36 timelines extraites de son store, avec leurs durées, leurs décalages et leurs courbes, y compris les deux courbes sur mesure. Ce qui n’était pas dans les données a été mesuré au navigateur, pas à l’œil.
| Effet | Réglages d’origine | Comment |
|---|---|---|
| Anneau de 14 photos | CSS de l’export : rotateY(i × 360/14) translateZ(480px), perspective 2500 px, un tour en 50 s, plan incliné de 6° | repris tel quel : c’est du CSS, pas du code d’éditeur. Un masque en dégradé efface les deux bords |
| Entrée de l’accueil | au chargement : titre découpé en lignes, +25 px, décalage 0,1 s, 0,7 s power3.out ; texte et bouton +40 px ; l’anneau monte de 50 px depuis 0,8× | mêmes valeurs, jouées après le premier rendu ; la découpe en lignes est refaite en 40 lignes de JavaScript |
| Titres de section (5) | au scroll, top 90 % : lignes, +25 px, décalage 0,16 s, 0,8 s, courbe sur mesure M0,160 C40,0 80,0 160,0 | identique — la courbe se traduit en cubic-bezier(.25, 1, .5, 1). Seuil déplacé à 15 % du bas de l’écran |
| Étiquettes « (About Us) » (6) | top 90 % : +18 px, opacité, power3.out | identique |
| Apparition des blocs (24) | top 90 % : +40 px sur 0,7 s power2.out, ou +50 px avec 0,12 s de décalage | IntersectionObserver, mêmes valeurs et mêmes décalages |
| Cascade des cartes | délai par colonne suivant 1 − 0,5ⁿ multiplié par 0,5 s, remis à zéro à chaque rangée | même courbe, calculée en JavaScript — l’original la calcule en CSS avec sibling-index(), que peu de navigateurs connaissent |
| Rideau des deux photos | scrub 0,8 de top 90 % à top 30 % (et 24 %) : clip-path: inset(100 % → 0) | même clip-path ; la progression est écrite dans une variable au scroll, puis lissée sur ~0,8 s |
| Cadre du film | scrub 0,8 de top 80 % à top 20 % : inset(25 % → 0) | identique, même mécanique |
| Bouton gluant (4) | CSS de l’export : filtre SVG feGaussianBlur + feColorMatrix, le rond glisse de 8 px en 0,5 s cubic-bezier(.34, 1.56, .64, 1) | repris tel quel — le filtre est défini une fois en fin de page |
| Liens fléchés (8) | survol : la flèche part en x +100 %, y −20 px, 0,3 s | corrigé : deux flèches dans une fenêtre de 18 px, diagonale complète (voir « Ce qui n’est pas parfait ») |
| Boutons ronds (4) | survol : x 0 → 100 %, y 0 → −100 %, power2.out | identique |
| Soulignement du menu (6) | survol : xPercent −100 → 0 en 0,3 s, linéaire | identique |
| Accordéon FAQ (6) | clic : hauteur 0 ↔ auto, le trait vertical du « + » tourne de 90° à 0, power2.inOut | <details> natif ; la hauteur est animée par le script, parce qu’un <details> fermé est retiré du rendu et qu’aucune keyframe n’y rejoue |
| Carrousel des avis (5) | slider Webflow, deux flèches | scroll-snap natif : il glisse, se balaye et s’atteint au clavier sans JavaScript. Les flèches ne font qu’un scrollBy, la position est annoncée |
| Photos du pied | top 90 % : opacité 0,5 % → 100 %, rotation 0 → −14° et 18°, 0,7 s | identique |
| Lettrage géant du pied | top bottom : monte de 100 %, opacité, 0,7 s power3.out | identique, mais l’observateur regarde le conteneur : un élément qu’on translate sous la page n’entre jamais dans l’écran |
| Menu mobile | clic : les deux traits tournent de ±45° et se décalent de 7 px, 0,4 s expo.inOut | identique, plus la fermeture à l’Échap et au clic sur un lien |
| Fenêtre du film | lightbox Webflow ouvrant une vidéo Vimeo | <dialog> natif et une vidéo locale de 226 Ko, dont la source n’est posée qu’au clic |
Ce qu’il faut savoir
- Aucune requête externe. 33 requêtes au chargement, toutes locales. L’original chargeait jQuery, deux bundles Webflow, quatre fichiers GSAP et deux polices depuis deux domaines, plus un lecteur Vimeo au clic.
-
L’apparition au scroll ne peut pas masquer le contenu.
L’état caché n’existe que si le JavaScript a tourné. Vérifié en coupant les
scripts : les sept titres restent visibles, les deux photos sont découvertes,
le cadre du film est ouvert et les six accordéons s’ouvrent. L’original, lui,
posait
visibility: hiddensur trente-quatre sélecteurs. - Zéro style et zéro script en ligne. Vérifié en servant le dossier avec la politique de sécurité stricte du site, pas supposé : zéro violation, zéro erreur JavaScript.
- Le déclencheur est à 15 % du bas de l’écran, pas au premier pixel comme dans l’original : un bloc entre visiblement au lieu d’avoir fini avant d’être vu. Un filet révèle ce qui reste quand on atteint le bas.
- Le carrousel des avis marche sans JavaScript. C’est un vrai conteneur de défilement horizontal : il se balaye au doigt et s’atteint au clavier. Les boutons viennent par-dessus.
-
La vidéo ne se charge qu’au clic.
preload="none"et une source posée par le script : un visiteur qui ne lance jamais le film ne télécharge pas ses 226 Ko. - Accessibilité : lien d’évitement, état du menu annoncé, FAQ native, position du carrousel annoncée, focus visible unique, décorations masquées aux lecteurs d’écran, animations et délais coupés si le système le demande — l’anneau s’arrête alors sur une image plutôt que de disparaître.
- Responsive vérifié à 390, 1440 et 2560 px, au chargement comme après défilement complet, sans débordement horizontal.
Ce qui n’est pas parfait
Autant le dire ici plutôt que te le laisser découvrir.
- La police n’est pas la même. L’original utilise Saprona, dont le fichier porte « All rights reserved » : elle ne se redistribue pas. Switzer la remplace — même famille de grotesques, mêmes ouvertures — mais elle est un peu plus étroite. Les retours à la ligne bougent, et c’est l’essentiel des écarts qui suivent.
- Le pied de page fait 2 % de moins à 767 px. Les trois colonnes de liens tiennent sur une rangée là où l’original en occupe deux, parce que les libellés sont plus courts et la police plus étroite. Rien ne se chevauche. Aux autres largeurs, l’écart total est de 0,1 % à 1440 px, 0,3 % à 991 px et 0,8 % à 479 px.
- Le film n’est pas celui de l’original. L’original ouvre une vidéo Vimeo — une requête externe, et une licence qui n’est pas la nôtre. Le nôtre est un travelling de 12 s fabriqué à partir de la photo du bloc, en local, 226 Ko. Comparé après ré-encodage : le WebM tombait à 113 Ko mais le mp4 se lit partout, y compris sur les anciens Safari.
- La flèche des liens a été corrigée. Dans l’original, la copie de la flèche est posée 100 px plus bas et ne remonte que de 20 px : l’animation ne se referme jamais. Ici les deux flèches font la diagonale complète dans une fenêtre de 18 px.
- Les pages internes n’existent pas. L’original est un site multi-pages ; tous ses liens sont ramenés à des ancres. Deux entrées de menu sans section équivalente ont été renommées — « Gallery » devient « Trips », « Blog » devient « Reviews » — et le groupe « Utility Pages » du pied devient « Explore ».
-
Le formulaire n’a pas de serveur. Il ouvre le client de
messagerie avec l’adresse saisie. Une politique stricte interdit d’ailleurs
form-action: mailto:: sans JavaScript, le formulaire retombe sur l’ancre du pied plutôt que d’envoyer dans le vide. - Les photos sont en AVIF. C’était déjà le format de l’original, et le WebP pesait 30 à 85 % de plus à qualité égale. Tous les navigateurs sortis depuis 2023 le lisent ; un navigateur plus ancien n’affichera pas les images.
- Les photos viennent du template d’origine et ont toutes l’allure d’images générées — leurs noms de fichiers le disent. Le code est à toi ; pour les visuels, vérifie la licence ou remplace-les. La police, elle, est sous ITF Free Font License et redistribuable.
Prends-le, ouvre-le, casse-le.
Duneva est gratuit. La bibliothèque complète — les autres templates et les composants — est incluse dans l’abonnement, avec la formation.
Accès immédiat · Sans engagement · Annulation en 1 clic