Studiu de caz

Frații Karamazov

O hartă interactivă pentru romanul lui Dostoievski — nu un rezumat, ci un loc prin care intri pe trei drumuri diferite: cronologia, orașul și personajele. Mai jos e ce am decis și cum am verificat că nu mă apuc singur cu ceva stricat.

Cum a fost construită

Aplicația a fost scrisă împreună cu un asistent AI (Claude Code), într-o sesiune de lucru dedicată doar ei, separată de portofoliu și de celelalte două aplicații. Eu am stabilit ce trebuie să arate romanul — cronologie, hartă, relații — am testat fiecare parte și am decis unde se oprește harta și unde rămâne doar text.

Mai jos sunt acele decizii de structură, nu o poveste despre cine a scris codul.

  • 23personaje, portrete procedurale
  • 6secțiuni interactive
  • ~6.844linii de cod
  • 0poze foto — totul e SVG

Cum arată

Nouă capturi de pe telefon, ca la celelalte două proiecte. Se pot derula lateral — la graful relațiilor, câteva nume din centru se văd mai bine mărite, din cauza densității personajelor din roman.

Pagina principală, cu drumurile de intrare în poveste
Cronologia, moment cu moment
Harta — orașul din carte și orașul real, într-un singur loc
Personajele, grupate pe familii
O fișă de personaj — Mitea
Graful relațiilor, cu legendă
Citatele, filtrabile pe temă
Quiz-ul — „Ce personaj ești”
Rezultatul — un personaj, nu doar un scor

De ce o hartă, nu un rezumat

„Frații Karamazov" are peste 900 de pagini, trei frați care se confundă ușor la prima lectură, un oraș inventat și o structură care sare în timp. Un rezumat liniar ar rezolva confuzia numelor, dar ar rata exact ce face romanul viu: cum se leagă un moment de un loc și de trei-patru oameni deodată.

Ideea a fost să nu existe un singur drum „corect" prin material. Cine vrea poveste în ordine deschide cronologia. Cine vrea să înțeleagă geografia deschide harta. Cine s-a pierdut printre nume deschide personajele — și de acolo, un click te duce direct în evenimentul sau relația care te-a adus la el. Trei intrări, aceleași date, legate între ele.

Portrete, nu poze

23 de personaje au nevoie de 23 de fețe recognoscibile. Poze reale ar fi fost un furt de imagine de undeva; o singură ilustrație de personaj ar fi cerut un desenator pentru fiecare — și tot 23 de stiluri diferite, dacă ar fi lucrat oameni diferiți sau sesiuni diferite.

