summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--info/cards.html137
-rw-r--r--play.css347
-rw-r--r--play.html354
-rw-r--r--play.js2
-rw-r--r--rules.js116
-rw-r--r--tools/boxes.svg384
-rw-r--r--tools/cards.sh8
-rw-r--r--tools/crop.sh123
-rw-r--r--tools/genboxes.py73
-rw-r--r--tools/gencolors.js85
-rw-r--r--tools/genhex.js63
-rw-r--r--tools/map.sh7
-rw-r--r--tools/map.svg3
-rw-r--r--tools/render.sh36
14 files changed, 1738 insertions, 0 deletions
diff --git a/info/cards.html b/info/cards.html
new file mode 100644
index 0000000..f085796
--- /dev/null
+++ b/info/cards.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<title>Time of Crisis - Cards</title>
+<style>
+
+html {
+ background-color: slategray;
+}
+
+.list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20px;
+ margin: 40px;
+}
+
+.card {
+ width: 250px;
+ height: 350px;
+ background-size: 250px 350px;
+ background-repeat: no-repeat;
+ border-radius: 12px;
+ box-shadow: 0 0 0 1px #444, 0 0 4px #000;
+}
+
+.card.event_back{background-image:url(../cards.1x/event_back.jpg)}
+.card.event_1{background-image:url(../cards.1x/event_01.jpg)}
+.card.event_2{background-image:url(../cards.1x/event_02.jpg)}
+.card.event_3{background-image:url(../cards.1x/event_03.jpg)}
+.card.event_4{background-image:url(../cards.1x/event_04.jpg)}
+.card.event_5{background-image:url(../cards.1x/event_05.jpg)}
+.card.event_6{background-image:url(../cards.1x/event_06.jpg)}
+.card.event_7{background-image:url(../cards.1x/event_07.jpg)}
+.card.event_8{background-image:url(../cards.1x/event_08.jpg)}
+.card.event_9{background-image:url(../cards.1x/event_09.jpg)}
+.card.event_10{background-image:url(../cards.1x/event_10.jpg)}
+.card.event_11{background-image:url(../cards.1x/event_11.jpg)}
+.card.event_12{background-image:url(../cards.1x/event_12.jpg)}
+.card.event_13{background-image:url(../cards.1x/event_13.jpg)}
+.card.event_14{background-image:url(../cards.1x/event_14.jpg)}
+.card.event_15{background-image:url(../cards.1x/event_15.jpg)}
+.card.event_16{background-image:url(../cards.1x/event_16.jpg)}
+.card.event_17{background-image:url(../cards.1x/event_17.jpg)}
+.card.event_18{background-image:url(../cards.1x/event_18.jpg)}
+.card.event_19{background-image:url(../cards.1x/event_19.jpg)}
+.card.influence_back{background-image:url(../cards.1x/influence_back.jpg)}
+.card.influence_m1{background-image:url(../cards.1x/influence_m1.jpg)}
+.card.influence_m2{background-image:url(../cards.1x/influence_m2.jpg)}
+.card.influence_m3{background-image:url(../cards.1x/influence_m3.jpg)}
+.card.influence_m4{background-image:url(../cards.1x/influence_m4.jpg)}
+.card.influence_p1{background-image:url(../cards.1x/influence_p1.jpg)}
+.card.influence_p2{background-image:url(../cards.1x/influence_p2.jpg)}
+.card.influence_p3{background-image:url(../cards.1x/influence_p3.jpg)}
+.card.influence_p4{background-image:url(../cards.1x/influence_p4.jpg)}
+.card.influence_s1{background-image:url(../cards.1x/influence_s1.jpg)}
+.card.influence_s2{background-image:url(../cards.1x/influence_s2.jpg)}
+.card.influence_s3{background-image:url(../cards.1x/influence_s3.jpg)}
+.card.influence_s4{background-image:url(../cards.1x/influence_s4.jpg)}
+
+@media (min-resolution:97dpi) {
+.card.event_back{background-image:url(../cards.2x/event_back.jpg)}
+.card.event_1{background-image:url(../cards.2x/event_01.jpg)}
+.card.event_2{background-image:url(../cards.2x/event_02.jpg)}
+.card.event_3{background-image:url(../cards.2x/event_03.jpg)}
+.card.event_4{background-image:url(../cards.2x/event_04.jpg)}
+.card.event_5{background-image:url(../cards.2x/event_05.jpg)}
+.card.event_6{background-image:url(../cards.2x/event_06.jpg)}
+.card.event_7{background-image:url(../cards.2x/event_07.jpg)}
+.card.event_8{background-image:url(../cards.2x/event_08.jpg)}
+.card.event_9{background-image:url(../cards.2x/event_09.jpg)}
+.card.event_10{background-image:url(../cards.2x/event_10.jpg)}
+.card.event_11{background-image:url(../cards.2x/event_11.jpg)}
+.card.event_12{background-image:url(../cards.2x/event_12.jpg)}
+.card.event_13{background-image:url(../cards.2x/event_13.jpg)}
+.card.event_14{background-image:url(../cards.2x/event_14.jpg)}
+.card.event_15{background-image:url(../cards.2x/event_15.jpg)}
+.card.event_16{background-image:url(../cards.2x/event_16.jpg)}
+.card.event_17{background-image:url(../cards.2x/event_17.jpg)}
+.card.event_18{background-image:url(../cards.2x/event_18.jpg)}
+.card.event_19{background-image:url(../cards.2x/event_19.jpg)}
+.card.influence_back{background-image:url(../cards.2x/influence_back.jpg)}
+.card.influence_m1{background-image:url(../cards.2x/influence_m1.jpg)}
+.card.influence_m2{background-image:url(../cards.2x/influence_m2.jpg)}
+.card.influence_m3{background-image:url(../cards.2x/influence_m3.jpg)}
+.card.influence_m4{background-image:url(../cards.2x/influence_m4.jpg)}
+.card.influence_p1{background-image:url(../cards.2x/influence_p1.jpg)}
+.card.influence_p2{background-image:url(../cards.2x/influence_p2.jpg)}
+.card.influence_p3{background-image:url(../cards.2x/influence_p3.jpg)}
+.card.influence_p4{background-image:url(../cards.2x/influence_p4.jpg)}
+.card.influence_s1{background-image:url(../cards.2x/influence_s1.jpg)}
+.card.influence_s2{background-image:url(../cards.2x/influence_s2.jpg)}
+.card.influence_s3{background-image:url(../cards.2x/influence_s3.jpg)}
+.card.influence_s4{background-image:url(../cards.2x/influence_s4.jpg)}
+}
+
+</style>
+
+<body>
+
+<div class="list">
+<div class="card influence_m1"></div>
+<div class="card influence_m2"></div>
+<div class="card influence_m3"></div>
+<div class="card influence_m4"></div>
+</div>
+<div class="list">
+<div class="card influence_p1"></div>
+<div class="card influence_p2"></div>
+<div class="card influence_p3"></div>
+<div class="card influence_p4"></div>
+</div>
+<div class="list">
+<div class="card influence_s1"></div>
+<div class="card influence_s2"></div>
+<div class="card influence_s3"></div>
+<div class="card influence_s4"></div>
+</div>
+
+<div class="list">
+<div class="card event_back"></div>
+<div class="card event_1"></div>
+<div class="card event_2"></div>
+<div class="card event_3"></div>
+<div class="card event_4"></div>
+<div class="card event_5"></div>
+<div class="card event_6"></div>
+<div class="card event_7"></div>
+<div class="card event_8"></div>
+<div class="card event_9"></div>
+<div class="card event_10"></div>
+<div class="card event_11"></div>
+<div class="card event_12"></div>
+<div class="card event_13"></div>
+<div class="card event_14"></div>
+<div class="card event_15"></div>
+</div>
+
+</body>
diff --git a/play.css b/play.css
new file mode 100644
index 0000000..969d92c
--- /dev/null
+++ b/play.css
@@ -0,0 +1,347 @@
+main { background-color: dimgray; }
+#roles { background-color: gray; }
+header { background-color: silver; }
+header.your_turn { background-color: orange; }
+#role_Red .role_name { background-color: salmon; }
+#role_Blue .role_name { background-color: #a0caec; }
+#role_Yellow .role_name { background-color: #ffe175; }
+#role_Green .role_name { background-color: #80b563; }
+#turn_info { background-color: gainsboro; }
+.role_vp { float: right; }
+
+#mapwrap {
+ width: 2550px;
+ height: 1650px;
+ box-shadow: 0px 1px 10px #0008;
+ margin-bottom: 36px;
+}
+
+#map {
+ width: 2550px;
+ height: 1650px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-image: url(map75.jpg);
+ background-color: #e8d792;
+}
+
+#crisis_table {
+ display: none;
+ position: absolute;
+ width: 262px;
+ height: 356px;
+ top: 159px;
+ right: 93px;
+}
+
+#crisis_table.p2 {
+ display: block;
+ background-image: url(overlay_2p_75.jpg);
+}
+#crisis_table.p3 {
+ display: block;
+ background-image: url(overlay_3p_75.jpg);
+}
+
+#pieces div {
+ position: absolute;
+}
+
+#pieces .no_place_governor {
+ margin-top: 6px;
+ margin-left: 6px;
+ border-radius: 0;
+}
+
+/* COUNTERS */
+
+.amphitheater, .basilica, .limes { background-color: #efebea; }
+.red { background-color: #ed1b2f; }
+.blue { background-color: #a0caec; }
+.yellow { background-color: #ffe175; }
+.green { background-color: #80b563; }
+.alamanni { background-color: #c3bc8e; }
+.franks { background-color: #9cb4be; }
+.goths { background-color: #3a9cd6; }
+.nomads { background-color: #f99d1c; }
+.sassanids { background-color: #8e5ca6; }
+.rival { background-color: #b8b996; }
+.militia, .legion { background-color: #ffffff; }
+.neutral { background-color: #e3dedc; }
+
+/* :r !node tools/gencolors.js */
+.amphitheater, .basilica, .limes { border-color: #fffefd #b2aead #b2aead #fffefd; box-shadow: 0 0 0 1px #444140, 1px 2px 4px #0008; }
+.red { border-color: #ff5455 #c00000 #c00000 #ff5455; box-shadow: 0 0 0 1px #680000, 1px 2px 4px #0008; }
+.blue { border-color: #d5ffff #6e96b6 #6e96b6 #d5ffff; box-shadow: 0 0 0 1px #113854, 1px 2px 4px #0008; }
+.yellow { border-color: #fffe92 #c3a634 #c3a634 #fffe92; box-shadow: 0 0 0 1px #553a00, 1px 2px 4px #0008; }
+.green { border-color: #ace48f #568837 #568837 #ace48f; box-shadow: 0 0 0 1px #033600, 1px 2px 4px #0008; }
+.alamanni { border-color: #f7efc0 #928b5f #928b5f #f7efc0; box-shadow: 0 0 0 1px #393204, 1px 2px 4px #0008; }
+.franks { border-color: #cce5ef #6f868f #6f868f #cce5ef; box-shadow: 0 0 0 1px #1d323a, 1px 2px 4px #0008; }
+.goths { border-color: #68c6ff #0073ab #0073ab #68c6ff; box-shadow: 0 0 0 1px #002759, 1px 2px 4px #0008; }
+.nomads { border-color: #ffcf60 #c56c00 #c56c00 #ffcf60; box-shadow: 0 0 0 1px #610700, 1px 2px 4px #0008; }
+.sassanids { border-color: #b17dca #6d3c83 #6d3c83 #b17dca; box-shadow: 0 0 0 1px #2f0042, 1px 2px 4px #0008; }
+.rival { border-color: #eaebc7 #888968 #888968 #eaebc7; box-shadow: 0 0 0 1px #323214, 1px 2px 4px #0008; }
+.neutral { border-color: #fffefc #a9a4a2 #a9a4a2 #fffefc; box-shadow: 0 0 0 1px #403d3b, 1px 2px 4px #0008; }
+.militia { border-color: #ffffff #b2b2b2 #b2b2b2 #ffffff; box-shadow: 0 0 0 1px #434343, 1px 2px 4px #0008; }
+.legion { border-color: #ffffff #b2b2b2 #b2b2b2 #ffffff; box-shadow: 0 0 0 1px #434343, 1px 2px 4px #0008; }
+.no_place_governor { border-color: #8e8e8e #505050 #505050 #8e8e8e; box-shadow: 0 0 0 1px #191919, 1px 2px 4px #0008; }
+
+#legion_1 { background-position: 0px 0px }
+#legion_2 { background-position: -55px 0px }
+#legion_3 { background-position: -110px 0px }
+#legion_4 { background-position: -165px 0px }
+#legion_5 { background-position: -220px 0px }
+#legion_6 { background-position: -275px 0px }
+#legion_7 { background-position: -330px 0px }
+#legion_8 { background-position: -385px 0px }
+#legion_9 { background-position: -440px 0px }
+#legion_10 { background-position: 0px -55px }
+#legion_11 { background-position: -55px -55px }
+#legion_12 { background-position: -110px -55px }
+#legion_13 { background-position: -165px -55px }
+#legion_14 { background-position: -220px -55px }
+#legion_15 { background-position: -275px -55px }
+#legion_16 { background-position: -330px -55px }
+#legion_17 { background-position: -385px -55px }
+#legion_18 { background-position: -440px -55px }
+#legion_19 { background-position: 0px -110px }
+#legion_20 { background-position: -55px -110px }
+#legion_21 { background-position: -110px -110px }
+#legion_22 { background-position: -165px -110px }
+#legion_23 { background-position: -220px -110px }
+#legion_24 { background-position: -275px -110px }
+#legion_25 { background-position: -330px -110px }
+#legion_26 { background-position: -385px -110px }
+#legion_27 { background-position: -440px -110px }
+#legion_28 { background-position: 0px -165px }
+#legion_29 { background-position: -55px -165px }
+#legion_30 { background-position: -110px -165px }
+#legion_31 { background-position: -165px -165px }
+#legion_32 { background-position: -220px -165px }
+#legion_33 { background-position: -275px -165px }
+
+.amphitheater, .basilica, .limes {
+ width: 92px;
+ height: 46px;
+ background-size: 92px 46px;
+ border-radius: 8px;
+ border-width: 2px;
+ border-style: solid;
+}
+
+.militia, .legion,
+.alamanni, .franks, .goths, .nomads, .sassanids, .rival,
+.general, .emperor_turns, .no_place_governor {
+ width: 55px;
+ height: 55px;
+ background-size: 55px 55px;
+ border-radius: 8px;
+ border-width: 2px;
+ border-style: solid;
+}
+
+.governor, .legacy, .legacy_40 {
+ width: 50px;
+ height: 50px;
+ background-size: 50px 50px;
+ border-radius: 50%;
+ border-width: 2px;
+ border-style: solid;
+}
+
+.castra, .quaestor, .mob, .mob_x2, .seat_of_power, .breakaway {
+ width: 54px;
+ height: 64px;
+ background-size: 54px 64px;
+ background-color: transparent;
+ box-shadow: none;
+ filter: drop-shadow(1px 1px 6px #0008);
+}
+
+.legion {
+ background-size: 900% 400%;
+}
+
+
+.no_place_governor { background-image: url(images/no_place_governor.png) }
+.legion { background-image: url(images/legion_full.png) }
+.legion.reduced { background-image: url(images/legion_reduced.png) }
+.amphitheater { background-image: url(images/amphitheater.png) }
+.basilica { background-image: url(images/basilica.png) }
+.limes { background-image: url(images/limes.png) }
+.militia { background-image: url(images/militia.png) }
+.alamanni { background-image: url(images/alamanni_active.png) }
+.franks { background-image: url(images/franks_active.png) }
+.goths { background-image: url(images/goths_active.png) }
+.nomads { background-image: url(images/nomads_active.png) }
+.sassanids { background-image: url(images/sassanids_active.png) }
+.alamanni.inactive { background-image: url(images/alamanni_inactive.png) }
+.franks.inactive { background-image: url(images/franks_inactive.png) }
+.goths.inactive { background-image: url(images/goths_inactive.png) }
+.nomads.inactive { background-image: url(images/nomads_inactive.png) }
+.sassanids.inactive { background-image: url(images/sassanids_inactive.png) }
+.neutral.governor { background-image: url(images/neutral_governor.png) }
+
+#ardashir { background-image: url(images/ardashir.png) }
+#cniva { background-image: url(images/cniva.png) }
+#shapur { background-image: url(images/shapur.png) }
+#postumus { background-image: url(images/rival_postumus.png) }
+#priest_king { background-image: url(images/rival_priest_king.png) }
+#zenobia { background-image: url(images/rival_zenobia.png) }
+
+.blue.governor { background-image: url(images/blue_governor.png) }
+.green.governor { background-image: url(images/green_governor.png) }
+.red.governor { background-image: url(images/red_governor.png) }
+.yellow.governor { background-image: url(images/yellow_governor.png) }
+.blue.legacy { background-image: url(images/blue_legacy.png) }
+.green.legacy { background-image: url(images/green_legacy.png) }
+.red.legacy { background-image: url(images/red_legacy.png) }
+.yellow.legacy { background-image: url(images/yellow_legacy.png) }
+.blue.legacy_40 { background-image: url(images/blue_legacy_40.png) }
+.green.legacy_40 { background-image: url(images/green_legacy_40.png) }
+.red.legacy_40 { background-image: url(images/red_legacy_40.png) }
+.yellow.legacy_40 { background-image: url(images/yellow_legacy_40.png) }
+.blue.general { background-image: url(images/blue_general.png) }
+.green.general { background-image: url(images/green_general.png) }
+.red.general { background-image: url(images/red_general.png) }
+.yellow.general { background-image: url(images/yellow_general.png) }
+.blue.emperor_turns { background-image: url(images/blue_emperor_turns.png) }
+.green.emperor_turns { background-image: url(images/green_emperor_turns.png) }
+.red.emperor_turns { background-image: url(images/red_emperor_turns.png) }
+.yellow.emperor_turns { background-image: url(images/yellow_emperor_turns.png) }
+
+.castra { background-image: url(images/castra.svg) }
+.quaestor { background-image: url(images/quaestor.svg) }
+.mob { background-image: url(images/mob.svg) }
+.mob_x2 { background-image: url(images/mob_x2.svg) }
+.blue.seat_of_power { background-image: url(images/blue_seat_of_power.svg) }
+.green.seat_of_power { background-image: url(images/green_seat_of_power.svg) }
+.red.seat_of_power { background-image: url(images/red_seat_of_power.svg) }
+.yellow.seat_of_power { background-image: url(images/yellow_seat_of_power.svg) }
+.blue.breakaway { background-image: url(images/blue_breakaway.svg) }
+.green.breakaway { background-image: url(images/green_breakaway.svg) }
+.red.breakaway { background-image: url(images/red_breakaway.svg) }
+.yellow.breakaway { background-image: url(images/yellow_breakaway.svg) }
+
+/* CARDS */
+
+.card {
+ width: 250px;
+ height: 350px;
+ background-size: 250px 350px;
+ background-repeat: no-repeat;
+ border-radius: 16px;
+ box-shadow: 0 0 0 1px #444, 0 0 4px #000;
+}
+
+.card.event_back{background-image:url(cards.1x/event_back.jpg)}
+.card.event_1{background-image:url(cards.1x/event_01.jpg)}
+.card.event_2{background-image:url(cards.1x/event_02.jpg)}
+.card.event_3{background-image:url(cards.1x/event_03.jpg)}
+.card.event_4{background-image:url(cards.1x/event_04.jpg)}
+.card.event_5{background-image:url(cards.1x/event_05.jpg)}
+.card.event_6{background-image:url(cards.1x/event_06.jpg)}
+.card.event_7{background-image:url(cards.1x/event_07.jpg)}
+.card.event_8{background-image:url(cards.1x/event_08.jpg)}
+.card.event_9{background-image:url(cards.1x/event_09.jpg)}
+.card.event_10{background-image:url(cards.1x/event_10.jpg)}
+.card.event_11{background-image:url(cards.1x/event_11.jpg)}
+.card.event_12{background-image:url(cards.1x/event_12.jpg)}
+.card.event_13{background-image:url(cards.1x/event_13.jpg)}
+.card.event_14{background-image:url(cards.1x/event_14.jpg)}
+.card.event_15{background-image:url(cards.1x/event_15.jpg)}
+.card.influence_back{background-image:url(cards.1x/influence_back.jpg)}
+.card.influence_m1{background-image:url(cards.1x/influence_m1.jpg)}
+.card.influence_m2{background-image:url(cards.1x/influence_m2.jpg)}
+.card.influence_m3{background-image:url(cards.1x/influence_m3.jpg)}
+.card.influence_m4{background-image:url(cards.1x/influence_m4.jpg)}
+.card.influence_p1{background-image:url(cards.1x/influence_p1.jpg)}
+.card.influence_p2{background-image:url(cards.1x/influence_p2.jpg)}
+.card.influence_p3{background-image:url(cards.1x/influence_p3.jpg)}
+.card.influence_p4{background-image:url(cards.1x/influence_p4.jpg)}
+.card.influence_s1{background-image:url(cards.1x/influence_s1.jpg)}
+.card.influence_s2{background-image:url(cards.1x/influence_s2.jpg)}
+.card.influence_s3{background-image:url(cards.1x/influence_s3.jpg)}
+.card.influence_s4{background-image:url(cards.1x/influence_s4.jpg)}
+
+@media (min-resolution:97dpi) {
+.card.event_back{background-image:url(cards.2x/event_back.jpg)}
+.card.event_1{background-image:url(cards.2x/event_01.jpg)}
+.card.event_2{background-image:url(cards.2x/event_02.jpg)}
+.card.event_3{background-image:url(cards.2x/event_03.jpg)}
+.card.event_4{background-image:url(cards.2x/event_04.jpg)}
+.card.event_5{background-image:url(cards.2x/event_05.jpg)}
+.card.event_6{background-image:url(cards.2x/event_06.jpg)}
+.card.event_7{background-image:url(cards.2x/event_07.jpg)}
+.card.event_8{background-image:url(cards.2x/event_08.jpg)}
+.card.event_9{background-image:url(cards.2x/event_09.jpg)}
+.card.event_10{background-image:url(cards.2x/event_10.jpg)}
+.card.event_11{background-image:url(cards.2x/event_11.jpg)}
+.card.event_12{background-image:url(cards.2x/event_12.jpg)}
+.card.event_13{background-image:url(cards.2x/event_13.jpg)}
+.card.event_14{background-image:url(cards.2x/event_14.jpg)}
+.card.event_15{background-image:url(cards.2x/event_15.jpg)}
+.card.influence_back{background-image:url(cards.2x/influence_back.jpg)}
+.card.influence_m1{background-image:url(cards.2x/influence_m1.jpg)}
+.card.influence_m2{background-image:url(cards.2x/influence_m2.jpg)}
+.card.influence_m3{background-image:url(cards.2x/influence_m3.jpg)}
+.card.influence_m4{background-image:url(cards.2x/influence_m4.jpg)}
+.card.influence_p1{background-image:url(cards.2x/influence_p1.jpg)}
+.card.influence_p2{background-image:url(cards.2x/influence_p2.jpg)}
+.card.influence_p3{background-image:url(cards.2x/influence_p3.jpg)}
+.card.influence_p4{background-image:url(cards.2x/influence_p4.jpg)}
+.card.influence_s1{background-image:url(cards.2x/influence_s1.jpg)}
+.card.influence_s2{background-image:url(cards.2x/influence_s2.jpg)}
+.card.influence_s3{background-image:url(cards.2x/influence_s3.jpg)}
+.card.influence_s4{background-image:url(cards.2x/influence_s4.jpg)}
+}
+
+.influence_m1 + .influence_m1 { margin-left: -180px; }
+.influence_p1 + .influence_p1 { margin-left: -180px; }
+.influence_s1 + .influence_s1 { margin-left: -180px; }
+.influence_m2 + .influence_m2 { margin-left: -180px; }
+.influence_p2 + .influence_p2 { margin-left: -180px; }
+.influence_s2 + .influence_s2 { margin-left: -180px; }
+.influence_m3 + .influence_m3 { margin-left: -180px; }
+.influence_p3 + .influence_p3 { margin-left: -180px; }
+.influence_s3 + .influence_s3 { margin-left: -180px; }
+.influence_m4 + .influence_m4 { margin-left: -180px; }
+.influence_p4 + .influence_p4 { margin-left: -180px; }
+.influence_s4 + .influence_s4 { margin-left: -180px; }
+
+/* PANELS */
+
+.panel {
+ min-width: 1368px;
+ max-width: 1636px;
+ margin: 12px auto 36px auto;
+ background-color: #555;
+}
+
+.panel_header {
+ background-color: #444;
+ color: white;
+ user-select: none;
+ font-weight: bold;
+ text-align: center;
+ padding: 3px 1em;
+}
+
+.panel_body {
+ display: flex;
+ justify-content: start;
+ flex-wrap: wrap;
+ padding: 18px;
+ gap: 18px;
+}
+
+#action_body {
+ display: block;
+}
+
+.action_row {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 8px;
+}
+
diff --git a/play.html b/play.html
new file mode 100644
index 0000000..fabd597
--- /dev/null
+++ b/play.html
@@ -0,0 +1,354 @@
+<!DOCTYPE html>
+<!-- vim:set nowrap: -->
+<html lang="en">
+<head>
+<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
+<meta charset="utf-8">
+<title>TIME OF CRISIS</title>
+<link rel="icon" href="favicon.png">
+<link rel="stylesheet" href="/fonts/fonts.css">
+<link rel="stylesheet" href="/common/play.css">
+<link rel="stylesheet" href="play.css">
+<script defer src="/common/play.js"></script>
+<script defer src="play.js"></script>
+</head>
+<body>
+
+<header>
+ <div id="toolbar">
+ <div class="menu">
+ <div class="menu_title"><img src="/images/cog.svg"></div>
+ <div class="menu_popup">
+ <a class="menu_item" target="_blanK" href="/time-of-crisis/info/rules.html">Rules of Play</a>
+ <a class="menu_item" target="_blanK" href="/time-of-crisis/info/expansion.html">Expansion Rules</a>
+ <a class="menu_item" target="_blanK" href="/time-of-crisis/info/cards.html">Card Gallery</a>
+ <div class="debug menu_separator"></div>
+ <div class="debug menu_item" onclick="send_save()">&#x1F41E; Save</div>
+ <div class="debug menu_item" onclick="send_restore()">&#x1F41E; Restore</div>
+ <div class="debug menu_item" onclick="send_restart('Standard')">&#x26a0; Restart</div>
+ </div>
+ </div>
+ <div class="icon_button" onclick="toggle_pieces()"><img src="/images/earth-africa-europe.svg"></div>
+ <div class="icon_button" onclick="toggle_log()"><img src="/images/scroll-quill.svg"></div>
+ </div>
+ <div id="prompt"></div>
+ <div id="actions"></div>
+</header>
+
+<aside>
+ <div id="roles">
+ <div class="role" id="role_Red"><div class="role_name">Red<div class="role_vp"></div><div class="role_user">-</div></div></div>
+ <div class="role" id="role_Blue"><div class="role_name">Blue<div class="role_vp"></div><div class="role_user">-</div></div></div>
+ <div class="role" id="role_Yellow"><div class="role_name">Yellow<div class="role_vp"></div><div class="role_user">-</div></div></div>
+ <div class="role" id="role_Green"><div class="role_name">Green<div class="role_vp"></div><div class="role_user">-</div></div></div>
+ </div>
+ <div id="log"></div>
+</aside>
+
+<main>
+
+<div id="mapwrap">
+<div id="map">
+<div id="crisis_table" class="p4"></div>
+
+<div style="position:relative;display:flex;flex-wrap:wrap;gap:8px;top:200px;left:600px;width:1000px;">
+<div class="amphitheater"></div>
+<div class="basilica"></div>
+<div class="limes"></div>
+<div class="militia"></div>
+<div class="alamanni"></div>
+<div class="alamanni inactive"></div>
+<div class="franks"></div>
+<div class="franks inactive"></div>
+<div class="goths"></div>
+<div class="goths inactive"></div>
+<div class="nomads"></div>
+<div class="nomads inactive"></div>
+<div class="sassanids"></div>
+<div class="sassanids inactive"></div>
+<div class="goths" id="cniva"></div>
+<div class="sassanids" id="ardashir"></div>
+<div class="sassanids" id="shapur"></div>
+<div class="rival" id="postumus"></div>
+<div class="rival" id="priest_king"></div>
+<div class="rival" id="zenobia"></div>
+<div class="red governor"></div>
+<div class="blue governor"></div>
+<div class="yellow governor"></div>
+<div class="green governor"></div>
+<div class="red general"></div>
+<div class="blue general"></div>
+<div class="yellow general"></div>
+<div class="green general"></div>
+<div class="red emperor_turns"></div>
+<div class="blue emperor_turns"></div>
+<div class="yellow emperor_turns"></div>
+<div class="green emperor_turns"></div>
+<div class="red legacy"></div>
+<div class="blue legacy"></div>
+<div class="yellow legacy"></div>
+<div class="green legacy"></div>
+<div class="red legacy_40"></div>
+<div class="blue legacy_40"></div>
+<div class="yellow legacy_40"></div>
+<div class="green legacy_40"></div>
+<div class="red seat_of_power"></div>
+<div class="blue seat_of_power"></div>
+<div class="yellow seat_of_power"></div>
+<div class="green seat_of_power"></div>
+<div class="red breakaway"></div>
+<div class="blue breakaway"></div>
+<div class="yellow breakaway"></div>
+<div class="green breakaway"></div>
+<div class="castra"></div>
+<div class="quaestor"></div>
+<div class="mob"></div>
+<div class="mob_x2"></div>
+<div class="legion" id="legion_1"></div>
+<div class="legion" id="legion_2"></div>
+<div class="legion" id="legion_3"></div>
+<div class="legion" id="legion_4"></div>
+<div class="legion" id="legion_5"></div>
+<div class="legion" id="legion_6"></div>
+<div class="legion" id="legion_7"></div>
+<div class="legion" id="legion_8"></div>
+<div class="legion" id="legion_9"></div>
+<div class="legion" id="legion_10"></div>
+<div class="legion" id="legion_11"></div>
+<div class="legion" id="legion_12"></div>
+<div class="legion" id="legion_13"></div>
+<div class="legion" id="legion_14"></div>
+<div class="legion" id="legion_15"></div>
+<div class="legion" id="legion_16"></div>
+<div class="legion" id="legion_17"></div>
+<div class="legion" id="legion_18"></div>
+<div class="legion" id="legion_19"></div>
+<div class="legion" id="legion_20"></div>
+<div class="legion" id="legion_21"></div>
+<div class="legion" id="legion_22"></div>
+<div class="legion" id="legion_23"></div>
+<div class="legion" id="legion_24"></div>
+<div class="legion" id="legion_25"></div>
+<div class="legion" id="legion_26"></div>
+<div class="legion" id="legion_27"></div>
+<div class="legion" id="legion_28"></div>
+<div class="legion" id="legion_29"></div>
+<div class="legion" id="legion_30"></div>
+<div class="legion" id="legion_31"></div>
+<div class="legion" id="legion_32"></div>
+<div class="legion reduced" id="legion_33"></div>
+
+</div>
+
+<div id="pieces">
+
+<div id="red_emperor_turns" class="red emperor_turns" style="top:10px;left:47px;"></div>
+<div id="blue_emperor_turns" class="blue emperor_turns" style="top:30px;left:41px;"></div>
+<div id="green_emperor_turns" class="green emperor_turns" style="top:50px;left:38px;"></div>
+<div id="yellow_emperor_turns" class="yellow emperor_turns" style="top:70px;left:35px;"></div>
+<div id="red_legacy" class="red legacy" style="top:12px;left:50px;"></div>
+<div id="blue_legacy" class="blue legacy" style="top:32px;left:47px;"></div>
+<div id="green_legacy" class="green legacy" style="top:52px;left:44px;"></div>
+<div id="yellow_legacy" class="yellow legacy" style="top:72px;left:41px;"></div>
+
+<div id=""Aegyptus_Support" class="neutral governor" style="left:1700px;top:1468px"></div>
+<div id=""Africa_Support" class="neutral governor" style="left:647px;top:1290px"></div>
+<div id=""Asia_Support" class="neutral governor" style="left:1679px;top:1000px"></div>
+<div id=""Britannia_Support" class="neutral governor" style="left:231px;top:260px"></div>
+<div id=""Galatia_Support" class="neutral governor" style="left:1954px;top:931px"></div>
+<div id=""Gallia_Support" class="neutral governor" style="left:460px;top:507px"></div>
+<div id=""Hispania_Support" class="neutral governor" style="left:154px;top:980px"></div>
+<div id=""Italia_Support" class="neutral governor" style="left:1028px;top:835px"></div>
+<div id=""Macedonia_Support" class="neutral governor" style="left:1384px;top:936px"></div>
+<div id=""Pannonia_Support" class="neutral governor" style="left:1154px;top:626px"></div>
+<div id=""Syria_Support" class="neutral governor" style="left:2034px;top:1280px"></div>
+<div id=""Thracia_Support" class="neutral governor" style="left:1502px;top:720px"></div>
+
+<div id="Aegyptus_NPG" class="no_place_governor" style="left:1793px;top:1380px"></div>
+<div id="Africa_NPG" class="no_place_governor" style="left:741px;top:1204px"></div>
+<div id="Asia_NPG" class="no_place_governor" style="left:1790px;top:908px"></div>
+<div id="Britannia_NPG" class="no_place_governor" style="left:325px;top:177px"></div>
+<div id="Galatia_NPG" class="no_place_governor" style="left:2048px;top:842px"></div>
+<div id="Gallia_NPG" class="no_place_governor" style="left:554px;top:418px"></div>
+<div id="Hispania_NPG" class="no_place_governor" style="left:249px;top:892px"></div>
+<div id="Italia_NPG" class="no_place_governor" style="left:1038px;top:743px"></div>
+<div id="Macedonia_NPG" class="no_place_governor" style="left:1477px;top:850px"></div>
+<div id="Pannonia_NPG" class="no_place_governor" style="left:1214px;top:536px"></div>
+<div id="Syria_NPG" class="no_place_governor" style="left:2174px;top:1193px"></div>
+<div id="Thracia_NPG" class="no_place_governor" style="left:1594px;top:631px"></div>
+
+<!--
+<div id="Aegyptus_Militia" class="militia" style="left:1793px;top:1380px"></div>
+<div id="Africa_Militia" class="militia" style="left:741px;top:1204px"></div>
+<div id="Asia_Militia" class="militia" style="left:1790px;top:908px"></div>
+<div id="Britannia_Militia" class="militia" style="left:325px;top:177px"></div>
+<div id="Galatia_Militia" class="militia" style="left:2048px;top:842px"></div>
+<div id="Gallia_Militia" class="militia" style="left:554px;top:418px"></div>
+<div id="Hispania_Militia" class="militia" style="left:249px;top:892px"></div>
+<div id="Italia_Militia" class="militia" style="left:1038px;top:743px"></div>
+<div id="Macedonia_Militia" class="militia" style="left:1477px;top:850px"></div>
+<div id="Pannonia_Militia" class="militia" style="left:1214px;top:536px"></div>
+<div id="Syria_Militia" class="militia" style="left:2174px;top:1193px"></div>
+<div id="Thracia_Militia" class="militia" style="left:1594px;top:631px"></div>
+-->
+
+<div class="neutral governor" style="top:259px;left:230px;"></div>
+<div class="quaestor" style="top:220px;left:258px;"></div>
+<div class="militia" style="top:192px;left:320px;"></div>
+<div class="blue general" style="top:172px;left:340px;"></div>
+<div class="castra" style="top:172px;left:344px;"></div>
+<div class="franks inactive" style="top:172px;left:420px;"></div>
+<div class="limes" style="top:320px;left:260px;"></div>
+
+<div class="legion hide" id="legion_1"></div>
+<div class="legion hide" id="legion_2"></div>
+<div class="legion hide" id="legion_3"></div>
+<div class="legion hide" id="legion_4"></div>
+<div class="legion hide" id="legion_5"></div>
+<div class="legion hide" id="legion_6"></div>
+<div class="legion hide" id="legion_7"></div>
+<div class="legion hide" id="legion_8"></div>
+<div class="legion hide" id="legion_9"></div>
+<div class="legion hide" id="legion_10"></div>
+<div class="legion hide" id="legion_11"></div>
+<div class="legion hide" id="legion_12"></div>
+<div class="legion hide" id="legion_13"></div>
+<div class="legion hide" id="legion_14"></div>
+<div class="legion hide" id="legion_15"></div>
+<div class="legion hide" id="legion_16"></div>
+<div class="legion hide" id="legion_17"></div>
+<div class="legion hide" id="legion_18"></div>
+<div class="legion hide" id="legion_19"></div>
+<div class="legion hide" id="legion_20"></div>
+<div class="legion hide" id="legion_21"></div>
+<div class="legion hide" id="legion_22"></div>
+<div class="legion hide" id="legion_23"></div>
+<div class="legion hide" id="legion_24"></div>
+<div class="legion hide" id="legion_25"></div>
+<div class="legion hide" id="legion_26"></div>
+<div class="legion hide" id="legion_27"></div>
+<div class="legion hide" id="legion_28"></div>
+<div class="legion hide" id="legion_29"></div>
+<div class="legion hide" id="legion_30"></div>
+<div class="legion hide" id="legion_31"></div>
+<div class="legion hide" id="legion_32"></div>
+<div class="legion hide" id="legion_33"></div>
+
+</div>
+
+</div>
+</div>
+</div>
+
+<div id="action_panel" class="panel">
+<div id="action_header" class="panel_header">Actions</div>
+<div id="action_body" class="panel_body">
+
+<div class="action_row">
+<button>Recruit General 1</button>
+<button>Recruit General 2</button>
+<button>Recruit General 3</button>
+<button>Recruit General 4</button>
+<button>Recruit General 5</button>
+</div>
+<div class="action_row">
+<button>Add Legion to Army</button>
+<button>Create Army</button>
+<button>Train Legions</button>
+<button>Move Army</button>
+<button>Initiate Battle</button>
+<button>Disperse Mob</button>
+</div>
+
+<div class="action_row">
+<button>Recruit Governor 1</button>
+<button>Recruit Governor 2</button>
+<button>Recruit Governor 3</button>
+<button>Recruit Governor 4</button>
+<button>Recruit Governor 5</button>
+</div>
+<div class="action_row">
+<button>Place Governor</button>
+<button>Recall Governor</button>
+</div>
+
+<div class="action_row">
+<button>Increase Support Level</button>
+<button>Place Militia</button>
+<button>Hold Games</button>
+<button id="build_limes">Build Limes</button>
+<button id="build_amphitheater">Build Amphitheater</button>
+<button id="build_basilica">Build Basilica</button>
+</div>
+
+<div class="action_row">
+
+<button>Castra</button>
+<button>Flanking Maneuver</button>
+<button>Praetorian Guard</button>
+<button>Tribute</button>
+<button>Foederati</button>
+<button>Damnatio Memoriae</button>
+<button>Quaestor</button>
+<button>Mob</button>
+<button>Pretender</button>
+</div>
+
+</div>
+</div>
+
+<div id="played_panel" class="panel">
+<div id="played_header" class="panel_header">Played / Events</div>
+<div id="played" class="panel_body">
+ <div class="card event_back"></div>
+</div>
+</div>
+
+<div id="hand_panel" class="panel">
+<div id="hand_header" class="panel_header">Hand</div>
+<div id="hand" class="panel_body">
+ <div class="card influence_m1"></div>
+ <div class="card influence_m1"></div>
+ <div class="card influence_m1"></div>
+ <div class="card influence_s1"></div>
+ <div class="card influence_s1"></div>
+</div>
+</div>
+
+<div id="draw_panel" class="panel">
+<div id="draw_header" class="panel_header">Draw</div>
+<div id="draw" class="panel_body">
+ <div class="card influence_s1"></div>
+ <div class="card influence_p1"></div>
+ <div class="card influence_p1"></div>
+ <div class="card influence_p1"></div>
+</div>
+</div>
+
+<div id="discard_panel" class="panel">
+<div id="discard_header" class="panel_header">Discard</div>
+<div id="discard" class="panel_body">
+ <div class="card influence_back"></div>
+</div>
+</div>
+
+<div id="market_panel" class="panel">
+<div id="market_header" class="panel_header">Market</div>
+<div id="market" class="panel_body">
+ <div class="card influence_m2"></div>
+ <div class="card influence_s2"></div>
+ <div class="card influence_p2"></div>
+ <div class="card influence_m3"></div>
+ <div class="card influence_s3"></div>
+ <div class="card influence_p3"></div>
+ <div class="card influence_m4"></div>
+ <div class="card influence_s4"></div>
+ <div class="card influence_p4"></div>
+</div>
+</div>
+
+</main>
+
+<footer id="status"></footer>
+
+</body>
diff --git a/play.js b/play.js
new file mode 100644
index 0000000..eeeab9d
--- /dev/null
+++ b/play.js
@@ -0,0 +1,2 @@
+
+scroll_with_middle_mouse("main")
diff --git a/rules.js b/rules.js
new file mode 100644
index 0000000..52bdd63
--- /dev/null
+++ b/rules.js
@@ -0,0 +1,116 @@
+"use strict"
+
+const P1 = "Red"
+const P2 = "Blue"
+const P3 = "Yellow"
+const P4 = "Green"
+
+const player_names = [ P1, P2, P3, P4 ]
+
+const player_names_by_scenario = {
+ "4P": [ P1, P2, P3, P4 ],
+ "3P": [ P1, P2, P3 ],
+ "2P": [ P1, P2 ],
+}
+
+const scenario_player_count = {
+ "4P": 4,
+ "3P": 3,
+ "2P": 2,
+}
+
+const player_index = {
+ [P1]: 0,
+ [P2]: 1,
+ [P3]: 2,
+ [P4]: 3,
+ "Observer": -1,
+}
+
+var game
+var view
+
+const states = {}
+
+exports.scenarios = [ "Standard" ]
+
+exports.roles = function (scenario, options) {
+ if (options.players == 2)
+ return [ P1, P2 ]
+ if (options.players == 3)
+ return [ P1, P2, P3 ]
+ return [ P1, P2, P3, P4 ]
+}
+
+exports.setup = function (seed, scenario, options) {
+ let players = options.players || 4
+ game = {
+ seed: seed,
+ log: [],
+ undo: [],
+ players: players,
+ active: 0,
+ state: "none",
+ }
+ return save_game()
+}
+
+function load_game(state) {
+ game = state
+ game.active = player_index[game.active]
+}
+
+function save_game() {
+ game.active = player_names[game.active]
+ return game
+}
+
+exports.action = function (state, player, action, arg) {
+ load_game(state)
+ let S = states[game.state]
+ if (action in S) {
+ S[action](arg)
+ } else {
+ if (action === "undo" && game.undo && game.undo.length > 0)
+ pop_undo()
+ else
+ throw new Error("Invalid action: " + action)
+ }
+ return save_game()
+}
+
+exports.view = function (state, player_name) {
+ let player = player_index[player_name]
+
+ load_game(state)
+
+ view = {
+ log: game.log,
+ active: player_names[game.active],
+ prompt: null,
+ }
+
+ if (game.state === "game_over") {
+ view.prompt = game.victory
+ } else if (game.active !== player) {
+ let inactive = states[game.state].inactive || game.state
+ view.prompt = `Waiting for ${player_names[game.active]} \u2014 ${inactive}...`
+ } else {
+ view.actions = {}
+ states[game.state].prompt()
+ view.prompt = player_names[game.active] + ": " + view.prompt
+ if (game.undo && game.undo.length > 0)
+ view.actions.undo = 1
+ else
+ view.actions.undo = 0
+ }
+
+ save_game()
+ return view
+}
+
+// STATES
+
+states.none = {
+ prompt() {}
+}
diff --git a/tools/boxes.svg b/tools/boxes.svg
new file mode 100644
index 0000000..7e5f781
--- /dev/null
+++ b/tools/boxes.svg
@@ -0,0 +1,384 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="2550"
+ height="1650"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="boxes.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="640"
+ inkscape:window-height="480"
+ id="namedview6"
+ showgrid="true"
+ inkscape:zoom="1.7859922"
+ inkscape:cx="2122.3644"
+ inkscape:cy="944.53822"
+ inkscape:current-layer="svg4"
+ inkscape:document-rotation="0">
+ <inkscape:grid
+ type="xygrid"
+ id="grid14" />
+ </sodipodi:namedview>
+ <image
+ sodipodi:absref="/home/tor/src/rally/public/time-of-crisis/map75.png"
+ xlink:href="../map75.png"
+ sodipodi:insensitive="true"
+ id="image2"
+ height="1650"
+ width="2550"
+ style="display:inline;image-rendering:pixelated" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect873"
+ width="258"
+ height="52"
+ x="1502"
+ y="720"
+ ry="0.610479"
+ inkscape:label="Thracia Support" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect881"
+ width="258"
+ height="52"
+ x="2034"
+ y="1280"
+ ry="0.610479"
+ inkscape:label="Syria Support" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect871"
+ width="258"
+ height="53"
+ x="1154"
+ y="626"
+ ry="0.610479"
+ inkscape:label="Pannonia Support" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect875"
+ width="258"
+ height="53"
+ x="1384"
+ y="936"
+ ry="0.610479"
+ inkscape:label="Macedonia Support" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect887"
+ width="258"
+ height="53"
+ x="154"
+ y="980"
+ ry="0.610479"
+ inkscape:label="Hispania Support" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect869"
+ width="258"
+ height="53"
+ x="460"
+ y="507"
+ ry="0.610479"
+ inkscape:label="Gallia Support" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect879"
+ width="258"
+ height="53"
+ x="1954"
+ y="931"
+ ry="0.610479"
+ inkscape:label="Galatia Support" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect867"
+ width="258"
+ height="52"
+ x="231"
+ y="260"
+ ry="0.610479"
+ inkscape:label="Britannia Support" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect877"
+ width="258"
+ height="52"
+ x="1679"
+ y="1000"
+ ry="0.610479"
+ inkscape:label="Asia Support" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect885"
+ width="258"
+ height="53"
+ x="647"
+ y="1290"
+ ry="0.610479"
+ inkscape:label="Africa Support" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect883"
+ width="258"
+ height="53"
+ x="1700"
+ y="1468"
+ ry="0.610479"
+ inkscape:label="Aegyptus Support" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect891"
+ width="258"
+ height="52"
+ x="1054"
+ y="887"
+ ry="0.610479"
+ inkscape:label="Italia Support 2" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect889"
+ width="258"
+ height="52"
+ x="1028"
+ y="835"
+ ry="0.610479"
+ inkscape:label="Italia Support 1" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect845"
+ width="70"
+ height="70"
+ x="1594"
+ y="631"
+ ry="0.610479"
+ inkscape:label="Thracia Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect853"
+ width="70"
+ height="70"
+ x="2174"
+ y="1193"
+ ry="0.610479"
+ inkscape:label="Syria Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect843"
+ width="70"
+ height="70"
+ x="1214"
+ y="536"
+ ry="0.610479"
+ inkscape:label="Pannonia Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect859"
+ width="70"
+ height="70"
+ x="1477"
+ y="850"
+ ry="0.610479"
+ inkscape:label="Macedonia Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect861"
+ width="70"
+ height="70"
+ x="1038"
+ y="743"
+ ry="0.610479"
+ inkscape:label="Italia Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect847"
+ width="70"
+ height="70"
+ x="249"
+ y="892"
+ ry="0.610479"
+ inkscape:label="Hispania Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect841"
+ width="70"
+ height="70"
+ x="554"
+ y="418"
+ ry="0.610479"
+ inkscape:label="Gallia Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect855"
+ width="70"
+ height="70"
+ x="2048"
+ y="842"
+ ry="0.610479"
+ inkscape:label="Galatia Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect839"
+ width="70"
+ height="70"
+ x="325"
+ y="177"
+ ry="0.610479"
+ inkscape:label="Britannia Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect857"
+ width="70"
+ height="70"
+ x="1790"
+ y="908"
+ ry="0.610479"
+ inkscape:label="Asia Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect849"
+ width="70"
+ height="70"
+ x="741"
+ y="1204"
+ ry="0.610479"
+ inkscape:label="Africa Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect851"
+ width="70"
+ height="70"
+ x="1793"
+ y="1380"
+ ry="0.610479"
+ inkscape:label="Aegyptus Capital" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect902"
+ width="130"
+ height="60"
+ x="1970"
+ y="620"
+ ry="0.610479"
+ inkscape:label="Pontus Euxinus XY" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect900"
+ width="100"
+ height="60"
+ x="1770"
+ y="1170"
+ ry="0.610479"
+ inkscape:label="Mare Orientale XY" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect898"
+ width="90"
+ height="60"
+ x="720"
+ y="890"
+ ry="0.610479"
+ inkscape:label="Mare Occidentale XY" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect893"
+ width="80"
+ height="50"
+ x="130"
+ y="495"
+ ry="0.610479"
+ inkscape:label="Oceanus Atlanticus XY" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect904"
+ width="165"
+ height="25"
+ x="705"
+ y="1495"
+ ry="0.610479"
+ inkscape:label="Nomads XY" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect906"
+ width="190"
+ height="25"
+ x="2295"
+ y="980"
+ ry="0.610479"
+ inkscape:label="Sassanids XY" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect912"
+ width="130"
+ height="15"
+ x="1840"
+ y="235"
+ ry="0.610479"
+ inkscape:label="Goths XY" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect910"
+ width="195"
+ height="15"
+ x="1370"
+ y="200"
+ ry="0.610479"
+ inkscape:label="Alamanni XY" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect908"
+ width="135"
+ height="15"
+ x="900"
+ y="200"
+ ry="0.610479"
+ inkscape:label="Franks XY" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect865"
+ width="2469"
+ height="80"
+ x="40"
+ y="40"
+ ry="0.610479"
+ inkscape:label="SCORE TRACK" />
+ <rect
+ style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect863"
+ width="262"
+ height="326"
+ x="2195"
+ y="189"
+ ry="0.610479"
+ inkscape:label="CRISIS TABLE" />
+</svg>
diff --git a/tools/cards.sh b/tools/cards.sh
new file mode 100644
index 0000000..5601ad3
--- /dev/null
+++ b/tools/cards.sh
@@ -0,0 +1,8 @@
+mkdir -p cards.1x cards.2x
+for F in HIRES/render/event*.png HIRES/render/influence*
+do
+ B=$(basename $F)
+ echo SCALING $B
+ convert -colorspace RGB -gravity Center -crop 3000x4200+0+0 -resize 8.333333% -colorspace sRGB $F cards.1x/$B
+ convert -colorspace RGB -gravity Center -crop 3000x4200+0+0 -resize 16.666667% -colorspace sRGB $F cards.2x/$B
+done
diff --git a/tools/crop.sh b/tools/crop.sh
new file mode 100644
index 0000000..11ee84b
--- /dev/null
+++ b/tools/crop.sh
@@ -0,0 +1,123 @@
+#!/bin/bash
+
+mkdir -p /tmp/1x /tmp/2x /tmp/xx images
+
+HEX=800x924
+
+# - original size -
+# SQUARE=896x896
+# RECT=1488x752
+# CIRCLE=816x816
+# HEX=800x924
+
+# - trimmed 1px border -
+SQUARE=880x880
+RECT=1472x736
+CIRCLE=800x800
+
+# FILTER="-filter triangle
+# UNSHARP="-unsharp 0x1+0.5"
+
+function resize() {
+ B=$(basename $2)
+ echo $B
+ convert $2 -gravity Center -crop $1+0+0 +repage -colorspace RGB $FILTER -resize 12.5% $UNSHARP -colorspace sRGB images/$B
+}
+
+function crop_only() {
+ B=$(basename $2)
+ echo $B
+ convert tools/original/$B -gravity Center -crop $1+0+0 +repage /tmp/xx/$B
+}
+
+function resize_only() {
+ B=$(basename $1)
+ echo $B
+ convert $1 -colorspace RGB $FILTER -resize 12.5% $UNSHARP -colorspace sRGB images/$B
+}
+
+for F in tools/original/legion_full_*.png tools/original/legion_reduced_*.png
+do
+ crop_only $SQUARE $F
+done
+montage -mode concatenate -tile 9x /tmp/xx/legion_full_*.png tools/original/legion_full.png
+montage -mode concatenate -tile 9x /tmp/xx/legion_reduced_*.png tools/original/legion_reduced.png
+
+resize_only tools/original/legion_full.png
+resize_only tools/original/legion_reduced.png
+
+resize $RECT tools/original/amphitheater.png
+resize $RECT tools/original/basilica.png
+resize $RECT tools/original/limes.png
+
+resize $RECT tools/original/amphitheater_back.png
+resize $RECT tools/original/basilica_back.png
+resize $RECT tools/original/limes_back.png
+
+resize $SQUARE tools/original/alamanni_active.png
+resize $SQUARE tools/original/alamanni_inactive.png
+resize $SQUARE tools/original/ardashir.png
+resize $SQUARE tools/original/cniva.png
+resize $SQUARE tools/original/cniva_back.png
+resize $SQUARE tools/original/first_player.png
+resize $SQUARE tools/original/franks_active.png
+resize $SQUARE tools/original/franks_inactive.png
+resize $SQUARE tools/original/goths_active.png
+resize $SQUARE tools/original/goths_inactive.png
+resize $SQUARE tools/original/militia.png
+resize $SQUARE tools/original/no_place_governor.png
+resize $SQUARE tools/original/nomads_active.png
+resize $SQUARE tools/original/nomads_inactive.png
+resize $SQUARE tools/original/rival_back.png
+resize $SQUARE tools/original/rival_postumus.png
+resize $SQUARE tools/original/rival_priest_king.png
+resize $SQUARE tools/original/rival_zenobia.png
+resize $SQUARE tools/original/sassanids_active.png
+resize $SQUARE tools/original/sassanids_inactive.png
+resize $SQUARE tools/original/shapur.png
+resize $SQUARE tools/original/shapur_back.png
+resize $SQUARE tools/original/red_emperor_turns.png
+resize $SQUARE tools/original/red_general.png
+resize $SQUARE tools/original/blue_emperor_turns.png
+resize $SQUARE tools/original/blue_general.png
+resize $SQUARE tools/original/yellow_emperor_turns.png
+resize $SQUARE tools/original/yellow_general.png
+resize $SQUARE tools/original/green_emperor_turns.png
+resize $SQUARE tools/original/green_general.png
+
+resize $HEX tools/original/mob.png
+resize $HEX tools/original/mob_x2.png
+resize $HEX tools/original/castra.png
+resize $HEX tools/original/quaestor.png
+resize $HEX tools/original/red_breakaway.png
+resize $HEX tools/original/blue_breakaway.png
+resize $HEX tools/original/yellow_breakaway.png
+resize $HEX tools/original/green_breakaway.png
+resize $HEX tools/original/red_seat_of_power.png
+resize $HEX tools/original/blue_seat_of_power.png
+resize $HEX tools/original/yellow_seat_of_power.png
+resize $HEX tools/original/green_seat_of_power.png
+
+resize $CIRCLE tools/original/neutral_governor.png
+resize $CIRCLE tools/original/red_governor.png
+resize $CIRCLE tools/original/red_legacy.png
+resize $CIRCLE tools/original/red_legacy_40.png
+resize $CIRCLE tools/original/blue_governor.png
+resize $CIRCLE tools/original/blue_legacy.png
+resize $CIRCLE tools/original/blue_legacy_40.png
+resize $CIRCLE tools/original/yellow_governor.png
+resize $CIRCLE tools/original/yellow_legacy.png
+resize $CIRCLE tools/original/yellow_legacy_40.png
+resize $CIRCLE tools/original/green_governor.png
+resize $CIRCLE tools/original/green_legacy.png
+resize $CIRCLE tools/original/green_legacy_40.png
+
+resize $CIRCLE tools/original/blue_governor_emperor.png
+resize $CIRCLE tools/original/green_governor_emperor.png
+resize $CIRCLE tools/original/red_governor_emperor.png
+resize $CIRCLE tools/original/yellow_governor_emperor.png
+
+resize $SQUARE tools/original/blue_general_emperor.png
+resize $SQUARE tools/original/green_general_emperor.png
+resize $SQUARE tools/original/red_general_emperor.png
+resize $SQUARE tools/original/yellow_general_emperor.png
diff --git a/tools/genboxes.py b/tools/genboxes.py
new file mode 100644
index 0000000..650f625
--- /dev/null
+++ b/tools/genboxes.py
@@ -0,0 +1,73 @@
+mode = None
+
+list = []
+
+x = y = w = h = 0
+name = None
+
+def flush():
+ global x, y, w, h, name
+ if mode == 'rect':
+ list.append((x,y,w,h,'box',name))
+ if mode == 'circle':
+ x = cx - rx
+ y = cy - ry
+ w = rx * 2
+ h = ry * 2
+ list.append((x,y,w,h,'circle',name))
+ x = y = w = h = 0
+ name = None
+
+for line in open("tools/boxes.svg").readlines():
+ line = line.strip()
+ if line == "<rect":
+ flush()
+ mode = 'rect'
+ x = y = w = h = 0
+ elif line == "<ellipse":
+ flush()
+ mode = 'circle'
+ cx = cy = rx = ry = 0
+ if line.startswith('x="'): x = round(float(line.split('"')[1]))
+ if line.startswith('y="'): y = round(float(line.split('"')[1]))
+ if line.startswith('width="'): w = round(float(line.split('"')[1]))
+ if line.startswith('height="'): h = round(float(line.split('"')[1]))
+ if line.startswith('cx="'): cx = round(float(line.split('"')[1]))
+ if line.startswith('cy="'): cy = round(float(line.split('"')[1]))
+ if line.startswith('rx="'): rx = round(float(line.split('"')[1]))
+ if line.startswith('ry="'): ry = round(float(line.split('"')[1]))
+ if line.startswith('inkscape:label="'): name = line.split('"')[1]
+flush()
+
+def print_list():
+ print("const boxes = {")
+ for (x,y,w,h,c,name) in list:
+ print(f'"{name}": [{x},{y},{w},{h}],')
+ print("}")
+
+def print_list2():
+ print("const centers = {")
+ for (x,y,w,h,c,name) in list:
+ xc = round((x+w/2.0))
+ yc = round((y+h/2.0))
+ print(f'"{name}": [{xc},{yc}],')
+ print("}")
+
+def print_html():
+ # print('<html><style>')
+ # print('.box{position:absolute;background-color:#f008;border:2px solid blue;}')
+ # print('.circle{position:absolute;background-color:#0f08;border-radius:50%;border:2px solid blue;}')
+ # print('img{position:absolute;display:block}')
+ # print('</style>')
+ # print('<div style="position:relative;width:1275px;heigth:1650px;">')
+ # print('<img src="map75.png">')
+ for (x,y,w,h,c,name) in list:
+ x = round(x)
+ y = round(y)
+ w = round(w)
+ h = round(h)
+ print(f'<div class="{c}" style="top:{y}px;left:{x}px;width:{w}px;height:{h}px">{name}</div>')
+ # print('</div>')
+
+#print_html()
+print_list()
diff --git a/tools/gencolors.js b/tools/gencolors.js
new file mode 100644
index 0000000..fbcb36c
--- /dev/null
+++ b/tools/gencolors.js
@@ -0,0 +1,85 @@
+const { parse_hex, format_hex, lrgb_from_any, rgb_from_any, oklab_from_any } = require("../../common/colors.js")
+
+const white = "#ffffff"
+const black = "#000000"
+
+function lerp(a, b, n) {
+ return a + (b - a) * n
+}
+
+function blend(a, b, n) {
+ a = lrgb_from_any(parse_hex(a))
+ b = lrgb_from_any(parse_hex(b))
+ return format_hex({
+ mode: "lrgb",
+ r: lerp(a.r, b.r, n),
+ g: lerp(a.g, b.g, n),
+ b: lerp(a.b, b.b, n)
+ })
+}
+
+function multiply_luminance(hex, m) {
+ let oklab = oklab_from_any(parse_hex(hex))
+ oklab.l = Math.max(0, Math.min(1, oklab.l * m))
+ return format_hex(oklab)
+}
+
+function add_luminance(hex, m) {
+ let oklab = oklab_from_any(parse_hex(hex))
+ oklab.l = Math.max(0, Math.min(1, oklab.l + m))
+ return format_hex(oklab)
+}
+
+function make_3d_colors(base) {
+ return [
+ base,
+ multiply_luminance(base, 0.9),
+ multiply_luminance(base, 0.8),
+ multiply_luminance(base, 0.7),
+ multiply_luminance(base, 0.4)
+ ]
+}
+
+function make_2d_colors(base) {
+ return [
+ base,
+ multiply_luminance(base, 1.2),
+ multiply_luminance(base, 0.8),
+ multiply_luminance(base, 0.4)
+ ]
+}
+
+function make_2d_colors_add(base) {
+ return [
+ base,
+ add_luminance(base, 0.2),
+ add_luminance(base, -0.2),
+ add_luminance(base, -0.5),
+ ]
+}
+
+function print(x) {
+ console.log(x)
+}
+
+function gencss(color, sel) {
+ let [ bg, hi, lo, sh ] = make_2d_colors(color)
+ print(`${sel} { border-color: ${hi} ${lo} ${lo} ${hi}; box-shadow: 0 0 0 1px ${sh}, 1px 2px 4px #0008; }`)
+}
+
+gencss("#efebea", ".amphitheater, .basilica, .limes")
+gencss("#ed1b2f", ".red")
+gencss("#a0caec", ".blue")
+gencss("#ffe175", ".yellow")
+gencss("#80b563", ".green")
+gencss("#c3bc8e", ".alamanni")
+gencss("#9cb4be", ".franks")
+gencss("#3a9cd6", ".goths")
+gencss("#f99d1c", ".nomads")
+gencss("#8e5ca6", ".sassanids")
+gencss("#b8b996", ".rival")
+gencss("#e3dedc", ".neutral")
+gencss("#f0f0f0", ".militia")
+gencss("#f0f0f0", ".legion")
+gencss("#6e6e6e", ".no_place_governor")
+
diff --git a/tools/genhex.js b/tools/genhex.js
new file mode 100644
index 0000000..c0e8812
--- /dev/null
+++ b/tools/genhex.js
@@ -0,0 +1,63 @@
+// hexagonal counters with embedded image and outline
+
+const fs = require('fs')
+
+function print_hex(output, input, hi, lo, bd) {
+ let image = fs.readFileSync(input).toString('base64')
+ let svg = []
+
+ let img_w = 50
+ let img_h = 58 // 57.735
+
+ let svg_w = img_w + 4
+ let svg_h = img_h + 6
+
+ svg.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${svg_w}" height="${svg_h}">`)
+
+ let iw = img_w / 2
+ let ih = iw / Math.sqrt(3)
+ let iy = (svg_h - ih * 4) / 2
+
+ let ow = img_w / 2 + 2
+ let oh = ow / Math.sqrt(3)
+ let oy = (svg_h - oh * 4) / 2
+
+ svg.push('<clipPath id="ic">')
+ svg.push(`<path d="M 1 ${iy} m 0 ${3*ih} v -${2*ih} l ${iw} -${ih} l ${iw} ${ih} v ${2*ih} l -${iw} ${ih} z"/>`)
+ svg.push('</clipPath>')
+
+ svg.push('<clipPath id="oc">')
+ svg.push(`<path d="M 0 ${oy} m 0 ${oh*3} v -${oh*2} l ${ow} -${oh} l ${ow} ${oh} z"/>`)
+ svg.push('</clipPath>')
+
+ svg.push(`<path fill="${bd}" d="M 0 ${oy} m 0 ${3*oh} v -${2*oh} l ${ow} -${oh} l ${ow} ${oh} v ${2*oh} l -${ow} ${oh} z"/>`)
+
+ svg.push(`<image x="2" y="3" width="${img_w}" height="${img_h}" clip-path="url(#ic)" href="data:image/png;base64,${image}"/>`)
+
+ svg.push(`<path fill="none" stroke="${lo}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"`)
+ svg.push(` d="M 2 ${iy} m ${2*iw} ${ih} v ${ih*2} l -${iw} ${ih} l -${iw} -${ih}"/>`)
+ svg.push(`<path fill="none" stroke="${hi}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" clip-path="url(#oc)"`)
+ svg.push(` d="M 2 ${iy} m 0 ${ih*3} v -${ih*2} l ${iw} -${ih} l ${iw} ${ih}"/>`)
+
+ svg.push('</svg>')
+
+ fs.writeFileSync(output, svg.join("\n") + "\n")
+}
+
+print_hex("images/castra.svg", "images/castra.png", "#ffffff", "#b2b2b2", "#434343")
+print_hex("images/quaestor.svg", "images/quaestor.png", "#ffffff", "#b2b2b2", "#434343")
+
+print_hex("images/mob.svg", "images/mob.png", "#eaebc7", "#888968", "#323214")
+print_hex("images/mob_x2.svg", "images/mob_x2.png", "#eaebc7", "#888968", "#323214")
+
+print_hex("images/blue_breakaway.svg", "images/blue_breakaway.png", "#d5ffff", "#6e96b6", "#113854")
+print_hex("images/blue_seat_of_power.svg", "images/blue_seat_of_power.png", "#d5ffff", "#6e96b6", "#113854")
+
+print_hex("images/green_breakaway.svg", "images/green_breakaway.png", "#ace48f", "#568837", "#033600")
+print_hex("images/green_seat_of_power.svg", "images/green_seat_of_power.png", "#ace48f", "#568837", "#033600")
+
+print_hex("images/red_breakaway.svg", "images/red_breakaway.png", "#ff5455", "#c00000", "#680000")
+print_hex("images/red_seat_of_power.svg", "images/red_seat_of_power.png", "#ff5455", "#c00000", "#680000")
+
+print_hex("images/yellow_breakaway.svg", "images/yellow_breakaway.png", "#fffe92", "#c3a634", "#553a00")
+print_hex("images/yellow_seat_of_power.svg", "images/yellow_seat_of_power.png", "#fffe92", "#c3a634", "#553a00")
diff --git a/tools/map.sh b/tools/map.sh
new file mode 100644
index 0000000..37b4eef
--- /dev/null
+++ b/tools/map.sh
@@ -0,0 +1,7 @@
+pngtopnm map600.png | pnmcut -left 300 -top 300 -width 20400 -height 13200 > map600.ppm
+cat map600.ppm | pnmdepth 65535 | pnmgamma -srgbramp -ungamma | pnmscale -reduce 4 | pnmgamma -srgbramp | pnmdepth 255 > map150.ppm
+cat map600.ppm | pnmdepth 65535 | pnmgamma -srgbramp -ungamma | pnmscale -reduce 8 | pnmgamma -srgbramp | pnmdepth 255 > map75.ppm
+cat overlay_2p_600.ppm | pnmdepth 65535 | pnmgamma -srgbramp -ungamma | pnmscale -reduce 4 | pnmgamma -srgbramp | pnmdepth 255 > overlay_2p_150.ppm
+cat overlay_2p_600.ppm | pnmdepth 65535 | pnmgamma -srgbramp -ungamma | pnmscale -reduce 8 | pnmgamma -srgbramp | pnmdepth 255 > overlay_2p_75.ppm
+cat overlay_3p_600.ppm | pnmdepth 65535 | pnmgamma -srgbramp -ungamma | pnmscale -reduce 4 | pnmgamma -srgbramp | pnmdepth 255 > overlay_3p_150.ppm
+cat overlay_3p_600.ppm | pnmdepth 65535 | pnmgamma -srgbramp -ungamma | pnmscale -reduce 8 | pnmgamma -srgbramp | pnmdepth 255 > overlay_3p_75.ppm
diff --git a/tools/map.svg b/tools/map.svg
new file mode 100644
index 0000000..c278b18
--- /dev/null
+++ b/tools/map.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2550" height="1650">
+<image xlink:href="../map75.png" style="display:inline;image-rendering:pixelated" width="2550" height="1650"/>
+</svg>
diff --git a/tools/render.sh b/tools/render.sh
new file mode 100644
index 0000000..f18ba5d
--- /dev/null
+++ b/tools/render.sh
@@ -0,0 +1,36 @@
+mkdir -p HIRES/render
+gs -r1200 -sDEVICE=png16m -o HIRES/render/counters_1B.png HIRES/CLEAN/TOC-Counters-1B-nf.p1.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/counters_1F.png HIRES/CLEAN/TOC-Counters-1F-nf.p1.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/counters_2B.png HIRES/CLEAN/TOC-Counters-2B-nf.p1.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/counters_2F.png HIRES/CLEAN/TOC-Counters-2F-nf.p1.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/counters_3B.png HIRES/CLEAN/TOC-Counters-3B-nf.p1.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/counters_3F.png HIRES/CLEAN/TOC-Counters-3F-nf.p1.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_back.png HIRES/CLEAN/TOC_EVENT_BACK.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_01.png HIRES/CLEAN/TOC_EVENTcard-01-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_02.png HIRES/CLEAN/TOC_EVENTcard-02-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_03.png HIRES/CLEAN/TOC_EVENTcard-03-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_04.png HIRES/CLEAN/TOC_EVENTcard-04-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_05.png HIRES/CLEAN/TOC_EVENTcard-05-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_06.png HIRES/CLEAN/TOC_EVENTcard-06-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_07.png HIRES/CLEAN/TOC_EVENTcard-07-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_08.png HIRES/CLEAN/TOC_EVENTcard-08-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_09.png HIRES/CLEAN/TOC_EVENTcard-09-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_10.png HIRES/CLEAN/TOC_EVENTcard-10-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_11.png HIRES/CLEAN/TOC_EVENTcard-11-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_12.png HIRES/CLEAN/TOC_EVENTcard-12-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_13.png HIRES/CLEAN/TOC_EVENTcard-13-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_14.png HIRES/CLEAN/TOC_EVENTcard-14-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/event_15.png HIRES/CLEAN/TOC_EVENTcard-15-nf.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_back.png HIRES/CLEAN/TOC_INFLUENCE_BACK.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_m1.png HIRES/CLEAN/TOC_INFcards-M1nf-x12.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_m2.png HIRES/CLEAN/TOC_INFcards-M2nf-x9.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_m3.png HIRES/CLEAN/TOC_INFcards-M3nf-x8.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_m4.png HIRES/CLEAN/TOC_INFcards-M4nf-x6.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_p1.png HIRES/CLEAN/TOC_INFcards-P1nf-x12.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_p2.png HIRES/CLEAN/TOC_INFcards-P2nf-x9.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_p3.png HIRES/CLEAN/TOC_INFcards-P3nf-x8.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_p4.png HIRES/CLEAN/TOC_INFcards-P4nf-x6.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_s1.png HIRES/CLEAN/TOC_INFcards-S1nf-x12.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_s2.png HIRES/CLEAN/TOC_INFcards-S2nf-x9.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_s3.png HIRES/CLEAN/TOC_INFcards-S3nf-x8.pdf
+gs -r1200 -sDEVICE=png16m -o HIRES/render/influence_s4.png HIRES/CLEAN/TOC_INFcards-S4nf-x6.pdf