initial commit
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
.pytest_cache/
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule.*
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
.idea/
|
||||
# End of https://www.gitignore.io/api/python
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
from flask import request, url_for
|
||||
from flask_api import FlaskAPI, status, exceptions
|
||||
from typing import Union, Optional
|
||||
from random import choices
|
||||
|
||||
from game import CamelGame
|
||||
|
||||
app = FlaskAPI(__name__)
|
||||
|
||||
games = {}
|
||||
|
||||
allowed_id_chars = [chr(c) for c in range(0x41, 0x5B)] + [chr(c) for c in range(0x61, 0x7B)]
|
||||
allowed_id_chars.remove('l')
|
||||
allowed_id_chars.remove('I')
|
||||
|
||||
|
||||
def _generate_id():
|
||||
while True:
|
||||
new_id = ''.join(choices(allowed_id_chars, k=6))
|
||||
if new_id not in games:
|
||||
return new_id
|
||||
|
||||
|
||||
def _get_game_obj(game_id: str) -> Optional[CamelGame]:
|
||||
return games.get(game_id, None)
|
||||
|
||||
|
||||
@app.route('/game/<game_id>', methods=['GET'])
|
||||
def get_game(game_id: str):
|
||||
game = _get_game_obj(game_id)
|
||||
if game is None:
|
||||
return '', status.HTTP_404_NOT_FOUND
|
||||
|
||||
return {
|
||||
'status': game.state,
|
||||
'amount_left': game.side_length,
|
||||
'winning_ace': game.get_winner(),
|
||||
'left_cards': game.get_open_side_cards(),
|
||||
'aces': game.ace_position,
|
||||
'drinks': game.get_drinks(),
|
||||
'bets': game.bets,
|
||||
}
|
||||
|
||||
|
||||
@app.route('/game/<game_id>/start', methods=['GET'])
|
||||
def start_game(game_id: str):
|
||||
game = _get_game_obj(game_id)
|
||||
if game is None:
|
||||
return '', status.HTTP_404_NOT_FOUND
|
||||
|
||||
return {
|
||||
'started': game.start_game()
|
||||
}
|
||||
|
||||
|
||||
@app.route('/game/<game_id>/bet', methods=['POST'])
|
||||
def bet_on_game(game_id: str):
|
||||
game = _get_game_obj(game_id)
|
||||
if game is None:
|
||||
return '', status.HTTP_404_NOT_FOUND
|
||||
|
||||
user = request.data['USER']
|
||||
suit = request.data['SUIT']
|
||||
amount = int(request.data['AMOUNT'])
|
||||
|
||||
if game.bet(user, suit, amount):
|
||||
return {'ok': True}
|
||||
return {'ok': False}
|
||||
|
||||
|
||||
@app.route('/game/<game_id>/round', methods=['GET'])
|
||||
def do_round(game_id: str):
|
||||
game = _get_game_obj(game_id)
|
||||
if game is None:
|
||||
return '', status.HTTP_404_NOT_FOUND
|
||||
|
||||
card = game.do_round()
|
||||
|
||||
if card is None:
|
||||
return "game probably already done", status.HTTP_400_BAD_REQUEST
|
||||
|
||||
return {
|
||||
'card': card
|
||||
}
|
||||
|
||||
|
||||
@app.route('/game/', methods=['GET'])
|
||||
def start_new_game():
|
||||
game = CamelGame()
|
||||
game_id = _generate_id()
|
||||
games[game_id] = game
|
||||
game.init_game()
|
||||
return {
|
||||
'initialized': True,
|
||||
'url': url_for('get_game', game_id=game_id)
|
||||
}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
from game import CamelGame
|
||||
|
||||
game = CamelGame()
|
||||
|
||||
if __name__ != '__main__':
|
||||
print("Not a module!")
|
||||
exit(1)
|
||||
|
||||
|
||||
def print_game():
|
||||
print(f'status: {game.state}')
|
||||
print(', '.join(game.get_open_side_cards()))
|
||||
for ace, pos in game.ace_position.items():
|
||||
print(" " * 4 * pos + ace)
|
||||
|
||||
|
||||
def print_drinks():
|
||||
drinks = game.get_drinks()
|
||||
if drinks is None:
|
||||
print("Not done yet!")
|
||||
return
|
||||
for user, amount in drinks.items():
|
||||
print(f"{user:16}{amount}")
|
||||
|
||||
|
||||
while True:
|
||||
inp = input("command: ")
|
||||
if inp == 'r':
|
||||
game.do_round()
|
||||
print_game()
|
||||
elif inp == 'i':
|
||||
game.init_game()
|
||||
elif inp.startswith('b '):
|
||||
_, u, s, a = inp.split()
|
||||
a = int(a)
|
||||
game.bet(u, s, a)
|
||||
elif inp == 's':
|
||||
game.start_game()
|
||||
elif inp == 'd':
|
||||
print_drinks()
|
||||
else:
|
||||
print("Unknown " + inp)
|
||||
@@ -0,0 +1,131 @@
|
||||
from random import shuffle
|
||||
from typing import List, Dict, Optional
|
||||
from collections import deque
|
||||
|
||||
nums = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
|
||||
suits = ['H', 'S', 'D', 'C']
|
||||
|
||||
|
||||
def get_deck(add_jokers: bool=False) -> deque:
|
||||
if add_jokers:
|
||||
jokers = ['J', 'J']
|
||||
else:
|
||||
jokers = []
|
||||
return deque([n + s for n in nums for s in suits] + jokers)
|
||||
|
||||
|
||||
def create_new_random_card_deck(add_jokers: bool=False) -> deque:
|
||||
deck = get_deck(add_jokers)
|
||||
shuffle(deck)
|
||||
return deck
|
||||
|
||||
|
||||
class CamelGame:
|
||||
deck: deque
|
||||
ace_position: dict
|
||||
|
||||
side_list: list
|
||||
side_length = 8
|
||||
side_open = 0
|
||||
|
||||
state: str
|
||||
|
||||
bets: List[Dict]
|
||||
|
||||
def __init__(self):
|
||||
self.deck = create_new_random_card_deck(False)
|
||||
for ace in ['AH', 'AS', 'AD', 'AC']:
|
||||
self.deck.remove(ace) # remove the aces
|
||||
|
||||
self.ace_position = {
|
||||
'AH': 0,
|
||||
'AS': 0,
|
||||
'AD': 0,
|
||||
'AC': 0,
|
||||
}
|
||||
|
||||
self.side_list = []
|
||||
|
||||
self.state = None
|
||||
self.bets = []
|
||||
|
||||
def init_game(self):
|
||||
for _ in range(self.side_length):
|
||||
self.side_list.append(self.deck.pop())
|
||||
|
||||
self.state = 'initialized'
|
||||
|
||||
def do_round(self) -> Optional[str]:
|
||||
if self.state != 'active':
|
||||
print("Game is not stated yet or already done")
|
||||
return None
|
||||
|
||||
card = self.popcard()
|
||||
|
||||
if any(v == self.side_length for v in self.ace_position.values()):
|
||||
self.state = 'done'
|
||||
|
||||
return card
|
||||
|
||||
def bet(self, user: str, suit: str, amount: int) -> bool:
|
||||
if self.state != 'initialized':
|
||||
return False
|
||||
if any(b['user'] == user for b in self.bets):
|
||||
return False
|
||||
if suit not in suits:
|
||||
return False
|
||||
self.bets.append({'user': user, 'suit': suit, 'amount': amount})
|
||||
return True
|
||||
|
||||
def start_game(self):
|
||||
if self.state != 'initialized':
|
||||
return False
|
||||
self.state = 'active'
|
||||
return True
|
||||
|
||||
def get_drinks(self) -> Optional[Dict]:
|
||||
if self.state != 'done':
|
||||
return None
|
||||
result = {}
|
||||
winner = self.get_winner()[-1]
|
||||
|
||||
drinks = sum(map(lambda b: b['amount'], filter(lambda b: b['suit'] == winner, self.bets)))
|
||||
|
||||
for bet in self.bets:
|
||||
if bet['suit'] == winner:
|
||||
mdrinks = 0
|
||||
else:
|
||||
mdrinks = drinks + bet['amount']
|
||||
result[bet['user']] = mdrinks
|
||||
|
||||
return result
|
||||
|
||||
def get_winner(self) -> Optional[str]:
|
||||
for card, pos in self.ace_position.items():
|
||||
if pos == self.side_length:
|
||||
return card
|
||||
return None
|
||||
|
||||
def get_open_side_cards(self) -> list:
|
||||
open_cards = self.side_list[:self.side_open]
|
||||
return open_cards + ['XX'] * (self.side_length - self.side_open)
|
||||
|
||||
def popcard(self) -> str:
|
||||
next_card = self.deck.pop()
|
||||
move_card = 'A' + next_card[-1]
|
||||
|
||||
self.ace_position[move_card] += 1
|
||||
|
||||
self.check_and_open_side()
|
||||
|
||||
return move_card
|
||||
|
||||
def check_and_open_side(self):
|
||||
if not all(v > self.side_open for v in self.ace_position.values()):
|
||||
return
|
||||
|
||||
opened_card = self.side_list[self.side_open]
|
||||
moved_ace = 'A' + opened_card[-1]
|
||||
self.ace_position[moved_ace] = self.side_open
|
||||
|
||||
self.side_open += 1
|
||||
@@ -0,0 +1,2 @@
|
||||
Flask
|
||||
Flask-API
|
||||
@@ -0,0 +1,48 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%; }
|
||||
|
||||
h1 {
|
||||
font-size: xx-large;
|
||||
font-weight: bolder; }
|
||||
|
||||
#topcards > .card {
|
||||
display: inline-block; }
|
||||
|
||||
.card-ace {
|
||||
position: relative;
|
||||
-webkit-transition: left 0.25s;
|
||||
-moz-transition: left 0.25s;
|
||||
-ms-transition: left 0.25s;
|
||||
-o-transition: left 0.25s;
|
||||
transition: left 0.25s;
|
||||
display: inline; }
|
||||
|
||||
.card {
|
||||
height: 100px; }
|
||||
.card svg {
|
||||
height: 100px; }
|
||||
|
||||
/*# sourceMappingURL=style.css.map */
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"mappings": "AAAA,UAAU;EACR,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;;AAEd,EAAE;EACA,SAAS,EAAE,QAAQ;EACnB,WAAW,EAAE,MAAM;;AAErB,iBAAiB;EACf,OAAO,EAAE,YAAY;;AAEvB,SAAS;EACP,QAAQ,EAAE,QAAQ;EAClB,kBAAkB,EAAE,UAAU;EAC9B,eAAe,EAAE,UAAU;EAC3B,cAAc,EAAE,UAAU;EAC1B,aAAa,EAAE,UAAU;EACzB,UAAU,EAAE,UAAU;EACtB,OAAO,EAAE,MAAM;;AAEjB,KAAK;EACH,MAAM,EAAE,KAAK;EACb,SAAG;IACD,MAAM,EAAE,KAAK",
|
||||
"sources": ["style.sass"],
|
||||
"names": [],
|
||||
"file": "style.css"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
html, body
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
h1
|
||||
font-size: xx-large
|
||||
font-weight: bolder
|
||||
|
||||
#topcards > .card
|
||||
display: inline-block
|
||||
|
||||
.card-ace
|
||||
position: relative
|
||||
-webkit-transition: left 0.25s
|
||||
-moz-transition: left 0.25s
|
||||
-ms-transition: left 0.25s
|
||||
-o-transition: left 0.25s
|
||||
transition: left 0.25s
|
||||
display: inline
|
||||
|
||||
.card
|
||||
height: 100px
|
||||
svg
|
||||
height: 100px
|
||||
@@ -0,0 +1,236 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Card Game</title>
|
||||
<script src="js/jquery-3.3.1.min.js"></script>
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/reset.css">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<script>
|
||||
function cardToPlayCard(card) {
|
||||
let pre, post;
|
||||
|
||||
if (card === 'XX') {
|
||||
return "svg/svg-cards.svg#back";
|
||||
}
|
||||
|
||||
switch (card[0]) {
|
||||
case 'A':
|
||||
pre = '1';
|
||||
break;
|
||||
case 'K':
|
||||
pre = 'king';
|
||||
break;
|
||||
case 'Q':
|
||||
pre = 'queen';
|
||||
break;
|
||||
case 'J':
|
||||
pre = 'jack';
|
||||
break;
|
||||
default:
|
||||
pre = card.substr(0, card.length - 1);
|
||||
}
|
||||
switch (card.substr(card.length - 1)) {
|
||||
case 'H':
|
||||
post = 'heart';
|
||||
break;
|
||||
case 'S':
|
||||
post = 'spade';
|
||||
break;
|
||||
case 'D':
|
||||
post = 'diamond';
|
||||
break;
|
||||
case 'C':
|
||||
post = 'club';
|
||||
break;
|
||||
}
|
||||
|
||||
return `svg/svg-cards.svg#${pre}_${post}`
|
||||
|
||||
}
|
||||
|
||||
$(function () {
|
||||
let btnNewGame = $("#newgame");
|
||||
let btnStartGame = $("#startgame");
|
||||
let btnDoRound = $("#doround");
|
||||
|
||||
let txtBetName = $("#bet-name");
|
||||
let selBetSuit = $("#bet-suit");
|
||||
let numBetAmount = $("#bet-amount");
|
||||
let btnBet = $("#bet");
|
||||
|
||||
let aces = {
|
||||
'AS': $("#card-ace-spades"),
|
||||
'AH': $("#card-ace-hearts"),
|
||||
'AD': $("#card-ace-diamonds"),
|
||||
'AC': $("#card-ace-clubs"),
|
||||
};
|
||||
|
||||
let templateCard = $("#template-card").html();
|
||||
|
||||
let topcards = $("#topcards");
|
||||
|
||||
let gameBase = null;
|
||||
|
||||
function buildTopCards(cards) {
|
||||
let html;
|
||||
topcards.empty();
|
||||
cards.forEach(function (card, ind, array) {
|
||||
console.log(card, ind, array);
|
||||
html = templateCard;
|
||||
html = html.replace("{href}", cardToPlayCard(card));
|
||||
topcards.append($(html));
|
||||
});
|
||||
}
|
||||
|
||||
function updateGame() {
|
||||
$.ajax({
|
||||
'url': gameBase
|
||||
}).done(function (data) {
|
||||
console.log(data);
|
||||
|
||||
buildTopCards(data['left_cards']);
|
||||
|
||||
$.each(data['aces'], function (key, value) {
|
||||
aces[key].css({'left': value * 150});
|
||||
});
|
||||
// todo check if game is done
|
||||
}).fail(function (data) {
|
||||
alert(data);
|
||||
console.log(data);
|
||||
})
|
||||
}
|
||||
|
||||
btnNewGame.click(function () {
|
||||
$.ajax({
|
||||
'url': '/game/'
|
||||
}).done(function (data) {
|
||||
gameBase = data['url'];
|
||||
updateGame();
|
||||
}).fail(function (data) {
|
||||
alert(data);
|
||||
console.log(data);
|
||||
});
|
||||
});
|
||||
|
||||
btnStartGame.click(function () {
|
||||
$.ajax({
|
||||
'url': gameBase + '/start'
|
||||
}).done(function (data) {
|
||||
updateGame();
|
||||
}).fail(function (data) {
|
||||
alert(data);
|
||||
console.log(data);
|
||||
});
|
||||
});
|
||||
|
||||
btnDoRound.click(function () {
|
||||
$.ajax({
|
||||
'url': gameBase + '/round'
|
||||
}).done(function (data) {
|
||||
console.log(data); // todo display card
|
||||
updateGame();
|
||||
}).fail(function (data) {
|
||||
alert(data);
|
||||
console.log(data);
|
||||
});
|
||||
});
|
||||
|
||||
btnBet.click(function () {
|
||||
let user, suit, amount;
|
||||
user = txtBetName.val();
|
||||
suit = selBetSuit.val();
|
||||
amount = numBetAmount.val();
|
||||
|
||||
$.ajax({
|
||||
'url': gameBase + '/bet',
|
||||
'method': 'POST',
|
||||
'data': {
|
||||
'USER': user,
|
||||
'SUIT': suit,
|
||||
'AMOUNT': amount,
|
||||
}
|
||||
}).done(function (data) {
|
||||
console.log(data);
|
||||
}).fail(function (data) {
|
||||
alert(data);
|
||||
});
|
||||
|
||||
txtBetName.val("");
|
||||
numBetAmount.val("")
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="header">
|
||||
<h1>Kamelen Race!</h1>
|
||||
</nav>
|
||||
<div id="game">
|
||||
<div id="control">
|
||||
<div>
|
||||
<button id="newgame">New Game</button>
|
||||
<button id="startgame">Start Game</button>
|
||||
<button id="doround">Do Round</button>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Bet</h1>
|
||||
<input type="text" id="bet-name" placeholder="Name">
|
||||
<label for="bet-suit">Suit</label>
|
||||
<select id="bet-suit">
|
||||
<option value="S">Spades</option>
|
||||
<option value="H">Hearts</option>
|
||||
<option value="D">Diamonds</option>
|
||||
<option value="C">Clubs</option>
|
||||
</select>
|
||||
<input type="number" placeholder="Amount" id="bet-amount">
|
||||
<button id="bet">Bet</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="topcards">
|
||||
</div>
|
||||
<div id="track">
|
||||
<div class="tracklane">
|
||||
<div id="card-ace-spades" class="card card-ace">
|
||||
<svg width="150" viewBox="0 0 262 180">
|
||||
<use href="svg/svg-cards.svg#1_spade" transform="rotate(90) translate(0,-260)"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tracklane">
|
||||
<div id="card-ace-hearts" class="card card-ace">
|
||||
<svg width="150" viewBox="0 0 262 180">
|
||||
<use href="svg/svg-cards.svg#1_heart" transform="rotate(90) translate(0,-260)"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tracklane">
|
||||
<div id="card-ace-diamonds" class="card card-ace">
|
||||
<svg width="150" viewBox="0 0 262 180">
|
||||
<use href="svg/svg-cards.svg#1_diamond" transform="rotate(90) translate(0,-260)"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tracklane">
|
||||
<div id="card-ace-clubs" class="card card-ace">
|
||||
<svg width="150" viewBox="0 0 262 180">
|
||||
<use href="svg/svg-cards.svg#1_club" transform="rotate(90) translate(0,-260)"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script language="html/template" id="template-card">
|
||||
<div class="card">
|
||||
<svg width="150" viewBox="0 0 262 180">
|
||||
<use href="{href}" transform="rotate(90) translate(0,-260)"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+10364
File diff suppressed because it is too large
Load Diff
Vendored
+2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 941 KiB |
Reference in New Issue
Block a user