Abonnement café

VELANZA

Les meilleurs grains, torréfiés de manière artisanale à Antibes pour garantir une expérience café d'exception.

L'Art de la Rencontre

Par Maison Velanza

Trois piliers qui guident chaque grain que nous torréfions.

D'un engagement commun à une passion partagée

L'histoire de Velanza, c'est d'abord celle de notre amitié. Anciens collègues engagés dans la levée de fonds pour des ONGs, nous avons parcouru le monde ensemble. Au fil de nos voyages, une évidence nous a frappés : partout sur le globe, le café est ce langage universel qui rassemble et crée de la joie.

Le refus du compromis

Pourtant, il est trop souvent relégué à une boisson standardisée et sans âme. Maison Velanza est née de cette frustration et d'une exigence simple : rendre ses lettres de noblesse à ce moment précieux. Nous voulions un café responsable, sourcé avec éthique, et dont la dégustation suscite une véritable émotion.

Notre vision c'est bâtir des lieux de vie

Aujourd'hui, en tant qu'artisans torréfacteurs, notre ambition dépasse le simple grain. Nous voulons faire de chaque tasse un vecteur de lien social et transformer vos espaces en véritables lieux de vie et d'échanges. Une pause prestigieuse, à un prix juste, conçue pour ceux qui refusent l'ordinaire.

La quête du grain parfait

Une recherche passionnée des plus beaux terroirs, où chaque rencontre avec un producteur est une évidence partagée.

L'éveil des arômes

À Antibes, nous torréfions le café pour éveiller les sens et créer des instants à savourer.

Un écrin de prestige

De la main au sachet, nous façonnons nous-mêmes chaque détail pour préserver ce moment si précieux.

Le langage de la joie

Transformer vos espaces en lieux de vie, où chaque tasse devient un vecteur de lien social et d'échange.

Bien plus que du café

Nous travaillons exclusivement avec les 5% meilleurs cafés au monde, que nous torréfions artisanalement. 

Colombie
Plus que 3 en stock

Colombie — Douceur du Cauca

Colombie

Onctueux et sirupeux. Caramel, fève de cacao et pomme rouge.

CaramelFève de cacaoPomme rouge
Intensité
ight('perou', '1kg', this)"> 1kg
Éthiopie

Éthiopie — Aleta Wondo, Sidamo

Éthiopie

Floral. Abricot et thé noir.

FloralAbricotThé noir
Intensité
Expérience Premium

Devenez partenaire Velanza

Oubliez les box génériques. Velanza réinvente l'abonnement avec le premier parcours de dégustation évolutif. Chaque mois recevez nos cafés fraîchement torréfié à Antibes, donnez nous votre avis, et laissez notre Maître Torréfacteur adaptez votre prochaine box à vos goûts. 

Box café Velanza

Découvrez

Recevez chaque mois des cafés de spécialités et des micro-lots exclusifs.

Interagissez 

Flashez le QR code sur le paquet et dites nous ce que vous avez ressenti en quelques clics. 

Explorez

Vos prochaines box s'adaptent à vos retours pour une expérience personnalisée grâce aux recommandations du Maître Torréfacteur. 

Profitez 

Après 6 mois, recevez votre passeport aromatique sur mesure. 

Sans engagement • Résiliable à tout moment

Nos actualités

Découvrir nos cafés et nos étapes

Colombie
Plus que 3 en stock

Colombie — Douceur du Cauca

Colombie

Onctueux et sirupeux. Caramel, fève de cacao et pomme rouge.

CaramelFève de cacaoPomme rouge
Intensité
Pérou

Pérou — Cajamarca

Pérou

Doux. Raisin, zeste de citron et fève de cacao.

RaisinZeste de citronFève Cacao
Intensité
Éthiopie

Éthiopie — Aleta Wondo, Sidamo

Éthiopie

Floral. Abricot et thé noir.

FloralAbricotThé noir
Intensité
Nicaragua

Nicaragua — Jinotega SHG EP

Nicaragua

Onctueux. Cacao noir, canne à sucre caramélisé, notes végétales.

Cacao noirCanne à sucreNotes végétales
Intensité
Costa Rica

Costa Rica — Tarrazú Dulce

Costa Rica

Citronné et velouté. Chocolat au lait et mandarine.

CitronnéChocolat au laitMandarine
Intensité
Brésil

Brésil — Lua Roxa, Cerrado

Brésil

Doux. Chocolat lacté, noisette toastée et raisin mûr.

Chocolat lactéNoisette toastéeRaisin mûr
Intensité
Chemex 6 Tasses

Chemex

Chemex 6 Tasses

L'iconique cafetière filtre en verre borosilicate.

Hario V60 Ceramic

Hario

Hario V60 Ceramic

Dripper céramique pour un café filtre parfait.

Moulin Comandante C40

Comandante

Moulin Comandante C40

Moulin manuel haut de gamme, meules en acier.

Balance Acaia Pearl

Acaia

Balance Acaia Pearl

Balance de précision connectée pour baristas.

Bouilloire Fellow Stagg

Fellow

Bouilloire Fellow Stagg

Bouilloire col de cygne, contrôle précis du débit.

AeroPress Original

AeroPress

AeroPress Original

Méthode d'extraction rapide et polyvalente.

Oubliez la routine,
devenez des partenaires

Le premier abonnement de café évolutif et sur-mesure

Chez VELANZA, torréfacteur artisanal à Antibes, nous avons une conviction : le café de spécialité est une aventure gustative qui se partage, il est fait pour ceux qui veulent réapprendre à goûter.

Mais comment savoir si vous préférez les notes florales d'un Éthiopie ou la rondeur chocolatée d'un Brésil si vous ne les avez jamais comparés ?

C'est là que notre modèle est unique. Nous n'envoyons pas la même box à tout le monde. Nous avons créé un véritable "Parcours de Dégustation" qui s'adapte à vous, mois après mois, pour transformer votre palais.

Comment fonctionne votre parcours VELANZA ?

Nous fonctionnons comme votre sommelier personnel, en 3 étapes simples :

📦

L'Exploration

Vous recevez vos premiers cafés fraîchement torréfiés (dans la semaine) à Antibes. Vous les dégustez tranquillement chez vous.

📱

Le Carnet interactif

Sur chaque paquet se trouve un QR Code. Flashez-le après votre tasse. En 3 clics, dites-nous ce que vous en avez pensé.

L'Affinage

Notre Maître Torréfacteur modifie la composition de votre prochaine box selon vos retours pour une box 100% sur-mesure.

(Et pour nos abonnés avancés, une surprise vous attend au 6ème mois...)

Choisissez votre niveau d'immersion

DÉCOUVERTE
35€ / mois

Le point d'entrée idéal pour commencer à éduquer votre palais, sans bouleverser vos habitudes.

L'essentiel
  • Les Quotidiens (2 x 230g) : Deux cafés de saison, doux et équilibrés.
  • Le Grand Cru (1 x 150g) : Un micro-lot exclusif (Score SCA 86+).
Engagement
  • Adaptation mensuelle via vos retours digitaux.
  • Livraison point relais incluse.
  • Engagement 6 mois : Kit Barista offert (V60 + filtres).
CONNAISSEUR
60€ / mois

Format pensé pour les amateurs exigeants ou les familles qui souhaitent supprimer l'industriel.

La sélection
  • La Réserve Maison (4 x 230g) : Rotation de nos plus belles sélections.
  • L'Introuvable (1 x 150g) : Grade d'exception (Score SCA 87/88+).
Privilèges
  • Recettes personnalisées du torréfacteur à chaque box.
  • Livraison à domicile incluse.
  • Bilan de Palais & Passeport Aromatique au 6ème mois.
LE BOARD
100€ / mois

Entrez dans l'intimité de notre maison de torréfaction. Un accès exclusif limité à 30 partenaires.

Collection Privée
  • Le Grand Format (6 x 230g) : L'excellence quotidienne.
  • Collection Privée (2 x 150g) : Nos deux lots les plus prestigieux.
Cercle Partenaires
  • Coaching privé semestriel avec notre Maître Torréfacteur.
  • Accès au Cercle WhatsApp : influencez les futurs sourcings.
  • Invitations prioritaires aux cuppings à l'atelier d'Antibes.

L'Engagement VELANZA

Trois garanties qui font la différence, parce que votre confiance mérite plus qu'un simple abonnement.

Fraîcheur Absolue

Torréfié à Antibes la semaine de votre expédition. En direct de l'atelier à votre tasse, pour une expérience aromatique à son apogée.

Éthique Radicale

Nos cafés de spécialité garantissent une rémunération juste pour les fermiers, déconnectée des prix de la bourse. Un impact réel à chaque tasse.

Liberté Totale

