A tournament stream deck list viewer with real-time Ably integration for broadcasting card displays. This project enables streamers to display card images during tournaments with smooth animations and real-time synchronization.
tam/streamerControls/index.html - Publisher only - Modern TypeScript streamer interface
streamer.html with inline JavaScript to modular TypeScript
architecture<base href="../../" /> for clean relative path resolution from nested directory
structurewindow.streamerControls for HTML onclick handlers with TypeScript backingdecks.json via DeckDataManager moduleAblyManager moduletam/index.html for live broadcast preview (300px ร 420px)tam/index.html - Subscriber only - Modern TypeScript-based broadcast view
AnimationControllerdecks.json - Central data file containing all player deck information
The streamer controls were completely refactored from streamer.html (inline JavaScript) to
tam/streamerControls/index.html with a modern TypeScript architecture:
src/typescript/streamerControls/)StreamerController.ts - Main orchestration controller
DeckDataManager.ts - Deck data loading and management
decks.jsonDeckRenderer.ts - HTML generation for deck displays
AblyManager.ts - Real-time communication management
StatisticsCalculator.ts - Deck analysis utilities
types.ts - TypeScript interfaces and type definitions
PlayerDeck, Section, Card interfacesAblyConfig, TokenData typesSelectedCard, CardData typesThe migration from streamer.html to tam/streamerControls/index.html provided significant improvements:
Before (Legacy streamer.html):
After (tam/streamerControls/index.html):
<base href="../../" /> enables clean relative paths from nested directorywindow.streamerControls provides clean HTML-to-TypeScript bridgewindow.debugStreamerController for developmentvite.config.streamerControls.jsassets/js/streamerControls/publish, subscribe, presence, history, object_publish, object_subscribe
(required for LiveObjects)Since token generation has been moved to a separate service, you need to:
tam/index.html: Set TOKEN_ENDPOINT in the broadcast viewtam/streamerControls/index.html: Token endpoint is configured in StreamerController.tssrc/typescript/streamerControls/StreamerController.ts line 27: tokenEndpoint property {
"appId": "your-ably-app-id",
"tokenRequest": {
"keyName": "your-app.your-key",
"clientId": "streamer-publisher",
"timestamp": 1234567890,
"nonce": "unique-nonce",
"mac": "signature",
"ttl": 3600000
},
"expiresAt": 1234567890,
"generatedAt": 1234567890
}
main or gh-pagesโโโ tam/ # Main tournament application
โ โโโ index.html # Broadcast view (subscriber only)
โ โโโ streamer.html # Streamer view (publisher only)
โ โโโ admin/ # Admin control panel
โ โโโ index.html # Admin refresh control panel
โโโ cardChecker/ # Card checker utility
โ โโโ index.html # Card checker interface
โ โโโ assets/ # Card checker specific assets
โ โโโ css/ # Compiled CSS for card checker
โ โ โโโ card-checker.css # Compiled card checker styles
โ โ โโโ card-checker.css.map # Source maps
โ โโโ js/ # Compiled JavaScript
โ โ โโโ card-checker.min.js # Main application logic (minified)
โ โ โโโ card-checker.min.js.map # Source maps
โ โ โโโ card-item-component.min.js # Web component (minified)
โ โ โโโ card-item-component.min.js.map # Source maps
โ โโโ scss/ # SASS source files
โ โ โโโ card-checker.scss # Card checker styles
โ โโโ ts/ # TypeScript source files
โ โโโ card-checker.ts # Main application logic with simplified Card interface
โ โโโ card-item-component.ts # Web component for card display
โโโ deckViewer/ # Deck viewer utility
โ โโโ index.html # Deck viewer interface
โ โโโ assets/ # Deck viewer specific assets
โ โโโ css/ # Compiled CSS for deck viewer
โ โ โโโ deck-viewer.css # Compiled deck viewer styles
โ โ โโโ deck-viewer.css.map # Source maps
โ โโโ js/ # Compiled JavaScript
โ โ โโโ deck-viewer.min.js # Main application logic (minified)
โ โ โโโ deck-viewer.min.js.map # Source maps
โ โ โโโ deck-card-item.min.js # Card item web component (minified)
โ โ โโโ deck-card-item.min.js.map # Source maps
โ โ โโโ deck-section.min.js # Section web component (minified)
โ โ โโโ deck-section.min.js.map # Source maps
โ โ โโโ deck-info-panel.min.js # Info panel web component (minified)
โ โ โโโ deck-info-panel.min.js.map # Source maps
โ โโโ scss/ # SASS source files
โ โ โโโ deck-viewer.scss # Deck viewer styles
โ โโโ ts/ # TypeScript source files
โ โโโ deck-viewer.ts # Main application logic with namespaced interfaces
โ โโโ deck-card-item.ts # Card item web component
โ โโโ deck-section.ts # Section web component
โ โโโ deck-info-panel.ts # Info panel web component
โโโ flipDemo/ # Animation demo
โ โโโ index.html # Flip animation demonstration
โโโ assets/
โ โโโ css/ # Compiled CSS files
โ โ โโโ index.css # Broadcast view styles
โ โ โโโ index.css.map # Source maps for broadcast view
โ โ โโโ streamer.css # Streamer view styles
โ โ โโโ streamer.css.map # Source maps for streamer view
โ โโโ images/ # Card images (WebP format, 1186 files)
โ โโโ js/ # Compiled JavaScript (currently empty)
โโโ deck_viewer.html # Legacy deck viewer (deprecated)
โโโ decks.json # Tournament deck data (minified JSON, 11k+ lines)
โ # Contains player information, deck names, and complete card listings
โ # Structure: [{ player, deckName, character, character_slot, main_deck }]
โโโ src/
โ โโโ styles/ # SASS build system
โ โ โโโ index.scss # Broadcast view SASS entry point
โ โ โโโ main.scss # Main SASS entry point
โ โ โโโ streamer.scss # Streamer view SASS entry point
โ โ โโโ cardChecker/ # Card checker specific styles (directory)
โ โ โโโ components/ # Component-specific styles
โ โ โ โโโ _broadcast.scss # Broadcast view components
โ โ โ โโโ _streamer.scss # Streamer view components
โ โ โโโ utilities/ # Utility styles and mixins
โ โ โโโ _variables.scss # SASS variables and colors
โ โ โโโ _mixins.scss # Reusable SASS mixins
โ โโโ test/ # Test suite
โ โ โโโ setup.ts # Test configuration
โ โ โโโ broadcast-view.test.ts # Broadcast view tests
โ โ โโโ streamer-view.test.ts # Streamer view tests
โ โ โโโ ably-integration.test.ts # Ably integration tests
โ โ โโโ system.test.ts # System tests
โ โโโ typescript/ # TypeScript source directory (currently empty)
โโโ .github/
โ โโโ workflows/
โ โโโ static.yml # GitHub Pages deployment workflow
โโโ .husky/ # Git hooks directory
โ โโโ pre-commit # Pre-commit hook for SASS building
โโโ .vscode/
โ โโโ settings.json # VS Code workspace settings
โโโ .cursorrules # Development guidelines
โโโ prettier.config.mjs # Prettier configuration
โโโ tsconfig.json # TypeScript configuration
โโโ vitest.config.js # Vitest configuration
โโโ package.json # Node.js dependencies
โโโ README.md # Main documentation
# Install dependencies
npm install
# Run tests
npm test
# Run tests with UI
npm run test:ui
# Watch mode for development
npm run test:watch
# SASS development
npm run build:sass # Build all SASS files
npm run build:index # Build broadcast view styles only
npm run build:streamer # Build streamer view styles only
npm run watch:index # Watch broadcast view SASS files
npm run watch:streamer # Watch streamer view SASS files
# TypeScript development (for card checker)
npm run build:typescript # Build and minify TypeScript
npm run watch:typescript # Watch TypeScript files
# Complete build
npm run build # Build all assets (SASS + TypeScript + Card Checker)
# Development workflow
# 1. Edit SCSS files in src/styles/
# 2. Run appropriate watch command for live compilation
# 3. Changes are automatically compiled to assets/css/
# 4. Commit changes (pre-commit hook checks CSS status)
tam/index.html and tam/streamer.html in browserdecks.jsoncardChecker/index.htmlflipDemo/index.htmlThe project maintains comprehensive test coverage across multiple dimensions:
| Component | Unit Tests | Integration Tests | Error Handling |
|---|---|---|---|
| Ably Integration | โ Auth, messaging | โ Real-time sync | โ Network failures |
| Animation System | โ Flip/slide logic | โ Queue management | โ Image load errors |
| Deck Data | โ JSON parsing | โ UI population | โ Fetch failures |
| Player Selection | โ Dropdown logic | โ Selection handling | โ Empty data |
| Heartbeat System | โ Timer logic | โ Connection monitoring | โ Timeout recovery |
| UI Components | โ Structure validation | โ Responsive design | โ Missing elements |
This project uses a comprehensive testing strategy combining unit tests (Vitest + JSDOM) for pure JavaScript logic and E2E tests (Playwright) for DOM interactions, animations, and integration testing.
Tests are organized into logical directories for easy identification and maintenance:
src/test/
โโโ broadcast/ # Broadcast Module Tests (44 tests)
โ โโโ AblyManager.test.ts # Tests src/typescript/broadcast/AblyManager.ts
โ โโโ AdminHandler.test.ts # Tests src/typescript/broadcast/AdminHandler.ts
โ โโโ ImagePreloader.test.ts # Tests src/typescript/broadcast/ImagePreloader.ts
โ โโโ MessageQueue.test.ts # Tests src/typescript/broadcast/MessageQueue.ts
โโโ streamer/ # Streamer Module Tests (44 tests)
โ โโโ StreamerController.test.ts # Tests src/typescript/streamerControls/*.ts
โโโ modules/ # Standalone Module Tests (45 tests)
โ โโโ CardChecker.test.ts # Tests cardChecker/assets/ts/card-checker.ts
โ โโโ DeckViewer.test.ts # Tests deckViewer/assets/ts/deck-viewer.ts
โ โโโ DeckViewerComponents.test.ts # Tests deck viewer interfaces
โโโ system/ # System & Integration Tests (60 tests)
โโโ AblyIntegration.test.ts # Tests Ably SDK integration
โโโ BuildSystem.test.ts # Tests build system processes
โโโ SystemArchitecture.test.ts # Tests overall system architecture
| Module Category | Coverage | Strategy |
|---|---|---|
| Streamer Controls | 81.73% | Unit tests for pure logic |
| Card Checker | 99.01% | Unit tests + E2E for web components |
| Deck Viewer | 91.66% | Unit tests + E2E for web components |
| Broadcast Modules | 49.85% | Unit tests for testable logic, E2E for DOM/animations |
We use /* c8 ignore start/stop */ comments to exclude code thatโs better tested via E2E:
/* c8 ignore start */
// DOM initialization - tested via Playwright E2E
document.addEventListener('DOMContentLoaded', async () => {
// ... DOM manipulation code
});
/* c8 ignore stop */
Whatโs Ignored from Unit Tests:
DOMContentLoaded event handlersImage constructor, document.hidden, etc.Whatโs Unit Tested:
Unit Testing (Vitest + JSDOM)
E2E Testing (Playwright)
# Unit Tests
npm test # Interactive mode
npm run test:ci # CI mode with coverage
npm run test:watch # Watch mode
npm run test:ui # Vitest UI
# E2E Tests
npm run test:e2e # Run Playwright tests
npm run test:e2e:ui # Playwright UI mode
npm run test:e2e:headed # Run with browser visible
npm run test:e2e:debug # Debug mode
# Coverage Analysis
npm run test:ci -- --coverage # Unit test coverage
npm run test:e2e:coverage # E2E coverage
npm run coverage:combined # Combined analysis
This project uses SASS (Syntactically Awesome StyleSheets) with an automated build system that compiles SCSS files to compressed CSS with source maps.
src/styles/ directory (tracked in Git)npm run watch:sass# Build all SASS files
npm run build:sass
# Build specific views
npm run build:index # Broadcast view only
npm run build:streamer # Streamer view only
# Watch mode for development (continuous compilation)
npm run watch:index # Watch broadcast view
npm run watch:streamer # Watch streamer view
# Build all assets (SASS + TypeScript + Card Checker)
npm run build
The project uses Git hooks to check SASS compilation status on every commit:
.husky/pre-commitnpm run build:sassSince automatic compilation in git hooks can be unreliable due to Node.js environment differences, please:
src/styles/ directorynpm run build:sass (or specific view commands)assets/css/index.css, assets/css/streamer.css, and assets/css/admin.cssThis ensures reliable builds and proper version control of both source and compiled assets.
decks.json FormatThe deck data file contains an array of player deck objects with the following structure:
[
{
"player": "Player Name",
"deckName": "Deck Display Name",
"character": "Character Name",
"character_slot": [
{
"type": "Characters",
"cards": [
{
"qty": 1,
"card": "Character Card Name",
"img": "character-image.webp",
"blockZone": "mid",
"backside": null
}
]
}
],
"main_deck": [
{
"type": "Attacks",
"cards": [
{
"qty": 4,
"card": "Attack Card Name",
"img": "attack-image.webp",
"blockZone": "high",
"backside": {
"name": "Backside Card Name",
"img": "backside-image.webp"
}
}
]
}
]
}
]
| Field | Type | Description |
|---|---|---|
player |
string | Playerโs display name (used in dropdown) |
deckName |
string | Full deck name for display |
character |
string | Main character name |
character_slot |
array | Character slot sections |
main_deck |
array | Main deck sections by card type |
| Field | Type | Description |
|---|---|---|
qty |
number | Number of copies of this card |
card |
string | Full card name |
img |
string | Image filename (relative to assets/cardImages/) |
blockZone |
enum | Block zone: "high", "mid", "low", "none" |
backside |
object? | Optional flip card info with name and img |
| Aspect | Details |
|---|---|
| Loading | tam/streamer.html fetches decks.json at runtime via fetch() |
| UI Population | Player dropdowns auto-populate from player + deckName |
| Card Broadcasting | Click handlers broadcast selected cards via Ably |
| Image Resolution | All img paths prefixed with assets/cardImages/ |
| Error Handling | Graceful fallbacks for network/parsing failures |
| File Format | Minified single-line JSON (excluded from Prettier) |
| Performance | Large dataset (11k+ lines) optimized for speed |
src/styles/ directory$primary-color: #007bff;
$spacer: 1rem;
$border-radius: 0.375rem;
@mixin button-variant($color, $background, $border) {
color: $color;
background-color: $background;
border-color: $border;
}
@include media-breakpoint-up(md) {
.card-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
}
.scss) are tracked in Git as source code.css) and source maps (.css.map) are committed automaticallyFor the best development experience, install the โLive SASS Compilerโ extension:
Ctrl+Shift+X / Cmd+Shift+X) {
"liveSassCompile.settings.formats": [
{
"format": "compressed",
"extensionName": ".css",
"savePath": "/src/styles"
}
],
"liveSassCompile.settings.includeItems": ["/src/styles/**/*.scss"]
}
The admin system provides remote refresh control for stream displays, enabling instant refresh of broadcast views across multiple windows, tabs, and iframe embeds.
/tam/admin/index.html)ADMIN_REFRESH_BROADCASTADMIN_REFRESH_CONTROLS/tam/index.html)ADMIN_REFRESH_BROADCAST โ Refreshes the stream display itselfADMIN_REFRESH_CONTROLS โ Refreshes the parent page (if in iframe)ADMIN_REFRESH_REQUEST messages/tam/admin/index.htmlsrc/styles/admin.scssassets/css/admin.cssnpm run build:adminnpm run watch:adminThe Card Checker is a standalone utility located in cardChecker/ that provides a comprehensive view of all unique
cards across all tournament decks. It features advanced filtering, search capabilities, and a modern web component
architecture.
<card-item> components for consistent renderingThe Card Checker has been significantly simplified with a cleaner TypeScript architecture:
interface Card {
card: string; // Card name
img?: string; // Image filename
qty: number; // Quantity in deck
symbols?: string[]; // Card symbols for filtering
blockZone?: string; // Block zone: "high", "mid", "low", "none"
type?: string; // Card type: "Characters", "Attacks", etc.
backside?: {
// Optional flip card info
name: string;
img: string;
} | null;
}
EnhancedCard interface - Single Card interface now contains all datasource field - No longer tracking card originblockZone directly instead of blockZoneSymbol// Extract unique cards using Set for simplicity
const uniqueCards = new Set<string>();
const allCards: Card[] = [];
// Process all sections uniformly
sections.forEach((section) => {
if (section.cards) {
section.cards.forEach((card) => {
if (card && card.card && !uniqueCards.has(card.card)) {
uniqueCards.add(card.card);
allCards.push({
...card,
type: section.type || 'Unknown',
blockZone: card.blockZone || 'unknown',
});
}
});
}
});
cardChecker/index.htmlcardChecker/assets/ts/cardChecker/assets/js/ (automatically generated)cardChecker/assets/scss/cardChecker/assets/css/ (automatically generated)npm run build:card-checkernpm run watch:card-checkerThe Deck Viewer is a modernized version of the original deck viewer with component-based architecture. It provides a comprehensive view of individual player decks with enhanced web components and clean TypeScript architecture.
<deck-card-item>Displays individual cards with image, name, quantity, and metadata.
<deck-card-item card-name="Attack Card" quantity="4" image="attack.webp" block-zone="high"> </deck-card-item>
<deck-section>Organizes cards by sections (Character, Main Deck, Side Board) with type groupings.
<deck-section section-name="Main Deck" section-data='[{"type":"Attacks","cards":[...]}]'> </deck-section>
<deck-info-panel>Displays player information, deck name, character, and statistics.
<deck-info-panel
player-name="Player Name"
deck-name="Deck Name"
character="Character"
main-deck-count="40"
side-board-count="15"
>
</deck-info-panel>
Uses namespaced interfaces to avoid conflicts with other modules:
namespace DeckViewer {
export interface Card {
card: string;
img?: string;
qty: number;
blockZone?: string;
backside?: { name: string; img: string } | null;
}
export interface Section {
type: string;
cards: Card[];
}
export interface PlayerDeck {
player: string;
deckName?: string;
character?: string;
character_slot?: Section[];
main_deck?: Section[];
side_board?: Section[];
}
}
deckViewer/index.htmldeckViewer/assets/ts/deckViewer/assets/js/ (automatically generated)deckViewer/assets/scss/deckViewer/assets/css/ (automatically generated)npm run build:deckviewernpm run watch:deckviewer| Benefit | Description |
|---|---|
| Short-lived | Tokens expire automatically, limiting exposure |
| Revocable | Can be revoked immediately if compromised |
| Scoped | Tokens have only necessary permissions |
| Secure | API keys never exposed in client code |
| Auto-refresh | SDK handles renewal automatically |
In development mode, these functions are available in the browser console:
// Broadcast view debugging
window.debugBroadcast.updateMainImage('path/to/card.webp');
window.debugBroadcast.resetToCardBack();
window.debugBroadcast.currentCardImage();
window.debugBroadcast.isAnimating();
// Streamer view debugging
window.debugBroadcastController.broadcastMainImageUpdate('path/to/card.webp');
window.debugBroadcastController.isConnected();
window.debugBroadcastController.testBroadcast();
The generated tokens have these capabilities for the tournament-stream channel:
subscribe: Receive messages and state updatespublish: Send messages and state updatespresence: Join/leave presencehistory: Access message historyThe broadcast view uses a sophisticated two-image keyframes animation system for smooth card transitions:
<div class="card-scene">
<div class="card">
<div class="card-face card-back">
<img src="card_back.webp" id="backImage" />
</div>
<div class="card-face card-front">
<img src="current_card.webp" id="frontImage" />
</div>
</div>
</div>
| Animation | Trigger | Duration | Method |
|---|---|---|---|
| Flip | Card back โ Card front | 0.5s | CSS keyframes with 3D transforms |
| Slide | Card front โ Different card | 0.5s | Temporary DOM element overlay |
State Management:
isCardBackShowing() - Checks CSS class state to determine visible cardcard-back visible, card-front hidden behindCard Back Showing + New Card:
card-front srccardFlip keyframes animationcard-front now visible on topCard Front Showing + New Card:
card-front src underneathReset to Card Back:
cardUnFlip animationtransform-style: preserve-3drotateY() transitions with subtle rotateZ() effectsgraph LR
A[decks.json] --> B[Player Dropdown]
B --> C[Card Selection]
C --> D[Ably Broadcast]
D --> E[Live Display]
.broadcast-iframe {
width: 300px;
height: 420px;
border: none;
background: transparent;
display: block;
margin: 0 auto;
}
The application uses heartbeat mechanisms to maintain connection health and handle visibility changes:
// Check last heartbeat timestamp
window.debugBroadcast.getHeartbeat();
// Get heartbeat age in minutes/seconds
window.debugBroadcast.getHeartbeatAge();
The workflow outputs:
# Setup
npm install # Install dependencies
npm run build # Build all assets (SASS + TypeScript)
# Development
npm run watch:index # Watch broadcast view SASS
npm run watch:streamer # Watch streamer view SASS
npm run watch:typescript # Watch TypeScript files
npm run test:watch # Watch mode testing
# Quality
npm test # Run all tests
npm run format # Format code
| Aspect | Standard |
|---|---|
| Code Style | Prettier with 2-space indentation |
| Testing | Vitest with 70%+ coverage |
| Styling | SASS with BEM methodology |
| Commits | Conventional commits |
git checkout -b feature/your-featurenpm test && npm run formatThe project uses Husky pre-commit hooks to automatically build and stage compiled assets when source files change:
cardChecker/assets/ts/*.ts and deckViewer/assets/ts/*.ts.js and .js.map files to respective assets/js/ directories.min.js and .min.js.map files for productioncompiled-ts/ directorysrc/styles/*.scss, cardChecker/assets/scss/*.scss, deckViewer/assets/scss/*.scss.css and .css.map files to respective assets/css/ directoriesโ
Smart Detection - Only builds what changed (TypeScript, SASS, etc.)
โ
Automatic Cleanup - Removes temporary build directories (compiled-ts/)
โ
Auto-staging - Adds compiled assets to git commit automatically
โ
Build Verification - Ensures no temporary directories remain
โ
Comprehensive Coverage - Handles all project modules (main, cardChecker, deckViewer)
# Build everything
npm run build
# Build specific components
npm run build:typescript # All TypeScript projects
npm run build:sass # Main project SASS
npm run build:cardchecker # Card checker (SASS only)
npm run build:deckviewer # Deck viewer (SASS + TypeScript)
# Watch modes for development
npm run watch:sass # Watch main SASS files
npm run watch:typescript # Watch all TypeScript files
npm run watch:cardchecker # Watch card checker SASS
npm run watch:deckviewer # Watch deck viewer files
# Cleanup commands
npm run clean:typescript # Remove compiled-ts directory
npm run clean:all # Remove all compiled assets
.ts, .scss) - tracked in gitThis project is licensed under the MIT License - see the LICENSE file for details.
| Issue Type | Resource |
|---|---|
| General Issues | Troubleshooting Guide |
| Ably Integration | Ably Documentation |
| Deployment | GitHub Actions logs |
| Bugs/Features | GitHub Issues |