<!DOCTYPE html>
<html lang="fr">
<head>
  <base href="/">
  <meta charset="UTF-8">
  <script>
    // Domaine d'exécution officiel de l'application Cloud. Certains anciens
    // liens ou la configuration du domaine personnalisé peuvent encore ouvrir
    // www.mypilotis.com ; on conserve le hash Flutter pendant la normalisation.
    (function() {
      var host = window.location.hostname;
      if (host === 'www.mypilotis.com' || host === 'mypilotis.com') {
        var target = 'https://mypilotis.web.app'
          + window.location.pathname
          + window.location.search
          + window.location.hash;
        window.location.replace(target);
      }
    })();

    // ─────────────────────────────────────────────────────────────────────
    // Kill switch service worker — OFF par défaut.
    // Historiquement, ce script purgeait le SW à chaque déploiement pour
    // corriger un « écran gris » d'un ancien SW mal invalidé. Effet de bord :
    // AUCUN cache offline → l'app web ne se lance pas sans réseau.
    //
    // On garde une purge « manuelle » via localStorage['pilotis_sw_kill'] = '1'
    // (à activer via la console du navigateur en cas de nouveau bug de SW).
    // Sinon, le service worker généré par Flutter cache le shell et
    // l'utilisateur peut ouvrir Pilotis hors ligne.
    //
    // Le SW gère lui-même le versionning (via `serviceWorkerVersion` dans
    // flutter_bootstrap.js, régénéré à chaque `flutter build web`) : à un
    // nouveau déploiement il télécharge la nouvelle version en tâche de
    // fond puis prend la main au prochain chargement. Aucun purge nécessaire.
    //
    // `PL_BUILD` identifie le shell servi (injecté par deploy_web.sh, dérivé
    // du HASH DE CONTENU de main.dart.js — stable si le code n'a pas changé).
    // Il est reflété dans localStorage['pl_build'] à CHAQUE chargement : le
    // centre de mises à jour (update_service.dart) le compare au releaseId de
    // /version.json pour proposer « Mettre à jour » quand un déploiement
    // survient pendant que l'app est ouverte.
    //
    // IMPORTANT — plus AUCUNE purge automatique ici. L'ancienne purge « à
    // chaque nouvelle version » vidait SW + caches + rechargeait pour TOUS les
    // utilisateurs à CHAQUE déploiement (~15 Mo re-téléchargés, lancement très
    // lent × plusieurs deploys/jour). Elle était redondante : index.html est
    // no-cache, et pilotis_sw.js sert la navigation ET le code applicatif en
    // network-first avec revalidation ETag — le nouveau code arrive tout seul,
    // les fichiers inchangés répondent 304. La purge lourde reste disponible :
    //  - manuelle : localStorage['pilotis_sw_kill'] = '1' (bouton du splash) ;
    //  - au clic « Mettre à jour maintenant » : clearCacheAndReload() (Dart).
    window.PL_BUILD = 'pilotis-core-updates-2.8.0-2026-07-31-04';
    // Un ancien SW peut avoir mélangé main.dart.js et une part différée. Si
    // Flutter remonte cette erreur, on purge une seule fois pour ce shell et
    // on relance proprement. Le marqueur session évite toute boucle infinie.
    (function() {
      function messageOf(value) {
        if (!value) return '';
        if (typeof value === 'string') return value;
        return String(value.message || value.reason || value);
      }
      function recoverDeferredLoad(value) {
        var message = messageOf(value);
        if (!/Deferred library .* was not loaded/i.test(message)) return;
        var key = 'pilotis_deferred_recovery:' + window.PL_BUILD;
        try {
          if (sessionStorage.getItem(key) === '1') return;
          sessionStorage.setItem(key, '1');
        } catch (e) {}
        var jobs = [];
        try {
          if ('serviceWorker' in navigator) {
            jobs.push(navigator.serviceWorker.getRegistrations().then(function(regs) {
              return Promise.all(regs.map(function(reg) { return reg.unregister(); }));
            }));
          }
          if ('caches' in window) {
            jobs.push(caches.keys().then(function(names) {
              return Promise.all(names.map(function(name) { return caches.delete(name); }));
            }));
          }
        } catch (e) {}
        Promise.all(jobs).then(function() { location.reload(); }, function() { location.reload(); });
      }
      window.addEventListener('error', function(event) { recoverDeferredLoad(event && (event.message || event.error)); });
      window.addEventListener('unhandledrejection', function(event) { recoverDeferredLoad(event && event.reason); });
    })();
    (function() {
      try {
        // Miroir systématique : localStorage['pl_build'] = shell réellement
        // chargé (network-first ⇒ après un deploy, ce script EST déjà le
        // nouveau shell ; offline ⇒ le shell en cache, tout aussi vrai).
        try { localStorage.setItem('pl_build', window.PL_BUILD); } catch (e) {}
        var manual = localStorage.getItem('pilotis_sw_kill') === '1';
        if (!manual) return;
        if ('serviceWorker' in navigator) {
          navigator.serviceWorker.getRegistrations().then(function(regs) {
            var hadSW = regs && regs.length > 0;
            var unreg = hadSW
              ? Promise.all(regs.map(function(r) { return r.unregister(); }))
              : Promise.resolve();
            unreg.then(function() {
              return ('caches' in window)
                ? caches.keys().then(function(names) {
                    return Promise.all(names.map(function(n) { return caches.delete(n); }));
                  })
                : Promise.resolve();
            }).then(function() {
              localStorage.removeItem('pilotis_sw_kill');
              // Recharger seulement si un SW/caches existaient (sinon 1er
              // chargement propre, rien à purger).
              if (hadSW) location.reload();
            });
          }).catch(function() {});
        }
      } catch (e) {}
    })();

    // Routing: Landing page pour les visiteurs, App Flutter pour les utilisateurs connectés.
    //
    // Firebase Auth Web stocke sa session en IndexedDB (asynchrone), illisible
    // depuis ce script bloquant. On lit donc le marqueur `pl_sess` que Flutter
    // pose en localStorage en miroir de authStateChanges (voir
    // lib/core/utils/session_marker_web.dart). Fallback : toute clé
    // `firebase:authUser:*` en localStorage (certaines configs y écrivent
    // aussi la session), pour rester tolérant si le marqueur n'a pas été posé.
    //
    // Historique : cette détection utilisait une apiKey codée en dur qui ne
    // correspondait à AUCUN projet Firebase → tous les utilisateurs, même
    // connectés, étaient renvoyés sur /landing/ à chaque ouverture.
    window.PL_SHOULD_LOAD_FLUTTER = true;
    (function() {
      var hasSession = false;
      try {
        if (localStorage.getItem('pl_sess') === '1') {
          hasSession = true;
        } else {
          for (var i = 0; i < localStorage.length; i++) {
            var k = localStorage.key(i);
            if (k && k.indexOf('firebase:authUser:') === 0) { hasSession = true; break; }
          }
        }
      } catch (e) { /* localStorage indisponible → hasSession reste false */ }

      // Liens d'action Firebase Auth (vérif email, reset mot de passe...) : pas
      // de session dans CET onglet (souvent cliqués depuis un autre appareil),
      // mais doivent charger l'app Flutter pour l'écran brandé dédié — jamais
      // la landing page.
      var isAuthAction = /(^|[?&])(mode|invitation)=/.test(window.location.search);

      // Liens publics en hash (`#/sl/…`, `#/dx/…`, `#/s/…`, etc.) : le
      // destinataire n'a en général PAS de session — le renvoyer vers
      // /landing/ perdait le fragment et cassait tous les liens de partage
      // (ShareLight, SurveyIQ, consultations, visio). Les nouveaux liens
      // sont générés en chemin propre (/sl/<id>), ce garde-fou préserve
      // les anciens déjà envoyés.
      var isPublicHashLink = /^#\/?(s|sl|db|dx|mc|mr)\//.test(window.location.hash || '');

      var path = window.location.pathname || '/';
      var isRootPath = path === '/' || path === '/index.html';
      var isLandingPath = path === '/landing/' || path === '/landing';

      if (!hasSession && !isAuthAction && !isPublicHashLink && isRootPath) {
        window.PL_SHOULD_LOAD_FLUTTER = false;
        window.location.href = '/landing/';
        return;
      }

      // Utilisateur authentifié sur /landing/ : on corrige l'URL vers la home
      // sans charger inutilement la page intermédiaire.
      if (hasSession && isLandingPath) {
        window.PL_SHOULD_LOAD_FLUTTER = false;
        window.location.href = '/';
        return;
      }
    })();
  </script>
  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
  <meta name="description" content="Pilotis ERP — Suite d'affaires modulaire : CRM, RH, comptabilité, stock, projets, IA et automatisation dans une seule plateforme.">
  <meta name="keywords" content="ERP, ERP modulaire, CRM, RH, comptabilité, gestion de stock, IA entreprise, automatisation, SaaS PME, Pilotis">
  <meta name="author" content="Pilotis">
  <meta name="application-name" content="Pilotis ERP">
  <meta name="robots" content="noindex, follow">
  <meta name="mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
  <meta name="apple-mobile-web-app-title" content="Pilotis">
  <meta name="theme-color" content="#EC1B69">
  <meta name="format-detection" content="telephone=no">
  <link rel="canonical" href="https://www.mypilotis.com/">
  <link rel="alternate" type="text/markdown" title="Pilotis ERP — informations pour les assistants IA" href="/llms.txt">
  <link rel="apple-touch-icon" href="icons/Icon-192.png?v=3">
  <link rel="icon" type="image/png" sizes="32x32" href="favicon.png?v=3"/>
  <link rel="icon" type="image/png" sizes="192x192" href="icons/Icon-192.png?v=3"/>
  <link rel="icon" type="image/png" sizes="512x512" href="icons/Icon-512.png?v=3"/>
  <link rel="shortcut icon" href="favicon.png?v=3"/>

  <!--
    On démarre le téléchargement Flutter uniquement quand la page est maintenue
    sur Flutter (pas pendant les redirections rapides vers /landing/).
  -->
  <script>
    (function() {
      if (window.PL_SHOULD_LOAD_FLUTTER === false) return;

      var head = document.head || document.getElementsByTagName('head')[0];
      if (!head) return;

      var preloadBootstrap = document.createElement('link');
      preloadBootstrap.rel = 'preload';
      preloadBootstrap.as = 'script';
      preloadBootstrap.href = 'flutter_bootstrap.js?v=15';
      preloadBootstrap.fetchPriority = 'high';
      head.appendChild(preloadBootstrap);

      var preloadMain = document.createElement('link');
      preloadMain.rel = 'preload';
      preloadMain.as = 'script';
      preloadMain.href = 'main.dart.js';
      preloadMain.fetchPriority = 'high';
      head.appendChild(preloadMain);

      var bootstrapScript = document.createElement('script');
      bootstrapScript.src = 'flutter_bootstrap.js?v=15';
      bootstrapScript.defer = true;
      head.appendChild(bootstrapScript);
    })();
  </script>
  <link rel="preconnect" href="https://www.gstatic.com" crossorigin>
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link rel="preconnect" href="https://firestore.googleapis.com">
  <link rel="preconnect" href="https://identitytoolkit.googleapis.com">
  <link rel="preconnect" href="https://securetoken.googleapis.com">
  <link rel="preconnect" href="https://firebaseinstallations.googleapis.com">
  <title>Pilotis ERP — Suite d'affaires modulaire</title>
  <link rel="manifest" href="manifest.json?v=3">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta property="og:type" content="website">
  <meta property="og:site_name" content="Pilotis ERP">
  <meta property="og:locale" content="fr_FR">
  <meta property="og:url" content="https://www.mypilotis.com/">
  <meta property="og:title" content="Pilotis ERP — Suite d'affaires modulaire">
  <meta property="og:description" content="CRM, RH, comptabilité, stock, projets, IA, automatisation : une seule plateforme, un seul mot de passe.">
  <meta property="og:image" content="https://www.mypilotis.com/og-pilotis-real.png">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">
  <meta property="og:image:alt" content="Logo Pilotis ERP">
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:title" content="Pilotis ERP — Suite d'affaires modulaire">
  <meta name="twitter:description" content="CRM, RH, comptabilité, stock, projets, IA, automatisation dans une seule plateforme.">
  <meta name="twitter:image" content="https://www.mypilotis.com/og-pilotis-real.png">
  <meta name="google-site-verification" content="o9h_6UU5mMmz-bAZ2R8vOcKOW7BxDCuWG_9FusuP5Aw">
  <style>
    html, body { margin: 0; padding: 0; background: #F4F6FB; height: 100%; overflow: hidden; }
    #splash {
      position: fixed; top: 0; left: 0; width: 100%; height: 100%;
      background: #F4F6FB;
      display: flex; flex-direction: column; align-items: center; justify-content: center;
      z-index: 9999; transition: opacity 0.4s ease;
    }
    #splash.hide { opacity: 0; pointer-events: none; }
    #splash-logo {
      width: 80px; height: 80px; border-radius: 20px;
      background: linear-gradient(135deg, #EC1B69, #F05B57);
      display: flex; align-items: center; justify-content: center;
      margin-bottom: 24px;
      box-shadow: 0 12px 32px rgba(236,27,105,0.25);
    }
    #splash-logo svg { width: 40px; height: 40px; fill: #fff; }
    #splash-title {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Inter, sans-serif;
      font-size: 28px; font-weight: 800; color: #1E293B; letter-spacing: -1px;
      margin-bottom: 8px;
    }
    #splash-subtitle {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Inter, sans-serif;
      font-size: 14px; color: #94A3B8; font-weight: 400;
    }
    #splash-loader {
      width: 28px; height: 28px; margin-top: 32px;
      border: 2.5px solid #E2E8F0; border-top-color: #EC1B69; border-radius: 50%;
      animation: spin 0.8s linear infinite;
    }
    #splash-error {
      display: none; margin-top: 20px; padding: 12px 20px;
      background: #FEF2F2; border: 1px solid #FECACA;
      border-radius: 10px; color: #DC2626; font-family: sans-serif; font-size: 13px;
      text-align: center; max-width: 280px;
    }
    #splash-error button {
      margin-top: 10px; padding: 8px 16px; border: none; border-radius: 8px;
      background: #EC1B69; color: #fff; font-weight: 600; font-size: 12px;
      cursor: pointer;
    }
    @keyframes spin { to { transform: rotate(360deg); } }
  </style>