Mettez votre abonnement en pause ou résiliez en un clic. Sans engagement de durée (sauf offre L'Initié 6 mois avec avantages).

Offre entreprises

La machine à café, cœur battant de votre entreprise

Arrêtez les capsules industrielles et les machines en panne. Nous gérons tout : café, machine, entretien.

Le café au bureau ne devrait pas être un casse-tête

Café de mauvaise qualité qui démotive les équipes. Machines qui tombent en panne sans prévenir. Négociations de prix compliquées. Gestion des commandes qui prend du temps.

C'est beaucoup de gestion pour un simple moment de pause.

Notre solution

Café + Machine + Entretien. Nous gérons tout.

Une offre tout-en-un qui s'occupe de tout, pour vous offrir zéro charge mentale et une expérience café d'exception.

Café

Grains de spécialité 100% Arabica. Torréfaction artisanale à Antibes. Livraison régulière, fraîcheur garantie.

Machine

Machines professionnelles haut de gamme. Installation incluse. Adaptées à vos besoins.

Entretien

Maintenance préventive intégrée. Dépannage sur site garanti. SAV réactif 7j/7.

Ce que vous gagnez

29cts/tasse

Bien moins cher que les capsules.

Top 5% des meilleurs cafés au monde

Noté 82+ SCA, garantis par nos Q-Graders.

Zéro charge mentale pour vous

On s'occupe de tout. Vous n'avez qu'à profiter.

SAV réactif

Problème ? On intervient sous 48h. Zéro stress.

Torréfaction locale à Antibes

Fraîcheur garantie chaque semaine.

Zéro capsule = zéro déchet

Grains et filtres 100% recyclables.

Notre engagement

Un café responsable, choisi avec cœur

Rémunération juste des fermiers partenaires — nous payons au-dessus des prix du marché.

Circuit court — du producteur à votre tasse, sans intermédiaires inutiles.

Économie circulaire — nous récupérons le marc de café pour éviter le gaspillage.

Aide aux jeunes entrepreneurs — une partie de notre impact va aux nouvelles initiatives locales.

Notre offre détaillée

Maison Velanza — le cœur battant de votre entreprise.

Café de qualité — grains de spécialité 100% Arabica, provenance traçable.

Machines professionnelles — équipements fiables et performants.

Entretien inclus — maintenance, dépannage, SAV réactif sur site.

Questions fréquentes

C'est cher ?

Dès 29cts la tasse. C'est souvent moins cher que vos capsules actuelles.

Comment ça marche ?

On installe la machine. On livre le café. On entretient tout. Vous buvez. Point.

Engagement ?

Flexible. On s'adapte à vos besoins. Sans engagement rigide.

Contactez Sam & Tim

Invitez-nous pour une dégustation gratuite dans vos locaux. On vient, on installe, on vous fait découvrir.

06 01 02 03 04 [email protected]
ze: 0.95rem;">Envoyer ma demande

Devenir Franchisé

Rejoignez l'aventure Velanza et ouvrez votre propre maison de torréfaction.

Un concept clé en main

Nous vous accompagnons de A à Z : recherche du local, aménagement, formation barista et torréfaction, et approvisionnement exclusif en grains de spécialité.

Boutique Velanza

Questions Fréquentes

Tout ce que vous devez savoir sur nos cafés, livraisons et services.

Tous nos cafés sont torréfiés artisanalement dans notre atelier à Antibes. Nous torréfions en petites quantités pour garantir une fraîcheur optimale. Chaque lot est torréfié à la commande et expédié sous 48h.

Les commandes sont expédiées sous 48h après torréfaction. La livraison standard prend 3 à 5 jours ouvrés en France métropolitaine. La livraison est offerte dès 100€ d'achat, ou incluse dans nos abonnements et offres entreprises.

Pour préserver tous les arômes, conservez votre café dans un endroit frais et sec, à l'abri de la lumière. Nos sachets sont équipés d'une valve de dégazage. Une fois ouvert, consommez idéalement sous 3 semaines.

Chaque café a son caractère. Pour un goût doux et gourmand, optez pour le Brésil (Lua Roxa — chocolat lacté, noisette toastée) ou la Colombie (Douceur du Cauca — caramel, pomme rouge). Pour un café floral et délicat, l'Éthiopie (Aleta Wondo — abricot, thé noir) est idéale. Le Nicaragua (Jinotega — cacao noir) et le Costa Rica (Tarrazú Dulce — citronné, mandarine) offrent un bel équilibre. N'hésitez pas à nous contacter pour une dégustation gratuite.

Notre abonnement vous permet de recevoir votre café préféré à la fréquence de votre choix (hebdomadaire, bi-mensuel ou mensuel). Vous pouvez modifier, suspendre ou annuler votre abonnement à tout moment depuis votre espace client.

Actuellement, nous livrons en France métropolitaine, Belgique, Suisse et Luxembourg. Pour d'autres destinations, contactez-nous pour étudier les possibilités.

Nous travaillons avec des restaurants, hôtels et cafés partenaires. Rendez-vous dans notre section "Professionnels" ou contactez-nous directement pour discuter d'un partenariat.

Nous acceptons les cartes bancaires (Visa, Mastercard, American Express), Apple Pay, Google Pay et PayPal. Tous les paiements sont sécurisés via Stripe.

Maison de Torréfaction

L'excellence du grain,
la vérité du terroir.

Aujourd'hui, notre priorité est de maîtriser la qualité de nos produits. Nous contrôlons entièrement le processus, du café vert au café torréfié.

Torréfaction artisanale
Notre Genèse

Maîtriser chaque étape

Nos cafés sont torréfiés de manière artisanale avec un soin particulier. Dès leur arrivée, les grains de café vert sont rigoureusement contrôlés, et un suivi méticuleux est effectué après la torréfaction pour garantir la constance et la traçabilité de chaque lot.

L'ensemble du processus, de la sélection des grains de café vert jusqu'à la torréfaction, en passant par le contrôle qualité et le conditionnement, est entièrement réalisé par nos soins. C'est ça l'entrepreneuriat VELANZA.

Pour notre gamme, nous avons soigneusement sélectionné des produits et élaboré des recettes pour satisfaire tous les goûts. Nous travaillons exclusivement avec les 5% meilleurs cafés au monde.

Nos Piliers

Valeurs Fondamentales

Transparence Radicale

Du prix payé au producteur jusqu'à la date de torréfaction sur votre sachet, nous ne vous cachons rien.

Impact Direct

En travaillant sans intermédiaires, nous assurons que la valeur retourne directement aux communautés qui cultivent votre café.

Qualité Sans Compromis

Nous ne torréfions que des grains notés 82+ par la SCA, garantissant une complexité aromatique supérieure.

Les Visages de Velanza

Samuel

Samuel

Co-fondateur & Sourcing

Timothé

Timothé

Co-fondateur & Torréfaction

Notre Manifeste

Imparfaits, mais profondément vrais.

Excellence SCA 82+

Nous ne sélectionnons que des cafés de spécialité, classés parmi les 5% meilleurs au monde.

Prix Juste

Une rémunération supérieure pour les producteurs et un prix accessible pour vous.

Ancrage Local

Torréfaction à Antibes et livraison en circuit court pour limiter notre empreinte carbone.

Zéro Gaspillage

Packaging kraft recyclable et revalorisation systématique de notre marc de café.

Votre Panier

Pérou

Pérou

230g • Grain entier

1

11.90€

2014 Cajamarca', notes: 'Doux. Raisin, zeste de citron et f\u00e8ve de cacao.', flavors: ['Raisin', 'Zeste de citron', 'F\u00e8ve Cacao'], intensity: 3, process: 'Lav\u00e9', producer: 'Alpes Amazonicos SAC', altitude: '900 \u2013 2 000 m', variety: 'Bourbon, Caturra, Catimor, Pache', profile: { acidity: 2, body: 4, sweetness: 4 }, prices: { '230g': 11.90, '1kg': 46.90 }, image: 'https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=600&q=80', images: [ 'https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=600&q=80', 'https://images.unsplash.com/photo-1497636577773-f1231844b336?w=600&q=80', 'https://images.unsplash.com/photo-1461023058943-07fcbe16d735?w=600&q=80' ] }, { id: 'ethiopie', name: '\u00c9thiopie', category: 'coffee', flag: 'et', origin: '\u00c9thiopie \u2014 Aleta Wondo, Sidamo', notes: 'Floral. Abricot et th\u00e9 noir.', flavors: ['Floral', 'Abricot', 'Th\u00e9 noir'], intensity: 2, process: 'Lav\u00e9', producer: 'Petits exploitants agricoles (Farming Accelerator Project)', altitude: '1 400 \u2013 2 200 m', variety: 'H\u00e9ritage', profile: { acidity: 5, body: 2, sweetness: 4 }, prices: { '230g': 11.90, '1kg': 46.90 }, image: 'https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?w=600&q=80', images: [ 'https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?w=600&q=80', 'https://images.unsplash.com/photo-1497636577773-f1231844b336?w=600&q=80', 'https://images.unsplash.com/photo-1461023058943-07fcbe16d735?w=600&q=80' ] }, { id: 'nicaragua', name: 'Nicaragua', category: 'coffee', flag: 'ni', origin: 'Nicaragua \u2014 Jinotega SHG EP', notes: 'Onctueux. Cacao noir, canne \u00e0 sucre caram\u00e9lis\u00e9, notes v\u00e9g\u00e9tales.', flavors: ['Cacao noir', 'Canne \u00e0 sucre', 'Notes v\u00e9g\u00e9tales'], intensity: 3, process: 'Lav\u00e9', producer: 'Divers agriculteurs', altitude: '1 000 \u2013 1 700 m', variety: 'Caturra, Bourbon, Maracaturra, Maragogype, Pacamara', profile: { acidity: 2, body: 4, sweetness: 4 }, prices: { '230g': 10.90, '1kg': 42.90 }, image: 'https://images.unsplash.com/photo-1559056199-641a0ac8b55e?w=600&q=80', images: [ 'https://images.unsplash.com/photo-1559056199-641a0ac8b55e?w=600&q=80', 'https://images.unsplash.com/photo-1512568400610-62da28bc8a13?w=600&q=80', 'https://images.unsplash.com/photo-1507133750040-4a8f57021571?w=600&q=80' ] }, { id: 'costa-rica', name: 'Costa Rica', category: 'coffee', flag: 'cr', origin: 'Costa Rica \u2014 Tarraz\u00fa Dulce', notes: 'Citronn\u00e9 et velout\u00e9. Chocolat au lait et mandarine.', flavors: ['Citronn\u00e9', 'Chocolat au lait', 'Mandarine'], intensity: 3, process: 'Lav\u00e9', producer: 'CoopeTarraz\u00fa', altitude: '1 200 \u2013 1 900 m', variety: 'Catuai, Caturra', profile: { acidity: 4, body: 3, sweetness: 4 }, prices: { '230g': 10.90, '1kg': 42.90 }, image: 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=600&q=80', images: [ 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=600&q=80', 'https://images.unsplash.com/photo-1498804103079-a6351b050096?w=600&q=80', 'https://images.unsplash.com/photo-1442512595331-e89e73853f31?w=600&q=80' ] }, { id: 'bresil', name: 'Br\u00e9sil', category: 'coffee', flag: 'br', origin: 'Br\u00e9sil \u2014 Lua Roxa, Cerrado', notes: 'Doux. Chocolat lact\u00e9, noisette toast\u00e9e et raisin m\u00fbr.', flavors: ['Chocolat lact\u00e9', 'Noisette toast\u00e9e', 'Raisin m\u00fbr'], intensity: 3, process: 'Naturel (non lav\u00e9)', producer: 'Diverses fazendas', altitude: '800 \u2013 1 300 m', variety: 'Mundo Novo, Red Catuai', profile: { acidity: 2, body: 4, sweetness: 4 }, prices: { '230g': 9.90, '1kg': 39.90 }, image: 'https://images.unsplash.com/photo-1498804103079-a6351b050096?w=600&q=80', images: [ 'https://images.unsplash.com/photo-1498804103079-a6351b050096?w=600&q=80', 'https://images.unsplash.com/photo-1504630083234-14187a9df0f5?w=600&q=80', 'https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?w=600&q=80' ] }, // Equipment products (kept as-is) { id: 'chemex-6-tasses', name: 'Chemex 6 Tasses', category: 'equipment', brand: 'Chemex', notes: 'L\'iconique cafeti\u00e8re filtre en verre borosilicate.', price: 45, image: 'https://images.unsplash.com/photo-1517256064527-09c73fc73e38?w=600&q=80', images: [ 'https://images.unsplash.com/photo-1517256064527-09c73fc73e38?w=600&q=80', 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=600&q=80', 'https://images.unsplash.com/photo-1518057111178-44a106bad636?w=600&q=80' ] }, { id: 'hario-v60', name: 'Hario V60 Ceramic', category: 'equipment', brand: 'Hario', notes: 'Dripper c\u00e9ramique pour un caf\u00e9 filtre parfait.', price: 28, image: 'https://images.unsplash.com/photo-1544787219-7f47ccb76574?w=600&q=80', images: [ 'https://images.unsplash.com/photo-1544787219-7f47ccb76574?w=600&q=80', 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=600&q=80', 'https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=600&q=80' ] }, { id: 'moulin-comandante', name: 'Moulin Comandante C40', category: 'equipment', brand: 'Comandante', notes: 'Moulin manuel haut de gamme, meules en acier.', price: 249, image: 'https://images.unsplash.com/photo-1587734195503-904fca47e0e9?w=600&q=80', images: [ 'https://images.unsplash.com/photo-1587734195503-904fca47e0e9?w=600&q=80', 'https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?w=600&q=80', 'https://images.unsplash.com/photo-1497636577773-f1231844b336?w=600&q=80' ] }, { id: 'balance-acaia', name: 'Balance Acaia Pearl', category: 'equipment', brand: 'Acaia', notes: 'Balance de pr\u00e9cision connect\u00e9e pour baristas.', price: 189, image: 'https://images.unsplash.com/photo-1611854779393-1b2da9d400fe?w=600&q=80', images: [ 'https://images.unsplash.com/photo-1611854779393-1b2da9d400fe?w=600&q=80', 'https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=600&q=80', 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=600&q=80' ] }, { id: 'bouilloire-fellow', name: 'Bouilloire Fellow Stagg', category: 'equipment', brand: 'Fellow', notes: 'Bouilloire col de cygne, contr\u00f4le pr\u00e9cis du d\u00e9bit.', price: 159, image: 'https://images.unsplash.com/photo-1570968915860-54d5c301fa9f?w=600&q=80', images: [ 'https://images.unsplash.com/photo-1570968915860-54d5c301fa9f?w=600&q=80', 'https://images.unsplash.com/photo-1517256064527-09c73fc73e38?w=600&q=80', 'https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?w=600&q=80' ] }, { id: 'aeropress', name: 'AeroPress Original', category: 'equipment', brand: 'AeroPress', notes: 'M\u00e9thode d\'extraction rapide et polyvalente.', price: 35, image: 'https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?w=600&q=80', images: [ 'https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?w=600&q=80', 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=600&q=80', 'https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=600&q=80' ] } ]; const grindOptions = [ { id: 'grain', name: 'Café en grain', icon: 'coffee-bean' }, { id: 'espresso', name: 'Mouture espresso', icon: 'coffee' }, { id: 'filtre', name: 'Mouture filtre', icon: 'droplet' } ]; // Slug-ify a product name for use as an HTML-safe ID function slugify(name) { return name.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); } // Transform a DB product row into the format renderers expect function dbToProduct(row) { const r = row.pricing_rules || {}; const photos = row.photo_urls || []; const base = { id: slugify(row.name), dbId: row.id, name: row.name, category: row.category || 'coffee', notes: row.description || '', image: photos[0] || '', images: photos, stock_level: row.stock_level, }; if (row.category === 'equipment') { return { ...base, brand: r.brand || '', price: row.price }; } return { ...base, flag: row.subcategory || '', origin: r.origin || row.name, flavors: row.tags || [], intensity: r.intensity || 3, process: r.process || '', producer: r.producer || '', altitude: r.altitude || '', variety: r.variety || '', profile: r.profile || { acidity: 3, body: 3, sweetness: 3 }, prices: r.prices || { '230g': row.price, '1kg': row.price * 4 }, }; } async function loadProducts() { try { const resp = await fetch(BUSINESS_API); if (!resp.ok) return; const data = await resp.json(); if (data.products && data.products.length > 0) { products = data.products .filter(p => p.category === 'coffee' || p.category === 'equipment') .map(dbToProduct); } } catch (e) { console.warn('Failed to load products from DB, using fallback', e); } } // Custom coffee bean SVG icon const coffeeBeanSVG = ` `; // Cart State let cart = []; let cardQuantities = {}; let currentPage = 'home'; // Initialize selections with 1kg as default for all coffee products const selections = {}; // ===================================================== // SCROLL & REVEAL ANIMATIONS // ===================================================== function initScrollReveal() { const reveals = document.querySelectorAll('.reveal, .stagger-children'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); } }); }, { threshold: 0.1 }); reveals.forEach(el => observer.observe(el)); // Individual product card reveal with stagger initCardReveal(); } function initCardReveal() { const cards = document.querySelectorAll('.product-card'); const cardObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting && !entry.target.classList.contains('revealed')) { // Get the card's index within its parent grid const card = entry.target; const parent = card.parentElement; const siblings = Array.from(parent.querySelectorAll('.product-card')); const index = siblings.indexOf(card); // Calculate delay based on position (250ms between each card) const delay = index * 250; setTimeout(() => { card.classList.add('revealed'); }, delay); } }); }, { threshold: 0.15, rootMargin: '0px 0px -50px 0px' }); cards.forEach(card => cardObserver.observe(card)); } // Nav scroll effect & Parallax let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { requestAnimationFrame(() => { const scrollY = window.scrollY; // Nav effect const nav = document.querySelector('nav'); if (scrollY > 50) { nav.classList.add('scrolled'); } else { nav.classList.remove('scrolled'); } // Parallax effect on hero background and content const hero = document.querySelector('.hero'); if (hero) { const heroHeight = hero.offsetHeight; if (scrollY < heroHeight) { // Background moves slower (parallax depth effect) const bgParallaxSpeed = 0.5; const bgOffset = scrollY * bgParallaxSpeed; hero.style.setProperty('--parallax-y', `${bgOffset}px`); // Content moves slightly slower than scroll for subtle effect const contentParallaxSpeed = 0.15; const contentOffset = scrollY * contentParallaxSpeed; hero.style.setProperty('--content-parallax-y', `${contentOffset}px`); } } ticking = false; }); ticking = true; } }); // ===================================================== // NAVIGATION SYSTEM // ===================================================== function navigateTo(pageId) { // Hide promo on B2B page const promo = document.getElementById('stickyPromo'); if (promo) promo.style.display = (pageId === 'b2b') ? 'none' : 'block'; document.querySelectorAll('.page').forEach(page => { page.classList.remove('active'); }); const targetPage = document.getElementById(`page-${pageId}`); if (targetPage) { targetPage.classList.add('active'); targetPage.classList.add('page-transition'); setTimeout(() => targetPage.classList.remove('page-transition'), 500); } document.querySelectorAll('.nav-links a').forEach(link => { link.classList.remove('active'); if (link.dataset.page === pageId) { link.classList.add('active'); } }); window.location.hash = pageId === 'home' ? '' : pageId; currentPage = pageId; window.scrollTo({ top: 0, behavior: 'smooth' }); // Re-init scroll reveal for new page setTimeout(() => { initScrollReveal(); // Reset and re-init card reveals for the new page document.querySelectorAll('.product-card').forEach(card => { card.classList.remove('revealed'); }); setTimeout(initCardReveal, 50); }, 100); } window.addEventListener('hashchange', () => { const hash = window.location.hash.slice(1) || 'home'; navigateTo(hash); }); // ===================================================== // IMAGE LOADING // ===================================================== function handleImageLoad(img) { img.classList.add('loaded'); } function switchProductImage(productId, imageIndex) { const product = products.find(p => p.id === productId); if (!product || !product.images) return; const card = document.querySelector(`[data-product-id="${productId}"]`); if (!card) return; const img = card.querySelector('.product-card-img'); const dots = card.querySelectorAll('.product-image-dot'); // Fade transition img.style.opacity = '0'; setTimeout(() => { img.src = product.images[imageIndex]; img.style.opacity = '1'; }, 200); // Update dots dots.forEach((dot, i) => { dot.classList.toggle('active', i === imageIndex); }); } // ===================================================== // PRODUCTS // ===================================================== function renderProductCard(product) { if (product.category === 'equipment') { return renderEquipmentCard(product); } const productImages = product.images || [product.image]; const dotsHTML = productImages.length > 1 ? `
${productImages.map((_, i) => `
`).join('')}
` : ''; const intensityDots = Array(5).fill(0).map((_, i) => `` ).join(''); // Premium Best Seller badge for Brazil product const isBestSeller = product.id === 'bresil'; const bestSellerBadge = isBestSeller ? `
Best Seller
` : ''; const grindIcons = { 'grain': '', 'espresso': '', 'filtre': '' }; // Get current selection and price const currentSelection = selections[product.id] || { weight: '1kg', grind: 'grain' }; const currentPrice = product.prices[currentSelection.weight] || product.prices['1kg']; const weights = Object.keys(product.prices); return `
${bestSellerBadge} ${product.name} ${dotsHTML}

${product.origin}

${product.name}${product.flag ? ` ` : ''}

${product.notes}

${product.flavors.map(f => `${f}`).join('')}
Intensité
${intensityDots}
${weights.map((weight, i) => ` `).join('')}
${grindOptions.map((grind, i) => ` `).join('')}
`; } function renderEquipmentCard(product) { return `
${product.name}

${product.brand}

${product.name}${product.flag ? ` ` : ''}

${product.notes}

`; } let currentFilter = 'all'; function filterProducts(filter) { currentFilter = filter; // Update active button document.querySelectorAll('.filter-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.filter === filter); }); // Filter and render products const grid = document.getElementById('productsGrid'); if (grid) { let filteredProducts = filter === 'all' ? products : products.filter(p => p.category === filter); // Move Brazil product to first position const brazilIndex = filteredProducts.findIndex(p => p.id === 'bresil'); if (brazilIndex > -1) { const [brazilProduct] = filteredProducts.splice(brazilIndex, 1); filteredProducts.unshift(brazilProduct); } // Reset selections to 1kg before rendering filteredProducts.forEach(p => { if (p.category === 'coffee' && selections[p.id]) { selections[p.id].weight = '1kg'; } }); grid.innerHTML = filteredProducts.map(product => renderProductCard(product)).join(''); // Re-initialize Lucide icons if (typeof lucide !== 'undefined') { } // Re-trigger reveal animations initCardReveal(); } } function renderProducts() { filterProducts(currentFilter); } function renderFeaturedProducts() { const grid = document.getElementById('featuredProducts'); if (grid) { const coffeeProducts = products.filter(p => p.category === 'coffee'); // Move Brazil product to first position const brazilIndex = coffeeProducts.findIndex(p => p.id === 'bresil'); let displayProducts = [...coffeeProducts]; if (brazilIndex > -1) { const [brazilProduct] = displayProducts.splice(brazilIndex, 1); displayProducts.unshift(brazilProduct); } // Reset selections to 1kg before rendering featured products displayProducts.forEach(p => { if (selections[p.id]) { selections[p.id].weight = '1kg'; } }); grid.innerHTML = displayProducts.slice(0, 3).map(product => renderProductCard(product)).join(''); // Re-initialize Lucide icons for the new cards if (typeof lucide !== 'undefined') { } } } let modalQuantity = 1; function selectWeight(productId, weight, btn) { selections[productId].weight = weight; const card = btn.closest('.product-card'); card.querySelectorAll('.variant-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); const product = products.find(p => p.id === productId); const priceEl = card.querySelector(`#price-${productId}`); // Update displayed price immediately const newPrice = product.prices[weight].toFixed(2) + '€'; // Price animation with flash effect priceEl.classList.add('updating'); setTimeout(() => { priceEl.textContent = newPrice; priceEl.classList.remove('updating'); priceEl.classList.add('updated'); setTimeout(() => priceEl.classList.remove('updated'), 600); }, 150); // Update cart if this item exists in cart const currentGrind = selections[productId].grind; const cartItemId = `${productId}-${weight}-${currentGrind}`; const cartItemIndex = cart.findIndex(item => item.id === cartItemId); // Also update variant prices for existing cart items (same product, different weight) cart.forEach(item => { if (item.productId === productId && item.grind === grindOptions.find(g => g.id === currentGrind).name) { const newCartPrice = product.prices[weight] || product.price; item.price = newCartPrice; } }); saveCart(); updateCartUI(); } function selectGrind(productId, grind, btn) { selections[productId].grind = grind; const card = btn.closest('.product-card'); card.querySelectorAll('.grind-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); } // ===================================================== // CART // ===================================================== function adjustCardQty(productId, delta) { if (!cardQuantities[productId]) cardQuantities[productId] = 1; cardQuantities[productId] += delta; if (cardQuantities[productId] < 1) cardQuantities[productId] = 1; const el = document.getElementById('cardQty-' + productId); if (el) el.textContent = cardQuantities[productId]; } function addToCart(productId, btn) { const qty = cardQuantities[productId] || 1; cardQuantities[productId] = 1; // Reset after add const product = products.find(p => p.id === productId); const selection = selections[productId]; // Ensure we have the latest price based on current selection const currentPrice = product.prices[selection.weight] || product.price; const cartItem = { id: `${productId}-${selection.weight}-${selection.grind}`, productId, name: product.name, weight: selection.weight, grind: grindOptions.find&&grindOptions.find(g => g.id === selection.grind)?grindOptions.find(g => g.id === selection.grind).name:selection.grind, price: currentPrice, image: product.image, quantity: qty }; const existingIndex = cart.findIndex(item => item.id === cartItem.id); if (existingIndex > -1) { cart[existingIndex].quantity++; } else { cart.push(cartItem); } // Button feedback btn.classList.add('added'); btn.textContent = 'Ajouté ✓'; setTimeout(() => { btn.classList.remove('added'); btn.textContent = 'Ajouter'; }, 1500); // Cart count bump animation const cartCount = document.getElementById('cartCount'); cartCount.classList.add('bump'); setTimeout(() => cartCount.classList.remove('bump'), 400); saveCart(); updateCartUI(); showToast('Ajouté au panier'); } function addSubscriptionToCart(planId, price, btn) { const subscriptionNames = { 'découverte': 'Abonnement DÉCOUVERTE', 'connaisseur': 'Abonnement CONNAISSEUR', 'board': 'Abonnement LE BOARD' }; const subscriptionImages = { 'découverte': 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=800&q=80', 'connaisseur': 'https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?w=800&q=80', 'board': 'https://images.unsplash.com/photo-1447933601403-0c6688de566e?w=800&q=80' }; const cartItem = { id: `subscription-${planId}`, productId: `subscription-${planId}`, name: subscriptionNames[planId], weight: '', grind: '', price: price, image: subscriptionImages[planId], quantity: 1, isSubscription: true }; // Remove any other subscription from cart (only one subscription allowed) cart = cart.filter(item => !item.isSubscription); cart.push(cartItem); // Button feedback btn.classList.add('added'); btn.textContent = 'Ajouté ✓'; setTimeout(() => { btn.classList.remove('added'); btn.textContent = 'Ajouter au panier'; }, 1500); // Cart count bump animation const cartCount = document.getElementById('cartCount'); cartCount.classList.add('bump'); setTimeout(() => cartCount.classList.remove('bump'), 400); saveCart(); updateCartUI(); showToast('Abonnement ajouté au panier'); } function addEquipmentToCart(productId, btn) { const product = products.find(p => p.id === productId); const cartItem = { id: productId, productId, name: product.name, weight: '', grind: '', price: product.price, image: product.image, quantity: 1, isEquipment: true }; const existingIndex = cart.findIndex(item => item.id === cartItem.id); if (existingIndex > -1) { cart[existingIndex].quantity++; } else { cart.push(cartItem); } // Button feedback btn.classList.add('added'); btn.textContent = 'Ajouté ✓'; setTimeout(() => { btn.classList.remove('added'); btn.textContent = 'Ajouter'; }, 1500); // Cart count bump animation const cartCount = document.getElementById('cartCount'); cartCount.classList.add('bump'); setTimeout(() => cartCount.classList.remove('bump'), 400); saveCart(); updateCartUI(); showToast('Ajouté au panier'); } function removeFromCart(itemId) { cart = cart.filter(item => item.id !== itemId); saveCart(); updateCartUI(); } function updateCartQuantity(itemId, delta) { const item = cart.find(i => i.id === itemId); if (item) { item.quantity += delta; if (item.quantity <= 0) { removeFromCart(itemId); } else { saveCart(); updateCartUI(); } } } function updateCartUI() { const cartItems = document.getElementById('cartItems'); const cartCount = document.getElementById('cartCount'); const cartTotal = document.getElementById('cartTotal'); const checkoutBtn = document.getElementById('checkoutBtn'); const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0); const totalPrice = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); cartCount.textContent = totalItems; cartTotal.textContent = totalPrice.toFixed(2) + '€'; checkoutBtn.disabled = cart.length === 0; if (cart.length === 0) { cartItems.innerHTML = '
Votre panier est vide
'; } else { cartItems.innerHTML = cart.map(item => { if (item.isSubscription) { return `
${item.name}

${item.name}

Abonnement mensuel

${item.quantity}

${(item.price * item.quantity).toFixed(2)}€/mois

`; } return `
${item.name}

${item.name}

${item.weight} • ${item.grind}

${item.quantity}

${(item.price * item.quantity).toFixed(2)}€

`; }).join(''); } } function updateCheckoutRecommendations() { const recoContainer = document.getElementById('checkoutRecommendations'); const recoItems = document.getElementById('checkoutRecoItems'); if (cart.length === 0) { recoContainer.style.display = 'none'; return; } // Analyze cart contents const cartProductIds = cart.map(item => item.productId || item.id.split('-').slice(0, -2).join('-')); const hasCoffee = cart.some(item => item.category === 'coffee'); const hasEquipment = cart.some(item => item.category === 'equipment'); // Get recommendations based on cart content let recommendations = []; if (hasCoffee && !hasEquipment) { // Recommend equipment to coffee buyers recommendations = products .filter(p => p.category === 'equipment') .slice(0, 3); } else if (hasEquipment && !hasCoffee) { // Recommend coffee to equipment buyers recommendations = products .filter(p => p.category === 'coffee') .slice(0, 3); } else { // Mix of both - recommend complementary items not in cart const coffeeNotInCart = products .filter(p => p.category === 'coffee' && !cartProductIds.includes(p.id)) .slice(0, 2); const equipmentNotInCart = products .filter(p => p.category === 'equipment' && !cartProductIds.includes(p.id)) .slice(0, 2); recommendations = [...coffeeNotInCart, ...equipmentNotInCart].slice(0, 3); } // Filter out items already in cart recommendations = recommendations.filter(p => !cartProductIds.includes(p.id)); if (recommendations.length === 0) { recoContainer.style.display = 'none'; return; } recoContainer.style.display = 'block'; recoItems.innerHTML = recommendations.map(product => { const price = product.prices ? Object.values(product.prices)[0] : product.price; return `
${product.name}
${product.name}
À partir de ${price}€
`; }).join(''); } function quickAddToCart(productId) { const product = products.find(p => p.id === productId); if (!product) return; const defaultWeight = product.prices ? Object.keys(product.prices)[0] : null; const price = product.prices ? product.prices[defaultWeight] : product.price; const cartItem = { id: `${product.id}-${defaultWeight || 'unit'}-grain-${Date.now()}`, productId: product.id, name: product.name, category: product.category, weight: defaultWeight || 'Unité', grind: product.category === 'coffee' ? 'grain' : '-', price: price, quantity: 1, image: product.image }; cart.push(cartItem); saveCart(); updateCartUI(); showToast(`${product.name} ajouté au panier`); // Update checkout if it's open if (document.getElementById('checkoutModal').classList.contains('active')) { const total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); document.getElementById('paymentAmount').textContent = total.toFixed(2) + '€'; updateCheckoutRecommendations(); } } function toggleCart() { document.getElementById('cartOverlay').classList.toggle('active'); document.getElementById('cartSidebar').classList.toggle('active'); const isActive = document.getElementById('cartSidebar').classList.contains('active'); document.body.style.overflow = isActive ? 'hidden' : ''; } function saveCart() { localStorage.setItem('velanza_cart', JSON.stringify(cart)); } function loadCart() { const saved = localStorage.getItem('velanza_cart'); if (saved) { cart = JSON.parse(saved); updateCartUI(); } } // ===================================================== // SUPABASE AUTH + STRIPE CHECKOUT // ===================================================== const SUPABASE_URL = 'https://db.quirel.com'; const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5MDczNDg4MTV9.dCh6HENwzgvP9mScXcq9w2Ple9cLZ4eJLlwZbrIBL_8'; const CHECKOUT_API = 'https://agent-api.quirel.com/v1/site-checkout/session'; const BUSINESS_USER_ID = '682c3412-ee90-4edb-aae2-b44bbaf73845'; const BUSINESS_API = 'https://agent-api.quirel.com/v1/site-business/' + BUSINESS_USER_ID + '/business'; const sbClient = (typeof window.supabase !== 'undefined' && window.supabase.createClient) ? window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY) : null; var currentUser = null; var pendingAuthAction = null; async function initAuth() { if (!sbClient) return; const { data: { session } } = await sbClient.auth.getSession(); if (session) { currentUser = session.user; updateNavForUser(); } sbClient.auth.onAuthStateChange((_event, session) => { currentUser = session ? session.user : null; updateNavForUser(); if (session && localStorage.getItem('velanza_pending_checkout')) { localStorage.removeItem('velanza_pending_checkout'); closeAccountModal(); setTimeout(() => { openCheckout(); }, 300); } }); // Resume pending checkout after OAuth redirect if (session && localStorage.getItem('velanza_pending_checkout')) { localStorage.removeItem('velanza_pending_checkout'); setTimeout(() => { openCheckout(); }, 500); } // Handle payment return const params = new URLSearchParams(window.location.search); if (params.get('payment') === 'success') { showToast('Paiement réussi ! Merci pour votre commande.'); cart = []; saveCart(); updateCartUI(); window.history.replaceState({}, '', window.location.pathname); } else if (params.get('payment') === 'cancelled') { showToast('Paiement annulé.', true); window.history.replaceState({}, '', window.location.pathname + '#home'); navigateTo('home'); window.scrollTo(0, 0); } } function updateNavForUser() { const btn = document.querySelector('.nav-account'); if (!btn) return; if (currentUser) { btn.innerHTML = `${currentUser.email || 'Mon compte'}`; btn.onclick = () => showNoOrdersModal(); } else { btn.innerHTML = `Mon compte`; btn.onclick = openAccountModal; } } async function openCustomerPortal() { if (!currentUser) return; showToast('Chargement...'); try { const res = await fetch('https://agent-api.quirel.com/v1/site-checkout/portal', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ customer_email: currentUser.email, return_url: window.location.href }) }); const data = await res.json(); if (data.url) { window.location.href = data.url; } else if (res.status === 404) { showNoOrdersModal(); } else { showToast(data.detail || 'Erreur', true); } } catch (err) { showToast('Erreur lors du chargement', true); } } function handleSignOut() { if (sbClient) sbClient.auth.signOut(); currentUser = null; updateNavForUser(); showToast('Déconnexion réussie'); } function showNoOrdersModal() { const overlay = document.createElement('div'); overlay.className = 'modal-overlay active'; overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); }; overlay.innerHTML = ` `; document.body.appendChild(overlay); } function showAuthStep(step) { document.querySelectorAll('#accountModal .auth-form').forEach(f => f.classList.remove('active')); document.getElementById('authStep' + step.charAt(0).toUpperCase() + step.slice(1)).classList.add('active'); } async function signInWithGoogle() { if (!sbClient) { showToast('Authentification non disponible', true); return; } const { error } = await sbClient.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: window.location.origin + window.location.pathname } }); if (error) showToast('Erreur Google : ' + error.message, true); } async function handleEmailSubmit(event) { event.preventDefault(); const email = document.getElementById('authEmail').value; const btn = document.getElementById('otpSendBtn'); btn.disabled = true; btn.textContent = 'Envoi...'; if (!sbClient) { showToast('Authentification non disponible', true); return; } const { error } = await sbClient.auth.signInWithOtp({ email }); btn.disabled = false; btn.textContent = 'Recevoir un code'; if (error) { showToast('Erreur : ' + error.message, true); return; } document.getElementById('otpEmailDisplay').textContent = email; showAuthStep('otp'); } async function handleOtpSubmit(event) { event.preventDefault(); const email = document.getElementById('authEmail').value; const token = document.getElementById('otpCode').value; const btn = document.getElementById('otpVerifyBtn'); btn.disabled = true; btn.textContent = 'Vérification...'; const { data, error } = await sbClient.auth.verifyOtp({ email, token, type: 'email' }); btn.disabled = false; btn.textContent = 'Vérifier'; if (error) { showToast('Code invalide : ' + error.message, true); return; } currentUser = data.user; updateNavForUser(); showAuthStep('success'); setTimeout(() => { closeAccountModal(); if (pendingAuthAction) { localStorage.removeItem('velanza_pending_checkout'); const action = pendingAuthAction; pendingAuthAction = null; action(); } }, 1500); } function requireAuth(callback) { if (currentUser) { callback(); return; } pendingAuthAction = callback; localStorage.setItem('velanza_pending_checkout', 'true'); openAccountModal(); } async function createCheckoutSession(items, subscriptionPlan) { const body = { success_url: window.location.href.split('?')[0] + '?payment=success', cancel_url: window.location.href.split('?')[0] + '?payment=cancelled', user_id: BUSINESS_USER_ID, }; if (subscriptionPlan) { body.subscription_plan = subscriptionPlan; } else { body.items = items; } if (currentUser) body.customer_email = currentUser.email; const resp = await fetch(CHECKOUT_API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); if (resp.status === 409 && err.error === 'insufficient_stock') { const msgs = err.items.map(i => `${i.name}: ${i.available} disponible(s)`); throw new Error('Stock insuffisant:\n' + msgs.join('\n')); } throw new Error(err.detail || 'Erreur serveur'); } return resp.json(); } // ===================================================== // CHECKOUT // ===================================================== function openCheckout() { if (cart.length === 0) return; requireAuth(() => { document.getElementById('cartOverlay').classList.remove('active'); document.getElementById('cartSidebar').classList.remove('active'); document.getElementById('checkoutModal').classList.add('active'); document.body.style.overflow = 'hidden'; const total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); document.getElementById('paymentAmount').textContent = total.toFixed(2) + '€'; updateCheckoutRecommendations(); }); } function closeCheckout() { document.getElementById('checkoutModal').classList.remove('active'); } async function proceedToStripeCheckout() { const btn = document.getElementById('stripeCheckoutBtn'); btn.disabled = true; btn.textContent = 'Redirection...'; try { const items = cart.map(item => { const product = products.find(p => p.id === item.productId); const grindSuffix = item.grind ? ' — ' + item.grind : ''; return { name: item.name + (item.weight !== 'Unité' ? ' (' + item.weight + ')' : '') + grindSuffix, price: item.price, quantity: item.quantity, product_id: product ? product.dbId : null }; }); const { url } = await createCheckoutSession(items); window.location.href = url; } catch (err) { showToast(err.message || 'Erreur de paiement', true); btn.disabled = false; btn.textContent = 'Payer par carte'; } } // Account Modal Functions function openAccountModal() { document.getElementById('accountModal').classList.add('active'); showAuthStep('choice'); } function closeAccountModal() { document.getElementById('accountModal').classList.remove('active'); setTimeout(() => { showAuthStep('choice'); document.querySelectorAll('#accountModal form').forEach(f => f.reset()); }, 300); } function openContactModal() { document.getElementById('contactModal').classList.add('active'); } function closeContactModal() { document.getElementById('contactModal').classList.remove('active'); } // Close modals when clicking overlay document.getElementById('accountModal').addEventListener('click', function(e) { if (e.target === this) closeAccountModal(); }); // Legacy functions (no longer used, kept for onclick references) function showRegister() { openAccountModal(); } function showLogin() { openAccountModal(); } // Product Modal Functions let currentModalProduct = null; let modalSelection = { weight: '230g', grind: 'grain' }; let currentImageIndex = 0; function openProductModal(productId) { const product = products.find(p => p.id === productId); if (!product) return; currentModalProduct = product; modalSelection = { weight: '230g', grind: 'grain' }; modalQuantity = 1; currentImageIndex = 0; const modal = document.getElementById('productModal'); // Set up image carousel const productImages = product.images || [product.image]; document.getElementById('modalProductImage').src = productImages[0]; // Create thumbnails const thumbsContainer = document.getElementById('modalImageThumbs'); if (productImages.length > 1) { thumbsContainer.innerHTML = productImages.map((img, i) => `` ).join(''); thumbsContainer.style.display = 'flex'; } else { thumbsContainer.style.display = 'none'; } document.getElementById('modalProductOrigin').textContent = product.category === 'equipment' ? product.brand : product.origin; document.getElementById('modalProductName').innerHTML = product.name + (product.flag ? ' ' : ''); // Extended description based on product const descriptions = { 'colombie': 'Au cœur des montagnes volcaniques du Cauca, la coopérative ACEC réunit 500 producteurs engagés, dont 80 femmes, qui cultivent leur café biologique sur de petites parcelles familiales. Grâce à des formations continues, ils améliorent sans cesse la qualité de leur récolte tout en préservant l\'environnement unique de cette région aux microclimats favorables. Ce café lavé révèle une fraîcheur citronnée agréable et une texture douce et équilibrée. Les saveurs gourmandes de caramel, fève de cacao et pomme rouge apportent une belle rondeur au goût. L\'ensemble s\'harmonise pour offrir une tasse claire, agréable et facile à apprécier.', 'perou': 'Située dans les hauteurs des Andes péruviennes, la région de Cajamarca bénéficie d\'un terroir exceptionnel où sols volcaniques et altitude élevée créent des conditions idéales pour la culture du café. La coopérative Alpes Amazonicos SAC regroupe 226 producteurs engagés dans une agriculture biologique et équitable, soucieux de préserver la richesse naturelle de leur territoire. Ce café lavé se distingue par sa douceur veloutée et ses notes de fève de cacao, raisin mûr et zeste de citron, offrant une tasse équilibrée et pleine de caractère.', 'ethiopie': 'Considérée comme le berceau du café, l\'Éthiopie offre un terroir unique dans la région d\'Aleta Wondo, où les petits exploitants agricoles participent au projet Farming Accelerator. Ce programme leur apporte formations et soutien pour des pratiques durables adaptées au changement climatique. Ici, pas d\'explosion de saveurs, seulement l\'élégance subtile et la finale nette caractéristique du café éthiopien. Ce café lavé révèle des notes florales et d\'abricot mûr délicatement équilibrées par une touche de thé noir, pour une dégustation pure et raffinée, reflet d\'un savoir-faire ancestral et d\'un engagement durable.', 'nicaragua': 'Sur les hauts plateaux du Nicaragua, ce café Arabica est issu d\'une sélection rigoureuse des grains cultivés à plus de 1 200 mètres d\'altitude, une garantie de concentration aromatique et de qualité supérieure, connue sous le nom de Strictly High Grown (SHG). Chaque grain a été soigneusement trié à la main pour éliminer toute imperfection, assurant une pureté optimale. Ce café lavé se distingue par une onctuosité délicate, portée par des arômes profonds de cacao noir, de sucre de canne caramélisé et de notes végétales raffinées. Une expérience qui mêle tradition, savoir-faire et richesse du terroir nicaraguayen, pour une finale élégante et persistante.', 'costa-rica': 'Né sur les hauteurs volcaniques de Tarrazú, ce café bénéficie d\'un climat tempéré et d\'une maturation lente qui mettent en valeur la finesse de son terroir. Depuis 1960, la coopérative CoopeTarrazú réunit des milliers de producteurs engagés dans une culture responsable et exigeante, guidée par la qualité et le respect de l\'environnement. Le process lavé révèle une acidité citronnée propre au Costa Rica, accompagnée d\'une texture veloutée et d\'une douceur évoquant le chocolat au lait. Une touche de mandarine apporte fraîcheur et équilibre, donnant à ce café un profil clair, harmonieux et très agréable en bouche.', 'bresil': 'Dans les paysages ouverts du Cerrado, ce Lua Roxa naît d\'un assemblage de fazendas qui partagent la même vision : offrir un café doux, raffiné, avec une richesse authentique. Issu d\'un processus naturel et cultivé entre 800 et 1 300 mètres d\'altitude, chaque grain est sélectionné avec soin pour garantir une qualité exceptionnelle. En bouche, ce café enveloppe le palais d\'une texture souple et délicate, portée par des notes chaleureuses de chocolat lacté et de noisette toastée. Une subtile présence de raisin mûr vient en équilibre, apportant une complexité discrète qui invite à savourer chaque gorgée avec attention et plaisir.', 'chemex-6-tasses': 'La Chemex est une icône du design et de la préparation café. Son verre borosilicate et ses filtres épais produisent un café d\'une clarté exceptionnelle. Capacité : 6 tasses. Livrée avec filtres.', 'hario-v60': 'Le dripper V60 en céramique assure une excellente rétention de chaleur pour une extraction optimale. Son design à spirale permet un contrôle précis du débit. Le choix des baristas professionnels.', 'moulin-comandante': 'Le moulin manuel de référence. Meules en acier haute précision pour une mouture parfaitement homogène. Construction allemande, garantie à vie. Idéal pour tous types d\'extraction.', 'balance-acaia': 'Balance de précision au dixième de gramme. Connectée via Bluetooth pour suivre vos recettes. Design minimaliste et résistante à l\'eau. L\'outil indispensable pour une extraction reproductible.', 'bouilloire-fellow': 'Bouilloire électrique avec col de cygne pour un contrôle parfait du débit. Maintien de température et affichage digital. Design primé et construction premium.', 'aeropress': 'Méthode d\'extraction rapide et polyvalente. Produit un café concentré et savoureux en moins de 2 minutes. Idéale en voyage. Inclut 350 filtres et accessoires.' }; document.getElementById('modalProductDescription').textContent = descriptions[productId] || product.notes; // Build details section let detailsHTML = ''; if (product.category === 'coffee') { // Build intensity dots const intensityDots = Array(5).fill(0).map((_, i) => `` ).join(''); // Build flavor profile bars const profileLabels = { acidity: 'Acidité', body: 'Corps', sweetness: 'Douceur' }; const profileBars = Object.entries(product.profile).map(([key, value]) => `
${profileLabels[key]}
${value}/5
`).join(''); detailsHTML = `
Intensité
${intensityDots}
Profil
${profileBars}
${product.process || product.producer || product.altitude || product.variety ? `
${product.process ? `
Processus
${product.process}
` : ''} ${product.producer ? `
Producteur
${product.producer}
` : ''} ${product.altitude ? `
Altitude
${product.altitude}
` : ''} ${product.variety ? `
Variété
${product.variety}
` : ''}
` : ''}

Choisir le poids :

Choisir la mouture :

`; } document.getElementById('modalProductDetails').innerHTML = detailsHTML; // Build footer const price = product.category === 'equipment' ? product.price : product.prices['230g']; document.getElementById('modalProductFooter').innerHTML = ` ${price.toFixed(2)}€
`; modal.classList.add('active'); modal.scrollTop = 0; const innerModal = modal.querySelector('.modal'); if (innerModal) innerModal.scrollTop = 0; document.body.style.overflow = 'hidden'; } function closeProductModal() { document.getElementById('productModal').classList.remove('active'); document.body.style.overflow = ''; currentModalProduct = null; } function navigateModalImage(direction) { if (!currentModalProduct) return; const productImages = currentModalProduct.images || [currentModalProduct.image]; currentImageIndex += direction; // Loop around if (currentImageIndex < 0) currentImageIndex = productImages.length - 1; if (currentImageIndex >= productImages.length) currentImageIndex = 0; updateModalImage(); } function goToModalImage(index) { currentImageIndex = index; updateModalImage(); } function updateModalImage() { if (!currentModalProduct) return; const productImages = currentModalProduct.images || [currentModalProduct.image]; const img = document.getElementById('modalProductImage'); // Fade transition img.style.opacity = '0'; setTimeout(() => { img.src = productImages[currentImageIndex]; img.style.opacity = '1'; }, 200); // Update thumbnails document.querySelectorAll('.modal-image-thumb').forEach((thumb, i) => { thumb.classList.toggle('active', i === currentImageIndex); }); } function navigateProduct(direction) { if (!currentModalProduct) return; // Get filtered products based on current filter const filteredProducts = currentFilter === 'all' ? products : products.filter(p => p.category === currentFilter); const currentIndex = filteredProducts.findIndex(p => p.id === currentModalProduct.id); let newIndex = currentIndex + direction; // Loop around if (newIndex < 0) newIndex = filteredProducts.length - 1; if (newIndex >= filteredProducts.length) newIndex = 0; openProductModal(filteredProducts[newIndex].id); } // Keyboard navigation for modal document.addEventListener('keydown', function(e) { if (!document.getElementById('productModal').classList.contains('active')) return; if (e.key === 'ArrowLeft') navigateProduct(-1); if (e.key === 'ArrowRight') navigateProduct(1); if (e.key === 'Escape') closeProductModal(); }); function updateModalQty(delta) { modalQuantity += delta; if (modalQuantity < 1) modalQuantity = 1; document.getElementById('modalQtyDisplay').textContent = modalQuantity; } function selectModalWeight(weight, btn) { modalSelection.weight = weight; btn.parentElement.querySelectorAll('.modal-variant-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); // Update price immediately const priceEl = document.getElementById('modalPrice'); priceEl.classList.add('updating'); setTimeout(() => { priceEl.textContent = (currentModalProduct.prices[weight] * modalQuantity).toFixed(2) + '€'; priceEl.classList.remove('updating'); }, 100); // Sync with main product selector selections[currentModalProduct.id].weight = weight; } function selectModalGrind(grind, btn) { modalSelection.grind = grind; btn.parentElement.querySelectorAll('.modal-grind-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); } function addFromModal() { if (!currentModalProduct) return; if (currentModalProduct.category === 'equipment') { const cartItem = { id: currentModalProduct.id, productId: currentModalProduct.id, name: currentModalProduct.name, weight: '', grind: '', price: currentModalProduct.price, image: currentModalProduct.image, quantity: modalQuantity, isEquipment: true }; const existingIndex = cart.findIndex(item => item.id === cartItem.id); if (existingIndex > -1) { cart[existingIndex].quantity += modalQuantity; } else { cart.push(cartItem); } } else { const cartItem = { id: `${currentModalProduct.id}-${modalSelection.weight}-${modalSelection.grind}`, productId: currentModalProduct.id, name: currentModalProduct.name, weight: modalSelection.weight, grind: grindOptions.find(g => g.id === modalSelection.grind).name, price: currentModalProduct.prices[modalSelection.weight], image: currentModalProduct.image, quantity: modalQuantity }; const existingIndex = cart.findIndex(item => item.id === cartItem.id); if (existingIndex > -1) { cart[existingIndex].quantity += modalQuantity; } else { cart.push(cartItem); } } const cartCount = document.getElementById('cartCount'); cartCount.classList.add('bump'); setTimeout(() => cartCount.classList.remove('bump'), 400); saveCart(); updateCartUI(); closeProductModal(); showToast('Ajouté au panier'); } // Close product modal on overlay click document.getElementById('productModal').addEventListener('click', function(e) { if (e.target === this) closeProductModal(); }); async function subscribeToStripe(planId) { requireAuth(async () => { showToast('Redirection vers le paiement...'); try { const { url } = await createCheckoutSession(null, planId); window.location.href = url; } catch (err) { showToast(err.message || 'Erreur lors de la création', true); } }); } function openB2BModal() { openContactModal(); } // ===================================================== // UTILITIES // ===================================================== // Blog Carousel function scrollBlog(direction) { const carousel = document.getElementById('blogCarousel'); const cardWidth = 350 + 32; // card width + gap carousel.scrollBy({ left: direction * cardWidth, behavior: 'smooth' }); } const blogArticles = [ { title: "Qu'est-ce qu'un café de spécialité ?", tag: "Science", image: "https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=1200&q=80", meta: "7 min de lecture", content: `

Le titre "café de spécialité" n'est pas un argument marketing, mais une certification technique régie par la Specialty Coffee Association (SCA). Pour être qualifié, un café doit obtenir un score minimal de 80/100 sur une grille de cupping standardisée évaluant 10 attributs (corps, équilibre, netteté...).

Physiologie et Altitude

L'altitude impacte directement la densité du grain (Hard Bean). Au-dessus de 1500m, la pression atmosphérique plus faible et les nuits fraîches ralentissent la maturation des cerises. Des analyses chromatographiques montrent une augmentation de +40% des acides chlorogéniques et une concentration en précurseurs de saveurs (sucres et acides aminés) plus élevée, augmentant la complexité aromatique finale.

Lavé vs Naturel : La chimie de fermentation

Le traitement impacte radicalement le pH et le profil gustatif :

` }, { title: "Les secrets d'une extraction parfaite", tag: "Technique", image: "https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?w=1200&q=80", meta: "9 min de lecture", content: `

L'extraction est la physique du transfert de masse du solide vers le liquide. Selon les études du Coffee Center d'UC Davis, la fenêtre d'or se situe entre un ratio d'extraction de 18% et 22% du poids de la mouture sèche.

La fenêtre de solubilité

Pour un équilibre parfait, on vise un TDS (Total Dissolved Solids) de 1,15% à 1,45%. En dessous, le café est sous-extrait (acide et salé) ; au-dessus, il est sur-extrait (amer et sec). La température de l'eau entre 90°C et 96°C est critique : en dessous de 88°C, l'énergie cinétique est insuffisante pour extraire les sucres complexes.

Physique des cafetières

` }, { title: "Thermodynamique : Bien conserver son café", tag: "Conservation", image: "https://images.unsplash.com/photo-1447933601403-0c6688de566e?w=1200&q=80", meta: "6 min de lecture", content: `

Dès la sortie du torréfacteur, le café subit une dégradation physico-chimique. Une étude de l'Institut Norvégien de Recherche démontre que 50% des composés volatiles (arômes) s'échappent en moins de 24h si les grains sont laissés à l'air libre.

Processus Oxydatifs et Lipides

Le café contient environ 15% de lipides. L'exposition à l'oxygène déclenche une oxydation des acides gras insaturés, créant des peroxydes responsables du goût rance. La photolyse UV accélère également la rupture des liaisons moléculaires des composés aromatiques complexes.

Études comparatives Velanza

Nos tests en laboratoire montrent que l'utilisation d'emballages avec valve unidirectionnelle et barrière aluminium permet de maintenir 85% de la stabilité aromatique après 30 jours, contre seulement 40% dans un sachet papier standard kraft non traité. Idéalement, conservez votre café à une température constante de 20°C dans un environnement dont l'hygrométrie ne dépasse pas 50%.

` }, { title: "Éthiopie : Géologie et Microbiologie du Berceau", tag: "Terroir", image: "https://images.unsplash.com/photo-1524350876685-274059332603?w=1200&q=80", meta: "10 min de lecture", content: `

Le terroir d'Aleta Wondo en Éthiopie (1800-2200m d'altitude) repose sur des nitisols volcaniques profonds, extrêmement riches en fer et en minéraux, favorisant le système enzymatique du plant de café.

