{"project":{"id":"gzhbcu6","userId":"davidyarham@gmail.com","username":null,"userPicture":null,"name":"Klondike Solitaire","visible":true,"contributors":"","githubRepo":null,"forkedFrom":null,"isTemplate":false,"tags":"","files":{"folder":"","files":[{"name":"index.html","content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Untitled</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n<link rel=\"stylesheet\" href=\"https://unpkg.com/lucide-static@latest/font/lucide.min.css\">\n<script src=\"https://unpkg.com/lucide@latest/dist/umd/lucide.js\"></script>\n</head>\n<body>\n<div class=\"solitaire\">\n\t<header class=\"solitaire__header\">\n\t\t<div class=\"solitaire__brand\">\n\t\t\t<i data-lucide=\"spade\" class=\"solitaire__icon\"></i>\n\t\t\t<h1 class=\"solitaire__title\">Klondike</h1>\n\t\t</div>\n\t\t<div class=\"solitaire__stats\">\n\t\t\t<div class=\"solitaire__stat\">\n\t\t\t\t<span class=\"solitaire__stat-label\">Moves</span>\n\t\t\t\t<span class=\"solitaire__stat-value\" id=\"moveCount\">0</span>\n\t\t\t</div>\n\t\t\t<div class=\"solitaire__stat\">\n\t\t\t\t<span class=\"solitaire__stat-label\">Time</span>\n\t\t\t\t<span class=\"solitaire__stat-value\" id=\"timer\">0:00</span>\n\t\t\t</div>\n\t\t\t<div class=\"solitaire__stat\">\n\t\t\t\t<span class=\"solitaire__stat-label\">Score</span>\n\t\t\t\t<span class=\"solitaire__stat-value\" id=\"score\">0</span>\n\t\t\t</div>\n\t\t</div>\n\t\t<button class=\"solitaire__new-game solitaire__auto-btn\" id=\"autoPlayBtn\">\n      <i data-lucide=\"zap\"></i>\n      Auto\n    </button>\n\t\t<button class=\"solitaire__new-game\" id=\"newGameBtn\">\n      <i data-lucide=\"rotate-ccw\"></i>\n      New Deal\n    </button>\n\t</header>\n\n\t<div class=\"solitaire__board\" id=\"board\">\n\t\t<div class=\"solitaire__top-row\">\n\t\t\t<div class=\"solitaire__stock-waste\">\n\t\t\t\t<div class=\"card-zone card-zone--stock\" id=\"stock\" data-zone=\"stock\">\n\t\t\t\t\t<div class=\"card-zone__label\">Stock</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"card-zone card-zone--waste\" id=\"waste\" data-zone=\"waste\">\n\t\t\t\t\t<div class=\"card-zone__label\">Waste</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"solitaire__foundations\">\n\t\t\t\t<div class=\"card-zone card-zone--foundation\" id=\"foundation-0\" data-zone=\"foundation\" data-index=\"0\">\n\t\t\t\t\t<span class=\"card-zone__suit\">♠</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"card-zone card-zone--foundation\" id=\"foundation-1\" data-zone=\"foundation\" data-index=\"1\">\n\t\t\t\t\t<span class=\"card-zone__suit\">♥</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"card-zone card-zone--foundation\" id=\"foundation-2\" data-zone=\"foundation\" data-index=\"2\">\n\t\t\t\t\t<span class=\"card-zone__suit\">♦</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"card-zone card-zone--foundation\" id=\"foundation-3\" data-zone=\"foundation\" data-index=\"3\">\n\t\t\t\t\t<span class=\"card-zone__suit\">♣</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div class=\"solitaire__tableau\">\n\t\t\t<div class=\"card-zone card-zone--tableau\" id=\"tableau-0\" data-zone=\"tableau\" data-index=\"0\"></div>\n\t\t\t<div class=\"card-zone card-zone--tableau\" id=\"tableau-1\" data-zone=\"tableau\" data-index=\"1\"></div>\n\t\t\t<div class=\"card-zone card-zone--tableau\" id=\"tableau-2\" data-zone=\"tableau\" data-index=\"2\"></div>\n\t\t\t<div class=\"card-zone card-zone--tableau\" id=\"tableau-3\" data-zone=\"tableau\" data-index=\"3\"></div>\n\t\t\t<div class=\"card-zone card-zone--tableau\" id=\"tableau-4\" data-zone=\"tableau\" data-index=\"4\"></div>\n\t\t\t<div class=\"card-zone card-zone--tableau\" id=\"tableau-5\" data-zone=\"tableau\" data-index=\"5\"></div>\n\t\t\t<div class=\"card-zone card-zone--tableau\" id=\"tableau-6\" data-zone=\"tableau\" data-index=\"6\"></div>\n\t\t</div>\n\t</div>\n\n\t<div class=\"solitaire__win-overlay\" id=\"winOverlay\">\n\t\t<div class=\"solitaire__win-card\">\n\t\t\t<h2 class=\"solitaire__win-title\">You Win!</h2>\n\t\t\t<div class=\"solitaire__win-stats\">\n\t\t\t\t<div><span>Moves:</span> <strong id=\"winMoves\">0</strong></div>\n\t\t\t\t<div><span>Time:</span> <strong id=\"winTime\">0:00</strong></div>\n\t\t\t\t<div><span>Score:</span> <strong id=\"winScore\">0</strong></div>\n\t\t\t</div>\n\t\t\t<button class=\"solitaire__new-game\" id=\"winNewGame\">Deal Again</button>\n\t\t</div>\n\t</div>\n</div>\n  <script type=\"module\" src=\"main.js\"></script>\n</body>\n</html>"},{"name":"main.js","content":"const SUITS = ['♠', '♥', '♦', '♣'];\nconst SUIT_NAMES = ['spades', 'hearts', 'diamonds', 'clubs'];\nconst RANKS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];\nconst RED_SUITS = ['♥', '♦'];\n\nlet state = {\n\tstock: [],\n\twaste: [],\n\tfoundations: [\n\t\t[],\n\t\t[],\n\t\t[],\n\t\t[]\n\t],\n\ttableau: [\n\t\t[],\n\t\t[],\n\t\t[],\n\t\t[],\n\t\t[],\n\t\t[],\n\t\t[]\n\t],\n\tmoves: 0,\n\tscore: 0,\n\ttime: 0,\n\ttimerInterval: null,\n\tgameStarted: false,\n\tdragging: null\n};\n\nfunction createDeck() {\n\tconst deck = [];\n\tfor (let s = 0; s < 4; s++) {\n\t\tfor (let r = 0; r < 13; r++) {\n\t\t\tdeck.push({\n\t\t\t\tsuit: SUITS[s],\n\t\t\t\tsuitName: SUIT_NAMES[s],\n\t\t\t\trank: RANKS[r],\n\t\t\t\tvalue: r,\n\t\t\t\tisRed: RED_SUITS.includes(SUITS[s]),\n\t\t\t\tfaceUp: false,\n\t\t\t\tid: `${RANKS[r]}-${SUIT_NAMES[s]}`\n\t\t\t});\n\t\t}\n\t}\n\treturn deck;\n}\n\nfunction shuffle(arr) {\n\tfor (let i = arr.length - 1; i > 0; i--) {\n\t\tconst j = Math.floor(Math.random() * (i + 1));\n\t\t[arr[i], arr[j]] = [arr[j], arr[i]];\n\t}\n\treturn arr;\n}\n\nfunction getDealIndex(col, row) {\n\tlet index = 0;\n\tfor (let r = 0; r < row; r++) index += (7 - r);\n\treturn index + (col - row);\n}\n\nfunction deal() {\n\tconst deck = shuffle(createDeck());\n\tstate.tableau = [\n\t\t[],\n\t\t[],\n\t\t[],\n\t\t[],\n\t\t[],\n\t\t[],\n\t\t[]\n\t];\n\tstate.foundations = [\n\t\t[],\n\t\t[],\n\t\t[],\n\t\t[]\n\t];\n\tstate.waste = [];\n\tstate.moves = 0;\n\tstate.score = 0;\n\tstate.time = 0;\n\tstate.gameStarted = false;\n\tclearInterval(state.timerInterval);\n\tdocument.getElementById('timer').textContent = '0:00';\n\tdocument.getElementById('moveCount').textContent = '0';\n\tdocument.getElementById('score').textContent = '0';\n\tdocument.getElementById('winOverlay').classList.remove('solitaire__win-overlay--visible');\n\n\tlet idx = 0;\n\tfor (let col = 0; col < 7; col++) {\n\t\tfor (let row = 0; row <= col; row++) {\n\t\t\tconst card = deck[idx++];\n\t\t\tcard.faceUp = row === col;\n\t\t\tstate.tableau[col].push(card);\n\t\t}\n\t}\n\tstate.stock = deck.slice(idx).reverse();\n\tstate.dealing = true;\n\trender();\n\tsetTimeout(() => {\n\t\tstate.dealing = false;\n\t}, 2400);\n}\n\nfunction startTimer() {\n\tif (state.gameStarted) return;\n\tstate.gameStarted = true;\n\tstate.timerInterval = setInterval(() => {\n\t\tstate.time++;\n\t\tconst m = Math.floor(state.time / 60);\n\t\tconst s = state.time % 60;\n\t\tdocument.getElementById('timer').textContent = `${m}:${s.toString().padStart(2, '0')}`;\n\t}, 1000);\n}\n\nfunction addScore(pts) {\n\tstate.score = Math.max(0, state.score + pts);\n\tdocument.getElementById('score').textContent = state.score;\n}\n\nfunction addMove() {\n\tstate.moves++;\n\tdocument.getElementById('moveCount').textContent = state.moves;\n}\n\nfunction createCardEl(card, stackIndex, totalInStack) {\n\tconst el = document.createElement('div');\n\tel.className = `card ${card.faceUp ? 'card--face-up' : 'card--face-down'}`;\n\tif (card.faceUp) {\n\t\tel.classList.add(card.isRed ? 'card--red' : 'card--black');\n\t\tel.innerHTML = `\n      <div class=\"card__corner card__corner--top\">\n        <span class=\"card__rank\">${card.rank}</span>\n        <span class=\"card__suit\">${card.suit}</span>\n      </div>\n      <span class=\"card__center-suit\">${card.suit}</span>\n      <div class=\"card__corner card__corner--bottom\">\n        <span class=\"card__rank\">${card.rank}</span>\n        <span class=\"card__suit\">${card.suit}</span>\n      </div>\n    `;\n\t}\n\tel.dataset.cardId = card.id;\n\tel.style.setProperty('--z', stackIndex + 1);\n\treturn el;\n}\n\nfunction render() {\n\t// Stock\n\tconst stockEl = document.getElementById('stock');\n\tstockEl.querySelectorAll('.card').forEach(c => c.remove());\n\tif (state.stock.length > 0) {\n\t\tstockEl.classList.remove('card-zone--empty');\n\t\tconst cardEl = document.createElement('div');\n\t\tcardEl.className = 'card card--face-down';\n\t\tif (state.dealing) {\n\t\t\tcardEl.classList.add('card--deal-stock');\n\t\t\tcardEl.style.setProperty('--deal-delay', '0ms');\n\t\t}\n\t\tcardEl.style.setProperty('--z', 1);\n\t\tstockEl.appendChild(cardEl);\n\t} else {\n\t\tstockEl.classList.add('card-zone--empty');\n\t}\n\n\t// Waste\n\tconst wasteEl = document.getElementById('waste');\n\twasteEl.querySelectorAll('.card').forEach(c => c.remove());\n\tif (state.waste.length > 0) {\n\t\tconst card = state.waste[state.waste.length - 1];\n\t\tconst cardEl = createCardEl(card, 0, 1);\n\t\twasteEl.appendChild(cardEl);\n\t}\n\n\t// Foundations\n\tfor (let i = 0; i < 4; i++) {\n\t\tconst fEl = document.getElementById(`foundation-${i}`);\n\t\tfEl.querySelectorAll('.card').forEach(c => c.remove());\n\t\tconst pile = state.foundations[i];\n\t\tif (pile.length > 0) {\n\t\t\tconst card = pile[pile.length - 1];\n\t\t\tconst cardEl = createCardEl(card, 0, 1);\n\t\t\tfEl.appendChild(cardEl);\n\t\t}\n\t}\n\n\t// Tableau\n\tfor (let col = 0; col < 7; col++) {\n\t\tconst tEl = document.getElementById(`tableau-${col}`);\n\t\ttEl.querySelectorAll('.card').forEach(c => c.remove());\n\t\tconst pile = state.tableau[col];\n\t\tpile.forEach((card, idx) => {\n\t\t\tconst cardEl = createCardEl(card, idx, pile.length);\n\t\t\tconst offset = card.faceUp ?\n\t\t\t\tpile.slice(0, idx).reduce((sum, c) => sum + (c.faceUp ? 1 : 0.4), 0) :\n\t\t\t\tpile.slice(0, idx).filter(c => !c.faceUp).length * 0.4;\n\t\t\tcardEl.style.setProperty('--top', `calc(var(--stack-offset) * ${offset.toFixed(2)})`);\n\t\t\tif (state.dealing) {\n\t\t\t\tconst dealIdx = getDealIndex(col, idx);\n\t\t\t\tcardEl.classList.add('card--dealing');\n\t\t\t\tcardEl.style.setProperty('--deal-delay', `${dealIdx * 70}ms`);\n\t\t\t}\n\t\t\ttEl.appendChild(cardEl);\n\t\t});\n\t}\n}\n\nfunction findCard(cardId) {\n\tfor (let col = 0; col < 7; col++) {\n\t\tconst idx = state.tableau[col].findIndex(c => c.id === cardId);\n\t\tif (idx !== -1) return {\n\t\t\tzone: 'tableau',\n\t\t\tindex: col,\n\t\t\tcardIndex: idx\n\t\t};\n\t}\n\tif (state.waste.length && state.waste[state.waste.length - 1].id === cardId) {\n\t\treturn {\n\t\t\tzone: 'waste',\n\t\t\tindex: 0,\n\t\t\tcardIndex: state.waste.length - 1\n\t\t};\n\t}\n\tfor (let i = 0; i < 4; i++) {\n\t\tconst pile = state.foundations[i];\n\t\tif (pile.length && pile[pile.length - 1].id === cardId) {\n\t\t\treturn {\n\t\t\t\tzone: 'foundation',\n\t\t\t\tindex: i,\n\t\t\t\tcardIndex: pile.length - 1\n\t\t\t};\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction getCards(loc) {\n\tif (loc.zone === 'tableau') return state.tableau[loc.index].slice(loc.cardIndex);\n\tif (loc.zone === 'waste') return [state.waste[state.waste.length - 1]];\n\tif (loc.zone === 'foundation') return [state.foundations[loc.index][state.foundations[loc.index].length - 1]];\n\treturn [];\n}\n\nfunction canPlaceOnTableau(cards, targetCol) {\n\tconst pile = state.tableau[targetCol];\n\tconst movingCard = cards[0];\n\tif (pile.length === 0) {\n\t\treturn movingCard.value === 12; // King\n\t}\n\tconst topCard = pile[pile.length - 1];\n\tif (!topCard.faceUp) return false;\n\treturn topCard.isRed !== movingCard.isRed && topCard.value === movingCard.value + 1;\n}\n\nfunction canPlaceOnFoundation(card, foundationIdx) {\n\tconst pile = state.foundations[foundationIdx];\n\tif (pile.length === 0) {\n\t\treturn card.value === 0; // Ace\n\t}\n\tconst topCard = pile[pile.length - 1];\n\treturn topCard.suit === card.suit && topCard.value === card.value - 1;\n}\n\nfunction moveCards(from, toZone, toIndex) {\n\tstartTimer();\n\tconst cards = getCards(from);\n\tif (cards.length === 0) return false;\n\n\tif (toZone === 'tableau') {\n\t\tif (!canPlaceOnTableau(cards, toIndex)) return false;\n\t\t// Remove from source\n\t\tif (from.zone === 'tableau') {\n\t\t\tstate.tableau[from.index].splice(from.cardIndex);\n\t\t\tflipTopCard(from.index);\n\t\t\tif (from.index !== toIndex) addScore(5);\n\t\t} else if (from.zone === 'waste') {\n\t\t\tstate.waste.pop();\n\t\t\taddScore(5);\n\t\t} else if (from.zone === 'foundation') {\n\t\t\tstate.foundations[from.index].pop();\n\t\t\taddScore(-15);\n\t\t}\n\t\tstate.tableau[toIndex].push(...cards);\n\t\taddMove();\n\t\tSFX.drop();\n\t\trender();\n\t\treturn true;\n\t}\n\n\tif (toZone === 'foundation' && cards.length === 1) {\n\t\tif (!canPlaceOnFoundation(cards[0], toIndex)) return false;\n\t\tif (from.zone === 'tableau') {\n\t\t\tstate.tableau[from.index].splice(from.cardIndex);\n\t\t\tflipTopCard(from.index);\n\t\t} else if (from.zone === 'waste') {\n\t\t\tstate.waste.pop();\n\t\t} else if (from.zone === 'foundation') {\n\t\t\tstate.foundations[from.index].pop();\n\t\t}\n\t\tstate.foundations[toIndex].push(cards[0]);\n\t\taddMove();\n\t\taddScore(10);\n\t\tSFX.foundation();\n\t\trender();\n\t\tcheckWin();\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nfunction flipTopCard(col) {\n\tconst pile = state.tableau[col];\n\tif (pile.length > 0 && !pile[pile.length - 1].faceUp) {\n\t\tpile[pile.length - 1].faceUp = true;\n\t\taddScore(5);\n\t\tSFX.flip();\n\t}\n}\n\nfunction drawFromStock() {\n\tstartTimer();\n\tif (state.stock.length === 0) {\n\t\tif (state.waste.length === 0) return;\n\t\tstate.stock = state.waste.reverse();\n\t\tstate.stock.forEach(c => c.faceUp = false);\n\t\tstate.waste = [];\n\t\taddScore(-20);\n\t\tSFX.recycle();\n\t} else {\n\t\tconst card = state.stock.pop();\n\t\tcard.faceUp = true;\n\t\tstate.waste.push(card);\n\t\tSFX.draw();\n\t}\n\taddMove();\n\trender();\n}\n\nfunction tryAutoFoundation(cardId) {\n\tconst loc = findCard(cardId);\n\tif (!loc) return false;\n\tconst cards = getCards(loc);\n\tif (cards.length !== 1) return false;\n\tconst card = cards[0];\n\n\tfor (let i = 0; i < 4; i++) {\n\t\tif (canPlaceOnFoundation(card, i)) {\n\t\t\treturn moveCards(loc, 'foundation', i);\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction checkWin() {\n\tconst total = state.foundations.reduce((sum, f) => sum + f.length, 0);\n\tif (total === 52) {\n\t\tclearInterval(state.timerInterval);\n\t\tdocument.getElementById('winMoves').textContent = state.moves;\n\t\tdocument.getElementById('winTime').textContent = document.getElementById('timer').textContent;\n\t\tdocument.getElementById('winScore').textContent = state.score;\n\t\tSFX.win();\n\t\tsetTimeout(() => {\n\t\t\tdocument.getElementById('winOverlay').classList.add('solitaire__win-overlay--visible');\n\t\t}, 300);\n\t}\n}\n\n// Drag and drop\nlet dragState = null;\n\nfunction getCardElement(target) {\n\treturn target.closest('.card--face-up');\n}\n\nfunction getZoneElement(target) {\n\treturn target.closest('[data-zone]');\n}\n\nconst board = document.getElementById('board');\n\nboard.addEventListener('mousedown', handleDragStart);\nboard.addEventListener('touchstart', handleDragStart, {\n\tpassive: false\n});\n\nfunction handleDragStart(e) {\n\tconst touch = e.touches ? e.touches[0] : e;\n\tconst cardEl = getCardElement(touch.target || e.target);\n\tif (!cardEl) return;\n\n\tconst cardId = cardEl.dataset.cardId;\n\tconst loc = findCard(cardId);\n\tif (!loc) return;\n\n\tconst cards = getCards(loc);\n\tif (cards.length === 0) return;\n\n\t// Collect card elements to drag\n\tconst zoneEl = cardEl.closest('[data-zone]');\n\tconst allCards = [...zoneEl.querySelectorAll('.card')];\n\tconst cardIdx = allCards.indexOf(cardEl);\n\tconst dragEls = allCards.slice(cardIdx).filter(el => el.classList.contains('card--face-up'));\n\n\tif (dragEls.length === 0) return;\n\tif (e.touches) e.preventDefault();\n\n\tconst rect = cardEl.getBoundingClientRect();\n\tconst offsetX = touch.clientX - rect.left;\n\tconst offsetY = touch.clientY - rect.top;\n\n\tdragState = {\n\t\tloc,\n\t\tcards,\n\t\telements: dragEls,\n\t\toffsetX,\n\t\toffsetY,\n\t\tstartX: touch.clientX,\n\t\tstartY: touch.clientY,\n\t\tmoved: false,\n\t\toriginalPositions: dragEls.map(el => ({\n\t\t\tparent: el.parentElement,\n\t\t\ttop: el.style.getPropertyValue('--top'),\n\t\t\tz: el.style.getPropertyValue('--z')\n\t\t}))\n\t};\n\n\tdragEls.forEach((el, i) => {\n\t\tif (i === 0) el.classList.add('card--dragging');\n\t\telse el.classList.add('card--dragging-child');\n\t});\n\n\tdocument.addEventListener('mousemove', handleDragMove);\n\tdocument.addEventListener('touchmove', handleDragMove, {\n\t\tpassive: false\n\t});\n\tdocument.addEventListener('mouseup', handleDragEnd);\n\tdocument.addEventListener('touchend', handleDragEnd);\n}\n\nfunction handleDragMove(e) {\n\tif (!dragState) return;\n\tif (e.touches) e.preventDefault();\n\tconst touch = e.touches ? e.touches[0] : e;\n\n\tconst dx = touch.clientX - dragState.startX;\n\tconst dy = touch.clientY - dragState.startY;\n\n\tif (!dragState.moved && Math.abs(dx) < 3 && Math.abs(dy) < 3) return;\n\tdragState.moved = true;\n\n\tdragState.elements.forEach((el, i) => {\n\t\tconst origRect = dragState.originalPositions[i];\n\t\tel.style.position = 'fixed';\n\t\tel.style.left = `${touch.clientX - dragState.offsetX}px`;\n\t\tel.style.top = `${touch.clientY - dragState.offsetY + i * 25}px`;\n\t\tel.style.setProperty('--z', 10000 + i);\n\t\tel.style.setProperty('--top', '0px');\n\t});\n\n\t// Highlight drop targets\n\tdocument.querySelectorAll('.card-zone--highlight').forEach(el => el.classList.remove('card-zone--highlight'));\n\tconst elemBelow = document.elementFromPoint(touch.clientX, touch.clientY);\n\tif (elemBelow) {\n\t\tconst zone = elemBelow.closest('[data-zone]');\n\t\tif (zone) {\n\t\t\tconst zType = zone.dataset.zone;\n\t\t\tconst zIdx = parseInt(zone.dataset.index);\n\t\t\tlet valid = false;\n\t\t\tif (zType === 'tableau') valid = canPlaceOnTableau(dragState.cards, zIdx);\n\t\t\telse if (zType === 'foundation' && dragState.cards.length === 1) valid = canPlaceOnFoundation(dragState.cards[0], zIdx);\n\t\t\tif (valid) zone.classList.add('card-zone--highlight');\n\t\t}\n\t}\n}\n\nfunction handleDragEnd(e) {\n\tif (!dragState) return;\n\n\tdocument.removeEventListener('mousemove', handleDragMove);\n\tdocument.removeEventListener('touchmove', handleDragMove);\n\tdocument.removeEventListener('mouseup', handleDragEnd);\n\tdocument.removeEventListener('touchend', handleDragEnd);\n\tdocument.querySelectorAll('.card-zone--highlight').forEach(el => el.classList.remove('card-zone--highlight'));\n\n\tconst touch = e.changedTouches ? e.changedTouches[0] : e;\n\n\tif (!dragState.moved) {\n\t\t// It was a click — try auto-foundation on double-click behavior\n\t\tresetDragVisuals();\n\t\tconst cardId = dragState.cards[0].id;\n\t\ttryAutoFoundation(cardId);\n\t\tdragState = null;\n\t\treturn;\n\t}\n\n\t// Find drop target\n\t// Temporarily hide dragged elements to find what's underneath\n\tdragState.elements.forEach(el => el.style.display = 'none');\n\tconst elemBelow = document.elementFromPoint(touch.clientX, touch.clientY);\n\tdragState.elements.forEach(el => el.style.display = '');\n\n\tlet dropped = false;\n\tif (elemBelow) {\n\t\tconst zone = elemBelow.closest('[data-zone]');\n\t\tif (zone) {\n\t\t\tconst zType = zone.dataset.zone;\n\t\t\tconst zIdx = parseInt(zone.dataset.index);\n\t\t\tif (zType === 'tableau' || zType === 'foundation') {\n\t\t\t\tdropped = moveCards(dragState.loc, zType, zIdx);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!dropped) {\n\t\tresetDragVisuals();\n\t\trender();\n\t}\n\n\tdragState = null;\n}\n\nfunction resetDragVisuals() {\n\tif (!dragState) return;\n\tdragState.elements.forEach((el, i) => {\n\t\tel.classList.remove('card--dragging', 'card--dragging-child');\n\t\tel.style.position = '';\n\t\tel.style.left = '';\n\t\tel.style.top = '';\n\t\tel.style.setProperty('--z', dragState.originalPositions[i].z);\n\t\tel.style.setProperty('--top', dragState.originalPositions[i].top);\n\t});\n}\n\n// Stock click\ndocument.getElementById('stock').addEventListener('click', (e) => {\n\tif (!e.target.closest('.card--face-up')) {\n\t\tdrawFromStock();\n\t}\n});\n\n// Double-click for auto-foundation\nboard.addEventListener('dblclick', (e) => {\n\tconst cardEl = getCardElement(e.target);\n\tif (!cardEl) return;\n\ttryAutoFoundation(cardEl.dataset.cardId);\n});\n\n// Autoplay button\ndocument.getElementById('autoPlayBtn').addEventListener('click', () => {\n\tif (autoPlayInterval) stopAutoPlay();\n\telse startAutoPlay();\n});\n\n// New game buttons\ndocument.getElementById('newGameBtn').addEventListener('click', () => {\n\tstopAutoPlay();\n\tdeal();\n});\ndocument.getElementById('winNewGame').addEventListener('click', () => {\n\tstopAutoPlay();\n\tdeal();\n});\n\n// ─── Autoplay ────────────────────────────────────────────────────────────────\nlet autoPlayInterval = null;\n\nfunction boardStateKey() {\n\tconst t = state.tableau.map(p => p.map(c => (c.faceUp ? c.id : '?')).join(',')).join('|');\n\tconst f = state.foundations.map(p => p.length ? p[p.length - 1].id : '').join('|');\n\tconst w = state.waste.length ? state.waste[state.waste.length - 1].id : '';\n\tconst s = state.stock.length;\n\treturn `${t}/${f}/${w}/${s}`;\n}\n\nfunction findBestMove() {\n\t// 1. Move to foundation (always safe, always progress)\n\tconst wasteTop = state.waste.length ? [{\n\t\tzone: 'waste',\n\t\tindex: 0,\n\t\tcardIndex: state.waste.length - 1\n\t}] : [];\n\tconst tableauTops = state.tableau.map((pile, col) =>\n\t\tpile.length ? {\n\t\t\tzone: 'tableau',\n\t\t\tindex: col,\n\t\t\tcardIndex: pile.length - 1\n\t\t} : null\n\t).filter(Boolean);\n\n\tfor (const loc of [...wasteTop, ...tableauTops]) {\n\t\tconst cards = getCards(loc);\n\t\tif (cards.length !== 1) continue;\n\t\tfor (let fi = 0; fi < 4; fi++) {\n\t\t\tif (canPlaceOnFoundation(cards[0], fi)) return {\n\t\t\t\tfrom: loc,\n\t\t\t\ttoZone: 'foundation',\n\t\t\t\ttoIndex: fi\n\t\t\t};\n\t\t}\n\t}\n\n\t// 2. Tableau moves that uncover a face-down card (genuine progress)\n\tfor (let col = 0; col < 7; col++) {\n\t\tconst pile = state.tableau[col];\n\t\tif (!pile.length) continue;\n\t\tconst firstFaceUp = pile.findIndex(c => c.faceUp);\n\t\tif (firstFaceUp <= 0) continue; // no face-down cards beneath\n\t\tconst cards = pile.slice(firstFaceUp);\n\t\tfor (let dest = 0; dest < 7; dest++) {\n\t\t\tif (dest === col) continue;\n\t\t\tif (state.tableau[dest].length === 0) continue; // don't move to empty unless king\n\t\t\tif (canPlaceOnTableau(cards, dest)) {\n\t\t\t\treturn {\n\t\t\t\t\tfrom: {\n\t\t\t\t\t\tzone: 'tableau',\n\t\t\t\t\t\tindex: col,\n\t\t\t\t\t\tcardIndex: firstFaceUp\n\t\t\t\t\t},\n\t\t\t\t\ttoZone: 'tableau',\n\t\t\t\t\ttoIndex: dest\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\t// 3. Move king to empty column only if it uncovers a face-down card\n\tfor (let col = 0; col < 7; col++) {\n\t\tconst pile = state.tableau[col];\n\t\tif (!pile.length) continue;\n\t\tconst firstFaceUp = pile.findIndex(c => c.faceUp);\n\t\tif (firstFaceUp <= 0) continue;\n\t\tconst cards = pile.slice(firstFaceUp);\n\t\tif (cards[0].value !== 12) continue; // must be a king\n\t\tfor (let dest = 0; dest < 7; dest++) {\n\t\t\tif (dest === col) continue;\n\t\t\tif (state.tableau[dest].length !== 0) continue;\n\t\t\treturn {\n\t\t\t\tfrom: {\n\t\t\t\t\tzone: 'tableau',\n\t\t\t\t\tindex: col,\n\t\t\t\t\tcardIndex: firstFaceUp\n\t\t\t\t},\n\t\t\t\ttoZone: 'tableau',\n\t\t\t\ttoIndex: dest\n\t\t\t};\n\t\t}\n\t}\n\n\t// 4. Move from waste to tableau (only if it lands on a face-up card, not empty)\n\tif (state.waste.length) {\n\t\tconst loc = {\n\t\t\tzone: 'waste',\n\t\t\tindex: 0,\n\t\t\tcardIndex: state.waste.length - 1\n\t\t};\n\t\tconst cards = getCards(loc);\n\t\tfor (let dest = 0; dest < 7; dest++) {\n\t\t\tif (state.tableau[dest].length === 0) continue;\n\t\t\tif (canPlaceOnTableau(cards, dest)) return {\n\t\t\t\tfrom: loc,\n\t\t\t\ttoZone: 'tableau',\n\t\t\t\ttoIndex: dest\n\t\t\t};\n\t\t}\n\t}\n\n\t// 5. Draw from stock\n\tif (state.stock.length > 0 || state.waste.length > 0) {\n\t\treturn {\n\t\t\taction: 'draw'\n\t\t};\n\t}\n\n\treturn null;\n}\n\nfunction autoStep() {\n\tconst keyBefore = boardStateKey();\n\tconst move = findBestMove();\n\tif (!move) {\n\t\tstopAutoPlay();\n\t\treturn;\n\t}\n\n\tif (move.action === 'draw') {\n\t\tdrawFromStock();\n\t} else {\n\t\tmoveCards(move.from, move.toZone, move.toIndex);\n\t}\n\n\tconst keyAfter = boardStateKey();\n\tif (keyAfter === keyBefore) {\n\t\t// Nothing actually changed — we're stuck\n\t\tstopAutoPlay();\n\t\treturn;\n\t}\n\t// If we've seen this state before, we're looping — stop\n\tif (autoVisited.has(keyAfter)) {\n\t\tstopAutoPlay();\n\t\treturn;\n\t}\n\tautoVisited.add(keyAfter);\n}\n\nfunction startAutoPlay() {\n\tif (autoPlayInterval) return;\n\tconst btn = document.getElementById('autoPlayBtn');\n\tbtn.classList.add('is-playing');\n\tbtn.querySelector('i,svg').setAttribute('data-lucide', 'zap-off');\n\tlucide.createIcons();\n\tautoPlayInterval = setInterval(autoStep, 320);\n}\n\nfunction stopAutoPlay() {\n\tclearInterval(autoPlayInterval);\n\tautoPlayInterval = null;\n\tconst btn = document.getElementById('autoPlayBtn');\n\tbtn.classList.remove('is-playing');\n\tbtn.querySelector('i,svg').setAttribute('data-lucide', 'zap');\n\tlucide.createIcons();\n}\n// ─────────────────────────────────────────────────────────────────────────────\n\n// ─── Sound Engine ───────────────────────────────────────────────────────────\nconst Audio = window.AudioContext || window.webkitAudioContext;\nlet audioCtx = null;\n\nfunction getAudioCtx() {\n\tif (!audioCtx) audioCtx = new Audio();\n\treturn audioCtx;\n}\n\nfunction playTone({\n\ttype = 'sine',\n\tfreq = 440,\n\tfreq2 = null,\n\tgain = 0.18,\n\tattack = 0.005,\n\tdecay = 0.08,\n\tduration = 0.12\n} = {}) {\n\ttry {\n\t\tconst ctx = getAudioCtx();\n\t\tconst osc = ctx.createOscillator();\n\t\tconst gainNode = ctx.createGain();\n\t\tosc.connect(gainNode);\n\t\tgainNode.connect(ctx.destination);\n\t\tosc.type = type;\n\t\tosc.frequency.setValueAtTime(freq, ctx.currentTime);\n\t\tif (freq2) osc.frequency.linearRampToValueAtTime(freq2, ctx.currentTime + duration);\n\t\tgainNode.gain.setValueAtTime(0, ctx.currentTime);\n\t\tgainNode.gain.linearRampToValueAtTime(gain, ctx.currentTime + attack);\n\t\tgainNode.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + attack + decay);\n\t\tosc.start(ctx.currentTime);\n\t\tosc.stop(ctx.currentTime + attack + decay + 0.01);\n\t} catch (e) {}\n}\n\nfunction playNoise({\n\tgain = 0.07,\n\tduration = 0.04\n} = {}) {\n\ttry {\n\t\tconst ctx = getAudioCtx();\n\t\tconst bufSize = ctx.sampleRate * duration;\n\t\tconst buf = ctx.createBuffer(1, bufSize, ctx.sampleRate);\n\t\tconst data = buf.getChannelData(0);\n\t\tfor (let i = 0; i < bufSize; i++) data[i] = (Math.random() * 2 - 1);\n\t\tconst src = ctx.createBufferSource();\n\t\tsrc.buffer = buf;\n\t\tconst gainNode = ctx.createGain();\n\t\tconst filter = ctx.createBiquadFilter();\n\t\tfilter.type = 'bandpass';\n\t\tfilter.frequency.value = 1800;\n\t\tfilter.Q.value = 0.8;\n\t\tsrc.connect(filter);\n\t\tfilter.connect(gainNode);\n\t\tgainNode.connect(ctx.destination);\n\t\tgainNode.gain.setValueAtTime(gain, ctx.currentTime);\n\t\tgainNode.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + duration);\n\t\tsrc.start(ctx.currentTime);\n\t\tsrc.stop(ctx.currentTime + duration + 0.01);\n\t} catch (e) {}\n}\n\nconst SFX = {\n\tdraw() {\n\t\tplayNoise({\n\t\t\tgain: 0.10,\n\t\t\tduration: 0.035\n\t\t});\n\t\tplayTone({\n\t\t\ttype: 'triangle',\n\t\t\tfreq: 520,\n\t\t\tfreq2: 440,\n\t\t\tgain: 0.07,\n\t\t\tdecay: 0.07\n\t\t});\n\t},\n\tflip() {\n\t\tplayNoise({\n\t\t\tgain: 0.07,\n\t\t\tduration: 0.03\n\t\t});\n\t\tplayTone({\n\t\t\ttype: 'triangle',\n\t\t\tfreq: 480,\n\t\t\tfreq2: 560,\n\t\t\tgain: 0.06,\n\t\t\tdecay: 0.06\n\t\t});\n\t},\n\tdrop() {\n\t\tplayNoise({\n\t\t\tgain: 0.13,\n\t\t\tduration: 0.04\n\t\t});\n\t\tplayTone({\n\t\t\ttype: 'sine',\n\t\t\tfreq: 300,\n\t\t\tfreq2: 260,\n\t\t\tgain: 0.12,\n\t\t\tdecay: 0.10\n\t\t});\n\t},\n\tfoundation() {\n\t\tplayTone({\n\t\t\ttype: 'sine',\n\t\t\tfreq: 660,\n\t\t\tgain: 0.15,\n\t\t\tdecay: 0.12\n\t\t});\n\t\tplayTone({\n\t\t\ttype: 'sine',\n\t\t\tfreq: 880,\n\t\t\tgain: 0.10,\n\t\t\tdecay: 0.10\n\t\t});\n\t\tplayNoise({\n\t\t\tgain: 0.06,\n\t\t\tduration: 0.03\n\t\t});\n\t},\n\trecycle() {\n\t\tplayTone({\n\t\t\ttype: 'sine',\n\t\t\tfreq: 320,\n\t\t\tfreq2: 200,\n\t\t\tgain: 0.12,\n\t\t\tdecay: 0.18\n\t\t});\n\t\tplayNoise({\n\t\t\tgain: 0.08,\n\t\t\tduration: 0.06\n\t\t});\n\t},\n\twin() {\n\t\tconst ctx = getAudioCtx();\n\t\tconst notes = [523, 659, 784, 1047];\n\t\tnotes.forEach((freq, i) => {\n\t\t\tconst osc = ctx.createOscillator();\n\t\t\tconst g = ctx.createGain();\n\t\t\tosc.connect(g);\n\t\t\tg.connect(ctx.destination);\n\t\t\tosc.type = 'sine';\n\t\t\tosc.frequency.setValueAtTime(freq, ctx.currentTime);\n\t\t\tconst t = ctx.currentTime + i * 0.13;\n\t\t\tg.gain.setValueAtTime(0, t);\n\t\t\tg.gain.linearRampToValueAtTime(0.18, t + 0.01);\n\t\t\tg.gain.exponentialRampToValueAtTime(0.0001, t + 0.35);\n\t\t\tosc.start(t);\n\t\t\tosc.stop(t + 0.36);\n\t\t});\n\t}\n};\n// ────────────────────────────────────────────────────────────────────────────\n\n// Initialize\nlucide.createIcons();\ndeal();"},{"name":"style.css","content":":root {\n\tcolor-scheme: dark;\n\tfont-family: 'Segoe UI', system-ui, sans-serif;\n\t--felt: #1a5c38;\n\t--felt-dark: #0f3d24;\n\t--felt-light: #237a4b;\n\t--gold: #c9a84c;\n\t--gold-dim: #8a7235;\n\t--card-white: #f5f0e8;\n\t--card-shadow: rgba(0, 0, 0, 0.4);\n\t--red-suit: #c0392b;\n\t--black-suit: #1a1a2e;\n\t--card-w: min(11vw, 100px);\n\t--card-h: calc(var(--card-w) * 1.45);\n\t--card-radius: clamp(4px, 0.6vw, 8px);\n\t--stack-offset: calc(var(--card-h) * 0.18);\n\t--stack-offset-hidden: calc(var(--card-h) * 0.07);\n}\n\n* {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tpadding: 0;\n}\n\nbody {\n\tbackground: var(--felt-dark);\n\toverflow: hidden;\n\theight: 100dvh;\n\tuser-select: none;\n\t-webkit-user-select: none;\n}\n\n.solitaire {\n\theight: 100dvh;\n\tdisplay: flex;\n\tflex-direction: column;\n\tbackground:\n\t\tradial-gradient(ellipse at 50% 30%, var(--felt-light) 0%, var(--felt) 50%, var(--felt-dark) 100%);\n}\n\n/* Header */\n.solitaire__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 10px clamp(12px, 3vw, 24px);\n\tbackground: rgba(0, 0, 0, 0.3);\n\tbackdrop-filter: blur(8px);\n\tborder-bottom: 1px solid rgba(201, 168, 76, 0.2);\n\tflex-shrink: 0;\n}\n\n.solitaire__brand {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.solitaire__icon {\n\twidth: 22px;\n\theight: 22px;\n\tcolor: var(--gold);\n}\n\n.solitaire__title {\n\tfont-size: clamp(16px, 2.5vw, 22px);\n\tfont-weight: 700;\n\tcolor: var(--gold);\n\tletter-spacing: 0.05em;\n\ttext-transform: uppercase;\n}\n\n.solitaire__stats {\n\tdisplay: flex;\n\tgap: clamp(12px, 3vw, 32px);\n}\n\n.solitaire__stat {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 1px;\n}\n\n.solitaire__stat-label {\n\tfont-size: 10px;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.08em;\n\tcolor: rgba(255, 255, 255, 0.4);\n}\n\n.solitaire__stat-value {\n\tfont-size: clamp(14px, 2vw, 18px);\n\tfont-weight: 700;\n\tcolor: #fff;\n\tfont-variant-numeric: tabular-nums;\n}\n\n.solitaire__new-game {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 8px 16px;\n\tbackground: rgba(201, 168, 76, 0.15);\n\tborder: 1px solid var(--gold-dim);\n\tborder-radius: 6px;\n\tcolor: var(--gold);\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tcursor: pointer;\n\ttransition: all 0.2s;\n\twhite-space: nowrap;\n}\n\n.solitaire__auto-btn {\n\tborder-color: #4a9eff55;\n\tcolor: #4a9eff;\n\tbackground: rgba(74, 158, 255, 0.10);\n}\n\n.solitaire__auto-btn:hover {\n\tbackground: rgba(74, 158, 255, 0.22) !important;\n\tborder-color: #4a9eff !important;\n\tcolor: #4a9eff !important;\n}\n\n.solitaire__auto-btn.is-playing {\n\tbackground: rgba(74, 158, 255, 0.22);\n\tborder-color: #4a9eff;\n\tcolor: #4a9eff;\n\tanimation: autoPulse 1s ease-in-out infinite;\n}\n\n@keyframes autoPulse {\n\t0%, 100% {\n\t\tbox-shadow: 0 0 0 0 rgba(74, 158, 255, 0.35);\n\t}\n\n\t50% {\n\t\tbox-shadow: 0 0 0 6px rgba(74, 158, 255, 0);\n\t}\n}\n\n.solitaire__new-game:hover {\n\tbackground: rgba(201, 168, 76, 0.25);\n\tborder-color: var(--gold);\n}\n\n.solitaire__new-game svg {\n\twidth: 14px;\n\theight: 14px;\n}\n\n/* Board */\n.solitaire__board {\n\tflex: 1;\n\tdisplay: flex;\n\tflex-direction: column;\n\tpadding: clamp(8px, 2vw, 20px) clamp(8px, 2vw, 24px);\n\tgap: clamp(10px, 2vw, 20px);\n\toverflow: hidden;\n}\n\n.solitaire__top-row {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tflex-shrink: 0;\n}\n\n.solitaire__stock-waste {\n\tdisplay: flex;\n\tgap: clamp(6px, 1.5vw, 14px);\n}\n\n.solitaire__foundations {\n\tdisplay: flex;\n\tgap: clamp(6px, 1.5vw, 14px);\n}\n\n/* Card zones */\n.card-zone {\n\twidth: var(--card-w);\n\tmin-height: var(--card-h);\n\tposition: relative;\n\tborder-radius: var(--card-radius);\n}\n\n.card-zone--stock,\n.card-zone--waste,\n.card-zone--foundation {\n\theight: var(--card-h);\n\tbackground: rgba(0, 0, 0, 0.15);\n\tborder: 2px dashed rgba(255, 255, 255, 0.1);\n}\n\n.card-zone--stock {\n\tcursor: pointer;\n}\n\n.card-zone__label {\n\tposition: absolute;\n\tinset: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 11px;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.1em;\n\tcolor: rgba(255, 255, 255, 0.12);\n\tpointer-events: none;\n}\n\n.card-zone__suit {\n\tposition: absolute;\n\tinset: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: clamp(20px, 3vw, 32px);\n\tcolor: rgba(255, 255, 255, 0.08);\n\tpointer-events: none;\n}\n\n.card-zone--tableau {\n\tflex: 1;\n\tmin-height: calc(var(--card-h) + var(--stack-offset) * 6);\n}\n\n.solitaire__tableau {\n\tdisplay: flex;\n\tgap: clamp(4px, 1vw, 12px);\n\tflex: 1;\n\tjustify-content: center;\n}\n\n/* Cards */\n.card {\n\tposition: absolute;\n\twidth: var(--card-w);\n\theight: var(--card-h);\n\tborder-radius: var(--card-radius);\n\tcursor: pointer;\n\ttransition: box-shadow 0.15s;\n\ttransform-origin: center center;\n\tz-index: var(--z, 1);\n\ttop: var(--top, 0);\n\tleft: 0;\n}\n\n.card--face-up {\n\tbackground: var(--card-white);\n\tborder: 1px solid rgba(0, 0, 0, 0.12);\n\tbox-shadow: 0 1px 3px var(--card-shadow);\n\tdisplay: flex;\n\tflex-direction: column;\n\tpadding: clamp(2px, 0.4vw, 5px);\n}\n\n.card--face-up:hover {\n\tbox-shadow: 0 3px 10px var(--card-shadow);\n}\n\n.card--face-down {\n\tbackground:\n\t\trepeating-linear-gradient(45deg,\n\t\t\t#1a3a6e,\n\t\t\t#1a3a6e 3px,\n\t\t\t#1e4080 3px,\n\t\t\t#1e4080 6px);\n\tborder: 2px solid #264d93;\n\tbox-shadow:\n\t\t0 1px 3px var(--card-shadow),\n\t\tinset 0 0 0 3px rgba(0, 0, 0, 0.15),\n\t\tinset 0 0 0 5px rgba(100, 140, 200, 0.15);\n}\n\n.card--red {\n\tcolor: var(--red-suit);\n}\n\n.card--black {\n\tcolor: var(--black-suit);\n}\n\n.card__corner {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tline-height: 1;\n\tgap: 0;\n}\n\n.card__corner--top {\n\talign-self: flex-start;\n}\n\n.card__corner--bottom {\n\talign-self: flex-end;\n\ttransform: rotate(180deg);\n\tmargin-top: auto;\n}\n\n.card__rank {\n\tfont-size: clamp(11px, 1.6vw, 16px);\n\tfont-weight: 800;\n}\n\n.card__suit {\n\tfont-size: clamp(10px, 1.4vw, 14px);\n\tline-height: 1;\n}\n\n.card__center-suit {\n\tposition: absolute;\n\ttop: 50%;\n\tleft: 50%;\n\ttransform: translate(-50%, -50%);\n\tfont-size: clamp(22px, 3.5vw, 36px);\n\topacity: 0.3;\n}\n\n/* Dragging */\n.card--dragging {\n\tz-index: 10000 !important;\n\tbox-shadow: 0 12px 40px rgba(0, 0, 0, 0.5) !important;\n\tpointer-events: none;\n\ttransition: none;\n\ttransform: rotate(2deg);\n}\n\n.card--dragging-child {\n\tz-index: 10000 !important;\n\tpointer-events: none;\n\ttransition: none;\n}\n\n/* Drop target highlight */\n.card-zone--highlight {\n\tbox-shadow: 0 0 0 3px var(--gold), 0 0 20px rgba(201, 168, 76, 0.3);\n}\n\n/* Card dealing animation */\n.card--dealing {\n\topacity: 0;\n\tanimation: dealCard 0.4s cubic-bezier(0.22, 0.68, 0.35, 1.2) forwards;\n\tanimation-delay: var(--deal-delay, 0s);\n}\n\n@keyframes dealCard {\n\t0% {\n\t\topacity: 0;\n\t\ttransform: translateY(-40px) translateX(-20px) scale(0.8) rotate(-4deg);\n\t}\n\n\t50% {\n\t\topacity: 1;\n\t}\n\n\t75% {\n\t\ttransform: translateY(3px) translateX(0) scale(1.015) rotate(0.5deg);\n\t}\n\n\t100% {\n\t\topacity: 1;\n\t\ttransform: translateY(0) translateX(0) scale(1) rotate(0deg);\n\t}\n}\n\n.card--deal-stock {\n\topacity: 0;\n\tanimation: dealStock 0.2s ease-out forwards;\n\tanimation-delay: var(--deal-delay, 0s);\n}\n\n@keyframes dealStock {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: scale(0.9);\n\t}\n\n\tto {\n\t\topacity: 1;\n\t\ttransform: scale(1);\n\t}\n}\n\n/* Stock recycle indicator */\n.card-zone--stock.card-zone--empty {\n\tcursor: pointer;\n}\n\n.card-zone--stock.card-zone--empty::after {\n\tcontent: '';\n\tposition: absolute;\n\tinset: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tborder-radius: var(--card-radius);\n\tbackground: rgba(201, 168, 76, 0.08);\n}\n\n/* Win overlay */\n.solitaire__win-overlay {\n\tposition: fixed;\n\tinset: 0;\n\tbackground: rgba(0, 0, 0, 0.7);\n\tbackdrop-filter: blur(6px);\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tz-index: 20000;\n\topacity: 0;\n\tpointer-events: none;\n\ttransition: opacity 0.4s;\n}\n\n.solitaire__win-overlay--visible {\n\topacity: 1;\n\tpointer-events: all;\n}\n\n.solitaire__win-card {\n\tbackground: linear-gradient(145deg, #1a3322, #0f2618);\n\tborder: 2px solid var(--gold);\n\tborder-radius: 16px;\n\tpadding: 40px 48px;\n\ttext-align: center;\n\tbox-shadow: 0 0 60px rgba(201, 168, 76, 0.2);\n}\n\n.solitaire__win-title {\n\tfont-size: 36px;\n\tcolor: var(--gold);\n\tmargin-bottom: 20px;\n\tletter-spacing: 0.05em;\n}\n\n.solitaire__win-stats {\n\tdisplay: flex;\n\tgap: 32px;\n\tjustify-content: center;\n\tmargin-bottom: 28px;\n\tcolor: rgba(255, 255, 255, 0.6);\n\tfont-size: 15px;\n}\n\n.solitaire__win-stats strong {\n\tcolor: #fff;\n}\n\n/* Double-click hint */\n.card--hint {\n\tanimation: hintPulse 0.4s ease;\n}\n\n@keyframes hintPulse {\n\t0%, 100% {\n\t\ttransform: scale(1);\n\t}\n\n\t50% {\n\t\ttransform: scale(1.03);\n\t\tbox-shadow: 0 0 15px rgba(201, 168, 76, 0.4);\n\t}\n}\n\n@media (max-width: 600px) {\n\t:root {\n\t\t--card-w: 13vw;\n\t}\n\n\t.solitaire__header {\n\t\tpadding: 8px 10px;\n\t}\n\n\t.solitaire__stats {\n\t\tgap: 10px;\n\t}\n\n\t.solitaire__new-game span {\n\t\tdisplay: none;\n\t}\n}"}],"folders":[]},"variants":null,"createdAt":"2026-03-02T01:48:54.416Z","updatedAt":"2026-03-04T20:52:08.006Z","hasThumbnail":true}}