Passa al contenuto principale

RPG Creature Search App

Interfaccia della RPG Creature Search App durante la ricerca di una creatura

Clicca per vedere la demo (1080p, 28 s, 1.1 MB)

Il Progetto

RPG Creature Search App sviluppata come Certification Project finale del corso JavaScript Algorithms and Data Structures di freeCodeCamp. Un’app che unisce JavaScript, gestione dello stato e UI curata in stile Aqua/macOS early 2000, con particolare attenzione a SVG vettoriali, effetti “jelly” e ottimizzazione estrema delle immagini.

Codice Sorgente

<!-- DESIGN 
------
* This file contains the HTML structure of the "RPG Creature Search App" application.
* The design emulates the "Aqua" user interface from Mac OS X 10 (2000s) but reinterpreted
* in certain aspects

* Font Choice:
* I didn't import external fonts, instead I chose native system font stacks
* (like Lucida Grande, Verdana) to ensure historical fidelity to the original OS and instant
* loading performance with no external dependencies

* Flow:
* - Head with meta tags and link to the local style sheet
* - Body containing a <main id="window-container"> element that acts as the frame for the
* main window
* - Inside the <main>, the structure is divided into two inner containers to allow
* independent management, specifically:
* - A <div> ".header-overlap-wrapper" for the title bar and controls (always visible)
* - A <div> "#main-content-container" that wraps all the functional content

* The main container (#main-content-container) is structured in two main blocks:
* - #search-container: Contains the input interface. Uses a "stack" technique
* that overlays elements (background image, icon, and transparent text box) one
* on top of another, instead of placing them side by side, to perfectly recreate the complex
* graphic appearance of the original bar.
* - #creature-output-box: Contains the results area. Currently has "mock-up" data (Creature #1,
* Pyrolynx) that serves as visual placeholder meant to facilitate my subsequent CSS design
* and provides the structure that will be dynamically updated via JavaScript.
-->


<!DOCTYPE html>
<html lang="en">
<head>
<title>RPG Creature Search App</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!--
* freeCodeCamp instructions:
* You should have an input element with an id of "search-input", and is required. ✔️
* You should have a button element with an id of "search-button". ✔️
* You should have an element with an id of "creature-name". ✔️
* You should have an element with an id of "creature-id". ✔️
* You should have an element with an id of "weight". ✔️
* You should have an element with an id of "height". ✔️
* You should have an element with an id of "types". ✔️
* You should have an element with an id of "hp". ✔️
* You should have an element with an id of "attack". ✔️
* You should have an element with an id of "defense". ✔️
* You should have an element with an id of "special-attack". ✔️
* You should have an element with an id of "special-defense". ✔️
* You should have an element with an id of "speed". ✔️
-->

<!--
* Later I use a div instead of a button because if I do that I would have
* to write a lot of "reset" CSS to cancel all the native browser styles,
* still risking unexpected behavior on different browsers.
* I'll handle this accessibility "debt" with semantics and JavaScript
-->

<div id="desktop-app-icon" class="hidden" role="button" aria-label="Reopen RPG Search App" tabindex="0"></div>
<main id="window-container">
<section id="main-content-container">
<div class="header-overlap-wrapper">
<div class="window-top-separator"></div>
<div class="traffic-lights-container">
<button id="btn-close" class="traffic-light red" aria-label="Close window" title="Close"></button>
<button id="btn-minimize" class="traffic-light yellow disabled" aria-label="Minimize window" title="Minimize"></button>
<button id="btn-maximize" class="traffic-light green" aria-label="Maximize window" title="Maximize"></button>
</div>
<h2 class="window-title">RPG Creature Search App</h2>
</div>
<div id="search-container">
<h1 id="main-heading">Search for Creature Name or ID:</h1>
<div class="input-stack">
<div class="search-bg-layer"></div>
<div class="search-content-layer">
<!--
* The magnifying glass icon is a separate element, not drawn directly on the background
* This allows positioning it with millimetric precision via CSS over the bar,
* maintaining independent control of the three layers (background, icon, text)
-->
<svg class="search-icon-physical" width="24" height="24" viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.564 18.1162L24.8043 25.1027" stroke="#686868" stroke-width="3.28349" stroke-linecap="round"/>
<circle cx="10.8521" cy="10.8521" r="9.21039" stroke="#686868" stroke-width="3.28349"/>
</svg>
<input type="text" id="search-input" required aria-label="Search for creature name or ID" autocomplete="off"/>
<div id="aqua-tooltip" class="aqua-tooltip-box hidden-tooltip">Enter an ID (1-20) or name (e.g. Pyrolynx).</div>
</div>
</div>
<div class="search-btn-container">
<button id="search-button">Search</button>
</div>
</div>
<section id="creature-output-box" class="output-container">
<div class="output-left-col">
<div class="creature-title-container">
<h2>
<span id="creature-name">PYROLYNX</span>
<span id="creature-id">#1</span>
</h2>
</div>
<div class="attributes-container">
<p class="description">
<span id="special-name" class="special-name">Blazing Reflex</span><br>
<span id="special-description" class="special-description">Increases speed when hit by a Fire-type move.</span>
</p>