Biodiversité et Génétique

L'Éthiopie abrite plus de 5 000 variétés Heirloom sauvages. Contrairement aux monocultures mondiales, l'agroforesterie yirgacheffienne engendre une concentration accrue de +25% en sucres résiduels dans la cerise, grâce à l'apport en matière organique naturelle des arbres d'ombrage.

Microbiologie de Fermentation

Lors du process lavé, nous observons une activité prédominante de la levure Saccharomyces cerevisiae qui agit en synergie avec des bactéries lactiques pour décomposer le mucilage tout en synthétisant des esters floraux complexes. Cet engagement technique est soutenu par une prime équitable de +30% au-dessus des cours de l'ECX (Ethiopia Commodity Exchange), assurant la pérennité de ces écosystèmes micro-biologiques uniques.

` }, { title: "Cinétique de Torréfaction : L'art du ROR", tag: "Artisanal", image: "https://images.unsplash.com/photo-1498804103079-a6351b050096?w=1200&q=80", meta: "11 min de lecture", content: `

La torréfaction est une suite de réactions thermochimiques endothermiques et exothermiques. Chez Velanza, nous pilotons chaque batch selon 5 phases précises.

Réaction de Maillard et Strecker

La phase de développement (entre 150°C et 170°C) est cruciale : la réaction de Maillard y forme plus de 60% des précurseurs aromatiques en moins de 180 secondes. Nous maintenons un ROR (Rate of Rise) optimal de 8°C à 12°C par minute pour éviter les chocs thermiques (tipping).

