{"id":17752,"date":"2026-09-23T13:27:56","date_gmt":"2026-09-23T13:27:56","guid":{"rendered":"https:\/\/papslogistics.com\/?page_id=17752"},"modified":"2026-09-23T13:49:44","modified_gmt":"2026-09-23T13:49:44","slug":"simulateur-devis","status":"publish","type":"page","link":"https:\/\/papslogistics.com\/en\/simulateur-devis\/","title":{"rendered":"simulateur-devis"},"content":{"rendered":"<div class=\"grve-section grve-row-section grve-fullwidth-background grve-padding-top-1x grve-padding-bottom-1x grve-with-bg-transparent\" data-header-color=\"default\"><div class=\"grve-container\"><div class=\"grve-row grve-bookmark grve-columns-gap-default grve-mobile-vertical-gap-30\"><div class=\"grve-column wpb_column grve-bookmark grve-column-1  grve-with-bg-transparent\"><div class=\"grve-column-wrapper\"><div class=\"grve-column-content\" >\r\n\t<div class=\"wpb_raw_code wpb_content_element wpb_raw_html\" >\r\n\t\t<div class=\"wpb_wrapper\">\r\n\t\t\t<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Simulateur tarifaire logistique<\/title>\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Poppins:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n<style>\n:root{\n  --navy:#272A5E;\n  --navy-deep:#1C1F4A;\n  --navy-soft:#3A3E7A;\n  --yellow:#FBC02D;\n  --yellow-deep:#E3A50F;\n  --paper:#F4F5F9;\n  --card:#FFFFFF;\n  --ink:#1F2233;\n  --muted:#6E7288;\n  --line:#DFE2EC;\n  --ok:#16794C;\n  --radius:14px;\n}\n*{box-sizing:border-box}\nhtml,body{margin:0;padding:0}\nbody{\n  font-family:'Poppins','Segoe UI',system-ui,-apple-system,sans-serif;\n  background:var(--paper); color:var(--ink); font-size:15px; line-height:1.5;\n}\nbutton,input,select{font-family:inherit;font-size:inherit}\n\n\/* ---------- Header ---------- *\/\nheader{background:var(--navy); color:#fff; padding:18px 22px}\n.head-wrap{max-width:1240px;margin:0 auto;display:flex;align-items:center;gap:16px;flex-wrap:wrap}\n.brand{display:flex;align-items:center;gap:12px}\n.logo-box{width:46px;height:46px;display:flex;align-items:center;justify-content:center;color:var(--yellow)}\n.logo-box svg{width:100%;height:100%}\n.logo-box img{width:100%;height:100%;object-fit:contain}\n.brand-name{font-weight:700;font-size:20px;letter-spacing:-.3px;line-height:1.1}\n.brand-sub{font-size:12.5px;color:#C9CCE4;font-weight:400}\n.head-actions{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}\n.btn{border:0;border-radius:10px;padding:9px 14px;cursor:pointer;font-weight:600;transition:.15s background}\n.btn-y{background:var(--yellow);color:var(--navy-deep)}\n.btn-y:hover{background:var(--yellow-deep)}\n.btn-ghost{background:rgba(255,255,255,.12);color:#fff}\n.btn-ghost:hover{background:rgba(255,255,255,.22)}\n.btn-line{background:#fff;color:var(--navy);border:1px solid var(--line)}\n.btn-line:hover{background:#F0F1F7}\n.btn-sm{padding:5px 10px;font-size:13px;border-radius:8px}\n\n\/* ---------- Indicateur de fra\u00eecheur des tarifs ---------- *\/\n.status{display:flex;align-items:center;gap:8px;font-size:12.5px;color:#C9CCE4}\n.status .dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto;background:#8A8FB5}\n.status.ok   .dot{background:#4ADE80}\n.status.warn .dot{background:var(--yellow)}\n.status.load .dot{background:#8A8FB5;animation:pulse 1.1s ease-in-out infinite}\n@keyframes pulse{0%,100%{opacity:.3}50%{opacity:1}}\n@media(prefers-reduced-motion:reduce){.status.load .dot{animation:none;opacity:.6}}\n\n\/* ---------- Tabs ---------- *\/\nnav.tabs{background:var(--navy-deep);padding:0 22px;overflow-x:auto}\n.tabs-wrap{max-width:1240px;margin:0 auto;display:flex;gap:2px}\n.tab{background:none;border:0;color:#B9BDDA;padding:13px 16px;cursor:pointer;font-weight:600;white-space:nowrap;border-bottom:3px solid transparent}\n.tab:hover{color:#fff}\n.tab.on{color:#fff;border-bottom-color:var(--yellow)}\n\n\/* ---------- Layout ---------- *\/\nmain{max-width:1240px;margin:0 auto;padding:22px}\n.grid2{display:grid;grid-template-columns:1fr 380px;gap:20px;align-items:start}\n@media(max-width:900px){.grid2{grid-template-columns:1fr}}\n\n\/* ---------- Total permanent (mobile) ---------- *\/\n.totalbar{display:none}\n@media(max-width:900px){\n  .totalbar{\n    display:flex;align-items:center;gap:12px;\n    position:fixed;left:0;right:0;bottom:0;z-index:40;\n    width:100%;border:0;border-top:1px solid rgba(255,255,255,.12);\n    background:var(--navy);color:#fff;text-align:left;cursor:pointer;\n    padding:12px 18px;\n    \/* Marge basse sur les \u00e9crans \u00e0 encoche \/ barre d'accueil iOS *\/\n    padding-bottom:calc(12px + env(safe-area-inset-bottom, 0px));\n    box-shadow:0 -6px 20px rgba(31,34,51,.18);\n    transform:translateY(100%);transition:transform .22s ease;\n  }\n  .totalbar.up{transform:translateY(0)}\n  .totalbar:active{background:var(--navy-deep)}\n  .tb-txt{display:flex;flex-direction:column;min-width:0}\n  .tb-lbl{font-size:12px;color:#C9CCE4;font-weight:500;line-height:1.3}\n  .tb-sub{font-size:12.5px;font-weight:600;line-height:1.3;\n          white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .tb-val{margin-left:auto;font-size:19px;font-weight:700;\n          color:var(--yellow);font-variant-numeric:tabular-nums;white-space:nowrap}\n  .tb-arrow{width:18px;height:18px;flex:0 0 auto;color:#9AA0C8}\n  \/* Aucun contenu ne doit finir sous la barre, quel que soit le dernier bloc *\/\n  body.has-totalbar{padding-bottom:calc(80px + env(safe-area-inset-bottom, 0px))}\n}\n@media(prefers-reduced-motion:reduce){.totalbar{transition:none}}\n@media print{.totalbar{display:none !important}}\n.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:20px;margin-bottom:18px}\n.card h2{margin:0 0 4px;font-size:17px;font-weight:600}\n.card .hint{color:var(--muted);font-size:13px;margin:0 0 16px}\n.row{display:grid;grid-template-columns:1fr 1fr;gap:14px}\n@media(max-width:560px){.row{grid-template-columns:1fr}}\n.field{margin-bottom:14px}\n.field label{display:block;font-size:13px;font-weight:600;margin-bottom:5px;color:#3B3F55}\n.field .sub{font-weight:400;color:var(--muted);font-size:12px}\nselect,input[type=number],input[type=text]{\n  width:100%;padding:10px 11px;border:1px solid var(--line);border-radius:9px;background:#fff;color:var(--ink)\n}\nselect:focus,input:focus{outline:2px solid var(--yellow);outline-offset:1px;border-color:var(--yellow)}\n.check{display:flex;align-items:center;gap:9px;padding:9px 0;font-size:14px;cursor:pointer}\n.check input{width:17px;height:17px;accent-color:var(--navy)}\nfieldset{border:1px solid var(--line);border-radius:11px;padding:12px 14px;margin:0 0 14px}\nlegend{font-size:13px;font-weight:600;padding:0 6px;color:var(--navy)}\n\n\/* ---------- R\u00e9cap ---------- *\/\n.recap{position:sticky;top:18px;background:var(--navy);color:#fff;border-radius:var(--radius);padding:20px;border:0}\n.recap h2{color:#fff;font-size:16px;margin:0 0 14px}\n.rline{display:flex;justify-content:space-between;gap:12px;padding:7px 0;border-bottom:1px dashed rgba(255,255,255,.18);font-size:13.5px}\n.rline .l{color:#D5D8EC}\n.rline .v{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:500}\n.rline.sum{border-bottom:0;font-weight:600;font-size:15px;padding-top:12px}\n.rtotal{margin-top:14px;background:var(--yellow);color:var(--navy-deep);border-radius:11px;padding:14px 16px;display:flex;justify-content:space-between;align-items:baseline}\n.rtotal .lbl{font-weight:600;font-size:14px}\n.rtotal .val{font-weight:700;font-size:22px;font-variant-numeric:tabular-nums}\n.rnote{margin-top:12px;font-size:12px;color:#BFC3DE;line-height:1.5}\n.recap .btn{width:100%;margin-top:12px}\n\n\/* ---------- Tables ---------- *\/\n.tablewrap{overflow-x:auto;border:1px solid var(--line);border-radius:11px}\ntable{border-collapse:collapse;width:100%;font-size:13.5px}\nth,td{padding:7px 9px;border-bottom:1px solid var(--line);text-align:right;white-space:nowrap}\nth:first-child,td:first-child,th.txt,td.txt{text-align:left}\nthead th{background:var(--navy);color:#fff;font-weight:600;position:sticky;top:0}\ntbody tr:nth-child(even){background:#FAFBFE}\ntd input{padding:5px 7px;border-radius:7px;min-width:86px;text-align:right;font-variant-numeric:tabular-nums}\ntd input.txt{text-align:left;min-width:150px}\n.del{background:#fff;border:1px solid var(--line);color:#B4364B;border-radius:7px;padding:3px 8px;cursor:pointer}\n.del:hover{background:#FDECEF}\n.subtabs{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:16px}\n.subtab{background:#fff;border:1px solid var(--line);border-radius:20px;padding:7px 14px;cursor:pointer;font-size:13.5px;font-weight:500}\n.subtab.on{background:var(--navy);color:#fff;border-color:var(--navy)}\n.tag{display:inline-block;background:#EEF0F8;color:var(--navy);border-radius:6px;padding:2px 8px;font-size:12px;font-weight:600}\n.saved{color:var(--ok);font-size:13px;font-weight:600;opacity:0;transition:opacity .3s}\n.saved.on{opacity:1}\nfooter{max-width:1240px;margin:0 auto;padding:10px 22px 40px;color:var(--muted);font-size:12.5px}\n.hidden{display:none !important}\n\n\/* ---------- Impression ---------- *\/\n#printArea{display:none}\n@media print{\n  body{background:#fff}\n  header,nav.tabs,main,footer{display:none !important}\n  #printArea{display:block;padding:0}\n  .p-head{display:flex;align-items:center;gap:14px;border-bottom:3px solid var(--navy);padding-bottom:12px;margin-bottom:18px}\n  .p-logo{width:52px;height:52px;color:var(--navy)}\n  .p-title{font-size:20px;font-weight:700;color:var(--navy)}\n  #printArea table{width:100%;font-size:13px}\n  #printArea th{background:#272A5E !important;-webkit-print-color-adjust:exact;print-color-adjust:exact}\n  .p-total{margin-top:14px;font-size:17px;font-weight:700;text-align:right;color:var(--navy)}\n  .p-note{margin-top:20px;font-size:11.5px;color:#555}\n}\n<\/style>\n<\/head>\n<body>\n\n<header>\n  <div class=\"head-wrap\">\n    <div class=\"brand\">\n      <div class=\"logo-box\" id=\"logoBox\">\n        <svg viewbox=\"0 0 120 100\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" aria-hidden=\"true\">\n          <g fill=\"none\" stroke=\"currentColor\" stroke-width=\"4.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n            <circle cx=\"60\" cy=\"14\" r=\"4.5\"\/>\n            <path d=\"M60 19v5\"\/>\n            <path d=\"M60 24c-11.5 0-20.5 9-20.5 20.5 0 13-3.5 19.5-8 24h57c-4.5-4.5-8-11-8-24C80.5 33 71.5 24 60 24Z\"\/>\n            <path d=\"M52.5 74a7.5 7.5 0 0 0 15 0\"\/>\n            <path d=\"M40 44c-9-11-21-16-34-14 7 5 9.5 11.5 9 18.5 8.5-3 16.5-3.5 25-4.5Z\"\/>\n            <path d=\"M80 44c9-11 21-16 34-14-7 5-9.5 11.5-9 18.5-8.5-3-16.5-3.5-25-4.5Z\"\/>\n          <\/g>\n        <\/svg>\n      <\/div>\n      <div>\n        <div class=\"brand-name\" id=\"brandName\">Paps<\/div>\n        <div class=\"brand-sub\">Simulateur de prix \u2014 services logistiques<\/div>\n      <\/div>\n    <\/div>\n    <div class=\"head-actions\">\n      <div class=\"status\" id=\"status\" hidden>\n        <span class=\"dot\" id=\"statusDot\"><\/span>\n        <span id=\"statusText\"><\/span>\n        <button class=\"btn btn-ghost btn-sm\" id=\"btnRetry\" hidden>R\u00e9essayer<\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/header>\n\n<nav class=\"tabs\"><div class=\"tabs-wrap\" id=\"tabs\"><\/div><\/nav>\n\n<main>\n  <!-- ============ MESSAGERIE ============ -->\n  <section id=\"pane-messagerie\">\n    <div class=\"grid2\">\n      <div>\n        <div class=\"card\">\n          <h2>Livraison messagerie<\/h2>\n          <p class=\"hint\">Tarif au colis selon la zone et la taille. Les tailles sont limit\u00e9es par le poids et les dimensions.<\/p>\n          <div class=\"row\">\n            <div class=\"field\">\n              <label for=\"m_ville\">Destination<\/label>\n              <select id=\"m_ville\"><\/select>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"m_taille\">Taille du colis<\/label>\n              <select id=\"m_taille\"><\/select>\n              <div class=\"sub\" id=\"m_dim\"><\/div>\n            <\/div>\n          <\/div>\n          <div class=\"row\">\n            <div class=\"field\">\n              <label for=\"m_nb\">Nombre de colis<\/label>\n              <input type=\"number\" id=\"m_nb\" value=\"1\" min=\"1\" step=\"1\">\n            <\/div>\n            <div class=\"field\">\n              <label>Zone appliqu\u00e9e<\/label>\n              <div style=\"padding-top:8px\"><span class=\"tag\" id=\"m_zone\">Dakar<\/span> <span class=\"sub\" id=\"m_km\"><\/span><\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"card\">\n          <h2>Ramassage (pickup)<\/h2>\n          <p class=\"hint\">Soit un tarif \u00e0 la commande, soit un v\u00e9hicule d\u00e9di\u00e9 mis \u00e0 disposition.<\/p>\n          <div class=\"field\">\n            <label for=\"m_pickup\">Mode de ramassage<\/label>\n            <select id=\"m_pickup\">\n              <option value=\"none\">Aucun<\/option>\n              <option value=\"cmd\">Tarif \u00e0 la commande<\/option>\n              <option value=\"veh\">V\u00e9hicule fixe d\u00e9di\u00e9<\/option>\n            <\/select>\n          <\/div>\n          <div class=\"row\" id=\"m_pickupCmdBox\" style=\"display:none\">\n            <div class=\"field\">\n              <label for=\"m_pickupTaille\">Taille \u00e0 la commande<\/label>\n              <select id=\"m_pickupTaille\"><\/select>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"m_pickupNb\">Nombre de ramassages<\/label>\n              <input type=\"number\" id=\"m_pickupNb\" value=\"1\" min=\"0\" step=\"1\">\n            <\/div>\n          <\/div>\n          <div class=\"row\" id=\"m_pickupVehBox\" style=\"display:none\">\n            <div class=\"field\">\n              <label for=\"m_pickupVeh\">V\u00e9hicule<\/label>\n              <select id=\"m_pickupVeh\">\n                <option value=\"scooter\">Scooter<\/option>\n                <option value=\"minivan\">Minivan<\/option>\n                <option value=\"van\">Van<\/option>\n              <\/select>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"m_pickupZone\">Zone de ramassage<\/label>\n              <select id=\"m_pickupZone\"><\/select>\n            <\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"card\">\n          <h2>Pr\u00e9paration de commande et services<\/h2>\n          <div class=\"row\">\n            <div class=\"field\">\n              <label class=\"check\"><input type=\"checkbox\" id=\"m_emb\"> Mise en emballage<\/label>\n              <input type=\"number\" id=\"m_embNb\" value=\"0\" min=\"0\" step=\"1\" placeholder=\"Nombre d'unit\u00e9s\">\n            <\/div>\n            <div class=\"field\">\n              <label class=\"check\"><input type=\"checkbox\" id=\"m_trt\"> Traitement de commande<\/label>\n              <input type=\"number\" id=\"m_trtNb\" value=\"0\" min=\"0\" step=\"1\" placeholder=\"Nombre de commandes\">\n            <\/div>\n          <\/div>\n          <div class=\"row\">\n            <div class=\"field\">\n              <label for=\"m_cash\">Montant \u00e0 collecter <span class=\"sub\">(cash management)<\/span><\/label>\n              <input type=\"number\" id=\"m_cash\" value=\"0\" min=\"0\" step=\"500\">\n            <\/div>\n            <div class=\"field\">\n              <label for=\"m_assur\">Valeur d\u00e9clar\u00e9e <span class=\"sub\">(assurance compl\u00e9mentaire)<\/span><\/label>\n              <input type=\"number\" id=\"m_assur\" value=\"0\" min=\"0\" step=\"1000\">\n            <\/div>\n          <\/div>\n        <\/div>\n        <div class=\"card\" data-commercial>\n          <h2>Conditions commerciales<\/h2>\n          <div class=\"row\">\n            <div class=\"field\"><label for=\"m_marge\">Marge appliqu\u00e9e (%)<\/label><input type=\"number\" id=\"m_marge\" value=\"0\" step=\"0.5\"><\/div>\n            <div class=\"field\"><label for=\"m_remise\">Remise commerciale (%)<\/label><input type=\"number\" id=\"m_remise\" value=\"0\" step=\"0.5\"><\/div>\n          <\/div>\n          <label class=\"check\"><input type=\"checkbox\" id=\"m_tva\"> Facturer avec la TVA<\/label>\n        <\/div>\n      <\/div>\n      <div><div class=\"recap\" id=\"recap-messagerie\"><\/div><\/div>\n    <\/div>\n  <\/section>\n\n  <!-- ============ MAD VEHICULE ============ -->\n  <section id=\"pane-mad\" class=\"hidden\">\n    <div class=\"grid2\">\n      <div>\n        <div class=\"card\">\n          <h2>Mise \u00e0 disposition de v\u00e9hicule<\/h2>\n          <p class=\"hint\">Petits et moyens porteurs, par destination. Carburant exclu pour les porteurs 10T et plus.<\/p>\n          <div class=\"row\">\n            <div class=\"field\">\n              <label for=\"v_ville\">Destination<\/label>\n              <select id=\"v_ville\"><\/select>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"v_type\">Type de v\u00e9hicule<\/label>\n              <select id=\"v_type\">\n                <option value=\"minivan\">Mini-Van (624 kg)<\/option>\n                <option value=\"van\">Van (1,4 T \u00e0 2 T)<\/option>\n                <option value=\"t10\">10 T<\/option>\n                <option value=\"t12\">12 T<\/option>\n                <option value=\"t15\">15 T<\/option>\n              <\/select>\n            <\/div>\n          <\/div>\n          <div class=\"row\">\n            <div class=\"field\">\n              <label for=\"v_nb\">Nombre de v\u00e9hicules \/ rotations<\/label>\n              <input type=\"number\" id=\"v_nb\" value=\"1\" min=\"1\" step=\"1\">\n            <\/div>\n            <div class=\"field\">\n              <label for=\"v_ar\">Trajet<\/label>\n              <select id=\"v_ar\">\n                <option value=\"1\">Aller simple<\/option>\n                <option value=\"2\">Aller-retour (\u00d72)<\/option>\n              <\/select>\n            <\/div>\n          <\/div>\n        <\/div>\n        <div class=\"card\">\n          <h2>Options<\/h2>\n          <div class=\"row\">\n            <div class=\"field\">\n              <label class=\"check\"><input type=\"checkbox\" id=\"v_manut\"> Manutention<\/label>\n              <select id=\"v_manutType\">\n                <option value=\"full\">Chargement et d\u00e9chargement<\/option>\n                <option value=\"half\">Un seul sens<\/option>\n              <\/select>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"v_manutRef\">Bar\u00e8me manutention<\/label>\n              <select id=\"v_manutRef\"><\/select>\n              <div class=\"sub\">Bar\u00e8me repris de la grille des axes.<\/div>\n            <\/div>\n          <\/div>\n          <div class=\"field\">\n            <label for=\"v_extra\">Frais suppl\u00e9mentaires <span class=\"sub\">(p\u00e9age, attente, hors zone\u2026)<\/span><\/label>\n            <input type=\"number\" id=\"v_extra\" value=\"0\" min=\"0\" step=\"1000\">\n          <\/div>\n        <\/div>\n        <div class=\"card\" data-commercial>\n          <h2>Conditions commerciales<\/h2>\n          <div class=\"row\">\n            <div class=\"field\"><label for=\"v_marge\">Marge appliqu\u00e9e (%)<\/label><input type=\"number\" id=\"v_marge\" value=\"0\" step=\"0.5\"><\/div>\n            <div class=\"field\"><label for=\"v_remise\">Remise commerciale (%)<\/label><input type=\"number\" id=\"v_remise\" value=\"0\" step=\"0.5\"><\/div>\n          <\/div>\n          <label class=\"check\"><input type=\"checkbox\" id=\"v_tva\"> Facturer avec la TVA<\/label>\n        <\/div>\n      <\/div>\n      <div><div class=\"recap\" id=\"recap-mad\"><\/div><\/div>\n    <\/div>\n  <\/section>\n\n  <!-- ============ AXES ============ -->\n  <section id=\"pane-axes\" class=\"hidden\">\n    <div class=\"grid2\">\n      <div>\n        <div class=\"card\">\n          <h2>Location par axe et tourn\u00e9e<\/h2>\n          <p class=\"hint\">Co\u00fbt de location du v\u00e9hicule par voyage, carburant exclu.<\/p>\n          <div class=\"row\">\n            <div class=\"field\">\n              <label for=\"a_axe\">Axe<\/label>\n              <select id=\"a_axe\"><\/select>\n              <div class=\"sub\" id=\"a_axeInfo\"><\/div>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"a_veh\">Type de v\u00e9hicule<\/label>\n              <select id=\"a_veh\"><\/select>\n            <\/div>\n          <\/div>\n          <div class=\"row\">\n            <div class=\"field\">\n              <label for=\"a_nb\">Nombre de v\u00e9hicules<\/label>\n              <input type=\"number\" id=\"a_nb\" value=\"1\" min=\"1\" step=\"1\">\n            <\/div>\n            <div class=\"field\">\n              <label for=\"a_voy\">Nombre de voyages<\/label>\n              <input type=\"number\" id=\"a_voy\" value=\"1\" min=\"1\" step=\"1\">\n            <\/div>\n          <\/div>\n        <\/div>\n        <div class=\"card\">\n          <h2>Convoyeur et manutention<\/h2>\n          <div class=\"row\">\n            <div class=\"field\">\n              <label class=\"check\"><input type=\"checkbox\" id=\"a_conv\" checked> Convoyeur (paie et frais)<\/label>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"a_manut\">Manutention<\/label>\n              <select id=\"a_manut\">\n                <option value=\"none\">Aucune<\/option>\n                <option value=\"full\">Chargement et d\u00e9chargement<\/option>\n                <option value=\"half\">Un seul sens<\/option>\n              <\/select>\n            <\/div>\n          <\/div>\n          <div class=\"field\">\n            <label for=\"a_extra\">Frais suppl\u00e9mentaires<\/label>\n            <input type=\"number\" id=\"a_extra\" value=\"0\" min=\"0\" step=\"1000\">\n          <\/div>\n        <\/div>\n        <div class=\"card\" data-commercial>\n          <h2>Conditions commerciales<\/h2>\n          <div class=\"row\">\n            <div class=\"field\"><label for=\"a_marge\">Marge appliqu\u00e9e (%)<\/label><input type=\"number\" id=\"a_marge\" value=\"0\" step=\"0.5\"><\/div>\n            <div class=\"field\"><label for=\"a_remise\">Remise commerciale (%)<\/label><input type=\"number\" id=\"a_remise\" value=\"0\" step=\"0.5\"><\/div>\n          <\/div>\n          <label class=\"check\"><input type=\"checkbox\" id=\"a_tva\"> Facturer avec la TVA<\/label>\n        <\/div>\n      <\/div>\n      <div><div class=\"recap\" id=\"recap-axes\"><\/div><\/div>\n    <\/div>\n  <\/section>\n\n  <!-- ============ GROS PORTEURS ============ -->\n  <section id=\"pane-gros\" class=\"hidden\">\n    <div class=\"grid2\">\n      <div>\n        <div class=\"card\">\n          <h2>Gros porteurs<\/h2>\n          <p class=\"hint\">Facturation au forfait par camion ou au tonnage transport\u00e9.<\/p>\n          <div class=\"row\">\n            <div class=\"field\">\n              <label for=\"g_dep\">D\u00e9partement<\/label>\n              <select id=\"g_dep\"><\/select>\n              <div class=\"sub\" id=\"g_km\"><\/div>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"g_mode\">Mode de facturation<\/label>\n              <select id=\"g_mode\">\n                <option value=\"forfait\">Forfait par camion<\/option>\n                <option value=\"tonne\">Prix \u00e0 la tonne<\/option>\n              <\/select>\n            <\/div>\n          <\/div>\n          <div class=\"row\" id=\"g_forfaitBox\">\n            <div class=\"field\">\n              <label for=\"g_ton\">Tonnage du camion<\/label>\n              <select id=\"g_ton\">\n                <option value=\"t30\">30 T<\/option>\n                <option value=\"t35\">35 T<\/option>\n                <option value=\"t40\">40 T<\/option>\n                <option value=\"t45\">45 T<\/option>\n              <\/select>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"g_nb\">Nombre de camions<\/label>\n              <input type=\"number\" id=\"g_nb\" value=\"1\" min=\"1\" step=\"1\">\n            <\/div>\n          <\/div>\n          <div class=\"field\" id=\"g_tonneBox\" style=\"display:none\">\n            <label for=\"g_qte\">Quantit\u00e9 \u00e0 livrer <span class=\"sub\">(en tonnes)<\/span><\/label>\n            <input type=\"number\" id=\"g_qte\" value=\"30\" min=\"0\" step=\"1\">\n          <\/div>\n          <div class=\"field\">\n            <label for=\"g_extra\">Frais suppl\u00e9mentaires<\/label>\n            <input type=\"number\" id=\"g_extra\" value=\"0\" min=\"0\" step=\"5000\">\n          <\/div>\n        <\/div>\n        <div class=\"card\" data-commercial>\n          <h2>Conditions commerciales<\/h2>\n          <div class=\"row\">\n            <div class=\"field\"><label for=\"g_marge\">Marge appliqu\u00e9e (%)<\/label><input type=\"number\" id=\"g_marge\" value=\"0\" step=\"0.5\"><\/div>\n            <div class=\"field\"><label for=\"g_remise\">Remise commerciale (%)<\/label><input type=\"number\" id=\"g_remise\" value=\"0\" step=\"0.5\"><\/div>\n          <\/div>\n          <label class=\"check\"><input type=\"checkbox\" id=\"g_tva\"> Facturer avec la TVA<\/label>\n        <\/div>\n      <\/div>\n      <div><div class=\"recap\" id=\"recap-gros\"><\/div><\/div>\n    <\/div>\n  <\/section>\n<\/main>\n\n<footer id=\"foot\">Montants en FCFA, hors taxe sauf mention contraire. Grille de r\u00e9f\u00e9rence : tarifs messagerie 2026 et co\u00fbts de transport par axe.<\/footer>\n\n<!-- Total permanent, mobile uniquement -->\n<button class=\"totalbar\" id=\"totalBar\" type=\"button\" hidden>\n  <span class=\"tb-txt\">\n    <span class=\"tb-lbl\" id=\"tbLabel\">Total HT<\/span>\n    <span class=\"tb-sub\" id=\"tbSub\"><\/span>\n  <\/span>\n  <span class=\"tb-val\" id=\"tbVal\">\u2014<\/span>\n  <svg class=\"tb-arrow\" viewbox=\"0 0 24 24\" aria-hidden=\"true\">\n    <path d=\"M7 10l5 5 5-5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"\n          stroke-linecap=\"round\" stroke-linejoin=\"round\"\/>\n  <\/svg>\n<\/button>\n\n<div id=\"printArea\"><\/div>\n\n<script>\n\/* =========================================================\n   0. CONFIGURATION\n   ---------------------------------------------------------\n   Seule ligne \u00e0 renseigner apr\u00e8s le d\u00e9ploiement : API_URL.\n   Coller l'URL \/exec fournie par Apps Script.\n   ========================================================= *\/\nconst CONFIG = {\n  API_URL: 'https:\/\/script.google.com\/macros\/s\/AKfycbw1HLFTH_eOAyF4JvH6uk7MMGe-vMjuKBu6tRibVnI8MMS0phWc57XYpOqzwOxJA7g\/exec',              \/\/ ex. https:\/\/script.google.com\/macros\/s\/AKfy...\/exec\n  TIMEOUT_MS: 8000,         \/\/ d\u00e9lai au-del\u00e0 duquel on bascule sur le cache\n  SHOW_BRAND: true,         \/\/ false = aucune marque dans l'en-t\u00eate et le devis\n  BRAND_NAME: 'Paps',\n  SHOW_COMMERCIAL: true     \/\/ false = masque marge, remise et TVA\n};\n\n\/* =========================================================\n   1. GRILLE DE SECOURS (embarqu\u00e9e)\n   ---------------------------------------------------------\n   Utilis\u00e9e uniquement au tout premier lancement hors connexion,\n   ou si l'API est injoignable et qu'aucun cache local n'existe.\n   Le classeur Excel reste la source de v\u00e9rit\u00e9.\n   ========================================================= *\/\nconst DEFAULT_GRID = {\n  meta:{ devise:\"FCFA\", tva:18, cashFee:1.5, assurFee:0, margeDefaut:0 },\n\n  tailles:[\n    {code:\"ENV\", label:\"ENV (+500 g)\", dim:\"Enveloppe A4 maximum\"},\n    {code:\"S\",   label:\"S (max 3 kg)\", dim:\"Longueur max 40 cm \u2014 L+l+h < 80 cm\"},\n    {code:\"M\",   label:\"M (max 15 kg)\", dim:\"Longueur max 60 cm \u2014 L+l+h < 150 cm\"},\n    {code:\"L\",   label:\"L (max 30 kg)\", dim:\"Longueur max 100 cm \u2014 L+l+h < 220 cm\"},\n    {code:\"XL\",  label:\"XL (max 50 kg)\", dim:\"Longueur max 150 cm \u2014 L+l+h < 300 cm\"},\n    {code:\"XXL\", label:\"XXL (max 75 kg)\", dim:\"Longueur max 200 cm \u2014 L+l+h < 350 cm\"}\n  ],\n\n  messagerie:[\n    {zone:\"Dakar\",     ENV:1300, S:1900,  M:3800,  L:6300,  XL:11300, XXL:16900},\n    {zone:\"Banlieue\",  ENV:1600, S:2500,  M:5000,  L:7500,  XL:12500, XXL:19400},\n    {zone:\"R\u00e9gion 1\",  ENV:3200, S:3800,  M:6300,  L:10000, XL:13800, XXL:23200},\n    {zone:\"R\u00e9gion 2\",  ENV:4300, S:5000,  M:7500,  L:11300, XL:15000, XXL:26300},\n    {zone:\"R\u00e9gion 3\",  ENV:5300, S:6300,  M:8800,  L:12500, XL:16300, XXL:29400},\n    {zone:\"R\u00e9gion 4\",  ENV:6500, S:7500,  M:10000, L:13800, XL:17500, XXL:32500},\n    {zone:\"R\u00e9gion 5\",  ENV:7400, S:8800,  M:11300, L:15000, XL:18800, XXL:35700},\n    {zone:\"R\u00e9gion 6\",  ENV:8500, S:10000, M:12500, L:16300, XL:20000, XXL:38800},\n    {zone:\"R\u00e9gion 7\",  ENV:9700, S:11300, M:13800, L:17500, XL:21300, XXL:41900},\n    {zone:\"R\u00e9gion 8\",  ENV:10700,S:12500, M:15000, L:18800, XL:22500, XXL:45000}\n  ],\n\n  decoupage:[\n    {ville:\"Dakar (ville)\", zone:\"Dakar\", km:0},\n    {ville:\"Banlieue de Dakar\", zone:\"Banlieue\", km:0},\n    {ville:\"Mbour\", zone:\"R\u00e9gion 1\", km:85},\n    {ville:\"Diass\", zone:\"R\u00e9gion 1\", km:50},\n    {ville:\"Saly\", zone:\"R\u00e9gion 1\", km:80},\n    {ville:\"Thi\u00e8s\", zone:\"R\u00e9gion 1\", km:70},\n    {ville:\"Tivaouane\", zone:\"R\u00e9gion 1\", km:95},\n    {ville:\"Bambey\", zone:\"R\u00e9gion 2\", km:123},\n    {ville:\"Diourbel\", zone:\"R\u00e9gion 2\", km:146},\n    {ville:\"Fatick\", zone:\"R\u00e9gion 2\", km:150},\n    {ville:\"Kaolack\", zone:\"R\u00e9gion 2\", km:196},\n    {ville:\"K\u00e9b\u00e9mer\", zone:\"R\u00e9gion 2\", km:155},\n    {ville:\"Mback\u00e9\", zone:\"R\u00e9gion 2\", km:180},\n    {ville:\"Mboro\", zone:\"R\u00e9gion 2\", km:118},\n    {ville:\"Touba\", zone:\"R\u00e9gion 2\", km:195},\n    {ville:\"Dahra (10 Daara)\", zone:\"R\u00e9gion 2\", km:263},\n    {ville:\"Kaffrine\", zone:\"R\u00e9gion 3\", km:292},\n    {ville:\"Louga\", zone:\"R\u00e9gion 3\", km:205},\n    {ville:\"Nioro du Rip\", zone:\"R\u00e9gion 3\", km:257},\n    {ville:\"Saint-Louis\", zone:\"R\u00e9gion 3\", km:265},\n    {ville:\"Dagana\", zone:\"R\u00e9gion 4\", km:388},\n    {ville:\"Lingu\u00e8re\", zone:\"R\u00e9gion 4\", km:305},\n    {ville:\"Richard Toll\", zone:\"R\u00e9gion 4\", km:367},\n    {ville:\"Bignona\", zone:\"R\u00e9gion 5\", km:452},\n    {ville:\"Kolda\", zone:\"R\u00e9gion 5\", km:478},\n    {ville:\"Podor\", zone:\"R\u00e9gion 5\", km:465},\n    {ville:\"S\u00e9dhiou\", zone:\"R\u00e9gion 5\", km:420},\n    {ville:\"Tambacounda\", zone:\"R\u00e9gion 5\", km:467},\n    {ville:\"Ziguinchor\", zone:\"R\u00e9gion 5\", km:482},\n    {ville:\"Matam\", zone:\"R\u00e9gion 6\", km:529},\n    {ville:\"Ourossogui\", zone:\"R\u00e9gion 6\", km:519},\n    {ville:\"V\u00e9lingara\", zone:\"R\u00e9gion 6\", km:600},\n    {ville:\"Bakel\", zone:\"R\u00e9gion 7\", km:687},\n    {ville:\"Kidira\", zone:\"R\u00e9gion 7\", km:654},\n    {ville:\"K\u00e9dougou\", zone:\"R\u00e9gion 8\", km:738}\n  ],\n\n  ramassageVeh:[\n    {zone:\"Dakar\",    scooter:500,  minivan:5000, van:10000},\n    {zone:\"Banlieue\", scooter:1000, minivan:7500, van:15000}\n  ],\n  ramassageCmd:[\n    {code:\"D\",   label:\"D (moins de 500 g)\", prix:100},\n    {code:\"S\",   label:\"S (max 3 kg)\",  prix:200},\n    {code:\"M\",   label:\"M (max 15 kg)\", prix:500},\n    {code:\"L\",   label:\"L (max 30 kg)\", prix:1000},\n    {code:\"XL\",  label:\"XL (max 50 kg)\",prix:1500},\n    {code:\"XXL\", label:\"XXL (max 75 kg)\",prix:2000}\n  ],\n  preparation:[\n    {code:\"emballage\", label:\"Mise en emballage\", prix:300},\n    {code:\"traitement\", label:\"Traitement de commande\", prix:350}\n  ],\n\n  mad:[\n    {ville:\"Dakar centre\",            region:\"R\u00e9gion 0\", km:0,   minivanConso:13000, minivanBase:20000, vanConso:15000, vanBase:35000, t10:85000,  t12:150000, t15:195000},\n    {ville:\"Dakar corniche\",          region:\"R\u00e9gion 0\", km:0,   minivanConso:13000, minivanBase:20000, vanConso:15000, vanBase:35000, t10:85000,  t12:150000, t15:195000},\n    {ville:\"Dakar presque banlieue\",  region:\"R\u00e9gion 0\", km:0,   minivanConso:13000, minivanBase:20000, vanConso:15000, vanBase:35000, t10:100000, t12:165000, t15:210000},\n    {ville:\"Dakar banlieue proche\",   region:\"R\u00e9gion 0\", km:0,   minivanConso:15000, minivanBase:20000, vanConso:18000, vanBase:35000, t10:120000, t12:185000, t15:230000},\n    {ville:\"Dakar banlieue lointaine\",region:\"R\u00e9gion 0\", km:0,   minivanConso:18000, minivanBase:20000, vanConso:20000, vanBase:35000, t10:120000, t12:185000, t15:230000},\n    {ville:\"Mbour\",       region:\"R\u00e9gion 1\", km:85,  minivanConso:20000, minivanBase:35000, vanConso:27000, vanBase:55000,  t10:150000, t12:215000, t15:273000},\n    {ville:\"Saly\",        region:\"R\u00e9gion 1\", km:80,  minivanConso:20000, minivanBase:35000, vanConso:27000, vanBase:55000,  t10:150000, t12:215000, t15:273000},\n    {ville:\"Thi\u00e8s\",       region:\"R\u00e9gion 1\", km:70,  minivanConso:20000, minivanBase:35000, vanConso:27000, vanBase:55000,  t10:150000, t12:215000, t15:273000},\n    {ville:\"Diass\",       region:\"R\u00e9gion 1\", km:50,  minivanConso:20000, minivanBase:35000, vanConso:27000, vanBase:55000,  t10:150000, t12:215000, t15:273000},\n    {ville:\"Tivaouane\",   region:\"R\u00e9gion 1\", km:95,  minivanConso:20000, minivanBase:45000, vanConso:27000, vanBase:55000,  t10:150000, t12:215000, t15:273000},\n    {ville:\"Bambey\",      region:\"R\u00e9gion 2\", km:123, minivanConso:30000, minivanBase:55000, vanConso:35000, vanBase:105000, t10:180000, t12:245000, t15:303000},\n    {ville:\"Fatick\",      region:\"R\u00e9gion 2\", km:150, minivanConso:30000, minivanBase:55000, vanConso:35000, vanBase:105000, t10:200000, t12:265000, t15:323000},\n    {ville:\"Mboro\",       region:\"R\u00e9gion 2\", km:118, minivanConso:30000, minivanBase:55000, vanConso:35000, vanBase:105000, t10:200000, t12:265000, t15:323000},\n    {ville:\"Diourbel\",    region:\"R\u00e9gion 2\", km:146, minivanConso:37000, minivanBase:55000, vanConso:42000, vanBase:105000, t10:200000, t12:265000, t15:323000},\n    {ville:\"Kaolack\",     region:\"R\u00e9gion 2\", km:196, minivanConso:37000, minivanBase:55000, vanConso:42000, vanBase:105000, t10:200000, t12:265000, t15:323000},\n    {ville:\"K\u00e9b\u00e9mer\",     region:\"R\u00e9gion 2\", km:155, minivanConso:30000, minivanBase:55000, vanConso:35000, vanBase:105000, t10:200000, t12:265000, t15:323000},\n    {ville:\"Mback\u00e9\",      region:\"R\u00e9gion 2\", km:180, minivanConso:37000, minivanBase:55000, vanConso:42000, vanBase:105000, t10:200000, t12:265000, t15:323000},\n    {ville:\"Touba\",       region:\"R\u00e9gion 2\", km:195, minivanConso:37000, minivanBase:55000, vanConso:42000, vanBase:105000, t10:200000, t12:265000, t15:323000},\n    {ville:\"Louga\",       region:\"R\u00e9gion 3\", km:205, minivanConso:40000, minivanBase:65000, vanConso:45000, vanBase:80000,  t10:225000, t12:290000, t15:348000},\n    {ville:\"Nioro du Rip\",region:\"R\u00e9gion 3\", km:257, minivanConso:40000, minivanBase:75000, vanConso:40000, vanBase:80000,  t10:225000, t12:290000, t15:348000},\n    {ville:\"Saint-Louis\", region:\"R\u00e9gion 3\", km:292, minivanConso:45000, minivanBase:75000, vanConso:45000, vanBase:80000,  t10:250000, t12:315000, t15:373000},\n    {ville:\"Kaffrine\",    region:\"R\u00e9gion 3\", km:254, minivanConso:45000, minivanBase:75000, vanConso:45000, vanBase:80000,  t10:250000, t12:315000, t15:373000},\n    {ville:\"Dara\",        region:\"R\u00e9gion 3\", km:263, minivanConso:48000, minivanBase:75000, vanConso:48000, vanBase:80000,  t10:250000, t12:315000, t15:373000},\n    {ville:\"Lingu\u00e8re\",    region:\"R\u00e9gion 4\", km:305, minivanConso:67000, minivanBase:100000, vanConso:67000, vanBase:125000, t10:295000, t12:360000, t15:418000},\n    {ville:\"Richard Toll\",region:\"R\u00e9gion 4\", km:367, minivanConso:67000, minivanBase:100000, vanConso:67000, vanBase:125000, t10:300000, t12:365000, t15:423000},\n    {ville:\"Dagana\",      region:\"R\u00e9gion 4\", km:388, minivanConso:67000, minivanBase:100000, vanConso:67000, vanBase:125000, t10:300000, t12:365000, t15:423000},\n    {ville:\"Kolda\",       region:\"R\u00e9gion 5\", km:478, minivanConso:125000, minivanBase:135000, vanConso:125000, vanBase:160000, t10:375000, t12:440000, t15:498000},\n    {ville:\"Podor\",       region:\"R\u00e9gion 5\", km:465, minivanConso:102000, minivanBase:135000, vanConso:102000, vanBase:160000, t10:375000, t12:440000, t15:498000},\n    {ville:\"S\u00e9dhiou\",     region:\"R\u00e9gion 5\", km:420, minivanConso:125000, minivanBase:135000, vanConso:125000, vanBase:160000, t10:375000, t12:440000, t15:498000},\n    {ville:\"Ziguinchor\",  region:\"R\u00e9gion 5\", km:482, minivanConso:125000, minivanBase:135000, vanConso:125000, vanBase:160000, t10:375000, t12:440000, t15:498000},\n    {ville:\"Bignona\",     region:\"R\u00e9gion 5\", km:452, minivanConso:125000, minivanBase:135000, vanConso:125000, vanBase:160000, t10:375000, t12:440000, t15:498000},\n    {ville:\"Tambacounda\", region:\"R\u00e9gion 5\", km:467, minivanConso:125000, minivanBase:135000, vanConso:125000, vanBase:160000, t10:375000, t12:440000, t15:498000},\n    {ville:\"Matam\",       region:\"R\u00e9gion 6\", km:529, minivanConso:135000, minivanBase:150000, vanConso:135000, vanBase:170000, t10:425000, t12:490000, t15:548000},\n    {ville:\"V\u00e9lingara\",   region:\"R\u00e9gion 6\", km:600, minivanConso:142000, minivanBase:150000, vanConso:142000, vanBase:170000, t10:450000, t12:515000, t15:573000},\n    {ville:\"Ourossogui\",  region:\"R\u00e9gion 6\", km:519, minivanConso:135000, minivanBase:150000, vanConso:135000, vanBase:170000, t10:450000, t12:515000, t15:573000},\n    {ville:\"Bakel\",       region:\"R\u00e9gion 7\", km:687, minivanConso:165000, minivanBase:180000, vanConso:165000, vanBase:210000, t10:495000, t12:560000, t15:618000},\n    {ville:\"Kidira\",      region:\"R\u00e9gion 7\", km:654, minivanConso:160000, minivanBase:180000, vanConso:160000, vanBase:210000, t10:545000, t12:610000, t15:668000},\n    {ville:\"K\u00e9dougou\",    region:\"R\u00e9gion 8\", km:738, minivanConso:160000, minivanBase:200000, vanConso:160000, vanBase:235000, t10:575000, t12:640000, t15:698000}\n  ],\n\n  axesInfo:[\n    {code:\"axe1\", nom:\"Axe 1 \u2014 Louga, Saint-Louis, Richard Toll\", km:403},\n    {code:\"axe2\", nom:\"Axe 2 \u2014 Diourbel, Touba, Dahra, Lingu\u00e8re, Ourossogui\", km:551},\n    {code:\"axe3\", nom:\"Axe 3 \u2014 Fatick, Kaolack, Tamba, K\u00e9dougou\", km:705},\n    {code:\"axe4\", nom:\"Axe 4 \u2014 Mbour, Ngaparou, Thi\u00e8s, Tivaouane, Mboro\", km:207},\n    {code:\"axe5\", nom:\"Axe 5 \u2014 Nioro, Bignona, Ziguinchor, S\u00e9dhiou, Kolda\", km:665},\n    {code:\"axe6\", nom:\"Axe 6 \u2014 Tourn\u00e9e Thi\u00e8s, Tamba, Thi\u00e8s\", km:906}\n  ],\n  convoyeur:{axe1:7000, axe2:16000, axe3:27000, axe4:7000, axe5:32000, axe6:42000},\n  axes:[\n    {type:\"Mini-Van\", axe1:110000, axe2:180000, axe3:230000, axe4:90000,  axe5:200000, axe6:320000, manutFull:15000,  manutHalf:12500},\n    {type:\"Van\",      axe1:150000, axe2:260000, axe3:350000, axe4:150000, axe5:300000, axe6:370000, manutFull:30000,  manutHalf:20000},\n    {type:\"3T\",       axe1:250000, axe2:300000, axe3:400000, axe4:100000, axe5:350000, axe6:420000, manutFull:55000,  manutHalf:32500},\n    {type:\"5T\",       axe1:420000, axe2:430000, axe3:550000, axe4:170000, axe5:480000, axe6:570000, manutFull:55000,  manutHalf:32500},\n    {type:\"10T\",      axe1:380000, axe2:450000, axe3:530000, axe4:160000, axe5:500000, axe6:630000, manutFull:75000,  manutHalf:42500},\n    {type:\"12T\",      axe1:480000, axe2:500000, axe3:650000, axe4:180000, axe5:525000, axe6:670000, manutFull:75000,  manutHalf:42500},\n    {type:\"15T\",      axe1:550000, axe2:620000, axe3:720000, axe4:220000, axe5:650000, axe6:720000, manutFull:100000, manutHalf:55000},\n    {type:\"20T\",      axe1:580000, axe2:650000, axe3:740000, axe4:240000, axe5:680000, axe6:775000, manutFull:120000, manutHalf:65000},\n    {type:\"30T\",      axe1:680000, axe2:750000, axe3:810000, axe4:350000, axe5:780000, axe6:820000, manutFull:200000, manutHalf:105000},\n    {type:\"35T\",      axe1:745000, axe2:800000, axe3:890000, axe4:400000, axe5:840000, axe6:895000, manutFull:250000, manutHalf:130000},\n    {type:\"40T\",      axe1:798000, axe2:850000, axe3:950000, axe4:545000, axe5:890000, axe6:970000, manutFull:275000, manutHalf:142500},\n    {type:\"45T\",      axe1:865000, axe2:950000, axe3:1125000,axe4:600000, axe5:980000, axe6:1100000,manutFull:300000, manutHalf:155000},\n    {type:\"20T grue\", axe1:1500000,axe2:1800000,axe3:2250000,axe4:880000, axe5:1975000,axe6:2825000,manutFull:0, manutHalf:0},\n    {type:\"25T grue\", axe1:1800000,axe2:2100000,axe3:2550000,axe4:1150000,axe5:2275000,axe6:3125000,manutFull:0, manutHalf:0}\n  ],\n\n  gros:[\n    {dep:\"Rufisque\", km:30, tonne:7980, t30:239400, t35:279300, t40:319200, t45:359100},\n    {dep:\"Thi\u00e8s\", km:90, tonne:9890, t30:296700, t35:346150, t40:395600, t45:445050},\n    {dep:\"Mbour\", km:90, tonne:9890, t30:296700, t35:346150, t40:395600, t45:445050},\n    {dep:\"Tivaouane\", km:100, tonne:10340, t30:310200, t35:361900, t40:413600, t45:465300},\n    {dep:\"Bambey\", km:140, tonne:13990, t30:419700, t35:489650, t40:559600, t45:629550},\n    {dep:\"Fatick\", km:150, tonne:13990, t30:419700, t35:489650, t40:559600, t45:629550},\n    {dep:\"Gossas\", km:180, tonne:13990, t30:419700, t35:489650, t40:559600, t45:629550},\n    {dep:\"Guinguin\u00e9o\", km:180, tonne:13990, t30:419700, t35:489650, t40:559600, t45:629550},\n    {dep:\"Louga\", km:200, tonne:16000, t30:480000, t35:560000, t40:640000, t45:720000},\n    {dep:\"K\u00e9b\u00e9mer\", km:200, tonne:16000, t30:480000, t35:560000, t40:640000, t45:720000},\n    {dep:\"Diourbel\", km:200, tonne:16000, t30:480000, t35:560000, t40:640000, t45:720000},\n    {dep:\"Mback\u00e9\", km:200, tonne:16000, t30:480000, t35:560000, t40:640000, t45:720000},\n    {dep:\"Foundiougne\", km:230, tonne:16000, t30:480000, t35:560000, t40:640000, t45:720000},\n    {dep:\"Kaolack\", km:250, tonne:20300, t30:609000, t35:710500, t40:812000, t45:913500},\n    {dep:\"Birkilane\", km:250, tonne:20300, t30:609000, t35:710500, t40:812000, t45:913500},\n    {dep:\"Nioro\", km:257, tonne:20300, t30:609000, t35:710500, t40:812000, t45:913500},\n    {dep:\"Malem Hodar\", km:260, tonne:20300, t30:609000, t35:710500, t40:812000, t45:913500},\n    {dep:\"Saint-Louis\", km:300, tonne:25780, t30:773400, t35:902300, t40:1031200, t45:1160100},\n    {dep:\"Kaffrine\", km:300, tonne:25780, t30:773400, t35:902300, t40:1031200, t45:1160100},\n    {dep:\"Koungheul\", km:350, tonne:27600, t30:828000, t35:966000, t40:1104000, t45:1242000},\n    {dep:\"Lingu\u00e8re\", km:400, tonne:30250, t30:907500, t35:1058750, t40:1210000, t45:1361250},\n    {dep:\"Ran\u00e9rou\", km:400, tonne:30250, t30:907500, t35:1058750, t40:1210000, t45:1361250},\n    {dep:\"Dagana\", km:400, tonne:30250, t30:907500, t35:1058750, t40:1210000, t45:1361250},\n    {dep:\"Koumpentoum\", km:400, tonne:30250, t30:907500, t35:1058750, t40:1210000, t45:1361250},\n    {dep:\"Bounkiling\", km:400, tonne:30250, t30:907500, t35:1058750, t40:1210000, t45:1361250},\n    {dep:\"Bignona\", km:400, tonne:30250, t30:907500, t35:1058750, t40:1210000, t45:1361250},\n    {dep:\"S\u00e9dhiou\", km:450, tonne:30560, t30:916800, t35:1069600, t40:1222400, t45:1375200},\n    {dep:\"Ziguinchor\", km:450, tonne:30560, t30:916800, t35:1069600, t40:1222400, t45:1375200},\n    {dep:\"Podor\", km:500, tonne:31400, t30:942000, t35:1099000, t40:1256000, t45:1413000},\n    {dep:\"Goudomp\", km:500, tonne:31400, t30:942000, t35:1099000, t40:1256000, t45:1413000},\n    {dep:\"Oussouye\", km:500, tonne:31400, t30:942000, t35:1099000, t40:1256000, t45:1413000},\n    {dep:\"Goudiry\", km:570, tonne:35467, t30:1064010, t35:1241345, t40:1418680, t45:1596015},\n    {dep:\"V\u00e9lingara\", km:600, tonne:36790, t30:1103700, t35:1287650, t40:1471600, t45:1655550},\n    {dep:\"Tambacounda\", km:467, tonne:32560, t30:976800, t35:1139600, t40:1302400, t45:1465200},\n    {dep:\"Bakel\", km:687, tonne:39670, t30:1190100, t35:1388450, t40:1586800, t45:1785150},\n    {dep:\"Kolda\", km:688, tonne:39670, t30:1190100, t35:1388450, t40:1586800, t45:1785150},\n    {dep:\"M\u00e9dina Yoro Foula\", km:688, tonne:39670, t30:1190100, t35:1388450, t40:1586800, t45:1785150},\n    {dep:\"K\u00e9dougou\", km:750, tonne:42345, t30:1270350, t35:1482075, t40:1693800, t45:1905525},\n    {dep:\"Saraya\", km:760, tonne:42345, t30:1270350, t35:1482075, t40:1693800, t45:1905525},\n    {dep:\"Salemata\", km:773, tonne:43570, t30:1307100, t35:1524950, t40:1742800, t45:1960650},\n    {dep:\"Matam\", km:816, tonne:48590, t30:1457700, t35:1700650, t40:1943600, t45:2186550},\n    {dep:\"Kanel\", km:800, tonne:48590, t30:1457700, t35:1700650, t40:1943600, t45:2186550}\n  ]\n};\n\n\/* =========================================================\n   2. \u00c9TAT ET UTILITAIRES\n   ========================================================= *\/\nlet G = JSON.parse(JSON.stringify(DEFAULT_GRID));\nconst $ = id => document.getElementById(id);\nconst nf = new Intl.NumberFormat('fr-FR');\nconst fmt = n => nf.format(Math.round(n || 0)) + ' ' + G.meta.devise;\nconst num = id => parseFloat($(id).value) || 0;\nconst opt = (v, t) => `<option value=\"${v}\">${t}<\/option>`;\n\n\/* =========================================================\n   2 bis. CHARGEMENT DE LA GRILLE DEPUIS LE CLASSEUR\n   ---------------------------------------------------------\n   Ordre de priorit\u00e9 :\n     1. l'API Apps Script (tarifs officiels du jour) ;\n     2. le dernier t\u00e9l\u00e9chargement r\u00e9ussi, mis en cache local ;\n     3. la grille de secours embarqu\u00e9e ci-dessus.\n   L'application reste utilisable hors connexion dans tous les cas.\n   ========================================================= *\/\nconst CACHE_KEY = 'paps-grille-v1';\n\nfunction setStatus(kind, texte, retry){\n  const box = $('status');\n  box.hidden = false;\n  box.className = 'status ' + kind;\n  $('statusText').textContent = texte;\n  $('btnRetry').hidden = !retry;\n}\n\nfunction cacheRead(){\n  try{\n    const raw = localStorage.getItem(CACHE_KEY);\n    return raw ? JSON.parse(raw) : null;\n  }catch(e){ return null; }\n}\nfunction cacheWrite(grid){\n  try{ localStorage.setItem(CACHE_KEY, JSON.stringify(grid)); }catch(e){}\n}\n\n\/** Repli JSONP : certaines WebView bloquent la redirection 302 d'Apps Script. *\/\nfunction fetchJsonp(url, timeout){\n  return new Promise((resolve, reject)=>{\n    const cb = 'papsCb' + Date.now();\n    const tag = document.createElement('script');\n    const clean = ()=>{ delete window[cb]; tag.remove(); clearTimeout(timer); };\n    const timer = setTimeout(()=>{ clean(); reject(new Error('d\u00e9lai d\u00e9pass\u00e9')); }, timeout);\n    window[cb] = data => { clean(); resolve(data); };\n    tag.onerror = ()=>{ clean(); reject(new Error('script injoignable')); };\n    tag.src = url + (url.includes('?') ? '&' : '?') + 'callback=' + cb;\n    document.head.appendChild(tag);\n  });\n}\n\nasync function fetchGrid(){\n  const ctrl = new AbortController();\n  const timer = setTimeout(()=>ctrl.abort(), CONFIG.TIMEOUT_MS);\n  try{\n    const r = await fetch(CONFIG.API_URL, {signal:ctrl.signal, cache:'no-store', redirect:'follow'});\n    if(!r.ok) throw new Error('HTTP ' + r.status);\n    return await r.json();\n  }catch(err){\n    \/\/ Une erreur r\u00e9seau sur Apps Script est le plus souvent un blocage CORS\n    \/\/ ou de redirection : on retente en JSONP avant d'abandonner.\n    return await fetchJsonp(CONFIG.API_URL, CONFIG.TIMEOUT_MS);\n  }finally{\n    clearTimeout(timer);\n  }\n}\n\n\/** Refuse une charge utile amput\u00e9e plut\u00f4t que d'afficher des prix faux. *\/\nfunction grilleComplete(g){\n  if(!g || g.ok === false || !g.meta || !g.meta.devise) return false;\n  return ['messagerie','decoupage','mad','axes','axesInfo','gros',\n          'tailles','ramassageCmd','preparation','ramassageVeh']\n         .every(k => Array.isArray(g[k]) && g[k].length > 0)\n         && g.convoyeur && Object.keys(g.convoyeur).length > 0;\n}\n\nfunction dateFr(iso){\n  const d = iso ? new Date(iso) : new Date();\n  return isNaN(d) ? '' : d.toLocaleDateString('fr-FR') + ' \u00e0 ' +\n         d.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'});\n}\n\nasync function loadGrid(o){\n  o = o || {};\n  if(!CONFIG.API_URL){\n    setStatus('warn', 'Tarifs de d\u00e9monstration \u2014 API non configur\u00e9e', false);\n    return 'secours';\n  }\n  setStatus('load', 'Chargement des tarifs\u2026', false);\n\n  try{\n    const data = await fetchGrid();\n    if(!grilleComplete(data)) throw new Error('grille incompl\u00e8te');\n    G = data;\n    cacheWrite(data);\n    setStatus('ok', 'Tarifs \u00e0 jour \u2014 ' + dateFr(data.generatedAt), false);\n    return 'reseau';\n  }catch(err){\n    const cached = cacheRead();\n    if(cached && grilleComplete(cached)){\n      G = cached;\n      setStatus('warn', 'Hors connexion \u2014 tarifs du ' + dateFr(cached.generatedAt), true);\n      return 'cache';\n    }\n    setStatus('warn', 'Hors connexion \u2014 tarifs de r\u00e9f\u00e9rence embarqu\u00e9s', true);\n    return 'secours';\n  }\n}\n\n\/* Application des drapeaux de configuration. *\/\nfunction applyConfig(){\n  if(!CONFIG.SHOW_BRAND){\n    $('logoBox').style.display = 'none';\n    $('brandName').textContent = 'Simulateur tarifaire';\n  }else{\n    $('brandName').textContent = CONFIG.BRAND_NAME;\n  }\n  if(!CONFIG.SHOW_COMMERCIAL){\n    document.querySelectorAll('[data-commercial]').forEach(el=>el.classList.add('hidden'));\n  }\n}\n\n\/* =========================================================\n   3. ONGLETS\n   ========================================================= *\/\nconst PANES = [\n  {id:'messagerie', label:'Messagerie'},\n  {id:'mad',        label:'Location de v\u00e9hicule'},\n  {id:'axes',       label:'Axes et tourn\u00e9es'},\n  {id:'gros',       label:'Gros porteurs'}\n];\nlet currentPane = PANES[0].id;\n\nfunction buildTabs(){\n  $('tabs').innerHTML = PANES.map((p,i)=>`<button class=\"tab ${i===0?'on':''}\" data-pane=\"${p.id}\">${p.label}<\/button>`).join('');\n  document.querySelectorAll('.tab').forEach(t=>t.onclick=()=>{\n    document.querySelectorAll('.tab').forEach(x=>x.classList.remove('on'));\n    t.classList.add('on');\n    currentPane = t.dataset.pane;\n    PANES.forEach(p=>$('pane-'+p.id).classList.toggle('hidden', p.id!==currentPane));\n    updateTotalBar();\n  });\n}\n\n\/* ---------------------------------------------------------\n   Total permanent (mobile)\n   Le r\u00e9capitulatif complet est en bas de page sur petit \u00e9cran :\n   cette barre garde le montant visible et y ram\u00e8ne d'un tap.\n   --------------------------------------------------------- *\/\nfunction updateTotalBar(){\n  const s = RECAP_STATE[currentPane];\n  const bar = $('totalBar');\n  if(!s){ bar.hidden = true; document.body.classList.remove('has-totalbar'); return; }\n\n  bar.hidden = false;\n  document.body.classList.add('has-totalbar');\n  $('tbLabel').textContent = s.opts.tva ? 'Total TTC' : 'Total HT';\n  $('tbSub').textContent = s.lignes.length\n    ? s.lignes.length + (s.lignes.length > 1 ? ' prestations' : ' prestation')\n    : 'Aucune prestation';\n  $('tbVal').textContent = s.lignes.length ? fmt(s.ttc) : '\u2014';\n\n  \/\/ L'animation d'entr\u00e9e ne se joue qu'une fois.\n  if(!bar.classList.contains('up')) requestAnimationFrame(()=>bar.classList.add('up'));\n}\n\n$('totalBar').onclick = ()=>{\n  const el = $('recap-' + currentPane);\n  if(el) el.scrollIntoView({behavior:'smooth', block:'center'});\n};\n\n\/* =========================================================\n   4. R\u00c9CAPITULATIF PARTAG\u00c9\n   ========================================================= *\/\nfunction commOpts(pre){\n  return {\n    marge: parseFloat(($(pre+'_marge')||{}).value) || 0,\n    remise: parseFloat(($(pre+'_remise')||{}).value) || 0,\n    tva: ($(pre+'_tva')||{}).checked || false\n  };\n}\n\nconst RECAP_STATE = {};\n\nfunction drawRecap(target, titre, lignes, opts){\n  opts = opts || {};\n  const sousTotal = lignes.reduce((s,l)=>s+l.montant, 0);\n  const marge = sousTotal * (opts.marge||0)\/100;\n  const venteHT = sousTotal + marge;\n  const remise = venteHT * (opts.remise||0)\/100;\n  const netHT = venteHT - remise;\n  const tva = opts.tva ? netHT * G.meta.tva\/100 : 0;\n  const ttc = netHT + tva;\n\n  let html = `<h2>${titre}<\/h2>`;\n  lignes.forEach(l=>{\n    html += `<div class=\"rline\"><span class=\"l\">${l.label}<\/span><span class=\"v\">${fmt(l.montant)}<\/span><\/div>`;\n  });\n  if(!lignes.length) html += `<div class=\"rline\"><span class=\"l\">Aucune prestation s\u00e9lectionn\u00e9e<\/span><span class=\"v\">\u2014<\/span><\/div>`;\n  html += `<div class=\"rline sum\"><span class=\"l\">Co\u00fbt de revient<\/span><span class=\"v\">${fmt(sousTotal)}<\/span><\/div>`;\n  if(marge)  html += `<div class=\"rline\"><span class=\"l\">Marge ${opts.marge} %<\/span><span class=\"v\">+ ${fmt(marge)}<\/span><\/div>`;\n  if(remise) html += `<div class=\"rline\"><span class=\"l\">Remise ${opts.remise} %<\/span><span class=\"v\">\u2212 ${fmt(remise)}<\/span><\/div>`;\n  if(opts.tva) html += `<div class=\"rline\"><span class=\"l\">TVA ${G.meta.tva} %<\/span><span class=\"v\">+ ${fmt(tva)}<\/span><\/div>`;\n  html += `<div class=\"rtotal\"><span class=\"lbl\">${opts.tva?'Total TTC':'Total HT'}<\/span><span class=\"val\">${fmt(ttc)}<\/span><\/div>`;\n  if(opts.notes && opts.notes.length) html += `<div class=\"rnote\">${opts.notes.join('<br>')}<\/div>`;\n  html += `<button class=\"btn btn-y\" data-print=\"${target}\">Imprimer le devis<\/button>`;\n\n  $('recap-'+target).innerHTML = html;\n  RECAP_STATE[target] = {titre, lignes, opts, sousTotal, marge, remise, netHT, tva, ttc};\n}\n\ndocument.addEventListener('click', e=>{\n  if(e.target.dataset.print) printDevis(e.target.dataset.print);\n});\n\n\/* =========================================================\n   5. SIMULATEURS\n   ========================================================= *\/\nfunction zoneOf(ville){\n  const d = G.decoupage.find(x=>x.ville===ville);\n  return d ? d.zone : 'Dakar';\n}\nfunction prixMessagerie(zone, taille){\n  const r = G.messagerie.find(x=>x.zone===zone);\n  return r ? (r[taille]||0) : 0;\n}\n\nfunction calcMessagerie(){\n  const ville = $('m_ville').value;\n  const zone = zoneOf(ville);\n  const d = G.decoupage.find(x=>x.ville===ville);\n  const taille = $('m_taille').value;\n  const nb = num('m_nb');\n  $('m_zone').textContent = zone;\n  $('m_km').textContent = d && d.km ? `\u2014 ${d.km} km de Dakar` : '';\n  const tl = G.tailles.find(t=>t.code===taille);\n  $('m_dim').textContent = tl ? tl.dim : '';\n\n  const lignes = [];\n  const pu = prixMessagerie(zone, taille);\n  if(nb>0 && pu>0) lignes.push({label:`Livraison ${taille} vers ${zone} \u2014 ${nb} \u00d7 ${fmt(pu)}`, montant: pu*nb});\n\n  const mode = $('m_pickup').value;\n  $('m_pickupCmdBox').style.display = mode==='cmd' ? '' : 'none';\n  $('m_pickupVehBox').style.display = mode==='veh' ? '' : 'none';\n  if(mode==='cmd'){\n    const c = G.ramassageCmd.find(x=>x.code===$('m_pickupTaille').value);\n    const q = num('m_pickupNb');\n    if(c && q>0) lignes.push({label:`Ramassage \u00e0 la commande ${c.code} \u2014 ${q} \u00d7 ${fmt(c.prix)}`, montant:c.prix*q});\n  } else if(mode==='veh'){\n    const r = G.ramassageVeh.find(x=>x.zone===$('m_pickupZone').value);\n    const v = $('m_pickupVeh').value;\n    if(r) lignes.push({label:`Ramassage ${v} \u2014 zone ${r.zone}`, montant:r[v]||0});\n  }\n\n  const pEmb = G.preparation.find(p=>p.code==='emballage') || {prix:0, label:'Mise en emballage'};\n  const pTrt = G.preparation.find(p=>p.code==='traitement') || {prix:0, label:'Traitement de commande'};\n  if($('m_emb').checked){\n    const q = num('m_embNb') || nb;\n    lignes.push({label:`Mise en emballage \u2014 ${q} \u00d7 ${fmt(pEmb.prix)}`, montant:pEmb.prix*q});\n  }\n  if($('m_trt').checked){\n    const q = num('m_trtNb') || nb;\n    lignes.push({label:`Traitement de commande \u2014 ${q} \u00d7 ${fmt(pTrt.prix)}`, montant:pTrt.prix*q});\n  }\n  const cash = num('m_cash');\n  if(cash>0) lignes.push({label:`Cash management ${G.meta.cashFee} % sur ${fmt(cash)}`, montant: cash*G.meta.cashFee\/100});\n  const val = num('m_assur');\n  if(val>0 && G.meta.assurFee>0) lignes.push({label:`Assurance ${G.meta.assurFee} % sur ${fmt(val)}`, montant: val*G.meta.assurFee\/100});\n\n  drawRecap('messagerie', `Messagerie \u2014 ${ville}`, lignes, Object.assign(commOpts('m'), {\n    notes:['D\u00e9lais Dakar : commande avant 11 h livr\u00e9e l\\'apr\u00e8s-midi.',\n           'Colis de plus de 30 kg vers les r\u00e9gions : un d\u00e9part par semaine et par destination.']\n  }));\n}\n\nfunction calcMad(){\n  const row = G.mad.find(r=>r.ville===$('v_ville').value) || G.mad[0];\n  if(!row) return drawRecap('mad','Mise \u00e0 disposition',[],commOpts('v'));\n  const type = $('v_type').value;\n  const nb = num('v_nb');\n  const ar = parseFloat($('v_ar').value);\n  const lignes = [];\n  const notes = [];\n  let pu = 0, detail = '';\n  if(type==='minivan'){ pu = (row.minivanConso||0)+(row.minivanBase||0); detail = `carburant ${fmt(row.minivanConso)} + mise \u00e0 disposition ${fmt(row.minivanBase)}`; }\n  else if(type==='van'){ pu = (row.vanConso||0)+(row.vanBase||0); detail = `carburant ${fmt(row.vanConso)} + mise \u00e0 disposition ${fmt(row.vanBase)}`; }\n  else { pu = row[type]||0; notes.push('Porteur 10 T et plus : tarif hors carburant.'); }\n  const label = $('v_type').selectedOptions[0].textContent;\n  if(pu>0) lignes.push({label:`${label} vers ${row.ville} \u2014 ${nb} \u00d7 ${ar>1?'aller-retour':'aller'}`, montant: pu*nb*ar});\n  if(detail) notes.push(`D\u00e9tail unitaire : ${detail}.`);\n\n  if($('v_manut').checked){\n    const ref = G.axes.find(a=>a.type===$('v_manutRef').value);\n    const key = $('v_manutType').value==='full' ? 'manutFull' : 'manutHalf';\n    if(ref) lignes.push({label:`Manutention ${$('v_manutType').selectedOptions[0].textContent.toLowerCase()} \u2014 ${ref.type}`, montant:(ref[key]||0)*nb});\n  }\n  const ex = num('v_extra');\n  if(ex>0) lignes.push({label:'Frais suppl\u00e9mentaires', montant:ex});\n  if(row.km) notes.push(`Distance depuis Dakar : ${row.km} km.`);\n\n  drawRecap('mad', `Mise \u00e0 disposition \u2014 ${row.ville}`, lignes, Object.assign(commOpts('v'), {notes}));\n}\n\nfunction calcAxes(){\n  const axe = $('a_axe').value;\n  const info = G.axesInfo.find(a=>a.code===axe);\n  $('a_axeInfo').textContent = info ? `${info.km} km aller, ${info.km*2} km aller-retour` : '';\n  const veh = G.axes.find(v=>v.type===$('a_veh').value) || G.axes[0];\n  if(!veh) return drawRecap('axes','Axes et tourn\u00e9es',[],commOpts('a'));\n  const nb = num('a_nb'), voy = num('a_voy');\n  const lignes = [];\n  const base = veh[axe]||0;\n  if(base>0) lignes.push({label:`${veh.type} \u2014 ${nb} v\u00e9hicule(s) \u00d7 ${voy} voyage(s)`, montant: base*nb*voy});\n  if($('a_conv').checked){\n    const c = G.convoyeur[axe]||0;\n    if(c>0) lignes.push({label:`Convoyeur (paie et frais) \u2014 ${nb} \u00d7 ${voy}`, montant:c*nb*voy});\n  }\n  const m = $('a_manut').value;\n  if(m!=='none'){\n    const val = m==='full' ? veh.manutFull : veh.manutHalf;\n    if(val>0) lignes.push({label:`Manutention ${m==='full'?'chargement et d\u00e9chargement':'un seul sens'} \u2014 ${nb} \u00d7 ${voy}`, montant:val*nb*voy});\n    else lignes.push({label:'Manutention assur\u00e9e par le camion', montant:0});\n  }\n  const ex = num('a_extra');\n  if(ex>0) lignes.push({label:'Frais suppl\u00e9mentaires', montant:ex});\n\n  drawRecap('axes', info ? info.nom : 'Axe', lignes, Object.assign(commOpts('a'), {\n    notes:['Co\u00fbts de location par voyage, carburant exclu.']\n  }));\n}\n\nfunction calcGros(){\n  const row = G.gros.find(r=>r.dep===$('g_dep').value) || G.gros[0];\n  if(!row) return drawRecap('gros','Gros porteurs',[],commOpts('g'));\n  $('g_km').textContent = `${row.km} km depuis Dakar`;\n  const mode = $('g_mode').value;\n  $('g_forfaitBox').style.display = mode==='forfait' ? '' : 'none';\n  $('g_tonneBox').style.display = mode==='tonne' ? '' : 'none';\n  const lignes = [];\n  if(mode==='forfait'){\n    const t = $('g_ton').value, nb = num('g_nb');\n    const pu = row[t]||0;\n    lignes.push({label:`${$('g_ton').selectedOptions[0].textContent} vers ${row.dep} \u2014 ${nb} camion(s)`, montant:pu*nb});\n  } else {\n    const q = num('g_qte');\n    lignes.push({label:`${q} tonne(s) vers ${row.dep} \u2014 ${fmt(row.tonne)} par tonne`, montant:row.tonne*q});\n  }\n  const ex = num('g_extra');\n  if(ex>0) lignes.push({label:'Frais suppl\u00e9mentaires', montant:ex});\n  drawRecap('gros', `Gros porteurs \u2014 ${row.dep}`, lignes, Object.assign(commOpts('g'), {\n    notes:['Tarifs indicatifs pour un chargement complet, hors manutention sp\u00e9cifique.']\n  }));\n}\n\nfunction calcAll(){ calcMessagerie(); calcMad(); calcAxes(); calcGros(); updateTotalBar(); }\n\n\/* =========================================================\n   6. REMPLISSAGE DES LISTES\n   ========================================================= *\/\nfunction fillSelectors(){\n  const keep = (id) => $(id).value;\n  const v1 = keep('m_ville'), v2 = keep('m_taille'), v3 = keep('v_ville'), v4 = keep('a_veh'), v5 = keep('g_dep');\n\n  $('m_ville').innerHTML = G.decoupage.map(d=>opt(d.ville, `${d.ville} \u2014 ${d.zone}`)).join('');\n  $('m_taille').innerHTML = G.tailles.map(t=>opt(t.code, t.label)).join('');\n  $('m_pickupTaille').innerHTML = G.ramassageCmd.map(c=>opt(c.code, `${c.label} \u2014 ${fmt(c.prix)}`)).join('');\n  $('m_pickupZone').innerHTML = G.ramassageVeh.map(r=>opt(r.zone, r.zone)).join('');\n  $('v_ville').innerHTML = G.mad.map(r=>opt(r.ville, `${r.ville} \u2014 ${r.region}`)).join('');\n  $('v_manutRef').innerHTML = G.axes.map(a=>opt(a.type, a.type)).join('');\n  $('a_axe').innerHTML = G.axesInfo.map(a=>opt(a.code, a.nom)).join('');\n  $('a_veh').innerHTML = G.axes.map(a=>opt(a.type, a.type)).join('');\n  $('g_dep').innerHTML = G.gros.map(r=>opt(r.dep, `${r.dep} \u2014 ${r.km} km`)).join('');\n\n  if(v1) $('m_ville').value = v1; if(v2) $('m_taille').value = v2;\n  if(v3) $('v_ville').value = v3; if(v4) { $('a_veh').value = v4; $('v_manutRef').value = v4; }\n  if(v5) $('g_dep').value = v5;\n  if(!$('v_manutRef').value) $('v_manutRef').value = 'Van';\n\n}\n\n\/* =========================================================\n   7. IMPRESSION DU DEVIS\n   ========================================================= *\/\nfunction printDevis(target){\n  const s = RECAP_STATE[target];\n  if(!s) return;\n  const logo = CONFIG.SHOW_BRAND ? $('logoBox').innerHTML : '';\n  const nom = CONFIG.SHOW_BRAND ? CONFIG.BRAND_NAME : 'Devis de simulation';\n  const date = new Date().toLocaleDateString('fr-FR');\n  let h = `<div class=\"p-head\"><div class=\"p-logo\">${logo}<\/div>\n      <div><div class=\"p-title\">${nom}<\/div><div>${s.titre} \u2014 ${date}<\/div><\/div><\/div>\n    <table><thead><tr><th class=\"txt\">Prestation<\/th><th>Montant<\/th><\/tr><\/thead><tbody>`;\n  s.lignes.forEach(l=>h += `<tr><td class=\"txt\">${l.label}<\/td><td>${fmt(l.montant)}<\/td><\/tr>`);\n  h += `<tr><td class=\"txt\"><b>Sous-total<\/b><\/td><td><b>${fmt(s.sousTotal)}<\/b><\/td><\/tr>`;\n  if(s.marge) h += `<tr><td class=\"txt\">Marge ${s.opts.marge} %<\/td><td>${fmt(s.marge)}<\/td><\/tr>`;\n  if(s.remise) h += `<tr><td class=\"txt\">Remise ${s.opts.remise} %<\/td><td>\u2212 ${fmt(s.remise)}<\/td><\/tr>`;\n  if(s.tva) h += `<tr><td class=\"txt\">TVA ${G.meta.tva} %<\/td><td>${fmt(s.tva)}<\/td><\/tr>`;\n  h += `<\/tbody><\/table><div class=\"p-total\">Total ${s.opts.tva?'TTC':'HT'} : ${fmt(s.ttc)}<\/div>`;\n  h += `<div class=\"p-note\">Simulation indicative, valable 30 jours. Montants en ${G.meta.devise}.${s.opts.notes?'<br>'+s.opts.notes.join('<br>'):''}<\/div>`;\n  $('printArea').innerHTML = h;\n  window.print();\n}\n\n\/* =========================================================\n   8. D\u00c9MARRAGE\n   ========================================================= *\/\ndocument.addEventListener('input',  e=>{ if(e.target.closest('main section')) calcAll(); });\ndocument.addEventListener('change', e=>{ if(e.target.closest('main section')) calcAll(); });\n\nfunction applyGrid(){\n  fillSelectors();\n  ['m','v','a','g'].forEach(p=>{ $(p+'_marge').value = G.meta.margeDefaut; });\n  calcAll();\n}\n\nasync function init(){\n  applyConfig();\n  buildTabs();\n  applyGrid();                 \/\/ rendu imm\u00e9diat avec le repli embarqu\u00e9\n  const src = await loadGrid(); \/\/ puis tarifs officiels d\u00e8s qu'ils arrivent\n  if(src !== 'secours') applyGrid();\n}\n\n$('btnRetry').onclick = async ()=>{\n  const src = await loadGrid({force:true});\n  if(src !== 'secours') applyGrid();\n};\n\ninit();\n<\/script>\n<\/body>\n<\/html>\r\n\t\t<\/div>\r\n\t<\/div>\r\n<\/div><\/div><\/div><\/div><\/div><\/div>","protected":false},"excerpt":{"rendered":"Simulateur tarifaire logistique Paps Simulateur de prix \u2014 services logistiques R\u00e9essayer Livraison messagerie Tarif au colis selon la zone et la taille. Les tailles sont limit\u00e9es par le poids et les dimensions. Destination Taille du colis Nombre de colis Zone appliqu\u00e9e Dakar Ramassage (pickup) Soit un tarif \u00e0 la commande, soit un v\u00e9hicule d\u00e9di\u00e9 mis [...]","protected":false},"author":8,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-17752","page","type-page","status-publish","hentry","grve-entry-item"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.6 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>simulateur-devis - Paps Logistique<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/papslogistics.com\/en\/simulateur-devis\/\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"simulateur-devis - Paps Logistique\" \/>\n<meta property=\"og:url\" content=\"https:\/\/papslogistics.com\/en\/simulateur-devis\/\" \/>\n<meta property=\"og:site_name\" content=\"Paps Logistique\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-23T13:49:44+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/papslogistics.com\/simulateur-devis\/\",\"url\":\"https:\/\/papslogistics.com\/simulateur-devis\/\",\"name\":\"simulateur-devis - Paps Logistique\",\"isPartOf\":{\"@id\":\"https:\/\/papslogistics.com\/#website\"},\"datePublished\":\"2026-09-23T13:27:56+00:00\",\"dateModified\":\"2026-09-23T13:49:44+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/papslogistics.com\/simulateur-devis\/#breadcrumb\"},\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/papslogistics.com\/simulateur-devis\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/papslogistics.com\/simulateur-devis\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/papslogistics.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"simulateur-devis\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/papslogistics.com\/#website\",\"url\":\"https:\/\/papslogistics.com\/\",\"name\":\"Paps Logistique\",\"description\":\"Une exp\u00e9rience de logistique unique\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/papslogistics.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-GB\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"simulateur-devis - Paps Logistique","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/papslogistics.com\/en\/simulateur-devis\/","og_locale":"en_GB","og_type":"article","og_title":"simulateur-devis - Paps Logistique","og_url":"https:\/\/papslogistics.com\/en\/simulateur-devis\/","og_site_name":"Paps Logistique","article_modified_time":"2026-09-23T13:49:44+00:00","twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/papslogistics.com\/simulateur-devis\/","url":"https:\/\/papslogistics.com\/simulateur-devis\/","name":"simulateur-devis - Paps Logistique","isPartOf":{"@id":"https:\/\/papslogistics.com\/#website"},"datePublished":"2026-09-23T13:27:56+00:00","dateModified":"2026-09-23T13:49:44+00:00","breadcrumb":{"@id":"https:\/\/papslogistics.com\/simulateur-devis\/#breadcrumb"},"inLanguage":"en-GB","potentialAction":[{"@type":"ReadAction","target":["https:\/\/papslogistics.com\/simulateur-devis\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/papslogistics.com\/simulateur-devis\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/papslogistics.com\/"},{"@type":"ListItem","position":2,"name":"simulateur-devis"}]},{"@type":"WebSite","@id":"https:\/\/papslogistics.com\/#website","url":"https:\/\/papslogistics.com\/","name":"Paps Logistique","description":"An unparalleled logistic experience","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/papslogistics.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-GB"}]}},"_links":{"self":[{"href":"https:\/\/papslogistics.com\/en\/wp-json\/wp\/v2\/pages\/17752","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/papslogistics.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/papslogistics.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/papslogistics.com\/en\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/papslogistics.com\/en\/wp-json\/wp\/v2\/comments?post=17752"}],"version-history":[{"count":0,"href":"https:\/\/papslogistics.com\/en\/wp-json\/wp\/v2\/pages\/17752\/revisions"}],"wp:attachment":[{"href":"https:\/\/papslogistics.com\/en\/wp-json\/wp\/v2\/media?parent=17752"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}