diff --git a/public/assets/js/game.js b/public/assets/js/game.js
new file mode 100644
index 0000000..6826907
--- /dev/null
+++ b/public/assets/js/game.js
@@ -0,0 +1,275 @@
+
+const ranks = ['A','2','3','4','5','6','7','8','9','10','J','Q','K'];
+const suits = ['♠','♥','♦','♣'];
+
+const cardsWrapper = document.getElementById('cardsWrapper');
+const dealerWrapper = document.getElementById('dealerWrapper');
+const deckEl = document.getElementById('deck');
+
+const hitBtn = document.getElementById('hit');
+const standBtn = document.getElementById('stand');
+const endScreen = document.getElementById('endScreen');
+const endMessage = document.getElementById('endMessage');
+const playAgainBtn = document.getElementById('playAgainBtn');
+
+let playerCards = [];
+let dealerCards = [];
+let playerEls = [];
+let dealerEls = [];
+let dealerHiddenCards = [];
+let dealerHiddenEls = [];
+let gamePhase = 'PLAYING'; // langsung start
+
+/* ---------- helpers ---------- */
+function randomCard(){
+ const r = ranks[Math.floor(Math.random()*ranks.length)];
+ const s = suits[Math.floor(Math.random()*suits.length)];
+ return {rank:r, suit:s, color:(s==='♥'||s==='♦')?'red':'black'};
+}
+
+function createCardEl(card){
+ const el = document.createElement('div');
+ el.className = 'card' + (card.color==='red' ? ' red' : '');
+ el.innerHTML = `
+
${card.rank}
+ ${card.suit}
+ ${card.rank}
+ `;
+ return el;
+}
+
+function createBackCardEl(){
+ const el = document.createElement('div');
+ el.className = 'card back-card';
+ el.innerText = 'HIT';
+ return el;
+}
+
+function wrapCardInContainer(cardEl, isDealer = false){
+ const wrapper = document.createElement('div');
+ wrapper.className = 'card-wrapper';
+ if (!isDealer) wrapper.style.perspective = '1000px';
+ wrapper.appendChild(cardEl);
+ return wrapper;
+}
+
+function calc(cards){
+ let total=0, ace=0;
+ for(const c of cards){
+ if(c.rank==='A'){ total+=11; ace++; }
+ else if(['J','Q','K'].includes(c.rank)) total+=10;
+ else total+=Number(c.rank);
+ }
+ while(total>21 && ace>0){ total-=10; ace--; }
+ return total;
+}
+
+/* ---------- UI updates ---------- */
+function updateTotals(){
+ document.getElementById('playerTotalUI').innerText = calc(playerCards);
+ const hiddenExists = dealerHiddenEls.length > 0;
+ if(hiddenExists) document.getElementById('dealerTotalUI').innerText = '??';
+ else document.getElementById('dealerTotalUI').innerText = calc(dealerCards);
+}
+
+/* layout overlap */
+function layoutOverlap(list, wrapper){
+ const isDealer = (wrapper === dealerWrapper);
+ const cardW = 110;
+ const cardH = 154;
+ const overlap = 28;
+ const count = list.length;
+ const totalWidth = cardW + Math.max(0, count-1)*overlap;
+ const startX = (wrapper.clientWidth - totalWidth)/2;
+ list.forEach((el, i)=>{
+ const x = startX + i*overlap;
+ let y = (wrapper.clientHeight - cardH) / 2;
+ el.style.transform = `translate3d(${x}px, ${y}px, 0)`;
+ el.style.zIndex = 100 + i;
+ });
+}
+
+/* animation from deck */
+function animateFromDeck(cardWrapper, isInitial = true){
+ const startX = deckEl.offsetLeft;
+ const startY = deckEl.offsetTop;
+ const targetTransformMatch = cardWrapper.style.transform.match(/translate3d\((.*?)px,\s*(.*?)px/);
+ const targetX = parseFloat(targetTransformMatch ? targetTransformMatch[1] : startX);
+ const targetY = parseFloat(targetTransformMatch ? targetTransformMatch[2] : startY);
+ cardWrapper.style.transition = 'none';
+ cardWrapper.style.transform = `translate3d(${startX}px, ${startY}px, 0) scale(0.4)`;
+ const inner = cardWrapper.querySelector('.card');
+ if(inner) inner.classList.add('is-moving');
+ requestAnimationFrame(()=>{
+ requestAnimationFrame(()=>{
+ cardWrapper.style.transition = '';
+ cardWrapper.style.transform = `translate3d(${targetX}px, ${targetY}px, 0) scale(1)`;
+ const rotZ = (Math.random() - 0.5) * 12;
+ const rotX = isInitial ? 0 : (Math.random() - 0.5) * 10;
+ if(inner){
+ inner.style.transition = 'transform .4s cubic-bezier(.68,-0.55,.27,1.55), filter .3s ease';
+ inner.style.transform = `rotateZ(${rotZ}deg) rotateX(${rotX}deg)`;
+ }
+ setTimeout(()=>{
+ if(inner){
+ inner.classList.remove('is-moving');
+ inner.style.transform = `rotateZ(0deg) rotateX(0deg)`;
+ inner.style.transition = '';
+ }
+ },420);
+ });
+ });
+}
+
+/* ---------- dealing ---------- */
+function dealPlayer(){
+ const card = randomCard();
+ playerCards.push(card);
+ const cardEl = createCardEl(card);
+ const wrapper = wrapCardInContainer(cardEl, false);
+ cardsWrapper.appendChild(wrapper);
+ playerEls.push(wrapper);
+ layoutOverlap(playerEls, cardsWrapper);
+ animateFromDeck(wrapper);
+ updateTotals();
+}
+
+function dealDealer(faceDown=false){
+ const card = randomCard();
+ dealerCards.push(card);
+ let wrapper;
+ if(faceDown){
+ const backEl = createBackCardEl();
+ wrapper = wrapCardInContainer(backEl, true);
+ dealerHiddenCards.push(card);
+ dealerHiddenEls.push(wrapper);
+ } else {
+ const cardEl = createCardEl(card);
+ wrapper = wrapCardInContainer(cardEl, true);
+ }
+ dealerEls.push(wrapper);
+ dealerWrapper.appendChild(wrapper);
+ layoutOverlap(dealerEls, dealerWrapper);
+ animateFromDeck(wrapper);
+ updateTotals();
+}
+
+function flipAllDealerHidden(){
+ const hiddenWrappers = Array.from(dealerHiddenEls);
+ hiddenWrappers.forEach((wrapper, i) => {
+ const backEl = wrapper.querySelector('.back-card');
+ const cardObj = dealerHiddenCards[i];
+ const realEl = createCardEl(cardObj);
+ setTimeout(()=>{
+ if(backEl){ backEl.style.transform = 'rotateY(90deg)'; backEl.style.opacity = '0'; }
+ realEl.style.transform = 'rotateY(-90deg)'; realEl.style.opacity = '0';
+ wrapper.appendChild(realEl);
+ setTimeout(()=>{
+ if(backEl) backEl.remove();
+ realEl.style.transform = 'rotateY(0deg)';
+ realEl.style.opacity = '1';
+ updateTotals();
+ },220);
+ },300 + i*300);
+ });
+ setTimeout(()=>{
+ dealerHiddenEls = [];
+ dealerHiddenCards = [];
+ }, 300 + hiddenWrappers.length*300 + 100);
+}
+
+/* ---------- dealer play & game end ---------- */
+function dealerPlay(){
+ gamePhase = 'DEALER_TURN';
+ const cycle = setInterval(()=>{
+ if(calc(dealerCards) < 17){
+ dealDealer(false);
+ } else {
+ clearInterval(cycle);
+ finishResult();
+ }
+ }, 900);
+}
+
+function finishResult(){
+ gamePhase = 'END';
+ const p = calc(playerCards);
+ const d = calc(dealerCards);
+ let msg = '';
+ if(p > 21) msg = 'PLAYER BUST — YOU LOSE';
+ else if(d > 21) msg = 'DEALER BUST — YOU WIN!';
+ else if(p === 21 && playerCards.length === 2 && d !== 21) msg = 'BLACKJACK! — YOU WIN! (3:2)';
+ else if(p > d) msg = 'YOU WIN!';
+ else if(p < d) msg = 'YOU LOSE!';
+ else msg = 'PUSH (DRAW) — TARUHAN KEMBALI';
+ showEnd(msg);
+}
+
+/* ---------- end-screen handling ---------- */
+function showEnd(msg){
+ document.getElementById('status').innerText = msg;
+ hitBtn.disabled = true;
+ standBtn.disabled = true;
+ endMessage.innerText = msg;
+ endScreen.style.display = 'flex';
+ playAgainBtn.focus();
+}
+
+/* ---------- controls ---------- */
+function hit(){
+ if(gamePhase !== 'PLAYING' || hitBtn.disabled) return;
+ dealPlayer();
+ if(calc(playerCards) > 21){
+ setTimeout(() => finishResult(), 500);
+ }
+}
+
+function stand(){
+ if(gamePhase !== 'PLAYING' || standBtn.disabled) return;
+ gamePhase = 'DEALER_TURN';
+ hitBtn.disabled = true;
+ standBtn.disabled = true;
+ document.getElementById('status').innerText = 'DEALER TURN';
+ flipAllDealerHidden();
+ setTimeout(()=> dealerPlay(), 300 + dealerHiddenEls.length*300);
+}
+
+/* ---------- start / restart ---------- */
+function startGame(){
+ // disable during dealing to prevent accidental clicks
+ hitBtn.disabled = true;
+ standBtn.disabled = true;
+ playerCards = []; dealerCards = []; playerEls.forEach(e=>e.remove()); dealerEls.forEach(e=>e.remove());
+ playerEls = []; dealerEls = []; dealerHiddenCards = []; dealerHiddenEls = [];
+ endScreen.style.display = 'none';
+
+ // dealing sequence
+ setTimeout(()=> dealPlayer(), 80);
+ setTimeout(()=> dealDealer(false), 280); // dealer first open
+ setTimeout(()=> dealPlayer(), 480);
+ setTimeout(()=> dealDealer(true), 680); // dealer second hidden
+
+ // after dealing finished, enable buttons and set phase to PLAYING
+ setTimeout(()=>{
+ gamePhase = 'PLAYING';
+ document.getElementById('status').innerText = 'YOUR TURN';
+ hitBtn.disabled = false;
+ standBtn.disabled = false;
+ // auto-stand on natural
+ if(calc(playerCards) === 21) stand();
+ }, 900);
+}
+
+function restart(){
+ // reset and start again
+ startGame();
+}
+
+/* ---------- events ---------- */
+hitBtn.addEventListener('click', hit);
+standBtn.addEventListener('click', stand);
+playAgainBtn.addEventListener('click', restart);
+window.addEventListener('resize', ()=>{ layoutOverlap(playerEls, cardsWrapper); layoutOverlap(dealerEls, dealerWrapper); });
+
+/* start immediately */
+startGame();
diff --git a/public/game.php b/public/game.php
new file mode 100644
index 0000000..154ced2
--- /dev/null
+++ b/public/game.php
@@ -0,0 +1,247 @@
+
+
+
+
+
+Hit or Run
+
+
+
+
+
+
+
+
+
DECK
+
+
+
+
DEALER
+
+
Total Dealer: 0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/signin.php b/public/signin.php
index 94bf352..d25fe9f 100644
--- a/public/signin.php
+++ b/public/signin.php
@@ -19,7 +19,7 @@
header("Location: home");
exit;
} else {
- $error = "Wrong Username or Password.";
+ $error = "Wrong Username or Password.";s
}
} else {
$error = "Username not found.";