Le Premier Crack et Refroidissement

Le passage exothermique du "Premier Crack" intervient entre 196°C et 205°C selon la densité du grain. À l'arrêt, le phénomène de "carry-over cooking" (la chaleur interne continuant la cuisson) impose un refroidissement immédiat en moins de 3 minutes pour fixer les arômes avant la décomposition thermique des huiles.

` } , { title: "Notre histoire : une passion née du voyage", tag: "Notre Maison", image: "https://images.unsplash.com/photo-1501339847302-ac426a4a7cbb?w=1200&q=80", meta: "6 min de lecture", content: `

Tout a commencé par une amitié et un goût commun pour l’aventure. Tim et Sam se sont rencontrés dans le monde associatif, unis par un même engagement : lever des fonds pour des ONG à travers le globe.

Du terrain à la tasse

De Bogotá à Addis-Abeba, chaque escale révélait le même rituel : deux tasses posées sur une table, et soudain, les barrières tombaient. Le café était bien plus qu’une boisson — c’était un langage universel, un pont entre les cultures.

La naissance de Velanza

De retour en France, l’évidence s’est imposée : pourquoi ne pas partager cette découverte ? En 2022, Maison Velanza voit le jour à Antibes, avec une promesse : proposer les meilleurs grains du monde, torréfiés avec soin, et créer autour de chaque tasse un moment de connexion authentique.

