Download index.html from mrwhy06/solitaire: direct link, hf CLI and curl.
- Browser
- Download file 31.1 kB
-
https://huggingface.co/spaces/mrwhy06/solitaire/resolve/main/index.html
- Command line
-
hf download hf://spaces/mrwhy06/solitaire/index.html
-
curl -L -o index.html https://huggingface.co/spaces/mrwhy06/solitaire/resolve/main/index.html
31.1 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Solitaire</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| user-select: none; | |
| } | |
| body { | |
| font-family: 'Roboto', sans-serif; | |
| background: linear-gradient(135deg, #1e5799, #207cca, #2989d8, #7db9e8); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| color: #333; | |
| overflow: hidden; | |
| } | |
| .game-container { | |
| width: 100%; | |
| max-width: 1000px; | |
| margin: 20px auto; | |
| display: flex; | |
| flex-direction: column; | |
| height: calc(100vh - 100px); | |
| } | |
| .header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 10px 0; | |
| margin-bottom: 10px; | |
| color: white; | |
| } | |
| .score { | |
| font-size: 1.2rem; | |
| font-weight: bold; | |
| } | |
| .restart-btn { | |
| background: rgba(255, 255, 255, 0.2); | |
| border: none; | |
| color: white; | |
| padding: 8px 15px; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| font-size: 1rem; | |
| transition: background 0.3s; | |
| } | |
| .restart-btn:hover { | |
| background: rgba(255, 255, 255, 0.3); | |
| } | |
| .game-board { | |
| display: flex; | |
| flex-direction: column; | |
| flex-grow: 1; | |
| } | |
| .top-section { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 20px; | |
| } | |
| .deck { | |
| width: 70px; | |
| height: 100px; | |
| position: relative; | |
| } | |
| .stock { | |
| cursor: pointer; | |
| position: relative; | |
| } | |
| .waste { | |
| position: relative; | |
| margin-left: 10px; | |
| } | |
| .foundations { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .foundation { | |
| width: 70px; | |
| height: 100px; | |
| border: 2px dashed rgba(255, 255, 255, 0.2); | |
| border-radius: 5px; | |
| } | |
| .tableau-section { | |
| display: flex; | |
| justify-content: space-between; | |
| flex-grow: 1; | |
| } | |
| .pile { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 3px; | |
| min-height: 100px; | |
| position: relative; | |
| } | |
| .pile.bottom { | |
| margin-top: 30px; | |
| } | |
| .card { | |
| width: 70px; | |
| height: 100px; | |
| background: white; | |
| border-radius: 5px; | |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); | |
| position: absolute; | |
| transition: transform 0.2s; | |
| cursor: pointer; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: space-between; | |
| padding: 5px; | |
| font-weight: bold; | |
| font-size: 1.2rem; | |
| overflow: hidden; | |
| } | |
| .card.red { | |
| color: red; | |
| } | |
| .card.black { | |
| color: black; | |
| } | |
| .card.hidden { | |
| background: linear-gradient(135deg, #3498db, #2980b9); | |
| color: transparent; | |
| box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2); | |
| border: 2px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .card.hidden .card-value, | |
| .card.hidden .card-suit { | |
| display: none; | |
| } | |
| .card-suit { | |
| text-align: center; | |
| font-size: 30px; | |
| } | |
| .card-bottom-suit { | |
| transform: rotate(180deg); | |
| } | |
| .card-placeholder { | |
| border: 2px dashed rgba(255, 255, 255, 0.3); | |
| border-radius: 5px; | |
| } | |
| .card.dragging { | |
| opacity: 0.8; | |
| z-index: 100; | |
| transform: rotate(2deg); | |
| box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3); | |
| } | |
| .card.selected { | |
| transform: translateY(-10px); | |
| } | |
| .win-message { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.7); | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 1000; | |
| color: white; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 0.5s; | |
| } | |
| .win-message.show { | |
| opacity: 1; | |
| pointer-events: all; | |
| } | |
| .win-text { | |
| font-size: 3rem; | |
| margin-bottom: 20px; | |
| text-align: center; | |
| text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); | |
| } | |
| .win-details { | |
| font-size: 1.2rem; | |
| margin-bottom: 30px; | |
| } | |
| .move-counter { | |
| font-size: 1.1rem; | |
| margin-bottom: 5px; | |
| color: rgba(255, 255, 255, 0.8); | |
| } | |
| @media (max-width: 768px) { | |
| .game-container { | |
| transform: scale(0.8); | |
| transform-origin: top center; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .game-container { | |
| transform: scale(0.6); | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="game-container"> | |
| <div class="header"> | |
| <div class="score">Score: <span id="score">0</span></div> | |
| <div class="move-counter">Moves: <span id="moves">0</span></div> | |
| <button class="restart-btn" id="restart">New Game</button> | |
| </div> | |
| <div class="game-board"> | |
| <div class="top-section"> | |
| <div class="deck"> | |
| <div class="card-placeholder stock" id="stock"></div> | |
| <div class="card-placeholder waste" id="waste"></div> | |
| </div> | |
| <div class="foundations"> | |
| <div class="card-placeholder foundation" id="foundation-1"></div> | |
| <div class="card-placeholder foundation" id="foundation-2"></div> | |
| <div class="card-placeholder foundation" id="foundation-3"></div> | |
| <div class="card-placeholder foundation" id="foundation-4"></div> | |
| </div> | |
| </div> | |
| <div class="tableau-section"> | |
| <div class="pile" id="pile-1"></div> | |
| <div class="pile" id="pile-2"></div> | |
| <div class="pile" id="pile-3"></div> | |
| <div class="pile" id="pile-4"></div> | |
| <div class="pile" id="pile-5"></div> | |
| <div class="pile" id="pile-6"></div> | |
| <div class="pile" id="pile-7"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="win-message" id="win-message"> | |
| <div class="win-text">You Won!</div> | |
| <div class="win-details">Congratulations on completing Solitaire!</div> | |
| <div>Final Score: <span id="final-score">0</span></div> | |
| <div>Total Moves: <span id="final-moves">0</span></div> | |
| <button class="restart-btn" style="margin-top: 20px;">Play Again</button> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Game state | |
| const game = { | |
| deck: [], | |
| stock: [], | |
| waste: [], | |
| foundations: [[], [], [], []], | |
| piles: [[], [], [], [], [], [], []], | |
| score: 0, | |
| moves: 0, | |
| draggingCards: [], | |
| dragSource: null, | |
| gameStarted: false | |
| }; | |
| // DOM elements | |
| const elements = { | |
| stock: document.getElementById('stock'), | |
| waste: document.getElementById('waste'), | |
| foundations: [ | |
| document.getElementById('foundation-1'), | |
| document.getElementById('foundation-2'), | |
| document.getElementById('foundation-3'), | |
| document.getElementById('foundation-4') | |
| ], | |
| piles: [ | |
| document.getElementById('pile-1'), | |
| document.getElementById('pile-2'), | |
| document.getElementById('pile-3'), | |
| document.getElementById('pile-4'), | |
| document.getElementById('pile-5'), | |
| document.getElementById('pile-6'), | |
| document.getElementById('pile-7') | |
| ], | |
| scoreDisplay: document.getElementById('score'), | |
| movesDisplay: document.getElementById('moves'), | |
| restartBtn: document.getElementById('restart'), | |
| winMessage: document.getElementById('win-message'), | |
| finalScore: document.getElementById('final-score'), | |
| finalMoves: document.getElementById('final-moves') | |
| }; | |
| // Card suits and values | |
| const suits = ['♥', '♦', '♠', '♣']; | |
| const values = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']; | |
| // Initialize game | |
| function initGame() { | |
| resetGame(); | |
| createDeck(); | |
| shuffleDeck(); | |
| dealCards(); | |
| updateUI(); | |
| game.gameStarted = true; | |
| } | |
| // Reset game state | |
| function resetGame() { | |
| game.deck = []; | |
| game.stock = []; | |
| game.waste = []; | |
| game.foundations = [[], [], [], []]; | |
| game.piles = [[], [], [], [], [], [], []]; | |
| game.score = 0; | |
| game.moves = 0; | |
| game.draggingCards = []; | |
| game.dragSource = null; | |
| game.gameStarted = false; | |
| // Clear all piles and foundations | |
| elements.piles.forEach(pile => pile.innerHTML = ''); | |
| elements.foundations.forEach(foundation => foundation.innerHTML = ''); | |
| elements.waste.innerHTML = ''; | |
| } | |
| // Create a standard deck of 52 cards | |
| function createDeck() { | |
| game.deck = []; | |
| for (let suitIndex = 0; suitIndex < suits.length; suitIndex++) { | |
| for (let valueIndex = 0; valueIndex < values.length; valueIndex++) { | |
| const card = { | |
| suit: suits[suitIndex], | |
| value: values[valueIndex], | |
| color: (suitIndex < 2) ? 'red' : 'black', // Hearts and diamonds are red | |
| rank: valueIndex + 1, // A=1, K=13 | |
| hidden: true | |
| }; | |
| game.deck.push(card); | |
| } | |
| } | |
| } | |
| // Shuffle the deck using Fisher-Yates algorithm | |
| function shuffleDeck() { | |
| for (let i = game.deck.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [game.deck[i], game.deck[j]] = [game.deck[j], game.deck[i]]; | |
| } | |
| } | |
| // Deal cards to piles | |
| function dealCards() { | |
| let cardIndex = 0; | |
| // Deal to each pile | |
| for (let pileIndex = 0; pileIndex < game.piles.length; pileIndex++) { | |
| const pile = game.piles[pileIndex]; | |
| // Number of cards to deal to this pile is pileIndex + 1 | |
| for (let i = 0; i <= pileIndex; i++) { | |
| const card = game.deck[cardIndex++]; | |
| // Only the last card in each pile is face up | |
| card.hidden = i !== pileIndex; | |
| pile.push(card); | |
| } | |
| } | |
| // Remaining cards go to stock | |
| game.stock = game.deck.slice(cardIndex); | |
| } | |
| // Update the UI based on game state | |
| function updateUI() { | |
| updateStock(); | |
| updateWaste(); | |
| updateFoundations(); | |
| updatePiles(); | |
| updateScore(); | |
| checkWin(); | |
| } | |
| // Update stock display | |
| function updateStock() { | |
| elements.stock.innerHTML = ''; | |
| if (game.stock.length > 0) { | |
| const cardBack = document.createElement('div'); | |
| cardBack.className = 'card hidden'; | |
| elements.stock.appendChild(cardBack); | |
| } else { | |
| elements.stock.classList.add('empty'); | |
| } | |
| } | |
| // Update waste display | |
| function updateWaste() { | |
| elements.waste.innerHTML = ''; | |
| if (game.waste.length > 0) { | |
| const lastCard = game.waste[game.waste.length - 1]; | |
| const cardEl = createCardElement(lastCard); | |
| cardEl.style.left = '0'; | |
| cardEl.style.top = '0'; | |
| elements.waste.appendChild(cardEl); | |
| } | |
| } | |
| // Update foundations display | |
| function updateFoundations() { | |
| game.foundations.forEach((foundation, index) => { | |
| elements.foundations[index].innerHTML = ''; | |
| if (foundation.length > 0) { | |
| const lastCard = foundation[foundation.length - 1]; | |
| const cardEl = createCardElement(lastCard); | |
| elements.foundations[index].appendChild(cardEl); | |
| } | |
| }); | |
| } | |
| // Update tableaux (piles) display | |
| function updatePiles() { | |
| game.piles.forEach((pile, pileIndex) => { | |
| elements.piles[pileIndex].innerHTML = ''; | |
| pile.forEach((card, cardIndex) => { | |
| const cardEl = createCardElement(card); | |
| cardEl.style.top = `${cardIndex * 25}px`; // Stack cards vertically | |
| cardEl.setAttribute('data-pile-index', pileIndex); | |
| cardEl.setAttribute('data-card-index', cardIndex); | |
| elements.piles[pileIndex].appendChild(cardEl); | |
| }); | |
| }); | |
| } | |
| // Create a card DOM element | |
| function createCardElement(card) { | |
| const cardEl = document.createElement('div'); | |
| cardEl.className = `card ${card.color}`; | |
| cardEl.innerHTML = ` | |
| <div class="card-value">${card.value}</div> | |
| <div class="card-suit">${card.suit}</div> | |
| <div class="card-suit card-bottom-suit">${card.suit}</div> | |
| `; | |
| if (card.hidden) { | |
| cardEl.classList.add('hidden'); | |
| } | |
| // Add drag event listeners | |
| cardEl.draggable = true; | |
| cardEl.addEventListener('dragstart', handleDragStart); | |
| cardEl.addEventListener('mouseup', handleCardClick); | |
| return cardEl; | |
| } | |
| // Update score display | |
| function updateScore() { | |
| elements.scoreDisplay.textContent = game.score; | |
| elements.movesDisplay.textContent = game.moves; | |
| } | |
| // Draw card from stock to waste | |
| function drawCard() { | |
| if (!game.gameStarted) return; | |
| if (game.stock.length === 0) { | |
| // If stock is empty, return all waste cards to stock | |
| game.waste.reverse().forEach(card => { | |
| card.hidden = true; | |
| }); | |
| game.stock = [...game.waste]; | |
| game.waste = []; | |
| game.score = Math.max(0, game.score - 100); // Penalty for recycling waste | |
| } else { | |
| // Draw the top card from stock | |
| const card = game.stock.pop(); | |
| card.hidden = false; | |
| game.waste.push(card); | |
| game.score += 5; // Small points for drawing | |
| } | |
| game.moves++; | |
| updateUI(); | |
| } | |
| // Handle drag start | |
| function handleDragStart(e) { | |
| if (!game.gameStarted) return; | |
| const cardEl = e.target.closest('.card'); | |
| if (!cardEl || cardEl.classList.contains('hidden')) { | |
| e.preventDefault(); | |
| return; | |
| } | |
| const pileIndex = parseInt(cardEl.getAttribute('data-pile-index')); | |
| const cardIndex = parseInt(cardEl.getAttribute('data-card-index')); | |
| // Get all cards from this one to the end of the pile | |
| game.draggingCards = game.piles[pileIndex].slice(cardIndex); | |
| game.dragSource = { type: 'pile', index: pileIndex }; | |
| // For foundation or waste, just move the top card | |
| if (isNaN(pileIndex)) { | |
| if (cardEl.parentElement === elements.waste) { | |
| game.draggingCards = [game.waste[game.waste.length - 1]]; | |
| game.dragSource = { type: 'waste' }; | |
| } else { | |
| const foundationIndex = elements.foundations.findIndex(f => cardEl.parentElement === f); | |
| if (foundationIndex !== -1) { | |
| game.draggingCards = [game.foundations[foundationIndex][game.foundations[foundationIndex].length - 1]]; | |
| game.dragSource = { type: 'foundation', index: foundationIndex }; | |
| } | |
| } | |
| } | |
| // Set drag image | |
| const dragImage = cardEl.cloneNode(true); | |
| dragImage.style.position = 'fixed'; | |
| dragImage.style.left = '-1000px'; | |
| dragImage.style.top = '0'; | |
| dragImage.style.zIndex = '10000'; | |
| document.body.appendChild(dragImage); | |
| e.dataTransfer.setDragImage(dragImage, 35, 50); | |
| setTimeout(() => document.body.removeChild(dragImage), 0); | |
| // Set effect allowed | |
| e.dataTransfer.effectAllowed = 'move'; | |
| // Add dragging class | |
| setTimeout(() => cardEl.classList.add('dragging'), 0); | |
| } | |
| // Handle drop on a pile or foundation | |
| function handleDrop(target, e) { | |
| e.preventDefault(); | |
| if (!game.draggingCards.length) return; | |
| const card = game.draggingCards[0]; | |
| // Determine target type | |
| let targetType, targetIndex; | |
| if (target === elements.waste) { | |
| return; // Can't drop on waste | |
| } else if (target === elements.stock) { | |
| return; // Can't drop on stock | |
| } else if (elements.foundations.includes(target)) { | |
| targetType = 'foundation'; | |
| targetIndex = elements.foundations.indexOf(target); | |
| } else if (elements.piles.includes(target)) { | |
| targetType = 'pile'; | |
| targetIndex = elements.piles.indexOf(target); | |
| } else { | |
| return; | |
| } | |
| // Check if move is valid | |
| let isValid = false; | |
| if (targetType === 'foundation') { | |
| isValid = canMoveToFoundation(card, targetIndex); | |
| } else if (targetType === 'pile') { | |
| isValid = canMoveToPile(card, targetIndex); | |
| } | |
| if (isValid) { | |
| // Remove cards from source | |
| if (game.dragSource.type === 'pile') { | |
| game.piles[game.dragSource.index].splice( | |
| game.piles[game.dragSource.index].length - game.draggingCards.length, | |
| game.draggingCards.length | |
| ); | |
| // Reveal next card in source pile if needed | |
| const sourcePile = game.piles[game.dragSource.index]; | |
| if (sourcePile.length > 0 && sourcePile[sourcePile.length - 1].hidden) { | |
| sourcePile[sourcePile.length - 1].hidden = false; | |
| game.score += 5; // Points for revealing a card | |
| } | |
| } else if (game.dragSource.type === 'waste') { | |
| game.waste.pop(); | |
| } else if (game.dragSource.type === 'foundation') { | |
| game.foundations[game.dragSource.index].pop(); | |
| } | |
| // Add cards to target | |
| if (targetType === 'foundation') { | |
| game.foundations[targetIndex].push(card); | |
| game.score += 10; // Points for moving to foundation | |
| } else if (targetType === 'pile') { | |
| game.piles[targetIndex].push(...game.draggingCards); | |
| // Points for moving from waste or another pile | |
| game.score += (game.draggingCards.length === 1) ? 5 : 0; | |
| } | |
| game.moves++; | |
| game.draggingCards = []; | |
| game.dragSource = null; | |
| updateUI(); | |
| } | |
| } | |
| // Check if card can be moved to foundation | |
| function canMoveToFoundation(card, foundationIndex) { | |
| const foundation = game.foundations[foundationIndex]; | |
| if (foundation.length === 0) { | |
| // Only Aces can start a foundation | |
| return card.value === 'A'; | |
| } else { | |
| const topCard = foundation[foundation.length - 1]; | |
| // Must be same suit and next in sequence | |
| return topCard.suit === card.suit && card.rank === topCard.rank + 1; | |
| } | |
| } | |
| // Check if card can be moved to a pile | |
| function canMoveToPile(card, pileIndex) { | |
| const pile = game.piles[pileIndex]; | |
| if (pile.length === 0) { | |
| // Only Kings can be placed on empty pile | |
| return card.value === 'K'; | |
| } else { | |
| const topCard = pile[pile.length - 1]; | |
| // Must be opposite color and one rank lower | |
| return topCard.color !== card.color && card.rank === topCard.rank - 1; | |
| } | |
| } | |
| // Handle card click | |
| function handleCardClick(e) { | |
| if (!game.gameStarted) return; | |
| const cardEl = e.target.closest('.card'); | |
| if (!cardEl || cardEl.classList.contains('hidden')) return; | |
| // If clicked on waste, try to auto-move to foundation | |
| if (cardEl.parentElement === elements.waste && game.waste.length > 0) { | |
| const card = game.waste[game.waste.length - 1]; | |
| // Check all foundations for possible moves | |
| for (let i = 0; i < game.foundations.length; i++) { | |
| if (canMoveToFoundation(card, i)) { | |
| game.foundations[i].push(game.waste.pop()); | |
| game.score += 10; | |
| game.moves++; | |
| updateUI(); | |
| return; | |
| } | |
| } | |
| } | |
| // If clicked on pile card, try to auto-move to foundation if it's the top card | |
| const pileIndex = parseInt(cardEl.getAttribute('data-pile-index')); | |
| const cardIndex = parseInt(cardEl.getAttribute('data-card-index')); | |
| if (!isNaN(pileIndex) && cardIndex === game.piles[pileIndex].length - 1) { | |
| const card = game.piles[pileIndex][cardIndex]; | |
| for (let i = 0; i < game.foundations.length; i++) { | |
| if (canMoveToFoundation(card, i)) { | |
| game.foundations[i].push(game.piles[pileIndex].pop()); | |
| game.score += 10; | |
| game.moves++; | |
| // Reveal next card if needed | |
| if (game.piles[pileIndex].length > 0 && game.piles[pileIndex][game.piles[pileIndex].length - 1].hidden) { | |
| game.piles[pileIndex][game.piles[pileIndex].length - 1].hidden = false; | |
| game.score += 5; | |
| } | |
| updateUI(); | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| // Check if player has won | |
| function checkWin() { | |
| const allCardsInFoundations = game.foundations.every(foundation => foundation.length === 13); | |
| if (allCardsInFoundations) { | |
| showWinMessage(); | |
| } | |
| } | |
| // Show win message | |
| function showWinMessage() { | |
| elements.finalScore.textContent = game.score; | |
| elements.finalMoves.textContent = game.moves; | |
| elements.winMessage.classList.add('show'); | |
| } | |
| // Setup event listeners | |
| function setupEventListeners() { | |
| // Stock click to draw card | |
| elements.stock.addEventListener('click', drawCard); | |
| // Restart button | |
| elements.restartBtn.addEventListener('click', initGame); | |
| elements.winMessage.querySelector('.restart-btn').addEventListener('click', () => { | |
| elements.winMessage.classList.remove('show'); | |
| initGame(); | |
| }); | |
| // Drag and drop setup | |
| document.addEventListener('dragover', e => { | |
| e.preventDefault(); | |
| const target = document.elementFromPoint(e.clientX, e.clientY)?.closest('.pile, .foundation, .waste, .stock'); | |
| if (target) { | |
| const rect = target.getBoundingClientRect(); | |
| const isOver = e.clientX > rect.left && e.clientX < rect.right && | |
| e.clientY > rect.top && e.clientY < rect.bottom; | |
| if (isOver) { | |
| if (target === elements.stock || target === elements.waste) return; | |
| target.classList.add('highlight'); | |
| } | |
| } | |
| }); | |
| document.addEventListener('dragleave', e => { | |
| const target = document.elementFromPoint(e.clientX, e.clientY)?.closest('.pile, .foundation'); | |
| if (!target || e.target !== target) { | |
| elements.piles.forEach(pile => pile.classList.remove('highlight')); | |
| elements.foundations.forEach(foundation => foundation.classList.remove('highlight')); | |
| } | |
| }); | |
| document.addEventListener('drop', e => { | |
| e.preventDefault(); | |
| elements.piles.forEach(pile => pile.classList.remove('highlight')); | |
| elements.foundations.forEach(foundation => foundation.classList.remove('highlight')); | |
| const target = document.elementFromPoint(e.clientX, e.clientY)?.closest('.pile, .foundation, .waste, .stock'); | |
| if (target && game.draggingCards.length) { | |
| handleDrop(target, e); | |
| } | |
| }); | |
| document.addEventListener('dragend', () => { | |
| document.querySelectorAll('.card').forEach(card => card.classList.remove('dragging')); | |
| elements.piles.forEach(pile => pile.classList.remove('highlight')); | |
| elements.foundations.forEach(foundation => foundation.classList.remove('highlight')); | |
| }); | |
| // Keyboard shortcuts | |
| document.addEventListener('keydown', e => { | |
| if (e.code === 'Space' && !e.target.matches('button, input, textarea')) { | |
| e.preventDefault(); | |
| drawCard(); | |
| } else if (e.code === 'KeyR' && !e.target.matches('button, input, textarea')) { | |
| e.preventDefault(); | |
| initGame(); | |
| } | |
| }); | |
| } | |
| // Initialize the game | |
| setupEventListeners(); | |
| initGame(); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> | |
| </html> |