Hero-ul primei pagini se vopsește din HTML-ul servit
Ține de #44 (closed) și de #10 (closed). Nu le închide — se închid când cifrele de pe producție confirmă.
Problema, măsurată
curl https://hyperice.md/ dă 9.417 octeți, din care capul ~8.500 — corpul are sub o mie. Nimic nu se vopsește până nu ajunge și pornește JavaScript-ul. Măsurat azi (Lighthouse 13.4.1, mobil, mediana a 5 rulări, mașină liniștită): FCP 2,93 s, LCP 4,21 s, față de pragul de 2,5 s.
LCP nu poate fi niciodată mai devreme decât FCP. Livrarea de azi a dovedit-o din partea cealaltă: indiciile din cap (!48 (merged)) au funcționat — capul conține acum preload-ul pozei și nouă modulepreload-uri, verificate pe producție — și LCP-ul s-a mișcat cu −0,26 s, adică sub zgomot. Nimic de deasupra podelei nu poate atinge pragul.
Ce face acest MR
Rendererul scrie stratul de imagine al hero-ului direct în <div id="root">, ca browserul să-l vopsească imediat ce are documentul, fișierul de stiluri și poza — fără JavaScript. React preia după aceea.
Verificat empiric pe React 19.2.7, versiunea din proiect: createRoot peste markup pus de server în #root îl înlocuiește curat la prima randare, cu zero mesaje în consolă. Nu e nevoie de hidratare.
Corpul emis are 629 de octeți.
Scop restrâns deliberat
Doar prima pagină. Pe pagina de produs elementul LCP începe la y=198 — are conținut deasupra — deci markup-ul serverului l-ar așeza la y=0 și ar sări când vine React. Pe prima pagină antetul stă peste hero: elementul LCP măsurat are boundingRect.top = 2.
Doar stratul de imagine, nu și textul. Copia hero-ului (h1 cu cn(), clase condiționate, --hl-size din numărul setat de admin, BannerButton) e prea intricată ca să fie dublată într-un șir fără să divergă în tăcere la prima editare din panou. Elementul LCP măsurat este poza (412×695), nu textul. Textul vine cu React și nu mută nimic: cutia are min-h-[695px], poza e absolute inset-0, iar copia intră într-un flex items-end mult sub înălțimea aia.
Săgețile, punctele și butonul de pauză sunt absolute — React le adaugă fără să miște nimic.
Geometria, măsurată
| Fereastră | Markup server, fără JS | Producție după React | Diferență |
|---|---|---|---|
| 1440×900 | x0 y0 1440×873 |
x0 y0 1440×873 |
zero |
| 412×823 | x0 y0 412×695 |
x0 y2 412×695 |
2 px |
Cei 2 px vin din antetul mobil de 106 px față de marginea negativă de −104. Contribuția la CLS: ~0,002, față de 0,011 azi și un prag de 0,1.
Confirmat separat, cu foaia de stil reală de producție: picture { display: contents } se aplică, secțiunea iese 375×695 la y=0, iar currentSrc al pozei coincide cu fișierul numit de preload — deci o singură descărcare, nu două.
Testul de sincronizare
backend/tests/hero-markup.test.ts citește HeroBanner.tsx, extrage cele patru liste de clase și verifică că exact acele șiruri apar în documentul randat. Prinde orice clasă adăugată, scoasă sau reordonată, și orice listă mutată în cn() (ancora nu se mai găsește și testul pică zgomotos). Nu prinde structura: un element nou între cutie și poză, altă imbricare, sau o modificare în index.css. Scris explicit în comentariul testului.
Verificat
| Comandă | Rezultat |
|---|---|
backend/ npm run build |
exit 0 |
backend/ npm test |
543 de teste, toate trec |
frontend/ npx tsc --noEmit -p tsconfig.app.json |
exit 0 |
frontend/ npx tsc --noEmit -p tsconfig.test.json |
exit 0 |
frontend/ npm run build |
exit 0 |
frontend/ npm test |
226 de teste, toate trec |
Ce nu s-a verificat: câștigul de LCP. Se măsoară după deploy, mediana a 5 rulări pe mașină liniștită, ca restul cifrelor din #10 (closed).