Deschizi un site. Vezi un header, niște butoane, un grid cu produse, un formular care-ți cere email-ul. Apeși ceva, pagina reacționează. Redimensionezi fereastra, totul se rearanjează frumos.
Cineva a construit toate astea. Fiecare pixel pe care-l vezi, fiecare click care răspunde, fiecare animație care curge lin — ăsta e frontend development. Iar odată ce înțelegi cum funcționează, web-ul încetează să mai pară magic și începe să pară ceva ce poți modela.
Ce face de fapt un frontend developer
Un frontend developer construiește partea din site sau aplicație pe care oamenii o văd și cu care interacționează. Atât. Definiția sună simplu, dar munca din spate se împarte în trei straturi pe care le vei învăța în restul cursului:
HTML — structura și conținutul. Titluri, paragrafe, imagini, link-uri, formulare. Gândește-te la el ca la scheletul paginii.
<!-- HTML + CSS + JS: comportament --><buttonclass="cta"onclick="alert('Comandă plasată!')">
Cumpără acum
</button>
Fără CSS, butonul funcționează dar arată ca în 1995. Fără JavaScript, arată super dar nu se întâmplă nimic când apeși. Toate trei împreună fac o interfață modernă.
Frontend vs backend vs full-stack
Oamenii aruncă cuvintele astea în conversație de parcă toată lumea știe ce înseamnă. Versiunea onestă:
Frontend e tot ce rulează în browser-ul utilizatorului. Scrii cod, browser-ul afișează ceva. Ciclu rapid de feedback, muncă foarte vizuală.
Backend e tot ce rulează pe server, invizibil pentru utilizator. Baze de date, API-uri, autentificare, procesare plăți. Când trimiți un formular, backend-ul e cel care primește, salvează și răspunde.
Full-stack înseamnă un dezvoltator care face ambele. Util pentru echipe mici și freelance, dar majoritatea job-urilor sunt specializate pe o parte.
Dacă îți place să vezi imediat rezultatul vizual al muncii tale, frontend e de obicei mai potrivit. Dacă-ți plac puzzle-urile logice și datele, backend ți-ar putea plăcea mai mult. Nu trebuie să alegi pentru totdeauna — mulți developeri încep cu unul și plutesc spre celălalt.
Idei greșite des întâlnite
Frontend nu e „doar design". Un designer decide cum ar trebui să arate ceva. Un frontend developer face să funcționeze efectiv pe mii de device-uri, browsere și dimensiuni de ecran.
Frontend nu e „ușor" față de backend. E diferit. Să faci un layout care arată bine pe un telefon de 320px și pe un monitor 4K cu același cod e un tip de dificultate propriu.
Nu trebuie să fii designer ca să faci frontend. Îți trebuie gust și atenție la detalii. Pe astea ți le poți construi.
Ce vei putea face după acest curs
La finalul cursului HTML & CSS Fundamentals, vei putea:
Construi orice pagină web statică de la zero
Să arate profesional fără să copiezi template-uri
Să funcționeze pe orice dimensiune de ecran, de la telefon la desktop
Să fie accesibilă pentru persoane care folosesc cititoare de ecran sau navighează din tastatură
Să o publici pe internet, ca oricine din lume s-o poată vizita
E suficient să poți prelua muncă reală de freelance, să-ți construiești un portofoliu sau să aplici pentru un rol junior frontend. Când adaugi și JavaScript (următorul curs), treci pragul către aplicații reale.
Prima ta vedere din ce urmează Live
Nu-ți face griji că nu înțelegi sintaxa încă — pentru asta e Modulul 1. Important e să simți ciclul: tastezi, browser-ul afișează, instant.
Cum să profiți la maximum de acest curs
Trei reguli care separă oamenii care termină de cei care renunță:
Scrie cod odată cu lecția, mereu. A privi pe altcineva scriind cod e ca a privi pe altcineva făcând exerciții fizice. Deschide playground-ul, tastează tu însuți, sparge, repară. Acolo se întâmplă învățarea de fapt.
Nu sări exercițiile. Sunt scurte intenționat. Fiecare consolidează conceptul din lecția anterioară. Să le sari înseamnă că următoarea lecție se construiește pe nisip.
Construiește proiectele pe bune. La finalul câtorva module vei construi ceva complet. Nu doar urmări — fă schimbări mici, adaugă detalii proprii, pune-ți numele în footer. Așa le vei ține minte.
Verificare rapidă
Care dintre acestea NU e responsabilitatea HTML-ului?
Recapitulare
Frontend development construiește ce văd și cu ce interacționează utilizatorii în browser
Cele trei straturi sunt HTML (structură), CSS (stil), JavaScript (comportament)
Acest curs acoperă HTML și CSS în profunzime — JavaScript primește propriul curs mai târziu
Calea înainte e: scrii cod, spargi lucruri, construiești proiecte. Nu doar citești.
Lecția următoare deschidem capota web-ului — ce se întâmplă de fapt când tastezi un URL și apeși enter. E scurtă, dar va face totul după ea să se așeze la locul lui.
Lecția 1 din 76
Modulul 0 · Lecția 27 min citire
Cum funcționează web-ul
Tastezi un URL, apeși enter, apare o pagină. Pare instantaneu. Pare evident.
În spatele acelei jumătăți de secundă se întâmplă un întreg dans între laptop-ul tău și un calculator undeva în lume. Odată ce vezi coregrafia, vei scrie cod mai bun — pentru că vei ști ce face browser-ul de fapt cu el.
Versiunea într-o propoziție
Când vizitezi un site, browser-ul tău cere de la un calculator numit server niște fișiere, serverul le trimite înapoi, iar browser-ul le asamblează în pagina pe care o vezi.
Atât. Restul e detaliu. Dar detaliile contează, așa că mergem un strat mai adânc.
Călătoria unei cereri
Iată ce se întâmplă când tastezi example.com și apeși enter:
Browser-ul întreabă: „Care calculator e example.com?" — această căutare se numește DNS. E ca o agendă telefonică ce traduce nume în numere (adrese IP).
Browser-ul deschide o conexiune cu acel calculator (serverul) și spune: „Trimite-mi pagina pentru example.com."
Serverul se uită la cerere și decide ce să trimită înapoi — de obicei un fișier HTML, plus instrucțiuni despre ce alte fișiere va mai avea nevoie browser-ul.
Browser-ul citește HTML-ul și observă că pomenește alte fișiere: un stylesheet, câteva imagini, poate un fișier JavaScript. Le cere și pe ele.
Odată ce toate au sosit, browser-ul asamblează totul în pagina pe care o vezi.
Toate astea se întâmplă în câteva sute de milisecunde pe o conexiune decentă.
Exemplu explicat
Ce cere browser-ul de fapt
GET /index.html
Host: example.com
Fiecare cerere e un mic mesaj text. Iar serverul răspunde cu ceva de genul:
200 OK înseamnă „am găsit, uite". Dacă pagina nu există, ai primi 404 Not Found — de acolo vine celebrul cod de eroare.
Cele trei fișiere pe care browser-ul le iubește
Orice pagină web, de la cea mai simplă la Gmail, se reduce la trei tipuri de fișiere pe care browser-ul le înțelege nativ:
.html — documentul în sine. Structură și conținut.
.css — stylesheet-uri. Cum ar trebui să arate documentul.
.js — JavaScript. Cum ar trebui să se comporte documentul.
Tehnic, poți avea o pagină doar cu HTML. Poți adăuga CSS să arate frumos. Poți adăuga JavaScript s-o faci interactivă. Browser-ul gestionează toate trei fără să aibă nevoie de vreun plugin sau software suplimentar.
Orice altceva — React, Vue, Tailwind, TypeScript, build tools — se compilează în final la aceste trei tipuri de fișiere. De aceea învățarea HTML-ului și CSS-ului în profunzime nu e niciodată timp pierdut. Înveți runtime-ul, nu un trend.
O greșeală comună de model mental
Oamenii noi în web development cred adesea că „site-ul" e pe calculatorul lor după ce-l vizitează. Nu chiar. Ce ai e o copie temporară pe care browser-ul a descărcat-o ca să-ți afișeze pagina. Site-ul propriu-zis trăiește pe server. Fiecare vizitator primește propria copie proaspătă livrată la cerere.
De aceea un site poate arăta diferit pentru oameni diferiți (țară diferită, logat vs nelogat, oră diferită din zi) — serverul decide ce să trimită în funcție de cine întreabă.
Unde va trăi codul tău
Când înveți, codul tău trăiește pe calculatorul tău într-un folder. Deschizi fișierul HTML în browser și vezi rezultatul — nu e nevoie de server încă. Browser-ul citește fișierul direct de pe disc.
Când vrei ca alți oameni să-ți vadă site-ul, uploadezi aceleași fișiere pe un server undeva. Asta poate fi:
Un cont de shared hosting (ca cele pe care probabil le cunoști de la WordPress)
Un serviciu de static hosting precum Netlify, Vercel sau GitHub Pages (de obicei gratuit)
Un VPS sau server cloud pe care-l închiriezi la ora
Pe durata acestui curs, vei lucra local pe calculatorul tău. Deployment-ul îl acoperim în modulul final.
HTTP, HTTPS și de ce vezi un lacăt
Protocolul pe care browserele îl folosesc să vorbească cu serverele se numește HTTP — HyperText Transfer Protocol. Când vezi https:// în bara de URL, e HTTP plus un strat extra de criptare. Iconița mică cu lacăt înseamnă că conexiunea e securizată — nimeni între tine și server nu poate citi ce curge înainte și înapoi.
În zilele noastre, orice site serios folosește HTTPS. Browserele avertizează activ utilizatorii când un site nu o face. Pentru site-urile pe care le construiești, HTTPS e de obicei automat — majoritatea serviciilor de hosting îl configurează gratuit.
Verificare rapidă
Când vizitezi un site, unde trăiește efectiv codul HTML al paginii?
Recapitulare
A vizita un site e o conversație: browser-ul cere, serverul răspunde
Fiecare pagină e făcută din HTML (structură), CSS (stil) și JavaScript (comportament)
DNS traduce nume de domenii în adrese IP, ca browserele să știe unde să meargă
HTTPS e HTTP cu criptare — standardul pentru site-urile moderne
Codul tău va trăi în fișiere pe calculatorul tău cât înveți, apoi pe un server când publici
Lecția 2 din 76
Modulul 0 · Lecția 38 min citire
Instalare VS Code & setup
Orice frontend developer profesionist folosește aceeași trusă de bază: un editor de cod, un browser și câteva obiceiuri. Uneltele sunt gratuite. Obiceiurile sunt simple. Zece minute de setup acum îți salvează ore de frustrare mai târziu.
Hai să le instalăm.
De ce ai nevoie
Pentru acest curs, și pentru orice muncă de frontend în 2026, iată trusa minimă:
Un editor de cod — vom folosi VS Code
Un browser modern — Chrome, Firefox sau Edge (nu Internet Explorer, și nu default Safari dacă ești pe Mac — deși Safari merge)
Un folder undeva pe calculatorul tău unde-ți vei pune munca
Atât. Fără instalări de runtime-uri ciudate, fără linie de comandă, fără Node.js încă. Tot ce e în Modulele 1-9 rulează în browser-ul tău, citind fișiere de pe disc.
Pasul 1: Instalează VS Code
VS Code (Visual Studio Code) e un editor de cod gratuit făcut de Microsoft. E ce folosesc majoritatea frontend developerilor — inclusiv eu, în fiecare zi.
Mergi pe code.visualstudio.com și descarcă installer-ul pentru sistemul tău. Instalarea e simplă — apeși next de câteva ori și gata.
Nu confunda cele două
VS Code (ce vrei) e un editor de cod lightweight. Visual Studio (ce nu vrei) e un IDE masiv pentru C#/.NET.
Aceeași companie, produse foarte diferite. Asigură-te că pagina spune „Visual Studio Code".
Pasul 2: Instalează două extensii
VS Code are mii de extensii. De majoritatea nu ai nevoie. De astea două, da:
Live Server
Când construiești o pagină web, ai nevoie să vezi rezultatul. Live Server pornește un mic server web local care-ți afișează pagina în browser și o reîmprospătează automat ori de câte ori salvezi un fișier. Salvezi, browser-ul se actualizează, vezi schimbarea — ăsta e ciclul pe care-l vrei.
Cum se instalează: Apasă iconița de extensii din partea stângă a VS Code (arată ca patru pătrate), caută „Live Server" de Ritwick Dey, apasă Install.
Prettier
Prettier îți formatează codul consistent. Indentare, ghilimele, spațiere — toate aplicate automat când salvezi. Vei înceta să te gândești la formatare și te vei concentra pe ce face codul.
Cum se instalează: Același panou de extensii, caută „Prettier - Code formatter", instalează.
După instalare, activează format-on-save: deschide setările (Cmd/Ctrl + virgulă), caută „format on save", bifează caseta.
Ăsta e tot setup-ul de extensii. N-ai nevoie de Emmet (e built-in), n-ai nevoie de syntax highlighting HTML/CSS (built-in), n-ai nevoie de auto-close tags (built-in).
Pasul 3: Creează folderul de proiect
Undeva pe calculatorul tău — poate Documents/curs-web/ — fă un folder nou. Aici vor trăi toate fișierele din acest curs.
În interiorul acelui folder, creează trei lucruri:
Fiecare lecție primește propriul subfolder cu un fișier index.html. De ce index.html specific? Pentru că serverele web folosesc index.html ca pagina implicită când vizitezi un folder — e o convenție care va continua să conteze toată cariera ta.
Exemplu explicat
Un fișier HTML minimal să-ți testezi setup-ul
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="UTF-8">
<title>Prima mea pagină</title>
</head>
<body>
<h1>Funcționează!</h1>
<p>Oficial construiesc pentru web.</p>
</body>
</html>
Deschide VS Code. Deschide folderul lectia-01 (File → Open Folder). În index.html, lipește codul de mai sus.
Salvează fișierul. Click dreapta în editor → „Open with Live Server". Browser-ul se deschide, vezi un titlu care spune „Funcționează!" și un paragraf dedesubt.
Felicitări. Asta e o pagină web pe care ai construit-o tu.
Câteva obiceiuri VS Code de construit de acum
Shortcut-urile de tastatură salvează ore. Trei de învățat primele:
Cmd/Ctrl + S — salvează (declanșează Prettier + reîmprospătare Live Server)
Cmd/Ctrl + / — comentează/decomentează linia curentă
Cmd/Ctrl + D — selectează următoarea apariție a aceluiași cuvânt (apoi tastează să înlocuiești tot deodată)
Păstrează-ți fișierele organizate. O lecție = un folder. Nu arunca totul într-un mega-folder — vei regreta prin Modulul 4.
Ține terminalul închis dacă nu-ți trebuie. În acest curs nu va fi nevoie, până la final. Dacă vezi tutoriale rulând npm install sau npx — alea sunt lucruri mai avansate, ajungem la ele mai târziu.
Pasul 4: Înțelege DevTools-ul browser-ului
Apasă F12 în orice browser (sau click dreapta → Inspect). Se deschide un panou cu tab-uri: Elements, Console, Network, Sources. Astea sunt parbrizul tău spre ce face browser-ul de fapt cu codul tău.
Nu trebuie să le stăpânești acum. Doar să știi că există. Pe parcursul cursului, o să-ți arăt feature-uri specifice DevTools la momentul potrivit.
Recapitulare
VS Code e editorul tău, Chrome/Firefox/Edge sunt browserele
Instalează doar două extensii: Live Server (pentru reîmprospătare live) și Prettier (pentru formatare)
Fiecare lecție primește propriul folder cu un fișier index.html înăuntru
Familiarizează-te cu DevTools — apasă F12, explorează, nu te stresa să înțelegi tot
Shortcut-urile de tastatură nu-s opționale dacă vrei să lucrezi rapid
Lecția 3 din 76
Modulul 0 · Lecția 45 min citire
Cum să folosești cursul
Majoritatea oamenilor care încep un curs de web development nu-l termină. Nu pentru că e prea greu cursul, ci pentru că-l tratează ca pe o serie Netflix — urmărești, dai din cap, mergi mai departe.
Lecția asta e cea mai simplă din curs. Dar dacă îți iei cinci minute să o citești cu adevărat și să fii de acord, vei fi în minoritatea mică ce efectiv învață să construiască site-uri.
Cum arată fiecare lecție
Din lecția următoare în colo, fiecare lecție din curs urmează aceeași formă:
Hook — de ce contează subiectul, într-un paragraf scurt
Explicație — conceptul, explicat direct
Exemplu — cod pe care-l poți citi, cu rezultatul alături
Playground — cod pe care-l poți edita, rulând live în browser
Greșeli comune — ce greșesc începătorii în acel punct
Exercițiu — o mică provocare cu verificare automată
Recapitulare — trei puncte cu care să rămâi
Lecțiile sunt de 5-15 minute fiecare. Scurte intenționat. Nu trebuie să citești trei lecții la rând — creierul tău nu va ține pasul. Fă una, ia o pauză, revino.
Când folosești playground-ul vs editorul tău
Playground-ul (editorul interactiv integrat în fiecare lecție) e unde experimentezi cu exact conceptul care se predă. Ajustezi valori, spargi lucruri, vezi ce se întâmplă. E low-stakes.
Editorul tău propriu (VS Code cu Live Server, din lecția anterioară) e unde construiești proiecte reale. La finalul majorității modulelor, îți voi cere să construiești ceva de la zero — nu în playground, ci în editorul tău, în folderul tău, salvând pe calculatorul tău.
De ce ambele? Pentru că playground-ul te învață conceptele rapid, dar proiectele reale te învață workflow — salvarea fișierelor, organizarea folderelor, debug când ceva merge prost. Ai nevoie de ambele abilități. Playground-ul e roțile ajutătoare; editorul tău e bicicleta.
Cea mai mare greșeală pe care o văd la începători
Citesc fără să tasteze.
Se simte eficient să parcurgi lecțiile, să dai din cap și să-ți spui „da, am înțeles". N-ai înțeles. Nu cu adevărat. Creierul învață codul la fel cum învață să meargă pe bicicletă — făcând, căzând, ajustând, făcând din nou.
De fiecare dată când vezi un exemplu de cod, retastează-l în playground sau în editorul tău. Nu da copy-paste. Actul de a tasta construiește memoria musculară și te forțează să observi detalii pe care altfel le-ai sări.
Cât va dura asta
Cursul are cam 80 de lecții, cu o medie de 10 minute fiecare. Asta e ~13 ore de citit. Dar a învăța ≠ a citi — dacă faci și exercițiile și proiectele (ceea ce ar trebui), socotește 30-40 de ore totale să parcurgi tot.
Ce înseamnă asta în timp calendaristic? Total la tine:
Intensiv — 2 ore pe zi, 3-4 săptămâni
Seri și weekend-uri — 5-6 ore pe săptămână, ~2 luni
Relaxat — o oră când ai chef, 3-4 luni
Nu e vreun premiu pentru terminat rapid. E premiu mare pentru terminat, orice ritm.
Cele trei obiceiuri care contează
Tot ce ține de învățarea web development se reduce la trei obiceiuri. Dacă le construiești, vei termina cursul. Dacă nu, niciun conținut grozav nu te va duce mai departe.
1. Scrie cod odată cu lecția
Fiecare exemplu pe care-l vezi, tastează-l tu. Fiecare playground, chiar deschide-l și joacă-te. Raportul între „citit" și „tastat" ar trebui să fie în jur de 50/50, nu 90/10.
2. Termină ce începi
Fiecare lecție se încheie cu un exercițiu mic. Fă-l înainte să treci la următoarea. Nu „revin eu la ăsta" — nu revii. Exercițiul e unde lecția se așază de fapt. Să-l sari înseamnă că lecția următoare se construiește pe nisip.
3. Construiește proiectele pe bune
La finalul fiecărui grup de module e un proiect. Deschide VS Code, fă un folder nou, construiește-l efectiv. Nu-l urma în silă — fă schimbări mici, adaugă detalii proprii. Pune-l pe calculator, fii mândru de el. Proiectele alea devin portofoliul tău la finalul cursului.
Când te blochezi
Blocat e normal. Blocat e unde se întâmplă învățarea. Iată ordinea în care să încerci lucruri:
Recitește lecția. Adesea răspunsul e cu trei paragrafe mai sus.
Verifică codul tău caracter cu caracter. O greșeală într-un tag sau un punct-virgulă lipsă cauzează 90% din problemele începătorilor. Browser-ul e foarte pretențios.
Caută eroarea pe Google. Fiecare eroare pe care o vei întâlni a fost întâlnită de o mie de oameni înaintea ta. Probabil e un răspuns la primul rezultat.
Întreabă. Răspund la mesaje directe, de obicei în 24 de ore. E și un canal de comunitate (link în dashboard-ul tău).
Ce nu ar trebui să faci: să sari peste problemă și să speri că lecția următoare o rezolvă. Nu o rezolvă.
Recapitulare
Fiecare lecție are aceeași formă: hook, explicație, exemplu, playground, exercițiu, recapitulare
Folosește playground-ul pentru concepte, editorul tău pentru proiecte reale
Socotește 30-40 de ore totale — împarte-le pe câte săptămâni ți se potrivesc
Trei obiceiuri contează: scrii cod odată cu lecția, termini exercițiile, construiești proiectele pe bune
Când te blochezi, recitește, verifică codul, Google, întrebi — în ordinea asta
Lecția 4 din 76
Modulul 1 · Lecția 110 min citire
Primul document HTML
Fiecare pagină web de pe internet — de la un blog personal la Amazon — începe cu același schelet. Odată ce-l știi, poți să te uiți la sursa oricărei pagini și să înțelegi ce se întâmplă. Poți și să scrii una de la zero în 30 de secunde.
Șase linii de schelet, o linie de conținut propriu-zis. Hai să parcurgem fiecare bucată.
Declarația <!DOCTYPE html>
Prima linie a fiecărui fișier HTML. Nu e chiar un tag — e o instrucțiune pentru browser: „tratează asta ca HTML modern."
O scrii exact așa cum e afișat. Nu e nimic de personalizat, nimic de reținut, doar că e prima linie. Mereu.
De ce e acolo
HTML-ul vechi avea multe versiuni cu particularități diferite. DOCTYPE-ul specifica versiunea — și dacă-l uitai, browserele intrau în „quirks mode" și-ți randa pagina ciudat să se potrivească cu bug-uri de la 1999.
Astăzi există un singur HTML (numit adesea HTML5) iar <!DOCTYPE html> e modul universal de a spune „modul standard, te rog". Doar scrie-l.
Elementul <html>
Tot restul trăiește înăuntrul <html>. E containerul cel mai exterior al documentului.
Atributul lang spune browser-ului (și cititoarelor de ecran, și motoarelor de căutare, și uneltelor de traducere) în ce limbă e pagina. Folosește "ro" pentru română, "en" pentru engleză, "fr" pentru franceză, etc. E un cod de două litere bazat pe standardul ISO.
Nu e cosmetic — setând lang corect ajută uneltele de accesibilitate să pronunțe cuvintele corect și ajută browserele să ofere traducere când e nevoie. Setează-l mereu.
Elementul <head>
<head> conține metadate — informații despre pagină care nu apar în pagina propriu-zisă. Gândește-te la el ca la panoul de setări al paginii.
Lucruri comune care trăiesc în <head>:
<meta charset="UTF-8"> — spune browser-ului ce codare de caractere să folosească. UTF-8 suportă orice limbă și emoji, deci folosește-l mereu.
<title> — textul care apare în tab-ul browser-ului și în rezultatele motoarelor de căutare. Fiecare pagină ar trebui să aibă unul.
<meta name="description"> — o descriere scurtă a paginii. Motoarele de căutare o afișează ca snippet sub titlul paginii tale.
<link rel="stylesheet" href="..."> — fișiere CSS externe (le vom folosi mult începând cu Modulul 2).
<script src="..."> — fișiere JavaScript.
Nimic din astea nu se arată pe pagină. E configurare.
Elementul <body>
<body> conține tot ce utilizatorul vede efectiv. Text, imagini, butoane, formulare, videoclipuri — toate merg în <body>.
În exemplul nostru minimal, body-ul are doar un titlu. Majoritatea paginilor reale au sute de elemente în <body>, organizate în secțiuni.
Exemplu explicat
O pagină puțin mai realistă
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Despre mine — portofoliul lui Alex</title>
<meta name="description" content="Frontend developer din București.">
</head>
<body>
<h1>Salut, sunt Alex</h1>
<p>Construiesc site-uri pentru afaceri mici.</p>
</body>
</html>
<meta name="viewport"> e crucial pentru mobil. Fără el, telefoanele randează pagina ca și cum ar fi un monitor desktop, zoom-at out. Fiecare pagină modernă include această linie. Scrie-o exact așa.
<title>-ul e descriptiv. „Despre mine — portofoliul lui Alex" e mai bun decât doar „Despre" pentru că apare în rezultatele de căutare și în tab-urile browser-ului unde contextul ajută utilizatorul să găsească ce caută.
Regulile tag-urilor HTML
Trei reguli care se aplică fiecărui tag pe care-l vei scrie vreodată:
1. Majoritatea tag-urilor vin în perechi. Un tag de deschidere ca <p> și unul de închidere ca </p>. Conținutul stă între ele. Tag-ul de închidere are un slash.
2. Unele tag-uri sunt self-closing. Nu au conținut și nu necesită tag de închidere. Exemple: <img>, <br>, <input>, <meta>.
3. Tag-urile se imbrică. Poți pune tag-uri în alte tag-uri. Tag-ul interior trebuie închis înainte ca cel exterior să se închidă.
Construiește prima ta pagină Live
Atribute: configurarea tag-urilor
Tag-urile au adesea atribute — informații suplimentare scrise în tag-ul de deschidere:
<html lang="ro">
<a href="https://example.com">Vezi example</a>
<img src="poza.jpg" alt="O poză cu un câine">
Tiparul e nume="valoare". Valorile merg în ghilimele duble. Mai multe atribute sunt separate prin spații.
Fiecare tag are propriul set de atribute care au sens pentru el. Le vom întâlni pe parcurs. Deocamdată, doar să știi că atunci când vezi nume="valoare" într-un tag, acela e un atribut care configurează comportamentul tag-ului.
Verificare rapidă
Unde trebuie să pui tag-ul <title> într-un document HTML?
Recapitulare
Fiecare document HTML începe cu <!DOCTYPE html> și împachetează totul în <html lang="...">
<body> ține conținutul vizibil — tot ce vede utilizatorul și cu ce interacționează
Tag-urile vin în perechi (cu </tag> de închidere) sau sunt self-closing (<img>, <meta>)
Atributele merg în tag-urile de deschidere ca nume="valoare"
Lecția 5 din 76
Modulul 1 · Lecția 29 min citire
Elemente de text
O mare parte din web e doar text. Articole de blog, descrieri de produse, pagini „despre", documentație. Înainte să ajungem la layout-uri fanteziste și interacțiuni, hai să stăpânim tag-urile care tratează cuvintele cum trebuie.
Titluri: <h1> până la <h6>
HTML are șase niveluri de titluri, de la <h1> (cel mai important) la <h6> (cel mai puțin important). Browser-ul afișează nivelurile superioare cu text mai mare implicit, dar adevărata treabă a titlurilor e semantică — îi spun browser-ului, motoarelor de căutare și cititoarelor de ecran care e structura conținutului tău.
<h1>Titlul principal al paginii</h1>
<h2>O secțiune majoră</h2>
<h3>O sub-secțiune din acea secțiune</h3>
<h4>O subdiviziune și mai mică</h4>
<h5>Rar folosit</h5>
<h6>Aproape niciodată folosit</h6>
Gândește-te la titluri ca la un cuprins. <h1> e titlul întregii pagini. <h2>-urile sunt capitolele. <h3>-urile sunt secțiunile din capitole. Și așa mai departe.
Regula unui singur h1
Folosește exact un <h1> per pagină. E titlul principal al paginii.
Alte reguli care contează:
Nu sări peste niveluri. Nu sări de la <h2> la <h4> — confuză cititoarele de ecran și uneltele SEO.
Nu folosi titluri pentru stilizare. Dacă vrei text mare și bold, folosește CSS. Titlurile sunt pentru structură, nu decorație.
Potrivește nivelul titlului cu importanța, nu cu cât de mare vrei să arate textul.
Paragrafe: <p>
Tag-ul de paragraf e exact ce pare: un bloc de text curgător.
<p>Acesta e un paragraf. Poate conține mai multe propoziții. Browser-ul
încadrează liniile automat pe baza lățimii disponibile — nu încerca
să controlezi asta cu line break-uri.</p>
<p>Acesta e un alt paragraf. Paragrafele primesc ceva spațiu vertical
deasupra și dedesubt implicit.</p>
Câteva lucruri de știut:
Line break-urile din sursa ta nu contează. Browser-ul colapsează mai multe spații și newline-uri într-un singur spațiu.
Paragrafele ar trebui să conțină text curgător, nu layout. Nu pune titluri sau alte paragrafe într-un <p>.
Scurt sau lung e ok. Un paragraf de o propoziție e valid.
Formatare inline: <strong>, <em>, <code>, <mark>
În paragrafe, adesea vrei să accentuezi sau să formatezi câteva cuvinte.
<strong> marchează textul ca important. Browserele îl afișează bold implicit. Folosește-l pentru lucruri care contează — avertismente, termeni cheie, accentuare reală.
<em> marchează textul cu accent de tonalitate. Browserele îl afișează italic. Folosește-l cum ai ridica vocea pe un cuvânt când vorbești.
<code> marchează textul ca bucată de cod. Browserele îl afișează cu font monospace. Folosește-l pentru nume de fișiere, nume de funcții, variabile, fragmente scurte.
<mark> evidențiază textul. Browserele îl afișează cu fundal galben.
strong vs b, em vs i
S-ar putea să vezi HTML vechi folosind <b> și <i> în loc de <strong> și <em>. Arată identic în browser. Dar înseamnă lucruri diferite:
<strong> / <em> poartă sens — cititoarele de ecran le detectează și accentuează cuvintele când citesc cu voce tare.
<b> / <i> sunt pur vizuale, fără sens semantic.
Folosește <strong> și <em> implicit. Folosește <b> sau <i> doar când vrei efectul vizual fără sensul semantic (rar — de exemplu, să italicizezi titlul unei cărți unde nu e implicată nicio accentuare reală).
Exemplu explicat
Formatare inline în acțiune
<p>Ca să instalezi pachetul, rulează <code>npm install react</code>
în terminal. <strong>Nu uita</strong> să incluzi flag-ul
<code>--save</code> în proiectele mai vechi. Asta instalează
<em>cea mai recentă versiune stabilă</em>, care la momentul
scrierii e <mark>18.2.0</mark>.</p>
Randat, asta devine un paragraf unde fragmentele de cod ies în evidență cu monospace, avertismentul e bold, „cea mai recentă versiune stabilă" e italic, iar numărul versiunii e evidențiat.
Line break-uri și linii orizontale
<br> forțează un line break. E self-closing. Folosește-l rar — de cele mai multe ori, paragrafele gestionează break-urile automat.
<p>Strada Principală 123<br>
București, 010101<br>
România</p>
Adrese, versuri de poezie, versuri de cântec — astea sunt utilizările legitime. Nu folosi <br> să creezi spațiu între paragrafe; folosește două tag-uri <p> separate.
<hr> (linie orizontală) creează o pauză tematică între secțiuni. Browserele îl randează ca o linie orizontală. Folosește-l când conținutul schimbă subiectul în aceeași pagină.
Citate: <blockquote> și <q>
<blockquote> e pentru pasaje citate — când reproduci ceva dintr-o altă sursă. Browserele îl indentează de obicei.
<blockquote cite="https://example.com/discurs">
<p>Singura cale să faci muncă grozavă e să iubești ce faci.</p>
<p>— Steve Jobs</p>
</blockquote>
<q> e pentru citate inline într-o propoziție. Browserele adaugă automat ghilimele în jurul lui.
O mică verificare semantică
De fiecare dată când întinzi mâna spre un tag, întreabă: tag-ul descrie ce e conținutul, sau doar cum arată? HTML-ului îi pasă de „ce". Dacă vrei doar ca ceva să arate într-un anumit fel, aia e treaba CSS-ului (vine în Modulul 2).
Ăsta e cel mai important obicei mental din HTML. Fă-l corect și paginile tale vor fi mai accesibile, se vor clasa mai bine în căutare și vor fi mai ușor de stilizat.
Alege tag-ul potrivit
Vrei să avertizezi utilizatorii despre o problemă critică de securitate cu text bold. Care tag e cel mai potrivit?
Recapitulare
Folosește un <h1> per pagină; folosește <h2> până la <h6> pentru ierarhie logică, niciodată sărind niveluri
<p> e pentru paragrafe; browser-ul gestionează încadrarea liniilor
Preferă <strong> față de <b> și <em> față de <i> — poartă sens
<code> pentru termeni tehnici și fragmente, <mark> pentru evidențieri
Folosește <blockquote> pentru citate bloc, <q> pentru citate inline, <br> rar
Lecția 6 din 76
Modulul 1 · Lecția 39 min citire
Liste și tabele
Bullet points. Pași numerotați. Date structurate în rânduri și coloane. Aceste trei lucruri apar practic pe orice site. HTML are tag-uri dedicate pentru fiecare, iar alegerea celui potrivit contează mai mult decât realizează începătorii.
Liste neordonate: <ul> și <li>
Când ordinea elementelor nu contează, folosește o listă neordonată. Browserele o randează cu bullet points.
Lista imbricată trebuie să intre într-un <li>, nu între elemente <li>. E o greșeală comună.
Tabele: <table>, <tr>, <th>, <td>
Tabelele sunt pentru date tabulare — informații care au rânduri și coloane reale, ca un spreadsheet. Gândește-te la cifre de vânzări, programe, specificații de produs.
Cele patru tag-uri de care ai nevoie:
<table> — containerul
<tr> — un rând de tabel
<th> — o celulă de antet (de obicei primul rând și/sau prima coloană)
Pentru tabele reale, împachetează rândurile de antet în <thead> și rândurile de body în <tbody>. Face structura mai clară atât pentru oameni cât și pentru tehnologii asistive.
Niciodată nu folosi tabele pentru layout
Există o eră infamă a web design-ului (aproximativ 1997-2007) în care oamenii foloseau <table> pentru a poziționa totul pe o pagină — navigare, sidebar-uri, layout-uri întregi.
Nu face asta. Folosește tabele doar pentru date tabulare reale — date care au natural rânduri și coloane și unde relația între celule contează.
Pentru layout (aranjarea vizuală pe pagină), vom folosi Flexbox și Grid în Modulele 4 și 5. Astea sunt uneltele potrivite pentru job.
Alege corect
Faci o listă cu top 5 cele mai mari filme din 2025. Ce tip de listă se potrivește cel mai bine?
Recapitulare
<ul> pentru elemente neordonate, <ol> pentru ordonate — bazat pe dacă ordinea poartă sens
Imbrică liste punând lista-copil în <li>, nu între elemente
Tabelele sunt doar pentru date tabulare, niciodată pentru layout de pagină
Folosește <thead> și <tbody> pentru claritate
Lecția 7 din 76
Modulul 1 · Lecția 49 min citire
Link-uri și navigare
„H"-ul din HTML vine de la Hypertext — text cu link-uri. Link-urile sunt ce au făcut web-ul un sistem interconectat, nu o grămadă de documente deconectate. Ai dat click pe milioane dintre ele. Acum le vei scrie.
Ancora de bază: <a>
Link-urile folosesc tag-ul <a> (ancoră). Destinația merge în atributul href.
<a href="https://example.com">Vezi Example</a>
Orice e între tag-ul de deschidere și cel de închidere devine clickabil — text, imagini, chiar și secțiuni întregi de pagină.
Link-uri interne vs externe
Există două moduri comune de a scrie href-ul:
Link-uri externe merg la alt site. Folosește URL-ul complet cu protocol (https://).
<a href="https://google.com">Google</a>
<a href="https://github.com/vercel/next.js">Next.js pe GitHub</a>
Link-uri interne merg la altă pagină de pe același site. Folosește o cale relativă la pagina curentă.
Slash-ul / la început înseamnă „de la rădăcina acestui site". Fără el, browser-ul caută relativ la folder-ul paginii curente.
Capcana cu calea
Trei greșeli comune cu căile:
Lipsa protocolului la link-uri externe.href="example.com" NU merge la example.com — browser-ul îl interpretează ca link intern relativ. Include mereu https://.
Uitarea slash-ului inițial.href="despre.html" vs href="/despre.html" — al doilea e neambiguu (întotdeauna de la rădăcină). Primul depinde unde ești în site.
Link-uri de ancoră: sari în interiorul paginii
Poți face link la o secțiune specifică folosind hash (#) urmat de un id pe elementul țintă.
Când dai click, browser-ul derulează la elementul cu acel ID. Asta face să funcționeze link-urile „Înapoi sus", navigarea tip cuprins și site-urile lungi de o singură pagină.
Atributul target
Implicit, clicul pe un link înlocuiește pagina curentă. Ca să deschizi link-ul într-un tab nou, folosește target="_blank".
<a href="https://example.com" target="_blank">Deschide în tab nou</a>
Când să folosești target="_blank":
Link-uri la site-uri externe (utilizatorii vor adesea să țină site-ul tău deschis)
Link-uri la PDF-uri sau fișiere descărcabile
Link-uri deschise într-un flux de completare formular pe care nu vrei să-l întrerupi
Atributul rel cu target="_blank"
Când folosești target="_blank", adaugă și rel="noopener noreferrer".
<a href="/preturi">Click aici</a>
<a href="/ghid/flexbox">Citește mai mult</a>
Cititoarele de ecran anunță adesea link-urile separat de textul din jur. Un utilizator nevăzător navigând doar prin link-uri aude „click aici, click aici, citește mai mult" fără context. Text descriptiv rezolvă asta.
Repară link-ul
Care din astea e modul corect de a face link la un site extern într-un tab nou?
Recapitulare
<a href="..."> creează un link; conținutul dintre tag-uri devine clickabil
Folosește URL-uri complete cu https:// pentru link-uri externe, căi pentru interne
Link-urile de ancoră (#id-sectiune) derulează la elemente cu id corespunzător
Adaugă target="_blank" pentru taburi noi, mereu cu rel="noopener noreferrer"
Folosește mailto: pentru email-uri, tel: pentru telefoane
Textul link-ului ar trebui să descrie destinația, nu să spună „click aici"
Lecția 8 din 76
Modulul 1 · Lecția 510 min citire
Imagini și media
O pagină web fără imagini se simte neterminată. Din fericire, adăugarea imaginilor e unul dintre cele mai simple lucruri din HTML — și unul dintre cele mai ușoare locuri unde greșești. O pagină de 100MB pentru că nimeni n-a comprimat imaginea hero e un rit de trecere pe care nu-l doriți.
Hai să facem imaginile bine de la început.
Tag-ul <img>
Adăugarea unei imagini ia un singur tag self-closing:
<img src="poza.jpg" alt="Un apus peste ocean">
Două atribute contează de fiecare dată:
src — sursa imaginii. Poate fi o cale relativă la un fișier din proiectul tău, sau un URL complet la o imagine externă.
alt — text alternativ. O descriere scurtă a imaginii pentru cititoarele de ecran și pentru când imaginea nu se încarcă. Scrie mereu text alt cu sens; să-l sari e rău pentru accesibilitate.
Scrierea unui text alt bun
Textul alt ar trebui să descrie ce transmite imaginea, nu că e o imagine.
<!-- Rău: -->
<img src="ceo.jpg" alt="Imagine cu CEO-ul nostru">
<!-- Bun: -->
<img src="ceo.jpg" alt="Maria Popescu, CEO, zâmbind la biroul ei">
<!-- Și bun (imagine pur decorativă): -->
<img src="separator.svg" alt="">
Când o imagine e pur decorativă (un separator liniar, o iconiță lângă un text), folosește alt="" — un șir gol. Asta spune cititoarelor de ecran să o sară. Nu omite atributul complet — asta face cititoarele de ecran să încerce să citească numele fișierului.
Formate de imagine: care când
Un ghid rapid:
JPEG (.jpg) — fotografii. Se comprimă bine, arată grozav pentru tonuri continue. Nu folosi pentru logouri sau text.
PNG (.png) — imagini cu transparență, logo-uri, capturi de ecran. Fișiere mai mari decât JPEG pentru poze, dar margini clare.
WebP (.webp) — format modern, mai mic decât JPEG/PNG la calitate egală. Suportat în orice browser modern. Folosește-l implicit pentru fotografii.
AVIF (.avif) — chiar mai mic decât WebP. Mai nou, dar suportat destul de larg.
SVG (.svg) — grafică vectorială (logouri, iconițe, diagrame). Scalează la orice dimensiune fără pierdere. Fișiere minuscule pentru forme geometrice.
Regula pentru 2026: folosește WebP pentru fotografii, SVG pentru logouri și iconițe, PNG doar dacă ai specific nevoie de raster cu transparență.
Atributele width și height
Setează mereu width și height pe imaginile tale — în pixeli bruti, potrivind dimensiunile naturale ale fișierului.
De ce? Pentru că browser-ul folosește aceste dimensiuni să rezerve spațiu pentru imagine înainte să se încarce. Fără ele, conținutul paginii sare în timp ce imaginile sosesc, creând o experiență deranjantă numită layout shift.
Lazy loading
Pentru imaginile de sub fold (nu vizibile la încărcarea paginii), folosește loading="lazy". Browser-ul așteaptă să le descarce până ce utilizatorul derulează aproape.
Nu folosi loading="lazy" pe imaginile de deasupra fold-ului (imaginea hero, logo-ul) — acelea ar trebui să se încarce imediat pentru cea mai bună primă impresie.
Imagini responsive cu <picture>
Ecrane diferite merită imagini diferite — ecranele mici n-au nevoie de imaginea hero 4K făcută pentru desktop.
Elementul <picture> îți lasă să oferi surse multiple. Browser-ul alege cea mai bună în funcție de dimensiunea viewport-ului, densitatea pixelilor, sau formatele suportate.
<img>-ul din <picture> e fallback-ul — include-l mereu. Browserele care nu înțeleg <picture> sau nu se potrivesc cu nicio <source> cad înapoi la <img>-ul simplu.
Browser-ul încearcă AVIF primul, cade înapoi la WebP, cade înapoi la JPEG. Fiecare primește cel mai mic fișier pe care browser-ul lui îl suportă. Imaginea e lazy-loaded, cu dimensiuni corecte, cu text alt descriptiv. Ia mai multe linii decât <img src="hero.jpg">, dar e abordarea profesională.
Video: <video>
Tag-ul video HTML gestionează redarea video nativ:
controls arată controalele implicite (play/pause, volum)
autoplay pornește automat. Folosește cu grijă.
muted pornește fără sunet.
loop reia la final.
poster="img.jpg" arată o imagine specifică înainte să pornească.
Audio: <audio>
Tipar similar pentru audio:
<audio src="podcast.mp3" controls>
Browser-ul tău nu suportă redarea audio.
</audio>
Figuri cu subtitluri: <figure> și <figcaption>
Când o imagine are un subtitlu, împachetează-le împreună în <figure>:
<figure>
<img src="grafic-2024.png" alt="Grafic creștere venituri 40% YoY"
width="800" height="500">
<figcaption>Fig. 1: Creșterea veniturilor în 2024</figcaption>
</figure>
Găsește problema
Care dintre aceste tag-uri imagine are o problemă ce ar putea cauza o experiență proastă?
Recapitulare
Folosește <img src="..." alt="..."> — include mereu alt (șir gol pentru decorative)
WebP pentru fotografii, SVG pentru logo-uri și iconițe
Setează mereu width și height pentru a preveni layout shift
Folosește loading="lazy" pentru imagini sub fold, niciodată pe hero
Folosește <picture> pentru imagini responsive și fallback-uri de format
Lecția 9 din 76
Modulul 1 · Lecția 611 min citire
HTML semantic
Ai putea construi un site întreg folosind doar tag-uri <div>. Ar arăta identic cu unul construit cu HTML semantic corect. Dar o pagină semantică e dramatic mai bună pentru motoarele de căutare, cititoarele de ecran și orice om care-ți citește codul mai târziu.
Lecția asta e unde developerii buni se separă de cei okay. Fă asta corect și tot ce urmează devine mai ușor.
Ce înseamnă „semantic"
Un tag e semantic când numele lui descrie sensul conținutului, nu aspectul. Compară:
Browser-ul le randează identic. Dar a doua versiune spune ce e fiecare secțiune — nu doar „o cutie". Acea informație e de neprețuit pentru cititoare de ecran, motoare de căutare și pentru oricine menține codul tău.
Elementele semantice mari
<header>
Secțiunea introductivă de sus a unei pagini, sau de sus a unei secțiuni. Conține de obicei logo-ul site-ului, navigarea principală, poate un slogan.
O secțiune conținând link-uri de navigare. Folosește-l pentru navigarea principală a site-ului, sidebar, cuprins, paginare — oriunde ai un grup de link-uri care ghidează utilizatorii.
<main>
Conținutul principal al paginii. Un singur <main> per pagină — marchează conținutul unic, față de lucruri partajate ca header și footer.
Cititoarele de ecran au un shortcut să sară direct la <main>, sărind peste navigare. E un ajutor mare pentru utilizatorii care navighează din tastatură.
<article>
O bucată de conținut self-contained care ar putea sta singură — un articol de blog, o știre, un listing de produs, un comentariu.
<article>
<h2>Cum centrezi un div în 2026</h2>
<p>Ani de zile, centrarea lucrurilor în CSS a fost un meme...</p>
</article>
Test: dacă ai putea sindica această bucată pe alt site (un feed RSS, un newsletter), probabil merită <article>.
<section>
O secțiune generică de conținut — o grupare tematică. De obicei are propriul titlu.
Diferența față de <article>: <section> e o parte dintr-un întreg mai mare; <article> e standalone.
<aside>
Conținut tangențial legat de conținutul principal. Sidebar-uri, link-uri conexe, cutii de callout, biografii de autor lângă articole.
<footer>
Secțiunea finală de jos a unei pagini sau secțiuni. Ține de obicei copyright, info contact, navigare secundară, link-uri sociale.
Observă că <article>-ul are propriul <header> și <footer> — sunt limitate la articol, nu la pagina întreagă. E ok și încurajat.
Când să folosești totuși <div> și <span>
Tag-urile semantice acoperă majoritatea nevoilor structurale. Dar <div> și <span> nu-s deprecate — sunt încă corecte pentru grupare pur vizuală care nu poartă sens semantic.
<div> — un container block-level generic. Folosește când ai nevoie să grupezi elemente pentru scopuri de stilizare CSS și niciun tag semantic nu se potrivește.
<span> — un container inline generic. Folosește când ai nevoie să stilizezi o bucată de text într-o linie.
div-itis
Un anti-pattern comun: împachetarea totului în <div> cu nume de clase precum class="header", class="main-content", class="sidebar". Numele claselor descriu sensul, dar tag-ul nu.
Dacă te prinzi dându-i unui <div> o clasă care se potrivește cu numele unui tag semantic (header, nav, footer), e un semnal puternic că ar trebui să folosești tag-ul semantic.
De ce contează semantica
Trei motive concrete:
1. SEO. Motoarele de căutare folosesc tag-urile semantice să înțeleagă despre ce e pagina ta. O pagină cu <article>, <header> și <main> corecte dă Google-ului o hartă mai clară a conținutului.
2. Accesibilitate. Utilizatorii de cititoare de ecran navighează prin landmark-uri — „sari la main", „listează toate titlurile", „listează toate navigările". Tag-urile semantice oferă acele landmark-uri automat.
3. Mentenabilitate. Peste șase luni, când tu (sau altcineva) deschizi codul ăsta, <article> e auto-explicativ. <div class="post-wrapper-inner"> nu.
Alege tag-ul potrivit
Construiești o pagină cu listing de produse. Fiecare produs are titlu, imagine, descriere și preț. Care tag reprezintă cel mai bine un produs individual?
Recapitulare
Tag-urile semantice descriu sensul, nu aspectul
Folosește <header>, <nav>, <main> (o dată per pagină), <article>, <section>, <aside>, <footer>
<div> și <span> sunt ok pentru grupare pur vizuală
Înlocuiește <div class="header"> cu <header>
Lecția 10 din 76
Modulul 1 · Lecția 711 min citire
Formulare
Formularele sunt cum utilizatorii îți răspund la site — înregistrare, login, trimitere mesaje, plasare comenzi. Sunt și unul dintre cele mai comune locuri unde paginile îi eșuează pe utilizatori: câmpuri confuze, label-uri lipsă, validare stricată.
HTML-ul modern are toate uneltele să construiești formulare care funcționează bine și se simt native.
Tag-ul <form>
Fiecare formular e împachetat într-un element <form>:
<form action="/submit" method="POST">
<!-- câmpurile merg aici -->
</form>
action — URL-ul care primește datele trimise (gestionat de un server).
method — de obicei POST (pentru trimitere date) sau GET (pentru trimitere interogări ca parametri URL, ca formularele de căutare).
Label-uri și inputuri
Fiecare input are nevoie de un label. Mereu. Fără excepție.
Atributul for de pe <label> se potrivește cu id-ul de pe <input>. Asta le leagă, ceea ce înseamnă:
Click pe label focusează input-ul
Cititoarele de ecran anunță label-ul când utilizatorul focusează input-ul
Label-ul devine o țintă mai mare de click, îmbunătățind UX-ul mobil
Placeholder nu e un label
Nu folosi placeholder ca înlocuitor pentru un label.
Placeholder-urile dispar când utilizatorul începe să tasteze, ceea ce face greu să le referezi în timp ce completezi formularul. Au și contrast slab implicit (rău pentru accesibilitate). Folosește placeholder pentru un indiciu sau exemplu, nu pentru numele câmpului.
Numeroasele tipuri de input
Atributul type spune browser-ului ce tip de date vrei, care activează tastaturi specifice pe mobil, validare și widget-uri.
<input type="text"> <!-- Text simplu -->
<input type="email"> <!-- Email, validează format -->
<input type="password"> <!-- Parolă, caractere ascunse -->
<input type="tel"> <!-- Telefon, arată tastatură numerică -->
<input type="url"> <!-- URL, validează format -->
<input type="number"> <!-- Doar numere, cu săgeți sus/jos -->
<input type="date"> <!-- Selector de dată -->
<input type="color"> <!-- Selector de culoare -->
<input type="file"> <!-- Buton upload fișier -->
<input type="checkbox"> <!-- Toggle da/nu -->
<input type="radio"> <!-- Alegere exclusivă între opțiuni -->
<input type="range"> <!-- Slider -->
Folosește tipul potrivit de fiecare dată.type="email" pe mobil arată o tastatură cu tasta @; type="tel" arată un pad numeric. Ăsta e un câștig gratuit de usability.
Browser-ul blochează trimiterea până ce câmpurile obligatorii sunt completate.
Alte atribute de validare încorporate:
<!-- Lungime minimă și maximă pentru text: -->
<input type="text" minlength="3" maxlength="50">
<!-- Valori minime și maxime pentru numere: -->
<input type="number" min="18" max="120">
Checkbox-uri și radio-buttons
Checkbox-urile sunt pentru alegeri da/nu sau multiple:
<fieldset>
<legend>Interese</legend>
<label>
<input type="checkbox" name="interese" value="web">
Web development
</label>
<label>
<input type="checkbox" name="interese" value="mobile">
Mobile development
</label>
</fieldset>
Radio-buttons sunt pentru exact-una-dintre-câteva alegeri. Atributul name partajat le grupează:
Fiecare input are un label. Câmpurile obligatorii sunt marcate. Câmpul de email validează formatul email. Mesajul are lungime min și max. Totul funcționează cu tastatură și cititoare de ecran. Așa arată un formular corect.
Ce tip e potrivit?
Construiești un formular unde utilizatorii introduc vârsta (doar 18+). Care setup de input e corect?
Recapitulare
Fiecare formular e împachetat în <form action="..." method="...">
Fiecare input are nevoie de un <label>, asociat prin for/id sau prin împachetare
Alege tipul potrivit — activează tastaturi mobile și validare browser
Adaugă required, minlength/maxlength, min/max, pattern pentru validare nativă
Grupează inputuri legate cu <fieldset> și <legend>
Trimite cu <button type="submit">
Lecția 11 din 76
Modulul 1 · Lecția 810 min citire
Atribute globale & proiect
Majoritatea atributelor HTML funcționează doar pe tag-uri specifice — href pe link-uri, src pe imagini, type pe inputuri. Dar o mână de atribute funcționează pe orice tag, iar astea sunt cele pe care le vei folosi zilnic, în special pe măsură ce înveți CSS și JavaScript.
Hai să le învățăm, apoi să punem totul din modulul ăsta împreună într-un proiect real.
Atribute globale pe care le vei folosi zilnic
id — un identificator unic
id dă unui element un nume unic. Fiecare id trebuie să fie unic per pagină — două elemente nu pot împărți același id.
Al treilea element are două clase: card și featured. Mai multe clase sunt separate prin spații.
Ăsta e atributul pe care-l vei folosi cel mai mult în tot web development-ul.
id vs class, în practică
Regula din manual e „id e unic, class e reutilizabil". Adevărat tehnic, dar regula practică e:
Folosește clase pentru stilizare. Folosește id pentru identificare.
Chiar și un element unic pe o pagină (logo-ul principal) ar trebui stilizat printr-o clasă — nu știi niciodată când vei vrea același stil în altă parte. Rezervă id pentru când ai nevoie să legi sau să găsești programatic acel element specific.
data-* — date personalizate
Ai nevoie să stochezi informații pe un element pentru ca JavaScript să le citească mai târziu? Folosește un atribut data-*. Orice e după data- poate fi ce vrei.
Deschide asta în VS Code. Rulează-l cu Live Server. Personalizează totul.
Cum va arăta pagina ta
Fără niciun CSS, pagina ta va arăta ca ceva din 1995 — text negru Times New Roman, link-uri albastre subliniate, spațiere implicită peste tot. Asta e exact corect la acest nivel.
Scopul nu e s-o faci frumoasă încă. Scopul e s-o faci corectă: semantică, accesibilă, structurată. În Modulul 2 începem să adăugăm CSS și vedem acea pagină urâtă transformată în ceva frumos.
Rezistă impulsului să adaugi CSS
Știi puțin CSS. Ai putea strecura un bloc <style> și s-o faci frumoasă. Nu o face.
Obiceiul de a construi întâi structura, apoi să stilizezi e unul dintre cei mai mari separatori între începători și profesioniști. Începătorii hack-uiesc HTML și CSS simultan și ajung cu cod greu de menținut. Profesioniștii fac HTML-ul corect întâi, apoi adaugă stilurile deasupra.
Recapitulare
id e unic per pagină — folosește pentru link-uri de ancoră, ținte label, și JavaScript
class e reutilizabil — folosește pentru stilizare (îl vei folosi cel mai mult)
title adaugă tooltip-uri (limitate pe mobil)
data-* stochează date personalizate citibile de JavaScript
Proiect Modulul 1: construiește pagina Despre Mine folosind tot ce ai învățat — fără CSS încă
🎉 Ai terminat Modulul 1!
Paginile tale au structură reală acum. Modulul 2 (CSS) adaugă stil și transformă totul în ceva frumos.
Lecția 12 din 76
Modulul 2 · Lecția 18 min citire
Cum adaugi CSS la HTML
Ai construit pagini cu HTML semantic. Sunt structurate, accesibile și mai urâte decât un site geocities din 2002. Urmează să schimbăm asta.
CSS — Cascading Style Sheets — e cum transformăm acele pagini brute în ceva care arată proiectat. În acest modul înveți fundamentele. Hai să începem cu cea mai simplă întrebare posibilă: unde pui CSS-ul?
Trei moduri de a adăuga CSS
Poți adăuga stiluri la o pagină HTML în trei moduri. Doar unul e calea corectă pentru proiecte reale, dar ar trebui să le recunoști pe toate trei.
1. Stiluri inline (cel mai rău pentru muncă reală)
Pui stilul direct pe element folosind atributul style:
Pro: totul într-un singur fișier, bun pentru experimente rapide.
Contra: stilurile nu pot fi partajate între pagini. Dacă ai 10 pagini care ar trebui să arate la fel, ar trebui să lipești același bloc <style> în toate 10.
3. Stylesheet extern (calea corectă)
Pui CSS într-un fișier .css separat și îl linkuiești din HTML:
Pro: un singur stylesheet stilizează fiecare pagină din site-ul tău; browser-ul cache-uiește fișierul deci se încarcă o dată; separarea preocupărilor (HTML e structură, CSS e stil); mai ușor de întreținut.
Folosește stylesheet-uri externe 99% din timp. Intern pentru prototipuri rapide. Inline aproape niciodată.
Tag-ul link nu are închidere
Observă <link rel="stylesheet" href="styles.css"> — nu există </link>. Tag-ul <link> e self-closing, ca <img> și <meta>.
De asemenea, atributul rel="stylesheet" e obligatoriu — spune browser-ului ce tip de fișier e. Uită-l și stilurile tale nu se încarcă.
Anatomia unei reguli CSS
O regulă CSS are trei părți:
h1 {
color: red;
font-size: 32px;
}
Selector — h1 — spune browser-ului ce elemente să stilizeze
Proprietate — color, font-size — ce aspect să schimbe
Valoare — red, 32px — ce să-l schimbe la
Proprietățile și valorile sunt scrise ca proprietate: valoare;, în acolade. Fiecare declarație se termină cu punct-virgulă.
Comentarii în CSS
Scrie comentarii cu /* */:
/* Asta stilizează titlul principal */
h1 {
color: #c8553d;
}
/* Text paragraf — dimensiune confortabilă pentru citit */
p {
font-size: 18px;
line-height: 1.6;
}
Regula de aur: separă preocupările
Marele principiu din spatele stylesheet-urilor externe e separarea preocupărilor:
Când astea sunt amestecate, codul tău devine imposibil de întreținut. Când sunt separate, poți schimba aspectul întregului site editând un singur fișier CSS, fără să atingi HTML-ul.
Verificare rapidă
Construiești un site de 10 pagini unde fiecare pagină ar trebui să folosească aceleași stiluri. Care e abordarea potrivită?
Recapitulare
Trei moduri de a adăuga CSS: inline (style="..."), intern (bloc <style>), extern (<link> la fișier .css)
Stylesheet-urile externe sunt implicitul corect pentru proiecte reale
O regulă CSS = selector + { proprietate: valoare; }
Folosește /* */ pentru comentarii
Păstrează HTML și CSS în fișiere separate pentru mentenabilitate
Lecția 13 din 76
Modulul 2 · Lecția 210 min citire
Selectori fundamentali
Selectorii CSS sunt cum spui browser-ului „stilizează chestia asta, nu aia". Îi faci corect și stilizarea unei pagini complexe devine o plăcere. Îi faci greșit și vei ajunge să scrii !important peste tot, întrebându-te de ce nu se aplică stilurile tale.
Selector de element — potrivește după numele tag-ului
Cel mai simplu selector. Țintește fiecare element cu un tag dat.
h1 {
color: #c8553d;
}
p {
line-height: 1.6;
}
a {
color: #1e6091;
}
Fiecare <h1> de pe pagină devine roșu. Fiecare <p> primește line-height confortabil. Fiecare <a> devine albastru.
Când să folosești: pentru implicituri la nivelul întregii pagini. „Toate paragrafele ar trebui să aibă acest line-height."
Selector de clasă — potrivește după atributul class
Țintește elemente cu un atribut class specific. Scris cu un punct înainte.
Doar paragrafele cu class="lead" primesc stilul lead. <p>-ul simplu din mijloc e neatins.
Ăsta e selectorul pe care-l vei folosi cel mai mult. Clasele îți permit să aplici aceeași stilizare oriunde ai nevoie, indiferent de tag.
Selector de ID — potrivește după atributul id
Țintește singurul element cu un id specific. Scris cu diez la început.
<main id="continut">
<p>Conținutul principal aici.</p>
</main>
#continut {
max-width: 800px;
margin: 0 auto;
}
Dar iată chestia: aproape niciodată nu vrei să folosești selectori de ID pentru stilizare. Pentru că ID-urile sunt unice, nu poți reutiliza stilizarea. Și ID-urile au specificitate mare, ceea ce face greu să le suprascrii mai târziu.
Folosește id pentru link-uri de ancoră și JavaScript. Folosește clase pentru stilizare.
Regula de internalizat
Întinde mâna la o clasă 99% din timp. Selectori de element pentru implicituri globale. ID-uri pentru identificare, nu stilizare.
Developerii mai noi sunt tentați să folosească ID-uri pentru stilizare pentru că par „mai specifice" sau „mai semantice" pentru elemente unice. E o capcană. Folosește mereu clase.
Combinarea selectorilor
Mai multe clase pe un element
<article class="card featured">...</article>
.card.featured {
border: 2px solid gold;
}
.card.featured — fără spațiu — țintește elemente care au ambele clase.
Selector descendent (spațiu)
article p {
font-size: 18px;
}
Asta țintește fiecare <p>care e înăuntrul unui <article> — la orice adâncime.
Selector copil direct (>)
article > p {
font-size: 18px;
}
> înseamnă „copil direct" — doar paragrafe care sunt copii imediați ai unui article, nu imbricate mai adânc.
Numele claselor descriu ce este fiecare lucru. .produs-titlu e titlul unui produs. Claritatea aia face CSS-ul să fie auto-documentat.
Specificitate — versiunea simplă
Când mai multe reguli se aplică aceluiași element, care câștigă? Asta e specificitatea.
Stiluri inline bat totul
ID-urile bat clasele
Clasele bat selectorii de element
Selectorii de element sunt cei mai slabi
Regulile mai târzii bat regulile mai devreme dacă specificitatea e egală
Nu te bloca pe specificitate
Începătorii pierd adesea ore luptându-se cu specificitatea. Sfatul profesional: evită selectori cu specificitate mare în primul rând. Stai la clase, ține selectorii scurți și rar vei avea nevoie să te gândești la specificitate.
Alege selectorul potrivit
Ai multe card-uri de produse pe pagină, unele marcate ca „la reducere". Vrei ca toate card-urile la reducere să aibă un border roșu. Ce selector e cel mai bun?
Recapitulare
Selectori de element (h1, p) pentru implicituri globale
Selectori de clasă (.button, .card) pentru stiluri reutilizabile — folosește-i cel mai mult
Selectori de ID (#hero) rar; preferă clase pentru stilizare
Combină cu spațiu (descendent), > (copil direct), , (grupare)
Culoarea și tipografia sunt cele două lucruri pe care utilizatorii le observă înainte de orice altceva. Le faci bine și pagina se simte proiectată. Le faci greșit și niciun layout polish nu te va salva.
Culori în CSS — cinci moduri de a le scrie
Culori cu nume
.titlu {
color: red;
}
150+ culori au nume: red, blue, tomato, rebeccapurple. Rar folosite în muncă reală. Ok pentru prototipuri rapide.
Culori hex
.titlu {
color: #c8553d;
}
Șase cifre hex: două pentru roșu, două pentru verde, două pentru albastru. Fiecare pereche merge de la 00 (deloc) la ff (maxim). #c8553d e un roșu-maroniu cald.
Hex e cel mai comun format în livrabilele designerilor.
RGB
.titlu {
color: rgb(200, 85, 61);
}
Trei numere de la 0-255 pentru roșu, verde, albastru. Cu alpha (transparență):
Hue (0-360, o roată de culori), Saturation (0-100%, gri la viu), Lightness (0-100%, negru la alb). Aceeași culoare, mod diferit de gândit.
HSL e mai ușor de ajustat manual. Vrei o versiune puțin mai întunecată? Scade lightness. Hex și RGB nu te lasă să raționezi despre culoare așa.
OKLCH — alegerea modernă
.titlu {
color: oklch(56% 0.11 35);
}
Idee similară cu HSL dar perceptual uniformă. Înseamnă: dacă crești lightness cu 10%, culoarea chiar pare cu 10% mai luminoasă pentru ochi.
OKLCH e viitorul pentru sisteme de design. Orice browser modern îl suportă.
Proprietăți de tipografie
font-family
Setează tipografia. Oferă mereu fallback-uri în caz că prima alegere nu e disponibilă.
body {
font-family: 'Inter Tight', -apple-system, BlinkMacSystemFont, sans-serif;
}
Citește de la stânga la dreapta: încearcă „Inter Tight" primul. Dacă nu e disponibil, încearcă -apple-system. Apoi BlinkMacSystemFont. În final, sans-serif (fallback-ul generic).
font-size
body {
font-size: 16px;
}
h1 {
font-size: 48px;
}
font-weight
Valorile merg de la 100 (subțire) la 900 (gros). Cele comune:
400 — normal (implicit)
500 — mediu
700 — bold
line-height
body {
line-height: 1.6;
}
h1 {
line-height: 1.1;
}
Font diferit pentru titluri vs body (împerechere clasică serif/sans). Line-height generos pe body pentru citire confortabilă. Line-height strâns pe titluri pentru că sunt scurte. max-width: 65ch pe paragrafe — 65 de caractere e lățimea ideală pentru citit.
Încărcarea de fonturi din Google Fonts
Browserele vin cu câteva fonturi implicite. Pentru orice altceva, încarci fonturi de la un serviciu ca Google Fonts.
Copiezi tag-ul <link>, îl lipești în <head>-ul HTML
Folosești numele fontului în CSS
Nu încărca 8 fonturi
E tentant să navighezi pe Google Fonts și să încarci 5 tipografii pentru că toate arată cool. Nu o face. Fiecare fișier de font e bytes în plus pe care utilizatorul trebuie să-i descarce.
Un design curat folosește de obicei unul sau două fonturi, maxim. Unul pentru titluri, unul pentru body.
Alege line-height-ul potrivit
Stilizezi un articol blog lung cu text body de 18px. Ce line-height e cel mai potrivit pentru paragrafe?
Recapitulare
Culori: hex (#c8553d), rgb, hsl sau oklch — alege una și rămâi cu ea
font-family cu fallback-uri: 'Fontul tău', -apple-system, sans-serif
font-weight pe scala 100-900; 400 e normal, 700 e bold
line-height fără unitate, 1.5-1.7 pentru body, 1.0-1.3 pentru titluri
Google Fonts pentru tipografii personalizate; încarcă doar ce ai nevoie
Lecția 15 din 76
Modulul 2 · Lecția 49 min citire
Variabile CSS
Imaginează-ți că ai scris 500 de linii de CSS folosind culoarea de brand #c8553d în 40 de locuri diferite. Acum echipa de marketing zice „hai să ajustăm puțin roșul de brand".
Fără variabile, faci search-and-replace în 40 de locuri și te rogi să le fi prins pe toate. Cu variabile, schimbi o singură linie.
Sintaxa
Definești o variabilă cu --nume, o folosești cu var(--nume).
Numește variabilele după ce înseamnă, nu după cum arată.
/* Rău — se strică de îndată ce roșul devine albastru */
--rosu: #c8553d;
--gri-mediu: #888;
/* Bun — sensul e stabil */
--color-accent: #c8553d;
--color-text-muted: #888;
Când o variabilă se numește --rosu și rebranduiești la albastru, fiecare nume de variabilă e acum înșelător.
Variabilele pot conține orice
Nu doar culori. Orice valoare CSS validă poate fi o variabilă:
Acum, adaugă data-theme="dark" la <html> sau <body>: fiecare variabilă se schimbă deodată. Butonul, fundalul, textul, toate se comută la valorile dark.
Când folosești variabile vs valori simple
Folosește variabile când:
Valoarea apare în 3+ locuri
Valoarea face parte dintr-un sistem de design (culori, fonturi, spațiere)
Vei dori să o tematizi sau suprascrii mai târziu
Folosește valori simple când:
E o ajustare unică — ca margin-top: 4px pentru o singură ajustare vizuală
Care nume e mai bun?
Configurezi culori pentru un sistem de design. Care numire e cea mai bună?
Recapitulare
Definește variabile la :root cu --nume: valoare
Folosește-le oriunde cu var(--nume)
Numește după scop (--color-accent), nu după aspect (--rosu)
Suprascrie variabile în selectori specifici pentru tematizare
Folosește pentru valori repetate sau părți ale sistemului tău de design
Lecția 16 din 76
Modulul 2 · Lecția 59 min citire
Unități și valori
CSS are multe unități. Asta e intenționat — unități diferite se potrivesc job-urilor diferite. Folosirea celei greșite creează bug-uri subtile care-ți bântuie layout-ul pe device-uri pe care nu le-ai testat.
Unități absolute — px
px e unitatea pe care o știe toată lumea. Un pixel pe ecran.
em e util când spațierea ar trebui să scaleze cu dimensiunea componentei. Dacă crești font-size-ul lui .button la 20px, padding-ul crește proporțional.
Capcana imbricării em
em e relativ la font-size-ul imediat al elementului. Dacă imbrici dimensiuni em, se compun:
Pe browserele mobile, bara URL se arată și se ascunde în timpul scroll-ului. Asta schimbă „înălțimea viewport-ului". Dacă folosești 100vh, elementul tău ar putea fi prea înalt.
CSS modern rezolvă asta cu unități de viewport dinamice:
100dvh — înălțime dinamică de viewport (se ajustează când bara URL apare/dispare)
.hero {
min-height: 100dvh; /* înălțime completă, se ajustează corect pe mobil */
}
Folosește dvh în loc de vh pentru secțiuni full-height prietenoase cu mobil. Ăsta e implicitul modern.
Unitate de caractere — ch
1ch e lățimea caracterului 0 din fontul curent.
p {
max-width: 65ch;
}
Asta face paragrafele să fie în jur de 65 de caractere lățime — o lungime de linie optimă pentru citit, larg cercetată.
Care unitate când — un cheat sheet
Dimensiuni de font → rem
Spațiere între blocuri → rem
Padding component intern care scalează → em
Lățimi de border → px
Secțiuni fullscreen → dvh
Lățime de paragraf citibilă → ch
Alege unitatea
Setezi font-size-ul unui paragraf ca să respecte preferințele de font-size ale browser-ului utilizatorului. Care unitate e cea mai bună?
Recapitulare
px pentru border-uri și lucruri fixe în pixeli
rem pentru majoritatea dimensiunilor de font și spațiere — respectă preferințele utilizatorului
em pentru scalare intern-componentă
% pentru lățimi proporționale în părinte
dvh (nu vh) pentru secțiuni full-height prietenoase cu mobil
ch pentru lățime de citit confortabilă (~65ch pentru paragrafe)
Lecția 17 din 76
Modulul 2 · Lecția 69 min citire
Proprietatea display
Fiecare element HTML are un mod implicit de a ocupa spațiu pe pagină. Unele elemente se stivuiesc vertical (paragrafe, div-uri, titluri). Altele curg într-o linie cu text (span, strong, a). Asta e controlat de proprietatea display.
Înțelegerea display e poarta spre înțelegerea layout-ului.
Cele trei valori clasice de display
block
Elementele block se stivuiesc vertical. Fiecare începe pe o linie nouă și ocupă lățimea completă a containerului.
Elemente block implicit: <p>, <h1>-<h6>, <div>, <section>, <article>.
Comportament block:
Ia lățimea completă a părintelui implicit
Începe pe o linie nouă
Respectă width, height, margin, padding pe toate părțile
inline
Elementele inline curg cu textul. Nu încep linii noi — stau în fluxul de text.
Elemente inline implicit: <span>, <strong>, <em>, <a>, <code>.
<p>
Asta e un paragraf cu <strong>text bold</strong> și
<a href="#">un link</a> înăuntru.
</p>
Comportament inline:
Ia doar atât cât e conținutul
Stă pe aceeași linie ca elementele inline vecine
width și heightnu funcționează pe elementele inline
inline-block
Se comportă ca inline (stă pe aceeași linie cu textul), dar acceptă width și height ca block.
Cele două div-uri block se stivuiesc vertical, fiecare 120px. Spans-urile inline curg orizontal, dar lățimea e ignorată. Spans-urile inline-block curg orizontal ȘI respectă lățimea.
Schimbarea display
Link-ul block-level
Un pattern comun — făcând un card întreg clickabil:
Vrei un buton personalizat care stă inline cu textul din jur dar are o lățime și padding specifice. Care valoare de display?
Recapitulare
block se stivuiește vertical, ia lățime completă, respectă toate dimensiunile
inline curge cu textul, ignoră width/height, nu cauzează line break-uri
inline-block combină cele două: flow inline, suport complet pentru dimensiuni
display: none înlătură un element din pagină complet
Flex și Grid vin în Modulele 4 și 5 — vor înlocui majoritatea nevoilor de layout
Lecția 18 din 76
Modulul 2 · Lecția 77 min citire
Box-sizing și reset
Orice fișier CSS pentru restul carierei tale ar trebui să înceapă cu două lucruri: o regulă box-sizing și un mic reset. Aceste câteva linii elimină o clasă întreagă de bug-uri care confuză începătorii și încetinesc profesioniștii.
Problema box-sizing
Când setezi width: 300px pe o cutie, cât spațiu ocupă de fapt?
Reset-ul de mai sus e o versiune condensată a standardelor. Două celebre:
Eric Meyer's reset (2008) — istoric, aduce totul la zero
Normalize.css — normalizează diferențele între browsere fără să elimine implicite
Nu ai nevoie să le memorezi. Doar lipește reset-ul la începutul fiecărui proiect.
De ce border-box?
Ce face setarea box-sizing: border-box pe un element?
Recapitulare
box-sizing: content-box implicit adaugă padding/border la width (confuz)
box-sizing: border-box face width dimensiunea totală exterioară (predictibil)
Aplică-l global cu *, *::before, *::after { box-sizing: border-box; }
Un reset modern aduce la zero margini implicite, repară comportamentul imaginilor și moștenește fontul pe form elements
Fiecare proiect începe cu acest boilerplate — lipește-l la începutul stylesheet-ului
Lecția 19 din 76
Modulul 2 · Lecția 815 min citire
Proiect: stilizează pagina Despre Mine
Ai o pagină Despre Mine solidă în HTML din Modulul 1. Ai trusa CSS din Modulul 2. Timp să le punem împreună.
Ăsta e un proiect ghidat. Te conduc prin decizii, îți arăt codul și explic raționamentul. La final, pagina ta urâtă de 1995 va arăta ca ceva ce ai pune efectiv pe internet.
Ce construim
Ia pagina Despre Mine din Modulul 1. Adaugă CSS ca să:
Aibă o ierarhie vizuală clară — titlurile ies în evidență, textul body se citește confortabil
Folosească o combinație distinctivă de fonturi (serif pentru titluri, sans-serif pentru body)
Aibă o paletă de culori caldă și coerentă (fără negru și albastru implicit)
Aibă spațiere plăcută în jurul fiecărui element
Pasul 1: Începe stylesheet-ul
Creează styles.css lângă index.html. Linkează-l din <head>:
Fontul serif pentru titluri contrastează frumos cu body-ul sans-serif. Line-height strâns și letter-spacing negativ fac textul mare să se simtă intenționat.
Lățimi și line-height-uri confortabile pentru citit
Focus states pe form inputs
Mici tranziții hover pe link-uri și butoane
Personalizează, nu doar copia
Scopul proiectului nu e ca toată lumea să termine cu aceeași pagină. Schimbă culorile. Alege fonturi diferite. Folosește o paletă mai rece. Fă h1-ul mai mare. Fă textul body mai lat.
Skill-urile sunt în ajustat — nu în a tasta ce ți-am dat verbatim.
Recapitulare
Începe fiecare proiect cu un reset și design tokens
Focus states, hover transitions și :last-child cleanups sunt detaliile care separă amator de pro
Un max-width pe body ține conținutul long-form lizibil
Modulul 3 îți va da control real peste cutii (padding, margini, border-uri, background-uri)
🎉 Ai terminat Modulul 2!
Ai acum fundamentele CSS. Modulul 3 (Box Model) îți dă control real peste cutii — padding, margini, backgrounds, borders.
Lecția 20 din 76
Modulul 3 · Lecția 19 min citire
Box model-ul pe bune
În CSS, fiecare element de pe pagină e o cutie. Fără excepție. Titlurile, paragrafele, imaginile, link-urile — toate sunt cutii dreptunghiulare care ocupă spațiu.
Acele cutii au patru straturi. Odată ce le înțelegi, controlezi complet cum se aranjează lucrurile pe pagină. Asta e lecția aia.
Cele patru straturi ale unei cutii
Fiecare cutie CSS are, din interior spre exterior:
Content — conținutul (textul, imaginea, copiii)
Padding — spațiul dintre conținut și border
Border — marginea vizuală în jurul cutiei
Margin — spațiul dintre această cutie și celelalte
Zona interioară unde trăiește textul sau alte elemente. Dimensiunea ei e controlată de width și height (sau lăsată implicit să se adapteze la conținut).
Padding
Spațiu între conținut și border. Crează „loc de respirat" înăuntrul cutiei.
/* Padding egal pe toate cele 4 părți */
.box { padding: 20px; }
/* Pe verticală 20px, pe orizontală 40px */
.box { padding: 20px 40px; }
/* Sus 10, dreapta 20, jos 30, stânga 40 (în sensul acelor de ceas) */
.box { padding: 10px 20px 30px 40px; }
/* Individual */
.box {
padding-top: 10px;
padding-right: 20px;
padding-bottom: 30px;
padding-left: 40px;
}
Border
Marginea vizibilă în jurul cutiei. Are 3 părți: lățime, stil, culoare.
Padding — pentru spațiu în interiorul cutiei (între conținut și border). Dacă ai un card, padding-ul e ce face textul să nu stea lipit de margini.
Margin — pentru spațiu între cutii diferite. Dacă ai două carduri unul sub altul, margin-ul e ce-i separă.
Un card are ambele: padding ca să aerisească interiorul, margin ca să-l distanțeze de alte carduri.
Vizualizare în DevTools
Deschide DevTools (F12) pe orice site, selectează un element, uită-te în tab-ul „Computed" sau „Box Model". Vei vedea o diagramă colorată cu cele 4 straturi și valorile lor curente. E cel mai bun mod să înțelegi ce se întâmplă în layout-ul tău.
Exemplu explicat
Un card complet
.card {
/* Lățime totală cu border-box */
box-sizing: border-box;
width: 320px;
/* Interior */
padding: 24px;
/* Margine vizuală subtilă */
border: 1px solid rgba(0,0,0,0.1);
border-radius: 12px;
/* Fundal ca să iasă în evidență */
background: white;
/* Spațiu între el și alte carduri */
margin-bottom: 16px;
}
Cu box-sizing: border-box (din Modulul 2), cardul e fix 320px lățime — padding-ul crește înspre interior. Fără asta, 320 + 2×24 + 2×1 = 370px total. Predictibilitatea contează.
Valori negative pentru margin
Spre deosebire de padding, margin poate fi negativ. Asta trage elementul mai aproape de vecini sau chiar peste ei.
.overlapping-card {
margin-top: -20px; /* se suprapune cu cardul de deasupra */
}
Rar folosit, dar ocazional util pentru design-uri unde elementele se suprapun vizual.
Alege corect
Ai un card cu text înăuntru. Textul pare lipit de marginea cardului — vrei să respire mai mult în interior. Ce proprietate ajustezi?
Recapitulare
Fiecare element e o cutie cu 4 straturi: content, padding, border, margin
Padding — spațiu interior (între conținut și border)
Margin — spațiu exterior (între cutii)
Border — marginea vizuală
Folosește padding: 10px 20px 30px 40px pentru 4 valori (sus, dreapta, jos, stânga)
Margin poate fi negativ, padding nu
Lecția 21 din 76
Modulul 3 · Lecția 28 min citire
Width, height și limite
Părerea că „lățimea e doar un număr" ține până ajungi la ecrane mici, conținut lung sau imagini ciudate. CSS are câteva unelte dincolo de simplul width care fac layout-urile să funcționeze în situații reale.
Comportamentul implicit
Înainte să setezi vreo lățime manual, reține ce fac elementele implicit:
Elementele block (div, p, h1, section) se întind automat pe lățimea părintelui
Elementele inline (span, a, strong) iau doar atât cât e conținutul
Imaginile au lățimea intrinsecă a fișierului
De cele mai multe ori, comportamentul implicit e exact ce vrei. Intervenim cu width când vrem altceva.
Width și height de bază
.card {
width: 320px;
height: 200px;
}
Cu box-sizing: border-box, asta setează lățimea totală (inclusiv padding și border).
Valorile pot fi în orice unitate: px, %, rem, vw, etc.
Min-width și max-width
Mai utile decât width fix pentru design responsive.
.container {
width: 90%;
max-width: 1200px; /* nu depăși 1200px chiar dacă 90% e mai mult */
}
.card {
width: 100%;
min-width: 280px; /* niciodată mai îngust de 280px */
max-width: 400px; /* niciodată mai lat de 400px */
}
De ce e asta important:
max-width pe un container previne să devină prea lat pe ecrane mari
min-width previne elementele să devină prea înguste și ilizibile
Combinația cu width: 100% îți dă un element care se întinde în limite sigure
Înălțimea e mai complicată
Spre deosebire de lățime, înălțimea e de obicei lăsată automată. Setez rareori o înălțime fixă pe un element care conține text.
/* Problematic — dacă textul crește, se taie sau iese */
.card { height: 200px; }
/* Mai bine — crește cu conținutul, dar nu mai jos de 200px */
.card { min-height: 200px; }
Regula aurie: min-height, nu height
Pentru elemente cu text variabil (carduri, celule, secțiuni), folosește min-height în loc de height.
Motivul: height: 200px e rigid. Dacă utilizatorul mărește dimensiunea fontului, sau tu adaugi text, conținutul fie se taie, fie iese în afară. min-height: 200px garantează un minim dar permite cutiei să crească când e nevoie.
Height fixă doar pentru elemente decorative (iconițe, separatoare) unde dimensiunea nu trebuie să se schimbe.
Pattern-uri comune de lățime
Container centrat cu lățime maximă
.container {
max-width: 1200px;
margin: 0 auto; /* centrează orizontal */
padding: 0 1.5rem; /* spațiu pe margini pe ecrane mici */
}
Cel mai folosit pattern de layout pe web. Content-ul stă frumos la mijloc pe ecrane mari și umple ecranul pe cele mici.
Secțiune full-width cu content centrat
section {
width: 100%; /* toată lățimea */
background: #f4f2ec; /* fundal plin până la margini */
}
section .inner {
max-width: 1200px;
margin: 0 auto;
padding: 4rem 1.5rem;
}
Secțiunea întinde fundalul peste tot ecranul, dar conținutul rămâne centrat la lățime citibilă.
Text cu lățime optimă de citit
article p {
max-width: 65ch;
}
65 de caractere e lățimea optimă pentru citit — conform cercetărilor în tipografie.
Exemplu explicat
Layout tipic de blog
body {
/* body-ul n-are lățime setată — implicit ia tot ecranul */
}
.page {
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1.5rem;
}
article {
max-width: 65ch;
margin: 0 auto; /* centrat în interiorul .page */
}
article img {
width: 100%; /* umple lățimea articolului */
max-width: 100%; /* nu crește peste asta */
height: auto; /* păstrează proporțiile */
}
Container-ul .page limitează lățimea pe desktop. Articolul se îngustează și mai mult pentru citit confortabil. Imaginile umplu articolul dar păstrează proporțiile naturale.
Layout responsive
Vrei un container care ocupă tot ecranul pe telefoane dar nu depășește 1200px pe monitoare mari. Ce setări sunt corecte?
margin: 0 auto îl centrează pe ecrane mari. width: 1200px l-ar forța să fie exact 1200px mereu — cauzând scroll orizontal pe telefon." data-msg-wrong="Nu chiar. Răspunsul corect e B. max-width setează o limită superioară, lăsând elementul să fie mai îngust când e nevoie. width l-ar forța rigid.">
Recapitulare
Elementele block iau lățimea completă implicit, inline iau cât e conținutul
Folosește max-width în loc de width pentru containere responsive
Folosește min-height în loc de height pentru elemente cu text
Pattern container centrat: max-width + margin: 0 auto
Pentru text lizibil: max-width: 65ch
Lecția 22 din 76
Modulul 3 · Lecția 38 min citire
Margini și margin collapse
Margins sunt simple — setezi un spațiu, primești un spațiu. Până când două margini se întâlnesc și se comportă ciudat. Bună vedere la margin collapse, fenomenul CSS care confuză developerii de 25 de ani.
Înveți regulile odată, nu te mai prinde niciodată.
Repetarea rapidă a margin-urilor
/* Toate 4 părțile */
.box { margin: 16px; }
/* Vertical / orizontal */
.box { margin: 20px 40px; }
/* Fiecare parte */
.box { margin: 10px 20px 30px 40px; } /* sus, dreapta, jos, stânga */
/* Individual */
.box {
margin-top: 2rem;
margin-bottom: 1rem;
}
Margin collapse — ce e
Când două elemente block adiacente au margini verticale, acestea se colapsează în una singură — cea mai mare dintre ele.
Ai crede că spațiul între ele e 30 + 20 = 50px. De fapt e doar 30px. Cea mai mare câștigă, cealaltă e ignorată.
De ce se întâmplă asta
E o moștenire din epoca documentelor tipărite — când ai un paragraf cu margin jos de 20px și următorul cu margin sus de 20px, nu vrei 40px între ele. Vrei un spațiu consistent.
CSS a păstrat comportamentul, chiar dacă e contraintuitiv pentru dev-eri noi. Acceptă-l și mergi mai departe.
Când se colapsează margin-urile
Doar în situații verticale și block:
Între două elemente block adiacente (paragraf după paragraf)
Între un părinte și primul/ultimul copil al său
Când un element gol are margin sus și jos
Când NU se colapsează
Margini orizontale (stânga/dreapta) niciodată nu se colapsează
Padding rupe colapsarea — dacă părintele are padding, nu colapsează cu copilul
Border rupe colapsarea — același lucru
Flex și Grid containers — nu se aplică deloc margin collapse pe copii
Pattern: niciodată margin-top pe primul, niciodată margin-bottom pe ultimul
O convenție pe care o adoptă majoritatea dezvoltatorilor pentru a evita confuzii:
/* Folosește doar margin-bottom pe elemente */
h2 { margin-bottom: 1rem; }
p { margin-bottom: 1rem; }
ul { margin-bottom: 1rem; }
/* Niciodată margin-top — evită colapsări surprinzătoare */
Sau inversul — doar margin-top. Important e să fii consistent într-un proiect.
Soluția modernă: gap cu flex și grid
Dacă folosești Flexbox sau Grid (module viitoare), proprietatea gap elimină toate problemele de margin collapse:
.lista-carduri {
display: flex;
flex-direction: column;
gap: 1rem; /* spațiu consistent între carduri, fără drama margin-ului */
}
Ăsta e modul modern de a gândi spațierea. Vei folosi gap în fiecare proiect de aici încolo.
Exemplu explicat
Vezi diferența cu/fără padding pe părinte
/* Margin-ul h2 colapsează cu părintele */
section {
background: yellow;
}
section h2 {
margin-top: 30px;
/* În loc să creeze spațiu sus în section,
colapsează în afara lui și pushează section-ul întreg */
}
/* Cu padding, colapsarea e blocată */
section {
padding-top: 1px; /* un pixel e suficient să rupă */
background: yellow;
}
section h2 {
margin-top: 30px;
/* Acum margin-ul e în interiorul section-ului */
}
Un truc pro: adaugă padding: 1px sau overflow: hidden pe un container care „mănâncă" margin-urile copiilor.
Margin: auto — centrat cu magie
Setând margin: 0 auto (sau margin-left: auto; margin-right: auto) centrezi un element block orizontal. Browser-ul distribuie spațiul rămas egal între stânga și dreapta.
Notă: nu merge pentru centrare verticală (margin: auto 0 nu face nimic pe block). Pentru verticală, folosește Flexbox (Modul 4).
Rezolvă colapsarea
Ai două paragrafe unul sub altul. Primul are margin-bottom: 24px, al doilea margin-top: 16px. Ce spațiu apare între ele?
Recapitulare
Margin collapse: margini verticale adiacente se combină — cea mai mare câștigă
Se întâmplă doar pe vertical, doar între elemente block
Nu se întâmplă în Flex sau Grid containers
Convenție: folosește doar margin-bottom sau doar margin-top, nu amândouă
Modern: folosește gap în Flex/Grid și evită problema complet
margin: 0 auto centrează block orizontal
Lecția 23 din 76
Modulul 3 · Lecția 49 min citire
Background-uri
Fiecare element poate avea un fundal. O culoare plină, o imagine, un gradient, sau combinații între toate trei. Background-urile bune fac pagina să pară proiectată cu gust. Background-urile proaste o fac să arate ca un site de spam din 2005.
Gradient-ul se întunecă de sus în jos peste imagine, făcând textul alb lizibil. Fără overlay, textul poate să dispară pe zonele luminoase ale imaginii.
Background attachment
Mai rar folosit dar util pentru efecte parallax:
.parallax {
background: url('peisaj.jpg') center / cover;
background-attachment: fixed; /* stă pe loc când scrollezi */
}
Atenție: background-attachment: fixed poate fi lent pe mobil. Testează pe telefon înainte să-l folosești în producție.
Alege setarea potrivită
Vrei o imagine de fundal care umple tot ecranul, se adaptează la orice dimensiune, și nu se taie ciudat. Ce setări folosești?
Recapitulare
background acceptă culoare, imagine, gradient — sau toate combinate
cover = acoperă tot (poate tăia); contain = încape tot (poate lăsa goluri)
Gradient-urile sunt tratate ca imagini: linear-gradient, radial-gradient
Pot fi stivuite mai multe fundaluri separate prin virgulă
Overlay peste imagine = gradient semi-transparent deasupra pentru lizibilitate
Lecția 24 din 76
Modulul 3 · Lecția 58 min citire
Border-radius și shadows
Colțurile rotunjite și umbrele subtile sunt printre cele mai mici detalii care fac diferența între un design amator și unul matur. Ambele sunt simple — dar se folosesc greșit mai des decât oricare alte proprietăți CSS.
Border-radius — colțuri rotunjite
/* Toate 4 colțurile la fel */
.card { border-radius: 8px; }
/* Diferite pe fiecare colț */
.card { border-radius: 8px 16px 8px 16px; } /* sus-stg, sus-dr, jos-dr, jos-stg */
/* Individual */
.card {
border-top-left-radius: 8px;
border-bottom-right-radius: 8px;
}
Valori comune
4px — rotunjire foarte subtilă, elemente tehnice
8px — standard pentru butoane, inputs
12-16px — carduri moderne
24px — elemente mari, buton gras
50% — cerc perfect (pentru elemente pătrate)
9999px — „pill" (capsulă) pentru elemente nepătrate
Cercuri și capsule
/* Avatar — element pătrat → cerc perfect */
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
}
/* Buton capsulă — indiferent de lățime */
.pill-button {
padding: 8px 24px;
border-radius: 9999px; /* valoare mare = capsulă */
}
Radius consistent într-un design system
Nu amesteca la întâmplare 4px, 6px, 10px, 15px prin proiect. Alege 3-4 valori și rămâi la ele.
/* Subtilă — pentru carduri pe fundal deschis */
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
/* Medie — pentru elemente hover, dropdowns */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
/* Pronunțată — pentru modale */
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
/* Mai multe straturi — cel mai realistic */
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.04),
0 4px 12px rgba(0, 0, 0, 0.08),
0 20px 40px rgba(0, 0, 0, 0.12);
Ultimul pattern — 3 straturi de umbră — imită cum se comportă lumina în realitate. O umbră aproape pentru apropiere, una medie pentru distanță, una mare pentru atmosferă. Profesioniștii îl folosesc constant.
Reguli de umbre
Umbrele doar în jos. Y-offset pozitiv (2, 4, 8). Lumina vine de sus în design UI — umbrele merg spre podea.
Opacitate mică. Rar mai mult de 0.15 alfa. Umbrele prea închise arată ieftin.
Blur mare, spread mic. Umbrele reale sunt difuze, nu ascuțite.
Niciodată umbre colorate aleator. Doar negru cu transparență. Excepție: umbră în culoarea elementului pentru efect „glow".
Reguli de umbre: jos, opacitate mică (≤0.15), blur mare, doar negru
Umbrele stratificate (3 rânduri) imită lumina reală și arată cel mai premium
Lecția 25 din 76
Modulul 3 · Lecția 67 min citire
Overflow
Ce se întâmplă când conținutul e mai mare decât containerul? Răspunsul depinde de setarea overflow. E una dintre proprietățile cele mai ignorate de începători și cele mai importante pentru layout-uri solide.
Situația
Imaginează-ți un card de 200px înălțime care conține un text de 500px. Ce se întâmplă cu cei 300px în plus?
Implicit, textul iese din card și e vizibil în afara lui. Poate arăta ciudat, poate suprascrie alte elemente. Uneori e ce vrei, alteori nu.
Cele patru valori
overflow: visible (implicit)
Conținutul iese din container, e vizibil în afara lui. Implicit pentru toate elementele.
overflow: hidden
Conținutul care depășește e tăiat — nu se vede, nu se poate derula.
.card {
height: 200px;
overflow: hidden;
/* Textul mai lung de 200px e invizibil */
}
Util când vrei margini curate — de exemplu, într-un card cu imagine și border-radius, fără overflow: hidden pe container, imaginea ar ieși din colțurile rotunjite.
overflow: scroll
Întotdeauna afișează bare de scroll, chiar dacă nu e nevoie. Rareori ce vrei.
overflow: auto
Browser-ul decide: dacă conținutul e mai mare, adaugă bare de scroll. Dacă încape, nu. Ăsta e cel pe care-l vei folosi.
.scroll-container {
max-height: 400px;
overflow: auto;
/* Barele de scroll apar doar când e necesar */
}
Overflow pe o singură axă
/* Doar vertical */
.modal-content {
max-height: 80vh;
overflow-y: auto;
overflow-x: hidden; /* fără scroll orizontal */
}
/* Doar orizontal (ex: un tabel mare) */
.table-wrapper {
overflow-x: auto;
}
Fără overflow: hidden, imaginea s-ar vedea pătrată la colțurile cardului.
Text pe o linie cu „..."
.card-title {
white-space: nowrap; /* pe o singură linie */
overflow: hidden; /* ce iese se taie */
text-overflow: ellipsis; /* înlocuiește tăierea cu "..." */
}
Pattern ultra-folosit pentru titluri lungi în carduri, nume lungi în tabele, etc.
Trei utilizări de overflow într-un singur card: tăierea imaginii la colțuri, titlu pe o linie cu „...", descriere pe maxim 3 linii. Toate indiferent de conținutul real — cardul arată consistent.
Scenariu comun
Ai un sidebar cu o listă lungă de nav-items. Vrei să scrollezi în interiorul sidebar-ului dacă lista depășește înălțimea ecranului. Ce setezi?
Recapitulare
visible (implicit) — conținutul iese din container
hidden — conținutul care depășește se taie
auto — scroll apare doar când e necesar (cel mai folosit)
Ai tot ce-ți trebuie acum să construiești componente reale. Hai să facem o galerie de carduri — tipul de componentă pe care o vezi pe orice site de e-commerce, portofoliu sau blog.
Fără Flexbox sau Grid încă (acelea vin în modulele următoare). Doar ce ai învățat. Vei fi surprins cât de departe ajungem.
Ce construim
O pagină cu un set de carduri de produse. Fiecare card are: imagine, titlu, descriere scurtă, preț, buton. Arată profesional, funcționează pe orice ecran, folosește doar HTML și CSS din modulele 1-3.
Aici vine limita acestui modul. Pentru o galerie adevărată cu 3 carduri pe rând, avem nevoie de Flexbox sau Grid. Pentru moment, folosim inline-block:
.galerie {
/* Spațiu între carduri - margin-ul de pe card face treaba */
}
.card {
display: inline-block;
vertical-align: top;
margin-right: 1.5rem;
margin-bottom: 1.5rem;
}
Pe ecrane mari, cardurile vor curge unul după altul orizontal. Pe ecrane mici, se vor stivui natural. Nu e perfect — spațierile pot fi ciudate — dar funcționează.
Preview: cu Flexbox/Grid va fi mult mai bine
Layout-ul cu inline-block are limitări: spații ciudate între carduri, aliniere dificilă. În Modulul 4 vom folosi display: flex și totul devine:
Hover state cu transform și umbră mai puternică = interactivitate plăcută
line-clamp face textul uniform indiferent de conținut
Variabile CSS fac schimbarea temei instant
Cu Flexbox/Grid (modulele următoare) vei obține același card cu layout mai bun
🎉 Ai terminat Modulul 3!
Stăpânești box model-ul. Modulul 4 (Flexbox) e unde CSS-ul devine cu adevărat puternic pentru layout.
Lecția 27 din 76
Modulul 4 · Lecția 18 min citire
Ce e Flexbox
Ani de zile, aranjarea lucrurilor pe o pagină era un coșmar. Ca să pui trei carduri unul lângă altul trebuia să lupți cu float, să rezolvi bug-uri de clearfix, să-ți scrii hack-uri ciudate. Apoi a apărut Flexbox și tot peisajul s-a schimbat.
Flexbox e modul modern de a aranja conținut într-o direcție — pe orizontală sau pe verticală. Pentru 90% din layout-urile tale, Flexbox e răspunsul.
Problema pe care o rezolvă
Imaginează-ți: trei carduri, vrei să stea unul lângă altul, echidistant, toate aceeași înălțime chiar dacă conținutul diferă. Înainte de Flexbox, asta cerea 50 de linii de CSS. Cu Flexbox, 3.
.container {
display: flex;
gap: 1rem;
}
Atât. Container-ul devine „flex", copiii lui se aranjează orizontal cu spațiu între ei. Magia începe aici.
Container vs items — terminologia esențială
Flexbox vorbește despre două lucruri:
Flex container — elementul pe care ai scris display: flex
Flex items — copiii direcți ai container-ului (și doar ei — nepoții nu sunt flex items)
Regulile flex se aplică pe container (cum se aranjează items-urile) SAU pe items (cum se comportă fiecare individual). O să le vedem pe toate în următoarele lecții.
Axe: principală și transversală
Cheia înțelegerii Flexbox e conceptul de axe:
Axa principală (main axis) — direcția în care se aranjează items-urile. Implicit: orizontală (stânga-dreapta).
Axa transversală (cross axis) — perpendiculară pe cea principală. Implicit: verticală (sus-jos).
Când îți setezi flex-direction: column, axele se inversează — principala devine verticală, transversala devine orizontală.
De ce contează axele
Două dintre cele mai importante proprietăți Flexbox — justify-content și align-items — lucrează pe axe diferite:
justify-content lucrează pe axa principală
align-items lucrează pe axa transversală
Dacă inversezi direcția (column vs row), inversezi și efectele acestor proprietăți. Dev-erii noi se împiedică aici des. Ține minte: justify = principal, align = transversal.
Fără Flexbox, link-urile ar fi stat unul sub altul (dacă ar fi fost block) sau fără spațiere (dacă ar fi fost inline). Cu cele două linii de mai sus — navbar orizontal perfect spațiat.
Acum copiii se aranjează pe verticală, cu 1rem între ei. Formularele arată consistent fără să te mai gândești la margini.
Când folosești Flexbox
Flexbox e pentru aranjare într-o singură direcție — rând SAU coloană. Dacă ai nevoie de layout 2D (rânduri ȘI coloane cu structură), folosești Grid (Modulul 5).
Flexbox e perfect pentru:
Navbar-uri
Butoane cu iconițe
Formulare verticale
Carduri unul lângă altul
Layout-uri tip „titlu + subtitlu + buton" aliniate
Problema „centrează o cutie pe mijlocul paginii" a chinuit dev-erii 20 de ani. Cu Flexbox, 3 linii. Asta e diferența.
Prima verificare
Care e terminologia corectă — unde aplici display: flex?
Recapitulare
Flexbox = aranjarea conținutului într-o direcție (rând sau coloană)
display: flex merge pe container, nu pe copii
Axa principală = direcția de aranjare; axa transversală = perpendiculară
justify-content lucrează pe axa principală
align-items lucrează pe axa transversală
Pentru layout 2D (ex. dashboard) folosești Grid (Modulul 5)
Lecția 28 din 76
Modulul 4 · Lecția 27 min citire
flex-direction și flex-wrap
Primul lucru pe care-l decizi despre un layout Flexbox: în ce direcție se aranjează lucrurile? Al doilea: ce se întâmplă dacă nu mai încap?
flex-direction — stabilește axa principală
.container {
display: flex;
flex-direction: row; /* implicit — orizontal, stg la dr */
/* flex-direction: row-reverse; orizontal, dr la stg */
/* flex-direction: column; vertical, sus la jos */
/* flex-direction: column-reverse; vertical, jos la sus */
}
row — copiii curg orizontal, de la stânga la dreapta
column — copiii curg vertical, de sus în jos
Variantele -reverse inversează ordinea
Când folosești column
Majoritatea layout-urilor mari (pagini întregi) sunt column — titluri, paragrafe, secțiuni, footer. Curg vertical.
main {
display: flex;
flex-direction: column;
gap: 2rem;
}
Asta spațiază automat toate secțiunile unui main cu 2rem între ele. Fără margins, fără colapsări.
Când folosești row
Pentru componente mici aranjate orizontal:
.card-footer {
display: flex;
/* row e implicit, nu trebuie specificat */
gap: 1rem;
align-items: center;
}
Un footer de card cu preț + buton, aliniate pe aceeași linie.
flex-wrap — ce se întâmplă când conținutul e prea mare
Implicit, flex items nu se înfășoară. Dacă pui 6 carduri într-un rând și lățimea lor totală depășește container-ul, toate se îngustează să încapă. Pot ajunge ilizibil de înguste.
.galerie {
display: flex;
flex-wrap: wrap; /* items-urile care nu încap sar pe rândul următor */
gap: 1rem;
}
Valorile flex-wrap:
nowrap (implicit) — toate pe un rând, chiar dacă se înghesuie
wrap — sar pe rândul următor când nu mai încap
wrap-reverse — wrap, dar în ordinea inversă (rar folosit)
Prescurtarea flex-flow
Pentru direction + wrap într-o singură declarație:
Combinația flex-wrap: wrap + lățime minimă pe card = galerie automat responsive. Pe ecrane mari, 4-5 carduri pe rând. Pe ecrane mici, se pliază. Fără media queries.
Când să nu folosești wrap
Există layout-uri unde wrap-ul distruge design-ul: navbar-uri simple, butoane grupate, liste orizontale scurte. Dacă items-urile tale trebuie mereu să stea pe o singură linie, nu seta flex-wrap: wrap.
Pentru conținut care chiar trebuie să se încadreze indiferent de spațiu, consideră overflow-x: auto pe container — devine scroll orizontal (util pentru tab-uri multe, carousel-uri simple).
Decide ce setezi
Faci un layout de pagină cu header sus, content în mijloc, footer jos. Toate ocupă lățimea completă. Cum configurezi body-ul?
Recapitulare
flex-direction: row (implicit) pentru orizontal, column pentru vertical
flex-wrap: wrap permite items-urilor să sară pe rânduri noi când nu mai încap
flex-wrap: nowrap (implicit) le forțează pe o singură linie
flex-flow: row wrap = prescurtare pentru ambele
Pentru page layout: column. Pentru elemente grupate: row.
Lecția 29 din 76
Modulul 4 · Lecția 38 min citire
justify-content
Ai items aranjate într-o direcție. Acum, cum le poziționezi în interiorul container-ului? Toate la stânga? Toate la dreapta? Egal distanțate? justify-content face exact asta — aliniează items-urile pe axa principală.
Cele șase valori
Imaginează-ți un container cu 3 carduri și spațiu în plus. Cum distribuim acel spațiu?
flex-start (implicit)
Items-urile se adună la începutul container-ului. Spațiul liber e la final.
Logo la stânga, nav la dreapta — space-between împinge automat. Dacă ai 3 grupuri (logo, meniu, profil user), space-between le distribuie pe toate trei corect.
Pattern: buton izolat la final
Ce faci dacă ai items multe și vrei ultimul la dreapta?
margin-left: auto pe un item flex îl împinge la dreapta de tot. Funcționează pentru că margin auto absoarbe tot spațiul disponibil. Truc ultra-folosit.
Butoanele sunt împinse la dreapta — convenție UI standard pentru modale. Spațiul dintre ele e 0.75rem, controlat de gap.
Alege setarea potrivită
Ai 4 iconițe de social media într-un rând. Vrei spațiu egal între ele și la margini — distribuție perfect uniformă. Ce valoare folosești?
Recapitulare
justify-content aliniază items pe axa principală (unde curg)
flex-start, flex-end, center — grupare la început/sfârșit/mijloc
space-between — primul/ultimul la margini, spațiu între
space-evenly — spațiu egal peste tot (cel mai natural)
space-around — spațiu în jurul fiecărui item
Truc: margin-left: auto împinge un item la dreapta
Lecția 30 din 76
Modulul 4 · Lecția 48 min citire
align-items
justify-content aliniează pe axa principală. align-items face același lucru — dar pe axa transversală. Dacă flex-direction e row, align-items controlează alinierea verticală.
De ce e atât de util
Să zicem că ai un card cu titlu și buton unul lângă altul. Titlul are 2 linii, butonul are una. Cum le aliniezi? Centrate? Titlul sus și butonul sus? Ambele jos? align-items rezolvă asta instant.
Valorile
stretch (implicit)
Items-urile se întind să umple container-ul pe axa transversală. Dacă flex-direction e row, toate items-urile vor avea aceeași înălțime — egală cu cel mai înalt.
.galerie {
display: flex;
align-items: stretch; /* implicit — nu trebuie scris */
}
/* Toate cardurile vor avea aceeași înălțime */
Util pentru galerii de carduri unde vrei înălțime uniformă chiar dacă unele au mai mult conținut.
flex-start
Items-urile se aliniază la începutul axei transversale (sus, dacă flex-direction e row).
flex-end
La sfârșitul axei transversale (jos pentru row).
center
Centrate pe axa transversală.
.card-footer {
display: flex;
align-items: center; /* preț și buton aliniate la mijloc vertical */
justify-content: space-between;
}
baseline
Items-urile se aliniază după baseline-ul textului. Util când ai texte de dimensiuni diferite pe aceeași linie și vrei ca textul să fie aliniat frumos indiferent de dimensiunea elementelor.
Pattern important: cardul e flex-column cu min-height. Descrierea cu flex-grow: 1 împinge footer-ul în jos. Footer-ul e un sub-flex cu preț la stânga și buton la dreapta, ambele aliniate pe baseline cu align-items: center.
align-content — pentru flex-wrap
Când ai flex-wrap: wrap și items-urile se întind pe mai multe rânduri, align-content controlează cum sunt distribuite rândurile pe axa transversală.
.galerie {
display: flex;
flex-wrap: wrap;
gap: 1rem;
min-height: 500px;
align-content: flex-start; /* rândurile se adună sus */
/* align-content: center; toate rândurile centrate vertical */
/* align-content: space-between; primul sus, ultimul jos */
}
Rar folosit, dar util când ai un container mai înalt decât conținutul și vrei control peste distribuția rândurilor.
Problema clasică
Vrei să centrezi un card perfect pe mijlocul ecranului (orizontal ȘI vertical). Ce setări aplici pe container?
Recapitulare
align-items aliniează pe axa transversală (perpendiculară pe direcția principală)
stretch (implicit) — items-urile umplu axa transversală (aceeași înălțime pentru row)
center, flex-start, flex-end — grupare pe axa transversală
baseline — aliniază după linia de bază a textului
align-self suprascrie alinierea pentru un singur item
Centrare perfectă: justify-content: center + align-items: center + înălțime
Lecția 31 din 76
Modulul 4 · Lecția 510 min citire
flex: grow, shrink, basis
Până acum am controlat containerul. Acum învățăm cum să controlezi items-urile individual — cât cresc, cât se micșorează, cât de lat vor fi ideal. Astea sunt cele trei proprietăți care fac layout-urile flex cu adevărat puternice.
Proprietățile pe items
Acestea se aplică pe items, nu pe container:
flex-grow — cât crește un item dacă e spațiu în plus
flex-shrink — cât se micșorează dacă nu e destul spațiu
flex-basis — lățimea ideală înainte de creștere/micșorare
flex-grow — împarte spațiul liber
Dacă ai 3 items-uri și spațiu liber în container, cine primește spațiul?
.item {
flex-grow: 0; /* implicit — nu crește */
}
.item-special {
flex-grow: 1; /* primește tot spațiul liber */
}
Fiecare card pornește la 280px (basis), dar crește să umple rândul (grow: 1) și se poate micșora dacă e nevoie. Plus flex-wrap: wrap le lasă să sară pe rânduri noi. Rezultat: galerie care se reorganizează singură când schimbi lățimea ecranului.
flex: 0 0 auto — dimensiune rigidă
Folosit pentru elemente care trebuie să-și păstreze exact dimensiunea naturală:
.icon {
flex: 0 0 auto; /* exact atât cât e conținutul, nu se schimbă */
}
flex: 1 vs width: 100%
Par similare, dar diferă:
width: 100% — „100% din părinte", ignoră alte items. Poate face elementul să iasă afară.
flex: 1 — „ia tot spațiul rămas după ce celelalte items s-au aranjat". Respectă alte items și nu iese afară.
În context Flexbox, folosește mereu flex: 1 pentru „umple restul", nu width: 100%.
Un layout de aplicație tipică (gândește-te la Slack, Notion): sidebar stâng fix, main central fluid, panel drept fix. Flex face asta cu 3 reguli simple — niciun media query, niciun calculc manual.
Formula potrivită
Vrei un sidebar de 200px fix pe stânga și un main care ocupă tot restul spațiului. Ce pui pe fiecare?
Recapitulare
flex-grow — cât crește un item când e spațiu liber (0 = nu crește)
flex-shrink — cât se micșorează când nu încape (0 = nu se micșorează)
flex-basis — lățimea ideală de pornire
flex: 1 = umple tot spațiul rămas (cea mai comună)
flex: 0 0 200px = dimensiune fixă care nu se schimbă
Timp de ani, spațierea între flex items se făcea cu margini pe copii — și cu tot bagajul de bug-uri și ajustări care veneau cu asta. Apoi a venit gap, cea mai simplă și mai elegantă proprietate din Flexbox modern.
Sintaxa de bază
.container {
display: flex;
gap: 1rem;
}
O linie. Spațiul între toate items-urile e 1rem. Între ele, nu la margini. Nu trebuie să te gândești la primul/ultimul item, nu trebuie margini negative pe container, nu trebuie hack-uri.
Gap pe două axe
Când ai flex-wrap: wrap, poți seta spațieri diferite între rânduri și coloane:
.galerie {
display: flex;
flex-wrap: wrap;
gap: 2rem 1rem;
/* 2rem între rânduri, 1rem între coloane */
}
Sau prescurtat:
.galerie {
gap: 2rem 1rem; /* row-gap column-gap */
}
/* Sau pe proprietăți separate */
.galerie {
row-gap: 2rem;
column-gap: 1rem;
}
De ce e mult mai bun decât margini
Înainte de gap, spațierea se făcea așa:
/* Varianta veche — problematică */
.item {
margin-right: 1rem;
}
.item:last-child {
margin-right: 0; /* ca să nu ai spațiu în plus la final */
}
Probleme cu varianta veche:
Trebuie să știi care e primul sau ultimul (complicat cu flex-wrap)
Margine în plus când items-urile se înfășoară pe rând nou
Nu merge pe două direcții simultan
Multe linii de cod pentru o chestie simplă
Cu gap: o proprietate, totul funcționează.
Gap funcționează și în Grid
gap funcționează identic în CSS Grid (Modulul 5). Ambele folosesc același concept de spațiere între items. Învață odată, folosești peste tot.
Formularul (flex column) are 1.5rem între grupuri. Fiecare grup (flex column) are 0.5rem între label și input. Zero margini scrise manual. Curat.
Când NU folosești gap
Rar, dar există cazuri:
Când vrei spațieri diferite între anumite items (un item special cu margin-top mai mare)
Când ai nevoie de separatori vizibili între items (linii, puncte — deși astea se fac mai bine cu borduri sau pseudo-elemente)
În 95% din cazuri însă, gap e răspunsul corect.
Verificare rapidă
Ai un container flex cu 5 items-uri. Vrei 1rem între fiecare item. Care e cea mai curată soluție?
Recapitulare
gap: 1rem pe flex container = spațiu între toate items-urile
Spațierea e doar între items, niciodată la margini
gap: 2rem 1rem pentru row-gap și column-gap diferite
Funcționează identic în CSS Grid
Înlocuiește complet margini pe copii pentru spațiere
Lecția 33 din 76
Modulul 4 · Lecția 710 min citire
Pattern-uri comune
Cunoști regulile Flexbox. Acum hai să le aplici la layout-urile pe care le vei face în fiecare proiect. Șase pattern-uri care acoperă 90% din munca reală.
Când ai mai multe carduri, butoanele lor vor fi aliniate la aceeași înălțime chiar dacă descrierile diferă. Truc esențial pentru galerii uniforme.
Pattern 3: Sticky footer
„Footer-ul să stea jos de tot, chiar dacă conținutul e scurt":
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
main {
flex: 1; /* main-ul crește să ocupe restul */
}
footer {
/* footer-ul rămâne la dimensiunea lui naturală, dar împins jos */
}
Pattern 4: Galerie de carduri responsive
Pattern-ul care a transformat modul cum scriem layout-uri:
Pattern-ul ăsta chinuia pe toată lumea înainte de Flexbox. Cu Flexbox, câteva linii.
Flexbox are limite — când să folosești Grid
Flexbox e excelent pentru o direcție. Dacă ai nevoie de layout 2D cu rânduri ȘI coloane care se corelează — de exemplu un tabel de dashboard, sau o pagină complexă cu multe zone — Grid e răspunsul (Modulul 5).
Regula simplă: dacă aranjezi într-un rând sau o coloană = Flexbox. Dacă aranjezi într-o matrice 2D = Grid.
.hero {
display: flex;
justify-content: center;
align-items: center;
min-height: 80vh;
padding: 2rem;
text-align: center;
}
.hero-content {
display: flex;
flex-direction: column;
gap: 1.5rem;
max-width: 640px;
}
.cta-group {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap; /* pe mobil sar pe două rânduri */
}
Trei nivele de flex: hero-ul centrează conținutul; conținutul e coloană cu spațiu; butoanele sunt rând. Fiecare nivel face doar ce e responsabil să facă.
Pattern recognition
Ai 3 carduri într-un rând și unele au mai mult conținut. Vrei ca toate să aibă aceeași înălțime. Ce setare de Flexbox face asta automat?
Recapitulare
Navbar: justify-content: space-between + align-items: center
Card uniform cu footer jos: flex-direction: column + flex-grow: 1 pe textul central
Pentru layout 2D complex (nu doar un rând sau o coloană), folosește Grid (Modulul 5)
Lecția 34 din 76
Modulul 4 · Lecția 815 min citire
Proiect: navbar și features
Ai Flexbox. Hai să construim ceva care să fie efectiv util: un navbar profesional urmat de o secțiune de features cu carduri. Două componente pe care le vei refolosi în orice proiect.
Ce construim
Primul: un navbar modern cu logo la stânga, link-uri în centru și un buton CTA la dreapta. Al doilea: o secțiune de features cu 3 carduri aliniate, care se reorganizează natural pe mobil.
Layout-ul e complet responsive fără niciun media query. Iată cum:
flex-wrap: wrap pe grid permite cardurilor să sară pe rânduri noi
flex: 1 1 280px pe fiecare card le dă o lățime minimă (280px) dar le lasă să crească
Pe ecrane mari: 3 carduri pe rând
Pe tabletă: 2 carduri pe rând, unul singur jos
Pe mobil: 1 card pe rând
Totul fără să scrii o singură media query
Cardurile sunt perfect uniforme. Chiar dacă descrierile diferă în lungime:
align-items: stretch (implicit) le face aceeași înălțime
flex-direction: column pe card le organizează vertical
flex-grow: 1 pe paragraf împinge link-ul la fund, uniform pe toate cardurile
Navbar-ul trebuie făcut responsive
Versiunea de mai sus arată bine pe desktop și tabletă, dar pe ecrane sub 640px, link-urile ar trebui să devină un meniu hamburger. Asta implică JavaScript (deschide/închide meniul) și media queries — subiecte pentru modulele viitoare.
Pentru moment, pe mobil probabil vei avea scroll orizontal sau link-urile se vor suprapune. Îl vom repara în Modulul 6 (Responsive Design).
Recapitulare
Navbar pattern: justify-content: space-between + align-items: center
Carduri uniforme ca înălțime: align-items: stretch (implicit Flexbox)
Link în subsol aliniat: flex-direction: column + flex-grow: 1 pe paragraf
Hover cu transform + shadow = feedback vizual plăcut
🎉 Ai terminat Modulul 4!
Stăpânești Flexbox — cea mai folosită unealtă de layout din CSS. Modulul 5 (Grid) e pentru layout-uri 2D complexe.
Lecția 35 din 76
Modulul 5 · Lecția 17 min citire
Ce e Grid
Flexbox aranjează într-o direcție. Grid aranjează în două. Dacă Flexbox e un raft cu cărți, Grid e o bibliotecă cu rafturi pe rânduri și coloane. Ambele utile — dar pentru lucruri diferite.
Diferența fundamentală
Flexbox e uni-direcțional: aranjezi lucrurile ori în rând, ori în coloană. Items-urile curg în flow natural, se înfășoară dacă e nevoie.
Grid e bi-direcțional: definești rânduri ȘI coloane simultan, iar items-urile se plasează într-un sistem de celule — ca un spreadsheet.
Când folosești fiecare
Flexbox pentru:
Navbar-uri
Liste de butoane
Carduri uniforme într-un rând
Formulare verticale
Componente mici cu aranjare liniară
Grid pentru:
Layout-uri de pagină complete (header, sidebar, main, footer)
Dashboard-uri cu zone multiple
Galerii masonry
Orice layout 2D unde elementele se corelează pe ambele axe
Grid și Flexbox se completează
Nu alege unul „împotriva" celuilalt. Proiectele reale folosesc ambele:
Grid pentru layout-ul principal al paginii
Flexbox pentru componentele din interiorul zonelor grid
De exemplu: Grid pentru dashboard (sidebar + header + main + widget area), Flexbox pentru fiecare widget în parte.
Trei coloane egale. Orice copil al container-ului intră într-o celulă, de la stânga la dreapta, pe rânduri. Când umpli 3 celule, se creează automat rândul următor.
Similar ca rezultat pentru 4 carduri pe un rând. Dar Grid-ul iese în față când ai nevoie de rânduri care se corelează — de exemplu, 4 coloane pe 3 rânduri unde cardurile din rândul 2 sunt aliniate cu cele din rândul 1.
Terminologia Grid
Câteva cuvinte pe care le vei întâlni peste tot:
Grid container — elementul pe care ai display: grid
Grid items — copiii direcți ai container-ului
Grid lines — liniile imaginare dintre coloane și rânduri (numerotate 1, 2, 3...)
Grid tracks — coloanele sau rândurile (spațiul între linii)
Grid cells — o singură celulă din grid (intersecția unei coloane cu un rând)
În Flexbox, același layout ar cere nested flex containers și multă gândire. În Grid, definești dimensiunile o dată și plasezi items-urile în zone. Mult mai natural pentru layout-uri complexe.
Alege unealta potrivită
Faci o pagină cu header sus, sidebar stânga, main în centru, panel dreapta și footer jos. Toate trebuie să se alinieze perfect. Ce folosești?
Grid pentru layout-uri de pagină complete, dashboard-uri, orice 2D
Flexbox pentru componente simple într-o direcție
Se folosesc împreună: Grid pentru layout mare, Flexbox pentru componente înăuntru
Terminologia: container, items, lines, tracks, cells, areas
Lecția 36 din 76
Modulul 5 · Lecția 28 min citire
Coloane și rânduri
Grid-ul nu începe cu nimic — îi spui câte coloane, câte rânduri și ce dimensiune au. Două proprietăți simple rezolvă asta: grid-template-columns și grid-template-rows.
Fiecare valoare separată prin spațiu e o coloană. Numărul de valori determină câte coloane sunt.
Unități pe care le poți folosi
px — dimensiune fixă (200px)
% — procent din container (50%)
fr — „fraction" din spațiul rămas (o să vedem în lecția următoare)
auto — exact cât e conținutul
rem / em — relativ la font
/* Amestec de unități */
.layout {
display: grid;
grid-template-columns: 250px 1fr auto;
/* sidebar fix 250px, main care umple, panel ce e cât conținutul */
}
5 carduri, 3 coloane: primele 3 pe rândul 1, următoarele 2 pe rândul 2. Grid umple automat rândurile necesare. Spațierea e consistentă în ambele direcții.
Alege sintaxa corectă
Vrei un grid cu 4 coloane egale. Care e cel mai bun mod de a-l defini?
Recapitulare
grid-template-columns definește câte coloane și ce dimensiune au
grid-template-rows definește rânduri (opțional — se creează automat)
Valorile separate prin spațiu = coloane/rânduri separate
fr e unitatea magică pentru proporții (lecția următoare)
Lecția 37 din 76
Modulul 5 · Lecția 39 min citire
fr, repeat() și minmax()
Trei unelte care fac Grid-ul cu adevărat flexibil: fr pentru proporții, repeat() pentru scriere scurtă, minmax() pentru limite. Împreună, rezolvă 80% din nevoile tale de layout.
Unitatea fr — fracțiuni din spațiu
fr = „fraction" — o parte din spațiul disponibil rămas după ce dimensiunile fixe au fost scăzute.
.layout {
display: grid;
grid-template-columns: 200px 1fr;
/* 200px fix, apoi 1fr ia tot restul */
}
Cu proporții:
.layout {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
/* coloana din mijloc e dublă față de cele laterale */
}
Total e 4fr (1+2+1). Prima ia 1/4, mijlocul ia 2/4, ultima ia 1/4 din spațiul disponibil.
De ce fr e mai bun decât procente
Cu procente, trebuie să socotești gap-urile manual:
/* Problematic — depășește containerul dacă ai gap */
.galerie {
display: grid;
grid-template-columns: 33.33% 33.33% 33.33%;
gap: 1rem; /* acum depășește! */
}
/* Perfect — fr ia în calcul gap-ul */
.galerie {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1rem;
}
fr calculează spațiul rămas după gap și-l împarte. Procentele nu țin cont de gap.
repeat() — scriere scurtă
În loc să scrii 1fr 1fr 1fr 1fr 1fr 1fr, folosești repeat():
minmax(0, 1fr) rezolvă o problemă subtilă: items-urile nu depășesc niciodată container-ul. Fără min: 0, conținut foarte lung (URL-uri, cod) poate face coloanele să crească peste spațiul disponibil.
minmax(0, 1fr) - de ce contează
Implicit, 1fr înseamnă „o parte din spațiu, dar cel puțin cât conținutul meu". Dacă ai un cuvânt foarte lung (ex. URL), coloana crește să-l încapă — rupând layout-ul.
minmax(0, 1fr) îi spune explicit: „minim 0, poți fi atât cât îți spun". Conținutul lung e trunchiat sau se înfășoară, nu rupe layout-ul.
Folosește minmax(0, 1fr) în loc de 1fr când ai conținut dinamic (texte lungi, cod, link-uri).
.dashboard {
display: grid;
grid-template-columns:
minmax(200px, 280px) /* sidebar: între 200 și 280px */
minmax(0, 1fr) /* main: umple restul */
minmax(240px, 320px); /* panel: între 240 și 320px */
gap: 1rem;
min-height: 100vh;
}
Sidebar-ul și panel-ul au limite clare — nu devin prea mici (minim) nici prea mari (maxim). Main-ul umple tot ce rămâne, dar nu depășește (minmax 0, 1fr).
Combinație optimă
Vrei 4 coloane egale care se adaptează la lățimea disponibilă, dar nu mai mici de 250px fiecare. Care sintaxă e cea mai bună?
Recapitulare
fr = fracțiune din spațiul disponibil (după scăderea dimensiunilor fixe)
1fr 2fr 1fr = proporții (1/4, 2/4, 1/4)
repeat(N, valoare) = scriere scurtă pentru N coloane identice
minmax(min, max) = dimensiune cu limite
minmax(0, 1fr) = 1fr care nu depășește container-ul
Cea mai elegantă linie de cod din CSS modern. O singură declarație Grid care transformă automat orice galerie într-una responsive — fără media queries, fără JavaScript. Odată ce o cunoști, o folosești peste tot.
Problema
Cu repeat(4, 1fr) ai mereu 4 coloane, indiferent de ecran. Pe telefon, cardurile devin ilizibil de înguste. Vrei ca numărul de coloane să se schimbe automat în funcție de spațiu.
auto-fit înlocuiește numărul din repeat(). În loc să specifici „4 coloane", spui „cât încap, fiecare minim 280px".
Browser-ul calculează: „container-ul e 1200px, minus gap-uri, am 1184px disponibili. Pot încăpea 4 coloane de 280px fiecare (total 1120px), rămân 64px → le distribui între coloane, fiecare crește la 296px."
Pe un container mai mic (600px): „pot încăpea 2 coloane de 280px, le distribui restul → fiecare devine 290px."
auto-fit vs auto-fill
Două cuvinte, diferență subtilă dar importantă.
auto-fit — coloanele existente cresc să umple spațiul disponibil.
auto-fill — dacă nu sunt destule items, rămân coloane goale.
/* 3 carduri, container 1200px, minmax(280px, 1fr) */
.auto-fit {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
/* 3 carduri, fiecare ~390px (umplu containerul) */
}
.auto-fill {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
/* 3 carduri de 280px + spațiu gol pentru ce ar încăpea */
}
Când folosești care
Pentru galerii normale (carduri de produse, articole, etc.), folosește auto-fit. E ce vrea 90% din lume — items-urile umplu spațiul natural.
auto-fill e util rar — când vrei ca cardurile să rămână la dimensiunea minimă specificată chiar și cu spațiu rămas. De exemplu, o listă cu grid-uri de dimensiune fixă unde „mai multe ar fi încăput".
Regula: când nu ești sigur, folosește auto-fit.
Valoarea magică — minmax + auto-fit
Combinația repeat(auto-fit, minmax(X, 1fr)) e pattern-ul pe care-l vei folosi cel mai des în toată cariera ta CSS:
Pe desktop: 4 cards pe un rând. Pe tabletă: 2 pe un rând, 2 pe al doilea. Pe telefon: stivă verticală. Niciodată cards mai înguste de 200px. Zero media queries.
Pattern recognition
Vrei o galerie de carduri care se adaptează automat la ecran: pe desktop 4 pe rând, pe mobil 1. Fiecare card minim 250px. Care e sintaxa?
Recapitulare
repeat(auto-fit, minmax(X, 1fr)) = grid responsive fără media queries
auto-fit = items-urile existente umplu spațiul
auto-fill = lasă spațiu gol pentru items care ar încăpea (rar folosit)
Folosește auto-fit pentru 95% din cazuri
Singurul lucru de ajustat: valoarea minimă (200px, 280px, etc.)
Un singur rând de CSS înlocuiește 3-4 media queries
Lecția 39 din 76
Modulul 5 · Lecția 59 min citire
Plasarea items-urilor
Până acum, Grid-ul plasa automat fiecare item într-o celulă, în ordine. Dar Grid îți permite și control explicit — să spui „cardul ăsta ocupă 2 coloane și 3 rânduri". Aici Grid devine cu adevărat puternic.
Sistemul de linii
Grid numerotează liniile — nu celulele, ci liniile dintre ele:
/* Pentru 3 coloane, sunt 4 linii de coloană (1, 2, 3, 4) */
/* Linia 1 e la stânga de tot */
/* Linia 4 e la dreapta de tot */
.layout {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
Similar pentru rânduri — dacă ai 3 rânduri, sunt 4 linii (1 sus, 4 jos).
grid-column și grid-row
Pe un item, spui de la ce linie pornește și la ce linie se termină:
.hero {
grid-column: 1 / 4;
/* pornește la linia 1, se termină la linia 4 */
/* adică se întinde pe toate 3 coloanele */
}
Valori negative pentru „ultima linie"
-1 înseamnă „ultima linie", indiferent câte coloane ai:
.full-width {
grid-column: 1 / -1;
/* de la prima la ultima linie = ocupă tot rândul */
}
Pattern foarte folosit pentru header, footer, secțiuni full-width într-un grid.
span — alternativă mai lizibilă
În loc de linii explicite, poți spune „ocupă N coloane":
.item {
grid-column: span 2; /* ocupă 2 coloane, pornind de unde ar fi natural */
}
.card-mare {
grid-column: span 2;
grid-row: span 2;
/* 2x2 — cel mai mare card din grid */
}
Pentru cele mai multe cazuri, span e mai intuitiv decât să numeri linii.
Items-urile normale ocupă 1 celulă. Cele mari ocupă 2x2. Cele late ocupă 2x1. Grid-ul le aranjează automat să umple spațiul.
Alinierea în interiorul celulei
Dacă un item e mai mic decât celula lui, îl poți alinia similar cu Flexbox:
.item {
justify-self: center; /* orizontal */
align-self: center; /* vertical */
/* Sau pe container, pentru toate items-urile */
}
.grid {
justify-items: center;
align-items: center;
}
Exemplu explicat
Dashboard cu hero mare
.dashboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 200px;
gap: 1rem;
padding: 1rem;
}
.widget-hero {
grid-column: span 2;
grid-row: span 2;
/* un widget mare 2x2 */
}
.widget-wide {
grid-column: span 2;
/* un widget lat 2x1 */
}
.widget {
/* normal — 1x1 */
}
Într-un dashboard, statisticile mici sunt 1x1, graficul principal e 2x2, tabelele late sunt 2x1. Grid aranjează totul ca tetris, umplând spațiile disponibile.
Plasează corect
Ai un grid cu 3 coloane. Vrei ca un header să se întindă peste toate 3 coloanele. Care e sintaxa corectă?
grid-column: 1 / -1 (de la prima la ultima) sau grid-column: 1 / 4 (de la linia 1 la linia 4, pentru 3 coloane sunt 4 linii). Varianta A are eroare — merge de la linia 1 la linia 3, adică doar 2 coloane. grid-columns nu există, width nu influențează grid." data-msg-wrong="Nu chiar. Răspunsul corect e B. span 3 = ocupă 3 coloane. Varianta A are o eroare: pentru 3 coloane ai 4 linii, deci trebuie 1 / 4.">
Recapitulare
Grid numerotează liniile între coloane/rânduri, nu celulele
grid-column: 1 / 4 = de la linia 1 la linia 4
grid-column: 1 / -1 = întinde pe toate coloanele
grid-column: span 2 = ocupă 2 coloane (de unde ar fi natural)
grid-row funcționează identic pentru rânduri
Galerii mozaic: combină span pe coloane și rânduri pentru items de dimensiuni diferite
Lecția 40 din 76
Modulul 5 · Lecția 68 min citire
Named grid areas
Cea mai poetică feature din tot CSS-ul. Desenezi layout-ul cu cuvinte, în textul CSS-ului. Chiar și persoane care nu știu CSS pot înțelege ce face. O să vezi.
Ideea
În loc să spui „header-ul merge de la linia 1 la linia 4, sidebar-ul de la linia 1 la linia 2 pe rândul 2", dai nume zonelor și desenezi harta.
Citești CSS-ul și imediat vezi layout-ul: header sus, sidebar+main+panel la mijloc, footer jos. Fără să numeri linii.
Sintaxa grid-template-areas
Fiecare string e un rând. Fiecare cuvânt din string e o celulă.
grid-template-areas:
"a b c"
"d e f";
/* 2 rânduri x 3 coloane, 6 zone */
Îmbinarea celulelor
Repetă numele ca să îmbini celule adiacente:
grid-template-areas:
"header header header"
"nav main main"
"footer footer footer";
„header" apare de 3 ori pe rândul 1 → o singură zonă care ocupă toate 3 coloanele. „main" apare de 2 ori pe rândul 2 → zonă care ocupă 2 coloane.
Celule goale cu punct
Folosește . pentru celule fără conținut:
grid-template-areas:
"header header header"
"nav main ."
"footer footer footer";
/* celula dreapta-sus rămâne goală */
Plasarea items-urilor
Folosești grid-area cu numele zonei:
.main {
grid-area: main;
/* se plasează în zona „main" definită în grid-template-areas */
}
Reguli pentru grid-template-areas
Fiecare rând e un string separat (cu ghilimele)
Toate rândurile trebuie să aibă același număr de celule
Zonele trebuie să fie dreptunghiulare (nu forme ciudate)
Zonele trebuie să fie conectate (nu poți avea „header" în două zone separate)
De ce e atât de puternic
Imaginează-ți că vrei să muți sidebar-ul de la stânga la dreapta. Fără areas:
/* trebuie să schimbi grid-column pe fiecare element */
.sidebar { grid-column: 3; } /* era 1 */
.main { grid-column: 1 / 3; } /* era 2 / 4 */
.panel { grid-column: 1; } /* era 3 */
Cu areas, schimbi doar harta:
grid-template-areas:
"header header header"
"panel main sidebar" /* doar asta s-a schimbat */
"footer footer footer";
/* CSS-ul pe items rămâne identic */
Rearanjare pentru mobil
Combinat cu media queries, areas îți permit să rearanjezi complet layout-ul pentru ecrane mici:
Trei nivele de Grid: dashboard-ul mare (areas), stat cards (auto-fit), content cu 2 coloane. Fiecare nivel rezolvă o problemă specifică cu pattern-ul potrivit.
Alege pattern-ul
Faci o pagină produse cu mulți cards. Vrei să arate bine pe orice ecran fără media queries. Ce pattern folosești?
Imagine lângă text: grid-template-columns: 1fr 1fr
Mozaic: grid-column: span N + grid-row: span N
Formulare: câmpuri pe 2 coloane + câmpuri full cu grid-column: 1 / -1
Centrare: place-items: center
Grid pentru layout, Flexbox pentru componente
Lecția 42 din 76
Modulul 5 · Lecția 815 min citire
Proiect: dashboard
Momentul de glorie al Grid-ului. Construim un dashboard — genul de layout pentru care Grid a fost creat. Sidebar, header, zone de statistici, tabel. Totul aliniat, responsive, fără hack-uri.
Ce construim
Dashboard cu:
Sidebar stâng cu navigație
Header sus cu titlu și user info
4 cards de statistici
O zonă principală cu grafic mare + tabel
Totul construit exclusiv cu Grid și Flexbox, fără media queries.
Dashboard general — grid-template-areas pentru sidebar+top+content
Stats cards — auto-fit + minmax pentru responsive automat
Main grid — 2fr 1fr pentru grafic lat + tabel îngust
Flexbox în interior — sidebar-ul, navigarea, fiecare ordin din tabel, top bar-ul. Grid pentru structura mare, Flexbox pentru componente.
Zero media queries — stats se adaptează automat. Pe desktop mic, dashboard-ul se poate strica — ar trebui un media query la 768px care schimbă grid-template-columns pe dashboard și convertește sidebar-ul într-un hamburger. Asta pentru Modulul 6 (Responsive).
Ce nu am făcut aici
Acesta e un exemplu educațional, nu o aplicație reală. Într-un proiect serios:
Ai avea hamburger menu pentru sidebar pe mobil
Graficul ar fi o bibliotecă reală (Chart.js, D3)
Datele ar veni dintr-un backend, nu hardcodate
Ai avea accesibilitate pentru sidebar-ul de navigare (ARIA labels)
Dar structura Grid rămâne aceeași. De aia merită să înveți bine fundamentele — le vei folosi peste tot.
Recapitulare
Dashboard clasic = Grid areas (sidebar + top + content)
Stats cards = auto-fit + minmax pentru responsive
Content zone = fr proportions (2fr 1fr)
Grid pentru layout mare, Flexbox pentru componente mici
Nivele de Grid nested — fiecare nivel rezolvă o problemă
Proiectul real are nevoie și de Responsive (Modulul 6 urmează)
🎉 Ai terminat Modulul 5!
Flexbox + Grid = acoperi orice layout din frontend modern. Modulul 6 (Responsive Design) adaugă reglajul fin pentru diferite dispozitive.
Lecția 43 din 76
Modulul 6 · Lecția 18 min citire
Ce înseamnă responsive
În 2007, site-urile erau făcute pentru desktop. Când a apărut iPhone-ul, toți am început să zoom-uim și să pan-uim pe site-uri rupte. Azi, peste 60% din traficul web e de pe telefon. A face site-uri care arată bine pe orice ecran nu e opțional — e mesaj de bază.
Responsive design — cele 3 principii
1. Layout fluid — elementele se întind și se adaptează la spațiul disponibil. Fără lățimi fixe mari.
2. Imagini flexibile — imaginile nu depășesc containerul, se scalează.
3. Media queries — reguli CSS care se aplică doar în anumite condiții (ex. ecrane mici).
Mobile-first — filozofia modernă
Cândva, developerii scriau CSS pentru desktop și apoi „reparau" pentru mobil. Astăzi, standardul e invers: scrii întâi pentru mobil, apoi adaugi reguli pentru ecrane mai mari.
/* Base styles — pentru mobil */
.card {
padding: 1rem;
font-size: 0.875rem;
}
/* Pentru tablete și peste */
@media (min-width: 768px) {
.card {
padding: 1.5rem;
font-size: 1rem;
}
}
/* Pentru desktop și peste */
@media (min-width: 1200px) {
.card {
padding: 2rem;
}
}
De ce mobile-first:
Telefonul e cel mai comun device — proiectezi pentru majoritate
Constrângerile te forțează să prioritizezi (ce e esențial pe un ecran mic?)
E mai ușor să adaugi complexitate decât să o îndepărtezi
Performanța e mai bună — încarci doar ce e necesar pe mobil
Meta viewport — pasul obligatoriu
Pe fiecare pagină HTML, trebuie acest tag în <head>:
Fără el, browser-ul mobil pretinde că ecranul e 980px lat, apoi face zoom-out. Pagina ta arată ca o versiune miniatură. Cu el, pagina folosește lățimea reală a ecranului.
Nu uita meta viewport
Cel mai comun bug de responsive: pagina arată ciudat pe telefon pentru că lipsește <meta name="viewport">. Adaugă-l în fiecare HTML. Noi l-am inclus deja în toate exemplele din modulele anterioare.
Layout care merge pe orice ecran
Multe lucruri pe care le-ai învățat deja sunt intrinsec responsive:
Grid cu auto-fit + minmax (Modulul 5) — adaptare automată fără media queries
Flexbox cu flex-wrap: wrap (Modulul 4) — items-urile sar pe rânduri noi
max-width în loc de width (Modulul 3) — elementele nu depășesc ecranul
Unități relative (rem, em, %) (Modulul 2) — scalare naturală
Dacă ai urmat lecțiile, site-urile tale sunt deja parțial responsive. Media queries adaugă ajustări fine.
Exemplu explicat
Structură mobile-first simplă
/* BASE — stilurile pentru mobil (ecrane mici) */
.hero {
padding: 2rem 1rem;
text-align: center;
}
.hero h1 {
font-size: 2rem;
line-height: 1.2;
}
.hero p {
font-size: 1rem;
}
/* De la tabletă în sus, totul se mărește */
@media (min-width: 768px) {
.hero {
padding: 4rem 2rem;
}
.hero h1 {
font-size: 3rem;
}
.hero p {
font-size: 1.125rem;
}
}
/* De la desktop, și mai generos */
@media (min-width: 1200px) {
.hero h1 {
font-size: 4rem;
}
}
Baza funcționează pe telefon. Pe tabletă, padding-ul și fontul cresc. Pe desktop, titlul devine și mai mare. Creșterea e organică, nu salturi bruște.
Strategia potrivită
Care e abordarea recomandată azi pentru a face un site responsive?
Recapitulare
Responsive = site care arată bine pe orice ecran (mobil, tabletă, desktop)
Cele 3 principii: layout fluid + imagini flexibile + media queries
Mobile-first = scrii întâi pentru ecranul cel mai mic, apoi adaugi pentru mai mari
<meta name="viewport"> e obligatoriu
Ce ai învățat deja (Grid, Flexbox, max-width) te duce mult
Lecția 44 din 76
Modulul 6 · Lecția 29 min citire
Media queries
Media queries sunt „dacă-atunci"-urile CSS-ului. „Dacă ecranul are cel puțin 768px, atunci aplică stilurile astea." O sintaxă simplă, un efect enorm asupra cum arată site-urile tale.
Sintaxa de bază
@media (condiție) {
/* reguli CSS care se aplică doar când condiția e adevărată */
.clasa {
proprietate: valoare;
}
}
Cele mai folosite condiții
min-width — ecran de cel puțin X
@media (min-width: 768px) {
/* se aplică pe ecrane de 768px și mai mari */
.header {
padding: 2rem;
}
}
Folosit în mobile-first: scrii baza pentru mobil, apoi „crești" la ecrane mai mari.
max-width — ecran de cel mult X
@media (max-width: 767px) {
/* se aplică pe ecrane de 767px sau mai mici */
.sidebar {
display: none;
}
}
Util pentru ajustări specifice mobilului. Dar în mobile-first, rar folosit — preferi să nu aplici sidebar-ul deloc până când nu e spațiu.
Intervale — combinații
@media (min-width: 768px) and (max-width: 1199px) {
/* doar pe tabletă */
}
@media (min-width: 1200px) {
/* desktop */
}
Orientarea ecranului
@media (orientation: landscape) {
/* telefon ținut pe orizontal */
}
@media (orientation: portrait) {
/* telefon ținut pe vertical */
}
Unii utilizatori au sensibilitate la animații (vertigo, tulburări vestibulare). Sistemul lor operațional le permite să dezactiveze animațiile — tu trebuie să respecți preferința asta.
De ce em: dacă utilizatorul mărește dimensiunea fontului (din preferințe browser), breakpoint-urile se ajustează proporțional. Cu px, rămân fixe indiferent de zoom.
În practică, multe proiecte folosesc px pentru că e mai ușor de citit. Ambele sunt acceptate.
Pe mobil: hamburger vizibil, link-uri ascunse. Pe desktop: link-urile apar, hamburger-ul dispare. Doar CSS — fără JavaScript, doar structura e gata pentru când adaugi JS.
Strategia media query
Scrii mobile-first. Vrei ca un sidebar care nu apare pe telefoane să se afișeze începând cu 900px. Care e media query-ul corect?
Recapitulare
@media (condiție) { ... } = CSS condițional
Mobile-first folosește min-width, nu max-width
Combinație cu and: (min-width: 768px) and (max-width: 1199px)
Alte media queries utile: prefers-color-scheme, prefers-reduced-motion, orientation
em e mai accesibil decât px pentru breakpoints
Lecția 45 din 76
Modulul 6 · Lecția 38 min citire
Breakpoints — unde și cum
„La ce lățime ar trebui să-mi pun media queries?" Întrebarea bună. Răspunsul modern: nu la dimensiuni de telefoane specifice — la punctele unde design-ul tău se rupe.
Abordarea veche — pe device
Înainte, breakpoints-urile urmăreau device-uri populare: iPhone 320px, iPad 768px, desktop 1024px. Problema: prea multe device-uri cu dimensiuni diferite. Telefonul mediu azi e între 375 și 428px lățime, iar tablete există între 600 și 1200px.
Abordarea modernă — pe design
Adaugi un breakpoint când design-ul tău are nevoie. Iei layout-ul de la mobile și crești lățimea browser-ului. Când începe să arate rău (texte prea largi, prea mult spațiu gol, layout prea înghesuit) — ăla e breakpoint-ul.
Mulți au 0 breakpoints. Alții au 6. Nu există un număr magic.
Breakpoints comune
Cu toate astea, există valori convenționale care merg pentru majoritatea proiectelor:
Notă: CSS custom properties nu pot fi folosite direct în media queries. Sunt pentru referință. Breakpoint-urile efective:
/* Tablete și peste */
@media (min-width: 768px) { }
/* Laptop și peste */
@media (min-width: 1024px) { }
/* Desktop lat și peste */
@media (min-width: 1280px) { }
Breakpoints pentru Tailwind (ca referință)
Frameworks-urile populare standardizează breakpoints. Tailwind CSS, cel mai folosit framework utility-first:
sm: 640px
md: 768px
lg: 1024px
xl: 1280px
2xl: 1536px
Dacă lucrezi cu ei, ai un punct de referință. Dacă nu, te poți inspira.
Strategia cu puține breakpoints
Mulți designeri moderni folosesc doar 2 breakpoints:
Mobile (base, 0-767px)
Tablet+ (768px și peste)
Desktop (1200px și peste) — opțional
Cu layout-uri flexibile (Grid auto-fit, Flexbox wrap), adesea ăsta e tot ce ai nevoie. Extra breakpoints adaugă complexitate fără valoare.
Nu pune media queries în multe locuri
Anti-pattern: scrii media queries la finalul fiecărui component. Rezultat: sute de media queries împrăștiate.
Grupează media queries pe breakpoint, nu pe component
Component-specific breakpoints sunt ok (900px pentru navbar, 768px pentru grid)
Lecția 46 din 76
Modulul 6 · Lecția 49 min citire
Viewport units & clamp()
Media queries sunt foarte folositoare, dar sunt sărituri bruște — la 768px pur-și-simplu se schimbă totul. Uneori vrei ceva mai fluid: tipografie care crește gradual cu ecranul, spații care se ajustează natural. Aici intră viewport units și clamp().
Viewport units — recapitulare
Unitățile legate de dimensiunea viewport-ului browser-ului:
vw — 1% din lățimea viewport-ului
vh — 1% din înălțimea viewport-ului
vmin — 1% din cea mai mică dimensiune
vmax — 1% din cea mai mare dimensiune
.hero {
min-height: 100vh; /* tot ecranul pe vertical */
}
.title {
font-size: 5vw; /* scalează cu lățimea ecranului */
}
Dynamic viewport units — moderne
Pe mobil, bara de URL a browser-ului apare și dispare când scrollezi. Asta schimbă înălțimea viewport-ului. 100vh se referă la cea mare (fără bara), ceea ce cauzează probleme.
Soluția modernă:
dvh — dynamic viewport height (se ajustează cu bara)
svh — small viewport height (cu bara vizibilă)
lvh — large viewport height (fără bara)
.hero {
min-height: 100dvh; /* funcționează corect pe mobil modern */
}
Problema cu vw direct
Dacă setezi font-size: 5vw, pe un ecran de 1920px ai 96px (uriaș), pe un ecran de 375px ai 18.75px (acceptabil). Variația e prea mare.
Plus, fontul scalează la infinit. Pe un monitor 4K, titlul devine absurd. Avem nevoie de limite.
clamp() — dimensiunea cu limite
clamp(min, preferat, max) îți dă o valoare care:
Nu e niciodată mai mică decât min
Nu e niciodată mai mare decât max
Între acestea, folosește preferat
.title {
font-size: clamp(2rem, 5vw, 4rem);
}
Traducere: „cel puțin 2rem (32px), cel mult 4rem (64px), iar între, 5% din lățimea ecranului".
De ce e revoluționar
Cu clamp, tipografia se scalează continuu — fără salturi la breakpoints. Pe orice ecran, titlul are exact dimensiunea potrivită.
Înlocuiește complet 2-3 media queries pentru font-size:
Sistem de tipografie care scalează continuu. Pe orice ecran, totul are o relație proporțională. Fără niciun media query. Pot fi folosite peste tot ca variabile CSS.
Aplică clamp
Vrei titlul hero să fie cel puțin 32px pe mobil, să scaleze cu lățimea, dar să nu treacă de 80px pe ecrane mari. Care e expresia corectă?
Recapitulare
vw, vh scalează cu viewport-ul (dar la infinit)
dvh e înălțimea dinamică (ajustare pentru bara URL mobil)
clamp(min, preferat, max) = dimensiune cu limite superioară și inferioară
Tipografie fluidă = clamp pentru font-size, înlocuiește 2-3 media queries
Formula bună: clamp(Xrem, Yrem + Zvw, Wrem)
Combină responsive (media queries pentru layout) cu fluid (clamp pentru dimensiuni)
Lecția 47 din 76
Modulul 6 · Lecția 58 min citire
Imagini responsive
O imagine de 2400x1600 pe telefon e 3MB descărcați degeaba. Pe desktop, o imagine de 400x300 arată ca dintr-un joc din 1998. Responsive nu înseamnă doar „text care se adaptează" — imaginile au propriile tehnici.
Asta previne ca imaginile să depășească container-ul. height: auto menține proporțiile. Aplicat la toate imaginile, rezolvă 80% din problemele responsive.
object-fit — umplere controlată
Dacă dai unei imagini dimensiuni fixe (pentru cards uniforme), ai nevoie de object-fit:
Browser-ul alege imaginea cea mai potrivită în funcție de:
Lățimea viewport-ului
Densitatea pixelilor ecranului (retina)
Condițiile de rețea
Atributul sizes explicat
Spune browser-ului cât spațiu va ocupa imaginea în layout:
sizes="(max-width: 640px) 100vw, 50vw"
/* Pe ecrane sub 640px, imaginea ocupă 100% din viewport.
Altfel, ocupă 50%. */
Browser-ul folosește asta să aleagă ce dimensiune să descarce. Dacă imaginea va ocupa 50% din ecran, nu descărca versiunea full-width.
Picture — art direction
Uneori vrei crop-uri diferite pentru ecrane diferite, nu doar dimensiuni. De exemplu: pe desktop arăți o imagine lată (16:9), pe mobil una pătrată (1:1):
Browser-ul încearcă AVIF primul. Dacă nu suportă, încearcă WebP. Dacă nu suportă nici aia, JPG-ul ca fallback. Utilizatorii cu browsere moderne primesc fișiere mai mici.
Lazy loading — gratuit și important
Imaginile de sub „fold" (nu vizibile la încărcare) pot fi încărcate doar când utilizatorul scroll-ează aproape:
<img src="foto.jpg" alt="..." loading="lazy">
O linie. Browser-ul face treaba. Site-ul se încarcă semnificativ mai rapid pe pagini cu multe imagini (galerii, blog-uri).
Nu lazy-load prima imagine de deasupra fold-ului (hero) — ar vedea un moment de întârziere.
Pare complex dar e logic: AVIF (cel mai mic) → WebP (mediu) → JPG (fallback). Fiecare format oferă 3 dimensiuni. Browser-ul alege combinația optimă. Imaginile sunt lazy-loaded. Dimensiunile previn layout shift.
CSS minim pentru imagini
Care e regula CSS de bază pe care ar trebui să o ai pentru toate imaginile dintr-un site responsive?
object-fit: cover pentru imagini cu dimensiuni fixe
srcset + sizes pentru versiuni multiple la rezoluții diferite
<picture> pentru art direction (crop-uri diferite) sau formate multiple
WebP și AVIF sunt mai eficiente decât JPG/PNG
loading="lazy" pentru imagini sub fold
Specifică width și height pentru a preveni layout shift
Lecția 48 din 76
Modulul 6 · Lecția 68 min citire
Container queries
Media queries răspund la dimensiunea viewport-ului. Dar dacă un card arată aceeași variantă într-un sidebar îngust și într-o zonă principală lată? Media queries nu pot distinge. Container queries pot. E cel mai important feature CSS din ultimul deceniu.
Problema cu media queries
Să zicem că ai un component „card de produs" cu imagine și text. Îl folosești în două locuri:
În sidebar (300px lățime) — text sub imagine, compact
În main content (800px lățime) — text lângă imagine, relaxat
Cu media queries, nu poți face asta ușor. La 800px viewport, sidebar-ul are tot 300px, dar media query-ul privește viewport-ul. Cardul din sidebar va arăta la fel ca cel din main.
Container queries — soluția
Container queries îți permit să aplici stiluri bazat pe dimensiunea container-ului părinte, nu a viewport-ului.
Acum cardul se adaptează la lățimea container-ului său, nu la viewport. Într-un sidebar de 300px, e vertical. Într-o zonă de 800px, e orizontal. Același component, contextual.
@container (min-width: 400px) {
/* fără nume — se aplică containerului cel mai apropiat */
.card {
flex-direction: row;
}
}
@container card (min-width: 400px) {
/* cu nume — se referă explicit la containerul „card" */
.card {
flex-direction: row;
}
}
În orice pagină, cardul se adaptează la spațiul disponibil. Fără să știi unde e folosit.
Unități de container — cqw, cqh
Similar cu vw/vh, dar relativ la container:
cqw — 1% din lățimea container-ului
cqh — 1% din înălțimea container-ului
cqi — inline (de obicei lățime)
cqb — block (de obicei înălțime)
.card-title {
/* scalează cu container-ul, nu cu viewport-ul */
font-size: clamp(1rem, 4cqw, 2rem);
}
Container queries vs media queries — când care
Ambele sunt utile:
Media queries pentru schimbări la nivel de pagină — layout general, sidebar show/hide, navbar hamburger
Container queries pentru componente reutilizabile care trebuie să se adapteze la context
Container queries nu înlocuiesc media queries — le completează.
Suport browser
Container queries sunt suportate în orice browser modern din 2023. Chrome, Safari, Firefox, Edge — toate merg. Dacă proiectul tău țintește browsere foarte vechi (mai vechi de 2 ani), verifică pe caniuse.com.
Exemplu explicat
Widget reutilizabil care se adaptează
/* Widget de statistici utilizabil în orice context */
.stat-widget-wrapper {
container-type: inline-size;
}
.stat-widget {
padding: 1rem;
background: white;
border-radius: 8px;
}
.stat-value {
font-size: 1.5rem;
font-weight: 500;
}
.stat-label {
font-size: 0.875rem;
color: #666;
}
/* Pe container mai mare de 300px, mărim */
@container (min-width: 300px) {
.stat-value {
font-size: 2rem;
}
.stat-label {
font-size: 1rem;
}
}
/* Pe container mai mare de 500px, layout orizontal */
@container (min-width: 500px) {
.stat-widget {
display: flex;
align-items: baseline;
gap: 1rem;
}
.stat-value {
font-size: 3rem;
}
}
Același widget. În sidebar compact, arată small-stacked. În dashboard lat, arată big-horizontal. Decizia ta e bazată pe spațiul disponibil, nu pe unde e plasat.
Alege corect
Ai un component „card articol" folosit atât pe homepage (lătime mare) cât și în sidebar (lățime mică). Vrei ca în spațiu mic să arate vertical și în spațiu mare să arate orizontal. Ce folosești?
Recapitulare
Container queries răspund la dimensiunea container-ului părinte, nu a viewport-ului
Utile pentru componente reutilizabile în contexte diferite
Pas 1: container-type: inline-size pe părinte
Pas 2: @container (min-width: X) pentru reguli
Unități noi: cqw, cqh, cqi, cqb
Nu înlocuiesc media queries — se completează
Suport modern browser din 2023
Lecția 49 din 76
Modulul 6 · Lecția 714 min citire
Proiect: landing responsive
Facem un landing page complet responsive — genul pe care l-ai pune pentru orice produs sau serviciu. Arată profesional pe telefon, tabletă și desktop. Mobile-first, cu tipografie fluidă și fără bug-uri.
Tipografia e fluidă — clamp pe toate dimensiunile de text. Scalează continuu de la mobil la desktop mare.
Spațierea e fluidă — --space-section și --space-inline se ajustează cu ecranul.
Features-grid e auto-responsive — auto-fit + minmax face treaba, fără media queries.
Un singur breakpoint — doar navbar-ul are media query (pentru hamburger vs full menu).
Butoanele se înfășoară — flex-wrap: wrap pe hero-ctas.
Ce mai trebuie adăugat
Pentru un proiect real, mai ai de făcut:
JavaScript pentru hamburger menu (deschide/închide)
Animații la scroll (deja cu prefers-reduced-motion guard)
Formular de înscriere funcțional
Analytics
Optimizare imagini (dacă adaugi)
Dar structura responsive e gata. HTML-ul și CSS-ul de aici pot fi baza unui site real.
Recapitulare
Design tokens cu variabile CSS + clamp = tipografie fluidă la scară întregului site
Un singur breakpoint (768px) pentru navbar — restul e deja fluid
Grid auto-fit face features responsive gratuit
Butoane cu flex-wrap = adaptare automată pe mobil
Spațierea fluidă cu clamp dă site-ului ritm natural pe orice ecran
🎉 Ai terminat Modulul 6!
Site-urile tale funcționează pe orice ecran. Urmează Modulul 7 (Selectori avansați) — unde CSS-ul devine cu adevărat expresiv.
Lecția 50 din 76
Modulul 7 · Lecția 18 min citire
Combinatori
Până acum ai folosit selectori simpli — după clasă, tag sau id. Combinatorii îți permit să spui „acest element, dar numai când e într-un anumit context". E diferența între a stiliza toate link-urile și a stiliza doar link-urile dintr-un nav.
Cei patru combinatori
În CSS există 4 combinatori, fiecare cu sintaxă și scop diferite:
Spațiu — descendent (oricât de adânc)
> — copil direct
+ — adjacent (fratele imediat)
~ — general sibling (toți frații)
Spațiu — descendent
Cel mai comun. „Toate X-urile care sunt înăuntrul Y-ului, indiferent cât de adânc":
nav a {
color: var(--color-accent);
}
/* Toate <a> dinăuntrul unui <nav> */
<nav>
<ul>
<li>
<a href="#">Link</a> <!-- afectat, chiar dacă e adânc -->
</li>
</ul>
</nav>
<a href="#">Alt link</a> <!-- NU e afectat, e în afara nav -->
Util pentru navigare multi-nivel unde vrei să stilizezi doar elementele de top, nu și sub-meniurile.
+ — adjacent sibling
Elementul care vine imediat după un alt element (același părinte):
h2 + p {
font-size: 1.125rem;
color: var(--color-text-soft);
}
<h2>Titlu</h2>
<p>Primul paragraf — devine lead</p> <!-- ✓ afectat -->
<p>Al doilea paragraf</p> <!-- ✗ NU e afectat -->
Util pentru „intro paragraph" după titlu, separatoare între items, etc.
~ — general sibling
Toți frații care vin după un element, nu doar primul:
h2 ~ p {
color: var(--color-text-soft);
}
<h2>Titlu</h2>
<p>Primul paragraf</p> <!-- ✓ -->
<p>Al doilea paragraf</p> <!-- ✓ -->
<div>Un div între</div>
<p>Al treilea paragraf</p> <!-- ✓ (încă după h2) -->
<h1>Alt titlu</h1>
<p>Ultim paragraf</p> <!-- ✓ (încă după primul h2) -->
Combinații multiple
Poți înlănțui combinatori:
.sidebar > ul li a:hover {
/* Link-uri cu hover, în orice li, care e copil direct al ul,
care e copil direct al .sidebar */
}
Puternic, dar folosit cu grijă. Cu cât selectorul e mai specific, cu atât e mai greu de suprascris.
Combinatori vs clase BEM
Multe proiecte moderne evită combinatorii adânci în favoarea claselor explicite (BEM, Tailwind):
/* Cu combinatori — elegant dar fragil */
.card .header .title {
font-size: 1.5rem;
}
/* Cu clase — verbose dar robust */
.card-title {
font-size: 1.5rem;
}
Pentru componente mari, clase explicite sunt mai lizibile. Pentru situații punctuale (primul paragraf după h2, link-uri în nav), combinatori sunt ideali.
Exemplu explicat
Blog post cu tipografie contextuală
article {
max-width: 65ch;
}
/* Primul paragraf după fiecare h2 e lead */
article h2 + p {
font-size: 1.25rem;
color: var(--color-text-soft);
}
/* Paragraf imediat după listă are margin extra */
article ul + p,
article ol + p {
margin-top: 2rem;
}
/* Doar link-urile din articol, nu cele din nav/footer */
article a {
color: var(--color-accent);
text-decoration: underline;
}
Combinatorii îți dau control fin asupra tipografiei fără să adaugi clase peste tot. Blog post-urile arată tipărit, nu generic.
Ce selectează
Ce selectează .card > h2?
Recapitulare
Spațiu = descendent (A B)
> = copil direct (A > B)
+ = frate imediat (A + B)
~ = toți frații după (A ~ B)
Pentru componente mari, preferă clase explicite
Pentru tipografie contextuală, combinatorii sunt ideali
Lecția 51 din 76
Modulul 7 · Lecția 210 min citire
Pseudo-classes
Pseudo-classes îți permit să stilizezi elemente bazat pe stare sau poziție — lucruri care nu sunt reflectate direct în HTML. Hover, focus, primul copil, al n-lea element. Fără ele, interactivitatea CSS-ului ar fi moartă.
/* Al 3-lea copil */
li:nth-child(3) { color: red; }
/* Toți copiii pari */
li:nth-child(even) { background: #f5f5f5; }
/* Toți copiii impari */
li:nth-child(odd) { background: white; }
/* Formula an+b */
li:nth-child(3n) { /* al 3-lea, al 6-lea, al 9-lea... */ }
/* Primii 3 */
li:nth-child(-n+3) { /* 1, 2, 3 */ }
/* De la al 4-lea încolo */
li:nth-child(n+4) { }
:nth-of-type — similar dar pentru tag
Diferența subtilă dar importantă:
p:nth-child(2) = „al doilea copil, dacă e p"
p:nth-of-type(2) = „al doilea p din părinte"
<div>
<h2>Titlu</h2> <!-- copil 1, tip h2 nr 1 -->
<p>P1</p> <!-- copil 2, tip p nr 1 -->
<p>P2</p> <!-- copil 3, tip p nr 2 -->
</div>
/* p:nth-child(2) = P1 (al doilea copil, care e p) */
/* p:nth-of-type(2) = P2 (al doilea p) */
Alte pseudo-classes utile
:empty — elementul nu are copii/conținut
:not(selector) — exclude elemente
:is(s1, s2, s3) — unul din selectori (mai scurt decât a scrie toate)
:where(...) — ca :is dar fără impact pe specificitate
/* Toate butoanele care NU au clasa „primary" */
button:not(.primary) {
background: transparent;
border: 1px solid #ddd;
}
/* Headers mai concis */
:is(h1, h2, h3) {
font-family: var(--font-display);
}
/* Echivalent cu: h1, h2, h3 { ... } dar mai ușor de extins */
Transition mereu cu :hover
Schimbările bruște între stări sunt urâte. Adaugă mereu transition:
Rânduri alternante pentru lizibilitate, hover pentru interacțiune, borduri mai groase la extremități pentru separare vizuală. Totul fără să adaugi clase pe fiecare rând.
Aplică corect
Vrei să stilizezi ultimul link dintr-un nav diferit (fără border-right). Care e cel mai curat mod?
Adaugă mereu transition pentru schimbări fluide (150-250ms)
Nu scoate outline fără să pui alt indicator de focus
Lecția 52 din 76
Modulul 7 · Lecția 39 min citire
Pseudo-elements
Pseudo-elements îți permit să stilizezi părți ale elementelor care nu există în HTML — prima literă, prima linie, conținut înainte sau după. Cu ele adaugi decorațiuni, iconițe și detalii fără să aglomerezi HTML-ul.
Două colon-uri vs unul
Pseudo-classes folosesc un colon (:hover). Pseudo-elements folosesc două (::before). Diferența vizibilă.
Moștenire istorică: în CSS 2, se folosea un colon pentru ambele. CSS 3 a introdus :: pentru pseudo-elements. Sintaxa veche încă funcționează dar folosește :: pentru claritate.
::before și ::after — conținut generat
Cele mai folosite pseudo-elements. Adaugă conținut înainte sau după, fără să atingi HTML-ul:
content e obligatoriu — fără el, pseudo-element-ul nu apare. Poate fi text, gol ("") pentru forme geometrice, sau attr() pentru a lua valori din atribute.
Tooltip pur-CSS, fără JavaScript. attr(data-tooltip) extrage valoarea atributului și o afișează.
::first-letter și ::first-line
Pentru efecte tipografice clasice:
/* Drop cap ca în carte */
article p:first-child::first-letter {
font-family: var(--font-display);
font-size: 4rem;
float: left;
line-height: 0.9;
margin-right: 0.5rem;
color: var(--color-accent);
}
/* Prima linie în small-caps */
p::first-line {
font-variant: small-caps;
color: var(--color-text-soft);
}
Două decorațiuni vizuale fără niciun HTML extra. Accentul linear e pe fiecare card. Badge-ul „NEW" apare doar pe cele cu clasa .new.
Pseudo-element corect
Vrei să adaugi o săgeată „→" automat după toate link-urile cu clasa .external. Ce sintaxă folosești?
Recapitulare
:: pentru pseudo-elements (nu : care e pentru pseudo-classes)
content e obligatoriu pe ::before și ::after
content: "" pentru decorațiuni, content: "text" pentru text
attr(nume) extrage valoare din atribute HTML
Alte pseudo-elements: ::first-letter, ::selection, ::placeholder, ::marker
Nu funcționează pe <img>, <input>, <br>
Lecția 53 din 76
Modulul 7 · Lecția 48 min citire
:has() — selectorul părinte
Pentru 20 de ani, CSS n-a avut selector părinte. „Stilizează un card dacă conține o imagine" — imposibil în CSS. Trebuia JavaScript. În 2023, :has() a schimbat asta. E probabil cel mai important feature CSS din ultimii 10 ani.
Problema
Să zicem că ai carduri. Unele au imagine, altele nu. Vrei cardurile cu imagine să aibă un layout diferit — imaginea sus, textul jos.
Înainte de :has(), soluții:
Adăugai o clasă specială (.card-with-image) manual — muncă și fragil
Foloseai JavaScript să detectezi și adaugi clasa — complexitate inutilă
Traducere: „Cardurile care conțin o imagine". CSS pur, zero HTML modificări, zero JavaScript.
Sintaxa
parinte:has(copil) {
/* stilizezi părintele pe baza a ceea ce conține */
}
Exemple practice
Card cu badge
/* Card cu badge „NEW" primește margin extra sus */
.product:has(.badge-new) {
margin-top: 2rem;
}
Form cu erori
/* Form-ul devine roșu dacă are erori */
form:has(input:invalid) {
border-color: red;
}
/* Buton submit dezactivat vizual dacă form-ul nu e valid */
form:has(input:invalid) .submit-btn {
opacity: 0.5;
pointer-events: none;
}
Section cu titlu
/* Section care conține h2 primește padding mai mare */
section:has(h2) {
padding-top: 4rem;
}
/* Section fără titlu rămâne cu padding normal */
section:not(:has(h2)) {
padding-top: 2rem;
}
Nav cu sub-meniu
/* Item de nav care are sub-meniu primește săgeată */
nav li:has(ul)::after {
content: " ▼";
font-size: 0.7em;
}
Combinare cu alte selectori
:has() poate fi combinat cu orice:
/* Card care conține O imagine ȘI un tag de preț */
.product:has(img):has(.price) {
border: 2px solid var(--color-accent);
}
/* Card care NU conține imagine */
.product:not(:has(img)) {
background: #f5f5f5;
}
/* Article care are h2 + p imediat după */
article:has(h2 + p) {
/* stilizare specifică */
}
Selectoare complexe înăuntrul :has()
/* Form care conține un input cu eroare */
form:has(input.error) {
border-color: red;
}
/* Card care conține cel puțin 3 imagini */
.gallery:has(img:nth-of-type(3)) {
grid-template-columns: repeat(3, 1fr);
}
/* Body care conține un modal activ */
body:has(.modal.active) {
overflow: hidden; /* oprește scroll-ul paginii */
}
Un exemplu real — dark mode automat
Poți face dark mode care se activează când un checkbox e bifat, fără JavaScript:
Când checkbox-ul e bifat, :has() schimbă variabilele CSS pe body. Toată pagina devine dark mode. Fără JavaScript, fără reload.
Suport browser
:has() e suportat în toate browserele moderne din 2023. Chrome 105+, Safari 15.4+, Firefox 121+, Edge modern. Dacă proiectul tău nu trebuie să suporte Firefox vechi, poți folosi :has().
Exemplu explicat
Layout dinamic de grid bazat pe conținut
.gallery {
display: grid;
gap: 1rem;
}
/* Dacă are 1 item, un singur card mare */
.gallery:has(> :only-child) {
grid-template-columns: 1fr;
}
/* Dacă are 2 items, split în 2 */
.gallery:has(> :nth-child(2):last-child) {
grid-template-columns: 1fr 1fr;
}
/* Dacă are 3 items, 2+1 */
.gallery:has(> :nth-child(3):last-child) {
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
}
.gallery:has(> :nth-child(3):last-child) > :first-child {
grid-column: 1 / -1;
}
/* 4+ items — grid responsive normal */
.gallery:has(> :nth-child(4)) {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
Galeria își schimbă layout-ul bazat pe câte items are. Imposibil fără :has() în CSS pur înainte de 2023.
Scenariu
Vrei să marchezi vizual (background galben) orice card care conține un label „Urgent". Cum faci?
Recapitulare
:has() = selectorul părinte, absent din CSS 20 de ani
Sintaxă: părinte:has(copil) { ... }
Funcționează cu orice selectori înăuntru (clase, stări, combinatori)
Cazuri de utilizare: layout contextual, form validation, dark mode
Suport modern browser din 2023 încoace
Înlocuiește foarte multă JavaScript condițional
Lecția 54 din 76
Modulul 7 · Lecția 59 min citire
Specificitate și @layer
„De ce nu se aplică CSS-ul meu?!" Cea mai comună întrebare a începătorilor. Răspunsul aproape mereu: specificitate. În lecția asta demistificăm regulile și învățăm @layer, soluția modernă pentru probleme de specificitate.
Ce e specificitatea
Când mai multe selectori targetează același element, CSS trebuie să decidă care câștigă. Regula: selectorul mai specific câștigă. Dacă sunt la fel de specifici, cel care apare mai târziu în CSS câștigă.
Sistemul de puncte
Specificitatea se calculează pe 4 coloane (0-0-0-0):
Inline styles (style="...") = 1-0-0-0
ID-uri (#header) = 0-1-0-0
Clase, atribute, pseudo-classes = 0-0-1-0
Elemente, pseudo-elements = 0-0-0-1
Exemple de calcul
/* 0-0-0-1 (un element) */
p { color: red; }
/* 0-0-1-0 (o clasă) */
.text { color: blue; }
/* 0-0-1-1 (o clasă + un element) */
p.text { color: green; }
/* 0-0-2-1 (două clase + un element) */
.article p.text { color: purple; }
/* 0-1-0-0 (un id) */
#main { color: orange; }
/* 0-1-1-1 (id + clasă + element) */
#main p.text { color: black; }
Cu cât mai sus în ierarhie, cu atât mai puternic. Un ID bate orice număr de clase.
De ce să eviți IDs pentru stilizare
IDs au specificitate atât de mare încât greu le suprascrii:
Accesibilitate critică (focus indicator care nu trebuie ascuns)
@layer — soluția modernă
Din 2022, CSS are @layer — o cale de a controla specificitatea fără hack-uri. Definești straturi în ordinea dorită, iar regulile din straturile ulterioare bat pe cele din straturile anterioare, indiferent de specificitatea selectoarelor.
Selectorii de atribute îți permit să targetezi elemente bazat pe ce atribute au — href, type, data-*. Combinate cu tot ce ai învățat, construiești selectori care fac magie fără JavaScript.
Atribute de bază
/* Element cu atribut X (indiferent de valoare) */
a[target] { ... }
input[required] { ... }
/* Element cu atribut = valoare exactă */
a[target="_blank"] { ... }
input[type="email"] { ... }
a[href="https://example.com"] { ... }
Tipuri de input în CSS
Selectorii de atribute fac posibilă stilizarea distinctă pentru tipuri de input:
Selectorii de atribute au aceeași specificitate ca clasele (0-0-1-0):
.primary { background: blue; } /* 0-0-1-0 */
[type="submit"] { background: red; } /* 0-0-1-0 */
/* Dacă un buton are ambele, cel mai târziu în CSS câștigă */
Fiecare tip de link are aspect distinct, identificat doar după atribute. Zero clase adăugate manual pe link-uri. Infrastructura HTML semantică (aria-current, href) e suficientă.
Selectează corect
Vrei să stilizezi toate link-urile care trimit la fișiere ZIP. Cum faci?
Recapitulare
Selectori de atribute: [attr], [attr="val"], [attr^="x"], [attr$="x"], [attr*="x"]
Tipurile de input se selectează cu input[type="..."]
Link-uri externe cu a[href^="https://"]
Fișiere cu a[href$=".pdf"]
Atributele data-* sunt ideale pentru state-uri custom
Tooltips pur-CSS cu attr() și atribute data-*
🎉 Ai terminat Modulul 7!
Ai acum toate uneltele de selectat din CSS modern. Urmează Modulul 8 — Animații și tranziții, unde pagina prinde viață.
Lecția 56 din 76
Modulul 8 · Lecția 19 min citire
Transitions — fundamente
Un buton care își schimbă culoarea brusc la hover pare stricat. Același buton cu o tranziție de 200ms pare profesional. Diferența: trei cuvinte de CSS. transition e cel mai ieftin „efect wow" pe care-l poți adăuga unui site.
Ce face transition
Spune browser-ului: „când o proprietate se schimbă, nu face saltul instant — fă o animație lină între vechea și noua valoare".
Fiecare proprietate cu propria durată și curbă. Transform-ul se mișcă cu 200ms, umbra cu 300ms — mișcări care se suprapun dau impresia de profunzime.
transition: all — evitat
/* Problematic */
.button {
transition: all 300ms;
}
transition: all animează orice schimbare — chiar și cele pe care nu le vrei animate. Performanță proastă, comportament imprevizibil. Folosește mereu proprietăți explicite.
Ce poate fi animat
Nu toate proprietățile se pot anima. Regulă: proprietăți care au valori continue (numere, culori) se animează. Proprietăți cu valori discrete (display, position) nu.
Se animează:
Culori (color, background, border-color)
Dimensiuni (width, height, padding, margin)
Opacitate (opacity)
Transform-uri (translate, rotate, scale)
Border, shadow, filter
Nu se animează direct:
display — dar poți combina cu opacity + transition-delay
Schimbări de tip (de la flex la block)
Performanță: transform și opacity
Nu toate animațiile sunt egale. Browser-ul poate accelera transform și opacity pe GPU — mișcări fluide chiar pe mobile lent.
Alte proprietăți (width, height, margin) cer recalculare layout — mai lent, mai sacadat.
Pentru animații fluide, folosește transform și opacity când e posibil.
Durate: 150-200ms pentru hover, 300-400ms pentru transform-uri
ease-out e cel mai folosit timing pentru UI
Animează mai multe proprietăți separat, nu cu all
Transform și opacity sunt cele mai performante
Durate diferite pe proprietăți diferite = sensație de profunzime
Lecția 57 din 76
Modulul 8 · Lecția 29 min citire
Transform
transform e o proprietate magică. Mută, rotește, mărește sau deformează elemente fără să afecteze layout-ul din jur. Accelerat de GPU, fluid pe orice device. Dacă transition e motorul animațiilor, transform e materia lor primă.
Cele 4 funcții principale
translate() — mutare
.box {
transform: translateX(20px); /* la dreapta cu 20px */
transform: translateY(-10px); /* sus cu 10px */
transform: translate(20px, -10px); /* ambele */
}
Spre deosebire de margin sau position, translate nu afectează alte elemente. Elementul „planează" peste pagină în poziția translate-ată. Perfect pentru hover effects, drag-and-drop, etc.
scale() — mărire/micșorare
.box {
transform: scale(1.1); /* cu 10% mai mare */
transform: scale(0.9); /* cu 10% mai mic */
transform: scale(1.5, 2); /* mai lat 1.5x, mai înalt 2x */
}
Valoarea 1 = dimensiune originală. 2 = dublu. 0.5 = jumătate. Util pentru hover pe cards, micro-interacțiuni.
rotate() — rotație
.box {
transform: rotate(45deg);
transform: rotate(-90deg);
transform: rotate(0.25turn); /* un sfert de rotație */
}
skew() — deformare
.box {
transform: skew(20deg); /* înclinare pe X */
transform: skewY(10deg); /* înclinare pe Y */
}
Folosit rar — de obicei pentru efecte vizuale decorative.
Combinarea transform-urilor
Poți combina mai multe transform-uri într-o singură declarație:
Ordinea contează. Translate apoi rotate e diferit de rotate apoi translate. Experimentează să vezi diferența.
transform-origin — punctul de pivotare
Implicit, transformele se fac în jurul centrului elementului. Poți schimba asta:
.box {
transform-origin: top left; /* pivot în colț stânga-sus */
transform: rotate(45deg);
/* acum rotația e din colțul stâng-sus, nu din centru */
}
Valori: top, bottom, left, right, center, procente, sau coordonate.
Tranziție + Transform = UI modern
Combinate, construiesc efectele pe care le vezi pe site-urile moderne:
/* Card care „se ridică" la hover */
.card {
transition: transform 200ms ease-out;
}
.card:hover {
transform: translateY(-6px);
}
/* Buton care se mărește subtil */
.btn {
transition: transform 150ms ease-out;
}
.btn:hover {
transform: scale(1.05);
}
/* Icon care se rotește la hover */
.icon {
transition: transform 300ms ease-out;
}
.icon:hover {
transform: rotate(180deg);
}
Transform vs poziție reală
Un aspect important: transform mută vizual elementul, dar pentru browser, elementul e în aceeași poziție ca înainte.
Hit detection (click) e tot în poziția vizuală (modernă)
Alte elemente nu observă mutarea (nu se rearanjează)
Perfect pentru animații — nimic nu sare pe pagină
Asta îl face diferit de position: relative; top: X care afectează layout-ul în timpul animației.
Transform-uri 3D
Pentru efecte mai dramatice, CSS suportă și 3D:
.card {
perspective: 1000px; /* setează perspective pe părinte */
}
.card-inner {
transform: rotateY(180deg); /* rotație 3D */
transform-style: preserve-3d;
}
/* Efecte 3D comune */
.flip:hover {
transform: rotateY(180deg);
}
.tilt:hover {
transform: perspective(500px) rotateX(10deg) rotateY(-10deg);
}
Exemplu explicat
Card interactiv profesional
.product-card {
padding: 2rem;
background: white;
border-radius: 12px;
cursor: pointer;
transition:
transform 300ms cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 300ms ease-out;
/* Box-shadow inițială subtilă */
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.product-card:hover {
/* Se ridică și se înclină subtil */
transform: translateY(-6px) rotate(-0.5deg);
/* Umbra se extinde și se întunecă */
box-shadow:
0 12px 24px rgba(0, 0, 0, 0.1),
0 2px 4px rgba(0, 0, 0, 0.05);
}
.product-card:active {
/* Se „apasă" când e click */
transform: translateY(-2px) scale(0.99);
transition: transform 100ms;
}
Card care se ridică, se înclină ușor (-0.5deg dă senzație de „naturalitate"), umbra se extinde. Click-ul îl „apasă" înapoi. Detalii mici, impresie profesională.
Transform corect
Vrei ca un card să se ridice cu 4px la hover, fără să afecteze layout-ul. Ce folosești?
Transitions animează de la A la B. Keyframes îți permit să treci prin mai multe puncte: A → B → C → D. Animații complexe, loop-uri, secvențe — toate cu keyframes. E cel mai puternic nivel de animație din CSS.
Sintaxa de bază
Definești o animație cu @keyframes, apoi o aplici cu animation:
Titlul apare primul (100ms delay), paragraful la 300ms, CTA la 500ms. Fiecare cu animația completă de 700ms. Rezultatul: intro în cascadă, profesional, fără JavaScript.
Alege tool-ul
Vrei un efect de „puls" (se mărește și se micșorează la infinit) pe un buton pentru a atrage atenția. Ce folosești?
Recapitulare
@keyframes nume { 0% {...} 50% {...} 100% {...} }
animation: nume durată timing delay count direction fill-mode
fill-mode: forwards păstrează starea finală
direction: alternate pentru bounce back-and-forth
infinite pentru loop continuu
Stagger cu animation-delay pe :nth-child
Transition pentru stări, Animation pentru secvențe
Lecția 59 din 76
Modulul 8 · Lecția 48 min citire
Animații pe scroll
Ai văzut site-uri unde elementele apar frumos când scrollezi la ele? Până recent, asta cerea JavaScript și o librărie (AOS, GSAP). Acum CSS pur poate face asta cu un feature nou: scroll-driven animations.
Problema
Vrei ca un element să se anime când devine vizibil în viewport. Cu CSS tradițional, @keyframes pornește la încărcarea paginii — nu la scroll. Soluția veche: JavaScript cu IntersectionObserver.
Când elementul intră în viewport, animația se joacă. Nu la încărcare, ci la scroll. Zero JavaScript.
Cum funcționează
animation-timeline: view() spune animației: „urmărește poziția elementului față de viewport". Animația avansează pe măsură ce elementul intră în ecran.
animation-range definește când începe și se termină animația:
entry 0% — când elementul abia începe să intre în viewport
entry 100% — când elementul e complet vizibil
exit 0% — când începe să iasă
exit 100% — când e complet ieșit
/* Animație care se joacă pe măsură ce elementul intră în ecran */
.fade-up {
animation: fadeIn linear both;
animation-timeline: view();
animation-range: entry 0% entry 60%;
}
Pe măsură ce scrollezi către secțiunea de features, fiecare card apare în cascadă. Browser-uri care nu suportă văd cards-urile normale (fallback gratuit). Progress enhancement la maximum.
Tehnica potrivită
Vrei ca pe site-ul tău să apară fade-in elementele pe măsură ce utilizatorul scroll-ează. Ce abordare e cea mai bună?
Recapitulare
animation-timeline: view() = animație legată de scroll
animation-range = când începe/se termină (entry, exit, cover)
Abordare progresivă: @supports pentru browsere vechi
Lecția 60 din 76
Modulul 8 · Lecția 56 min citire
prefers-reduced-motion
Unii utilizatori au condiții (vertigo, tulburări vestibulare, migrene) unde animațiile provoacă real disconfort — uneori greață. Sistemul lor operațional are o setare „reduce motion". Tu, ca dev, trebuie să respecți acea setare. E o lecție scurtă dar esențială.
Problema
Parallax masiv, elemente care zboară, carusele care se învârt — pentru majoritate sunt plăcute. Pentru un procent mic, provoacă rău fizic. Aceștia își setează sistemul pe „reduce motion" și se așteaptă ca site-urile să respecte setarea.
Media query-ul
@media (prefers-reduced-motion: reduce) {
/* Stilurile astea se aplică DOAR pentru utilizatorii cu
„reduce motion" activat în sistem */
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Pattern-ul standard: reduci toate animațiile la aproape instant (0.01ms). Tehnic nu sunt animații, dar browser-ul încă respectă finalizarea.
Abordare mai nuanțată
Uneori vrei să păstrezi anumite animații esențiale (fade-uri scurte) dar să elimini cele mari (parallax, bounce):
/* Animații mici — ok pentru toată lumea */
.button {
transition: background 150ms;
}
/* Animații mari — dezactivate pentru reduced motion */
@media (prefers-reduced-motion: no-preference) {
.card {
transition: transform 300ms;
}
.card:hover {
transform: translateY(-6px);
}
.hero-image {
animation: floatUp 3s infinite alternate;
}
}
no-preference = „utilizatorul NU a cerut reduce motion". Pui animațiile mari înăuntru. Dacă preferința e „reduce", sar peste tot.
Ce să eviți în mod special
Parallax — cel mai rău pentru vertigo
Mișcări orizontale mari — carusele lente, slide-in-uri lungi
Rotații peste 180°
Zoom dramatic — scale(0.5) → scale(1.5)
Bouncing — elemente care sar repetitiv
Ce e de obicei ok
Fade-uri (opacity 0 → 1) — fără mișcare fizică
Color transitions
Micro-mișcări (translate 2-4px)
Animații scurte (sub 200ms)
Testarea
Cum să vezi cum arată site-ul tău pentru utilizatorii cu reduce motion:
Mac: System Preferences → Accessibility → Display → Reduce motion
Windows: Settings → Ease of Access → Display → Show animations in Windows (off)
/* Base — fără animații în exces */
.card {
transition: background 150ms;
}
/* Animații complete doar pentru cine nu a cerut reducere */
@media (prefers-reduced-motion: no-preference) {
.card {
transition:
background 150ms,
transform 300ms ease-out,
box-shadow 300ms ease-out;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0,0,0,0.1);
}
.hero-title {
animation: fadeUp 700ms ease-out forwards;
opacity: 0;
}
}
/* Pentru reduced motion — doar o tranziție instant de opacity */
@media (prefers-reduced-motion: reduce) {
.hero-title {
opacity: 1; /* direct vizibil, fără animație */
}
}
Hover-ul pe card doar schimbă culoarea pentru reduced motion. Animațiile de intrare sunt dezactivate. Pentru restul utilizatorilor, totul arată lin și profesional.
Cât de important
Ar trebui să respecți prefers-reduced-motion chiar dacă doar 1% din utilizatori îl activează?
Recapitulare
@media (prefers-reduced-motion: reduce) = utilizatorul a cerut reducere
@media (prefers-reduced-motion: no-preference) = ok să animezi normal
Pattern comun: resetezi toate animațiile la 0.01ms pentru reduce
Evită parallax, rotații mari, bouncing dramatic
Fade-uri și color transitions sunt de obicei ok
Testează cu System Preferences sau Chrome DevTools
Lecția 61 din 76
Modulul 8 · Lecția 68 min citire
Micro-interacțiuni
Ce separă un site „ok" de unul „wow"? De obicei, nu design-ul mare — ci detaliile mici. Un buton care se „apasă" la click. Un checkbox care are un pulse când e bifat. Un toast care alunecă în colț când trimiți un form. Astea sunt micro-interacțiunile.
Ce e o micro-interacțiune
Un efect vizual scurt care oferă feedback la acțiunile utilizatorului:
Click pe buton → buton se „apasă"
Hover pe card → card se ridică
Check pe checkbox → pulse scurt
Form submit → buton afișează spinner
Toggle pe switch → alunecare lină
Niciuna nu e obligatorie. Toate combinate fac site-ul să se simtă „viu" și profesional.
Tendința începătorilor: „să animăm TOTUL". Rezultat: site-ul pare atacat de hiperactivitate.
Regula: 2-3 micro-interacțiuni pe viewport. Butoane, iconițe importante, cards principale. Restul rămân statice. Contrastul cu mișcările subtile e ce dă impact.
Like button cu 3 stări: idle, hover (scale up), liked (bounce + pulse ring). Fiecare stare are propria micro-interacțiune. Satisfăcător la apăsat.
Principiu corect
Care e durata optimă pentru o micro-interacțiune la click pe buton?
Recapitulare
Micro-interacțiuni = feedback vizual scurt la acțiuni
Scurte (100-300ms), subtile, funcționale
Butoane: hover + active states cu transform + shadow
Input focus: floating labels
Loading state: spinner din text transparent
2-3 micro-interacțiuni per viewport — nu mai multe
Consistență: același pattern de hover peste tot în site
Lecția 62 din 76
Modulul 8 · Lecția 712 min citire
Proiect: landing animat
Luăm landing page-ul din Modulul 6 și îl facem viu cu animații elegante. Fără exces, doar detalii care îl ridică de la „ok" la „wow". Respectăm accesibilitatea cu prefers-reduced-motion.
Ce adăugăm
Hero cu animații în cascadă (titlu, subtitlu, butoane)
Respect pentru accesibilitate — tot înăuntrul prefers-reduced-motion: no-preference
Totul orchestrat, nimic exagerat. Asta e balanța.
Recapitulare
Intro cu cascadă — delay-uri incrementale (100ms, 300ms, 500ms)
Scroll reveal cu animation-timeline (+ fallback pentru browsere vechi)
Hover effects consistente peste tot — ridicare + shadow
Micro-interacțiuni: icon la hover, underline animat
Progress bar de reading — pur CSS
Tot înveluit în @media (prefers-reduced-motion: no-preference)
Transform + opacity peste tot = GPU-accelerated, fluid
🎉 Ai terminat Modulul 8!
Pagina ta prinde viață. Urmează Modulul 9 — Forms stilizate. Cel mai subestimat skill din CSS.
Lecția 63 din 76
Modulul 9 · Lecția 19 min citire
Toate tipurile de input
HTML are 20+ tipuri de input — fiecare cu propriile super-puteri. Alege tipul potrivit și browser-ul îți face jumătate din muncă: validare, tastaturi mobile potrivite, UI specializat. Alege greșit și utilizatorii se chinuie să completeze form-ul.
Text-uri de bază
type="text"
<input type="text" name="nume">
Default-ul. Orice text scurt — nume, titluri, căutări.
type="email"
<input type="email" name="email" required>
Validează automat format de email. Pe mobil, activează tastatura cu @. Folosește-l mereu pentru adrese de email.
Fiecare câmp cu tipul potrivit: email → validare + tastatură mobile; password → ascundere + integrare manager; number cu min/max; date cu limită de vârstă (18+). Browser-ul face mult.
Tipul potrivit
Ce tip de input folosești pentru adresa de email într-un form de înregistrare?
= validare automată format + tastatură mobilă optimizată (cu @). Browser-ul nu va lăsa utilizatorul să trimită form-ul cu o adresă greșită ca „tom@.com\". type=\"text\" ar funcționa dar fără validare și fără optimizare mobile. url e pentru URL-uri, search pentru căutări." data-msg-wrong="Nu chiar. Răspunsul corect e B. type=\"email\" activează validarea și tastatura potrivită.">
Recapitulare
20+ tipuri de input, fiecare cu super-puteri
Folosește type potrivit: email, tel, url, number, date — nu doar text
Checkbox multiple = nume identic pentru selecție multiplă
Radio = nume identic pentru selecție unică
Lecția 64 din 76
Modulul 9 · Lecția 210 min citire
Stilizarea completă
Input-urile default ale browser-ului sunt urâte — fiecare browser le afișează diferit. Chrome pe Mac arată altfel decât Firefox pe Windows. Stilizarea completă dă consistență și polish. Pattern-ul e același pentru orice design system.
Reset-ul pentru forms
Browserele dau form elements stiluri default care trebuie „sparse":
Chrome pune un fundal galben pe input-urile auto-completate. Pentru a-l păstra consistent cu design-ul tău:
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px white inset;
-webkit-text-fill-color: var(--color-text);
transition: background-color 5000s; /* trick ca să nu se schimbe */
}
Nu scoate outline-ul fără înlocuire
Greșeala clasică de accesibilitate: input:focus { outline: none; } fără nimic în loc.
Utilizatorii care navighează cu tastatura (Tab prin form) nu mai văd unde sunt. Devine imposibil de utilizat.
Regula: dacă scoți outline, adaugă box-shadow sau border-color distinct la :focus. Utilizatorii trebuie să vadă mereu ce e focusat.
Textarea cu resize: vertical și font-family: inherit
Nu scoate outline fără alt indicator de focus!
Lecția 65 din 76
Modulul 9 · Lecția 39 min citire
Validare nativă
HTML are validare încorporată. Fără JavaScript. Fără librării. Adaugi required, type="email", pattern — browserul blochează form-ul dacă datele sunt greșite. CSS răspunde la stările de validare. Pentru forms simple, asta e tot ce ai nevoie.
Attribute de validare
required
<input type="email" name="email" required>
Câmpul trebuie completat. Form-ul nu se trimite dacă e gol.
<!-- Cod poștal 6 cifre (ex. România) -->
<input type="text" pattern="\d{6}" title="6 cifre">
<!-- Număr telefon România -->
<input type="tel" pattern="(\+40|0)[0-9]{9}">
title e afișat ca mesaj de eroare când pattern-ul nu se potrivește.
Pseudo-classes de validare
CSS știe dacă input-ul e valid sau nu:
:valid — input-ul trece validarea
:invalid — input-ul nu trece validarea
:required — câmp obligatoriu
:optional — câmp opțional
:user-invalid — invalid ȘI utilizatorul a interacționat cu el (modern)
Evită :invalid pe câmpuri goale
Un bug tipic: utilizatorul abia a deschis pagina, toate câmpurile required sunt deja „invalid" (pentru că sunt goale). Le arăți roșii de la început — agresiv.
Soluția modernă — :user-invalid:
/* Vechea problemă */
input:invalid {
border-color: red;
}
/* → toate câmpurile sunt roșii la încărcare */
/* Soluția modernă */
input:user-invalid {
border-color: red;
}
/* → roșu doar după ce utilizatorul a încercat să submit-uieze sau
a părăsit câmpul fără să-l completeze */
:user-invalid e suportat în browsere moderne din 2023.
Mesaje de eroare custom
Mesajele native ale browser-ului sunt uneori generice. Poți pune text custom:
<input
type="email"
required
title="Te rog introdu o adresă de email validă"
>
Pentru control total, folosești JavaScript cu setCustomValidity(). Dar pentru majoritatea cazurilor, title e suficient.
Validare fără JavaScript. Feedback instant la blur (părăsirea câmpului) sau la submit. Mesaj de eroare apare doar după ce utilizatorul a interacționat. Green border pentru valid. Toate doar cu CSS.
Alege pseudo-class
Vrei să arăți border roșu pe un input invalid DAR doar după ce utilizatorul a interacționat (nu la încărcarea paginii). Ce folosești?
Recapitulare
Attribute de validare: required, pattern, min/max, minlength/maxlength
Preferă :user-invalid față de :invalid — doar după interacțiune
Mesaje custom cu title pe input
Error messages cu :has() — CSS pur
Validare client = UX rapid; validare server = securitate
Lecția 66 din 76
Modulul 9 · Lecția 49 min citire
Checkbox și radio custom
Checkbox-urile și radio-urile default sunt mici, urâte și diferite în fiecare browser. Majoritatea site-urilor profesionale le stilizează custom. Tehnica e simplă odată ce o vezi — ascunzi input-ul original și desenezi unul fake cu CSS.
Tehnica „hide + replace"
Nu poți stiliza checkbox-ul direct (browser-ul îl controlează). Dar poți ascunde input-ul real și stiliza un element vizual care-i ia locul.
Componenta e self-contained și accesibilă. Bounce animation când e bifat (cubic-bezier cu valori >1 la capetele). Focus ring pentru tastatură. Hover pentru mouse. Totul orchestrat în 40 de linii.
Regulă importantă
Cum ascunzi checkbox-ul nativ pentru a-l înlocui vizual, fără să afectezi accesibilitatea?
Recapitulare
Tehnica „hide + replace": input invizibil + element vizual
Ascunde cu opacity: 0 + position: absolute, NU display: none
Stilizează cu input:checked + .visual
Animația bounce cu cubic-bezier(0.34, 1.56, 0.64, 1)
Focus ring obligatoriu pentru accesibilitate
Pattern „option card" cu :has() pentru selectare vizuală
Lecția 67 din 76
Modulul 9 · Lecția 513 min citire
Proiect: formular contact
Construim un formular de contact complet — cu validare nativă, stări visual, feedback, checkbox custom, totul responsive. E genul de formular pe care-l poți copia pe orice site profesional.
Netlify Forms — dacă hostuiești pe Netlify, 100 free/lună
Backend propriu — PHP, Node.js, Python cu SendGrid/Mailgun
Pentru CSS-ul nostru, e irelevant ce e în spate — formularul arată și validează identic.
Recapitulare
Form complet cu grid pentru rânduri (grid-template-columns: 1fr 1fr)
Reset-ul se transformă pe mobile cu media query
Validare nativă cu :user-invalid + mesaje via :has()
Option cards cu radio invizibil + :has(:checked)
Checkbox custom cu bounce animation
Loading state cu spinner pur CSS
JavaScript minim doar pentru submit (restul e CSS)
🎉 Ai terminat Modulul 9!
Urmează Modulul 10 — proiectul final: construim împreună un portofoliu personal complet.
Lecția 68 din 76
Modulul 10 · Lecția 110 min citire
Planificare și structură
Ai parcurs 9 module. Ai toate uneltele. Acum construim ceva complet — un portofoliu personal pe care-l poți folosi efectiv. E mai mult decât un exercițiu: e prima bucată de „dovadă" că știi frontend. Orice dev-er junior are nevoie de unul.
Ce construim
Un portofoliu single-page, modern, cu:
Hero — numele tău, ce faci, call to action
Navbar sticky — link-uri la secțiuni, hamburger pe mobil
Proiecte — card-uri cu imagini, titluri, tehnologii folosite
Înainte să scriem cod, câteva principii care fac diferența între „ok" și „impresionant":
1. Un singur obiectiv
Portofoliul are UN scop: să te ajute să primești un job sau un client. Fiecare secțiune trebuie să servească asta. Nu adăuga chestii „pentru că arată bine" dacă nu ajută conversia (apăsarea butonului „Contact" sau trimiterea unui email).
2. Arată, nu spune
„Sunt creativ" nu convinge pe nimeni. Un portofoliu frumos design-at demonstrează creativitatea. Design-ul tău e demonstrația.
3. Constrângeri, nu libertate
Tinerii dev-eri încearcă să folosească TOT ce au învățat. Rezultat: chaos. Portofoliile bune au 3-4 culori, 2 fonturi, spațiere consistentă. Mai puțin e mai mult.
Moodboard-ul tău
Înainte să scrii HTML, decide:
Personalitate: cool și tehnic? Cald și prietenos? Elegant și minimalist?
Paletă: 2-3 culori de bază + 1 accent. Limitate, distinctive.
Tipografie: 1 font display pentru titluri + 1 sans pentru body. Maxim.
Tonul textelor: formal sau casual? (Alege tonul care te reprezintă.)
Paleta pentru cursul nostru
Pentru exemplul ghidat, folosim paleta caldă deja introdusă în curs:
Când cineva partajează link-ul tău pe Twitter/LinkedIn/WhatsApp, ar trebui să apară un card frumos cu titlu, descriere, imagine. Asta se face cu tag-uri <meta property="og:*">.
og:title — titlul care apare în preview
og:description — descrierea scurtă
og:image — imaginea (1200x630px recomandat)
og:url — URL-ul canonic
Creezi og-image.jpg cu designul portofoliului + numele tău + „Frontend Developer". Un singur screenshot bun = mii de impresii când partajezi.
Plan înainte să scrii cod
Cea mai comună greșeală la începători: deschizi editor-ul și scrii HTML random. Ajungi cu cod haotic.
Pas corect: desenează pe hârtie (sau Figma) cum arată fiecare secțiune. Ce e hero-ul? Câte carduri de proiecte? În ce ordine curg secțiunile? Odată ce ai claritate, codul devine o simplă transcriere.
Planifică înainte să scrii cod — pe hârtie, în Figma, oriunde
Un scop clar: să primești job sau client
Constrângeri în design — 2-3 culori, 2 fonturi, simplu
Structură HTML semantică: header, main cu sections, footer
Tag-uri Open Graph pentru partajare pe social
CSS organizat în 8 zone: reset, tokens, base, layout, components, sections, animations, media
Lecția 69 din 76
Modulul 10 · Lecția 215 min citire
Hero și navbar
Primele 3 secunde contează enorm. Când cineva deschide portofoliul, ce vede în hero decide dacă continuă sau închide tab-ul. Construim un hero care convinge și un navbar care nu distrage.
„Eyebrow" (sprânceană) e textul mic, colorat, peste titlu. În exemplu: „Frontend Developer" deasupra titlului mare.
E un detaliu care separă portofoliile profesionale de cele amatorești. Dă context imediat, pregătește privitorul pentru titlul mare, și arată polish.
Format tipic: text scurt (1-3 cuvinte), uppercase, letter-spacing larg, culoare accent.
Ce am construit
Navbar-ul și hero-ul sunt gata. Ce face pattern-ul ăsta special:
Navbar sticky cu blur efect (frosted glass)
Hamburger care se transformă în X — zero librării
Hero cu animații în cascadă
Scroll indicator animat care invită la scroll
Butoane cu două stiluri (primary și ghost) — o convenție puternică
Tipografie fluidă (clamp) care se adaptează la orice ecran
Recapitulare
Navbar sticky + backdrop-filter pentru frosted glass
Underline animat sub link-uri la hover (transform: scaleX)
Hamburger menu cu CSS transforms (span-uri care rotesc în X)
Hero cu eyebrow + titlu mare + lead + CTAs
Tipografie fluidă cu clamp pe toate dimensiunile
Animații în cascadă pe elementele hero
Scroll indicator pentru a invita la explorare
Lecția 70 din 76
Modulul 10 · Lecția 315 min citire
Proiecte și despre
Miezul portofoliului: secțiunea de proiecte. Ce ai construit, pentru cine, cu ce tehnologii. Apoi secțiunea „despre" — nu biografia ta completă, ci ce e relevant pentru job.
Section title pattern
Toate secțiunile de la acest punct folosesc același pattern pentru titlu. Reutilizabilitate:
aspect-ratio 16:10 — toate imaginile au aceeași proporție, layout-ul rămâne consistent indiferent de imagini
img se mărește la hover — efect subtil care semnalează că cardul e clickabil
Tags în pill-uri — arată rapid stack-ul tehnic
Săgeata „→" se mișcă la hover — transition: gap e un trick elegant
auto-fit grid — 1 coloană pe mobil, 2 pe tabletă, 3 pe desktop lat, fără media queries
Despre — HTML
<section class="about section" id="about">
<h2 class="section-title">Despre <em>mine</em></h2>
<div class="about-grid">
<div class="about-text">
<p class="about-lead">
Am început să programez la 16 ani, pentru că-mi plăceau jocurile și voiam să le înțeleg. Astăzi, construiesc interfețe pentru startup-uri și agenții.
</p>
<p>
Lucrez cel mai mult cu React și TypeScript, dar am o slăbiciune specială pentru CSS. Cred că un frontend bun nu se cunoaște — se simte: iei o decizie, și pagina răspunde exact cum te așteptai.
</p>
<p>
În afara job-ului: fotografie pe film, fugit (prost, dar constant), și cafea făcută acasă cu aeropress.
</p>
</div>
<div class="about-skills">
<h3 class="skills-title">Ce folosesc</h3>
<ul class="skills-list">
<li>
<strong>Frontend</strong>
<span>React, Next.js, TypeScript</span>
</li>
<li>
<strong>Styling</strong>
<span>CSS modern, Tailwind, design systems</span>
</li>
<li>
<strong>Tooling</strong>
<span>Vite, Git, Figma</span>
</li>
<li>
<strong>Experiență</strong>
<span>4 ani, 12+ proiecte live</span>
</li>
</ul>
</div>
</div>
</section>
Cardurile apar pe măsură ce intră în viewport. Browser-uri moderne primesc efectul. Vechi le văd normale (fallback gratuit).
Recapitulare
Section-title pattern reutilizabil cu em italic pentru accent
Project cards cu aspect-ratio pentru layout consistent
Hover zoom pe imagine + ridicare card + săgeata care alunecă
Tags ca pill-uri pentru stack tehnic
About grid cu 1.5fr : 1fr (text mai lat decât skills)
Animații la scroll cu animation-timeline (progresiv)
Text: eu > noi, rezultate > responsibilități, personalitate > CV
Lecția 71 din 76
Modulul 10 · Lecția 414 min citire
Contact, footer și polish
Am ajuns la ultima bucată — contact, footer, și trecem prin detaliile care separă „ok" de „wow". Micile polishuri care fac portofoliul să pară terminat de un profesionist.
O bară subțire sus care crește pe măsură ce scroll-ezi. Utilizatorii văd cât mai au de parcurs.
Checklist înainte să publici
Înainte să trimiți portofoliul oricui:
✅ Verificat pe telefon, tabletă, desktop
✅ Toate link-urile merg (inclusiv cele la proiecte)
✅ Imaginile sunt optimizate (WebP, sub 200KB fiecare)
✅ Formularul funcționează (testat cu date reale)
✅ Favicon setat
✅ Meta tag-uri (title, description, og:image) completate
✅ Nume de fișiere fără spații sau diacritice
✅ 404 page personalizată (dacă găzduiești static)
✅ Testat cu Lighthouse (target: 95+ pe toate categoriile)
✅ Citit tot textul pentru typos și erori
Testează pe un prieten
Ultimul test: dă link-ul unui prieten non-tehnic. Privește-l cum folosește site-ul timp de 2 minute.
Vei vedea instant:
Unde se blochează
Ce nu înțelege
Ce butoane nu sunt clare
Cât de greu încarcă
5 minute cu un utilizator real bat 5 ore de auto-review.
Recapitulare
Contact cu două coloane: info + formular
Contact methods ca link-uri tap-abile (mailto, tel)
Footer cu brand + social links + copyright
Polish: smooth scroll, selection color, focus ring, external link indicators
Reading progress bar pentru context
Checklist înainte de launch
Test final: un prieten non-tehnic
🎉 Ai terminat Modulul 10 — proiectul final!
Ai un portofoliu complet, production-ready. Urmează Modulul 11 — ultimul — despre cum să-l pui online.
Lecția 72 din 76
Modulul 11 · Lecția 18 min citire
Deployment pe Netlify
Ai portofoliul pe calculator. Super. Dar până nu e online, e doar un fișier. Urmează 5 minute care-l pun pe internet, gratuit, cu HTTPS, la o adresă pe care o poți trimite oricui. Fără server, fără complicații.
Ce e Netlify
Netlify e un serviciu care hostuiește site-uri statice (HTML, CSS, JS) gratuit. Fără setup de server, fără bază de date. Îți iei un cont, încarci fișierele, primești un link.
Pentru portofoliul tău (sau orice site static), e exact ce-ți trebuie.
De ce Netlify în loc de altele
100% gratuit pentru proiecte personale (până la anumite limite generoase)
HTTPS automat — site-ul tău are certificat SSL din prima secundă
CDN global — se încarcă rapid oriunde în lume
Drag-and-drop deploy — încarci folderul, gata
Deploy automat din GitHub (optional, pentru proiecte serioase)
Forms gratuite — până la 100 trimiteri/lună fără backend
Preview URL-uri pentru fiecare modificare
Pasul 1: Pregătește fișierele
Pune toate fișierele portofoliului într-un folder:
Link-urile în HTML trebuie să fie relative (href="styles.css", nu href="/Users/tu/.../styles.css")
Pasul 2: Creează cont Netlify
Mergi la netlify.com
Click „Sign up" (dreapta sus)
Alege una dintre metodele: GitHub, GitLab, Email
Pentru început, email e cel mai simplu
Confirmă email-ul
Pasul 3: Deploy prin drag-and-drop
După login, ajungi pe dashboard. Cea mai simplă metodă de deploy:
Scroll până găsești secțiunea „Sites"
Click „Add new site" → „Deploy manually"
Deschide folderul portofoliu/ pe calculatorul tău
Selectează tot conținutul (Ctrl+A sau Cmd+A)
Drag-and-drop în zona Netlify
Așteptați 10-30 secunde
Gata. Site-ul tău e online.
✓ Site is live
URL: https://melodic-froyo-1a2b3c.netlify.app
URL-ul aleator
Netlify generează un URL aleator ca melodic-froyo-1a2b3c.netlify.app. Poți schimba partea de început:
În dashboard, dai click pe site
Settings → Site details → Change site name
Alege ceva relevant: alex-ionescu.netlify.app
Pentru domeniu personal (ex. alexionescu.ro), treci la lecția 11-3.
Actualizări — cum schimbi fișierele
Când faci modificări la portofoliu, ai 2 opțiuni:
Metoda 1: Drag-and-drop din nou
Dashboard → site-ul tău → tab „Deploys"
„Trigger deploy" → „Deploy site"
Sau drag-and-drop fișierele noi peste zona indicată
Metoda 2: Git (recomandată pentru proiecte serioase)
Pui proiectul pe GitHub, conectezi repo-ul la Netlify. Fiecare git push trimite site-ul automat. Se numește „continuous deployment" și e modul profesional de lucru.
Pentru asta, ai nevoie de Git — subiect pentru un curs separat. Pentru moment, drag-and-drop e perfect.
Formular de contact cu Netlify Forms
Dacă ai un formular de contact în portofoliu (din Modulul 9), îl poți face să trimită emailuri reale adăugând un atribut:
<form name="contact" netlify>
...
</form>
Atât. Netlify detectează automat formularul, colectează trimiterile, trimite email cu fiecare. 100/lună gratuit. Pentru portofoliu personal, mai mult decât suficient.
Ce primești gratis cu Netlify
HTTPS cu certificat SSL
100 GB trafic/lună
300 min build-uri/lună
Deploy nelimitat
Forms — 100 trimiteri/lună
Branch deploys (preview pentru fiecare versiune)
Analytics de bază
Pentru portofoliu personal, nu vei depăși niciodată limitele. Totul rămâne gratis.
Recapitulare
Netlify = host gratuit pentru site-uri statice
Fișierul principal trebuie să fie index.html
Deploy prin drag-and-drop — 30 secunde
HTTPS automat, CDN global, preview URL-uri
Poți schimba URL-ul în Settings → Site details
Pentru updates: drag-and-drop din nou sau conectează GitHub
Netlify Forms gratuit — 100 trimiteri/lună cu netlify pe form
Lecția 73 din 76
Modulul 11 · Lecția 27 min citire
Deployment pe Vercel
Vercel e alternativa principală la Netlify. Aceeași idee — host static gratuit, deploy rapid. Câteva diferențe subtile. Te las să alegi pe care-ți place.
Netlify vs Vercel — diferențele reale
Netlify
Pionier în spațiul ăsta (primul din lista „JAMstack")
Forms native (100/lună gratuit)
Drag-and-drop simplu
Comunitate mare, multă documentație în română
Interfață puțin mai simplă pentru începători
Vercel
Făcut de echipa Next.js — cel mai bun pentru Next.js
Analytics mai bune și gratuite
Edge Functions rapide (dacă ajungi acolo)
Preview URL-uri excelente
Mai potrivit pentru proiecte React/Next.js
Pentru portofoliu simplu HTML/CSS: ambele sunt identice ca experiență. Dacă planifici să înveți React mai târziu, alege Vercel. Altfel, Netlify.
Pasul 1: Creează cont Vercel
Mergi la vercel.com
Click „Sign Up"
Recomandare: folosește GitHub (integrare mai bună)
Sau email, dacă nu ai GitHub încă
Pasul 2: Opțiunea rapidă — Vercel CLI
Vercel recomandă instalarea unui instrument de comandă. Pentru cine are Node.js:
# Instalare (o singură dată)
npm install -g vercel
# În folder-ul portofoliului
cd /Users/tu/portofoliu
vercel
# Răspunzi la câteva întrebări
# ✔ Set up and deploy? [Y/n] y
# ✔ Which scope? Your Name
# ✔ Link to existing project? [y/N] n
# ✔ What's your project's name? portofoliu
# ✔ In which directory is your code located? ./
# În 20-30 secunde, site-ul e live
# ✔ Preview: https://portofoliu-xyz.vercel.app
Pasul 3: Opțiunea fără terminal — GitHub
Dacă Node.js/terminalul te intimidează:
Pui proiectul pe GitHub (poți folosi GitHub Desktop pentru UI prietenos)
În Vercel dashboard, „Add New Project"
Conectezi cu GitHub, selectezi repo-ul
Vercel detectează că e site static, face deploy automat
Pasul 4: Upload direct (drag-and-drop)
Vercel suportă și drag-and-drop ca Netlify, dar mai puțin promovat:
Dashboard → „Add New..." → „Project"
Scroll jos, „Deploy a Static Site" (sau similar)
Drag folder-ul
După deploy
Primești un URL ca portofoliu-xyz.vercel.app. Poți:
Schimba numele din Settings
Conecta domeniu custom
Vedea analytics (câte vizite, de unde)
Vedea deploy log-uri pentru debug
Care alegere pentru tine
Pentru portofoliul din cursul ăsta (HTML/CSS pur), am o recomandare clară:
Dacă vrei cel mai simplu start: Netlify cu drag-and-drop
Dacă planifici să înveți React/Next.js în viitor: Vercel (te obișnuiești cu ecosistemul)
Dacă ai deja GitHub: ambele sunt la fel, alege orice
Ambele sunt gratuite, ambele sunt producție-ready, ambele au comunități mari. Nu e o decizie reversibilă — poți muta site-ul în 5 minute.
Ce primești gratis cu Vercel
HTTPS cu SSL
100 GB trafic/lună
Build-uri practic nelimitate (6000 min/lună)
Deploy nelimitat
Analytics excelent (diferit de Netlify — mai detaliat)
Preview pentru fiecare commit/push
Ce NU ai în Vercel (față de Netlify)
Nu are Forms native — trebuie serviciu separat (Formspree, Basin)
Interfață puțin mai tehnică pentru începători
Recapitulare
Vercel = alternativă la Netlify, funcționalități similare
Pentru portofoliu simplu: ambele la fel
Vercel mai bun dacă vei folosi Next.js în viitor
Netlify mai simplu pentru începători absoluți
Deploy prin CLI, GitHub, sau drag-and-drop
Ambele gratuite pentru uz personal
Lecția 74 din 76
Modulul 11 · Lecția 39 min citire
Domeniu custom și email
alex-ionescu.netlify.app merge, dar alexionescu.ro convinge mai mult. Un domeniu custom costă ~40 lei/an și durează 15 minute să-l configurezi. Plus îți poți face email alex@alexionescu.ro — arată profesional față de alexionescu.dev@gmail.com.
De ce merită un domeniu custom
Credibilitate — clienții serioși se așteaptă la un domeniu real
Memorabil — numele tău punct ceva < subdomenii aleatoare
Email profesional — @domeniultau.ro bate Gmail-ul
Portabil — dacă schimbi host-ul, domeniul rămâne
Ieftin — sub 50 lei/an pentru majoritatea TLD-urilor
Ce TLD să alegi
TLD = partea de după punct (.com, .ro, .dev, etc.). Recomandări:
.ro — ideal pentru români care lucrează cu clienți români. ~40-60 lei/an
Pentru .ro, multe firme vând (Hostico, Romarg, GoDaddy, etc.)
Preț similar, diferențe mici
Atenție la reînnoire — unele firme dublează prețul în al doilea an
4. host-profesional.com (recomandarea mea pentru români)
Pentru cineva care e la început și lucrează cu clienți români, recomand host-profesional.com. Sunt transparent — e serviciul meu (al lui Alin, autorul cursului). Dar îl recomand fiindcă:
Prețuri corecte la .ro și .com, fără surprize la reînnoire
Suport în română, rapid (nu linie call center din Filipine)
Hosting pentru WordPress inclus dacă vrei să migrezi de pe Netlify mai târziu
Email profesional pe domeniu inclus în majoritatea planurilor
Experiență directă cu nevoile dev-erilor români
Dacă te încurci cu setările DNS, oamenii de la host-profesional.com le fac pentru tine — trimiți un ticket cu „vreau să conectez domeniul la Netlify" și te ajută.
Cumpărarea — pas cu pas (Namecheap exemplu)
Mergi la namecheap.com
Caută numele tău: alexionescu
Site-ul arată TLD-urile disponibile cu prețuri
Alege unul, „Add to Cart"
Checkout cu card
În 1-2 minute, domeniul e al tău
Conectarea domeniului la Netlify
Acum că ai alexionescu.ro, îl legi la site-ul tău Netlify:
Pasul 1: Netlify dashboard
Dashboard → site-ul tău → „Domain settings"
„Add custom domain" → introduci alexionescu.ro
Verifică că e al tău → „Add domain"
Pasul 2: Setările DNS la registrar
La registrar (ex. Namecheap), găsești domeniul și accesezi DNS settings. Adaugi 2 înregistrări:
Type | Host | Value | TTL
-------|------|-------------------------|------
A | @ | 75.2.60.5 | Auto
CNAME | www | alexionescu.netlify.app| Auto
(Valorile exacte le iei din Netlify — ei îți arată ce să pui.)
Pasul 3: Așteaptă
DNS-ul se propagă în câteva minute până la 24 ore. De obicei, 15-30 minute. Apoi alexionescu.ro arată site-ul tău.
Pasul 4: HTTPS automat
Netlify detectează domeniul nou și generează certificat SSL în câteva minute. alexionescu.ro devine https://alexionescu.ro automat.
Email profesional
Cu domeniul alexionescu.ro, poți avea contact@alexionescu.ro. Două opțiuni:
Opțiunea 1: Google Workspace (recomandat)
30 lei/lună — pentru Gmail pe domeniul tău
Interfața Gmail familiară
15 GB storage
Integrare cu Calendar, Drive, Docs
Cel mai simplu și fiabil
Opțiunea 2: Zoho Mail (gratuit!)
Complet gratuit pentru un user
Până la 5 adrese email
5 GB storage
Interfață ok, nu la fel de bună ca Gmail
Ideal pentru start
Opțiunea 3: Email forwarding (cel mai simplu)
Unii registrari (Namecheap, Cloudflare) oferă forwarding gratuit:
Folosește email forwarding gratuit sau planul cu email inclus — suficient la început
Mai târziu, upgrade la Google Workspace când ai primii clienți
Total cost: ~40 lei/an pentru domeniu + host gratuit (Netlify) sau hosting inclus la host-profesional.com. Sub 5 euro/an pentru un portofoliu complet profesional.
.ro pentru clienți români (40-60 lei/an), .com pentru internațional
Cumperi la Namecheap, Cloudflare, Hostico
Conectezi la Netlify prin 2 înregistrări DNS (A + CNAME)
HTTPS automat, propagare în 15-30 minute
Email: Google Workspace (30 lei/lună), Zoho (gratuit), sau forwarding
Lecția 75 din 76
Modulul 11 · Lecția 410 min citire
Drumul mai departe
Ai ajuns la capăt. 76 de lecții, 11 module, un portofoliu complet. Știi HTML, CSS modern, layouts, animații, validare, responsive, deployment. Ce urmează? Depinde unde vrei să ajungi. Hai să-ți dau o hartă.
Ce știi acum
Să facem inventarul. Știi:
HTML semantic complet — tag-uri, forms, accesibilitate, SEO
CSS de la zero la avansat — selectori, box model, flex, grid
Twitter — urmărește dev-eri relevanți, învață zi de zi
Cursuri platite care merită
Josh Comeau — CSS for JS Developers — cel mai bun curs CSS avansat
Frontend Masters — subscripție, toate subiectele
Kent C. Dodds — Epic React — cel mai complet pentru React
Cursuri viitoare din Școala de WordPress
Dacă ți-a plăcut stilul ăsta de predare, urmează:
JavaScript pentru dev-eri frontend — 10 module, de la zero la React-ready
WordPress development modern — teme și plugin-uri profesionale
WooCommerce dev — magazin-uri online complete
React pentru practicanți — aplicații reale, nu tutoriale
Pentru hosting și domenii
Când îți lansezi primele proiecte (portofoliu, site-uri de client), îți recomand host-profesional.com. E serviciul meu, așa că sunt parte interesată — dar îl recomand deoarece știu direct de ce au nevoie dev-erii români la început: prețuri corecte, suport rapid în română, hosting care scalează de la portofoliu la magazin WooCommerce.
Bonus: cursul ăsta e făcut cu ❤️ de la host-profesional.com — dacă vrei să ne susții, domeniul tău următor făcut de aici e cel mai bun mulțumesc.
Sfatul final
Construiește proiecte, nu doar lecții. Orice curs, oricât de bun, e doar teorie până îl aplici.
Pentru fiecare modul nou pe care-l înveți, fă un mic proiect. Pune-l pe GitHub. Pune link în portofoliu. În 6 luni vei avea 10-15 proiecte reale — mult mai valoros decât orice certificat.
Cel mai important sfat
Nu te opri aici. Majoritatea oamenilor care termină un curs de frontend nu ajung niciodată dev-eri reali. De ce? Se opresc. Cred că „au terminat". Nu e terminat nimic.
Dev-ul e o meserie în care înveți toată viața. Tehnologia se schimbă. Framework-uri noi apar. Best practices evoluează. Dar fundația pe care o ai acum — HTML, CSS, design responsive, thinking semantic — rămâne valabilă mereu.
Construiește. Publică. Aplică pentru job-uri chiar dacă nu te simți pregătit. Cel mai bun moment să începi a fost acum 5 ani. Al doilea cel mai bun e astăzi.
Resurse: javascript.info, MDN, Josh Comeau, Kevin Powell
Comunități: Dev.to, Reddit, Discord
Construiește proiecte constant — mai valoros decât certificate
Nu aștepta să „știi tot" — aplică pentru job-uri cât de curând
Învățarea nu se oprește aici. Niciodată.
🎓 Ai terminat cursul complet HTML & CSS!
76 de lecții, 11 module, sute de exemple, un portofoliu real, totul în română.
Mulțumesc că ai avut încredere în acest curs.
Abia aștept să văd ce vei construi.
— Alin, Școala de WordPress
Lecția 76 din 76
ComunitateRecenzii și feedback
Ce spun cursanții
Cursul ăsta crește prin feedback-ul vostru. Pe măsură ce vor veni recenzii și impresii, le voi adăuga aici. Iar dacă tu ai parcurs cursul — pozitiv sau cu sugestii — te invit să-mi scrii.
Recenzii recente
★★★★★
Cel mai bun curs de HTML/CSS în română pe care l-am parcurs. Explicațiile sunt clare, exemplele sunt practice și am terminat cu un portofoliu pe care chiar îl pot arăta.
— Maria, junior frontend
★★★★★
Mi-a plăcut că nu e doar teorie. Fiecare modul are exerciții și proiecte. Am construit un dashboard complet din zero după Modulul 5.
— Andrei, dev junior
★★★★★
Recomand cu încredere. Am încercat alte cursuri pe Udemy, dar acesta e mult mai bine structurat și totul e în română. Container queries, :has(), animation-timeline — explicate cu exemple clare.
— Cristian, web designer
Lasă-mi părerea ta
Cu un click se deschide email-ul cu un mesaj pre-completat. Tu doar îl personalizezi și-l trimiți.