# V3-13: Card Value Tooltips ## Overview New players often forget card values, especially special cards (2=-2, K=0, Joker=-2). This feature adds tooltips showing card point values on long-press or hover. **Dependencies:** None **Dependents:** None --- ## Goals 1. Show card point value on long-press (mobile) or hover (desktop) 2. Especially helpful for special value cards 3. Show house rule modified values if active 4. Don't interfere with normal gameplay 5. Optional: disable for experienced players --- ## Current State No card value tooltips exist. Players must remember: - Standard values: A=1, 2-10=face, J/Q=10, K=0 - Special values: 2=-2, Joker=-2 - House rules: super_kings=-2, ten_penny=1, etc. --- ## Design ### Tooltip Content ``` ┌─────────┐ │ K │ ← Normal card display │ ♠ │ └─────────┘ │ ▼ ┌───────┐ │ 0 pts │ ← Tooltip on hover/long-press └───────┘ ``` For special cards: ``` ┌────────────┐ │ -2 pts │ │ (negative!)│ └────────────┘ ``` ### Activation - **Desktop:** Hover for 500ms (not instant to avoid cluttering) - **Mobile:** Long-press (300ms threshold) - **Dismiss:** Mouse leave / touch release --- ## Implementation ### JavaScript ```javascript // Card tooltip system initCardTooltips() { this.tooltip = document.createElement('div'); this.tooltip.className = 'card-value-tooltip hidden'; document.body.appendChild(this.tooltip); this.tooltipTimeout = null; this.currentTooltipTarget = null; } bindCardTooltipEvents(cardElement, cardData) { // Desktop hover cardElement.addEventListener('mouseenter', () => { this.scheduleTooltip(cardElement, cardData); }); cardElement.addEventListener('mouseleave', () => { this.hideCardTooltip(); }); // Mobile long-press let pressTimer = null; cardElement.addEventListener('touchstart', (e) => { pressTimer = setTimeout(() => { this.showCardTooltip(cardElement, cardData); // Prevent triggering card click e.preventDefault(); }, 300); }); cardElement.addEventListener('touchend', () => { clearTimeout(pressTimer); this.hideCardTooltip(); }); cardElement.addEventListener('touchmove', () => { clearTimeout(pressTimer); this.hideCardTooltip(); }); } scheduleTooltip(cardElement, cardData) { this.hideCardTooltip(); if (!cardData?.face_up || !cardData?.rank) return; this.tooltipTimeout = setTimeout(() => { this.showCardTooltip(cardElement, cardData); }, 500); // 500ms delay on desktop } showCardTooltip(cardElement, cardData) { if (!cardData?.face_up || !cardData?.rank) return; const value = this.getCardPointValue(cardData); const special = this.getCardSpecialNote(cardData); // Build tooltip content let content = `${value} pts`; if (special) { content += `${special}`; } this.tooltip.innerHTML = content; // Position tooltip const rect = cardElement.getBoundingClientRect(); const tooltipRect = this.tooltip.getBoundingClientRect(); let left = rect.left + rect.width / 2; let top = rect.bottom + 8; // Keep on screen if (left + tooltipRect.width / 2 > window.innerWidth) { left = window.innerWidth - tooltipRect.width / 2 - 10; } if (left - tooltipRect.width / 2 < 0) { left = tooltipRect.width / 2 + 10; } if (top + tooltipRect.height > window.innerHeight) { top = rect.top - tooltipRect.height - 8; } this.tooltip.style.left = `${left}px`; this.tooltip.style.top = `${top}px`; this.tooltip.classList.remove('hidden'); this.currentTooltipTarget = cardElement; } hideCardTooltip() { clearTimeout(this.tooltipTimeout); this.tooltip.classList.add('hidden'); this.currentTooltipTarget = null; } getCardPointValue(cardData) { const values = this.gameState?.card_values || { 'A': 1, '2': -2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10, 'J': 10, 'Q': 10, 'K': 0, '★': -2 }; return values[cardData.rank] ?? 0; } getCardSpecialNote(cardData) { const rank = cardData.rank; const value = this.getCardPointValue(cardData); // Special notes for notable cards if (value < 0) { return 'Negative - keep it!'; } if (rank === 'K' && value === 0) { return 'Safe card'; } if (rank === 'K' && value === -2) { return 'Super King!'; } if (rank === '10' && value === 1) { return 'Ten Penny rule'; } if (rank === 'J' || rank === 'Q') { return 'High - replace if possible'; } return null; } ``` ### CSS ```css /* Card value tooltip */ .card-value-tooltip { position: fixed; transform: translateX(-50%); background: rgba(26, 26, 46, 0.95); color: white; padding: 6px 12px; border-radius: 8px; font-size: 0.85em; text-align: center; z-index: 500; pointer-events: none; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); transition: opacity 0.15s; } .card-value-tooltip.hidden { opacity: 0; pointer-events: none; } .card-value-tooltip::before { content: ''; position: absolute; top: -6px; left: 50%; transform: translateX(-50%); border: 6px solid transparent; border-bottom-color: rgba(26, 26, 46, 0.95); } .tooltip-value { display: block; font-size: 1.2em; font-weight: bold; } .tooltip-value.negative { color: #27ae60; } .tooltip-note { display: block; font-size: 0.85em; color: rgba(255, 255, 255, 0.7); margin-top: 2px; } /* Visual indicator that tooltip is available */ .card[data-has-tooltip]:hover { cursor: help; } ``` ### Integration with renderGame ```javascript // In renderGame, after creating card elements renderPlayerCards() { // ... existing card rendering ... const cards = this.playerCards.querySelectorAll('.card'); const myData = this.getMyPlayerData(); cards.forEach((cardEl, i) => { const cardData = myData?.cards[i]; if (cardData?.face_up) { cardEl.dataset.hasTooltip = 'true'; this.bindCardTooltipEvents(cardEl, cardData); } }); } // Similar for opponent cards renderOpponentCards(player, container) { // ... existing card rendering ... const cards = container.querySelectorAll('.card'); player.cards.forEach((cardData, i) => { if (cardData?.face_up && cards[i]) { cards[i].dataset.hasTooltip = 'true'; this.bindCardTooltipEvents(cards[i], cardData); } }); } ``` --- ## House Rule Awareness Tooltip values should reflect active house rules: ```javascript getCardPointValue(cardData) { // Use server-provided values which include house rules if (this.gameState?.card_values) { return this.gameState.card_values[cardData.rank] ?? 0; } // Fallback to defaults return DEFAULT_CARD_VALUES[cardData.rank] ?? 0; } ``` The server already provides `card_values` in game state that accounts for: - `super_kings` (K = -2) - `ten_penny` (10 = 1) - `lucky_swing` (Joker = -5) - etc. --- ## Performance Considerations - Only bind tooltip events to face-up cards - Remove tooltip events when cards re-render - Use event delegation if performance becomes an issue ```javascript // Event delegation approach this.playerCards.addEventListener('mouseenter', (e) => { const card = e.target.closest('.card'); if (card && card.dataset.hasTooltip) { const cardData = this.getCardDataForElement(card); this.scheduleTooltip(card, cardData); } }, true); ``` --- ## Settings Option (Optional) Let players disable tooltips: ```javascript // In settings this.showCardTooltips = localStorage.getItem('showCardTooltips') !== 'false'; // Check before showing showCardTooltip(cardElement, cardData) { if (!this.showCardTooltips) return; // ... rest of method } ``` --- ## Test Scenarios 1. **Hover on face-up card** - Tooltip appears after delay 2. **Long-press on mobile** - Tooltip appears 3. **Move mouse away** - Tooltip disappears 4. **Face-down card** - No tooltip 5. **Special cards (K, 2, Joker)** - Show special note 6. **House rules active** - Modified values shown 7. **Rapid card changes** - No stale tooltips --- ## Acceptance Criteria - [ ] Hover (500ms delay) shows tooltip on desktop - [ ] Long-press (300ms) shows tooltip on mobile - [ ] Tooltip shows point value - [ ] Negative values highlighted green - [ ] Special notes for notable cards - [ ] House rule modified values displayed - [ ] Tooltips don't interfere with gameplay - [ ] Tooltips position correctly (stay on screen) - [ ] Face-down cards have no tooltip --- ## Implementation Order 1. Create tooltip element and basic CSS 2. Implement `showCardTooltip()` method 3. Implement `hideCardTooltip()` method 4. Add desktop hover events 5. Add mobile long-press events 6. Integrate with `renderGame()` 7. Add house rule awareness 8. Test on mobile and desktop 9. Optional: Add settings toggle --- ## Notes for Agent - **CSS vs anime.js**: CSS is appropriate for tooltip show/hide transitions (simple UI) - The 500ms delay prevents tooltips appearing during normal play - Mobile long-press should be discoverable but not intrusive - Use server-provided `card_values` for house rule accuracy - Consider: Quick reference card in rules screen? (Separate feature) - Don't show tooltip during swap animation