Une communauté grandissante

Aujourd’hui, Velanza fédère une communauté de passionnés qui partagent cette vision : le café comme vecteur de rencontre, de partage et de joie. Chaque grain raconte une histoire, chaque tasse est une invitation au voyage.

` }, { title: "Notre engagement : un café sans compromis", tag: "Notre Maison", image: "https://images.unsplash.com/photo-1611854779393-1b2da9d400fe?w=1200&q=80", meta: "5 min de lecture", content: `

Chez Velanza, chaque décision est guidée par une conviction : le café d’exception ne doit jamais être le fruit du hasard, mais d’une exigence constante à chaque étape.

Sourcing éthique et traçable

Nous travaillons en direct avec des coopératives certifiées dans six pays producteurs. Chaque lot est traçable, de la parcelle à votre tasse. Nos producteurs reçoivent une rémunération juste, bien au-dessus des cours du marché.

Torréfaction artisanale

Notre atelier d’Antibes torréfie en micro-lots pour préserver l’identité de chaque terroir. Pas de torréfaction foncée qui masque les défauts — seulement des profils qui révèlent la richesse naturelle du grain.

Zéro compromis sur la qualité

Seuls les 5% meilleurs grains de la production mondiale atteignent nos standards. Chaque café est noté par des Q-Graders certifiés avant d’intégrer notre gamme. Si un lot ne nous émeut pas, il ne vous sera jamais proposé.

