finalizzazione del progetto

This commit is contained in:
2026-04-10 05:16:11 +02:00
parent 00e81f9da4
commit ba1717cbb6
24 changed files with 1650 additions and 389 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

+139 -10
View File
@@ -1,19 +1,148 @@
/* --- VARIABILI COLORI --- */
:root {
--bg-light: #f8fafc;
--text-dark: #1e293b;
--green-eco: #10b981;
--green-dark: #065f46;
--gold: #f1c40f;
--silver: #95a5a6;
--bronze: #cd7f32;
--white: #ffffff;
}
/* --- RESET E BASE --- */
body {
font-family: "Arial";
}
table, h1 {
margin: auto;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: var(--bg-light);
margin: 0;
padding: 40px 20px;
color: var(--text-dark);
line-height: 1.5;
}
h1 {
text-align: center;
padding: 15px;
color: var(--text-dark);
font-size: 2.5rem;
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: 30px;
}
table {
width: 75vw;
/* --- BOTTONI ORDINAMENTO --- */
.sorting-buttons {
text-align: center;
border-collapse: collapse;
margin-bottom: 30px;
}
.sort-button {
display: inline-block;
padding: 12px 25px;
margin: 0 10px;
background-color: var(--white);
color: var(--green-eco);
text-decoration: none;
border-radius: 12px;
border: 2px solid var(--green-eco);
font-weight: 600;
transition: all 0.3s ease;
}
.sort-button:hover {
background-color: var(--green-eco);
color: var(--white);
transform: translateY(-2px);
}
.sort-button.active {
background-color: var(--green-eco);
color: var(--white);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
}
/* --- TABELLA --- */
table {
width: 100%;
max-width: 1100px;
margin: 0 auto;
border-collapse: separate;
border-spacing: 0;
background-color: var(--white);
border-radius: 20px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0,0,0,0.05);
}
th, td {
padding: 10px;
border: 1px solid black;
padding: 18px;
text-align: center;
border-bottom: 1px solid #edf2f7;
}
th {
background-color: var(--green-eco);
color: var(--white);
font-weight: 700;
text-transform: uppercase;
font-size: 0.85rem;
letter-spacing: 1px;
}
/* --- COLORAZIONE PODIO DINAMICA (Basata su Classi) --- */
/* 1° Posto - ORO (Funziona ovunque sia la riga) */
tr.rank-gold {
background-color: rgba(241, 196, 15, 0.08) !important;
}
tr.rank-gold td:first-child strong {
color: var(--gold);
font-size: 1.4rem;
display: inline-block;
}
/* 2° Posto - ARGENTO */
tr.rank-silver {
background-color: rgba(189, 195, 199, 0.08) !important;
}
tr.rank-silver td:first-child strong {
color: var(--silver);
font-size: 1.25rem;
}
/* 3° Posto - BRONZO */
tr.rank-bronze {
background-color: rgba(205, 127, 50, 0.05) !important;
}
tr.rank-bronze td:first-child strong {
color: var(--bronze);
font-size: 1.15rem;
}
/* Stile base per la colonna posizione */
td strong {
font-weight: 800;
color: var(--text-dark);
}
/* Effetto al passaggio del mouse sulle righe */
tr:hover {
background-color: #f1f5f9;
}
/* Arrotondamento angoli inferiori della tabella */
table tr:last-child td:first-child { border-bottom-left-radius: 20px; }
table tr:last-child td:last-child { border-bottom-right-radius: 20px; }
/* Rimuove l'ultima linea per pulizia estetica */
table tr:last-child td {
border-bottom: none;
}
/* --- RESPONSIVE --- */
@media (max-width: 768px) {
body { padding: 20px 10px; }
table { font-size: 0.8rem; }
th, td { padding: 12px 8px; }
h1 { font-size: 1.8rem; }
.sort-button { padding: 10px 15px; margin: 5px; }
}
+292 -115
View File
@@ -1,31 +1,25 @@
/* --- VARIABILI GLOBALI --- */
:root {
--green-bright: #2ecc71;
--green-glow: rgba(46, 204, 113, 0.6);
--glass-bg: rgba(0, 0, 0, 0.7);
--glass-border: rgba(46, 204, 113, 0.3);
--top: 10px;
--border: 20px;
--text: 1.5rem;
}
/* --- RESET E BODY --- */
body {
margin: 0;
padding: 0;
overflow: hidden;
font-family: sans-serif;
}
#punti {
position: absolute;
top: var(--top);
left: var(--border);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #000;
color: white;
font-size: var(--text);
pointer-events: none;
display: none;
}
#tempo {
position: absolute;
top: var(--top);
right: var(--border);
color: white;
font-size: var(--text);
pointer-events: none;
display: none;
}
/* --- HUD DI GIOCO (Punti e Tempo) --- */
#punti, #tempo {
position: absolute;
top: var(--top);
@@ -33,182 +27,365 @@ body {
font-size: var(--text);
pointer-events: none;
display: none;
/* Aggiungi questa riga */
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8), 0 0 10px var(--green-glow);
font-weight: bold;
z-index: 50;
}
/* Cursore */
#punti { left: var(--border); }
#tempo { right: var(--border); }
#fps-counter {
position: absolute;
top: var(--top); /* Usa la stessa variabile degli altri */
left: 50%;
transform: translateX(-50%); /* Lo centra perfettamente */
color: var(--green-bright);
font-size: 1.2rem; /* Leggermente più piccolo dei punti per non distrarre */
font-family: monospace;
pointer-events: none;
display: none; /* Verrà mostrato insieme agli altri al lock() */
text-shadow: 0 0 10px var(--green-glow);
z-index: 50;
}
/* --- MIRINO (Crosshair) --- */
#crosshair {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
color: var(--green-bright);
font-size: 24px;
font-family: sans-serif;
pointer-events: none;
display: none; /* Sarà visualizzato solo durante il gioco */
display: none;
z-index: 10;
text-shadow: 0 0 5px #000;
}
/* Schermata iniziale del gioco */
/* --- SCHERMATA DI CARICAMENTO (MATRIX) --- */
#loading-screen {
position: fixed;
inset: 0;
background: #000;
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
overflow: hidden;
}
#matrix-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.3;
}
.loader-content {
position: relative;
z-index: 10;
width: 80%;
max-width: 500px;
text-align: center;
}
.eco-title {
font-family: 'Courier New', monospace;
color: var(--green-bright);
font-size: 1.5rem;
font-weight: bold;
letter-spacing: 5px;
margin-bottom: 30px;
text-shadow: 0 0 10px var(--green-glow);
}
.bar-container {
width: 100%;
background: #222 !important;
height: 10px;
border-radius: 5px;
overflow: hidden;
position: relative;
z-index: 20;
border: 1px solid rgba(255,255,255,0.1);
}
#loading-bar {
width: 0%;
height: 100%;
background-color: var(--green-bright) !important;
box-shadow: 0 0 20px var(--green-bright);
transition: width 0.3s ease-out;
}
#loading-text {
color: var(--green-bright);
font-family: monospace;
font-size: 0.9rem;
letter-spacing: 2px;
opacity: 0.8;
margin-top: 10px;
}
/* --- POPUP INSERIMENTO USERNAME --- */
/* Overlay con sfocatura profonda */
#name-popup-overlay {
position: fixed;
inset: 0;
z-index: 9999;
background: radial-gradient(circle, rgba(0, 20, 10, 0.7) 0%, rgba(0, 0, 0, 0.95) 100%);
backdrop-filter: blur(12px);
display: none;
align-items: center;
justify-content: center;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* Contenitore Popup */
.name-popup {
background: rgba(10, 15, 12, 0.85);
border: 2px solid var(--green-bright);
padding: 3rem;
border-radius: 30px;
text-align: center;
box-shadow: 0 0 40px rgba(46, 204, 113, 0.2), inset 0 0 20px rgba(46, 204, 113, 0.1);
max-width: 450px;
width: 90%;
transform: translateY(20px);
animation: popupFadeIn 0.6s ease forwards;
border-top: 5px solid var(--green-bright); /* Accento superiore */
}
@keyframes popupFadeIn {
to { transform: translateY(0); opacity: 1; }
}
.name-popup h3 {
color: var(--green-bright);
margin-bottom: 10px;
letter-spacing: 3px;
text-transform: uppercase;
font-weight: 900;
text-shadow: 0 0 10px var(--green-glow);
}
.name-popup p {
color: rgba(255, 255, 255, 0.7);
font-size: 0.9rem;
margin-bottom: 2rem;
font-style: italic;
}
/* Input stilizzato */
#username-input {
width: 100%;
padding: 15px;
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(46, 204, 113, 0.4);
border-radius: 12px;
color: #fff;
font-size: 1.2rem;
margin-bottom: 25px;
outline: none;
text-align: center;
transition: all 0.3s ease;
box-sizing: border-box;
letter-spacing: 1px;
}
#username-input:focus {
border-color: var(--green-bright);
box-shadow: 0 0 15px var(--green-glow);
background: rgba(46, 204, 113, 0.05);
}
/* Pulsante di conferma */
#confirm-name-btn {
width: 100%;
background: var(--green-bright);
color: #002b12;
border: none;
padding: 15px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 2px;
cursor: pointer;
border-radius: 12px;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
#confirm-name-btn:hover {
background: #fff;
color: #000;
box-shadow: 0 0 25px #fff;
transform: scale(1.03);
}
/* Effetto errore (scuotimento) */
.shake {
animation: shakeAnim 0.4s ease;
}
@keyframes shakeAnim {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-10px); }
75% { transform: translateX(10px); }
}
/* --- SCHERMATA INIZIALE (START) --- */
/* L'overlay serve come ulteriore strato di colore per far leggere bene i testi */
.overlay {
position: absolute;
inset: 0;
background: radial-gradient(circle, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 100%);
z-index: 0;
pointer-events: none;
}
/* Assicurati che il contenitore start non mostri lo scale dell'immagine fuori dai bordi */
#start {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
color: white;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
/* Queste 4 righe garantiscono la centratura totale */
inset: 0;
overflow: hidden; /* Fondamentale per il trucco dello scale */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100; /* Assicurati che sia sopra al renderer di Three.js */
z-index: 100;
}
/* Video a tutto schermo */
#bg-video {
#bg-island {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: -1;
object-fit: cover; /* Riempie lo schermo senza deformare */
/* Effetto sfocatura e luminosità ridotta */
filter: blur(6px) brightness(0.5);
/* Lo scale(1.1) serve a "ingrandire" l'immagine quel tanto che basta
per nascondere i bordi sfocati che altrimenti diventerebbero bianchi */
transform: scale(1.1);
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 0;
pointer-events: none; /* Il click attraversa l'overlay e arriva a #start */
pointer-events: none;
}
.content {
position: relative;
z-index: 1;
height: 100%;
width: 100%; /* Aggiunto */
width: 100%;
display: flex;
flex-direction: column;
}
/* Divisione 50% Sopra */
/* Titolo Missione */
.top-section {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding-top: 40px;
}
.main-title {
width: 25%;
height: auto;
filter: drop-shadow(0 0 15px var(--green-bright));
}
/* Divisione 50% Sotto */
/* Layout a Tre Colonne */
.bottom-section {
flex: 1;
display: flex;
flex-direction: row;
width: 100%; /* Forza l'espansione orizzontale */
align-items: flex-start;
padding-top: 20px;
flex: 1.5;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
padding: 0 5%;
gap: 20px;
}
.item {
/* flex: 1 0 33%; significa: cresci, non restringerti, base 33% */
flex: 1 0 33.33%;
display: flex;
justify-content: center;
text-align: center;
}
/* Styling dei Controlli */
.controls-box {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
/* Box con effetto Vetro (Glassmorphism) */
.controls-box, .instructions-box {
background: var(--glass-bg);
backdrop-filter: blur(10px);
border: 1px solid var(--glass-border);
padding: 25px;
border-radius: 15px;
color: white;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
font-family: 'Press Start 2P', cursive;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
width: 100%;
max-width: 350px;
}
.controls-box h3 {
margin-bottom: 10px;
font-size: 1.4rem;
.controls-box h3, .instructions-box h3 {
margin-top: 0;
margin-bottom: 15px;
font-size: 1.3rem;
text-transform: uppercase;
color: var(--green-bright);
text-align: center;
border-bottom: 1px solid var(--glass-border);
padding-bottom: 10px;
}
.controls-box p {
margin: 6px 0;
font-size: 1.1rem;
.controls-box p, .instructions-box p {
margin: 8px 0;
font-size: 1rem;
text-align: center;
line-height: 1.5;
}
/* Stile dei tasti */
.controls-box kbd {
background-color: #5e5d5d;
color: #fff;
padding: 4px 8px;
/* Tasti Tastiera */
kbd {
background-color: #333;
border: 1px solid var(--green-bright);
color: var(--green-bright);
padding: 3px 6px;
border-radius: 4px;
font-family: monospace;
font-weight: bold;
margin: 6px 0;
box-shadow: 0 0 5px var(--green-glow);
}
/* Box delle istruzioni */
.instructions-box {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: white;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
font-family: 'Press Start 2P', cursive;
}
.instructions-box h3 {
margin-bottom: 10px;
font-size: 1.4rem;
text-transform: uppercase;
text-align: center;
}
.instructions-box p {
margin: 6px 0;
font-size: 1.1rem;
text-align: center;
}
/* Animazione per "Clicca per giocare" */
/* Testo Pulsante Centrale */
.pulse-text {
font-size: 1.5rem;
font-size: 2rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 2px;
letter-spacing: 4px;
color: #fff;
text-shadow: 0 0 15px var(--green-bright);
cursor: pointer;
animation: pulse 2s infinite;
text-align: center;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.4; }
100% { opacity: 1; }
0% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.95); }
100% { opacity: 1; transform: scale(1); }
}
/* Responsive per schermi piccoli */
@media (max-width: 900px) {
.bottom-section {
grid-template-columns: 1fr;
overflow-y: auto;
padding-bottom: 40px;
}
.main-title { width: 50%; }
}
+96 -63
View File
@@ -1,123 +1,156 @@
/* --- VARIABILI GLOBALI --- */
:root {
--bg-light: #f8fafc;
--text-dark: #1e293b;
--green-eco: #10b981;
--red-alert: #ef4444;
--counter-top: #334155;
}
/* --- RESET BASE --- */
body {
margin: 0;
font-family: Arial, sans-serif;
background-color: #f0f0f0;
padding: 0;
font-family: 'Inter', -apple-system, sans-serif;
background-color: var(--bg-light);
color: var(--text-dark);
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
/* --- UI SUPERIORE (Punti e Tempo) --- */
#ui {
margin-top: 20px;
padding: 12px 40px;
background: white;
border-radius: 16px;
font-size: 1.1rem;
font-weight: 700;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
border: 1px solid #e2e8f0;
z-index: 1000;
}
#ui span {
color: var(--green-eco);
}
/* --- CONTAINER GIOCO --- */
#game-container {
width: 1000px;
height: 600px;
background-color: #000;
width: 92vw;
height: 82vh;
background-color: #000; /* Fondo nero per il mix-blend-mode */
position: relative;
overflow: hidden;
border: 5px solid #333;
margin: 0 auto;
cursor: default;
border-radius: 24px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
margin: 20px auto;
border: 8px solid white; /* Cornice stile tablet */
}
/* Area dei bidoni */
/* --- AREA DEI BIDONI (Ingranditi) --- */
#bins-container {
display: flex;
justify-content: space-between;
padding: 20px 10px;
justify-content: center; /* Centra i bidoni */
gap: 12px; /* Spazio tra i bidoni */
align-items: flex-end;
padding: 30px 20px;
width: 100%;
height: 72%; /* Più spazio verticale per cestini grandi */
box-sizing: border-box;
}
/* Effetto bagliore per risposta corretta (Verde) */
.glow-success {
box-shadow: 0 0 30px 15px rgba(0, 255, 0, 0.6);
border-radius: 50%; /* Rende l'effetto ovale/circolare */
transition: box-shadow 0.2s ease-in-out;
.bin {
position: relative;
/* Ingrandimento: ora i bidoni occupano fino al 20% della larghezza */
width: 20%;
max-width: 200px;
min-width: 140px;
transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Effetto bagliore per risposta errata (Rosso) */
.glow-error {
box-shadow: 0 0 30px 15px rgba(255, 0, 0, 0.6);
border-radius: 50%;
transition: box-shadow 0.2s ease-in-out;
.bin:hover {
transform: scale(1.08) translateY(-10px);
}
/* Gestione immagini con sfondo nero e scritta interna */
.bin img {
width: 100%;
height: auto;
mix-blend-mode: screen; /* Rende il nero dell'immagine trasparente */
pointer-events: none;
z-index: 2;
position: relative;
/* Questa riga è la magia: */
mix-blend-mode: screen;
/* 'screen' mantiene i colori chiari e rende invisibile il nero.
In questo modo il bagliore dietro passerà attraverso le zone nere. */
/* Aumentiamo luminosità e contrasto per far leggere meglio le scritte nere */
filter: brightness(1.25) contrast(1.1);
}
/* Modifica leggera al bagliore per renderlo più visibile sotto il bidone */
/* --- EFFETTI FEEDBACK (Bagliore sotto) --- */
.bin::after {
content: "";
position: absolute;
top: 60%; /* Abbassato un po' per centrarsi meglio sulla base del bidone */
bottom: 5%;
left: 50%;
transform: translate(-50%, -50%);
width: 150px;
height: 100px; /* Più largo che alto per un effetto ovale perfetto */
transform: translateX(-50%);
width: 100%;
height: 100px;
border-radius: 50%;
opacity: 0;
filter: blur(10px); /* Sfuma i bordi della luce */
transition: opacity 0.3s;
z-index: -1;
filter: blur(25px);
transition: opacity 0.3s ease;
z-index: 1;
}
/* Stato Successo (Verde) */
.glow-success::after {
opacity: 1;
box-shadow: 0 0 40px 20px rgba(0, 255, 0, 0.7);
background-color: rgba(0, 255, 0, 0.2); /* Opzionale: un leggero centro colorato */
opacity: 0.8;
background: var(--green-eco);
box-shadow: 0 0 60px 30px var(--green-eco);
}
/* Stato Errore (Rosso) */
.glow-error::after {
opacity: 1;
box-shadow: 0 0 40px 20px rgba(255, 0, 0, 0.7);
background-color: rgba(255, 0, 0, 0.2);
opacity: 0.8;
background: var(--red-alert);
box-shadow: 0 0 60px 30px var(--red-alert);
}
.bin img {
width: 100%;
height: auto;
pointer-events: none; /* Impedisce che il mouse "prenda" l'immagine del bidone */
}
/* I rifiuti */
/* --- I RIFIUTI (Trash) --- */
.trash {
width: 100px;
height: 100px;
width: 90px;
height: 90px;
object-fit: contain;
position: absolute;
z-index: 100;
z-index: 500;
cursor: grab;
/* Ombra per farli sembrare sollevati */
filter: drop-shadow(0 12px 10px rgba(0,0,0,0.4));
}
.trash:active {
cursor: grabbing;
}
/* Il bancone marrone in basso */
/* --- IL BANCONE (Counter) --- */
#counter {
position: absolute;
bottom: 0;
width: 100%;
height: 100px;
background-color: #5d4037;
/* Colore marrone bancone */
height: 120px;
background: var(--counter-top);
border-top: 3px solid rgba(255,255,255,0.1);
display: flex;
justify-content: center;
align-items: center;
}
/* Area UI (Punti e Tempo) */
#ui {
margin-top: 10px;
font-size: 24px;
font-weight: bold;
/* Effetto profondità sul bordo del bancone */
#counter::before {
content: "";
position: absolute;
top: 0;
width: 100%;
height: 8px;
background: linear-gradient(to bottom, rgba(0,0,0,0.3), transparent);
}
+125
View File
@@ -0,0 +1,125 @@
body {
margin: 0;
font-family: Arial, sans-serif;
background-color: #f0f0f0;
display: flex;
flex-direction: column;
align-items: center;
}
#game-container {
/*width: 1000px;
height: 600px;*/
width: 90vw;
height: 90vh;
background-color: #000;
position: relative;
overflow: hidden;
border: 5px solid #333;
margin: 0 auto;
cursor: default;
}
/* Area dei bidoni */
#bins-container {
display: flex;
justify-content: space-between;
padding: 20px 10px;
width: 100%;
box-sizing: border-box;
}
/* Effetto bagliore per risposta corretta (Verde) */
.glow-success {
box-shadow: 0 0 30px 15px rgba(0, 255, 0, 0.6);
border-radius: 50%; /* Rende l'effetto ovale/circolare */
transition: box-shadow 0.2s ease-in-out;
}
/* Effetto bagliore per risposta errata (Rosso) */
.glow-error {
box-shadow: 0 0 30px 15px rgba(255, 0, 0, 0.6);
border-radius: 50%;
transition: box-shadow 0.2s ease-in-out;
}
.bin img {
width: 100%;
height: auto;
pointer-events: none;
/* Questa riga è la magia: */
mix-blend-mode: screen;
/* 'screen' mantiene i colori chiari e rende invisibile il nero.
In questo modo il bagliore dietro passerà attraverso le zone nere. */
}
/* Modifica leggera al bagliore per renderlo più visibile sotto il bidone */
.bin::after {
content: "";
position: absolute;
top: 60%; /* Abbassato un po' per centrarsi meglio sulla base del bidone */
left: 50%;
transform: translate(-50%, -50%);
width: 150px;
height: 100px; /* Più largo che alto per un effetto ovale perfetto */
border-radius: 50%;
opacity: 0;
filter: blur(10px); /* Sfuma i bordi della luce */
transition: opacity 0.3s;
z-index: -1;
}
/* Stato Successo (Verde) */
.glow-success::after {
opacity: 1;
box-shadow: 0 0 40px 20px rgba(0, 255, 0, 0.7);
background-color: rgba(0, 255, 0, 0.2); /* Opzionale: un leggero centro colorato */
}
/* Stato Errore (Rosso) */
.glow-error::after {
opacity: 1;
box-shadow: 0 0 40px 20px rgba(255, 0, 0, 0.7);
background-color: rgba(255, 0, 0, 0.2);
}
.bin img {
width: 100%;
height: auto;
pointer-events: none; /* Impedisce che il mouse "prenda" l'immagine del bidone */
}
/* I rifiuti */
.trash {
width: 100px;
height: 100px;
object-fit: contain;
position: absolute;
z-index: 100;
}
.trash:active {
cursor: grabbing;
}
/* Il bancone marrone in basso */
#counter {
position: absolute;
bottom: 0;
width: 100%;
height: 100px;
background-color: #5d4037;
/* Colore marrone bancone */
display: flex;
justify-content: center;
align-items: center;
}
/* Area UI (Punti e Tempo) */
#ui {
margin-top: 10px;
font-size: 24px;
font-weight: bold;
}
+194
View File
@@ -0,0 +1,194 @@
/* --- VARIABILI GLOBALI --- */
:root {
--green-bright: #2ecc71;
--green-glow: rgba(46, 204, 113, 0.4);
--glass-bg: rgba(255, 255, 255, 0.05);
--glass-border: rgba(46, 204, 113, 0.2);
--top: 10px;
--text-base: 1.1rem;
}
/* --- RESET E BASE --- */
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #000; /* Sfondo di sicurezza */
}
/* --- SCHERMATA FASE 2 --- */
#start-fase2 {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 500;
color: white;
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
/* Sfondo Sfumato Base */
background: radial-gradient(circle at center, #0a2f1a 0%, #000000 100%);
transition: opacity 0.5s ease;
}
/* Overlay per dare texture allo sfondo */
.overlay-gradient {
position: absolute;
inset: 0;
background: linear-gradient(180deg, transparent 0%, rgba(46, 204, 113, 0.05) 100%);
pointer-events: none;
z-index: 0;
}
/* --- CONTENUTO --- */
.content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
/* Sezione Titolo (50% sopra) */
.top-section {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.top-section h1 {
font-size: 3.5rem;
text-transform: uppercase;
letter-spacing: 5px;
color: var(--green-bright);
text-shadow: 0 0 20px var(--green-glow);
margin: 0;
padding: 20px;
}
/* Sezione Box (50% sotto) */
.bottom-section {
flex: 1;
display: flex;
flex-direction: row;
width: 100%;
align-items: flex-start;
padding-top: 20px;
}
.item {
flex: 1;
display: flex;
justify-content: center;
align-items: flex-start;
padding: 0 20px;
}
/* --- BOX STILIZZATI (Glassmorphism) --- */
.controls-box, .instructions-box {
background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-top: 4px solid var(--green-bright);
padding: 30px;
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0,0,0,0.6);
width: 100%;
max-width: 350px;
min-height: 200px;
text-align: center;
}
.controls-box h3, .instructions-box h3 {
margin-top: 0;
margin-bottom: 20px;
font-size: 1.4rem;
text-transform: uppercase;
color: var(--green-bright);
letter-spacing: 1px;
}
.controls-box p, .instructions-box p {
font-size: var(--text-base);
line-height: 1.6;
margin: 10px 0;
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
}
/* --- ELEMENTI SPECIALI --- */
kbd {
background: #222;
color: var(--green-bright);
border: 1px solid var(--green-bright);
padding: 3px 10px;
border-radius: 5px;
font-family: 'Courier New', Courier, monospace;
font-weight: bold;
box-shadow: 0 0 5px var(--green-glow);
}
b, i {
color: var(--green-bright);
font-style: normal;
}
/* --- PULSANTE CENTRALE --- */
.item.center {
align-items: center; /* Il pulsante centrale lo vogliamo a metà altezza */
height: 50%;
}
.pulse-text {
display: inline-block;
padding: 20px 40px;
font-size: 1.5rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 2px;
color: white;
border: 2px solid var(--green-bright);
border-radius: 50px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
animation: pulse 2s infinite;
background: rgba(46, 204, 113, 0.1);
}
.pulse-text:hover {
background: var(--green-bright);
color: black;
box-shadow: 0 0 30px var(--green-glow);
transform: scale(1.1);
animation-play-state: paused; /* Si ferma quando ci passi sopra */
}
/* --- ANIMAZIONI --- */
@keyframes pulse {
0% { transform: scale(1); opacity: 1; box-shadow: 0 0 0 0 rgba(46, 204, 113, 0.7); }
50% { transform: scale(1.05); opacity: 0.7; box-shadow: 0 0 20px 10px rgba(46, 204, 113, 0); }
100% { transform: scale(1); opacity: 1; box-shadow: 0 0 0 0 rgba(46, 204, 113, 0); }
}
/* --- RESPONSIVE VELOCE --- */
@media (max-width: 900px) {
.bottom-section {
flex-direction: column;
align-items: center;
}
.item {
margin-bottom: 20px;
width: 80%;
}
.top-section h1 {
font-size: 2rem;
}
}
+203
View File
@@ -0,0 +1,203 @@
:root {
--green-bright: #2ecc71;
--green-glow: #27ae60;
--bg-overlay: rgba(0, 0, 0, 0.6);
--panel-bg: rgba(0, 0, 0, 0.3);
--text-main: 1.2rem;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: white;
background: url('../assets/media/img/sfondo_finale.png') no-repeat center center fixed;
background-size: cover;
}
/* Overlay per scurire leggermente lo sfondo e far risaltare il testo */
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--bg-overlay);
z-index: 0;
}
/* Layout Principale (Diviso in Sinistra e Destra) */
#start {
position: relative;
width: 100vw;
height: 100vh;
z-index: 10;
}
.content {
display: flex;
width: 100%;
height: 100%;
position: relative;
}
/* --- SEZIONE SINISTRA (60% del totale) --- */
.left1 {
flex: 1.5;
display: flex;
flex-direction: column; /* Divide in Alto e Basso */
border-right: 1px solid rgba(46, 204, 113, 0.2);
}
/* Parte in ALTO a sinistra (Titolo) */
.left1 .top {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.left1 .top h1 {
font-size: 4.5rem;
margin: 0;
color: #fff;
text-shadow: 0 0 20px var(--green-bright), 0 0 30px var(--green-glow);
letter-spacing: 5px;
text-align: center;
}
/* Parte in BASSO a sinistra (Divisa in Bottoni e Punteggi) */
.left1 .bottom {
flex: 1;
display: flex; /* Divide in Sinistra (left2) e Destra (right2) */
padding: 40px;
gap: 30px;
}
/* Bottoni (Basso-Sinistra) */
.left2 {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 20px;
}
button {
width: 280px;
padding: 18px;
font-size: 1.1rem;
font-weight: bold;
color: white;
background: transparent;
border: 2px solid var(--green-bright);
border-radius: 8px;
cursor: pointer;
text-transform: uppercase;
transition: all 0.3s ease;
}
button:hover {
background: var(--green-bright);
color: black;
box-shadow: 0 0 25px var(--green-bright);
transform: scale(1.05);
}
/* Riepilogo Punteggi (Basso-Destra) */
.right2 {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: var(--panel-bg);
border: 1px solid rgba(46, 204, 113, 0.3);
border-radius: 20px;
padding: 20px;
}
.right2 p {
font-size: 1.4rem;
line-height: 2;
margin: 0;
}
.right2 b {
color: var(--green-bright);
font-size: 1.6rem;
display: block;
margin-bottom: 10px;
}
.right2 i {
color: #ccc;
font-style: normal;
font-weight: bold;
}
/* --- SEZIONE DESTRA (40% del totale - Classifica) --- */
.right1 {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px;
background: rgba(0, 0, 0, 0.4);
}
.right1 h2 {
font-size: 2.5rem;
color: var(--green-bright);
margin-bottom: 30px;
text-transform: uppercase;
}
/* Tabella Classifica */
table.container {
width: 90%;
border-collapse: collapse;
background: rgba(255, 255, 255, 0.03);
border-radius: 10px;
overflow: hidden;
}
table.container th {
background: rgba(46, 204, 113, 0.5);
padding: 15px;
text-align: center;
font-size: 1.1rem;
}
table.container td {
padding: 12px;
text-align: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
font-size: 1.1rem;
}
table.container tr:last-child td {
border-bottom: none;
}
table.container tr:hover {
background: rgba(46, 204, 113, 0.1);
}
/* Supporto per Mobile/Schermi piccoli */
@media (max-width: 1000px) {
.content {
flex-direction: column;
overflow-y: auto;
}
.left1 .bottom {
flex-direction: column;
}
.left1 .top h1 {
font-size: 2.5rem;
}
}
+9
View File
@@ -15,3 +15,12 @@ CREATE TABLE punteggi (
-- precisione = score2 / score1
-- punteggio finale = 10000 * ( (score1 / MAX_SCORE) + (score2 / MAX_SCORE) ) / 2
-- MAX_SCORE = 180 (numero massimo possibile di rifiuti raccolti in 1 minuto)
-- DATI DI TEST
INSERT INTO punteggi (data_partita, score1, score2, scoreT, nome) VALUES
('2024-06-01', 150, 120, 7500, 'Simone'),
('2024-06-02', 180, 160, 9444, 'Luca'),
('2024-06-03', 120, 100, 6111, 'Giulia'),
('2024-06-04', 90, 70, 4444, 'Marco'),
('2024-06-05', 60, 50, 3055, 'Sara');
+9 -1
View File
@@ -1,9 +1,17 @@
<head>
<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=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<script>
const MAX_SCORE = 180;
</script>
</head>
<?php
$route = [
'start' => 'fase1.html',
'mid' => 'istruzioniFase2.html',
'sep' => 'fase2.html',
'end' => 'risultatiFinali.html',
'end' => 'risultatiFinali.php',
'leaderboard' => 'classifica.php',
'error' => 'error.html'
];
+80 -13
View File
@@ -2,29 +2,79 @@ import * as THREE from 'three';
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
// 0. IMPOSTAZIONE FPS
let lastTime = performance.now();
let frameCount = 0;
let fps = 0;
const fpsDisplay = document.getElementById('fps-counter');
// 1a. INIZIALIZZAZIONE CARICAMENTO ASSETS
const manager = new THREE.LoadingManager();
manager.onStart = function (url, itemsLoaded, itemsTotal) {
document.getElementById("loading-text").innerHTML = `Inizio caricamento: ${url}`;
console.log(`Inizio caricamento: ${url}`);
// Testo iniziale
document.getElementById("loading-text").innerHTML = "IDENTIFICAZIONE RISORSE...";
};
manager.onLoad = function () {
document.getElementById('loading-screen').style.display = 'none';
animate(); // avvia il rendering
const loadingText = document.getElementById("loading-text");
// Testo in italiano, tecnico e pulito
loadingText.innerHTML = "CARICAMENTO COMPLETATO CON SUCCESSO";
// Estetica finale: testo fisso e brillante
loadingText.style.animation = "none";
loadingText.style.color = "#ffffff";
loadingText.style.textShadow = "0 0 15px var(--green-bright)";
loadingText.style.opacity = "1";
// Timeout per lasciare all'utente il tempo di leggere il successo
setTimeout(() => {
const loadingScreen = document.getElementById('loading-screen');
loadingScreen.style.opacity = '0';
loadingScreen.style.transition = 'opacity 0.8s ease';
setTimeout(() => {
loadingScreen.style.display = 'none';
// MOSTRA IL POPUP DEL NOME
document.getElementById('name-popup-overlay').style.display = 'flex';
}, 800);
}, 500);
animate(); // Parte il loop di Three.js
};
manager.onProgress = function (url, itemsLoaded, itemsTotal) {
// Calcola la percentuale
const percent = (itemsLoaded / itemsTotal) * 100;
document.getElementById('loading-bar').style.width = percent + '%';
// Aggiorna il testo
document.getElementById("loading-text").innerHTML = `Caricamento: ${itemsLoaded} di ${itemsTotal} - ${url}`;
console.log(`Caricamento: ${itemsLoaded} di ${itemsTotal} - ${url}`);
const percent = Math.round((itemsLoaded / itemsTotal) * 100);
// Estraiamo solo il nome del file dall'URL per pulizia
// Esempio: "assets/models/bottiglia.glb" diventa "bottiglia.glb"
const fileName = url.split('/').pop().toUpperCase();
// Aggiorniamo la barra
const bar = document.getElementById('loading-bar');
if (bar) bar.style.width = percent + '%';
// Nuovo formato del testo: NOME_FILE [XX%]
document.getElementById("loading-text").innerHTML = `${fileName} [${percent}%]`;
};
manager.onError = function (url) {
document.getElementById("loading-text").innerHTML = `Errore nel caricamento: ${url}`;
console.log(`Errore nel caricamento: ${url}`);
document.getElementById("loading-text").innerHTML = `ERRORE CARICAMENTO: ${url.split('/').pop()}`;
document.getElementById("loading-text").style.color = "#ff4d4d";
};
// GESTIONE CONFERMA NOME
document.getElementById('confirm-name-btn').addEventListener('click', function() {
const input = document.getElementById('username-input');
const name = input.value.trim();
localStorage.setItem("username", name);
// 1. Nascondi il Popup
const popup = document.getElementById('name-popup-overlay');
popup.style.opacity = '0';
popup.style.transition = 'opacity 0.5s ease';
setTimeout(() => {
popup.style.display = 'none';
// 2. MOSTRA LA SCHERMATA INIZIALE (Controlli e Titolo)
const mainContent = document.getElementById('main-start-content');
mainContent.style.display = 'flex';
// Piccolo trucco per l'animazione di entrata (fade in)
setTimeout(() => {
mainContent.style.opacity = '1';
mainContent.style.transition = 'opacity 1s ease';
}, 50);
}, 500);
});
// 1b. SCENA E CAMERA
const scene = new THREE.Scene();
@@ -150,6 +200,7 @@ function lock() {
document.getElementById('punti').style.display = 'block';
document.getElementById('tempo').style.display = 'block';
document.getElementById('crosshair').style.display = 'block';
document.getElementById('fps-counter').style.display = 'block';
}
function unlock() {
timer.pause();
@@ -157,6 +208,7 @@ function unlock() {
document.getElementById('punti').style.display = 'none';
document.getElementById('tempo').style.display = 'none';
document.getElementById('crosshair').style.display = 'none';
document.getElementById('fps-counter').style.display = 'none';
}
const controls = new PointerLockControls(camera, document.body);
document.getElementById('start').addEventListener('click', () => {
@@ -255,6 +307,21 @@ let lastSafePosition = camera.position.clone();
function animate() {
requestAnimationFrame(animate);
// --- Calcolo FPS ---
frameCount++;
const currentTime = performance.now();
// Ogni secondo (1000ms) aggiorniamo il contatore visivo
if (currentTime >= lastTime + 1000) {
fps = frameCount;
fpsDisplay.innerText = `FPS: ${fps}`;
// Colore dinamico: verde se fluido, giallo/rosso se lagga
if (fps < 30) fpsDisplay.style.color = "#ff4d4d";
else if (fps < 55) fpsDisplay.style.color = "#f1c40f";
else fpsDisplay.style.color = "var(--green-bright)";
frameCount = 0;
lastTime = currentTime;
}
if (controls.isLocked) {
// 1. Salva la posizione attuale prima del movimento
+38 -36
View File
@@ -14,8 +14,7 @@ const listaRifiuti = [
{ img: 'assets/media/img/fase2/rifiuti/pannolino.png', tipo: 'indifferenziata' }
];
let punteggio = 0;
let tempo = 60;
let rifiutiTotali = 0;
const counter = document.getElementById('counter');
const scoreDisplay = document.getElementById('score');
@@ -143,6 +142,7 @@ function controllaCollisione(rifiuto) {
// PORTIAMO IL BIDONE IN PRIMO PIANO
bidone.style.zIndex = "2000";
rifiutiTotali++;
if (rifiuto.dataset.tipo === bidone.dataset.type) {
// CORRETTO
punteggio += 1;
@@ -164,7 +164,9 @@ function controllaCollisione(rifiuto) {
bidone.style.zIndex = "1"; // Ritorna al suo posto dopo l'effetto
}, 400);
}
if (rifiutiTotali >= localStorage.getItem('punteggioFase1')) {
mostraFineGioco();
}
document.getElementById('score').innerText = punteggio;
rifiuto.remove();
setTimeout(generaRifiuto, 500);
@@ -181,23 +183,27 @@ let timerIntervallo;
let tempoRimanente;
function avviaTimer(secondiIniziali) {
const displayTimer = document.getElementById('timer');
const minuti = document.getElementById('minuti');
const secondi = document.getElementById('secondi');
// Inizializziamo la variabile globale con il valore ricevuto
tempoRimanente = secondiIniziali;
displayTimer.innerText = tempoRimanente;
minuti.innerText = String(Math.floor(tempoRimanente / 60)).padStart(2, '0');
secondi.innerText = String(tempoRimanente % 60).padStart(2, '0');
// Puliamo timer precedenti
clearInterval(timerIntervallo);
timerIntervallo = setInterval(() => {
tempoRimanente--; // Sottraiamo un secondo alla variabile globale
displayTimer.innerText = tempoRimanente; // Aggiorniamo lo schermo
minuti.innerText = String(Math.floor(tempoRimanente / 60)).padStart(2, '0');
secondi.innerText = String(tempoRimanente % 60).padStart(2, '0');
if (tempoRimanente <= 0) {
clearInterval(timerIntervallo);
tempoRimanente = 0;
displayTimer.innerText = "0";
minuti.innerText = "00";
secondi.innerText = "00";
mostraFineGioco();//LISA E' COMPITO TUO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
}
}, 1000);
@@ -236,36 +242,32 @@ function riposizionaSulBancone(rifiuto) {
function mostraFineGioco() {
// Blocca la generazione di nuovi rifiuti
clearInterval(timerIntervallo);
// Mostra un messaggio finale più gradevole
const messaggio = document.createElement('div');
messaggio.id = 'fine-gioco';
messaggio.innerHTML = `
<h2>Tempo scaduto!</h2>
<p>Il tuo punteggio finale è: <strong>${punteggio}</strong></p>
<button id="restart-btn">Gioca di nuovo</button>
`;
messaggio.style.position = 'absolute';
messaggio.style.top = '50%';
messaggio.style.left = '50%';
messaggio.style.transform = 'translate(-50%, -50%)';
messaggio.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
messaggio.style.padding = '20px';
messaggio.style.borderRadius = '10px';
messaggio.style.textAlign = 'center';
messaggio.style.zIndex = '3000';
document.body.appendChild(messaggio);
// Salva il punteggio nel localStorage
localStorage.setItem('punteggioFase2', punteggio);
// Aggiunge il pulsante per ricominciare
document.getElementById('restart-btn').addEventListener('click', () => {
document.body.removeChild(messaggio);
punteggio = 0;
scoreDisplay.innerText = punteggio;
avviaTimer(60); // o il tempo che vuoi
generaRifiuto();
const username = localStorage.getItem('username') || 'Anonimo';
const score1 = parseInt(localStorage.getItem('punteggioFase1')) || 0;
const score2 = parseInt(localStorage.getItem('punteggioFase2')) || 0;
const scoreT = 10_000 * ((score1 / MAX_SCORE) + (score2 / MAX_SCORE)) / 2;
const data_partita = new Date().toISOString().split('T')[0]; // Formato YYYY-MM-DD
fetch('http://localhost:8888/php/save_score.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
nome: username,
score1: score1,
score2: score2,
scoreT: scoreT,
data_partita: data_partita
})
})
.then(response => response.text())
.then(result => {
console.log("Punteggio salvato con successo:", result);
})
.catch(error => {
console.error("Errore nella richiesta:", error);
});
window.location = "?pagina=end";
}
+38
View File
@@ -0,0 +1,38 @@
const canvas = document.getElementById('matrix-canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const symbols = '♻☘01RECYCLE-EARTH-SAVE-FUTURE';
const fontSize = 16;
const columns = canvas.width / fontSize;
const drops = Array(Math.floor(columns)).fill(1);
function drawMatrix() {
// Sfondo semi-trasparente per creare l'effetto scia
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#2ecc71'; // Il tuo verde brillante
ctx.font = fontSize + 'px monospace';
for (let i = 0; i < drops.length; i++) {
const text = symbols.charAt(Math.floor(Math.random() * symbols.length));
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
drops[i] = 0;
}
drops[i]++;
}
}
// Avvia l'animazione
setInterval(drawMatrix, 35);
// Adatta se l'utente ridimensiona la finestra
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
Binary file not shown.
+58 -13
View File
@@ -1,32 +1,77 @@
<!DOCTYPE html>
<html lang="it">
<head>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Classifica completa</title>
<link rel="stylesheet" href="classifica.css">
</head>
<body>
<link rel="stylesheet" href="css/classifica.css">
</head>
<body>
<h1>Classifica</h1>
<?php
require 'php/leaderboard_utils.php';
$sort = $_GET['sort'] ?? 'score';
// Carico la mappa delle posizioni (logica ID -> Posizione)
$rankMap = getRankMap();
// Carico i dati in base all'ordinamento scelto
if ($sort === 'data') {
$leaderboard = getSortedLeaderboardByDate();
} else {
$leaderboard = getSortedLeaderboardByScore();
}
?>
<div class="sorting-buttons">
<a href="?pagina=leaderboard&sort=data" class="sort-button <?php echo $sort === 'data' ? 'active' : ''; ?>">Ordina per data</a>
<a href="?pagina=leaderboard&sort=score" class="sort-button <?php echo $sort === 'score' ? 'active' : ''; ?>">Ordina per punteggio</a>
</div>
<table>
<thead>
<tr>
<th>Posizione</th>
<th>Nome</th>
<th>Punteggio 1° fase</th>
<th>Punteggio 2° fase</th>
<th>Punteggio totale</th>
<th>Data di gioco</th>
<th>1° Fase (RPS)</th>
<th>2° Fase (%)</th>
<th>Punteggio Totale (% isola pulita)</th>
<th>Data</th>
</tr>
</thead>
<tbody>
<?php if (empty($leaderboard)): ?>
<tr><td colspan="6" style="text-align:center;">Nessun punteggio registrato.</td></tr>
<?php else: ?>
<?php foreach ($leaderboard as $entry): ?>
<?php
require 'db.php';
foreach ($db_test as $posizione => $record) {
riga($posizione, $record);
}
// Recupero la posizione reale (1, 2, 3...)
$posizioneReale = $rankMap[$entry['id']] + 1;
// Determiniamo la classe del podio
$classePodio = '';
if ($posizioneReale === 1) $classePodio = 'rank-gold';
elseif ($posizioneReale === 2) $classePodio = 'rank-silver';
elseif ($posizioneReale === 3) $classePodio = 'rank-bronze';
// Formattazione dati (tua logica esistente)
$rps = number_format(intval($entry['score1']) / 60, 2);
$fase2_perc = (intval($entry['score1']) > 0) ? intval(intval($entry['score2']) / intval($entry['score1']) * 100) : 0;
$punteggio_finale = number_format(intval($entry['scoreT']) / 100, 2);
$data_formattata = date("d/m/Y", strtotime($entry['data_partita']));
?>
<tr class="<?php echo $classePodio; ?>">
<td><strong><?php echo $posizioneReale; ?>°</strong></td>
<td><?php echo htmlspecialchars($entry['nome']); ?></td>
<td><?php echo $rps; ?> rps</td>
<td><?php echo $fase2_perc; ?>%</td>
<td><?php echo $punteggio_finale; ?>%</td>
<td><?php echo $data_formattata; ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</body>
</body>
</html>
+37 -22
View File
@@ -7,28 +7,40 @@
</head>
<body>
<!-- SCHERMATA DI CARICAMENTO -->
<div id="loading-screen" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #000; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 9999;">
<div style="width: 80%; max-width: 600px; background: #222; height: 20px; border-radius: 10px; overflow: hidden; margin-bottom: 10px;">
<div id="loading-bar" style="width: 0%; height: 100%; background: #4CAF50; transition: width 0.3s;"></div>
<div id="loading-screen">
<canvas id="matrix-canvas"></canvas>
<div class="loader-content">
<div class="eco-title">SAVE THE ISLAND</div>
<div class="bar-container">
<div id="loading-bar"></div>
</div>
<div id="loading-text">Caricamento delle risorse...</div>
</div>
</div>
<!-- POPUP INSERIMENTO USERNAME -->
<div id="name-popup-overlay">
<div class="name-popup">
<h3>IDENTIFICAZIONE GIOCATORE</h3>
<p>Inserisci il tuo nome per iniziare a giocare:</p>
<input type="text" id="username-input" placeholder="Nome Giocatore..." maxlength="15">
<button id="confirm-name-btn">ACCEDI AL GIOCO</button>
</div>
<div id="loading-text" style="color: #fff; font-family: monospace; font-size: 14px;">Loading...</div>
</div>
<!-- SCHERMATA INIZIALE -->
<div id="start">
<video autoplay muted loop id="bg-video">
<source src="assets/media/video/background.mp4" type="video/mp4">
</video>
<img src="assets/media/img/sfondo_iniziale.png" alt="Sfondo Isola" id="bg-island">
<div class="overlay"></div>
<div class="content">
<div class="top-section">
<img src="assets/media/img/titolo.png" alt="titolo" class="main-title" />
</div>
<div class="container bottom-section">
<div class="item left">
<div class="controls-box">
<header class="top-section">
<img src="assets/media/img/titolo.png" alt="Eco-Mission" class="main-title" />
</header>
<main class="bottom-section">
<section class="item side-panel">
<div class="glass-box">
<h3>Controlli</h3>
<p>Movimento</p>
<div class="control-list">
<p><kbd>Freccia su / W</kbd> Avanti</p>
<p><kbd>Freccia giù / S</kbd> Indietro</p>
<p><kbd>Freccia destra / D</kbd> Destra</p>
@@ -36,23 +48,25 @@
<p><kbd>Mouse</kbd> Punto di vista</p>
</div>
</div>
<div class="item center">
<span class="pulse-text">Clicca per giocare</span>
</div>
<div class="item right">
<div class="instructions-box">
</section>
<section class="item center-panel">
<div class="pulse-text" id="btn-start">Clicca per giocare</div>
</section>
<section class="item side-panel">
<div class="glass-box">
<h3>Istruzioni</h3>
<p>Obiettivo: raccogliere tutti i rifiuti sullisola</p>
<p><strong>Obiettivo:</strong> raccogliere tutti i rifiuti sullisola</p>
<p>Attenzione allo scadere del tempo!</p>
<p>Usa i comandi per esplorare e ripulire</p>
</div>
</div>
</div>
</section>
</main>
</div>
</div>
<!-- INTERFACCIA GRAFICA DEL GIOCO -->
<div id="punti">Rifiuti: <span id="score">0</span></div>
<div id="fps-counter">FPS: 0</div>
<div id="tempo">Tempo: <span id="minuti">XX</span>:<span id="secondi">XX</span></div>
<div id="crosshair">+</div>
<script type="importmap">
@@ -64,5 +78,6 @@
}
</script>
<script type="module" src="js/fase1.js"></script>
<script src="js/loadingScreen.js"></script>
</body>
</html>
+1 -1
View File
@@ -8,7 +8,7 @@
</head>
<body>
<div id="ui">
Punti: <span id="score">0</span> | Tempo: <span id="timer"></span>s
Punti: <span id="score">0</span> | Tempo: <span id="minuti"></span>:<span id="secondi"></span>
</div>
<div id="game-container">
+60
View File
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Istruzioni fase 2</title>
<link rel="stylesheet" href="css/istruzioniFase2.css">
</head>
<body>
<div id="start-fase2">
<div class="overlay-gradient"></div>
<div class="content">
<div class="top-section">
<h1>Istruzioni fase 2</h1>
</div>
<div class="container bottom-section">
<div class="item left">
<div class="controls-box">
<h3>Controlli</h3>
<p><kbd>Mouse</kbd> Per trascinare i rifiuti nei cestini</p>
</div>
</div>
<div class="item center">
<span class="pulse-text" id="btn-start-f2">Clicca per giocare</span>
</div>
<div class="item right">
<div class="instructions-box">
<h3>Istruzioni</h3>
<p>
Hai <b><i id="tempo-f2-val">--</i></b> secondi per completare il livello
<br>
Hai raccolto <b><i id="punti-f2-val">--</i></b> rifiuti nella fase precedente
<br>
<b>Obiettivo:</b> separare correttamente i rifiuti nei cestini corrispondenti
</p>
</div>
</div>
</div>
</div>
</div>
<script defer>
const punti = localStorage.getItem('punteggioFase1') || 0;
document.getElementById('punti-f2-val').textContent = punti;
document.getElementById('tempo-f2-val').textContent = punti * 5; // 5 secondi per ogni rifiuto raccolto nella fase 1
document.getElementById('btn-start-f2').style.cursor = 'pointer';
const schermata = document.getElementById('start-fase2');
schermata.style.display = 'flex';
// Listener per il pulsante centrale
document.getElementById('btn-start-f2').onclick = function() {
schermata.style.opacity = '0';
schermata.style.transition = 'opacity 0.5s ease';
setTimeout(() => {
schermata.style.display = 'none';
window.location = '?pagina=sep';
}, 500);
};
</script>
</body>
</html>
View File
+88
View File
@@ -0,0 +1,88 @@
<?php
require 'php/leaderboard_utils.php';
$sort = $_GET['sort'] ?? 'data';
// Carico la mappa delle posizioni (logica ID -> Posizione)
$rankMap = getRankMap();
// Carico i dati in base all'ordinamento scelto
$leaderboard = getTop10byScore();
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Schermata Finale</title>
<link rel="stylesheet" href="css/risultatiFinali.css">
</head>
<body>
<!-- Fase 2: separazione dei rifiuti tramite drag&drop -->
<!-- Layout simile a div#start in pages/fase1.html -->
<div id="start">
<div class="overlay"></div>
<div class="content">
<div class="left1">
<div class="top">
<h1>MISSIONE COMPIUTA</h1>
</div>
<div class="bottom">
<div class="left2">
<button type="button" onclick="window.open('?pagina=leaderboard', '_blank')">Classifica Completa</button>
<button type="button" onclick="localStorage.clear(); window.location = '?pagina=start'">Gioca Ancora</button>
</div>
<div class="right2">
<p>
<b>Punteggi:</b>
<br>
Prima fase: <i><span id="score1a"></span>/180</i>
<br>
Seconda fase: <i><span id="score2"></span>/<span id="score1b"></span></i>
<br>
Totale: <i><span id="scoreTotale"></span>/100</i>
</p>
</div>
</div>
</div>
<div class="right1">
<h2>Top 10</h2>
<table class="container">
<thead>
<tr>
<th>N°</th>
<th>Nome</th>
<th>Punteggio</th>
</tr>
</thead>
<tbody>
<?php if (empty($leaderboard)): ?>
<tr><td colspan="3" style="text-align:center;">Nessun punteggio registrato.</td></tr>
<?php else: ?>
<?php foreach ($leaderboard as $entry): ?>
<?php
// Recupero la posizione reale dalla mappa tramite l'ID
$posizioneReale = $rankMap[$entry['id']] + 1;
$punteggio_finale = number_format(intval($entry['scoreT']) / 100, 2);
?>
<tr>
<td><strong><?php echo $posizioneReale; ?>°</strong></td>
<td><?php echo htmlspecialchars($entry['nome']); ?></td>
<td><?php echo $punteggio_finale; ?>%</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<script defer>
const score1 = localStorage.getItem('punteggioFase1') || 0;
const score2 = localStorage.getItem('punteggioFase2') || 0;
const scoreT = 10_000 * ((score1 / MAX_SCORE) + (score2 / MAX_SCORE)) / 2;
document.getElementById('score1a').textContent = score1;
document.getElementById('score1b').textContent = score1;
document.getElementById('score2').textContent = score2;
document.getElementById('scoreTotale').textContent = scoreT.toFixed(0) / 100;
</script>
</body>
</html>
+7 -32
View File
@@ -1,35 +1,10 @@
<?php
$debug = false;
function setDebug($val) {
$debug = $val;
if ($debug) {
echo "db";
$servername = "localhost";
$username = "root";
$password = "root";
$dbname = "save-the-island";
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
}
$db_test = [
[
'nome' => 's1',
'p1' => 95,
'p2' => 90,
'pt' => 50,
'g' => '24-11-2025'
],
[
'nome' => 's1',
'p1' => 95,
'p2' => 90,
'pt' => 50,
'g' => '24-11-2025'
]
];
$classifica = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function riga($pos, $data) {
$pos++;
$nome = $data['nome'];
$p1 = $data['p1'];
$p2 = $data['p2'];
$pt = $data['pt'];
$g = $data['g'];
echo '<tr><td>'.$pos.'</td><td>'.$nome.'</td><td>'.$p1.'</td><td>'.$p2.'</td><td>'.$pt.'</td><td>'.$g.'</td></tr>';
}
?>
+48
View File
@@ -0,0 +1,48 @@
<?php
/*
database schema:
CREATE TABLE punteggi (
id INT AUTO_INCREMENT PRIMARY KEY,
data_partita DATE NOT NULL,
score1 INT NOT NULL,
score2 INT NOT NULL,
scoreT INT NOT NULL,
nome VARCHAR(100) NOT NULL
);
*/
define('TABLE_NAME', 'punteggi_test'); // Cambia in 'punteggi_test' se necessario
require 'db.php';
/**
* Crea una mappa [ID => Posizione] basata sullo scoreT
* Utile per sapere la posizione di una partita a prescindere dall'ordinamento visivo
*/
function getRankMap() {
global $conn;
$result = $conn->query("SELECT id FROM " . TABLE_NAME . " ORDER BY scoreT DESC");
$orderedIds = array_column($result->fetch_all(MYSQLI_ASSOC), 'id');
// array_flip trasforma [0 => id1, 1 => id2] in [id1 => 0, id2 => 1]
$rankMap = array_flip($orderedIds);
return $rankMap;
}
function getSortedLeaderboardByDate() {
global $conn;
$result = $conn->query("SELECT * FROM " . TABLE_NAME . " ORDER BY data_partita DESC, id DESC");
return $result->fetch_all(MYSQLI_ASSOC);
}
function getSortedLeaderboardByScore() {
global $conn;
$result = $conn->query("SELECT * FROM " . TABLE_NAME . " ORDER BY scoreT DESC");
return $result->fetch_all(MYSQLI_ASSOC);
}
// Altre funzioni di utility se necessarie...
function getTop10byScore() {
global $conn;
$result = $conn->query("SELECT * FROM " . TABLE_NAME . " ORDER BY scoreT DESC LIMIT 10");
return $result->fetch_all(MYSQLI_ASSOC);
}
?>
+15
View File
@@ -0,0 +1,15 @@
<?php
require 'db.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data_partita = $_POST['data_partita'];
$score1 = $_POST['score1'];
$score2 = $_POST['score2'];
$scoreT = $_POST['scoreT'];
$nome = $_POST['nome'];
$stmt = $conn->prepare("INSERT INTO punteggi_test (data_partita, score1, score2, scoreT, nome) VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param("siiis", $data_partita, $score1, $score2, $scoreT, $nome);
$stmt->execute();
$stmt->close();
}
?>
+30
View File
@@ -0,0 +1,30 @@
<?php
// file per testare save_score.php
// richiesta post a save_score.php con i seguenti dati:
// nome: "Simone"
// score1: 120
// score2: 118
// scoreT: 80
$data = [
'nome' => 'TEST',
'score1' => 120,
'score2' => 118,
'scoreT' => 6611,
'data_partita' => date("Y-m-d")
];
// richiesta post a save_score.php con i dati richiesti
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data),
],
];
$context = stream_context_create($options);
$result = file_get_contents('http://localhost:8888/php/save_score.php', false, $context);
if ($result === FALSE) {
echo "Errore nella richiesta";
} else {
echo "Punteggio salvato con successo";
}
?>