Rework dei bonus giornalieri + codici riscattabili — guadagna frammenti ogni giorno e usa i codici della community
Due cambiamenti per la community arrivano insieme. (1) Il login giornaliero non distribuisce più pacchetti gratis con uno schema piatto — ora concede frammenti di pacchetto ogni giorno su un ciclo [3,3,3,5,5,5,8], e completare una settimana consecutiva dà un bonus pacchetti crescente (1 → 2 → 3 → 4, tetto a 4). Saltare un giorno fa azzerare la serie — completa la settimana per portarti a casa la ricompensa di ancoraggio. (2) Nuova sezione nel tab Profilo per riscattare codici (es. WELCOME-2026, codici di partner, giveaway di creator). I codici possono dare qualsiasi combinazione di pacchetti, frammenti e pezzi per rarità; l'admin li scrive dal nuovo tab Codici del pannello, con tetti di uso e scadenze opzionali.
Bonus giornalieri
- Ciclo giornaliero di frammenti: [3, 3, 3, 5, 5, 5, 8] secondo la posizione nella serie. I frammenti si accumulano per la forgia e la conversione in pacchetti.
- Bonus pacchetti a fine settimana al giorno 7 / 14 / 21 / 28 / …: +1 pacchetto la prima settimana, +2 la seconda, +3 la terza, +4 dalla settimana 4 in poi (tetto).
- Salti un giorno → la serie si azzera; il prossimo bonus di 'settimana completata' riparte da +1. I vecchi bonus a tappe +3 / +5 / +10 ai giorni 7 / 14 / 28 sono ritirati.
- Interfaccia del calendario aggiornata: striscia di 7 giorni con la settimana corrente e un badge 'Sett N' che mostra le settimane completate cumulative.
- Le serie esistenti sono state azzerate al deploy così tutti iniziano il nuovo schema alla pari — niente credito di mezza settimana riportato.
Codici riscattabili
- Nuova sezione 'Riscatta' nel tab Profilo — inserisci un codice, vedi la ricompensa, controlla gli ultimi 20 riscatti.
- Ogni utente può riscattare lo stesso codice al massimo una volta (garantito da un indice unico per (codice, utente) — niente riscatti doppi).
- I codici possono contenere pacchetti, frammenti e pezzi per rarità in qualsiasi combinazione; l'admin imposta la ricompensa alla creazione.
- Tetto totale di utilizzi opzionale (es. 'solo i primi 100 riscatti') e data di scadenza per codice (es. 'valido fino a venerdì').
- Limitato a 5 tentativi al minuto per utente per togliere di mezzo il brute-force a colpi di typo. Ogni creazione / modifica / cancellazione di codici da parte dell'admin finisce nel log di audit.
Sweep più ampio di traduzioni in-game
- Le etichette di carta (ATK / DEF / PV / PM / Passiva), i badge (NUOVA!, ✦ Nuova, In rotazione presto, Legacy) e le fasi (Cucciolo / Fase 1-3 / MAX) ora appaiono nella tua lingua in collezione, rivelazione gacha e schermate di battaglia.
- Campanella delle notifiche + toast di guerra di gilda: ogni riga, suffisso 'X fa', badge vittoria/sconfitta/pareggio e riepilogo ricompense ora passa per la locale, con plurali corretti in polacco e russo.
- Il decoro della sala di battaglia (copy della lobby, overlay 'ruota il dispositivo', etichetta della mano, prefisso dell'avversario, suggerimento di bersaglio, link Annulla) e l'overlay del video con ricompensa (caricamento, motivi di errore, pulsante di ritiro, avviso di tenere visibile) sono ora completamente localizzati.
- Flusso di conferma della galleria di copie (progresso di livello, conferme di scioglimento, avvisi di mazzo bloccato e scambio in sospeso, countdown del rate limit, anteprima di frammenti / pezzi) — ogni stringa che era solo in inglese ora è per lingua.
- Il fallback ErrorBoundary (pulsante Ricarica + copy sicuro) e i tooltip della WalletBar (pacchetti / frammenti / pity / bonus giornaliero pronto) ora appaiono nella lingua attiva nelle 12 locale supportate.
- Schermate pre-login tradotte da capo a piedi: login / registrazione / 2FA / password dimenticata / reset password (titoli, sottotitoli, placeholder, pulsanti, messaggi di errore e successo, copy del gate età + ToS, avviso email non verificata con timer di cooldown).
- Overlay di onboarding al primo avvio (3 passi attivi + indicatori a puntini, pulsanti salta e avanti, tutte le descrizioni di tipi di carta / rarità / holo / fase / abilità) ora nella tua lingua perché il flusso del primo pacchetto arrivi nativo.
- Frasi-flavor durante l'apertura dei pacchetti: 58 frasi uniche × 12 locali (~700 scritte creative) così il testo che ruota mentre si apre un pacchetto si legge nella tua lingua su pacchetti standard, oro e diamante.
Rifinitura linguistica su tutto il sito
- Il banner dei cookie ora cambia lingua correttamente ogni volta che cambi locale — prima rimaneva bloccato sulla prima lingua visitata dal secondo cambio in poi (es. /es → /en cambiava, ma /en → /it lasciava il banner in inglese sopra la pagina italiana).
- Carte in Vetrina (la pagina showcase) tradotta da capo a piedi in tutte le 12 lingue supportate — ogni nome di carta e ogni testo marketing da 100-150 parole è ora nativo invece di ricadere sullo spagnolo per i visitatori non ispanofoni e non anglofoni.
- Voci della pagina Novità: questa voce e le due precedenti (2026-04-28 — traduzione del testo delle carte + chiarimento del tetto di Scioglimento in massa) ora sono anche completamente native in tutte le 12 lingue. Le voci più vecchie ricadono elegantemente sull'inglese mentre le completiamo nelle prossime release.
- Modale dei dettagli della carta: l'etichetta 'X copie possedute' e il tetto di Scioglimento in massa ora riflettono la carta che stai effettivamente guardando — prima, aprire una carta e poi navigare a un'altra nella stessa sessione del modale mostrava il conteggio della PRIMA carta per ogni carta successiva, dando numeri sbagliati come '4 copie' su una pila di cui ne possedevi davvero 19.
Il testo delle carte ora nella tua lingua — nomi, flavor e descrizioni delle abilità tradotti per tutte le 113 carte
Fino a oggi la cromatura dell'interfaccia era localizzata ma il contenuto delle carte restava in inglese: nomi, righe di flavor, nomi e descrizioni delle abilità apparivano in inglese a prescindere dalla lingua scelta. Abbiamo tradotto le 113 carte in spagnolo (Spagna + fallback LATAM), francese, italiano, tedesco, portoghese (BR + fallback PT), polacco, olandese, russo e turco — cioè il nome canonico più il flavor, più i nomi e le descrizioni delle abilità attive e passive quando esistono. Il payload server in inglese resta la fonte autoritativa per il database; ciò che vedi a schermo viene sovrapposto lato client in base alla lingua, così l'identità di una carta resta la stessa fra cambi di lingua e scambi. Se manca una traduzione per qualche campo di qualche carta, cade sul testo inglese, mai su uno spazio vuoto.
Cosa è cambiato
- Tutti i 113 nomi delle carte tradotti in 11 lingue target (es, es-419, fr, it, de, pt-BR, pt-PT, pl, nl, ru, tr) — pt-PT e es-419 sono ora popolati in toto come varianti native, non come fallback.
- PT-PT (portoghese europeo): grafie regionali (Fênix→Fénix, Tectônico→Tectónico, Gêiser→Géiser, Fumaça→Fumo), lessico distinto (mordida→dentada, filhote→cria, pulinho→saltinho, quica→salta, terremoto→terramoto, demais→restantes, libera→liberta, bravo→zangado), posizionamento enclitico (se alimenta→alimenta-se, a se tornar→a tornar-se) e imperativi in forma tu (Você não passará→Tu não passarás).
- es-419 (spagnolo latino-americano): formulazioni naturali LATAM (Picado en Bomba→Bombardeo en Picada), evitato 'Concha' (volgare nel Cono Sud) sostituito con 'Caparazón' per sicurezza, e nota chiara che il voseo (AR/UY) non è intenzionalmente applicato per tenere il catalogo universale in tutto il LATAM.
- Righe di flavor tradotte per ogni carta — 113 stringhe uniche × 9 lingue.
- Nomi e descrizioni delle abilità attive tradotti per le 60 creature che ne hanno.
- Nomi e descrizioni delle abilità passive tradotti per le 50 creature che ne hanno.
- Collegato in ogni punto in cui una carta viene renderizzata: griglia della collezione, modale carta, comparatore, deck builder, modale di forgia, scheda Forgia, rivelazione gacha, sala draft, mano e zone in battaglia, tooltip al passaggio del mouse, anteprime durante il drag, dropdown delle proposte di scambio e anteprima dello scambio.
Note
- Le righe di stat degli oggetti come '+5 ATK.' o 'Fuoco +3 ATK.' restano com'erano — sono testo meccanico dove la traduzione non aggiungerebbe nulla.
- L'identità della carta (card_id, instance_id, seme holo, ATK/DEF/PV/PM) si preserva attraverso l'overlay di lingua — cambiano solo le stringhe mostrate. Scambi, mazzi e replay di battaglia sono 100 % compatibili fra lingue.
Il tetto di Scioglimento in massa ora si spiega da solo — vedi perché il tuo massimo è quello
Feedback dei giocatori dopo il lancio di Scioglimento in massa: il changelog diceva "fino a 50 per lotto" ma un giocatore con solo 21 copie di una carta si trovava un tetto di 20 e pensava che qualcosa fosse rotto. Nulla era rotto — il server garantisce "tieni almeno 1 copia di ogni carta", quindi una pila di 21 copie raggiunge il tetto a 20 in un colpo. Il tetto è sempre stato min(tetto server 50, copie idonee, copie totali − 1); era solo invisibile. Ora l'etichetta del bottone d'ingresso dice esattamente il tuo tetto di lotto in formato "max N di M idonee" e il tooltip al passaggio del mouse spiega la restrizione che lo lega (tetto server, idoneità o tieni-1). L'intestazione del pannello di selezione ripete la restrizione così non devi indovinare.
Cosa è cambiato
- Il bottone d'ingresso di Scioglimento in massa ora dice "💠💠 Scioglimento in massa... (max N di M idonee)" al posto del solo "(M idonee)" — N è il tetto reale di selezione che onora tetto server + tieni-1.
- Il tooltip al passaggio del mouse sul bottone mostra la causa vincolante: "max server 50", "M idonee" o "tieni 1 su T totali".
- L'intestazione del pannello di selezione ora aggiunge la restrizione vincolante dopo il numero del tetto, così la causa si vede senza dover passare con il mouse.
- Comportamento invariato: la regola tieni-1 è sempre stata applicata lato server. Questo cambio è pura chiarezza UX — stesse protezioni, etichette più trasparenti.
Scioglimento in massa — sciogli fino a 50 copie di una carta in una sola azione
Accumulatori, questa è per voi. Sciogliere le copie comuni una alla volta bruciava la finestra di rate-limit per utente — un giocatore con 80 comuni di una sola carta non poteva realisticamente liberarsene in una sola seduta. La vista dettaglio della carta mostra ora un bottone "Scioglimento in massa..." sotto lo Scioglimento per copia già esistente, che appare quando hai almeno 2 copie idonee della carta che stai guardando. Cliccaci sopra, scegli quali numeri di stampa distruggere da una griglia di tessere (le copie bloccate, nel mazzo o in uno scambio attivo appaiono attenuate e disabilitate), e l'intero lotto passa con una sola conferma + un solo slot di rate-limit. Il tetto server è 50 per chiamata, così la risposta resta rapida. L'anteprima ricompensa ti mostra esattamente i frammenti + i pezzi per rarità che riceverai prima di premere Distruggi tutto.
Cosa è cambiato
- Vista dettaglio carta → nuovo bottone "💠💠 Scioglimento in massa... (N idonee)" sotto lo Scioglimento per copia. Appare solo quando hai ≥2 copie idonee (idonee = sbloccate + non in mazzo + non in scambio attivo).
- La griglia di tessere mostra ogni copia con il suo numero di stampa e i marker holo / bloccata / nel-mazzo / in-scambio. Le tessere non idonee sono attenuate e disabilitate. La selezione è limitata a min(50, idonee, copie − 1) così non puoi mai azzerare la pila.
- Controlli "Seleziona tutto" + "Pulisci" + un'anteprima dal vivo "+X frammenti / +Y pezzi R" che rispecchia l'UI di ricompensa per copia. Il modale di conferma chiede una volta prima di qualsiasi chiamata di rete: "Sciogliere N copie di \"Nome carta\"? +X frammenti +Y pezzi R. [Annulla] [Distruggi tutto]".
- Lato server: viene consumato un solo slot di rate-limit a prescindere dalla dimensione del lotto — è proprio il senso della funzione. L'intera transazione è atomica (tutte e 50 le copie si sciolgono insieme oppure nessuna).
- Sicurezza sugli scambi pendenti: se una copia selezionata si trova in uno scambio attivo, il server avvisa e offre un prompt "Annulla & Sciogli" che reinvia la chiamata con conferma esplicita. Niente sovrascrittura silenziosa di uno scambio in corso.
- Sicurezza ultima copia: per ogni carta, il server garantisce che mantieni almeno una copia. Un lotto che azzererebbe una pila viene rifiutato con un errore chiaro e nessuna riga viene cancellata.
Collezione: filtro "In rotazione presto" + classifiche pubbliche dei tornei di gilda
Due piccole ma visibili migliorie di qualità della vita. Nella scheda Collezione, il menu a tendina di disponibilità guadagna l'opzione "In rotazione presto" che filtra la griglia alle carte che scadono nei prossimi 30 giorni — stessa soglia del badge che già appariva su quelle carte, così filtro e badge restano sempre d'accordo. Nella scheda Torneo, i tornei di gilda mostrano ora il loro pannello classifica a qualunque visitatore — non solo ai partecipanti iscritti. Prima si apriva un torneo di gilda e si vedevano i partecipanti ma nessun tabellone se la gilda non era registrata. Ora il pannello pubblico si renderizza per tutti, leggendo gli stessi dati che il server già restituiva, così puoi vedere chi è in testa senza doverti prima iscrivere.
Cosa è cambiato
- Collezione → menu disponibilità → nuova opzione "In rotazione presto" filtra le carte il cui `available_until` cade nei prossimi 30 giorni. La riga dei chip attivi mostra un chip "In rotazione presto" rimovibile mentre il filtro è attivo.
- La finestra di 30 giorni è ora un'unica costante condivisa, così il filtro e il badge "In rotazione presto" esistente non possono divergere.
- Scheda Torneo → tornei di gilda → il pannello classifica si renderizza anche per i visitatori non iscritti. Stessi dati della vista partecipante; il pannello ti mostra chi è in testa anche prima che la tua gilda si registri.
- I tornei non di gilda restano invariati — il pannello pubblico si renderizza solo quando `tournament_type === "guild"`.
Hardening build + CI — gate di qualità spostati in CI, deroghe per l'audit delle dipendenze, refresh giornaliero della IAB GVL — sweep di revisione ostile delle 14 aree completato
L'ultima area dello sweep di revisione ostile delle 14 aree — pipeline di build e gate di CI. La maggior parte dei cambiamenti è invisibile ai giocatori (vivono in `.github/workflows/` e `scripts/`) ma spostano la qualità da «solo locale con bypass --no-verify» a «bloccante per il merge su ogni PR». L'intera suite vitest (~2700 test), il type-check TypeScript, il drift gate di Drizzle, la parità delle chiavi i18n, il pin BullMQ, la validazione battle-config e la baseline bare-db-execute girano ora tutti in CI su ogni PR. Lo scan CVE di produzione ha guadagnato un gate consapevole delle deroghe che espone le vulnerabilità moderate (la soglia precedente ne silenziava sei). L'IAB Global Vendor List usata dal banner di consenso si aggiorna ora quotidianamente tramite un workflow programmato che apre una PR quando la lista cambia. Due test skipped della suite battle portano riferimenti tracciati (T-201, T-202) e un nuovo gate invariante fa fallire il build su ogni skip futuro non taggato così che il codice morto non si accumuli.
Cosa è cambiato per i giocatori
- Niente di visivo. L'intero lotto è tooling interno — workflow CI, gate di audit dipendenze, igiene di build. La nota di chiusura conta perché la revisione di 14 aree ha fatto emergere ~600 risultati su tutto il codice (auth, motore di battaglia, schema, gacha, API admin, API pubblica, Game.tsx + tab, UI carte/mazzi/battaglia, internals profilo/gilda/torneo, anticheat, i18n, legale/consenso, osservabilità, infra build/test) — ogni fix visibile ai giocatori delle precedenti tredici aree è già uscito in entry di changelog precedenti. Questa entry chiude solo lo sweep.
- Lato operatore: l'IAB Global Vendor List che il banner di consenso usa per la disclosure per fornitore si aggiorna ora quotidianamente tramite una GitHub Action programmata che apre una PR quando la lista cambia. Una GVL obsoleta era un rischio di deriva di scope per fornitore (caso peggiore: Limited Ads di AdSense in UE). Una persona rivede ogni PR di refresh prima del merge.
- Due dipendenze di produzione con vulnerabilità note (next-intl + postcss-via-next) sono ora formalmente documentate in `context/refs/cve-waivers.md` con date di revisione esplicite (2026-07-25). Il gate CVE precedente le nascondeva alzando la soglia di severità; il nuovo gate espone tutte le moderate-e-superiori e fa fallire la CI a meno che una riga di deroga non le copra. Visibilità onesta invece di rischio silenzioso.
Stabilità del server + privacy nei log — handler di errori del processo, retention dell'audit applicata, nomi dei giocatori oscurati nei log
Una passata sulla pipeline di osservabilità e audit ha chiuso il divario tra quello che la privacy policy dice e quello che il server fa davvero. Niente è cambiato visualmente per i giocatori, ma alcune cose sono migliorate sotto: il processo Node.js ora registra e conta gli errori non catturati invece di andare giù in silenzio e lasciare che la piattaforma lo riavvii senza spiegazione; la tabella audit_log viene ora purgata quotidianamente da un cron che applica la finestra di retention di 90 giorni promessa dalla privacy policy; i nomi visualizzati dei giocatori vengono ora oscurati dalle righe di log del server (il Considerando 30 del GDPR tratta i nomi visualizzati come dati personali); e la pipeline di audit degli eventi di sicurezza ha guadagnato check di durabilità e provenance per IP-hash per azione, così un'ispezione del regolatore può ricostruire qualunque azione di account con timestamp, attore e origine.
Cosa è cambiato
- Il processo Node.js ora registra un errore non catturato prima che la piattaforma lo riavvii. Prima il server moriva in silenzio, il container si riavviava da solo e il debugging post-incidente richiedeva di indovinare dai timestamp di morte del container. Ora ogni errore fatale atterra nel log strutturato con lo stack trace completo + un contatore così possiamo graficare i tassi di crash nel tempo.
- La privacy policy dice che i log di audit sono conservati 90 giorni. Prima nulla lo applicava davvero — le vecchie righe si accumulavano per sempre. Un nuovo cron giornaliero (`/api/cron/audit-retention`) elimina tutto ciò che ha più di 90 giorni in lotti, così la tabella resta limitata e la promessa della policy è onesta.
- I nomi visualizzati dei giocatori vengono ora oscurati dalle righe di log del server. Il Considerando 30 del GDPR tratta i nomi visualizzati come dati personali; la forma precedente dei log includeva i campi winnerName / challengerName / opponentName per esteso in ogni riga di fine battaglia. Pino redact li maschera con [Redacted] prima che raggiungano l'aggregatore.
- Lato operatore: gli endpoint /api/metrics + /api/health supportano ora un gate per token Bearer in produzione così i dati di osservabilità non sono esposti sull'internet aperto (impostare METRICS_BEARER_TOKEN / HEALTH_TOKEN; il dev tollera l'assenza). L'endpoint di report di violazione CSP (/api/csp-report) limita ora la frequenza per IP così uno spammer non può sommergere gli alert CSP reali.
Rafforzamento legale e del consenso — stringa TCF v2.3 ufficiale, audit del consenso duraturo, età + ToS imposti lato server, elenco reale dei processori, cancellazione + esportazione dati ampliate
Un giro sulla superficie legale e del consenso ha chiuso una lunga lista di buchi di compliance. Titoli: la stringa di consenso IAB TCF v2.3 è ora il formato bit-packed ufficiale prodotto dalle librerie @iabtechlabtcf/* invece di uno shim base64(JSON) — il parser di Google AdSense rifiutava il nostro shim dalla scadenza del 2026-03-01, e questo declassava silenziosamente i visitatori UE a Limited Ads. Ogni scelta sul banner del consenso (Accetta tutto / Rifiuta tutto / Personalizza / ritiro / prompt di version bump / riapertura) scrive ora una riga duratura su audit_log così possiamo dimostrare il consenso sotto un'ispezione Art.7(1) GDPR. La conferma dell'età 16+ e l'accettazione dei ToS in registrazione sono ora imposte lato server tramite gli additional fields di Better Auth — la precedente checkbox solo client si poteva saltare via curl. La privacy policy nomina ora i veri terzi (Arsys per hosting + Google AdSense per advertising) invece di copy generico — Postgres e Redis girano su infrastruttura Arsys gestita dall'operatore e non sono processori distinti. La cancellazione dell'account ripulisce ora un insieme molto più ampio di tabelle con dati personali (amicizie, appartenenza alla gilda, iscrizioni a tornei, ticket, storico ad-watch, seed provably-fair, token di verifica e diverse altre), usa un suffisso pseudonimo crypto-random che rompe la precedente correlabilità con audit_log, rifiuta di ricreare una richiesta di cancellazione per un account già completamente cancellato, e limita i retry del cron così un fallimento transitorio non resta in loop per sempre. L'artefatto di esportazione dati ha guadagnato sei nuove sezioni (appartenenza alla gilda, tornei, amicizie, ticket di supporto, storico ad-watch, storico seed provably-fair) e tetti per riga per sezione così un utente power non possa mandare in OOM la build dell'export. Il banner del consenso è ora un vero dialog modale con focus trap e aria-modal, il selettore di lingua è visibile a ogni breakpoint (era nascosto su mobile), e i ban admin consegnano ora una Dichiarazione dei Motivi Art.17 DSA via notifica in-app con la regola violata e un percorso di ricorso. Inoltre: una IAB Global Vendor List fresca inclusa e uno script di refresh giornaliero così i cambi di scope dei fornitori non vadano alla deriva, i cookie first-party di AdSense davvero cancellati al ritiro del consenso (prima veniva smontato solo il tag script), e un nuovo gate CSRF con token sugli endpoint di cancellazione account e esportazione dati così una pagina attaccante con form falsificato non possa avviare il countdown di cancellazione per un visitatore loggato. Versione del consenso passata da 3.1 a 3.2 — ogni visitatore vedrà il banner una volta alla prossima visita per riconfermare sotto la lista aggiornata di processori.
Stringa IAB TCF v2.3 reale + audit del consenso duraturo
- La stringa di consenso TCF v2.3 è ora prodotta dall'encoder ufficiale @iabtechlabtcf/core e l'API di gestione del consenso è la libreria ufficiale @iabtechlabtcf/cmpapi — sostituendo uno shim base64(JSON) che il parser di Google AdSense rifiutava dal 2026-03-01, declassando i visitatori UE a Limited Ads. La CMP si installa come `__tcfapi` sulla pagina esattamente secondo la specifica IAB CMP v2.
- Ogni risoluzione del banner del consenso (Accetta tutto / Rifiuta tutto / Personalizza / ritiro / prompt di version bump / riapertura) scrive una riga duratura su audit_log con tag scope='legal' con il payload completo di consenso per finalità, gli opt-in di funzioni speciali, la stringa addtl_consent, la versione precedente e un flag per indicare se la curation Google ATP mancava al momento del consenso. Ora possiamo rispondere a 'dimostra che questo utente ha acconsentito in questo momento sotto questa versione' solo da audit_log — prima del fix la traccia non esisteva.
- La IAB Global Vendor List inclusa è stata aggiornata e uno script `npm run refresh:gvl` permette a un cron giornaliero di depositare uno snapshot fresco. Una GVL stantia era un rischio di drift dello scope dei fornitori — ogni refresh esegue un controllo di schema prima del rename atomico su disco, così un fetch interrotto non lascia mai un file a metà.
Età + ToS + CSRF + elenco dei processori imposti lato server
- La registrazione persiste ora `age_confirmed_at` e `tos_version_accepted` sulla riga utente tramite gli additional fields di Better Auth, e un hook lato server rifiuta in modo duro qualsiasi registrazione priva di uno dei due valori. Prima del fix erano entrambe checkbox solo lato client che qualsiasi richiesta curl poteva saltare.
- L'elenco dei processori nella privacy policy nomina ora i veri terzi: Arsys (Spagna) per l'hosting + Google AdSense / AdMob per la pubblicità. Postgres e Redis girano su infrastruttura Arsys gestita dall'operatore e non sono processori separati. La voce ipotetica 'processore di pagamento' sparisce finché lo store cosmetico non esce davvero sotto un futuro flag.
- Gli endpoint di cancellazione account ed esportazione dati richiedono ora un token CSRF (pattern double-submit cookie), così una pagina attaccante con form falsificato non può avviare il countdown di cancellazione a 30 giorni né rubare l'artefatto di esportazione di una vittima loggata. Il cookie di sessione di Better Auth è sameSite=Lax, che altrimenti avrebbe permesso POST cross-origin da navigazione top-level.
Cancellazione account ed esportazione dati ampliate
- La cancellazione account cancella o anonimizza ora un insieme molto più ampio di tabelle con dati personali: amicizie e richieste di amicizia, appartenenza e chat di gilda, richieste di ingresso e wishlist di gilda, partecipanti e round di torneo, ticket di supporto e risposte, token e storico ad-watch, storico seed provably-fair, token di verifica email e reset password — e applica l'anonimizzazione sentinella a match-telemetry, donazioni al fondo di gilda e diverse altre tabelle con ritenzione per audit. Prima del fix più di dieci tabelle conservavano silenziosamente i dati dell'utente anche dopo che la cancellazione era 'completata'.
- Lo pseudonimo usato per sovrascrivere nome ed email di un account cancellato usa ora un suffisso crypto-random invece di una sezione deterministica di 8 caratteri dell'id utente originale. La sezione precedente portava 32 bit di entropia dall'id originale, così un regolatore poteva correlare il nome sentinella all'utente cancellato tramite audit_log.target_id. Il nuovo suffisso rompe del tutto il collegamento.
- Due richieste di cancellazione simultanee dello stesso utente collassano ora in una sola riga a livello di database (indice unico parziale su `(user_id) WHERE status IN ('pending','processing')`); il POST della richiesta restituisce `already_pending` invece di crashare con un 500. Un utente la cui cancellazione è già completata non può creare una nuova richiesta — il nuovo check `wasUserEverDeleted` rifiuta direttamente, difendendo da un futuro un-ban che altrimenti resusciterebbe un account a metà anonimizzato.
- Se una transazione di cancellazione fallisce a metà (violazione di FK, lock timeout, blip di rete) il worker del cron incrementa ora un contatore di retry e salva l'ultimo messaggio di errore invece di restare in loop in `pending`. Dopo cinque tentativi falliti la riga passa a `failed` per attenzione admin.
- L'esportazione dati copre ora sei nuove sezioni: appartenenza alla gilda, iscrizioni a tornei, amicizie, ticket di supporto e risposte, storico ad-watch, e storico seed provably-fair. L'auto-documentazione `gaps[]` precedente le elencava come 'ce l'abbiamo ma non la diamo' — il che violava la portabilità dell'Art.20 GDPR. Ogni sezione porta anche un tetto di 10.000 righe così un utente power non manda in OOM la build dell'export, e un campo `truncations[]` segnala onestamente gli export parziali.
Accessibilità del banner + igiene dei cookie AdSense + consegna Art.17 DSA
- Il banner dei cookie è ora un vero dialog modale: role='dialog', aria-modal='true', aria-labelledby che punta a un'intestazione solo per screen reader, e un focus trap che cattura il focus all'apertura e lo ripristina alla chiusura. Il Tab non può scappare sulla pagina sotto. L'Esc intenzionalmente NON chiude perché sarebbe un rifiuto implicito — un dark pattern.
- Il selettore di lingua (ES | EN) è ora visibile a ogni breakpoint, inclusi i mobile — prima del fix era nascosto sotto il breakpoint sm:, e si abbinava male al bug del middleware F-A11-009 che già silenziosamente metteva i visitatori non-default-locale nella lingua sbagliata.
- Ritirare il consenso pubblicitario cancella ora davvero i cookie first-party di AdSense (`__gads`, `__gpi`, `__eoi`, `FCNEC`, `FCCDCF`) dal browser, non solo il tag di script iniettato. Prima del fix un ritiro lasciava quei cookie al loro posto, il che violava ePrivacy + Art.7(3) GDPR (il ritiro deve equivalere alla cancellazione dei cookie).
- Quando un admin banna un utente, l'utente riceve ora una notifica in-app con la Dichiarazione dei Motivi Art.17 DSA con la regola violata, la ragione e un percorso di ricorso — e la riga di audit_log porta lo stesso payload sotto scope='legal'. Prima del fix i ban erano silenziosi: l'utente non poteva loggarsi, non sapeva il perché e non aveva alcun percorso di ricorso documentato. L'endpoint rifiuta ora qualsiasi ban che non fornisca regola e ragione.
i18n hardening — Spanish chrome everywhere, locale-aware dates, mobile language switcher, unified contact email
A pass through the bilingual surface closed every visible English-only chrome remnant on Spanish pages and tightened a few middleware + SEO gaps. Headline items: the marketing nav (About / How to Play / Cards / FAQ / Contact / Updates / Log in / Play for Free), every legal-page header link, the global cookie-consent banner, the offline banner, and the language-switcher screen-reader label all read from the active-locale message bundle now — Spanish visitors no longer see English wrappers around Spanish copy. The language switcher is also visible on phones (was hidden under the sm: breakpoint), so a visitor landing on the wrong locale on mobile can finally jump in-page. Changelog dates are stored as ISO YYYY-MM-DD and rendered through Intl.DateTimeFormat per locale — Spanish visitors see "26 de abril de 2026" / English visitors see "April 26, 2026". Email addresses across the legal + contact surface have been unified to a single canonical contact@drawntcg.com so visitors don't have to guess between info / privacy / support inboxes. A new /robots.txt now points crawlers at the per-locale sitemap, and link-preview crawlers (Slack, Discord, iMessage, Twitter) get an Open Graph card with the right locale tag. A subtle middleware bug that made the <html lang="…"> attribute always say "es" on /en/* pages — silently breaking screen-reader locale routing and Google's hreflang verification — has been fixed.
Spanish chrome on every public surface
- MarketingHeader (every public page) reads its nav labels and CTAs from the marketing_header.* namespace — Spanish visitors see Sobre nosotros / Cómo jugar / Cartas / FAQ / Contacto / Novedades / Iniciar sesión / Juega Gratis instead of the English equivalents.
- Legal page chrome (the back button, the four short links in the header — Terms / Privacy / Cookies / Legal Notice — and the long versions in the footer plus the all-rights-reserved line) all translate. Pre-fix /es/legal/* pages had a Spanish body with English chrome wrapping it.
- Global cookie-consent banner (the EU-mandated TCF v2.3 surface) translates intro copy, the Accept All / Reject All / Customize / Save Preferences buttons, every section heading, and every screen-reader aria-label so Spanish visitors don't see an English banner when they first land.
- The "You appear to be offline" banner (mounted on every page) and the language-switcher's accessibility label both read from the active locale.
Locale-aware dates + visible language switcher on mobile
- Changelog entry dates are now stored as ISO YYYY-MM-DD and rendered with Intl.DateTimeFormat — "26 de abril de 2026" in Spanish, "April 26, 2026" in English, with a proper <time dateTime="…"> wrapper for SEO.
- The Spanish changelog page heading is now "Registro de cambios" instead of the English word "Changelog".
- The language switcher (ES | EN) is now visible at every breakpoint, including phones. Pre-fix it was hidden under the sm: breakpoint, so a phone visitor landing on the wrong locale had to manually edit the URL to switch.
Single canonical contact email + softer Discord copy
- Every contact channel across the marketing, legal, and privacy surface points at a single canonical address: contact@drawntcg.com. Pre-fix the same visitor could see info@ on the contact page, privacy@ in the privacy policy, and support@ in the terms — confusing for anyone trying to figure out where to write.
- The Discord callout was softened — instead of "the invite link is being set up — when it lands it will show up here" it now says the invite is coming soon and points readers at the changelog page for updates in the meantime. No false promise of imminence.
Crawler + middleware fixes (mostly invisible but real)
- A new /robots.txt enumerates the public surface, blocks the authenticated game shell + admin + API + auth-only paths from crawl, and points search engines at /sitemap.xml so the per-locale sitemap is no longer discovered organically.
- Link-preview crawlers (Slack, Discord, iMessage, Twitter / X) now receive an Open Graph card with the active locale tag (es_ES or en_US) plus the alternate-locale list and a Twitter summary_large_image hint — bilingual link shares no longer surface the wrong-locale variant by accident.
- A subtle middleware bug that made <html lang="…"> always say "es" on /en/* pages is fixed. Pre-fix the request-header forwarding ran before next-intl's locale-injection, so the root layout never saw the active locale and fell back to the default — silently breaking screen-reader locale routing and Google's hreflang verification.
- Set-Cookie headers are now appended (not set) when merging next-intl's response — multiple cookies on a single response are no longer collapsed into one comma-joined value that browsers misparse.
Anticheat + provably-fair hardening — atomic seed rotation, replay scrubbing, spectator strategy strip
A pass through the anticheat surface (provably-fair seeds, replay sanitization, spectator state, collusion-detection telemetry) closed a small list of subtle but real defects. Headline items: pack-opening seed rotation is now atomic against in-flight pack opens, so a verifier replay can never reproduce N−1 of N openings while the Nth orphan dangles unverifiable; replay history scrubs raw player ids out of turn-log strings before persistence so the publishable replay file truly cannot be cross-correlated by id; spectator state strips two alpha-strategy hints (adrenaline counters and per-turn summon counts) that pre-fix flowed through unmasked; provably-fair seed rotations and client-seed changes are now durably audited so a player dispute has a recoverable trail; the history endpoint no longer 500s on garbage page params and no longer leaks raw Postgres errors to clients.
Provably-fair integrity
- Pack-opening seed rotation is now atomic against any in-flight pack open. Pre-fix a rotate landing between a pack-open's seed read and nonce update could publish a stale total_nonces value, leaving the in-flight opening unverifiable by the verifier. The race is closed via a transactional row lock plus a database-level unique-active-seed constraint.
- Both seed rotations and player-supplied client_seed changes now write a durable audit row, so a player who later disputes "I rotated at 12:34 and my opening is broken" has a recoverable trail in audit_log alone instead of relying on log-line forensics.
- PATCH client_seed now validates the format (8-64 hex chars) so unicode, control characters, or `:` separators that would ambiguate the verifier's HMAC message string can no longer be saved.
- Provably-fair history page now degrades gracefully on garbage page parameters (?page=NaN, ?page=abc) and no longer leaks raw Postgres error messages to authenticated clients.
Spectator + replay privacy
- Spectator state no longer surfaces adrenaline counters or per-turn summon counts — these are alpha-strategy hints that competitive metagame analysis tooling could be built off of. Combatants still see them in their own action-planning UI.
- Persisted replays now scrub raw player ids out of turn-log strings before storage. Pre-fix the JSON-keyed identifier fields were already pseudonymized to player_1 / player_2 but the free-text log lines could still embed raw ids — a non-admin reader of the replay blob could correlate replays by matching id substrings.
Profile, guild, tournament, and battle UI hardening — public profile clarity, achievement toasts, action timeouts
A pass through tournaments, guilds, the profile sections, and the in-battle internals fixed a long list of small bugs and added the visible affordances that were missing. Headline items: viewing a stranger's profile now tells you why bio / display case / achievements are hidden ("add as friend to see more") instead of rendering blank sections; achievement toasts auto-dismiss after 6 seconds (and cap at 5 visible at once) instead of stacking forever; every in-battle action — summon, attack, ability, mulligan, end turn, forfeit — now has a 5-second "no response from server" safety net so a flaky socket can't leave the UI stuck pending; the forfeit button uses an in-app modal instead of the browser's native confirm() popup that iOS PWAs sometimes silently suppress; the tournament bracket marks forfeit / disconnect / bye matches with a small badge so admins and spectators can tell them apart from real wins; deck-builder save validates the 3-copies-per-card limit client-side before letting you press Save; and a list of guild-management quality-of-life polish items (kick / promote confirmation, donation amount cap, war-end refresh filtering, identical-message dedupe).
Public profile + achievement toasts
- Viewing another player's profile now shows an inline notice ("Add as friend to see this player's bio, display case, achievements, and recent activity") when those sections are gated by the friendship check. Pre-fix the sections just rendered blank and players thought their account was broken.
- Public profile now shows the player's division badge (Bronze / Silver / Gold / etc.) alongside ELO — was missing on other-player profiles even though it was on your own.
- Sharing a profile link now shows "Link copied!" / "Could not share" feedback instead of being silent.
- Achievement toasts now auto-dismiss after 6 seconds and cap at 5 visible at once. Pre-fix they stacked forever and players who unlocked 5 achievements in a battle had to manually click each ×.
In-battle robustness
- Every player action — summon, evolve, attack, ability, equip, set trap, place field, use object, mulligan, end turn, go to battle, forfeit — now races against a 5-second server-ack timeout. If the server doesn't respond (network drop, server hiccup) you get a "No response from server" toast instead of the action sitting visually pending forever.
- Forfeit button uses an in-app confirmation modal showing the consequences (loss for record + rating). Replaces the browser's native confirm() popup that iOS PWA installs sometimes silently suppress, leading to "I clicked forfeit and nothing happened" reports.
- Mulligan decision can no longer multi-fire: a rapid double-tap on Keep/Mulligan now sends only one decision to the server.
- Spectator orientation hardening: the in-battle action handlers now no-op for spectators as a defense-in-depth layer over the existing UI gating.
- Battle tooltips no longer flicker shut on Android because of the touch+click race; status / zone tooltips now stay open on tap until you tap somewhere else.
- Mulligan timer screen and the in-app confirm modals now have proper aria-modal labelling for screen-reader users; previously they were unannounced.
Tournaments + guilds
- Tournament bracket now shows a small FF / DC / BYE badge on matches that ended by forfeit, disconnect, or auto-advance — pre-fix those rendered identical to real wins.
- Guild kick / promote / demote actions now require a second click to confirm (button changes to "Click again" with a 4-second auto-cancel). Pre-fix a single accidental click on Kick removed the member instantly.
- Guild chat: Enter spam can no longer post identical messages within 5 seconds; emoji and RTL names render their first character correctly in avatar bubbles.
- Guild pool donation amount is now bounded (1 to 99,999 fragments) and rejects garbage values like NaN that the form could previously generate.
- Guild war end-of-war notification now only refetches our own guild's wars instead of every guild_war_ended event in the world.
Profile sections
- Display Case selection: ordering is now user-controllable (←→ buttons on selected cards); cards traded away or dissolved no longer linger as zombie selections; the per-page collection cap was raised to 1000 cards with a clear notice if you have more than that.
- Profile name / avatar / bio now save independently — pre-fix clicking Save next to your avatar (after also typing a new name) submitted both, accidentally burning the 30-day name change cooldown.
- Avatar URL field now shows an inline preview as you type, with a fallback when the URL is invalid.
- Password change client validation now mirrors the server policy (min 8 chars + at least one digit + at least one letter + must differ from current). Pre-fix the only check was 8-char minimum.
- 2FA setup secrets (TOTP URI, backup codes, password fields) now wipe from React state on unmount so browser extensions can't scrape them from a stale snapshot.
- Provably-fair Rotate Seed: failures now surface a clear error (was silent), and the revealed seed gets a "Copy seed" button + a warning that the next rotation will reveal a new one and this one cannot be re-displayed.
- Account deletion request: surfaces "Deletion service is temporarily unavailable" upfront if the backend isn't configured, and double-clicks are rejected synchronously so two deletion rows can't be scheduled.
Battle UI fixes — spectator view, mulligan timer, deck builder hardening
A pass through the in-battle screen, the deck builder, and the card / trade modals fixed a long list of small but visible bugs. The headline items: spectators were seeing the battle mirrored to the wrong side (their view labelled the opponent as "you"), the mulligan timer reset to 30 every time anything happened on the board, the queue size showed blank when joining casual matchmaking, and trade history's wishlist 🎯 markers showed the wrong player's wishlist. The card modal now cancels in-flight fetches when you flip through a gallery, the deck builder caps how many pages it loads (so a server hiccup can't lock the screen), and copying a deck export now actually tells you whether it worked.
In-battle correctness
- Spectators now see the battle with a fixed orientation — challenger always at the bottom, opponent always at the top — instead of having the view randomly mirror to the wrong side and label the wrong player as you.
- Mulligan timer no longer resets to 30 every time the opponent acts. It now starts at 30 once when the mulligan phase begins and counts down honestly from there.
- Card tooltips no longer get wiped mid-read every time the opponent's HP ticks. They only close on real turn / phase changes now.
- If you tab out of a battle and come back, the in-battle screen requests a fresh state from the server instead of rendering whatever stale snapshot it had.
- Leaving a battle from the lobby (before the opponent joins) now actually frees your slot on the server — pre-fix you could get stuck in "already in a battle" trying to requeue.
- Casual queue size now displays correctly on join (was showing blank / NaN due to a payload mismatch).
- Battle history (the resolved list under your decks) now updates live when a battle ends — no more refresh required to see your latest match.
- Rematch banner now only shows after a battle that resolved naturally (win / loss). It used to show after forfeits and opponent disconnects too, which was confusing.
- Tournament report-result now uses an in-app modal that shows the opponent's name. Native browser confirm() popups are gone (one less spot the design guidelines didn't apply).
Deck builder + card modal
- Deck builder now validates the 3-copies-per-card limit before letting you press Save (was checked at add time only — corrupt or imported decks could slip through with 4+ copies and silently fail server-side).
- Loading a giant collection in the deck builder is now bounded — there's a hard cap on how many pages it'll fetch (50, well above any realistic collection) so a server pagination bug can't lock the screen.
- Card modal cancels in-flight network requests when you flip to another card. Pre-fix the previous card's wishlist / instances / evolutions could resolve last and overwrite the new card's data.
- Locking / unlocking a card copy now surfaces an error if the server rejects it. Used to silently swallow the failure.
- Trade history shows a Retry button if the load fails, instead of a fake "No completed trades yet" forever.
- Copying a deck export to clipboard now shows a Copied! / Copy failed toast. Used to be silent.
- Live battles list (the spectate panel) no longer shows a Watch button on your own battles, and pauses its 15-second poll when the tab is in the background.
Battle action feedback — no more frozen UI when the server hiccups
When you summon a creature, attack, or end your turn, the client now waits up to 5 seconds for the server to confirm. If the confirmation never arrives — server hiccup, network drop, dropped packet — you get a clear "No response from server — check your connection" message instead of a button that just stops responding. If the server rejects the action (out of phase, illegal target, rate-limited, etc.) the rejection reason now reaches you immediately even on flaky connections, instead of riding the next state push and arriving seconds late. The other (less critical) actions are unchanged. Spanish and English locales also got `not-found` and `loading` pages so a stale link or a slow load lands on a localised page instead of the framework default.
Battle action feedback
- Summon, attack, and end-turn now show an error toast if the server doesn't acknowledge within 5 seconds, instead of leaving the action visually pending.
- Server-side rejections (out of phase, illegal target, rate-limited) now reach you immediately even when state push is delayed.
- Localised `not-found` and `loading` pages — stale links and slow loads now land on a Spanish or English page instead of the framework default.
Privacy + safety tightening
- Profile bio, display case, recent activity, and achievements are now visible only to friends and to yourself. Strangers see your name, rating, and division but not your collection wall — closes a long-standing complaint that anyone with your user URL could browse your private profile content.
- Blocking a player is now a hard hide — your profile returns 404 to anyone you've blocked (and theirs to you), instead of just hiding tabs while still leaking the basics.
- Friend activity feed no longer surfaces fields that the original event didn't intend to share. Each event type has a strict allowlist (rare pull, achievement, battle result, trade, guild join, tournament win) so nothing extra leaks even if a future event payload changes shape.
- Leaderboard now requires sign-in. Anonymous bots can no longer scrape the rating table.
Notifications + import
- You can now mark individual notifications as read instead of "all or nothing" — the inbox now passes the specific ids you tap.
- Deck import now caps the pasted text at 4 KB so a giant accidental paste returns a clear error instead of stalling. The cap is large enough for any legitimate deck list.
Real-time push fixes
- Friend requests, accepts, and rejects now actually update the badge counters and friend list without a manual refresh. The wire vocabulary between client and server had drifted apart and every notification was being silently dropped at the server allowlist; the canonical verbs are now shared and the relay forwards them end-to-end.
- Trade offers, accepts, declines, and cancellations also push to the other player live now — the badge ticks up the moment the offer arrives, instead of waiting for the next page load.
- Trade UI: the 🎯 markers now correctly show cards the OTHER player wants (used to silently show your own wishlist on both columns).
Tab UX + battery savings
- Hidden tabs no longer drain bandwidth in the background — the leaderboard and challenges tabs now pause their live subscriptions when you're on another tab and resume when you come back.
- Leaderboard now shows a clear error + retry button if the load fails, instead of the perpetual "Loading leaderboard…" spinner.
- Challenge / Accept buttons in Battles no longer lock forever when the socket hiccups. After 15 seconds without a server ack you get a "Network timeout — try again" toast and the button releases.
- Tournament "I Won" confirmation is now a styled in-app dialog showing the opponent's name, instead of the browser's native confirm() popup. Same for visual polish + accessibility (aria-modal).
- Support ticket replies are now a multi-line textbox (Shift+Enter for newline, Enter to send) instead of a single-line input that silently truncated paragraph replies.
SEO sweep — sitemap, hreflang, canonical URLs across all locales — Phase 1 i18n fully closed
The bilingual public surface is now wired up for search engines. Every Spanish and English page emits a `<link rel="canonical">` pointing at its own locale URL plus an `<link rel="alternate" hreflang>` map covering both locales and an `x-default` fallback, so Google can pair the /es and /en variants of the same page instead of treating them as duplicates. A new `/sitemap.xml` enumerates every Phase 1 public page in every locale with the same hreflang alternates per entry. With this release Phase 1 of the i18n project is fully closed: 4 legal pages bilingual, 7 marketing pages bilingual, controlling-version clauses live in both languages, locale switcher in both headers, hreflang plumbing in metadata and sitemap, and a written workflow template for any future page.
SEO + future-page template
- New `/sitemap.xml` enumerates every Phase 1 public page (landing, about, contact, FAQ, how-to-play, showcase, changelog, legal/cookies, legal/notice, legal/privacy, legal/terms) in both Spanish and English, with per-entry hreflang alternates so search engines pair the locale variants properly.
- Every translated page now emits a per-page canonical URL pointing at its own /es or /en path and an hreflang `languages` map covering both locales plus `x-default` (defaulting to Spanish), so duplicate-content penalties are off the table.
- Future-page workflow template added to `context/refs/marketing-hero-draft.md` — every new public page added after Phase 1 must ship in BOTH Spanish and English from the first commit. The template covers the dual-locale draft workflow, TCG vocabulary, register rules, and the required SEO wiring.
- Phase 1 i18n closes here: 11 public pages × 2 locales fully bilingual end-to-end, plus the SEO infrastructure, controlling-version clauses, locale switcher, and the written contract for future bilingual pages.
Marketing pages, mastery, tournaments, and a sweep of UX gaps
A big batch of player-facing work landed across the spring. The headline items: a public marketing surface for new visitors, a card mastery system that rewards loyalty to specific cards, a tournament bracket viewer that finally lets you see how a tournament is unfolding, and a long list of small UX fixes that close gaps players have been pointing out for months. Below is the rough shape of what changed, organised by area. Patch notes for individual cards and balance tweaks live in the in-game patch feed; this changelog covers the structural and surface-level stuff.
Marketing pages (public)
- New landing page that actually explains the game, instead of dropping you straight onto a login form. Returning players get a one-click "Log in" in the sticky header.
- About, How to Play, FAQ, Contact, Featured Cards, and this Changelog — all reachable from the same header on every public page.
- Cookie consent banner upgraded to the IAB TCF v2.3 standard so EU players get the proper opt-in flow with all the per-purpose toggles.
Crafting and collection
- New Forge tab dedicated to crafting cards from the fragments you earn by dissolving duplicates.
- Mastery system: as you play with the same card across battles, it levels up. Once a card hits level 5, you get a 10% crafting discount on copies of it. At level 10 it gets a "Mastered" badge.
- Crafting cost now shows the discounted price inline at the craft button, so you can see the saving without diving into card detail.
- Wallet bar now shows your fragment stash split per rarity, instead of one aggregate number. Easier to see at a glance whether you can afford a craft.
- Card sets browser: see every set, what cards belong to it, and your progress through it. Helps you target which sets to chase.
- Persistent "Verify past pulls" link in the Gacha tab — tap it any time to audit any pack you have ever opened against the provably-fair RNG record.
Battles and competitive
- Tournament bracket viewer: when you click into a single-elimination tournament, you now see the full bracket tree with every match, who is playing whom, and who has won so far.
- Public guild tournament standings: non-participants browsing a guild tournament can see the standings panel without being enrolled.
- Battle replays now have their own shareable URL. Open the replay in a new tab, send the link to a friend, point at the moment you want to talk about. Works for any battle either of you played in.
- Element field-spell weather effects now show up properly in damage calculations during battle — fire fields boost fire creatures, water fields boost water, etc.
- Daily and weekly challenges have a streak system: completing them on consecutive days builds a streak with milestone rewards at 7, 14, and 28 days.
- Battle stats panel shows per-element breakdowns + period filtering, so you can see how your fire decks have been doing this week vs last month.
- AI battle spam fix: a race condition that allowed double-starting AI battles is closed. No more orphan match windows.
Account and trust
- Two-factor authentication available from your profile security section. Standard authenticator apps (Authy, Google Authenticator, 1Password, Bitwarden) all work.
- Self-serve account deletion lives in the profile section — no support ticket, no won't-let-you-leave dance.
- Self-serve data export: download a copy of everything we hold about your account.
- Session security tightened: changing your password or enabling 2FA now invalidates all your other active sessions automatically. If someone was logged in as you on a different device, they get kicked.
- Email verification flow improvements: clearer messaging when verification is pending, retry-with-cooldown on the resend button.
Performance and reliability
- Battle reconnection improvements: if you drop mid-match for under a minute, you reconnect to the same game state with no penalty.
- Server-side deck-out handling rewrites — the slow control decks that win by exhausting your opponent's library now work cleanly across reconnects.
- Tournament forfeit timeouts moved to a durable job queue, so a server restart no longer eats a forfeit-pending tournament.
- Offline detection banner: if your connection drops, you see a clear banner explaining what happened, instead of the UI silently failing.
Notes for next time
- We are still adding cards, sets, and modes. The world has plenty of room to grow.
- Discord invite link is being set up — when it lands the contact page will surface it.
- If you spot something here that does not match what you see in-game, tell us — most likely we shipped it differently than the note suggests.
All marketing pages now available in Spanish — Phase 1 complete
Six more marketing pages are now bilingual, closing out Phase 1 of the marketing translation effort. /es/about renders the team / vision / game-world write-up in Spanish. /es/contact translates every channel. /es/faq covers all 12 Q&A items. /es/how-to-play translates the 10-minute rundown. /es/showcase renders the curated 7-card showcase with bilingual card names + flavor blurbs. /es/changelog (this page) is now bilingual too — every ChangelogEntry carries both Spanish and English copy together in the source so future updates land in both locales atomically. The four legal pages shipped bilingual earlier this month, so the public surface is now fully bilingual end-to-end.
Marketing pages translated
- About page (Sobre Nosotros) translated to Spanish — hero 'Hecho entre amigos, por gusto', team / vision / game-world sections all mirror the English source 1:1.
- Contact page translated — section headings 'Correo / Discord / Tickets de soporte / Una nota sobre los tiempos de respuesta', back / FAQ navigation buttons.
- FAQ translated — all 12 Q&A items across three sections (Sobre el juego / Cómo jugar / Cuenta), including questions about rewarded ads, account deletion, and where data is stored.
- How to Play translated — three sections (Mecánicas, Tipos de cartas y elementos, Flujo de batalla — turno a turno). TCG glossary mazo / sobre / carta / PV / zona de monstruo / mulligan kept as-is.
- Featured Cards (showcase) translated — every card name + 50-150 word marketing blurb. Element + monster-class labels also translated (Fuego/Agua/Viento/Tierra/Metal/Rayo/Neutral; Dragón/Bestia/Constructo/Leviatán/Alado; slime preserved as TCG term).
- Changelog page itself bilingual — every ChangelogEntry now carries both Spanish and English copy together in the source. MAINTAINER NOTE in the page source updated: from this release on, every new ChangelogEntry MUST land in BOTH Spanish AND English in the same commit. Single-locale entries are forbidden.
- All Spanish versions use the indie/playful voice from the landing page — informal tú throughout, TCG vocabulary preserved (sobres, mazos, cartas, tablero, dorsos, gremios).
- Locale switcher in the marketing header lets you flip between /es and /en versions of any page without losing your place. Phase 1 marketing translations now complete.
Landing page now available in Spanish — first marketing page translated
The homepage hero is now bilingual. Spanish-speaking visitors landing on /es see the full marketing pitch in Spanish — anti-pay-to-win positioning, daily-free-packs promise, multi-pace modes (casual, ranked, AI, just collecting), element chips, and the 'built among friends' footer — all in informal tú voice. English visitors keep the original wording at /en. This is the first marketing page to land in two languages; the rest are queued up next.
Marketing pages
- Landing page (homepage hero) translated to Spanish — every paragraph mirrors the English source 1:1: hero badge ('Gratis · Sin pay-to-win'), headline, subhead, three 'why play' tiles, 'Cómo funciona' section with element chips (Fuego, Agua, Viento, Tierra, Metal, Rayo, Neutral), and the 'Hecho entre amigos por gusto' footer.
- Spanish version uses the same playful indie register as the English source — informal tú throughout, TCG vocabulary players already know (sobres, mazos, cartas), no marketing-speak.
- Marketing translations rolling out next: about, how-to-play, FAQ, contact, showcase, changelog. Each will follow the same per-page review gate.
- Locale switcher in the marketing header lets you flip between /es and /en versions of the landing page without losing your place — same flow already in use on the legal pages.
Privacy Policy now available in Spanish — Phase 1 legal coverage complete
The Privacy Policy is now bilingual, closing out Phase 1 of the legal-page localization effort. Spanish-speaking visitors land on a fully translated /es/legal/privacy, English visitors keep the original wording at /en/legal/privacy. With this release, all four legal pages — Terms of Service, Cookie Policy, Legal Notice, and Privacy Policy — ship in both English and Spanish, each carrying the controlling-version notice that names the English text as the legally authoritative version. Phase 1 legal coverage is now fully complete.
Legal pages
- Privacy Policy translated to Spanish — every section, every data table, every right enumeration mirrors the English source 1:1 (data controller, categories of data collected, purposes and legal basis, data sharing with processors, international transfers, retention periods, GDPR data-subject rights including access/rectification/erasure/portability/objection/restriction/withdrawal of consent, GDPR Article 21 objection box, AEPD complaint route, GDPR Article 33/34 breach notification, CCPA/CPRA rights for California users, cookie disclosure, children-under-16 protections, automated-decision-making disclosure).
- Spanish version uses canonical RGPD terminology throughout: 'responsable del tratamiento', 'interesado', 'encargado del tratamiento', 'base legal', 'consentimiento', 'interés legítimo', 'Delegado de Protección de Datos (DPD)', plus the full Spanish names for each GDPR right ('derecho de acceso', 'derecho de supresión', 'derecho de oposición', 'derecho a la portabilidad', 'derecho a la limitación').
- Phase 1 legal coverage now fully complete: Terms, Cookies, Legal Notice, and Privacy all available in both Spanish and English with controlling-version notices in place.
- Locale switcher in the legal header lets you flip between /es/legal/privacy and /en/legal/privacy without losing your place — same flow already in use on the other three legal pages.
Terms of Service now available in Spanish — three legal pages bilingual
The Terms of Service page is now bilingual. Spanish-speaking visitors land on a fully translated /es/legal/terms, English visitors keep the original wording at /en/legal/terms. Both versions carry the same controlling-version notice at the top, making clear that the English text remains the legally authoritative version and the Spanish translation is provided for convenience. Three of four legal pages — Terms, Cookies, and Legal Notice — now ship in both languages; Privacy is the next page in line.
Legal pages
- Terms of Service translated to Spanish — every clause, every list item, every legal definition mirrors the English source 1:1 (eligibility, gacha mechanics, ad disclosures, IP ownership, DSA Article 17 appeal flow, Belgian gacha note, EU consumer right of withdrawal).
- Phase 1 legal-page coverage is now three-quarters complete: Terms, Cookies, and Legal Notice all available in Spanish and English. Privacy Policy translation is the remaining page.
- Controlling-version banner present at the top of every translated legal page in both locales: the English text remains legally binding and translations are explicitly marked as non-authoritative for legal interpretation.
- Locale switcher in the legal header lets you flip between /es and /en versions of any legal page without losing your place.
Cookie Policy and Legal Notice now available in Spanish
The Cookie Policy and Legal Notice pages are now bilingual. Spanish-speaking visitors land on fully translated /es/legal/cookies and /es/legal/notice, English visitors keep the original wording at /en/legal/cookies and /en/legal/notice. All four pages carry a controlling-version notice at the top making clear that the English text is the legally authoritative version and the Spanish translation is provided for convenience. Terms and privacy will follow the same pattern in upcoming releases.
Legal pages
- Cookie Policy translated to Spanish — every section, every cookie table, every browser-settings note is mirrored 1:1 with the English source.
- Legal Notice (Aviso Legal) translated to Spanish — operator identity, NIF/CIF cell, Spain-based address, applicable LSSI-CE/RGPD/LOPDGDD/DSA legislation list, intellectual property, liability and dispute-resolution sections all mirrored 1:1.
- Controlling-version banner added at the top of both legal pages in both locales: the English text remains the legally binding version and translations are explicitly marked as non-authoritative for legal interpretation.
- Locale switcher in the legal header lets you flip between /es/legal/* and /en/legal/* without losing your place.
Vuoi suggerire la prossima cosa? La pagina contatti ha email e Discord. Vuoi seguire gli aggiornamenti? Salva questa pagina nei preferiti — la aggiorniamo quando un cambiamento visibile al giocatore arriva.