<!--
* I decided to put the fixed text ("Weight: " and "Height: ") and then the number in
* two separate "span" elements, because this way it will be easier in the JavaScript
* phase: when it needs to update the data, it only needs to update the field with
* the number.
-->
<p class="attribute-item">
<span>Weight: </span><span id="weight">42</span>
</p>
<p class="attribute-item">
<span>Height: </span><span id="height">32</span>
</p>
</div>
<div id="types" class="types-container">
<span class="type-badge type-fire">Fire</span>
</div>
</div>
<div class="output-right-col">
<table class="stats-table">
<thead>
<tr>
<th>Base</th>
<th class="text-right">Stats</th>
</tr>
</thead>
<tbody>
<tr>
<td>HP</td>
<td id="hp" class="stat-value">85</td>
</tr>
<tr>
<td>Attack</td>
<td id="attack" class="stat-value">50</td>
</tr>
<tr>
<td>Defense</td>
<td id="defense" class="stat-value">120</td>
</tr>
<tr>
<td>Sp. Attack</td>
<td id="special-attack" class="stat-value">100</td>
</tr>
<tr>
<td>Sp. Defense</td>
<td id="special-defense" class="stat-value">115</td>
</tr>
<tr>
<td>Speed</td>
<td id="speed" class="stat-value">55</td>
</tr>
</tbody>
</table>
</div>
</section>
</section>
</main>
<script src="script.js"></script>
</body>
</html>

Il Progetto Preferito (davvero)

Difficile concludere questo percorso in un modo migliore di questo.
Lo dico spesso, ma questa volta lo sento in modo ancora più forte: è stato il mio progetto preferito.
Mi sono reso conto del perché fatico a trovare cose da dire nei README: i commenti all’interno dei file sono già il README. È lì che ho documentato le decisioni, i compromessi e le scelte progettuali.

Per non lasciare questo README vuoto, ci tengo a riportare due elementi che rappresentano due aspetti importanti del lavoro fatto: il design vettoriale in stile Aqua e la pipeline di compressione immagini che mi ha portato a risultati sorprendenti.

Design: Aqua, Jelly e Icona Figma

Dal foglio di stile:

“Per ottenere la massima fedeltà ai complessi effetti ‘gelatina’ (jelly) e alle texture tipiche di Aqua, ho optato per asset SVG vettoriali per la cornice e i pulsanti principali, delegando al CSS solo le ombre per la profondità.”

Tutti gli elementi chiave dell’interfaccia, ovvero cornice, pulsanti principali, dettagli jelly, li ho creati da zero in Figma.
Per il bottone Search invece ho tratto grande aiuto da una creazione di Arthur Objartel che ha generosamente condiviso il bottone su Figma con la community, che ho adattato allo stile dell’app.

L’icona dell’app (app chiusa) l’ho disegnata anch’essa in Figma:

  • Ho creato il fuoco in stile freeCodeCamp con effetto pixel, usando lo strumento penna: un lavoro minuzioso, ma è stato eccezionale per esercitarmi con questo strumento.
  • La lente che avvolge il fuoco nasce dall’idea di citare il Safari dell’epoca, con quel bordo skeuomorfico e le parti lucide.
  • Ho aggiunto due zone lucide e, per completare il tributo a freeCodeCamp, ho fatto ricadere le parti più scure del contorno sui lati destro e sinistro, proprio come nel logo di freeCodeCamp.

Performance: Pipeline di Compressione Immagini

Sempre da styles.css:

“Ho raggiunto i soli 163KB del background del desktop affinando il mio processo di compressione delle immagini…”

Invece di fare upscaling AI direttamente sul PNG originale, ho seguito una pipeline in tre step:

  1. Conversione PNG → JPEG con già una leggera compressione.
  2. Upscaling AI sul JPEG, non sul PNG, per lavorare su un file già più leggero.
  3. Passaggio finale in ImageOptim per ridurre ancora il peso, mantenendo la qualità visiva.

