// Onboarding — 4 écrans au premier lancement, une seule fois. // // Passe après la connexion : on ne présente pas l'app à quelqu'un qui n'est // pas encore entré. Mémorisé en localStorage sous `vasco_onboarded` ; le // panneau Tweaks sait le remettre à zéro pour rejouer la séquence en démo. // // Navigation : balayage horizontal, tap sur « Suivant », ou « Passer ». const OB_VT = window.VT || {}; const OB_INK = OB_VT.ink || '#0a0a0a'; const OB_SUB = OB_VT.sub || '#6b7280'; const OB_BG = OB_VT.paper || '#f6f5f1'; const OB_SURF = OB_VT.surface || '#ffffff'; const OB_HAIR = OB_VT.hair || 'rgba(10,10,10,0.06)'; const OB_HAIR2 = OB_VT.hair2 || 'rgba(10,10,10,0.10)'; const OB_MED = OB_VT.medBlue || '#3d6b8f'; const OB_MED_TINT = OB_VT.medBlueTint || 'rgba(61,107,143,0.10)'; const OB_OK = OB_VT.success || '#059669'; const OB_OK_TINT = OB_VT.successTint || 'rgba(5,150,105,0.10)'; const VASCO_ONBOARD_KEY = 'vasco_onboarded'; // `?onboarding` dans l'URL force la relecture — c'est le chemin de démo. // Le panneau Tweaks ne s'ouvre que depuis un hôte d'aperçu (il attend un // postMessage) : dans un navigateur normal son bouton est inatteignable. const obForced = () => { try { return /(^|[?&])onboarding(=|&|$)/.test(window.location.search); } catch (e) { return false; } }; // Même prudence que pour l'auth : localStorage lève en navigation privée. const obRead = () => { if (obForced()) return null; try { return window.localStorage.getItem(VASCO_ONBOARD_KEY); } catch (e) { return null; } }; const obWrite = () => { try { window.localStorage.setItem(VASCO_ONBOARD_KEY, '1'); } catch (e) { /* sans mémoire */ } }; const obClear = () => { try { window.localStorage.removeItem(VASCO_ONBOARD_KEY); } catch (e) { /* rien à faire */ } }; // ───────────────────────────────────────────────────────────── // Illustrations — géométriques, sur la palette de l'app. // Pas d'images : tout est vectoriel, donc net à toutes les tailles. // ───────────────────────────────────────────────────────────── function ObArtDictee() { return (
{/* Halos concentriques — la même respiration que le micro du dashboard */} {[0, 1, 2].map((i) => (
))}
{/* Onde — barres qui pulsent sous le micro */}
{[0, 1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
))}
); } function ObArtPatients() { // Reprend trois teintes de la palette de dossiers (manila / mint / sky). const cards = [ { body: '#dbeafe', spine: '#94bff0', rot: -9, x: -46, y: 10, z: 1 }, { body: '#dcfce7', spine: '#a7e8be', rot: 5, x: 44, y: 4, z: 2 }, { body: '#fdf3c4', spine: '#f5d97a', rot: -2, x: 0, y: -6, z: 3 }, ]; return (
{cards.map((c, i) => ( // Deux niveaux : l'extérieur anime l'entrée, l'intérieur porte la // position et l'inclinaison. Sur un seul élément, le transform de // l'animation écraserait le rotate et les cartes s'empileraient.
{/* Onglet du dossier */}
))}
); } function ObArtAssistant() { const chips = ['Courrier', 'CCAM', 'RDV']; return (
{/* Demande du praticien */}
Code CCAM du pontage ?
{/* Réponse de l'assistant */}
EBAF003
Pontage fémoro-poplité
{/* Actions proposées */}
{chips.map((c, i) => ( {c} ))}
); } function ObArtPret() { return (
{/* Le tracé se dessine plutôt que d'apparaître d'un coup */}
); } const OB_SLIDES = [ { id: 'dictee', art: , title: 'Dictez,\non rédige.', body: "Un compte rendu opératoire complet en parlant deux minutes. Vous relisez, vous corrigez, vous validez.", }, { id: 'patients', art: , title: 'Vos dossiers,\nen un geste.', body: "Wallet, liste ou grille — chaque patient à portée de pouce, avec ses alertes et son prochain rendez-vous.", }, { id: 'assistant', art: , title: 'Un assistant\nqui agit.', body: "Courrier au correspondant, codage CCAM, contrôle à programmer : vous demandez, c'est préparé.", }, { id: 'pret', art: , title: 'Tout est prêt,\nDocteur.', body: "Les données affichées sont fictives. Rien ne quitte l'appareil tant que la connexion n'est pas établie.", }, ]; // ───────────────────────────────────────────────────────────── // Écran // ───────────────────────────────────────────────────────────── function OnboardingScreen({ accent, onDone }) { const [idx, setIdx] = React.useState(0); const [drag, setDrag] = React.useState(0); const startX = React.useRef(null); const width = React.useRef(1); const last = OB_SLIDES.length - 1; const tint = accent || OB_MED; const finish = () => { obWrite(); onDone(); }; const next = () => (idx === last ? finish() : setIdx(idx + 1)); // ── Balayage ── const onDown = (e) => { startX.current = e.clientX; width.current = e.currentTarget.getBoundingClientRect().width || 1; e.currentTarget.setPointerCapture && e.currentTarget.setPointerCapture(e.pointerId); }; const onMove = (e) => { if (startX.current === null) return; const dx = e.clientX - startX.current; // Résistance aux deux extrémités : on sent le bout de la pile. const overshoot = (idx === 0 && dx > 0) || (idx === last && dx < 0); setDrag(overshoot ? dx * 0.28 : dx); }; const onUp = () => { if (startX.current === null) return; const seuil = width.current * 0.18; if (drag < -seuil && idx < last) setIdx(idx + 1); else if (drag > seuil && idx > 0) setIdx(idx - 1); startX.current = null; setDrag(0); }; const n = OB_SLIDES.length; const dragging = startX.current !== null; return (
{/* Passer */}
{/* Piste des écrans */}
{OB_SLIDES.map((s, i) => (
{/* L'illustration est toujours rendue — sinon l'écran voisin serait vide pendant le balayage. Le `key` la remonte quand elle devient active, ce qui rejoue son animation d'entrée pile au moment où elle arrive à l'écran. */}
{s.art}
{s.title}
{s.body}
))}
{/* Progression + action */}
{OB_SLIDES.map((s, i) => (
); } window.OnboardingScreen = OnboardingScreen; window.vascoOnboardRead = obRead; window.vascoOnboardClear = obClear; console.log('%c[VASCO] onboarding.jsx chargé', 'color:#3d6b8f;font-weight:bold');