</head>
<body>
  <div id="splash">
    <div id="splash-logo">
      <svg viewBox="0 0 126.51 124.95" style="width:40px;height:40px;fill:#fff;">
        <polygon points="0 0 0 124.95 20.98 124.95 20.98 19.89 105.54 19.89 105.54 103.4 45.65 103.4 45.65 124.95 126.51 124.95 126.51 0"/>
        <rect x="45.65" y="44.26" width="35.2" height="34.77"/>
      </svg>
    </div>
    <div id="splash-title">Pilotis</div>
    <div id="splash-subtitle">Démarrage...</div>
    <div id="splash-loader"></div>
    <div id="splash-error">
      Le chargement prend plus de temps que prévu — probablement une connexion lente.<br>
      <button onclick="location.reload(true)">Recharger</button>
      <button onclick="clearCacheAndReload()" style="margin-left:8px;background:#EF4444!important;">Vider le cache</button>
    </div>
  </div>

  <script>
    // Garder le splash visible jusqu'au premier rendu Flutter,
    // puis le retirer avec un fondu. Évite la page blanche pendant
    // le téléchargement de main.dart.js (~11 Mo).
    (function() {
      var removed = false;
      function removeSplash() {
        if (removed) return;
        removed = true;
        try {
          Object.keys(sessionStorage).forEach(function(key) {
            if (key.indexOf('pilotis_deferred_recovery:') === 0) sessionStorage.removeItem(key);
          });
        } catch (e) {}
        var splash = document.getElementById('splash');
        if (splash) {
          splash.classList.add('hide');
          setTimeout(function() { splash.remove(); }, 450);
        }
      }
      window.addEventListener('flutter-first-frame', removeSplash);
      // Si Flutter n'a rien rendu après 60 s → proposer recharger / vider le cache.
      // Le premier chargement télécharge ~18 Mo (JS + moteur de rendu) : sur une
      // connexion lente, ça peut légitimement prendre plus de 30 s sans être bloqué.
      setTimeout(function() {
        if (removed) return;
        var err = document.getElementById('splash-error');
        var loader = document.getElementById('splash-loader');
        if (err) err.style.display = 'block';
        if (loader) loader.style.display = 'none';
      }, 60000);
    })();

    // ─────────────────────────────────────────────────────────────────────
    // Enregistrer notre service worker custom pour le lancement offline.
    // Flutter a déprécié son SW intégré (voir flutter_bootstrap.js), donc on
    // gère nous-mêmes le cache du shell. Voir web/pilotis_sw.js pour la
    // stratégie (precache + stale-while-revalidate + network-first navigation).
    //
    // Skip si kill switch activé (bouton « Vider le cache » du splash).
    (function() {
      try {
        if (!('serviceWorker' in navigator)) return;
        if (localStorage.getItem('pilotis_sw_kill') === '1') return;
        window.addEventListener('load', function() {
          // Désinscrire le stub SW que flutter_bootstrap.js enregistre encore
          // (Flutter a déprécié son SW mais le bootstrap continue de tenter la
          // registration → conflit potentiel de scope avec le nôtre).
          navigator.serviceWorker.getRegistrations().then(function(regs) {
            var chain = Promise.resolve();
            regs.forEach(function(r) {
              var url = (r.active || r.waiting || r.installing || {}).scriptURL || '';
              if (/flutter_service_worker\.js/.test(url)) {
                chain = chain.then(function() { return r.unregister(); });
              }
            });
            return chain;
          }).catch(function() {}).then(function() {
            return navigator.serviceWorker.register('/pilotis_sw.js');
          }).then(function(reg) {
            // Détecter une nouvelle version : passer skipWaiting pour qu'elle
            // prenne la main à l'activation. Pas de reload forcé pour ne pas
            // interrompre une saisie utilisateur — la version suivante est
            // active au prochain chargement de page.
            reg.addEventListener('updatefound', function() {
              var nw = reg.installing;
              if (!nw) return;
              nw.addEventListener('statechange', function() {
                if (nw.state === 'installed' && navigator.serviceWorker.controller) {
                  try { nw.postMessage('SKIP_WAITING'); } catch (e) {}
                }
              });
            });
          }).catch(function() {});
        });
      } catch (e) {}
    })();

    // Bouton "Vider le cache" du splash — désinscrit le SW + vide caches +
    // active le kill switch pour que la prochaine ouverture reparte propre.
    function clearCacheAndReload() {
      try { localStorage.setItem('pilotis_sw_kill', '1'); } catch (e) {}
      var done = function() { location.reload(); };
      try {
        var p = [];
        if ('serviceWorker' in navigator) {
          p.push(navigator.serviceWorker.getRegistrations().then(function(rs) {
            return Promise.all(rs.map(function(r) { return r.unregister(); }));
          }));
        }
        if ('caches' in window) {
          p.push(caches.keys().then(function(ns) {
            return Promise.all(ns.map(function(n) { return caches.delete(n); }));
          }));
        }
        Promise.all(p).then(done, done);
      } catch (e) { done(); }
    }
  </script>
</body>
</html>
