summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyle Gunger <corechg@gmail.com>2020-09-18 12:19:44 -0400
committerKyle Gunger <corechg@gmail.com>2020-09-18 12:19:44 -0400
commitdbaf69557c0d6e648120b068fec1920b9391a24a (patch)
treef31f7b956455b1243d344d0c54d5b04e9e80fb3f
parente5515d3e1603339b4957421c9494ca619490a03d (diff)
Update from local repo
-rw-r--r--LICENSE21
-rw-r--r--README.md5
-rw-r--r--assets/standard/diamond.svg37
-rw-r--r--assets/standard/heart.svg33
-rw-r--r--client.html66
-rw-r--r--images/vanilla.pngbin0 -> 3191 bytes
-rw-r--r--index.html114
-rw-r--r--scripts/cards/card.js110
-rw-r--r--scripts/cards/deck.js146
-rw-r--r--scripts/cards/drag.js128
-rw-r--r--scripts/client.js147
-rw-r--r--scripts/cookie.js36
-rw-r--r--scripts/gui/chat.js149
-rw-r--r--scripts/gui/input.js231
-rw-r--r--scripts/gui/lobby.js (renamed from scripts/lobby.js)245
-rw-r--r--scripts/gui/table.js79
-rw-r--r--scripts/socket/message.js (renamed from scripts/message.js)18
-rw-r--r--scripts/socket/sock.js (renamed from scripts/sock.js)48
-rw-r--r--scripts/table.js16
-rw-r--r--scripts/theme.js26
-rw-r--r--styles/client/base.css275
-rw-r--r--styles/client/card.css191
-rw-r--r--styles/client/desktop.css41
-rw-r--r--styles/client/mobile.css8
-rw-r--r--styles/client/tablet.css4
-rw-r--r--styles/home/base.css67
-rw-r--r--styles/input.css425
-rw-r--r--styles/themes/colors-base.css98
-rw-r--r--styles/themes/colors-dark.css103
29 files changed, 2551 insertions, 316 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..15d71cd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Kyle Gunger
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next
+paragraph) shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index 9a9c0af..0c6b070 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,5 @@
# WebCardsClient
-Client for webcards server
+
+![WebCards](http://35.11.215.147:3000/CCGKyle/WebCardsClient/raw/branch/master/images/wc-icon-144.png)
+
+An in-browser client to play WebCards \ No newline at end of file
diff --git a/assets/standard/diamond.svg b/assets/standard/diamond.svg
new file mode 100644
index 0000000..41f7cc2
--- /dev/null
+++ b/assets/standard/diamond.svg
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ style="enable-background:new"
+ id="svg8"
+ version="1.1"
+ viewBox="0 0 33.866666 33.866668"
+ height="128"
+ width="128">
+ <defs
+ id="defs2" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-263.13332)"
+ style="opacity:1"
+ id="layer1">
+ <path
+ id="path859"
+ d="M 16.933333,295.73051 5.3453296,280.06665 16.933332,264.40279 28.521336,280.06665 Z"
+ style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.49378231;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/assets/standard/heart.svg b/assets/standard/heart.svg
new file mode 100644
index 0000000..8cc32e4
--- /dev/null
+++ b/assets/standard/heart.svg
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ width="128"
+ height="128"
+ viewBox="0 0 33.866666 33.866668"
+ version="1.1"
+ id="svg8"
+ style="enable-background:new">
+ <defs
+ id="defs2" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <path
+ transform="matrix(0.26458334,0,0,0.26458334,1.2695285,1.26953)"
+ style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.3796902;stroke-opacity:1;enable-background:new"
+ d="M 31.880591,0.16895874 C 14.609272,3.6552705 -0.10970932,24.675453 6.162248e-4,42.266772 0.20206882,74.388447 59.172962,118.40354 59.172962,118.40354 c 0,0 59.020808,-44.003376 59.229928,-76.136768 C 118.51733,24.675491 103.79578,3.6475603 86.522912,0.16895874 76.21487,-1.9070223 59.229844,15.963831 59.229844,15.963831 c 0,0 -17.02625,-17.8786392 -27.349253,-15.79487226 z"
+ id="path815" />
+</svg>
diff --git a/client.html b/client.html
index e7d6153..c5d7cc7 100644
--- a/client.html
+++ b/client.html
@@ -8,21 +8,37 @@
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="styles/icofont.css">
+ <link rel="stylesheet" type="text/css" href="styles/input.css">
+
<link rel="stylesheet" type="text/css" href="styles/client/base.css">
<link rel="stylesheet" type="text/css" href="styles/client/desktop.css">
<link rel="stylesheet" type="text/css" href="styles/client/tablet.css">
<link rel="stylesheet" type="text/css" href="styles/client/mobile.css">
+ <link rel="stylesheet" type="text/css" href="styles/client/card.css">
+
+ <link id="theme" rel="stylesheet" type="text/css" href="styles/themes/colors-base.css">
<link rel="icon" sizes="32x32" href="images/wc-icon-32.png">
<link rel="icon" sizes="48x48" href="images/wc-icon-48.png">
<link rel="icon" sizes="96x96" href="images/wc-icon-96.png">
<link rel="icon" sizes="144x144" href="images/wc-icon-144.png">
<link rel="icon" sizes="288x288" href="images/wc-icon-288.png">
-
- <script src="scripts/message.js"></script>
- <script src="scripts/sock.js"></script>
- <script src="scripts/lobby.js"></script>
- <script src="scripts/table.js"></script>
+
+ <script src="scripts/cookie.js"></script>
+ <script src="scripts/theme.js"></script>
+
+ <script src="scripts/cards/card.js"></script>
+ <script src="scripts/cards/deck.js"></script>
+ <script src="scripts/cards/drag.js"></script>
+
+ <script src="scripts/gui/input.js"></script>
+ <script src="scripts/gui/lobby.js"></script>
+ <script src="scripts/gui/table.js"></script>
+ <script src="scripts/gui/chat.js"></script>
+
+ <script src="scripts/socket/message.js"></script>
+ <script src="scripts/socket/sock.js"></script>
+
<script src="scripts/client.js"></script>
<title>WebCards - Client</title>
@@ -30,33 +46,33 @@
<body>
- <div class="table" style="visibility: hidden;">
+ <div class="table" state="closed">
</div>
<div class="topbar" style="height: auto;">
<div class="top-buttons">
- <button id="newgame" class="top-button" onclick="game.lob.newGame()"></button>
- <button id="settings" class="top-button" onclick="game.lob.mobileSettings()"></button>
+ <button id="newgame" class="top-button" onclick="game.lobby.newGame()"></button>
+ <button id="settings" class="top-button" onclick="game.lobby.mobileSettings()"></button>
<button id="reset" class="top-button" onclick="game.reset()"></button>
</div>
<div class="new-game" style="display: none;"></div>
- <div class="mobile-settings" style="display: none;">
- <span><input type="text" id="name" placeholder="Username"/></span>
- <span><input type="color" id="usercolor" value="#f00"/></span>
+ <div class="settings mobile-settings" style="display: none;">
+ <span><input type="text" placeholder="Username"/></span>
+ <span><input type="color" value="#f00"/></span>
<button id="set">Accept Settings</button>
</div>
- <div class="status" style="background-color: #DA0;"></div>
+ <div class="status"></div>
</div>
<div class="lobby">
<div class="server">
- <span style="background-color: #DA0; font-weight: bold; color: white;" class="status">Connecting</span>
+ <span style="font-weight: 700;" class="status">Connecting</span>
<span class="addr"></span>
</div>
@@ -89,16 +105,30 @@
</div>
</div>
- <div class="settings">
- <span><input type="text" id="name" placeholder="Username"/></span>
- <span><input type="color" id="usercolor" value="#f00"/></span>
-
- <button onclick="game.acceptSettings()">Accept Settings</button>
+ <div class="settings" >
+ <div class="input-container" type="color" onclick="this.getElementsByTagName('input')[0].click()">
+ <input type="color">
+ </div>
</div>
</div>
</div>
</div>
+ <div class="chat">
+ <button class="toggle-chat">Toggle Chat</button>
+
+ <div class="chat-select">
+ </div>
+
+ <div class="chat-text">
+ </div>
+
+ <div class="chat-input">
+ <input type="text" placeholder="Chat..."/>
+ <button>Send</button>
+ </div>
+ </div>
+
<script>
var params = new URLSearchParams((new URL(window.location)).search);
var game = new Client(params.get("s"), params.get("g"));
diff --git a/images/vanilla.png b/images/vanilla.png
new file mode 100644
index 0000000..26b3011
--- /dev/null
+++ b/images/vanilla.png
Binary files differ
diff --git a/index.html b/index.html
index ed8df51..2f20bc5 100644
--- a/index.html
+++ b/index.html
@@ -18,39 +18,115 @@
<link rel="stylesheet" type="text/css" href="styles/home/desktop.css">
<link rel="stylesheet" type="text/css" href="styles/home/mobile.css">
+ <link rel="stylesheet" type="text/css" href="styles/input.css">
+
+ <link id="theme" rel="stylesheet" type="text/css" href="styles/themes/colors-base.css">
+
+ <script src="scripts/gui/input.js"></script>
+
+ <script src="scripts/cookie.js"></script>
+ <script src="scripts/theme.js"></script>
+
<title>WebCards</title>
</head>
<body>
<div class="content">
- <p style="font-size: 30px; font-weight: bold;"><span style="color: dodgerblue; font-weight: normal;">Web</span>Cards</p>
+ <p style="font-size: 30px; font-weight: normal;">Web<span style="color: #0084ff; font-weight: bold;">Cards</span></p>
<div>
- <select id="type">
- <option value="ws://" selected>ws</option>
- <option value="wss://">wss</option>
- </select>
+ <div class="input-container" tabindex="0" type="select">
+ <div id="protocol" tabindex="0" selected="1" class="input-select">
+ <div value="ws://" onmousedown="MakeInput.selOption(this)">ws</div>
+ <div value="wss://" onmousedown="MakeInput.selOption(this)" selected="true">wss</div>
+ </div>
+ </div>
- <input id="addr" type="text" value="127.0.0.1">
+ <input id="addr" type="text" value="localhost">
<input id="port" type="number" value="4040">
</div>
- <a id="conn" href="client.html?s=ws://127.0.0.1:4040&g=-1">Connect</a>
+ <button id="conn" onclick="connect()">Connect</button>
+
+ <div id="prev" class="prev">
+
+ </div>
+
+ <a style="border: none; padding: 0; display: contents; margin: 0;" href="http://vanilla-js.com/"><img src="images/vanilla.png"></a>
</div>
<script>
- var t = document.getElementById("type");
- var a = document.getElementById("addr");
- var p = document.getElementById("port");
- var c = document.getElementById("conn");
-
- t.onchange = updateLink;
- a.onchange = updateLink;
- p.onchange = updateLink;
-
- function updateLink() {
- var url = "client.html?s=" + t.value + a.value + ":" + p.value + "&g=-1";
- c.setAttribute("href", url);
+ var prev = Cookies.getCookie("prevAddr");
+ var prevEl = document.getElementById("prev");
+
+ function hasAddr(url) {
+ let urls = prev.split(",");
+
+ for(let i in urls)
+ {
+ if(urls[i] == url)
+ return true;
+ }
+
+ return false;
+ }
+
+ function addAddr(url) {
+ if(hasAddr(url))
+ return;
+
+ if(prev != "")
+ prev = prev + "," + url;
+ else
+ prev = url;
+
+ Cookies.setYearCookie("prevAddr", prev);
+ }
+
+ function delAddr(el, btn) {
+ let url = el.innerText;
+ let urls = prev.split(",");
+ urls.splice(urls.indexOf(url), 1);
+ Cookies.setCookie("prevAddr", urls.join());
+ el.remove();
+ btn.remove();
+ prev = Cookies.getCookie("prevAddr");
+ }
+
+ let urls = prev.split(",");
+ for(let i in urls) {
+ if(urls[i] == "")
+ continue;
+
+ let a = document.createElement("a");
+ a.innerText = urls[i];
+ a.href = "client.html?s=" + urls[i] + "&g=-1";
+
+ let b = document.createElement("button");
+ b.innerText = "X";
+ b.onclick = delAddr.bind(null, a, b);
+
+ prevEl.appendChild(a);
+ prevEl.append(b);
+ }
+
+ </script>
+
+ <script>
+ var proto = document.getElementById("protocol");
+ var addr = document.getElementById("addr");
+ var port = document.getElementById("port");
+ var conn = document.getElementById("conn");
+
+ //t.onchange = updateLink;
+ //a.onchange = updateLink;
+ //p.onchange = updateLink;
+
+ function connect() {
+ let concat = MakeInput.selValue(proto) + addr.value + ":" + port.value;
+ addAddr(concat);
+ //c.setAttribute("href", url);
+ window.location = "client.html?s=" + concat + "&g=-1";
}
</script>
</body>
diff --git a/scripts/cards/card.js b/scripts/cards/card.js
new file mode 100644
index 0000000..750d124
--- /dev/null
+++ b/scripts/cards/card.js
@@ -0,0 +1,110 @@
+'use strict';
+
+// Possible positions of content in a card
+const CardPos = ["top", "topl", "topr", "mid", "midt", "midb", "bot", "botl", "botr", "all"];
+
+// Card class represents one card.
+// Every card should have a deck.
+// Use deck.appendCard or deck.prependCard to make a card visible
+class Card {
+ constructor (data)
+ {
+ this.e = document.createElement("card");
+ this.generateElements(data);
+ this.e.style.left = "0px";
+ this.e.style.top = "0px";
+ }
+
+ // Generate a card with basic text only
+ static generateBasicCard (data, el)
+ {
+ let t = document.createElement("carea");
+ t.className = "mid";
+ t.innerText = data;
+ el.appendChild(t);
+ }
+
+ // Generate a card with a simple error message.
+ static generateErrorCard (el)
+ {
+ Card.generateBasicCard("Card Error: data", el);
+ }
+
+ // Generate an area of a card
+ static generateCArea (data, carea, assetURL)
+ {
+ // Create and set area
+ let area = document.createElement("carea");
+ area.className = carea;
+
+ // Create inner area text and images
+ for (let i in data)
+ {
+ if (i == "style")
+ {
+ for (j in data.style)
+ area.style[j] = data.style[j];
+ }
+
+ if (data[i].type == "text")
+ {
+ let e = document.createElement("ctext");
+
+ e.innerText = data[i].text;
+
+ for (let j in data[i].style)
+ e.style[j] = data[i].style[j];
+
+ area.appendChild(e);
+
+ }
+ else if (data[i].type == "image")
+ {
+ let e = document.createElement("cimage");
+
+ e.style.backgroundImage = "url(\"" + assetURL + data[i].image + "\")";
+
+ for (let j in data[i].style)
+ e.style[j] = data[i].style[j];
+
+ area.appendChild(e);
+ }
+ }
+
+ return area;
+ }
+
+ // Generate a card with rich visuals
+ static generateObjectCard (data, el)
+ {
+ // Generate card areas.
+ for (let i in CardPos)
+ {
+ if (typeof data[CardPos[i]] == "object")
+ el.appendChild(this.generateCArea(data[CardPos[i]], CardPos[i], data.assetURL));
+ }
+ }
+
+ generateElements (data)
+ {
+ while(this.e.firstElementChild != null)
+ this.e.firstElementChild.remove();
+
+ switch (typeof data)
+ {
+ case "object":
+ Card.generateObjectCard(data, this.e);
+ break;
+ case "string":
+ Card.generateBasicCard(data, this.e);
+ break;
+ default:
+ Card.generateErrorCard(this.e);
+ }
+ }
+
+ setPos (p)
+ {
+ this.e.style.setProperty("--cpos", p);
+ }
+}
diff --git a/scripts/cards/deck.js b/scripts/cards/deck.js
new file mode 100644
index 0000000..6da24b0
--- /dev/null
+++ b/scripts/cards/deck.js
@@ -0,0 +1,146 @@
+'use strict';
+
+// Deck class represents multiple cards.
+// Can be arranged in multiple ways.
+// Decks work as FIFO
+class Deck {
+
+ cards = [];
+ inf = false;
+ smode = "";
+ sct = 0;
+ x = 0;
+ y = 0;
+ e = null;
+
+ constructor(options = {mode: "stack", smode: "one", sct: 0, pos: [0, 0]})
+ {
+ // View mode
+ // infdraw - infinite draw. always appears as if there are multiple cards
+ // stack - stack mode
+ // strip
+ // horizontal
+ // left (strip-hl)
+ // right (strip-hr)
+ // vertical
+ // up (strip-vu)
+ // down (strip-vd)
+ this.inf = options.mode == "infdraw";
+
+ // Select mode - controls what other cards are selected when one card is selected
+ // above - selectes cards above the selected one
+ // below - selects cards below the selected one
+ // around - selects cards above and below
+ // one - selects only card chosen
+ // all - selects all cards when card selected
+ this.smode = options.smode;
+
+ // Select count (negative defaults to 0)
+ // above - controls number of cards above clicked are selected
+ // below - controls number of cards below clicked are selected
+ // around
+ // number - number above and below selected
+ // array - [first number: number above selected] [second number: number below selected]
+ // one - no effect
+ // all - no effect
+ this.sct = options.sct > 0 ? options.sct : 0;
+
+ // Position
+ // array of where the deck is centered
+ this.x = options.pos[0];
+ this.y = options.pos[1];
+
+ this.e = document.createElement("deck");
+ this.e.style.left = this.x + "px";
+ this.e.style.top = this.y + "px";
+ this.e.setAttribute("mode", options.mode);
+ }
+
+ updatePos()
+ {
+ let len = this.cards.length - 1;
+ for(let i in this.cards)
+ this.cards[i].setPos(len-i);
+ this.updateCount();
+ }
+
+ appendCard(card)
+ {
+ this.cards.push(card);
+ this.e.appendChild(card.e);
+ this.updatePos();
+
+ }
+
+ prependCard(card)
+ {
+ this.cards.unshift(card);
+ this.e.prepend(card.e);
+ card.setPos(this.cards.length - 1);
+ this.updateCount();
+ }
+
+ addCardAt(card, index)
+ {
+ if(index < 0 || index > this.cards.length)
+ return
+
+ if(index == 0) {
+ this.prependCard(card);
+ } else if (index == this.cards.length) {
+ this.appendCard(card);
+ } else {
+ let temp = this.cards.slice(0, index);
+ temp[temp.length - 1].e.after(card.e);
+ temp.push(card);
+ this.cards.unshift(...temp);
+ this.updatePos();
+ }
+ }
+
+ swapCards(index1, index2)
+ {
+ if(index1 < 0 || index1 >= this.cards.length || index2 < 0 || index2 >= this.cards.length)
+ return
+
+ var temp = this.cards[index1]
+ this.cards[index1] = this.cards[index2];
+ this.cards[index2] = temp;
+
+ this.cards[index1 - 1].e.after(this.cards[index1]);
+ this.cards[index2 - 1].e.after(this.cards[index2]);
+ }
+
+ removeCard(index)
+ {
+ if(index < 0 || index >= this.cards.length)
+ return
+
+ this.e.removeChild(this.cards[index].e);
+ let c = this.cards.splice(index, 1)[0];
+
+ this.updatePos();
+ return c;
+ }
+
+ removeFront()
+ {
+ return this.removeCard(this.cards.length - 1);
+ }
+
+ removeBack()
+ {
+ return this.removeCard(0);
+ }
+
+ updateCount ()
+ {
+ this.e.style.setProperty("--ccount", this.cards.length - 1);
+ }
+
+ isInside(x, y)
+ {
+ var rect = this.e.getBoundingClientRect();
+ return (x > rect.left && x < rect.right && y > rect.top && y < rect.bottom)
+ }
+}
diff --git a/scripts/cards/drag.js b/scripts/cards/drag.js
new file mode 100644
index 0000000..3b02eff
--- /dev/null
+++ b/scripts/cards/drag.js
@@ -0,0 +1,128 @@
+'use strict';
+
+class MultiDrag extends EventTarget {
+ del = false;
+ drag = [];
+ cbs = [];
+
+ constructor() {
+ super();
+
+ window.addEventListener("mousemove", this.update.bind(this));
+ document.body.addEventListener("mouseleave", this.stopDraggingAll.bind(this));
+ }
+
+ addDragEl(el, ox, oy, px, py, pt) {
+ if(this.del)
+ return;
+
+ el.style.transitionDuration = "0.04s";
+
+ this.drag.push({
+ e: el,
+ osx: ox,
+ osy: oy,
+ prx: px,
+ pry: py,
+ ptd: pt
+ });
+
+ return this.drag.length - 1;
+ }
+
+ dragging(e) {
+ for(let i in this.drag) {
+ if(this.drag[i].e == e)
+ return true;
+ }
+ return false;
+ }
+
+ startDragging(e) {
+ if(this.del)
+ return;
+
+ console.log(e);
+
+ if(e.button != 0)
+ return;
+
+ this.dispatchEvent(new Event("dragstart", {target: e.target}));
+
+ return this.addDragEl(
+ e.target,
+ e.pageX - parseInt(e.target.style.left),
+ e.pageY - parseInt(e.target.style.top),
+ e.target.style.left,
+ e.target.style.top,
+ e.target.style.transitionDuration
+ );
+ }
+
+ stopDragging(i) {
+ if(this.del)
+ return;
+
+ this.del = true;
+
+ if (i < 0 || i >= this.drag.length)
+ return;
+
+ var cap = {target: null, x: 0, y: 0};
+
+ this.drag[i].e.style.transitionDuration = this.drag[i].ptd;
+
+ cap.x = parseInt(this.drag[i].e.style.left);
+ this.drag[i].e.style.left = this.drag[i].prx;
+
+ cap.y = parseInt(this.drag[i].e.style.top);
+ this.drag[i].e.style.top = this.drag[i].pry;
+
+ cap.target = this.drag.splice(i, 1).e;
+
+ this.del = false;
+
+ this.dispatchEvent(new Event("dragstop", cap));
+ }
+
+ stopDraggingEl(el) {
+ for(let d of this.drag) {
+ if(d.e === el)
+ this.stopDragging(this.drag.indexOf(d));
+ }
+ }
+
+ stopDraggingAll() {
+ if(this.del)
+ return;
+
+ this.del = true;
+
+ while (this.drag.length > 0) {
+ this.drag[0].e.style.transitionDuration = this.drag[0].ptd;
+ this.drag[0].e.style.left = this.drag[0].prx;
+ this.drag[0].e.style.top = this.drag[0].pry;
+
+ this.drag.shift();
+ }
+
+ this.del = false;
+
+ this.dispatchEvent(new Event("dragstopall"));
+ }
+
+ update(e) {
+ for (let i = 0; i < this.drag.length && !this.del; i++) {
+ this.drag[i].e.style.left = e.pageX - this.drag[i].osx + "px";
+ this.drag[i].e.style.top = e.pageY - this.drag[i].osy + "px";
+ }
+ }
+
+ addTarget(e) {
+ e.addEventListener("mousedown", this.startDragging.bind(this));
+ e.addEventListener("mouseup", this.stopDraggingEl.apply(this, [e]))
+ }
+
+ removeTarget (e) {
+ }
+} \ No newline at end of file
diff --git a/scripts/client.js b/scripts/client.js
index ea62e26..acb3d90 100644
--- a/scripts/client.js
+++ b/scripts/client.js
@@ -1,109 +1,128 @@
+const VERSION = "1.0.0";
+
// Client acts as the message hub for the whole game.
// WebSocket messages come into Client and Client redirects them to the lobby or table based on the state of the game.
// Client also performs the handshake for first starting the connection and messages everyone if the connection errors or closes.
-function Client(serveraddr, game) {
- this.state = "handshake";
-
- this.soc = new SockWorker(serveraddr, "1", this.cb.bind(this));
+class Client{
- this.lob = new Lobby(document.getElementsByClassName("lobby")[0], this.soc);
- this.tab = new Table(document.getElementsByClassName("table")[0], this.soc);
+ constructor (serveraddr, game)
+ {
+ this.socket = new SockWorker(serveraddr, VERSION);
+ this.socket.addEventListener("error", this.socketError.bind(this));
+ this.socket.addEventListener("closed", this.socketClose.bind(this));
+ this.socket.addEventListener("handshake", this.handshake.bind(this));
+ this.socket.addEventListener("menu", this.menu.bind(this));
+ this.socket.addEventListener("game", this.game.bind(this));
- this.game = game;
-}
+ this.lobby = new Lobby(document.getElementsByClassName("lobby")[0], this.socket);
+
+ this.drag = new MultiDrag();
+
+ this.table = new Table(document.getElementsByClassName("table")[0], this.drag, this.socket);
+
+ this.chat = new Chat(document.getElementsByClassName("chat")[0], this.socket);
+ this.chat.addChannel("global");
+ this.chat.switchChannel("global");
+
+ this.game = game;
+ }
-Client.prototype = {
// Initialize the connection
- init: function() {
- this.soc.init();
- },
-
- // Entry point for a message from the server.
- // If it's a close message, we close the game if it is open and change the lobby to reflect the error/close.
- cb: function(m) {
- console.log(m);
-
- if(m.type == "error" || m.type == "closed") {
- var t = m.type;
- t = t[0].toUpperCase() + t.slice(1)
- this.lob.setState(t, "#D00", this.soc.server);
- this.tab.handleClose();
- return;
- }
+ init ()
+ {
+ this.socket.init();
+ }
- switch(this.state) {
- case "handshake":
- this.handshake(m);
- break;
- case "lobby":
- this.lobby(m);
- break;
- case "game":
- break;
- }
- },
+ // Callbacks for if the socket fails or closes
+
+ socketError() {
+ this.lobby.setState("Error", "closed", this.socket.server);
+ this.table.handleClose();
+ }
- // Called when negotiating with the server for the first time and we are determining versions
- handshake: function(m) {
+ socketClose() {
+ this.lobby.setState("Closed", "closed", this.socket.server);
+ this.table.handleClose();
+ }
+
+ // Callback when negotiating with the server for the first time and we are determining versions
+ handshake (m)
+ {
switch (m.type) {
case "verr":
- this.soc.close();
+ this.socket.close();
alert(`Error connecting to server: version of client (${this.version}) not accepted.`);
- console.error("Error connecting to server: version of client (${this.version}) not accepted.");
+ console.error(`Error connecting to server: version of client (${this.version}) not accepted.`);
console.error(m.data);
return;
- case "lobby":
- this.state = "lobby";
- this.soc.send("ready", "");
+ case "ready":
+ this.socket.send("ready", "");
return;
}
- },
+ }
- // Lobby switch, called when in the lobby and a message arrives from the server
- lobby: function (m) {
+ // Menu switch, called when in the lobby and a message arrives from the server
+ menu (m)
+ {
switch (m.type) {
case "plist":
- this.lob.packList(m.data);
+ this.lobby.packList(m.data);
break;
case "glist":
- this.lob.gameList(m.data, this.game);
+ this.lobby.gameList(m.data, this.game);
this.game = null;
break;
case "players":
- this.lob.players(m.data);
+ this.lobby.players(m.data);
break;
case "gdel":
- this.lob.removeGame(m.data);
+ this.lobby.removeGame(m.data);
break;
case "gadd":
- this.lob.addGame(m.data);
+ this.lobby.addGame(m.data);
break;
case "pdel":
- this.lob.removePlayer(m.data);
+ this.lobby.removePlayer(m.data);
break;
case "padd":
- this.lob.addPlayer(m.data);
+ this.lobby.addPlayer(m.data);
break;
case "pmove":
- this.lob.movePlayer(m.data);
+ this.lobby.movePlayer(m.data);
break;
}
- },
+ }
// Game switch, called when in game and a message arrives from the server
- game: function (m) {
+ game (m)
+ {
switch (m.type) {
}
- },
+ }
- // Reset the lobby and table, then attempt to reopen the connection to the server.
- reset: function() {
- this.state = "handshake";
+ // Callback when a chat event is recieved from the server
+ chat (m)
+ {
+ switch (m.type) {
+ case "delchan":
+ this.chat.deleteChannel(m.data);
+ break;
+ case "newchan":
+ this.chat.addChannel(m.data);
+ break;
+
+ case "message":
+ this.chat.recieveMessage(m.data.type, m.data.data);
+ }
+ }
- this.lob.reset();
- this.tab.reset();
+ // Reset the lobby and table, then attempt to reopen the connection to the server.
+ reset ()
+ {
+ this.lobby.reset();
+ this.table.reset();
- this.soc.init();
+ this.socket.init();
}
-};
+}
diff --git a/scripts/cookie.js b/scripts/cookie.js
new file mode 100644
index 0000000..d614af7
--- /dev/null
+++ b/scripts/cookie.js
@@ -0,0 +1,36 @@
+'use strict';
+
+class Cookies {
+ static getCookie(name){
+ let cookies = document.cookie.split(";");
+ for(let i in cookies) {
+ let cname = cookies[i].trim().split("=")[0];
+ if(cname == name){
+ return cookies[i].trim().slice(name.length + 1);
+ }
+ }
+ return "";
+ }
+
+ static setCookie(name, value, data = {}) {
+ let extra = "";
+
+ for(let key in data)
+ {
+ extra += "; " + key + "=" + data[key];
+ }
+
+ document.cookie = name + "=" + value + extra;
+ }
+
+ static setYearCookie(name, value) {
+ var date = new Date(Date.now());
+ date.setFullYear(date.getFullYear() + 1);
+ Cookies.setCookie(name, value, {expires: date.toUTCString()});
+ }
+
+ static removeCookie(name) {
+ var date = new Date(0);
+ Cookies.setCookie(name, "", {expires: date.toUTCString()});
+ }
+}
diff --git a/scripts/gui/chat.js b/scripts/gui/chat.js
new file mode 100644
index 0000000..68b8f5d
--- /dev/null
+++ b/scripts/gui/chat.js
@@ -0,0 +1,149 @@
+'use strict';
+
+class Chat {
+ constructor(e)
+ {
+ this.chats = [];
+ this.root = e;
+ e.getElementsByClassName("toggle-chat")[0].onclick = this.toggle.bind(this);
+ }
+
+ getChannel (name)
+ {
+ for(let i in this.chats)
+ {
+ if (this.chats[i].name == name)
+ {
+ return this.chats[i];
+ }
+ }
+
+ return null;
+ }
+
+ isActive (name)
+ {
+ for(let i in this.chats)
+ {
+ if (this.chats[i].name == name)
+ {
+ if(this.chats[i].btn.getAttribute("active") == "true")
+ return true;
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ addChannel (name, follow = true)
+ {
+ if(this.getChannel(name) != null)
+ return;
+
+ let d = document.createElement("div");
+ let b = document.createElement("button");
+
+ this.root.getElementsByClassName("chat-select")[0].appendChild(b);
+
+ b.setAttribute("active", false);
+
+ b.onclick = this.switchChannel.bind(this, name);
+
+ b.innerText = name[0].toUpperCase() + name.slice(1).toLowerCase();
+
+ d.className = "chat-text";
+
+ this.chats.push({name: name, e: d, btn: b});
+
+ if(follow)
+ this.switchChannel(name)
+ }
+
+ getActiveChannel ()
+ {
+ for(let i in this.chats)
+ {
+ if (this.chats[i].btn.getAttribute("active") == "true")
+ {
+ return this.chats[i];
+ }
+ }
+
+ return null;
+ }
+
+ switchChannel (name)
+ {
+ let c = this.getChannel(name);
+
+ if(c == null)
+ return;
+
+ if(this.getActiveChannel() != null)
+ this.getActiveChannel().btn.setAttribute("active", false);
+
+ c.btn.setAttribute("active", true);
+ var ct = this.root.getElementsByClassName("chat-text")[0];
+ ct.replaceWith(c.e);
+
+ c.e.scroll({
+ top: c.e.scrollTopMax
+ });
+ }
+
+ recieveMessage (channel, msg)
+ {
+ let c = this.getChannel(channel);
+
+ if(c == null)
+ return;
+
+ let autoscroll = c.e.scrollTop == c.e.scrollTopMax;
+
+ let csp = document.createElement("span");
+ csp.style.color = msg.color;
+ csp.innerText = msg.user + ": ";
+ let tsp = document.createElement("span");
+ tsp.innerText = msg.text;
+ let d = document.createElement("div");
+ d.appendChild(csp);
+ d.appendChild(tsp);
+
+ c.e.appendChild(d);
+
+ if(autoscroll)
+ c.e.scroll({top: c.e.scrollTopMax});
+ }
+
+ clearChannel (name)
+ {
+ let c = this.getChannel(name);
+ if(c == null)
+ return;
+
+ while(c.e.firstElementChild != null)
+ c.e.firstElementChild.remove();
+ }
+
+ deleteChannel (name)
+ {
+ let c = this.getChannel(name);
+ if(c == null)
+ return;
+
+ while(c.e.firstElementChild != null)
+ c.e.firstElementChild.remove();
+
+ c.btn.remove();
+
+ this.chats.splice(this.chats.indexOf(c), 1);
+ }
+
+ toggle () {
+ if(this.root.getAttribute("show") != "true")
+ this.root.setAttribute("show", "true");
+ else
+ this.root.setAttribute("show", "false");
+ }
+}
diff --git a/scripts/gui/input.js b/scripts/gui/input.js
new file mode 100644
index 0000000..6ed3d39
--- /dev/null
+++ b/scripts/gui/input.js
@@ -0,0 +1,231 @@
+'use strict';
+
+//This whole clusterfuq of functions needs fixing.
+class MakeInput {
+ static createInput(type = "text", id)
+ {
+ var el = document.createElement("input");
+ el.setAttribute("type", type);
+
+ if(typeof id == "string")
+ el.setAttribute("id", id);
+
+ el.getValue = function () {
+ return this.value;
+ }
+
+ return el;
+ }
+
+ static inputLabel(text, id)
+ {
+ var el = document.createElement("label");
+ el.innerText = text;
+ if(typeof id == "string")
+ el.setAttribute("for", id);
+ return el;
+ }
+
+ static colorInput (value, id) {
+ var el = MakeInput.createInput("color", id);
+ el.value = value;
+ return el;
+ }
+
+ static textInput (value, placeholder, id)
+ {
+ var el = MakeInput.createInput("text", id);
+ el.setAttribute("placeholder", placeholder);
+ el.value = value;
+ return el;
+ }
+
+ static numberInput (value, id)
+ {
+ var el = MakeInput.createInput("number", id);
+ el.value = value;
+ return el;
+ }
+
+ //To fix
+ static fileInput (value, id) {
+ var el = MakeInput.createInput("file", id);
+
+ el.value = value;
+
+ el.setAttribute("data-files", "Choose a file");
+
+ el.firstElementChild.onchange = function () {
+ let text = "";
+ switch (this.files.length) {
+ case 0:
+ text = "Choose a file";
+ break;
+ case 1:
+ text = "File: " + this.files[0].name;
+ break;
+ default:
+ text = "Files: " + this.files[0].name + "...";
+ break;
+ }
+ el.setAttribute("data-files", text);
+ }
+
+ return el;
+ }
+
+ static checkboxInput (checked = false, id) {
+ var el = MakeInput.createInput("checkbox", false, id);
+ if(checked)
+ el.setAttribute("checked");
+ return el;
+ }
+
+ static radioInput (group, value, checked = false, id) {
+ var el = MakeInput.createInput("radio", false, id);
+ el.setAttribute("name", group);
+ el.setAttribute("value", value);
+ if(checked)
+ el.checked = true;
+ return el;
+ }
+
+ static radioInputs (group, names, values, checked = 0, id) {
+
+ let toWrap = [];
+
+ for(let i = 0; i < values.length; i++) {
+ toWrap.push(MakeInput.inputLabel(names[i], group+"-"+i));
+ if(i == checked)
+ toWrap.push(MakeInput.radioInput(group, values[i], true, group+"-"+i));
+ else
+ toWrap.push(MakeInput.radioInput(group, values[i], false, group+"-"+i));
+ toWrap.push(document.createElement("br"));
+ }
+
+ var wrapper = MakeInput.wrapInputs("radio", ...toWrap);
+
+ wrapper.getValue = function() {
+ for(let i = 0; i < this.children.length; i++){
+ if(this.children[i].checked)
+ return this.children[i].value;
+ }
+ };
+
+ if(typeof id == "string")
+ wrapper.setAttribute("id", id);
+
+ return wrapper;
+ }
+
+ static selectOption (text, value, selected) {
+ var so = document.createElement("div");
+ so.innerText = text;
+ so.setAttribute("value", value);
+ so.addEventListener("mousedown", customSelectOption.bind(null, so));
+
+ if(selected === true)
+ so.setAttribute("selected", true);
+
+ return so
+ }
+
+ static selectInput (names, values, id, select = 0) {
+ var se = document.createElement("div");
+ se.className = "input-select";
+ se.setAttribute("tabindex", 0);
+ se.setAttribute("selected", select);
+
+ for(let i in names)
+ {
+ se.appendChild(MakeInput.selectOption(names[i], values[i], i == select));
+ }
+
+ if(typeof id == "string")
+ se.setAttribute("id", id);
+
+ var wrapper = MakeInput.wrapInputs("select", se);
+ wrapper.getValue = MakeInput.selValue.bind(null, se);
+ wrapper.setAttribute("tabindex", 0);
+
+ return wrapper;
+ }
+
+ static wrapInputs (type, ...el) {
+
+ var wrapper = document.createElement("div");
+ wrapper.className = "input-container";
+ wrapper.setAttribute("type", type);
+
+ for(let i = 0; i < el.length; i++)
+ {
+ wrapper.appendChild(el[i]);
+ }
+
+ return wrapper;
+ }
+
+ static selValue (el) {
+ let sel = parseInt(el.getAttribute("selected"));
+
+ if(typeof sel != "undefined") {
+ return el.children[sel].getAttribute("value");
+ }
+
+ return "";
+ }
+
+ static selOption (el) {
+ let sn = Array.prototype.indexOf.call(el.parentElement.children, el);
+ let psn = parseInt(el.parentElement.getAttribute("selected"));
+
+ if(Number.isInteger(psn))
+ el.parentElement.children[psn].setAttribute("selected", false);
+
+ el.parentElement.setAttribute("selected", sn);
+ el.setAttribute("selected", true);
+ }
+}
+
+class Settings {
+ constructor (template = {})
+ {
+ this.settings = Settings.genSettings(template);
+ }
+
+ static genSettings (template)
+ {
+ var out = {};
+
+ for(let key in template)
+ {
+ switch(template[key].type)
+ {
+ case "radio":
+ out[key] = MakeInput.radioInputs(...template[key].args);
+ break;
+ default:
+ if(typeof MakeInput[template[key].type+"Input"] != null)
+ out[key] = MakeInput[template[key].type+"Input"](...template[key].args);
+ }
+ }
+
+ return out;
+ }
+
+ getSettings ()
+ {
+ var out = {};
+
+ for(let key in this.settings)
+ out[key] = this.settings[key].getValue();
+
+ return out;
+ }
+
+ putSettings (el)
+ {
+ for(let key in this.settings)
+ el.appendChild(this.settings[key]);
+ }
+}
diff --git a/scripts/lobby.js b/scripts/gui/lobby.js
index 4d8332d..7d2b6cd 100644
--- a/scripts/lobby.js
+++ b/scripts/gui/lobby.js
@@ -1,43 +1,93 @@
-// Lobby manages the players and games provided by the server and allows users to join or create their own games.
-function Lobby(el){
- this.root = el;
-
- this.elements = {
- status: this.root.getElementsByClassName("status")[0],
- addr: this.root.getElementsByClassName("addr")[0],
- games: this.root.getElementsByClassName("games")[0],
-
- stats: {
- game: document.getElementById("game"),
- packs: document.getElementById("packs"),
- online: document.getElementById("online"),
- ingame: document.getElementById("ingame"),
- pubgame: document.getElementById("pubgame")
- },
-
- settings: {
- name: document.getElementById("name"),
- color: document.getElementById("usercolor")
- }
- };
-
- this.top = new TopBar(document.getElementsByClassName("topbar")[0]);
-
- this.init = false;
- this.online = [];
- this.games = [];
- this.packs = [];
- this.players = [];
+// ###############
+// # TopBar Code #
+// ###############
+
+// TopBar represents the bar at the top of the screen when client is in the lobby.
+
+class TopBar{
+ constructor (el)
+ {
+ this.root = el;
+
+ this.newGame = el.getElementsByClassName("new-game")[0];
+ this.mobileSettings = el.getElementsByClassName("mobile-settings")[0];
+ this.status = el.getElementsByClassName("status")[0];
+ }
+
+ // Set color of status bar
+ setStatus (s) {
+ this.status.setAttribute("s", s);
+ }
+
+ // Toggle showing the new game screen
+ toggleNewGame () {
+ if (this.newGame.style.display !== "none")
+ this.newGame.style.display = "none";
+ else
+ this.newGame.style.display = "block";
+ }
+
+ // Toggle showing the mobile settings
+ toggleMobileSettings () {
+ if (this.mobileSettings.style.display !== "none")
+ this.mobileSettings.style.display = "none";
+ else
+ this.mobileSettings.style.display = "block";
+ }
+}
+
+// #############
+// # Game code #
+// #############
+
+// Game represents a single game in the lobby view. It has methods for setting up the elements and such.
+class Game{
+ constructor (name, packs, maxp, id)
+ {
+ }
}
-Lobby.prototype = {
+// ##############
+// # Lobby Code #
+// ##############
+
+// Lobby manages the players and games provided by the server and allows users to join or create their own games.
+class Lobby {
+ constructor (el)
+ {
+ this.root = el;
+
+ this.e = {
+ status: el.getElementsByClassName("status")[0],
+ addr: el.getElementsByClassName("addr")[0],
+ games: el.getElementsByClassName("games")[0],
+ settings: el.getElementsByClassName("settings")[0],
+
+ stats: {
+ game: document.getElementById("game"),
+ packs: document.getElementById("packs"),
+ online: document.getElementById("online"),
+ ingame: document.getElementById("ingame"),
+ pubgame: document.getElementById("pubgame")
+ }
+ };
+
+ this.top = new TopBar(document.getElementsByClassName("topbar")[0]);
+
+ this.init = false;
+ this.online = [];
+ this.games = [];
+ this.packs = [];
+ this.players = [];
+ }
+
// Set initial pack list
// {data array} array of strings representing pack names
- packList: function(data) {
+ packList (data) {
this.packs = data;
this.top.setPacks(this.packs)
- this.elements.stats.packs.innerText = this.packs.length();
- },
+ this.e.stats.packs.innerText = this.packs.length();
+ }
// Set initial game list.
// { data object } object containing {games} and {name}
@@ -47,20 +97,20 @@ Lobby.prototype = {
// { data.games[n].packs } list of the pack names used by this game
// { data.games[n].id } room identifier (uuid)
// { data.games[n].max } max players in room
- gameList: function(data) {
- while (this.elements.games.firstChild != null) {
- this.elements.games.remove(this.elements.games.firstChild)
+ gameList (data) {
+ while (this.e.games.firstChild != null) {
+ this.e.games.remove(this.elements.games.firstChild)
}
for (let i in data.games) {
- let gel = new GameEl(i.name, i.packs, i.id);
+ let gel = new Game(i.name, i.packs, i.id);
this.games.push(gel);
- this.elements.games.appendChild(gel.getElement());
+ this.e.games.appendChild(gel.getElement());
}
- this.elements.stats.game.innerText = data.name;
- this.elements.stats.pubgame.innerText = this.games.length();
- },
+ this.e.stats.game.innerText = data.name;
+ this.e.stats.pubgame.innerText = this.games.length();
+ }
// Set the initial player list.
// { data array } represents a list of player objects from the server
@@ -68,26 +118,26 @@ Lobby.prototype = {
// { data[n].game string } id of the game room (empty if not in game).
// { data[n].color string } css color chosen by player.
// { data[n].uuid string } uuid of the player
- players: function(data) {
+ players (data) {
- this.elements.stats.online.innerText = this.players.length();
+ this.e.stats.online.innerText = this.players.length();
this.init = true;
- },
+ }
// Called when a new public game is created on the server
// { data object } the game object
// { data.name } room name
// { data.packs } list of the pack names used by this game
// { data.id } room identifier (uuid)
- addGame: function(data) {
+ addGame (data) {
- },
+ }
// Called when a new public game is removed on the server
// { data string } the uuid of the game to delete
- removeGame: function(data) {
+ removeGame (data) {
- },
+ }
// Called when a new player enters the lobby.
// { data object } an object representing the player
@@ -95,106 +145,65 @@ Lobby.prototype = {
// { data.game string } id of the game room (empty if not in game).
// { data.color string } css color chosen by player.
// { data.uuid string } uuid of the player
- addPlayer: function(data) {
+ addPlayer (data) {
- },
+ }
// Called when a player modifies their settings in the lobby.
// { data object } new player settings
// { data.name string } non null if the player has changed their name
// { data.color string } non null if the player has changed their color
// { data.uuid string } uuid of player changing their settings
- modPlayer: function(data) {
+ modPlayer (data) {
- },
+ }
// Called when a player moves between the lobby and a game, or between two games
// { data object } new location
// { data.player } uuid of player changing location
// { data.loc } uuid of room player is moving to (empty if moving to lobby)
- movePlayer: function(data) {
+ movePlayer (data) {
- },
+ }
// Called when a player exits the game (from lobby or game)
- // {data string } uuid of player
- removePlayer: function(data) {
+ // { data string } uuid of player
+ removePlayer (data) {
- },
+ }
// Called when the client wants to toggle the new game screen
- newGame: function() {
+ newGame () {
//if(this.init) return;
this.top.toggleNewGame();
- },
+ }
// Called when the client wants to toggle the mobile settings screen
- mobileSettings: function() {
+ mobileSettings () {
//if(this.init) return;
this.top.toggleMobileSettings();
- },
+ }
// Called when the WebSocket state has changed.
- setState: function(text, color, server) {
- this.elements.status.style.backgroundColor = color;
- this.elements.status.innerText = text;
- this.elements.addr.innerText = server;
- this.top.setColor(color);
- },
+ setState (text, s, server) {
+ this.e.status.setAttribute("s", s);
+ if(this.e.status.innerText != "Error" || ( this.e.status.innerText == "Error" && text != "Closed"))
+ this.e.status.innerText = text;
+ this.e.addr.innerText = server;
+ this.top.setStatus(s);
+ }
+
+ getState () {
+ return this.e.status.innerText.toLowerCase();
+ }
// Called when we are resetting the game.
- reset: function() {
- while (this.elements.games.firstChild != null) {
- this.elements.games.remove(this.elements.games.firstChild)
+ reset () {
+ while (this.e.games.firstElementChild != null) {
+ this.e.games.removeChild(this.e.games.firstElementChild)
}
- this.setState("Connecting", "#DA0", this.elements.addr.innerText);
+ this.setState("Connecting", "loading", this.e.addr.innerText);
this.init = false;
}
-};
-
-// ###############
-// # TopBar Code #
-// ###############
-
-// TopBar represents the bar at the top of the screen when client is in the lobby.
-
-function TopBar(el) {
- this.root = el;
-
- this.newGame = el.getElementsByClassName("new-game")[0];
- this.mobileSettings = el.getElementsByClassName("mobile-settings")[0];
- this.status = el.getElementsByClassName("status")[0];
}
-
-TopBar.prototype = {
- // Set color of status bar
- setColor: function(color) {
- this.status.style.backgroundColor = color;
- },
-
- // Toggle showing the new game screen
- toggleNewGame: function() {
- if (this.newGame.style.display !== "none")
- this.newGame.style.display = "none";
- else
- this.newGame.style.display = "block";
- },
-
- // Toggle showing the mobile settings
- toggleMobileSettings: function() {
- if (this.mobileSettings.style.display !== "none")
- this.mobileSettings.style.display = "none";
- else
- this.mobileSettings.style.display = "block";
- }
-};
-
-// #############
-// # Game code #
-// #############
-
-// GameEl represents a single game in the lobby view. It has methods for setting up the elements and such.
-function GameEl(name, packs, maxp, id) {
-
-} \ No newline at end of file
diff --git a/scripts/gui/table.js b/scripts/gui/table.js
new file mode 100644
index 0000000..c4878a0
--- /dev/null
+++ b/scripts/gui/table.js
@@ -0,0 +1,79 @@
+// Table represents and manages the actual game. It accepts inputs from the server and tries to query the server when the player makes a move.
+class Table{
+ constructor(e, drag, socket) {
+ this.root = e;
+ this.drag = drag;
+
+ this.root.addEventListener("mouseup", drag.stopDraggingAll.bind(drag));
+
+ //drag.addEventListener("dragstop", );
+
+ this.socket = socket;
+
+ this.decks = [];
+ }
+
+ openTable ()
+ {
+ let state = this.root.getAttribute("state")
+ if((state == "close" || state == "closed") && state != "") {
+ this.root.setAttribute("state", "closed");
+ setTimeout(this.root.setAttribute.bind(this.root), 50, "state", "open");
+ }
+ }
+
+ closeTable ()
+ {
+ let state = this.root.getAttribute("state")
+ if(state != "close" && state != "closed") {
+ this.root.setAttribute("state", "");
+ setTimeout(this.root.setAttribute.bind(this.root), 50, "state", "close");
+ }
+ }
+
+ handleClose ()
+ {
+ this.reset();
+ }
+
+ reset ()
+ {
+ while(this.root.firstElementChild != null)
+ this.root.firstElementChild.remove();
+
+ this.decks = [];
+
+ this.closeTable();
+ this.drag.stopDraggingAll();
+ }
+
+ /* Deck and card functions */
+ newDeck(options)
+ {
+ var d = new Deck(options);
+ this.decks.push(d);
+ this.root.appendChild(d.e);
+ }
+
+ newCard(data, deck = 0)
+ {
+ var c = new Card(data);
+ this.decks[deck].appendCard(c);
+ this.drag.addTarget(c.e);
+ }
+
+ checkDeck(x, y)
+ {
+ for(let d of this.decks)
+ {
+ if(d.isInside(x, y))
+ return true;
+ }
+ return false;
+ }
+
+ dragCheck(cap)
+ {
+ console.log(cap);
+ }
+}
diff --git a/scripts/message.js b/scripts/socket/message.js
index 5e821c4..044027d 100644
--- a/scripts/message.js
+++ b/scripts/socket/message.js
@@ -1,14 +1,18 @@
-function Message(type, data){
- this.t = type;
- this.d = data;
-}
+'use strict';
+
+class Message{
+ constructor (type, data)
+ {
+ this.t = type;
+ this.d = data;
+ }
-Message.prototype = {
- stringify: function(){
+ stringify ()
+ {
var dat = this.d
if(typeof dat !== "string"){
dat = JSON.stringify(dat);
}
return JSON.stringify({type: this.t, data: dat});
}
-};
+}
diff --git a/scripts/sock.js b/scripts/socket/sock.js
index cf06a5e..4eacc18 100644
--- a/scripts/sock.js
+++ b/scripts/socket/sock.js
@@ -1,13 +1,15 @@
// A wrapper around the wrapper
-function SockWorker(serveraddr, version, callback) {
- this.server = serveraddr;
- this.version = version;
- this.cb = callback;
-}
+class SockWorker extends EventTarget{
+ constructor (serveraddr, version)
+ {
+ super();
+
+ this.server = serveraddr;
+ this.version = version;
+ }
-SockWorker.prototype = {
// Initialize the connection.
- init: function() {
+ init () {
if(this.server == "" || this.server == null) {
return;
}
@@ -22,42 +24,42 @@ SockWorker.prototype = {
} catch (e) {
this.err();
}
- },
+ }
// Called when the connection connects to the server
- o: function() {
+ o () {
this.send("version", this.version);
- },
+ }
// Called when the connection gets a message from the server
// Attempts to turn the message into a usable object and pass it to the callback
- msg: function(e) {
+ msg (e) {
if(typeof e.data == "string") {
var dat = JSON.parse(e.data)
- this.cb(dat);
+ this.dispatchEvent(new Event(dat.type, dat.data));
}
- },
+ }
// Called when the connection closes.
// Passes a close object to the callback.
- c: function() {
- this.cb({type: "close", data: ""});
- },
+ c () {
+ this.dispatchEvent(new Event("closed"));
+ }
// Called when the connection encounters an error.
// Passes an error to the callback
- err: function() {
- this.cb({type: "error", data: ""});
- },
+ err () {
+ this.dispatchEvent(new Event("error"));
+ }
// Call to close the connection to the server
- close: function() {
+ close () {
this.socket.close();
- },
+ }
// Send a message to the server
- send: function(type, data) {
+ send (type, data) {
var m = new Message(type, data);
this.socket.send(m.stringify())
}
-}; \ No newline at end of file
+}
diff --git a/scripts/table.js b/scripts/table.js
deleted file mode 100644
index 911763a..0000000
--- a/scripts/table.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// Table represents and manages the actual game. It accepts inputs from the server and tries to queries the server when the player makes a move.
-function Table(el, soc) {
- this.root = el;
- this.soc = soc;
-}
-
-Table.prototype = {
-
- handleClose: function() {
-
- },
-
- reset: function() {
-
- }
-} \ No newline at end of file
diff --git a/scripts/theme.js b/scripts/theme.js
new file mode 100644
index 0000000..e93f5b5
--- /dev/null
+++ b/scripts/theme.js
@@ -0,0 +1,26 @@
+'use strict';
+
+class Theme{
+ static theme = document.getElementById("theme");
+
+ static init()
+ {
+ if(Cookies.getCookie("theme") == ""){
+ Cookies.setYearCookie("theme", "styles/themes/colors-base.css");
+ }
+ }
+
+ static restore()
+ {
+ Theme.init();
+ Theme.theme.setAttribute("href", Cookies.getCookie("theme") + "?v=" + Date.now());
+ }
+
+ static set(sheet)
+ {
+ Cookies.setYearCookie("theme", sheet);
+ Theme.restore();
+ }
+}
+
+Theme.restore(); \ No newline at end of file
diff --git a/styles/client/base.css b/styles/client/base.css
index cae61f6..889d7bf 100644
--- a/styles/client/base.css
+++ b/styles/client/base.css
@@ -11,12 +11,19 @@ html, body {
display: block;
+ background-color: var(--main-bg);
+ color: var(--main-color);
+
+ overflow: hidden;
}
-.topbar {
+/* Topbar rules */
+
+.topbar
+{
position: fixed;
- background-color: white;
+ background-color: var(--gui-bg-main);
top: 0;
left: 0;
@@ -29,66 +36,95 @@ html, body {
z-index: 1;
}
-.top-buttons {
+.top-buttons
+{
display: flex;
}
-button, input[type="button"] {
- display: inline-block;
- border: none;
- background-color: dodgerblue;
- padding: 10px;
- font-size: medium;
- border-radius: 5px;
- box-sizing: border-box;
-}
-
-button.top-button {
+button.top-button
+{
border-radius: 0;
- background-color: white;
+ background-color: var(--top-bg-button);
+ color: var(--top-color-button);
flex: 1;
- border-left: 1px solid black;
- border-right: 1px solid black;
+ border-left: 1px solid var(--top-border);
+ border-right: 1px solid var(--top-border);
+
+ transition-duration: 0.2s;
+}
+
+button.top-button:hover {
+ background-color: var(--top-bg-button-hover);
+ color: var(--top-color-button-hover);
}
-.top-buttons > button:first-child {
+button.top-button:active {
+ background-color: var(--top-bg-button-active);
+ color: var(--top-color-button-active);
+}
+
+.top-buttons > button:first-child
+{
border-left: none;
}
-.top-buttons > button:last-child {
+.top-buttons > button:last-child
+{
border-right: none;
}
-div.new-game {
+div.new-game
+{
flex: 1;
flex-grow: 1;
- border-top: 2px solid black;
+ border-top: 2px solid var(--top-border);
}
-div.mobile-settings {
+div.mobile-settings
+{
flex: 1;
flex-grow: 1;
- border-top: 2px solid black;
+ border-top: 2px solid var(--top-border);
}
-div.topbar > div.status {
+div.topbar > div.status
+{
height: 5px;
flex-basis: auto;
+
+ background-color: var(--server-bg-loading);
}
-div.stats {
+div.topbar > div.status[s=loading] {
+ background-color: var(--server-bg-loading);
+}
+
+div.topbar > div.status[s=ok] {
+ background-color: var(--server-bg-ok);
+}
+
+div.topbar > div.status[s=closed] {
+ background-color: var(--server-bg-closed);
+}
+
+/* Content rules */
+
+div.stats
+{
padding: 5px;
display: flex;
flex-direction: column;
}
-div.stats > div {
+div.stats > div
+{
flex: 1;
display: flex;
}
-div.stats > div > span {
+div.stats > div > span
+{
flex-basis: content;
display: inline-block;
}
@@ -100,13 +136,194 @@ div.stats > div > span:last-child {
}
div.game {
- background-color: white;
+ background-color: var(--gui-bg-game);
+ color: var(--gui-color-game);
border-radius: 5px;
- box-shadow: lightgray 3px 3px 2px;
+ box-shadow: var(--gui-shadow-game) 3px 3px 2px;
box-sizing: border-box;
margin-bottom: 10px;
+ padding: 5px;
+
+ text-align: left;
}
div.game:last-child {
margin-bottom: none;
+}
+
+div.settings {
+ display:flex;
+ flex-direction: column;
+ padding: 5px;
+}
+
+/* Table rules */
+
+.table {
+ position: absolute;
+ z-index: 2;
+ width: 100vw;
+ height: 100vh;
+
+ animation-duration: 0.8s;
+
+ background-color: var(--table-bg);
+
+ overflow: hidden;
+}
+
+.table[state=open] ,.table[state=close]{
+ animation-name: slide-in;
+ animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
+ animation-iteration-count: 1;
+ animation-direction: normal;
+ animation-fill-mode: forwards;
+}
+
+.table[state=close] {
+ animation-direction: reverse;
+}
+
+.table[state=closed] {
+ transform: translate(0, -100vh);
+ opacity: 0;
+}
+
+@keyframes slide-in {
+ from {
+ transform: translate(0, -100vh);
+ opacity: 0;
+ }
+
+ to {
+ transform: translate(0, 0);
+ opacity: 1;
+ }
+}
+
+/* Chat */
+
+div.chat {
+ border-top-left-radius: 10px;
+ background-color: var(--chat-bg);
+ color: var(--chat-color);
+
+ width: 50vw;
+ height: 50vh;
+ display: flex;
+ flex-direction: column;
+ position: absolute;
+ right: -50vw;
+
+ box-sizing: border-box;
+
+ top: 50vh;
+
+ transition-duration: 0.2s;
+
+ padding: 5px;
+
+ z-index: 4;
+}
+
+div.chat[show=true] {
+ right: 0;
+}
+
+button.toggle-chat {
+ position: absolute;
+ bottom: 5px;
+ left: -5px;
+ transform-origin: bottom left;
+ transform: rotate(-90deg);
+}
+
+div.chat > div {
+ box-sizing: border-box;
+ margin: 5px;
+}
+
+div.chat-select, div.chat-input {
+ flex-basis: content;
+ display: flex;
+}
+
+div.chat-text {
+ flex: 1;
+ overflow-y: auto;
+ overflow-x: hidden;
+ padding: 5px;
+ background-color: var(--chat-bg-text);
+ border-radius: 10px;
+ border: 5px solid rgba(0, 0, 0, 0);
+}
+
+div.chat-select > button {
+ flex: 1;
+
+ margin-right: 2px;
+ margin-left: 2px;
+}
+
+div.chat-select > button[active="false"] {
+ background-color: var(--chat-bg-inactive);
+}
+
+div.chat-select > button[active="false"]:hover {
+ background-color: var(--chat-bg-inactive-hover);
+}
+
+div.chat-select > button[active="false"]:active {
+ background-color: var(--chat-bg-inactive-active);
+}
+
+div.chat-select > button[active="true"] {
+ background-color: var(--chat-bg-active);
+}
+
+div.chat-select > button[active="true"]:hover {
+ background-color: var(--chat-bg-active-hover);
+}
+
+div.chat-select > button[active="true"]:active {
+ background-color: var(--chat-bg-active-active);
+}
+
+div.chat-select > button:first-child {
+ margin-left: 0;
+}
+
+div.chat-select > button:last-child {
+ margin-right: 0;
+}
+
+div.chat-input > input {
+ flex: 5;
+
+ margin: 0;
+ margin-right: 5px;
+
+ border-radius: 5px;
+
+ transition-duration: 0.2s;
+ border: none;
+
+ background-color: var(--chat-bg-input);
+}
+
+div.chat-input > input:hover {
+ background-color: var(--chat-bg-input-hover);
+}
+
+div.chat-input > input:focus {
+ background-color: var(--chat-bg-input-active);
+}
+
+div.chat-input > button {
+ flex: 1;
+}
+
+div.chat-text > div {
+ word-wrap: break-word;
+ word-break: break-all;
} \ No newline at end of file
diff --git a/styles/client/card.css b/styles/client/card.css
new file mode 100644
index 0000000..de49175
--- /dev/null
+++ b/styles/client/card.css
@@ -0,0 +1,191 @@
+card
+{
+ position: absolute;
+ display: block;
+ width: 150px;
+ height: 230px;
+ background-color: var(--card-bg);
+ color: var(--card-color);
+ border: 2px solid var(--card-border);
+ border-radius: 10px;
+ transition-duration: 0.2s;
+ cursor: pointer;
+ flex-direction: column;
+ overflow: hidden;
+ user-select: none;
+ box-sizing: border-box;
+}
+
+card:hover
+{
+ box-shadow: 0 0 10px var(--card-hover);
+}
+
+card > * {
+ pointer-events: none;
+}
+
+carea
+{
+ padding-left: 2px;
+ padding-right: 2px;
+ position: absolute;
+ display: flex;
+ vertical-align: middle;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+carea.top, carea.topl, carea.topr
+{
+ top: 0;
+ height: 8%;
+ text-align: center;
+}
+
+carea.topl
+{
+ width: 50%;
+ text-align: left;
+}
+
+carea.topr
+{
+ right: 0;
+ width: 50%;
+ text-align: right;
+}
+
+carea.mid
+{
+ top: 8%;
+ height: 84%;
+}
+
+carea.midt
+{
+ top: 8%;
+ height: 42%;
+}
+
+carea.midb
+{
+
+ top: 50%;
+ height: 42%;
+}
+
+carea.bot, carea.botl, carea.botr
+{
+ bottom: 0;
+ height: 8%;
+ text-align: center;
+}
+
+carea.botl
+{
+ width: 50%;
+ text-align: left;
+}
+
+carea.botr
+{
+ right: 0;
+ width: 50%;
+ text-align: right;
+}
+
+carea.all
+{
+ top: 0;
+ height: 100%;
+}
+
+ctext
+{
+ display: inline-block;
+ flex: 1;
+ font-size: small;
+ height: auto;
+}
+
+cimage
+{
+ background-position: center center;
+ background-repeat: no-repeat;
+ background-size: contain;
+ flex: 1;
+}
+
+card[drag=true]
+{
+ z-index: 3;
+}
+
+/* Deck */
+
+deck {
+ --ccount: 0;
+
+ position: absolute;
+ width: 166px;
+ height: 250px;
+
+ top: 0;
+ left: 0;
+
+ overflow: visible;
+
+ border-radius: 10px;
+
+ transition-duration: 0.2s;
+
+ border: 3px solid var(--deck-shadow);
+ box-sizing: border-box;
+}
+
+deck:hover {
+ border: 3px solid var(--deck-hover);
+}
+
+/* Deck modes */
+
+deck[mode="stack"] > card {
+ transform: translate(5px, calc(var(--cpos) * 3px + 7px));
+}
+
+deck[mode="stack"] {
+ height: calc(var(--ccount) * 3px + 250px);
+}
+
+deck[mode="strip-hl"] > card {
+ transform: translate(calc(var(--ccount) * 75px + var(--cpos) * -75px + 5px), 7px);
+}
+
+deck[mode="strip-hl"] {
+ width: calc(var(--ccount) * 75px + 160px);
+}
+
+deck[mode="strip-hr"] > card {
+ transform: translate(calc(var(--cpos) * 75px + 5px), 7px);
+}
+
+deck[mode="strip-hr"] {
+ width: calc(var(--ccount) * 75px + 160px);
+}
+
+deck[mode="strip-vu"] > card {
+ transform: translate(5px, calc(var(--ccount) * 115px + var(--cpos) * -115px + 7px));
+}
+
+deck[mode="strip-vu"] {
+ height: calc(var(--ccount) * 115px + 240px);
+}
+
+deck[mode="strip-vd"] > card {
+ transform: translate(5px, calc(var(--cpos) * 115px + 7px));
+}
+
+deck[mode="strip-vd"] {
+ height: calc(var(--ccount) * 115px + 240px);
+} \ No newline at end of file
diff --git a/styles/client/desktop.css b/styles/client/desktop.css
index 46dc83d..10caa15 100644
--- a/styles/client/desktop.css
+++ b/styles/client/desktop.css
@@ -10,14 +10,6 @@
TopBar rules
*/
- button.top-button {
- transition-duration: 0.2s;
- }
-
- button.top-button:hover {
- background-color: #ddd;
- }
-
button#newgame:before{
content: "Create Game";
}
@@ -36,6 +28,7 @@
div.lobby {
min-width: 70vw;
min-height: 70vh;
+ max-height: 70vh;
display: flex;
text-align: center;
@@ -60,6 +53,24 @@
padding: 10px;
border-radius: 10px;
flex-basis: content;
+
+ background-color: var(--server-bg-loading);
+ color: var(--server-color-loading);
+ }
+
+ span.status[s=loading] {
+ background-color: var(--server-bg-loading);
+ color: var(--server-color-loading);
+ }
+
+ span.status[s=ok] {
+ background-color: var(--server-bg-ok);
+ color: var(--server-color-ok);
+ }
+
+ span.status[s=closed] {
+ background-color: var(--server-bg-closed);
+ color: var(--server-color-closed);
}
span.addr {
@@ -67,7 +78,7 @@
padding: 10px;
border-radius: 10px;
- background-color: #ddd;
+ background-color: var(--gui-bg-main);
flex: 1;
}
@@ -100,15 +111,15 @@
overflow-x: hidden;
padding: 10px;
border-radius: 10px;
- background-color: #ddd;
- flex: 4;
+ background-color: var(--gui-bg-main);
+ flex: 2;
- border: 5px solid #ddd;
+ border: 5px solid var(--gui-bg-main);
}
div.stats, div.settings {
border-radius: 10px;
- background-color: #ddd;
+ background-color: var(--gui-bg-main);
box-sizing: border-box;
@@ -131,4 +142,8 @@
/*
Table rules
*/
+
+ /*
+ Chat
+ */
}
diff --git a/styles/client/mobile.css b/styles/client/mobile.css
index 4659ce6..ed2e19d 100644
--- a/styles/client/mobile.css
+++ b/styles/client/mobile.css
@@ -1,4 +1,4 @@
-@media (max-width: 599px) or (max-height: 500px) {
+@media (max-width: 599px), (max-height: 500px) {
/*
Hide stuff
*/
@@ -10,6 +10,10 @@
display: none;
}
+ div.chat {
+ display: none;
+ }
+
/*
TopBar for mobile
*/
@@ -69,7 +73,7 @@
overflow-x: hidden;
overflow-y: auto;
- background-color: #ddd;
+ background-color: var(--gui-bg-main);
padding: 5px;
box-sizing: border-box;
diff --git a/styles/client/tablet.css b/styles/client/tablet.css
index 37750f8..b9b5bd1 100644
--- a/styles/client/tablet.css
+++ b/styles/client/tablet.css
@@ -38,4 +38,8 @@
margin-top: 0px;
margin-left: 10px;
}
+
+ div.games {
+ flex: 4;
+ }
}
diff --git a/styles/home/base.css b/styles/home/base.css
index 2ef3693..a082ec2 100644
--- a/styles/home/base.css
+++ b/styles/home/base.css
@@ -17,14 +17,15 @@ body {
align-items: center;
justify-content: center;
+
+ background-color: var(--main-bg);
}
div.content {
padding: 20px;
- background-color: #ddd;
+ background-color: var(--gui-bg-main);
border-radius: 10px;
- max-width: 500px;
display: flex;
text-align: center;
@@ -33,21 +34,75 @@ div.content {
align-items: center;
}
-a {
- font-size: 24px;
+a, button {
+ font-size: 1em;
display: block;
color: white;
+ box-sizing: border-box;
border-radius: 5px;
+ border: none;
padding: 5px;
- background-color: dodgerblue;
+ background-color: #0084ff;
transition-duration: 0.2s;
text-decoration: none;
margin-top: 10px;
+}
+
+
+
+div.content > div.prev {
+ width: 100%;
+ margin: 10px;
+
+ border-radius: 5px;
+
+ display: block;
+
+ padding: 0;
+}
+
+div.prev > a{
+ display: inline-block;
+ width: 80%;
+ margin: 0;
+ padding: 5px;
+ border-radius: 0;
+}
+
+div.prev > a:first-of-type {
+ border-top-left-radius: 5px;
+}
+
+div.prev > a:last-of-type {
+ border-bottom-left-radius: 5px;
+}
+
+div.prev > button {
+ display: inline-block;
+ width: 20%;
+ margin: 0;
+ background-color: #ff1e00;
+ padding: 5px;
+ border-radius: 0;
+}
+
+div.prev > button:first-of-type {
+ border-top-right-radius: 5px;
+}
+
+div.prev > button:last-of-type {
+ border-bottom-right-radius: 5px;
+}
+
+div.prev > button:hover {
+ background-color: #ff5741;
+}
- max-width: 100px;
+div.prev > button:active {
+ background-color: #9c1200;
} \ No newline at end of file
diff --git a/styles/input.css b/styles/input.css
new file mode 100644
index 0000000..42656a0
--- /dev/null
+++ b/styles/input.css
@@ -0,0 +1,425 @@
+/* Begin Input CSS */
+
+/* All input */
+
+input, select
+{
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+
+ margin: 5px;
+
+ display: block;
+
+ box-sizing: border-box;
+}
+
+/* Button input */
+
+input[type=button], input[type=submit], button, a
+{
+ padding: 10px;
+
+ border: none;
+ border-radius: 5px;
+
+ background-color: var(--input-bg-button);
+ color: var(--input-color-button);
+
+ font-size: medium;
+
+ transition-duration: 0.2s;
+
+ cursor: pointer;
+}
+
+input[type=button]:hover, input[type=submit]:hover, button:hover, a:hover
+{
+ background-color: var(--input-bg-button-hover);
+}
+
+input[type=button]:active, input[type=submit]:active, button:active, a:active
+{
+ background-color: var(--input-bg-button-active);
+}
+
+/* Text, date, number, and time input */
+
+input[type=text], input[type=date], input[type=time], input[type="number"]
+{
+ border: 2px solid var(--input-border-text);
+ border-radius: 3px;
+ padding: 5px;
+ background-color: var(--input-bg-text);
+ color: var(--input-color-text);
+ font-size: 1em;
+}
+
+input[type=text]:hover, input[type=date]:hover, input[type=time]:hover, input[type="number"]:hover
+{
+ border-color: var(--input-border-text-hover);
+}
+
+input[type=text]:focus, input[type=date]:focus, input[type=time]:focus, input[type="number"]:focus
+{
+ border-color: var(--input-border-text-active);
+}
+
+/* Radial input */
+
+input[type=radio]
+{
+ width: 20px;
+ height: 20px;
+
+ border: 3px solid var(--input-border-bool);
+ border-radius: 50%;
+
+ transition-duration: 0.2s;
+
+ background-color: var(--input-bg-bool);
+
+ cursor: pointer;
+
+ display: inline-block;
+ vertical-align: middle;
+}
+
+input[type=radio]:checked, input[type=radio]:hover{
+ border-width: 6px;
+}
+
+input[type=radio]:checked
+{
+ background-color: var(--input-bg-bool-true);
+ border-color: var(--input-border-bool-true);
+}
+
+input[type=radio]:hover
+{
+ background-color: var(--input-bg-bool-hover);
+ border-color: var(--input-border-bool-hover);
+}
+
+input[type=radio]:active
+{
+ background-color: var(--input-bg-bool-active);
+ border-color: var(--input-border-bool-active);
+}
+
+/* Checkbox input */
+
+input[type=checkbox]
+{
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+
+ display: inline-block;
+ vertical-align: middle;
+
+ width: 20px;
+ height: 20px;
+
+ border: 3px solid var(--input-border-bool);
+
+ border-radius: 2px;
+
+ transition-duration: 0.2s;
+
+ background-color: var(--input-bg-bool);
+
+ cursor: pointer;
+}
+
+input[type=checkbox]:checked
+{
+ border-width: 10px;
+ background-color: var(--input-border-bool-true);
+ border-color: var(--input-border-bool-true);
+}
+
+input[type=checkbox]:hover
+{
+ background-color: var(--input-bg-bool-hover);
+ border-color: var(--input-border-bool-hover);
+}
+
+input[type=checkbox]:active
+{
+ background-color: var(--input-bg-bool-active);
+ border-color: var(--input-border-bool-active);
+}
+
+input[type=checkbox]::after
+{
+ height: 15px;
+ width: 10px;
+
+ box-sizing: border-box;
+
+ position: relative;
+
+ display: block;
+
+ border-color: var(--input-bg-bool-true);
+ border-style: solid;
+ border-width: 0px 0px 0px 0px;
+
+ top: calc(50% - 10px);
+ left: calc(50% - 5px);
+
+ transform-origin: center;
+ transform: rotate(45deg);
+
+ content: '';
+}
+
+input[type=checkbox]:checked::after
+{
+ border-width: 0px 3px 3px 0px;
+}
+
+/* Color input */
+
+input[type=color]
+{
+ display: inline-block;
+
+ vertical-align: middle;
+
+ height: 20px;
+ width: 20px;
+
+ margin: 0;
+ padding: 0;
+
+ border: 0;
+
+ cursor: pointer;
+}
+
+/* File input */
+
+input[type=file]
+{
+ display: none;
+}
+
+/* Custom select */
+
+div.input-select
+{
+ font-size: 1em;
+
+ display: block;
+ position: absolute;
+
+}
+
+div.input-select > div
+{
+ display: none;
+ pointer-events: none;
+}
+
+div.input-select > div[selected=true]
+{
+ display: block;
+}
+
+div.input-container:focus > div.input-select
+{
+ pointer-events: all;
+
+ transform: translate(0, 2em);
+
+ border: 2px solid var(--input-border-select-active);
+ background-color: var(--input-bg-select-active);
+}
+
+div.input-container:focus > div.input-select > div
+{
+ pointer-events: all;
+ display: block;
+ padding: 5px;
+ width: 6em;
+}
+
+div.input-container:focus > div.input-select > div:hover
+{
+ background-color: var(--input-bg-select-hover);
+ color: var(--input-color-select-hover);
+}
+
+div.input-container:focus > div.input-select > div[selected=true]:after
+{
+ font-family: "IcoFont";
+ content: '\eed8';
+ font-size: medium;
+}
+
+/* Input container */
+
+*.input-container {
+ margin: 5px;
+ padding: 5px;
+
+ border-radius: 3px;
+
+ display: inline-block;
+}
+
+/* Color container */
+
+div.input-container[type=color]
+{
+ text-align: center;
+
+ background-color: var(--input-bg-button);
+ color: var(--input-color-button);
+
+ transition-duration: 0.2s;
+
+ cursor: pointer;
+
+ width: max-content;
+}
+
+div.input-container[type=color]::after
+{
+ display: inline;
+
+ vertical-align: middle;
+
+ content: ' Pick a color';
+}
+
+div.input-container[type=color]:hover
+{
+ background-color: var(--input-bg-button-hover);
+}
+
+div.input-container[type=color]:active
+{
+ background-color: var(--input-bg-button-active);
+}
+
+/* File input container */
+
+div.input-container[type=file]
+{
+ background-color: var(--input-bg-button);
+ color: var(--input-color-button);
+
+ transition-duration: 0.2s;
+
+ cursor: pointer;
+
+ text-align: center;
+
+ width: max-content;
+ height: max-content;
+}
+
+div.input-container[type=file]:hover
+{
+ background-color: var(--input-bg-button-hover);
+}
+
+div.input-container[type=file]:active
+{
+ background-color: var(--input-bg-button-active);
+}
+
+div.input-container[type=file]::after
+{
+ display: inline;
+
+ vertical-align: middle;
+
+ content: attr(data-files);
+}
+
+/* Radio input container */
+
+div.input-container[type=radio]::before
+{
+ display: block;
+ content: 'Select one';
+ transition-duration: 0.2s;
+}
+
+div.input-container[type=radio]
+{
+ background-color: var(--input-bg-multi);
+}
+
+div.input-container[type=radio]:hover
+{
+ background-color: var(--input-bg-multi);
+}
+
+/* Select input container */
+
+div.input-container[type=select]
+{
+ border: 2px solid var(--input-border-select);
+ background-color: var(--input-bg-select);
+ min-height: 1em;
+ width: 7em;
+
+ color: var(--input-color-select);
+
+ cursor: pointer;
+
+ overflow: hidden;
+
+ display: inline-block;
+
+ text-align: left;
+}
+
+div.input-container[type=select]:hover
+{
+ border-color: var(--input-border-select-hover);
+ background-color: var(--input-bg-select-hover);
+
+ color: var(--input-color-select-hover);
+}
+
+div.input-container[type=select]:focus
+{
+ border-color: var(--input-border-select-active);
+ background-color: var(--input-bg-select-active);
+
+ color: var(--input-color-select-active);
+
+ overflow: visible;
+}
+
+div.input-container[type=select]:after
+{
+ font-family: "IcoFont";
+ font-size: medium;
+ content: '\eab2';
+ display: block;
+ height: 100%;
+ width: 100%;
+ color: var(--input-color-select);
+ text-align: right;
+}
+
+div.input-container[type=select]:after:hover
+{
+ color: var(--input-color-select-hover);
+}
+
+div.input-container[type=select]:focus::after
+{
+ content: '\eab9';
+ color: var(--input-color-select-active);
+}
+
+/* End Input CSS */ \ No newline at end of file
diff --git a/styles/themes/colors-base.css b/styles/themes/colors-base.css
new file mode 100644
index 0000000..56b06d8
--- /dev/null
+++ b/styles/themes/colors-base.css
@@ -0,0 +1,98 @@
+* {
+ /* Main */
+ --main-bg: white;
+ --main-color: black;
+
+ /* Server */
+ --server-bg-ok: #0C0;
+ --server-bg-loading: #DA0;
+ --server-bg-closed: #D00;
+ --server-color-ok: white;
+ --server-color-loading: white;
+ --server-color-closed: white;
+
+ /* Gui and topbar */
+ --gui-bg-main: #ddd;
+ --gui-bg-game: white;
+ --gui-color-game: black;
+ --gui-shadow-game: gray;
+
+ --top-border: black;
+ --top-bg: #eee;
+ --top-bg-button: #eee;
+ --top-bg-button-hover: #ccc;
+ --top-bg-button-active: white;
+ --top-color-button: black;
+ --top-color-button-hover: black;
+ --top-color-button-active: black;
+
+ /* Table */
+ --table-bg: rgb(20, 110, 50);
+
+ /* Card defaults */
+ --card-color: black;
+ --card-bg: white;
+ --card-border: #bbb;
+ --card-hover: #0f0;
+
+ --deck-shadow: #2696ff;
+ --deck-hover: #73bbff;
+
+ /* Input */
+
+ --input-color-text: black;
+ --input-color-text-hover: black;
+ --input-color-text-active: black;
+ --input-bg-text: white;
+ --input-bg-text-hover: white;
+ --input-bg-text-active: white;
+ --input-border-text: #555;
+ --input-border-text-hover: black;
+ --input-border-text-active: #0084ff;
+
+ --input-color-button: white;
+ --input-color-button-hover: white;
+ --input-color-button-active: white;
+ --input-bg-button: #0084ff;
+ --input-bg-button-hover: #3ea2ff;
+ --input-bg-button-active:#0056a7;
+
+ --input-bg-bool: white;
+ --input-bg-bool-hover: white;
+ --input-bg-bool-active: white;
+ --input-border-bool: black;
+ --input-border-bool-hover: #3ea2ff;
+ --input-border-bool-active: black;
+ --input-border-bool-true: #0084ff;
+
+ --input-color-select: black;
+ --input-color-select-hover: black;
+ --input-color-select-active: #555;
+ --input-bg-select: white;
+ --input-bg-select-hover: white;
+ --input-bg-select-active: white;
+ --input-border-select: #555;
+ --input-border-select-hover: black;
+ --input-border-select-active: #0084ff;
+
+ --input-bg-multi: rgba(30, 30, 30, 0.3);
+ --input-bg-multi-hover: rgba(200, 200, 200, 0.3);
+
+ /* Chat */
+ --chat-color: black;
+ --chat-bg: white;
+
+ --chat-bg-text: rgba(0, 0, 0, 0.2);
+
+ --chat-bg-input: rgb(220, 220, 220);
+ --chat-bg-input-hover: rgb(220, 220, 220);
+ --chat-bg-input-active: rgb(220, 220, 220);
+
+ --chat-bg-inactive: rgb(80, 80, 80);
+ --chat-bg-inactive-hover: rgb(130, 130, 130);
+ --chat-bg-inactive-active: rgb(40, 40, 40);
+
+ --chat-bg-active: #0084ff;
+ --chat-bg-active-hover: #3ea2ff;
+ --chat-bg-active-active:#0056a7;
+} \ No newline at end of file
diff --git a/styles/themes/colors-dark.css b/styles/themes/colors-dark.css
new file mode 100644
index 0000000..f630ac0
--- /dev/null
+++ b/styles/themes/colors-dark.css
@@ -0,0 +1,103 @@
+* {
+ /* Main */
+ --main-bg: #333;
+ --main-color: white;
+
+ /* Server */
+ --server-bg-ok: #0B0;
+ --server-bg-loading: #DA0;
+ --server-bg-closed: #D00;
+ --server-color-ok: white;
+ --server-color-loading: white;
+ --server-color-closed: white;
+
+ /* Gui and topbar */
+ --gui-bg-main: #555;
+ --gui-bg-game: #777;
+ --gui-color-game: white;
+ --gui-shadow-game: #444;
+
+ --top-border: #999;
+ --top-bg: #333;
+ --top-bg-button: #555;
+ --top-bg-button-hover: #999;
+ --top-bg-button-active: #777;
+ --top-color-button: white;
+ --top-color-button-hover: white;
+ --top-color-button-active: white;
+
+ /* Table */
+ --table-bg: rgb(20, 110, 50);
+
+ /* Card defaults */
+ --card-color: black;
+ --card-bg: white;
+ --card-border: #bbb;
+ --card-hover: #0f0;
+
+ --deck-shadow: #2696ff;
+ --deck-hover: #3ea2ff;
+
+ /* Input */
+
+ --input-color-text: white;
+ --input-color-text-hover: white;
+ --input-color-text-active: white;
+ --input-bg-text: #777;
+ --input-bg-text-hover: #777;
+ --input-bg-text-active: #777;
+ --input-border-text: #222;
+ --input-border-text-hover: #AAA;
+ --input-border-text-active: #3ea2ff;
+
+ --input-color-button: white;
+ --input-color-button-hover: white;
+ --input-color-button-active: white;
+ --input-bg-button: #0084ff;
+ --input-bg-button-hover: #3ea2ff;
+ --input-bg-button-active:#0056a7;
+
+ --input-bg-bool: #555;
+ --input-bg-bool-hover: #999;
+ --input-bg-bool-active: #777;
+ --input-bg-bool-true: white;
+ --input-border-bool: #999;
+ --input-border-bool-hover: #999;
+ --input-border-bool-active: #999;
+ --input-border-bool-true: #999;
+
+ --input-color-select: white;
+ --input-color-select-hover: white;
+ --input-color-select-active: white;
+ --input-bg-select: #777;
+ --input-bg-select-hover: #888;
+ --input-bg-select-active: #777;
+ --input-border-select: #222;
+ --input-border-select-hover: #AAA;
+ --input-border-select-active: #3ea2ff;
+
+ --input-bg-multi: rgba(30, 30, 30, 0.3);
+ --input-bg-multi-hover: rgba(200, 200, 200, 0.3);
+
+ /* Chat */
+ --chat-color: white;
+ --chat-bg: #555;
+
+ --chat-bg-text: #444;
+
+ --chat-bg-input: #555;
+ --chat-bg-input-hover: #777;
+ --chat-bg-input-active: #444;
+
+ --chat-bg-select: #444;
+ --chat-bg-select-hover: #777;
+ --chat-bg-select-active: #333;
+
+ --chat-bg-inactive: #444;
+ --chat-bg-inactive-hover: #777;
+ --chat-bg-inactive-active: #333;
+
+ --chat-bg-active: rgb(255, 133, 34);
+ --chat-bg-active-hover: rgb(255, 165, 92);
+ --chat-bg-active-active: rgb(226, 102, 0);
+} \ No newline at end of file