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.
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