summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.css1253
-rw-r--r--play.html182
-rw-r--r--play.js1943
3 files changed, 3378 insertions, 0 deletions
diff --git a/play.css b/play.css
new file mode 100644
index 0000000..e9dab9f
--- /dev/null
+++ b/play.css
@@ -0,0 +1,1253 @@
+main { background-color: dimgray; }
+#roles { background-color: gray; }
+header { background-color: silver; }
+header.your_turn { background-color: orange; }
+#role_Teutons .role_name { background-color: hsl(210,30%,80%); }
+#role_Russians .role_name { background-color: hsl(35,40%,80%); }
+#turn_info { background-color: gainsboro; }
+.role_held { float: right; }
+
+#log { background-color: whitesmoke; }
+#log .h1 { font-weight: bold; padding-top:2px; padding-bottom:2px; text-align: center; }
+#log .h2 { padding-top:2px; padding-bottom:2px; text-align: center; }
+#log .h3 { text-align: center; }
+#log .h4 { text-decoration: underline; }
+#log .h5 { text-decoration: underline; }
+
+#log .h1 { background-color: hsl(0,0%,80%); }
+#log .h2.teutonic { background-color: hsl(210,30%,85%); }
+#log .h2.russian { background-color: hsl(36,40%,85%); }
+#log .h3.teutonic { background-color: hsl(210,30%,90%); }
+#log .h3.russian { background-color: hsl(35,40%,90%); }
+
+#log div { padding-left: 20px; text-indent: -12px; }
+#log div.i { padding-left: 32px; text-indent: -12px; }
+#log div.ii { padding-left: 44px; text-indent: -12px; }
+
+#log .card_tip { font-style: italic; }
+#log .card_tip:hover { text-decoration: underline; }
+#log .lord_tip:hover { cursor: pointer; text-decoration: underline; }
+#log .locale_tip:hover { cursor: pointer; text-decoration: underline; }
+#log .way_tip:hover { cursor: pointer; text-decoration: underline; }
+
+.action {
+ cursor: pointer;
+}
+
+#log {
+ font-variant-numeric: tabular-nums;
+}
+
+/* PANELS */
+
+.panel {
+ min-width: 1275px;
+ max-width: 1275px;
+ 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: center;
+ flex-wrap: wrap;
+ padding: 18px;
+ gap: 18px;
+}
+
+.court_panel {
+ max-width: fit-content;
+}
+
+.court_body {
+ gap: 24px;
+}
+
+/* ARTS OF WAR */
+
+#arts_of_war_list {
+ min-height: 260px;
+}
+
+/* PLAN */
+
+#plan {
+ min-height: 260px;
+}
+
+#plan_actions {
+ background-color: #444;
+ min-height: 130px;
+}
+
+#plan_actions.hide {
+ display: none
+}
+
+#plan_actions .card {
+ width: 93px;
+ height: 130px;
+ background-size: 93px 130px;
+}
+
+body.Russians #plan_actions .teutonic { display: none }
+body.Teutons #plan_actions .russian { display: none }
+
+/* BATTLE GRID WITH LORD MATS */
+
+#battle_grid {
+ display: grid;
+ grid-template-columns: auto auto auto;
+ margin: 0 auto;
+ justify-content: center;
+ padding: 18px;
+}
+
+#battle_grid .grid_array {
+ width: 376px;
+ height: 506px;
+ padding: 36px 12px 12px 12px;
+}
+
+#battle_grid #grid_ga { height: auto; }
+#battle_grid #grid_sw { height: auto; }
+
+.grid_array { background-repeat: no-repeat }
+
+.grid_array.action {
+ background-image: url(images/battle_array_action.svg);
+ background-position: top center;
+}
+
+#battle_grid {
+ background-repeat: no-repeat;
+ background-position:
+ 95% 350px,
+ 85% 175px,
+ 10% 300px,
+ 90% 800px,
+ 10% 850px,
+ 40% 1000px;
+ background-image:
+ url(images/battlefield_buildings.png),
+ url(images/battlefield_trees1.png),
+ url(images/battlefield_hill1.png),
+ url(images/battlefield_hill2.png),
+ url(images/battlefield_trees2.png),
+ url(images/battlefield_trees3.png);
+}
+
+#battle_grid.attacker {
+ background-position:
+ 10% 900px,
+ 5% 725px,
+ 90% 850px,
+ 10% 250px,
+ 90% 250px,
+ 60% 100px;
+}
+
+#grid_rg1:empty:not(.action), #grid_rg2:empty:not(.action), #grid_rg3:empty:not(.action),
+#grid_sa1:empty:not(.action), #grid_sa2:empty:not(.action), #grid_sa3:empty:not(.action) {
+ display: none;
+}
+
+#battle_header { grid-row: 1; grid-column: 1 / 4; }
+
+.defender .att,
+.attacker .def,
+.defender .att .unit,
+.attacker .def .unit,
+.defender .att .asset,
+.attacker .def .asset,
+.defender .att .service_marker,
+.attacker .def .service_marker,
+.defender .att .moved_fought,
+.attacker .def .moved_fought,
+.defender .att .feed_x2,
+.attacker .def .feed_x2
+{
+ transform: rotate(180deg)
+}
+
+.defender .att .background,
+.attacker .def .background
+{
+ border-color: #b1a05f #f2e19d #f2e19d #b1a05f;
+ box-shadow: 0 0 0 1px #584800, -1px -2px 4px #0008;
+}
+
+.defender #grid_a1 { grid-row: 1; grid-column: 1; }
+.defender #grid_a2 { grid-row: 1; grid-column: 2; }
+.defender #grid_a3 { grid-row: 1; grid-column: 3; }
+.defender #grid_ga { grid-row: 2; grid-column: 2; }
+.defender #grid_d1 { grid-row: 3; grid-column: 1; }
+.defender #grid_d2 { grid-row: 3; grid-column: 2; }
+.defender #grid_d3 { grid-row: 3; grid-column: 3; }
+.defender #grid_rg1 { grid-row: 4; grid-column: 1; }
+.defender #grid_rg2 { grid-row: 4; grid-column: 2; }
+.defender #grid_rg3 { grid-row: 4; grid-column: 3; }
+.defender #grid_sw { grid-row: 5; grid-column: 2; }
+.defender #grid_sa1 { grid-row: 6; grid-column: 1; }
+.defender #grid_sa2 { grid-row: 6; grid-column: 2; }
+.defender #grid_sa3 { grid-row: 6; grid-column: 3; }
+
+.attacker #grid_a1 { grid-row: 6; grid-column: 3; }
+.attacker #grid_a2 { grid-row: 6; grid-column: 2; }
+.attacker #grid_a3 { grid-row: 6; grid-column: 1; }
+.attacker #grid_ga { grid-row: 5; grid-column: 2; }
+.attacker #grid_d1 { grid-row: 4; grid-column: 3; }
+.attacker #grid_d2 { grid-row: 4; grid-column: 2; }
+.attacker #grid_d3 { grid-row: 4; grid-column: 1; }
+.attacker #grid_rg1 { grid-row: 3; grid-column: 3; }
+.attacker #grid_rg2 { grid-row: 3; grid-column: 2; }
+.attacker #grid_rg3 { grid-row: 3; grid-column: 1; }
+.attacker #grid_sw { grid-row: 2; grid-column: 2; }
+.attacker #grid_sa1 { grid-row: 1; grid-column: 3; }
+.attacker #grid_sa2 { grid-row: 1; grid-column: 2; }
+.attacker #grid_sa3 { grid-row: 1; grid-column: 1; }
+
+.defender #pursuit.rotate,
+.attacker #pursuit:not(.rotate)
+{
+ transform: rotate(180deg);
+ border-color: #a68c61 #e7cb9e #e7cb9e #a68c61;
+ box-shadow: 0 0 0 1px #4e370a, -1px -2px 4px #0008;
+}
+
+#pursuit {
+ margin: 15px auto;
+}
+
+.siegeworks {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 4px;
+ margin: 12px;
+}
+
+.siegeworks:empty { display: none }
+
+#garrison {
+ background-image: url(images/garrison.svg);
+ background-repeat: no-repeat;
+ background-position: center;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+ width: 376px;
+ height: 80px;
+ gap: 2px;
+ margin: 0 auto;
+}
+
+#garrison.action {
+ background-image: url(images/garrison_action.svg);
+}
+
+#garrison.hide { display: none }
+
+#garrison .unit { position: static }
+
+/* MATS */
+
+.mat {
+ position: relative;
+ width: 376px;
+ height: 506px;
+ z-index: 1;
+}
+
+.mat .background {
+ position: absolute;
+ width: 372px;
+ height: 372px;
+ border-radius: 12px;
+ border-width: 2px;
+ border-style: solid;
+ background-color: #d1c07e;
+ background-position: center;
+ background-size: 360px 360px;
+ background-repeat: no-repeat;
+ z-index: 4;
+}
+
+.court_body .mat.besieged .background {
+ filter: grayscale(50%)
+}
+
+#battle_grid .mat.ambushed .background {
+ filter: grayscale(50%)
+}
+
+.court_body .mat.hidden .background {
+ filter: grayscale(100%)
+}
+
+.court_body .mat.besieged.hidden .background {
+ filter: grayscale(100%) brightness(75%)
+}
+
+.mat.command .background {
+ box-shadow: 0 0 0 1px #584800, 0 0 0 3px gold;
+}
+
+.mat.selected .background {
+ box-shadow: 0 0 0 1px #584800, 0 0 0 4px yellow;
+}
+
+.mat .capabilities {
+ position: absolute;
+ width: 372px;
+ height: 260px;
+ z-index: 2;
+ top: 246px;
+ left: 2px;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 0px;
+}
+
+.mat .events {
+ position: absolute;
+ z-index: 3;
+ width: 372px;
+ height: 260px;
+ left: 2px;
+ top: -36px;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 0px;
+ transition-property: top;
+ transition-duration: 100ms;
+}
+
+.mat .events:hover {
+ top: -130px;
+}
+
+.mat .events:empty {
+ display: none;
+}
+
+.mat .moved_fought, .mat .feed_x2 {
+ position: absolute;
+ z-index: 5;
+}
+
+.mat .feed_x2 { left: 8px; top: 164px; }
+.mat .moved_fought.one { right: 72px; top: 12px; }
+.mat .moved_fought.two { right: 66px; top: 6px; }
+
+body.shift .capabilities,
+body.shift .events,
+body.shift #capabilities1,
+body.shift #capabilities2 {
+ z-index: 200;
+}
+
+.mat .forces, .mat .routed, .mat .assets, #veche {
+ position: absolute;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: start;
+ justify-items: center;
+ align-content: center;
+ align-items: center;
+ gap: 4px 6px;
+ z-index: 5;
+ //background-color: #f004;
+}
+
+#veche {
+ gap: 4px;
+}
+
+.mat > .background > div {
+ //outline: 1px solid red;
+}
+
+.mat .forces, #veche {
+ justify-content: center;
+}
+
+.mat .forces, .mat .routed {
+ gap: 2px;
+}
+
+.mat .ready_vassals {
+ position: absolute;
+ z-index: 6;
+}
+
+.mat .mustered_vassals {
+ position: absolute;
+ z-index: 5;
+}
+
+body.shift .mustered_vassals {
+ display: block;
+}
+
+.mat .ready_vassals .service_marker,
+.mat .mustered_vassals .service_marker {
+ transition: margin 100ms;
+ margin-top: -24px;
+}
+
+.mat .ready_vassals:hover .service_marker,
+.mat .mustered_vassals:hover .service_marker {
+ margin-top: 1px;
+}
+
+.mat .mustered_vassals .service_marker {
+ margin-top: -38px;
+}
+
+.mat .shield {
+ position: absolute;
+ background-repeat: no-repeat;
+}
+
+.mat.teutonic .shield {
+ top: 10px; left: 6px;
+ width: 75px; height: 85px;
+ border-radius: 0 0 50% 50%;
+ filter: drop-shadow(0 0 4px white);
+}
+
+.mat.russian .shield,
+.mat.yaroslav .shield {
+ top: 6px; left: 7px;
+ width: 63px; height: 125px;
+ border-radius: 30px 30px 50% 50%;
+ filter: drop-shadow(0 0 3px white);
+}
+
+.mat.teutonic .shield.action {
+ background-image: url(images/shield_teutonic.svg);
+ filter: drop-shadow(0 0 3px white);
+}
+
+.mat.russian .shield.action, .mat.yaroslav .shield.action {
+ background-image: url(images/shield_russian.svg);
+ filter: drop-shadow(0 0 3px white);
+}
+
+.mat .forces { top: 96px; left: 66px; width: 240px; height: 96px; }
+.mat .routed { top: 200px; left: 18px; width: 236px; height: 48px; }
+.mat .assets { top: 255px; left: 24px; width: 230px; height: 96px; }
+.mat .ready_vassals { bottom: 22px; right: 22px; width: 94px; }
+.mat .mustered_vassals { bottom: 134px; right: 6px; width: 94px; }
+
+.mat.teutonic.andreas .background { background-image:url(mats.1x/mat_teutonic_andreas-fs8.png) }
+.mat.teutonic.heinrich .background { background-image:url(mats.1x/mat_teutonic_heinrich-fs8.png) }
+.mat.teutonic.hermann .background { background-image:url(mats.1x/mat_teutonic_hermann-fs8.png) }
+.mat.teutonic.knud_and_abel .background { background-image:url(mats.1x/mat_teutonic_knud_and_abel-fs8.png) }
+.mat.teutonic.rudolf .background { background-image:url(mats.1x/mat_teutonic_rudolf-fs8.png) }
+.mat.teutonic.yaroslav .background { background-image:url(mats.1x/mat_teutonic_yaroslav-fs8.png) }
+.mat.russian.aleksandr .background { background-image:url(mats.1x/mat_russian_aleksandr-fs8.png) }
+.mat.russian.andrey .background { background-image:url(mats.1x/mat_russian_andrey-fs8.png) }
+.mat.russian.domash .background { background-image:url(mats.1x/mat_russian_domash-fs8.png) }
+.mat.russian.gavrilo .background { background-image:url(mats.1x/mat_russian_gavrilo-fs8.png) }
+.mat.russian.karelians .background { background-image:url(mats.1x/mat_russian_karelians-fs8.png) }
+.mat.russian.vladislav .background { background-image:url(mats.1x/mat_russian_vladislav-fs8.png) }
+
+@media (min-resolution: 97dpi) {
+.mat.teutonic.andreas .background { background-image:url(mats.2x/mat_teutonic_andreas-fs8.png) }
+.mat.teutonic.heinrich .background { background-image:url(mats.2x/mat_teutonic_heinrich-fs8.png) }
+.mat.teutonic.hermann .background { background-image:url(mats.2x/mat_teutonic_hermann-fs8.png) }
+.mat.teutonic.knud_and_abel .background { background-image:url(mats.2x/mat_teutonic_knud_and_abel-fs8.png) }
+.mat.teutonic.rudolf .background { background-image:url(mats.2x/mat_teutonic_rudolf-fs8.png) }
+.mat.teutonic.yaroslav .background { background-image:url(mats.2x/mat_teutonic_yaroslav-fs8.png) }
+.mat.russian.aleksandr .background { background-image:url(mats.2x/mat_russian_aleksandr-fs8.png) }
+.mat.russian.andrey .background { background-image:url(mats.2x/mat_russian_andrey-fs8.png) }
+.mat.russian.domash .background { background-image:url(mats.2x/mat_russian_domash-fs8.png) }
+.mat.russian.gavrilo .background { background-image:url(mats.2x/mat_russian_gavrilo-fs8.png) }
+.mat.russian.karelians .background { background-image:url(mats.2x/mat_russian_karelians-fs8.png) }
+.mat.russian.vladislav .background { background-image:url(mats.2x/mat_russian_vladislav-fs8.png) }
+}
+
+.locale_markers > .conquered + .conquered { margin-left: -44px; margin-bottom: 13px; }
+.locale_markers > .conquered + .conquered + .conquered { margin-bottom: 26px; }
+.locale_markers > .conquered + .conquered + .conquered + .conquered { margin-bottom: 39px; }
+
+.locale_markers .siege + .siege { margin-left: -44px; margin-bottom: 13px; }
+.locale_markers .siege + .siege + .siege { margin-bottom: 26px; }
+.locale_markers .siege + .siege + .siege + .siege { margin-bottom: 39px; }
+
+/* TUCKED CARDS */
+
+.tuck_under_map {
+ margin: 0 auto 36px auto;
+ width: 1275px;
+ min-height: 260px;
+ margin-top: -130px;
+ display: grid;
+ grid-template-columns: auto auto;
+}
+
+#capabilities1, #capabilities2 {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+}
+
+#capabilities1 {
+ margin-left: 24px;
+ justify-content: start;
+}
+
+#capabilities2 {
+ margin-right: 24px;
+ justify-content: end;
+}
+
+/* ASSETS */
+
+.asset {
+ width: 42px;
+ height: 42px;
+ background-size: cover;
+ border: 2px solid transparent;
+ border-radius: 8px;
+ box-shadow: 0 0 0 1px #444, 0 1px 4px #0008;
+}
+
+.asset.action {
+ box-shadow: 0 0 0 1px #444, 0 0 0 4px white !important;
+}
+
+.asset.selected {
+ box-shadow: 0 0 0 1px #444, 0 0 0 4px yellow !important;
+}
+
+.asset.boat.x1 { background-image: url(images/asset_boat_x1.png); }
+.asset.boat.x2 { background-image: url(images/asset_boat_x2.png) }
+.asset.boat.x4 { background-image: url(images/asset_boat_x4.png) }
+.asset.cart.x1 { background-image: url(images/asset_cart_x1.png); }
+.asset.cart.x2 { background-image: url(images/asset_cart_x2.png) }
+.asset.cart.x4 { background-image: url(images/asset_cart_x4.png) }
+.asset.coin.x1 { background-image: url(images/asset_coin_x1.png); }
+.asset.coin.x2 { background-image: url(images/asset_coin_x2.png) }
+.asset.coin.x3 { background-image: url(images/asset_coin_x3.png) }
+.asset.coin.x4 { background-image: url(images/asset_coin_x4.png) }
+.asset.loot.x1 { background-image: url(images/asset_loot_x1.png); }
+.asset.loot.x2 { background-image: url(images/asset_loot_x2.png) }
+.asset.loot.x3 { background-image: url(images/asset_loot_x3.png) }
+.asset.loot.x4 { background-image: url(images/asset_loot_x4.png) }
+.asset.prov.x1 { background-image: url(images/asset_prov_x1.png); }
+.asset.prov.x2 { background-image: url(images/asset_prov_x2.png) }
+.asset.prov.x3 { background-image: url(images/asset_prov_x3.png) }
+.asset.prov.x4 { background-image: url(images/asset_prov_x4.png) }
+.asset.ship.x1 { background-image: url(images/asset_ship_x1.png); }
+.asset.ship.x2 { background-image: url(images/asset_ship_x2.png) }
+.asset.ship.x4 { background-image: url(images/asset_ship_x4.png) }
+.asset.sled.x1 { background-image: url(images/asset_sled_x1.png); }
+.asset.sled.x2 { background-image: url(images/asset_sled_x2.png) }
+.asset.sled.x4 { background-image: url(images/asset_sled_x4.png) }
+
+.marker {
+ border: 2px solid aqua;
+ border-radius: 8px;
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+.marker.square {
+ width: 42px;
+ height: 42px;
+}
+
+.marker.small {
+ width: 36px;
+ height: 36px;
+ border-radius: 6px;
+}
+
+.marker.rectangle {
+ width: 90px;
+ height: 42px;
+}
+
+.marker.circle {
+ width: 51px;
+ height: 51px;
+ background-size: 51px 51px;
+ border-radius: 50%;
+}
+
+.marker.turn.levy { background-image: url(images/marker_levy.png) }
+.marker.turn.campaign { background-image: url(images/marker_campaign.png) }
+.marker.storm { background-image: url(images/marker_storm.png) }
+.marker.battle { background-image: url(images/marker_battle.png) }
+.marker.conquered.teutonic { background-image: url(images/marker_conquered_teutonic.png) }
+.marker.conquered.russian { background-image: url(images/marker_conquered_russian.png) }
+.marker.ravaged.teutonic { background-image: url(images/marker_ravaged_teutonic.png) }
+.marker.ravaged.russian { background-image: url(images/marker_ravaged_russian.png) }
+.marker.siege.teutonic { background-image: url(images/marker_siege_teutonic.png) }
+.marker.siege.russian { background-image: url(images/marker_siege_russian.png) }
+.marker.enemy_lords_removed.teutonic { background-image: url(images/marker_enemy_lords_removed_teutonic.png) }
+.marker.enemy_lords_removed.russian { background-image: url(images/marker_enemy_lords_removed_russian.png) }
+.marker.victory.teutonic { background-image: url(images/marker_victory_teutonic.png) }
+.marker.victory.russian { background-image: url(images/marker_victory_russian.png) }
+.marker.victory.half.teutonic { background-image: url(images/marker_victory_half_teutonic.png) }
+.marker.victory.half.russian { background-image: url(images/marker_victory_half_russian.png) }
+.marker.pursuit.teutonic { background-image: url(images/marker_pursuit_teutonic.png) }
+.marker.pursuit.russian { background-image: url(images/marker_pursuit_russian.png) }
+.marker.castle.russian { background-image: url(images/marker_castle_russian.png) }
+.marker.castle.teutonic { background-image: url(images/marker_castle_teutonic.png) }
+.marker.walls { background-image: url(images/marker_walls.a.png) }
+.marker.moved_fought { background-image: url(images/marker_moved_fought.png) }
+.marker.feed_x2 { background-image: url(images/almoravid_marker_feed_x2.png) }
+
+.marker.number.teutonic.n1 { background-image: url(images/marker_1_teutonic.png) }
+.marker.number.teutonic.n2 { background-image: url(images/marker_2_teutonic.png) }
+.marker.number.teutonic.n3 { background-image: url(images/marker_3_teutonic.png) }
+.marker.number.teutonic.n4 { background-image: url(images/marker_4_teutonic.png) }
+.marker.number.teutonic.n5 { background-image: url(images/marker_5_teutonic.png) }
+.marker.number.teutonic.n6 { background-image: url(images/marker_6_teutonic.png) }
+.marker.number.russian.n1 { background-image: url(images/marker_1_russian.png) }
+.marker.number.russian.n2 { background-image: url(images/marker_2_russian.png) }
+.marker.number.russian.n3 { background-image: url(images/marker_3_russian.png) }
+.marker.number.russian.n4 { background-image: url(images/marker_4_russian.png) }
+.marker.number.russian.n5 { background-image: url(images/marker_5_russian.png) }
+.marker.number.russian.n6 { background-image: url(images/marker_6_russian.png) }
+
+/* UNITS */
+
+.unit {
+ background-size: contain;
+ background-repeat: no-repeat;
+ filter: drop-shadow(0px 2px 2px #0004);
+}
+
+.unit.action {
+ filter:
+ drop-shadow(2px 0px 0px white)
+ drop-shadow(0px 2px 0px white)
+ drop-shadow(0px -2px 0px white)
+ drop-shadow(-2px 0px 0px white);
+}
+
+.unit.knights, .unit.sergeants, .unit.light_horse, .unit.asiatic_horse {
+ width: 30px;
+ height: 35px;
+}
+
+.unit.men_at_arms, .unit.militia, .unit.serfs {
+ width: 43px;
+ height: 35px;
+}
+
+.unit.knights { background-image: url(images/unit_knights.svg) }
+.unit.sergeants { background-image: url(images/unit_sergeants.svg) }
+.unit.light_horse { background-image: url(images/unit_light_horse.svg) }
+.unit.asiatic_horse { background-image: url(images/unit_asiatic_horse.svg) }
+.unit.men_at_arms { background-image: url(images/unit_men_at_arms.svg) }
+.unit.militia { background-image: url(images/unit_militia.svg) }
+.unit.serfs { background-image: url(images/unit_serfs.svg) }
+
+/* CARDS */
+
+.hand {
+ margin: 24px auto;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+ min-height: 260px;
+ max-width: 1275px;
+ gap: 16px;
+}
+
+.card {
+ background-size: 186px 260px;
+ width: 186px;
+ height: 260px;
+ border-radius: 8px;
+ transition: 100ms;
+}
+
+.card_info {
+ border-bottom: 1px solid black;
+}
+
+.card_info .card {
+ margin: 12px auto;
+}
+
+.card.disabled {
+ filter: brightness(80%);
+}
+
+.card.action.teutonic { background-color: #e1e6e8; box-shadow: 0 0 0 1px #666a6c, 0 0 0 4px white; }
+.card.action.russian { background-color: #e1d6c1; box-shadow: 0 0 0 1px #665c4a, 0 0 0 4px white; }
+
+.card.selected.teutonic { background-color: #e1e6e8; box-shadow: 0 0 0 1px #666a6c, 0 0 0 4px yellow; }
+.card.selected.russian { background-color: #e1d6c1; box-shadow: 0 0 0 1px #665c4a, 0 0 0 4px yellow; }
+
+/* MAP */
+
+#mapwrap {
+ width: 1275px;
+ height: 1650px;
+ box-shadow: 0px 1px 10px #0008;
+ z-index: 3;
+}
+
+#map {
+ background-repeat: no-repeat;
+ background-size: cover;
+ width: 1275px;
+ height: 1650px;
+ overflow: clip;
+}
+
+#map { background-image: url(map75.jpg) }
+@media (min-resolution: 97dpi) {
+#map { background-image: url(map150.jpg) }
+}
+
+.box {
+ position: absolute;
+ border: 3px solid transparent;
+}
+
+.veche_border {
+ position: absolute;
+}
+
+.box.victory {
+ border-radius: 50%;
+}
+
+.box.turn {
+ border-radius: 50%;
+}
+
+.box.calendar.end {
+ background-color: #8886;
+}
+
+.box.calendar.action {
+ border-color: white;
+ background-color: #fff6;
+}
+
+.box.way.action {
+ border-color: dodgerblue;
+}
+
+.box.way.crossroads.action {
+ border-color: saddlebrown;
+}
+
+.box.way.crossroads { border-radius: 36px; }
+.box.way.wirz { border-radius: 36px 36px 75% 75% }
+.box.way.peipus-east { border-radius: 75% 36px 75% 36px }
+.box.way.peipus-north { border-radius: 36px 36px 36px 75% }
+
+.locale {
+ box-sizing: border-box;
+ position: absolute;
+ border: 3px solid transparent;
+}
+
+.locale_name {
+ box-sizing: border-box;
+ position: absolute;
+ border: 3px solid transparent;
+}
+
+.locale.region { border-radius: 50% }
+.locale.town { border-radius: 80px 80px 36px 36px; }
+
+.locale.traderoute { border-radius: 60% 40% 0 0 }
+.locale.fort { border-radius: 24px 24px 0 0 }
+.locale.castle { border-radius: 18px 18px 0 0 }
+.locale.city { border-radius: 50% 50% 0 0 }
+.locale.bishopric { border-radius: 50% 50% 0 0 }
+.locale.novgorod { border-radius: 50% 50% 0 0 }
+
+.locale.action.region { background-color: #5A02 }
+.locale.action.town { background-color: #fc02 }
+.locale.action.traderoute { background-color: #0af2 }
+.locale.action.fort { background-color: #f552 }
+.locale.action.city { background-color: #f552 }
+.locale.action.novgorod { background-color: #f552 }
+.locale.action.castle { background-color: #0002 }
+.locale.action.bishopric { background-color: #0002 }
+
+.locale.action { border-color: white; box-shadow: 0 0 4px white; }
+.locale.action.laden { border-color: white; box-shadow: 0 0 0 3px indianred; background-color: #cd5c5c66 }
+
+.locale.supply_path { border-color: indianred; background-color: #cd5c5c66; }
+.locale.supply_source { border-color: seagreen; background-color: #2e8b5766; }
+.locale.supply_source.action { border-color: white; background-color: #2e8b5766; box-shadow: 0 0 0 3px seagreen; }
+
+.way.tip, .locale.tip, .locale_name.tip {
+ background-color: #ff08;
+ box-shadow: 0 0 8px #ff08;
+}
+
+.locale_markers {
+ position: absolute;
+ pointer-events: none;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+ gap: 4px;
+}
+
+.veche_label {
+ pointer-events: none;
+ border: 3px solid transparent;
+}
+
+.veche_border {
+ pointer-events: none;
+}
+
+#veche { left: 1046px; top: 1460px; width: 215px; height: 161px; }
+#veche_border_w { left: 1046px; top: 1460px; width: 50px; height: 157px; }
+#veche_border_e { left: 1208px; top: 1460px; width: 50px; height: 157px; }
+#veche_border_sw { left: 1046px; top: 1618px; width: 59px; height: 0; }
+#veche_border_se { left: 1197px; top: 1618px; width: 64px; height: 0; }
+
+#veche_label_top { left: 1097px; top: 1457px; width: 107px; height: 20px; }
+#veche_label_bottom { left: 1104px; top: 1605px; width: 89px; height: 13px; }
+
+#veche_border_w { border-top: 3px solid transparent; border-left: 3px solid transparent; }
+#veche_border_e { border-top: 3px solid transparent; border-right: 3px solid transparent; }
+#veche_border_sw { border-bottom: 3px solid transparent; }
+#veche_border_se { border-bottom: 3px solid transparent; }
+
+#veche.action {
+ box-shadow: 0 0 8px white;
+}
+
+#veche.action ~ .veche_label {
+ box-shadow: 0 0 8px white;
+ border-color: white;
+}
+
+#veche.action ~ .veche_border {
+ border-color: white;
+}
+
+#smerdi {
+ display: flex;
+ bottom: 12px;
+ right: 370px;
+ gap: 2px;
+}
+
+#vp2.stack:not(.half) ~ #vp1.stack:not(.half) { transform: translate(0px,-36px); }
+#vp2.stack.half ~ #vp1.stack.half { transform: translate(0px,-36px); }
+#vp2.stack.half ~ #vp1.stack:not(.half) { transform: translate(-12px,-30px); }
+#vp2.stack:not(.half) ~ #vp1.stack.half { transform: translate(12px,-30px); }
+
+.marker.russian.enemy_lords_removed { transform: translate(-12px,18px); }
+.marker.teutonic.enemy_lords_removed { transform: translate(12px,18px); }
+
+.marker.victory, .marker.turn, .marker.enemy_lords_removed {
+ position: absolute;
+}
+
+.v0 { top: 132px; left: 2px; }
+
+.v1, .v2, .v3, .v4, .v5, .v6, .v7, .v8, .t1, .t2, .t3, .t4, .t5, .t6, .t7, .t8 { top: 46px }
+.v9, .v10, .v11, .v12, .v13, .v14, .v15, .v16, .t9, .t10, .t11, .t12, .t13, .t14, .t15, .t16 { top: 284px }
+
+.v1, .v9 { left: 44px }
+.v2, .v10 { left: 196px }
+.v3, .v11 { left: 362px }
+.v4, .v12 { left: 514px }
+.v5, .v13 { left: 680px }
+.v6, .v14 { left: 832px }
+.v7, .v15 { left: 999px }
+.v8, .v16 { left: 1151px }
+
+.t1, .t9 { left: 100px }
+.t2, .t10 { left: 252px }
+.t3, .t11 { left: 418px }
+.t4, .t12 { left: 570px }
+.t5, .t13 { left: 737px }
+.t6, .t14 { left: 888px }
+.t7, .t15 { left: 1055px }
+.t8, .t16 { left: 1207px }
+
+/* PIECES */
+
+#legate, .cylinder, .marker {
+ transition-property: top, left;
+ transition-duration: 700ms;
+ transition-timing-function: ease;
+}
+
+.service_marker {
+ transition-property: top, left;
+ transition-duration: 100ms, 500ms;
+}
+
+#legate {
+ position: absolute;
+ width: 32px;
+ height: 64px;
+ background-image: url(images/legate.svg);
+ filter: drop-shadow(0px 2px 4px #0004);
+}
+
+#legate.action {
+ filter:
+ drop-shadow(2px 0px 0px white)
+ drop-shadow(0px 2px 0px white)
+ drop-shadow(0px -2px 0px white)
+ drop-shadow(-2px 0px 0px white);
+}
+
+#legate.selected {
+ filter:
+ drop-shadow(2px 0px 0px yellow)
+ drop-shadow(0px 2px 0px yellow)
+ drop-shadow(0px -2px 0px yellow)
+ drop-shadow(-2px 0px 0px yellow);
+}
+
+.cylinder {
+ position: absolute;
+ width: 44px;
+ height: 48px;
+ background-size: 44px 48px;
+}
+
+.cylinder:not(.lieutenant) {
+ filter: drop-shadow(0px 2px 4px #0004);
+}
+
+#map .cylinder.besieged {
+ transform: translateY(8px)
+}
+
+.cylinder.command {
+ filter:
+ drop-shadow(2px 0px 0px gold)
+ drop-shadow(0px 2px 0px gold)
+ drop-shadow(0px -2px 0px gold)
+ drop-shadow(-2px 0px 0px gold);
+}
+
+.cylinder.action {
+ filter:
+ drop-shadow(2px 0px 0px white)
+ drop-shadow(0px 2px 0px white)
+ drop-shadow(0px -2px 0px white)
+ drop-shadow(-2px 0px 0px white);
+}
+
+.cylinder.selected {
+ filter:
+ drop-shadow(2px 0px 0px yellow)
+ drop-shadow(0px 2px 0px yellow)
+ drop-shadow(0px -2px 0px yellow)
+ drop-shadow(-2px 0px 0px yellow);
+}
+
+.cylinder.andreas { background-image: url(images/lord_teutonic_1.svg) }
+.cylinder.hermann.marshal { background-image: url(images/lord_teutonic_2.svg) }
+.cylinder.hermann { background-image: url(images/lord_teutonic_3.svg) }
+.cylinder.heinrich { background-image: url(images/lord_teutonic_4.svg) }
+.cylinder.rudolf { background-image: url(images/lord_teutonic_5.svg) }
+.cylinder.knud_and_abel { background-image: url(images/lord_teutonic_6.svg) }
+.cylinder.yaroslav { background-image: url(images/lord_teutonic_7.svg) }
+
+.cylinder.aleksandr { background-image: url(images/lord_russian_1.svg) }
+.cylinder.andrey { background-image: url(images/lord_russian_2.svg) }
+.cylinder.domash { background-image: url(images/lord_russian_3.svg) }
+.cylinder.gavrilo { background-image: url(images/lord_russian_4.svg) }
+.cylinder.vladislav { background-image: url(images/lord_russian_5.svg) }
+.cylinder.karelians { background-image: url(images/lord_russian_6.svg) }
+.cylinder.andrey.marshal { background-image: url(images/lord_russian_7.svg) }
+
+.marker.teutonic,
+.service_marker.teutonic.lord {
+ background-color: #444;
+ border-color: #555 #222 #222 #555;
+ box-shadow: 0 0 0 1px #111, 0 2px 4px #0008;
+}
+
+.marker.russian,
+.marker.teutonic.enemy_lords_removed,
+.service_marker.russian.lord {
+ background-color: #fff;
+ border-color: #eee #ccc #ccc #eee;
+ box-shadow: 0 0 0 1px #555, 0 2px 4px #0008;
+}
+
+.service_marker {
+ background-repeat: no-repeat;
+ width: 90px;
+ height: 42px;
+ border-radius: 8px;
+ box-shadow: 0 1px 6px #0008;
+ border-width: 2px;
+ border-style: solid;
+}
+
+#pieces .service_marker {
+ position: absolute;
+}
+
+#pieces .castle {
+ position: absolute;
+ pointer-events: none;
+}
+
+.service_marker.teutonic.lord.selected { box-shadow: 0 0 0 1px #111, 0 0 0 4px yellow; }
+.service_marker.russian.lord.selected { box-shadow: 0 0 0 1px #555, 0 0 0 4px yellow; }
+.service_marker.teutonic.lord.action { box-shadow: 0 0 0 1px #111, 0 0 0 4px white; }
+.service_marker.russian.lord.action { box-shadow: 0 0 0 1px #555, 0 0 0 4px white; }
+.service_marker.teutonic.lord.action.bad { box-shadow: 0 0 0 1px #111, 0 0 0 4px tomato; }
+.service_marker.russian.lord.action.bad { box-shadow: 0 0 0 1px #555, 0 0 0 4px tomato; }
+
+.service_marker.teutonic.vassal.selected { box-shadow: 0 0 0 1px #0f0d0d, 0 0 0 4px yellow; }
+.service_marker.russian.vassal.selected { box-shadow: 0 0 0 1px #736e5e, 0 0 0 4px yellow; }
+.service_marker.teutonic.vassal.action { box-shadow: 0 0 0 1px #0f0d0d, 0 0 0 4px white; }
+.service_marker.russian.vassal.action { box-shadow: 0 0 0 1px #736e5e, 0 0 0 4px white; }
+
+.service_marker.lord { background-size: 90px 252px }
+.service_marker.vassal { background-size: 270px 210px }
+
+.service_marker.lord.teutonic { background-image:url(images/service_lords_teutonic.png) }
+.service_marker.lord.russian { background-image:url(images/service_lords_russian.png) }
+.service_marker.vassal.teutonic { background-image:url(images/service_vassals_teutonic.png) }
+.service_marker.vassal.russian { background-image:url(images/service_vassals_russian.png) }
+
+.service_marker.lord.image0{background-position:0 -0px}
+.service_marker.lord.image1{background-position:0 -42px}
+.service_marker.lord.image2{background-position:0 -84px}
+.service_marker.lord.image3{background-position:0 -126px}
+.service_marker.lord.image4{background-position:0 -168px}
+.service_marker.lord.image5{background-position:0 -210px}
+
+.service_marker.vassal.image0{background-position:0 -0px}
+.service_marker.vassal.image1{background-position:-90px -0px}
+.service_marker.vassal.image2{background-position:-180px -0px}
+.service_marker.vassal.image3{background-position:0 -42px}
+.service_marker.vassal.image4{background-position:-90px -42px}
+.service_marker.vassal.image5{background-position:-180px -42px}
+.service_marker.vassal.image6{background-position:0 -84px}
+.service_marker.vassal.image7{background-position:-90px -84px}
+.service_marker.vassal.image8{background-position:-180px -84px}
+.service_marker.vassal.image9{background-position:0 -126px}
+.service_marker.vassal.image10{background-position:-90px -126px}
+.service_marker.vassal.image11{background-position:-180px -126px}
+.service_marker.vassal.image12{background-position:0 -168px}
+.service_marker.vassal.image13{background-position:-90px -168px}
+.service_marker.vassal.image14{background-position:-180px -168px}
+
+/* BACKGROUND COLORS AND BORDERS */
+/* :r !node tools/colors.mjs */
+
+#battle_mat { background-color: #d1c07e; border-color: #f2e19d #b1a05f #b1a05f #f2e19d; box-shadow: 0 0 0 1px #584800, 1px 2px 4px #0008; }
+.mat .background { background-color: #d1c07e; border-color: #f2e19d #b1a05f #b1a05f #f2e19d; box-shadow: 0 0 0 1px #584800, 1px 2px 4px #0008; }
+.card.teutonic { background-color: #e1e6e8; border-color: #fbffff #c1c5c7 #c1c5c7 #fbffff; box-shadow: 0 0 0 1px #666a6c, 1px 2px 4px #0008; }
+.card.russian { background-color: #e1d6c1; border-color: #fff7e1 #c1b6a1 #c1b6a1 #fff7e1; box-shadow: 0 0 0 1px #665c4a, 1px 2px 4px #0008; }
+.service_marker.teutonic.vassal { background-color: #777474; border-color: #959292 #5a5858 #5a5858 #959292; box-shadow: 0 0 0 1px #0f0d0d, 1px 2px 4px #0008; }
+.service_marker.russian.vassal { background-color: #f0ead8; border-color: #ffffed #cfc9b8 #cfc9b8 #ffffed; box-shadow: 0 0 0 1px #736e5e, 1px 2px 4px #0008; }
+.asset.sled { background-color: #e5dcc1; border-color: #fffde2 #c5bca1 #c5bca1 #fffde2; box-shadow: 0 0 0 1px #69614a, 1px 2px 4px #0008; }
+.asset.boat { background-color: #adceed; border-color: #cdefff #8eaecc #8eaecc #cdefff; box-shadow: 0 0 0 1px #38556f, 1px 2px 4px #0008; }
+.asset.cart.x1 { background-color: #daba8b; border-color: #fbdaaa #ba9b6d #ba9b6d #fbdaaa; box-shadow: 0 0 0 1px #5f4315, 1px 2px 4px #0008; }
+.asset.cart.x2 { background-color: #d1a973; border-color: #f2c992 #b18a55 #b18a55 #f2c992; box-shadow: 0 0 0 1px #563400, 1px 2px 4px #0008; }
+.asset.cart.x4 { background-color: #c4975b; border-color: #e5b67a #a4793c #a4793c #e5b67a; box-shadow: 0 0 0 1px #4b2500, 1px 2px 4px #0008; }
+.asset.coin.x1 { background-color: #d2d5d4; border-color: #f3f6f5 #b2b5b4 #b2b5b4 #f3f6f5; box-shadow: 0 0 0 1px #595c5b, 1px 2px 4px #0008; }
+.asset.coin.x2 { background-color: #d2d5d4; border-color: #f3f6f5 #b2b5b4 #b2b5b4 #f3f6f5; box-shadow: 0 0 0 1px #595c5b, 1px 2px 4px #0008; }
+.asset.coin.x3 { background-color: #b3b5b4; border-color: #d3d5d4 #949695 #949695 #d3d5d4; box-shadow: 0 0 0 1px #3f4040, 1px 2px 4px #0008; }
+.asset.coin.x4 { background-color: #b3b5b4; border-color: #d3d5d4 #949695 #949695 #d3d5d4; box-shadow: 0 0 0 1px #3f4040, 1px 2px 4px #0008; }
+.asset.prov.x1 { background-color: #ffe293; border-color: #fffdad #dec173 #dec173 #fffdad; box-shadow: 0 0 0 1px #80650a, 1px 2px 4px #0008; }
+.asset.prov.x2 { background-color: #ffe293; border-color: #fffdad #dec173 #dec173 #fffdad; box-shadow: 0 0 0 1px #80650a, 1px 2px 4px #0008; }
+.asset.prov.x3 { background-color: #ffcd66; border-color: #ffee88 #dead43 #dead43 #ffee88; box-shadow: 0 0 0 1px #7f5200, 1px 2px 4px #0008; }
+.asset.prov.x4 { background-color: #ffcd66; border-color: #ffee88 #dead43 #dead43 #ffee88; box-shadow: 0 0 0 1px #7f5200, 1px 2px 4px #0008; }
+.asset.ship.x1 { background-color: #79b7e4; border-color: #98d7ff #5a98c3 #5a98c3 #98d7ff; box-shadow: 0 0 0 1px #004066, 1px 2px 4px #0008; }
+.asset.ship.x2 { background-color: #79b7e4; border-color: #98d7ff #5a98c3 #5a98c3 #98d7ff; box-shadow: 0 0 0 1px #004066, 1px 2px 4px #0008; }
+.asset.ship.x4 { background-color: #5da9dd; border-color: #7dc9ff #3d8abc #3d8abc #7dc9ff; box-shadow: 0 0 0 1px #00335f, 1px 2px 4px #0008; }
+.asset.loot.x1 { background-color: #f0b64f; border-color: #ffd771 #cf9628 #cf9628 #ffd771; box-shadow: 0 0 0 1px #703d00, 1px 2px 4px #0008; }
+.asset.loot.x2 { background-color: #eda44c; border-color: #ffc46e #cb8526 #cb8526 #ffc46e; box-shadow: 0 0 0 1px #6c2c00, 1px 2px 4px #0008; }
+.asset.loot.x3 { background-color: #eb924a; border-color: #ffb26b #c97326 #c97326 #ffb26b; box-shadow: 0 0 0 1px #681800, 1px 2px 4px #0008; }
+.asset.loot.x4 { background-color: #e1884a; border-color: #ffa86a #bf6928 #bf6928 #ffa86a; box-shadow: 0 0 0 1px #5f0c00, 1px 2px 4px #0008; }
+.marker.battle { background-color: #d0bf7d; border-color: #f1e09c #b09f5e #b09f5e #f1e09c; box-shadow: 0 0 0 1px #574700, 1px 2px 4px #0008; }
+.marker.storm { background-color: #d0bf7d; border-color: #f1e09c #b09f5e #b09f5e #f1e09c; box-shadow: 0 0 0 1px #574700, 1px 2px 4px #0008; }
+.marker.pursuit { background-color: #c6ab7f; border-color: #e7cb9e #a68c61 #a68c61 #e7cb9e; box-shadow: 0 0 0 1px #4e370a, 1px 2px 4px #0008; }
+.marker.turn.campaign { background-color: #6a8aa8; border-color: #88a9c8 #4d6c89 #4d6c89 #88a9c8; box-shadow: 0 0 0 1px #001c34, 1px 2px 4px #0008; }
+.marker.turn.levy { background-color: #967348; border-color: #b59165 #78562b #78562b #b59165; box-shadow: 0 0 0 1px #240800, 1px 2px 4px #0008; }
+.marker.teutonic.victory { background-color: #ffd400; border-color: #fff64b #deb300 #deb300 #fff64b; box-shadow: 0 0 0 1px #805600, 1px 2px 4px #0008; }
+.marker.teutonic.siege { background-color: #a39382; border-color: #c3b2a0 #857565 #857565 #c3b2a0; box-shadow: 0 0 0 1px #312416, 1px 2px 4px #0008; }
+.marker.russian.conquered { background-color: #649655; border-color: #82b573 #477838 #477838 #82b573; box-shadow: 0 0 0 1px #002500, 1px 2px 4px #0008; }
+.marker.teutonic.enemy_lords_removed { background-color: #ffd400; border-color: #fff64b #deb300 #deb300 #fff64b; box-shadow: 0 0 0 1px #805600, 1px 2px 4px #0008; }
+.marker.russian.victory { background-color: #2d8b47; border-color: #4faa64 #006d2a #006d2a #4faa64; box-shadow: 0 0 0 1px #001a00, 1px 2px 4px #0008; }
+.marker.walls { background-color: #e3dedc; border-color: #fffefc #c3bebc #c3bebc #fffefc; box-shadow: 0 0 0 1px #686362, 1px 2px 4px #0008; }
+.marker.russian.number { background-color: #c6992f; border-color: #e7b954 #a67a00 #a67a00 #e7b954; box-shadow: 0 0 0 1px #4d2400, 1px 2px 4px #0008; }
+.marker.teutonic.number { background-color: #a02532; border-color: #c2474e #7e0017 #7e0017 #c2474e; box-shadow: 0 0 0 1px #1a0000, 1px 2px 4px #0008; }
+.marker.moved_fought { background-color: #0072bc; border-color: #3491dd #00549c #00549c #3491dd; box-shadow: 0 0 0 1px #00003f, 1px 2px 4px #0008; }
+.marker.feed_x2 { background-color: #0072bc; border-color: #3491dd #00549c #00549c #3491dd; box-shadow: 0 0 0 1px #00003f, 1px 2px 4px #0008; }
+
+/* CARD IMAGES */
+
+.card.teutonic.aow_back{background-image:url(cards.1x/aow_teutonic_back.jpg)}
+.card.teutonic.cc_back{background-image:url(cards.1x/cc_teutonic_back.jpg)}
+.card.teutonic.cc_pass{background-image:url(cards.1x/cc_teutonic_pass.jpg)}
+.card.russian.aow_back{background-image:url(cards.1x/aow_russian_back.jpg)}
+.card.russian.cc_back{background-image:url(cards.1x/cc_russian_back.jpg)}
+.card.russian.cc_pass{background-image:url(cards.1x/cc_russian_pass.jpg)}
+.card.aow_0{background-image:url(cards.1x/aow_teutonic_01.jpg)}
+.card.aow_1{background-image:url(cards.1x/aow_teutonic_02.jpg)}
+.card.aow_2{background-image:url(cards.1x/aow_teutonic_03.jpg)}
+.card.aow_3{background-image:url(cards.1x/aow_teutonic_04.jpg)}
+.card.aow_4{background-image:url(cards.1x/aow_teutonic_05.jpg)}
+.card.aow_5{background-image:url(cards.1x/aow_teutonic_06.jpg)}
+.card.aow_6{background-image:url(cards.1x/aow_teutonic_07.jpg)}
+.card.aow_7{background-image:url(cards.1x/aow_teutonic_08.jpg)}
+.card.aow_8{background-image:url(cards.1x/aow_teutonic_09.jpg)}
+.card.aow_9{background-image:url(cards.1x/aow_teutonic_10.jpg)}
+.card.aow_10{background-image:url(cards.1x/aow_teutonic_11.jpg)}
+.card.aow_11{background-image:url(cards.1x/aow_teutonic_12.jpg)}
+.card.aow_12{background-image:url(cards.1x/aow_teutonic_13.jpg)}
+.card.aow_13{background-image:url(cards.1x/aow_teutonic_14.jpg)}
+.card.aow_14{background-image:url(cards.1x/aow_teutonic_15.jpg)}
+.card.aow_15{background-image:url(cards.1x/aow_teutonic_16.jpg)}
+.card.aow_16{background-image:url(cards.1x/aow_teutonic_17.jpg)}
+.card.aow_17{background-image:url(cards.1x/aow_teutonic_18.jpg)}
+.card.aow_18{background-image:url(cards.1x/aow_teutonic_none.jpg)}
+.card.aow_19{background-image:url(cards.1x/aow_teutonic_none.jpg)}
+.card.aow_20{background-image:url(cards.1x/aow_teutonic_none.jpg)}
+.card.aow_21{background-image:url(cards.1x/aow_russian_01.jpg)}
+.card.aow_22{background-image:url(cards.1x/aow_russian_02.jpg)}
+.card.aow_23{background-image:url(cards.1x/aow_russian_03.jpg)}
+.card.aow_24{background-image:url(cards.1x/aow_russian_04.jpg)}
+.card.aow_25{background-image:url(cards.1x/aow_russian_05.jpg)}
+.card.aow_26{background-image:url(cards.1x/aow_russian_06.jpg)}
+.card.aow_27{background-image:url(cards.1x/aow_russian_07.jpg)}
+.card.aow_28{background-image:url(cards.1x/aow_russian_08.jpg)}
+.card.aow_29{background-image:url(cards.1x/aow_russian_09.jpg)}
+.card.aow_30{background-image:url(cards.1x/aow_russian_10.jpg)}
+.card.aow_31{background-image:url(cards.1x/aow_russian_11.jpg)}
+.card.aow_32{background-image:url(cards.1x/aow_russian_12.jpg)}
+.card.aow_33{background-image:url(cards.1x/aow_russian_13.jpg)}
+.card.aow_34{background-image:url(cards.1x/aow_russian_14.jpg)}
+.card.aow_35{background-image:url(cards.1x/aow_russian_15.jpg)}
+.card.aow_36{background-image:url(cards.1x/aow_russian_16.jpg)}
+.card.aow_37{background-image:url(cards.1x/aow_russian_17.jpg)}
+.card.aow_38{background-image:url(cards.1x/aow_russian_18.jpg)}
+.card.aow_39{background-image:url(cards.1x/aow_russian_none.jpg)}
+.card.aow_40{background-image:url(cards.1x/aow_russian_none.jpg)}
+.card.aow_41{background-image:url(cards.1x/aow_russian_none.jpg)}
+.card.cc_lord_0{background-image:url(cards.1x/cc_teutonic_andreas.jpg)}
+.card.cc_lord_1{background-image:url(cards.1x/cc_teutonic_heinrich.jpg)}
+.card.cc_lord_2{background-image:url(cards.1x/cc_teutonic_hermann.jpg)}
+.card.cc_lord_3{background-image:url(cards.1x/cc_teutonic_knud_and_abel.jpg)}
+.card.cc_lord_4{background-image:url(cards.1x/cc_teutonic_rudolf.jpg)}
+.card.cc_lord_5{background-image:url(cards.1x/cc_teutonic_yaroslav.jpg)}
+.card.cc_lord_6{background-image:url(cards.1x/cc_russian_aleksandr.jpg)}
+.card.cc_lord_7{background-image:url(cards.1x/cc_russian_andrey.jpg)}
+.card.cc_lord_8{background-image:url(cards.1x/cc_russian_domash.jpg)}
+.card.cc_lord_9{background-image:url(cards.1x/cc_russian_gavrilo.jpg)}
+.card.cc_lord_10{background-image:url(cards.1x/cc_russian_karelians.jpg)}
+.card.cc_lord_11{background-image:url(cards.1x/cc_russian_vladislav.jpg)}
+
+@media (min-resolution: 97dpi) {
+.card.teutonic.aow_back{background-image:url(cards.2x/aow_teutonic_back.jpg)}
+.card.teutonic.cc_back{background-image:url(cards.2x/cc_teutonic_back.jpg)}
+.card.teutonic.cc_pass{background-image:url(cards.2x/cc_teutonic_pass.jpg)}
+.card.russian.aow_back{background-image:url(cards.2x/aow_russian_back.jpg)}
+.card.russian.cc_back{background-image:url(cards.2x/cc_russian_back.jpg)}
+.card.russian.cc_pass{background-image:url(cards.2x/cc_russian_pass.jpg)}
+.card.aow_0{background-image:url(cards.2x/aow_teutonic_01.jpg)}
+.card.aow_1{background-image:url(cards.2x/aow_teutonic_02.jpg)}
+.card.aow_2{background-image:url(cards.2x/aow_teutonic_03.jpg)}
+.card.aow_3{background-image:url(cards.2x/aow_teutonic_04.jpg)}
+.card.aow_4{background-image:url(cards.2x/aow_teutonic_05.jpg)}
+.card.aow_5{background-image:url(cards.2x/aow_teutonic_06.jpg)}
+.card.aow_6{background-image:url(cards.2x/aow_teutonic_07.jpg)}
+.card.aow_7{background-image:url(cards.2x/aow_teutonic_08.jpg)}
+.card.aow_8{background-image:url(cards.2x/aow_teutonic_09.jpg)}
+.card.aow_9{background-image:url(cards.2x/aow_teutonic_10.jpg)}
+.card.aow_10{background-image:url(cards.2x/aow_teutonic_11.jpg)}
+.card.aow_11{background-image:url(cards.2x/aow_teutonic_12.jpg)}
+.card.aow_12{background-image:url(cards.2x/aow_teutonic_13.jpg)}
+.card.aow_13{background-image:url(cards.2x/aow_teutonic_14.jpg)}
+.card.aow_14{background-image:url(cards.2x/aow_teutonic_15.jpg)}
+.card.aow_15{background-image:url(cards.2x/aow_teutonic_16.jpg)}
+.card.aow_16{background-image:url(cards.2x/aow_teutonic_17.jpg)}
+.card.aow_17{background-image:url(cards.2x/aow_teutonic_18.jpg)}
+.card.aow_18{background-image:url(cards.2x/aow_teutonic_none.jpg)}
+.card.aow_19{background-image:url(cards.2x/aow_teutonic_none.jpg)}
+.card.aow_20{background-image:url(cards.2x/aow_teutonic_none.jpg)}
+.card.aow_21{background-image:url(cards.2x/aow_russian_01.jpg)}
+.card.aow_22{background-image:url(cards.2x/aow_russian_02.jpg)}
+.card.aow_23{background-image:url(cards.2x/aow_russian_03.jpg)}
+.card.aow_24{background-image:url(cards.2x/aow_russian_04.jpg)}
+.card.aow_25{background-image:url(cards.2x/aow_russian_05.jpg)}
+.card.aow_26{background-image:url(cards.2x/aow_russian_06.jpg)}
+.card.aow_27{background-image:url(cards.2x/aow_russian_07.jpg)}
+.card.aow_28{background-image:url(cards.2x/aow_russian_08.jpg)}
+.card.aow_29{background-image:url(cards.2x/aow_russian_09.jpg)}
+.card.aow_30{background-image:url(cards.2x/aow_russian_10.jpg)}
+.card.aow_31{background-image:url(cards.2x/aow_russian_11.jpg)}
+.card.aow_32{background-image:url(cards.2x/aow_russian_12.jpg)}
+.card.aow_33{background-image:url(cards.2x/aow_russian_13.jpg)}
+.card.aow_34{background-image:url(cards.2x/aow_russian_14.jpg)}
+.card.aow_35{background-image:url(cards.2x/aow_russian_15.jpg)}
+.card.aow_36{background-image:url(cards.2x/aow_russian_16.jpg)}
+.card.aow_37{background-image:url(cards.2x/aow_russian_17.jpg)}
+.card.aow_38{background-image:url(cards.2x/aow_russian_18.jpg)}
+.card.aow_39{background-image:url(cards.2x/aow_russian_none.jpg)}
+.card.aow_40{background-image:url(cards.2x/aow_russian_none.jpg)}
+.card.aow_41{background-image:url(cards.2x/aow_russian_none.jpg)}
+.card.cc_lord_0{background-image:url(cards.2x/cc_teutonic_andreas.jpg)}
+.card.cc_lord_1{background-image:url(cards.2x/cc_teutonic_heinrich.jpg)}
+.card.cc_lord_2{background-image:url(cards.2x/cc_teutonic_hermann.jpg)}
+.card.cc_lord_3{background-image:url(cards.2x/cc_teutonic_knud_and_abel.jpg)}
+.card.cc_lord_4{background-image:url(cards.2x/cc_teutonic_rudolf.jpg)}
+.card.cc_lord_5{background-image:url(cards.2x/cc_teutonic_yaroslav.jpg)}
+.card.cc_lord_6{background-image:url(cards.2x/cc_russian_aleksandr.jpg)}
+.card.cc_lord_7{background-image:url(cards.2x/cc_russian_andrey.jpg)}
+.card.cc_lord_8{background-image:url(cards.2x/cc_russian_domash.jpg)}
+.card.cc_lord_9{background-image:url(cards.2x/cc_russian_gavrilo.jpg)}
+.card.cc_lord_10{background-image:url(cards.2x/cc_russian_karelians.jpg)}
+.card.cc_lord_11{background-image:url(cards.2x/cc_russian_vladislav.jpg)}
+}
+
+@media (min-width: 2800px) {
+ main { display: grid; gap: 0 12px; }
+ main { grid-template-columns: 0 1275px auto 0; }
+ #sec_map { grid-row: 1; grid-column: 2 }
+ #sec_battle { grid-row: 2; grid-column: 2 }
+ #sec_lords { grid-row: 1 / 3; grid-column: 3 }
+}
+
+@media (min-width: 3200px) {
+ main { grid-template-columns: 36px 1275px auto 0; }
+}
+
+@media (min-width: 4100px) {
+ main { grid-template-columns: 0 1275px 1fr 1275px 0; }
+ #sec_map { grid-row: 1; grid-column: 2 }
+ #sec_lords { grid-row: 1; grid-column: 3 }
+ #sec_battle { grid-row: 1; grid-column: 4 }
+}
diff --git a/play.html b/play.html
new file mode 100644
index 0000000..cb1fd2b
--- /dev/null
+++ b/play.html
@@ -0,0 +1,182 @@
+<!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>NEVSKY</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="data.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="/nevsky/info/rules.html">Rules of Play</a>
+ <a class="menu_item" target="_blanK" href="/nevsky/info/playbook.html">Background Book</a>
+ <a class="menu_item" target="_blanK" href="/nevsky/info/pac.html">Reference Sheets</a>
+ <a class="menu_item" target="_blanK" href="/nevsky/info/cards.html">Arts of War</a>
+ <a class="menu_item" target="_blanK" href="/nevsky/info/lords.html">Lord Mats</a>
+ <div class="resign menu_separator"></div>
+ <div class="resign menu_item" onclick="confirm_resign()">Resign</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_Teutons">
+ <div class="role_name">
+ Teutons
+ <div class="role_held">0 Held</div>
+ <div class="role_user">-</div>
+ </div>
+ </div>
+ <div class="role" id="role_Russians">
+ <div class="role_name">
+ Russians
+ <div class="role_held">0 Held</div>
+ <div class="role_user">-</div>
+ </div>
+ </div>
+ <div class="card_info"><div id="command" class="card teutonic aow_back"></div></div>
+ </div>
+ <div id="log"></div>
+</aside>
+
+<main>
+
+<!-- MAP, TUCKED, EVENTS -->
+<section id="sec_map">
+
+<div id="mapwrap">
+<div id="map">
+
+<div id="veche"></div>
+<div class="box veche_label" id="veche_label_top"></div>
+<div class="box veche_label" id="veche_label_bottom"></div>
+<div class="veche_border" id="veche_border_w"></div>
+<div class="veche_border" id="veche_border_e"></div>
+<div class="veche_border" id="veche_border_sw"></div>
+<div class="veche_border" id="veche_border_se"></div>
+
+<div id="locales"></div>
+<div id="boxes"></div>
+
+<div id="pieces">
+ <div id="elr1" class="marker circle enemy_lords_removed teutonic hide"></div>
+ <div id="elr2" class="marker circle enemy_lords_removed russian hide"></div>
+ <div id="turn" class="hide marker circle turn levy t1"></div>
+ <div id="vp2" class="hide marker circle victory russian v0 stack"></div>
+ <div id="vp1" class="hide marker circle victory teutonic v0 stack"></div>
+ <div id="legate" class="hide"></div>
+ <div id="smerdi" class="box"></div>
+ <div id="castle11" class="hide marker rectangle castle teutonic"/></div>
+ <div id="castle12" class="hide marker rectangle castle teutonic"/></div>
+ <div id="castle21" class="hide marker rectangle castle russian"/></div>
+ <div id="castle22" class="hide marker rectangle castle russian"/></div>
+</div>
+
+</div>
+</div>
+
+<div class="tuck_under_map">
+<div id="capabilities1"></div>
+<div id="capabilities2"></div>
+</div>
+
+<div id="events_panel" class="panel hide">
+<div id="events_header" class="panel_header">Events</div>
+<div id="events" class="panel_body"></div>
+</div>
+</div>
+
+</section>
+
+<!-- BATTLE / ARTS OF WAR -->
+<section id="sec_battle">
+
+<div id="arts_of_war_panel" class="panel hide">
+<div id="arts_of_war_header" class="panel_header">Arts of War</div>
+<div id="arts_of_war" class="panel_body"></div>
+</div>
+
+<div id="battle_panel" class="panel hide">
+<div id="battle_header" class="panel_header">Battle</div>
+<div id="battle_grid">
+ <div id="grid_ga">
+ <div id="pursuit" class="marker rectangle pursuit russian hide"></div>
+ <div id="battle_walls1" class="siegeworks"></div>
+ <div id="battle_walls2" class="siegeworks"></div>
+ <div id="garrison"></div>
+ <div id="battle_walls3" class="siegeworks"></div>
+ <div id="battle_walls4" class="siegeworks"></div>
+ </div>
+ <div class="grid_array att" id="grid_a1"></div>
+ <div class="grid_array att" id="grid_a2"></div>
+ <div class="grid_array att" id="grid_a3"></div>
+ <div class="grid_array def" id="grid_d1"></div>
+ <div class="grid_array def" id="grid_d2"></div>
+ <div class="grid_array def" id="grid_d3"></div>
+ <div class="grid_array att" id="grid_rg1"></div>
+ <div class="grid_array att" id="grid_rg2"></div>
+ <div class="grid_array att" id="grid_rg3"></div>
+ <div class="siegeworks" id="grid_sw"></div>
+ <div class="grid_array def" id="grid_sa1"></div>
+ <div class="grid_array def" id="grid_sa2"></div>
+ <div class="grid_array def" id="grid_sa3"></div>
+</div>
+</div>
+
+<div id="reserves_panel" class="panel hide">
+<div id="reserves_header" class="panel_header">Reserves</div>
+<div id="reserves" class="panel_body"></div>
+</div>
+
+</section>
+
+<!-- LORD MATS, HAND, PLAN -->
+<section id="sec_lords">
+
+<div class="panel court_panel">
+<div id="court1_header" class="panel_header">Lords</div>
+<div id="court1" class="panel_body court_body"></div>
+</div>
+
+<div id="hand_panel" class="panel hide">
+<div id="hand_header" class="panel_header">Hand</div>
+<div id="hand" class="panel_body"></div>
+</div>
+</div>
+
+<div id="plan_panel" class="panel hide">
+<div id="plan_header" class="panel_header">Plan</div>
+<div id="plan" class="panel_body"></div>
+<div id="plan_actions" class="panel_body"></div>
+</div>
+
+<div class="panel court_panel">
+<div id="court2_header" class="panel_header">Lords</div>
+<div id="court2" class="panel_body court_body"></div>
+</div>
+
+</section>
+
+</main>
+
+<footer id="status"></footer>
+
+</body>
diff --git a/play.js b/play.js
new file mode 100644
index 0000000..b3e2f56
--- /dev/null
+++ b/play.js
@@ -0,0 +1,1943 @@
+"use strict"
+
+// TODO: show strikers and targets highlighting on battle mat?
+
+function toggle_pieces() {
+ document.getElementById("pieces").classList.toggle("hide")
+}
+
+// === COMMON LIBRARY ===
+
+function map_has(map, key) {
+ let a = 0
+ let b = (map.length >> 1) - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = map[m<<1]
+ if (key < x)
+ b = m - 1
+ else if (key > x)
+ a = m + 1
+ else
+ return true
+ }
+ return false
+}
+
+function map_get(map, key, missing) {
+ let a = 0
+ let b = (map.length >> 1) - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = map[m<<1]
+ if (key < x)
+ b = m - 1
+ else if (key > x)
+ a = m + 1
+ else
+ return map[(m<<1)+1]
+ }
+ return missing
+}
+
+function set_has(set, item) {
+ let a = 0
+ let b = set.length - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = set[m]
+ if (item < x)
+ b = m - 1
+ else if (item > x)
+ a = m + 1
+ else
+ return true
+ }
+ return false
+}
+
+// === CONSTANTS (matching those in rules.js) ===
+
+function find_lord(name) { return data.lords.findIndex((x) => x.name === name) }
+function find_card(name) { return data.cards.findIndex((x) => x.name === name) }
+
+const LORD_ANDREAS = find_lord("Andreas")
+const LORD_HERMANN = find_lord("Hermann")
+const LORD_ALEKSANDR = find_lord("Aleksandr")
+const LORD_ANDREY = find_lord("Andrey")
+const LORD_KNUD_ABEL = find_lord("Knud & Abel")
+const LORD_RUDOLF = find_lord("Rudolf")
+
+const first_p1_lord = 0
+const last_p1_lord = 5
+const first_p2_lord = 6
+const last_p2_lord = 11
+
+const first_p1_card = 0
+const last_p1_card = 20
+const first_p2_card = 21
+const last_p2_card = 41
+
+const first_p1_locale = 0
+const last_p1_locale = 23
+const first_p2_locale = 24
+const last_p2_locale = 52
+
+const R1 = find_card("R1")
+const R11 = find_card("R11")
+const R17 = find_card("R17")
+const T4 = find_card("T4")
+const T10 = find_card("T10")
+const T14 = find_card("T14")
+const EVENT_RUSSIAN_BRIDGE = R1
+const EVENT_TEUTONIC_BRIDGE = T4
+const EVENT_TEUTONIC_FIELD_ORGAN = T10
+const AOW_TEUTONIC_TREBUCHETS = T14
+const EVENT_RUSSIAN_VALDEMAR = R11
+const EVENT_RUSSIAN_DIETRICH_VON_GRUNINGEN = R17
+
+const A1 = 0, A2 = 1, A3 = 2, D1 = 3, D2 = 4, D3 = 5, SA1 = 6, SA2 = 7, SA3 = 8, RG1 = 9, RG2 = 10, RG3 = 11
+
+const KNIGHTS = 0, SERGEANTS = 1, LIGHT_HORSE = 2, ASIATIC_HORSE = 3, MEN_AT_ARMS = 4, MILITIA = 5, SERFS = 6
+const force_type_count = 7
+const force_action_name = [ "knights", "sergeants", "light_horse", "asiatic_horse", "men_at_arms", "militia", "serfs" ]
+const routed_force_action_name = [ "routed_knights", "routed_sergeants", "routed_light_horse", "routed_asiatic_horse", "routed_men_at_arms", "routed_militia", "routed_serfs" ]
+
+const COIN = 1
+const asset_type_count = 7
+const asset_action_name = [ "prov", "coin", "loot", "cart", "sled", "boat", "ship" ]
+const asset_type_x3 = [ 1, 1, 1, 0, 0, 0, 0 ]
+
+const VECHE = 100
+const SUMMER = 0, EARLY_WINTER = 1, LATE_WINTER = 2, RASPUTITSA = 3
+const SEASONS = [ null,
+ SUMMER, SUMMER, EARLY_WINTER, EARLY_WINTER, LATE_WINTER, LATE_WINTER, RASPUTITSA, RASPUTITSA,
+ SUMMER, SUMMER, EARLY_WINTER, EARLY_WINTER, LATE_WINTER, LATE_WINTER, RASPUTITSA, RASPUTITSA,
+ null ]
+
+const VASSAL_READY = 1
+const VASSAL_MUSTERED = 2
+const NOWHERE = -1
+const CALENDAR = 100
+const LEGATE_INDISPOSED = -2
+const LEGATE_ARRIVED = -1
+const GARRISON = 100
+
+// === ACTIONS ===
+
+function is_action(action, arg) {
+ if (arg === undefined)
+ return !!(view.actions && view.actions[action] === 1)
+ return !!(view.actions && view.actions[action] && set_has(view.actions[action], arg))
+}
+
+function on_action(evt) {
+ if (evt.button === 0) {
+ if (evt.target.my_id === undefined) {
+ send_action(evt.target.my_action)
+ if (evt.target.my_action_2)
+ send_action(evt.target.my_action_2)
+ } else {
+ send_action(evt.target.my_action, evt.target.my_id)
+ if (evt.target.my_action_2)
+ send_action(evt.target.my_action_2, evt.target.my_id)
+ }
+ }
+}
+
+function register_action(elt, action, id, action_2) {
+ elt.my_id = id
+ elt.my_action = action
+ elt.my_action_2 = action_2
+ elt.onmousedown = on_action
+}
+
+// === TOOLTIPS ===
+
+function register_tooltip(elt, focus, blur) {
+ if (typeof focus === "function")
+ elt.onmouseenter = focus
+ else
+ elt.onmouseenter = () => on_focus(focus)
+ if (blur)
+ elt.onmouseleave = blur
+ else
+ elt.onmouseleave = on_blur
+}
+
+function on_focus(text) {
+ document.getElementById("status").textContent = text
+}
+
+function on_blur() {
+ document.getElementById("status").textContent = ""
+}
+
+function get_locale_tip(id) {
+ let loc = data.locales[id]
+ let tip = loc.name
+ if (loc.name !== "Novgorod") {
+ if (loc.type === "traderoute")
+ tip += " - Trade Route"
+ else
+ tip += " - " + loc.type[0].toUpperCase() + loc.type.substring(1)
+ }
+ if (data.seaports.includes(id))
+ tip += " - Seaport"
+ let list = []
+ if (loc.name === "Adsel" || loc.name === "Fellin" || loc.name === "Leal" || loc.name === "Wenden")
+ list.push("Commandery")
+ if (loc.name === "Novgorod")
+ list.push("Archbishopric")
+ for (let lord = 0; lord < data.lords.length; ++lord) {
+ if (data.lords[lord].seats.includes(id))
+ list.push(data.lords[lord].name)
+ }
+ if (loc.name === "Pskov")
+ list.push("Yaroslav")
+ if (list.length > 0)
+ tip += " - " + list.join(", ")
+ return tip
+}
+
+function is_event_in_play(c) {
+ return set_has(view.events, c)
+}
+
+function on_focus_cylinder(evt) {
+ let lord = evt.target.my_id
+ let info = data.lords[lord]
+ let loc = view.pieces.locale[lord]
+ let tip = info.name
+
+ if (loc >= CALENDAR) {
+ if (lord !== LORD_ALEKSANDR)
+ tip += ` - ${info.fealty} Fealty`
+ tip += ` - ${info.service} Service`
+ }
+
+ if (lord === LORD_KNUD_ABEL)
+ if (is_event_in_play(EVENT_RUSSIAN_VALDEMAR))
+ tip += ` - No Muster because of Valdemar!`
+ if (lord === LORD_ANDREAS || lord === LORD_RUDOLF)
+ if (is_event_in_play(EVENT_RUSSIAN_DIETRICH_VON_GRUNINGEN))
+ tip += ` - No Muster because of Dietrich von Grüningen!`
+
+ on_focus(tip)
+}
+
+function on_focus_lord_service_marker(evt) {
+ let lord = evt.target.my_id
+ let info = data.lords[lord]
+ on_focus(`${info.full_name} - ${info.title}`)
+ if (expand_calendar !== view.pieces.service[lord]) {
+ expand_calendar = view.pieces.service[lord]
+ layout_calendar()
+ }
+}
+
+function on_blur_lord_service_marker(evt) {
+ let id = evt.target.my_id
+ on_blur(evt)
+ if (expand_calendar === view.pieces.service[id]) {
+ expand_calendar = -1
+ layout_calendar()
+ }
+}
+
+// === GAME STATE ===
+
+function current_season() {
+ return SEASONS[view.turn >> 1]
+}
+
+function max_plan_length() {
+ switch (current_season()) {
+ case SUMMER: return 6
+ case EARLY_WINTER: return 4
+ case LATE_WINTER: return 4
+ case RASPUTITSA: return 5
+ }
+}
+
+function is_p1_lord(lord) {
+ return lord >= first_p1_lord && lord <= last_p1_lord
+}
+
+function is_p2_lord(lord) {
+ return lord >= first_p2_lord && lord <= last_p2_lord
+}
+
+function is_lord_besieged(lord) {
+ let besieged = pack1_get(view.pieces.besieged, lord)
+ // show sallying lords as not besieged
+ if (view.battle && view.battle.array && view.battle.reserves.includes(lord))
+ return false
+ return besieged
+}
+
+function is_lord_on_left_or_right(lord) {
+ if (view.battle.array[A1] === lord) return true
+ if (view.battle.array[A3] === lord) return true
+ if (view.battle.array[D1] === lord) return true
+ if (view.battle.array[D3] === lord) return true
+ if (view.battle.array[SA1] === lord) return true
+ if (view.battle.array[SA3] === lord) return true
+ if (view.battle.array[RG1] === lord) return true
+ if (view.battle.array[RG3] === lord) return true
+ return false
+}
+
+function is_lord_ambushed(lord) {
+ if (view.battle) {
+ // ambush & 2 = attacker played ambush
+ // ambush & 1 = defender played ambush
+ if (view.battle.attacker === "Teutons") {
+ if ((view.battle.ambush & 1) && is_p1_lord(lord))
+ return is_lord_on_left_or_right(lord)
+ if ((view.battle.ambush & 2) && is_p2_lord(lord))
+ return is_lord_on_left_or_right(lord)
+ } else {
+ if ((view.battle.ambush & 1) && is_p2_lord(lord))
+ return is_lord_on_left_or_right(lord)
+ if ((view.battle.ambush & 2) && is_p1_lord(lord))
+ return is_lord_on_left_or_right(lord)
+ }
+ }
+ return false
+}
+
+function get_lord_moved(lord) {
+ return pack2_get(view.pieces.moved, lord)
+}
+
+function get_lord_forces(lord, n) {
+ return pack4_get(view.pieces.forces[lord], n)
+}
+
+function count_lord_all_forces(lord) {
+ return (
+ get_lord_forces(lord, KNIGHTS) +
+ get_lord_forces(lord, SERGEANTS) +
+ get_lord_forces(lord, LIGHT_HORSE) +
+ get_lord_forces(lord, ASIATIC_HORSE) +
+ get_lord_forces(lord, MEN_AT_ARMS) +
+ get_lord_forces(lord, MILITIA) +
+ get_lord_forces(lord, SERFS)
+ )
+}
+
+function is_p1_locale(loc) {
+ return loc >= first_p1_locale && loc <= last_p1_locale
+}
+
+function is_p2_locale(loc) {
+ return loc >= first_p2_locale && loc <= last_p2_locale
+}
+
+function count_vp1() {
+ let vp = view.pieces.elr1 << 1
+ vp += view.pieces.castles1.length << 1
+ for (let loc of view.pieces.conquered)
+ if (is_p2_locale(loc))
+ vp += data.locales[loc].vp << 1
+ for (let loc of view.pieces.ravaged)
+ if (is_p2_locale(loc))
+ vp += 1
+ return vp
+}
+
+function count_vp2() {
+ let vp = view.pieces.elr2 << 1
+ vp += view.pieces.veche_vp << 1
+ vp += view.pieces.castles2.length << 1
+ for (let loc of view.pieces.conquered)
+ if (is_p1_locale(loc))
+ vp += data.locales[loc].vp << 1
+ for (let loc of view.pieces.ravaged)
+ if (is_p1_locale(loc))
+ vp += 1
+ return vp
+}
+
+function get_lord_locale(lord) {
+ return view.pieces.locale[lord]
+}
+
+function is_lord_on_map(lord) {
+ let loc = get_lord_locale(lord)
+ return loc !== NOWHERE && loc < CALENDAR
+}
+
+function is_vassal_ready(vassal) {
+ return view.pieces.vassals[vassal] === VASSAL_READY
+}
+
+function is_vassal_mustered(vassal) {
+ return view.pieces.vassals[vassal] === VASSAL_MUSTERED
+}
+
+function is_legate_selected() {
+ return player === "Teutons" && !!view.pieces.legate_selected
+}
+
+function is_levy_phase() {
+ return (view.turn & 1) === 0
+}
+
+function is_upper_lord(lord) {
+ return map_has(view.pieces.lieutenants, lord)
+}
+
+function is_lower_lord(lord) {
+ for (let i = 1; i < view.pieces.lieutenants.length; i += 2)
+ if (view.pieces.lieutenants[i] === lord)
+ return true
+ return false
+}
+
+function get_lower_lord(upper) {
+ return map_get(view.pieces.lieutenants, upper, -1)
+}
+
+function is_lord_in_battle(lord) {
+ if (view.battle && view.battle.array) {
+ for (let i = 0; i < 12; ++i)
+ if (view.battle.array[i] === lord)
+ return true
+ if (view.battle.reserves.includes(lord))
+ return true
+ }
+ return false
+}
+
+function is_lord_command(ix) {
+ return view.command === ix
+}
+
+function is_lord_selected(ix) {
+ if (view.who >= 0)
+ return ix === view.who
+ if (view.group)
+ return view.group.includes(ix)
+ return false
+}
+
+function is_town_locale(loc) {
+ return data.locales[loc].type === "town"
+}
+
+function has_castle_marker(loc) {
+ return (
+ set_has(view.pieces.castles1, loc) ||
+ set_has(view.pieces.castles2, loc)
+ )
+}
+
+function is_castle(loc) {
+ return data.locales[loc].type === "castle" || has_castle_marker(loc)
+}
+
+function is_bishopric(loc) {
+ return data.locales[loc].type === "bishopric"
+}
+
+function has_walls(loc) {
+ return set_has(view.pieces.walls, loc)
+}
+
+function lord_has_unrouted_units(lord) {
+ return view.pieces.forces[lord] !== 0
+}
+
+function get_lord_capability(lord, n) {
+ return view.pieces.capabilities[(lord << 1) + n]
+}
+
+function lord_has_capability_card(lord, c) {
+ let name = data.cards[c].capability
+ let c1 = get_lord_capability(lord, 0)
+ if (c1 >= 0 && data.cards[c1].capability === name)
+ return true
+ let c2 = get_lord_capability(lord, 1)
+ if (c2 >= 0 && data.cards[c2].capability === name)
+ return true
+ return false
+}
+
+function lord_has_capability(lord, card_or_list) {
+ if (Array.isArray(card_or_list)) {
+ for (let card of card_or_list)
+ if (lord_has_capability_card(lord, card))
+ return true
+ return false
+ }
+ return lord_has_capability_card(lord, card_or_list)
+}
+
+function attacker_has_trebuchets() {
+ if (view.battle.attacker === "Teutons") {
+ for (let lord = first_p1_lord; lord <= last_p1_lord; ++lord) {
+ if (get_lord_locale(lord) === view.battle.where && lord_has_unrouted_units(lord)) {
+ if (lord_has_capability(lord, AOW_TEUTONIC_TREBUCHETS))
+ return true
+ }
+ }
+ }
+ return false
+}
+
+function count_siege_markers(loc) {
+ return map_get(view.pieces.sieges, loc, 0)
+}
+
+// === BUILD UI ===
+
+const original_boxes = {
+ "way crossroads": [ 375, 1179, 116, 37 ],
+ "way wirz": [ 324, 1132, 44, 88 ],
+ "way peipus-east": [ 558, 1049, 55, 120 ],
+ "way peipus-north": [ 513, 958, 90, 57 ],
+ "calendar summer box1": [ 10, 42, 150, 231 ],
+ "calendar summer box2": [ 163, 42, 150, 231 ],
+ "calendar winter box3": [ 328, 42, 150, 231 ],
+ "calendar winter box4": [ 481, 42, 150, 231 ],
+ "calendar winter box5": [ 647, 42, 150, 231 ],
+ "calendar winter box6": [ 799, 42, 150, 231 ],
+ "calendar rasputitsa box7": [ 965, 42, 150, 231 ],
+ "calendar rasputitsa box8": [ 1118, 42, 150, 231 ],
+ "calendar summer box9": [ 10, 280, 150, 231 ],
+ "calendar summer box10": [ 163, 280, 150, 231 ],
+ "calendar winter box11": [ 328, 280, 150, 231 ],
+ "calendar winter box12": [ 481, 280, 150, 231 ],
+ "calendar winter box13": [ 647, 280, 150, 231 ],
+ "calendar winter box14": [ 799, 280, 150, 231 ],
+ "calendar rasputitsa box15": [ 965, 280, 150, 231 ],
+ "calendar rasputitsa box16": [ 1118, 280, 150, 231 ],
+ "calendar box0": [ 2, 16, 316, 22 ],
+ "calendar box17": [ 957, 514, 316, 22 ],
+}
+
+const calendar_xy = [
+ [ 10, 2 ],
+ [ 10, 42 ],
+ [ 162, 42 ],
+ [ 328, 42 ],
+ [ 480, 42 ],
+ [ 646, 42 ],
+ [ 799, 42 ],
+ [ 965, 42 ],
+ [ 1117, 42 ],
+ [ 10, 280 ],
+ [ 162, 280 ],
+ [ 328, 280 ],
+ [ 480, 280 ],
+ [ 646, 280 ],
+ [ 799, 280 ],
+ [ 965, 280 ],
+ [ 1117, 280 ],
+ [ 1115, 517 ],
+]
+
+const locale_xy = []
+
+let expand_calendar = -1
+
+const ui = {
+ locale: [],
+ locale_name: [],
+ locale_markers: [],
+ lord_cylinder: [],
+ lord_service: [],
+ lord_mat: [],
+ lord_buttons: [],
+ vassal_service: [],
+ forces: [],
+ routed: [],
+ assets: [],
+ ready_vassals: [],
+ mustered_vassals: [],
+ lord_capabilities: [],
+ lord_events: [],
+ lord_moved1: [],
+ lord_moved2: [],
+ lord_feed_x2: [],
+ cards: [],
+ boxes: {},
+ ways: [],
+ smerdi: document.getElementById("smerdi"),
+ legate: document.getElementById("legate"),
+ veche: document.getElementById("veche"),
+
+ plan_panel: document.getElementById("plan_panel"),
+ plan: document.getElementById("plan"),
+ plan_actions: document.getElementById("plan_actions"),
+ plan_cards: [],
+ plan_action_cards: [],
+
+ arts_of_war_panel: document.getElementById("arts_of_war_panel"),
+ arts_of_war: document.getElementById("arts_of_war"),
+
+ reserves_panel: document.getElementById("reserves_panel"),
+ reserves: document.getElementById("reserves"),
+
+ events_panel: document.getElementById("events_panel"),
+ events: document.getElementById("events"),
+
+ hand_panel: document.getElementById("hand_panel"),
+ hand: document.getElementById("hand"),
+
+ held1: document.querySelector("#role_Teutons .role_held"),
+ held2: document.querySelector("#role_Russians .role_held"),
+
+ capabilities1: document.getElementById("capabilities1"),
+ capabilities2: document.getElementById("capabilities2"),
+ command: document.getElementById("command"),
+ turn: document.getElementById("turn"),
+ elr1: document.getElementById("elr1"),
+ elr2: document.getElementById("elr2"),
+ vp1: document.getElementById("vp1"),
+ vp2: document.getElementById("vp2"),
+ court1_header: document.getElementById("court1_header"),
+ court2_header: document.getElementById("court2_header"),
+ court1: document.getElementById("court1"),
+ court2: document.getElementById("court2"),
+ garrison: document.getElementById("garrison"),
+ battle_walls: [
+ document.getElementById("battle_walls1"),
+ document.getElementById("battle_walls2"),
+ document.getElementById("battle_walls3"),
+ document.getElementById("battle_walls4"),
+ ],
+ battle_siegeworks: document.getElementById("grid_sw"),
+ battle_panel: document.getElementById("battle_panel"),
+ battle_header: document.getElementById("battle_header"),
+ pursuit: document.getElementById("pursuit"),
+ battle_grid: document.getElementById("battle_grid"),
+ battle_grid_array: [
+ document.getElementById("grid_a1"),
+ document.getElementById("grid_a2"),
+ document.getElementById("grid_a3"),
+ document.getElementById("grid_d1"),
+ document.getElementById("grid_d2"),
+ document.getElementById("grid_d3"),
+ document.getElementById("grid_sa1"),
+ document.getElementById("grid_sa2"),
+ document.getElementById("grid_sa3"),
+ document.getElementById("grid_rg1"),
+ document.getElementById("grid_rg2"),
+ document.getElementById("grid_rg3"),
+ ],
+ castles: [
+ document.getElementById("castle11"),
+ document.getElementById("castle12"),
+ document.getElementById("castle21"),
+ document.getElementById("castle22"),
+ ],
+}
+
+let locale_layout = []
+let calendar_layout_service = []
+let calendar_layout_cylinder = []
+
+function clean_name(name) {
+ return name.toLowerCase().replaceAll("&", "and").replaceAll(" ", "_")
+}
+
+function build_div(parent, className) {
+ let e = document.createElement("div")
+ e.className = className
+ if (parent)
+ parent.appendChild(e)
+ return e
+}
+
+function build_lord_mat(lord, ix, side, name) {
+ let mat = build_div(null, `mat ${side} ${name}`)
+ let bg = build_div(mat, "background")
+ ui.forces[ix] = build_div(bg, "forces")
+ ui.routed[ix] = build_div(bg, "routed")
+ ui.assets[ix] = build_div(bg, "assets")
+ ui.ready_vassals[ix] = build_div(bg, "ready_vassals")
+ ui.mustered_vassals[ix] = build_div(bg, "mustered_vassals")
+ ui.lord_buttons[ix] = build_div(bg, "shield")
+ ui.lord_capabilities[ix] = build_div(mat, "capabilities")
+ ui.lord_events[ix] = build_div(mat, "events")
+ ui.lord_moved1[ix] = build_div(mat, "marker square moved_fought one hide")
+ ui.lord_moved2[ix] = build_div(mat, "marker square moved_fought two hide")
+ ui.lord_feed_x2[ix] = build_div(mat, "marker small feed_x2")
+ ui.lord_mat[ix] = mat
+ register_action(ui.lord_buttons[ix], "lord", ix)
+}
+
+function build_card(side, c) {
+ let card = ui.cards[c] = document.createElement("div")
+ card.className = `card ${side} aow_${c}`
+ register_action(card, "card", c)
+}
+
+function build_plan() {
+ let elt
+ for (let i = 0; i < 6; ++i) {
+ elt = document.createElement("div")
+ elt.className = "hide"
+ ui.plan_cards.push(elt)
+ ui.plan.appendChild(elt)
+ }
+ for (let lord = 0; lord < 12; ++lord) {
+ let side = lord < 6 ? "teutonic" : "russian"
+ elt = document.createElement("div")
+ elt.className = `card ${side} cc_lord_${lord}`
+ register_action(elt, "plan", lord)
+ ui.plan_action_cards.push(elt)
+ ui.plan_actions.appendChild(elt)
+ }
+
+ ui.plan_action_pass_p1 = elt = document.createElement("div")
+ elt.className = `card teutonic cc_pass`
+ register_action(elt, "plan", -1)
+ ui.plan_actions.appendChild(elt)
+
+ ui.plan_action_pass_p2 = elt = document.createElement("div")
+ elt.className = `card russian cc_pass`
+ register_action(elt, "plan", -1)
+ ui.plan_actions.appendChild(elt)
+}
+
+function build_way(name, sel) {
+ let way = data.ways.findIndex(w => w.name === name)
+ ui.ways[way] = document.querySelector(sel)
+ register_action(ui.ways[way], "way", way)
+}
+
+const locale_size = {
+ region: [ 88, 56 ],
+ town: [ 80, 72 ],
+ traderoute: [ 90, 54 ],
+ fort: [ 96, 54 ],
+ castle: [ 96, 56 ],
+ city: [ 126, 80 ],
+ bishopric: [ 106, 72 ],
+ novgorod: [ 144, 86 ],
+}
+
+function build_map() {
+ for (let i = 0; i < data.locales.length; ++i)
+ locale_layout[i] = []
+
+ data.locales.forEach((locale, ix) => {
+ let region = clean_name(locale.region)
+ let { x, y, w, h } = locale.box
+ let xc = Math.round(x + w / 2)
+ let yc = Math.round(y + h / 2)
+ let e
+
+ switch (locale.type) {
+ case "town":
+ locale_xy[ix] = [ xc, y - 24 ]
+ w = locale_size.town[0]
+ h = locale_size.town[1]
+ x = xc - w/2
+ y = y - h + 16
+ break
+ case "region":
+ xc += 2
+ yc -= 3
+ locale_xy[ix] = [ xc, yc - 24 ]
+ w = locale_size.region[0]
+ h = locale_size.region[1]
+ x = xc - w/2
+ y = yc - h/2
+ break
+ default:
+ locale_xy[ix] = [ xc, y - 36 ]
+ break
+ }
+
+ // Main Area
+ e = ui.locale[ix] = document.createElement("div")
+ e.className = "locale " + locale.type + " " + region
+ if (locale.type !== "region" && locale.type !== "town") {
+ let ew = locale_size[locale.type][0]
+ let eh = locale_size[locale.type][1]
+ e.style.top = (y - eh) + "px"
+ e.style.left = (xc - ew/2) + "px"
+ e.style.width = (ew) + "px"
+ e.style.height = (eh) + "px"
+ } else {
+ e.style.left = x + "px"
+ e.style.top = y + "px"
+ e.style.width = w + "px"
+ e.style.height = h + "px"
+ }
+ register_action(e, "locale", ix, "laden_march")
+ register_tooltip(e, get_locale_tip(ix))
+ document.getElementById("locales").appendChild(e)
+
+ // Name Plate
+ if (locale.type !== 'region' && locale.type !== 'town') {
+ e = ui.locale_name[ix] = document.createElement("div")
+ e.className = "locale_name " + locale.type + " " + region
+ e.style.left = x + "px"
+ e.style.top = y + "px"
+ e.style.width = w + "px"
+ e.style.height = h + "px"
+ register_action(e, "locale", ix, "laden_march")
+ register_tooltip(e, get_locale_tip(ix))
+ document.getElementById("locales").appendChild(e)
+ }
+
+ // Locale Markers
+ e = ui.locale_markers[ix] = document.createElement("div")
+ e.className = "locale_markers " + locale.type + " " + region
+ x = locale_xy[ix][0] - 196/2
+ y = locale_xy[ix][1] + 36
+ e.style.top = y + "px"
+ e.style.left = x + "px"
+ e.style.width = 196 + "px"
+ document.getElementById("pieces").appendChild(e)
+ })
+
+ data.lords.forEach((lord, ix) => {
+ let e = ui.lord_cylinder[ix] = document.createElement("div")
+ e.className = "cylinder lord " + clean_name(lord.side) + " " + clean_name(lord.name) + " hide"
+ register_action(e, "lord", ix)
+ register_tooltip(e, on_focus_cylinder)
+ document.getElementById("pieces").appendChild(e)
+
+ e = ui.lord_service[ix] = document.createElement("div")
+ e.className = "service_marker lord image" + lord.image + " " + clean_name(lord.side) + " " + clean_name(lord.name) + " hide"
+ register_action(e, "service", ix, "service_bad")
+ register_tooltip(e, on_focus_lord_service_marker, on_blur_lord_service_marker)
+ document.getElementById("pieces").appendChild(e)
+
+ build_lord_mat(lord, ix, clean_name(lord.side), clean_name(lord.name))
+ })
+
+ data.vassals.forEach((vassal, ix) => {
+ let lord = data.lords[vassal.lord]
+ let e = ui.vassal_service[ix] = document.createElement("div")
+ e.className = "service_marker vassal image" + vassal.image + " " + clean_name(lord.side) + " " + clean_name(vassal.name) + " hide"
+ register_action(e, "vassal", ix)
+ register_tooltip(e, data.vassals[ix].name)
+ document.getElementById("pieces").appendChild(e)
+ })
+
+ register_action(ui.legate, "legate")
+ register_tooltip(ui.legate, "William of Modena")
+
+ register_action(ui.veche, "veche")
+
+ for (let name in original_boxes) {
+ let x = original_boxes[name][0]
+ let y = original_boxes[name][1]
+ let w = original_boxes[name][2] - 8
+ let h = original_boxes[name][3] - 8
+ let e = ui.boxes[name] = document.createElement("div")
+ e.className = "box " + name
+ e.style.left = x + "px"
+ e.style.top = y + "px"
+ e.style.width = w + "px"
+ e.style.height = h + "px"
+ document.getElementById("boxes").appendChild(e)
+ }
+
+ ui.calendar = [
+ document.querySelector(".calendar.box0"),
+ document.querySelector(".calendar.box1"),
+ document.querySelector(".calendar.box2"),
+ document.querySelector(".calendar.box3"),
+ document.querySelector(".calendar.box4"),
+ document.querySelector(".calendar.box5"),
+ document.querySelector(".calendar.box6"),
+ document.querySelector(".calendar.box7"),
+ document.querySelector(".calendar.box8"),
+ document.querySelector(".calendar.box9"),
+ document.querySelector(".calendar.box10"),
+ document.querySelector(".calendar.box11"),
+ document.querySelector(".calendar.box12"),
+ document.querySelector(".calendar.box13"),
+ document.querySelector(".calendar.box14"),
+ document.querySelector(".calendar.box15"),
+ document.querySelector(".calendar.box16"),
+ document.querySelector(".calendar.box17")
+ ]
+
+ for (let i = 0; i <= 17; ++i)
+ register_action(ui.calendar[i], "calendar", i)
+
+ build_way("Crossroads", ".way.crossroads")
+ build_way("Peipus E", ".way.peipus-east")
+ build_way("Peipus W", ".way.peipus-north")
+ build_way("Wirz", ".way.wirz")
+
+ build_plan()
+
+ register_action(ui.garrison, "garrison")
+ for (let i = 0; i < 12; ++i)
+ register_action(ui.battle_grid_array[i], "array", i)
+
+ for (let c = first_p1_card; c <= last_p1_card; ++c)
+ build_card("teutonic", c)
+ for (let c = first_p2_card; c <= last_p2_card; ++c)
+ build_card("russian", c)
+}
+
+// === UPDATE UI ===
+
+let used_cache = {}
+let unused_cache = {}
+
+function get_cached_element(className, action, id) {
+ let key = className
+ if (action !== undefined)
+ key += "/" + action + "/" + id
+ if (!(key in unused_cache)) {
+ unused_cache[key] = []
+ used_cache[key] = []
+ }
+ if (unused_cache[key].length > 0) {
+ let elt = unused_cache[key].pop()
+ used_cache[key].push(elt)
+ return elt
+ }
+ let elt = document.createElement("div")
+ elt.className = className
+ used_cache[key].push(elt)
+ if (action !== undefined)
+ register_action(elt, action, id)
+ return elt
+}
+
+function restart_cache() {
+ for (let k in used_cache) {
+ let u = used_cache[k]
+ let uu = unused_cache[k]
+ while (u.length > 0)
+ uu.push(u.pop())
+ }
+}
+
+function update_current_card_display() {
+ if (typeof view.what === "number" && view.what >= 0) {
+ if (view.what <= first_p1_card)
+ ui.command.className = `card teutonic aow_${view.what}`
+ else
+ ui.command.className = `card russian aow_${view.what}`
+ } else if ((view.turn & 1) === 0) {
+ if (player === "Russians")
+ ui.command.className = `card russian aow_back`
+ else
+ ui.command.className = `card teutonic aow_back`
+ } else if (view.command < 0) {
+ if (player === "Russians")
+ ui.command.className = `card russian cc_back`
+ else
+ ui.command.className = `card teutonic cc_back`
+ } else {
+ if (view.command < 6)
+ ui.command.className = `card russian cc_lord_${view.command}`
+ else
+ ui.command.className = `card teutonic cc_lord_${view.command}`
+ }
+}
+
+function layout_locale_item(loc, e, is_upper) {
+ locale_layout[loc].push([e, is_upper])
+ e.classList.toggle("lieutenant", is_upper)
+}
+
+function layout_locale_cylinders(loc) {
+ let [xc, yc] = locale_xy[loc]
+
+ let n = 0
+ for (let [e,is_upper] of locale_layout[loc])
+ if (!is_upper)
+ ++n
+
+ let wrap = 3
+ switch (data.locales[loc].type) {
+ case "region": wrap = 2; break
+ case "town": wrap = 2; break
+ case "novgorod": wrap = 4; break
+ }
+
+ let m = Math.floor((n-1) / wrap)
+ let i = 0
+ let k = 0
+ for (let [e,is_upper] of locale_layout[loc]) {
+ let nn = n
+ if (nn > wrap)
+ nn = wrap
+ let x = xc + (i - (nn-1)/2) * 44 + k * 22
+ let y = yc + (k * 32) - m * 32
+ let z = 1
+ if (is_upper) {
+ y -= 18
+ z = 2
+ }
+ if (e === ui.legate) {
+ y -= 16
+ z = 3
+ }
+ e.style.top = (y - 23) + "px"
+ e.style.left = (x - 23) + "px"
+ e.style.zIndex = z
+ if (!is_upper)
+ ++i
+ if (i >= wrap) {
+ i = 0
+ ++k
+ }
+ }
+}
+
+function layout_calendar() {
+ for (let loc = 0; loc < 18; ++loc) {
+ let [cx, cy] = calendar_xy[loc]
+ let list = calendar_layout_service[loc]
+ for (let i = 0; i < list.length; ++i) {
+ let e = list[i]
+ let x = cx, y = cy, z = 60 - i
+ let d = 46 - 24
+ if (loc === expand_calendar) {
+ d = 46
+ z += 100
+ }
+ if (loc === 0) {
+ x += -6 + 46 * i
+ z = 1
+ } else if (loc === 17) {
+ x += 60 - 46 * i
+ z = 60 - i
+ } else {
+ x += (146 - 94 - 2)
+ y += (227 - 46 - 2) - i * d
+ }
+ e.style.top = y + "px"
+ e.style.left = x + "px"
+ e.style.zIndex = z
+ }
+
+ list = calendar_layout_cylinder[loc]
+ for (let i = 0; i < list.length; ++i) {
+ let e = list[i]
+ let x = cx, y = cy, z = 61
+ if (loc === 0) {
+ let k = calendar_layout_service[0].length
+ if (k > 0)
+ x += k * 46 + 46 + i * 46
+ else
+ x += 0 + i * 46
+ } else if (loc === 17) {
+ let k = calendar_layout_service[17].length
+ if (k > 0)
+ x += 60 - k * 46 - i * 46
+ else
+ x += 60 + i * 46
+ } else if (loc === 1) {
+ x += 46 + (i%2) * 46 + (i/2|0) * 12
+ y += 66 + (i/2|0) * 36
+ } else {
+ x += 6 + (i%3) * 46 + (i/3|0) * 24
+ y += 66 + (i/3|0) * 36
+ }
+ e.style.top = y + "px"
+ e.style.left = x + "px"
+ e.style.zIndex = z
+ }
+ }
+}
+
+function add_force(parent, type, lord, routed) {
+ let elt
+ if (routed) {
+ if (is_action(routed_force_action_name[type], lord))
+ elt = get_cached_element("action unit " + force_action_name[type], routed_force_action_name[type], lord)
+ else
+ elt = get_cached_element("unit " + force_action_name[type], routed_force_action_name[type], lord)
+ } else {
+ if (is_action(force_action_name[type], lord))
+ elt = get_cached_element("action unit " + force_action_name[type], force_action_name[type], lord)
+ else
+ elt = get_cached_element("unit " + force_action_name[type], force_action_name[type], lord)
+ }
+ parent.appendChild(elt)
+}
+
+function add_asset(parent, type, n, lord) {
+ let elt
+ if (lord === VECHE) {
+ if (is_action("veche_coin"))
+ elt = get_cached_element("action asset " + asset_action_name[type] + " x"+n, "veche_coin", undefined)
+ else
+ elt = get_cached_element("asset " + asset_action_name[type] + " x"+n)
+ } else {
+ if (is_action(asset_action_name[type], lord))
+ elt = get_cached_element("action asset " + asset_action_name[type] + " x"+n, asset_action_name[type], lord)
+ else
+ elt = get_cached_element("asset " + asset_action_name[type] + " x"+n)
+ }
+ parent.appendChild(elt)
+}
+
+function add_veche_vp(parent) {
+ parent.appendChild(get_cached_element("marker square conquered russian"))
+}
+
+function update_forces(parent, forces, lord_ix, routed) {
+ parent.replaceChildren()
+ for (let i = 0; i < force_type_count; ++i) {
+ let n = pack4_get(forces, i)
+ for (let k = 0; k < n; ++k) {
+ add_force(parent, i, lord_ix, routed)
+ }
+ }
+}
+
+function update_assets(id, parent, assets) {
+ parent.replaceChildren()
+ for (let i = 0; i < asset_type_count; ++i) {
+ let n = pack4_get(assets, i)
+ while (n >= 4) {
+ add_asset(parent, i, 4, id)
+ n -= 4
+ }
+ if (asset_type_x3[i]) {
+ while (n >= 3) {
+ add_asset(parent, i, 3, id)
+ n -= 3
+ }
+ }
+ while (n >= 2) {
+ add_asset(parent, i, 2, id)
+ n -= 2
+ }
+ while (n >= 1) {
+ add_asset(parent, i, 1, id)
+ n -= 1
+ }
+ }
+}
+
+function update_vassals(ready_parent, mustered_parent, lord_ix) {
+ for (let v of data.lords[lord_ix].vassals) {
+ let e = ui.vassal_service[v]
+ if (is_vassal_ready(v)) {
+ e.classList.remove("hide")
+ ready_parent.appendChild(e)
+ }
+ else if (is_vassal_mustered(v)) {
+ e.classList.remove("hide")
+ mustered_parent.appendChild(e)
+ }
+ else {
+ e.classList.add("hide")
+ }
+ e.classList.toggle("action", is_action("vassal", v))
+ }
+}
+
+function update_lord_mat(ix) {
+ if (view.reveal & (1 << ix)) {
+ ui.lord_mat[ix].classList.remove("hidden")
+ update_assets(ix, ui.assets[ix], view.pieces.assets[ix])
+ update_vassals(ui.ready_vassals[ix], ui.mustered_vassals[ix], ix)
+ update_forces(ui.forces[ix], view.pieces.forces[ix], ix, false)
+ update_forces(ui.routed[ix], view.pieces.routed[ix], ix, true)
+ ui.lord_feed_x2[ix].classList.toggle("hide", count_lord_all_forces(ix) <= 6)
+ } else {
+ ui.lord_mat[ix].classList.add("hidden")
+ ui.assets[ix].replaceChildren()
+ ui.ready_vassals[ix].replaceChildren()
+ ui.mustered_vassals[ix].replaceChildren()
+ ui.forces[ix].replaceChildren()
+ ui.routed[ix].replaceChildren()
+ ui.lord_moved1[ix].classList.add("hide")
+ ui.lord_moved2[ix].classList.add("hide")
+ ui.lord_feed_x2[ix].classList.add("hide")
+ }
+ let m = get_lord_moved(ix)
+ ui.lord_moved1[ix].classList.toggle("hide", is_levy_phase() || (m !== 1 && m !== 2))
+ ui.lord_moved2[ix].classList.toggle("hide", is_levy_phase() || (m !== 2))
+}
+
+function update_lord(ix) {
+ let locale = view.pieces.locale[ix]
+ let service = view.pieces.service[ix]
+ if (locale < 0) {
+ ui.lord_cylinder[ix].classList.add("hide")
+ ui.lord_service[ix].classList.add("hide")
+ ui.lord_mat[ix].classList.remove("action")
+ return
+ }
+ if (locale < 100) {
+ calendar_layout_service[service].push(ui.lord_service[ix])
+
+ if (!is_lower_lord(ix)) {
+ if (is_upper_lord(ix)) {
+ let lo = get_lower_lord(ix)
+ if (view.pieces.locale[lo] === locale) {
+ layout_locale_item(locale, ui.lord_cylinder[ix], 1)
+ layout_locale_item(locale, ui.lord_cylinder[lo], 0)
+ } else {
+ layout_locale_item(locale, ui.lord_cylinder[ix], 0)
+ }
+ } else {
+ layout_locale_item(locale, ui.lord_cylinder[ix], 0)
+ }
+ }
+
+ ui.lord_cylinder[ix].classList.remove("hide")
+ ui.lord_service[ix].classList.remove("hide")
+ update_lord_mat(ix)
+ } else {
+ let t = locale - 100
+ if (t > 17) t = 17
+ calendar_layout_cylinder[t].push(ui.lord_cylinder[ix])
+ ui.lord_cylinder[ix].classList.remove("hide")
+ ui.lord_service[ix].classList.add("hide")
+ }
+ ui.lord_cylinder[ix].classList.toggle("besieged", is_lord_besieged(ix))
+ ui.lord_buttons[ix].classList.toggle("action", is_action("lord", ix))
+ ui.lord_cylinder[ix].classList.toggle("action", is_action("lord", ix))
+ ui.lord_service[ix].classList.toggle("action", is_action("service", ix) || is_action("service_bad", ix))
+ ui.lord_service[ix].classList.toggle("bad", is_action("service_bad", ix))
+
+ if (ix === LORD_HERMANN)
+ ui.lord_cylinder[ix].classList.toggle("marshal", !is_lord_on_map(LORD_ANDREAS))
+ if (ix === LORD_ANDREY)
+ ui.lord_cylinder[ix].classList.toggle("marshal", !is_lord_on_map(LORD_ALEKSANDR))
+
+ ui.lord_cylinder[ix].classList.toggle("selected", is_lord_selected(ix))
+ ui.lord_service[ix].classList.toggle("selected", is_lord_selected(ix))
+ ui.lord_mat[ix].classList.toggle("selected", is_lord_selected(ix))
+
+ ui.lord_cylinder[ix].classList.toggle("command", is_lord_command(ix))
+ ui.lord_mat[ix].classList.toggle("command", is_lord_command(ix))
+
+ ui.lord_mat[ix].classList.toggle("besieged", is_lord_besieged(ix))
+ ui.lord_mat[ix].classList.toggle("ambushed", is_lord_ambushed(ix))
+}
+
+function update_legate() {
+ if (view.pieces.legate === LEGATE_INDISPOSED) {
+ ui.legate.classList.add("hide")
+ } else {
+ ui.legate.classList.remove("hide")
+ ui.legate.classList.toggle("action", is_action("legate"))
+ ui.legate.classList.toggle("selected", is_legate_selected())
+ if (view.pieces.legate === LEGATE_ARRIVED) {
+ ui.legate.style.top = "1356px"
+ ui.legate.style.left = "24px"
+ } else {
+ layout_locale_item(view.pieces.legate, ui.legate, 0)
+ }
+ }
+}
+
+function update_smerdi() {
+ ui.smerdi.replaceChildren()
+ for (let i = 0; i < view.pieces.smerdi; ++i)
+ ui.smerdi.appendChild(get_cached_element("unit serfs"))
+}
+
+function update_veche() {
+ ui.veche.replaceChildren()
+
+ let n = view.pieces.veche_coin
+ while (n >= 4) {
+ add_asset(ui.veche, COIN, 4, VECHE)
+ n -= 4
+ }
+ while (n >= 3) {
+ add_asset(ui.veche, COIN, 3, VECHE)
+ n -= 3
+ }
+ while (n >= 2) {
+ add_asset(ui.veche, COIN, 2, VECHE)
+ n -= 2
+ }
+ while (n >= 1) {
+ add_asset(ui.veche, COIN, 1, VECHE)
+ n -= 1
+ }
+
+ for (let i = 0; i < view.pieces.veche_vp; ++i)
+ add_veche_vp(ui.veche)
+}
+
+function update_castle(elt, loc) {
+ if (loc === undefined) {
+ elt.classList.toggle("hide", true)
+ } else {
+ elt.classList.toggle("hide", false)
+ let [xc, yc] = locale_xy[loc]
+ if (is_town_locale(loc)) {
+ elt.style.top = (yc - 26) + "px"
+ elt.style.left = (xc - 49) + "px"
+ } else {
+ elt.style.top = (yc - 15) + "px"
+ elt.style.left = (xc - 49) + "px"
+ }
+ elt.style.zIndex = 0
+ }
+}
+
+function is_teutonic_siege_marker(loc) {
+ if (set_has(view.pieces.castles2, loc))
+ return true
+ if (set_has(view.pieces.castles1, loc))
+ return false
+ if (is_p1_locale(loc))
+ return set_has(view.pieces.conquered, loc)
+ else
+ return !set_has(view.pieces.conquered, loc)
+}
+
+function update_locale(loc) {
+ layout_locale_cylinders(loc)
+
+ ui.locale[loc].classList.toggle("action", is_action("locale", loc) || is_action("laden_march", loc))
+ ui.locale[loc].classList.toggle("laden", is_action("laden_march", loc))
+ ui.locale[loc].classList.toggle("supply_path", !!(view.supply && view.supply[0] === loc))
+ ui.locale[loc].classList.toggle("supply_source", !!(view.supply && view.supply[1] === loc))
+ if (ui.locale_name[loc]) {
+ ui.locale_name[loc].classList.toggle("action", is_action("locale", loc) || is_action("laden_march", loc))
+ }
+
+ ui.locale_markers[loc].replaceChildren()
+
+ if (view.battle && view.battle.where === loc)
+ if (view.battle.storm)
+ ui.locale_markers[loc].appendChild(get_cached_element("marker circle storm"))
+ else
+ ui.locale_markers[loc].appendChild(get_cached_element("marker circle battle"))
+
+ if (set_has(view.pieces.ravaged, loc)) {
+ let cn
+ if (is_p1_locale(loc))
+ cn = "marker small ravaged russian"
+ else
+ cn = "marker small ravaged teutonic"
+ ui.locale_markers[loc].appendChild(get_cached_element(cn))
+ }
+
+ if (set_has(view.pieces.conquered, loc)) {
+ let cn
+ if (is_p1_locale(loc))
+ cn = "marker square conquered russian"
+ else
+ cn = "marker square conquered teutonic"
+ for (let i = 0; i < data.locales[loc].vp; ++i)
+ ui.locale_markers[loc].appendChild(get_cached_element(cn))
+ }
+
+ if (set_has(view.pieces.walls, loc))
+ ui.locale_markers[loc].appendChild(get_cached_element("marker square walls"))
+
+ let sieges = map_get(view.pieces.sieges, loc)
+ if (sieges > 0) {
+ let cn
+ if (is_teutonic_siege_marker(loc))
+ cn = "marker square siege teutonic"
+ else
+ cn = "marker square siege russian"
+ for (let i = 0; i < sieges; ++i)
+ ui.locale_markers[loc].appendChild(get_cached_element(cn))
+ }
+}
+
+function update_plan() {
+ if (view.plan) {
+ let is_planning = view.actions && view.actions.plan
+
+ ui.plan_panel.classList.remove("hide")
+ for (let i = 0; i < 6; ++i) {
+ if (i < view.plan.length) {
+ let lord = view.plan[i]
+ if (lord < 0) {
+ if (player === "Teutons")
+ ui.plan_cards[i].className = "card teutonic cc_pass"
+ else
+ ui.plan_cards[i].className = "card russian cc_pass"
+ } else {
+ if (lord < 6)
+ ui.plan_cards[i].className = "card teutonic cc_lord_" + lord
+ else
+ ui.plan_cards[i].className = "card russian cc_lord_" + lord
+ }
+ } else if (is_planning && i < max_plan_length()) {
+ if (player === "Teutons")
+ ui.plan_cards[i].className = "card teutonic cc_back"
+ else
+ ui.plan_cards[i].className = "card russian cc_back"
+ } else {
+ ui.plan_cards[i].className = "hide"
+ }
+ }
+
+ if (is_planning) {
+ ui.plan_actions.classList.remove("hide")
+ for (let lord = 0; lord < 12; ++lord) {
+ if (is_action("plan", lord)) {
+ ui.plan_action_cards[lord].classList.add("action")
+ ui.plan_action_cards[lord].classList.remove("disabled")
+ } else {
+ ui.plan_action_cards[lord].classList.remove("action")
+ ui.plan_action_cards[lord].classList.add("disabled")
+ }
+ }
+ if (is_action("plan", -1)) {
+ ui.plan_action_pass_p1.classList.add("action")
+ ui.plan_action_pass_p1.classList.remove("disabled")
+ ui.plan_action_pass_p2.classList.add("action")
+ ui.plan_action_pass_p2.classList.remove("disabled")
+ } else {
+ ui.plan_action_pass_p1.classList.remove("action")
+ ui.plan_action_pass_p1.classList.add("disabled")
+ ui.plan_action_pass_p2.classList.remove("action")
+ ui.plan_action_pass_p2.classList.add("disabled")
+ }
+ } else {
+ ui.plan_actions.classList.add("hide")
+ }
+ } else {
+ ui.plan_panel.classList.add("hide")
+ }
+}
+
+function update_cards() {
+ for (let c = 0; c < 42; ++c) {
+ let elt = ui.cards[c]
+ elt.classList.toggle("selected", c === view.what)
+ elt.classList.toggle("action", is_action("card", c))
+ }
+
+ if (view.arts_of_war) {
+ ui.arts_of_war_panel.classList.remove("hide")
+ ui.arts_of_war.replaceChildren()
+ for (let c of view.arts_of_war)
+ ui.arts_of_war.appendChild(ui.cards[c])
+ } else {
+ ui.arts_of_war_panel.classList.add("hide")
+ }
+
+ if (view.events.length > 0) {
+ ui.events_panel.classList.remove("hide")
+ ui.events.replaceChildren()
+ for (let c of view.events)
+ ui.events.appendChild(ui.cards[c])
+ } else {
+ ui.events_panel.classList.add("hide")
+ }
+
+ if (view.hand && view.hand.length > 0) {
+ ui.hand_panel.classList.remove("hide")
+ ui.hand.replaceChildren()
+ if (view.hand) {
+ for (let c of view.hand)
+ ui.hand.appendChild(ui.cards[c])
+ }
+ } else {
+ ui.hand_panel.classList.add("hide")
+ }
+
+ ui.capabilities1.replaceChildren()
+ for (let i = first_p1_card; i <= last_p1_card; ++i)
+ if (view.capabilities.includes(i))
+ ui.capabilities1.appendChild(ui.cards[i])
+
+ ui.capabilities2.replaceChildren()
+ for (let i = first_p2_card; i <= last_p2_card; ++i)
+ if (view.capabilities.includes(i))
+ ui.capabilities2.appendChild(ui.cards[i])
+
+ for (let ix = 0; ix < data.lords.length; ++ix) {
+ ui.lord_capabilities[ix].replaceChildren()
+ ui.lord_events[ix].replaceChildren()
+ if (view.reveal & (1 << ix)) {
+ let c = view.pieces.capabilities[(ix << 1) + 0]
+ if (c >= 0)
+ ui.lord_capabilities[ix].appendChild(ui.cards[c])
+ c = view.pieces.capabilities[(ix << 1) + 1]
+ if (c >= 0)
+ ui.lord_capabilities[ix].appendChild(ui.cards[c])
+ if (view.battle && view.battle.field_organ === ix)
+ ui.lord_events[ix].appendChild(ui.cards[EVENT_TEUTONIC_FIELD_ORGAN])
+ if (view.battle && view.battle.bridge && view.battle.bridge.lord1 === ix)
+ ui.lord_events[ix].appendChild(ui.cards[EVENT_RUSSIAN_BRIDGE])
+ if (view.battle && view.battle.bridge && view.battle.bridge.lord2 === ix)
+ ui.lord_events[ix].appendChild(ui.cards[EVENT_TEUTONIC_BRIDGE])
+ }
+ }
+}
+
+function update_battle() {
+ let array = view.battle.array
+
+ // Pursuit marker points "up" towards the conceding side
+ if (view.battle.conceded === "Russians") {
+ if (view.battle.attacker === "Russians")
+ ui.pursuit.className = "marker rectangle pursuit teutonic"
+ else
+ ui.pursuit.className = "marker rectangle pursuit teutonic rotate"
+ } else if (view.battle.conceded === "Teutons") {
+ if (view.battle.attacker === "Teutons")
+ ui.pursuit.className = "marker rectangle pursuit russian"
+ else
+ ui.pursuit.className = "marker rectangle pursuit russian rotate"
+ } else {
+ ui.pursuit.className = "hide"
+ }
+
+ for (let i = 0; i < array.length; ++i) {
+ let lord = array[i]
+ ui.battle_grid_array[i].replaceChildren()
+ if (lord >= 0)
+ ui.battle_grid_array[i].appendChild(ui.lord_mat[lord])
+ ui.battle_grid_array[i].classList.toggle("action", is_action("array", i))
+ }
+
+ ui.reserves.replaceChildren()
+ for (let lord of view.battle.reserves)
+ ui.reserves.appendChild(ui.lord_mat[lord])
+
+ ui.garrison.classList.toggle("hide", !view.battle.storm)
+ ui.garrison.classList.toggle("action", is_action("garrison"))
+
+ ui.garrison.replaceChildren()
+ if (view.battle.garrison) {
+ for (let i = 0; i < view.battle.garrison.knights; ++i)
+ add_force(ui.garrison, KNIGHTS, GARRISON, 0)
+ for (let i = 0; i < view.battle.garrison.men_at_arms; ++i)
+ add_force(ui.garrison, MEN_AT_ARMS, GARRISON, 0)
+ }
+
+ let here = view.battle.where
+
+ let def_prot = 0
+ let def_walls = 0
+ let att_prot = 0
+ let sally_prot = 0
+
+ if (view.battle.storm) {
+ if (is_bishopric(here) || is_castle(here))
+ def_prot = 4
+ else
+ def_prot = 3
+ if (attacker_has_trebuchets())
+ def_prot--
+ if (has_walls(here))
+ def_walls++
+ att_prot = count_siege_markers(view.battle.where)
+ }
+
+ if (view.battle.sally)
+ def_prot = count_siege_markers(view.battle.where)
+ else if (view.battle.array[SA2] >= 0)
+ sally_prot = count_siege_markers(view.battle.where)
+
+ let att_ui, def_ui
+ if (player === view.battle.attacker) {
+ att_ui = ui.battle_walls[3]
+ def_ui = ui.battle_walls[2]
+ } else {
+ att_ui = ui.battle_walls[0]
+ def_ui = ui.battle_walls[1]
+ }
+
+ for (let i = 0; i < 4; ++i)
+ ui.battle_walls[i].replaceChildren()
+
+ for (let i = 0; i < def_prot; ++i)
+ if (view.battle.attacker === "Teutons")
+ def_ui.appendChild(get_cached_element("marker square russian siege"))
+ else
+ def_ui.appendChild(get_cached_element("marker square teutonic siege"))
+ for (let i = 0; i < def_walls; ++i)
+ def_ui.appendChild(get_cached_element("marker square walls"))
+
+ ui.battle_siegeworks.replaceChildren()
+ for (let i = 0; i < sally_prot; ++i)
+ if (view.battle.attacker === "Teutons")
+ ui.battle_siegeworks.appendChild(get_cached_element("marker square russian siege"))
+ else
+ ui.battle_siegeworks.appendChild(get_cached_element("marker square teutonic siege"))
+
+ for (let i = 0; i < att_prot; ++i)
+ if (view.battle.attacker === "Teutons")
+ att_ui.appendChild(get_cached_element("marker square teutonic siege"))
+ else
+ att_ui.appendChild(get_cached_element("marker square russian siege"))
+}
+
+function update_court() {
+ let tcourt_hdr = (player === "Russians") ? ui.court2_header : ui.court1_header
+ let rcourt_hdr = (player === "Russians") ? ui.court1_header : ui.court2_header
+ tcourt_hdr.textContent = "Teutonic Lords"
+ rcourt_hdr.textContent = "Russian Lords"
+ let tcourt = (player === "Russians") ? ui.court2 : ui.court1
+ let rcourt = (player === "Russians") ? ui.court1 : ui.court2
+ tcourt.replaceChildren()
+ rcourt.replaceChildren()
+ for (let lord = 0; lord < 6; ++lord)
+ if (!is_lord_in_battle(lord) && is_lord_on_map(lord))
+ tcourt.appendChild(ui.lord_mat[lord])
+ for (let lord = 6; lord < 12; ++lord)
+ if (!is_lord_in_battle(lord) && is_lord_on_map(lord))
+ rcourt.appendChild(ui.lord_mat[lord])
+}
+
+function on_update() {
+ restart_cache()
+
+ for (let i = 0; i < 18; ++i) {
+ calendar_layout_cylinder[i] = []
+ calendar_layout_service[i] = []
+ }
+
+ for (let i = 0; i < data.locales.length; ++i)
+ locale_layout[i].length = 0
+
+ for (let ix = 0; ix < data.lords.length; ++ix) {
+ if (view.pieces.locale[ix] < 0) {
+ ui.lord_cylinder[ix].classList.add("hide")
+ ui.lord_service[ix].classList.add("hide")
+ } else {
+ ui.lord_cylinder[ix].classList.remove("hide")
+ update_lord(ix)
+ }
+ }
+
+ for (let way = 0; way < ui.ways.length; ++way) {
+ if (is_action("way", way))
+ ui.ways[way].classList.add("action")
+ else
+ ui.ways[way].classList.remove("action")
+ }
+
+ layout_calendar()
+
+ update_legate()
+ update_smerdi()
+ update_veche()
+
+ for (let loc = 0; loc < data.locales.length; ++loc)
+ update_locale(loc)
+
+ update_castle(ui.castles[0], view.pieces.castles1[0])
+ update_castle(ui.castles[1], view.pieces.castles1[1])
+ update_castle(ui.castles[2], view.pieces.castles2[0])
+ update_castle(ui.castles[3], view.pieces.castles2[1])
+
+ update_current_card_display()
+
+ if (view.turn & 1)
+ ui.turn.className = `marker circle turn campaign t${view.turn>>1}`
+ else
+ ui.turn.className = `marker circle turn levy t${view.turn>>1}`
+
+ let vp1 = count_vp1()
+ let vp2 = count_vp2()
+ if ((vp1 >> 1) === (vp2 >> 1)) {
+ if (vp1 & 1)
+ ui.vp1.className = `marker circle victory teutonic stack v${vp1>>1} half`
+ else
+ ui.vp1.className = `marker circle victory teutonic stack v${vp1>>1}`
+ if (vp2 & 1)
+ ui.vp2.className = `marker circle victory russian stack v${vp2>>1} half`
+ else
+ ui.vp2.className = `marker circle victory russian stack v${vp2>>1}`
+ } else {
+ if (vp1 & 1)
+ ui.vp1.className = `marker circle victory teutonic v${vp1>>1} half`
+ else
+ ui.vp1.className = `marker circle victory teutonic v${vp1>>1}`
+ if (vp2 & 1)
+ ui.vp2.className = `marker circle victory russian v${vp2>>1} half`
+ else
+ ui.vp2.className = `marker circle victory russian v${vp2>>1}`
+ }
+
+ if (view.pieces.elr1)
+ ui.elr1.classList = `marker circle enemy_lords_removed teutonic v${view.pieces.elr1}`
+ else
+ ui.elr1.classList = `marker circle enemy_lords_removed teutonic hide`
+ if (view.pieces.elr2)
+ ui.elr2.classList = `marker circle enemy_lords_removed russian v${view.pieces.elr2}`
+ else
+ ui.elr2.classList = `marker circle enemy_lords_removed russian hide`
+
+ ui.held1.textContent = `${view.held1} Held`
+ ui.held2.textContent = `${view.held2} Held`
+
+ update_plan()
+ update_cards()
+
+ ui.veche.classList.toggle("action", is_action("veche"))
+
+ if (view.battle && view.battle.array) {
+ ui.reserves_panel.classList.remove("hide")
+ ui.battle_panel.classList.remove("hide")
+ if (view.battle.storm)
+ ui.battle_header.textContent = "Storm at " + data.locales[view.battle.where].name
+ else if (view.battle.sally)
+ ui.battle_header.textContent = "Sally at " + data.locales[view.battle.where].name
+ else
+ ui.battle_header.textContent = "Battle at " + data.locales[view.battle.where].name
+ if (view.battle.attacker === player) {
+ ui.battle_grid.className = "attacker"
+ } else {
+ ui.battle_grid.className = "defender"
+ }
+ update_battle()
+ } else {
+ ui.battle_panel.classList.add("hide")
+ }
+
+ if (view.battle && view.battle.array && view.battle.reserves.length > 0)
+ ui.reserves_panel.classList.remove("hide")
+ else
+ ui.reserves_panel.classList.add("hide")
+
+ update_court()
+
+ let first_turn = view.scenario >> 5
+ let last_turn = view.scenario & 31
+ for (let i = 0; i <= 17; ++i) {
+ ui.calendar[i].classList.toggle("action", is_action("calendar", i))
+ if (i >= 1 && i <= 16)
+ ui.calendar[i].classList.toggle("end", i < first_turn || i > last_turn)
+ }
+
+ // Misc
+ action_button("lordship", "Lordship")
+ action_button("march", "March")
+ action_button("avoid", "Avoid Battle")
+ action_button("withdraw", "Withdraw")
+ action_button("retreat", "Retreat")
+ action_button("remove", "Remove")
+ action_button("surrender", "Surrender")
+ action_button("siegeworks", "Siegeworks")
+ action_button("boats_x2", "Boats x2")
+
+ // Use all commands
+ action_button("use_legate", "Legate")
+ action_button("stonemasons", "Stonemasons")
+ action_button("stone_kremlin", "Stone Kremlin")
+ action_button("tax", "Tax")
+ action_button("siege", "Siege")
+
+ // Use one command
+ action_button("smerdi", "Smerdi")
+ action_button("storm", "Storm")
+ action_button("sally", "Sally")
+ action_button("sail", "Sail")
+ action_button("ravage", "Ravage")
+ action_button("forage", "Forage")
+ action_button("supply", "Supply")
+
+ // Muster & Spoils
+ action_button("take_prov", "Provender")
+ action_button("take_loot", "Loot")
+ action_button("take_coin", "Coin")
+ action_button("take_ship", "Ship")
+ action_button("take_boat", "Boat")
+ action_button("take_cart", "Cart")
+ action_button("take_sled", "Sled")
+ action_button("capability", "Capability")
+
+ // Events
+ action_button("decline", "Decline")
+ action_button("deploy", "Deploy")
+ action_button("discard", "Discard")
+ action_button("hold", "Hold")
+ action_button("play", "Play")
+
+ action_button("approach", "Approach")
+ action_button("concede", "Concede")
+ action_button("battle", "Battle")
+
+ action_button("end_array", "End Array")
+ action_button("end_avoid_battle", "End Avoid Battle")
+ action_button("end_call_to_arms", "End Call to Arms")
+ action_button("end_command", "End Command")
+ action_button("end_disband", "End Disband")
+ action_button("end_discard", "End Discard")
+ action_button("end_feed", "End Feed")
+ action_button("end_growth", "End Growth")
+ action_button("end_levy", "End Levy")
+ action_button("end_muster", "End Muster")
+ action_button("end_pay", "End Pay")
+ action_button("end_plan", "End Plan")
+ action_button("end_plow_and_reap", "End Plow and Reap")
+ action_button("end_ransom", "End Ransom")
+ action_button("end_remove", "End Remove")
+ action_button("end_reposition", "End Reposition")
+ action_button("end_sack", "End Sack")
+ action_button("end_sally", "End Sally")
+ action_button("end_setup", "End Setup")
+ action_button("end_spoils", "End Spoils")
+ action_button("end_supply", "End Supply")
+ action_button("end_wastage", "End Wastage")
+ action_button("end_withdraw", "End Withdraw")
+
+ action_button("pass", "Pass")
+ action_button("done", "Done")
+ action_button("undo", "Undo")
+}
+
+// === LOG ===
+
+function on_focus_card_tip(c) {
+ if (c <= first_p1_card)
+ ui.command.className = `card teutonic aow_${c}`
+ else
+ ui.command.className = `card russian aow_${c}`
+}
+
+function on_blur_card_tip() {
+ update_current_card_display()
+}
+
+function sub_card_capability(match, p1) {
+ let x = p1 | 0
+ return `<span class="card_tip" onmouseenter="on_focus_card_tip(${x})" onmouseleave="on_blur_card_tip(${x})">${data.cards[x].capability}</span>`
+}
+
+function sub_card_event(match, p1) {
+ let x = p1 | 0
+ return `<span class="card_tip" onmouseenter="on_focus_card_tip(${x})" onmouseleave="on_blur_card_tip(${x})">${data.cards[x].event}</span>`
+}
+
+function on_focus_locale_tip(loc) {
+ ui.locale[loc].classList.add("tip")
+ if (ui.locale_name[loc])
+ ui.locale_name[loc].classList.add("tip")
+}
+
+function on_blur_locale_tip(loc) {
+ ui.locale[loc].classList.remove("tip")
+ if (ui.locale_name[loc])
+ ui.locale_name[loc].classList.remove("tip")
+}
+
+function on_click_locale_tip(loc) {
+ ui.locale[loc].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" })
+}
+
+function on_focus_way_tip(way) {
+ ui.ways[way].classList.add("tip")
+}
+
+function on_blur_way_tip(way) {
+ ui.ways[way].classList.remove("tip")
+}
+
+function on_click_way_tip(way) {
+ ui.ways[way].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" })
+}
+
+function on_click_lord_tip(lord) {
+ ui.lord_mat[lord].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" })
+}
+
+function sub_locale_name(match, p1) {
+ let x = p1 | 0
+ let n = data.locales[x].name
+ return `<span class="locale_tip" onmouseenter="on_focus_locale_tip(${x})" onmouseleave="on_blur_locale_tip(${x})" onclick="on_click_locale_tip(${x})">${n}</span>`
+}
+
+function sub_lord_name(match, p1) {
+ let x = p1 | 0
+ let n = data.lords[x].name
+ return `<span class="lord_tip" onclick="on_click_lord_tip(${x})">${n}</span>`
+}
+
+function sub_way_name(match, p1) {
+ let x = p1 | 0
+ let n = data.ways[x].name
+ return `<span class="way_tip" onmouseenter="on_focus_way_tip(${x})" onmouseleave="on_blur_way_tip(${x})" onclick="on_click_way_tip(${x})">${n}</span>`
+}
+
+function on_log(text) {
+ let p = document.createElement("div")
+
+ if (text.match(/^>>/)) {
+ text = text.substring(2)
+ p.className = "ii"
+ }
+
+ if (text.match(/^>/)) {
+ text = text.substring(1)
+ p.className = "i"
+ }
+
+ text = text.replace(/&/g, "&amp;")
+ text = text.replace(/</g, "&lt;")
+ text = text.replace(/>/g, "&gt;")
+
+ text = text.replace(/C(\d+)/g, sub_card_capability)
+ text = text.replace(/E(\d+)/g, sub_card_event)
+ text = text.replace(/L(\d+)/g, sub_lord_name)
+ text = text.replace(/%(\d+)/g, sub_locale_name)
+ text = text.replace(/W(\d+)/g, sub_way_name)
+
+ if (text.match(/^\.h1/)) {
+ text = text.substring(4)
+ p.className = "h1"
+ }
+ else if (text.match(/^\.h2t/)) {
+ text = text.substring(5)
+ p.className = "h2 teutonic"
+ }
+ else if (text.match(/^\.h2r/)) {
+ text = text.substring(5)
+ p.className = "h2 russian"
+ }
+ else if (text.match(/^\.h2/)) {
+ text = text.substring(4)
+ p.className = "h2"
+ }
+ else if (text.match(/^\.h3t/)) {
+ text = text.substring(5)
+ p.className = "h3 teutonic"
+ }
+ else if (text.match(/^\.h3r/)) {
+ text = text.substring(5)
+ p.className = "h3 russian"
+ }
+ else if (text.match(/^\.h3/)) {
+ text = text.substring(4)
+ p.className = "h3"
+ }
+ else if (text.match(/^\.h4/)) {
+ text = text.substring(4)
+ p.className = "h4"
+ }
+ else if (text.match(/^\.h5/)) {
+ text = text.substring(4)
+ p.className = "h5"
+ }
+
+ p.innerHTML = text
+ return p
+}
+
+function pack1_get(word, n) {
+ return (word >>> n) & 1
+}
+
+function pack2_get(word, n) {
+ n = n << 1
+ return (word >>> n) & 3
+}
+
+function pack4_get(word, n) {
+ n = n << 2
+ return (word >>> n) & 15
+}
+
+build_map()
+scroll_with_middle_mouse("main")