Il confronto è stato netto, con la procedura standard (upscaling su PNG → JPEG → ImageOptim) ho ottenuto 382 KB, mentre con la procedura ottimizzata (PNG → JPEG compresso → upscaling AI → ImageOptim) soltanto ~163 KB.
Più del 50% di riduzione, con qualità visiva pressoché identica!

Dallo "Static Archive" al Version Control

Questo progetto non rappresenta solo il culmine di questo percorso, ma anche il punto di svolta del mio metodo di lavoro.
Ho trattato intenzionalmente questa repository come un archivio di snapshot perfette: ogni progetto caricato rappresentava un capitolo chiuso e immutabile del mio percorso di apprendimento. Il mio obiettivo era infatti di tracciare la progressione lineare, non il processo di sviluppo ramificato.

Toccare il Limite

Ho toccato con mano con questo progetto i limiti di questo approccio, che potremmo definire "upload manuale".
Ho capito che in un contesto di Software Engineering reale, Git non è un archivio di backup (il Google Drive del codice), ma una macchina del tempo e uno strumento di gestione della complessità. Sono altresì consapevole che ogni metodo di lavoro ha il suo contesto.
Per un percorso di apprendimento come questo sono felice col senno di poi di aver messo come priorità assoluta l'apprendimento dei concetti che mi ha trasmesso freeCodeCamp, ma ora sono pronto per il next level.

Cosa Ho Imparato

Architettura JavaScript e gestione asincrona:

  • Organizzazione del codice in tre macro-aree chiare: gestione dati (fetch, parsing, mapping), gestione UI/accessibilità e emulazione del window manager in stile macOS, con funzioni globali e facilmente testabili.
  • Uso di async/await con try...catch per gestire le chiamate all’API delle creature, distinguendo tra errori attesi (404 "Creature not found") ed errori tecnici generici, e coordinando il tutto tramite una funzione principale handleSearch.
  • Destructuring dei dati API (es. const { id, name, weight: weightValue, height: heightValue, types: creatureTypes, stats, special } = data;) e uso di mappe (STAT_MAPPING) per collegare nomi statistica API a elementi DOM (hp, attack, ecc.), riducendo codice duplicato e aumentando la chiarezza.
  • Pattern di “pulizia” e “rendering” separati: clearResults() per azzerare in blocco output e UI (incluso nascondere il container), displayCreatureData() per riempire i campi, creare dinamicamente i badge dei tipi con createElement/appendChild e riavviare l’animazione CSS con il trucco remove → reflow → add.

Gestione eventi, UX e accessibilità via JS:

  • Gestione coerente dell’input: normalizzazione con .trim().toLowerCase(), guard clause su input vuoto, invio via Enter intercettando keydown e trasformandolo in click sul bottone.
  • Tooltip “Aqua” personalizzato con timer: delay di 300ms su mouseenter, clearTimeout su mouseleave e chiusura immediata su focus/keydown per evitare distrazioni quando l’utente inizia a digitare.
  • Emulazione dei pulsanti “semaforo” (chiudi/minimizza/massimizza) con logica centralizzata in updateTrafficLightStates(), che tiene conto di: finestra chiusa/aperta, stato maximized, e dimensione viewport (desktop vs mobile), aggiornando pulsanti e classi in maniera reattiva anche su resize.
  • Aggiunta di accessibilità a elementi non semantici: icona desktop implementata come <div> ma resa accessibile via tastiera con listener su keydown per Enter, replicando il comportamento di un vero button.

Web Performance Optimization (HTML/CSS):

  • Pipeline ragionata per ridurre drasticamente il peso del background desktop: PNG originale → JPEG leggermente compresso → upscaling AI sul JPEG → passaggio finale in ImageOptim, passando da ~382 KB a ~163 KB senza perdita visibile di qualità.
  • Uso mirato dell’embedding Base64 per asset molto piccoli (sotto i 5KB), iniettandoli direttamente nel CSS per eliminare richieste HTTP extra e prevenire flash di caricamento di icone/texture di interfaccia.

