about summary refs log tree commit diff stats
path: root/html/cards
diff options
context:
space:
mode:
Diffstat (limited to 'html/cards')
-rw-r--r--html/cards/cards.js441
-rw-r--r--html/cards/fonts/DotGothic16-Regular.ttfbin0 -> 2027048 bytes
-rw-r--r--html/cards/fonts/OFL copy.txt93
-rw-r--r--html/cards/fonts/OFL.txt93
-rw-r--r--html/cards/fonts/PressStart2P-Regular.ttfbin0 -> 116008 bytes
-rw-r--r--html/cards/index.html36
m---------html/cards/pokemon-font0
7 files changed, 663 insertions, 0 deletions
diff --git a/html/cards/cards.js b/html/cards/cards.js
new file mode 100644
index 0000000..98aa0e1
--- /dev/null
+++ b/html/cards/cards.js
@@ -0,0 +1,441 @@
+/**
+ * @fileOverview Trying to make an easily extensible bit of code to handle
+ * creating and drawing any number of decks of cards. 
+ * 
+ * @author: eli_oat
+ * @license: no gods, no masters
+ */
+
+/**
+ * @typedef {Object} Card
+ * @property {number} x
+ * @property {number} y
+ * @property {CardData} card
+ * @property {boolean} isFaceUp
+ */
+
+/**
+ * @typedef {Object} CardData
+ * @property {string} suit
+ * @property {string} value
+ */
+
+/**
+ * @typedef {Object} GameState
+ * @property {Card[]} cards
+ * @property {Card|null} draggingCard
+ * @property {CardData[]} deck
+ * @property {{x: number, y: number}} stackPosition
+ */
+
+const CARD_WIDTH = 100;
+const CARD_HEIGHT = 150;
+const PADDING = 10;
+const SUITS = ['❤️', '♦️', '♣️', '♠️'];
+const VALUES = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
+const PATTERN_SIZE = 10;
+const INITIAL_CARD_X = 20;
+const INITIAL_CARD_Y = 20;
+const FONT_SIZE = '34px "pokemon-font", monospace';
+const CARD_BORDER_COLOR = '#000000';
+const CARD_FACE_COLOR = '#FFFFFF';
+const DECK_COUNT = 4; // Can be changed to any number
+const BASE_COLORS = [
+    { primary: '#FF9900', secondary: '#FFCC00' }, // Original orange deck
+    { primary: '#6B8E23', secondary: '#9ACD32' }, // Olive green deck
+    { primary: '#4169E1', secondary: '#87CEEB' }, // Royal blue deck
+    { primary: '#8B008B', secondary: '#DA70D6' }, // Purple deck
+    { primary: '#CD853F', secondary: '#DEB887' }  // Brown deck
+];
+
+
+// Pile layout
+const PILE_SPACING = CARD_WIDTH + PADDING * 4; // Space between piles
+const PILE_OFFSET = 5; // Vertical offset for stacked cards
+
+
+// Setting up the canvas
+const canvas = document.getElementById('cards');
+const ctx = canvas.getContext('2d');
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+
+
+/**
+ * Shuffles an array in place and returns a new shuffled array.
+ * @param {Array} array - The array to shuffle.
+ * @returns {Array} A new array containing the shuffled elements.
+ */
+const shuffle = array => {
+    const result = [...array];
+    for (let i = result.length - 1; i > 0; i--) {
+        const j = Math.floor(Math.random() * (i + 1));
+        [result[i], result[j]] = [result[j], result[i]];
+    }
+    return result;
+};
+
+
+/**
+ * Creates a deck of cards for a given deck index.
+ * @param {number} deckIndex - The index of the deck being created.
+ * @returns {Array} An array of card objects, each containing suit, value, and deckId.
+ */
+const createDeck = (deckIndex) => SUITS.flatMap(suit => 
+    VALUES.map(value => ({ 
+        suit, 
+        value,
+        deckId: deckIndex // Add deckId to track which deck a card belongs to
+    }))
+);
+
+/**
+ * Creates multiple decks of cards based on the specified count.
+ * If the count exceeds the number of unique deck colors defined, 
+ * some decks will repeat colors.
+ * 
+ * @param {number} count - The number of decks to create.
+ * @returns {Array} An array of card objects, each containing suit, value, and deckId.
+ */
+const createDecks = (count) => {
+    if (count > BASE_COLORS.length) {
+        console.warn(`Only ${BASE_COLORS.length} unique deck colors are defined. Some decks will repeat colors.`);
+    }
+    return Array.from({ length: count }, (_, i) => createDeck(i)).flat();
+};
+
+/**
+ * Creates a card object with a known position and some card data.
+ * @param {number} x - The x-coordinate of the card.
+ * @param {number} y - The y-coordinate of the card.
+ * @param {CardData} cardData - The data for the card, including suit and value.
+ * @returns {Card} A new card object.
+ */
+const createCard = (x, y, cardData) => Object.freeze({
+    x: x + PADDING,
+    y: y + PADDING,
+    card: Object.freeze({ ...cardData }),
+    isFaceUp: false
+});
+
+/**
+ * Determines if a point is within a card.
+ * Used to determine where to hold a card when dragging.
+ * @param {number} x - The x-coordinate of the point.
+ * @param {number} y - The y-coordinate of the point.
+ * @param {Card} card - The card to check by card object reference.
+ * @returns {boolean} True if the point is within the card.
+ */
+const isPointInCard = (x, y, card) =>
+    x >= card.x && x <= card.x + CARD_WIDTH && y >= card.y && y <= card.y + CARD_HEIGHT;
+
+const clearCanvas = () => {
+    ctx.fillStyle = 'beige';
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+};
+
+const drawCardBack = card => {
+    ctx.fillRect(card.x, card.y, CARD_WIDTH, CARD_HEIGHT);
+    drawRetroPattern(card);
+    ctx.strokeStyle = CARD_BORDER_COLOR;
+    ctx.strokeRect(card.x, card.y, CARD_WIDTH, CARD_HEIGHT);
+};
+
+const drawRetroPattern = card => {
+    const checkeredSize = 10;
+    const deckColors = BASE_COLORS[card.card.deckId % BASE_COLORS.length];
+    
+    for (let i = 0; i < CARD_WIDTH; i += checkeredSize) {
+        for (let j = 0; j < CARD_HEIGHT; j += checkeredSize) {
+            ctx.fillStyle = (Math.floor(i / checkeredSize) + Math.floor(j / checkeredSize)) % 2 === 0 
+                ? deckColors.primary 
+                : deckColors.secondary;
+            ctx.fillRect(card.x + i, card.y + j, checkeredSize, checkeredSize);
+        }
+    }
+};
+
+const drawCardFront = card => {
+    ctx.fillStyle = CARD_FACE_COLOR;
+    ctx.fillRect(card.x, card.y, CARD_WIDTH, CARD_HEIGHT);
+    ctx.fillStyle = CARD_BORDER_COLOR;
+    ctx.font = FONT_SIZE;
+    ctx.strokeRect(card.x, card.y, CARD_WIDTH, CARD_HEIGHT);
+    
+    drawCardValue(card.card.value, card.x + 12, card.y + 42, 'left');
+    drawCardSuit(card.card.suit, card.x + CARD_WIDTH / 2, card.y + CARD_HEIGHT / 2 + 20);
+};
+
+const drawCardValue = (value, x, y, alignment) => {
+    ctx.textAlign = alignment;
+    ctx.fillStyle = CARD_BORDER_COLOR;
+    ctx.fillText(value, x, y);
+};
+
+const drawCardSuit = (suit, x, y) => {
+    ctx.textAlign = 'center';
+    ctx.fillStyle = CARD_BORDER_COLOR;
+    ctx.fillText(suit, x, y);
+};
+
+/**
+ * Renders a card, determining which side to draw based on its face-up state.
+ * @param {Card} card - The card to render by card object reference.
+ */
+const renderCard = card => {
+    card.isFaceUp ? drawCardFront(card) : drawCardBack(card);
+};
+
+const renderAllCards = cards => {
+    clearCanvas();
+    cards.forEach(renderCard);
+};
+
+let gameState;
+
+const initializeGameState = () => ({
+    cards: [],
+    draggingCard: null,
+    deck: shuffle(createDecks(DECK_COUNT)),
+    stackPosition: { x: 0, y: 0 }
+});
+
+const initializeGame = () => {
+    try {
+        gameState = initializeGameState();
+        
+        // Group cards by deck
+        const cardsByDeck = gameState.deck.reduce((acc, cardData) => {
+            const deckId = cardData.deckId;
+            if (!acc[deckId]) acc[deckId] = [];
+            acc[deckId].push(cardData);
+            return acc;
+        }, {});
+
+        // Calculate starting X position to center all piles
+        // FIXME: How can I make the deck position be dynamic?
+        const totalWidth = PILE_SPACING * DECK_COUNT;
+        const startX = (canvas.width - totalWidth) / 2;
+
+        // Create cards for each deck in its own pile
+        gameState.cards = Object.entries(cardsByDeck).flatMap(([deckId, deckCards]) => {
+            const pileX = startX + (parseInt(deckId) * PILE_SPACING);
+            
+            return deckCards.map((cardData, indexInDeck) => 
+                createCard(
+                    pileX,
+                    INITIAL_CARD_Y + (indexInDeck * PILE_OFFSET),
+                    cardData
+                )
+            );
+        });
+
+        // TODO: Consider adding another level Box > Deck > Pile > Card
+
+        clearCanvas();
+        renderAllCards(gameState.cards);
+        setupEventListeners();
+
+    } catch (error) {
+        console.error('Failed to initialize game:', error);
+        alert('Failed to initialize game. Please refresh the page.');
+    }
+};
+
+const setupEventListeners = () => {
+    canvas.addEventListener('mousedown', handleMouseDown);
+    canvas.addEventListener('contextmenu', e => e.preventDefault());
+    document.addEventListener('keydown', e => {
+        if (e.key === 'q') handleResetGame();
+    });
+};
+
+const handleMouseMove = e => {
+    if (!gameState.draggingCard) return;
+
+    const rect = canvas.getBoundingClientRect();
+    const newX = e.clientX - rect.left - dragOffset.x;
+    const newY = e.clientY - rect.top - dragOffset.y;
+
+    const updatedCard = moveCard(gameState.draggingCard, newX, newY);
+    gameState.cards = gameState.cards.map(card => 
+        card === gameState.draggingCard ? updatedCard : card
+    );
+    gameState.draggingCard = updatedCard;
+
+    renderAllCards(gameState.cards);
+};
+
+const handleMouseUp = e => {
+    if (!gameState.draggingCard) {
+        const rect = canvas.getBoundingClientRect();
+        const x = e.clientX - rect.left;
+        const y = e.clientY - rect.top;
+
+        // Was the card clicked?
+        const clickedCard = gameState.cards.slice().reverse().find(card => isPointInCard(x, y, card));
+        if (clickedCard) {
+            // Move the clicked card to the top of the stack
+            gameState.cards = gameState.cards.filter(card => card !== clickedCard);
+            gameState.cards.push(clickedCard);
+            renderAllCards(gameState.cards); // Re-render all cards
+        }
+    }
+
+    gameState.draggingCard = null;
+    document.removeEventListener('mousemove', handleMouseMove);
+    document.removeEventListener('mouseup', handleMouseUp);
+};
+
+let dragOffset = { x: 0, y: 0 }; // To store the offset of the click position
+
+/**
+ * Finds the card that was clicked.
+ * @param {number} x - The x-coordinate of the click.
+ * @param {number} y - The y-coordinate of the click.
+ * @param {Card[]} cards - The list of cards to search through by card object reference.
+ * @returns {Card|null} The card that was clicked, or null if no card was clicked.
+ */
+const findClickedCard = (x, y, cards) => 
+    cards.slice().reverse().find(card => isPointInCard(x, y, card));
+
+/**
+ * Moves a card to the top of the stack.
+ * @param {Card} targetCard - The card to move to the top.
+ * @param {Card[]} cards - The list of cards to search through by card object reference.
+ * @returns {Card[]} A new array with the target card moved to the top.
+ */
+const moveCardToTop = (targetCard, cards) => [
+    ...cards.filter(card => card !== targetCard),
+    targetCard
+];
+
+const handleMouseDown = e => {
+    const rect = canvas.getBoundingClientRect();
+    const x = e.clientX - rect.left;
+    const y = e.clientY - rect.top;
+
+    if (e.button === 2) {
+        e.preventDefault();
+        const clickedCard = findClickedCard(x, y, gameState.cards);
+        if (clickedCard) {
+            const updatedCard = toggleCardFace(clickedCard);
+            gameState.cards = gameState.cards.map(card => 
+                card === clickedCard ? updatedCard : card
+            );
+            renderAllCards(gameState.cards);
+        }
+        return;
+    }
+
+    const clickedCard = findClickedCard(x, y, gameState.cards);
+    if (clickedCard) {
+        gameState.draggingCard = clickedCard;
+        dragOffset = {
+            x: x - clickedCard.x,
+            y: y - clickedCard.y
+        };
+        gameState.cards = moveCardToTop(clickedCard, gameState.cards);
+        
+        document.addEventListener('mousemove', handleMouseMove);
+        document.addEventListener('mouseup', handleMouseUp);
+    }
+};
+
+const handleResetGame = () => {
+    if (confirm("Would you like to reset the cards?")) {
+        resetCardsToOriginalPiles();
+    }
+};
+
+/**
+ * Moves a card to a new position.
+ * @param {Card} card - The card to move.
+ * @param {number} newX - The new x-coordinate for the card.
+ * @param {number} newY - The new y-coordinate for the card.
+ * @returns {Card} A new card object with updated position.
+ */
+const moveCard = (card, newX, newY) => ({
+    ...card,
+    x: newX,
+    y: newY
+});
+
+const toggleCardFace = card => ({
+    ...card,
+    isFaceUp: !card.isFaceUp
+});
+
+/**
+ * Calculates some stats for each deck, including total and face-up counts.
+ * @returns {Map} A map containing the total and face-up counts for each deck. 
+ * Useful for debugging information to the canvas.
+ */
+const getDeckStats = () => {
+    const stats = new Map();
+    gameState.cards.forEach(card => {
+        const deckId = card.card.deckId;
+        const current = stats.get(deckId) || { total: 0, faceUp: 0 };
+        stats.set(deckId, {
+            total: current.total + 1,
+            faceUp: current.faceUp + (card.isFaceUp ? 1 : 0)
+        });
+    });
+    return stats;
+};
+
+const renderDeckStats = () => {
+    const stats = getDeckStats();
+    ctx.font = '16px "pokemon-font", monospace';
+    
+    // Calculate the same starting X position as the piles
+    const totalWidth = PILE_SPACING * DECK_COUNT;
+    const startX = (canvas.width - totalWidth) / 2;
+    
+    stats.forEach((stat, deckId) => {
+        const colors = BASE_COLORS[deckId % BASE_COLORS.length];
+        const pileX = startX + (deckId * PILE_SPACING);
+        
+        ctx.fillStyle = colors.primary;
+        ctx.textAlign = 'center';
+        ctx.fillText(
+            `Deck ${deckId + 1}: ${stat.faceUp}/${stat.total}`, 
+            pileX + CARD_WIDTH / 2,
+            INITIAL_CARD_Y - 10
+        );
+    });
+};
+
+// FIXME: this is too complicated, and would probably work better if I had a better way of handling state. 
+const resetCardsToOriginalPiles = () => {
+    const totalWidth = PILE_SPACING * DECK_COUNT;
+    const startX = (canvas.width - totalWidth) / 2;
+
+    // Group cards by deck
+    const cardsByDeck = gameState.cards.reduce((acc, card) => {
+        const deckId = card.card.deckId;
+        if (!acc[deckId]) acc[deckId] = [];
+        acc[deckId].push(card);
+        return acc;
+    }, {});
+
+    // Reset position for each deck
+    Object.entries(cardsByDeck).forEach(([deckId, deckCards]) => {
+        const pileX = startX + (parseInt(deckId) * PILE_SPACING);
+        
+        deckCards.forEach((card, index) => {
+            card.x = pileX;
+            card.y = INITIAL_CARD_Y + (index * PILE_OFFSET);
+            card.isFaceUp = false;
+        });
+    });
+
+    renderAllCards(gameState.cards);
+};
+
+initializeGame();
+
+window.addEventListener('unload', () => {
+    canvas.removeEventListener('mousedown', handleMouseDown);
+    canvas.removeEventListener('contextmenu', e => e.preventDefault());
+}); 
\ No newline at end of file
diff --git a/html/cards/fonts/DotGothic16-Regular.ttf b/html/cards/fonts/DotGothic16-Regular.ttf
new file mode 100644
index 0000000..6634bc1
--- /dev/null
+++ b/html/cards/fonts/DotGothic16-Regular.ttf
Binary files differdiff --git a/html/cards/fonts/OFL copy.txt b/html/cards/fonts/OFL copy.txt
new file mode 100644
index 0000000..7c6649c
--- /dev/null
+++ b/html/cards/fonts/OFL copy.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The DotGothic16 Project Authors (https://github.com/fontworks-fonts/DotGothic16)

+

+This Font Software is licensed under the SIL Open Font License, Version 1.1.

+This license is copied below, and is also available with a FAQ at:

+https://openfontlicense.org

+

+

+-----------------------------------------------------------

+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007

+-----------------------------------------------------------

+

+PREAMBLE

+The goals of the Open Font License (OFL) are to stimulate worldwide

+development of collaborative font projects, to support the font creation

+efforts of academic and linguistic communities, and to provide a free and

+open framework in which fonts may be shared and improved in partnership

+with others.

+

+The OFL allows the licensed fonts to be used, studied, modified and

+redistributed freely as long as they are not sold by themselves. The

+fonts, including any derivative works, can be bundled, embedded, 

+redistributed and/or sold with any software provided that any reserved

+names are not used by derivative works. The fonts and derivatives,

+however, cannot be released under any other type of license. The

+requirement for fonts to remain under this license does not apply

+to any document created using the fonts or their derivatives.

+

+DEFINITIONS

+"Font Software" refers to the set of files released by the Copyright

+Holder(s) under this license and clearly marked as such. This may

+include source files, build scripts and documentation.

+

+"Reserved Font Name" refers to any names specified as such after the

+copyright statement(s).

+

+"Original Version" refers to the collection of Font Software components as

+distributed by the Copyright Holder(s).

+

+"Modified Version" refers to any derivative made by adding to, deleting,

+or substituting -- in part or in whole -- any of the components of the

+Original Version, by changing formats or by porting the Font Software to a

+new environment.

+

+"Author" refers to any designer, engineer, programmer, technical

+writer or other person who contributed to the Font Software.

+

+PERMISSION & CONDITIONS

+Permission is hereby granted, free of charge, to any person obtaining

+a copy of the Font Software, to use, study, copy, merge, embed, modify,

+redistribute, and sell modified and unmodified copies of the Font

+Software, subject to the following conditions:

+

+1) Neither the Font Software nor any of its individual components,