` }, { title: "Velanza, bien plus qu’un torréfacteur", tag: "Notre Maison", image: "https://images.unsplash.com/photo-1521017432531-fbd92d768814?w=1200&q=80", meta: "5 min de lecture", content: `

Notre ambition dépasse le simple grain de café. Velanza est né pour transformer les espaces et créer des lieux où le café devient prétexte à la rencontre.

Des espaces pensés pour le lien

Que ce soit dans nos corners dédiés ou chez nos partenaires, chaque installation Velanza est conçue pour favoriser l’échange. Un comptoir ouvert, une ambiance chaleureuse, et ce parfum de café fraîchement torréfié qui invite naturellement à s’attarder.

Le café au bureau, réinventé

Nous accompagnons les entreprises qui veulent offrir à leurs équipes bien plus qu’une simple machine. Nos solutions B2B intègrent formation, équipement et approvisionnement pour faire de la pause café un vrai moment de cohésion.

Une communauté, pas juste des clients

Ateliers de dégustation, événements exclusifs, visites de notre atelier — nous construisons une communauté de passionnés autour de valeurs communes : l’exigence, le partage et la convivialité.

` } ]; let currentBlogIndex = 0; function openBlogArticle(index) { currentBlogIndex = index; renderBlogArticle(); const blogModalEl = document.getElementById('blogModal'); blogModalEl.classList.add('active'); blogModalEl.scrollTop = 0; const blogInner = blogModalEl.querySelector('.blog-modal'); if (blogInner) blogInner.scrollTop = 0; document.body.style.overflow = 'hidden'; } function renderBlogArticle() { const article = blogArticles[currentBlogIndex]; const container = document.getElementById('blogArticleContent'); container.innerHTML = `
${article.title}
${article.tag}

