Questa guida (non ufficiale) altro non è che un Episode Tracker per la serie The Clone Wars, con filtri per ordinare gli episodi, checkbox per tenere traccia di quelli guardati e molto altro.
La serie conta un totale di 133 episodi divisi 7 stagioni ed un film. È stata trasmessa dal 2008 al 2020 e tratta le vicende della famosa Guerra dei Cloni collocata tra Episodio Due ed Episodio Tre.
Inizialmente si trattava di una sorta di antologia, motivo per cui molti episodi non sono stati distribuiti senza seguire l'ordine degli eventi (molto StarWarsiana come cosa). Molti infatti suggeriscono la visione seguendo l'ordine cronologico degli eventi, disponibile in una lista sul sito ufficiale 🔗. Gli episodi infatti sono stati pensati per archi narrativi da due o più puntate connesse tra loro. Non tutte portano avanti la trama principale della serie in modo diretto, ma sono comunque interessanti per i temi trattati, i luoghi, i personaggi approfonditi e le sequenze di azione.
Molti prodotti Star Wars successivi fanno riferimento a personaggi o trame introdotte per la prima volta in The Clone Wars, come The Mandalorian, Ahsoka, Rebels, Rogue One, Andor , The Bad Batch (sequel)
A causa della grande quantità di episodi, l'ordine sparso e il tono a volte infantile della serie, molti abbandonano la serie subito o non la iniziano proprio.
I fan, per incentivare la visione di questo piccolo capolavoro, hanno creato delle guide suggerendo quali, secondo loro, sono gli archi narrativi più essenziali per la trama principale, spiegando caso per caso i motivi.
Come già detto, le guide raccomandano la visione degli episodi seguendo un preciso ordine, diverso da quello ufficiale, ma sulla piattaforma di DisneyPlus non si puo impostare un ordine personalizzato, si deve cercare episodio per episodio. (Il primo episodio da guardare sarebbe il 16esimo della terza stagione...)
La soluzione più immediata è una lista di link diretti agli episodi. La pagina ufficiale 🔗 fa proprio questo, ma:
D'altra parte le liste non ufficiali redatte dai fan forniscono un'attenta descrizione degli archi narrativi, evidenziando quali sono quelli essenziali con attente motivazioni, ma:
La soluzione è un Episode Tracker sviluppato in pagina html interattiva, che salva i progressi in locale del browser (utilizzando il localStorage).
Per ogni episodio si riassumono le informazioni essenziali come titolo, codice dell'episodio, il link DisneyPlus e alla guida ufficiale e soprattutto un checkbox per contrassegnarlo come guardato.
I colori, le animazioni e molti degli elementi del DOM fanno in qualche modo riferimento all'ambientazione di Star Wars.
Il checkbox per contrassegnare l'episodio come visto ha la forma di un ingranaggio (tipico di Star Wars) e ruota su se stesso quando attivato. La forma dell'ingranaggio è l'unione di un cerchio con il bordo spesso e bianco ed uno con il bordo tratteggiato colorato, sovrapposti sull'elemento principale Element
.
I due cerchi non sono altro che le pseudo-classi ::after
e ::before
di Element
mentre il piccolo rettangolo luminoso fa parte invece dello sfondo background-image
e si muove attraverso la proprietà background-position
. (I due "cerchi" sono vuoti proprio per poter vedere lo sfondo sottostante).
Questo frame è interattivo! Premi sui pulsanti per attivare o per espandere il checkbox
Sia gli slot degli episodi che i pulsanti per i filtri quanto attivati si colorano con una animazione che ricorda l'accensione di una Spada Laser.
Questo frame è interattivo! Premi sugli slot per vedere l'animazione
L'animazione segue questi step:
Il glow è generato con il filtro dropshadow
mentre lo sfarfallio è simulato attraverso una funzione lineare altalenante.
filter: dropshadow(0 0 10px blue);
transition: filter 1s linear(0 0%, 0.01 11.76%, 0.08 22.79%, 0.2 27.94%, 0.67 28.68%, 0.31 36.03%, 0.68 38.24%, 0.52 43.38%, 0.76 45.59%, 0.73 52.21%, 0.92 58.09%, 0.84 67.65%, 0.95 77.21%, 1 100%);
funzione altalenante
Lo sfondo del rettangolo invece è un gradiente lineare, inizialmente colorato solo per metà e largo 0%
. L'animazione si baserà sulla proprietà della dimensione, passando da 0%
a 200%
(in modo da superare la metà arrivare alla fine).
background-image:
linear-gradient(
90deg,
orange 50%,
transparent 50%);
background-repeat: no-repeat;
background-size: 0%;
0% 100% 200%
Aggiungiamo due colori simili per dare una piccola sfumatura ed impostiamo una inclinazione di 100deg
per dare un taglio obliquo allo sfondo. Di conseguenza dovremo spostare la dimensione finale a 205%
e spostiamo il punto del trasparente a 50% + 1px
, sufficiente a rendere più morbido lo sfondo evitando una fastidiosa scalettatura (anti-aliasing).
background-image:
linear-gradient(
100deg,
var(--main) 25%,
var(--alt) 50%,
transparent calc(50% + 1px));
background-size: 0%; /* spento */
background-size: 205%; /* acceso */
100% 200% 205%
Il progetto è stato sviluppato con il framework Vuejs💚.
La lista degli episodi è generata utilizzando la potentissima direttiva v-for
, che per ogni elemento di un lista genera direttamente i blocchi nel DOM in modo reattivo. Modificando infatti la variabile della lista (con un filtro ad esempio), l'elenco nel DOM si aggiornerà in tempo reale.
Esempio (approssimativo) di come funziona v-for. list
è un Array di oggetti e ogni oggetto ha un title
, una cover
...:
<div v-for="episode in list">
<p>{{ episode.title }}</p>
<p>{{ episode.code }}</p>
<img :src="episode.cover" alt="">
<a :href="episode.play">Guarda su DisneyPlus</a>
<a :href="episode.guide">Guida</a>
</div>
Se applicassimo la funzione filter()
su list
, selezionando ad esempio solo gli episodi essenziali (episode.must == true
) la direttiva v-for
aggiornerebbe immediatamente l'elenco di elementi.
list
è infatti di tipo Ref
, una classe reattiva di vue per cui ogni sua modifica viene aggiornata in ogni punto del documento.
Esempio di un episodio nella lista EPISODES
{
order: "76",
id: "4x10",
arc: 34,
guide:
"https://www.starwars.com/tv-shows/clone-wars/carnage-of-krell-episode-guide",
title: {
it: "La carneficina di Krell",
en: "Carnage of Krell"
},
play: "https://www.disneyplus.com/video/3ea87b6f-c298-4847-a69d-05d6ec748f59",
must: true,
img: "https://...",
rate: 4,
}
...
Il valore di order
è l'indice dell'episodio nell'ordine cronologico, mentre id
fa riferimento al codice (stagione)X(episodio). Questo è utilizzato proprio per riordinare seguendo la data d'uscita.
Il rate
contiene il giudizio critico, in una scala da 0 a 5.
L'attributo arc
contiene l'indice dell'arco narrativo di cui fa parte l'episodio. Questi sono raccolti in una seconda lista di oggetti ARCS
.
Esempio di un arco narrativo nella relativa lista ARCS
:
{
title: "L'assedio di Umbara",
episodes: ["4x07", "4x08", "4x09", "4x10"],
must: 5,
rate: 4,
reason:
'Questo arco è ampiamente considerato <b>essenziale</b> e cruciale per lo sviluppo dei cloni, in particolare di Rex. Sotto il comando del generale Krell si trovano infatti in una situazione morale e militare molto complessa, che mette alla prova le loro lealtà e la loro individualità.',
}
Ci sono due tipi di valutazione, quella critica rate
e quella della rilevanza per la trama must
. Entrambe esprimono un voto da 0 a 5.
In reason
si spiega principalmente il motivo della valutazione must
, raccontando quello che è il contesto degli episodi, personaggi, luoghi ecc ecc, anticipando le relative connessioni con episodi successivi o le serie tv e film successivi.
Questo paragrafo è importante per lasciare la libertà all'utente di scegliere se guardare o ignorare quell'arco narrativo, sulla base dei propri interessi. Ad esempio alcuni potrebbero ritenere noioso un episodio incentrato sulla politica e preferire quelli sui combattimenti, mentre altri sono più interessati magari ad episodi più lenti e strategici. Alcuni sono interessati ad episodi che approfondiscono certi personaggi e luoghi...
Siccome gli episodi sono 133 e gli archi 52, le due liste occupano molto spazio nel file episodes-data.js
. Si è quindi pensato di compattare il piu possibile, sostituendo le ripetizioni con delle variabili.
Ad esempio i link iniziano sempre nello stesso modo:
guide: "https://www.disneyplus.com/video/...[id-streaming]",
play: "https://www.starwars.com/tv-shows/clone-wars/...[id-guide]"
Se sostituissimo la parte iniziale con una variabile diminuiremmo drasticamente i caratteri ripetutti su tutto il file:
guide: a + "...[id-streaming]",
play: b + "...[id-guide]"
Tuttavia anche le parole chiave dell'oggetto (guide
, play
, rate
...) sono ripetute in ogni oggetto, quindi 133 volte! Ma non possiamo rinunciare ad avere un oggetto, perché diventerebbe scomodissimo reperire le informazioni. L'idea è quindi trasformare la lista di oggetti in una lista di array e creare l'oggetto in un secondo momento.
/* informazioni dell'episodio in un Object */
{
order: "13",
id: "1x09",
arc: 7,
guide:
"https://www.starwars.com/tv-shows/clone-wars/cloak-of-darkness-episode-guide",
title: { it: "Il velo dell'oscurità", en: "Cloak of Darkness" },
play: "https://www.disneyplus.com/video/96574cf0-9dd5-48f4-9625-545a13cff7db?cid=DTCI-Synergy-DDNrs-US-...",
must: false,
img: "https://lumiere-a.akamaihd.net/v1/images/image_17a3b5fc.jpeg?region=0%2C0%2C1560%2C878",
rate: 3,
3,
}
come si presentava l'oggetto prima della "compressione"
Mettiamo le tutte le informazioni dell'episodio dentro delle liste e al fondo inseriamo una funzione map()
che crea l'oggetto:
const EPISODES =
[
/*[...], [...], [...], ... */
[
13,
"1x09",
7,
a + "cloak-of-darkness" + c,
["Il velo dell'oscurità", "Cloak of Darkness"],
d + "96574cf0-9dd5-48f4-9625-545a13cff7db" + f,
F,
e + g + "17a3b5fc" + h + "0%2C0%2C1560%2C878",
],
/*... [...], [...], [...]*/
].map((e) => {
return {
order: e[0].toString(),
id: e[1],
arc: e[2],
guide: e[3],
title: {
it: e[4][0],
en: e[4][1]
},
play: e[5],
must: e[6],
img: e[7],
rate: e[8],
};
});
Stesso procedimento anche per la lista ARCS
.
Il risultato è quasi un dimezzamento della dimensione del file, da circa 100 KB
iniziali a 54 KB
. I caratteri passano da 102.079
a 55.697
(spazi ed indentazioni incluse).
La pagina salva i progressi dell'utente nel localStorage del browser, ovvero in locale e non sono quindi sincronizzati con un database. Questi non vengono persi ricaricando o abbandonando la pagina, ma solo se l'utente resetta il proprio browser.
Script che gestisce la sincronizzazione in locale:
const KEY_STORAGE = "clone-wars-0.1";
export const saveStorage = (s) => {
localStorage[KEY_STORAGE] = JSON.stringify(s);
};
export const loadStorage = () => {
let storage;
try {
storage = JSON.parse(localStorage[KEY_STORAGE]);
} catch (error) {
storage = [];
}
if (!Array.isArray(storage)) {
storage = [];
}
saveStorage(storage)
return storage;
};
Ogni qualvolta l'utente contrassegna un episodio come visto, si aggiorna l'array watchList
e lo si butta nel localStorage
.
Quando l'utente accede alla pagina si fa un tentativo di recuperare dal localStorage
la lista watchList
, se la lista non esiste o se è corrotta ne viene creata una nuova vuota.
Si utilizza il localStorage
perché si prevede che l'utente accederà alla pagina sempre dallo stesso dispositivo. Tuttavia è possibile salvare i propri progressi e sincronizzarli con un metodo quasi "manuale".
Premendo su esporta
si genera un link, in cui come parametro c'è proprio la lista degli episodi. Aprendo quel link su un altro dispositivo o browser la pagina sincronizzerà il proprio localStorage.
In aggiunta alla lista degli episodi c'è anche un timestamp, per evitare di sincronizzare i progressi vecchi su quelli nuovi. In questo caso la pagina chiede all'utente se vuole sovrascrivere con un salvataggio precedente, suggerendo di non farlo.