+in Original or Modified Versions, may be sold by itself.

+

+2) Original or Modified Versions of the Font Software may be bundled,

+redistributed and/or sold with any software, provided that each copy

+contains the above copyright notice and this license. These can be

+included either as stand-alone text files, human-readable headers or

+in the appropriate machine-readable metadata fields within text or

+binary files as long as those fields can be easily viewed by the user.

+

+3) No Modified Version of the Font Software may use the Reserved Font

+Name(s) unless explicit written permission is granted by the corresponding

+Copyright Holder. This restriction only applies to the primary font name as

+presented to the users.

+

+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font

+Software shall not be used to promote, endorse or advertise any

+Modified Version, except to acknowledge the contribution(s) of the

+Copyright Holder(s) and the Author(s) or with their explicit written

+permission.

+

+5) The Font Software, modified or unmodified, in part or in whole,

+must be distributed entirely under this license, and must not be

+distributed under any other license. The requirement for fonts to

+remain under this license does not apply to any document created

+using the Font Software.

+

+TERMINATION

+This license becomes null and void if any of the above conditions are

+not met.

+

+DISCLAIMER

+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF

+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT

+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE

+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,

+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL

+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM

+OTHER DEALINGS IN THE FONT SOFTWARE.

diff --git a/html/cards/fonts/OFL.txt b/html/cards/fonts/OFL.txt
new file mode 100644
index 0000000..70041e1
--- /dev/null
+++ b/html/cards/fonts/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2012 The Press Start 2P Project Authors (cody@zone38.net), with Reserved Font Name "Press Start 2P".

