Import map svg in an object; add a dev server

This commit is contained in:
Catalin Constantin Mititiuc 2025-06-16 22:41:28 -07:00
parent 6aed9539fc
commit f42ad7c7be
28 changed files with 4478 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.log
*.tgz
dist
node_modules

22
README.md Normal file
View File

@ -0,0 +1,22 @@
## Install dev server packages
docker run --rm -w /app -v $PWD:/app -u $(id -u):$(id -u) node bash -c "npm install"
## Start the dev server
docker run --rm --init -it -w /app -v $PWD:/app -p 8080:8080 node bash -c "node dev-server.js"
Visit `localhost:8080` to view.
## Rough way to save the SVG map generated by JavaScript client-side
const XMLS = new XMLSerializer();
const svg_xmls = XMLS.serializeToString(svg);
let bl = new Blob([svg_xmls], {type: "text/html" });
let a = document.createElement("a");
a.href = URL.createObjectURL(bl);
a.download = "map.svg";
a.hidden = true;
document.body.appendChild(a);
a.innerHTML = "something random - nobody will see this, it doesn't matter what you put here";
a.click()

11
dev-server.js Normal file
View File

@ -0,0 +1,11 @@
require('esbuild-server')
.createServer(
{
bundle: true,
entryPoints: ['src/index.js'],
},
{
static: 'public',
}
)
.start();

431
package-lock.json generated Normal file
View File

@ -0,0 +1,431 @@
{
"name": "app",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"esbuild": "^0.20.2",
"esbuild-server": "^0.3.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.20.2",
"@esbuild/android-arm": "0.20.2",
"@esbuild/android-arm64": "0.20.2",
"@esbuild/android-x64": "0.20.2",
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@esbuild/freebsd-arm64": "0.20.2",
"@esbuild/freebsd-x64": "0.20.2",
"@esbuild/linux-arm": "0.20.2",
"@esbuild/linux-arm64": "0.20.2",
"@esbuild/linux-ia32": "0.20.2",
"@esbuild/linux-loong64": "0.20.2",
"@esbuild/linux-mips64el": "0.20.2",
"@esbuild/linux-ppc64": "0.20.2",
"@esbuild/linux-riscv64": "0.20.2",
"@esbuild/linux-s390x": "0.20.2",
"@esbuild/linux-x64": "0.20.2",
"@esbuild/netbsd-x64": "0.20.2",
"@esbuild/openbsd-x64": "0.20.2",
"@esbuild/sunos-x64": "0.20.2",
"@esbuild/win32-arm64": "0.20.2",
"@esbuild/win32-ia32": "0.20.2",
"@esbuild/win32-x64": "0.20.2"
}
},
"node_modules/esbuild-server": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/esbuild-server/-/esbuild-server-0.3.0.tgz",
"integrity": "sha512-8RuzIdM13gs7MyYwxn/c88nDdx086aREBvzWDk4G3cC7nudF8480OTrvAvanVmFZ9anDv9U4cRX/OKbladaRVA==",
"dev": true,
"engines": {
"node": ">=14"
},
"peerDependencies": {
"esbuild": ">=0.17.0"
}
}
}
}

6
package.json Normal file
View File

@ -0,0 +1,6 @@
{
"devDependencies": {
"esbuild": "^0.20.2",
"esbuild-server": "^0.3.0"
}
}

BIN
public/counter_prone.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

64
public/damage_block.css Normal file
View File

@ -0,0 +1,64 @@
.damage-selector, .damage-effect-indicator, label {
width: 20px;
}
.damage-selector, .damage-effect-indicator {
height: 30px;
}
.damage-selector {
margin: 0;
padding: 0;
border: 1px solid black;
display: inline-block;
}
.damage-effect-indicator {
position: absolute;
margin: 0;
padding: 0;
display: block;
pointer-events: none;
font-family: monospace;
}
.damage-selector.clear {
background-color: white;
}
.damage-selector.bruise {
background-color: orange;
}
.damage-selector.lethal {
background-color: red;
}
label input[type="radio"] {
position: absolute;
opacity: 0;
margin: 0;
padding: 0;
}
label {
display: none;
margin: 0 auto;
padding: 0;
}
label:first-of-type {
display: block;
}
label:has(input:checked) {
display: none;
}
label:has(+ label input:checked) {
display: none;
}
label:has(input:checked) + label {
display: block;
}

