Palindrome Checker
Il Progetto
Palindrome Checker con design system completo e interfaccia dinamica. Un'applicazione che controlla se una parola o frase è palindroma, con transizioni fluide tra tre stati visivi diversi e ottimizzazione completa delle performance.
Codice Sorgente
- index.html
- styles.css
- script.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Work+Sans:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/1a1259f0-0b84-478b-a26f-460d2228203d">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/99a6c549-5d45-4a36-8354-1c8039c4f7cb">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/d1126873-ad30-4742-a1e2-d050088f0af9">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/3b636ec0-524b-40ae-8dbc-a872ebbb49a4">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/9ac63aa1-47ba-41f1-b006-7aba6de92e95">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/9662ce58-c14a-4212-ab5d-2ae226c9ae85">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/77d3bd71-77f1-48aa-9e39-718d601c0239">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/ed4bba81-e535-43a9-958b-af6d0a0d4964">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/e9db924c-83b1-45db-a9ef-5fe17db3b56a">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/989f36b1-383c-4a3b-b1c7-472d7aa38d05">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/950ca8ae-6e4d-4598-bd38-15989d782771">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/076efb6d-4a7c-46e2-85b7-81606216149b">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/33f4a4cd-86c5-42da-be16-6292ec264251">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/1b3257a4-eb5a-4245-9c50-2bd43d5f9926">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/df01223b-9bae-4707-869b-b188b1ac191c">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/2a4e62bd-e766-4fcb-a771-eecbf2d5d713">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/318b9e76-1290-448d-b6cd-242103a60163">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/e6b2e38e-a467-4b18-bba6-31c783b1896b">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/e6edc35e-a909-4cc0-8241-ee83b1ec35b7">
<link rel="preload" as="image" href="https://github.com/user-attachments/assets/518957aa-ca39-4456-bd8e-d69fd2497ae0">
</head>
<body>
<div class="card">
<div class="title">Is it a Mirror Word?</div>
<div class="description">A palindrome reads the same forward and<br>backward, like a perfect mirror reflection,<br>ignoring punctuation, case, and spacing</div>
<div class="input-container">
<input type="text" placeholder="Enter text to check its reflection" id="text-input">
</div>
<div class="button-container">
<button id="check-btn">Mirror</button>
</div>
<div id="result" class="hidden"></div>
</div>
<script src="script.js"></script>
</body>
</html>
:root {
/* Every Page */
--background-button-hover: url("https://github.com/user-attachments/assets/d1126873-ad30-4742-a1e2-d050088f0af9");
--color-text-primary: #fff;
--font-style: normal;
--title-letter-spacing: -0.56px;
--font-family-primary: "Poppins", sans-serif;
--font-family-secondary: "Work Sans", sans-serif;
--font-size-title: 39.17px;
--font-size-button: 22.38px;
--font-weight-primary: 700;
--font-size-description: 16.79px;
--font-weight-secondary: 400;
--size-card-width: 481px;
--size-card-height: 512px;
--size-card-border-radius: 46px;
--size-button-width: 243px;
--size-button-height: 73px;
--size-button-border-radius: 50px;
--space-above-title: 55.86px;
--space-above-description: 33.43px;
--space-above-button: 35.74px;
--space-below-button: 55.86px;
/* Default Page */
--background-page-default: url("https://github.com/user-attachments/assets/1a1259f0-0b84-478b-a26f-460d2228203d");
--background-card-default: url("https://github.com/user-attachments/assets/99a6c549-5d45-4a36-8354-1c8039c4f7cb");
--background-button-default: url("https://github.com/user-attachments/assets/3b636ec0-524b-40ae-8dbc-a872ebbb49a4");
--input-border-width: 1.5px;
--input-border-color-default: rgba(255, 255, 255, 0.60);
--color-placeholder-text-focus: rgba(255, 255, 255, 0.30);
--input-border-color-focus: #fff;
--font-size-input: 20px;
--size-input-width: 340px;
--size-input-height: 30px;
--space-above-input: 89.97px;
--padding-text-input: 8px;
/* Palindrome Checked Page AND Palindrome Not Match Page*/
--font-size-result: 30.78px;
--top-result: 78px;
/* Palindrome Checked Page */
--background-page-palindrome: url("https://github.com/user-attachments/assets/9ac63aa1-47ba-41f1-b006-7aba6de92e95");
--background-card-palindrome: url("https://github.com/user-attachments/assets/9662ce58-c14a-4212-ab5d-2ae226c9ae85");
--background-button-palindrome: url("https://github.com/user-attachments/assets/77d3bd71-77f1-48aa-9e39-718d601c0239");
/* Palindrome Not Match Page */
--background-page-not-palindrome: url("https://github.com/user-attachments/assets/ed4bba81-e535-43a9-958b-af6d0a0d4964");
--background-card-not-palindrome: url("https://github.com/user-attachments/assets/e9db924c-83b1-45db-a9ef-5fe17db3b56a");
--background-button-not-palindrome: url("https://github.com/user-attachments/assets/989f36b1-383c-4a3b-b1c7-472d7aa38d05");
}
/* RESET */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* TEXT */
body {
margin: 0;
padding: 0;
transition: background-image 1s ease-in-out;
}
html {
font-style: normal;
line-height: normal;
background-image: var(--background-page-default);
background-size: cover;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
background-attachment: fixed;
}
.title, .description, #check-btn {
color: var(--color-text-primary);
font-family: var(--font-family-primary);
font-style: var(--font-style);
}
.description, .placeholder, #check-btn, #result {
font-weight: var(--font-weight-secondary);
}
.title {
font-size: var(--font-size-title);
font-weight: var(--font-weight-primary);
letter-spacing: -0.56px;
margin-top: var(--space-above-title);
}
.description {
font-size: var(--font-size-description);
margin-top: var(--space-above-description);
}
.placeholder, #result {
font-family: var(--font-family-secondary);
}
.placeholder {
color: var(--color-placeholder-default);
font-family: var(--font-family-secondary);
font-size: var(--font-size-input);
}
#result {
color: var(--color-text-primary);
font-size: var(--font-size-result);
top: var(--top-result);
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
pointer-events: none;
z-index: 10;
}
.input-container {
margin-top: var(--space-above-input);
position: relative;
width: var(--size-input-width);
height: var(--size-input-height);
}
#text-input {
outline: 0;
padding: var(--padding-text-input);
width: 100%;
border: none;
background: transparent;
color: var(--color-text-primary);
font-family: "Work Sans";
font-size: var(--font-size-input);
border-bottom: var(--input-border-width) solid var(--input-border-color-default);
text-align: center;
transition: width 0.2s ease;
}
#text-input::placeholder {
color: var(--color-placeholder-default);
}
#text-input:focus {
border-bottom-color: var(--input-border-color-focus);;
transition: border-bottom 0.3s ease;
}
/* BOX */
.card {
width: var(--size-card-width);
height: var(--size-card-height);
border-radius: var(--size-card-border-radius);
background-image: var(--background-card-default);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
#check-btn {
font-size: var(--font-size-button);
display: flex;
margin-top: var(--space-above-button);
margin-bottom: var(--space-below-button);
display: flex;
width: var(--size-button-width);
height: var(--size-button-height);
padding: 14px;
justify-content: center;
align-items: center;
gap: 14px;
border-radius: var(--size-card-border-radius);
background-image: var(--background-button-default);
border: none;
transition: background-image 0.5s ease, transform 0.25s ease;
}
#check-btn:hover {
background-image: var(--background-button-hover);
cursor: pointer;
transform: scale(1.02);
}
#check-btn:active {
transform: scale(0.95);
}
.hidden-input {
visibility: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.hidden {
display: none;
}
html.palindrome-background {
background-image: var(--background-page-palindrome);
}
html.not-palindrome-background {
background-image: var(--background-page-not-palindrome);
}
.card.palindrome-card {
background-image: var(--background-card-palindrome);
}
.card.not-palindrome-card {
background-image: var(--background-card-not-palindrome);
}
#check-btn.palindrome-button {
background-image: var(--background-button-palindrome);
}
#check-btn.not-palindrome-button {
background-image: var(--background-button-not-palindrome);
}
#text-input:focus::placeholder {
color: var(--color-placeholder-text-focus);
transition: color 0.25s ease-out;
}
@media (max-width: 768px) {
:root {
/* Every Page */
--background-button-hover: url("https://github.com/user-attachments/assets/518957aa-ca39-4456-bd8e-d69fd2497ae0");
--color-text-primary: #fff;
--font-style: normal;
--title-letter-spacing: -0.56px;
--font-family-primary: "Poppins", sans-serif;
--font-family-secondary: "Work Sans", sans-serif;
--font-size-title: 28px;
--font-size-button: 16px;
--font-weight-primary: 700;
--font-size-description: 12px;
--font-weight-secondary: 400;
--size-card-width: 344px;
--size-card-height: 366px;
--size-card-border-radius: 32px;
--size-button-width: 174px;
--size-button-height: 52px;
--size-button-border-radius: 36px;
--space-above-title: 34px;
--space-above-description: 24px;
--space-above-button: 25px;
--space-below-button: 39px;
/* Default Page */
--background-page-default: url("https://github.com/user-attachments/assets/950ca8ae-6e4d-4598-bd38-15989d782771");
--background-card-default: url("https://github.com/user-attachments/assets/076efb6d-4a7c-46e2-85b7-81606216149b");
--background-button-default: url("https://github.com/user-attachments/assets/33f4a4cd-86c5-42da-be16-6292ec264251");
--input-border-width: 1.5px;
--input-border-color-default: rgba(255, 255, 255, 0.60);
--color-placeholder-text-focus: rgba(255, 255, 255, 0.30);
--input-border-color-focus: #fff;
--font-size-input: 14px;
--size-input-width: 240px;
--size-input-height: 42px;
--space-above-input: 64px;
--padding-text-input: 6px;
/* Palindrome Checked Page AND Palindrome Not Match Page*/
--font-size-result: 22px;
--top-result: 50px;
/* Palindrome Checked Page */
--background-page-palindrome: url("https://github.com/user-attachments/assets/1b3257a4-eb5a-4245-9c50-2bd43d5f9926");
--background-card-palindrome: url("https://github.com/user-attachments/assets/df01223b-9bae-4707-869b-b188b1ac191c");
--background-button-palindrome: url("https://github.com/user-attachments/assets/2a4e62bd-e766-4fcb-a771-eecbf2d5d713");
/* Palindrome Not Match Page */
--background-page-not-palindrome: url("https://github.com/user-attachments/assets/318b9e76-1290-448d-b6cd-242103a60163");
--background-card-not-palindrome: url("https://github.com/user-attachments/assets/e6b2e38e-a467-4b18-bba6-31c783b1896b");
--background-button-not-palindrome: url("https://github.com/user-attachments/assets/e6edc35e-a909-4cc0-8241-ee83b1ec35b7");
}
}
const textInput = document.getElementById("text-input");
const checkBtn = document.getElementById("check-btn");
const result = document.getElementById("result");
const card = document.querySelector(".card");
const htmlElement = document.documentElement;
let lastValue = "";
const checkPalindrome = () => {
const userValue = textInput.value;
lastValue = userValue;
if (userValue === "") {
alert("Please input a value");
return;
}
const cleanInput = userValue.toLowerCase().replace(/[^a-z0-9]/g, "");
const reverseText = cleanInput.split("").reverse().join("");
textInput.classList.add("hidden-input");
result.classList.remove("hidden");
if (cleanInput===reverseText) {
result.textContent = `${userValue} is a palindrome`;
htmlElement.classList.add("palindrome-background");
card.classList.add("palindrome-card");
checkBtn.classList.add("palindrome-button");
checkBtn.textContent = "Clear";
} else {
result.textContent = `${userValue} is not a palindrome`;
checkBtn.textContent = "Clear";
htmlElement.classList.add("not-palindrome-background");
card.classList.add("not-palindrome-card");
checkBtn.classList.add("not-palindrome-button");
}
};
const resetInterface = () => {
textInput.value = "";
textInput.classList.remove("hidden-input");
result.textContent = "";
result.classList.add("hidden");
htmlElement.classList.remove("palindrome-background", "not-palindrome-background");
card.classList.remove("palindrome-card", "not-palindrome-card");
checkBtn.classList.remove("palindrome-button", "not-palindrome-button")
checkBtn.textContent = "Mirror";
lastValue = "";
}
checkBtn.addEventListener("click", () => {
if (checkBtn.textContent === "Clear" && (textInput.value === lastValue || textInput.value === "")) {
resetInterface();
}
else {
checkPalindrome();
}
})
Il Progetto Più Divertente della Mia Vita!
È stato il progetto code e design più divertente della mia vita!
Ho unito tanti puntini.
Qualche giorno fa ero in macchina che attendevo una persona e nell'attesa mi sono chiesto: come si fa a replicare il Liquid Glass di Apple con solo CSS? Sarà necessario React? Ho visto un tutorial che prometteva di ottenere il Liquid Glass con semplici linee di codice che suggeriva, non ho approfondito lì per lì ma appena mi sono apprestato a guardare attentamente il tutorial l'altro ieri, quando ho cominciato il progetto, mi sono reso conto dopo che sarebbe servito React. Questo perché il liquid glass non è il semplice effetto background blur oppure il design Aero nato con Windows Vista. È molto più complesso, è infatti ricco di parametri avanzati che l'effetto blur può solo sognarsi.
L'Ispirazione Interna
Non ho avuto bisogno di ispirazioni esterne questa volta, perché mi sono ispirato alla mia prima app creata con Figma. Era un compito del corso di Breccia che feci, nella quale usai alcune foto di Lucy, la mia cagnolina, metto due schermate qui sotto.
Da esso ho preso la scelta dei colori che ho poi adattato agli sfondi di ogni cosa, dalla card ai bottoni, ognuno estratto da ogni "situazione" creata in Figma, avendo prima prototipato lì, come sempre. A dire il vero devo ancora informarmi in merito ma suppongo che il dare background-image diversi ad ogni elemento che cambiano sempre rispetto alla schermata successiva e precedente non sia una best practice.
La Sfida dell'Ottimizzazione
Finché testavo il codice con Live Server in Visual Studio Code non c'era nessun glitch/lag, perché aveva la disponibilità di immagini alla velocità della luce, avendole nella cartella del progetto; ma quando le ho esportate in GitHub, nella repository asset, ho cominciato a vedere i limiti. Ho provato successivamente a risolverli con TinyPng ma i risultati erano molto scarsi, non avevo nemmeno il controllo sulla qualità di uscita. Allora mi sono imbattuto in altri formati, in particolare il WebP ma nulla di fatto, perdeva troppa qualità pur mantenendo i parametri della qualità a livelli alti.
La svolta fu un programma, totalmente open source, si chiama ImageOptim, che ha fatto lavorare il mio computer come non mai: è arrivata a 92° la CPU, questo perché gira completamente in locale. Ha ridotto la qualità di appena il 30%, ma è bastato. Ora l'applicazione non ha più glitch e le immagini vengono pre-caricate grazie alla proprietà "preload" che ho avuto il piacere di conoscere proprio oggi, mentre cercavo modi per ottimizzare l'UX.
La Sensazione di Crescita
Sono estremamente felice del risultato, perché l'ispirazione è stata pressoché nulla e proprio per questo lo sento come un progetto più "mio" rispetto agli altri, pur non avendo mai e poi mai copiato contenuti altrui, ma soprattutto è questa sensazione di avere sempre meno bisogno d'ispirazione che ti fa percepire il progresso. È come in palestra, dopo qualche anno avrei potuto creare (e l'ho fatto) un allenamento dal nulla, anche in modo creativo, ma non per questo meno tecnico.
I Tre Giorni No-Stop
Sono stati 3 giorni no-stop, mi sono fermato solo per fare una visita medica. Ho sentito la fatica nei momenti più duri, perché mentirei se dicessi che è stato tutto semplice, soprattutto per il JavaScript. Il CSS mi ha fatto perdere la pazienza con le posizioni degli elementi ma che poi ho risolto e capendo anche come farlo le prossime volte, può sembrare strano ma ho usato i principi di Refactoring UI e anche la calcolatrice per farlo esattamente ad immagine e somiglianza della versione Figma, ma il JavaScript invece mi ha dato problemi non tanto con i calcoli, bensì con i pulsanti. Una minima modifica, una minima svista di sintassi ed ero punto a capo. Sicuramente mi sono complicato la vita prevedendo più interfacce ma sono contento così.
I Tre Edge Case
Ultimissima curiosità, ho cambiato il telefono ormai quasi un anno fa con un foldable per trarre i vantaggi di usarlo come reader in mensa al lavoro e nei momenti dove avrei rischiato di perdere tempo, dato che il telefono piccolo come quello che avevo non era stato di certo creato per aumentare la produttività, lo usavo al massimo per leggere brevi documenti ma è sempre stato impossibile leggerci libri. Ebbene sto parlando dell'iPhone 12 mini, e proprio qui che ho compreso il Liquid Glass, beta dopo beta, miglioramento dopo miglioramento. Ma non mi riferisco solo a questo. Ho avuto l'idea di usarlo come "prototype in progress" mentre usavo Figma sul desktop, ogni modifica fatta da lì la vedevo materializzarsi istantaneamente anche sull'iPhone. Ho basato le dimensioni per la versione mobile proprio visualizzandola step by step da lì. Rappresenta un vero e proprio edge case, infatti, resta inevitabile che se la fruizione con esso sarà perfetta, sarà ancora migliore con tutti gli altri. E non è finita qui, come scritto prima il mio telefono principale è un foldable: senza farlo apposta ho ben 3 edge case! Perché l'iPhone è il telefono piccolo, il foldable chiuso è il telefono stretto, il foldable aperto è il telefono enorme. Di tanto in tanto ho adottato il processo precedente di prototype in progress anche col foldable.
Come sempre, come insegnato da Refactoring UI, ho iniziato progettando per mobile.
Cosa Ho Imparato
Advanced CSS Variables System:
- Design system completo con variabili per ogni elemento
- Responsive variables che cambiano completamente tra desktop e mobile
- CSS Variables per background-image dynamic switching
- Color system e spacing system strutturati
Performance Optimization:
rel="preload"per pre-caricamento immagini- Image optimization con ImageOptim
Complex State Management:
- JavaScript state tracking con
lastValuevariable - Dynamic button text change ("Mirror" ↔ "Clear")
- UI state synchronization tra multiple elementi
- Error handling per empty input validation
Advanced JavaScript Patterns:
- String manipulation per palindrome logic
toLowerCase(),replace()con regex,split(),reverse(),join()
Mobile-First Responsive Design:
- Edge case testing su 3 dispositivi diversi
- Real-time prototype testing workflow
- Mobile-first approach come insegnato da Refactoring UI
User Experience Engineering:
- Accessibilità con placeholder descriptions
- Visual feedback immediato per user actions
- Prevenzione degli errori con input validation
Riflessione
Sto diventando sempre più veloce e non mi sta passando l'interesse, è vero il contrario.
Prossimo Progetto: Imparare l’oggetto Date costruendo un Date Formatter