From dbaf69557c0d6e648120b068fec1920b9391a24a Mon Sep 17 00:00:00 2001 From: Kyle Gunger Date: Fri, 18 Sep 2020 12:19:44 -0400 Subject: Update from local repo --- LICENSE | 21 +++ README.md | 5 +- assets/standard/diamond.svg | 37 ++++ assets/standard/heart.svg | 33 ++++ client.html | 66 +++++-- images/vanilla.png | Bin 0 -> 3191 bytes index.html | 114 +++++++++-- scripts/cards/card.js | 110 +++++++++++ scripts/cards/deck.js | 146 +++++++++++++++ scripts/cards/drag.js | 128 +++++++++++++ scripts/client.js | 147 ++++++++------- scripts/cookie.js | 36 ++++ scripts/gui/chat.js | 149 +++++++++++++++ scripts/gui/input.js | 231 +++++++++++++++++++++++ scripts/gui/lobby.js | 209 +++++++++++++++++++++ scripts/gui/table.js | 79 ++++++++ scripts/lobby.js | 200 -------------------- scripts/message.js | 14 -- scripts/sock.js | 63 ------- scripts/socket/message.js | 18 ++ scripts/socket/sock.js | 65 +++++++ scripts/table.js | 16 -- scripts/theme.js | 26 +++ styles/client/base.css | 275 ++++++++++++++++++++++++--- styles/client/card.css | 191 +++++++++++++++++++ styles/client/desktop.css | 41 ++-- styles/client/mobile.css | 8 +- styles/client/tablet.css | 4 + styles/home/base.css | 67 ++++++- styles/input.css | 425 ++++++++++++++++++++++++++++++++++++++++++ styles/themes/colors-base.css | 98 ++++++++++ styles/themes/colors-dark.css | 103 ++++++++++ 32 files changed, 2680 insertions(+), 445 deletions(-) create mode 100644 LICENSE create mode 100644 assets/standard/diamond.svg create mode 100644 assets/standard/heart.svg create mode 100644 images/vanilla.png create mode 100644 scripts/cards/card.js create mode 100644 scripts/cards/deck.js create mode 100644 scripts/cards/drag.js create mode 100644 scripts/cookie.js create mode 100644 scripts/gui/chat.js create mode 100644 scripts/gui/input.js create mode 100644 scripts/gui/lobby.js create mode 100644 scripts/gui/table.js delete mode 100644 scripts/lobby.js delete mode 100644 scripts/message.js delete mode 100644 scripts/sock.js create mode 100644 scripts/socket/message.js create mode 100644 scripts/socket/sock.js delete mode 100644 scripts/table.js create mode 100644 scripts/theme.js create mode 100644 styles/client/card.css create mode 100644 styles/input.css create mode 100644 styles/themes/colors-base.css create mode 100644 styles/themes/colors-dark.css 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 @@ + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/client.html b/client.html index e7d6153..c5d7cc7 100644 --- a/client.html +++ b/client.html @@ -8,21 +8,37 @@ + + + + + - - - - - + + + + + + + + + + + + + + + + WebCards - Client @@ -30,33 +46,33 @@ - +
+ + +
+
+ +
+
+ +
+ + +
+
+ + + + + WebCards
-

WebCards

+

WebCards