BIN
public/firing_arc_large.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
public/firing_arc_small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

390
public/index.html Normal file
View File

@ -0,0 +1,390 @@
<!DOCTYPE html>
<html>
<head>
<title>Infantry Combat Solo Basic</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<template id="damage-block">
<link rel="stylesheet" href="damage_block.css">
<slot name="block-number"></slot>
<span class="damage-effect-indicator">
<slot name="firing-modifier"></slot><br>
<slot name="movement-points"></slot>
</span>
<label>
<input type="radio" name="damage" checked>
<span class="damage-selector lethal"></span>
</label>
<label>
<input type="radio" name="damage">
<span class="damage-selector clear"></span>
</label>
<label>
<input type="radio" name="damage">
<span class="damage-selector bruise"></span>
</label>
</template>
<template id="soldier-record-block">
<link rel="stylesheet" href="soldier_record_block.css">
<p class="physical-status-track">
<span is="damage-block">
<span slot="block-number">10</span>
<span slot="movement-points">8</span>
</span>
<span is="damage-block">
<span slot="block-number">9</span>
<span slot="movement-points">8</span>
</span>
<span is="damage-block">
<span slot="block-number">8</span>
<span slot="movement-points">8</span>
</span>
<span is="damage-block">
<span slot="block-number">7</span>
<span slot="movement-points">8</span>
<span slot="firing-modifier">+1</span>
</span>
<span is="damage-block">
<span slot="block-number">6</span>
<span slot="movement-points">7</span>
<span slot="firing-modifier">+1</span>
</span>
<span is="damage-block">
<span slot="block-number">5</span>
<span slot="movement-points">7</span>
<span slot="firing-modifier">+2</span>
</span>
<span is="damage-block">
<span slot="block-number">4</span>
<span slot="movement-points">6</span>
<span slot="firing-modifier">+2</span>
</span>
<span is="damage-block">
<span slot="block-number">3</span>
<span slot="movement-points">6</span>
<span slot="firing-modifier">+2</span>
</span>
<span is="damage-block">
<span slot="block-number">2</span>
<span slot="movement-points">5</span>
<span slot="firing-modifier">+3</span>
</span>
<span is="damage-block">
<span slot="block-number">1</span>
<span slot="movement-points">4</span>
<span slot="firing-modifier">+3</span>
</span>
<span is="damage-block">
<span slot="block-number">0</span>
<span slot="movement-points">None</span>
<span slot="firing-modifier">0</span>
</span>
<span is="damage-block">
<span slot="block-number">DEAD</span>
</span>
</p>
<p><span>Troop Number</span> <slot name="troop-number">1</slot></p>
<p><span>Primary Weapon Type</span> <slot name="primary-weapon-type">Rifle</slot></p>
<ul>
<li><span>Damage</span> <slot name="primary-weapon-damage">4L</slot></li>
<li><span>Short</span> <slot name="primary-weapon-range-short">1-27</slot></li>
<li><span>Long</span> <slot name="primary-weapon-range-long">28-75</slot></li>
</ul>
<p>
<span>Hand Grenades</span>
<input type="number" min="0" max="4" value="4" />
</p>
</template>
<div id="map-container">
<div id="toggle-firing-arc-vis">
<div>davion</div>
<input type="checkbox" data-allegiance="davion" />
<div>liao</div>
<input type="checkbox" data-allegiance="liao" />
</div>
<div id="panel">
<fieldset name="point">
<legend>hex</legend>
<div>
<label>translateX <input name="translateX" type="number" step="0.1" value="0" /></label>
<label>translateY <input name="translateY" type="number" step="0.1" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scaleX <input name="scaleX" type="number" step="0.1" value="1" /></label>
<label>scaleY <input name="scaleY" type="number" step="0.1" value="1" /></label>
</div>
</fieldset>
<fieldset name="points">
<legend>grid</legend>
<div>
<label>translateX <input name="translateX" type="number" step="0.1" value="0" /></label>
<label>translateY <input name="translateY" type="number" step="0.1" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scaleX <input name="scaleX" type="number" step="0.1" value="1" /></label>
<label>scaleY <input name="scaleY" type="number" step="0.1" value="1" /></label>
</div>
</fieldset>
<fieldset name="map2">
<legend>map2</legend>
<div>
<label>translateX <input name="translateX" type="number" step="0.1" value="0" /></label>
<label>translateY <input name="translateY" type="number" step="0.1" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scaleX <input name="scaleX" type="number" step="0.001" value="1" /></label>
<label>scaleY <input name="scaleY" type="number" step="0.001" value="1" /></label>
</div>
</fieldset>
<fieldset name="map3">
<legend>map3</legend>
<div>
<label>translateX <input name="translateX" type="number" step="0.1" value="0" /></label>
<label>translateY <input name="translateY" type="number" step="0.1" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scaleX <input name="scaleX" type="number" step="0.001" value="1" /></label>
<label>scaleY <input name="scaleY" type="number" step="0.001" value="1" /></label>
</div>
</fieldset>
</div>
<object type="image/svg+xml" data="map.svg"></object>
<!-- <svg viewbox="-49 -40 2390 3163" xmlns="http://www.w3.org/2000/svg">
<defs>
<polygon id="point" points="0,10 8.66,5 8.66,-5 0,-10 -8.66,-5 -8.66,5" />
<circle id="counter-base" cx="0" cy="0" r="5" />
<g id="t-1" class="troop-counter-template"><use href="#counter-base" /><text>1</text></g>
<g id="t-2" class="troop-counter-template"><use href="#counter-base" /><text>2</text></g>
<g id="t-3" class="troop-counter-template"><use href="#counter-base" /><text>3</text></g>
<g id="t-4" class="troop-counter-template"><use href="#counter-base" /><text>4</text></g>
<g id="t-5" class="troop-counter-template"><use href="#counter-base" /><text>5</text></g>
<g id="t-6" class="troop-counter-template"><use href="#counter-base" /><text>6</text></g>
<g id="t-7" class="troop-counter-template"><use href="#counter-base" /><text>7</text></g>
<g id="davion-1" class="troop-counter"><use href="#t-1" /></g>
<g id="davion-2" class="troop-counter"><use href="#t-2" /></g>
<g id="davion-3" class="troop-counter"><use href="#t-3" /></g>
<g id="davion-4" class="troop-counter"><use href="#t-4" /></g>
<g id="davion-5" class="troop-counter"><use href="#t-5" /></g>
<g id="davion-6" class="troop-counter"><use href="#t-6" /></g>
<g id="davion-7" class="troop-counter"><use href="#t-7" /></g>
<g id="liao-1" class="troop-counter"><use href="#t-1" /></g>
<g id="liao-2" class="troop-counter"><use href="#t-2" /></g>
<g id="liao-3" class="troop-counter"><use href="#t-3" /></g>
<g id="liao-4" class="troop-counter"><use href="#t-4" /></g>
<g id="liao-5" class="troop-counter"><use href="#t-5" /></g>
<g id="liao-6" class="troop-counter"><use href="#t-6" /></g>
<g id="liao-7" class="troop-counter"><use href="#t-7" /></g>
<image id="counter-prone" href="counter_prone.jpg" width="10px" />
<image id="numbers" href="rendered_numbers.png" width="182" height="22" />
<symbol id="n1" viewBox="1 0 17 22" width="17" height="22"><use href="#numbers" /></symbol>
<symbol id="n2" viewBox="19 0 16 22" width="16" height="22"><use href="#numbers" /></symbol>
<symbol id="n3" viewBox="36 0 18 22" width="18" height="22"><use href="#numbers" /></symbol>
<symbol id="n4" viewBox="54 0 18 22" width="18" height="22"><use href="#numbers" /></symbol>
<symbol id="n5" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol>
<symbol id="n6" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol>
<symbol id="n7" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol>
<symbol id="n8" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol>
<symbol id="n9" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol>
<symbol id="n0" viewBox="0 0 18 22" width="18" height="22"><use href="#numbers" /></symbol>
</defs>
<rect id="background" x="-1" y="-1" width="2287" height="3087" />
<g id="image-maps">
<image id="map2" class="map-scans" href="scans/map2.jpg" width="2284" height="1518" />
<image id="map3" class="map-scans" href="scans/map3.jpg" width="2284" height="1518" />
</g>
<g id="firing-arcs">
<g id="shapes"></g>
<g id="lines"></g>
</g>
<g id="grid">
<g id="points"></g>
<g id="counters"></g>
</g>
<image href="rendered_numbers.png" x="0" y="-46" width="182" height="22" opacity="0.5" />
<use href="#n1" x="1" y="-40" opacity="0.5" />
<use href="#n2" x="19" y="-40" opacity="0.5" />
<use href="#n3" x="36" y="-40" opacity="0.5" />
<use href="#n4" x="54" y="-40" opacity="0.5" />
</svg> -->
<div id="status">
<span id="hex-counter">Distance: <span id="hex-count">-</span></span>
</div>
</div>
<div id="content">
<input type="checkbox" class="visible" checked />
<div>
Set firing arc:
<button type="button" class="set-firing-arc" data-size="small">
<img src="firing_arc_small.png" height="12" /> 2 MP
</button>
<button type="button" class="set-firing-arc" data-size="medium">
<img src="firing_arc_medium.png" height="12" /> 4 MP
</button>
<button type="button" class="set-firing-arc" data-size="large">
<img src="firing_arc_large.png" height="12" /> 6 MP
</button>
<br>
Prone: <input type="checkbox" id="toggle-prone-counter" />
Turn:
<span id="turn-count" data-update="0">
<span name="count">0</span>
<span class="inning-top"></span>
<span class="inning-bottom"></span>
</span>
</div>
<div id="record-sheet">
<div>
<!-- <img class="logo" src="logo-davion.png" /> -->
<p>
<strong>Davion</strong>
<button type="button" class="end-move" data-allegiance="davion">
End Movement
</button>
<button type="button" class="end-turn" data-allegiance="liao">
End Turn
</button>
<br>
<!-- 1st Squad, 3rd Platoon, Bravo Company, 2nd Battalion<br>
17th Kestral Mechanized Infantry -->
</p>
<div is="soldier-record-block" class="soldier-record selected" data-troop-number="1" data-troop-allegiance="davion">
<span slot="troop-number">1</span>
<span slot="primary-weapon-type">Rifle</span>
<span slot="primary-weapon-damage">4L</span>
<span slot="primary-weapon-range-short">1-27</span>
<span slot="primary-weapon-range-long">28-75</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="2" data-troop-allegiance="davion">
<span slot="troop-number">2</span>
<span slot="primary-weapon-type">Rifle</span>
<span slot="primary-weapon-damage">4L</span>
<span slot="primary-weapon-range-short">1-27</span>
<span slot="primary-weapon-range-long">28-75</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="3" data-troop-allegiance="davion">
<span slot="troop-number">3</span>
<span slot="primary-weapon-type">SMG</span>
<span slot="primary-weapon-damage">3L</span>
<span slot="primary-weapon-range-short">1-15</span>
<span slot="primary-weapon-range-long">16-25</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="4" data-troop-allegiance="davion">
<span slot="troop-number">4</span>
<span slot="primary-weapon-type">SMG</span>
<span slot="primary-weapon-damage">3L</span>
<span slot="primary-weapon-range-short">1-15</span>
<span slot="primary-weapon-range-long">16-25</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="5" data-troop-allegiance="davion">
<span slot="troop-number">5</span>
<span slot="primary-weapon-type">SMG</span>
<span slot="primary-weapon-damage">3L</span>
<span slot="primary-weapon-range-short">1-15</span>
<span slot="primary-weapon-range-long">16-25</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="6" data-troop-allegiance="davion">
<span slot="troop-number">6</span>
<span slot="primary-weapon-type">SMG</span>
<span slot="primary-weapon-damage">3L</span>
<span slot="primary-weapon-range-short">1-15</span>
<span slot="primary-weapon-range-long">16-25</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="7" data-troop-allegiance="davion">
<span slot="troop-number">7</span>
<span slot="primary-weapon-type">Blazer</span>
<span slot="primary-weapon-damage">4L</span>
<span slot="primary-weapon-range-short">1-17</span>
<span slot="primary-weapon-range-long">18-105</span>
</div>
</div>
<div>
<!-- <img class="logo" src="logo-liao.png" /> -->
<p>
<strong>Liao</strong>
<button type="button" class="end-move" data-allegiance="liao">
End Movement
</button>
<button type="button" class="end-turn" data-allegiance="davion">
End Turn
</button>
<br>
<!-- 2nd Squad, 1st Platoon, 3rd Company, 2nd Battalion<br>
Aldebaran Home Guard -->
</p>
<div is="soldier-record-block" class="soldier-record" data-troop-number="1" data-troop-allegiance="liao">
<span slot="troop-number">1</span>
<span slot="primary-weapon-type">Rifle</span>
<span slot="primary-weapon-damage">4L</span>
<span slot="primary-weapon-range-short">1-27</span>
<span slot="primary-weapon-range-long">28-75</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="2" data-troop-allegiance="liao">
<span slot="troop-number">2</span>
<span slot="primary-weapon-type">Rifle</span>
<span slot="primary-weapon-damage">4L</span>
<span slot="primary-weapon-range-short">1-27</span>
<span slot="primary-weapon-range-long">28-75</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="3" data-troop-allegiance="liao">
<span slot="troop-number">3</span>
<span slot="primary-weapon-type">SMG</span>
<span slot="primary-weapon-damage">3L</span>
<span slot="primary-weapon-range-short">1-15</span>
<span slot="primary-weapon-range-long">16-25</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="4" data-troop-allegiance="liao">
<span slot="troop-number">4</span>
<span slot="primary-weapon-type">SMG</span>
<span slot="primary-weapon-damage">3L</span>
<span slot="primary-weapon-range-short">1-15</span>
<span slot="primary-weapon-range-long">16-25</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="5" data-troop-allegiance="liao">
<span slot="troop-number">5</span>
<span slot="primary-weapon-type">SMG</span>
<span slot="primary-weapon-damage">3L</span>
<span slot="primary-weapon-range-short">1-15</span>
<span slot="primary-weapon-range-long">16-25</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="6" data-troop-allegiance="liao">
<span slot="troop-number">6</span>
<span slot="primary-weapon-type">SMG</span>
<span slot="primary-weapon-damage">3L</span>
<span slot="primary-weapon-range-short">1-15</span>
<span slot="primary-weapon-range-long">16-25</span>
</div>
<div is="soldier-record-block" class="soldier-record" data-troop-number="7" data-troop-allegiance="liao">
<span slot="troop-number">7</span>
<span slot="primary-weapon-type">Blazer</span>
<span slot="primary-weapon-damage">4L</span>
<span slot="primary-weapon-range-short">1-17</span>
<span slot="primary-weapon-range-long">18-105</span>
</div>
</div>
</div>
</div>
<script src="soldier_record_block.js"></script>
<script src="index.js"></script>
</body>
</html>