+

+This Font Software is licensed under the SIL Open Font License, Version 1.1.

+This license is copied below, and is also available with a FAQ at:

+https://openfontlicense.org

+

+

+-----------------------------------------------------------

+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007

+-----------------------------------------------------------

+

+PREAMBLE

+The goals of the Open Font License (OFL) are to stimulate worldwide

+development of collaborative font projects, to support the font creation

+efforts of academic and linguistic communities, and to provide a free and

+open framework in which fonts may be shared and improved in partnership

+with others.

+

+The OFL allows the licensed fonts to be used, studied, modified and

+redistributed freely as long as they are not sold by themselves. The

+fonts, including any derivative works, can be bundled, embedded, 

+redistributed and/or sold with any software provided that any reserved

+names are not used by derivative works. The fonts and derivatives,

+however, cannot be released under any other type of license. The

+requirement for fonts to remain under this license does not apply

+to any document created using the fonts or their derivatives.

+

+DEFINITIONS

+"Font Software" refers to the set of files released by the Copyright

+Holder(s) under this license and clearly marked as such. This may

+include source files, build scripts and documentation.

+

+"Reserved Font Name" refers to any names specified as such after the

+copyright statement(s).

+

+"Original Version" refers to the collection of Font Software components as

+distributed by the Copyright Holder(s).

