docs(prd): 5.16 tipografie+bugfix editare + 5.17 tipuri cont + mockup-uri

PRD 5.16 (draft) — propagare design uniform peste aplicatie:
- fonturi standard web (system font stack), scala uniforma --fs-* (carduri aerisite)
- RAR online = dot in antet (datetime pe hover) + meniu burger; banda doar cand e blocat
- antet branded "ROMFAST AUTOPASS" + nume service + badge plan (gate is_authenticated)
- /login profesional (antet minimal pre-login), selector tema stil landing
- bug-uri editare: denumiri in picker, adaugare operatie extra, fix save no-op, fix Renunta
- dashboard compact: strip-less, contoare separate (mobil = bara numere), import colapsat,
  ordine carduri->import->tab-uri->lista, meniu cu separatoare
- wizard import (4 pasi) + editare/corectie aliniate la design

PRD 5.17 (draft) — tipuri de cont (Gratuit/Standard/Pro/Premium) + trial Pro 30 zile:
- model accounts.tier + trial_until, app/plans.py sursa unica
- enforcement DUR: limita Gratuit 60/luna (era 100) + API doar Pro+
- downgrade automat la expirare trial; aliniere landing (60, "Pro gratuit 30 zile")

Mockup-uri vizuale (docs/mockups/prd-5.16-*.html): fonturi, header+login+tema,
dashboard desktop+mobil, wizard import. Doar documentatie + mockup-uri; fara cod aplicatie.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-28 21:20:20 +00:00
parent 3fc53534e2
commit 8dd0e1678c
7 changed files with 1912 additions and 0 deletions

View File