${article.title}

${article.meta}
${article.content}
`; container.scrollTop = 0; const modal = container.closest('.blog-modal'); if (modal) modal.scrollTop = 0; } function navigateBlog(direction) { currentBlogIndex += direction; if (currentBlogIndex < 0) currentBlogIndex = blogArticles.length - 1; if (currentBlogIndex >= blogArticles.length) currentBlogIndex = 0; renderBlogArticle(); } function closeBlogModal() { document.getElementById('blogModal').classList.remove('active'); document.body.style.overflow = ''; } // Close blog modal on overlay click document.getElementById('blogModal').addEventListener('click', function(e) { if (e.target === this) closeBlogModal(); }); // Contact Form Submission async function sendContactForm(event, formType) { event.preventDefault(); const form = event.target; // Get form values const formData = new FormData(form); const email = formData.get('email'); const name = formData.get('name') || ''; const phone = formData.get('phone') || ''; const subject = formData.get('subject') || (formType === 'newsletter' ? 'Newsletter Subscription' : ''); const message = formData.get('message') || ''; if (!email) { showToast('Veuillez entrer votre email', true); return; } // Prepare request body const requestBody = { creation_id: '3be73b6b-ce89-42f4-b57b-e6b7dda11be9', user_id: '682c3412-ee90-4edb-aae2-b44bbaf73845', sender_email: email, sender_name: name || undefined, subject: subject || undefined, message: message || undefined, phone: phone || undefined }; try { const response = await fetch('https://agent-api.quirel.com/v1/contact-form/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody) }); const result = await response.json(); if (response.ok && result.success) { showToast(formType === 'newsletter' ? 'Merci ! Vous êtes inscrit à notre newsletter.' : 'Message envoyé !'); form.reset(); if (formType === 'contact') { closeContactModal(); } } else { showToast(result.detail || 'Unable to send message. Please try again.', true); } } catch (error) { showToast('Unable to send message. Please try again.', true); console.error('Contact form error:', error); } } // Newsletter (legacy - kept for compatibility) function subscribeNewsletter(event) { event.preventDefault(); const email = event.target.querySelector('input').value; event.target.querySelector('input').value = ''; showToast('Merci ! Vous êtes inscrit à notre newsletter.'); } const legalContent = { cgv: { title: "Conditions Générales de Vente", body: `