1920
public/map.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 213 KiB

BIN
public/rendered_numbers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

BIN
public/scans/map1-photo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

BIN
public/scans/map1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

BIN
public/scans/map1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 KiB

BIN
public/scans/map2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

BIN
public/scans/map3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

BIN
public/scans/map4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

View File

@ -0,0 +1,35 @@
span {
font-size: smaller;
font-family: monospace;
margin-right: 1em;
}
.physical-status-track {
text-align: center;
}
.physical-status-track span {
margin: 0;
padding: 0;
display: inline-block;
vertical-align: middle;
}
.physical-status-track span[slot="block-number"] {
font-family: serif;
font-size: unset;
}
ul {
margin: 0;
padding: 0;
}
ul li {
display: inline;
margin-left: 1em;
}
p {
margin: 0;
}

View File

@ -0,0 +1,40 @@
class SoldierRecordBlock extends HTMLDivElement {
constructor() {
super();
let template = document.getElementById('soldier-record-block');
let templateContent = template.content;
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.appendChild(templateContent.cloneNode(true));
// console.log(shadowRoot)
// this.shadowRoot
// .querySelectorAll('p:has(input[type="number"]), .physical-status-track')
// .forEach(el => el.addEventListener('click', e => e.stopPropagation()))
// ;
}
}
customElements.define(
'damage-block',
class extends HTMLSpanElement {
constructor() {
super();
let template = document.getElementById('damage-block');
let templateContent = template.content;
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.appendChild(templateContent.cloneNode(true));
}
},
{ extends: 'span' }
);
customElements.define(
'soldier-record-block',
SoldierRecordBlock,
{ extends: 'div'}
);

