/* Poarta pilotului: versiune -> cod de acces -> identitate -> aplicatie. Scrie DIRECT in localStorage, iar App citeste store-ul cand se monteaza (adica dupa ce poarta se deschide) - asa nu e nevoie de niciun canal de comunicare intre cele doua radacini de stare. Regula care nu se incalca: catre server pleaca DOAR metadate (cine, cand, cat, ce versiune). Niciun plan, nicio bifa, niciun raspuns. Serverul respinge orice camp in plus. */ const PILOT_CHEIE_STORE = "prp-credinte-proto-v1"; const PILOT_CHEIE_ACCES = "prp-pilot-acces"; const PILOT_COD = "PRP2026"; /* bariera, NU securitate: ruland in browser, e vizibil oricui */ const PILOT_API = "/api/ev"; function pilotCitesteStore() { try { return JSON.parse(localStorage.getItem(PILOT_CHEIE_STORE) || "{}"); } catch (e) { return {}; } } function pilotScrieStore(s) { try { localStorage.setItem(PILOT_CHEIE_STORE, JSON.stringify(s)); } catch (e) { /* plin: ignoram */ } } function pilotIdNou() { if (window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID(); return "id-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10); } /* Scrie in ACELASI camp `consola` pe care il foloseste aplicatia, ca poarta veche (`consolaOk`: nume + dataStart) sa fie satisfacuta de aceleasi date - altfel participantul ar trece prin doua ecrane de intrare care cer acelasi lucru. */ function pilotScrieConsola(campuri) { const s = pilotCitesteStore(); s.consola = Object.assign( { nume: "", dataStart: "", consultant: "Dr. Petre Nicolae", telefon: "", email: "", obs: "" }, s.consola, campuri); if (!s.pilotId) s.pilotId = pilotIdNou(); pilotScrieStore(s); return s; } /* ── Ecrane ────────────────────────────────────────────────────── */ function PilotEcran({ titlu, children }) { return (
Logo PRP
People Reconstruction Process

{titlu}

{children}
); } /* Trecerea pe versiunea noua: intai copie locala a datelor, apoi se sterge service worker-ul vechi si se reincarca. Datele raman in acelasi sertar - adresa nu se schimba niciodata. */ async function pilotActualizeaza() { /* Copia se face INTAI: daca ceva merge prost in pasii urmatori, datele sunt deja puse deoparte. */ window.PRP_PILOT.faBackupLocal(localStorage, PILOT_CHEIE_STORE, window.PRP_VERSIUNE || "necunoscuta"); try { if (navigator.serviceWorker) { const reg = await navigator.serviceWorker.getRegistration(); if (reg) { await reg.update(); /* aduce versiunea noua daca nu e deja in asteptare */ const nou = reg.waiting || reg.installing; if (nou) { /* asteptam sa fie gata de preluare, apoi ii cerem sa preia - fara asta, un service worker inca in instalare ar ignora mesajul si am reincarca degeaba */ await new Promise((gata) => { if (nou.state === "installed") return gata(); nou.addEventListener("statechange", () => { if (nou.state === "installed") gata(); }); setTimeout(gata, 8000); /* plasa: nu blocam participantul la nesfarsit */ }); /* `skipWaiting` e asincron: daca reincarcam imediat dupa mesaj, pagina e servita tot de workerul VECHI si abia a doua reincarcare aduce versiunea noua (masurat 25.07). Asteptam schimbarea efectiva a controlorului, ca un singur clic sa ajunga. */ const preluat = new Promise((gata) => { navigator.serviceWorker.addEventListener("controllerchange", gata, { once: true }); setTimeout(gata, 8000); }); nou.postMessage({ tip: "preia-controlul" }); await preluat; } else { /* nicio versiune in asteptare: stergem inregistrarea, ca reincarcarea sa ia totul de la server */ await reg.unregister(); } } } } catch (e) { /* fara service worker: reincarcarea simpla e suficienta */ } location.reload(); } function PilotEcranUpdate() { const [lucreaza, setLucreaza] = React.useState(false); return (

A apărut o versiune nouă, iar cea de pe calculatorul tău nu mai poate fi folosită. Datele tale rămân neatinse: înainte de actualizare se salvează automat o copie, tot aici, pe calculatorul tău.

); } /* Versiune mai veche, dar inca acceptata: nu blocam pe nimeni pentru o corectura marunta. */ function PilotBanner() { const [ascuns, setAscuns] = React.useState(false); if (ascuns) return null; return (
Există o versiune nouă a instrumentului.
); } function PilotEcranCod({ onOk }) { const [cod, setCod] = React.useState(""); const [gresit, setGresit] = React.useState(false); const trimite = (e) => { e.preventDefault(); if (cod.trim().toUpperCase() === PILOT_COD) { try { localStorage.setItem(PILOT_CHEIE_ACCES, "1"); } catch (err) { /* privat: continuam */ } onOk(); } else { setGresit(true); } }; return (

Introdu codul primit împreună cu linkul.

{ setCod(e.target.value); setGresit(false); }} /> {gresit &&
Codul nu este corect.
}
); } function PilotEcranIdentitate({ onOk }) { const c = pilotCitesteStore().consola || {}; const [nume, setNume] = React.useState(c.nume || ""); const [email, setEmail] = React.useState(c.email || ""); const [data, setData] = React.useState(c.dataStart || new Date().toISOString().slice(0, 10)); const [acord, setAcord] = React.useState(true); const valid = nume.trim().length >= 3 && /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email.trim()) && !!data; const trimite = (e) => { e.preventDefault(); if (!valid) return; const s = pilotScrieConsola({ nume: nume.trim(), email: email.trim().toLowerCase(), dataStart: data, }); s.pilotAcord = acord; pilotScrieStore(s); onOk(); }; return (

Instrumentul este al tău: tot ce scrii aici rămâne pe calculatorul acesta și nu pleacă nicăieri. Avem nevoie doar de numele și de emailul tău, ca să știm cine face parte din program.

); } /* ── Instalarea: automata unde se poate, o singura instructiune unde nu ── */ /* Deja instalata = aplicatia ruleaza in fereastra proprie, fara bara de adrese. */ function pilotEsteInstalata() { try { return window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true; } catch (e) { return false; } } /* Instructiunea se da DOAR unde browserul nu ofera instalare programatica, si e specifica browserului real - un manual cu toate variantele nu il citeste nimeni. */ function pilotInstructiuneManuala() { const ua = navigator.userAgent; const eIOS = /iPhone|iPad|iPod/.test(ua); const eSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(ua); if (eIOS) return "Apasă butonul Partajează din bara de jos, apoi „Adaugă pe ecranul principal”."; if (eSafari) return "Din meniul Fișier alege „Adaugă în Dock”."; if (/Firefox/.test(ua)) return null; /* Firefox nu instalează aplicații web; nu il batem la cap */ return null; } function PilotInstalare({ compact }) { const [prompt, setPrompt] = React.useState(() => window.__promptInstalare || null); const [instalata, setInstalata] = React.useState(pilotEsteInstalata); const [ascuns, setAscuns] = React.useState(() => { try { return localStorage.getItem("prp-pilot-instalare-ascuns") === "1"; } catch (e) { return false; } }); const [lucreaza, setLucreaza] = React.useState(false); React.useEffect(() => { const laDisponibil = () => setPrompt(window.__promptInstalare); const laInstalat = () => { setPrompt(null); setInstalata(true); }; window.addEventListener("prp-instalabil", laDisponibil); window.addEventListener("prp-instalat", laInstalat); return () => { window.removeEventListener("prp-instalabil", laDisponibil); window.removeEventListener("prp-instalat", laInstalat); }; }, []); const instaleaza = async () => { if (!prompt) return; setLucreaza(true); try { prompt.prompt(); const rez = await prompt.userChoice; if (rez && rez.outcome === "accepted") { setInstalata(true); } window.__promptInstalare = null; setPrompt(null); } catch (e) { /* utilizatorul a inchis dialogul */ } setLucreaza(false); }; const ascunde = () => { try { localStorage.setItem("prp-pilot-instalare-ascuns", "1"); } catch (e) { /* privat */ } setAscuns(true); }; if (instalata) { return compact ?
Aplicația este instalată pe acest calculator.
: null; } const manual = prompt ? null : pilotInstructiuneManuala(); if (!prompt && !manual) return null; /* browser fara instalare: nu spunem nimic */ if (ascuns && !compact) return null; const continut = ( <> {prompt ? "Instalează instrumentul pe calculatorul tău: se deschide cu o iconiță și merge și fără internet." : manual} {prompt && ( )} {!compact && } ); if (compact) return
{continut}
; return (
{continut}
); } /* ── Telemetria: prezenta si timp, nimic altceva ───────────────── */ function PilotTelemetrie() { React.useEffect(() => { if (pilotCitesteStore().pilotAcord === false) return; /* acord retras: instrumentul merge, masurarea nu */ const coada = window.PRP_PILOT.Coada(localStorage); const identitate = () => { const s = pilotCitesteStore(); const c = s.consola || {}; return { id: s.pilotId || "", nume: c.nume || "", email: c.email || "", versiune: window.PRP_VERSIUNE || "", }; }; const descarca = async () => { const ev = coada.toate(); if (!ev.length) return; try { const r = await fetch(PILOT_API, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(Object.assign(identitate(), { evenimente: ev })), }); if (r.ok) coada.goleste(); } catch (e) { /* fara retea: raman in coada pana data viitoare */ } }; coada.adauga({ tip: "deschidere", t: Date.now() }); descarca(); const puls = window.PRP_PILOT.Puls({ acum: () => Date.now(), trimite: (ev) => coada.adauga(ev), }); puls.interactiune(); const peInteractiune = () => puls.interactiune(); const evenimenteUI = ["pointerdown", "keydown", "wheel"]; evenimenteUI.forEach((e) => window.addEventListener(e, peInteractiune, { passive: true })); const tick = setInterval(() => puls.tick(document.visibilityState === "visible"), 60000); const sincron = setInterval(descarca, 300000); const laRevenire = () => descarca(); window.addEventListener("online", laRevenire); /* sendBeacon e singurul care mai apuca sa plece la inchiderea ferestrei */ const laPlecare = () => { coada.adauga({ tip: "plecare", t: Date.now() }); try { const corp = JSON.stringify(Object.assign(identitate(), { evenimente: coada.toate() })); if (navigator.sendBeacon(PILOT_API, new Blob([corp], { type: "application/json" }))) { coada.goleste(); } } catch (e) { /* raman in coada */ } }; window.addEventListener("pagehide", laPlecare); return () => { clearInterval(tick); clearInterval(sincron); puls.opreste(); evenimenteUI.forEach((e) => window.removeEventListener(e, peInteractiune)); window.removeEventListener("online", laRevenire); window.removeEventListener("pagehide", laPlecare); }; }, []); return null; } /* ── Poarta ────────────────────────────────────────────────────── */ /* Ordinea conteaza: versiunea se verifica PRIMA, ca o versiune retrasa sa nu mai poata nici colecta, nici scrie in store. */ function PilotGate({ children }) { const [stare, setStare] = React.useState("verifica"); const [pas, setPas] = React.useState(0); /* re-randare dupa ce s-a scris in localStorage */ React.useEffect(() => { let anulat = false; fetch("versiune.json?t=" + Date.now(), { cache: "no-store" }) .then((r) => r.json()) .then((srv) => { if (!anulat) setStare(window.PRP_PILOT.stareVersiune(window.PRP_VERSIUNE, srv)); }) .catch(() => { if (!anulat) setStare("ok"); }) /* fara retea: se lucreaza mai departe */; return () => { anulat = true; }; }, []); if (stare === "verifica") return null; if (stare === "blocat") return ; let acces = null; try { acces = localStorage.getItem(PILOT_CHEIE_ACCES); } catch (e) { /* privat */ } if (acces !== "1") return setPas((n) => n + 1)} />; const c = pilotCitesteStore().consola || {}; if (!c.nume || !c.email) return setPas((n) => n + 1)} />; return ( <> {children} {/* bannerul de update are prioritate: cand exista versiune noua, nu il incarcam si cu instalarea */} {stare === "optional" ? : } ); } /* ── Ecranul "Datele mele" ─────────────────────────────────────── */ function PilotDateleMele() { const s = pilotCitesteStore(); const c = s.consola || {}; const [acord, setAcord] = React.useState(s.pilotAcord !== false); const schimbaAcord = (v) => { const st = pilotCitesteStore(); st.pilotAcord = v; pilotScrieStore(st); setAcord(v); if (!v) { /* oprirea are efect imediat: golim si ce astepta in coada */ window.PRP_PILOT.Coada(localStorage).goleste(); } }; const stergeTot = () => { if (!confirm("Se șterg TOATE datele tale de pe acest calculator. Nu se mai pot recupera. Continui?")) return; if (!confirm("Ești sigur? Dacă nu ai exportat datele, fă-o întâi.")) return; try { Object.keys(localStorage) .filter((k) => k.indexOf("prp-credinte-proto") === 0 || k.indexOf("prp-pilot") === 0) .forEach((k) => localStorage.removeItem(k)); } catch (e) { /* ignoram */ } location.reload(); }; const backupuri = (() => { try { return Object.keys(localStorage).filter((k) => k.indexOf(PILOT_CHEIE_STORE + ".bak-") === 0) .map((k) => k.split(".bak-")[1]).sort(); } catch (e) { return []; } })(); return (
Ce știm despre tine
Nume{c.nume || "-"}
Email{c.email || "-"}
Versiunea instrumentului{window.PRP_VERSIUNE || "necunoscută"}
Momentele în care deschizi instrumentul și cât timp stai {acord ? "se înregistrează" : "nu se înregistrează"}

Nimic din ce scrii în instrument nu pleacă de pe acest calculator: nici planurile, nici bifele, nici răspunsurile la chestionare. Nu le putem citi.

Controalele tale

Pentru ștergerea din evidența programului, scrie consultantului tău.

Instalarea pe acest calculator

Instalată, aplicația se deschide cu o iconiță proprie și funcționează și fără internet. Datele rămân aceleași, indiferent dacă o deschizi din iconiță sau din link.

Copii de siguranță pe acest calculator
{backupuri.length === 0 ?
Încă nicio copie Se creează automat câte una înainte de fiecare actualizare.
:
    {backupuri.map((v) =>
  • copie de dinaintea versiunii {v}
  • )}
}

Copiile stau tot aici, la tine. Ele nu te apără dacă ștergi datele browserului sau schimbi calculatorul: pentru asta, exportă-ți procesul din Consolă și păstrează fișierul.

); } window.PilotGate = PilotGate; window.PilotDateleMele = PilotDateleMele; window.pilotCitesteStore = pilotCitesteStore; window.IcScut = (p) => ( );