În loc de asta, fiecare portret e generat din cod, dintr-o configurație: formă de față, ten, păr (stil și culoare), barbă, haine, accesorii, expresie, fundal. Componentele — față, păr, barbă, haine, accesorii — sunt piese SVG separate, compuse la nevoie. Același portret se desenează la trei mărimi diferite: bust întreg în pagina personajului, pastilă mică (variantă „avatar", decupată pe cap) în cronologie, și medalion încastrat direct în graful de relații. Nicio poză, nicio ilustrație externă — doar cod, deci fiecare adăugare de personaj e o configurație nouă, nu o comandă către altcineva.

Un oraș real în spatele ficțiunii

Skotoprigonyevsk, orașul din roman, e inventat — dar Dostoievski l-a modelat după Staraya Russa, orașul în care a locuit chiar el. Harta ilustrată din aplicație (mănăstirea, casa Karamazovilor, hanul din Mokroe, tribunalul) are o pereche: o hartă a orașului real, cu râurile lui adevărate — Polist, Porusya, Pereritsa, Malashka — desenate ca traseu SVG, în poziția lor reală unele față de altele, nu ca fundal generic de hartă.

Cele două hărți sunt legate: alegi un loc real, vezi ce loc din roman îi corespunde, și invers. E o punte între ficțiune și geografie reală, construită manual din cercetare, nu dintr-un API de hărți.

Graful relațiilor

36 de legături între 23 de personaje, aranjate cu o simulare fizică (d3-force) în loc de poziții fixate manual — mai puțin control exact, dar graful se recalculează singur, indiferent câte personaje sau relații se mai adaugă.

Alegi un personaj și tot ce nu se leagă direct de el se stinge vizual; restul rămâne, cu portretele lui încastrate ca medalioane pe noduri. Selecția e scrisă în URL (?focus=id), deci un link spre „relațiile lui Ivan" e ceva ce poți trimite cuiva, nu o stare care se pierde la reîncărcare.

Un quiz verificat, nu doar scris

Opt întrebări, patru variante fiecare — 4⁸, adică 65.536 de trasee de răspuns posibile. Un quiz scris „din ochi" poate ajunge să favorizeze un personaj în aproape toate variantele, sau să facă alt rezultat practic inaccesibil, fără ca nimeni să observe la o citire simplă a întrebărilor.

Un script separat parcurge toate cele 65.536 de trasee, nu doar câteva testate manual, și numără câte duc la fiecare rezultat. Dacă vreun rezultat trece de 30% sau ajunge la 0%, scriptul se oprește cu eroare, înainte să public ceva dezechilibrat. Ordinea de departajare la egalitate (QUIZ_TIEBREAK) vine tot din datele astea — de la cel mai rar rezultat spre cel mai des, ca răspunsurile rare să nu fie înghițite de cele comune.

Citatele, cu o precizare necesară

35 de citate, filtrabile pe personaj și pe una din 12 teme (vina, iertarea, credința, îndoiala...). Fiecare citat are un câmp de atribuire, iar deocamdată toate spun același lucru: traducere de lucru, făcută direct din originalul rus, aflat în domeniu public — nu un citat preluat dintr-o ediție românească publicată, care ar avea drepturi de autor proprii.

E genul de detaliu pe care nimeni nu-l verifică până devine o problemă. Câmpul există exact ca să nu rămână ambiguu de unde vine textul românesc.

Bug-ul de găzduire statică

Next.js 16 exportă încărcăturile de preîncărcare ca foldere — de exemplu harta/__next.harta/__PAGE__.txt. Pe un server Next adevărat, routerul din browser cere aceleași date scrise cu puncte, iar serverul rezolvă singur diferența. Pe o găzduire de fișiere statice, ca Cloudflare Pages, nu există nimeni care să facă rescrierea aia — cererea cu puncte dă 404, iar navigarea pierde preîncărcarea și cade pe o reîncărcare completă a paginii.

Reparația e un script care rulează după fiecare build: copiază fiecare fișier din interiorul folderului și sub numele lui cu puncte, fără să șteargă originalul. Nu e o soluție elegantă — e o constatare că exportul static al Next.js 16 și o găzduire pur statică nu vorbesc încă perfect aceeași limbă.

Un al doilea script, de siguranță

Un script separat verifică, tot după fiecare build, că fiecare link intern din exportul static duce spre un fișier care există și, dacă are o ancoră, că ancora aia chiar apare în pagina țintă. Rulează automat, nu la cerere — ca un link rupt să fie o eroare de build, nu o descoperire a cititorului.

Ce nu face încă

E complet static — nu are cont, nu are server, nu salvează nimic despre cititor. E o alegere, nu o scăpare: nu există niciun motiv real pentru care o hartă de lectură ar avea nevoie de conturi.

Mai lipsesc, și e corect să apară ca atare: căutare generală peste toate secțiunile (cronologie, personaje, citate deodată), și o singură limbă — română, fără variantă în alt idiom.

Stiva

Primul proiect din portofoliu care nu e HTML/CSS/JS simplu: Next.js 16.3.5, React 19 și TypeScript, cu Tailwind CSS 4 pentru stiluri și d3-force pentru layout-ul graful de relații. Exportat static (next build, ieșire în out/) și găzduit pe Cloudflare Pages — nicio parte a aplicației nu rulează pe server la cerere.

Toate portretele, harta ilustrată și harta reală sunt SVG desenat din cod — zero fotografii, zero ilustrații externe importate.

De ce aici și nu la celelalte două: Cheltuieli și Înfloresc au puține ecrane și o stare simplă, ținută de mână — vanilla JS le ajunge, și rămâne cel mai ușor de citit peste un an. Aici sunt șase secțiuni distincte, 23 de portrete compuse din piese interschimbabile și un graf cu stare partajabilă prin URL — genul de compunere și rutare pe care React și Next.js le tratează firesc, nu ceva de reinventat de mână doar ca să rămân la aceeași stivă de dragul consecvenței.

Cod

  • ~6.844 linii, în total
  • Next.js App Router
  • TypeScript

Date

  • 23 personaje
  • 41 evenimente
  • 36 relații
  • 35 citate, 12 teme

Servicii

  • Cloudflare Pages
  • d3-force
  • Tailwind CSS