- +
+
+
ws
+
wss
+
+
- +
- Connect + + + + +
+ + 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/gui/lobby.js b/scripts/gui/lobby.js new file mode 100644 index 0000000..7d2b6cd --- /dev/null +++ b/scripts/gui/lobby.js @@ -0,0 +1,209 @@ +// ############### +// # 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 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 (data) { + this.packs = data; + this.top.setPacks(this.packs) + this.e.stats.packs.innerText = this.packs.length(); + } + + // Set initial game list. + // { data object } object containing {games} and {name} + // { data.name string } name of the game the server runs + // { data.games array } array of public games the server is running + // { data.games[n].name } room name + // { 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 (data) { + while (this.e.games.firstChild != null) { + this.e.games.remove(this.elements.games.firstChild) + } + + for (let i in data.games) { + let gel = new Game(i.name, i.packs, i.id); + this.games.push(gel); + this.e.games.appendChild(gel.getElement()); + } + + 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 + // { data[n].name string } name of the player + // { 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 (data) { + + 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 (data) { + + } + + // Called when a new public game is removed on the server + // { data string } the uuid of the game to delete + removeGame (data) { + + } + + // Called when a new player enters the lobby. + // { data object } an object representing the player + // { data.name string } name of the player + // { 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 (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 (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 (data) { + + } + + // Called when a player exits the game (from lobby or game) + // { data string } uuid of player + removePlayer (data) { + + } + + // Called when the client wants to toggle the new game screen + newGame () { + //if(this.init) return; + this.top.toggleNewGame(); + } + + // Called when the client wants to toggle the mobile settings screen + mobileSettings () { + //if(this.init) return; + this.top.toggleMobileSettings(); + } + + // Called when the WebSocket state has changed. + 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 () { + while (this.e.games.firstElementChild != null) { + this.e.games.removeChild(this.e.games.firstElementChild) + } + + this.setState("Connecting", "loading", this.e.addr.innerText); + this.init = false; + } +} 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/lobby.js b/scripts/lobby.js deleted file mode 100644 index 4d8332d..0000000 --- a/scripts/lobby.js +++ /dev/null @@ -1,200 +0,0 @@ -// 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 = []; -} - -Lobby.prototype = { - // Set initial pack list - // {data array} array of strings representing pack names - packList: function(data) { - this.packs = data; - this.top.setPacks(this.packs) - this.elements.stats.packs.innerText = this.packs.length(); - }, - - // Set initial game list. - // { data object } object containing {games} and {name} - // { data.name string } name of the game the server runs - // { data.games array } array of public games the server is running - // { data.games[n].name } room name - // { 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) - } - - for (let i in data.games) { - let gel = new GameEl(i.name, i.packs, i.id); - this.games.push(gel); - this.elements.games.appendChild(gel.getElement()); - } - - this.elements.stats.game.innerText = data.name; - this.elements.stats.pubgame.innerText = this.games.length(); - }, - - // Set the initial player list. - // { data array } represents a list of player objects from the server - // { data[n].name string } name of the player - // { 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) { - - this.elements.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) { - - }, - - // Called when a new public game is removed on the server - // { data string } the uuid of the game to delete - removeGame: function(data) { - - }, - - // Called when a new player enters the lobby. - // { data object } an object representing the player - // { data.name string } name of the player - // { 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) { - - }, - - // 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) { - - }, - - // 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) { - - }, - - // Called when a player exits the game (from lobby or game) - // {data string } uuid of player - removePlayer: function(data) { - - }, - - // Called when the client wants to toggle the new game screen - newGame: function() { - //if(this.init) return; - this.top.toggleNewGame(); - }, - - // Called when the client wants to toggle the mobile settings screen - mobileSettings: function() { - //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); - }, - - // Called when we are resetting the game. - reset: function() { - while (this.elements.games.firstChild != null) { - this.elements.games.remove(this.elements.games.firstChild) - } - - this.setState("Connecting", "#DA0", this.elements.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/message.js b/scripts/message.js deleted file mode 100644 index 5e821c4..0000000 --- a/scripts/message.js +++ /dev/null @@ -1,14 +0,0 @@ -function Message(type, data){ - this.t = type; - this.d = data; -} - -Message.prototype = { - stringify: function(){ - 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/sock.js deleted file mode 100644 index cf06a5e..0000000 --- a/scripts/sock.js +++ /dev/null @@ -1,63 +0,0 @@ -// A wrapper around the wrapper -function SockWorker(serveraddr, version, callback) { - this.server = serveraddr; - this.version = version; - this.cb = callback; -} - -SockWorker.prototype = { - // Initialize the connection. - init: function() { - if(this.server == "" || this.server == null) { - return; - } - try { - this.socket = new WebSocket(this.server); - - this.socket.addEventListener("open", this.o.bind(this)); - this.socket.addEventListener("message", this.msg.bind(this)); - - this.socket.addEventListener("closed", this.c.bind(this)); - this.socket.addEventListener("error", this.err.bind(this)); - } catch (e) { - this.err(); - } - }, - - // Called when the connection connects to the server - o: function() { - 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) { - if(typeof e.data == "string") { - var dat = JSON.parse(e.data) - this.cb(dat); - } - }, - - // Called when the connection closes. - // Passes a close object to the callback. - c: function() { - this.cb({type: "close", data: ""}); - }, - - // Called when the connection encounters an error. - // Passes an error to the callback - err: function() { - this.cb({type: "error", data: ""}); - }, - - // Call to close the connection to the server - close: function() { - this.socket.close(); - }, - - // Send a message to the server - send: function(type, data) { - var m = new Message(type, data); - this.socket.send(m.stringify()) - } -}; \ No newline at end of file diff --git a/scripts/socket/message.js b/scripts/socket/message.js new file mode 100644 index 0000000..044027d --- /dev/null +++ b/scripts/socket/message.js @@ -0,0 +1,18 @@ +'use strict'; + +class Message{ + constructor (type, data) + { + this.t = type; + this.d = data; + } + + 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/socket/sock.js b/scripts/socket/sock.js new file mode 100644 index 0000000..4eacc18 --- /dev/null +++ b/scripts/socket/sock.js @@ -0,0 +1,65 @@ +// A wrapper around the wrapper +class SockWorker extends EventTarget{ + constructor (serveraddr, version) + { + super(); + + this.server = serveraddr; + this.version = version; + } + + // Initialize the connection. + init () { + if(this.server == "" || this.server == null) { + return; + } + try { + this.socket = new WebSocket(this.server); + + this.socket.addEventListener("open", this.o.bind(this)); + this.socket.addEventListener("message", this.msg.bind(this)); + + this.socket.addEventListener("closed", this.c.bind(this)); + this.socket.addEventListener("error", this.err.bind(this)); + } catch (e) { + this.err(); + } + } + + // Called when the connection connects to the server + 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 (e) { + if(typeof e.data == "string") { + var dat = JSON.parse(e.data) + this.dispatchEvent(new Event(dat.type, dat.data)); + } + } + + // Called when the connection closes. + // Passes a close object to the callback. + c () { + this.dispatchEvent(new Event("closed")); + } + + // Called when the connection encounters an error. + // Passes an error to the callback + err () { + this.dispatchEvent(new Event("error")); + } + + // Call to close the connection to the server + close () { + this.socket.close(); + } + + // Send a message to the server + send (type, data) { + var m = new Message(type, data); + this.socket.send(m.stringify()) + } +} 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 -- cgit v1.2.3