+

+"Modified Version" refers to any derivative made by adding to, deleting,

+or substituting -- in part or in whole -- any of the components of the

+Original Version, by changing formats or by porting the Font Software to a

+new environment.

+

+"Author" refers to any designer, engineer, programmer, technical

+writer or other person who contributed to the Font Software.

+

+PERMISSION & CONDITIONS

+Permission is hereby granted, free of charge, to any person obtaining

+a copy of the Font Software, to use, study, copy, merge, embed, modify,

+redistribute, and sell modified and unmodified copies of the Font

+Software, subject to the following conditions:

+

+1) Neither the Font Software nor any of its individual components,

+in Original or Modified Versions, may be sold by itself.

+

+2) Original or Modified Versions of the Font Software may be bundled,

+redistributed and/or sold with any software, provided that each copy

+contains the above copyright notice and this license. These can be

+included either as stand-alone text files, human-readable headers or

+in the appropriate machine-readable metadata fields within text or

+binary files as long as those fields can be easily viewed by the user.

+

+3) No Modified Version of the Font Software may use the Reserved Font

+Name(s) unless explicit written permission is granted by the corresponding

+Copyright Holder. This restriction only applies to the primary font name as

+presented to the users.

+

+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font

+Software shall not be used to promote, endorse or advertise any