@@ -0,0 +1,221 @@
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PRD 5.16 — Dashboard mobil 390px (RAR dot in antet + meniu)</title>
<style>
:root{
--font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Segoe UI Mono", "Roboto Mono", Menlo, Consolas, monospace;
--fs-xs:12px; --fs-sm:13.5px; --fs-base:15px; --fs-md:16px; --fs-lg:18px; --fs-xl:20px; --fs-2xl:28px;
--bg:#0f1218; --card:#181c24; --card2:#0f1218; --ink:#e6e9ef; --muted:#8b93a7;
--line:#262b36; --line2:#1f2530; --accent:#6ea2ec; --ok:#2FBF8F; --warn:#E0A93B; --err:#E05D5D;
--hbg:rgba(15,18,24,.95);
}
*{box-sizing:border-box;}
body{margin:0; background:#05070b; font-family:var(--font-ui); -webkit-font-smoothing:antialiased; padding:24px;}
.mono{font-family:var(--font-mono);} .muted{color:var(--muted);}
.stage{display:flex; gap:34px; justify-content:center; align-items:flex-start; flex-wrap:wrap;}
.cap{text-align:center; color:#9aa3b2; font-size:13px; margin-top:10px; max-width:390px;}
.phone{width:390px; background:var(--bg); color:var(--ink); border-radius:30px; border:10px solid #20242c; overflow:hidden; box-shadow:0 30px 70px -20px rgba(0,0,0,.7);}
.phone .screen{height:720px; overflow:hidden; position:relative;}
.scroll{height:100%; overflow:auto;}
header{position:sticky; top:0; z-index:5; display:flex; align-items:center; justify-content:space-between; gap:8px; height:56px; padding:0 12px; background:var(--hbg); backdrop-filter:blur(8px); border-bottom:1px solid var(--line);}
.logo-fallback{display:inline-flex; align-items:center; gap:4px; font-weight:800; font-size:var(--fs-base);}
.logo-fallback .rom{color:#D1342F;} .logo-fallback .fast{color:var(--accent);}
.h-center{flex:1; text-align:center; line-height:1.1; min-width:0;}
.h-title{font-size:var(--fs-sm); font-weight:700;} .h-title .accent{color:var(--accent);}
.tier{display:inline-block; margin-left:5px; padding:0 7px; border-radius:99px; font-size:9px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:var(--accent); background:color-mix(in srgb,var(--accent) 16%,transparent); vertical-align:middle;}
.h-sub{font-size:11px; color:var(--muted); margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
.h-sub .svc{color:var(--ink); font-weight:600;}
.h-right{display:flex; align-items:center; gap:7px;}
/* RAR online = dot compact in antet (title pe hover); blocat => rosu */
.rar-dot{width:38px; height:38px; border-radius:9px; border:1px solid color-mix(in srgb,var(--ok) 35%,var(--line)); background:color-mix(in srgb,var(--ok) 10%,transparent); display:inline-flex; align-items:center; justify-content:center; cursor:default;}
.rar-dot .d{width:11px; height:11px; border-radius:99px; background:var(--ok); box-shadow:0 0 0 4px color-mix(in srgb,var(--ok) 22%,transparent);}
.icon-btn{width:40px; height:40px; border-radius:9px; border:1px solid var(--line); background:transparent; color:var(--ink); cursor:pointer; display:inline-flex; align-items:center; justify-content:center;}
.body{padding:12px; display:flex; flex-direction:column; gap:12px;}
/* CARDURI compacte — doar numere, un rand */
.stats{display:flex; background:var(--card2); border:1px solid var(--line); border-radius:11px; overflow:hidden;}
.stat{flex:1; text-align:center; padding:10px 4px; border-right:1px solid var(--line2);}
.stat:last-child{border-right:none;}
.stat .n{font-size:var(--fs-xl); font-weight:700; line-height:1;}
.stat .l{font-size:11px; color:var(--muted); margin-top:4px;}
.s-ok{color:var(--ok);} .s-acc{color:var(--accent);} .s-err{color:var(--err);}
/* IMPORT colapsat */
.import-collapse{border:1px solid var(--line); border-radius:11px; background:var(--card); overflow:hidden;}
.import-collapse>summary{list-style:none; cursor:pointer; display:flex; align-items:center; justify-content:space-between; gap:8px; padding:13px 14px; font-size:var(--fs-base); font-weight:600; color:var(--ink); min-height:48px;}
.import-collapse>summary::-webkit-details-marker{display:none;}
.import-collapse>summary .ic-l{display:flex; align-items:center; gap:9px;}
.import-collapse .plus{display:inline-flex; width:24px; height:24px; align-items:center; justify-content:center; border-radius:7px; background:color-mix(in srgb,var(--accent) 16%,transparent); color:var(--accent); font-size:17px; line-height:1;}
.import-collapse>summary .chev{font-size:var(--fs-sm); color:var(--muted);}
/* NAV */
.subnav{display:flex; gap:6px; border-bottom:1px solid var(--line);}
.subnav a{flex:1; text-align:center; font-size:var(--fs-sm); font-weight:600; padding:10px 0; border-radius:9px 9px 0 0; color:var(--muted); text-decoration:none; border:1px solid transparent; border-bottom:none; margin-bottom:-1px;}
.subnav a.active{color:var(--ink); background:var(--card); border-color:var(--line); border-bottom:1px solid var(--card);}
.badge{display:inline-flex; align-items:center; justify-content:center; min-width:18px; height:18px; margin-left:5px; padding:0 5px; border-radius:99px; background:var(--err); color:#fff; font-size:11px; font-weight:700;}
/* LISTA — filtre se ASEAZA pe randuri (wrap), FARA linie de scroll */
.panel{background:var(--card); border:1px solid var(--line); border-radius:0 11px 11px 11px; overflow:hidden;}
.filtre{display:flex; gap:7px; flex-wrap:wrap; padding:11px 12px; border-bottom:1px solid var(--line2);}
.pillf{font-size:var(--fs-sm); padding:7px 14px; border-radius:99px; border:1px solid var(--line); background:transparent; color:var(--muted);}
.pillf.on{background:color-mix(in srgb,var(--accent) 16%,transparent); border-color:transparent; color:var(--accent); font-weight:600;}
.rand{display:flex; align-items:center; justify-content:space-between; gap:10px; padding:13px 13px; border-bottom:1px solid var(--line2); min-height:56px;}
.rand:last-child{border-bottom:none;}
.slim-vin{font-family:var(--font-mono); font-size:var(--fs-md); font-weight:500;}
.slim-meta{font-size:var(--fs-sm); color:var(--muted); margin-top:3px;}
.pill{display:inline-flex; align-items:center; gap:6px; padding:5px 11px; border-radius:99px; font-size:var(--fs-sm); font-weight:500; flex-shrink:0;}
.pill .pdot{width:7px; height:7px; border-radius:99px;}
.pill.sent{background:color-mix(in srgb,var(--ok) 14%,transparent); color:var(--ok);} .pill.sent .pdot{background:var(--ok);}
.pill.coada{background:color-mix(in srgb,var(--accent) 16%,transparent); color:var(--accent);} .pill.coada .pdot{background:var(--accent);}
.pill.err{background:color-mix(in srgb,var(--err) 14%,transparent); color:var(--err);} .pill.err .pdot{background:var(--err);}
/* meniu burger deschis */
.scrim{position:absolute; inset:0; background:rgba(0,0,0,.45); z-index:8;}
.menu{position:absolute; top:52px; right:10px; width:240px; background:var(--card); border:1px solid var(--line); border-radius:12px; box-shadow:0 20px 50px -16px rgba(0,0,0,.7); padding:7px; z-index:9;}
.menu-status{display:flex; align-items:center; gap:9px; padding:11px 11px; font-size:var(--fs-base); font-weight:600; color:var(--ok);}
.menu-status .d{width:10px; height:10px; border-radius:99px; background:var(--ok); box-shadow:0 0 0 4px color-mix(in srgb,var(--ok) 22%,transparent);}
.menu-status small{font-weight:400; color:var(--muted); font-family:var(--font-mono); font-size:11px;}
.menu-plan{display:flex; align-items:center; justify-content:space-between; padding:6px 11px 8px; font-size:var(--fs-sm); color:var(--muted);}
.menu-plan b{color:var(--accent);} .menu-plan .trial{font-size:11px;}
.menu a{display:flex; align-items:center; justify-content:space-between; padding:12px 11px; border-radius:8px; font-size:var(--fs-base); color:var(--ink); text-decoration:none;}
.menu a:active{background:var(--card2);}
.menu hr{border:none; border-top:1px solid var(--line); margin:5px 4px;}
/* ecran editare full-screen */
.modal-head{display:flex; align-items:center; justify-content:space-between; height:56px; padding:0 12px; border-bottom:1px solid var(--line); background:var(--hbg); position:sticky; top:0; z-index:5;}
.modal-head .t{font-size:var(--fs-md); font-weight:700;}
.field{margin-bottom:14px;}
.field label{display:block; font-size:var(--fs-sm); color:var(--muted); margin-bottom:6px;}
.field input, .field textarea, .field select{width:100%; font-family:var(--font-ui); font-size:var(--fs-md); color:var(--ink); background:var(--card2); border:1px solid var(--line); border-radius:9px; padding:11px 13px; min-height:46px;}
.field input.mono{font-family:var(--font-mono);}
.grid2{display:grid; grid-template-columns:1fr 1fr; gap:10px;}
.op-row{padding:11px 0; border-bottom:1px solid var(--line2);}
.op-name{font-size:var(--fs-md); font-weight:600; display:block; margin-bottom:8px;} .op-name small{font-weight:400; color:var(--muted); font-size:var(--fs-sm);}
.op-ctl{display:flex; align-items:center; gap:8px;}
.chip{display:inline-flex; align-items:center; gap:6px; font-family:var(--font-mono); font-size:var(--fs-sm); background:color-mix(in srgb,var(--accent) 18%,transparent); color:var(--accent); padding:7px 11px; border-radius:8px;}
.chip button{background:none; border:none; color:inherit; cursor:pointer; font-size:var(--fs-md);}
.addcode{width:100%; font-size:var(--fs-sm); border:1px dashed color-mix(in srgb,var(--accent) 55%,var(--line)); background:transparent; color:var(--accent); border-radius:9px; padding:11px; cursor:pointer; margin-top:10px;}
.actrow{display:flex; flex-direction:column; gap:10px; margin-top:18px;}
.btn-primary{width:100%; font-size:var(--fs-md); font-weight:600; height:46px; background:var(--accent); color:#fff; border:none; border-radius:9px; cursor:pointer;}
.btn-ghost{width:100%; font-size:var(--fs-md); height:46px; background:transparent; color:var(--ink); border:1px solid var(--line); border-radius:9px; cursor:pointer;}
</style>
</head>
<body data-theme="grafit">
<div class="stage">
<!-- ECRAN 1: DASHBOARD curat (RAR dot in antet, fara linie de scroll la filtre) -->
<div>
<div class="phone"><div class="screen"><div class="scroll">
<header>
<span class="logo-fallback"><span class="rom">ROM</span><span class="fast">FAST</span></span>
<div class="h-center">
<div class="h-title">ROMFAST <span class="accent">AUTOPASS</span><span class="tier">Pro</span></div>
<div class="h-sub">Service auto: <span class="svc">Service Auto Vâlcea SRL</span></div>
</div>
<div class="h-right">
<span class="rar-dot" title="RAR online · ultima autentificare 28.06.2026 09:41"><span class="d"></span></span>
<button class="icon-btn" title="Temă: Grafit"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/></svg></button>
<button class="icon-btn" title="Meniu">&#9776;</button>
</div>
</header>
<div class="body">
<div class="stats">
<div class="stat"><div class="n s-ok">847</div><div class="l">Total</div></div>
<div class="stat"><div class="n s-ok">124</div><div class="l">Lună</div></div>
<div class="stat"><div class="n s-ok">9</div><div class="l">Azi</div></div>
<div class="stat"><div class="n s-acc">12</div><div class="l">Coadă</div></div>
<div class="stat"><div class="n s-err">2</div><div class="l">Corectat</div></div>
</div>
<details class="import-collapse">
<summary><span class="ic-l"><span class="plus">+</span> Importă fișier (XLSX / CSV)</span><span class="chev"></span></summary>
</details>
<div>
<div class="subnav">
<a href="#" class="active">Trimiteri</a>
<a href="#">Mapări <span class="badge">2</span></a>
</div>
<div class="panel">
<div class="filtre">
<button class="pillf on">Toate</button>
<button class="pillf">În coadă</button>
<button class="pillf">Trimise</button>
<button class="pillf">De corectat</button>
</div>
<div class="rand"><div><div class="slim-vin">WBA8E9...K7F2</div><div class="slim-meta">Inspecție tehnică · 09:42</div></div><span class="pill sent"><span class="pdot"></span>Trimis</span></div>
<div class="rand"><div><div class="slim-vin">WVWZZZ...3M1</div><div class="slim-meta">Revizie periodică · 09:38</div></div><span class="pill coada"><span class="pdot"></span>În coadă</span></div>
<div class="rand"><div><div class="slim-vin">VF1RFB...A88</div><div class="slim-meta">Sistem frânare · 09:31</div></div><span class="pill err"><span class="pdot"></span>De corectat</span></div>
<div class="rand"><div><div class="slim-vin">ZAR937...C04</div><div class="slim-meta">Schimb ulei · 09:24</div></div><span class="pill sent"><span class="pdot"></span>Trimis</span></div>
</div>
</div>
</div>
</div></div></div>
<div class="cap">390px · Acasă — RAR online = dot în antet (dată/oră pe hover), filtre fără linie de scroll</div>
</div>
<!-- ECRAN 2: meniu burger deschis (RAR online si aici) -->
<div>
<div class="phone"><div class="screen">
<header>
<span class="logo-fallback"><span class="rom">ROM</span><span class="fast">FAST</span></span>
<div class="h-center">
<div class="h-title">ROMFAST <span class="accent">AUTOPASS</span><span class="tier">Pro</span></div>
<div class="h-sub">Service auto: <span class="svc">Service Auto Vâlcea SRL</span></div>
</div>
<div class="h-right">
<span class="rar-dot" title="RAR online"><span class="d"></span></span>
<button class="icon-btn" title="Temă: Grafit"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/></svg></button>
<button class="icon-btn" title="Închide meniu">&times;</button>
</div>
</header>
<div class="scrim"></div>
<div class="menu">
<div class="menu-status"><span class="d"></span> RAR online <small>· 09:41</small></div>
<div class="menu-plan">Plan: <b>Pro</b> <span class="trial">trial · 18 zile</span></div>
<hr>
<a href="#">Trimiteri</a>
<a href="#">Mapări <span class="badge">2</span></a>
<hr>
<a href="#">Nomenclator</a>
<hr>
<a href="#">Cont</a>
<a href="#">Integrare</a>
<a href="#">Jurnal</a>
<hr>
<a href="#">Ieși din cont</a>
</div>
</div></div>
<div class="cap">390px · Meniu burger — RAR online + Plan (Pro) + separatoare între secțiuni</div>
</div>
<!-- ECRAN 3: editare full-screen (trimitere nefinalizata) -->
<div>
<div class="phone"><div class="screen"><div class="scroll">
<div class="modal-head"><span class="t">Corectează trimiterea</span><button class="icon-btn" title="Închide">&times;</button></div>
<div class="body" style="gap:0;">
<div class="field"><label>VIN (serie șasiu)</label><input class="mono" value="VF1RFB00A88142073"></div>
<div class="grid2">
<div class="field"><label>Data prestației</label><input class="mono" value="2026-06-22"></div>
<div class="field"><label>Nr. înmatriculare</label><input class="mono" value="CT88NOE"></div>
</div>
<div class="field"><label>Observații (operațiile efectuate)</label><textarea rows="2">Schimbare plăcuțe frână față</textarea></div>
<div class="field" style="margin-bottom:6px;">
<label>Prestații — cod RAR pe fiecare operație</label>
<div class="op-row"><span class="op-name">REVIZIE PERIODICĂ <small>— la 15.000 km</small></span><div class="op-ctl"><span class="chip">REV2 <button>&times;</button></span></div></div>
<div class="op-row" style="border-left:2px solid var(--warn); padding-left:10px;"><span class="op-name">SCHIMB PLĂCUȚE FRÂNĂ <small style="color:var(--warn)">— lipsă cod</small></span><div class="op-ctl"><select><option>— alege cod RAR —</option><option>FRN1 — Sistem de frânare</option></select></div></div>
<button class="addcode">+ Adaugă altă operație / cod RAR</button>
</div>
<div class="actrow"><button class="btn-primary">Salvează și retrimite</button><button class="btn-ghost">Renunță</button></div>
</div>
</div></div></div>
<div class="cap">390px · Editare full-screen — trimitere nefinalizată (picker cod+denumire, Renunță)</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,241 @@
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PRD 5.16 — Dashboard aplicatie (compact, minimalist)</title>
<style>
:root{
--font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Segoe UI Mono", "Roboto Mono", Menlo, Consolas, monospace;
--fs-xs:12px; --fs-sm:13.5px; --fs-base:15px; --fs-md:16px; --fs-lg:18px; --fs-xl:20px; --fs-2xl:28px;
--bg:#0f1218; --card:#181c24; --card2:#0f1218; --ink:#e6e9ef; --muted:#8b93a7;
--line:#262b36; --line2:#1f2530; --accent:#6ea2ec; --ok:#2FBF8F; --warn:#E0A93B; --err:#E05D5D;
--hbg:rgba(15,18,24,.9);
}
body[data-theme="hartie"]{ --bg:#f3efe6; --card:#fffdf7; --card2:#f3efe6; --ink:#1e1a13; --muted:#6a6052; --line:#e2dccc; --line2:#ece6d9; --accent:#1F5FBF; --ok:#1c7d5d; --warn:#b45309; --err:#bd463c; --hbg:rgba(255,253,247,.92); }
body[data-theme="cobalt"]{ --bg:#080d1c; --card:#111a33; --card2:#0b1226; --ink:#e9ecfb; --muted:#8a93b8; --line:#1d2747; --line2:#161f3a; --accent:#8aa0ff; --ok:#2fd0a6; --err:#f06a7a; --hbg:rgba(8,13,28,.92); }
body[data-theme="cupru"]{ --bg:#15110b; --card:#211a12; --card2:#15110b; --ink:#efe6d6; --muted:#a89a85; --line:#36291c; --line2:#281e14; --accent:#dfa45c; --ok:#67b98c; --err:#e2685a; --hbg:rgba(21,17,11,.92); }
*{box-sizing:border-box;}
body{margin:0; background:var(--bg); color:var(--ink); font-family:var(--font-ui); font-size:var(--fs-base); line-height:1.55; -webkit-font-smoothing:antialiased;}
.mono{font-family:var(--font-mono);} .muted{color:var(--muted);}
/* HEADER branded (numele service e DOAR aici, nu se mai duplica jos) */
header{position:sticky; top:0; z-index:5; display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:16px; height:64px; padding:0 22px; background:var(--hbg); backdrop-filter:blur(8px); border-bottom:1px solid var(--line);}
.logo-fallback{display:inline-flex; align-items:center; gap:6px; font-weight:800; font-size:var(--fs-lg);}
.logo-fallback .rom{color:#D1342F;} .logo-fallback .fast{color:var(--accent);}
.h-center{text-align:center; line-height:1.15;}
.h-title{font-size:var(--fs-md); font-weight:700;} .h-title .accent{color:var(--accent);}
.h-sub{font-size:var(--fs-xs); color:var(--muted); margin-top:2px;} .h-sub .svc{color:var(--ink); font-weight:600;}
.env{display:inline-block; margin-left:8px; padding:1px 7px; border-radius:99px; font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:var(--warn); background:color-mix(in srgb,var(--warn) 16%,transparent);}
/* badge tip cont (Gratuit/Standard/Pro/Premium) */
.tier{display:inline-block; margin-left:6px; padding:1px 8px; border-radius:99px; font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:var(--accent); background:color-mix(in srgb,var(--accent) 16%,transparent);}
.h-right{display:flex; align-items:center; justify-content:flex-end; gap:10px;}
/* dot RAR online compact in antet (inlocuieste banda) — datetime pe title/hover */
.rar-chip{display:flex; align-items:center; gap:8px; height:38px; padding:0 13px; border-radius:99px; border:1px solid color-mix(in srgb,var(--ok) 35%,var(--line)); background:color-mix(in srgb,var(--ok) 10%,transparent); color:var(--ok); font-size:var(--fs-sm); font-weight:600; cursor:default;}
.rar-chip.blocat{border-color:color-mix(in srgb,var(--err) 45%,var(--line)); background:color-mix(in srgb,var(--err) 12%,transparent); color:var(--err);}
.rar-chip .dot{width:9px; height:9px; border-radius:99px; background:currentColor; box-shadow:0 0 0 4px color-mix(in srgb,currentColor 22%,transparent);}
.tema-btn{display:flex; align-items:center; gap:8px; height:38px; padding:0 13px; border-radius:8px; background:transparent; border:1px solid var(--line); color:var(--muted); font-family:var(--font-ui); font-size:var(--fs-sm); cursor:pointer;}
.tema-btn:hover{border-color:var(--accent); color:var(--ink);}
.ver{font-size:var(--fs-xs); color:var(--muted);}
.icon-btn{width:38px; height:38px; border-radius:8px; border:1px solid var(--line); background:transparent; color:var(--ink); font-size:18px; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; position:relative;}
/* meniu burger deschis (mockup) — contine si starea RAR */
.menu{position:absolute; top:46px; right:0; width:230px; background:var(--card); border:1px solid var(--line); border-radius:10px; box-shadow:0 18px 40px -16px rgba(0,0,0,.6); padding:6px; z-index:10; text-align:left;}
.menu-status{display:flex; align-items:center; gap:8px; padding:9px 10px; font-size:var(--fs-sm); font-weight:600; color:var(--ok);}
.menu-status small{font-weight:400; color:var(--muted); font-family:var(--font-mono); font-size:11px;}
.menu-plan{display:flex; align-items:center; justify-content:space-between; padding:8px 10px 4px; font-size:var(--fs-sm); color:var(--muted);}
.menu-plan b{color:var(--accent);}
.menu-plan .trial{font-size:11px; color:var(--muted);}
.menu a{display:flex; align-items:center; justify-content:space-between; padding:9px 10px; border-radius:7px; font-size:var(--fs-sm); color:var(--ink); text-decoration:none;}
.menu a:hover{background:var(--card2);}
.menu hr{border:none; border-top:1px solid var(--line); margin:5px 4px;}
.menu .badge{display:inline-flex; align-items:center; justify-content:center; min-width:18px; height:18px; padding:0 5px; border-radius:99px; background:var(--err); color:#fff; font-size:11px; font-weight:700;}
.wrap{max-width:1000px; margin:0 auto; padding:16px 22px 70px; display:flex; flex-direction:column; gap:14px;}
/* Banda de stare — APARE DOAR cand e blocat (zero-silent-failures) */
.strip{display:flex; align-items:center; justify-content:space-between; gap:12px; padding:12px 16px; border-radius:10px;
background:color-mix(in srgb, var(--ok) 13%, transparent); border:1px solid color-mix(in srgb, var(--ok) 30%, transparent);}
.strip.blocat{background:color-mix(in srgb, var(--err) 13%, transparent); border-color:color-mix(in srgb, var(--err) 35%, transparent); color:var(--err);}
.strip-left{display:flex; align-items:center; gap:10px; font-weight:700; font-size:var(--fs-md);}
.strip .dot{width:10px; height:10px; border-radius:99px; background:var(--ok); flex-shrink:0; box-shadow:0 0 0 4px color-mix(in srgb, var(--ok) 22%, transparent);}
.strip.blocat .dot{background:var(--err); box-shadow:0 0 0 4px color-mix(in srgb, var(--err) 22%, transparent);}
.strip-right{font-family:var(--font-mono); font-size:var(--fs-xs); color:var(--muted);}
/* 2. CARDURI contor — standalone, fara titlu de sectiune */
.contoare{display:grid; grid-template-columns:repeat(5,1fr); gap:10px;}
.contor-card{background:var(--card2); border:1px solid var(--line); border-radius:10px; padding:14px 16px;}
.contor-card.primar{border-color:color-mix(in srgb,var(--ok) 40%,var(--line));}
.contor-cifra{font-size:var(--fs-2xl); font-weight:700; line-height:1;}
.contor-label{font-size:var(--fs-sm); color:var(--muted); margin-top:7px;}
.s-ok{color:var(--ok);} .s-acc{color:var(--accent);} .s-err{color:var(--err);}
/* 3. IMPORT colapsat */
.import-collapse{border:1px solid var(--line); border-radius:10px; background:var(--card); overflow:hidden;}
.import-collapse>summary{list-style:none; cursor:pointer; display:flex; align-items:center; justify-content:space-between; gap:10px; padding:12px 16px; font-size:var(--fs-sm); font-weight:600; color:var(--ink);}
.import-collapse>summary::-webkit-details-marker{display:none;}
.import-collapse>summary .ic-l{display:flex; align-items:center; gap:10px;}
.import-collapse .plus{display:inline-flex; width:24px; height:24px; align-items:center; justify-content:center; border-radius:7px; background:color-mix(in srgb,var(--accent) 16%,transparent); color:var(--accent); font-size:17px; line-height:1;}
.import-collapse>summary .ic-r{font-size:var(--fs-xs); color:var(--muted);}
.import-collapse[open]>summary{border-bottom:1px solid var(--line);}
.import-body{display:flex; align-items:center; justify-content:space-between; gap:14px; padding:16px; border:1px dashed color-mix(in srgb,var(--accent) 45%,var(--line)); border-radius:10px; margin:12px;}
.import-body .u-tx{font-size:var(--fs-md); font-weight:600;}
.import-body .u-sub{font-size:var(--fs-sm); color:var(--muted); margin-top:2px;}
.btn-primary{font-family:var(--font-ui); font-size:var(--fs-md); font-weight:600; height:42px; padding:0 20px; background:var(--accent); color:#fff; border:none; border-radius:8px; cursor:pointer;}
/* 4. NAV tab-uri Trimiteri / Mapari */
.subnav{display:flex; gap:6px; border-bottom:1px solid var(--line);}
.subnav a{font-size:var(--fs-sm); font-weight:600; padding:9px 16px; border-radius:8px 8px 0 0; color:var(--muted); text-decoration:none; border:1px solid transparent; border-bottom:none; margin-bottom:-1px;}
.subnav a.active{color:var(--ink); background:var(--card); border-color:var(--line); border-bottom:1px solid var(--card);}
.subnav .badge{display:inline-flex; align-items:center; justify-content:center; min-width:18px; height:18px; margin-left:6px; padding:0 5px; border-radius:99px; background:var(--err); color:#fff; font-size:11px; font-weight:700;}
/* 5. LISTA (fara titlu/subtitlu de sectiune) */
.panel{background:var(--card); border:1px solid var(--line); border-radius:0 12px 12px 12px; overflow:hidden;}
.filtre{display:flex; gap:8px; padding:12px 16px; flex-wrap:wrap; border-bottom:1px solid var(--line2);}
.pillf{font-size:var(--fs-sm); padding:6px 13px; border-radius:99px; border:1px solid var(--line); background:transparent; color:var(--muted); cursor:pointer;}
.pillf.on{background:color-mix(in srgb,var(--accent) 16%,transparent); border-color:transparent; color:var(--accent); font-weight:600;}
.rand{display:flex; align-items:center; justify-content:space-between; padding:13px 16px; border-bottom:1px solid var(--line2); cursor:pointer;}
.rand:hover{background:color-mix(in srgb,var(--accent) 6%,transparent);}
.rand:last-child{border-bottom:none;}
.slim-vin{font-family:var(--font-mono); font-size:var(--fs-md); font-weight:500;}
.slim-meta{font-size:var(--fs-sm); color:var(--muted); margin-top:3px;}
.pill{display:inline-flex; align-items:center; gap:7px; padding:5px 12px; border-radius:99px; font-size:var(--fs-sm); font-weight:500;}
.pill .pdot{width:7px; height:7px; border-radius:99px;}
.pill.sent{background:color-mix(in srgb,var(--ok) 14%,transparent); color:var(--ok);} .pill.sent .pdot{background:var(--ok);}
.pill.coada{background:color-mix(in srgb,var(--accent) 16%,transparent); color:var(--accent);} .pill.coada .pdot{background:var(--accent);}
.pill.err{background:color-mix(in srgb,var(--err) 14%,transparent); color:var(--err);} .pill.err .pdot{background:var(--err);}
/* MODAL editare trimitere nefinalizata (la click pe rand) */
.editmodal{max-width:560px; background:var(--card); border:1px solid var(--line); border-radius:12px; overflow:hidden;}
.editmodal .mhead{display:flex; align-items:center; justify-content:space-between; padding:14px 18px; border-bottom:1px solid var(--line);}
.editmodal .mhead .t{font-size:var(--fs-md); font-weight:700;}
.editmodal .mbody{padding:18px;}
.field{margin-bottom:14px;}
.field label{display:block; font-size:var(--fs-sm); color:var(--muted); margin-bottom:6px;}
.field input, .field textarea, .field select{width:100%; font-family:var(--font-ui); font-size:var(--fs-md); color:var(--ink); background:var(--card2); border:1px solid var(--line); border-radius:8px; padding:9px 12px; min-height:40px;}
.field input.mono{font-family:var(--font-mono);}
.grid2{display:grid; grid-template-columns:1fr 1fr; gap:12px;}
.op-row{display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px 0; border-bottom:1px solid var(--line2);}
.op-name{font-size:var(--fs-md); font-weight:600;} .op-name small{font-weight:400; color:var(--muted); font-size:var(--fs-sm);}
.chip{display:inline-flex; align-items:center; gap:6px; font-family:var(--font-mono); font-size:var(--fs-sm); background:color-mix(in srgb,var(--accent) 18%,transparent); color:var(--accent); padding:5px 10px; border-radius:7px;}
.chip button{background:none; border:none; color:inherit; cursor:pointer; font-size:var(--fs-md);}
.addcode{font-size:var(--fs-sm); border:1px dashed color-mix(in srgb,var(--accent) 55%,var(--line)); background:transparent; color:var(--accent); border-radius:7px; padding:6px 12px; cursor:pointer;}
.btn-ghost{font-size:var(--fs-md); height:42px; padding:0 18px; background:transparent; color:var(--ink); border:1px solid var(--line); border-radius:8px; cursor:pointer;}
.actrow{display:flex; gap:10px; margin-top:16px;}
</style>
</head>
<body data-theme="grafit">
<header>
<div><span class="logo-fallback"><span class="rom">ROM</span><span class="fast">FAST</span></span></div>
<div class="h-center">
<div class="h-title">ROMFAST <span class="accent">AUTOPASS</span><span class="env">test</span><span class="tier">Pro</span></div>
<div class="h-sub">Service auto: <span class="svc">Service Auto Vâlcea SRL</span></div>
</div>
<div class="h-right">
<div class="rar-chip" title="Ultima autentificare RAR: 28.06.2026 09:41"><span class="dot"></span> RAR online</div>
<button class="tema-btn" onclick="cycle()">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/></svg>
<span id="t-label">Grafit</span>
</button>
<span class="ver">v5.16</span>
<button class="icon-btn" title="Meniu cont">&#9776;
<div class="menu">
<div class="menu-status"><span class="rar-chip" style="height:auto;padding:0;border:none;background:none;"><span class="dot"></span></span> RAR online <small>· 09:41</small></div>
<div class="menu-plan">Plan: <b>Pro</b> <span class="trial">trial · 18 zile rămase</span></div>
<hr>
<a href="#">Trimiteri</a>
<a href="#">Mapări <span class="badge">2</span></a>
<hr>
<a href="#">Nomenclator</a>
<hr>
<a href="#">Cont</a>
<a href="#">Integrare</a>
<a href="#">Jurnal</a>
<hr>
<a href="#">Ieși din cont</a>
</div>
</button>
</div>
</header>
<div class="wrap">
<!-- CARDURI (fara titlu de sectiune; RAR online e acum dot in antet) -->
<div class="contoare">
<div class="contor-card primar"><div class="contor-cifra s-ok">847</div><div class="contor-label">Total trimise</div></div>
<div class="contor-card"><div class="contor-cifra s-ok">124</div><div class="contor-label">Luna asta</div></div>
<div class="contor-card"><div class="contor-cifra s-ok">9</div><div class="contor-label">Azi</div></div>
<div class="contor-card"><div class="contor-cifra s-acc">12</div><div class="contor-label">În coadă</div></div>
<div class="contor-card"><div class="contor-cifra s-err">2</div><div class="contor-label">De corectat</div></div>
</div>
<!-- 3. IMPORT colapsat -->
<details class="import-collapse">
<summary>
<span class="ic-l"><span class="plus">+</span> Importă fișier (XLSX / CSV)</span>
<span class="ic-r">trage-l aici sau apasă pentru a deschide ▾</span>
</summary>
<div class="import-body">
<div><div class="u-tx">Încarcă un fișier sau trage-l aici</div><div class="u-sub">Mapezi coloanele o singură dată — apoi trimitem la RAR automat.</div></div>
<button class="btn-primary">Alege fișier</button>
</div>
</details>
<!-- 4 + 5. NAV + LISTA -->
<div>
<div class="subnav">
<a href="#" class="active">Trimiteri</a>
<a href="#">Mapări <span class="badge">2</span></a>
</div>
<div class="panel">
<div class="filtre">
<button class="pillf on">Toate</button>
<button class="pillf">În coadă</button>
<button class="pillf">Trimise</button>
<button class="pillf">De corectat</button>
</div>
<div class="rand"><div><div class="slim-vin">WBA8E9...K7F2</div><div class="slim-meta">Inspecție tehnică · 09:42</div></div><span class="pill sent"><span class="pdot"></span>Trimis</span></div>
<div class="rand"><div><div class="slim-vin">WVWZZZ...3M1</div><div class="slim-meta">Revizie periodică · 09:38</div></div><span class="pill coada"><span class="pdot"></span>În coadă</span></div>
<div class="rand"><div><div class="slim-vin">VF1RFB...A88</div><div class="slim-meta">Sistem frânare · 09:31</div></div><span class="pill err"><span class="pdot"></span>De corectat</span></div>
<div class="rand"><div><div class="slim-vin">ZAR937...C04</div><div class="slim-meta">Schimb ulei · 09:24</div></div><span class="pill sent"><span class="pdot"></span>Trimis</span></div>
<div class="rand"><div><div class="slim-vin">JTDBR...9920</div><div class="slim-meta">Inspecție tehnică · 09:18</div></div><span class="pill sent"><span class="pdot"></span>Trimis</span></div>
</div>
</div>
<!-- DOAR cand e BLOCAT: banda rosie reapare (zero-silent-failures) -->
<div style="margin-top:18px; font-size:var(--fs-xs); text-transform:uppercase; letter-spacing:.08em; color:var(--err); font-weight:700;">Stare BLOCAT — banda apare DOAR atunci (worker oprit / RAR inaccesibil)</div>
<div class="strip blocat">
<span class="strip-left"><span class="dot"></span> Blocat: RAR inaccesibil — declarațiile NU pleacă</span>
<span class="strip-right">Ultima autentificare RAR: 28.06.2026 09:41</span>
</div>
<!-- MODAL editare: apare la click pe o trimitere nefinalizata (needs_data / needs_mapping / error) -->
<div style="margin-top:22px; font-size:var(--fs-xs); text-transform:uppercase; letter-spacing:.08em; color:var(--accent); font-weight:700;">Modal editare — la click pe o trimitere nefinalizată (needs_data / needs_mapping)</div>
<div class="editmodal" style="margin-top:8px;">
<div class="mhead"><span class="t">Corectează trimiterea</span><button class="icon-btn" title="Închide">&times;</button></div>
<div class="mbody">
<div class="field"><label>VIN (serie șasiu)</label><input class="mono" value="VF1RFB00A88142073"></div>
<div class="grid2">
<div class="field"><label>Data prestației</label><input class="mono" value="2026-06-22"></div>
<div class="field"><label>Număr înmatriculare</label><input class="mono" value="CT88NOE"></div>
</div>
<div class="field"><label>Observații (operațiile efectuate)</label><textarea rows="2">Schimbare plăcuțe frână față</textarea></div>
<div class="field">
<label>Prestații — cod RAR pe fiecare operație</label>
<div class="op-row"><span class="op-name">REVIZIE PERIODICĂ <small>— la 15.000 km</small></span><span class="chip">REV2 <button>&times;</button></span></div>
<div class="op-row" style="border-left:2px solid var(--warn); padding-left:10px;"><span class="op-name">SCHIMB PLĂCUȚE FRÂNĂ <small style="color:var(--warn)">— lipsă cod</small></span><select><option>— alege cod RAR —</option><option>FRN1 — Sistem de frânare</option><option>REV2 — Revizie periodică</option></select></div>
<div style="margin-top:10px;"><button class="addcode">+ Adaugă altă operație / cod RAR</button></div>
</div>
<div class="actrow"><button class="btn-primary">Salvează și retrimite</button><button class="btn-ghost">Renunță</button></div>
</div>
</div>
</div>
<script>
var THEMES=[['grafit','Grafit'],['cobalt','Cobalt'],['cupru','Cupru'],['hartie','Hârtie']];
var i=0;
function cycle(){ i=(i+1)%THEMES.length; document.body.setAttribute('data-theme',THEMES[i][0]); document.getElementById('t-label').textContent=THEMES[i][1]; }
</script>
</body>
</html>

View File

@@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PRD 5.16 — Preview fonturi system-stack + scala tipografica</title>
<style>
/* ============================================================
PROPUNERE 5.16: fonturi STANDARD WEB (system font stack).
ZERO fisiere de font descarcate. Arata nativ pe fiecare OS.
Inlocuieste IBM Plex self-hostat din /static/fonts.
============================================================ */
:root{
/* Stive de font standard web (fara @font-face, fara /static/fonts) */
--font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Segoe UI Mono", "Roboto Mono", Menlo, Consolas, monospace;
/* SCALA TIPOGRAFICA UNIFORMA (sursa unica de adevar; azi e ad-hoc 10/11/13px) */
--fs-xs: 12px; /* meta, sub-linii mono, hint-uri (azi: 10px) */
--fs-sm: 13.5px; /* label-uri formular, pill-uri (azi: 11px) */
--fs-base: 15px; /* text body implicit (azi: ~13px) */
--fs-md: 16px; /* input-uri, text card (azi: 13px) */
--fs-lg: 18px; /* titluri de sectiune mici */
--fs-xl: 20px; /* sub-titluri */
--fs-2xl: 28px; /* cifra contor (azi: 22px) */
--fs-3xl: 34px; /* titlu pagina */
--lh-tight: 1.25;
--lh-body: 1.55;
/* paleta grafit (din DESIGN.md) — doar pentru context vizual */
--bg:#0f1218; --card:#181c24; --card2:#0f1218; --ink:#e6e9ef; --muted:#8b93a7;
--line:#262b36; --line2:#1f2530; --accent:#6ea2ec; --ok:#2FBF8F; --warn:#E0A93B; --err:#E05D5D;
}
body[data-theme="hartie"]{
--bg:#f3efe6; --card:#fffdf7; --card2:#f3efe6; --ink:#1e1a13; --muted:#6a6052;
--line:#e2dccc; --line2:#ece6d9; --accent:#1F5FBF; --ok:#1c7d5d; --warn:#b45309; --err:#bd463c;
}
*{box-sizing:border-box;}
body{
margin:0; background:var(--bg); color:var(--ink);
font-family:var(--font-ui);
font-size:var(--fs-base); line-height:var(--lh-body);
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1100px; margin:0 auto; padding:28px 22px 80px;}
.mono{font-family:var(--font-mono);}
h1{font-size:var(--fs-3xl); line-height:var(--lh-tight); margin:0 0 6px; letter-spacing:-.02em;}
.lead{color:var(--muted); font-size:var(--fs-md); margin:0 0 22px;}
.sec{font-size:var(--fs-lg); margin:34px 0 12px; padding-bottom:6px; border-bottom:1px solid var(--line);}
.toolbar{display:flex; gap:10px; align-items:center; margin-bottom:8px;}
.toolbar button{font-family:var(--font-ui); font-size:var(--fs-sm); height:36px; padding:0 14px;
border-radius:7px; border:1px solid var(--line); background:var(--card); color:var(--ink); cursor:pointer;}
.note{font-size:var(--fs-sm); color:var(--muted); margin:2px 0 0;}
/* ---- carduri-contor (aerisite, text mai mare) ---- */
.contoare{display:grid; grid-template-columns:repeat(3,1fr); gap:14px;}
.contor-card{background:var(--card2); border:1px solid var(--line); border-radius:12px; padding:18px 18px;}
.contor-cifra{font-size:var(--fs-2xl); font-weight:700; line-height:1;}
.contor-label{font-size:var(--fs-sm); color:var(--muted); margin-top:8px;}
.contor-sub{font-family:var(--font-mono); font-size:var(--fs-xs); color:var(--muted); margin-top:4px;}
.s-ok{color:var(--ok);} .s-acc{color:var(--accent);} .s-err{color:var(--err);} .s-muted{color:var(--muted);}
/* ---- strip sanatate cu DOT (nu bifa) pentru RAR online ---- */
.strip{display:flex; align-items:center; justify-content:space-between; gap:12px;
padding:12px 16px; border-radius:10px; margin-bottom:14px;
background:color-mix(in srgb, var(--ok) 13%, transparent);
border:1px solid color-mix(in srgb, var(--ok) 30%, transparent);}
.strip-left{display:flex; align-items:center; gap:10px; font-weight:700; font-size:var(--fs-md);}
.dot{width:10px; height:10px; border-radius:99px; background:var(--ok); flex-shrink:0;
box-shadow:0 0 0 4px color-mix(in srgb, var(--ok) 22%, transparent);}
.dot.live{animation:pulse 2s ease-in-out infinite;}
@keyframes pulse{0%,100%{opacity:1;} 50%{opacity:.55;}}
.strip-right{font-family:var(--font-mono); font-size:var(--fs-xs); color:var(--muted);}
/* ---- lista slim ---- */
.lista{background:var(--card); border:1px solid var(--line); border-radius:12px; overflow:hidden; margin-top:14px;}
.rand{display:flex; align-items:center; justify-content:space-between; padding:14px 18px; border-bottom:1px solid var(--line2);}
.rand:last-child{border-bottom:none;}
.slim-vin{font-family:var(--font-mono); font-size:var(--fs-md); font-weight:500;}
.slim-meta{font-size:var(--fs-sm); color:var(--muted); margin-top:3px;}
.pill{display:inline-flex; align-items:center; gap:7px; padding:5px 12px; border-radius:99px; font-size:var(--fs-sm); font-weight:500;}
.pill .pdot{width:7px; height:7px; border-radius:99px;}
.pill.sent{background:color-mix(in srgb,var(--ok) 14%,transparent); color:var(--ok);}
.pill.sent .pdot{background:var(--ok);}
.pill.coada{background:color-mix(in srgb,var(--accent) 16%,transparent); color:var(--accent);}
.pill.coada .pdot{background:var(--accent);}
.pill.err{background:color-mix(in srgb,var(--err) 14%,transparent); color:var(--err);}
.pill.err .pdot{background:var(--err);}
/* ---- formular editare slim ---- */
.form-card{background:var(--card); border:1px solid var(--line); border-radius:12px; padding:22px; margin-top:14px; max-width:560px;}
.camp{margin-bottom:14px;}
.camp label{display:block; font-size:var(--fs-sm); color:var(--muted); margin-bottom:6px;}
.camp input, .camp textarea, .camp select{
width:100%; font-family:var(--font-ui); font-size:var(--fs-md); color:var(--ink);
background:var(--card2); border:1px solid var(--line); border-radius:8px; padding:9px 12px; min-height:40px;}
.camp input.mono{font-family:var(--font-mono);}
.grid2{display:grid; grid-template-columns:1fr 1fr; gap:12px;}
.op-row{display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px 0; border-bottom:1px solid var(--line2);}
.op-name{font-size:var(--fs-md); font-weight:600;}
.op-name small{font-weight:400; color:var(--muted); font-size:var(--fs-sm);}
.chip{display:inline-flex; align-items:center; gap:6px; font-family:var(--font-mono); font-size:var(--fs-sm);
background:color-mix(in srgb,var(--accent) 18%,transparent); color:var(--accent); padding:5px 10px; border-radius:7px;}
.chip button{background:none; border:none; color:inherit; cursor:pointer; font-size:var(--fs-md); line-height:1;}
.addcode{font-size:var(--fs-sm); border:1px dashed color-mix(in srgb,var(--accent) 55%,var(--line));
background:transparent; color:var(--accent); border-radius:7px; padding:6px 12px; cursor:pointer;}
.btn-primary{font-family:var(--font-ui); font-size:var(--fs-md); font-weight:600; height:42px; padding:0 20px;
background:var(--accent); color:#fff; border:none; border-radius:8px; cursor:pointer;}
.btn-ghost{font-family:var(--font-ui); font-size:var(--fs-md); height:42px; padding:0 18px;
background:transparent; color:var(--ink); border:1px solid var(--line); border-radius:8px; cursor:pointer;}
/* tabel scala — referinta rapida */
table.scala{width:100%; border-collapse:collapse; font-size:var(--fs-sm); margin-top:8px;}
table.scala td{padding:7px 10px; border-bottom:1px solid var(--line2);}
table.scala td:first-child{font-family:var(--font-mono); color:var(--accent); white-space:nowrap;}
</style>
</head>
<body data-theme="grafit">
<div class="wrap">
<div class="toolbar">
<button onclick="document.body.setAttribute('data-theme', document.body.getAttribute('data-theme')==='grafit'?'hartie':'grafit')">Comuta tema (grafit / hartie)</button>
<span class="note">Fonturi: <span class="mono">system-ui, -apple-system, Segoe UI, Roboto…</span> — zero fisiere descarcate.</span>
</div>
<h1>Gateway RAR AUTOPASS</h1>
<p class="lead">Preview tipografie 5.16 — font stack nativ + scala uniforma, carduri aerisite, text mai mare.</p>
<div class="sec">Scala tipografica unica (tokeni)</div>
<table class="scala">
<tr><td>--fs-xs 12px</td><td style="font-size:var(--fs-xs)">Meta, hint-uri, sub-linii mono (azi 10px — prea mic)</td></tr>
<tr><td>--fs-sm 13.5px</td><td style="font-size:var(--fs-sm)">Label-uri formular, pill-uri de stare (azi 11px)</td></tr>
<tr><td>--fs-base 15px</td><td style="font-size:var(--fs-base)">Text body implicit pe toate paginile</td></tr>
<tr><td>--fs-md 16px</td><td style="font-size:var(--fs-md)">Input-uri, VIN mono, text de card (azi 13px)</td></tr>
<tr><td>--fs-2xl 28px</td><td style="font-size:var(--fs-2xl);font-weight:700">Cifra contor (azi 22px)</td></tr>
</table>
<div class="sec">Dashboard — strip sanatate (DOT, nu bifa) + carduri-contor</div>
<div class="strip">
<span class="strip-left"><span class="dot live"></span> RAR online · declaratiile curg normal</span>
<span class="strip-right">Ultima autentificare RAR: 28.06.2026 09:41</span>
</div>
<div class="contoare">
<div class="contor-card"><div class="contor-cifra s-ok">847</div><div class="contor-label">Trimise (total)</div><div class="contor-sub">luna 124 · azi 9</div></div>
<div class="contor-card"><div class="contor-cifra s-acc">12</div><div class="contor-label">In coada</div></div>
<div class="contor-card"><div class="contor-cifra s-muted">0</div><div class="contor-label">De corectat</div></div>
</div>
<div class="sec">Lista trimiteri — rand slim</div>
<div class="lista">
<div class="rand"><div><div class="slim-vin">WBA8E9...K7F2</div><div class="slim-meta">Inspectie tehnica · 09:42</div></div><span class="pill sent"><span class="pdot"></span>Trimis</span></div>
<div class="rand"><div><div class="slim-vin">WVWZZZ...3M1</div><div class="slim-meta">Revizie periodica · 09:38</div></div><span class="pill coada"><span class="pdot"></span>In coada</span></div>
<div class="rand"><div><div class="slim-vin">VF1RFB...A88</div><div class="slim-meta">Sistem franare · 09:31</div></div><span class="pill err"><span class="pdot"></span>De corectat</span></div>
</div>
<div class="sec">Formular editare — denumiri operatii in picker + adaugare operatie</div>
<div class="form-card">
<div class="camp"><label>VIN (serie sasiu)</label><input class="mono" value="WBA8E9C5K7F20143"></div>
<div class="grid2">
<div class="camp"><label>Data prestatiei</label><input class="mono" value="2026-06-22"></div>
<div class="camp"><label>Numar inmatriculare</label><input class="mono" value="CT88NOE"></div>
</div>
<div class="camp"><label>Observatii (operatiile efectuate)</label><textarea rows="2">Revizie; schimbare placute frana</textarea></div>
<div class="camp">
<label>Prestatii — cod RAR pe fiecare operatie</label>
<div class="op-row">
<span class="op-name">REVIZIE PERIODICA <small>— revizie la 15.000 km</small></span>
<span style="display:flex;gap:8px;align-items:center;"><span class="chip">REV2 <button>&times;</button></span></span>
</div>
<div class="op-row" style="border-left:2px solid var(--warn); padding-left:10px;">
<span class="op-name">SCHIMB PLACUTE FRANA <small style="color:var(--warn)">— lipsa cod</small></span>
<select><option>— alege cod RAR —</option><option>FRN1 — Sistem de franare</option><option>REV2 — Revizie periodica</option></select>
</div>
<div style="margin-top:10px;"><button class="addcode">+ Adauga alta operatie / cod RAR</button></div>
<p class="note">Picker-ul arata <strong>cod + denumire</strong> (FRN1 — Sistem de franare), nu doar codul.</p>
</div>
<div style="display:flex; gap:10px; margin-top:18px;">
<button class="btn-primary">Salveaza si retrimite</button>
<button class="btn-ghost">Renunta</button>
</div>
</div>
<p class="note" style="margin-top:30px;">Nota: tema/culorile sunt doar context. Subiectul acestui preview e <strong>fontul</strong> (system-ui) si <strong>scala</strong> (dimensiuni mai mari, uniforme). Deschide pe Windows si pe Mac ca sa vezi cum cade fontul nativ pe fiecare.</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,173 @@
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PRD 5.16 — Header profesional + /login + selector tema stil landing</title>
<style>
:root{
--font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Segoe UI Mono", "Roboto Mono", Menlo, Consolas, monospace;
--fs-xs:12px; --fs-sm:13.5px; --fs-base:15px; --fs-md:16px; --fs-lg:18px; --fs-xl:20px; --fs-2xl:28px;
--bg:#0f1218; --card:#181c24; --card2:#0f1218; --ink:#e6e9ef; --muted:#8b93a7;
--line:#262b36; --line2:#1f2530; --accent:#6ea2ec; --ok:#2FBF8F; --warn:#E0A93B; --err:#E05D5D;
--hbg:rgba(15,18,24,.88);
}
body[data-theme="hartie"]{
--bg:#f3efe6; --card:#fffdf7; --card2:#f3efe6; --ink:#1e1a13; --muted:#6a6052;
--line:#e2dccc; --line2:#ece6d9; --accent:#1F5FBF; --ok:#1c7d5d; --warn:#b45309; --err:#bd463c;
--hbg:rgba(255,253,247,.9);
}
body[data-theme="cobalt"]{ --bg:#080d1c; --card:#111a33; --card2:#0b1226; --ink:#e9ecfb; --muted:#8a93b8; --line:#1d2747; --line2:#161f3a; --accent:#8aa0ff; --ok:#2fd0a6; --err:#f06a7a; --hbg:rgba(8,13,28,.9); }
body[data-theme="cupru"]{ --bg:#15110b; --card:#211a12; --card2:#15110b; --ink:#efe6d6; --muted:#a89a85; --line:#36291c; --line2:#281e14; --accent:#dfa45c; --ok:#67b98c; --err:#e2685a; --hbg:rgba(21,17,11,.9); }
*{box-sizing:border-box;}
body{margin:0; background:var(--bg); color:var(--ink); font-family:var(--font-ui); font-size:var(--fs-base); -webkit-font-smoothing:antialiased;}
.mono{font-family:var(--font-mono);}
.muted{color:var(--muted);}
/* ===== HEADER aplicatie (logat) — profesional, branded ===== */
header{
display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
gap:16px; height:64px; padding:0 22px; background:var(--card); border:1px solid var(--line); border-radius:12px;
}
/* antet MINIMAL pe /login (neautentificat): doar logo + titlu + tema */
.login-topbar{display:flex; align-items:center; justify-content:space-between; gap:16px; height:60px; padding:0 22px; background:var(--card); border:1px solid var(--line); border-radius:12px 12px 0 0; border-bottom:none;}
.login-topbar .lt-brand{display:flex; align-items:center; gap:10px; font-weight:700; font-size:var(--fs-md);}
.login-topbar .lt-brand .accent{color:var(--accent);}
.h-left{display:flex; align-items:center; gap:12px;}
.logo{height:32px; width:auto; display:block;}
/* wordmark fallback in mockup (in app: PNG real ROMFAST) */
.logo-fallback{display:inline-flex; align-items:center; gap:7px; font-weight:800; letter-spacing:-.01em; font-size:var(--fs-lg);}
.logo-fallback .rom{color:#D1342F;} .logo-fallback .fast{color:var(--accent);}
.h-center{text-align:center; line-height:1.15;}
.h-title{font-size:var(--fs-md); font-weight:700; letter-spacing:.01em;}
.h-title .accent{color:var(--accent);}
.h-sub{font-size:var(--fs-xs); color:var(--muted); margin-top:2px;}
.h-sub .svc{color:var(--ink); font-weight:600;}
.env{display:inline-block; margin-left:8px; padding:1px 7px; border-radius:99px; font-size:10px; font-weight:700;
text-transform:uppercase; letter-spacing:.04em; color:var(--warn); background:color-mix(in srgb,var(--warn) 16%,transparent);}
.tier{display:inline-block; margin-left:6px; padding:1px 8px; border-radius:99px; font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:var(--accent); background:color-mix(in srgb,var(--accent) 16%,transparent);}
.h-right{display:flex; align-items:center; justify-content:flex-end; gap:10px;}
.rar-chip{display:flex; align-items:center; gap:8px; height:38px; padding:0 13px; border-radius:99px; border:1px solid color-mix(in srgb,var(--ok) 35%,var(--line)); background:color-mix(in srgb,var(--ok) 10%,transparent); color:var(--ok); font-size:var(--fs-sm); font-weight:600; cursor:default;}
.rar-chip .dot{width:9px; height:9px; border-radius:99px; background:currentColor; box-shadow:0 0 0 4px color-mix(in srgb,currentColor 22%,transparent);}
/* selector tema STIL LANDING: pill cu icon + eticheta tema curenta */
.tema-btn{display:flex; align-items:center; gap:8px; height:38px; padding:0 13px; border-radius:8px;
background:transparent; border:1px solid var(--line); color:var(--muted); font-family:var(--font-ui);
font-size:var(--fs-sm); cursor:pointer; transition:border-color .15s, color .15s;}
.tema-btn:hover{border-color:var(--accent); color:var(--ink);}
.tema-btn svg{flex-shrink:0;}
.ver{font-size:var(--fs-xs); color:var(--muted);}
.icon-btn{width:38px; height:38px; border-radius:8px; border:1px solid var(--line); background:transparent;
color:var(--ink); font-size:18px; cursor:pointer; display:inline-flex; align-items:center; justify-content:center;}
.wrap{max-width:1100px; margin:0 auto; padding:24px 22px 60px;}
.sec{font-size:var(--fs-lg); margin:30px 0 12px; padding-bottom:6px; border-bottom:1px solid var(--line);}
.note{font-size:var(--fs-sm); color:var(--muted);}
.toolbar{display:flex; gap:10px; align-items:center; margin:14px 0;}
.toolbar button{font-family:var(--font-ui); font-size:var(--fs-sm); height:34px; padding:0 12px; border-radius:7px; border:1px solid var(--line); background:var(--card); color:var(--ink); cursor:pointer;}
/* ===== /login profesional ===== */
.login-shell{min-height:520px; display:grid; grid-template-columns:1.1fr .9fr; border:1px solid var(--line); border-radius:16px; overflow:hidden; background:var(--card);}
.login-aside{padding:40px 38px; background:linear-gradient(160deg, color-mix(in srgb,var(--accent) 14%,var(--card)), var(--card)); border-right:1px solid var(--line); display:flex; flex-direction:column; justify-content:center;}
.login-brand{display:flex; align-items:center; gap:10px; margin-bottom:22px;}
.login-brand .logo-fallback{font-size:var(--fs-xl);}
.login-aside h2{font-size:var(--fs-2xl); line-height:1.2; margin:0 0 12px; letter-spacing:-.02em;}
.login-aside p{font-size:var(--fs-md); color:var(--muted); line-height:1.6; margin:0 0 18px; max-width:380px;}
.trust{display:flex; flex-direction:column; gap:9px; margin-top:6px;}
.trust div{display:flex; align-items:center; gap:9px; font-size:var(--fs-sm); color:var(--ink);}
.trust svg{flex-shrink:0; color:var(--ok);}
.login-form{padding:40px 38px; display:flex; flex-direction:column; justify-content:center;}
.login-form h3{font-size:var(--fs-xl); margin:0 0 4px;}
.login-form .lead{font-size:var(--fs-sm); color:var(--muted); margin:0 0 22px;}
.field{margin-bottom:16px;}
.field label{display:block; font-size:var(--fs-sm); color:var(--muted); margin-bottom:6px;}
.field input{width:100%; font-family:var(--font-ui); font-size:var(--fs-md); color:var(--ink); background:var(--card2); border:1px solid var(--line); border-radius:8px; padding:11px 13px; min-height:44px;}
.field input:focus{outline:2px solid var(--accent); border-color:var(--accent);}
.btn-primary{width:100%; height:46px; font-family:var(--font-ui); font-size:var(--fs-md); font-weight:600; background:var(--accent); color:#fff; border:none; border-radius:8px; cursor:pointer; margin-top:4px;}
.row-between{display:flex; align-items:center; justify-content:space-between; margin:-4px 0 18px;}
.link{color:var(--accent); font-size:var(--fs-sm); text-decoration:none;}
.login-foot{text-align:center; font-size:var(--fs-sm); color:var(--muted); margin-top:18px;}
</style>
</head>
<body data-theme="grafit">
<div class="wrap">
<div class="toolbar">
<span class="note">Comuta tema cu butonul de tema (stil landing: icon + eticheta).</span>
</div>
<!-- ===== A. Antet aplicatie — LOGAT ===== -->
<div class="sec">Antet aplicatie — LOGAT (branded)</div>
<header>
<div class="h-left">
<span class="logo-fallback"><span class="rom">ROM</span><span class="fast">FAST</span></span>
<span class="note" style="font-size:var(--fs-xs)">(in app: PNG logo real)</span>
</div>
<div class="h-center">
<div class="h-title">ROMFAST <span class="accent">AUTOPASS</span><span class="env">test</span><span class="tier">Pro</span></div>
<div class="h-sub">Service auto: <span class="svc">Service Auto Vâlcea SRL</span></div>
</div>
<div class="h-right">
<div class="rar-chip" title="Ultima autentificare RAR: 28.06.2026 09:41"><span class="dot"></span> RAR online</div>
<button class="tema-btn" onclick="cycle()">
<svg id="t-ic" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/></svg>
<span id="t-label">Grafit</span>
</button>
<span class="ver">v5.16</span>
<button class="icon-btn" title="Meniu cont">&#9776;</button>
</div>
</header>
<p class="note">Doar cand esti LOGAT: titlu <strong>ROMFAST AUTOPASS</strong> + badge plan
(<span class="mono">accounts.tier</span>) + sub titlu numele service-ului (<span class="mono">accounts.name</span>);
dreapta dot <strong>RAR online</strong> + selector tema + meniu cont. Toate gate-uite pe
<span class="mono">is_authenticated</span>.</p>
<!-- ===== B. /login — NEAUTENTIFICAT (antet minimal) ===== -->
<div class="sec">Pagina /login — NEAUTENTIFICAT (antet minimal)</div>
<div class="login-topbar">
<span class="lt-brand"><span class="logo-fallback"><span class="rom">ROM</span><span class="fast">FAST</span></span> &nbsp;ROMFAST <span class="accent">AUTOPASS</span></span>
<button class="tema-btn" onclick="cycle()">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/></svg>
<span id="t-label2">Grafit</span>
</button>
</div>
<div class="login-shell" style="border-radius:0 0 16px 16px; border-top:none;">
<div class="login-aside">
<div class="login-brand"><span class="logo-fallback"><span class="rom">ROM</span><span class="fast">FAST</span></span></div>
<h2>ROMFAST <span style="color:var(--accent)">AUTOPASS</span></h2>
<p>Declară prestațiile de service-auto la RAR AUTOPASS, automat. Conform Legii 142/2023.</p>
<div class="trust">
<div><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M20 6L9 17l-5-5"/></svg> Conform Legii 142/2023 și OMTI 210/2024</div>
<div><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="5" y="11" width="14" height="9" rx="1.5"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg> Datele tale criptate, șterse la 3 luni</div>
<div><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg> Parte din familia ROA — Romfast Applications</div>
</div>
</div>
<div class="login-form">
<h3>Autentificare</h3>
<p class="lead">Intră în contul service-ului tău.</p>
<div class="field"><label>Email</label><input type="email" value="contact@service-valcea.ro"></div>
<div class="field"><label>Parolă</label><input type="password" value="••••••••••"></div>
<div class="row-between"><span></span><a class="link" href="#">Ai uitat parola?</a></div>
<button class="btn-primary">Intră în cont</button>
<div class="login-foot">Cont nou? <a class="link" href="/signup">Înregistrează service-ul</a></div>
</div>
</div>
<p class="note">Antetul de <span class="mono">/login</span> NU are dot RAR, nume service sau badge plan —
utilizatorul nu e logat inca. Doar logo + titlu <strong>ROMFAST AUTOPASS</strong> + selector tema.
(RAR/service/plan/meniu apar abia dupa autentificare.)</p>
<div class="sec">Landing — butonul „Autentificare" duce la /login</div>
<p class="note">Pe landing, „Autentificare" (azi deschide modalul de register din landing pe tab-ul
login) devine un link real către <span class="mono">/login</span> (pagina de mai sus). „Creează cont"
rămâne neschimbat. Selectorul de teme din landing e exact modelul pe care îl preia aplicația.</p>
</div>
<script>
var THEMES=[['grafit','Grafit'],['cobalt','Cobalt'],['cupru','Cupru'],['hartie','Hârtie']];
var i=0;
function cycle(){ i=(i+1)%THEMES.length; document.body.setAttribute('data-theme',THEMES[i][0]); document.getElementById('t-label').textContent=THEMES[i][1]; var l2=document.getElementById('t-label2'); if(l2)l2.textContent=THEMES[i][1]; }
</script>
</body>
</html>

View File

@@ -0,0 +1,275 @@
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PRD 5.16 — Wizard import fișier (4 pași) + editare/corecție</title>
<style>
:root{
--font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Segoe UI Mono", "Roboto Mono", Menlo, Consolas, monospace;
--fs-xs:12px; --fs-sm:13.5px; --fs-base:15px; --fs-md:16px; --fs-lg:18px; --fs-xl:20px; --fs-2xl:28px;
--bg:#0f1218; --card:#181c24; --card2:#0f1218; --ink:#e6e9ef; --muted:#8b93a7;
--line:#262b36; --line2:#1f2530; --accent:#6ea2ec; --ok:#2FBF8F; --warn:#E0A93B; --err:#E05D5D;
--hbg:rgba(15,18,24,.9);
}
body[data-theme="hartie"]{ --bg:#f3efe6; --card:#fffdf7; --card2:#f3efe6; --ink:#1e1a13; --muted:#6a6052; --line:#e2dccc; --line2:#ece6d9; --accent:#1F5FBF; --ok:#1c7d5d; --warn:#b45309; --err:#bd463c; --hbg:rgba(255,253,247,.92); }
*{box-sizing:border-box;}
body{margin:0; background:var(--bg); color:var(--ink); font-family:var(--font-ui); font-size:var(--fs-base); -webkit-font-smoothing:antialiased;}
.mono{font-family:var(--font-mono);} .muted{color:var(--muted);}
header{position:sticky; top:0; z-index:5; display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:16px; height:64px; padding:0 22px; background:var(--hbg); backdrop-filter:blur(8px); border-bottom:1px solid var(--line);}
.logo-fallback{display:inline-flex; align-items:center; gap:6px; font-weight:800; font-size:var(--fs-lg);}
.logo-fallback .rom{color:#D1342F;} .logo-fallback .fast{color:var(--accent);}
.h-center{text-align:center; line-height:1.15;}
.h-title{font-size:var(--fs-md); font-weight:700;} .h-title .accent{color:var(--accent);}
.h-sub{font-size:var(--fs-xs); color:var(--muted); margin-top:2px;} .h-sub .svc{color:var(--ink); font-weight:600;}
.env{display:inline-block; margin-left:8px; padding:1px 7px; border-radius:99px; font-size:10px; font-weight:700; text-transform:uppercase; color:var(--warn); background:color-mix(in srgb,var(--warn) 16%,transparent);}
.tier{display:inline-block; margin-left:6px; padding:1px 8px; border-radius:99px; font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:var(--accent); background:color-mix(in srgb,var(--accent) 16%,transparent);}
.h-right{display:flex; align-items:center; justify-content:flex-end; gap:10px;}
.rar-chip{display:flex; align-items:center; gap:8px; height:38px; padding:0 13px; border-radius:99px; border:1px solid color-mix(in srgb,var(--ok) 35%,var(--line)); background:color-mix(in srgb,var(--ok) 10%,transparent); color:var(--ok); font-size:var(--fs-sm); font-weight:600; cursor:default;}
.rar-chip .dot{width:9px; height:9px; border-radius:99px; background:currentColor; box-shadow:0 0 0 4px color-mix(in srgb,currentColor 22%,transparent);}
.tema-btn{display:flex; align-items:center; gap:8px; height:38px; padding:0 13px; border-radius:8px; background:transparent; border:1px solid var(--line); color:var(--muted); font-size:var(--fs-sm); cursor:pointer;}
.icon-btn{width:38px; height:38px; border-radius:8px; border:1px solid var(--line); background:transparent; color:var(--ink); font-size:18px; cursor:pointer; display:inline-flex; align-items:center; justify-content:center;}
.wrap{max-width:1000px; margin:0 auto; padding:22px 22px 70px;}
.screen-cap{font-size:var(--fs-xs); text-transform:uppercase; letter-spacing:.08em; color:var(--accent); font-weight:700; margin:30px 0 10px;}
/* stepper slim */
.stepper{display:flex; align-items:center; gap:0; background:var(--card); border:1px solid var(--line); border-radius:11px; padding:6px; margin-bottom:14px;}
.step{flex:1; display:flex; align-items:center; gap:9px; padding:9px 12px; border-radius:8px; font-size:var(--fs-sm);}
.step .num{display:inline-flex; width:24px; height:24px; align-items:center; justify-content:center; border-radius:99px; font-size:var(--fs-sm); font-weight:700; background:var(--card2); border:1px solid var(--line); color:var(--muted); flex-shrink:0;}
.step.done .num{background:color-mix(in srgb,var(--ok) 20%,transparent); border-color:transparent; color:var(--ok);}
.step.active{background:color-mix(in srgb,var(--accent) 14%,transparent);}
.step.active .num{background:var(--accent); border-color:transparent; color:#fff;}
.step.active .t{color:var(--ink); font-weight:600;} .step .t{color:var(--muted);}
.step .sep{color:var(--line);}
.panel{background:var(--card); border:1px solid var(--line); border-radius:12px; overflow:hidden;}
.panel-head{padding:16px 18px; border-bottom:1px solid var(--line);}
.panel-head h3{margin:0; font-size:var(--fs-lg);}
.panel-head p{margin:4px 0 0; font-size:var(--fs-sm); color:var(--muted);}
.panel-body{padding:18px;}
.foot{display:flex; align-items:center; justify-content:space-between; gap:12px; padding:14px 18px; border-top:1px solid var(--line); background:var(--card2);}
.btn-primary{font-size:var(--fs-md); font-weight:600; height:44px; padding:0 22px; background:var(--accent); color:#fff; border:none; border-radius:9px; cursor:pointer;}
.btn-ghost{font-size:var(--fs-md); height:44px; padding:0 18px; background:transparent; color:var(--ink); border:1px solid var(--line); border-radius:9px; cursor:pointer;}
/* PAS 1 — drop zone */
.drop{border:2px dashed color-mix(in srgb,var(--accent) 45%,var(--line)); border-radius:12px; padding:46px 20px; text-align:center; background:var(--card2);}
.drop .ic{width:54px; height:54px; border-radius:12px; margin:0 auto 14px; display:flex; align-items:center; justify-content:center; background:color-mix(in srgb,var(--accent) 14%,transparent); color:var(--accent);}
.drop .big{font-size:var(--fs-lg); font-weight:700;}
.drop .sm{font-size:var(--fs-sm); color:var(--muted); margin:6px 0 16px;}
.formate{display:inline-flex; gap:8px; margin-top:14px;}
.badge-fmt{font-family:var(--font-mono); font-size:var(--fs-xs); padding:3px 9px; border-radius:6px; background:var(--card); border:1px solid var(--line); color:var(--muted);}
/* PAS 2 — mapare coloane */
.memo{display:flex; align-items:center; gap:9px; font-size:var(--fs-sm); color:var(--ok); background:color-mix(in srgb,var(--ok) 12%,transparent); border:1px solid color-mix(in srgb,var(--ok) 28%,transparent); border-radius:9px; padding:10px 14px; margin-bottom:14px;}
table{width:100%; border-collapse:collapse; font-size:var(--fs-base);}
.map th{text-align:left; font-size:var(--fs-xs); text-transform:uppercase; letter-spacing:.05em; color:var(--muted); padding:0 12px 8px; font-weight:700;}
.map td{padding:9px 12px; border-top:1px solid var(--line2); vertical-align:middle;}
.col-name{font-family:var(--font-mono); font-size:var(--fs-sm); font-weight:600;}
.col-sample{font-family:var(--font-mono); font-size:var(--fs-xs); color:var(--muted);}
.map select{width:100%; font-family:var(--font-ui); font-size:var(--fs-base); color:var(--ink); background:var(--card2); border:1px solid var(--line); border-radius:8px; padding:8px 10px; min-height:38px;}
.map .ignored select{color:var(--muted);}
.switch{display:inline-flex; align-items:center; gap:9px; font-size:var(--fs-sm); color:var(--muted);}
.switch .track{width:38px; height:22px; border-radius:99px; background:color-mix(in srgb,var(--accent) 70%,var(--line)); position:relative;}
.switch .knob{position:absolute; top:2px; right:2px; width:18px; height:18px; border-radius:99px; background:#fff;}
/* PAS 3 — preview */
.summary{display:flex; gap:10px; flex-wrap:wrap; margin-bottom:14px;}
.chipc{display:flex; align-items:center; gap:8px; font-size:var(--fs-sm); padding:7px 13px; border-radius:99px; border:1px solid var(--line); background:var(--card2);}
.chipc b{font-size:var(--fs-md);}
.pv th{text-align:left; font-size:var(--fs-xs); text-transform:uppercase; letter-spacing:.05em; color:var(--muted); padding:0 12px 9px; font-weight:700;}
.pv td{padding:11px 12px; border-top:1px solid var(--line2); font-size:var(--fs-sm);}
.pv .vin{font-family:var(--font-mono); font-size:var(--fs-sm);}
.pill{display:inline-flex; align-items:center; gap:6px; padding:4px 11px; border-radius:99px; font-size:var(--fs-xs); font-weight:600;}
.pill .pdot{width:7px; height:7px; border-radius:99px;}
.ok{background:color-mix(in srgb,var(--ok) 14%,transparent); color:var(--ok);} .ok .pdot{background:var(--ok);}
.warn{background:color-mix(in srgb,var(--warn) 16%,transparent); color:var(--warn);} .warn .pdot{background:var(--warn);}
.err{background:color-mix(in srgb,var(--err) 14%,transparent); color:var(--err);} .err .pdot{background:var(--err);}
.lnk{color:var(--accent); font-size:var(--fs-sm); cursor:pointer; background:none; border:none; padding:0; text-decoration:underline;}
tr.editing{background:color-mix(in srgb,var(--accent) 7%,transparent);}
/* editare inline / corectie (slim form) */
.editbox{margin:2px 12px 12px; border:1px solid color-mix(in srgb,var(--accent) 35%,var(--line)); border-radius:11px; background:var(--card2); padding:16px;}
.editbox .et{font-size:var(--fs-sm); font-weight:700; margin-bottom:12px; color:var(--accent);}
.field{margin-bottom:13px;}
.field label{display:block; font-size:var(--fs-sm); color:var(--muted); margin-bottom:6px;}
.field input, .field textarea, .field select{width:100%; font-family:var(--font-ui); font-size:var(--fs-md); color:var(--ink); background:var(--card); border:1px solid var(--line); border-radius:8px; padding:9px 12px; min-height:40px;}
.field input.mono{font-family:var(--font-mono);}
.grid3{display:grid; grid-template-columns:1.3fr 1fr 1fr; gap:12px;}
.op-row{display:flex; align-items:center; justify-content:space-between; gap:10px; padding:9px 0; border-bottom:1px solid var(--line2);}
.op-name{font-size:var(--fs-md); font-weight:600;} .op-name small{font-weight:400; color:var(--muted); font-size:var(--fs-sm);}
.chip{display:inline-flex; align-items:center; gap:6px; font-family:var(--font-mono); font-size:var(--fs-sm); background:color-mix(in srgb,var(--accent) 18%,transparent); color:var(--accent); padding:5px 10px; border-radius:7px;}
.chip button{background:none; border:none; color:inherit; cursor:pointer; font-size:var(--fs-md);}
.addcode{font-size:var(--fs-sm); border:1px dashed color-mix(in srgb,var(--accent) 55%,var(--line)); background:transparent; color:var(--accent); border-radius:7px; padding:6px 12px; cursor:pointer;}
.save-rule{font-size:var(--fs-xs); color:var(--muted); text-decoration:underline; background:none; border:none; cursor:pointer;}
.actrow{display:flex; gap:10px; margin-top:14px;}
/* PAS 4 — confirma */
.confirm-big{text-align:center; padding:8px 0 4px;}
.confirm-big .n{font-size:42px; font-weight:700; color:var(--ok); line-height:1;}
.confirm-big .l{font-size:var(--fs-md); color:var(--muted); margin-top:6px;}
.breakdown{display:flex; gap:10px; justify-content:center; margin:16px 0;}
.atest{display:flex; align-items:flex-start; gap:10px; font-size:var(--fs-sm); color:var(--ink); background:var(--card2); border:1px solid var(--line); border-radius:10px; padding:14px 16px; margin-top:6px;}
.atest input{margin-top:3px; width:18px; height:18px;}
.warn-note{display:flex; align-items:center; gap:9px; font-size:var(--fs-sm); color:var(--warn); margin-top:12px;}
</style>
</head>
<body data-theme="grafit">
<header>
<span class="logo-fallback"><span class="rom">ROM</span><span class="fast">FAST</span></span>
<div class="h-center">
<div class="h-title">ROMFAST <span class="accent">AUTOPASS</span><span class="env">test</span><span class="tier">Pro</span></div>
<div class="h-sub">Service auto: <span class="svc">Service Auto Vâlcea SRL</span></div>
</div>
<div class="h-right">
<div class="rar-chip" title="Ultima autentificare RAR: 28.06.2026 09:41"><span class="dot"></span> RAR online</div>
<button class="tema-btn"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/></svg> Grafit</button>
<button class="icon-btn">&#9776;</button>
</div>
</header>
<div class="wrap">
<!-- ============ PAS 1 ============ -->
<div class="screen-cap">Pas 1 — Încarcă fișier</div>
<div class="stepper">
<div class="step active"><span class="num">1</span><span class="t">Încarcă</span></div>
<div class="step"><span class="num">2</span><span class="t">Potrivește</span></div>
<div class="step"><span class="num">3</span><span class="t">Verifică</span></div>
<div class="step"><span class="num">4</span><span class="t">Confirmă</span></div>
</div>
<div class="panel">
<div class="panel-head"><h3>Încarcă fișierul cu prestații</h3><p>Trage un fișier xlsx/csv aici sau folosește butonul de alegere.</p></div>
<div class="panel-body">
<div class="drop">
<div class="ic"><svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M12 16V4M7 9l5-5 5 5"/><path d="M5 20h14"/></svg></div>
<div class="big">Trage fișierul aici</div>
<div class="sm">sau apasă pentru a alege de pe calculator · max 5 MB</div>
<button class="btn-primary">Alege fișier</button>
<div class="formate"><span class="badge-fmt">.xlsx</span><span class="badge-fmt">.csv</span><span class="badge-fmt">.xls</span></div>
</div>
</div>
</div>
<!-- ============ PAS 2 ============ -->
<div class="screen-cap">Pas 2 — Potrivește coloanele</div>
<div class="stepper">
<div class="step done"><span class="num"></span><span class="t">Încarcă</span></div>
<div class="step active"><span class="num">2</span><span class="t">Potrivește</span></div>
<div class="step"><span class="num">3</span><span class="t">Verifică</span></div>
<div class="step"><span class="num">4</span><span class="t">Confirmă</span></div>
</div>
<div class="panel">
<div class="panel-head"><h3>Potrivește coloanele fișierului cu câmpurile RAR</h3><p>Spune-ne ce coloană din fișier corespunde cu ce câmp RAR. <span class="mono">prestatii-iunie.xlsx</span> · 38 rânduri.</p></div>
<div class="panel-body">
<div class="memo"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg> Format recunoscut — am reaplicat maparea salvată pentru aceste coloane.</div>
<table class="map">
<thead><tr><th style="width:34%">Coloană din fișier</th><th style="width:30%">Exemplu</th><th style="width:36%">Câmp RAR</th></tr></thead>
<tbody>
<tr><td class="col-name">SASIU</td><td class="col-sample">WBA8E9C5K7F20143</td><td><select><option>VIN (serie șasiu)</option></select></td></tr>
<tr><td class="col-name">DATA</td><td class="col-sample">22.06.2026</td><td><select><option>Data prestației</option></select></td></tr>
<tr><td class="col-name">NR_AUTO</td><td class="col-sample">CT88NOE</td><td><select><option>Număr înmatriculare</option></select></td></tr>
<tr><td class="col-name">KM</td><td class="col-sample">142500</td><td><select><option>Odometru (km)</option></select></td></tr>
<tr><td class="col-name">OPERATIE</td><td class="col-sample">Revizie periodică</td><td><select><option>Operație service → cod RAR</option></select></td></tr>
<tr class="ignored"><td class="col-name">PRET</td><td class="col-sample">350 lei</td><td><select><option>— ignoră coloana —</option></select></td></tr>
</tbody>
</table>
</div>
<div class="foot">
<label class="switch"><span class="track"><span class="knob"></span></span> Ține minte maparea pentru acest format</label>
<div style="display:flex; gap:10px;"><button class="btn-ghost">Înapoi</button><button class="btn-primary">Continuă spre verificare</button></div>
</div>
</div>
<!-- ============ PAS 3 ============ -->
<div class="screen-cap">Pas 3 — Verifică (cu editare/corecție rând)</div>
<div class="stepper">
<div class="step done"><span class="num"></span><span class="t">Încarcă</span></div>
<div class="step done"><span class="num"></span><span class="t">Potrivește</span></div>
<div class="step active"><span class="num">3</span><span class="t">Verifică</span></div>
<div class="step"><span class="num">4</span><span class="t">Confirmă</span></div>
</div>
<div class="panel">
<div class="panel-head"><h3>Verifică rândurile înainte să le trimiți la RAR</h3><p>Corectează rândurile marcate. Restul sunt gata de trimis.</p></div>
<div class="panel-body">
<div class="summary">
<span class="chipc"><span class="pill ok"><span class="pdot"></span></span> <b>33</b> gata</span>
<span class="chipc"><span class="pill warn"><span class="pdot"></span></span> <b>2</b> Cod RAR lipsă</span>
<span class="chipc"><span class="pill err"><span class="pdot"></span></span> <b>1</b> Date incomplete</span>
<span class="chipc"><span class="pill warn"><span class="pdot"></span></span> <b>1</b> Duplicat în fișier</span>
<span class="chipc"><span class="pill ok"><span class="pdot"></span></span> <b>1</b> Deja trimis</span>
</div>
<table class="pv">
<thead><tr><th>VIN</th><th>Operație</th><th>Data</th><th>Stare</th><th></th></tr></thead>
<tbody>
<tr><td class="vin">WBA8E9...K7F2</td><td>Inspecție tehnică</td><td class="mono">22.06.2026</td><td><span class="pill ok"><span class="pdot"></span>Gata</span></td><td><button class="lnk">editează</button></td></tr>
<!-- rand in editare/corectie -->
<tr class="editing"><td class="vin">VF1RFB...A88</td><td>Schimb plăcuțe frână</td><td class="mono">22.06.2026</td><td><span class="pill warn"><span class="pdot"></span>Cod RAR lipsă</span></td><td><button class="lnk">închide</button></td></tr>
<tr class="editing"><td colspan="5" style="padding:0;">
<div class="editbox">
<div class="et">Corectează rândul — VF1RFB...A88</div>
<div class="grid3">
<div class="field"><label>VIN (serie șasiu)</label><input class="mono" value="VF1RFB00A88142073"></div>
<div class="field"><label>Data prestației</label><input class="mono" value="2026-06-22"></div>
<div class="field"><label>Nr. înmatriculare</label><input class="mono" value="CT88NOE"></div>
</div>
<div class="field"><label>Observații (operațiile efectuate)</label><textarea rows="2">Schimbare plăcuțe frână față</textarea></div>
<div class="field">
<label>Prestații — cod RAR pe fiecare operație</label>
<div class="op-row" style="border-left:2px solid var(--warn); padding-left:10px;">
<span class="op-name">SCHIMB PLĂCUȚE FRÂNĂ <small style="color:var(--warn)">— lipsă cod</small></span>
<span style="display:flex; gap:8px; align-items:center;">
<select><option>— alege cod RAR —</option><option>FRN1 — Sistem de frânare</option><option>REV2 — Revizie periodică</option></select>
</span>
</div>
<div style="margin-top:8px; display:flex; align-items:center; gap:12px;">
<button class="addcode">+ Adaugă altă operație / cod RAR</button>
<button class="save-rule">salvează ca regulă op→cod (deblochează rândurile la fel)</button>
</div>
</div>
<div class="actrow"><button class="btn-primary">Salvează rândul</button><button class="btn-ghost">Renunță</button></div>
</div>
</td></tr>
<tr><td class="vin">ZAR937...C04</td><td>Schimb ulei</td><td class="mono">21.06.2026</td><td><span class="pill err"><span class="pdot"></span>Date incomplete</span></td><td><button class="lnk">editează</button></td></tr>
<tr><td class="vin">WVWZZZ...3M1</td><td>Revizie periodică</td><td class="mono">22.06.2026</td><td><span class="pill warn"><span class="pdot"></span>Duplicat în fișier</span></td><td><button class="lnk">editează</button></td></tr>
</tbody>
</table>
</div>
<div class="foot">
<span class="muted" style="font-size:var(--fs-sm);">3 rânduri de corectat înainte de trimitere</span>
<div style="display:flex; gap:10px;"><button class="btn-ghost">Înapoi</button><button class="btn-primary">Confirmă valorile →</button></div>
</div>
</div>
<!-- ============ PAS 4 ============ -->
<div class="screen-cap">Pas 4 — Confirmă trimiterea</div>
<div class="stepper">
<div class="step done"><span class="num"></span><span class="t">Încarcă</span></div>
<div class="step done"><span class="num"></span><span class="t">Potrivește</span></div>
<div class="step done"><span class="num"></span><span class="t">Verifică</span></div>
<div class="step active"><span class="num">4</span><span class="t">Confirmă</span></div>
</div>
<div class="panel">
<div class="panel-head"><h3>Confirmă trimiterea la RAR</h3><p>Acțiunea e ireversibilă — prestațiile pleacă la RAR AUTOPASS.</p></div>
<div class="panel-body">
<div class="confirm-big"><div class="n">36</div><div class="l">prestații gata de trimis</div></div>
<div class="breakdown">
<span class="chipc"><span class="pill ok"><span class="pdot"></span></span> 36 vor pleca</span>
<span class="chipc"><span class="pill warn"><span class="pdot"></span></span> 1 sărit (duplicat)</span>
<span class="chipc"><span class="pill ok"><span class="pdot"></span></span> 1 deja trimis</span>
</div>
<label class="atest"><input type="checkbox" checked> Confirm că datele sunt corecte și autorizez trimiterea celor 36 de prestații la RAR AUTOPASS, conform Legii 142/2023.</label>
<div class="warn-note"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.8 2 18a2 2 0 0 0 1.7 3h16.6a2 2 0 0 0 1.7-3L13.7 3.8a2 2 0 0 0-3.4 0z"/></svg> O prestație finalizată la RAR nu mai poate fi anulată sau corectată prin aplicație.</div>
</div>
<div class="foot">
<button class="btn-ghost">Înapoi la verificare</button>
<button class="btn-primary">Trimite 36 de prestații la RAR</button>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,498 @@
# PRD 5.16 — Tipografie uniforma (fonturi standard web) + bug-fix formular editare + E2E
**Stare**: draft
> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Sistemul de design: `DESIGN.md` + `app/web/templates/base.html`. Landing: `app/web/templates/landing.html`.
> Mockup tipografie (REFERINTA VIZUALA): `docs/mockups/prd-5.16-fonturi-system-stack.html`
> — system font stack, scala uniforma, DOT pentru RAR online, denumiri in picker, buton Renunta.
> Mockup header + login + selector tema (REFERINTA VIZUALA): `docs/mockups/prd-5.16-header-login-tema.html`
> — antet branded "ROMFAST AUTOPASS" + nume service, pagina /login profesionala, selector tema stil landing.
> Mockup dashboard COMPLET (REFERINTA VIZUALA): `docs/mockups/prd-5.16-dashboard.html`
> — pagina Acasa cu antet branded, selector tema landing, strip DOT, contoare SEPARATE
> (Total/Luna/Azi/In coada/De corectat), lista slim, modal editare cu picker+denumiri+Renunta.
> Mockup dashboard MOBIL 390px (REFERINTA VIZUALA): `docs/mockups/prd-5.16-dashboard-mobil.html`
> — Acasa + editare full-screen pe telefon: antet compact (tema doar iconita <=560px ca pe landing),
> contoare separate (Total prominent + 2x2), strip DOT cu text pe 2 linii, butoane full-width.
> Mockup WIZARD import + editare/corectie (REFERINTA VIZUALA): `docs/mockups/prd-5.16-import-wizard.html`
> — cei 4 pasi (1 Incarca · 2 Potriveste coloanele · 3 Verifica · 4 Confirma), import colapsat,
> preview cu stari (Cod RAR lipsa/Date incomplete/Duplicat/Deja trimis) + editare/corectie rand
> inline (picker cod+denumire, + adauga operatie, salveaza regula, Renunta).
> Continua si finalizeaza propagarea de design inceputa in 5.15 (`docs/prd/prd-5.15-propagare-design-dashboard-editare.md`).
> Starea trece: `draft -> aprobat -> in-executie -> verify-pass -> inchis`.
## 1. Introducere
PRD 5.15 a propagat sistemul de design al landing-ului in aplicatie (carduri-contor, lista slim,
formular slim cu chips, 7 teme). La folosire reala userul a constatat ca rezultatul NU respecta inca
claritatea exemplelor din landing: **fonturile sunt prea mici si neuniforme** intre pagini si
formulare, **cardurile au textul inghesuit**, iar in formularul de editare au ramas patru
**bug-uri functionale** care fac corectia trimiterilor frustranta sau imposibila. In plus userul vrea
**fonturi standard web** (fara fisiere de font instalate), aceleasi in aplicatie SI in landing.
5.16 finalizeaza propagarea de design pe doua planuri:
1. **Tipografie**: o singura scala uniforma, lizibila, mai mare, pe fonturi de sistem (system font
stack) — atat in aplicatie cat si in landing — eliminand IBM Plex self-hostat.
2. **Bug-fix + ergonomie formular editare**: cele 4 probleme reale + indicatorul RAR online ca DOT.
Plus o trecere E2E in browser pe toate paginile, conforme cu `DESIGN.md` si spiritul landing-ului.
## 2. Obiective
### Obiectiv principal
Aplicatia sa para acelasi produs ca landing-ul comercial: tipografie clara, uniforma, lizibila, fara
text inghesuit; iar formularul de editare sa functioneze corect (salveaza, se inchide, permite
adaugarea de operatii cu denumiri citibile).
### Obiective secundare
- Zero fisiere de font in runtime (gateway intern) — fonturi 100% native.
- Sursa unica de adevar pentru dimensiunile de text (tokeni de scala), nu valori ad-hoc per template.
- Indicator de stare RAR online consistent cu landing-ul (dot pulsant, nu bifa).
### Metrici de succes
- Un esantion de 5 pagini (Acasa, Trimiteri, detaliu/editare, Mapari, Integrare) folosesc ACELEASI
dimensiuni de text pentru acelasi rol (label, body, cifra) — verificabil prin tokeni.
- Cele 4 bug-uri din §3 (US-004..US-007) reproductibile inainte, ne-reproductibile dupa (teste lock).
- E2E browser: zero overflow orizontal, fonturi native incarcate (fara request la `/static/fonts/`).
## 3. User Stories
> Backend + UI pentru acelasi comportament = stories separate. `base.html` e fisier FIERBINTE
> (serializat — un singur autor pe val). Toate UI verificate pe un esantion de teme (o luminoasa +
> una intunecata) si pe 390/1280.
### US-001: Fonturi standard web (system font stack) — eliminam IBM Plex self-hostat
**Ca** operator **vreau** fonturi web standard, lizibile, fara sa instalez nimic **pentru ca** vreau
text clar si uniform, fara dependente de fisiere de font.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/base.html` (`--font-ui`/`--font-mono` + `font-family` body),
`app/web/templates/landing.html` (sterge cele 9 `@font-face` IBM Plex + `font:... 'IBM Plex Sans'`),
`DESIGN.md` (sectiunea Tipografie rescrisa), `tests/test_tema.py` / `tests/test_web_responsive.py`
(~4 fisiere)
- **Test intai (RED)**: `test_font_stack_system_in_base`, `test_landing_fara_font_face_ibm_plex`,
`test_zero_referinte_static_fonts`
- **Acceptance criteria**:
- [ ] `base.html` defineste doi tokeni sursa-de-adevar in `:root`:
`--font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;`
si `--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Segoe UI Mono", "Roboto Mono",
Menlo, Consolas, monospace;`. `body` foloseste `var(--font-ui)`; codurile/VIN folosesc
`var(--font-mono)` prin clasa existenta (`.camp-mono`/`.slim-vin` etc.).
- [ ] `landing.html`: cele 9 reguli `@font-face` IBM Plex Sans/Mono ELIMINATE; toate aparitiile
`font: ... 'IBM Plex Sans'` / `'IBM Plex Mono'` inlocuite cu `var(--font-ui)` / `var(--font-mono)`
(sau un fallback explicit identic cu app-ul). Landing si app folosesc ACELASI stack.
- [ ] **Zero referinte la `/static/fonts/`** raman in template-uri (grep negativ in test). Fisierele
woff2 din `app/web/static/fonts/` pot ramane pe disc (curatare = follow-up optional, non-blocant),
dar nu mai sunt referite.
- [ ] Diacriticele romanesti (ă/â/î/ș/ț) se randeaza corect pe stack-ul nativ (acoperite de fonturile
de sistem — verificat vizual in E2E).
- [ ] `DESIGN.md` §Tipografie rescrisa: motiveaza system font stack (zero dependente runtime,
nativ pe fiecare OS), documenteaza cele doua stive, noteaza tradeoff-ul (aspect usor diferit
Windows/Mac/Linux) ca decizie acceptata de user.
- **Verificare E2E**: DevTools Network pe `/` si pe landing — niciun request catre `/static/fonts/`;
text lizibil pe Windows (Segoe UI) verificat de operator la deploy.
### US-002: Scala tipografica uniforma (tokeni `--fs-*`) + carduri aerisite
**Ca** operator **vreau** dimensiuni de text uniforme si mai mari, fara text inghesuit **pentru ca**
azi fonturile sunt ad-hoc (10/11/13px) si difera de la pagina la pagina.
- **Depinde de**: US-001 (acelasi val, `base.html`)
- **Fisiere**: `app/web/templates/base.html` (tokeni `--fs-*` + clasele slim/contor/camp consumandu-i),
`DESIGN.md`, `tests/test_web_responsive.py` (~3 fisiere)
- **Test intai (RED)**: `test_tokeni_scala_fs_definiti`, `test_componente_slim_folosesc_fs_tokeni`,
`test_fara_font_size_sub_12px_in_componente_noi`
- **Acceptance criteria**:
- [ ] `base.html` defineste o scala unica in `:root` (sursa de adevar): `--fs-xs:12px`, `--fs-sm:13.5px`,
`--fs-base:15px`, `--fs-md:16px`, `--fs-lg:18px`, `--fs-xl:20px`, `--fs-2xl:28px`, `--fs-3xl:34px`
(+ `--lh-tight`/`--lh-body`). Valorile finale pot fi ajustate la executie, dar DEFINITE ca tokeni.
Referinta vizuala: `docs/mockups/prd-5.16-fonturi-system-stack.html`.
- [ ] Componentele slim din 5.15 (`.contor-cifra`, `.contor-label`, `.contor-sub`, `.slim-vin`,
`.slim-meta`, `.camp-slim label/input`, `.chip`, `.op-row-name`, pill-uri) sunt RECABLATE pe
tokenii `--fs-*` (NU mai au px hardcodat sub 12px). Minim crestere fata de azi:
label-uri 11→13.5px, sub-linii 10→12px, cifra contor 22→28px, input 13→16px.
- [ ] **Carduri aerisite**: `.contor-card` si randurile slim cresc padding-ul (contor ~18px,
rand slim ~14px) ca textul mai mare sa nu para inghesuit; fara overflow pe 390px.
- [ ] **Contoare separate (NU inghesuite)**: statisticile "Trimise" se afiseaza ca **carduri
distincte** — `Total`, `Luna asta`, `Azi` fiecare cu eticheta proprie, pe langa `In coada` si
`De corectat` (5 in total). NU se mai inghesuie `luna`/`azi` ca sub-linie mono sub cifra "Total"
(asa cum era in 5.15/D4). Pe **desktop**: 5 carduri pe un rand, FARA titlu/eticheta de grup
deasupra (minimalist — userul nu vrea subtitlu de sectiune). Referinta:
`docs/mockups/prd-5.16-dashboard.html`.
**Nota**: REVIZUIESTE framing-ul D4/US-003 din 5.15 (cifra mare all-time + sub-linie `luna · azi`).
Sursa de timp/bucketare RO ramane cea din 5.15/US-003 (`sent_today`/`sent_month`, timp local RO
E7); doar PREZENTAREA se schimba.
- [ ] **Mobil: contoare COMPACTE (doar numere)**: pe 390px contoarele NU mai sunt 5 carduri mari, ci
o **bara compacta de statistici pe un singur rand** — cifra mare + eticheta scurta (`Total`/`Lună`/
`Azi`/`Coadă`/`Corectat`), separatoare subtiri, inaltime mica, fara a ocupa jumatate de ecran.
Referinta: `docs/mockups/prd-5.16-dashboard-mobil.html`.
- [ ] **Ordine dashboard (compact, minimalist)**: pe Acasa, ordinea verticala e **(1) carduri-contor
→ (2) import colapsat → (3) tab-uri Trimiteri/Mapari → (4) lista**. Indicatorul RAR online NU mai
ocupa o banda in corp — sta ca dot in antet (US-003). Tab-urile Trimiteri/Mapari stau imediat
deasupra listei (nu sub antet). Atat pe desktop cat si pe mobil.
- [ ] **Fara duplicare / fara subtitlu de sectiune**: numele service-ului apare DOAR in antet
(US-010), NU se mai repeta intr-un meta "Service Auto … · data" deasupra listei. Se ELIMINA
titlul de sectiune "Trimiteri RAR AUTOPASS" + linia meta de sub el (ocupau spatiu pe desktop si
mobil) — lista incepe direct sub tab-uri/filtre.
- [ ] `body` are `font-size:var(--fs-base)` si `line-height:var(--lh-body)` (azi implicit de browser).
- [ ] Zero regresie vizuala pe componentele existente non-slim (`.card/.pill/.act/.tabel-trimiteri`):
crestem unde e prea mic, fara a sparge layout-ul tabelului desktop.
- [ ] `DESIGN.md` §Tipografie + §Componente slim actualizat cu scala in tokeni.
- **Verificare E2E**: browser pe Acasa + Trimiteri + editare, esantion de teme + 390/1280 — text mai
mare, uniform, fara inghesuire, fara overflow.
### US-003: RAR online — dot compact in antet (nu banda) + in meniul burger; banda DOAR cand e blocat
**Ca** operator **vreau** ca "RAR online" sa fie un dot mic in antet (cu data/ora pe hover) si in meniul
burger, NU o banda care ocupa un rand intreg **pentru ca** cand totul e ok nu vreau sa-mi fure spatiu —
dar cand e blocat trebuie sa fie imposibil de ratat.
- **Depinde de**: US-002, US-011 (antet/selector tema)
- **Fisiere**: `app/web/templates/base.html` (antet + meniu burger), `app/web/templates/_status.html`
(banda doar pe blocat + dot), `app/web/routes.py` (context sanatate in layout, nu doar fragment),
`DESIGN.md`, `tests/test_web_status.py`, `tests/test_web_status_fragment.py` (~6 fisiere)
- **Test intai (RED)**: `test_rar_dot_in_antet_ok`, `test_rar_in_meniu_burger`,
`test_banda_apare_doar_cand_blocat`, `test_dot_title_datetime`, `test_blocat_text_accesibil`
- **Acceptance criteria**:
- [ ] **Stare OK (online)**: indicatorul RAR e un **dot/pill compact in antet**, langa selectorul de
tema (ex. dot verde + "RAR online"), stilat ca pill-ul "Live" din landing (`landing.html:117`).
Data/ora ultimei autentificari RAR apare pe **`title`/hover** (`title="Ultima autentificare RAR:
28.06.2026 09:41"`), NU pe un rand separat. NU mai exista banda full-width in corpul paginii in
starea OK. Referinta: `docs/mockups/prd-5.16-dashboard.html`.
- [ ] **Si in meniul burger**: starea RAR (dot + "RAR online" + ora ultimei autentificari) apare ca
prima intrare in meniul de cont, pentru cazul in care antetul e ingust (mobil) — vezi
`docs/mockups/prd-5.16-dashboard-mobil.html`.
- [ ] **Stare BLOCAT (worker oprit / RAR inaccesibil)**: ATUNCI (si numai atunci) reapare **banda
rosie full-width**, loud, in corp ("Blocat: RAR inaccesibil — declaratiile NU pleaca"), iar
dot-ul din antet devine rosu. Invariant zero-silent-failures (D6/5.15) PASTRAT: blocajul e
imposibil de ratat; doar starea OK se comprima la un dot.
- [ ] **Accesibilitate**: sensul NU depinde de culoare — `title`/`aria-label` pe dot ("RAR online" /
"RAR blocat") + textul explicit al benzii pe blocat poarta sensul pentru screen-reader/daltonisti.
Se inlocuieste glifa bifa/X (`✓/✗`) cu dot + text.
- [ ] `DESIGN.md` §Header & branding + §Componente actualizat: dot de stare in antet + banda doar pe
blocat documentate.
- **Verificare E2E**: browser pe `/` — worker viu → dot verde in antet (+ in meniu), fara banda, ora pe
hover; worker oprit → dot rosu + banda rosie in corp; screen-reader confirma sensul fara culoare.
### US-004: Bug — picker prestatii arata DENUMIREA, nu doar codul
**Ca** operator **vreau** sa vad denumirea operatiei/codului RAR cand aleg din picker **pentru ca**
nu imi dau seama ce e doar din cod (ex. "REV2").
- **Depinde de**: — (independent; UI-only in template chips)
- **Fisiere**: `app/web/templates/_chips_prestatii.html`, `tests/test_web_mapare_din_chip.py` SAU
`tests/test_web_corectie_prestatii.py` (~2 fisiere)
- **Test intai (RED)**: `test_picker_flat_arata_cod_si_denumire`, `test_picker_op_arata_denumire`
- **Acceptance criteria**:
- [ ] In **modul plat** (corectie pura, fara `op_service`), picker-ul `chips_add_cod_flat`
(`_chips_prestatii.html:146-148`) afiseaza `{{ cod }} — {{ nume_prestatie }}` pentru fiecare
optiune (azi arata DOAR `cod_prestatie`). Acelasi format ca modul operatii (`:101`).
- [ ] Optiunea placeholder ramane lizibila ("+ cod RAR" sau "— alege cod RAR —"); latimea
selectului creste cat sa incapa denumirea fara a sparge layout-ul chips.
- [ ] Consistenta: ORICE select de cod RAR din formularul de editare (plat + per-operatie) arata
cod + denumire. Niciun loc nu mai arata cod gol.
- **Verificare E2E**: editez o trimitere fara cod (corectie pura) → deschid picker-ul → optiunile arata
"FRN1 — Sistem de franare", nu doar "FRN1".
### US-005: Bug — adaugare de operatii/coduri RAR suplimentare la editare
**Ca** operator **vreau** sa pot adauga ALTE operatii/coduri RAR pe o trimitere cu probleme **pentru ca**
azi, cand trimiterea are deja operatii, nu pot adauga un cod RAR in plus.
- **Depinde de**: US-004 (acelasi template)
- **Fisiere**: `app/web/templates/_chips_prestatii.html`, `app/web/routes.py` (`post_form_chips`,
`routes.py:1867-1957` — actiune noua `add_extra`), `tests/test_web_corectie_prestatii.py` (~3 fisiere)
- **Test intai (RED)**: `test_adauga_cod_extra_in_mod_operatii`, `test_extra_cod_persistat_la_salvare`,
`test_extra_cod_validat_nomenclator`
- **Acceptance criteria**:
- [ ] In **modul operatii** (`_has_ops`), sub lista de operatii apare un control "**+ Adauga alta
operatie / cod RAR**" (picker cod+denumire + buton), care adauga un chip de cod RAR liber
(fara `op_service`) la lista — pe langa codurile per-operatie existente. Azi acest control
exista DOAR in modul plat; il aducem si in modul operatii.
- [ ] `post_form_chips` primeste o actiune noua (`chips_action=add_extra`) care valideaza codul fata
de nomenclator (invariant ORA-12899) si il adauga ca item `{cod_prestatie, cod_op_service:"",
denumire:""}`. Re-randeaza partial-ul chips.
- [ ] Codul extra adaugat SE PERSISTA la salvare: e emis ca hidden `cod_prestatie` (paralel cu
`chip_op_service`/`chip_denumire` goale) si cules de `post_corectie_trimitere`
(`routes.py:1334 getlist`). Lista finala `prestatii` contine si codurile per-operatie SI
codurile extra (dedup per-item E4 din 5.15 pastrat).
- [ ] Stergere simetrica: chip-ul extra are `×` (reuse `remove`/`remove_flat`).
- **Verificare E2E**: editez o trimitere cu o operatie mapata → adaug un cod RAR extra → salvez →
`prestatii` contine ambele coduri (verificat in detaliu / payload).
### US-006: Bug — salvarea codului ales pe o trimitere fara cod nu facea nimic
**Ca** operator **vreau** ca atunci cand aleg un cod RAR si salvez, sa se aplice **pentru ca** azi,
la o trimitere care nu avea cod operatie, aleg codul dar la salvare nu se intampla nimic.
- **Depinde de**: US-004, US-005
- **Fisiere**: `app/web/templates/_chips_prestatii.html`, `app/web/routes.py`
(`post_corectie_trimitere` / `post_form_chips`), `tests/test_web_corectie_prestatii.py` (~3 fisiere)
- **Test intai (RED)**: `test_cod_ales_in_picker_se_salveaza_fara_buton_add`,
`test_salvare_fara_chip_explicit_nu_e_no_op`
- **Acceptance criteria**:
- [ ] **Reproducere (RED)**: o trimitere fara `cod_prestatie` (mod plat, ZERO chips). Userul alege
un cod in picker-ul `chips_add_cod_flat` si apasa direct "Salveaza" (FARA sa apese "+" intai).
Azi: `getlist("cod_prestatie")` e gol → submission ramane `needs_mapping` → "nu se intampla
nimic". Cauza confirmata: selectul `chips_add_cod_flat` NU e citit de `post_corectie_trimitere`.
- [ ] **Fix**: `post_corectie_trimitere` (si `/repune`, `/editeaza`) culeg si codul nesubmis din
picker (`chips_add_cod_flat` + `chips_add_cod_{i}` per-operatie) ca un add implicit la salvare,
SAU formularul promoveaza automat selectia in curs intr-un chip inainte de submit (progressive
enhancement JS + fallback server). Rezultat: codul ales se aplica chiar fara click pe "+".
Decizie de implementare (la executie): preferinta pe calea SERVER (citeste picker-ul la submit),
ca sa functioneze si fara JS — aliniat cu E6 server-driven din 5.15.
- [ ] Codul ales e validat fata de nomenclator; cod necunoscut → mesaj, nu trimitere raw.
- [ ] Nu rupe fluxul existent "+ apoi Salveaza" (ramane valid; nu se dubleaza codul — dedup per-item).
- **Verificare E2E**: editez o trimitere needs_mapping fara cod → aleg un cod in picker → Salveaza
(fara "+") → submission devine `queued` cu codul ales.
### US-007: Bug — butonul Renunta/Anuleaza inchide formularul
**Ca** operator **vreau** ca butonul Renunta sa inchida modalul **pentru ca** azi nu il inchide si
raman blocat in formular.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/base.html` (handler `data-modal-close`, `base.html:1131-1133`),
`tests/test_web_corectie.py` SAU un test JS/markup nou (~2 fisiere)
- **Test intai (RED)**: `test_anuleaza_are_data_modal_close`,
`test_modal_close_pe_element_interior` (markup: butonul Anuleaza contine `<span>`/icon → tinta de
click e copilul)
- **Acceptance criteria**:
- [ ] **Cauza confirmata**: handler-ul `overlay.addEventListener('click', ...)` (`base.html:1132`)
verifica `e.target.hasAttribute('data-modal-close')`. Butonul Anuleaza/Renunta
(`_form_editare.html:106`) are atributul pe `<button>`, dar contine `<span class="act-tx">` +
un SVG icon → la click pe text/icon, `e.target` e copilul fara atribut → `close()` NU se apeleaza.
(X-ul `.modal-close` merge doar fiindca e `&times;` fara copii.)
- [ ] **Fix**: handler-ul foloseste `e.target.closest('[data-modal-close]')` in loc de
`hasAttribute` direct, ca un click pe orice descendent al unui element cu `data-modal-close`
sa inchida modalul. Pastreaza inchiderea pe backdrop + Esc.
- [ ] Butonul de Renunta e prezent in AMBELE modale unde se editeaza (detaliu + preview import):
verifica `with_cancel` / eticheta. Daca in modalul de detaliu nu exista buton de inchidere
in form, se adauga unul (text "Renunta"), nu doar X-ul din colt.
- [ ] Focus-ul revine logic dupa inchidere (comportament `close()` existent pastrat).
- **Verificare E2E**: deschid editarea → apas Renunta (pe text si pe icon) → modalul se inchide,
focus revine pe rand; Esc si backdrop inchid in continuare.
### US-008: Landing — aliniere copy si fonturi cu aplicatia (fara redesign)
**Ca** vizitator **vreau** ca landing-ul sa foloseasca aceleasi fonturi ca aplicatia **pentru ca**
userul a cerut explicit fonturi web uniforme intre cele doua.
- **Depinde de**: US-001
- **Fisiere**: `app/web/templates/landing.html`, `tests/test_web_*` (~2 fisiere)
- **Test intai (RED)**: `test_landing_foloseste_font_stack_app`
- **Acceptance criteria**:
- [ ] Landing-ul foloseste `var(--font-ui)`/`var(--font-mono)` (sau acelasi literal de stack) —
consecinta directa a US-001, fara redesign de layout.
- [ ] Mockup-ul intern din landing (cardul "prestatie noua", `landing.html:155-171`) — care arata
"Confirma Vin" ca al doilea camp VIN — ramane DOAR ilustrativ; NU il aliniem la formularul real
(VIN unic) in acest PRD (non-goal: redesign landing). Notat ca debt vizual minor.
- [ ] Fara alte modificari de continut in 5.16 (copy-ul de plan/limita 60 e in PRD-ul de tipuri-cont).
- **Verificare E2E**: landing in browser — fonturi identice cu app, fara request la `/static/fonts/`.
### US-010: Antet branded "ROMFAST AUTOPASS" + nume service + /login profesional
**Ca** operator **vreau** un antet profesional cu "ROMFAST AUTOPASS" si numele service-ului meu, si o
pagina /login pe masura **pentru ca** azi antetul arata generic ("Gateway RAR AUTOPASS") iar /login e
schelet — nu inspira incredere.
- **Depinde de**: US-001, US-002 (fonturi + scala)
- **Fisiere**: `app/web/templates/base.html` (header), `app/web/templates/login.html`,
`app/web/auth_routes.py` (`_base_ctx`/`login_get` — paseaza `account_name` cand e logat),
`app/web/routes.py` (context layout cu `account_name`), `DESIGN.md` (§Header & branding),
`tests/test_web_responsive.py` / un test de header (~5 fisiere)
- **Test intai (RED)**: `test_titlu_romfast_autopass`, `test_header_arata_nume_service_logat`,
`test_login_branded_nu_schelet`
- **Acceptance criteria**:
- [ ] Titlul din antet devine **"ROMFAST AUTOPASS"** (azi "Gateway RAR AUTOPASS",
`base.html:769`), pastrand badge-ul de mediu (test/prod) si link-ul la `/`.
- [ ] Cand userul e autentificat, sub titlu apare **numele service-ului auto** (din `accounts.name`,
ex. "Service auto: Service Auto Vâlcea SRL"), pasat in context (`account_name`). Pe paginile
neautentificate (login/signup) NU apare (nu exista cont logat).
- [ ] **Gate strict pe `is_authenticated` (NU pe /login/signup)**: dot-ul RAR online (US-003), numele
service-ului, badge-ul de plan/tier (US-010 + 5.17) si meniul burger se randeaza DOAR cand
`is_authenticated` e adevarat. Pe `/login` si `/signup` antetul e **minimal**: logo ROMFAST +
titlu "ROMFAST AUTOPASS" + selectorul de tema — atat. Fara RAR, fara nume service, fara tier,
fara meniu de cont (utilizatorul nu e logat). Referinta: `docs/mockups/prd-5.16-header-login-tema.html`
(blocurile "LOGAT" vs "/login NEAUTENTIFICAT").
- [ ] **/login profesional, nu schelet**: pagina capata un layout brandeit pe doua coloane (panou
stanga cu logo ROMFAST + "ROMFAST AUTOPASS" + tagline conformitate/criptare; dreapta formularul
de autentificare existent), folosind tokenii de fonturi/scala (US-001/002) si paleta temelor.
Functionalitatea formularului (`POST /login`, CSRF, link signup/parola) ramane neschimbata.
Referinta: `docs/mockups/prd-5.16-header-login-tema.html`. Degradeaza la o coloana pe mobil.
- [ ] `<title>` actualizat coerent ("ROMFAST AUTOPASS" in loc de "Gateway RAR AUTOPASS").
- [ ] **Tip cont (plan) in titlu**: langa titlu/badge-ul de mediu apare un **badge de plan**
(`Gratuit`/`Standard`/`Pro`/`Premium`), citit din `accounts.tier` (model definit in PRD 5.17;
in 5.16 e doar AFISARE — daca 5.17 nu e livrat inca, badge-ul citeste `tier` cu fallback
`Gratuit`). Referinta: `docs/mockups/prd-5.16-dashboard.html` / `...-mobil.html`.
- [ ] **Meniul burger — structura cu separatoare + plan + RAR**: meniul de cont primeste, in ordine:
(a) starea RAR online (US-003) + (b) linia "Plan: <tier> [· trial N zile]", apoi intrarile de
navigare **grupate cu separatoare (`<hr>`) intre sectiuni**: [Trimiteri · Mapari] | [Nomenclator]
| [Cont · Integrare · Jurnal] | [Iesi din cont]. Userul a cerut explicit separatoare clare intre
sectiuni. (Detaliile planului/trial vin din 5.17; in 5.16 = afisare.)
- [ ] `DESIGN.md` §Header & branding actualizat (titlu nou + nume service sub titlu + badge plan;
meniu burger cu separatoare).
- **Verificare E2E**: browser logat → antet "ROMFAST AUTOPASS" + nume service + badge plan; meniul
burger arata RAR + plan + sectiuni separate; `/login` brandeit pe desktop + o coloana pe 390px.
### US-011: Selector de tema stil landing (icon + eticheta temei curente)
**Ca** operator **vreau** ca selectorul de teme din aplicatie sa arate ca cel din landing **pentru ca**
azi e doar o iconita (☀) fara eticheta si nu stiu pe ce tema sunt.
- **Depinde de**: US-002 (acelasi val pe `base.html`)
- **Fisiere**: `app/web/templates/base.html` (butonul `#tema-toggle` + scriptul ciclic),
`DESIGN.md` (§Selector de tema), `tests/test_tema.py` (~3 fisiere)
- **Test intai (RED)**: `test_selector_tema_are_eticheta`, `test_eticheta_reflecta_tema_curenta`
- **Acceptance criteria**:
- [ ] Butonul de tema devine un **pill cu icon + eticheta** (numele temei curente vizibil, ex.
"Grafit"), ca pe landing (`landing.html:78-81`), nu doar iconita. Reuse structura `THEMES`
existenta (DRY E2 din 5.15) pentru a deriva eticheta — fara a doua sursa de adevar.
- [ ] Ciclarea prin toate temele (5.15) ramane; eticheta se actualizeaza la fiecare click; aria-label
si `#tema-live` (anunt screen-reader) reflecta tema curenta.
- [ ] Pe mobil (390px), daca spatiul e strans, eticheta se poate ascunde (ca pe landing,
`#theme-label{display:none}` la <=560px) pastrand iconita — degradare eleganta, fara overflow.
- [ ] Stil aliniat header-ului (US-010): bordura `--line`, hover `--accent`, fonturi system.
- [ ] `DESIGN.md` §Selector de tema actualizat (pill icon+eticheta, nu doar iconita).
- **Verificare E2E**: browser → butonul arata "Grafit"/"Hârtie" etc.; click cicleaza si eticheta se
schimba; pe 390px iconita ramane fara a sparge antetul.
### US-012: Landing — butonul "Autentificare" duce la /login
**Ca** vizitator **vreau** ca "Autentificare" sa ma duca la pagina reala de login **pentru ca** azi
deschide formularul de inregistrare din landing (tab login), nu pagina dedicata.
- **Depinde de**: US-010 (pagina /login brandeita e tinta)
- **Fisiere**: `app/web/templates/landing.html` (butonul `data-act="auth" data-tab="login"` +
scriptul de auth), `tests/test_web_*` (~2 fisiere)
- **Test intai (RED)**: `test_landing_autentificare_link_login`
- **Acceptance criteria**:
- [ ] Butonul "Autentificare" din header-ul landing (`landing.html:82`) devine un link/redirect real
catre **`/login`** (un `<a href="/login">` stilat ca butonul actual, sau handler care face
`location.href='/login'`), NU mai deschide modalul de auth din landing pe tab-ul login.
- [ ] "Creează cont" ramane neschimbat (deschide modalul de register din landing SAU duce la `/signup`
— pastram comportamentul actual; userul a cerut sa schimbam DOAR Autentificare).
- [ ] Daca dupa scoaterea tab-ului login modalul de auth ramane doar pe register, codul mort de
comutare login/register din landing se curata (fara a rupe register). Non-blocant daca modalul
ramane bi-tab; minim: butonul Autentificare nu mai deschide modalul.
- [ ] Link-ul "Autentifică-te" din interiorul modalului de register (`landing.html:370`) — decizie la
executie: poate ramane (tab intern) sau redirect la `/login`; implicit pastrat.
- **Verificare E2E**: click pe "Autentificare" in landing → navigheaza la `/login` (pagina brandeita
US-010), nu deschide modalul.
### US-013: Wizard import + preview/corectie aliniate la designul 5.16 (import colapsat)
**Ca** operator **vreau** ca paginile de import (cei 4 pasi + verificarea) sa arate la fel de
profesional ca restul aplicatiei **pentru ca** acolo petrec efectiv timpul, iar azi importul ocupa
spatiu pe Acasa chiar cand nu il folosesc.
- **Depinde de**: US-002 (scala/tokeni), US-004/005/006 (chips in editarea din preview)
- **Fisiere**: `app/web/templates/_stepper.html`, `app/web/templates/_upload.html`,
`app/web/templates/_mapcoloane.html`, `app/web/templates/_preview_import.html`,
`app/web/templates/_preview_rand.html`, `app/web/templates/_acasa.html` (import colapsat),
`tests/test_web_import_stepper.py` / `tests/test_web_responsive.py` (~6 fisiere)
- **Test intai (RED)**: `test_import_colapsat_implicit`, `test_wizard_foloseste_scala_tokeni`,
`test_preview_stari_pill_dot`
- **Acceptance criteria**:
- [ ] **Import colapsat pe Acasa**: zona de upload nu mai ocupa spatiu implicit — devine o bara slim
("+ Importa fisier (XLSX / CSV)") care se extinde la click (`<details>` nativ, fara JS custom),
ca lista de trimiteri sa fie primul lucru vizibil. Referinta: `docs/mockups/prd-5.16-dashboard.html`.
- [ ] **Cei 4 pasi** (`_stepper.html`, `_upload.html`, `_mapcoloane.html`, `_preview_import.html`)
consuma tokenii de fonturi/scala (US-001/002): stepper slim lizibil, tabel de mapare cu nume
coloana + exemplu + select "camp RAR", preview cu pill-uri de stare folosind **dot** (consistent
cu lista slim si stripul) — fara hex hardcodat, pe toate temele. Referinta:
`docs/mockups/prd-5.16-import-wizard.html`.
- [ ] **Editare/corectie rand in preview** foloseste ACELASI `_form_editare.html`/`_chips_prestatii.html`
ca modalul de detaliu — deci mostenesc automat fix-urile US-004 (denumiri in picker), US-005
(adaugare operatie), US-006 (salvare cod ales) si US-007 (Renunta inchide). Nu se duplica logica.
- [ ] Pill-urile de stare din preview pastreaza maparea din `labels.py` (`STARI_PREVIEW`:
"Cod RAR lipsa"/"Date incomplete"/"Verifica valori"/"Deja trimis"/"Duplicat in fisier") — zero
etichete noi; doar stilul (dot + scala) se aliniaza.
- [ ] Responsive: pe mobil stepperul ramane forma colapsata "Pasul N din 4" (5.13) si tabelele de
mapare/preview nu produc overflow orizontal.
- **Verificare E2E**: import xlsx → pas 1..4 in design 5.16; pe Acasa importul e colapsat implicit;
in preview, un rand needs_mapping editat cu picker (denumiri) + Renunta → comportament identic cu
modalul de detaliu.
### US-009: E2E browser final pe toate paginile + regresie
**Ca** dezvoltator **vreau** o trecere E2E completa **pentru ca** 5.16 atinge `base.html` (fierbinte)
si formularul de editare, si nu vreau regresii.
- **Depinde de**: US-002, US-003, US-006, US-007, US-008, US-010, US-011, US-012, US-013
- **Fisiere**: `tests/test_web_responsive.py`, `tests/test_tema.py`, `tests/test_web_corectie.py`
(~3 fisiere)
- **Test intai (RED)**: completare scenarii lipsa (scala pe toate componentele; bug-urile lock).
- **Acceptance criteria**:
- [ ] `python3 -m pytest -q -m "not live"` verde (fara regresii fata de baseline 5.15).
- [ ] E2E Playwright (sau headless screenshot ca fallback in sandbox) pe 390/1280, pe o tema
intunecata (grafit) + una luminoasa (hartie): Acasa (antet "ROMFAST AUTOPASS" + nume service,
selector tema cu eticheta, strip dot + contoare aerisite), Trimiteri (lista slim text mai mare),
editare (denumiri in picker, adaugare operatie extra, salvare cod ales, Renunta inchide),
wizard import (4 pasi + import colapsat pe Acasa + preview cu pill-uri dot + corectie rand),
`/login` brandeit, Mapari, Integrare, landing (Autentificare → /login) — fara overflow orizontal,
fonturi native (zero `/static/fonts/`), text uniform.
- [ ] Raport VERIFY documenteaza cele 4 bug-uri ca reproduse-inainte / reparate-dupa.
- **Verificare E2E**: rulare completa documentata in Raportul VERIFY.
## 4. Cerinte functionale (rezumat)
1. [REQ-001] Toate template-urile folosesc `var(--font-ui)`/`var(--font-mono)` (system stack); zero
`@font-face` si zero referinte `/static/fonts/`.
2. [REQ-002] Toate dimensiunile de text din componentele de design folosesc tokenii `--fs-*`.
3. [REQ-003] Indicatorul RAR online = dot colorat + text (nu bifa), accesibil fara culoare.
4. [REQ-004] Picker-ele de cod RAR arata cod + denumire peste tot.
5. [REQ-005] Se pot adauga operatii/coduri RAR suplimentare la editare, in modul operatii si plat.
6. [REQ-006] Codul ales in picker se aplica la salvare chiar si fara pasul intermediar "+".
7. [REQ-007] Butonul Renunta inchide modalul (click pe orice descendent).
8. [REQ-008] Antet branded "ROMFAST AUTOPASS" + nume service logat; `/login` profesional (nu schelet).
9. [REQ-009] Selector de tema stil landing (icon + eticheta temei curente).
10. [REQ-010] Butonul "Autentificare" din landing duce la `/login`.
## 5. Non-Goals (anti scope-creep)
- Fara redesign al layout-ului landing (doar fonturi); mockup-ul intern "Confirma Vin" ramane ilustrativ.
- Fara modificari pe backend-ul de trimitere (worker, masina de stari, idempotenta-logica `build_key`,
reconciliere, contract RAR). US-005/006 ating DOAR handler-ele de editare/`form-chips`, nu logica de
trimitere.
- Fara migrare de schema.
- Fara tipuri de cont / planuri / limite — sunt in PRD 5.17 (separat, la cererea userului).
- Fara stergerea fizica a fisierelor woff2 din `static/fonts/` (curatare optionala, follow-up).
- Fara a schimba cele 7 teme sau tokenii cromatici (5.15 ramane); 5.16 atinge doar tipografia.
## 6. Consideratii tehnice
- **Stack**: FastAPI + Jinja2 + HTMX, CSS pe variabile in `base.html` (fara build de CSS).
- **Patterns de urmat**: tokeni CSS sursa-unica (ca `THEMES`/`--card2`/`--line2` din 5.15);
server-driven HTMX (E6); `e.target.closest` pentru delegare de evenimente (deja folosit la
`data-modal-retry`, `base.html:1166`).
- **Fisier fierbinte**: `base.html` atins de US-001/002/003/007 → un singur autor pe val, serializat.
- **Riscuri**:
- System font stack arata diferit pe Windows/Mac/Linux — acceptat de user (tradeoff "no install").
Mitigare: scala in px (nu em-uri dependente de font) ramane uniforma indiferent de familie.
- Diacritice RO pe fonturi de sistem — toate stack-urile tinta le acopera; verificat in E2E.
- US-006 (save no-op) e un bug de cale dubla picker/chip — riscul e sa "reparam" doar JS-ul si sa
ramana rupt fara JS. Mitigare: fix pe calea server (citeste picker-ul la submit).
- `base.html` fierbinte + 7 teme = suprafata de test; recablarea pe `--fs-*` trebuie sa nu strice
tabelul desktop. Mitigare: US-002 AC de non-regresie + test ancorat pe sentinel.
## 7. Consideratii UI/UX
- Referinta vizuala obligatorie: `docs/mockups/prd-5.16-fonturi-system-stack.html`.
- Stari: strip verde (online) / rosu (blocat); picker gol vs cu denumiri; chip extra adaugabil/stergibil;
modal cu Renunta functional.
- Tinte 44px pe mobil pastrate; contrast AA in toate temele (text mai mare ajuta lizibilitatea).
## 8. Open Questions
- [ ] Valorile exacte ale scalei `--fs-*` (12/13.5/15/16/28…) — propuse in mockup; de validat la
executie pe ecran real, in limita "mai mare + uniform".
- [ ] Dot-ul RAR online: puls discret (animatie) DA/NU — implicit DA (ca "Live" din landing), dezactivabil
daca deranjeaza.
- [ ] Curatarea fizica a `static/fonts/*.woff2` — in 5.16 sau follow-up separat? (implicit: follow-up).
## 9. Valuri de executie
```
Val 1: [US-001] -> [US-002] -> [US-003] -> [US-010] -> [US-011] -> [US-007]
base.html: fonturi, scala, dot, antet branded,
selector tema, fix Renunta — SECVENTIAL (autor unic base.html) ||
[US-008] landing fonturi (dupa US-001 fixeaza stack-ul)
Val 2: [US-004] -> [US-005] -> [US-006] chips: denumiri, adaugare extra, save-no-op —
SECVENTIAL (acelasi _chips_prestatii.html + routes)
Val 3: [US-012] landing Autentificare → /login (dupa US-010 livreaza /login)
Val 4: [US-009] E2E final + regresie (dupa toate)
```
> Regula autor-unic: `base.html` (US-001/002/003/007/010/011) + `login.html`/`auth_routes.py` (US-010)
> si `routes.py`/`_chips_prestatii.html` (US-004/005/006) sunt fisiere fierbinti — serializate, nu
> paralele. US-010 atinge si `login.html` (separat), poate merge in paralel cu chips-urile din Val 2 daca
> autorul base.html e liber.
---
> Acest PRD nu a fost inca trecut prin `/plan-ceo-review` / `/plan-eng-review`. Recomandat inainte de
> executie (atinge `base.html` fierbinte + 4 bug-uri reale in calea de editare).

View File

@@ -0,0 +1,317 @@
# PRD 5.17 — Tipuri de cont (planuri) + trial Pro 30 zile + enforcement
**Stare**: draft
> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Landing comercial cu planurile: `app/web/templates/landing.html` (sectiunea PRICING).
> Lifecycle cont existent: `app/accounts.py`, `app/schema.sql` (tabela `accounts`, coloana `status`).
> Signup: `app/web/auth_routes.py` (`signup_post`, butoanele landing trimit `data-plan`).
> Starea trece: `draft -> aprobat -> in-executie -> verify-pass -> inchis`.
## 1. Introducere
Landing-ul comercial promite patru planuri — **Gratuit**, **Standard (39 lei)**, **Pro (59 lei, cu
API)**, **Premium (la cerere)** — si afirma ca **fiecare cont incepe cu acces gratuit 30 de zile** la
un plan superior. In aplicatie insa **nu exista deloc conceptul de tip de cont**: tabela `accounts`
are doar `status` (pending/active/blocked/archived/deleted) si `on_unmapped_error_default`. Nimic nu
diferentiaza un cont gratuit de unul platit, nimic nu aplica limita de volum sau gate-ul de API, si nu
exista niciun trial.
In plus, userul a decis doua corectii fata de landing-ul actual:
1. Trial-ul de 30 de zile e pe **Pro**, NU pe Premium (landing-ul scrie azi "Premium gratuit 30 de
zile" — gresit; trebuie "Pro 30 de zile").
2. Limita planului **Gratuit** scade de la **100** la **60 de prestatii/luna** — actualizata si in
landing si in aplicatie.
5.17 introduce modelul de tipuri de cont, trial-ul Pro de 30 de zile, **enforcement DUR** al
diferentelor (volum lunar + acces API), si downgrade automat la expirarea trial-ului. NU include
integrare de plata (nu exista inca sistem de facturare) — alocarea planului platit ramane manuala
(admin), iar trial-ul porneste automat la creare cont.
## 2. Obiective
### Obiectiv principal
Aplicatia sa sustina real diferentele dintre planuri pe care landing-ul le promite: cont nou →
trial Pro 30 zile → la expirare downgrade pe Gratuit (60/luna, fara API), cu enforcement efectiv.
### Obiective secundare
- Sursa unica de adevar pentru definitia planurilor (limite + capabilitati), consumata de backend si UI.
- Mesaje oneste cand un cont atinge limita sau cere o capabilitate neinclusa (3 niveluri, ca 5.4).
- Vizibilitate in dashboard: planul curent + zile ramase din trial + consum lunar.
### Metrici de succes
- Un cont Gratuit care depaseste 60 prestatii/luna primeste un raspuns clar de respingere (API + web),
iar contoarele lunare se reseteaza corect la inceput de luna (timp local RO).
- Un cont fara plan Pro+ primeste 403 onest pe `/v1/*` de import API.
- Un cont nou are trial Pro activ; dupa 30 zile (sau setand `trial_until` in trecut in test) trece
automat pe Gratuit, cu enforcement-ul aferent.
- Landing + app afiseaza coerent "60 prestatii/luna" si "Pro gratuit 30 de zile".
## 3. User Stories
> Database → backend → API → UI (ordinea dependentelor). Un singur autor pe `accounts.py`/`schema.sql`
> in valul de model.
### US-001: Schema — `accounts.tier` + `trial_until` + definitia planurilor
**Ca** sistem **vreau** sa stiu planul fiecarui cont si pana cand e in trial **pentru ca** restul
logicii depinde de asta.
- **Depinde de**: —
- **Fisiere**: `app/schema.sql` (coloane noi + migrare defensiva), `app/accounts.py` (helperi),
`app/plans.py` (NOU — definitia planurilor, sursa de adevar), `tests/test_accounts.py` /
`tests/test_plans.py` (~4 fisiere)
- **Test intai (RED)**: `test_migrare_tier_trial_defensiva`, `test_plan_definitii`,
`test_cont_nou_trial_pro_30z`
- **Acceptance criteria**:
- [ ] `accounts` capata (migrare aditiva defensiva, ca `email`/`status` in 5.5/5.12):
`tier TEXT NOT NULL DEFAULT 'free' CHECK (tier IN ('free','standard','pro','premium'))`
si `trial_until TEXT` (nullable; ISO datetime UTC sau NULL daca nu e in trial).
- [ ] `app/plans.py` = SINGURA sursa de adevar: dict `PLANS` cu, per plan,
`{label, monthly_limit, api_access, ...}`. Valori: `free``monthly_limit=60`, `api_access=False`;
`standard``monthly_limit=None` (nelimitat), `api_access=False`; `pro``monthly_limit=None`,
`api_access=True`; `premium``monthly_limit=None`, `api_access=True`. (Aliniat landing-ului,
cu limita Gratuit 60.)
- [ ] Helper `effective_tier(account)`: daca `trial_until` e in viitor → randeaza ca `pro`
(trial); altfel `tier`. (Trial-ul = acces Pro temporar peste tier-ul de baza `free`.)
- [ ] `create_account` seteaza `tier='free'` si `trial_until = now + 30 zile` (trial Pro automat la
creare). Contul implicit id=1 (dev) e exceptat / setat coerent (nu blocheaza dev-ul).
- [ ] Migrare idempotenta (re-rulabila); conturile legacy fara `tier` primesc `free` + fara trial
(sau trial calculat din `created_at` — decizie la executie; implicit: legacy → free fara trial).
- **Verificare E2E**: creez cont nou → `tier=free`, `trial_until ≈ now+30z`, `effective_tier=pro`.
### US-002: Numarator de consum lunar (prestatii/luna pe cont)
**Ca** sistem **vreau** sa stiu cate prestatii a trimis un cont in luna curenta **pentru ca** limita
Gratuit (60/luna) se aplica pe acest numar.
- **Depinde de**: US-001
- **Fisiere**: `app/accounts.py` SAU `app/plans.py` (`monthly_usage(conn, account_id)`),
`tests/test_plans.py` (~2 fisiere)
- **Test intai (RED)**: `test_consum_lunar_numara_sent_si_queued`, `test_consum_lunar_timp_local_ro`,
`test_consum_lunar_resetare_luna_noua`
- **Acceptance criteria**:
- [ ] `monthly_usage(conn, account_id)` numara prestatiile contului in luna calendaristica curenta.
**Definitia "prestatie consumata"** (de fixat la executie, propus): randuri `submissions` ale
contului cu `status` in (`queued`,`sending`,`sent`) cu `created_at` in luna curenta — adica
prestatiile ACCEPTATE in coada, nu cele respinse/blocate. (Justificare: limita e pe ce trimitem
la RAR, nu pe incercari esuate.) Alternativ doar `sent` — de decis; implicit: acceptate-in-coada.
- [ ] **Timp local RO** (ca E7 din 5.15): bucketarea lunii foloseste offset RO (`created_at,'+3 hours'`
sau echivalent), nu UTC pur, ca prestatiile de la granita de luna sa cada corect. Test la granita.
- [ ] Scoped strict pe cont (nu numara cross-account).
- [ ] Fara coloana noua daca `submissions.created_at` ajunge (respecta non-goal migrare minima).
- **Verificare E2E**: cont cu N trimiteri in luna → `monthly_usage == N`; luna urmatoare → reset la 0.
### US-003: Enforcement DUR — limita lunara Gratuit (60) pe ambele canale
**Ca** owner **vreau** ca un cont Gratuit care depaseste 60 prestatii/luna sa fie oprit **pentru ca**
asa sustinem diferenta de plan promisa.
- **Depinde de**: US-001, US-002
- **Fisiere**: `app/api/v1/router.py` (`create_prezentari`), `app/api/v1/import_router.py`
(commit import), `app/errors.py` (cod nou `PLAN_LIMITA_LUNARA`), `app/web/routes.py` (commit web),
`tests/test_api_scope.py` / `tests/test_web_*` / `tests/test_plans.py` (~6 fisiere)
- **Test intai (RED)**: `test_free_peste_60_respins_api`, `test_free_peste_60_respins_import_web`,
`test_pro_si_trial_nelimitat`, `test_eroare_3_niveluri_plan_limita`
- **Acceptance criteria**:
- [ ] La enqueue (API `POST /v1/prezentari` + commit import web + commit import API), daca
`effective_tier` are `monthly_limit` si `monthly_usage + nr_cerut > monthly_limit` → cererea
e respinsa (sau respinsa partial, la limita) cu eroare 3 niveluri (`app/errors.py`, cod
`PLAN_LIMITA_LUNARA`: problema "Ai atins limita planului Gratuit (60/luna)", cauza, fix
"Treci pe Standard/Pro sau astepti luna viitoare"). NU se face enqueue peste limita.
- [ ] `standard`/`pro`/`premium` si conturile in **trial Pro** → fara limita de volum.
- [ ] Comportament la cerere de lot care depaseste partial limita (ex. 50 folosite, vin 20):
decizie la executie — implicit RESPINGERE clara a intregului lot cu mesaj cat mai e disponibil
("mai poti trimite 10 luna asta"), NU enqueue partial tacut (evita surprize). De confirmat.
- [ ] Enforcement aliniat cu `AUTOPASS_REQUIRE_API_KEY` (dev vs prod): in dev, contul id=1 nu e
blocat artificial (trial/standard coerent), ca dogfooding-ul sa nu se loveasca de limita.
- [ ] **Idempotenta neatinsa**: respingerea pe limita se face INAINTE de `build_key`/enqueue; un
retry idempotent al unei prestatii deja acceptate nu consuma din nou cota.
- **Verificare E2E**: cont free cu 60 trimise → a 61-a respinsa cu mesaj 3 niveluri (API si import web);
cont pro → trece.
### US-004: Enforcement DUR — gate API doar pe Pro/Premium
**Ca** owner **vreau** ca importul prin API sa fie disponibil doar pe Pro+ **pentru ca** landing-ul
spune ca API-ul e o capabilitate Pro.
- **Depinde de**: US-001
- **Fisiere**: `app/auth.py` (sau dependinta de ruta), `app/api/v1/router.py`,
`app/api/v1/import_router.py`, `app/errors.py` (cod `PLAN_FARA_API`), `tests/test_api_scope.py`
(~5 fisiere)
- **Test intai (RED)**: `test_free_fara_api_403`, `test_standard_fara_api_403`, `test_pro_api_ok`,
`test_trial_pro_api_ok`, `test_dry_run_valideaza_ramane_permis`
- **Acceptance criteria**:
- [ ] Rutele de **import/ingestie prin API** (`POST /v1/prezentari`, `POST /v1/import`, etc.)
cer `effective_tier.api_access == True` (pro/premium sau trial Pro). Altfel 403 cu eroare
3 niveluri (`PLAN_FARA_API`: "Importul prin API e disponibil pe planul Pro", fix).
- [ ] **Canalul web ramane neafectat** — operatorii pe plan gratuit pot folosi import xlsx/csv prin
dashboard (asa promite landing-ul: Gratuit are import manual, NU API). Doar suprafata API e gated.
- [ ] `GET /v1/nomenclator` ramane public (coduri RAR, fara PII) — invariant CLAUDE.md.
- [ ] `POST /v1/prezentari/valideaza` (dry-run) — decizie: ramane permis pe orice plan (read-only,
ajuta integrarea inainte de upgrade) SAU gated ca restul API. Implicit: PERMIS (read-only,
fara enqueue). De confirmat.
- [ ] In dev (`AUTOPASS_REQUIRE_API_KEY=false`), contul id=1 are acces API (tier coerent), ca testele
API existente sa nu pice.
- **Verificare E2E**: cheie API pe cont free → 403 onest pe import; cheie pe cont pro/trial → 200.
### US-005: Downgrade automat la expirarea trial-ului
**Ca** owner **vreau** ca la expirarea celor 30 de zile contul sa treaca automat pe Gratuit **pentru ca**
landing-ul spune "apoi trece automat pe Gratuit, fara plata".
- **Depinde de**: US-001, US-003, US-004
- **Fisiere**: `app/plans.py` (`effective_tier` deja trateaza expirarea — lazy), optional
`app/worker/__main__.py` SAU un job de intretinere (eager), `tests/test_plans.py` (~3 fisiere)
- **Test intai (RED)**: `test_trial_expirat_efective_free`, `test_trial_expirat_aplica_limita_60`,
`test_trial_expirat_pierde_api`
- **Acceptance criteria**:
- [ ] **Lazy-first**: `effective_tier` returneaza `tier` de baza (`free`) imediat ce
`trial_until <= now` — fara job necesar pentru corectitudine (enforcement-ul US-003/004 se
bazeaza pe `effective_tier`, deci downgrade-ul e automat la prima cerere dupa expirare).
- [ ] Optional (eager, non-blocant): un pas in purjarea orara a worker-ului (T16 existent) poate
normaliza `trial_until` expirat → NULL pentru igiena (NU obligatoriu pentru corectitudine).
- [ ] Un cont cu `tier='standard'/'pro'/'premium'` setat de admin NU e downgradat de expirarea
trial-ului (trial-ul e un BONUS peste `free`; un plan platit alocat persista).
- [ ] Mesajele de limita/API dupa expirare sunt cele 3-niveluri din US-003/004.
- **Verificare E2E**: setez `trial_until` in trecut → contul aplica limita 60 + pierde API, fara restart.
### US-006: UI dashboard — plan curent + zile ramase din trial + consum lunar
**Ca** operator **vreau** sa vad pe ce plan sunt, cat mi-a mai ramas din trial si cat am consumat
luna asta **pentru ca** vreau sa stiu cand ma apropii de limita.
- **Depinde de**: US-001, US-002
- **Fisiere**: `app/web/routes.py` (context), `app/web/templates/_status.html` SAU `_cont.html`
(afisaj plan), `tests/test_web_status.py` / `tests/test_dashboard.py` (~4 fisiere)
- **Test intai (RED)**: `test_afisaj_plan_si_zile_trial`, `test_afisaj_consum_lunar`,
`test_avertizare_aproape_de_limita`
- **Acceptance criteria**:
- [ ] Dashboard-ul afiseaza discret planul curent (ex. "Plan: Pro · trial 18 zile ramase" sau
"Plan: Gratuit · 47/60 luna asta"). In trial → eticheta "trial" + zile ramase; pe Gratuit →
consum `N/60`.
- [ ] **Plasare (aliniat cu PRD 5.16)**: planul apare ca **badge in titlul din antet**
(`Gratuit`/`Standard`/`Pro`/`Premium`) SI ca linie in **meniul burger** ("Plan: <tier> [· trial
N zile]"), nu doar intr-un card pe Acasa. Vezi mockup-urile 5.16
(`docs/mockups/prd-5.16-dashboard.html` / `...-mobil.html`). 5.16 furnizeaza locul de afisare
(antet + meniu); 5.17 furnizeaza datele (tier, trial, consum).
- [ ] Avertizare vizuala cand consumul Gratuit se apropie de limita (ex. ≥80% → ton warn), fara a
ingropa stripul de sanatate (zero-silent-failures pastrat).
- [ ] Scoped pe cont; design conform 5.15/5.16 (tokeni, fonturi system, fara hex hardcodat).
- [ ] Pagina "Cont" arata planul + (daca exista) o explicatie "cum trec pe alt plan" (contact, ca
nu exista plata self-service inca).
- **Verificare E2E**: cont trial → "trial N zile"; cont free aproape de 60 → avertizare; cont pro →
fara contor de limita.
### US-007: Aliniere landing — limita 60 + trial pe Pro (nu Premium)
**Ca** vizitator **vreau** ca landing-ul sa spuna adevarul **pentru ca** azi promite "100/luna" si
"Premium gratuit 30 zile", dar realitatea va fi 60/luna si trial pe Pro.
- **Depinde de**: — (copy-only; aliniaza cu modelul din US-001)
- **Fisiere**: `app/web/templates/landing.html`, `tests/test_web_*` (~2 fisiere)
- **Test intai (RED)**: `test_landing_limita_60`, `test_landing_trial_pro_nu_premium`
- **Acceptance criteria**:
- [ ] Toate aparitiile "100 de prestatii/luna" / "100/luna" / `meta description`
(`landing.html:7,65,266` + oriunde apar) → **60**. Inclusiv cardul Gratuit din sectiunea PRICING.
- [ ] Textul "Fiecare cont incepe cu **Premium gratuit 30 de zile**" (`landing.html:256`) →
"**Pro gratuit 30 de zile**" (planul corect). Restul frazei ("Apoi trece automat pe Gratuit…")
ramane.
- [ ] Coerenta: orice alt loc care implica trial/limita reflecta 60 + Pro.
- [ ] Fara alte schimbari de pret/continut (39/59 lei raman).
- **Verificare E2E**: landing in browser — "60 prestatii/luna" peste tot, "Pro gratuit 30 de zile".
### US-008: Admin — alocare manuala de plan (fara plata self-service)
**Ca** admin **vreau** sa pot seta planul unui cont **pentru ca** nu exista inca facturare automata,
dar trebuie sa pot acorda Standard/Pro/Premium.
- **Depinde de**: US-001
- **Fisiere**: `tools/account.py` (CLI `set-tier`), optional `app/web/routes.py` (`/admin` actiune),
`tests/test_accounts.py` / `tests/test_web_admin*.py` (~3 fisiere)
- **Test intai (RED)**: `test_cli_set_tier`, `test_admin_set_tier_scoped`, `test_tier_invalid_respins`
- **Acceptance criteria**:
- [ ] CLI `python3 -m tools.account set-tier --account N --tier pro [--trial-days 30|--no-trial]`
seteaza `tier`/`trial_until`. Tier invalid → eroare clara.
- [ ] Optional (la executie): actiune in panoul `/admin` pentru a seta planul unui cont (scoped,
CSRF, ca bulk-ul de status din 5.5). Daca nu intra in 5.17, CLI e suficient (admin-only).
- [ ] Alocarea unui plan platit de catre admin NU e suprascrisa de expirarea trial-ului (US-005).
- [ ] Audit: schimbarea de plan se logheaza in `app_events` (reuse jurnalul din 5.6), fara PII nou.
- **Verificare E2E**: `set-tier --account 2 --tier pro` → contul 2 are API + volum nelimitat.
### US-009: Teste de regresie + E2E plan/trial/enforcement
**Ca** dezvoltator **vreau** acoperire completa **pentru ca** enforcement-ul atinge ambele canale de
ingestie si nu vreau sa blochez gresit conturi legitime.
- **Depinde de**: US-003, US-004, US-005, US-006, US-007
- **Fisiere**: `tests/test_plans.py`, `tests/test_api_scope.py`, `tests/test_web_*` (~3 fisiere)
- **Test intai (RED)**: matricea plan × capabilitate (volum, API) × canal (API, web) × trial activ/expirat.
- **Acceptance criteria**:
- [ ] `python3 -m pytest -q -m "not live"` verde; regresia de aur (`POST /v1/prezentari` → queued
pe un cont cu drept) ramane verde.
- [ ] Matrice testata: free(volum-blocat/API-blocat), standard(volum-ok/API-blocat),
pro(ok/ok), trial-pro(ok/ok), trial-expirat(=free).
- [ ] Contoarele lunare resetate la luna noua (test la granita timp local RO).
- [ ] Dev (id=1) nu e blocat de enforcement (dogfooding).
- **Verificare E2E**: rulare completa documentata in Raportul VERIFY.
## 4. Cerinte functionale (rezumat)
1. [REQ-001] `accounts.tier` ∈ {free,standard,pro,premium} + `trial_until`; migrare aditiva defensiva.
2. [REQ-002] `app/plans.py` = sursa unica: limite (free=60/luna) + capabilitati (API doar Pro+).
3. [REQ-003] Cont nou → trial Pro 30 zile automat; `effective_tier` randeaza Pro in trial, free dupa.
4. [REQ-004] Enforcement DUR: free peste 60/luna respins (API + import web) cu eroare 3 niveluri.
5. [REQ-005] Enforcement DUR: import API gated pe Pro+ (403 onest); canalul web ramane liber.
6. [REQ-006] Downgrade automat la expirare trial (lazy via `effective_tier`).
7. [REQ-007] Dashboard arata plan + zile trial + consum lunar; landing aliniat (60, Pro).
8. [REQ-008] Admin aloca planuri manual (CLI `set-tier`), audit in `app_events`.
## 5. Non-Goals (anti scope-creep)
- **Fara integrare de plata / facturare / abonamente** (Stripe etc.) — alocarea platita = manuala (admin).
- Fara self-service upgrade din UI (doar afisare plan + "contacteaza-ne"); plata vine intr-un PRD viitor.
- Fara modificari pe backend-ul de trimitere (worker, masina de stari, idempotenta `build_key`,
reconciliere, contract RAR). Enforcement-ul se face la ingestie/enqueue, INAINTE de coada.
- Fara schimbarea capabilitatilor de produs in sine (sugestii/mapare exista deja pe toate planurile in
cod; diferentierea 5.17 e pe VOLUM + ACCES API, exact ce promite landing-ul ca diferentiator hard).
- Fara modificari de design (tipografia/temele sunt 5.16/5.15); doar reuse-ul stilurilor existente.
## 6. Consideratii tehnice
- **Stack**: SQLite (migrare aditiva defensiva ca 5.5/5.12), FastAPI, Jinja2/HTMX.
- **Patterns de urmat**: sursa unica (`app/plans.py` ca `app/errors.py`); eroare 3 niveluri (5.4);
scope pe cont (5.15/US-011); timp local RO la bucketare (5.15/E7); audit `app_events` (5.6).
- **Riscuri**:
- **Blocare gresita a unui cont legitim** (enforcement prea agresiv) — risc de business. Mitigare:
dev id=1 exceptat; teste matrice; mesaje 3 niveluri cu cale de iesire; respingere INAINTE de enqueue
(nu pierde date).
- **Definitia "prestatie consumata"** (acceptate-in-coada vs sent) schimba cand musca limita.
Mitigare: o decidem explicit (US-002 AC) + test; documentam.
- **Granita de luna / fus orar** — off-by-a-day la reset. Mitigare: timp local RO + test la granita
(lectia E7 din 5.15).
- **Idempotenta vs cota** — un retry idempotent nu trebuie sa consume cota de doua ori. Mitigare:
enforce inainte de `build_key`; testul de retry.
- **Conturi legacy fara tier** — migrare le pune `free`; un cont real activ ar putea fi limitat brusc
la 60. Mitigare: decizie de migrare (legacy activ → ce plan?) confirmata cu user inainte de deploy.
## 7. Consideratii UI/UX
- Afisaj plan discret, conform 5.16 (fonturi system, tokeni `--fs-*`, fara hex).
- Stari: trial activ (zile ramase) / free (consum N/60, warn la ≥80%) / platit (fara contor limita).
- Mesaje de respingere oneste, actionabile (cum trec pe alt plan), nu doar "403".
## 8. Open Questions
- [ ] "Prestatie consumata" = acceptate-in-coada (queued+sending+sent) sau doar `sent`? (implicit: acceptate)
- [ ] Lot care depaseste partial limita → respingere totala sau enqueue partial? (implicit: respingere totala clara)
- [ ] `POST /v1/prezentari/valideaza` (dry-run) — gated pe Pro sau permis tuturor? (implicit: permis)
- [ ] Migrare conturi legacy active: raman `free` (risc limitare brusca) sau primesc un trial/plan? (de confirmat cu user)
- [ ] Standard (39 lei) si Premium difera de Pro doar prin API + suport in landing — pastram exact maparea
de capabilitati din landing in `plans.py`? (implicit: da)
## 9. Valuri de executie
```
Val 1: [US-001] schema tier+trial + app/plans.py (autor unic schema/accounts)
Val 2: [US-002] numarator consum lunar (dupa model) ||
[US-007] landing copy 60 + Pro (independent, copy-only)
Val 3: [US-003] [US-004] [US-005] enforcement volum + API + downgrade (consuma plans.py)
Val 4: [US-006] [US-008] UI dashboard plan/consum || admin set-tier
Val 5: [US-009] regresie + E2E matrice (dupa toate)
```
> Secventiere fata de 5.16: independent (5.16 = design/tipografie; 5.17 = model de cont). Pot rula in
> paralel; doar US-006 (afisaj plan in `_status.html`) atinge un fisier pe care 5.16/US-003 il modifica
> (dot RAR) — serializeaza acel template daca ambele PRD-uri sunt in executie simultan.
---
> Acest PRD nu a fost inca trecut prin `/plan-ceo-review` / `/plan-eng-review`. Recomandat inainte de
> executie (enforcement de business cu risc de blocare gresita + decizia de migrare a conturilor legacy).