437
public/style.css Normal file
View File

@ -0,0 +1,437 @@
body {
margin: 0;
display: flex;
overflow: hidden;
}
#map-container {
flex-basis: 100%;
position: relative;
max-height: 100vh;
}
svg, object {
background-color: darkgray;
width: 100%;
height: 100%;
}
svg image {
image-rendering: pixelated;
}
svg image.map-scans {
image-rendering: auto;
}
svg text {
user-select: none;
}
div#status {
position: absolute;
bottom: 0;
right: 0;
margin: 3px;
display: none;
user-select: none;
}
#hex-counter {
padding: 2px;
background-color: rgba(255, 255, 255, 0.5);
border-radius: 2px;
}
#hex-count {
font-family: monospace;
}
div#content {
display: flex;
/* display: none; */
border-left: 1px solid gray;
flex-basis: 0;
/* overflow: scroll; */
max-height: 100vh;
flex-direction: column;
/* padding: 2px; */
position: relative;
}
#content > div:first-of-type {
padding: 2px;
border-bottom: 1px solid gray;
}
#content #buttons {
line-height: 1.5em;
}
#content input[type="checkbox"].visible {
position: absolute;
right: 0;
}
#record-sheet {
/* max-height: 100%; */
overflow-y: auto;
display: flex;
flex-direction: column;
/* display: none; */
background-color: gray;
}
#record-sheet > div {
/* padding: 0 2px; */
overflow-y: auto;
/* border: 1px solid black; */
}
#record-sheet > div > p {
margin: 0;
margin-top: 0px;
margin-bottom: 1px;
padding: 6px 2px;
background-color: lightgray;
}
#record-sheet > div > div {
/* border: 1px solid black; */
margin-bottom: 1px;
padding: 6px 4px;
/* border-radius: 4px; */
}
#record-sheet > div:last-of-type {
margin-top: 2px;
}
#record-sheet > div > div:last-of-type {
margin-bottom: 0;
}
svg > defs > #point {
fill: inherit;
fill-opacity: inherit;
stroke: inherit;
stroke-width: inherit;
stroke-opacity: inherit;
}
use[href="#point"] {
opacity: 0;
fill: teal;
fill-opacity: 0.2;
stroke: black;
stroke-width: 0.5px;
}
use[href="#point"]:hover, use[href="#point"].hover {
opacity: 1;
fill: orange;
}
use[href="#point"].active {
opacity: 0.2;
}
use[href="#point"].sight-line-target {
opacity: 1;
stroke: orangered;
/* stroke-width: 1px; */
fill-opacity: 0.04;
}
polyline.move-trace {
stroke: gray;
stroke-dasharray: 2;
fill: none;
}
g#grid {
transform: translate(19px, 31px) scale(4);
}
#background {
fill: #bacae3;
}
#map2 {
transform-origin: 0px 0px;
transform: translate(-0.9px, -2.4px) scale(0.999, 1.007);
}
#map3 {
transform-origin: 0px 0px;
/* transform: translateY(1518px); */
transform: translate(1.3px, 1564.1px) rotate(0.1deg) scale(0.999, 1.002);
}
#panel {
display: none;
position: absolute;
right: 0;
background-color: white;
border: 1px solid black;
padding: 2px;
}
#panel fieldset label {
display: block;
text-align: right;
}
#toggle-firing-arc-vis {
position: absolute;
right: 0;
padding-top: 20px;
}
#toggle-firing-arc-vis div {
margin-top: 10px;
writing-mode: vertical-lr;
transform: rotate(180deg);
}
#counter-base {
r: inherit;
}
g.troop-counter-template, g.troop-counter-template use {
r: inherit;
}
g.troop-counter, g.troop-counter use {
r: inherit;
}
g.troop-counter-template text {
fill: white;
font-size: 12px;
font-weight: bold;
font-family: monospace;
cursor: default;
text-anchor: middle;
pointer-events: none;
user-select: none;
transform: translateY(4px);
stroke: none;
}
g.troop-counter [href="#counter-prone"] {
transform: translate(-5px, 6px);
}
g#points g use.counter {
r: 5px;
}
g#points g.hover use[href="#point"] {
opacity: 1;
fill: orange;
}
g#points g.hover use.counter {
r: 7px;
}
g#points g.hover use.counter:not(.clone) {
/* stroke: orange; */
/* stroke-width: 2px; */
}
g#points use.counter[data-troop-allegiance="davion"] {
fill: red;
}
g#points use.counter[data-troop-allegiance="liao"] {
fill: green;
}
g#points use.clone {
stroke: white;
stroke-width: 0.5px;
stroke-dasharray: 1;
}
g#points use[data-troop-allegiance="davion"].clone {
fill: rgb(255, 126, 126);
}
g#points use[data-troop-allegiance="liao"].clone {
fill: rgb(130, 190, 130);
}
/* ======================================================= */
/* g#counters {
pointer-events: none;
} */
/* g#counters use {
r: 5px;
}
g#counters use:hover {
stroke: orange;
stroke-width: 2px;
r: 8px;
} */
/* g#counters use[data-troop-allegiance="davion"] {
fill: red;
}
g#counters use[data-troop-allegiance="liao"] {
fill: green;
} */
/* g#counters use.clone {
stroke: white;
stroke-width: 0.5px;
stroke-dasharray: 1;
}
g#counters use[data-troop-allegiance="davion"].clone {
fill: rgb(255, 126, 126);
}
g#counters use[data-troop-allegiance="liao"].clone {
fill: rgb(130, 190, 130);
} */
text.counter, #troop-counter text {
font-size: 12px;
font-weight: bold;
/* stroke: black; */
fill: white;
/* stroke-width: 0.5px; */
font-family: sans-serif;
cursor: default;
text-anchor: middle;
/* transform: translateY(25%); */
transform: translateY(4px);
pointer-events: none;
user-select: none;
}
polygon.firing-arc[data-troop-allegiance="davion"] {
fill: red;
}
polygon.firing-arc[data-troop-allegiance="liao"] {
fill: green;
}
#shapes {
opacity: 0.1;
}
#shapes polygon {
stroke: none;
}
#lines polygon {
fill: none;
stroke: black;
}
button.set-firing-arc img {
vertical-align: middle;
pointer-events: none;
}
.sight-line {
stroke: orangered;
stroke-width: 0.5px;
pointer-events: none;
}
.soldier-record {
display: inline-block;
position: relative;
white-space: nowrap;
background-color: white;
}
.soldier-record [slot="troop-number"] {
color: white;
font-weight: bold;
border-radius: 10px;
padding: 0 4px;
}
.soldier-record[data-troop-allegiance="davion"] [slot="troop-number"] {
background-color: red;
}
.soldier-record[data-troop-allegiance="liao"] [slot="troop-number"] {
background-color: green;
}
.soldier-record.selected {
background-color: khaki;
}
.soldier-record.movement-ended {
background-color: none;
opacity: 0.5;
}
.wall {
fill: none;
stroke: red;
stroke-width: 7px;
opacity: 0.7;
}
#asterisk {
font-size: 30;
}
img.logo {
width: 100px;
margin: 0 auto;
display: block;
}
#turn-count[data-update="0"] span.inning-bottom {
display: none;
}
#turn-count[data-update="1"] span.inning-top {
display: none;
}
@media (width >= 1800px) {
#record-sheet {
flex-direction: row;
/* gap: 2px; */
}
#record-sheet > div > p {
margin-top: 0;
}
#record-sheet > div {
/* max-height: unset; */
overflow-y: unset;
height: min-content;
}
#record-sheet div:last-of-type {
margin-top: 0;
}
#record-sheet > div:first-of-type > div {
/* margin-left: 1px; */
margin-right: 1px;
}
#record-sheet > div:last-of-type > div {
margin-left: 1px;
/* margin-right: 1px; */
}
}

1118
src/index.js Normal file

File diff suppressed because it is too large Load Diff