--- /dev/null
+DEPLOY_HOST=lmno.games
+DEPLOY_DIR=/srv/lmno.games/www
+DO_NOT_DEPLOY="Makefile"
+
+deploy:
+ rm -rf .deploy-source
+ git clone . .deploy-source
+ rm -rf .deploy-source/.git
+ (cd .deploy-source; rsync -avz \
+ --exclude=$(DO_NOT_DEPLOY) \
+ ./ $(DEPLOY_HOST):$(DEPLOY_DIR) )
+ rm -rf .deploy-source
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+ <msapplication>
+ <tile>
+ <square150x150logo src="/mstile-150x150.png"/>
+ <TileColor>#ffc40d</TileColor>
+ </tile>
+ </msapplication>
+</browserconfig>
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+
+ <title>LMNO Games</title>
+
+ <link rel="stylesheet" href="/reset.css" type="text/css" />
+ <link rel="stylesheet" href="/style.css" type="text/css" />
+
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
+ <link rel="manifest" href="/site.webmanifest">
+ <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
+ <meta name="msapplication-TileColor" content="#ffc40d">
+ <meta name="theme-color" content="#ffffff">
+
+ </head>
+ <body>
+
+ <script src="/lmno.js"></script>
+
+ <div id="page">
+
+ <div id="message-area">
+ </div>
+
+ <form onsubmit="lmno_join(this); return false">
+
+ <div class="form-field large">
+ <label for="id">Game ID</label>
+ <input type="text" id="id" maxlength="4"
+ placeholder="Enter a 4-letter Game Code"
+ oninput="this.value = this.value.toUpperCase()"
+ autocomplete="off"
+ required>
+ </div>
+
+ <div class="form-field large">
+ <button type="submit">
+ Join Game
+ </button>
+ </div>
+
+ </form>
+
+ </div>
+ </body>
+</html>
--- /dev/null
+function undisplay(element) {
+ element.style.display="none";
+}
+
+function add_message(severity, message) {
+ message = `<div class="message ${severity}" onclick="undisplay(this)">
+<span class="hide-button" onclick="undisplay(this.parentElement)">×</span>
+${message}
+</div>`;
+ const message_area = document.getElementById('message-area');
+ message_area.insertAdjacentHTML('beforeend', message);
+}
+
+function join_loadend(request, game_id) {
+ if (request.status === 404) {
+ add_message("danger", game_id + " is not a valid game ID. Try again.");
+ return;
+ }
+}
+
+function lmno_join(form) {
+ const game_id = form.id.value;
+
+ var request = new XMLHttpRequest();
+ request.addEventListener("loadend", () => join_loadend(request, game_id));
+
+ request.open("GET", "/" + game_id);
+ request.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
+ request.send();
+
+ form.reset();
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<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"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="260"
+ height="260"
+ viewBox="0 0 41.275 41.275"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
+ sodipodi:docname="lmno.svg">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.979899"
+ inkscape:cx="99.276015"
+ inkscape:cy="131.79368"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ scale-x="0.6"
+ inkscape:window-width="1899"
+ inkscape:window-height="1436"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0" />
+ <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
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-228.20834)">
+ <path
+ sodipodi:nodetypes="ccccccc"
+ inkscape:connector-curvature="0"
+ id="path4524"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:23.28333282px;line-height:16.39887428px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.04445;stroke-linecap:butt;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 2.6520348,230.16805 h 4.3769937 v 13.66532 H 18.081943 v 3.30832 H 2.6520348 Z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4526"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:23.28333282px;line-height:16.39887428px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.04445;stroke-linecap:butt;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 19.739363,230.16805 h 5.570719 l 3.865397,9.08369 3.888135,-9.08369 h 5.559351 v 16.97364 h -4.138249 v -12.41474 l -3.910872,9.15189 h -2.773991 l -3.910873,-9.15189 v 12.41474 h -4.149617 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4528"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:23.28333282px;line-height:16.39887428px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.04445;stroke-linecap:butt;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 2.6520348,250.22028 h 4.8885904 l 6.1732668,11.64167 v -11.64167 h 4.149617 v 16.97364 h -4.88859 L 6.8016522,255.55225 v 11.64167 H 2.6520348 Z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4530"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:23.28333282px;line-height:16.39887428px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.04445;stroke-linecap:butt;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 29.891715,252.75553 q -2.000911,0 -3.103686,1.47795 -1.102775,1.47794 -1.102775,4.16098 0,2.67167 1.102775,4.14962 1.102775,1.47795 3.103686,1.47795 2.012281,0 3.115056,-1.47795 1.102775,-1.47795 1.102775,-4.14962 0,-2.68304 -1.102775,-4.16098 -1.102775,-1.47795 -3.115056,-1.47795 z m 0,-3.1719 q 4.092774,0 6.412012,2.34198 2.319238,2.34197 2.319238,6.46885 0,4.11551 -2.319238,6.45749 -2.319238,2.34197 -6.412012,2.34197 -4.081404,0 -6.412011,-2.34197 -2.319238,-2.34198 -2.319238,-6.45749 0,-4.12688 2.319238,-6.46885 2.330607,-2.34198 6.412011,-2.34198 z" />
+ </g>
+</svg>
--- /dev/null
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="585.000000pt" height="585.000000pt" viewBox="0 0 585.000000 585.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,585.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M410 5570 l-35 -5 0 -1190 c0 -655 3 -1194 7 -1200 7 -11 2155 -14
+2173 -3 8 5 13 421 6 458 -1 3 -346 5 -768 5 -423 0 -775 1 -783 3 -13 3 -15
+118 -14 968 l0 964 -35 1 c-154 4 -523 3 -551 -1z"/>
+<path d="M2820 5572 l-25 -3 1 -1193 c0 -656 3 -1197 6 -1202 5 -7 441 -11
+563 -5 20 1 21 4 20 871 0 479 1 870 3 870 3 0 43 -87 72 -155 6 -16 117 -275
+245 -575 l233 -545 94 -5 c51 -3 140 -4 198 -2 l105 3 209 492 c116 271 222
+519 236 552 15 33 42 96 60 140 18 44 37 84 41 89 5 5 8 -332 8 -750 0 -417 0
+-811 1 -874 l0 -115 286 0 c197 1 287 4 292 12 9 14 9 2380 0 2388 -5 5 -626
+9 -758 6 -23 -1 -30 -12 -64 -93 -22 -51 -50 -117 -62 -146 -13 -29 -36 -80
+-50 -115 -14 -34 -34 -80 -44 -102 -10 -22 -93 -217 -185 -434 l-168 -394 -22
+49 c-12 27 -29 65 -37 84 -17 38 -14 30 -283 665 -102 242 -191 450 -197 463
+-10 19 -19 22 -77 23 -398 3 -681 3 -701 1z"/>
+<path d="M4068 2815 c-1 -1 -26 -5 -55 -9 -492 -58 -851 -368 -967 -837 -35
+-143 -41 -197 -40 -399 0 -198 6 -255 39 -391 148 -598 653 -910 1371 -846
+174 16 382 79 513 157 288 169 485 479 526 825 3 28 8 62 11 77 10 50 6 323
+-6 408 -32 236 -105 423 -229 586 -71 94 -183 198 -271 251 -141 86 -257 126
+-480 169 -35 6 -405 15 -412 9z m315 -461 c151 -34 283 -144 354 -294 42 -88
+78 -215 89 -310 7 -65 7 -347 0 -358 -3 -5 -7 -29 -10 -54 -3 -26 -11 -59 -17
+-75 -6 -15 -10 -29 -9 -31 6 -11 -19 -79 -56 -153 -108 -212 -279 -310 -529
+-303 -259 8 -452 178 -526 464 -55 214 -46 541 21 736 68 195 214 337 393 379
+70 17 214 16 290 -1z"/>
+<path d="M378 2723 c-4 -7 -2 -2360 1 -2388 1 -12 559 -15 576 -3 7 4 10 287
+10 817 0 446 2 811 5 811 3 0 10 -10 15 -23 10 -20 51 -99 175 -332 21 -38 63
+-119 95 -180 31 -60 70 -132 85 -160 15 -27 51 -95 80 -150 29 -55 70 -131 90
+-170 36 -68 90 -170 172 -325 105 -199 152 -285 155 -290 3 -3 160 -5 349 -5
+l344 -1 0 1203 0 1203 -292 -2 -293 -3 0 -813 c0 -446 -3 -812 -6 -812 -3 0
+-21 28 -39 63 -64 121 -123 233 -160 302 -21 39 -61 115 -90 170 -29 55 -67
+127 -85 160 -18 33 -54 101 -80 150 -26 50 -64 122 -85 160 -72 134 -118 221
+-186 350 -37 72 -85 162 -105 200 l-38 70 -345 3 c-189 1 -346 -1 -348 -5z"/>
+</g>
+</svg>
--- /dev/null
+{
+ "name": "LMNO Games",
+ "short_name": "LMNO",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
--- /dev/null
+/*\
+|*|
+|*| Core elements: Sizing and padding
+|*|
+\*/
+
+body {
+ line-height: 1.5;
+ font-family: sans-serif;
+}
+
+h1 {
+ font-size: 150%;
+ font-weight: bold;
+}
+
+h2 {
+ font-size: 110%;
+ font-weight: bold;
+}
+
+/*\
+|*|
+|*| Overall page layout
+|*|
+|*| Assumes: Top-level div with id="page" for all content
+|*|
+\*/
+
+/* Mobile-first: At small sized we have a white background and use the
+ * entire screen width for the page (so the background color of the
+ * body is not visible anywhere). */
+body {
+ background-color: #333738;
+}
+
+#page {
+ background-color: white;
+}
+
+/* For a small screen (in either width or height) change the
+ * background of the body element to white so the application always
+ * appears as if it is "full screen".
+ */
+@media screen and (max-width: 500px) and (max-height: 860px) {
+ body {
+ background-color: white;
+ }
+}
+
+/* We never let the page content get larger than a large fixed width.
+ *
+ * And when the screen is wide enough, we can afford some "wasted"
+ * space on either side of the page content. This starts at 0 for a
+ * 620px wide page up to 50px on either side for a 720px wide page.
+ *
+ * Wider than that and we start to see the background on either side
+ * of the page content.
+ */
+#page {
+ box-sizing: border-box;
+ max-width: 720px;
+ margin-left: auto;
+ margin-right: auto;
+ padding-top: 0;
+ padding-bottom: 0;
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+@media screen and (min-width: 620px) and (max-width: 720px) {
+ #page {
+ padding-left: calc(1em + (100% - 620px)/2);
+ padding-right: calc(1em + (100% - 620px)/2);
+ }
+}
+
+@media screen and (min-width: 720px) {
+ #page {
+ padding-left: calc(1em + 50px);
+ padding-right: calc(1em + 50px);
+ }
+}
+
+/* The calculations for height are different. We don't have any
+ * vertical centering, so instead we have only some fixed padding on
+ * the top. Then, the margin which allows the background to be visible
+ * on the top and bottom starts appearing at a viewport height of 648
+ * and tops out at a size of 36px.
+ */
+#page {
+ min-height: 500px;
+ padding-top: 1em;
+}
+
+@media screen and (min-height: 648px) and (max-height: 720px) {
+ #page {
+ margin-top: calc((100vh - 648px)/2);
+ margin-bottom: calc((100vh - 648px)/2);
+ }
+}
+
+@media screen and (min-height: 720px) {
+ #page {
+ margin-top: 36px;
+ margin-bottom: 36px;
+ }
+}
+
+/*\
+|*|
+|*| Responsive form layout
+|*|
+\*/
+
+/*
+ *
+ * Within the form, fields can be placed in <div> elements with
+ * class "form-field". Each can also have an additional class of one
+ * of "small", "medium", or "large". These classes will be used to
+ * select either a 1- or 2-column layout for each row depending on the
+ * width of the screen.
+ *
+ * Expected layout is as follows:
+ *
+ * "large" rows: 1-column on all devices
+ *
+ * "medium" rows: 2-column on wide displays (laptop/desktop/tablet)
+ * 1-column on small display (phones)
+ *
+ * "small" rows: 2-column on all devices
+ *
+ * Finally, a class of either "left" or "right" will select which
+ * column the field should appear in for fields that end up in 2
+ * columns.
+ */
+form {
+ max-width: 100%;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-column-gap: 1em;
+}
+
+.form-field.small.left,.form-field.medium.left {
+ grid-column-start: 1;
+}
+
+.form-field.small.right,.form-field.medium.right {
+ grid-column-start: 2;
+}
+
+.form-field.large {
+ grid-column-start: 1;
+ grid-column-end: span 2;
+}
+
+/* For a narrow screen, use a single-column for medium form fields. */
+@media screen and (max-width: 600px) {
+ .form-field.medium.left,.form-field.medium.right {
+ grid-column-start: 1;
+ grid-column-end: span 2
+ }
+}
+
+/*\
+|*|
+|*| Styling for form input fields
+|*|
+\*/
+
+label {
+ font-size: 125%;
+}
+
+input {
+ box-sizing: border-box;
+ font-size: 125%;
+ padding: 0.5em;
+ width: 100%;
+ border: 1px solid #287789;
+ border-radius: 4px;
+}
+
+input:focus {
+ border: 2px solid #44c7ef;
+}
+
+button {
+ display: inline-block;
+ border-radius: 4px;
+ background-color: #287789;
+ border: none;
+ color: white;
+ text-align: center;
+ font-size: 125%;
+ margin-top: .25em;
+ padding-top: 0.25em;
+ padding-bottom: 0.25em;
+ width: 200px;
+}
+
+button:hover {
+ transform: translateY(-1px);
+ background-color: #44c7ef;
+}
+
+:focus {
+ outline: none;
+}
+
+::-moz-focus-inner {
+ border: 0;
+}
+
+/*\
+|*|
+|*| Styling for a message area
+|*|
+\*/
+
+/* Default message severity is "info" but can be overriden. */
+.message {
+ padding: 1em;
+ background-color: #44c7ef;
+ color: white;
+ transition: 0.3s;
+ margin-bottom: 0.5em;
+ font-weight: bold;
+ border-radius: 4px;
+ position: relative;
+}
+
+.success {
+ background-color: #44c7ef;
+}
+
+.warning {
+ background-color: #ffa92a;
+}
+
+.danger {
+ background-color: #f56257
+}
+
+.hide-button {
+ color: white;
+ font-size: 125%;
+ font-weight: bold;
+ cursor: pointer;
+ position: absolute;
+ right: 0.5em;
+ top: 0;
+}
+
+.hide-button:hover {
+ color: #bc2822;
+}