Update
This commit is contained in:
parent
08f00ee6d6
commit
05623f58df
601
2048.css
601
2048.css
@ -23,7 +23,6 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ADDED: Animated Grid Pattern (optional, bisa dihapus kalau ga suka) */
|
|
||||||
body::before {
|
body::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -49,7 +48,6 @@ body::before {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ADDED: Floating gradient blobs for depth */
|
|
||||||
body::after {
|
body::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -90,313 +88,6 @@ body::after {
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ======================
|
|
||||||
HEADER - All Centered Vertically
|
|
||||||
====================== */
|
|
||||||
.game-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: clamp(42px, 7vw, 68px);
|
|
||||||
font-weight: 800;
|
|
||||||
text-shadow: 0 0 25px #00eaff, 0 0 45px #0099ff;
|
|
||||||
letter-spacing: clamp(4px, 1vw, 8px);
|
|
||||||
margin: 0 0 clamp(10px, 1.5vh, 16px) 0;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Score Container - Centered below title */
|
|
||||||
.score-container {
|
|
||||||
display: flex;
|
|
||||||
gap: clamp(10px, 2vw, 14px);
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: clamp(12px, 2vh, 16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-box {
|
|
||||||
background: rgba(30, 0, 50, 0.85);
|
|
||||||
backdrop-filter: blur(15px);
|
|
||||||
border: 2px solid rgba(0, 217, 255, 0.45);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: clamp(10px, 1.5vh, 12px) clamp(20px, 3vw, 26px);
|
|
||||||
min-width: clamp(85px, 12vw, 105px);
|
|
||||||
box-shadow:
|
|
||||||
0 5px 20px rgba(0, 0, 0, 0.4),
|
|
||||||
0 0 20px rgba(0, 217, 255, 0.25),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-box:hover {
|
|
||||||
border-color: rgba(0, 217, 255, 0.7);
|
|
||||||
box-shadow:
|
|
||||||
0 5px 20px rgba(0, 0, 0, 0.4),
|
|
||||||
0 0 30px rgba(0, 217, 255, 0.4),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-label {
|
|
||||||
font-size: clamp(10px, 1.3vw, 12px);
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: rgba(0, 234, 255, 0.85);
|
|
||||||
letter-spacing: 1.2px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-value {
|
|
||||||
font-size: clamp(22px, 3vw, 28px);
|
|
||||||
font-weight: 800;
|
|
||||||
color: white;
|
|
||||||
text-shadow: 0 0 12px rgba(0, 234, 255, 0.9);
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ======================
|
|
||||||
TOP CONTROLS - Icon Buttons (Top Right)
|
|
||||||
====================== */
|
|
||||||
.top-controls {
|
|
||||||
position: fixed;
|
|
||||||
top: clamp(10px, 2vh, 20px);
|
|
||||||
right: clamp(10px, 2vw, 20px);
|
|
||||||
display: flex;
|
|
||||||
gap: clamp(8px, 1.5vw, 12px);
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sound-controls {
|
|
||||||
position: fixed;
|
|
||||||
top: clamp(10px, 2vh, 20px);
|
|
||||||
left: clamp(10px, 2vw, 20px);
|
|
||||||
display: flex;
|
|
||||||
gap: clamp(8px, 1.5vw, 12px);
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sound Button Styling */
|
|
||||||
.btn-sound {
|
|
||||||
position: relative;
|
|
||||||
width: clamp(36px, 6vw, 48px);
|
|
||||||
height: clamp(36px, 6vw, 48px);
|
|
||||||
padding: 0;
|
|
||||||
background: rgba(30, 0, 50, 0.85);
|
|
||||||
backdrop-filter: blur(15px);
|
|
||||||
border: 2px solid rgba(0, 217, 255, 0.45);
|
|
||||||
border-radius: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
box-shadow:
|
|
||||||
0 4px 18px rgba(0, 0, 0, 0.35),
|
|
||||||
0 0 20px rgba(0, 217, 255, 0.2),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sound svg {
|
|
||||||
width: clamp(18px, 3vw, 24px);
|
|
||||||
height: clamp(18px, 3vw, 24px);
|
|
||||||
color: rgba(0, 234, 255, 0.9);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BG Music Button - Purple */
|
|
||||||
#btn-sound-bg {
|
|
||||||
background: rgba(50, 0, 70, 0.85);
|
|
||||||
border-color: rgba(200, 100, 255, 0.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
#btn-sound-bg svg {
|
|
||||||
color: rgba(200, 100, 255, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
#btn-sound-bg:hover {
|
|
||||||
background: rgba(200, 100, 255, 0.15);
|
|
||||||
border-color: rgba(200, 100, 255, 0.8);
|
|
||||||
box-shadow:
|
|
||||||
0 6px 25px rgba(0, 0, 0, 0.45),
|
|
||||||
0 0 35px rgba(200, 100, 255, 0.4),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
#btn-sound-bg:hover svg {
|
|
||||||
color: rgba(200, 100, 255, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pop SFX Button - Cyan */
|
|
||||||
#btn-sound-pop {
|
|
||||||
background: rgba(0, 40, 50, 0.85);
|
|
||||||
border-color: rgba(0, 234, 255, 0.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
#btn-sound-pop svg {
|
|
||||||
color: rgba(0, 234, 255, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
#btn-sound-pop:hover {
|
|
||||||
background: rgba(0, 234, 255, 0.15);
|
|
||||||
border-color: rgba(0, 234, 255, 0.8);
|
|
||||||
box-shadow:
|
|
||||||
0 6px 25px rgba(0, 0, 0, 0.45),
|
|
||||||
0 0 35px rgba(0, 234, 255, 0.4),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
#btn-sound-pop:hover svg {
|
|
||||||
color: rgba(0, 234, 255, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Merge SFX Button - Orange/Yellow */
|
|
||||||
#btn-sound-merge {
|
|
||||||
background: rgba(60, 30, 0, 0.85);
|
|
||||||
border-color: rgba(255, 170, 0, 0.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
#btn-sound-merge svg {
|
|
||||||
color: rgba(255, 170, 0, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
#btn-sound-merge:hover {
|
|
||||||
background: rgba(255, 170, 0, 0.15);
|
|
||||||
border-color: rgba(255, 170, 0, 0.8);
|
|
||||||
box-shadow:
|
|
||||||
0 6px 25px rgba(0, 0, 0, 0.45),
|
|
||||||
0 0 35px rgba(255, 170, 0, 0.4),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
#btn-sound-merge:hover svg {
|
|
||||||
color: rgba(255, 170, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hover & Active States */
|
|
||||||
.btn-sound:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sound:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
box-shadow:
|
|
||||||
0 2px 12px rgba(0, 0, 0, 0.35),
|
|
||||||
0 0 20px rgba(0, 234, 255, 0.3),
|
|
||||||
inset 0 2px 6px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Muted State - Red with X */
|
|
||||||
.btn-sound.muted {
|
|
||||||
background: rgba(60, 0, 10, 0.85) !important;
|
|
||||||
border-color: rgba(255, 50, 50, 0.6) !important;
|
|
||||||
box-shadow:
|
|
||||||
0 4px 18px rgba(0, 0, 0, 0.35),
|
|
||||||
0 0 20px rgba(255, 50, 50, 0.3),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.1) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sound.muted svg.sound-icon {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sound.muted svg.mute-icon {
|
|
||||||
display: block !important;
|
|
||||||
color: rgba(255, 80, 80, 0.9) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sound.muted:hover {
|
|
||||||
background: rgba(255, 50, 50, 0.2) !important;
|
|
||||||
border-color: rgba(255, 80, 80, 0.8) !important;
|
|
||||||
box-shadow:
|
|
||||||
0 6px 25px rgba(0, 0, 0, 0.45),
|
|
||||||
0 0 35px rgba(255, 50, 50, 0.5),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.15) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sound.muted:hover svg.mute-icon {
|
|
||||||
color: rgba(255, 100, 100, 1) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Icon Transitions */
|
|
||||||
.btn-sound svg.sound-icon,
|
|
||||||
.btn-sound svg.mute-icon {
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sound:hover svg {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FORCE tombol sound untuk selalu visible */
|
|
||||||
.btn-sound-main {
|
|
||||||
display: flex !important;
|
|
||||||
position: relative !important;
|
|
||||||
z-index: 103 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume-panel {
|
|
||||||
position: relative !important;
|
|
||||||
z-index: 102 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.icon-btn {
|
|
||||||
width: clamp(36px, 6vw, 48px);
|
|
||||||
height: clamp(36px, 6vw, 48px);
|
|
||||||
padding: 0;
|
|
||||||
background: rgba(30, 0, 50, 0.85);
|
|
||||||
backdrop-filter: blur(15px);
|
|
||||||
border: 2px solid rgba(0, 217, 255, 0.45);
|
|
||||||
border-radius: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
box-shadow:
|
|
||||||
0 4px 18px rgba(0, 0, 0, 0.35),
|
|
||||||
0 0 20px rgba(0, 217, 255, 0.2),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-btn svg {
|
|
||||||
width: clamp(18px, 3vw, 24px);
|
|
||||||
height: clamp(18px, 3vw, 24px);
|
|
||||||
color: rgba(0, 234, 255, 0.9);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-btn:hover {
|
|
||||||
background: rgba(0, 234, 255, 0.15);
|
|
||||||
border-color: rgba(0, 234, 255, 0.8);
|
|
||||||
box-shadow:
|
|
||||||
0 6px 25px rgba(0, 0, 0, 0.45),
|
|
||||||
0 0 35px rgba(0, 234, 255, 0.4),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-btn:hover svg {
|
|
||||||
color: rgba(0, 234, 255, 1);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-restart-icon:hover svg {
|
|
||||||
transform: scale(1.1) rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-btn:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
box-shadow:
|
|
||||||
0 2px 12px rgba(0, 0, 0, 0.35),
|
|
||||||
0 0 20px rgba(0, 234, 255, 0.3),
|
|
||||||
inset 0 2px 6px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tutorial Button Specific */
|
/* Tutorial Button Specific */
|
||||||
.btn-tutorial {
|
.btn-tutorial {
|
||||||
background: rgba(50, 0, 70, 0.85);
|
background: rgba(50, 0, 70, 0.85);
|
||||||
@ -642,93 +333,6 @@ h1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================
|
|
||||||
TUTORIAL MODAL
|
|
||||||
========================== */
|
|
||||||
.tutorial-overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.88);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 998;
|
|
||||||
animation: fadeIn 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tutorial-modal {
|
|
||||||
background: rgba(20, 0, 40, 0.96);
|
|
||||||
backdrop-filter: blur(25px);
|
|
||||||
border: 3px solid rgba(200, 100, 255, 0.5);
|
|
||||||
border-radius: 24px;
|
|
||||||
padding: 45px 50px;
|
|
||||||
max-width: 500px;
|
|
||||||
width: 90%;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow:
|
|
||||||
0 35px 90px rgba(0, 0, 0, 0.75),
|
|
||||||
0 0 60px rgba(200, 100, 255, 0.4),
|
|
||||||
inset 0 0 50px rgba(200, 100, 255, 0.1);
|
|
||||||
animation: modalSlideUp 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-close {
|
|
||||||
position: absolute;
|
|
||||||
top: 15px;
|
|
||||||
right: 15px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
padding: 0;
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-close svg {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
color: rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-close:hover {
|
|
||||||
background: rgba(255, 50, 50, 0.2);
|
|
||||||
border-color: rgba(255, 50, 50, 0.6);
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-close:hover svg {
|
|
||||||
color: #ff5050;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tutorial-title {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 800;
|
|
||||||
color: #c864ff;
|
|
||||||
text-shadow: 0 0 25px rgba(200, 100, 255, 0.8);
|
|
||||||
margin-bottom: 30px;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tutorial-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tutorial-section h3 {
|
|
||||||
font-size: 18px;
|
|
||||||
color: rgba(200, 100, 255, 0.9);
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Show/Hide based on device */
|
/* Show/Hide based on device */
|
||||||
.mobile-controls {
|
.mobile-controls {
|
||||||
display: none;
|
display: none;
|
||||||
@ -838,208 +442,3 @@ h1 {
|
|||||||
text-shadow: 0 0 10px rgba(255, 215, 0, 0.6);
|
text-shadow: 0 0 10px rgba(255, 215, 0, 0.6);
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================
|
|
||||||
GAME OVER MODAL - WITH ICON BUTTONS
|
|
||||||
========================== */
|
|
||||||
|
|
||||||
.game-over-overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.93);
|
|
||||||
backdrop-filter: blur(15px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 999;
|
|
||||||
animation: fadeIn 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-over-modal {
|
|
||||||
background: linear-gradient(145deg, rgba(15, 0, 35, 0.98), rgba(25, 0, 45, 0.98));
|
|
||||||
backdrop-filter: blur(30px);
|
|
||||||
border: 3px solid rgba(255, 50, 100, 0.5);
|
|
||||||
border-radius: 32px;
|
|
||||||
padding: 55px 50px 45px;
|
|
||||||
max-width: 460px;
|
|
||||||
width: 90%;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow:
|
|
||||||
0 50px 120px rgba(0, 0, 0, 0.85),
|
|
||||||
0 0 100px rgba(255, 50, 100, 0.4),
|
|
||||||
inset 0 2px 80px rgba(255, 50, 100, 0.06);
|
|
||||||
animation: modalSlideUp 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes modalSlideUp {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(60px) scale(0.85);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0) scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Close Button (X) - Top Right */
|
|
||||||
.game-over-close {
|
|
||||||
position: absolute;
|
|
||||||
top: 18px;
|
|
||||||
right: 18px;
|
|
||||||
width: 38px;
|
|
||||||
height: 38px;
|
|
||||||
padding: 0;
|
|
||||||
background: rgba(255, 255, 255, 0.06);
|
|
||||||
border: 2px solid rgba(255, 255, 255, 0.15);
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-over-close svg {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
color: rgba(255, 255, 255, 0.6);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-over-close:hover {
|
|
||||||
background: rgba(255, 80, 80, 0.2);
|
|
||||||
border-color: rgba(255, 80, 80, 0.6);
|
|
||||||
transform: rotate(90deg) scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-over-close:hover svg {
|
|
||||||
color: #ff6666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-over-close:active {
|
|
||||||
transform: rotate(90deg) scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Title - Gradient Pink/Red */
|
|
||||||
.game-over-title {
|
|
||||||
font-size: clamp(32px, 5vw, 42px);
|
|
||||||
font-weight: 900;
|
|
||||||
background: linear-gradient(135deg, #ff3366 0%, #ff6699 50%, #ff99cc 100%);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
filter: drop-shadow(0 0 35px rgba(255, 51, 102, 0.5));
|
|
||||||
margin-bottom: 12px;
|
|
||||||
letter-spacing: 4px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Subtitle */
|
|
||||||
.game-over-subtitle {
|
|
||||||
font-size: 14px;
|
|
||||||
color: rgba(255, 255, 255, 0.5);
|
|
||||||
letter-spacing: 2px;
|
|
||||||
margin-bottom: 35px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Score Section - PURPLE/VIOLET gradient */
|
|
||||||
.game-over-score {
|
|
||||||
margin: 0 0 38px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-over-score-label {
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: rgba(200, 100, 255, 0.7);
|
|
||||||
letter-spacing: 3px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-over-score-value {
|
|
||||||
font-size: clamp(52px, 8vw, 68px);
|
|
||||||
font-weight: 900;
|
|
||||||
background: linear-gradient(135deg, #c864ff 0%, #8b5cf6 50%, #a855f7 100%);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
filter: drop-shadow(0 0 30px rgba(200, 100, 255, 0.6));
|
|
||||||
margin-bottom: 10px;
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* New High Score Badge - GOLD */
|
|
||||||
.new-high-score {
|
|
||||||
display: inline-block;
|
|
||||||
background: linear-gradient(135deg, rgba(255, 215, 0, 0.2), rgba(255, 165, 0, 0.2));
|
|
||||||
border: 2.5px solid rgba(255, 215, 0, 0.7);
|
|
||||||
color: #ffd700;
|
|
||||||
padding: 12px 26px;
|
|
||||||
border-radius: 50px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 800;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
margin-top: 18px;
|
|
||||||
box-shadow:
|
|
||||||
0 0 35px rgba(255, 215, 0, 0.35),
|
|
||||||
inset 0 0 30px rgba(255, 215, 0, 0.1);
|
|
||||||
animation: newHighScorePulse 2s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes newHighScorePulse {
|
|
||||||
0%, 100% {
|
|
||||||
box-shadow:
|
|
||||||
0 0 30px rgba(255, 215, 0, 0.4),
|
|
||||||
inset 0 0 30px rgba(255, 215, 0, 0.1);
|
|
||||||
border-color: rgba(255, 215, 0, 0.7);
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
box-shadow:
|
|
||||||
0 0 50px rgba(255, 215, 0, 0.7),
|
|
||||||
inset 0 0 40px rgba(255, 215, 0, 0.2);
|
|
||||||
border-color: rgba(255, 215, 0, 1);
|
|
||||||
transform: scale(1.02);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* High Score Display - ORANGE gradient */
|
|
||||||
.high-score-display {
|
|
||||||
margin-top: 28px;
|
|
||||||
padding-top: 28px;
|
|
||||||
border-top: 2px solid rgba(255, 140, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.high-score-label {
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: rgba(255, 160, 50, 0.7);
|
|
||||||
letter-spacing: 2.5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.high-score-value {
|
|
||||||
font-size: clamp(34px, 5vw, 42px);
|
|
||||||
font-weight: 900;
|
|
||||||
background: linear-gradient(135deg, #ff8c00 0%, #ffa500 50%, #ffb347 100%);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
filter: drop-shadow(0 0 20px rgba(255, 140, 0, 0.5));
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
}
|
|
||||||
@ -4,12 +4,14 @@
|
|||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
<title>2048</title>
|
<title>2048</title>
|
||||||
|
<link rel="stylesheet" href="2048.css"/>
|
||||||
|
<link rel="stylesheet" href="2048_Header.css"/>
|
||||||
<link rel="stylesheet" href="2048_Background_Effects.css"/>
|
<link rel="stylesheet" href="2048_Background_Effects.css"/>
|
||||||
<link rel="stylesheet" href="2048_Floating_Particles.css"/>
|
<link rel="stylesheet" href="2048_Floating_Particles.css"/>
|
||||||
<link rel="stylesheet" href="2048.css"/>
|
|
||||||
<link rel="stylesheet" href="2048_Tiles_Colors.css"/>
|
<link rel="stylesheet" href="2048_Tiles_Colors.css"/>
|
||||||
<link rel="stylesheet" href="2048_Sound.css"/>
|
|
||||||
<link rel="stylesheet" href="2048_Button.css"/>
|
<link rel="stylesheet" href="2048_Button.css"/>
|
||||||
|
<link rel="stylesheet" href="2048_Sound.css"/>
|
||||||
|
<link rel="stylesheet" href="2048_Modal.css"/>
|
||||||
<link rel="stylesheet" href="2048_Responsive.css"/>
|
<link rel="stylesheet" href="2048_Responsive.css"/>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
/* ==========================
|
|
||||||
BACKGROUND EFFECTS
|
|
||||||
========================== */
|
|
||||||
.particles {
|
.particles {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@ -68,8 +65,7 @@
|
|||||||
will-change: transform, opacity;
|
will-change: transform, opacity;
|
||||||
filter: blur(1px) drop-shadow(0 0 8px rgba(255,255,255,0.1));
|
filter: blur(1px) drop-shadow(0 0 8px rgba(255,255,255,0.1));
|
||||||
}
|
}
|
||||||
/* ENHANCED BACKGROUND EFFECTS */
|
|
||||||
/* Update .particles styling */
|
|
||||||
.particles {
|
.particles {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@ -100,7 +96,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enhanced Starfield */
|
|
||||||
.starfield {
|
.starfield {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@ -131,7 +126,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enhanced Cursor Light */
|
|
||||||
.cursor-light {
|
.cursor-light {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 280px;
|
width: 280px;
|
||||||
|
|||||||
71
2048_Header.css
Normal file
71
2048_Header.css
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
.game-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(42px, 7vw, 68px);
|
||||||
|
font-weight: 800;
|
||||||
|
text-shadow: 0 0 25px #00eaff, 0 0 45px #0099ff;
|
||||||
|
letter-spacing: clamp(4px, 1vw, 8px);
|
||||||
|
margin: 0 0 clamp(10px, 1.5vh, 16px) 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-container {
|
||||||
|
display: flex;
|
||||||
|
gap: clamp(10px, 2vw, 14px);
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: clamp(12px, 2vh, 16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-box {
|
||||||
|
background: rgba(30, 0, 50, 0.85);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
border: 2px solid rgba(0, 217, 255, 0.45);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: clamp(10px, 1.5vh, 12px) clamp(20px, 3vw, 26px);
|
||||||
|
min-width: clamp(85px, 12vw, 105px);
|
||||||
|
box-shadow:
|
||||||
|
0 5px 20px rgba(0, 0, 0, 0.4),
|
||||||
|
0 0 20px rgba(0, 217, 255, 0.25),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-box:hover {
|
||||||
|
border-color: rgba(0, 217, 255, 0.7);
|
||||||
|
box-shadow:
|
||||||
|
0 5px 20px rgba(0, 0, 0, 0.4),
|
||||||
|
0 0 30px rgba(0, 217, 255, 0.4),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-label {
|
||||||
|
font-size: clamp(10px, 1.3vw, 12px);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: rgba(0, 234, 255, 0.85);
|
||||||
|
letter-spacing: 1.2px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-value {
|
||||||
|
font-size: clamp(22px, 3vw, 28px);
|
||||||
|
font-weight: 800;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 0 12px rgba(0, 234, 255, 0.9);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-controls {
|
||||||
|
position: fixed;
|
||||||
|
top: clamp(10px, 2vh, 20px);
|
||||||
|
right: clamp(10px, 2vw, 20px);
|
||||||
|
display: flex;
|
||||||
|
gap: clamp(8px, 1.5vw, 12px);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
288
2048_Modal.css
Normal file
288
2048_Modal.css
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
.tutorial-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.88);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 998;
|
||||||
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tutorial-modal {
|
||||||
|
background: rgba(20, 0, 40, 0.96);
|
||||||
|
backdrop-filter: blur(25px);
|
||||||
|
border: 3px solid rgba(200, 100, 255, 0.5);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 45px 50px;
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow:
|
||||||
|
0 35px 90px rgba(0, 0, 0, 0.75),
|
||||||
|
0 0 60px rgba(200, 100, 255, 0.4),
|
||||||
|
inset 0 0 50px rgba(200, 100, 255, 0.1);
|
||||||
|
animation: modalSlideUp 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover {
|
||||||
|
background: rgba(255, 50, 50, 0.2);
|
||||||
|
border-color: rgba(255, 50, 50, 0.6);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover svg {
|
||||||
|
color: #ff5050;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tutorial-title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #c864ff;
|
||||||
|
text-shadow: 0 0 25px rgba(200, 100, 255, 0.8);
|
||||||
|
margin-bottom: 30px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tutorial-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tutorial-section h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
color: rgba(200, 100, 255, 0.9);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================
|
||||||
|
GAME OVER MODAL - WITH ICON BUTTONS
|
||||||
|
========================== */
|
||||||
|
|
||||||
|
.game-over-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.93);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 999;
|
||||||
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-modal {
|
||||||
|
background: linear-gradient(145deg, rgba(15, 0, 35, 0.98), rgba(25, 0, 45, 0.98));
|
||||||
|
backdrop-filter: blur(30px);
|
||||||
|
border: 3px solid rgba(255, 50, 100, 0.5);
|
||||||
|
border-radius: 32px;
|
||||||
|
padding: 55px 50px 45px;
|
||||||
|
max-width: 460px;
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow:
|
||||||
|
0 50px 120px rgba(0, 0, 0, 0.85),
|
||||||
|
0 0 100px rgba(255, 50, 100, 0.4),
|
||||||
|
inset 0 2px 80px rgba(255, 50, 100, 0.06);
|
||||||
|
animation: modalSlideUp 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes modalSlideUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(60px) scale(0.85);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close Button (X) - Top Right */
|
||||||
|
.game-over-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
right: 18px;
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
padding: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-close svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-close:hover {
|
||||||
|
background: rgba(255, 80, 80, 0.2);
|
||||||
|
border-color: rgba(255, 80, 80, 0.6);
|
||||||
|
transform: rotate(90deg) scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-close:hover svg {
|
||||||
|
color: #ff6666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-close:active {
|
||||||
|
transform: rotate(90deg) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title - Gradient Pink/Red */
|
||||||
|
.game-over-title {
|
||||||
|
font-size: clamp(32px, 5vw, 42px);
|
||||||
|
font-weight: 900;
|
||||||
|
background: linear-gradient(135deg, #ff3366 0%, #ff6699 50%, #ff99cc 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
filter: drop-shadow(0 0 35px rgba(255, 51, 102, 0.5));
|
||||||
|
margin-bottom: 12px;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subtitle */
|
||||||
|
.game-over-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
letter-spacing: 2px;
|
||||||
|
margin-bottom: 35px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Score Section - PURPLE/VIOLET gradient */
|
||||||
|
.game-over-score {
|
||||||
|
margin: 0 0 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-score-label {
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: rgba(200, 100, 255, 0.7);
|
||||||
|
letter-spacing: 3px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-score-value {
|
||||||
|
font-size: clamp(52px, 8vw, 68px);
|
||||||
|
font-weight: 900;
|
||||||
|
background: linear-gradient(135deg, #c864ff 0%, #8b5cf6 50%, #a855f7 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
filter: drop-shadow(0 0 30px rgba(200, 100, 255, 0.6));
|
||||||
|
margin-bottom: 10px;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* New High Score Badge - GOLD */
|
||||||
|
.new-high-score {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(135deg, rgba(255, 215, 0, 0.2), rgba(255, 165, 0, 0.2));
|
||||||
|
border: 2.5px solid rgba(255, 215, 0, 0.7);
|
||||||
|
color: #ffd700;
|
||||||
|
padding: 12px 26px;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
margin-top: 18px;
|
||||||
|
box-shadow:
|
||||||
|
0 0 35px rgba(255, 215, 0, 0.35),
|
||||||
|
inset 0 0 30px rgba(255, 215, 0, 0.1);
|
||||||
|
animation: newHighScorePulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes newHighScorePulse {
|
||||||
|
0%, 100% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 30px rgba(255, 215, 0, 0.4),
|
||||||
|
inset 0 0 30px rgba(255, 215, 0, 0.1);
|
||||||
|
border-color: rgba(255, 215, 0, 0.7);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 50px rgba(255, 215, 0, 0.7),
|
||||||
|
inset 0 0 40px rgba(255, 215, 0, 0.2);
|
||||||
|
border-color: rgba(255, 215, 0, 1);
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* High Score Display - ORANGE gradient */
|
||||||
|
.high-score-display {
|
||||||
|
margin-top: 28px;
|
||||||
|
padding-top: 28px;
|
||||||
|
border-top: 2px solid rgba(255, 140, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.high-score-label {
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: rgba(255, 160, 50, 0.7);
|
||||||
|
letter-spacing: 2.5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.high-score-value {
|
||||||
|
font-size: clamp(34px, 5vw, 42px);
|
||||||
|
font-weight: 900;
|
||||||
|
background: linear-gradient(135deg, #ff8c00 0%, #ffa500 50%, #ffb347 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
filter: drop-shadow(0 0 20px rgba(255, 140, 0, 0.5));
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
}
|
||||||
231
2048_Sound.css
231
2048_Sound.css
@ -1,6 +1,231 @@
|
|||||||
/* ==========================
|
.sound-controls {
|
||||||
ADVANCED SOUND CONTROL
|
position: fixed;
|
||||||
========================== */
|
top: clamp(10px, 2vh, 20px);
|
||||||
|
left: clamp(10px, 2vw, 20px);
|
||||||
|
display: flex;
|
||||||
|
gap: clamp(8px, 1.5vw, 12px);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sound Button Styling */
|
||||||
|
.btn-sound {
|
||||||
|
position: relative;
|
||||||
|
width: clamp(36px, 6vw, 48px);
|
||||||
|
height: clamp(36px, 6vw, 48px);
|
||||||
|
padding: 0;
|
||||||
|
background: rgba(30, 0, 50, 0.85);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
border: 2px solid rgba(0, 217, 255, 0.45);
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 18px rgba(0, 0, 0, 0.35),
|
||||||
|
0 0 20px rgba(0, 217, 255, 0.2),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound svg {
|
||||||
|
width: clamp(18px, 3vw, 24px);
|
||||||
|
height: clamp(18px, 3vw, 24px);
|
||||||
|
color: rgba(0, 234, 255, 0.9);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BG Music Button - Purple */
|
||||||
|
#btn-sound-bg {
|
||||||
|
background: rgba(50, 0, 70, 0.85);
|
||||||
|
border-color: rgba(200, 100, 255, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-bg svg {
|
||||||
|
color: rgba(200, 100, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-bg:hover {
|
||||||
|
background: rgba(200, 100, 255, 0.15);
|
||||||
|
border-color: rgba(200, 100, 255, 0.8);
|
||||||
|
box-shadow:
|
||||||
|
0 6px 25px rgba(0, 0, 0, 0.45),
|
||||||
|
0 0 35px rgba(200, 100, 255, 0.4),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-bg:hover svg {
|
||||||
|
color: rgba(200, 100, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pop SFX Button - Cyan */
|
||||||
|
#btn-sound-pop {
|
||||||
|
background: rgba(0, 40, 50, 0.85);
|
||||||
|
border-color: rgba(0, 234, 255, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-pop svg {
|
||||||
|
color: rgba(0, 234, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-pop:hover {
|
||||||
|
background: rgba(0, 234, 255, 0.15);
|
||||||
|
border-color: rgba(0, 234, 255, 0.8);
|
||||||
|
box-shadow:
|
||||||
|
0 6px 25px rgba(0, 0, 0, 0.45),
|
||||||
|
0 0 35px rgba(0, 234, 255, 0.4),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-pop:hover svg {
|
||||||
|
color: rgba(0, 234, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Merge SFX Button - Orange/Yellow */
|
||||||
|
#btn-sound-merge {
|
||||||
|
background: rgba(60, 30, 0, 0.85);
|
||||||
|
border-color: rgba(255, 170, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-merge svg {
|
||||||
|
color: rgba(255, 170, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-merge:hover {
|
||||||
|
background: rgba(255, 170, 0, 0.15);
|
||||||
|
border-color: rgba(255, 170, 0, 0.8);
|
||||||
|
box-shadow:
|
||||||
|
0 6px 25px rgba(0, 0, 0, 0.45),
|
||||||
|
0 0 35px rgba(255, 170, 0, 0.4),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-merge:hover svg {
|
||||||
|
color: rgba(255, 170, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover & Active States */
|
||||||
|
.btn-sound:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow:
|
||||||
|
0 2px 12px rgba(0, 0, 0, 0.35),
|
||||||
|
0 0 20px rgba(0, 234, 255, 0.3),
|
||||||
|
inset 0 2px 6px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Muted State - Red with X */
|
||||||
|
.btn-sound.muted {
|
||||||
|
background: rgba(60, 0, 10, 0.85) !important;
|
||||||
|
border-color: rgba(255, 50, 50, 0.6) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 18px rgba(0, 0, 0, 0.35),
|
||||||
|
0 0 20px rgba(255, 50, 50, 0.3),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound.muted svg.sound-icon {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound.muted svg.mute-icon {
|
||||||
|
display: block !important;
|
||||||
|
color: rgba(255, 80, 80, 0.9) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound.muted:hover {
|
||||||
|
background: rgba(255, 50, 50, 0.2) !important;
|
||||||
|
border-color: rgba(255, 80, 80, 0.8) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 6px 25px rgba(0, 0, 0, 0.45),
|
||||||
|
0 0 35px rgba(255, 50, 50, 0.5),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound.muted:hover svg.mute-icon {
|
||||||
|
color: rgba(255, 100, 100, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon Transitions */
|
||||||
|
.btn-sound svg.sound-icon,
|
||||||
|
.btn-sound svg.mute-icon {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound:hover svg {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FORCE tombol sound untuk selalu visible */
|
||||||
|
.btn-sound-main {
|
||||||
|
display: flex !important;
|
||||||
|
position: relative !important;
|
||||||
|
z-index: 103 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-panel {
|
||||||
|
position: relative !important;
|
||||||
|
z-index: 102 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.icon-btn {
|
||||||
|
width: clamp(36px, 6vw, 48px);
|
||||||
|
height: clamp(36px, 6vw, 48px);
|
||||||
|
padding: 0;
|
||||||
|
background: rgba(30, 0, 50, 0.85);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
border: 2px solid rgba(0, 217, 255, 0.45);
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 18px rgba(0, 0, 0, 0.35),
|
||||||
|
0 0 20px rgba(0, 217, 255, 0.2),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn svg {
|
||||||
|
width: clamp(18px, 3vw, 24px);
|
||||||
|
height: clamp(18px, 3vw, 24px);
|
||||||
|
color: rgba(0, 234, 255, 0.9);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn:hover {
|
||||||
|
background: rgba(0, 234, 255, 0.15);
|
||||||
|
border-color: rgba(0, 234, 255, 0.8);
|
||||||
|
box-shadow:
|
||||||
|
0 6px 25px rgba(0, 0, 0, 0.45),
|
||||||
|
0 0 35px rgba(0, 234, 255, 0.4),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn:hover svg {
|
||||||
|
color: rgba(0, 234, 255, 1);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-restart-icon:hover svg {
|
||||||
|
transform: scale(1.1) rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow:
|
||||||
|
0 2px 12px rgba(0, 0, 0, 0.35),
|
||||||
|
0 0 20px rgba(0, 234, 255, 0.3),
|
||||||
|
inset 0 2px 6px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
.sound-control-container {
|
.sound-control-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: clamp(10px, 2vh, 20px);
|
top: clamp(10px, 2vh, 20px);
|
||||||
|
|||||||
@ -1,44 +1,40 @@
|
|||||||
function checkAndShowTutorial() {
|
function checkAndShowTutorial() {
|
||||||
// Ambil user terbaru saat fungsi dijalankan
|
// Ambil user yang sedang login (atau guest)
|
||||||
const currentUser = sessionStorage.getItem("loggedInUser") || "guest";
|
const currentUser = sessionStorage.getItem("loggedInUser") || "guest";
|
||||||
const tutorialKey = 'tutorialSeen_' + currentUser;
|
const tutorialKey = 'tutorialSeen_' + currentUser;
|
||||||
|
|
||||||
// Cek di Console browser (tekan F12 -> Console)
|
|
||||||
console.log(`[Tutorial Check] User: ${currentUser}`);
|
console.log(`[Tutorial Check] User: ${currentUser}`);
|
||||||
console.log(`[Tutorial Check] Key: ${tutorialKey}`);
|
console.log(`[Tutorial Check] Key: ${tutorialKey}`);
|
||||||
|
|
||||||
// Cek status di LocalStorage
|
// Cek apakah tutorial sudah pernah dilihat
|
||||||
const hasSeenTutorial = localStorage.getItem(tutorialKey);
|
const hasSeenTutorial = localStorage.getItem(tutorialKey);
|
||||||
console.log(`[Tutorial Check] Status Seen: ${hasSeenTutorial}`);
|
console.log(`[Tutorial Check] Status Seen: ${hasSeenTutorial}`);
|
||||||
|
|
||||||
const tutorialOverlay = document.getElementById('tutorial-overlay');
|
const tutorialOverlay = document.getElementById('tutorial-overlay');
|
||||||
|
|
||||||
// Logic: Jika belum pernah lihat (null) -> Tampilkan
|
// Tampilkan tutorial jika belum pernah dilihat
|
||||||
if (!hasSeenTutorial && tutorialOverlay) {
|
if (!hasSeenTutorial && tutorialOverlay) {
|
||||||
console.log("-> Menampilkan Tutorial untuk user baru.");
|
|
||||||
tutorialOverlay.style.display = 'flex';
|
tutorialOverlay.style.display = 'flex';
|
||||||
} else {
|
|
||||||
console.log("-> User ini sudah pernah lihat tutorial (atau overlay tidak ketemu).");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jalankan otomatis saat halaman selesai loading
|
// Jalankan saat halaman selesai dimuat
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
checkAndShowTutorial();
|
checkAndShowTutorial(); // Cek & tampilkan tutorial
|
||||||
|
|
||||||
// Setup tombol close hanya sekali
|
|
||||||
const closeTutorialBtn = document.getElementById('close-tutorial');
|
const closeTutorialBtn = document.getElementById('close-tutorial');
|
||||||
const tutorialOverlay = document.getElementById('tutorial-overlay');
|
const tutorialOverlay = document.getElementById('tutorial-overlay');
|
||||||
|
|
||||||
|
// Event klik tombol tutup tutorial
|
||||||
if (closeTutorialBtn) {
|
if (closeTutorialBtn) {
|
||||||
closeTutorialBtn.addEventListener('click', () => {
|
closeTutorialBtn.addEventListener('click', () => {
|
||||||
// Ambil user SAAT INI (penting jika user berubah tanpa reload)
|
// Ambil user aktif saat ini
|
||||||
const currentUser = sessionStorage.getItem("loggedInUser") || "guest";
|
const currentUser = sessionStorage.getItem("loggedInUser") || "guest";
|
||||||
const tutorialKey = 'tutorialSeen_' + currentUser;
|
const tutorialKey = 'tutorialSeen_' + currentUser;
|
||||||
|
|
||||||
if(tutorialOverlay) tutorialOverlay.style.display = 'none';
|
// Sembunyikan tutorial & simpan status
|
||||||
|
if (tutorialOverlay) tutorialOverlay.style.display = 'none';
|
||||||
localStorage.setItem(tutorialKey, 'true');
|
localStorage.setItem(tutorialKey, 'true');
|
||||||
console.log(`[Tutorial Check] Disimpan: ${tutorialKey} = true`);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
// Mengizinkan akses dari semua domain
|
||||||
header('Access-Control-Allow-Origin: *');
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
// Menentukan metode HTTP yang diizinkan (POST, GET, OPTIONS)
|
||||||
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
||||||
|
// Menentukan header yang diizinkan dalam request
|
||||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||||
|
// Mengatur tipe konten output menjadi format JSON
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
$DB_HOST = "202.46.28.160";
|
$DB_HOST = "202.46.28.160";
|
||||||
@ -10,6 +14,7 @@ $DB_USER = "evelyn";
|
|||||||
$DB_PASS = "evelynsc25";
|
$DB_PASS = "evelynsc25";
|
||||||
$DB_NAME = "web";
|
$DB_NAME = "web";
|
||||||
|
|
||||||
|
// Memeriksa apakah ada error saat menghubungkan ke database
|
||||||
$conn = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_PORT);
|
$conn = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_PORT);
|
||||||
if ($conn->connect_error) {
|
if ($conn->connect_error) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
<title>Homepage</title>
|
<title>Homepage</title>
|
||||||
<link rel="stylesheet" href="Homepage.css">
|
<link rel="stylesheet" href="Homepage.css">
|
||||||
<link rel="stylesheet" href="Homepage_Credit.css">
|
<link rel="stylesheet" href="Homepage_Credit.css">
|
||||||
|
<link rel="stylesheet" href="Homepage_Scrollbar.css">
|
||||||
<link rel="stylesheet" href="Homepage_Responsive.css">
|
<link rel="stylesheet" href="Homepage_Responsive.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -75,4 +75,26 @@
|
|||||||
.btn-logout-confirm {
|
.btn-logout-confirm {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- GANTI BAGIAN SCROLLBAR DENGAN INI --- */
|
||||||
|
|
||||||
|
/* Tunjuk langsung HTML & Body biar browser gak bingung */
|
||||||
|
html::-webkit-scrollbar,
|
||||||
|
body::-webkit-scrollbar {
|
||||||
|
width: 6px !important; /* Pakai !important biar maksa */
|
||||||
|
background: #0b0b15;
|
||||||
|
}
|
||||||
|
|
||||||
|
html::-webkit-scrollbar-track,
|
||||||
|
body::-webkit-scrollbar-track {
|
||||||
|
background: #0b0b15;
|
||||||
|
}
|
||||||
|
|
||||||
|
html::-webkit-scrollbar-thumb,
|
||||||
|
body::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #bc13fe !important; /* Warna Magenta */
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 0 8px #bc13fe !important; /* Glow Neon */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
29
Homepage_Scrollbar.css
Normal file
29
Homepage_Scrollbar.css
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/* Atur lebar scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Background jalanan scrollbar (Track) - Gelap agar neon menyala */
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #0b0b15; /* Hitam keunguan sangat gelap */
|
||||||
|
border-left: 1px solid #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Batang Scroll (Thumb) - Efek Neon */
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
/* Gradasi dari Cyan ke Ungu */
|
||||||
|
background: linear-gradient(180deg, #00f2ff, #bc13fe);
|
||||||
|
border-radius: 10px;
|
||||||
|
/* Memberikan jarak sedikit agar terlihat melayang */
|
||||||
|
border: 3px solid transparent;
|
||||||
|
background-clip: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Efek saat di-hover (disorot mouse) - Lebih terang */
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: linear-gradient(180deg, #5dfaff, #d65eff);
|
||||||
|
border: 3px solid transparent;
|
||||||
|
background-clip: content-box;
|
||||||
|
/* Sedikit shadow untuk efek glow */
|
||||||
|
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
@ -1,20 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json'); // Response berupa JSON
|
||||||
require 'Connection.php';
|
require 'Connection.php'; // Koneksi database
|
||||||
session_start();
|
session_start(); // Ambil session user login
|
||||||
|
|
||||||
|
// Response default
|
||||||
$response = [
|
$response = [
|
||||||
"status" => "error",
|
"status" => "error",
|
||||||
"leaderboard" => [],
|
"leaderboard" => [],
|
||||||
"user_rank" => null
|
"user_rank" => null
|
||||||
];
|
];
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// Ambil Top 10 Leaderboard Global
|
||||||
// 1. Ambil Top 10 Global (DIPERBAIKI)
|
// Urut score terbesar
|
||||||
// Ditambahkan "user_id ASC" agar urutannya PASTI (Konsisten)
|
$query = "
|
||||||
// Jika skor sama, user dengan ID lebih kecil (daftar duluan) akan di atas
|
SELECT username, score
|
||||||
// ---------------------------------------------------------
|
FROM leaderboard
|
||||||
$query = "SELECT username, score FROM leaderboard ORDER BY score DESC, user_id ASC LIMIT 10";
|
ORDER BY score DESC, user_id ASC
|
||||||
|
LIMIT 10
|
||||||
|
";
|
||||||
$result = $conn->query($query);
|
$result = $conn->query($query);
|
||||||
|
|
||||||
if ($result && $result->num_rows > 0) {
|
if ($result && $result->num_rows > 0) {
|
||||||
@ -23,14 +26,16 @@ if ($result && $result->num_rows > 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// Ambil Ranking User yang Sedang Login (your rank)
|
||||||
// 2. Ambil Ranking User Login (DIPERBAIKI)
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if (isset($_SESSION['user_id'])) {
|
if (isset($_SESSION['user_id'])) {
|
||||||
$my_id = $_SESSION['user_id'];
|
$my_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
// Ambil score user saat ini
|
// Ambil username & score user
|
||||||
$scoreQuery = $conn->prepare("SELECT username, score FROM leaderboard WHERE user_id = ?");
|
$scoreQuery = $conn->prepare("
|
||||||
|
SELECT username, score
|
||||||
|
FROM leaderboard
|
||||||
|
WHERE user_id = ?
|
||||||
|
");
|
||||||
$scoreQuery->bind_param("i", $my_id);
|
$scoreQuery->bind_param("i", $my_id);
|
||||||
$scoreQuery->execute();
|
$scoreQuery->execute();
|
||||||
$scoreResult = $scoreQuery->get_result();
|
$scoreResult = $scoreQuery->get_result();
|
||||||
@ -39,38 +44,33 @@ if (isset($_SESSION['user_id'])) {
|
|||||||
$myScore = $scoreRow['score'];
|
$myScore = $scoreRow['score'];
|
||||||
$myUsername = $scoreRow['username'];
|
$myUsername = $scoreRow['username'];
|
||||||
|
|
||||||
// --- LOGIKA BARU ---
|
// Hitung jumlah user yang berada di atas
|
||||||
// Hitung ranking dengan Tie-Breaker.
|
|
||||||
// Rank = Jumlah orang yang skornya LEBIH BESAR
|
|
||||||
// DITAMBAH Jumlah orang yang skornya SAMA tapi ID-nya LEBIH KECIL (dia di atas kita)
|
|
||||||
|
|
||||||
$rankQuery = $conn->prepare("
|
$rankQuery = $conn->prepare("
|
||||||
SELECT COUNT(*) as rank_above
|
SELECT COUNT(*) AS rank_above
|
||||||
FROM leaderboard
|
FROM leaderboard
|
||||||
WHERE score > ?
|
WHERE score > ?
|
||||||
OR (score = ? AND user_id < ?)
|
OR (score = ? AND user_id < ?)
|
||||||
");
|
");
|
||||||
|
|
||||||
// Kita bind 3 parameter: score, score, id
|
|
||||||
$rankQuery->bind_param("iii", $myScore, $myScore, $my_id);
|
$rankQuery->bind_param("iii", $myScore, $myScore, $my_id);
|
||||||
|
|
||||||
$rankQuery->execute();
|
$rankQuery->execute();
|
||||||
$rankResult = $rankQuery->get_result();
|
$rankResult = $rankQuery->get_result();
|
||||||
$rankRow = $rankResult->fetch_assoc();
|
$rankRow = $rankResult->fetch_assoc();
|
||||||
|
|
||||||
// Rank adalah jumlah orang di atas kita + 1
|
// Rank = jumlah di atas + 1
|
||||||
$myRank = $rankRow['rank_above'] + 1;
|
|
||||||
|
|
||||||
$response['user_rank'] = [
|
$response['user_rank'] = [
|
||||||
"username" => $myUsername,
|
"username" => $myUsername,
|
||||||
"score" => $myScore,
|
"score" => $myScore,
|
||||||
"rank" => $myRank
|
"rank" => $rankRow['rank_above'] + 1
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Status sukses
|
||||||
$response['status'] = "success";
|
$response['status'] = "success";
|
||||||
|
|
||||||
|
// Kirim JSON ke client
|
||||||
echo json_encode($response);
|
echo json_encode($response);
|
||||||
|
|
||||||
|
// Tutup koneksi database
|
||||||
$conn->close();
|
$conn->close();
|
||||||
?>
|
?>
|
||||||
49
Login.php
49
Login.php
@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
// ... (Header CORS tetap sama) ...
|
|
||||||
header('Access-Control-Allow-Origin: *');
|
header('Access-Control-Allow-Origin: *');
|
||||||
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
||||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||||
@ -11,47 +10,61 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
session_start();
|
session_start(); // Mulai session (login state)
|
||||||
include 'Connection.php';
|
include 'Connection.php'; // Koneksi database
|
||||||
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
// Ambil Data Login dari Client
|
||||||
$username = $input['username'] ?? '';
|
$input = json_decode(file_get_contents('php://input'), true); // Ambil body JSON
|
||||||
$password = $input['password'] ?? '';
|
$username = $input['username'] ?? ''; // Username dari client
|
||||||
|
$password = $input['password'] ?? ''; // Password dari client
|
||||||
|
|
||||||
// ... (Validasi input kosong tetap sama) ...
|
// Cek Username di Database
|
||||||
|
$stmt = $conn->prepare(
|
||||||
// 🔴 PERBAIKAN 1: Tambahkan 'id' di dalam SELECT
|
"SELECT id, password FROM users WHERE username = ?"
|
||||||
$stmt = $conn->prepare("SELECT id, password FROM users WHERE username = ?");
|
);
|
||||||
$stmt->bind_param("s", $username);
|
$stmt->bind_param("s", $username);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->store_result();
|
$stmt->store_result();
|
||||||
|
|
||||||
|
// Jika Username Tidak Ada
|
||||||
if ($stmt->num_rows === 0) {
|
if ($stmt->num_rows === 0) {
|
||||||
echo json_encode(["success" => false, "message" => "Username Not Found"]);
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Username Not Found"
|
||||||
|
]);
|
||||||
$stmt->close();
|
$stmt->close();
|
||||||
$conn->close();
|
$conn->close();
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔴 PERBAIKAN 2: Bind result untuk menangkap 'id' dan 'password'
|
// Ambil Data User
|
||||||
$stmt->bind_result($userId, $hashedPassword);
|
$stmt->bind_result($userId, $hashedPassword); // Ambil id & password hash
|
||||||
$stmt->fetch();
|
$stmt->fetch();
|
||||||
|
|
||||||
|
// Cek Password
|
||||||
if (password_verify($password, $hashedPassword)) {
|
if (password_verify($password, $hashedPassword)) {
|
||||||
// 🔴 PERBAIKAN 3: Simpan 'user_id' ke dalam SESSION
|
|
||||||
|
// Simpan data login ke session
|
||||||
$_SESSION['user_id'] = $userId;
|
$_SESSION['user_id'] = $userId;
|
||||||
$_SESSION['username'] = $username;
|
$_SESSION['username'] = $username;
|
||||||
|
|
||||||
|
// Kirim respon login sukses
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"success" => true,
|
"success" => true,
|
||||||
"message" => "Login successful",
|
"message" => "Login successful",
|
||||||
"username" => $username,
|
"username" => $username,
|
||||||
"token" => bin2hex(random_bytes(32))
|
"token" => bin2hex(random_bytes(32)) // Token acak (bukan JWT)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
echo json_encode(["success" => false, "message" => "Incorrect password"]);
|
|
||||||
|
// Password salah
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Incorrect password"
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt->close();
|
$stmt->close(); // Tutup statement
|
||||||
$conn->close();
|
$conn->close(); // Tutup koneksi DB
|
||||||
?>
|
?>
|
||||||
44
Logout.js
44
Logout.js
@ -7,16 +7,13 @@
|
|||||||
const loginBtn = document.querySelector('.btn-login');
|
const loginBtn = document.querySelector('.btn-login');
|
||||||
|
|
||||||
if (loggedInUser && loginBtn) {
|
if (loggedInUser && loginBtn) {
|
||||||
// User is logged in - change to Logout button
|
// Mengubah button login menjadi logout
|
||||||
loginBtn.textContent = 'Logout';
|
loginBtn.textContent = 'Logout';
|
||||||
loginBtn.classList.remove('btn-login');
|
loginBtn.classList.remove('btn-login');
|
||||||
loginBtn.classList.add('btn-logout');
|
loginBtn.classList.add('btn-logout');
|
||||||
loginBtn.href = '#'; // Prevent navigation
|
loginBtn.href = '#';
|
||||||
|
|
||||||
// Remove existing click listeners (untuk menghindari duplikat)
|
|
||||||
loginBtn.replaceWith(loginBtn.cloneNode(true));
|
loginBtn.replaceWith(loginBtn.cloneNode(true));
|
||||||
|
|
||||||
// Get new reference dan add logout handler
|
|
||||||
const newLogoutBtn = document.querySelector('.btn-logout');
|
const newLogoutBtn = document.querySelector('.btn-logout');
|
||||||
if (newLogoutBtn) {
|
if (newLogoutBtn) {
|
||||||
newLogoutBtn.addEventListener('click', handleLogoutClick);
|
newLogoutBtn.addEventListener('click', handleLogoutClick);
|
||||||
@ -28,31 +25,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== LOGOUT CLICK HANDLER ====================
|
// LOGOUT CLICK HANDLER
|
||||||
|
// Mencegah halaman refresh secara otomatis dan langsung menampilkan jendela pop-up konfirmasi logout
|
||||||
function handleLogoutClick(e) {
|
function handleLogoutClick(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showLogoutModal();
|
showLogoutModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== SHOW LOGOUT CONFIRMATION MODAL ====================
|
// SHOW LOGOUT CONFIRMATION MODAL
|
||||||
function showLogoutModal() {
|
function showLogoutModal() {
|
||||||
const overlay = document.getElementById('logout-overlay');
|
const overlay = document.getElementById('logout-overlay');
|
||||||
if (overlay) {
|
if (overlay) {
|
||||||
overlay.style.display = 'flex';
|
overlay.style.display = 'flex';
|
||||||
setTimeout(() => overlay.classList.add('active'), 10);
|
setTimeout(() => overlay.classList.add('active'), 10);
|
||||||
|
|
||||||
// Setup modal buttons
|
|
||||||
setupModalButtons();
|
setupModalButtons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== SETUP MODAL BUTTONS ====================
|
// SETUP MODAL BUTTONS
|
||||||
function setupModalButtons() {
|
function setupModalButtons() {
|
||||||
const cancelBtn = document.getElementById('btn-logout-cancel');
|
const cancelBtn = document.getElementById('btn-logout-cancel');
|
||||||
const confirmBtn = document.getElementById('btn-logout-confirm');
|
const confirmBtn = document.getElementById('btn-logout-confirm');
|
||||||
const overlay = document.getElementById('logout-overlay');
|
const overlay = document.getElementById('logout-overlay');
|
||||||
|
|
||||||
// Remove previous listeners
|
// Remove previous listeners (mencegah error double click)
|
||||||
if (cancelBtn) {
|
if (cancelBtn) {
|
||||||
const newCancelBtn = cancelBtn.cloneNode(true);
|
const newCancelBtn = cancelBtn.cloneNode(true);
|
||||||
cancelBtn.replaceWith(newCancelBtn);
|
cancelBtn.replaceWith(newCancelBtn);
|
||||||
@ -65,7 +61,7 @@
|
|||||||
newConfirmBtn.onclick = confirmLogout;
|
newConfirmBtn.onclick = confirmLogout;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close on overlay click
|
// Close on overlay click (area gelap)
|
||||||
if (overlay) {
|
if (overlay) {
|
||||||
overlay.onclick = function(e) {
|
overlay.onclick = function(e) {
|
||||||
if (e.target === overlay) {
|
if (e.target === overlay) {
|
||||||
@ -74,11 +70,12 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ESC key to close (gunakan named function agar bisa di-remove)
|
// Aktifkan tombol escape untuk menutup modal
|
||||||
document.addEventListener('keydown', handleEscapeKey);
|
document.addEventListener('keydown', handleEscapeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== HANDLE ESCAPE KEY ====================
|
// HANDLE ESCAPE KEY
|
||||||
|
// Menutup jendela pop-up (modal) logout menggunakan tombol keyboard Esc
|
||||||
function handleEscapeKey(e) {
|
function handleEscapeKey(e) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
const overlay = document.getElementById('logout-overlay');
|
const overlay = document.getElementById('logout-overlay');
|
||||||
@ -88,7 +85,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== CLOSE LOGOUT MODAL ====================
|
// CLOSE LOGOUT MODAL
|
||||||
function closeLogoutModal() {
|
function closeLogoutModal() {
|
||||||
const overlay = document.getElementById('logout-overlay');
|
const overlay = document.getElementById('logout-overlay');
|
||||||
if (overlay) {
|
if (overlay) {
|
||||||
@ -103,12 +100,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CONFIRM LOGOUT
|
// CONFIRM LOGOUT
|
||||||
// GANTI fungsi confirmLogout() yang lama dengan ini:
|
|
||||||
|
|
||||||
function confirmLogout() {
|
function confirmLogout() {
|
||||||
console.log("Mencoba logout ke server..."); // Debugging
|
console.log("Mencoba logout ke server..."); // Debugging
|
||||||
|
|
||||||
// 1. Panggil PHP untuk hancurkan sesi server
|
// Panggil PHP untuk hancurkan sesi server
|
||||||
fetch('Logout.php')
|
fetch('Logout.php')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
console.log("Respon server:", response);
|
console.log("Respon server:", response);
|
||||||
@ -117,12 +112,12 @@
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
console.log("Status Logout:", data);
|
console.log("Status Logout:", data);
|
||||||
|
|
||||||
// 2. Hapus data di browser (Session Storage)
|
// Hapus data di browser (Session Storage)
|
||||||
sessionStorage.removeItem("loggedInUser");
|
sessionStorage.removeItem("loggedInUser");
|
||||||
sessionStorage.removeItem("authToken"); // Kalau ada
|
sessionStorage.removeItem("authToken");
|
||||||
sessionStorage.clear(); // Bersihkan semuanya biar aman
|
sessionStorage.clear(); // Bersihkan semuanya biar aman
|
||||||
|
|
||||||
// 3. Tutup Modal & Redirect
|
// Tutup Modal & Redirect
|
||||||
closeLogoutModal();
|
closeLogoutModal();
|
||||||
showSuccessModal();
|
showSuccessModal();
|
||||||
|
|
||||||
@ -139,7 +134,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== SHOW SUCCESS MODAL ====================
|
// SHOW SUCCESS MODAL
|
||||||
function showSuccessModal() {
|
function showSuccessModal() {
|
||||||
const successOverlay = document.getElementById('logout-success-overlay');
|
const successOverlay = document.getElementById('logout-success-overlay');
|
||||||
if (successOverlay) {
|
if (successOverlay) {
|
||||||
@ -157,7 +152,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ==================== INITIALIZE ====================
|
// INITIALIZE
|
||||||
|
// Menjamin agar kode tidak mencuri start sebelum elemen HTML tersedia.
|
||||||
function init() {
|
function init() {
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', checkLoginStatus);
|
document.addEventListener('DOMContentLoaded', checkLoginStatus);
|
||||||
@ -166,8 +162,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Start
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
})();
|
})();
|
||||||
@ -1,10 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json'); // Response berupa JSON
|
||||||
|
|
||||||
session_unset();
|
session_unset(); // Hapus semua data session
|
||||||
session_destroy();
|
session_destroy(); // Hancurkan session login
|
||||||
|
|
||||||
|
// Kirim respon logout sukses
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"status" => "success",
|
"status" => "success",
|
||||||
"message" => "Logout berhasil"
|
"message" => "Logout berhasil"
|
||||||
|
|||||||
63
Register.php
63
Register.php
@ -1,85 +1,72 @@
|
|||||||
<?php
|
<?php
|
||||||
// ✅ Set timezone Indonesia (WIB)
|
date_default_timezone_set('Asia/Jakarta'); // Set waktu WIB
|
||||||
date_default_timezone_set('Asia/Jakarta');
|
|
||||||
|
|
||||||
// ✅ CORS Headers
|
|
||||||
header('Access-Control-Allow-Origin: *');
|
header('Access-Control-Allow-Origin: *');
|
||||||
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
||||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||||
header('Access-Control-Max-Age: 86400');
|
header('Access-Control-Max-Age: 86400');
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
// ✅ Handle preflight OPTIONS
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Koneksi Database
|
||||||
include 'Connection.php';
|
include 'Connection.php';
|
||||||
|
|
||||||
// ✅ Handle input
|
// Ambil Input
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
$username = trim($input['username'] ?? $_POST['username'] ?? '');
|
$username = trim($input['username'] ?? $_POST['username'] ?? ''); // Username
|
||||||
$password = $input['password'] ?? $_POST['password'] ?? '';
|
$password = $input['password'] ?? $_POST['password'] ?? ''; // Password
|
||||||
|
|
||||||
// ✅ Validasi input kosong
|
// Validasi Kosong
|
||||||
if (empty($username) || empty($password)) {
|
if (empty($username) || empty($password)) {
|
||||||
echo json_encode([
|
echo json_encode(["status" => "error", "message" => "Username and password are required"]);
|
||||||
"status" => "error",
|
|
||||||
"message" => "Username and password are required"
|
|
||||||
]);
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Validasi panjang password
|
// Validasi Password
|
||||||
if (strlen($password) < 6) {
|
if (strlen($password) < 6) {
|
||||||
echo json_encode([
|
echo json_encode(["status" => "error", "message" => "Password must be at least 6 characters"]);
|
||||||
"status" => "error",
|
|
||||||
"message" => "Password must be at least 6 characters"
|
|
||||||
]);
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Validasi format username
|
// Validasi Username
|
||||||
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
|
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
|
||||||
echo json_encode([
|
echo json_encode(["status" => "error", "message" => "Invalid username format"]);
|
||||||
"status" => "error",
|
|
||||||
"message" => "Username may only contain letters, numbers, and underscores (3–20 characters)"
|
|
||||||
]);
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Cek apakah username sudah ada
|
// Cek Username
|
||||||
$check = $conn->prepare("SELECT id FROM users WHERE username = ?");
|
$check = $conn->prepare("SELECT id FROM users WHERE username = ?");
|
||||||
$check->bind_param("s", $username);
|
$check->bind_param("s", $username);
|
||||||
$check->execute();
|
$check->execute();
|
||||||
$check->store_result();
|
$check->store_result();
|
||||||
|
|
||||||
if ($check->num_rows > 0) {
|
if ($check->num_rows > 0) {
|
||||||
echo json_encode([
|
echo json_encode(["status" => "error", "message" => "Username already taken"]);
|
||||||
"status" => "error",
|
|
||||||
"message" => "Username is already taken"
|
|
||||||
]);
|
|
||||||
$check->close();
|
$check->close();
|
||||||
$conn->close();
|
$conn->close();
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
$check->close();
|
$check->close();
|
||||||
|
|
||||||
// ✅ Hash password dan insert ke database
|
// Simpan User Baru
|
||||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
$hashedPassword = password_hash($password, PASSWORD_DEFAULT); // Hash password
|
||||||
$created_at = date("Y-m-d H:i:s");
|
$created_at = date("Y-m-d H:i:s"); // Waktu daftar
|
||||||
|
|
||||||
$stmt = $conn->prepare("INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)");
|
$stmt = $conn->prepare(
|
||||||
|
"INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)"
|
||||||
|
);
|
||||||
$stmt->bind_param("sss", $username, $hashedPassword, $created_at);
|
$stmt->bind_param("sss", $username, $hashedPassword, $created_at);
|
||||||
|
|
||||||
if ($stmt->execute()) {
|
if ($stmt->execute()) {
|
||||||
// 🔥 PERBAIKAN UTAMA DI SINI (AUTO-LOGIN) 🔥
|
|
||||||
$new_user_id = $stmt->insert_id; // Ambil ID user baru
|
$new_user_id = $stmt->insert_id; // Ambil ID user baru
|
||||||
|
|
||||||
session_start();
|
session_start(); // Mulai session
|
||||||
$_SESSION['user_id'] = $new_user_id; // Set Session ID
|
$_SESSION['user_id'] = $new_user_id; // Set session ID
|
||||||
$_SESSION['username'] = $username; // Set Session Username
|
$_SESSION['username'] = $username; // Set session username
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"status" => "success",
|
"status" => "success",
|
||||||
@ -89,10 +76,10 @@ if ($stmt->execute()) {
|
|||||||
} else {
|
} else {
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"status" => "error",
|
"status" => "error",
|
||||||
"message" => "Failed to register: " . $conn->error
|
"message" => "Registration failed"
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt->close();
|
$stmt->close(); // Tutup statement
|
||||||
$conn->close();
|
$conn->close(); // Tutup DB
|
||||||
?>
|
?>
|
||||||
69
Score.php
69
Score.php
@ -1,70 +1,95 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start(); // Mulai session
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
require 'Connection.php'; // Gunakan require agar stop jika file tidak ada
|
require 'Connection.php'; // Koneksi DB
|
||||||
|
|
||||||
// 1. Pastikan user login & user_id tersedia
|
// Cek Login
|
||||||
if (!isset($_SESSION['username']) || !isset($_SESSION['user_id'])) {
|
if (!isset($_SESSION['username']) || !isset($_SESSION['user_id'])) {
|
||||||
echo json_encode(["status" => "error", "message" => "Not logged in or session is invalid"]);
|
echo json_encode([
|
||||||
|
"status" => "error",
|
||||||
|
"message" => "Not logged in"
|
||||||
|
]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$username = $_SESSION['username'];
|
// Ambil Data
|
||||||
$user_id = $_SESSION['user_id']; // AMBIL ID DARI SESSION
|
$username = $_SESSION['username']; // Username dari session
|
||||||
$score = intval($_POST['score'] ?? 0);
|
$user_id = $_SESSION['user_id']; // User ID dari session
|
||||||
|
$score = intval($_POST['score'] ?? 0); // Score dari client
|
||||||
|
|
||||||
// Validasi score
|
// Validasi Score
|
||||||
if ($score <= 0) {
|
if ($score <= 0) {
|
||||||
echo json_encode(["status" => "error", "message" => "Invalid score"]);
|
echo json_encode([
|
||||||
|
"status" => "error",
|
||||||
|
"message" => "Invalid score"
|
||||||
|
]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cek apakah user sudah punya record di leaderboard
|
// Cek Data Leaderboard
|
||||||
$checkStmt = $conn->prepare("SELECT score FROM leaderboard WHERE user_id = ?");
|
$checkStmt = $conn->prepare(
|
||||||
$checkStmt->bind_param("i", $user_id); // Cek berdasarkan ID, lebih akurat daripada username
|
"SELECT score FROM leaderboard WHERE user_id = ?"
|
||||||
|
); // Cek berdasarkan user_id
|
||||||
|
$checkStmt->bind_param("i", $user_id);
|
||||||
$checkStmt->execute();
|
$checkStmt->execute();
|
||||||
$result = $checkStmt->get_result();
|
$result = $checkStmt->get_result();
|
||||||
|
|
||||||
|
// Jika sudah ada score
|
||||||
if ($result->num_rows > 0) {
|
if ($result->num_rows > 0) {
|
||||||
$row = $result->fetch_assoc();
|
$row = $result->fetch_assoc();
|
||||||
$oldScore = $row['score'];
|
$oldScore = $row['score'];
|
||||||
|
|
||||||
if ($score > $oldScore) {
|
if ($score > $oldScore) {
|
||||||
// Update score berdasarkan user_id
|
|
||||||
$updateStmt = $conn->prepare("UPDATE leaderboard SET score = ?, username = ? WHERE user_id = ?");
|
// Update jika score lebih tinggi
|
||||||
// Kita update username juga untuk jaga-jaga kalau user ganti nama
|
$updateStmt = $conn->prepare(
|
||||||
|
"UPDATE leaderboard SET score = ?, username = ? WHERE user_id = ?"
|
||||||
|
);
|
||||||
$updateStmt->bind_param("isi", $score, $username, $user_id);
|
$updateStmt->bind_param("isi", $score, $username, $user_id);
|
||||||
|
|
||||||
if ($updateStmt->execute()) {
|
if ($updateStmt->execute()) {
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"status" => "success",
|
"status" => "success",
|
||||||
"message" => "High Score baru tercatat!",
|
"message" => "New high score saved",
|
||||||
"newHighScore" => true
|
"newHighScore" => true
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
echo json_encode(["status" => "error", "message" => "Failed to update the database"]);
|
echo json_encode([
|
||||||
|
"status" => "error",
|
||||||
|
"message" => "Failed to update score"
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
$updateStmt->close();
|
$updateStmt->close();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// Score lebih rendah dari record lama
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"status" => "success",
|
"status" => "success",
|
||||||
"message" => "The score is lower than the previous record",
|
"message" => "Score not higher than previous",
|
||||||
"newHighScore" => false
|
"newHighScore" => false
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Masukkan user_id, username, dan score
|
|
||||||
$insertStmt = $conn->prepare("INSERT INTO leaderboard (user_id, username, score) VALUES (?, ?, ?)");
|
// Jika Belum Ada Score
|
||||||
|
$insertStmt = $conn->prepare(
|
||||||
|
"INSERT INTO leaderboard (user_id, username, score) VALUES (?, ?, ?)"
|
||||||
|
);
|
||||||
$insertStmt->bind_param("isi", $user_id, $username, $score);
|
$insertStmt->bind_param("isi", $user_id, $username, $score);
|
||||||
|
|
||||||
if ($insertStmt->execute()) {
|
if ($insertStmt->execute()) {
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"status" => "success",
|
"status" => "success",
|
||||||
"message" => "The first score has been successfully saved",
|
"message" => "First score saved",
|
||||||
"newHighScore" => true
|
"newHighScore" => true
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
echo json_encode(["status" => "error", "message" => "Failed to insert into database"]);
|
echo json_encode([
|
||||||
|
"status" => "error",
|
||||||
|
"message" => "Failed to save score"
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
$insertStmt->close();
|
$insertStmt->close();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
function saveScore(score) {
|
function saveScore(score) {
|
||||||
|
// Kirim score ke server
|
||||||
fetch('Score.php', {
|
fetch('Score.php', {
|
||||||
method: 'POST',
|
method: 'POST', // Method POST
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded' // Data dikirim dalam format form data
|
||||||
},
|
},
|
||||||
body: 'score=' + encodeURIComponent(score)
|
body: 'score=' + encodeURIComponent(score) // Data score yang dikirim
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json()) // Ubah response ke JSON
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
// Cek status dari server
|
||||||
if (data.status === "success") {
|
if (data.status === "success") {
|
||||||
console.log("Score saved successfully");
|
console.log("Score saved successfully");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user