Layout avanzato con CSS Grid e Flexbox:

  • Sperimentazione con la “Stack Technique” tramite CSS Grid: definizione di una singola area (es. grid-area: stack) assegnata a più elementi (sfondo, icona, input) per sovrapporli con precisione, evitando assoluti caotici; comprensione che l’ordine di sovrapposizione segue anche l’ordine dell’HTML quando gli z-index sono impliciti.
  • Uso combinato di Flexbox e Grid: layout a colonna singola/flessibile sui device piccoli e passaggio a Grid su viewport più ampie per gestire meglio allineamenti, spaziature e proporzioni della finestra Aqua e dei suoi contenuti.

Problem solving UI: visibilità, layout e animazioni:

  • Comprensione profonda della differenza tra display: none e visibility: hidden e dei loro effetti su layout e animazioni:
    • display: none rimuove l’elemento dal flusso causando “salti” (collapse di altezza) su mobile.
    • visibility: hidden conserva lo spazio ma può interferire con animazioni che richiedono reinserimento nel flusso.
  • Soluzione tramite due utility distinte: una classe per controllare il layout (es. .hidden) e una per il solo aspetto/visibilità (es. .invisible), così da separare responsabilità tra “cosa occupa spazio” e “cosa si vede”.

Accessibilità (A11y) a livello di CSS e struttura:

  • Verifica dei contrasti colore per i badge dei tipi di creatura (FIRE, WATER, ROCK, ecc...) usando strumenti come WebAIM Contrast Checker, e adattamento dei colori per raggiungere almeno WCAG AA (4.5:1 su normal text).
  • Introduzione di @media (prefers-reduced-motion: reduce) per rispettare utenti sensibili al movimento, spegnendo o semplificando animazioni come il pop-up della finestra o transizioni troppo vistose.
  • Consapevolezza del “debito di accessibilità” quando si scelgono elementi non semantici (div al posto di button) per esigenze di design/CSS, e bilanciamento con role, aria-label e tabindex (anche se parte di questo è gestito soprattutto via JS).

Tipografia, design e “Aqua thinking”:

  • Studio dei font nativi sui vari sistemi (Lucida Grande, Verdana, DejaVu Sans, ecc.) per replicare il feeling Aqua senza dover caricare font esterni, costruendo una font stack robusta e storicamente accurata.
  • Scoperta e uso del moderno text-wrap: balance per ridurre “widows” e “orphans” tipografiche, distribuendo meglio il testo nei titoli e nei blocchi descrittivi.
  • Ricostruzione di uno stile skeuomorfico credibile usando combo di SVG vettoriali (per forme complesse) e CSS moderno (box-shadow, gradienti, filtri) invece di PNG pesanti, mantenendo al tempo stesso performance e nitidezza su schermi ad alta densità.

Architettura HTML/CSS orientata al JS futuro:

  • Organizzazione del CSS con un approccio “utility-first per le variabili”: colori, spaziature, dimensioni centralizzate in :root, e una gerarchia che combina ID per macro-sezioni e classi riusabili per componenti.
  • Strutturazione dell’HTML pensando alla manipolazione JavaScript: separazione tra etichette statiche (“Weight:”) e valori dinamici in <span> dedicati, facilitando aggiornamenti mirati senza dover ricostruire stringhe testuali intere.

Architettura di progetto e commenti come README:

  • Documentare direttamente nel codice le scelte architetturali e di design, in modo che chi legge i file abbia già tutto il contesto.
  • Usare i commenti non solo per spiegare “cosa fa” il codice, ma “perché è stato progettato così”.

Riflessione Finale

Questo è stato il culmine, dopo il quale sono obbligato a fermarmi: ho 5 esami universitari che mi aspettano, oltre a svariati elaborati e laboratori.
Vorrei fermare la mia vita ed iniziare subito il corso “Front End Development Libraries Certification” di freeCodeCamp, perché la “fiamma” è al massimo, ma se lo facessi ora rischierei di ritardare la laurea. E, paradossalmente, è lo stesso freeCodeCamp a suggerire pazienza: il corso non è ancora completo, e i moduli finali (Data Visualization and D3, TypeScript Fundamentals e l’esame finale) riportano ancora Coming 2026.

Per quanto siano stati mesi passati per lo più da solo, in compagnia di questo percorso, non esagero se dico che è stato uno dei periodi più belli della mia vita, se non il più bello.

È arrivato il momento di dedicare nuovamente tutte le energie all'università, ma non sarà difficile grazie alla resilienza e pensiero logico che mi ha donato JavaScript.


Next:
Università ed… evolvere questa repository in una knowledge base navigabile, così che non resti solo un archivio personale ma possa diventare uno strumento di supporto per chi sta affrontando lo stesso percorso.