Update
This commit is contained in:
parent
08f00ee6d6
commit
05623f58df
601
2048.css
601
2048.css
@ -23,7 +23,6 @@ body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ADDED: Animated Grid Pattern (optional, bisa dihapus kalau ga suka) */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
@ -49,7 +48,6 @@ body::before {
|
||||
}
|
||||
}
|
||||
|
||||
/* ADDED: Floating gradient blobs for depth */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
@ -90,313 +88,6 @@ body::after {
|
||||
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 */
|
||||
.btn-tutorial {
|
||||
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 */
|
||||
.mobile-controls {
|
||||
display: none;
|
||||
@ -838,208 +442,3 @@ h1 {
|
||||
text-shadow: 0 0 10px rgba(255, 215, 0, 0.6);
|
||||
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 name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<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_Floating_Particles.css"/>
|
||||
<link rel="stylesheet" href="2048.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_Sound.css"/>
|
||||
<link rel="stylesheet" href="2048_Modal.css"/>
|
||||
<link rel="stylesheet" href="2048_Responsive.css"/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
/* ==========================
|
||||
BACKGROUND EFFECTS
|
||||
========================== */
|
||||
.particles {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@ -68,8 +65,7 @@
|
||||
will-change: transform, opacity;
|
||||
filter: blur(1px) drop-shadow(0 0 8px rgba(255,255,255,0.1));
|
||||
}
|
||||
/* ENHANCED BACKGROUND EFFECTS */
|
||||
/* Update .particles styling */
|
||||
|
||||
.particles {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@ -100,7 +96,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Enhanced Starfield */
|
||||
.starfield {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@ -131,7 +126,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Enhanced Cursor Light */
|
||||
.cursor-light {
|
||||
position: absolute;
|
||||
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 @@
|
||||
/* ==========================
|
||||
ADVANCED SOUND CONTROL
|
||||
========================== */
|
||||
.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);
|
||||
}
|
||||
|
||||
.sound-control-container {
|
||||
position: fixed;
|
||||
top: clamp(10px, 2vh, 20px);
|
||||
|
||||
@ -1,44 +1,40 @@
|
||||
function checkAndShowTutorial() {
|
||||
// Ambil user terbaru saat fungsi dijalankan
|
||||
// Ambil user yang sedang login (atau guest)
|
||||
const currentUser = sessionStorage.getItem("loggedInUser") || "guest";
|
||||
const tutorialKey = 'tutorialSeen_' + currentUser;
|
||||
|
||||
// Cek di Console browser (tekan F12 -> Console)
|
||||
console.log(`[Tutorial Check] User: ${currentUser}`);
|
||||
console.log(`[Tutorial Check] Key: ${tutorialKey}`);
|
||||
|
||||
// Cek status di LocalStorage
|
||||
// Cek apakah tutorial sudah pernah dilihat
|
||||
const hasSeenTutorial = localStorage.getItem(tutorialKey);
|
||||
console.log(`[Tutorial Check] Status Seen: ${hasSeenTutorial}`);
|
||||
|
||||
const tutorialOverlay = document.getElementById('tutorial-overlay');
|
||||
|
||||
// Logic: Jika belum pernah lihat (null) -> Tampilkan
|
||||
// Tampilkan tutorial jika belum pernah dilihat
|
||||
if (!hasSeenTutorial && tutorialOverlay) {
|
||||
console.log("-> Menampilkan Tutorial untuk user baru.");
|
||||
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', () => {
|
||||
checkAndShowTutorial();
|
||||
checkAndShowTutorial(); // Cek & tampilkan tutorial
|
||||
|
||||
// Setup tombol close hanya sekali
|
||||
const closeTutorialBtn = document.getElementById('close-tutorial');
|
||||
const tutorialOverlay = document.getElementById('tutorial-overlay');
|
||||
|
||||
// Event klik tombol tutup tutorial
|
||||
if (closeTutorialBtn) {
|
||||
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 tutorialKey = 'tutorialSeen_' + currentUser;
|
||||
|
||||
if(tutorialOverlay) tutorialOverlay.style.display = 'none';
|
||||
// Sembunyikan tutorial & simpan status
|
||||
if (tutorialOverlay) tutorialOverlay.style.display = 'none';
|
||||
localStorage.setItem(tutorialKey, 'true');
|
||||
console.log(`[Tutorial Check] Disimpan: ${tutorialKey} = true`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
<?php
|
||||
// Mengizinkan akses dari semua domain
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
// Menentukan metode HTTP yang diizinkan (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');
|
||||
// Mengatur tipe konten output menjadi format JSON
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$DB_HOST = "202.46.28.160";
|
||||
@ -10,6 +14,7 @@ $DB_USER = "evelyn";
|
||||
$DB_PASS = "evelynsc25";
|
||||
$DB_NAME = "web";
|
||||
|
||||
// Memeriksa apakah ada error saat menghubungkan ke database
|
||||
$conn = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_PORT);
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
<title>Homepage</title>
|
||||
<link rel="stylesheet" href="Homepage.css">
|
||||
<link rel="stylesheet" href="Homepage_Credit.css">
|
||||
<link rel="stylesheet" href="Homepage_Scrollbar.css">
|
||||
<link rel="stylesheet" href="Homepage_Responsive.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -75,4 +75,26 @@
|
||||
.btn-logout-confirm {
|
||||
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
|
||||
header('Content-Type: application/json');
|
||||
require 'Connection.php';
|
||||
session_start();
|
||||
header('Content-Type: application/json'); // Response berupa JSON
|
||||
require 'Connection.php'; // Koneksi database
|
||||
session_start(); // Ambil session user login
|
||||
|
||||
// Response default
|
||||
$response = [
|
||||
"status" => "error",
|
||||
"leaderboard" => [],
|
||||
"user_rank" => null
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 1. Ambil Top 10 Global (DIPERBAIKI)
|
||||
// Ditambahkan "user_id ASC" agar urutannya PASTI (Konsisten)
|
||||
// Jika skor sama, user dengan ID lebih kecil (daftar duluan) akan di atas
|
||||
// ---------------------------------------------------------
|
||||
$query = "SELECT username, score FROM leaderboard ORDER BY score DESC, user_id ASC LIMIT 10";
|
||||
// Ambil Top 10 Leaderboard Global
|
||||
// Urut score terbesar
|
||||
$query = "
|
||||
SELECT username, score
|
||||
FROM leaderboard
|
||||
ORDER BY score DESC, user_id ASC
|
||||
LIMIT 10
|
||||
";
|
||||
$result = $conn->query($query);
|
||||
|
||||
if ($result && $result->num_rows > 0) {
|
||||
@ -23,14 +26,16 @@ if ($result && $result->num_rows > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. Ambil Ranking User Login (DIPERBAIKI)
|
||||
// ---------------------------------------------------------
|
||||
// Ambil Ranking User yang Sedang Login (your rank)
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
$my_id = $_SESSION['user_id'];
|
||||
|
||||
// Ambil score user saat ini
|
||||
$scoreQuery = $conn->prepare("SELECT username, score FROM leaderboard WHERE user_id = ?");
|
||||
// Ambil username & score user
|
||||
$scoreQuery = $conn->prepare("
|
||||
SELECT username, score
|
||||
FROM leaderboard
|
||||
WHERE user_id = ?
|
||||
");
|
||||
$scoreQuery->bind_param("i", $my_id);
|
||||
$scoreQuery->execute();
|
||||
$scoreResult = $scoreQuery->get_result();
|
||||
@ -39,38 +44,33 @@ if (isset($_SESSION['user_id'])) {
|
||||
$myScore = $scoreRow['score'];
|
||||
$myUsername = $scoreRow['username'];
|
||||
|
||||
// --- LOGIKA BARU ---
|
||||
// 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)
|
||||
|
||||
// Hitung jumlah user yang berada di atas
|
||||
$rankQuery = $conn->prepare("
|
||||
SELECT COUNT(*) as rank_above
|
||||
SELECT COUNT(*) AS rank_above
|
||||
FROM leaderboard
|
||||
WHERE score > ?
|
||||
OR (score = ? AND user_id < ?)
|
||||
");
|
||||
|
||||
// Kita bind 3 parameter: score, score, id
|
||||
$rankQuery->bind_param("iii", $myScore, $myScore, $my_id);
|
||||
|
||||
$rankQuery->execute();
|
||||
$rankResult = $rankQuery->get_result();
|
||||
$rankRow = $rankResult->fetch_assoc();
|
||||
|
||||
// Rank adalah jumlah orang di atas kita + 1
|
||||
$myRank = $rankRow['rank_above'] + 1;
|
||||
|
||||
// Rank = jumlah di atas + 1
|
||||
$response['user_rank'] = [
|
||||
"username" => $myUsername,
|
||||
"score" => $myScore,
|
||||
"rank" => $myRank
|
||||
"rank" => $rankRow['rank_above'] + 1
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Status sukses
|
||||
$response['status'] = "success";
|
||||
|
||||
// Kirim JSON ke client
|
||||
echo json_encode($response);
|
||||
|
||||
// Tutup koneksi database
|
||||
$conn->close();
|
||||
?>
|
||||
49
Login.php
49
Login.php
@ -1,5 +1,4 @@
|
||||
<?php
|
||||
// ... (Header CORS tetap sama) ...
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
@ -11,47 +10,61 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit();
|
||||
}
|
||||
|
||||
session_start();
|
||||
include 'Connection.php';
|
||||
session_start(); // Mulai session (login state)
|
||||
include 'Connection.php'; // Koneksi database
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$username = $input['username'] ?? '';
|
||||
$password = $input['password'] ?? '';
|
||||
// Ambil Data Login dari Client
|
||||
$input = json_decode(file_get_contents('php://input'), true); // Ambil body JSON
|
||||
$username = $input['username'] ?? ''; // Username dari client
|
||||
$password = $input['password'] ?? ''; // Password dari client
|
||||
|
||||
// ... (Validasi input kosong tetap sama) ...
|
||||
|
||||
// 🔴 PERBAIKAN 1: Tambahkan 'id' di dalam SELECT
|
||||
$stmt = $conn->prepare("SELECT id, password FROM users WHERE username = ?");
|
||||
// Cek Username di Database
|
||||
$stmt = $conn->prepare(
|
||||
"SELECT id, password FROM users WHERE username = ?"
|
||||
);
|
||||
$stmt->bind_param("s", $username);
|
||||
$stmt->execute();
|
||||
$stmt->store_result();
|
||||
|
||||
// Jika Username Tidak Ada
|
||||
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();
|
||||
$conn->close();
|
||||
exit;
|
||||
}
|
||||
|
||||
// 🔴 PERBAIKAN 2: Bind result untuk menangkap 'id' dan 'password'
|
||||
$stmt->bind_result($userId, $hashedPassword);
|
||||
// Ambil Data User
|
||||
$stmt->bind_result($userId, $hashedPassword); // Ambil id & password hash
|
||||
$stmt->fetch();
|
||||
|
||||
// Cek Password
|
||||
if (password_verify($password, $hashedPassword)) {
|
||||
// 🔴 PERBAIKAN 3: Simpan 'user_id' ke dalam SESSION
|
||||
|
||||
// Simpan data login ke session
|
||||
$_SESSION['user_id'] = $userId;
|
||||
$_SESSION['username'] = $username;
|
||||
|
||||
// Kirim respon login sukses
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"message" => "Login successful",
|
||||
"username" => $username,
|
||||
"token" => bin2hex(random_bytes(32))
|
||||
"token" => bin2hex(random_bytes(32)) // Token acak (bukan JWT)
|
||||
]);
|
||||
|
||||
} else {
|
||||
echo json_encode(["success" => false, "message" => "Incorrect password"]);
|
||||
|
||||
// Password salah
|
||||
echo json_encode([
|
||||
"success" => false,
|
||||
"message" => "Incorrect password"
|
||||
]);
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
$stmt->close(); // Tutup statement
|
||||
$conn->close(); // Tutup koneksi DB
|
||||
?>
|
||||
44
Logout.js
44
Logout.js
@ -7,16 +7,13 @@
|
||||
const loginBtn = document.querySelector('.btn-login');
|
||||
|
||||
if (loggedInUser && loginBtn) {
|
||||
// User is logged in - change to Logout button
|
||||
// Mengubah button login menjadi logout
|
||||
loginBtn.textContent = 'Logout';
|
||||
loginBtn.classList.remove('btn-login');
|
||||
loginBtn.classList.add('btn-logout');
|
||||
loginBtn.href = '#'; // Prevent navigation
|
||||
|
||||
// Remove existing click listeners (untuk menghindari duplikat)
|
||||
loginBtn.href = '#';
|
||||
loginBtn.replaceWith(loginBtn.cloneNode(true));
|
||||
|
||||
// Get new reference dan add logout handler
|
||||
const newLogoutBtn = document.querySelector('.btn-logout');
|
||||
if (newLogoutBtn) {
|
||||
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) {
|
||||
e.preventDefault();
|
||||
showLogoutModal();
|
||||
}
|
||||
|
||||
// ==================== SHOW LOGOUT CONFIRMATION MODAL ====================
|
||||
// SHOW LOGOUT CONFIRMATION MODAL
|
||||
function showLogoutModal() {
|
||||
const overlay = document.getElementById('logout-overlay');
|
||||
if (overlay) {
|
||||
overlay.style.display = 'flex';
|
||||
setTimeout(() => overlay.classList.add('active'), 10);
|
||||
|
||||
// Setup modal buttons
|
||||
setupModalButtons();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== SETUP MODAL BUTTONS ====================
|
||||
// SETUP MODAL BUTTONS
|
||||
function setupModalButtons() {
|
||||
const cancelBtn = document.getElementById('btn-logout-cancel');
|
||||
const confirmBtn = document.getElementById('btn-logout-confirm');
|
||||
const overlay = document.getElementById('logout-overlay');
|
||||
|
||||
// Remove previous listeners
|
||||
// Remove previous listeners (mencegah error double click)
|
||||
if (cancelBtn) {
|
||||
const newCancelBtn = cancelBtn.cloneNode(true);
|
||||
cancelBtn.replaceWith(newCancelBtn);
|
||||
@ -65,7 +61,7 @@
|
||||
newConfirmBtn.onclick = confirmLogout;
|
||||
}
|
||||
|
||||
// Close on overlay click
|
||||
// Close on overlay click (area gelap)
|
||||
if (overlay) {
|
||||
overlay.onclick = function(e) {
|
||||
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);
|
||||
}
|
||||
|
||||
// ==================== HANDLE ESCAPE KEY ====================
|
||||
// HANDLE ESCAPE KEY
|
||||
// Menutup jendela pop-up (modal) logout menggunakan tombol keyboard Esc
|
||||
function handleEscapeKey(e) {
|
||||
if (e.key === 'Escape') {
|
||||
const overlay = document.getElementById('logout-overlay');
|
||||
@ -88,7 +85,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== CLOSE LOGOUT MODAL ====================
|
||||
// CLOSE LOGOUT MODAL
|
||||
function closeLogoutModal() {
|
||||
const overlay = document.getElementById('logout-overlay');
|
||||
if (overlay) {
|
||||
@ -103,12 +100,10 @@
|
||||
}
|
||||
|
||||
// CONFIRM LOGOUT
|
||||
// GANTI fungsi confirmLogout() yang lama dengan ini:
|
||||
|
||||
function confirmLogout() {
|
||||
console.log("Mencoba logout ke server..."); // Debugging
|
||||
|
||||
// 1. Panggil PHP untuk hancurkan sesi server
|
||||
// Panggil PHP untuk hancurkan sesi server
|
||||
fetch('Logout.php')
|
||||
.then(response => {
|
||||
console.log("Respon server:", response);
|
||||
@ -117,12 +112,12 @@
|
||||
.then(data => {
|
||||
console.log("Status Logout:", data);
|
||||
|
||||
// 2. Hapus data di browser (Session Storage)
|
||||
// Hapus data di browser (Session Storage)
|
||||
sessionStorage.removeItem("loggedInUser");
|
||||
sessionStorage.removeItem("authToken"); // Kalau ada
|
||||
sessionStorage.removeItem("authToken");
|
||||
sessionStorage.clear(); // Bersihkan semuanya biar aman
|
||||
|
||||
// 3. Tutup Modal & Redirect
|
||||
// Tutup Modal & Redirect
|
||||
closeLogoutModal();
|
||||
showSuccessModal();
|
||||
|
||||
@ -139,7 +134,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== SHOW SUCCESS MODAL ====================
|
||||
// SHOW SUCCESS MODAL
|
||||
function showSuccessModal() {
|
||||
const successOverlay = document.getElementById('logout-success-overlay');
|
||||
if (successOverlay) {
|
||||
@ -157,7 +152,8 @@
|
||||
}
|
||||
|
||||
|
||||
// ==================== INITIALIZE ====================
|
||||
// INITIALIZE
|
||||
// Menjamin agar kode tidak mencuri start sebelum elemen HTML tersedia.
|
||||
function init() {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', checkLoginStatus);
|
||||
@ -166,8 +162,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Start
|
||||
init();
|
||||
|
||||
})();
|
||||
@ -1,10 +1,11 @@
|
||||
<?php
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Type: application/json'); // Response berupa JSON
|
||||
|
||||
session_unset();
|
||||
session_destroy();
|
||||
session_unset(); // Hapus semua data session
|
||||
session_destroy(); // Hancurkan session login
|
||||
|
||||
// Kirim respon logout sukses
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "Logout berhasil"
|
||||
|
||||
63
Register.php
63
Register.php
@ -1,85 +1,72 @@
|
||||
<?php
|
||||
// ✅ Set timezone Indonesia (WIB)
|
||||
date_default_timezone_set('Asia/Jakarta');
|
||||
date_default_timezone_set('Asia/Jakarta'); // Set waktu WIB
|
||||
|
||||
// ✅ CORS Headers
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
header('Access-Control-Max-Age: 86400');
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// ✅ Handle preflight OPTIONS
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Koneksi Database
|
||||
include 'Connection.php';
|
||||
|
||||
// ✅ Handle input
|
||||
// Ambil Input
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$username = trim($input['username'] ?? $_POST['username'] ?? '');
|
||||
$password = $input['password'] ?? $_POST['password'] ?? '';
|
||||
$username = trim($input['username'] ?? $_POST['username'] ?? ''); // Username
|
||||
$password = $input['password'] ?? $_POST['password'] ?? ''; // Password
|
||||
|
||||
// ✅ Validasi input kosong
|
||||
// Validasi Kosong
|
||||
if (empty($username) || empty($password)) {
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Username and password are required"
|
||||
]);
|
||||
echo json_encode(["status" => "error", "message" => "Username and password are required"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ Validasi panjang password
|
||||
// Validasi Password
|
||||
if (strlen($password) < 6) {
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Password must be at least 6 characters"
|
||||
]);
|
||||
echo json_encode(["status" => "error", "message" => "Password must be at least 6 characters"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ Validasi format username
|
||||
// Validasi Username
|
||||
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Username may only contain letters, numbers, and underscores (3–20 characters)"
|
||||
]);
|
||||
echo json_encode(["status" => "error", "message" => "Invalid username format"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ Cek apakah username sudah ada
|
||||
// Cek Username
|
||||
$check = $conn->prepare("SELECT id FROM users WHERE username = ?");
|
||||
$check->bind_param("s", $username);
|
||||
$check->execute();
|
||||
$check->store_result();
|
||||
|
||||
if ($check->num_rows > 0) {
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Username is already taken"
|
||||
]);
|
||||
echo json_encode(["status" => "error", "message" => "Username already taken"]);
|
||||
$check->close();
|
||||
$conn->close();
|
||||
exit;
|
||||
}
|
||||
$check->close();
|
||||
|
||||
// ✅ Hash password dan insert ke database
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
$created_at = date("Y-m-d H:i:s");
|
||||
// Simpan User Baru
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT); // Hash password
|
||||
$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);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
// 🔥 PERBAIKAN UTAMA DI SINI (AUTO-LOGIN) 🔥
|
||||
$new_user_id = $stmt->insert_id; // Ambil ID user baru
|
||||
|
||||
session_start();
|
||||
$_SESSION['user_id'] = $new_user_id; // Set Session ID
|
||||
$_SESSION['username'] = $username; // Set Session Username
|
||||
session_start(); // Mulai session
|
||||
$_SESSION['user_id'] = $new_user_id; // Set session ID
|
||||
$_SESSION['username'] = $username; // Set session username
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
@ -89,10 +76,10 @@ if ($stmt->execute()) {
|
||||
} else {
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Failed to register: " . $conn->error
|
||||
"message" => "Registration failed"
|
||||
]);
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
$stmt->close(); // Tutup statement
|
||||
$conn->close(); // Tutup DB
|
||||
?>
|
||||
69
Score.php
69
Score.php
@ -1,70 +1,95 @@
|
||||
<?php
|
||||
session_start();
|
||||
session_start(); // Mulai session
|
||||
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'])) {
|
||||
echo json_encode(["status" => "error", "message" => "Not logged in or session is invalid"]);
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Not logged in"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$username = $_SESSION['username'];
|
||||
$user_id = $_SESSION['user_id']; // AMBIL ID DARI SESSION
|
||||
$score = intval($_POST['score'] ?? 0);
|
||||
// Ambil Data
|
||||
$username = $_SESSION['username']; // Username dari session
|
||||
$user_id = $_SESSION['user_id']; // User ID dari session
|
||||
$score = intval($_POST['score'] ?? 0); // Score dari client
|
||||
|
||||
// Validasi score
|
||||
// Validasi Score
|
||||
if ($score <= 0) {
|
||||
echo json_encode(["status" => "error", "message" => "Invalid score"]);
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Invalid score"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Cek apakah user sudah punya record di leaderboard
|
||||
$checkStmt = $conn->prepare("SELECT score FROM leaderboard WHERE user_id = ?");
|
||||
$checkStmt->bind_param("i", $user_id); // Cek berdasarkan ID, lebih akurat daripada username
|
||||
// Cek Data Leaderboard
|
||||
$checkStmt = $conn->prepare(
|
||||
"SELECT score FROM leaderboard WHERE user_id = ?"
|
||||
); // Cek berdasarkan user_id
|
||||
$checkStmt->bind_param("i", $user_id);
|
||||
$checkStmt->execute();
|
||||
$result = $checkStmt->get_result();
|
||||
|
||||
// Jika sudah ada score
|
||||
if ($result->num_rows > 0) {
|
||||
$row = $result->fetch_assoc();
|
||||
$oldScore = $row['score'];
|
||||
|
||||
if ($score > $oldScore) {
|
||||
// Update score berdasarkan user_id
|
||||
$updateStmt = $conn->prepare("UPDATE leaderboard SET score = ?, username = ? WHERE user_id = ?");
|
||||
// Kita update username juga untuk jaga-jaga kalau user ganti nama
|
||||
|
||||
// Update jika score lebih tinggi
|
||||
$updateStmt = $conn->prepare(
|
||||
"UPDATE leaderboard SET score = ?, username = ? WHERE user_id = ?"
|
||||
);
|
||||
$updateStmt->bind_param("isi", $score, $username, $user_id);
|
||||
|
||||
if ($updateStmt->execute()) {
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "High Score baru tercatat!",
|
||||
"message" => "New high score saved",
|
||||
"newHighScore" => true
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(["status" => "error", "message" => "Failed to update the database"]);
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Failed to update score"
|
||||
]);
|
||||
}
|
||||
$updateStmt->close();
|
||||
|
||||
} else {
|
||||
|
||||
// Score lebih rendah dari record lama
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "The score is lower than the previous record",
|
||||
"message" => "Score not higher than previous",
|
||||
"newHighScore" => false
|
||||
]);
|
||||
}
|
||||
|
||||
} 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);
|
||||
|
||||
if ($insertStmt->execute()) {
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "The first score has been successfully saved",
|
||||
"message" => "First score saved",
|
||||
"newHighScore" => true
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(["status" => "error", "message" => "Failed to insert into database"]);
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Failed to save score"
|
||||
]);
|
||||
}
|
||||
$insertStmt->close();
|
||||
}
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
function saveScore(score) {
|
||||
// Kirim score ke server
|
||||
fetch('Score.php', {
|
||||
method: 'POST',
|
||||
method: 'POST', // Method POST
|
||||
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 => {
|
||||
// Cek status dari server
|
||||
if (data.status === "success") {
|
||||
console.log("Score saved successfully");
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user