This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a vanilla JavaScript web application for tracking cards during Twilight Struggle board game sessions. It's a single-page application that runs entirely in the browser with no build step or backend.
- Pure HTML/CSS/JavaScript (no framework)
- LocalStorage for game state persistence
- Single
index.html,script.js,style.cssstructure cards.jsoncontains the Twilight Struggle card database
Since this is a static web application with no build process:
-
Development: Open
index.htmldirectly in a browser, or use a local server:python -m http.server 8000 # or npx serve .
-
No build, test, or lint commands - this is vanilla JavaScript with no tooling.
The application uses a hybrid state management approach:
-
LocalStorage Schema:
cardCounter_games: Array of game metadata[{id, name}]cardCounter_currentGame: Currently selected game IDcardCounter_game_{gameId}: Individual game data including:title: Game titlenotes: User notescardPositions: Object mapping location IDs to arrays of card datalastModified: ISO timestamp
-
In-Memory State:
selectedHandLocation: Which hand is currently selected ('your-hand' or 'opponent-hand')useShortNames: Boolean for card name display modeselectedType: Currently selected card type filter (default: '-')selectedRegion: Currently selected region filter (default: '-')fullCardData: Card database lookup object (name -> card data)actionHistory: Array of undo actions (not persisted)currentGameId: Currently loaded game IDisLoading: Flag to prevent auto-save during load operations
Cards exist in specific DOM containers with these IDs:
your-hand: Player's handopponent-hand: Opponent's hand (supports unknown cards)deck-us: US cards in deckdeck-neutral: Neutral cards in deckdeck-ussr: USSR cards in deckdiscard: Discard pileremoved: Removed from game pilebox: Hidden container for mid/late war cards not yet in play
Each card has:
cardId: Unique numeric IDname: Full card nameshort: Abbreviated name (e.g., "nato" for "NATO")eventType: "us", "ussr", or "neutral"ops: Operation points (0-4)canBeRemoved: Boolean indicating if card can be removed from gamewar: "early", "mid", or "late" (determines when card enters deck)types: Array of card type tags (e.g., ["china", "defcon-modify", "hand-modify", "key", "suicide", "vps", "wars"])regions: Array of region tags (e.g., ["africa", "asia", "central-america", "europe", "middle-east", "south-america", "southeast-asia"])
Card elements store types and regions in dataset.types and dataset.regions as JSON strings for filtering.
The undo system (actionHistory) records actions with before/after snapshots:
- Maximum 20 actions stored
- Rate limited to 100ms between undos
- Action types: MOVE_CARD, ADD_UNKNOWN, REMOVE_UNKNOWN, ADD_DISCARDS, ADD_MID_WAR, ADD_LATE_WAR, REORDER_HAND
- Undo history is NOT persisted with game saves
- Keyboard shortcut: Ctrl/Cmd+Z (when not focused on input/textarea)
- Automatic sorting: Deck subsections sort by ops (ascending) then name
- Discard/Removed sorting: Sort by event type first (US, Neutral, USSR), then ops (ascending), then name
- Manual sorting: Hand locations (
your-hand,opponent-hand) support drag-and-drop reordering - Function:
sortCardsInContainer(container)- skips sorting for hand locations viashouldAutoSort()
-
Moving cards:
- Click card text: Toggle between deck and selected hand
- Click discard icon (↓): Move to discard
- Click remove icon (⊘): Move to removed (only if
canBeRemoved) - Drag and drop: Reorder within hand locations
-
Bulk operations:
- "Re-add Discards": Moves deck to opponent's hand, discard to deck
- "Add Mid"/"Add Late": Moves mid/late war cards from box to deck
- All bulk operations are undoable
-
Unknown cards:
- Created with "+" button in opponent's hand header
- Display current deck average instead of specific ops
- Can be removed with "−" button
- Automatically update when deck average changes
-
Card highlighting:
- Filter cards by type (China, DEFCON Modify, Hand Modify, Key, Suicide, VPs, Wars)
- Filter cards by region (Africa, Asia, Central America, Europe, Middle East, South America, Southeast Asia)
- Both filters can be active simultaneously (OR logic - card matches if it has the type OR the region)
- "Clear" button resets both filters
- Matching cards get
.highlightedclass applied - Function:
updateCardHighlighting()- checksselectedTypeandselectedRegionglobals
The application uses a modal dialog for creating new games:
- Function:
showNewGameModal()/hideNewGameModal() - Collects: opponent name, your side (US/USSR), game ID
- Modal element:
#new-game-modal - Form element:
#new-game-form - Clicking outside modal closes it
Discard and Removed piles display card counts in their headers:
- Elements:
#discard-count,#removed-count - Format: "X card(s)" or empty string if zero
- Updated automatically when cards move
The autoSaveIfNeeded() function saves the current game whenever:
- Cards are moved or reordered
- Title or notes change (but NOT during
isLoading) - Bulk operations complete
The isLoading flag prevents saves during game load/restore operations.
Cards store both full and short names in dataset.cardName and dataset.cardShort. The useShortNames global toggles which is displayed. When toggling, refreshCardDisplays() updates all card text elements without recreating cards.
Card action buttons (discard/remove icons) have complex visibility rules:
- Remove icon hidden if card is in "removed" location OR
canBeRemoved === false - Discard icon hidden if card is in "discard" location
updateCardButtonVisibility()manages this logic
Only cards in hand locations are draggable. The drag system:
- Uses HTML5 drag and drop API
- Shows visual feedback with
.draggingclass - Calculates drop position with
getDragAfterElement() - Records reorder actions for undo
When loading saved games, enrichCardData() merges saved card data with full card data from fullCardData lookup. This ensures cards retain properties like canBeRemoved even if not explicitly saved.
- Don't save during load: Always check
isLoadingbefore callingsaveCurrentGame()in event handlers - Skip undo during restore: Pass
{skipUndo: true}or{isUndoOperation: true}to prevent recording undo actions during game load/undo - Update button visibility: Call
updateCardButtonVisibility()after moving cards to update icon states - Sort after bulk moves: Call
sortCardsInContainer()after moving multiple cards - Finalize undo actions: Always call
finalizeAction(action)after operations complete to capture "after" state - Event type subsections: Cards in deck go to subsections based on
eventType, not directly to "deck" - Card metadata: When adding new cards or modifying card data, ensure
typesandregionsarrays are properly populated incards.json - Highlighting updates: Call
updateCardHighlighting()after adding/removing cards or changing filters to ensure highlight state is correct