templates/abonn/index.html.twig line 1

Open in your IDE?
  1. {% extends 'base.html.twig' %}
  2. {% block title %}Abonnements — Journal Officiel de l'Union des Comores
  3. {# ================================================================
  4. MODAL CHECKOUT — Standard & Premium
  5. ================================================================ #}
  6. <div id="checkout-modal"
  7. class="fixed inset-0 z-50 hidden items-center justify-center p-4"
  8. role="dialog" aria-modal="true" aria-labelledby="modal-title">
  9. <!-- Overlay -->
  10. <div id="modal-overlay"
  11. class="absolute inset-0 bg-black/60 backdrop-blur-sm"
  12. onclick="closeCheckoutModal()"></div>
  13. <!-- Panel -->
  14. <div class="relative bg-white rounded-2xl shadow-2xl w-full max-w-lg max-h-[90vh] overflow-y-auto z-10 animate-modal">
  15. <!-- Header -->
  16. <div id="modal-header" class="sticky top-0 z-20 bg-white border-b border-gray-100 px-6 py-4 flex items-center justify-between rounded-t-2xl">
  17. <div class="flex items-center gap-3">
  18. <!-- Étapes pills -->
  19. <div class="flex items-center gap-1 text-xs" id="modal-steps">
  20. <span class="modal-step-pill active w-6 h-6 rounded-full bg-[#006633] text-white flex items-center justify-center font-black text-[10px]" data-step="1">1</span>
  21. <div class="w-4 h-px bg-gray-200"></div>
  22. <span class="modal-step-pill w-6 h-6 rounded-full bg-gray-200 text-gray-400 flex items-center justify-center font-black text-[10px]" data-step="2">2</span>
  23. <div class="w-4 h-px bg-gray-200"></div>
  24. <span class="modal-step-pill w-6 h-6 rounded-full bg-gray-200 text-gray-400 flex items-center justify-center font-black text-[10px]" data-step="3">3</span>
  25. </div>
  26. <span class="text-sm font-semibold text-gray-600" id="modal-step-label">Récapitulatif</span>
  27. </div>
  28. <button type="button" onclick="closeCheckoutModal()"
  29. class="w-8 h-8 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-gray-500 transition">
  30. <i class="fas fa-times text-xs"></i>
  31. </button>
  32. </div>
  33. <!-- Corps du modal -->
  34. <div class="px-6 py-6">
  35. {# ── ÉTAPE 1 : Récapitulatif ── #}
  36. <div id="modal-step-1" class="modal-step-content">
  37. <!-- Badge plan sélectionné -->
  38. <div id="modal-plan-badge" class="rounded-xl p-4 mb-5 flex items-center gap-3">
  39. <div id="modal-plan-icon" class="w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0">
  40. <i class="fas fa-building text-white"></i>
  41. </div>
  42. <div>
  43. <p class="text-xs font-bold uppercase tracking-wider" id="modal-plan-sublabel">Abonnement</p>
  44. <p class="font-black text-lg" id="modal-plan-name">Standard</p>
  45. </div>
  46. <div class="ml-auto text-right">
  47. <p class="font-black text-xl" id="modal-plan-price">40 000 KMF</p>
  48. <p class="text-xs opacity-70" id="modal-plan-period">/ an · 1ère année</p>
  49. </div>
  50. </div>
  51. <!-- Durée -->
  52. <div class="mb-5">
  53. <label class="block text-sm font-bold text-gray-700 mb-3">Durée</label>
  54. <div class="grid grid-cols-2 gap-3">
  55. <label class="cursor-pointer">
  56. <input type="radio" name="modal-duree" value="annuel" class="sr-only" checked>
  57. <div class="modal-dur-card border-2 border-[#006633] bg-emerald-50/50 rounded-xl p-3 text-center transition-all">
  58. <p class="text-xs font-bold text-gray-700 mb-1">Annuel <span class="bg-[#006633] text-white text-[9px] px-1.5 py-0.5 rounded-full ml-1">−20%</span></p>
  59. <p class="font-black text-[#006633] text-base modal-dur-price" data-std-annuel="40 000" data-pre-annuel="72 000" data-std-mensuel="4 500" data-pre-mensuel="8 000"></p>
  60. <p class="text-[10px] text-gray-400">KMF / an</p>
  61. </div>
  62. </label>
  63. <label class="cursor-pointer">
  64. <input type="radio" name="modal-duree" value="mensuel" class="sr-only">
  65. <div class="modal-dur-card border-2 border-gray-200 bg-white rounded-xl p-3 text-center transition-all hover:border-gray-300">
  66. <p class="text-xs font-bold text-gray-700 mb-1">Mensuel</p>
  67. <p class="font-black text-gray-800 text-base modal-dur-mensuel-price"></p>
  68. <p class="text-[10px] text-gray-400">KMF / mois</p>
  69. </div>
  70. </label>
  71. </div>
  72. </div>
  73. <!-- Informations -->
  74. <div class="space-y-3 mb-5">
  75. <div class="grid grid-cols-2 gap-3">
  76. <div>
  77. <label class="block text-xs text-gray-500 mb-1">Prénom <span class="text-red-400">*</span></label>
  78. <input type="text" id="m-prenom" placeholder="Mohammed"
  79. class="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#006633]/40 focus:border-[#006633]">
  80. </div>
  81. <div>
  82. <label class="block text-xs text-gray-500 mb-1">Nom <span class="text-red-400">*</span></label>
  83. <input type="text" id="m-nom" placeholder="Said"
  84. class="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#006633]/40 focus:border-[#006633]">
  85. </div>
  86. </div>
  87. <div>
  88. <label class="block text-xs text-gray-500 mb-1">E-mail <span class="text-red-400">*</span></label>
  89. <input type="email" id="m-email" placeholder="votre@email.com"
  90. class="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#006633]/40 focus:border-[#006633]">
  91. </div>
  92. <div>
  93. <label class="block text-xs text-gray-500 mb-1">Organisation <span class="text-gray-400">(optionnel)</span></label>
  94. <input type="text" id="m-org" placeholder="Ministère, cabinet, entreprise..."
  95. class="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#006633]/40 focus:border-[#006633]">
  96. </div>
  97. </div>
  98. <button type="button" onclick="modalGoStep(2)"
  99. class="w-full bg-gradient-to-r from-[#006633] to-[#008040] text-white py-3.5 rounded-xl font-bold text-sm hover:from-[#004d26] transition shadow-md flex items-center justify-center gap-2">
  100. Continuer vers le paiement <i class="fas fa-arrow-right"></i>
  101. </button>
  102. </div>
  103. {# ── ÉTAPE 2 : Paiement ── #}
  104. <div id="modal-step-2" class="modal-step-content hidden">
  105. <button type="button" onclick="modalGoStep(1)"
  106. class="flex items-center gap-2 text-gray-400 hover:text-gray-600 text-xs mb-5 transition">
  107. <i class="fas fa-arrow-left"></i> Retour
  108. </button>
  109. <h3 class="text-base font-black text-gray-800 mb-4">Mode de paiement</h3>
  110. <div class="space-y-3 mb-5">
  111. <!-- Huri Money -->
  112. <label class="cursor-pointer block">
  113. <input type="radio" name="modal-methode" value="huri_money" class="sr-only" checked>
  114. <div class="modal-pay-card border-2 border-orange-400 bg-orange-50/40 rounded-xl p-3.5 flex items-center gap-3 transition-all">
  115. <div class="w-10 h-10 bg-orange-500 rounded-xl flex items-center justify-center flex-shrink-0">
  116. <i class="fas fa-mobile-alt text-white"></i>
  117. </div>
  118. <div class="flex-1">
  119. <p class="font-bold text-gray-800 text-sm">Huri Money</p>
  120. <p class="text-xs text-gray-500">Mobile Money · Comores Telecom</p>
  121. </div>
  122. <div class="w-4 h-4 rounded-full border-2 border-orange-400 flex items-center justify-center modal-radio-dot">
  123. <div class="w-2 h-2 rounded-full bg-orange-400"></div>
  124. </div>
  125. </div>
  126. </label>
  127. <!-- Mvola -->
  128. <label class="cursor-pointer block">
  129. <input type="radio" name="modal-methode" value="mvola" class="sr-only">
  130. <div class="modal-pay-card border-2 border-gray-200 bg-white rounded-xl p-3.5 flex items-center gap-3 transition-all hover:border-gray-300">
  131. <div class="w-10 h-10 bg-green-600 rounded-xl flex items-center justify-center flex-shrink-0">
  132. <i class="fas fa-mobile-alt text-white"></i>
  133. </div>
  134. <div class="flex-1">
  135. <p class="font-bold text-gray-800 text-sm">Mvola</p>
  136. <p class="text-xs text-gray-500">Mobile Money · Telma Comores</p>
  137. </div>
  138. <div class="w-4 h-4 rounded-full border-2 border-gray-300 flex items-center justify-center modal-radio-dot"></div>
  139. </div>
  140. </label>
  141. <!-- Holo -->
  142. <label class="cursor-pointer block">
  143. <input type="radio" name="modal-methode" value="holo" class="sr-only">
  144. <div class="modal-pay-card border-2 border-gray-200 bg-white rounded-xl p-3.5 flex items-center gap-3 transition-all hover:border-gray-300">
  145. <div class="w-10 h-10 bg-blue-600 rounded-xl flex items-center justify-center flex-shrink-0">
  146. <i class="fas fa-mobile-alt text-white"></i>
  147. </div>
  148. <div class="flex-1">
  149. <p class="font-bold text-gray-800 text-sm">Holo</p>
  150. <p class="text-xs text-gray-500">Mobile Money · Holo Comores</p>
  151. </div>
  152. <div class="w-4 h-4 rounded-full border-2 border-gray-300 flex items-center justify-center modal-radio-dot"></div>
  153. </div>
  154. </label>
  155. <!-- Visa désactivé -->
  156. <div class="border-2 border-dashed border-gray-200 rounded-xl p-3.5 flex items-center gap-3 opacity-50 cursor-not-allowed">
  157. <div class="w-10 h-10 bg-indigo-100 rounded-xl flex items-center justify-center flex-shrink-0">
  158. <i class="fas fa-credit-card text-indigo-400"></i>
  159. </div>
  160. <div class="flex-1">
  161. <p class="font-bold text-gray-500 text-sm">Visa / Mastercard</p>
  162. <p class="text-xs text-gray-400">Bientôt disponible</p>
  163. </div>
  164. <span class="text-[10px] bg-gray-100 text-gray-400 px-2 py-0.5 rounded-full font-semibold">À venir</span>
  165. </div>
  166. </div>
  167. <!-- Numéro téléphone -->
  168. <div class="bg-gray-50 border border-gray-200 rounded-xl p-4 mb-5">
  169. <label class="block text-sm font-bold text-gray-700 mb-2">
  170. Numéro <span id="modal-provider-label" class="text-[#006633]">Huri Money</span>
  171. </label>
  172. <div class="flex gap-2">
  173. <div class="flex items-center gap-1.5 bg-white border border-gray-200 rounded-xl px-3 py-2.5 flex-shrink-0">
  174. <img src="https://flagcdn.com/w20/km.png" alt="+269" class="w-4 rounded-sm">
  175. <span class="text-sm text-gray-600 font-semibold">+269</span>
  176. </div>
  177. <input type="tel" id="m-phone" placeholder="321 12 34" maxlength="10"
  178. class="flex-1 border border-gray-200 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#006633]/40 focus:border-[#006633]">
  179. </div>
  180. <p class="text-[10px] text-gray-400 mt-1.5">
  181. <i class="fas fa-info-circle mr-1"></i> Vous recevrez une notification pour confirmer.
  182. </p>
  183. </div>
  184. <!-- Récapitulatif mini -->
  185. <div class="bg-gray-50 border border-gray-100 rounded-xl p-3.5 mb-5 flex items-center justify-between text-sm">
  186. <div>
  187. <p class="font-semibold text-gray-700" id="modal-recap-plan">Standard · Annuel</p>
  188. <p class="text-xs text-gray-400">1ère année — remise −20% appliquée</p>
  189. </div>
  190. <p class="font-black text-[#006633] text-lg" id="modal-recap-prix">40 000 KMF</p>
  191. </div>
  192. <!-- CGU -->
  193. <label class="flex items-start gap-2.5 cursor-pointer mb-5">
  194. <input type="checkbox" id="m-cgu"
  195. class="mt-0.5 w-4 h-4 rounded border-gray-300 text-[#006633] focus:ring-[#006633] flex-shrink-0">
  196. <span class="text-xs text-gray-500 leading-relaxed">
  197. J'accepte les
  198. <a href="#" class="text-[#006633] font-semibold hover:underline">CGU</a>
  199. et la
  200. <a href="#" class="text-[#006633] font-semibold hover:underline">Politique de confidentialité</a>.
  201. </span>
  202. </label>
  203. <button type="button" id="modal-btn-pay"
  204. class="w-full bg-gradient-to-r from-[#006633] to-[#008040] text-white py-4 rounded-xl font-black text-base hover:from-[#004d26] transition shadow-lg flex items-center justify-center gap-2">
  205. <i class="fas fa-lock text-sm"></i>
  206. <span id="modal-btn-pay-label">Payer 40 000 KMF</span>
  207. </button>
  208. <p class="text-center text-[10px] text-gray-400 mt-2">
  209. <i class="fas fa-shield-alt mr-1 text-[#006633]"></i> Paiement sécurisé
  210. </p>
  211. </div>
  212. {# ── ÉTAPE 3 : Confirmation ── #}
  213. <div id="modal-step-3" class="modal-step-content hidden text-center py-4">
  214. <div class="w-16 h-16 bg-emerald-100 rounded-full flex items-center justify-center mx-auto mb-4">
  215. <i class="fas fa-check-circle text-3xl text-[#006633]"></i>
  216. </div>
  217. <h3 class="text-xl font-black text-gray-800 mb-1">Abonnement activé !</h3>
  218. <p class="text-gray-500 text-sm mb-1">
  219. Votre abonnement <strong id="modal-confirm-plan" class="text-gray-800">Standard</strong> est actif.
  220. </p>
  221. <p class="text-xs text-gray-400 mb-5">
  222. Réf : <span id="modal-confirm-ref" class="font-mono font-semibold text-gray-600"></span>
  223. </p>
  224. <div class="bg-gray-50 rounded-xl border border-gray-100 p-4 text-left text-sm mb-5 space-y-2">
  225. <div class="flex justify-between">
  226. <span class="text-gray-500">Plan</span>
  227. <span class="font-semibold text-gray-800" id="modal-conf-plan2">Standard</span>
  228. </div>
  229. <div class="flex justify-between">
  230. <span class="text-gray-500">Durée</span>
  231. <span class="font-semibold text-gray-800" id="modal-conf-duree">Annuel</span>
  232. </div>
  233. <div class="flex justify-between">
  234. <span class="text-gray-500">Montant</span>
  235. <span class="font-bold text-[#006633]" id="modal-conf-montant">40 000 KMF</span>
  236. </div>
  237. <div class="flex justify-between">
  238. <span class="text-gray-500">Renouvellement</span>
  239. <span class="font-semibold text-gray-800" id="modal-conf-renouvellement">—</span>
  240. </div>
  241. </div>
  242. <div class="flex flex-col gap-3">
  243. <a href="{{ path('app_dashboard') }}"
  244. class="block w-full bg-[#006633] text-white py-3 rounded-xl font-bold text-sm hover:bg-[#004d26] transition">
  245. <i class="fas fa-tachometer-alt mr-2"></i> Accéder à mon espace
  246. </a>
  247. <button type="button" onclick="closeCheckoutModal()"
  248. class="block w-full border border-gray-200 text-gray-600 py-3 rounded-xl font-semibold text-sm hover:bg-gray-50 transition">
  249. Fermer
  250. </button>
  251. </div>
  252. </div>
  253. </div>
  254. </div>
  255. </div>
  256. <style>
  257. @keyframes modalIn {
  258. from { opacity: 0; transform: scale(0.96) translateY(10px); }
  259. to { opacity: 1; transform: scale(1) translateY(0); }
  260. }
  261. .animate-modal { animation: modalIn 0.25s ease-out; }
  262. </style>
  263. <script>
  264. (function () {
  265. const PRIX = {
  266. standard: { annuel: 40000, mensuel: 4500, plein: 50000 },
  267. premium: { annuel: 72000, mensuel: 8000, plein: 90000 },
  268. };
  269. const LABELS = { standard: 'Standard', premium: 'Premium' };
  270. const PROVIDERS = { huri_money: 'Huri Money', mvola: 'Mvola', holo: 'Holo' };
  271. let currentPlan = 'standard';
  272. let currentDuree = 'annuel';
  273. // ── Ouvrir / fermer ────────────────────────────────────────────────────────
  274. window.openCheckoutModal = function (plan) {
  275. currentPlan = plan || 'standard';
  276. currentDuree = 'annuel';
  277. modalGoStep(1);
  278. updateModalPlanUI();
  279. updatePrices();
  280. const modal = document.getElementById('checkout-modal');
  281. modal.classList.remove('hidden');
  282. modal.classList.add('flex');
  283. document.body.style.overflow = 'hidden';
  284. };
  285. window.closeCheckoutModal = function () {
  286. const modal = document.getElementById('checkout-modal');
  287. modal.classList.add('hidden');
  288. modal.classList.remove('flex');
  289. document.body.style.overflow = '';
  290. };
  291. // Fermer avec Escape
  292. document.addEventListener('keydown', e => {
  293. if (e.key === 'Escape') closeCheckoutModal();
  294. });
  295. // ── Navigation étapes ──────────────────────────────────────────────────────
  296. window.modalGoStep = function (n) {
  297. if (n === 2) {
  298. const prenom = document.getElementById('m-prenom')?.value.trim();
  299. const nom = document.getElementById('m-nom')?.value.trim();
  300. const email = document.getElementById('m-email')?.value.trim();
  301. if (!prenom || !nom) { alert('Veuillez renseigner votre prénom et nom.'); return; }
  302. if (!email || !email.includes('@')) { alert('Adresse e-mail invalide.'); return; }
  303. }
  304. document.querySelectorAll('.modal-step-content').forEach(s => s.classList.add('hidden'));
  305. document.getElementById('modal-step-' + n)?.classList.remove('hidden');
  306. // Étapes pills
  307. const pills = document.querySelectorAll('.modal-step-pill');
  308. pills.forEach(pill => {
  309. const s = parseInt(pill.dataset.step);
  310. if (s < n) {
  311. pill.className = 'modal-step-pill w-6 h-6 rounded-full bg-[#006633] text-white flex items-center justify-center font-black text-[10px]';
  312. pill.innerHTML = '<i class="fas fa-check text-[9px]"></i>';
  313. } else if (s === n) {
  314. pill.className = 'modal-step-pill w-6 h-6 rounded-full bg-[#006633] text-white flex items-center justify-center font-black text-[10px]';
  315. pill.textContent = s;
  316. } else {
  317. pill.className = 'modal-step-pill w-6 h-6 rounded-full bg-gray-200 text-gray-400 flex items-center justify-center font-black text-[10px]';
  318. pill.textContent = s;
  319. }
  320. });
  321. const labels = { 1: 'Récapitulatif', 2: 'Paiement', 3: 'Confirmation' };
  322. const labelEl = document.getElementById('modal-step-label');
  323. if (labelEl) labelEl.textContent = labels[n];
  324. };
  325. // ── UI selon le plan ───────────────────────────────────────────────────────
  326. function updateModalPlanUI() {
  327. const isPremium = currentPlan === 'premium';
  328. const badge = document.getElementById('modal-plan-badge');
  329. const icon = document.getElementById('modal-plan-icon');
  330. const sub = document.getElementById('modal-plan-sublabel');
  331. const name = document.getElementById('modal-plan-name');
  332. if (isPremium) {
  333. badge.className = 'rounded-xl p-4 mb-5 flex items-center gap-3 bg-gradient-to-r from-[#00391a] to-[#006633] text-white';
  334. icon.className = 'w-10 h-10 bg-white/20 rounded-xl flex items-center justify-center flex-shrink-0';
  335. icon.innerHTML = '<i class="fas fa-crown text-amber-400"></i>';
  336. sub.className = 'text-xs font-bold uppercase tracking-wider text-emerald-300';
  337. name.className = 'font-black text-lg text-white';
  338. } else {
  339. badge.className = 'rounded-xl p-4 mb-5 flex items-center gap-3 bg-emerald-50 border border-emerald-200';
  340. icon.className = 'w-10 h-10 bg-emerald-100 rounded-xl flex items-center justify-center flex-shrink-0';
  341. icon.innerHTML = '<i class="fas fa-building text-[#006633]"></i>';
  342. sub.className = 'text-xs font-bold uppercase tracking-wider text-[#006633]';
  343. name.className = 'font-black text-lg text-gray-800';
  344. }
  345. if (name) name.textContent = LABELS[currentPlan];
  346. }
  347. // ── Mise à jour prix ───────────────────────────────────────────────────────
  348. function updatePrices() {
  349. const prix = PRIX[currentPlan][currentDuree];
  350. const isPremium = currentPlan === 'premium';
  351. // Badge header
  352. const priceEl = document.getElementById('modal-plan-price');
  353. const periodEl = document.getElementById('modal-plan-period');
  354. if (priceEl) priceEl.textContent = prix.toLocaleString('fr-FR') + ' KMF';
  355. if (periodEl) periodEl.textContent = currentDuree === 'annuel' ? '/ an · 1ère année' : '/ mois';
  356. // Cards durée
  357. const annuelPriceEl = document.querySelector('.modal-dur-price');
  358. const mensuelPriceEl = document.querySelector('.modal-dur-mensuel-price');
  359. if (annuelPriceEl) annuelPriceEl.textContent = PRIX[currentPlan].annuel.toLocaleString('fr-FR');
  360. if (mensuelPriceEl) mensuelPriceEl.textContent = PRIX[currentPlan].mensuel.toLocaleString('fr-FR');
  361. // Récap étape 2
  362. const recapPlan = document.getElementById('modal-recap-plan');
  363. const recapPrix = document.getElementById('modal-recap-prix');
  364. if (recapPlan) recapPlan.textContent = LABELS[currentPlan] + ' · ' + (currentDuree === 'annuel' ? 'Annuel' : 'Mensuel');
  365. if (recapPrix) recapPrix.textContent = prix.toLocaleString('fr-FR') + ' KMF';
  366. // Bouton payer
  367. const btnLabel = document.getElementById('modal-btn-pay-label');
  368. if (btnLabel) btnLabel.textContent = 'Payer ' + prix.toLocaleString('fr-FR') + ' KMF';
  369. // Couleur bouton payer si premium
  370. const btnPay = document.getElementById('modal-btn-pay');
  371. if (btnPay) {
  372. if (isPremium) {
  373. btnPay.className = btnPay.className.replace('from-[#006633] to-[#008040]', 'from-amber-500 to-amber-400').replace('hover:from-[#004d26]', 'hover:from-amber-600');
  374. } else {
  375. btnPay.className = btnPay.className.replace('from-amber-500 to-amber-400', 'from-[#006633] to-[#008040]').replace('hover:from-amber-600', 'hover:from-[#004d26]');
  376. }
  377. }
  378. }
  379. // ── Changement durée ──────────────────────────────────────────────────────
  380. document.querySelectorAll('input[name="modal-duree"]').forEach(radio => {
  381. radio.addEventListener('change', function () {
  382. currentDuree = this.value;
  383. updatePrices();
  384. document.querySelectorAll('.modal-dur-card').forEach(c => {
  385. c.className = c.className
  386. .replace('border-[#006633] bg-emerald-50/50', 'border-gray-200 bg-white')
  387. .replace('border-gray-200 bg-white', 'border-gray-200 bg-white');
  388. });
  389. const active = this.closest('label').querySelector('.modal-dur-card');
  390. active.className = active.className.replace('border-gray-200 bg-white', 'border-[#006633] bg-emerald-50/50');
  391. });
  392. });
  393. // ── Changement méthode paiement ───────────────────────────────────────────
  394. const COLORS = { huri_money: ['orange-400', 'orange-50/40'], mvola: ['green-600', 'green-50/40'], holo: ['blue-600', 'blue-50/40'] };
  395. document.querySelectorAll('input[name="modal-methode"]').forEach(radio => {
  396. radio.addEventListener('change', function () {
  397. document.querySelectorAll('.modal-pay-card').forEach(c => {
  398. c.className = 'modal-pay-card border-2 border-gray-200 bg-white rounded-xl p-3.5 flex items-center gap-3 transition-all hover:border-gray-300';
  399. });
  400. document.querySelectorAll('.modal-radio-dot').forEach(d => d.innerHTML = '');
  401. const [border, bg] = COLORS[this.value] || ['gray-400', 'gray-50'];
  402. const card = this.closest('label').querySelector('.modal-pay-card');
  403. card.className = `modal-pay-card border-2 border-${border} bg-${bg} rounded-xl p-3.5 flex items-center gap-3 transition-all`;
  404. const dot = this.closest('label').querySelector('.modal-radio-dot');
  405. dot.innerHTML = `<div class="w-2 h-2 rounded-full bg-${border}"></div>`;
  406. dot.className = `w-4 h-4 rounded-full border-2 border-${border} flex items-center justify-center modal-radio-dot`;
  407. const label = document.getElementById('modal-provider-label');
  408. if (label) label.textContent = PROVIDERS[this.value] || this.value;
  409. });
  410. });
  411. // ── Bouton payer ───────────────────────────────────────────────────────────
  412. document.getElementById('modal-btn-pay')?.addEventListener('click', async function () {
  413. const cgu = document.getElementById('m-cgu')?.checked;
  414. const phone = document.getElementById('m-phone')?.value.trim().replace(/\s/g, '');
  415. if (!cgu) { alert('Veuillez accepter les CGU.'); return; }
  416. if (phone.length < 7) { alert('Numéro de téléphone invalide.'); return; }
  417. this.disabled = true;
  418. this.innerHTML = '<svg class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>&nbsp;Traitement…';
  419. // TODO : remplacer par l'appel API de paiement réel
  420. await new Promise(r => setTimeout(r, 1800));
  421. // Remplir confirmation
  422. const ref = 'JO-' + Date.now().toString(36).toUpperCase();
  423. const prix = PRIX[currentPlan][currentDuree];
  424. const date = new Date();
  425. currentDuree === 'annuel' ? date.setFullYear(date.getFullYear() + 1) : date.setMonth(date.getMonth() + 1);
  426. document.getElementById('modal-confirm-plan').textContent = LABELS[currentPlan];
  427. document.getElementById('modal-confirm-ref').textContent = ref;
  428. document.getElementById('modal-conf-plan2').textContent = LABELS[currentPlan];
  429. document.getElementById('modal-conf-duree').textContent = currentDuree === 'annuel' ? 'Annuel' : 'Mensuel';
  430. document.getElementById('modal-conf-montant').textContent = prix.toLocaleString('fr-FR') + ' KMF';
  431. document.getElementById('modal-conf-renouvellement').textContent = date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' });
  432. modalGoStep(3);
  433. });
  434. })();
  435. </script>
  436. {% endblock %}
  437. {% block body %}
  438. {# ================================================================
  439. PAGE ABONNEMENT — CDC conforme
  440. Gratuit / Standard 50 000 KMF / Premium 90 000 KMF
  441. Remise -20% première année sur les offres payantes
  442. Paiement : Huri Money, Mvola, Holo, Visa/Mastercard
  443. ================================================================ #}
  444. <!-- Hero -->
  445. <section class="relative bg-gradient-to-br from-[#00391a] via-[#006633] to-[#008040] text-white overflow-hidden">
  446. <div class="absolute inset-0 pointer-events-none opacity-10">
  447. <svg class="w-full h-full" xmlns="http://www.w3.org/2000/svg">
  448. <pattern id="dots" x="0" y="0" width="30" height="30" patternUnits="userSpaceOnUse">
  449. <circle cx="2" cy="2" r="1.5" fill="white"/>
  450. </pattern>
  451. <rect width="100%" height="100%" fill="url(#dots)"/>
  452. </svg>
  453. </div>
  454. <div class="absolute top-0 right-0 w-96 h-96 bg-[#00994d] rounded-full blur-3xl opacity-20 -translate-y-1/2 translate-x-1/4"></div>
  455. <div class="container mx-auto px-4 py-20 relative z-10 text-center">
  456. <div class="inline-flex items-center gap-2 bg-white/15 backdrop-blur-sm border border-white/20 rounded-full px-5 py-2 mb-6 text-sm font-medium">
  457. <i class="fas fa-shield-alt text-emerald-300"></i>
  458. Accès officiel au Journal Officiel de l'Union des Comores
  459. </div>
  460. <h1 class="text-4xl md:text-5xl font-black mb-4 leading-tight">
  461. Choisissez votre accès
  462. </h1>
  463. <p class="text-white/75 max-w-xl mx-auto text-base leading-relaxed mb-8">
  464. De la consultation gratuite à la recherche enrichie par intelligence artificielle —
  465. un abonnement adapté à chaque besoin institutionnel, juridique ou citoyen.
  466. </p>
  467. <!-- Toggle Annuel / Mensuel -->
  468. <div class="inline-flex items-center bg-white/10 border border-white/20 rounded-full p-1 gap-1">
  469. <button id="btn-annuel"
  470. class="px-5 py-2 rounded-full text-sm font-semibold bg-white text-[#006633] transition-all duration-200">
  471. Annuel <span class="ml-1 text-xs bg-emerald-500 text-white px-1.5 py-0.5 rounded-full">−20%</span>
  472. </button>
  473. <button id="btn-mensuel"
  474. class="px-5 py-2 rounded-full text-sm font-semibold text-white/70 hover:text-white transition-all duration-200">
  475. Mensuel
  476. </button>
  477. </div>
  478. </div>
  479. <div class="absolute bottom-0 left-0 right-0">
  480. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 60" class="w-full">
  481. <path fill="#f9fafb" fill-opacity="1" d="M0,30L360,50L720,20L1080,50L1440,30L1440,60L0,60Z"></path>
  482. </svg>
  483. </div>
  484. </section>
  485. <!-- Cartes tarifs -->
  486. <section class="py-16 bg-gray-50" id="tarifs">
  487. <div class="container mx-auto px-4">
  488. <div class="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-6xl mx-auto items-stretch">
  489. <!-- ── GRATUIT ─────────────────────────────────────── -->
  490. <div class="bg-white rounded-2xl border border-gray-200 shadow-sm flex flex-col overflow-hidden hover:shadow-md transition-shadow duration-300">
  491. <div class="p-7 flex-1">
  492. <div class="flex items-center gap-3 mb-5">
  493. <div class="w-10 h-10 bg-gray-100 rounded-xl flex items-center justify-center">
  494. <i class="fas fa-user text-gray-500"></i>
  495. </div>
  496. <div>
  497. <p class="text-xs font-bold text-gray-400 uppercase tracking-widest">Accès</p>
  498. <h2 class="text-lg font-black text-gray-800">Gratuit</h2>
  499. </div>
  500. </div>
  501. <div class="mb-6">
  502. <span class="text-4xl font-black text-gray-800">0</span>
  503. <span class="text-gray-400 text-sm ml-1">KMF / an</span>
  504. </div>
  505. <p class="text-gray-500 text-sm mb-6">
  506. Accès en lecture aux publications officielles et moteur de recherche par filtres.
  507. </p>
  508. <ul class="space-y-3 text-sm">
  509. {% set gratuit_features = [
  510. {ok: true, label: 'Consultation numéros complets (PDF)'},
  511. {ok: true, label: 'Moteur de recherche par filtres'},
  512. {ok: true, label: 'Accès aux derniers actes publiés'},
  513. {ok: false, label: 'Alertes & veille juridique'},
  514. {ok: false, label: 'Textes isolés (< 5 ans)'},
  515. {ok: false, label: 'Archives & réquisitions foncières'},
  516. {ok: false, label: 'Recherche Ibunas.IA (OCR + IA)'},
  517. {ok: false, label: 'Alertes personnalisées par thème'},
  518. ] %}
  519. {% for f in gratuit_features %}
  520. <li class="flex items-start gap-3">
  521. {% if f.ok %}
  522. <i class="fas fa-check-circle text-[#006633] mt-0.5 flex-shrink-0"></i>
  523. <span class="text-gray-700">{{ f.label }}</span>
  524. {% else %}
  525. <i class="fas fa-times-circle text-gray-300 mt-0.5 flex-shrink-0"></i>
  526. <span class="text-gray-400">{{ f.label }}</span>
  527. {% endif %}
  528. </li>
  529. {% endfor %}
  530. </ul>
  531. </div>
  532. <div class="p-7 pt-0">
  533. {% if app.user %}
  534. <a href="{{ path('app_dashboard') }}"
  535. class="block w-full text-center border-2 border-gray-200 text-gray-600 py-3 rounded-xl text-sm font-semibold hover:border-gray-300 hover:bg-gray-50 transition">
  536. Votre accès actuel
  537. </a>
  538. {% else %}
  539. <a href=""
  540. class="block w-full text-center border-2 border-gray-200 text-gray-600 py-3 rounded-xl text-sm font-semibold hover:border-gray-300 hover:bg-gray-50 transition">
  541. Créer un compte gratuit
  542. </a>
  543. {% endif %}
  544. </div>
  545. </div>
  546. <!-- ── STANDARD ────────────────────────────────────── -->
  547. <div class="bg-white rounded-2xl border-2 border-[#006633]/30 shadow-lg flex flex-col overflow-hidden hover:shadow-xl transition-shadow duration-300 relative">
  548. <!-- Badge populaire -->
  549. <div class="absolute top-4 right-4">
  550. <span class="bg-[#006633] text-white text-xs font-bold px-3 py-1 rounded-full">
  551. Populaire
  552. </span>
  553. </div>
  554. <div class="h-1.5 bg-gradient-to-r from-[#006633] to-[#00994d]"></div>
  555. <div class="p-7 flex-1">
  556. <div class="flex items-center gap-3 mb-5">
  557. <div class="w-10 h-10 bg-emerald-100 rounded-xl flex items-center justify-center">
  558. <i class="fas fa-building text-[#006633]"></i>
  559. </div>
  560. <div>
  561. <p class="text-xs font-bold text-[#006633] uppercase tracking-widest">Abonnement</p>
  562. <h2 class="text-lg font-black text-gray-800">Standard</h2>
  563. </div>
  564. </div>
  565. <!-- Prix annuel -->
  566. <div class="mb-1 price-block" data-annuel="true">
  567. <div class="flex items-baseline gap-2">
  568. <span class="text-4xl font-black text-gray-800 price-value" data-annuel="40000" data-mensuel="4500">40 000</span>
  569. <span class="text-gray-400 text-sm price-unit">KMF / an</span>
  570. </div>
  571. <p class="text-xs text-emerald-600 font-semibold mt-1">
  572. <i class="fas fa-tag mr-1"></i>
  573. <span class="price-promo">−20% la 1ère année · au lieu de 50 000 KMF</span>
  574. </p>
  575. </div>
  576. <p class="text-gray-500 text-sm mb-6 mt-4">
  577. Pour les professionnels du droit, entreprises et administrations ayant besoin d'un suivi régulier.
  578. </p>
  579. <ul class="space-y-3 text-sm">
  580. {% set standard_features = [
  581. {ok: true, label: 'Consultation numéros complets (PDF)'},
  582. {ok: true, label: 'Moteur de recherche par filtres'},
  583. {ok: true, label: 'Alertes & veille juridique'},
  584. {ok: true, label: 'Textes isolés (actes < 5 ans)'},
  585. {ok: false, label: 'Archives historiques (> 5 ans)'},
  586. {ok: false, label: 'Réquisitions & actes fonciers'},
  587. {ok: false, label: 'Recherche Ibunas.IA (OCR + IA)'},
  588. {ok: false, label: 'Alertes personnalisées par thème'},
  589. ] %}
  590. {% for f in standard_features %}
  591. <li class="flex items-start gap-3">
  592. {% if f.ok %}
  593. <i class="fas fa-check-circle text-[#006633] mt-0.5 flex-shrink-0"></i>
  594. <span class="text-gray-700">{{ f.label }}</span>
  595. {% else %}
  596. <i class="fas fa-times-circle text-gray-300 mt-0.5 flex-shrink-0"></i>
  597. <span class="text-gray-400">{{ f.label }}</span>
  598. {% endif %}
  599. </li>
  600. {% endfor %}
  601. </ul>
  602. </div>
  603. <div class="p-7 pt-0">
  604. <button type="button" onclick="openCheckoutModal('standard')"
  605. class="block w-full text-center bg-gradient-to-r from-[#006633] to-[#008040] text-white py-3 rounded-xl text-sm font-bold hover:from-[#004d26] hover:to-[#006633] transition shadow-md hover:shadow-lg">
  606. <i class="fas fa-arrow-right mr-2"></i> Choisir Standard
  607. </button>
  608. <p class="text-center text-xs text-gray-400 mt-2">Sans engagement · Résiliable à tout moment</p>
  609. </div>
  610. </div>
  611. <!-- ── PREMIUM ─────────────────────────────────────── -->
  612. <div class="bg-gradient-to-br from-[#00391a] to-[#006633] rounded-2xl shadow-xl flex flex-col overflow-hidden hover:shadow-2xl transition-shadow duration-300 relative text-white">
  613. <!-- Badge IA -->
  614. <div class="absolute top-4 right-4">
  615. <span class="bg-amber-400 text-amber-900 text-xs font-black px-3 py-1 rounded-full flex items-center gap-1">
  616. <i class="fas fa-microchip text-[10px]"></i> Ibunas.IA
  617. </span>
  618. </div>
  619. <div class="p-7 flex-1">
  620. <div class="flex items-center gap-3 mb-5">
  621. <div class="w-10 h-10 bg-white/20 rounded-xl flex items-center justify-center">
  622. <i class="fas fa-crown text-amber-400"></i>
  623. </div>
  624. <div>
  625. <p class="text-xs font-bold text-emerald-300 uppercase tracking-widest">Abonnement</p>
  626. <h2 class="text-lg font-black text-white">Premium</h2>
  627. </div>
  628. </div>
  629. <div class="mb-1">
  630. <div class="flex items-baseline gap-2">
  631. <span class="text-4xl font-black text-white price-value" data-annuel="72000" data-mensuel="8000">72 000</span>
  632. <span class="text-white/60 text-sm price-unit">KMF / an</span>
  633. </div>
  634. <p class="text-xs text-amber-300 font-semibold mt-1">
  635. <i class="fas fa-tag mr-1"></i>
  636. <span class="price-promo">−20% la 1ère année · au lieu de 90 000 KMF</span>
  637. </p>
  638. </div>
  639. <p class="text-white/65 text-sm mb-6 mt-4">
  640. Accès complet aux archives, réquisitions foncières et moteur Ibunas.IA avec recherche OCR dans le contenu des actes.
  641. </p>
  642. <ul class="space-y-3 text-sm">
  643. {% set premium_features = [
  644. {label: 'Consultation numéros complets (PDF)'},
  645. {label: 'Moteur de recherche par filtres'},
  646. {label: 'Alertes & veille juridique avancée'},
  647. {label: 'Textes isolés (actes < 5 ans)'},
  648. {label: 'Archives historiques (> 5 ans)'},
  649. {label: 'Réquisitions & actes fonciers'},
  650. {label: 'Recherche Ibunas.IA · OCR + IA'},
  651. {label: 'Alertes personnalisées par thème'},
  652. ] %}
  653. {% for f in premium_features %}
  654. <li class="flex items-start gap-3">
  655. <i class="fas fa-check-circle text-amber-400 mt-0.5 flex-shrink-0"></i>
  656. <span class="text-white/90">{{ f.label }}</span>
  657. </li>
  658. {% endfor %}
  659. </ul>
  660. </div>
  661. <div class="p-7 pt-0">
  662. <button type="button" onclick="openCheckoutModal('premium')"
  663. class="block w-full text-center bg-amber-400 text-amber-900 py-3 rounded-xl text-sm font-black hover:bg-amber-300 transition shadow-md hover:shadow-lg">
  664. <i class="fas fa-crown mr-2"></i> Choisir Premium
  665. </button>
  666. <p class="text-center text-xs text-white/40 mt-2">Sans engagement · Résiliable à tout moment</p>
  667. </div>
  668. </div>
  669. </div>
  670. <!-- Note remise -->
  671. <p class="text-center text-xs text-gray-400 mt-6">
  672. <i class="fas fa-info-circle mr-1"></i>
  673. La remise de −20% s'applique uniquement lors de la 1ère année de souscription.
  674. Le tarif plein est appliqué au renouvellement.
  675. </p>
  676. </div>
  677. </section>
  678. <!-- Moyens de paiement -->
  679. <section class="py-12 bg-white border-t border-gray-100">
  680. <div class="container mx-auto px-4">
  681. <div class="max-w-3xl mx-auto text-center">
  682. <p class="text-xs font-bold text-gray-400 uppercase tracking-widest mb-5">
  683. Moyens de paiement acceptés
  684. </p>
  685. <div class="flex flex-wrap justify-center items-center gap-6">
  686. <div class="flex items-center gap-2.5 bg-gray-50 border border-gray-200 rounded-xl px-5 py-3">
  687. <div class="w-8 h-8 bg-orange-500 rounded-lg flex items-center justify-center">
  688. <i class="fas fa-mobile-alt text-white text-sm"></i>
  689. </div>
  690. <div class="text-left">
  691. <p class="text-xs font-bold text-gray-800">Huri Money</p>
  692. <p class="text-[10px] text-gray-400">Mobile Money</p>
  693. </div>
  694. </div>
  695. <div class="flex items-center gap-2.5 bg-gray-50 border border-gray-200 rounded-xl px-5 py-3">
  696. <div class="w-8 h-8 bg-green-600 rounded-lg flex items-center justify-center">
  697. <i class="fas fa-mobile-alt text-white text-sm"></i>
  698. </div>
  699. <div class="text-left">
  700. <p class="text-xs font-bold text-gray-800">Mvola</p>
  701. <p class="text-[10px] text-gray-400">Mobile Money</p>
  702. </div>
  703. </div>
  704. <div class="flex items-center gap-2.5 bg-gray-50 border border-gray-200 rounded-xl px-5 py-3">
  705. <div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
  706. <i class="fas fa-mobile-alt text-white text-sm"></i>
  707. </div>
  708. <div class="text-left">
  709. <p class="text-xs font-bold text-gray-800">Holo</p>
  710. <p class="text-[10px] text-gray-400">Mobile Money</p>
  711. </div>
  712. </div>
  713. <div class="flex items-center gap-2.5 bg-gray-50 border border-gray-200 rounded-xl px-5 py-3 opacity-60">
  714. <div class="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center">
  715. <i class="fas fa-credit-card text-white text-sm"></i>
  716. </div>
  717. <div class="text-left">
  718. <p class="text-xs font-bold text-gray-800">Visa / Mastercard</p>
  719. <p class="text-[10px] text-gray-400">Bientôt disponible</p>
  720. </div>
  721. </div>
  722. </div>
  723. </div>
  724. </div>
  725. </section>
  726. <!-- Tableau comparatif complet -->
  727. <section class="py-16 bg-gray-50">
  728. <div class="container mx-auto px-4">
  729. <div class="max-w-5xl mx-auto">
  730. <h2 class="text-2xl font-black text-gray-800 text-center mb-2">Comparaison détaillée</h2>
  731. <p class="text-gray-500 text-center text-sm mb-10">Toutes les fonctionnalités, offre par offre</p>
  732. <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
  733. <div class="overflow-x-auto">
  734. <table class="w-full text-sm min-w-[560px]">
  735. <thead>
  736. <tr class="border-b border-gray-100">
  737. <th class="text-left p-4 text-gray-500 font-semibold w-1/2">Fonctionnalité</th>
  738. <th class="text-center p-4 text-gray-600 font-bold">Gratuit</th>
  739. <th class="text-center p-4 text-[#006633] font-bold">Standard</th>
  740. <th class="text-center p-4 font-bold">
  741. <span class="text-[#006633]">Premium</span>
  742. <span class="ml-1 text-[10px] bg-amber-100 text-amber-700 px-1.5 py-0.5 rounded-full font-bold">IA</span>
  743. </th>
  744. </tr>
  745. </thead>
  746. <tbody>
  747. {% set rows = [
  748. {cat: true, label: 'Consultation'},
  749. {cat: false, label: 'Accès numéros complets (PDF)', g: true, s: true, p: true},
  750. {cat: false, label: 'Consultation en ligne HTML responsive', g: true, s: true, p: true},
  751. {cat: false, label: 'Textes isolés — actes récents (< 5 ans)', g: false, s: true, p: true},
  752. {cat: false, label: 'Archives historiques (> 5 ans)', g: false, s: false, p: true},
  753. {cat: false, label: 'Réquisitions & actes fonciers', g: false, s: false, p: true},
  754. {cat: true, label: 'Recherche'},
  755. {cat: false, label: 'Moteur de recherche par filtres', g: true, s: true, p: true},
  756. {cat: false, label: 'Recherche par institution émettrice', g: true, s: true, p: true},
  757. {cat: false, label: 'Filtrage chronologique (année / plage)', g: true, s: true, p: true},
  758. {cat: false, label: 'Filtrage par type d\'acte', g: true, s: true, p: true},
  759. {cat: false, label: 'Recherche Ibunas.IA (contenu OCR + IA)', g: false, s: false, p: true},
  760. {cat: false, label: 'Résumé automatique des actes', g: false, s: false, p: true},
  761. {cat: true, label: 'Alertes & veille'},
  762. {cat: false, label: 'Notifications de nouvelles publications', g: false, s: true, p: true},
  763. {cat: false, label: 'Veille juridique (domaines généraux)', g: false, s: true, p: true},
  764. {cat: false, label: 'Alertes personnalisées par thème', g: false, s: false, p: true},
  765. {cat: false, label: 'Mémentos juridiques (à venir)', g: false, s: false, p: true},
  766. ] %}
  767. {% for row in rows %}
  768. {% if row.cat %}
  769. <tr class="bg-gray-50 border-t border-gray-100">
  770. <td colspan="4" class="px-5 py-3 text-xs font-black text-gray-500 uppercase tracking-widest">
  771. {{ row.label }}
  772. </td>
  773. </tr>
  774. {% else %}
  775. <tr class="border-t border-gray-50 hover:bg-gray-50/50 transition-colors">
  776. <td class="p-4 px-5 text-gray-700">{{ row.label }}</td>
  777. <td class="p-4 text-center">
  778. {% if row.g %}
  779. <i class="fas fa-check text-[#006633]"></i>
  780. {% else %}
  781. <i class="fas fa-minus text-gray-300"></i>
  782. {% endif %}
  783. </td>
  784. <td class="p-4 text-center">
  785. {% if row.s %}
  786. <i class="fas fa-check text-[#006633]"></i>
  787. {% else %}
  788. <i class="fas fa-minus text-gray-300"></i>
  789. {% endif %}
  790. </td>
  791. <td class="p-4 text-center">
  792. {% if row.p %}
  793. <i class="fas fa-check text-amber-500"></i>
  794. {% else %}
  795. <i class="fas fa-minus text-gray-300"></i>
  796. {% endif %}
  797. </td>
  798. </tr>
  799. {% endif %}
  800. {% endfor %}
  801. <!-- Ligne tarif -->
  802. <tr class="border-t-2 border-gray-200 bg-gray-50">
  803. <td class="p-4 font-black text-gray-800">Tarif annuel</td>
  804. <td class="p-4 text-center font-black text-gray-800">Gratuit</td>
  805. <td class="p-4 text-center font-black text-[#006633]">
  806. 50 000 KMF<br>
  807. <span class="text-xs font-semibold text-emerald-600">40 000 KMF la 1ère année</span>
  808. </td>
  809. <td class="p-4 text-center font-black text-[#006633]">
  810. 90 000 KMF<br>
  811. <span class="text-xs font-semibold text-amber-600">72 000 KMF la 1ère année</span>
  812. </td>
  813. </tr>
  814. <tr class="border-t border-gray-100">
  815. <td class="p-4"></td>
  816. <td class="p-4 text-center">
  817. <a href=""
  818. class="inline-block border border-gray-200 text-gray-600 text-xs font-semibold px-4 py-2 rounded-lg hover:bg-gray-50 transition">
  819. Créer un compte
  820. </a>
  821. </td>
  822. <td class="p-4 text-center">
  823. <button type="button" onclick="openCheckoutModal('standard')"
  824. class="inline-block bg-[#006633] text-white text-xs font-bold px-4 py-2 rounded-lg hover:bg-[#004d26] transition">
  825. Choisir Standard
  826. </button>
  827. </td>
  828. <td class="p-4 text-center">
  829. <button type="button" onclick="openCheckoutModal('premium')"
  830. class="inline-block bg-amber-400 text-amber-900 text-xs font-black px-4 py-2 rounded-lg hover:bg-amber-300 transition">
  831. Choisir Premium
  832. </button>
  833. </td>
  834. </tr>
  835. </tbody>
  836. </table>
  837. </div> {# ← ferme overflow-x-auto #}
  838. </div>
  839. </div>
  840. </div>
  841. </div>
  842. </section>
  843. <!-- FAQ -->
  844. <section class="py-16 bg-white">
  845. <div class="container mx-auto px-4">
  846. <div class="max-w-2xl mx-auto">
  847. <h2 class="text-2xl font-black text-gray-800 text-center mb-2">Questions fréquentes</h2>
  848. <p class="text-gray-500 text-sm text-center mb-10">Tout ce que vous devez savoir avant de vous abonner</p>
  849. <div class="space-y-3" id="faq-list">
  850. {% set faqs = [
  851. {
  852. q: 'Comment fonctionne la remise de −20% ?',
  853. r: 'La remise s\'applique uniquement lors de votre première année d\'abonnement, que ce soit Standard ou Premium. À partir de la deuxième année, le tarif plein s\'applique automatiquement (50 000 KMF ou 90 000 KMF selon le plan).'
  854. },
  855. {
  856. q: 'Puis-je résilier à tout moment ?',
  857. r: 'Oui. Vous pouvez résilier votre abonnement à tout moment depuis votre espace personnel. Votre accès reste actif jusqu\'à la fin de la période payée, sans frais de résiliation.'
  858. },
  859. {
  860. q: 'Qu\'est-ce que la recherche Ibunas.IA ?',
  861. r: 'Ibunas.IA est notre moteur de recherche enrichi par intelligence artificielle. Il analyse le contenu textuel des actes grâce à l\'OCR (reconnaissance de caractères) et vous permet de rechercher des termes précis à l\'intérieur même des documents, pas seulement dans les titres et résumés.'
  862. },
  863. {
  864. q: 'Quels moyens de paiement sont acceptés ?',
  865. r: 'Nous acceptons les paiements Mobile Money comoriens : Huri Money, Mvola et Holo. Le paiement par carte Visa/Mastercard sera disponible prochainement, sous validation de la Banque Centrale des Comores.'
  866. },
  867. {
  868. q: 'Les abonnements peuvent-ils être partagés ?',
  869. r: 'Non, chaque abonnement est nominatif et lié à un seul compte utilisateur. Pour les institutions nécessitant plusieurs accès simultanés, contactez-nous pour un devis multi-utilisateurs.'
  870. },
  871. {
  872. q: 'Comment accéder aux archives historiques ?',
  873. r: 'Les archives antérieures à 5 ans sont exclusivement réservées aux abonnés Premium. Elles incluent les numérisations des anciens journaux officiels, indexées et rendues recherchables via notre système OCR.'
  874. },
  875. ] %}
  876. {% for faq in faqs %}
  877. <div class="faq-item bg-gray-50 rounded-xl border border-gray-100 overflow-hidden">
  878. <button type="button"
  879. class="faq-trigger w-full flex items-center justify-between p-5 text-left hover:bg-gray-100 transition-colors duration-200">
  880. <span class="font-semibold text-gray-800 text-sm pr-4">{{ faq.q }}</span>
  881. <i class="fas fa-chevron-down text-gray-400 text-xs flex-shrink-0 transition-transform duration-300 faq-icon"></i>
  882. </button>
  883. <div class="faq-answer hidden px-5 pb-5">
  884. <p class="text-gray-600 text-sm leading-relaxed">{{ faq.r }}</p>
  885. </div>
  886. </div>
  887. {% endfor %}
  888. </div>
  889. </div>
  890. </div>
  891. </section>
  892. <!-- CTA final -->
  893. <section class="py-16 bg-gradient-to-br from-[#006633] to-[#004d26] text-white">
  894. <div class="container mx-auto px-4 text-center">
  895. <div class="w-14 h-14 bg-white/15 rounded-2xl flex items-center justify-center mx-auto mb-5">
  896. <i class="fas fa-file-contract text-2xl text-white"></i>
  897. </div>
  898. <h2 class="text-2xl font-black mb-3">Besoin d'un accès institutionnel ?</h2>
  899. <p class="text-white/70 max-w-md mx-auto text-sm mb-7">
  900. Ministères, juridictions, cabinets d'avocats, universités —
  901. contactez-nous pour un devis multi-utilisateurs adapté à votre structure.
  902. </p>
  903. <a href="mailto:contact@journalofficiel.km"
  904. class="inline-flex items-center gap-2 bg-white text-[#006633] px-7 py-3 rounded-xl font-bold text-sm hover:bg-gray-50 transition shadow-lg">
  905. <i class="fas fa-envelope"></i> Nous contacter
  906. </a>
  907. </div>
  908. </section>
  909. <script>
  910. document.addEventListener('DOMContentLoaded', function () {
  911. // ── Toggle annuel / mensuel ────────────────────────────────────────────────
  912. const btnAnnuel = document.getElementById('btn-annuel');
  913. const btnMensuel = document.getElementById('btn-mensuel');
  914. const priceValues = document.querySelectorAll('.price-value');
  915. const priceUnits = document.querySelectorAll('.price-unit');
  916. const pricePromos = document.querySelectorAll('.price-promo');
  917. let isAnnuel = true;
  918. function updatePrices() {
  919. priceValues.forEach(el => {
  920. const val = isAnnuel ? el.dataset.annuel : el.dataset.mensuel;
  921. el.textContent = parseInt(val).toLocaleString('fr-FR');
  922. });
  923. priceUnits.forEach(el => {
  924. el.textContent = isAnnuel ? 'KMF / an' : 'KMF / mois';
  925. });
  926. pricePromos.forEach(el => {
  927. if (isAnnuel) {
  928. el.closest('p')?.classList.remove('hidden');
  929. } else {
  930. el.closest('p')?.classList.add('hidden');
  931. }
  932. });
  933. }
  934. btnAnnuel?.addEventListener('click', () => {
  935. isAnnuel = true;
  936. btnAnnuel.className = 'px-5 py-2 rounded-full text-sm font-semibold bg-white text-[#006633] transition-all duration-200';
  937. btnMensuel.className = 'px-5 py-2 rounded-full text-sm font-semibold text-white/70 hover:text-white transition-all duration-200';
  938. updatePrices();
  939. });
  940. btnMensuel?.addEventListener('click', () => {
  941. isAnnuel = false;
  942. btnMensuel.className = 'px-5 py-2 rounded-full text-sm font-semibold bg-white text-[#006633] transition-all duration-200';
  943. btnAnnuel.className = 'px-5 py-2 rounded-full text-sm font-semibold text-white/70 hover:text-white transition-all duration-200';
  944. updatePrices();
  945. });
  946. // ── FAQ accordion ──────────────────────────────────────────────────────────
  947. document.querySelectorAll('.faq-trigger').forEach(btn => {
  948. btn.addEventListener('click', () => {
  949. const item = btn.closest('.faq-item');
  950. const answer = item.querySelector('.faq-answer');
  951. const icon = item.querySelector('.faq-icon');
  952. const isOpen = !answer.classList.contains('hidden');
  953. // Fermer tous
  954. document.querySelectorAll('.faq-answer').forEach(a => a.classList.add('hidden'));
  955. document.querySelectorAll('.faq-icon').forEach(i => i.style.transform = 'rotate(0deg)');
  956. // Ouvrir si était fermé
  957. if (!isOpen) {
  958. answer.classList.remove('hidden');
  959. icon.style.transform = 'rotate(180deg)';
  960. }
  961. });
  962. });
  963. });
  964. </script>
  965. {# ================================================================
  966. MODAL CHECKOUT — Standard & Premium
  967. ================================================================ #}
  968. <div id="checkout-modal"
  969. class="fixed inset-0 z-50 hidden items-center justify-center p-4"
  970. role="dialog" aria-modal="true" aria-labelledby="modal-title">
  971. <!-- Overlay -->
  972. <div id="modal-overlay"
  973. class="absolute inset-0 bg-black/60 backdrop-blur-sm"
  974. onclick="closeCheckoutModal()"></div>
  975. <!-- Panel -->
  976. <div class="relative bg-white rounded-2xl shadow-2xl w-full max-w-lg max-h-[90vh] overflow-y-auto z-10 animate-modal">
  977. <!-- Header -->
  978. <div id="modal-header" class="sticky top-0 z-20 bg-white border-b border-gray-100 px-6 py-4 flex items-center justify-between rounded-t-2xl">
  979. <div class="flex items-center gap-3">
  980. <!-- Étapes pills -->
  981. <div class="flex items-center gap-1 text-xs" id="modal-steps">
  982. <span class="modal-step-pill active w-6 h-6 rounded-full bg-[#006633] text-white flex items-center justify-center font-black text-[10px]" data-step="1">1</span>
  983. <div class="w-4 h-px bg-gray-200"></div>
  984. <span class="modal-step-pill w-6 h-6 rounded-full bg-gray-200 text-gray-400 flex items-center justify-center font-black text-[10px]" data-step="2">2</span>
  985. <div class="w-4 h-px bg-gray-200"></div>
  986. <span class="modal-step-pill w-6 h-6 rounded-full bg-gray-200 text-gray-400 flex items-center justify-center font-black text-[10px]" data-step="3">3</span>
  987. </div>
  988. <span class="text-sm font-semibold text-gray-600" id="modal-step-label">Récapitulatif</span>
  989. </div>
  990. <button type="button" onclick="closeCheckoutModal()"
  991. class="w-8 h-8 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-gray-500 transition">
  992. <i class="fas fa-times text-xs"></i>
  993. </button>
  994. </div>
  995. <!-- Corps du modal -->
  996. <div class="px-6 py-6">
  997. {# ── ÉTAPE 1 : Récapitulatif ── #}
  998. <div id="modal-step-1" class="modal-step-content">
  999. <!-- Badge plan sélectionné -->
  1000. <div id="modal-plan-badge" class="rounded-xl p-4 mb-5 flex items-center gap-3">
  1001. <div id="modal-plan-icon" class="w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0">
  1002. <i class="fas fa-building text-white"></i>
  1003. </div>
  1004. <div>
  1005. <p class="text-xs font-bold uppercase tracking-wider" id="modal-plan-sublabel">Abonnement</p>
  1006. <p class="font-black text-lg" id="modal-plan-name">Standard</p>
  1007. </div>
  1008. <div class="ml-auto text-right">
  1009. <p class="font-black text-xl" id="modal-plan-price">40 000 KMF</p>
  1010. <p class="text-xs opacity-70" id="modal-plan-period">/ an · 1ère année</p>
  1011. </div>
  1012. </div>
  1013. <!-- Durée -->
  1014. <div class="mb-5">
  1015. <label class="block text-sm font-bold text-gray-700 mb-3">Durée</label>
  1016. <div class="grid grid-cols-2 gap-3">
  1017. <label class="cursor-pointer">
  1018. <input type="radio" name="modal-duree" value="annuel" class="sr-only" checked>
  1019. <div class="modal-dur-card border-2 border-[#006633] bg-emerald-50/50 rounded-xl p-3 text-center transition-all">
  1020. <p class="text-xs font-bold text-gray-700 mb-1">Annuel <span class="bg-[#006633] text-white text-[9px] px-1.5 py-0.5 rounded-full ml-1">−20%</span></p>
  1021. <p class="font-black text-[#006633] text-base modal-dur-price" data-std-annuel="40 000" data-pre-annuel="72 000" data-std-mensuel="4 500" data-pre-mensuel="8 000"></p>
  1022. <p class="text-[10px] text-gray-400">KMF / an</p>
  1023. </div>
  1024. </label>
  1025. <label class="cursor-pointer">
  1026. <input type="radio" name="modal-duree" value="mensuel" class="sr-only">
  1027. <div class="modal-dur-card border-2 border-gray-200 bg-white rounded-xl p-3 text-center transition-all hover:border-gray-300">
  1028. <p class="text-xs font-bold text-gray-700 mb-1">Mensuel</p>
  1029. <p class="font-black text-gray-800 text-base modal-dur-mensuel-price"></p>
  1030. <p class="text-[10px] text-gray-400">KMF / mois</p>
  1031. </div>
  1032. </label>
  1033. </div>
  1034. </div>
  1035. <!-- Informations -->
  1036. <div class="space-y-3 mb-5">
  1037. <div class="grid grid-cols-2 gap-3">
  1038. <div>
  1039. <label class="block text-xs text-gray-500 mb-1">Prénom <span class="text-red-400">*</span></label>
  1040. <input type="text" id="m-prenom" placeholder="Mohammed"
  1041. class="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#006633]/40 focus:border-[#006633]">
  1042. </div>
  1043. <div>
  1044. <label class="block text-xs text-gray-500 mb-1">Nom <span class="text-red-400">*</span></label>
  1045. <input type="text" id="m-nom" placeholder="Said"
  1046. class="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#006633]/40 focus:border-[#006633]">
  1047. </div>
  1048. </div>
  1049. <div>
  1050. <label class="block text-xs text-gray-500 mb-1">E-mail <span class="text-red-400">*</span></label>
  1051. <input type="email" id="m-email" placeholder="votre@email.com"
  1052. class="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#006633]/40 focus:border-[#006633]">
  1053. </div>
  1054. <div>
  1055. <label class="block text-xs text-gray-500 mb-1">Organisation <span class="text-gray-400">(optionnel)</span></label>
  1056. <input type="text" id="m-org" placeholder="Ministère, cabinet, entreprise..."
  1057. class="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#006633]/40 focus:border-[#006633]">
  1058. </div>
  1059. </div>
  1060. <button type="button" onclick="modalGoStep(2)"
  1061. class="w-full bg-gradient-to-r from-[#006633] to-[#008040] text-white py-3.5 rounded-xl font-bold text-sm hover:from-[#004d26] transition shadow-md flex items-center justify-center gap-2">
  1062. Continuer vers le paiement <i class="fas fa-arrow-right"></i>
  1063. </button>
  1064. </div>
  1065. {# ── ÉTAPE 2 : Paiement ── #}
  1066. <div id="modal-step-2" class="modal-step-content hidden">
  1067. <button type="button" onclick="modalGoStep(1)"
  1068. class="flex items-center gap-2 text-gray-400 hover:text-gray-600 text-xs mb-5 transition">
  1069. <i class="fas fa-arrow-left"></i> Retour
  1070. </button>
  1071. <h3 class="text-base font-black text-gray-800 mb-4">Mode de paiement</h3>
  1072. <div class="space-y-3 mb-5">
  1073. <!-- Huri Money -->
  1074. <label class="cursor-pointer block">
  1075. <input type="radio" name="modal-methode" value="huri_money" class="sr-only" checked>
  1076. <div class="modal-pay-card border-2 border-orange-400 bg-orange-50/40 rounded-xl p-3.5 flex items-center gap-3 transition-all">
  1077. <div class="w-10 h-10 bg-orange-500 rounded-xl flex items-center justify-center flex-shrink-0">
  1078. <i class="fas fa-mobile-alt text-white"></i>
  1079. </div>
  1080. <div class="flex-1">
  1081. <p class="font-bold text-gray-800 text-sm">Huri Money</p>
  1082. <p class="text-xs text-gray-500">Mobile Money · Comores Telecom</p>
  1083. </div>
  1084. <div class="w-4 h-4 rounded-full border-2 border-orange-400 flex items-center justify-center modal-radio-dot">
  1085. <div class="w-2 h-2 rounded-full bg-orange-400"></div>
  1086. </div>
  1087. </div>
  1088. </label>
  1089. <!-- Mvola -->
  1090. <label class="cursor-pointer block">
  1091. <input type="radio" name="modal-methode" value="mvola" class="sr-only">
  1092. <div class="modal-pay-card border-2 border-gray-200 bg-white rounded-xl p-3.5 flex items-center gap-3 transition-all hover:border-gray-300">
  1093. <div class="w-10 h-10 bg-green-600 rounded-xl flex items-center justify-center flex-shrink-0">
  1094. <i class="fas fa-mobile-alt text-white"></i>
  1095. </div>
  1096. <div class="flex-1">
  1097. <p class="font-bold text-gray-800 text-sm">Mvola</p>
  1098. <p class="text-xs text-gray-500">Mobile Money · Telma Comores</p>
  1099. </div>
  1100. <div class="w-4 h-4 rounded-full border-2 border-gray-300 flex items-center justify-center modal-radio-dot"></div>
  1101. </div>
  1102. </label>
  1103. <!-- Holo -->
  1104. <label class="cursor-pointer block">
  1105. <input type="radio" name="modal-methode" value="holo" class="sr-only">
  1106. <div class="modal-pay-card border-2 border-gray-200 bg-white rounded-xl p-3.5 flex items-center gap-3 transition-all hover:border-gray-300">
  1107. <div class="w-10 h-10 bg-blue-600 rounded-xl flex items-center justify-center flex-shrink-0">
  1108. <i class="fas fa-mobile-alt text-white"></i>
  1109. </div>
  1110. <div class="flex-1">
  1111. <p class="font-bold text-gray-800 text-sm">Holo</p>
  1112. <p class="text-xs text-gray-500">Mobile Money · Holo Comores</p>
  1113. </div>
  1114. <div class="w-4 h-4 rounded-full border-2 border-gray-300 flex items-center justify-center modal-radio-dot"></div>
  1115. </div>
  1116. </label>
  1117. <!-- Visa désactivé -->
  1118. <div class="border-2 border-dashed border-gray-200 rounded-xl p-3.5 flex items-center gap-3 opacity-50 cursor-not-allowed">
  1119. <div class="w-10 h-10 bg-indigo-100 rounded-xl flex items-center justify-center flex-shrink-0">
  1120. <i class="fas fa-credit-card text-indigo-400"></i>
  1121. </div>
  1122. <div class="flex-1">
  1123. <p class="font-bold text-gray-500 text-sm">Visa / Mastercard</p>
  1124. <p class="text-xs text-gray-400">Bientôt disponible</p>
  1125. </div>
  1126. <span class="text-[10px] bg-gray-100 text-gray-400 px-2 py-0.5 rounded-full font-semibold">À venir</span>
  1127. </div>
  1128. </div>
  1129. <!-- Numéro téléphone -->
  1130. <div class="bg-gray-50 border border-gray-200 rounded-xl p-4 mb-5">
  1131. <label class="block text-sm font-bold text-gray-700 mb-2">
  1132. Numéro <span id="modal-provider-label" class="text-[#006633]">Huri Money</span>
  1133. </label>
  1134. <div class="flex gap-2">
  1135. <div class="flex items-center gap-1.5 bg-white border border-gray-200 rounded-xl px-3 py-2.5 flex-shrink-0">
  1136. <img src="https://flagcdn.com/w20/km.png" alt="+269" class="w-4 rounded-sm">
  1137. <span class="text-sm text-gray-600 font-semibold">+269</span>
  1138. </div>
  1139. <input type="tel" id="m-phone" placeholder="321 12 34" maxlength="10"
  1140. class="flex-1 border border-gray-200 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#006633]/40 focus:border-[#006633]">
  1141. </div>
  1142. <p class="text-[10px] text-gray-400 mt-1.5">
  1143. <i class="fas fa-info-circle mr-1"></i> Vous recevrez une notification pour confirmer.
  1144. </p>
  1145. </div>
  1146. <!-- Récapitulatif mini -->
  1147. <div class="bg-gray-50 border border-gray-100 rounded-xl p-3.5 mb-5 flex items-center justify-between text-sm">
  1148. <div>
  1149. <p class="font-semibold text-gray-700" id="modal-recap-plan">Standard · Annuel</p>
  1150. <p class="text-xs text-gray-400">1ère année — remise −20% appliquée</p>
  1151. </div>
  1152. <p class="font-black text-[#006633] text-lg" id="modal-recap-prix">40 000 KMF</p>
  1153. </div>
  1154. <!-- CGU -->
  1155. <label class="flex items-start gap-2.5 cursor-pointer mb-5">
  1156. <input type="checkbox" id="m-cgu"
  1157. class="mt-0.5 w-4 h-4 rounded border-gray-300 text-[#006633] focus:ring-[#006633] flex-shrink-0">
  1158. <span class="text-xs text-gray-500 leading-relaxed">
  1159. J'accepte les
  1160. <a href="#" class="text-[#006633] font-semibold hover:underline">CGU</a>
  1161. et la
  1162. <a href="#" class="text-[#006633] font-semibold hover:underline">Politique de confidentialité</a>.
  1163. </span>
  1164. </label>
  1165. <button type="button" id="modal-btn-pay"
  1166. class="w-full bg-gradient-to-r from-[#006633] to-[#008040] text-white py-4 rounded-xl font-black text-base hover:from-[#004d26] transition shadow-lg flex items-center justify-center gap-2">
  1167. <i class="fas fa-lock text-sm"></i>
  1168. <span id="modal-btn-pay-label">Payer 40 000 KMF</span>
  1169. </button>
  1170. <p class="text-center text-[10px] text-gray-400 mt-2">
  1171. <i class="fas fa-shield-alt mr-1 text-[#006633]"></i> Paiement sécurisé
  1172. </p>
  1173. </div>
  1174. {# ── ÉTAPE 3 : Confirmation ── #}
  1175. <div id="modal-step-3" class="modal-step-content hidden text-center py-4">
  1176. <div class="w-16 h-16 bg-emerald-100 rounded-full flex items-center justify-center mx-auto mb-4">
  1177. <i class="fas fa-check-circle text-3xl text-[#006633]"></i>
  1178. </div>
  1179. <h3 class="text-xl font-black text-gray-800 mb-1">Abonnement activé !</h3>
  1180. <p class="text-gray-500 text-sm mb-1">
  1181. Votre abonnement <strong id="modal-confirm-plan" class="text-gray-800">Standard</strong> est actif.
  1182. </p>
  1183. <p class="text-xs text-gray-400 mb-5">
  1184. Réf : <span id="modal-confirm-ref" class="font-mono font-semibold text-gray-600"></span>
  1185. </p>
  1186. <div class="bg-gray-50 rounded-xl border border-gray-100 p-4 text-left text-sm mb-5 space-y-2">
  1187. <div class="flex justify-between">
  1188. <span class="text-gray-500">Plan</span>
  1189. <span class="font-semibold text-gray-800" id="modal-conf-plan2">Standard</span>
  1190. </div>
  1191. <div class="flex justify-between">
  1192. <span class="text-gray-500">Durée</span>
  1193. <span class="font-semibold text-gray-800" id="modal-conf-duree">Annuel</span>
  1194. </div>
  1195. <div class="flex justify-between">
  1196. <span class="text-gray-500">Montant</span>
  1197. <span class="font-bold text-[#006633]" id="modal-conf-montant">40 000 KMF</span>
  1198. </div>
  1199. <div class="flex justify-between">
  1200. <span class="text-gray-500">Renouvellement</span>
  1201. <span class="font-semibold text-gray-800" id="modal-conf-renouvellement">—</span>
  1202. </div>
  1203. </div>
  1204. <div class="flex flex-col gap-3">
  1205. <a href="{{ path('app_dashboard') }}"
  1206. class="block w-full bg-[#006633] text-white py-3 rounded-xl font-bold text-sm hover:bg-[#004d26] transition">
  1207. <i class="fas fa-tachometer-alt mr-2"></i> Accéder à mon espace
  1208. </a>
  1209. <button type="button" onclick="closeCheckoutModal()"
  1210. class="block w-full border border-gray-200 text-gray-600 py-3 rounded-xl font-semibold text-sm hover:bg-gray-50 transition">
  1211. Fermer
  1212. </button>
  1213. </div>
  1214. </div>
  1215. </div>
  1216. </div>
  1217. </div>
  1218. <style>
  1219. @keyframes modalIn {
  1220. from { opacity: 0; transform: scale(0.96) translateY(10px); }
  1221. to { opacity: 1; transform: scale(1) translateY(0); }
  1222. }
  1223. .animate-modal { animation: modalIn 0.25s ease-out; }
  1224. </style>
  1225. <script>
  1226. (function () {
  1227. const PRIX = {
  1228. standard: { annuel: 40000, mensuel: 4500, plein: 50000 },
  1229. premium: { annuel: 72000, mensuel: 8000, plein: 90000 },
  1230. };
  1231. const LABELS = { standard: 'Standard', premium: 'Premium' };
  1232. const PROVIDERS = { huri_money: 'Huri Money', mvola: 'Mvola', holo: 'Holo' };
  1233. let currentPlan = 'standard';
  1234. let currentDuree = 'annuel';
  1235. // ── Ouvrir / fermer ────────────────────────────────────────────────────────
  1236. window.openCheckoutModal = function (plan) {
  1237. currentPlan = plan || 'standard';
  1238. currentDuree = 'annuel';
  1239. modalGoStep(1);
  1240. updateModalPlanUI();
  1241. updatePrices();
  1242. const modal = document.getElementById('checkout-modal');
  1243. modal.classList.remove('hidden');
  1244. modal.classList.add('flex');
  1245. document.body.style.overflow = 'hidden';
  1246. };
  1247. window.closeCheckoutModal = function () {
  1248. const modal = document.getElementById('checkout-modal');
  1249. modal.classList.add('hidden');
  1250. modal.classList.remove('flex');
  1251. document.body.style.overflow = '';
  1252. };
  1253. // Fermer avec Escape
  1254. document.addEventListener('keydown', e => {
  1255. if (e.key === 'Escape') closeCheckoutModal();
  1256. });
  1257. // ── Navigation étapes ──────────────────────────────────────────────────────
  1258. window.modalGoStep = function (n) {
  1259. if (n === 2) {
  1260. const prenom = document.getElementById('m-prenom')?.value.trim();
  1261. const nom = document.getElementById('m-nom')?.value.trim();
  1262. const email = document.getElementById('m-email')?.value.trim();
  1263. if (!prenom || !nom) { alert('Veuillez renseigner votre prénom et nom.'); return; }
  1264. if (!email || !email.includes('@')) { alert('Adresse e-mail invalide.'); return; }
  1265. }
  1266. document.querySelectorAll('.modal-step-content').forEach(s => s.classList.add('hidden'));
  1267. document.getElementById('modal-step-' + n)?.classList.remove('hidden');
  1268. // Étapes pills
  1269. const pills = document.querySelectorAll('.modal-step-pill');
  1270. pills.forEach(pill => {
  1271. const s = parseInt(pill.dataset.step);
  1272. if (s < n) {
  1273. pill.className = 'modal-step-pill w-6 h-6 rounded-full bg-[#006633] text-white flex items-center justify-center font-black text-[10px]';
  1274. pill.innerHTML = '<i class="fas fa-check text-[9px]"></i>';
  1275. } else if (s === n) {
  1276. pill.className = 'modal-step-pill w-6 h-6 rounded-full bg-[#006633] text-white flex items-center justify-center font-black text-[10px]';
  1277. pill.textContent = s;
  1278. } else {
  1279. pill.className = 'modal-step-pill w-6 h-6 rounded-full bg-gray-200 text-gray-400 flex items-center justify-center font-black text-[10px]';
  1280. pill.textContent = s;
  1281. }
  1282. });
  1283. const labels = { 1: 'Récapitulatif', 2: 'Paiement', 3: 'Confirmation' };
  1284. const labelEl = document.getElementById('modal-step-label');
  1285. if (labelEl) labelEl.textContent = labels[n];
  1286. };
  1287. // ── UI selon le plan ───────────────────────────────────────────────────────
  1288. function updateModalPlanUI() {
  1289. const isPremium = currentPlan === 'premium';
  1290. const badge = document.getElementById('modal-plan-badge');
  1291. const icon = document.getElementById('modal-plan-icon');
  1292. const sub = document.getElementById('modal-plan-sublabel');
  1293. const name = document.getElementById('modal-plan-name');
  1294. if (isPremium) {
  1295. badge.className = 'rounded-xl p-4 mb-5 flex items-center gap-3 bg-gradient-to-r from-[#00391a] to-[#006633] text-white';
  1296. icon.className = 'w-10 h-10 bg-white/20 rounded-xl flex items-center justify-center flex-shrink-0';
  1297. icon.innerHTML = '<i class="fas fa-crown text-amber-400"></i>';
  1298. sub.className = 'text-xs font-bold uppercase tracking-wider text-emerald-300';
  1299. name.className = 'font-black text-lg text-white';
  1300. } else {
  1301. badge.className = 'rounded-xl p-4 mb-5 flex items-center gap-3 bg-emerald-50 border border-emerald-200';
  1302. icon.className = 'w-10 h-10 bg-emerald-100 rounded-xl flex items-center justify-center flex-shrink-0';
  1303. icon.innerHTML = '<i class="fas fa-building text-[#006633]"></i>';
  1304. sub.className = 'text-xs font-bold uppercase tracking-wider text-[#006633]';
  1305. name.className = 'font-black text-lg text-gray-800';
  1306. }
  1307. if (name) name.textContent = LABELS[currentPlan];
  1308. }
  1309. // ── Mise à jour prix ───────────────────────────────────────────────────────
  1310. function updatePrices() {
  1311. const prix = PRIX[currentPlan][currentDuree];
  1312. const isPremium = currentPlan === 'premium';
  1313. // Badge header
  1314. const priceEl = document.getElementById('modal-plan-price');
  1315. const periodEl = document.getElementById('modal-plan-period');
  1316. if (priceEl) priceEl.textContent = prix.toLocaleString('fr-FR') + ' KMF';
  1317. if (periodEl) periodEl.textContent = currentDuree === 'annuel' ? '/ an · 1ère année' : '/ mois';
  1318. // Cards durée
  1319. const annuelPriceEl = document.querySelector('.modal-dur-price');
  1320. const mensuelPriceEl = document.querySelector('.modal-dur-mensuel-price');
  1321. if (annuelPriceEl) annuelPriceEl.textContent = PRIX[currentPlan].annuel.toLocaleString('fr-FR');
  1322. if (mensuelPriceEl) mensuelPriceEl.textContent = PRIX[currentPlan].mensuel.toLocaleString('fr-FR');
  1323. // Récap étape 2
  1324. const recapPlan = document.getElementById('modal-recap-plan');
  1325. const recapPrix = document.getElementById('modal-recap-prix');
  1326. if (recapPlan) recapPlan.textContent = LABELS[currentPlan] + ' · ' + (currentDuree === 'annuel' ? 'Annuel' : 'Mensuel');
  1327. if (recapPrix) recapPrix.textContent = prix.toLocaleString('fr-FR') + ' KMF';
  1328. // Bouton payer
  1329. const btnLabel = document.getElementById('modal-btn-pay-label');
  1330. if (btnLabel) btnLabel.textContent = 'Payer ' + prix.toLocaleString('fr-FR') + ' KMF';
  1331. // Couleur bouton payer si premium
  1332. const btnPay = document.getElementById('modal-btn-pay');
  1333. if (btnPay) {
  1334. if (isPremium) {
  1335. btnPay.className = btnPay.className.replace('from-[#006633] to-[#008040]', 'from-amber-500 to-amber-400').replace('hover:from-[#004d26]', 'hover:from-amber-600');
  1336. } else {
  1337. btnPay.className = btnPay.className.replace('from-amber-500 to-amber-400', 'from-[#006633] to-[#008040]').replace('hover:from-amber-600', 'hover:from-[#004d26]');
  1338. }
  1339. }
  1340. }
  1341. // ── Changement durée ──────────────────────────────────────────────────────
  1342. document.querySelectorAll('input[name="modal-duree"]').forEach(radio => {
  1343. radio.addEventListener('change', function () {
  1344. currentDuree = this.value;
  1345. updatePrices();
  1346. document.querySelectorAll('.modal-dur-card').forEach(c => {
  1347. c.className = c.className
  1348. .replace('border-[#006633] bg-emerald-50/50', 'border-gray-200 bg-white')
  1349. .replace('border-gray-200 bg-white', 'border-gray-200 bg-white');
  1350. });
  1351. const active = this.closest('label').querySelector('.modal-dur-card');
  1352. active.className = active.className.replace('border-gray-200 bg-white', 'border-[#006633] bg-emerald-50/50');
  1353. });
  1354. });
  1355. // ── Changement méthode paiement ───────────────────────────────────────────
  1356. const COLORS = { huri_money: ['orange-400', 'orange-50/40'], mvola: ['green-600', 'green-50/40'], holo: ['blue-600', 'blue-50/40'] };
  1357. document.querySelectorAll('input[name="modal-methode"]').forEach(radio => {
  1358. radio.addEventListener('change', function () {
  1359. document.querySelectorAll('.modal-pay-card').forEach(c => {
  1360. c.className = 'modal-pay-card border-2 border-gray-200 bg-white rounded-xl p-3.5 flex items-center gap-3 transition-all hover:border-gray-300';
  1361. });
  1362. document.querySelectorAll('.modal-radio-dot').forEach(d => d.innerHTML = '');
  1363. const [border, bg] = COLORS[this.value] || ['gray-400', 'gray-50'];
  1364. const card = this.closest('label').querySelector('.modal-pay-card');
  1365. card.className = `modal-pay-card border-2 border-${border} bg-${bg} rounded-xl p-3.5 flex items-center gap-3 transition-all`;
  1366. const dot = this.closest('label').querySelector('.modal-radio-dot');
  1367. dot.innerHTML = `<div class="w-2 h-2 rounded-full bg-${border}"></div>`;
  1368. dot.className = `w-4 h-4 rounded-full border-2 border-${border} flex items-center justify-center modal-radio-dot`;
  1369. const label = document.getElementById('modal-provider-label');
  1370. if (label) label.textContent = PROVIDERS[this.value] || this.value;
  1371. });
  1372. });
  1373. // ── Bouton payer ───────────────────────────────────────────────────────────
  1374. document.getElementById('modal-btn-pay')?.addEventListener('click', async function () {
  1375. const cgu = document.getElementById('m-cgu')?.checked;
  1376. const phone = document.getElementById('m-phone')?.value.trim().replace(/\s/g, '');
  1377. if (!cgu) { alert('Veuillez accepter les CGU.'); return; }
  1378. if (phone.length < 7) { alert('Numéro de téléphone invalide.'); return; }
  1379. this.disabled = true;
  1380. this.innerHTML = '<svg class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>&nbsp;Traitement…';
  1381. // TODO : remplacer par l'appel API de paiement réel
  1382. await new Promise(r => setTimeout(r, 1800));
  1383. // Remplir confirmation
  1384. const ref = 'JO-' + Date.now().toString(36).toUpperCase();
  1385. const prix = PRIX[currentPlan][currentDuree];
  1386. const date = new Date();
  1387. currentDuree === 'annuel' ? date.setFullYear(date.getFullYear() + 1) : date.setMonth(date.getMonth() + 1);
  1388. document.getElementById('modal-confirm-plan').textContent = LABELS[currentPlan];
  1389. document.getElementById('modal-confirm-ref').textContent = ref;
  1390. document.getElementById('modal-conf-plan2').textContent = LABELS[currentPlan];
  1391. document.getElementById('modal-conf-duree').textContent = currentDuree === 'annuel' ? 'Annuel' : 'Mensuel';
  1392. document.getElementById('modal-conf-montant').textContent = prix.toLocaleString('fr-FR') + ' KMF';
  1393. document.getElementById('modal-conf-renouvellement').textContent = date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' });
  1394. modalGoStep(3);
  1395. });
  1396. })();
  1397. </script>
  1398. {% endblock %}