+Modified Version, except to acknowledge the contribution(s) of the

+Copyright Holder(s) and the Author(s) or with their explicit written

+permission.

+

+5) The Font Software, modified or unmodified, in part or in whole,

+must be distributed entirely under this license, and must not be

+distributed under any other license. The requirement for fonts to

+remain under this license does not apply to any document created

+using the Font Software.

+

+TERMINATION

+This license becomes null and void if any of the above conditions are

+not met.

+

+DISCLAIMER

+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF

+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT

+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE

+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,

+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL

+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM

+OTHER DEALINGS IN THE FONT SOFTWARE.

diff --git a/html/cards/fonts/PressStart2P-Regular.ttf b/html/cards/fonts/PressStart2P-Regular.ttf
new file mode 100644
index 0000000..2442aff
--- /dev/null
+++ b/html/cards/fonts/PressStart2P-Regular.ttf
Binary files differdiff --git a/html/cards/index.html b/html/cards/index.html
new file mode 100644
index 0000000..6c6c25e
--- /dev/null
+++ b/html/cards/index.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="One should always play fairly when one has the winning cards - Oscar Wilde">
+    <title>🃏 cards 🃏</title>
+    <style>
+        @font-face {
+            font-family: 'pokemon-font';
+            src: url('./pokemon-font/fonts/pokemon-font.ttf') format('ttf'); /* IE9 Compat Modes */
+            src: url('./pokemon-font/fonts/pokemon-font.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+                url('./pokemon-font/fonts/pokemon-font.woff2') format('woff2'), /* Super Modern Browsers */
+                url('./pokemon-font/fonts/pokemon-font.woff') format('woff'), /* Pretty Modern Browsers */
+                url('./pokemon-font/fonts/pokemon-font.ttf')  format('truetype') /* Safari, Android, iOS */
+        }
+
+        body {
+            margin: 0;
+            padding: 0;
+            font-smooth: never;
+            -webkit-font-smoothing: none;
+            font-family: "pokemon-font", monospace;
+            font-size: 16px;
+        }
+
+        canvas {
+            display: block;
+        }
+    </style>
+</head>
+<body>
+    <canvas id="cards"></canvas>
+    <script src="./cards.js"></script>
+</body>
+</html>
diff --git a/html/cards/pokemon-font b/html/cards/pokemon-font
new file mode 160000
+Subproject 81b60805150c75ebfdfcec6d8352c67e491f8a6