Article 1 - Champ d'application

Les présentes Conditions Générales de Vente (CGV) s'appliquent, sans restriction ni réserve, à l'ensemble des ventes conclues par la société VELANZA SAS ("le Vendeur") auprès d'acheteurs non professionnels ("les Clients"), désirant acquérir les produits proposés à la vente sur le site maison-velanza.com.

Article 2 - Produits et Commandes

Les caractéristiques essentielles des produits (cafés de spécialité, équipements) sont présentées sur le site. Le Client est tenu d'en prendre connaissance avant toute passation de commande. La vente ne sera considérée comme définitive qu'après l'envoi au Client de la confirmation de l'acceptation de la commande par le Vendeur par courrier électronique.

Article 3 - Tarifs et Paiement

Les produits sont fournis aux tarifs en vigueur figurant sur le site lors de l'enregistrement de la commande. Les prix sont exprimés en Euros, HT et TTC. Le paiement est exigible immédiatement au jour de la commande, sécurisé par le prestataire Stripe.

Article 4 - Livraison

Les livraisons interviennent dans un délai de 3 à 5 jours ouvrés à l'adresse indiquée par le Client. Le Vendeur s'engage à faire ses meilleurs efforts pour livrer les produits commandés dans les délais ci-dessus précisés.

Article 5 - Droit de rétractation

Conformément à l'article L221-28 du Code de la consommation, le droit de rétractation ne peut être exercé pour les contrats de fourniture de biens susceptibles de se détériorer ou de se périmer rapidement. En conséquence, les produits de torréfaction (café) descellés par le Client après la livraison sont exclus du droit de rétractation pour des raisons d'hygiène et de protection de la santé.

Article 6 - Litiges

Tous les litiges auxquels les opérations de vente conclues en application des présentes CGV pourraient donner lieu seront soumis aux tribunaux compétents dans les conditions de droit commun.

` }, mentions: { title: "Mentions Légales", body: `

1. Éditeur du site

Ce site a été créé avec Quirel (https://quirel.ai), une plateforme de création de sites web par intelligence artificielle.

2. Direction de la publication

Directeur de la publication : Le propriétaire du site.

3. Hébergement

Le site est hébergé par la société Cloudflare Inc., dont le siège social est situé 101 Townsend St, San Francisco, CA 94107, États-Unis.

4. Propriété Intellectuelle

L'ensemble de ce site relève de la législation française et internationale sur le droit d'auteur et la propriété intellectuelle. Tous les droits de reproduction sont réservés, y compris pour les documents téléchargeables et les représentations iconographiques et photographiques.

` }, privacy: { title: "Politique de Confidentialité (RGPD)", body: `

Article 1 - Collecte des données

Dans le cadre de l'exploitation du site, VELANZA collecte des données à caractère personnel (nom, prénom, email, adresse de livraison, données de navigation). Cette collecte est nécessaire à l'exécution du contrat de vente.

Article 2 - Finalités du traitement

Les données sont utilisées pour la gestion des commandes, l'envoi de newsletters (sous réserve de consentement), et l'amélioration de l'expérience utilisateur.

Article 3 - Conservation et Sécurité

Les données sont conservées pendant la durée nécessaire à la gestion de la relation commerciale. VELANZA met en œuvre des mesures de sécurité techniques et organisationnelles pour protéger vos données contre tout accès non autorisé.

Article 4 - Droits des utilisateurs

Conformément au Règlement Général sur la Protection des Données (RGPD), vous disposez d'un droit d'accès, de rectification, d'effacement et d'opposition au traitement de vos données. Vous pouvez exercer ces droits en contactant : [email protected].

` } }; function openLegalModal(type) { const content = legalContent[type]; document.getElementById('legalModalTitle').textContent = content.title; document.getElementById('legalModalBody').innerHTML = content.body; document.getElementById('legalModal').classList.add('active'); } function closeLegalModal() { document.getElementById('legalModal').classList.remove('active'); } function toggleFaq(button) { const item = button.parentElement; item.classList.toggle('active'); } function showToast(message, isError = false) { const toast = document.getElementById('toast'); toast.textContent = message; toast.className = 'toast' + (isError ? ' error' : ''); toast.classList.add('active'); setTimeout(() => toast.classList.remove('active'), 3000); } function updatePriceFromChange(element) { const card = element.closest('.product-card'); const productId = card.getAttribute('data-product-id'); const activeBtn = card.querySelector('.variant-btn.active'); if (activeBtn) { const weight = activeBtn.textContent.trim(); selectWeight(productId, weight, activeBtn); } } function toggleMobileMenu() { const navLinks = document.querySelector('.nav-links'); navLinks.style.display = navLinks.style.display === 'flex' ? 'none' : 'flex'; navLinks.style.flexDirection = 'column'; navLinks.style.position = 'absolute'; navLinks.style.top = '100%'; navLinks.style.left = '0'; navLinks.style.right = '0'; navLinks.style.background = 'var(--bg-primary)'; navLinks.style.padding = '1rem'; navLinks.style.boxShadow = 'var(--shadow-md)'; } document.addEventListener('keydown', e => { if (e.key === 'Escape') { closeCheckout(); if (document.getElementById('cartSidebar').classList.contains('active')) toggleCart(); } }); // ===================================================== // STOCK LEVELS // ===================================================== function updateStockBadges() { for (const product of products) { if (product.stock_level === undefined || product.stock_level === null) continue; const cards = document.querySelectorAll(`[data-product-id="${product.id}"]`); cards.forEach(card => { // Remove existing badge const existing = card.querySelector('.stock-badge'); if (existing) existing.remove(); const badge = document.createElement('div'); badge.className = 'stock-badge'; if (product.stock_level === 0) { badge.textContent = 'Rupture de stock'; badge.style.cssText = 'position:absolute;top:12px;left:12px;background:var(--error);color:#fff;padding:4px 10px;border-radius:4px;font-size:0.75rem;font-weight:600;z-index:2;letter-spacing:0.5px;'; // Disable add-to-cart buttons card.querySelectorAll('.add-to-cart').forEach(btn => { btn.disabled = true; btn.style.opacity = '0.5'; btn.style.cursor = 'not-allowed'; }); } else if (product.stock_level <= 5) { badge.textContent = `Plus que ${product.stock_level} en stock`; badge.style.cssText = 'position:absolute;top:12px;left:12px;background:#D4826A;color:#fff;padding:4px 10px;border-radius:4px;font-size:0.75rem;font-weight:600;z-index:2;letter-spacing:0.5px;'; } const imageDiv = card.querySelector('.product-image'); if (imageDiv && badge.textContent) { imageDiv.style.position = 'relative'; imageDiv.appendChild(badge); } }); } } // ===================================================== // INITIALIZE // ===================================================== document.addEventListener('DOMContentLoaded', async () => { const initialPage = window.location.hash.slice(1) || 'home'; navigateTo(initialPage); await loadProducts(); renderProducts(); renderFeaturedProducts(); updateStockBadges(); loadCart(); initAuth(); initScrollReveal(); // Initialize Lucide icons if (typeof lucide !== 'undefined') { } });