summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2025-02-23 18:37:17 +0100
committerTor Andersson <tor@ccxvii.net>2025-03-08 16:32:19 +0100
commitfe2bc3961ec3b3164786074b37e36581b81fa68c (patch)
treebfc80bc4fb161e83f290da6294f0bffe9116500b
parenta4c2b8458d1059c373c4a714bce0b5f68a3ce20f (diff)
downloadland-and-freedom-fe2bc3961ec3b3164786074b37e36581b81fa68c.tar.gz
New client and client data processing tools.
-rw-r--r--images/texture.pngbin0 -> 27561 bytes
-rw-r--r--land-and-freedom.css930
-rw-r--r--land-and-freedom.scss525
-rw-r--r--package.json5
-rw-r--r--play.css379
-rw-r--r--play.html108
-rw-r--r--play.js1060
-rw-r--r--play.ts623
-rw-r--r--rules.js2
-rw-r--r--rules.ts2
-rw-r--r--tools/colors.js143
-rw-r--r--tools/gencolor.js27
-rw-r--r--tools/gentextures.sh4
-rw-r--r--tools/parse-layout.js167
-rw-r--r--tools/rock_overlay@2x.pngbin0 -> 160515 bytes
15 files changed, 1330 insertions, 2645 deletions
diff --git a/images/texture.png b/images/texture.png
new file mode 100644
index 0000000..963f0ad
--- /dev/null
+++ b/images/texture.png
Binary files differ
diff --git a/land-and-freedom.css b/land-and-freedom.css
deleted file mode 100644
index 0fb77dc..0000000
--- a/land-and-freedom.css
+++ /dev/null
@@ -1,930 +0,0 @@
-main {
- background-color: #7b904b;
-}
-
-/* MAP */
-#mapwrap {
- width: 1000px;
- height: 850px;
- box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2666666667);
-}
-
-#map {
- background-repeat: no-repeat;
- background-size: 100% 100%;
- width: 1000px;
- height: 850px;
- overflow: clip;
-}
-
-#map {
- background-image: url(images/map75.png);
-}
-
-@media (min-resolution: 97dpi) {
- #map {
- background-image: url(images/map100.png);
- }
-}
-#current_events .card:last-child {
- transform: scale(1.2);
-}
-
-/* CURRENT CARD */
-#turn_info {
- border-bottom: 1px solid black;
- overflow: clip;
- background-color: gray;
- white-space: normal;
- padding: 8px 0px;
-}
-
-#turn_info .card {
- margin: 0 auto;
-}
-
-@media (max-height: 800px) {
- #turn_info {
- height: 95px;
- }
- #turn_info:hover {
- height: auto;
- }
-}
-@media (max-height: 600px) {
- #turn_info {
- display: none;
- }
-}
-.game_info {
- padding: 3px 4px;
- border-bottom: 1px solid black;
- font-size: 16px;
- line-height: 1.5;
- grid-column: 1;
- grid-row: 1;
- display: flex;
- flex-direction: column;
- background: floralwhite;
-}
-
-#roles {
- grid-column: 1;
- grid-row: 2;
-}
-
-.role_medallions {
- grid-row: 3;
- grid-column: 1/3;
- padding: 3px 4px;
- display: flex;
- flex-direction: row-reverse;
- gap: 4px;
-}
-
-.role_info {
- grid-row: 4;
- grid-column: 1/3;
-}
-
-#role_Anarchist {
- background-color: rgb(93, 89, 106);
-}
-
-#role_Communist {
- background-color: rgb(237, 36, 27);
-}
-
-#role_Moderate {
- background-color: rgb(134, 44, 97);
-}
-
-.role {
- color: floralwhite;
-}
-.role a {
- color: floralwhite;
-}
-
-#selectable_cards {
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
- padding: 12px;
- gap: 12px;
- min-height: 260px;
-}
-
-#selectable_cards:empty {
- display: none;
-}
-
-.panel {
- min-width: 1271px;
- max-width: 1271px;
- background-color: #58641d;
- margin: 12px auto;
- box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2666666667);
- border: 2px solid #333;
-}
-
-.panel_body {
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
- padding: 12px;
- gap: 12px;
- min-height: 260px;
-}
-
-.panel_body[data-active=inactive] {
- display: none;
-}
-
-.panel_header {
- color: floralwhite;
- user-select: none;
- font-weight: bold;
- text-align: center;
- border-bottom: 2px solid #333;
- background-color: red;
-}
-
-#player_area_header {
- background-color: #273b09;
- display: flex;
- border-bottom: none;
-}
-#player_area_header .player_area_tab {
- cursor: pointer;
- width: 25%;
- border-bottom: 2px solid #333;
- border-left: 1px solid #333;
- border-right: 1px solid #333;
-}
-#player_area_header .player_area_tab[data-active=active] {
- width: 25%;
- background-color: #58641d;
- border-bottom: none;
-}
-#player_area_header #hand_tab {
- border-left: none;
-}
-#player_area_header #trash_tab {
- border-right: none;
-}
-
-#player_area_header[data-faction-id=a],
-.panel_header[data-faction-id=a] {
- background-color: rgb(93, 89, 106);
-}
-
-#player_area_header[data-faction-id=c],
-.panel_header[data-faction-id=c] {
- background-color: rgb(237, 36, 27);
-}
-
-#player_area_header[data-faction-id=m],
-.panel_header[data-faction-id=m] {
- background-color: rgb(134, 44, 97);
-}
-
-.front {
- position: absolute;
- box-sizing: border-box;
- width: 93px;
- height: 102px;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 4px;
- border-radius: 20px;
-}
-
-.contributions {
- display: flex;
- flex-direction: row;
- gap: 2px;
- margin-top: 15px;
- height: 25px;
- z-index: -1;
-}
-.contributions .faction_token {
- z-index: -1;
-}
-
-.front .value {
- font-weight: bold;
- font-size: 30px;
- margin-top: auto;
- z-index: -1;
-}
-
-#madrid_front {
- width: 104px;
- height: 114px;
- border-radius: 23px;
-}
-
-#madrid_front .contributions {
- padding: 0px 2px;
-}
-
-.card {
- background-size: 100% 100%;
- width: 198px;
- height: 270px;
- border-radius: 5px;
- box-shadow: 0 0 0 1px #333;
-}
-
-.card[data-card-id="1"] {
- background-image: url("images/cards100/card_1.png");
-}
-
-.card[data-card-id="2"] {
- background-image: url("images/cards100/card_2.png");
-}
-
-.card[data-card-id="3"] {
- background-image: url("images/cards100/card_3.png");
-}
-
-.card[data-card-id="4"] {
- background-image: url("images/cards100/card_4.png");
-}
-
-.card[data-card-id="5"] {
- background-image: url("images/cards100/card_5.png");
-}
-
-.card[data-card-id="6"] {
- background-image: url("images/cards100/card_6.png");
-}
-
-.card[data-card-id="7"] {
- background-image: url("images/cards100/card_7.png");
-}
-
-.card[data-card-id="8"] {
- background-image: url("images/cards100/card_8.png");
-}
-
-.card[data-card-id="9"] {
- background-image: url("images/cards100/card_9.png");
-}
-
-.card[data-card-id="10"] {
- background-image: url("images/cards100/card_10.png");
-}
-
-.card[data-card-id="11"] {
- background-image: url("images/cards100/card_11.png");
-}
-
-.card[data-card-id="12"] {
- background-image: url("images/cards100/card_12.png");
-}
-
-.card[data-card-id="13"] {
- background-image: url("images/cards100/card_13.png");
-}
-
-.card[data-card-id="14"] {
- background-image: url("images/cards100/card_14.png");
-}
-
-.card[data-card-id="15"] {
- background-image: url("images/cards100/card_15.png");
-}
-
-.card[data-card-id="16"] {
- background-image: url("images/cards100/card_16.png");
-}
-
-.card[data-card-id="17"] {
- background-image: url("images/cards100/card_17.png");
-}
-
-.card[data-card-id="18"] {
- background-image: url("images/cards100/card_18.png");
-}
-
-.card[data-card-id="19"] {
- background-image: url("images/cards100/card_19.png");
-}
-
-.card[data-card-id="20"] {
- background-image: url("images/cards100/card_20.png");
-}
-
-.card[data-card-id="21"] {
- background-image: url("images/cards100/card_21.png");
-}
-
-.card[data-card-id="22"] {
- background-image: url("images/cards100/card_22.png");
-}
-
-.card[data-card-id="23"] {
- background-image: url("images/cards100/card_23.png");
-}
-
-.card[data-card-id="24"] {
- background-image: url("images/cards100/card_24.png");
-}
-
-.card[data-card-id="25"] {
- background-image: url("images/cards100/card_25.png");
-}
-
-.card[data-card-id="26"] {
- background-image: url("images/cards100/card_26.png");
-}
-
-.card[data-card-id="27"] {
- background-image: url("images/cards100/card_27.png");
-}
-
-.card[data-card-id="28"] {
- background-image: url("images/cards100/card_28.png");
-}
-
-.card[data-card-id="29"] {
- background-image: url("images/cards100/card_29.png");
-}
-
-.card[data-card-id="30"] {
- background-image: url("images/cards100/card_30.png");
-}
-
-.card[data-card-id="31"] {
- background-image: url("images/cards100/card_31.png");
-}
-
-.card[data-card-id="32"] {
- background-image: url("images/cards100/card_32.png");
-}
-
-.card[data-card-id="33"] {
- background-image: url("images/cards100/card_33.png");
-}
-
-.card[data-card-id="34"] {
- background-image: url("images/cards100/card_34.png");
-}
-
-.card[data-card-id="35"] {
- background-image: url("images/cards100/card_35.png");
-}
-
-.card[data-card-id="36"] {
- background-image: url("images/cards100/card_36.png");
-}
-
-.card[data-card-id="37"] {
- background-image: url("images/cards100/card_37.png");
-}
-
-.card[data-card-id="38"] {
- background-image: url("images/cards100/card_38.png");
-}
-
-.card[data-card-id="39"] {
- background-image: url("images/cards100/card_39.png");
-}
-
-.card[data-card-id="40"] {
- background-image: url("images/cards100/card_40.png");
-}
-
-.card[data-card-id="41"] {
- background-image: url("images/cards100/card_41.png");
-}
-
-.card[data-card-id="42"] {
- background-image: url("images/cards100/card_42.png");
-}
-
-.card[data-card-id="43"] {
- background-image: url("images/cards100/card_43.png");
-}
-
-.card[data-card-id="44"] {
- background-image: url("images/cards100/card_44.png");
-}
-
-.card[data-card-id="45"] {
- background-image: url("images/cards100/card_45.png");
-}
-
-.card[data-card-id="46"] {
- background-image: url("images/cards100/card_46.png");
-}
-
-.card[data-card-id="47"] {
- background-image: url("images/cards100/card_47.png");
-}
-
-.card[data-card-id="48"] {
- background-image: url("images/cards100/card_48.png");
-}
-
-.card[data-card-id="49"] {
- background-image: url("images/cards100/card_49.png");
-}
-
-.card[data-card-id="50"] {
- background-image: url("images/cards100/card_50.png");
-}
-
-.card[data-card-id="51"] {
- background-image: url("images/cards100/card_51.png");
-}
-
-.card[data-card-id="52"] {
- background-image: url("images/cards100/card_52.png");
-}
-
-.card[data-card-id="53"] {
- background-image: url("images/cards100/card_53.png");
-}
-
-.card[data-card-id="54"] {
- background-image: url("images/cards100/card_54.png");
-}
-
-.card[data-card-id="55"] {
- background-image: url("images/cards100/card_55.png");
-}
-
-.card[data-card-id="56"] {
- background-image: url("images/cards100/card_56.png");
-}
-
-.card[data-card-id="57"] {
- background-image: url("images/cards100/card_57.png");
-}
-
-.card[data-card-id="58"] {
- background-image: url("images/cards100/card_58.png");
-}
-
-.card[data-card-id="59"] {
- background-image: url("images/cards100/card_59.png");
-}
-
-.card[data-card-id="60"] {
- background-image: url("images/cards100/card_60.png");
-}
-
-.card[data-card-id="61"] {
- background-image: url("images/cards100/card_61.png");
-}
-
-.card[data-card-id="62"] {
- background-image: url("images/cards100/card_62.png");
-}
-
-.card[data-card-id="63"] {
- background-image: url("images/cards100/card_63.png");
-}
-
-.card[data-card-id="64"] {
- background-image: url("images/cards100/card_64.png");
-}
-
-.card[data-card-id="65"] {
- background-image: url("images/cards100/card_65.png");
-}
-
-.card[data-card-id="66"] {
- background-image: url("images/cards100/card_66.png");
-}
-
-.card[data-card-id="67"] {
- background-image: url("images/cards100/card_67.png");
-}
-
-.card[data-card-id="68"] {
- background-image: url("images/cards100/card_68.png");
-}
-
-.card[data-card-id="69"] {
- background-image: url("images/cards100/card_69.png");
-}
-
-.card[data-card-id="70"] {
- background-image: url("images/cards100/card_70.png");
-}
-
-.card[data-card-id="71"] {
- background-image: url("images/cards100/card_71.png");
-}
-
-.card[data-card-id="72"] {
- background-image: url("images/cards100/card_72.png");
-}
-
-.card[data-card-id="73"] {
- background-image: url("images/cards100/card_73.png");
-}
-
-.card[data-card-id="74"] {
- background-image: url("images/cards100/card_74.png");
-}
-
-.card[data-card-id="75"] {
- background-image: url("images/cards100/card_75.png");
-}
-
-.card[data-card-id="76"] {
- background-image: url("images/cards100/card_76.png");
-}
-
-.card[data-card-id="77"] {
- background-image: url("images/cards100/card_77.png");
-}
-
-.card[data-card-id="78"] {
- background-image: url("images/cards100/card_78.png");
-}
-
-.card[data-card-id="79"] {
- background-image: url("images/cards100/card_79.png");
-}
-
-.card[data-card-id="80"] {
- background-image: url("images/cards100/card_80.png");
-}
-
-.card[data-card-id="81"] {
- background-image: url("images/cards100/card_81.png");
-}
-
-.card[data-card-id="82"] {
- background-image: url("images/cards100/card_82.png");
-}
-
-.card[data-card-id="83"] {
- background-image: url("images/cards100/card_83.png");
-}
-
-.card[data-card-id="84"] {
- background-image: url("images/cards100/card_84.png");
-}
-
-.card[data-card-id="85"] {
- background-image: url("images/cards100/card_85.png");
-}
-
-.card[data-card-id="86"] {
- background-image: url("images/cards100/card_86.png");
-}
-
-.card[data-card-id="87"] {
- background-image: url("images/cards100/card_87.png");
-}
-
-.card[data-card-id="88"] {
- background-image: url("images/cards100/card_88.png");
-}
-
-.card[data-card-id="89"] {
- background-image: url("images/cards100/card_89.png");
-}
-
-.card[data-card-id="90"] {
- background-image: url("images/cards100/card_90.png");
-}
-
-.card[data-card-id="91"] {
- background-image: url("images/cards100/card_91.png");
-}
-
-.card[data-card-id="92"] {
- background-image: url("images/cards100/card_92.png");
-}
-
-.card[data-card-id="93"] {
- background-image: url("images/cards100/card_93.png");
-}
-
-.card[data-card-id="94"] {
- background-image: url("images/cards100/card_94.png");
-}
-
-.card[data-card-id="95"] {
- background-image: url("images/cards100/card_95.png");
-}
-
-.card[data-card-id="96"] {
- background-image: url("images/cards100/card_96.png");
-}
-
-.card[data-card-id="97"] {
- background-image: url("images/cards100/card_97.png");
-}
-
-.card[data-card-id="98"] {
- background-image: url("images/cards100/card_98.png");
-}
-
-.card[data-card-id="99"] {
- background-image: url("images/cards100/card_99.png");
-}
-
-.card[data-card-id="100"] {
- background-image: url("images/cards100/card_100.png");
-}
-
-.card[data-card-id="101"] {
- background-image: url("images/cards100/card_101.png");
-}
-
-.card[data-card-id="102"] {
- background-image: url("images/cards100/card_102.png");
-}
-
-.card[data-card-id="103"] {
- background-image: url("images/cards100/card_103.png");
-}
-
-.card[data-card-id="104"] {
- background-image: url("images/cards100/card_104.png");
-}
-
-.card[data-card-id="105"] {
- background-image: url("images/cards100/card_105.png");
-}
-
-.card[data-card-id="106"] {
- background-image: url("images/cards100/card_106.png");
-}
-
-.card[data-card-id="107"] {
- background-image: url("images/cards100/card_107.png");
-}
-
-.card[data-card-id="108"] {
- background-image: url("images/cards100/card_108.png");
-}
-
-.card.event {
- position: absolute;
- width: 132px;
- height: 180px;
-}
-
-.blank_marker {
- box-sizing: border-box;
- position: absolute;
- background-color: red;
- width: 29px;
- height: 29px;
- border-radius: 50%;
- box-shadow: 0 0 0 1px #333;
- margin-top: 1px;
-}
-
-.faction_token {
- box-sizing: border-box;
- background-size: 100% 100%;
-}
-
-#log .faction_token {
- display: inline-block;
- width: 25px;
- height: 25px;
- border-radius: 2px;
- vertical-align: middle;
- margin: 0px 1px;
-}
-
-#glory .faction_token {
- position: absolute;
- width: 34px;
- height: 34px;
- border-radius: 4px;
-}
-
-.contributions .faction_token {
- width: 25px;
- height: 25px;
- border-radius: 4px;
-}
-
-.faction_token[data-faction-id=a] {
- border: 1px #333 solid;
- background-image: url("images/factions/anarchists.png");
-}
-
-.faction_token[data-faction-id=c] {
- border: 1px #333 solid;
- background-image: url("images/factions/communitsts.png");
-}
-
-.faction_token[data-faction-id=m] {
- border: 1px #333 solid;
- background-image: url("images/factions/moderates.png");
-}
-
-.standee {
- box-sizing: border-box;
- position: absolute;
- width: 29px;
- height: 35px;
- background-size: cover;
- background-repeat: no-repeat;
- border-radius: 4px;
- box-shadow: 0 0 0 1px #333;
- transition-property: top, left;
- transition-duration: 700ms;
- transition-timing-function: ease;
- z-index: 1;
-}
-
-.blank_marker.action,
-.bonus.action,
-.card.action,
-.front.action,
-.medallion.action,
-.standee.action {
- box-shadow: 0 0 0 3px yellow;
-}
-
-.blank_marker.action {
- z-index: 2;
-}
-
-.blank_marker.action:hover,
-.bonus.action:hover,
-.card.action:hover,
-.card.selected,
-.front.action:hover,
-.medallion.action:hover,
-.standee.action:hover {
- box-shadow: 0 0 0 3px blue;
-}
-
-.blank_marker.action:hover {
- opacity: 0.5;
-}
-
-.standee[data-standee-id="0"] {
- background-image: url("images/standees/standee_0.png");
-}
-
-.standee[data-standee-id="1"] {
- background-image: url("images/standees/standee_1.png");
-}
-
-.standee[data-standee-id="2"] {
- background-image: url("images/standees/standee_2.png");
-}
-
-.standee[data-standee-id="3"] {
- background-image: url("images/standees/standee_3.png");
-}
-
-.standee[data-standee-id="4"] {
- background-image: url("images/standees/standee_4.png");
-}
-
-.bonus {
- box-sizing: border-box;
- position: absolute;
- width: 54px;
- height: 54px;
- background-size: cover;
- background-repeat: no-repeat;
- border-radius: 50%;
-}
-
-.bonus[data-bonus-id="0"][data-bonus-on="0"] {
- background-image: url("images/bonus/bonus_0_off.png");
-}
-
-.bonus[data-bonus-id="0"][data-bonus-on="1"] {
- background-image: url("images/bonus/bonus_0_on.png");
-}
-
-.bonus[data-bonus-id="1"][data-bonus-on="0"] {
- background-image: url("images/bonus/bonus_1_off.png");
-}
-
-.bonus[data-bonus-id="1"][data-bonus-on="1"] {
- background-image: url("images/bonus/bonus_1_on.png");
-}
-
-.initiative_token {
- width: 47px;
- height: 47px;
- background-size: cover;
- background-repeat: no-repeat;
- margin-right: auto;
- background-image: url("images/initiative_clockwise.png");
-}
-
-.initiative_token[data-year="2"] {
- background-image: url("images/initiative_counter_clockwise.png");
-}
-
-.medallion {
- width: 47px;
- height: 47px;
- background-size: cover;
- background-repeat: no-repeat;
- box-shadow: 0 0 0 1px #333;
- border-radius: 10px;
-}
-
-#medallions .medallion {
- box-sizing: border-box;
- position: absolute;
-}
-
-.medallion[data-medallion-id="0"] {
- background-image: url("images/medallions/medallion_0.png");
-}
-
-.medallion[data-medallion-id="1"] {
- background-image: url("images/medallions/medallion_1.png");
-}
-
-.medallion[data-medallion-id="2"] {
- background-image: url("images/medallions/medallion_2.png");
-}
-
-.medallion[data-medallion-id="3"] {
- background-image: url("images/medallions/medallion_3.png");
-}
-
-.medallion[data-medallion-id="4"] {
- background-image: url("images/medallions/medallion_4.png");
-}
-
-.medallion[data-medallion-id="5"] {
- background-image: url("images/medallions/medallion_5.png");
-}
-
-.medallion[data-medallion-id="6"] {
- background-image: url("images/medallions/medallion_6.png");
-}
-
-.medallion[data-medallion-id="7"] {
- background-image: url("images/medallions/medallion_7.png");
-}
-
-.medallion[data-medallion-id="8"] {
- background-image: url("images/medallions/medallion_8.png");
-}
-
-#log {
- background-color: floralwhite;
-}
-
-#log .h1,
-#log .h2 {
- font-size: 10px;
- padding-top: 2px;
- padding-bottom: 2px;
- text-align: center;
-}
-
-#log .h1 {
- background-color: rgb(233, 223, 200);
-}
-
-#log .h2 {
- background-color: rgb(233, 223, 200);
-}
-
-#log .h2.anarchist {
- background-color: rgb(93, 89, 106);
- color: floralwhite;
-}
-
-#log .h2.communist {
- background-color: rgb(237, 36, 27);
- color: floralwhite;
-}
-
-#log .h2.moderate {
- background-color: rgb(134, 44, 97);
- color: floralwhite;
-}
-
-#log .h2.fascist {
- background-color: rgb(183, 144, 105);
-}
-
-#log div.i {
- padding-left: 32px;
- text-indent: -12px;
-}
-
-#log div.ii {
- padding-left: 44px;
- text-indent: -12px;
-}
diff --git a/land-and-freedom.scss b/land-and-freedom.scss
deleted file mode 100644
index 07c55b2..0000000
--- a/land-and-freedom.scss
+++ /dev/null
@@ -1,525 +0,0 @@
-// @use "sass:math";
-@use 'sass:map';
-
-$selectable-color: yellow; // yellow;
-$selected-color: blue; //blue;
-
-$anarchist-color: rgb(93, 89, 106);
-$communist-color: rgb(237, 36, 27);
-$moderate-color: rgb(134, 44, 97);
-$fascist-color: rgb(183, 144, 105);
-$log-text-background-color: rgb(233 223 200);
-
-main {
- // background-color: rgb(213, 196, 131);
- // background-color: darkolivegreen;
- background-color: #7b904b;
-}
-
-/* MAP */
-
-#mapwrap {
- width: 1000px;
- height: 850px;
- // background-color: #999f85;
- box-shadow: 1px 2px 4px #0004;
-}
-
-// #map {
-// background-repeat: no-repeat;
-// background-size: cover;
-// width: 1275px;
-// height: 1650px;
-// overflow: clip;
-// }
-
-#map {
- background-repeat: no-repeat;
- background-size: 100% 100%;
- width: 1000px;
- height: 850px;
- overflow: clip;
-}
-
-#map {
- background-image: url(images/map75.png);
-}
-@media (min-resolution: 97dpi) {
- #map {
- background-image: url(images/map100.png);
- }
-}
-
-#current_events {
- .card:last-child {
- transform: scale(1.2);
- }
-}
-
-/* CURRENT CARD */
-
-#turn_info {
- border-bottom: 1px solid black;
- // padding: 12px;
- overflow: clip;
- background-color: gray;
- white-space: normal;
- padding: 8px 0px;
-}
-
-#turn_info .card {
- margin: 0 auto;
-}
-
-@media (max-height: 800px) {
- #turn_info { height: 95px; }
- #turn_info:hover { height: auto; }
-}
-
-@media (max-height: 600px) {
- #turn_info {
- display: none;
- }
-}
-
-.game_info {
- padding: 3px 4px;
- border-bottom: 1px solid black;
- font-size: 16px;
- line-height: 1.5;
- grid-column: 1;
- grid-row: 1;
- display: flex;
- flex-direction: column;
- background: floralwhite;
-}
-
-#roles {
- grid-column: 1;
- grid-row: 2;
-}
-
-.role_medallions {
- grid-row: 3;
- grid-column: 1 / 3;
- padding: 3px 4px;
- display: flex;
- flex-direction: row-reverse;
- gap: 4px;
-}
-
-.role_info {
- grid-row: 4;
- grid-column: 1 / 3;
-}
-
-// #role_Anarchist::before {
-// background-color: $anarchist-color;
-// content: ' ';
-// display: block;
-// height: 100%;
-// left: 0;
-// opacity: 0.6;
-// position: absolute;
-// top: 0;
-// width: 100%;
-// z-index: -1;
-// }
-
-#role_Anarchist {
- background-color: $anarchist-color;
-}
-
-#role_Communist {
- background-color: $communist-color;
-}
-
-#role_Moderate {
- background-color: $moderate-color;
-}
-
-.role {
- color: floralwhite;
-
- a {
- color: floralwhite;
- }
-}
-
-#selectable_cards {
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
- padding: 12px;
- gap: 12px;
- min-height: 260px;
-}
-
-#selectable_cards:empty {
- display: none;
-}
-
-.panel {
- min-width: 1271px;
- max-width: 1271px;
- // background-color: hsl(34, 10%, 35%);
- background-color: #58641d;
- margin: 12px auto;
- box-shadow: 1px 2px 4px #0004;
- border: 2px solid #333;
-}
-
-.panel_body {
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
- padding: 12px;
- gap: 12px;
- min-height: 260px;
-}
-
-.panel_body[data-active="inactive"] {
- display: none;
-}
-
-.panel_header {
- color: floralwhite;
- user-select: none;
- font-weight: bold;
- text-align: center;
- border-bottom: 2px solid #333;
- background-color: red;
-}
-
-#player_area_header {
- background-color: #273b09;
- display: flex;
- border-bottom: none;
-
- .player_area_tab {
- cursor: pointer;
- width: 25%;
- border-bottom: 2px solid #333;
- border-left: 1px solid #333;
- border-right: 1px solid #333;
- }
-
- .player_area_tab[data-active="active"] {
- width: 25%;
- background-color: #58641d;
- border-bottom: none;
- }
-
- #hand_tab {
- border-left: none;
- }
-
- #trash_tab {
- border-right: none;
- }
-}
-
-#player_area_header[data-faction-id='a'],
-.panel_header[data-faction-id='a'] {
- background-color: $anarchist-color;
-}
-
-#player_area_header[data-faction-id='c'],
-.panel_header[data-faction-id='c'] {
- background-color: $communist-color;
-}
-
-#player_area_header[data-faction-id='m'],
-.panel_header[data-faction-id='m'] {
- background-color: $moderate-color;
-}
-
-// #hand,
-// .tableau {
-// display: flex;
-// flex-direction: row;
-// gap: 8px;
-// margin-top: 16px;
-// padding: 16px;
-// justify-content: center;
-// }
-
-.front {
- position: absolute;
- box-sizing: border-box;
- width: 93px;
- height: 102px;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 4px;
- border-radius: 20px;
- // background-color: yellow;
- // opacity: 0.5;
-}
-
-.contributions {
- display: flex;
- flex-direction: row;
- gap: 2px;
- // width: 100%;
- margin-top: 15px;
- // background-color: yellow;
- // opacity: 0.5;
- height: 25px;
- z-index: -1;
-
- .faction_token {
- z-index: -1;
- }
-}
-
-.front .value {
- font-weight: bold;
- font-size: 30px;
- margin-top: auto;
- z-index: -1;
-}
-
-#madrid_front {
- width: 104px;
- height: 114px;
- border-radius: 23px;
-}
-
-#madrid_front .contributions {
- padding: 0px 2px;
-}
-
-// .front[data-front-id='m'] {
-// width: 104px;
-// height: 114px;
-// border-radius: 23px;
-// }
-
-.card {
- background-size: 100% 100%;
- // width: 275px;
- // height: 375px;
- width: 198px;
- height: 270px;
- // border: 1px solid black;
- border-radius: 5px;
- box-shadow: 0 0 0 1px #333;
-}
-
-@for $i from 1 through 108 {
- .card[data-card-id='#{$i}'] {
- background-image: url('images/cards100/card_#{$i}.png');
- }
-}
-
-.card.event {
- position: absolute;
- // opacity: 0.5;
- width: 132px;
- height: 180px;
-}
-
-.blank_marker {
- box-sizing: border-box;
- position: absolute;
- background-color: red;
- width: 29px;
- height: 29px;
- border-radius: 50%;
- box-shadow: 0 0 0 1px #333;
- margin-top: 1px;
-}
-
-.faction_token {
- box-sizing: border-box;
- // background-color: yellow;
- // opacity: 0.5;
- background-size: 100% 100%;
- // box-shadow: 0 0 0 1px #333;
-}
-
-#log .faction_token {
- display: inline-block;
- width: 25px;
- height: 25px;
- border-radius: 2px;
- vertical-align: middle;
- margin: 0px 1px;
-}
-
-#glory .faction_token {
- position: absolute;
- width: 34px;
- height: 34px;
- border-radius: 4px;
-}
-
-.contributions .faction_token {
- width: 25px;
- height: 25px;
- border-radius: 4px;
-}
-
-.faction_token[data-faction-id='a'] {
- border: 1px #333 solid;
- background-image: url('images/factions/anarchists.png');
-}
-
-.faction_token[data-faction-id='c'] {
- border: 1px #333 solid;
- background-image: url('images/factions/communitsts.png');
-}
-
-.faction_token[data-faction-id='m'] {
- border: 1px #333 solid;
- background-image: url('images/factions/moderates.png');
-}
-
-.standee {
- box-sizing: border-box;
- position: absolute;
- width: 29px;
- height: 35px;
- background-size: cover;
- background-repeat: no-repeat;
- border-radius: 4px;
- box-shadow: 0 0 0 1px #333;
- transition-property: top, left;
- transition-duration: 700ms;
- transition-timing-function: ease;
- z-index: 1;
- // opacity: 0.6;
-}
-
-.blank_marker.action,
-.bonus.action,
-.card.action,
-.front.action,
-.medallion.action,
-.standee.action {
- box-shadow: 0 0 0 3px $selectable-color;
-}
-
-.blank_marker.action {
- z-index: 2;
-}
-
-.blank_marker.action:hover,
-.bonus.action:hover,
-.card.action:hover,
-.card.selected,
-.front.action:hover,
-.medallion.action:hover,
-.standee.action:hover {
- box-shadow: 0 0 0 3px $selected-color;
-}
-
-.blank_marker.action:hover {
- opacity: 0.5;
-}
-
-// .standee.action:hover {
-// box-shadow: 0 0 0 2px blue;
-// }
-
-@for $i from 0 through 4 {
- .standee[data-standee-id='#{$i}'] {
- background-image: url('images/standees/standee_#{$i}.png');
- }
-}
-
-.bonus {
- box-sizing: border-box;
- position: absolute;
- width: 54px;
- height: 54px;
- background-size: cover;
- background-repeat: no-repeat;
- border-radius: 50%;
-}
-
-@for $i from 0 through 1 {
- .bonus[data-bonus-id='#{$i}'][data-bonus-on='0'] {
- background-image: url('images/bonus/bonus_#{$i}_off.png');
- }
- .bonus[data-bonus-id='#{$i}'][data-bonus-on='1'] {
- background-image: url('images/bonus/bonus_#{$i}_on.png');
- }
-}
-
-.initiative_token {
- width: 47px;
- height: 47px;
- background-size: cover;
- background-repeat: no-repeat;
- margin-right: auto;
- background-image: url('images/initiative_clockwise.png');
-}
-
-.initiative_token[data-year='2'] {
- background-image: url('images/initiative_counter_clockwise.png');
-}
-
-.medallion {
- width: 47px;
- height: 47px;
- background-size: cover;
- background-repeat: no-repeat;
- box-shadow: 0 0 0 1px #333;
- border-radius: 10px;
-}
-
-#medallions .medallion {
- box-sizing: border-box;
- position: absolute;
-}
-
-@for $i from 0 through 8 {
- .medallion[data-medallion-id='#{$i}'] {
- background-image: url('images/medallions/medallion_#{$i}.png');
- }
-}
-
-#log {
- background-color: floralwhite;
-}
-#log .h1,
-#log .h2 {
- font-size: 10px;
- padding-top: 2px;
- padding-bottom: 2px;
- text-align: center;
-}
-#log .h1 {
- // background-color: hsl(4, 40%, 73%);
- background-color: $log-text-background-color;
-}
-#log .h2 {
- // background-color: hsl(250, 40%, 83%);
- background-color: $log-text-background-color;
-}
-#log .h2.anarchist {
- background-color: $anarchist-color;
- color: floralwhite;
-}
-#log .h2.communist {
- background-color: $communist-color;
- color: floralwhite;
-}
-#log .h2.moderate {
- background-color: $moderate-color;
- color: floralwhite;
-}
-#log .h2.fascist {
- background-color: $fascist-color;
-}
-#log div.i {
- padding-left: 32px;
- text-indent: -12px;
-}
-#log div.ii {
- padding-left: 44px;
- text-indent: -12px;
-}
diff --git a/package.json b/package.json
index b63e15d..3463451 100644
--- a/package.json
+++ b/package.json
@@ -4,16 +4,13 @@
"description": "Land and Freedom for RTT",
"main": "rules.js",
"scripts": {
- "build:scss": "sass --no-source-map land-and-freedom.scss land-and-freedom.css",
"build:ts": "tsc",
- "watch:scss": "sass --no-source-map --watch land-and-freedom.scss land-and-freedom.css",
"watch:ts": "tsc --watch",
- "build": "npm run build:ts && npm run build:scss"
+ "build": "npm run build:ts"
},
"author": "Frans Bongers",
"license": "ISC",
"devDependencies": {
- "sass": "^1.81.0",
"typescript": "^5.7.2"
}
}
diff --git a/play.css b/play.css
new file mode 100644
index 0000000..de4ba00
--- /dev/null
+++ b/play.css
@@ -0,0 +1,379 @@
+main { background-color: #666; }
+
+body.Anarchist header.your_turn { background-color: hsl(257, 10%, 65%); }
+body.Communist header.your_turn { background-color: hsl(0, 55%, 65%); }
+body.Moderate header.your_turn { background-color: hsl(325, 30%, 65%); }
+body header.fascist.your_turn { background-color: hsl(30, 35%, 65%); }
+
+#role_Anarchist { background-color: hsl(257, 10%, 70%); }
+#role_Communist { background-color: hsl(0, 55%, 70%); }
+#role_Moderate { background-color: hsl(325, 30%, 70%); }
+
+#log div.h.a { background-color: hsl(257, 10%, 75%); }
+#log div.h.c { background-color: hsl(0, 55%, 75%); }
+#log div.h.m { background-color: hsl(325, 30%, 75%); }
+#log div.h.f { background-color: hsl(30, 35%, 75%); }
+
+.panel { background-image: url(images/texture.png) }
+.panel { background-color: hsl(0, 0%, 60%); }
+.panel.anarchist { background-color: hsl(257, 10%, 60%); }
+.panel.communist { background-color: hsl(0, 35%, 60%); }
+.panel.moderate { background-color: hsl(325, 20%, 60%); }
+.panel.fascist { background-color: hsl(30, 30%, 60%); }
+
+.panel .panel_header { background-color: hsl(0, 0%, 75%); }
+.panel.anarchist .panel_header { background-color: hsl(257, 10%, 75%); }
+.panel.communist .panel_header { background-color: hsl(0, 60%, 75%); }
+.panel.moderate .panel_header { background-color: hsl(325, 35%, 75%); }
+.panel.fascist .panel_header { background-color: hsl(30, 40%, 75%); }
+
+.track.action { border-color: cyan !important; }
+.front.action { border-color: cyan !important; }
+.token.action {
+ box-shadow: 0 0 0 1px black, 0 0 0 5px cyan !important;
+}
+
+.card.action { box-shadow: 0 0 0 4px cyan !important; }
+.card.selected { box-shadow: 0 0 0 4px blue !important; }
+
+
+
+#log div:empty {
+ min-height: 6px;
+}
+
+#log .h {
+ background-color: tan;
+ border-top: 1px solid black; border-bottom: 1px solid black;
+ margin: 8px 0;
+}
+
+#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; }
+
+#mapwrap {
+ width: 1650px;
+ height: 1275px;
+ border: 1px solid black;
+ border-top: none;
+ box-shadow: 2px 2px 4px #0004;
+}
+
+#map {
+ width: 1650px;
+ height: 1275px;
+ background-size: 1650px 1275px;
+ background-image: url(images/map75.jpg);
+}
+
+@media (min-resolution: 97dpi) {
+ #map { background-image: url(images/map150.jpg); }
+}
+
+/* PANELS */
+
+.panel:has(.panel_body:empty) {
+ display: none;
+}
+
+.panel {
+ padding: 4px;
+ max-width: calc(1650px - 8px);
+ min-height: 281px;
+ margin: 24px auto;
+ border: 1px solid black;
+ box-shadow: 2px 2px 4px #0004;
+}
+
+.panel_header {
+ padding: 3px 6px;
+ border: 1px solid black;
+ text-align: center;
+ font-weight: bold;
+ box-shadow: 1px 1px 4px #0003;
+}
+
+.panel_body {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16px;
+ padding: 12px;
+}
+
+/* SPACES */
+
+.track {
+ position: absolute;
+ border: 4px solid transparent;
+ width: 50px;
+ height: 52px;
+}
+
+.front {
+ position: absolute;
+ border: 6px solid transparent;
+ border-radius: 34px;
+}
+
+/* CONTAINERS */
+
+.front_container {
+ position: absolute;
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 4px;
+}
+
+.front_container div {
+ margin-right: -30px;
+ margin-bottom: -30px;
+}
+
+.medallion_container {
+ position: absolute;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+ width: 70px;
+ height: 70px;
+}
+
+.glory_container {
+ position: absolute;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+ gap: 9px;
+ padding: 4px;
+}
+
+.fascist_deck {
+ position: absolute;
+ display: flex;
+ flex-wrap: wrap;
+ height: 282px;
+}
+
+.fascist_deck .card {
+ margin-right: -210px;
+}
+
+.fascist_deck .card_118 { margin-top: -8px; }
+.fascist_deck .card_119 { margin-top: -4px; }
+.fascist_deck .card_120 { margin-top: 0px; }
+
+.current_events {
+ position: absolute;
+ width: 208px;
+ height: 282px;
+}
+
+.token_pool {
+ height: 64px;
+ display: flex;
+ flex-wrap: wrap;
+ padding: 0 0 12px 24px;
+ gap: 8px;
+ align-items: center;
+
+}
+
+/* TOKENS */
+
+#pieces > .token {
+ position: absolute;
+}
+
+.token {
+ border: 3px solid black;
+ width: 45px;
+ height: 45px;
+}
+
+/* :r! node tools/gencolor.js */
+.token.white { background-color: #bbbbbb; border-color: #d6d6d6 #737373 #737373 #d6d6d6; box-shadow: 0 0 0 1px #323232, 0px 1px 4px #0008; }
+.token.red { background-color: #ef0500; border-color: #ff5d49 #b40000 #b40000 #ff5d49; box-shadow: 0 0 0 1px #790000, 0px 1px 4px #0008; }
+.token.pink { background-color: #da997b; border-color: #fdba9b #94593c #94593c #fdba9b; box-shadow: 0 0 0 1px #521d00, 0px 1px 4px #0008; }
+.token.brown { background-color: #bbb079; border-color: #dbd097 #786d38 #786d38 #dbd097; box-shadow: 0 0 0 1px #3b3000, 0px 1px 4px #0008; }
+.token.gray { background-color: #a6a6a8; border-color: #c9c9cb #676769 #676769 #c9c9cb; box-shadow: 0 0 0 1px #2f2f30, 0px 1px 4px #0008; }
+
+.token.blank { width: 45px; height: 45px; }
+.token.round { width: 58px; height: 58px; }
+.token.square { width: 45px; height: 45px; }
+.token.standee { width: 44px; height: 54px; }
+.token.medallion { width: 53px; height: 53px; }
+
+.token.round, .token.blank { border-radius: 50%; }
+.token.standee { border-radius: 8px; }
+.token.medallion { border-radius: 8px; }
+
+/* CARDS */
+
+.card {
+ background-size: 100% 100%;
+ width: 206px;
+ height: 281px;
+ border-radius: 10px;
+ box-shadow: 0 0 0 1px black, 1px 1px 4px #0006;
+}
+
+/* IMAGES */
+
+.front_plus { background-image: url(images/tokens75/front_plus.png); }
+.front_minus { background-image: url(images/tokens75/front_minus.png); }
+
+.standee_0 { background-image: url(images/tokens75/standee_0.png); }
+.standee_1 { background-image: url(images/tokens75/standee_1.png); }
+.standee_2 { background-image: url(images/tokens75/standee_2.png); }
+.standee_3 { background-image: url(images/tokens75/standee_3.png); }
+.standee_4 { background-image: url(images/tokens75/standee_4.png); }
+
+.medallion_0 { background-image: url(images/tokens75/medallion_0.png); }
+.medallion_1 { background-image: url(images/tokens75/medallion_1.png); }
+.medallion_2 { background-image: url(images/tokens75/medallion_2.png); }
+.medallion_3 { background-image: url(images/tokens75/medallion_3.png); }
+.medallion_4 { background-image: url(images/tokens75/medallion_4.png); }
+.medallion_5 { background-image: url(images/tokens75/medallion_5.png); }
+.medallion_6 { background-image: url(images/tokens75/medallion_6.png); }
+.medallion_7 { background-image: url(images/tokens75/medallion_7.png); }
+.medallion_8 { background-image: url(images/tokens75/medallion_8.png); }
+.medallion_9 { background-image: url(images/tokens75/medallion_9.png); }
+
+.bonus_morale { background-image: url(images/tokens75/bonus_morale_on.png); }
+.bonus_morale.off { background-image: url(images/tokens75/bonus_morale_off.png); }
+.bonus_teamwork { background-image: url(images/tokens75/bonus_teamwork_on.png); }
+.bonus_teamwork.off { background-image: url(images/tokens75/bonus_teamwork_off.png); }
+
+.player.anarchist { background-image: url(images/tokens75/player_anarchist.png); }
+.player.communist { background-image: url(images/tokens75/player_communist.png); }
+.player.moderate { background-image: url(images/tokens75/player_moderate.png); }
+
+.initiative { background-image: url("images/tokens75/initiative_cw.png"); }
+.initiative.ccw { background-image: url("images/tokens75/initiative_ccw.png"); }
+
+.hero_point { background-image: url("images/tokens75/hero_point.png"); }
+
+.card_1 { background-image: url(images/cards75/card_1.avif); }
+.card_2 { background-image: url(images/cards75/card_2.avif); }
+.card_3 { background-image: url(images/cards75/card_3.avif); }
+.card_4 { background-image: url(images/cards75/card_4.avif); }
+.card_5 { background-image: url(images/cards75/card_5.avif); }
+.card_6 { background-image: url(images/cards75/card_6.avif); }
+.card_7 { background-image: url(images/cards75/card_7.avif); }
+.card_8 { background-image: url(images/cards75/card_8.avif); }
+.card_9 { background-image: url(images/cards75/card_9.avif); }
+.card_10 { background-image: url(images/cards75/card_10.avif); }
+.card_11 { background-image: url(images/cards75/card_11.avif); }
+.card_12 { background-image: url(images/cards75/card_12.avif); }
+.card_13 { background-image: url(images/cards75/card_13.avif); }
+.card_14 { background-image: url(images/cards75/card_14.avif); }
+.card_15 { background-image: url(images/cards75/card_15.avif); }
+.card_16 { background-image: url(images/cards75/card_16.avif); }
+.card_17 { background-image: url(images/cards75/card_17.avif); }
+.card_18 { background-image: url(images/cards75/card_18.avif); }
+.card_19 { background-image: url(images/cards75/card_19.avif); }
+.card_20 { background-image: url(images/cards75/card_20.avif); }
+.card_21 { background-image: url(images/cards75/card_21.avif); }
+.card_22 { background-image: url(images/cards75/card_22.avif); }
+.card_23 { background-image: url(images/cards75/card_23.avif); }
+.card_24 { background-image: url(images/cards75/card_24.avif); }
+.card_25 { background-image: url(images/cards75/card_25.avif); }
+.card_26 { background-image: url(images/cards75/card_26.avif); }
+.card_27 { background-image: url(images/cards75/card_27.avif); }
+.card_28 { background-image: url(images/cards75/card_28.avif); }
+.card_29 { background-image: url(images/cards75/card_29.avif); }
+.card_30 { background-image: url(images/cards75/card_30.avif); }
+.card_31 { background-image: url(images/cards75/card_31.avif); }
+.card_32 { background-image: url(images/cards75/card_32.avif); }
+.card_33 { background-image: url(images/cards75/card_33.avif); }
+.card_34 { background-image: url(images/cards75/card_34.avif); }
+.card_35 { background-image: url(images/cards75/card_35.avif); }
+.card_36 { background-image: url(images/cards75/card_36.avif); }
+.card_37 { background-image: url(images/cards75/card_37.avif); }
+.card_38 { background-image: url(images/cards75/card_38.avif); }
+.card_39 { background-image: url(images/cards75/card_39.avif); }
+.card_40 { background-image: url(images/cards75/card_40.avif); }
+.card_41 { background-image: url(images/cards75/card_41.avif); }
+.card_42 { background-image: url(images/cards75/card_42.avif); }
+.card_43 { background-image: url(images/cards75/card_43.avif); }
+.card_44 { background-image: url(images/cards75/card_44.avif); }
+.card_45 { background-image: url(images/cards75/card_45.avif); }
+.card_46 { background-image: url(images/cards75/card_46.avif); }
+.card_47 { background-image: url(images/cards75/card_47.avif); }
+.card_48 { background-image: url(images/cards75/card_48.avif); }
+.card_49 { background-image: url(images/cards75/card_49.avif); }
+.card_50 { background-image: url(images/cards75/card_50.avif); }
+.card_51 { background-image: url(images/cards75/card_51.avif); }
+.card_52 { background-image: url(images/cards75/card_52.avif); }
+.card_53 { background-image: url(images/cards75/card_53.avif); }
+.card_54 { background-image: url(images/cards75/card_54.avif); }
+.card_55 { background-image: url(images/cards75/card_55.avif); }
+.card_56 { background-image: url(images/cards75/card_56.avif); }
+.card_57 { background-image: url(images/cards75/card_57.avif); }
+.card_58 { background-image: url(images/cards75/card_58.avif); }
+.card_59 { background-image: url(images/cards75/card_59.avif); }
+.card_60 { background-image: url(images/cards75/card_60.avif); }
+.card_61 { background-image: url(images/cards75/card_61.avif); }
+.card_62 { background-image: url(images/cards75/card_62.avif); }
+.card_63 { background-image: url(images/cards75/card_63.avif); }
+.card_64 { background-image: url(images/cards75/card_64.avif); }
+.card_65 { background-image: url(images/cards75/card_65.avif); }
+.card_66 { background-image: url(images/cards75/card_66.avif); }
+.card_67 { background-image: url(images/cards75/card_67.avif); }
+.card_68 { background-image: url(images/cards75/card_68.avif); }
+.card_69 { background-image: url(images/cards75/card_69.avif); }
+.card_70 { background-image: url(images/cards75/card_70.avif); }
+.card_71 { background-image: url(images/cards75/card_71.avif); }
+.card_72 { background-image: url(images/cards75/card_72.avif); }
+.card_73 { background-image: url(images/cards75/card_73.avif); }
+.card_74 { background-image: url(images/cards75/card_74.avif); }
+.card_75 { background-image: url(images/cards75/card_75.avif); }
+.card_76 { background-image: url(images/cards75/card_76.avif); }
+.card_77 { background-image: url(images/cards75/card_77.avif); }
+.card_78 { background-image: url(images/cards75/card_78.avif); }
+.card_79 { background-image: url(images/cards75/card_79.avif); }
+.card_80 { background-image: url(images/cards75/card_80.avif); }
+.card_81 { background-image: url(images/cards75/card_81.avif); }
+.card_82 { background-image: url(images/cards75/card_82.avif); }
+.card_83 { background-image: url(images/cards75/card_83.avif); }
+.card_84 { background-image: url(images/cards75/card_84.avif); }
+.card_85 { background-image: url(images/cards75/card_85.avif); }
+.card_86 { background-image: url(images/cards75/card_86.avif); }
+.card_87 { background-image: url(images/cards75/card_87.avif); }
+.card_88 { background-image: url(images/cards75/card_88.avif); }
+.card_89 { background-image: url(images/cards75/card_89.avif); }
+.card_90 { background-image: url(images/cards75/card_90.avif); }
+.card_91 { background-image: url(images/cards75/card_91.avif); }
+.card_92 { background-image: url(images/cards75/card_92.avif); }
+.card_93 { background-image: url(images/cards75/card_93.avif); }
+.card_94 { background-image: url(images/cards75/card_94.avif); }
+.card_95 { background-image: url(images/cards75/card_95.avif); }
+.card_96 { background-image: url(images/cards75/card_96.avif); }
+.card_97 { background-image: url(images/cards75/card_97.avif); }
+.card_98 { background-image: url(images/cards75/card_98.avif); }
+.card_99 { background-image: url(images/cards75/card_99.avif); }
+.card_100 { background-image: url(images/cards75/card_100.avif); }
+.card_101 { background-image: url(images/cards75/card_101.avif); }
+.card_102 { background-image: url(images/cards75/card_102.avif); }
+.card_103 { background-image: url(images/cards75/card_103.avif); }
+.card_104 { background-image: url(images/cards75/card_104.avif); }
+.card_105 { background-image: url(images/cards75/card_105.avif); }
+.card_106 { background-image: url(images/cards75/card_106.avif); }
+.card_107 { background-image: url(images/cards75/card_107.avif); }
+.card_108 { background-image: url(images/cards75/card_108.avif); }
+.card_109 { background-image: url(images/cards75/card_109.avif); }
+.card_110 { background-image: url(images/cards75/card_110.avif); }
+.card_111 { background-image: url(images/cards75/card_111.avif); }
+.card_112 { background-image: url(images/cards75/card_112.avif); }
+.card_113 { background-image: url(images/cards75/card_113.avif); }
+.card_114 { background-image: url(images/cards75/card_114.avif); }
+.card_115 { background-image: url(images/cards75/card_115.avif); }
+.card_116 { background-image: url(images/cards75/card_116.avif); }
+.card_117 { background-image: url(images/cards75/card_117.avif); }
+.card_118 { background-image: url(images/cards75/card_118.avif); }
+.card_119 { background-image: url(images/cards75/card_119.avif); }
+.card_120 { background-image: url(images/cards75/card_120.avif); }
diff --git a/play.html b/play.html
index d89b2a9..edc11f3 100644
--- a/play.html
+++ b/play.html
@@ -9,15 +9,10 @@
<link rel="icon" href="favicon.png">
<link rel="stylesheet" href="/fonts/fonts.css">
<link rel="stylesheet" href="/common/client.css">
-<link rel="stylesheet" href="land-and-freedom.css">
+<link rel="stylesheet" href="play.css">
<script defer src="/common/client.js"></script>
-<script>var exports = {};</script>
-<script defer src="data.js"></script>
<script defer src="play.js"></script>
</head>
-<style>
-/* insert stylesheet here */
-</style>
<body>
<header>
@@ -26,104 +21,59 @@
<summary><img src="/images/cog.svg"></summary>
<menu>
<li><a href="info/rules.html" target="_blank">Rulebook</a>
+ <li><a href="info/cards.html" target="_blank">Cards</a>
<li><a href="info/pac_a.html" target="_blank">Player Aid: Anarchist</a>
<li><a href="info/pac_c.html" target="_blank">Player Aid: Communist</a>
<li><a href="info/pac_m.html" target="_blank">Player Aid: Moderate</a>
</menu>
</details>
+ <button onclick="toggle_pieces()"><img src="/images/earth-africa-europe.svg"></button>
</div>
</header>
<aside>
- <div class="game_info">
- <span id="year"></span>
- <span id="pool_hero_points"></span>
- <span id="bag_of_glory"></span>
- </div>
- <div id="roles">
- <div id="role_Anarchist" class="role">
- <div class="role_name"><span>Anarchist</span></div>
- <div class="role_stat"></div>
- <div class="role_user">-</div>
- <div class="role_medallions"></div>
- <div class="role_info"></div>
- </div>
- <div id="role_Communist" class="role">
- <div class="role_name"><span>Communist</span></div>
- <div class="role_stat"></div>
- <div class="role_user">-</div>
- <div class="role_medallions"></div>
- <div class="role_info"></div>
- </div>
- <div id="role_Moderate" class="role">
- <div class="role_name"><span>Moderate</span></div>
- <div class="role_stat"></div>
- <div class="role_user">-</div>
- <div class="role_medallions"></div>
- <div class="role_info"></div>
- </div>
- <div id="turn_info">
- <div id="turn_info_card" class="card"></div>
- </div>
- </div>
+ <div id="roles"></div>
<div id="log"></div>
</aside>
<main>
<div id="mapwrap">
<div id="map">
- <div id="fronts">
- <div id="northern_front" class="front" style="left: 89px; top: 96px;">
- <div class="contributions"></div>
- <span class="value"></span>
- </div>
- <div id="aragon_front" class="front" style="left: 340px; top: 182px;">
- <div class="contributions"></div>
- <span class="value"></span>
- </div>
- <div id="madrid_front" class="front" style="left: 115px; top: 262px;">
- <div class="contributions"></div>
- <span class="value"></span>
- </div>
- <div id="southern_front" class="front" style="left: 205px; top: 426px;">
- <div class="contributions"></div>
- <span class="value"></span>
- </div>
+ <div id="pieces">
</div>
- <div id="glory"></div>
- <div id="spaces"></div>
- <div id="pieces"></div>
- <div id="markers"></div>
- <div id="medallions"></div>
- <div id="tracks"></div>
- <div id="current_events"></div>
</div>
</div>
- <div id="selectable_cards"></div>
- <div id="player_area" class="panel">
- <div id="player_area_header" class="panel_header">
- <div id="hand_tab" data-active="active" class="player_area_tab">Hand</div>
- <div id="deck_tab" data-active="inactive" class="player_area_tab">Deck</div>
- <div id="discard_tab" data-active="inactive" class="player_area_tab">Discard Pile</div>
- <div id="trash_tab" data-active="inactive" class="player_area_tab">Trash</div>
- </div>
- <div id="hand" data-active="active" class="panel_body"></div>
- <div id="deck" data-active="inactive" class="panel_body"></div>
- <div id="discard" data-active="inactive" class="panel_body"></div>
- <div id="trash" data-active="inactive" class="panel_body"></div>
+
+ <div class="panel fascist">
+ <div class="panel_header">Fascist Cards</div>
+ <div id="fascist_cards" class="panel_body"></div>
+ </div>
+
+ <div class="panel trash">
+ <div class="panel_header">Trash</div>
+ <div id="trash" class="panel_body"></div>
</div>
+
+ <div id="hand_panel" class="panel">
+ <div class="panel_header">Hand</div>
+ <div id="hand" class="panel_body"></div>
+ </div>
+
<div id="player_areas">
- <div id="player_area_Anarchist" class="panel">
- <div class="panel_header" data-faction-id="a">Anarchist Tableau</div>
+ <div id="player_area_a" class="panel anarchist">
+ <div class="panel_header">Anarchist Tableau</div>
<div id="tableau_a" class="panel_body"></div>
+ <div id="tokens_a" class="token_pool"></div>
</div>
- <div id="player_area_Communist" class="panel">
- <div class="panel_header" data-faction-id="c">Communist Tableau</div>
+ <div id="player_area_c" class="panel communist">
+ <div class="panel_header">Communist Tableau</div>
<div id="tableau_c" class="panel_body"></div>
+ <div id="tokens_c" class="token_pool"></div>
</div>
- <div id="player_area_Moderate" class="panel">
- <div class="panel_header" data-faction-id="m">Moderate Tableau</div>
+ <div id="player_area_m" class="panel moderate">
+ <div class="panel_header">Moderate Tableau</div>
<div id="tableau_m" class="panel_body"></div>
+ <div id="tokens_m" class="token_pool"></div>
</div>
</div>
</main>
diff --git a/play.js b/play.js
index 5b002e8..9ef91e1 100644
--- a/play.js
+++ b/play.js
@@ -1,495 +1,591 @@
-'use strict';
-Object.defineProperty(exports, "__esModule", { value: true });
-const BONUSES_COUNT = 2;
-const CARD_COUNT = 109;
-const GLORY_COUNT = 9;
-const medallionS_COUNT = 9;
-const STANDEES_COUNT = 5;
-const TRACK_COUNT = 5;
-const TRACK_LENGTH = 11;
-const FACTIONS = ['a', 'c', 'm'];
+"use strict"
+
+/* global view, action_button, send_action */
+
+// TODO: pool for hero points to animate ?
+// TODO: pool for faction markers to animate ?
+
const ui = {
- bag_of_glory: document.getElementById('bag_of_glory'),
- map: document.getElementById('map'),
- medallions_container: document.getElementById('medallions'),
- markers: document.getElementById('markers'),
- fronts: {
- a: {
- front: document.getElementById('aragon_front'),
- value: document.querySelector('#aragon_front .value'),
- contributions: document.querySelector('#aragon_front .contributions'),
- },
- m: {
- front: document.getElementById('madrid_front'),
- value: document.querySelector('#madrid_front .value'),
- contributions: document.querySelector('#madrid_front .contributions'),
- },
- n: {
- front: document.getElementById('northern_front'),
- value: document.querySelector('#northern_front .value'),
- contributions: document.querySelector('#northern_front .contributions'),
- },
- s: {
- front: document.getElementById('southern_front'),
- value: document.querySelector('#southern_front .value'),
- contributions: document.querySelector('#southern_front .contributions'),
- },
- },
- glory_container: document.getElementById('glory'),
- hand: document.getElementById('hand'),
- player_area: document.getElementById('player_area'),
- current_events: document.getElementById('current_events'),
- roles: {
- a: {
- hero_points: document.querySelector('#role_Anarchist .role_stat'),
- medallions: document.querySelector('#role_Anarchist .role_medallions'),
- },
- c: {
- hero_points: document.querySelector('#role_Communist .role_stat'),
- medallions: document.querySelector('#role_Communist .role_medallions'),
- },
- m: {
- hero_points: document.querySelector('#role_Moderate .role_stat'),
- medallions: document.querySelector('#role_Moderate .role_medallions'),
- },
- pool: {
- hero_points: document.getElementById('pool_hero_points'),
- },
- Anarchist: document.getElementById('role_Anarchist'),
- Communist: document.getElementById('role_Communist'),
- Moderate: document.getElementById('role_Moderate'),
- container: document.getElementById('roles'),
- },
- player_area_tabs: {
- hand_tab: document.getElementById('hand_tab'),
- deck_tab: document.getElementById('deck_tab'),
- discard_tab: document.getElementById('discard_tab'),
- trash_tab: document.getElementById('trash_tab'),
- },
- player_area_cards: {
- hand: document.getElementById('hand'),
- deck: document.getElementById('deck'),
- discard: document.getElementById('discard'),
- trash: document.getElementById('trash'),
- },
- player_areas: {
- container: document.getElementById('player_areas'),
- Anarchist: document.getElementById('player_area_Anarchist'),
- Communist: document.getElementById('player_area_Communist'),
- Moderate: document.getElementById('player_area_Moderate'),
- },
- selectable_cards: document.getElementById('selectable_cards'),
- tableaus: {
- a: document.getElementById('tableau_a'),
- c: document.getElementById('tableau_c'),
- m: document.getElementById('tableau_m'),
- },
- tracks: document.getElementById('tracks'),
- hero_points: document.querySelector('#role_Anarchist .role_stat'),
- turn_info: document.getElementById('turn_info'),
- turn_info_card: document.getElementById('turn_info_card'),
- year: document.getElementById('year'),
- blank_markers: [],
- bonuses: [],
- tokens_on_front: {},
- glory: [],
- initiative_token: undefined,
- medallions: [],
- spaces: [],
- standees: [],
- pieces: [],
- cards: [],
-};
-let action_register = [];
-const LAYOUT_BONUSES = [
- [435, 481],
- [493, 481],
-];
-const LAYOUT_CURRENT_EVENTS = [
- [172, 648],
- [309, 648],
- [445, 648],
- [584, 648],
-];
-const LAYOUT_GLORY = [
- [801, 647],
- [860, 647],
- [897, 647],
- [848, 718],
- [775, 771],
- [812, 771],
- [849, 771],
- [885, 771],
- [922, 771],
-];
-const LAYOUT_medallionS = [
- [364, 556],
- [415, 556],
- [466, 556],
- [517, 556],
- [568, 556],
-];
-const LAYOUT_TRACKS = [
- [
- [581, 46],
- [618, 46],
- [655, 46],
- [691, 46],
- [728, 46],
- [765, 46],
- [801, 46],
- [838, 46],
- [874, 46],
- [911, 46],
- [948, 46],
- ],
- [
- [581, 156],
- [618, 156],
- [655, 156],
- [691, 156],
- [728, 156],
- [765, 156],
- [801, 156],
- [838, 156],
- [874, 156],
- [911, 156],
- [948, 156],
- ],
- [
- [581, 267],
- [618, 267],
- [655, 267],
- [691, 267],
- [728, 267],
- [765, 267],
- [801, 267],
- [838, 267],
- [874, 267],
- [911, 267],
- [948, 267],
- ],
- [
- [581, 378],
- [618, 378],
- [655, 378],
- [691, 378],
- [728, 378],
- [765, 378],
- [801, 378],
- [838, 378],
- [874, 378],
- [911, 378],
- [948, 378],
- ],
- [
- [581, 489],
- [618, 489],
- [655, 489],
- [691, 489],
- [728, 489],
- [765, 489],
- [801, 489],
- [838, 489],
- [874, 489],
- [911, 489],
- [948, 489],
- ],
-];
+ header: document.querySelector("header"),
+ hand_panel: document.getElementById("hand_panel"),
+
+ map_container: document.getElementById("pieces"),
+
+ fascist_cards: document.getElementById("fascist_cards"),
+ trash: document.getElementById("trash"),
+ hand: document.getElementById("hand"),
+
+ player_areas: document.getElementById("player_areas"),
+ panels: {
+ a: document.getElementById("player_area_a"),
+ c: document.getElementById("player_area_c"),
+ m: document.getElementById("player_area_m"),
+ },
+ tableaus: {
+ a: document.getElementById("tableau_a"),
+ c: document.getElementById("tableau_c"),
+ m: document.getElementById("tableau_m"),
+ },
+ tokens: {
+ a: document.getElementById("tokens_a"),
+ c: document.getElementById("tokens_c"),
+ m: document.getElementById("tokens_m"),
+ },
+
+ // spaces
+ tracks_x: [],
+ tracks: [],
+ fronts: [],
+ con_fronts: [],
+ str_fronts: [],
+
+ // tokens
+ standees: [],
+ bonuses: [],
+ medallions: [],
+ blank_markers: [],
+
+ // cards
+ cards: [],
+}
+
+/* :r! node tools/parse-layout.js */
+const boxes = {
+ "Northern": [149,146,149,149],
+ "Aragon": [563,275,149,149],
+ "Southern": [341,641,149,149],
+ "Madrid": [195,398,161,161],
+ "Liberty": [955,65,662,59],
+ "Collectivization": [954,231,663,60],
+ "SovietSupport": [954,564,663,59],
+ "ForeignAid": [954,730,663,59],
+ "Government": [1015,398,602,58],
+ "Medallion1": [608,834,69,70],
+ "Medallion2": [690,834,70,70],
+ "Medallion3": [772,834,70,70],
+ "Medallion4": [855,834,70,70],
+ "Medallion5": [937,834,70,70],
+ "CurrentYearDeck": [38,976,187,261],
+ "CurrentEvent1": [301,976,187,261],
+ "CurrentEvent2": [526,976,187,261],
+ "CurrentEvent3": [751,976,187,261],
+ "CurrentEvent4": [976,976,187,261],
+ "Glory1": [1320,966,59,59],
+ "Glory2": [1418,966,119,59],
+ "Glory3": [1399,1075,58,58],
+ "Glory4": [1278,1153,300,59],
+ "Bonus1": [728,726,74,73],
+ "Bonus2": [818,726,74,73],
+}
+
+function toggle_pieces() { // eslint-disable-line no-unused-vars
+ ui.map_container.classList.toggle("hide")
+}
+
+let animation_register = []
+
+function register_animation(e) {
+ animation_register.push(e)
+}
+
+function remember_position(e) {
+ if (e.parentElement) {
+ let prect = e.parentElement.getBoundingClientRect()
+ let rect = e.getBoundingClientRect()
+ e.my_visible = 1
+ e.my_parent = e.parentElement
+ e.my_px = prect.x
+ e.my_py = prect.y
+ e.my_x = rect.x
+ e.my_y = rect.y
+ } else {
+ e.my_visible = 0
+ e.my_parent = null
+ e.my_x = 0
+ e.my_y = 0
+ e.my_z = 0
+ }
+}
+
+function animate_position(e) {
+ if (e.parentElement && e.my_visible) {
+ let prect = e.parentElement.getBoundingClientRect()
+ let rect = e.getBoundingClientRect()
+ let dx, dy
+ if (e.parentElement === e.my_parent) {
+ // animate element within animated element...
+ dx = (e.my_x - e.my_px) - (rect.x - prect.x)
+ dy = (e.my_y - e.my_py) - (rect.y - prect.y)
+ } else {
+ dx = e.my_x - rect.x
+ dy = e.my_y - rect.y
+ }
+
+ // fade in
+ if (!e.my_parent) {
+ e.animate(
+ [
+ { opacity: 0 },
+ { opacity: 1 }
+ ],
+ { duration: 500, easing: "ease" }
+ )
+ }
+
+ if (dx !== 0 || dy !== 0) {
+ let dist = Math.sqrt((dx * dx) + (dy * dy))
+ let time = Math.max(500, Math.min(1000, dist / 2))
+ e.animate(
+ [
+ { transform: `translate(${dx}px, ${dy}px)`, },
+ { transform: "translate(0, 0)", },
+ ],
+ { duration: time, easing: "ease" }
+ )
+ }
+ }
+}
+
+
+let action_register = []
+
function register_action(e, action, id) {
- e.my_action = action;
- e.my_id = id;
- e.onmousedown = on_click_action;
- action_register.push(e);
+ e.my_action = action
+ e.my_id = id
+ e.onmousedown = on_click_action
+ action_register.push(e)
}
+
function on_click_action(evt) {
- if (evt.button === 0)
- if (send_action(evt.target.my_action, evt.target.my_id))
- evt.stopPropagation();
-}
-function on_click_tab(evt) {
- evt.stopPropagation();
- const { id } = evt.target;
- Object.entries(ui.player_area_tabs).forEach(([tab, elt]) => {
- const cards_area = ui.player_area_cards[tab.split('_')[0]];
- if (tab === id) {
- elt.setAttribute('data-active', 'active');
- cards_area.setAttribute('data-active', 'active');
- }
- else {
- elt.setAttribute('data-active', 'inactive');
- cards_area.setAttribute('data-active', 'inactive');
- }
- });
+ if (evt.button === 0)
+ if (send_action(evt.target.my_action, evt.target.my_id))
+ evt.stopPropagation()
}
+
function is_action(action, arg) {
- if (arg === undefined)
- return !!(view.actions && view.actions[action] === 1);
- return !!(view.actions &&
- view.actions[action] &&
- view.actions[action].includes(arg));
+ if (arg === undefined)
+ return !!(view.actions && view.actions[action] === 1)
+ return !!(view.actions && view.actions[action] && view.actions[action].includes(arg))
+}
+
+let on_init_once = false
+
+function build_track(t, action_name, track_name, a, b) {
+ let [x, y, w, h] = boxes[track_name]
+ let e
+
+ ui.tracks[t] = []
+ ui.tracks_x[t] = []
+ for (let s = a; s <= b; ++s) {
+ const bm = t * 11 + s
+
+ e = (ui.blank_markers[bm] = document.createElement("div"))
+ e.className = "red token blank hide"
+ e.style.top = Math.round(y+4) + "px"
+ e.style.left = Math.round(x+3) + "px"
+ register_action(e, "blank_marker", bm)
+ ui.map_container.appendChild(e)
+
+ ui.tracks_x[t][s] = Math.round(x) + 4 + "px"
+ e = ui.tracks[t][s] = document.createElement("div")
+ e.className = "track"
+ e.style.top = y + "px"
+ e.style.left = Math.round(x) + "px"
+ register_action(e, action_name, s)
+ ui.map_container.appendChild(e)
+
+ x += 60.5
+ }
+
+ e = (ui.standees[t] = document.createElement("div"))
+ e.className = "white token standee standee_" + t
+ e.style.top = y - 10 + "px"
+ e.style.left = boxes[track_name][0] + 4 + "px"
+ register_action(e, "standee", t)
+ register_animation(e)
+ ui.map_container.appendChild(ui.standees[t])
}
-let on_init_once = false;
+
+function build_bonus(b, box_name, cname) {
+ var [x, y, w, h] = boxes[box_name]
+ var e = ui.bonuses[b] = document.createElement("div")
+ e.className = "red token round " + cname
+ e.style.top = Math.round(y + w/2 - 32) + "px"
+ e.style.left = Math.round(x + w/2 - 32) + "px"
+ register_action(e, "bonus", b)
+ ui.map_container.appendChild(e)
+}
+
+function build_front(i, action_id, box_name) {
+ var [x, y, w, h] = boxes[box_name]
+ var e
+
+ e = ui.con_fronts[i] = document.createElement("div")
+ e.className = "front_container"
+ e.style.top = y + 5 + "px"
+ e.style.left = x + "px"
+ e.style.width = w - 25 + "px"
+ e.style.height = 55 + "px"
+ ui.map_container.appendChild(e)
+
+ e = ui.str_fronts[i] = document.createElement("div")
+ e.className = "front_container"
+ e.style.top = y + h - 55 - 5+ "px"
+ e.style.left = x + "px"
+ e.style.width = w - 25 + "px"
+ e.style.height = 55 + "px"
+ ui.map_container.appendChild(e)
+
+ e = ui.fronts[i] = document.createElement("div")
+ e.className = "front"
+ e.style.top = Math.round(y-3) + "px"
+ e.style.left = Math.round(x-3) + "px"
+ e.style.width = Math.round(w - 6) + "px"
+ e.style.height = Math.round(h - 6) + "px"
+ register_action(e, "front", action_id)
+ ui.map_container.appendChild(e)
+}
+
function on_init() {
- if (on_init_once)
- return;
- on_init_once = true;
- Object.values(ui.player_area_tabs).forEach((element) => {
- element.addEventListener('click', on_click_tab);
- });
- for (const player of view.player_order) {
- ui.player_areas.container.insertAdjacentElement('beforeend', ui.player_areas[player]);
- ui.roles.container.insertAdjacentElement('beforeend', ui.roles[player]);
- }
- ui.roles.container.insertAdjacentElement('beforeend', ui.turn_info);
- if (view.current === 'Observer') {
- ui.player_area.style.display = 'none';
- }
- else {
- document
- .getElementById('player_area_header')
- .setAttribute('data-faction-id', view.current_player_faction);
- }
- for (let t = 0; t < TRACK_COUNT; ++t) {
- for (let s = 0; s < TRACK_LENGTH; ++s) {
- const bm = t * 11 + s;
- let e = (ui.blank_markers[bm] = document.createElement('div'));
- e.className = 'blank_marker';
- register_action(e, 'blank_marker', bm);
- }
- }
- for (let b = 0; b < BONUSES_COUNT; ++b) {
- let e = (ui.bonuses[b] = document.createElement('div'));
- e.className = 'bonus';
- e.setAttribute('data-bonus-id', '' + b);
- e.style.left = LAYOUT_BONUSES[b][0] + 'px';
- e.style.top = LAYOUT_BONUSES[b][1] + 'px';
- LAYOUT_BONUSES;
- register_action(e, 'bonus', b);
- ui.map.appendChild(ui.bonuses[b]);
- }
- for (let g = 0; g < GLORY_COUNT; ++g) {
- let e = (ui.glory[g] = document.createElement('div'));
- e.className = 'faction_token';
- ui.glory_container.appendChild(ui.glory[g]);
- e.style.left = LAYOUT_GLORY[g][0] + 'px';
- e.style.top = LAYOUT_GLORY[g][1] + 'px';
- }
- for (let m = 0; m < medallionS_COUNT; ++m) {
- let e = (ui.medallions[m] = document.createElement('div'));
- e.className = 'medallion';
- e.setAttribute('data-medallion-id', '' + m);
- register_action(e, 'medallion', m);
- }
- for (let s = 0; s < STANDEES_COUNT; ++s) {
- let e = (ui.standees[s] = document.createElement('div'));
- e.className = 'standee';
- e.setAttribute('data-standee-id', '' + s);
- register_action(e, 'standee', s);
- ui.tracks.appendChild(ui.standees[s]);
- }
- for (let c = 1; c < CARD_COUNT; ++c) {
- let e = (ui.cards[c] = document.createElement('div'));
- e.className = 'card';
- e.setAttribute('data-card-id', '' + data.cards[c].id);
- register_action(e, 'card', c);
- }
- let e = (ui.initiative_token = document.createElement('div'));
- e.className = 'initiative_token';
- data.fronts.forEach((front) => {
- ui.tokens_on_front[front.id] = {};
- FACTIONS.forEach((faction_id) => {
- let e = (ui.tokens_on_front[front.id][faction_id] =
- document.createElement('div'));
- e.className = 'faction_token';
- e.setAttribute('data-faction-id', faction_id);
- });
- });
- Object.keys(ui.fronts).forEach((front_id) => {
- register_action(ui.fronts[front_id].front, 'front', front_id);
- });
+ var i, e
+
+ if (on_init_once)
+ return
+ on_init_once = true
+
+ ui.roles_list = document.getElementById("roles"),
+ ui.roles = {
+ a: document.getElementById("role_Anarchist"),
+ c: document.getElementById("role_Communist"),
+ m: document.getElementById("role_Moderate"),
+ }
+
+ build_track(0, "tr_liberty", "Liberty", 0, 10)
+ build_track(1, "tr_collectivization", "Collectivization", 0, 10)
+ build_track(2, "tr_government", "Government", 1, 10)
+ build_track(3, "tr_soviet_support", "SovietSupport", 0, 10)
+ build_track(4, "tr_foreign_aid", "ForeignAid", 0, 10)
+
+ build_bonus(0, "Bonus1", "bonus_morale")
+ build_bonus(1, "Bonus2", "bonus_teamwork")
+
+ build_front(0, "a", "Aragon")
+ build_front(1, "m", "Madrid")
+ build_front(2, "n", "Northern")
+ build_front(3, "s", "Southern")
+
+ ui.medallion_container = []
+ for (i = 0; i < 5; ++i) {
+ e = ui.medallion_container[i] = document.createElement("div")
+ e.className = "medallion_container"
+ e.style.top = boxes["Medallion"+(i+1)][1] + "px"
+ e.style.left = boxes["Medallion"+(i+1)][0] + "px"
+ ui.map_container.appendChild(e)
+ }
+
+ ui.raw_glory_container = []
+ for (i = 0; i < 4; ++i) {
+ e = ui.raw_glory_container[i] = document.createElement("div")
+ e.className = "glory_container"
+ e.style.left = boxes["Glory" + (i+1)][0] + "px"
+ e.style.top = boxes["Glory" + (i+1)][1] + "px"
+ ui.map_container.appendChild(e)
+ }
+ ui.glory_container = [
+ ui.raw_glory_container[0],
+ ui.raw_glory_container[1],
+ ui.raw_glory_container[1],
+ ui.raw_glory_container[2],
+ ui.raw_glory_container[3],
+ ui.raw_glory_container[3],
+ ui.raw_glory_container[3],
+ ui.raw_glory_container[3],
+ ui.raw_glory_container[3],
+ ]
+
+ e = ui.fascist_deck = document.createElement("div")
+ e.className = "fascist_deck"
+ e.style.top = boxes["CurrentYearDeck"][1] - 10 + "px"
+ e.style.left = boxes["CurrentYearDeck"][0] - 10 + "px"
+ ui.map_container.appendChild(e)
+
+ ui.current_events = []
+ for (i = 0; i < 4; ++i) {
+ e = (ui.current_events[i] = document.createElement("div"))
+ e.className = "current_events"
+ e.style.left = boxes["CurrentEvent" + (i+1)][0] - 10 + "px"
+ e.style.top = boxes["CurrentEvent" + (i+1)][1] - 10 + "px"
+ ui.map_container.appendChild(e)
+ }
+
+ for (i = 0; i < 9; ++i) {
+ e = (ui.medallions[i] = document.createElement("div"))
+ e.className = "pink token medallion medallion_" + i
+ register_action(e, "medallion", i)
+ register_animation(e)
+ }
+
+ for (i = 1; i <= 120; ++i) {
+ e = (ui.cards[i] = document.createElement("div"))
+ e.className = "card card_" + i
+ register_action(e, "card", i)
+ register_animation(e)
+ }
+
+ e = ui.initiative = document.createElement("div")
+ e.className = "red token round initiative"
+ register_animation(e)
}
+
function place_cards(e, cards) {
- e.replaceChildren();
- for (let c of cards) {
- ui.cards[c].classList.remove('selected');
- e.appendChild(ui.cards[c]);
- if (view.selected_cards.includes(c)) {
- ui.cards[c].classList.add('selected');
- }
- }
+ if (cards) {
+ for (var c of cards) {
+ e.appendChild(ui.cards[c])
+ ui.cards[c].classList.toggle("selected", view.selected_cards.includes(c))
+ }
+ }
+}
+
+const faction_class = {
+ "Anarchist": "anarchist",
+ "Communist": "communist",
+ "Moderate": "moderate",
+ "a": "anarchist",
+ "c": "communist",
+ "m": "moderate",
+}
+
+function update_front(str_container, con_container, front) {
+ var i, n, e, cn
+
+ str_container.replaceChildren()
+ if (front.value < 0) {
+ n = -front.value
+ cn = "brown token front_minus"
+ } else {
+ n = front.value
+ cn = "pink token front_plus"
+ }
+ for (i = 0; i < n; ++i) {
+ e = document.createElement("div")
+ e.className = cn
+ str_container.appendChild(e)
+ }
+
+ con_container.replaceChildren()
+ for (i of ["a", "c", "m"]) {
+ if (front.contributions.includes(i)) {
+ e = document.createElement("div")
+ e.className = "red token player " + faction_class[i]
+ con_container.appendChild(e)
+ }
+ }
+}
+
+function update_medallions(container, list) {
+ for (var i of list)
+ container.appendChild(ui.medallions[i])
+}
+
+function update_hero_points(container, n) {
+ for (var i = 0; i < n; ++i) {
+ var e = document.createElement("div")
+ e.className = "token pink hero_point"
+ container.appendChild(e)
+ }
+}
+
+function update_stat_line(x) {
+ var i, line
+ line = ""
+ for (i = 0; i < view.hero_points[x]; ++i)
+ line += "\u272b"
+ if (view.initiative === x)
+ line += " \u2bc5"
+ return line
}
-function on_update() {
- console.log('on_update', view);
- on_init();
- for (let key of Object.keys(view.hero_points)) {
- ui.roles[key].hero_points.replaceChildren(`Hero Points: ${view.hero_points[key]}`);
- }
- ui.bag_of_glory.replaceChildren(`Bag of Glory: ${view.bag_of_glory_count}`);
- ui.current_events.replaceChildren();
- for (let i = 0; i < view.current_events.length; i++) {
- const cardId = view.current_events[i];
- ui.current_events.appendChild(ui.cards[cardId]);
- ui.cards[cardId].classList.add('event');
- ui.cards[cardId].style.left = LAYOUT_CURRENT_EVENTS[i][0] + 'px';
- ui.cards[cardId].style.top = LAYOUT_CURRENT_EVENTS[i][1] + 'px';
- }
- ui.markers.replaceChildren();
- for (let bm of view.triggered_track_effects) {
- const s = bm % 11;
- const t = Math.floor(bm / 11);
- ui.markers.appendChild(ui.blank_markers[bm]);
- ui.blank_markers[bm].style.left = LAYOUT_TRACKS[t][s][0] + 'px';
- ui.blank_markers[bm].style.top = LAYOUT_TRACKS[t][s][1] + 'px';
- }
- for (let bonus_id of Object.keys(view.bonuses)) {
- ui.bonuses[bonus_id].setAttribute('data-bonus-on', view.bonuses[bonus_id] + 0);
- }
- place_cards(ui.hand, view.hand);
- ui.player_area_tabs.hand_tab.replaceChildren(`Hand (${view.hand.length})`);
- place_cards(ui.player_area_cards.deck, view.deck);
- ui.player_area_tabs.deck_tab.replaceChildren(`Deck (${view.deck.length})`);
- place_cards(ui.player_area_cards.discard, view.discard);
- ui.player_area_tabs.discard_tab.replaceChildren(`Discard (${view.discard.length})`);
- place_cards(ui.player_area_cards.trash, view.trash);
- ui.player_area_tabs.trash_tab.replaceChildren(`Trash (${view.trash.length})`);
- place_cards(ui.selectable_cards, view.selectable_cards);
- for (let faction_id of FACTIONS) {
- place_cards(ui.tableaus[faction_id], view.tableaus[faction_id]);
- }
- for (let i = 0; i < view.tracks.length; ++i) {
- ui.standees[i].style.left = LAYOUT_TRACKS[i][view.tracks[i]][0] + 'px';
- ui.standees[i].style.top = LAYOUT_TRACKS[i][view.tracks[i]][1] + 'px';
- }
- for (let front_id of Object.keys(view.fronts)) {
- const front_data = view.fronts[front_id];
- ui.fronts[front_id].value.replaceChildren(front_data.status !== null ? front_data.status : front_data.value);
- ui.fronts[front_id].contributions.replaceChildren();
- for (let faction_id of front_data.contributions) {
- ui.fronts[front_id].contributions.appendChild(ui.tokens_on_front[front_id][faction_id]);
- }
- }
- ui.medallions_container.replaceChildren();
- for (let i = 0; i < view.medallions.pool.length; ++i) {
- if (view.medallions.pool[i] !== null) {
- const id = view.medallions.pool[i];
- ui.medallions[id].style.left = LAYOUT_medallionS[i][0] + 'px';
- ui.medallions[id].style.top = LAYOUT_medallionS[i][1] + 'px';
- ui.medallions_container.appendChild(ui.medallions[id]);
- }
- }
- for (let f of FACTIONS) {
- ui.roles[f].medallions.replaceChildren();
- for (let m of view.medallions[f]) {
- ui.roles[f].medallions.appendChild(ui.medallions[m]);
- }
- }
- ui.roles[view.initiative].medallions.appendChild(ui.initiative_token);
- ui.initiative_token.setAttribute('data-year', view.year);
- if (view.played_card === null) {
- ui.turn_info.style.display = 'none';
- }
- else {
- ui.turn_info.style.display = '';
- ui.turn_info_card.setAttribute('data-card-id', view.played_card + '');
- }
- Object.values(ui.glory).forEach((elt) => elt.removeAttribute('data-faction-id'));
- for (let g = 0; g < view.glory.length; ++g) {
- ui.glory[g].setAttribute('data-faction-id', view.glory[g]);
- }
- for (let e of action_register)
- e.classList.toggle('action', is_action(e.my_action, e.my_id));
- ui.year.replaceChildren(`Year ${view.year}`);
- action_button('add_to_front', '+1 to a Front');
- action_button('d_liberty', 'Decrease Liberty');
- action_button('soviet_support', 'Soviet Support');
- action_button('collectivization', 'Collectivization');
- action_button('d_collectivization', 'Decrease Collectivization');
- action_button('d_foreign_aid', 'Decrease Foreign Aid');
- action_button('d_government', 'Decrease Government');
- action_button('d_soviet_support', 'Decrease Soviet Support');
- action_button('foreign_aid', 'Foreign Aid');
- action_button('government', 'Government');
- action_button('liberty', 'Liberty');
- action_button('government_to_center', 'Government towards center');
- action_button('teamwork_on', 'Teamwork Bonus On');
- action_button('Anarchist', 'Anarchist');
- action_button('Communist', 'Communist');
- action_button('Moderate', 'Moderate');
- action_button('gain_hp', 'Gain Hero Points');
- action_button('lose_hp', 'Lose Hero Points');
- action_button('draw_card', 'Draw a card');
- action_button('draw_cards', 'Draw cards');
- action_button('play_to_tableau', 'Play card to Tableau');
- action_button('play_for_event', 'Play card for Event');
- action_button('use_ap', 'Use Action Points');
- action_button('use_morale_bonus', 'Use Morale Bonus');
- action_button('move_track', 'Move a Track');
- action_button('turn_on_bonus', 'Turn on a Bonus');
- action_button('add_glory', 'Add to Bag of Glory');
- action_button('draw_glory', 'Draw from Bag of Glory');
- action_button('up', 'Up');
- action_button('down', 'Down');
- action_button('next', 'Next');
- action_button('remove_blank_marker', 'Remove Blank marker');
- action_button('confirm', 'Confirm');
- action_button('yes', 'Yes');
- action_button('no', 'No');
- action_button('skip', 'Skip');
- action_button('spend_hp', 'Spend Hero Points');
- action_button('use_momentum', 'Play second card (Momentum)');
- action_button('done', 'Done');
- action_button('end_turn', 'End turn');
- action_button('undo', 'Undo');
+
+function on_update() { // eslint-disable-line no-unused-vars
+ var i, x, e
+
+ on_init()
+
+ animation_register.forEach(remember_position)
+
+ ui.player_areas.replaceChildren()
+ ui.roles_list.replaceChildren()
+ for (i = 0; i < 3; ++i) {
+ ui.player_areas.appendChild(ui.panels[view.player_order[i]])
+ ui.roles_list.appendChild(ui.roles[view.player_order[i]])
+ }
+
+ ui.header.classList.toggle("fascist", !!view.fascist)
+
+ ui.initiative.classList.toggle("ccw", (view.year & 1) === 0)
+ roles.Anarchist.stat.textContent = update_stat_line("a")
+ roles.Communist.stat.textContent = update_stat_line("c")
+ roles.Moderate.stat.textContent = update_stat_line("m")
+
+ // TODO: played card (side panel?)
+
+ // TODO: player draw/discard/trash
+ // TODO: bag of glory
+
+ ui.fascist_deck.replaceChildren()
+ if (view.year === 1)
+ place_cards(ui.fascist_deck, [ 120, 119, 118 ])
+ else if (view.year === 2)
+ place_cards(ui.fascist_deck, [ 120, 119 ])
+ else
+ place_cards(ui.fascist_deck, [ 120])
+
+ ui.fascist_cards.replaceChildren()
+ place_cards(ui.fascist_cards, view.fascist_cards)
+
+ ui.trash.replaceChildren()
+ place_cards(ui.trash, view.trash)
+
+ ui.hand_panel.classList = "panel " + faction_class[player]
+ ui.hand.replaceChildren()
+ place_cards(ui.hand, view.hand)
+
+ ui.tableaus.a.replaceChildren()
+ ui.tableaus.c.replaceChildren()
+ ui.tableaus.m.replaceChildren()
+ ui.tableaus.a.appendChild(ui.cards[117])
+ ui.tableaus.c.appendChild(ui.cards[116])
+ ui.tableaus.m.appendChild(ui.cards[115])
+ place_cards(ui.tableaus.a, view.tableaus.a)
+ place_cards(ui.tableaus.c, view.tableaus.c)
+ place_cards(ui.tableaus.m, view.tableaus.m)
+
+ for (i = 0; i <= 1; ++i) {
+ ui.bonuses[i].classList.toggle("red", !!view.bonuses[i])
+ ui.bonuses[i].classList.toggle("gray", !view.bonuses[i])
+ ui.bonuses[i].classList.toggle("off", !view.bonuses[i])
+ }
+
+ for (i = 0; i < 4; ++i) {
+ ui.current_events[i].replaceChildren()
+ if (i < view.current_events.length)
+ ui.current_events[i].appendChild(ui.cards[view.current_events[i]])
+ }
+
+ for (i = 0; i < 5; ++i)
+ ui.standees[i].style.left = ui.tracks_x[i][view.tracks[i]]
+
+ for (i = 0; i < 55; ++i) {
+ if (ui.blank_markers[i])
+ ui.blank_markers[i].classList.toggle("hide",
+ !view.triggered_track_effects.includes(i))
+ }
+
+ for (i = 0; i < 5; ++i) {
+ ui.medallion_container[i].replaceChildren()
+ if (i < view.medallions.pool.length) {
+ x = view.medallions.pool[i]
+ if (x !== null)
+ ui.medallion_container[i].appendChild(ui.medallions[x])
+ }
+ }
+
+ ui.tokens.a.replaceChildren()
+ ui.tokens.c.replaceChildren()
+ ui.tokens.m.replaceChildren()
+ ui.tokens[view.initiative].appendChild(ui.initiative)
+ update_medallions(ui.tokens.a, view.medallions.a)
+ update_medallions(ui.tokens.c, view.medallions.c)
+ update_medallions(ui.tokens.m, view.medallions.m)
+ update_hero_points(ui.tokens.a, view.hero_points.a)
+ update_hero_points(ui.tokens.c, view.hero_points.c)
+ update_hero_points(ui.tokens.m, view.hero_points.m)
+
+ update_front(ui.str_fronts[0], ui.con_fronts[0], view.fronts.a)
+ update_front(ui.str_fronts[1], ui.con_fronts[1], view.fronts.m)
+ update_front(ui.str_fronts[2], ui.con_fronts[2], view.fronts.n)
+ update_front(ui.str_fronts[3], ui.con_fronts[3], view.fronts.s)
+
+ for (e of ui.raw_glory_container)
+ e.replaceChildren()
+ for (i = 0; i < view.glory.length; ++i) {
+ x = view.glory[i]
+ e = document.createElement("div")
+ e.className = "red token player " + faction_class[x]
+ ui.glory_container[i].appendChild(e)
+ }
+
+ action_button("add_to_front", "+1 to a Front")
+ action_button("d_liberty", "Decrease Liberty")
+ action_button("soviet_support", "Soviet Support")
+ action_button("collectivization", "Collectivization")
+ action_button("d_collectivization", "Decrease Collectivization")
+ action_button("d_foreign_aid", "Decrease Foreign Aid")
+ action_button("d_government", "Decrease Government")
+ action_button("d_soviet_support", "Decrease Soviet Support")
+ action_button("foreign_aid", "Foreign Aid")
+ action_button("government", "Government")
+ action_button("liberty", "Liberty")
+ action_button("government_to_center", "Government towards center")
+ action_button("teamwork_on", "Teamwork Bonus On")
+ action_button("Anarchist", "Anarchist")
+ action_button("Communist", "Communist")
+ action_button("Moderate", "Moderate")
+ action_button("gain_hp", "Gain Hero Points")
+ action_button("lose_hp", "Lose Hero Points")
+ action_button("draw_card", "Draw a card")
+ action_button("draw_cards", "Draw cards")
+ action_button("play_to_tableau", "Play card to Tableau")
+ action_button("play_for_event", "Play card for Event")
+ action_button("use_ap", "Use Action Points")
+ action_button("use_morale_bonus", "Use Morale Bonus")
+ action_button("move_track", "Move a Track")
+ action_button("turn_on_bonus", "Turn on a Bonus")
+ action_button("add_glory", "Add to Bag of Glory")
+ action_button("draw_glory", "Draw from Bag of Glory")
+ action_button("up", "Up")
+ action_button("down", "Down")
+ action_button("next", "Next")
+ action_button("remove_blank_marker", "Remove Blank marker")
+ action_button("confirm", "Confirm")
+ action_button("yes", "Yes")
+ action_button("no", "No")
+ action_button("skip", "Skip")
+ action_button("spend_hp", "Spend Hero Points")
+ action_button("use_momentum", "Play second card (Momentum)")
+ action_button("done", "Done")
+ action_button("end_turn", "End turn")
+ action_button("undo", "Undo")
+
+ for (e of action_register)
+ e.classList.toggle("action", is_action(e.my_action, e.my_id))
+
+ animation_register.forEach(animate_position)
}
-const IMG_FTA = '<span class="faction_token" data-faction-id="a"></span>';
-const IMG_FTC = '<span class="faction_token" data-faction-id="c"></span>';
-const IMG_FTM = '<span class="faction_token" data-faction-id="m"></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';
- }
- if (text.match(/^\.h1/)) {
- text = text.substring(4);
- p.className = 'h1';
- }
- else if (text.match(/^\.h2\.Moderate/)) {
- text = text.substring(13);
- p.className = 'h2 moderate';
- }
- else if (text.match(/^\.h2\.Anarchist/)) {
- text = text.substring(14);
- p.className = 'h2 anarchist';
- }
- else if (text.match(/^\.h2\.Communist/)) {
- text = text.substring(14);
- p.className = 'h2 communist';
- }
- else if (text.match(/^\.h2\.fascist/)) {
- text = text.substring(11);
- p.className = 'h2 fascist';
- }
- else if (text.match(/^\.h2\.glory/)) {
- text = text.substring(9);
- p.className = 'h2 glory';
- }
- else if (text.match(/^\.h2/)) {
- text = text.substring(4);
- p.className = 'h2';
- }
- else if (text.match(/^\.h3/)) {
- text = text.substring(4);
- p.className = 'h3';
- }
- text = text.replace(/<fta>/g, IMG_FTA);
- text = text.replace(/<ftc>/g, IMG_FTC);
- text = text.replace(/<ftm>/g, IMG_FTM);
- p.innerHTML = text;
- return p;
+
+function on_log(text) { // eslint-disable-line no-unused-vars
+ 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(/-( ?[\d(])/g, "\u2212$1")
+
+ // text = text.replace(/C(\d+)/g, sub_card)
+
+ if (text.startsWith("#")) {
+ p.className = "h " + text[1]
+ text = text.substring(3)
+ }
+
+ p.innerHTML = text
+ return p
}
diff --git a/play.ts b/play.ts
deleted file mode 100644
index 5080b37..0000000
--- a/play.ts
+++ /dev/null
@@ -1,623 +0,0 @@
-'use strict';
-
-import { StaticData, View } from './types';
-
-declare function action_button(action: string, text: string): void;
-// declare function register_action(element: HTMLElement, type: string, s: number): void;
-declare function send_action(action: string, text: string): boolean;
-declare const data: StaticData;
-declare const view: View;
-
-/* global view, player, send_action, action_button */
-
-const BONUSES_COUNT = 2;
-// const PIECE_COUNT = 32;
-const CARD_COUNT = 109;
-const GLORY_COUNT = 9;
-const medallionS_COUNT = 9;
-const STANDEES_COUNT = 5;
-const TRACK_COUNT = 5;
-const TRACK_LENGTH = 11;
-const FACTIONS = ['a', 'c', 'm'];
-// const ROLES = ['Anarchist', 'Communist', 'Moderate'];
-
-const ui = {
- bag_of_glory: document.getElementById('bag_of_glory'),
- map: document.getElementById('map'),
- medallions_container: document.getElementById('medallions'),
- markers: document.getElementById('markers'),
- fronts: {
- a: {
- front: document.getElementById('aragon_front'),
- value: document.querySelector('#aragon_front .value'),
- contributions: document.querySelector('#aragon_front .contributions'),
- },
- m: {
- front: document.getElementById('madrid_front'),
- value: document.querySelector('#madrid_front .value'),
- contributions: document.querySelector('#madrid_front .contributions'),
- },
- n: {
- front: document.getElementById('northern_front'),
- value: document.querySelector('#northern_front .value'),
- contributions: document.querySelector('#northern_front .contributions'),
- },
- s: {
- front: document.getElementById('southern_front'),
- value: document.querySelector('#southern_front .value'),
- contributions: document.querySelector('#southern_front .contributions'),
- },
- },
- glory_container: document.getElementById('glory'),
- hand: document.getElementById('hand'),
- player_area: document.getElementById('player_area'),
- current_events: document.getElementById('current_events'),
- roles: {
- a: {
- hero_points: document.querySelector('#role_Anarchist .role_stat'),
- medallions: document.querySelector('#role_Anarchist .role_medallions'),
- },
- c: {
- hero_points: document.querySelector('#role_Communist .role_stat'),
- medallions: document.querySelector('#role_Communist .role_medallions'),
- },
- m: {
- hero_points: document.querySelector('#role_Moderate .role_stat'),
- medallions: document.querySelector('#role_Moderate .role_medallions'),
- },
- pool: {
- hero_points: document.getElementById('pool_hero_points'),
- },
- Anarchist: document.getElementById('role_Anarchist'),
- Communist: document.getElementById('role_Communist'),
- Moderate: document.getElementById('role_Moderate'),
- container: document.getElementById('roles'),
- },
- player_area_tabs: {
- hand_tab: document.getElementById('hand_tab'),
- deck_tab: document.getElementById('deck_tab'),
- discard_tab: document.getElementById('discard_tab'),
- trash_tab: document.getElementById('trash_tab'),
- },
- player_area_cards: {
- hand: document.getElementById('hand'),
- deck: document.getElementById('deck'),
- discard: document.getElementById('discard'),
- trash: document.getElementById('trash'),
- },
- player_areas: {
- container: document.getElementById('player_areas'),
- Anarchist: document.getElementById('player_area_Anarchist'),
- Communist: document.getElementById('player_area_Communist'),
- Moderate: document.getElementById('player_area_Moderate'),
- },
- selectable_cards: document.getElementById('selectable_cards'),
- tableaus: {
- a: document.getElementById('tableau_a'),
- c: document.getElementById('tableau_c'),
- m: document.getElementById('tableau_m'),
- },
- tracks: document.getElementById('tracks'),
- hero_points: document.querySelector('#role_Anarchist .role_stat'),
- turn_info: document.getElementById('turn_info'),
- turn_info_card: document.getElementById('turn_info_card'),
- year: document.getElementById('year'),
- blank_markers: [],
- bonuses: [],
- // fronts: {},
- tokens_on_front: {},
- // front_values: {},
- glory: [],
- initiative_token: undefined,
- medallions: [],
- spaces: [],
- standees: [],
- pieces: [],
- cards: [],
-};
-
-let action_register = [];
-
-const LAYOUT_BONUSES = [
- [435, 481],
- [493, 481],
-];
-
-const LAYOUT_CURRENT_EVENTS = [
- [172, 648],
- [309, 648],
- [445, 648],
- [584, 648],
-];
-
-const LAYOUT_GLORY = [
- [801, 647],
- [860, 647],
- [897, 647],
- [848, 718],
- [775, 771],
- [812, 771],
- [849, 771],
- [885, 771],
- [922, 771],
-];
-
-const LAYOUT_medallionS = [
- [364, 556],
- [415, 556],
- [466, 556],
- [517, 556],
- [568, 556],
-];
-
-const LAYOUT_TRACKS = [
- [
- [581, 46],
- [618, 46],
- [655, 46],
- [691, 46],
- [728, 46],
- [765, 46],
- [801, 46],
- [838, 46],
- [874, 46],
- [911, 46],
- [948, 46],
- ],
- [
- [581, 156],
- [618, 156],
- [655, 156],
- [691, 156],
- [728, 156],
- [765, 156],
- [801, 156],
- [838, 156],
- [874, 156],
- [911, 156],
- [948, 156],
- ],
- [
- [581, 267],
- [618, 267],
- [655, 267],
- [691, 267],
- [728, 267],
- [765, 267],
- [801, 267],
- [838, 267],
- [874, 267],
- [911, 267],
- [948, 267],
- ],
- [
- [581, 378],
- [618, 378],
- [655, 378],
- [691, 378],
- [728, 378],
- [765, 378],
- [801, 378],
- [838, 378],
- [874, 378],
- [911, 378],
- [948, 378],
- ],
- [
- [581, 489],
- [618, 489],
- [655, 489],
- [691, 489],
- [728, 489],
- [765, 489],
- [801, 489],
- [838, 489],
- [874, 489],
- [911, 489],
- [948, 489],
- ],
-];
-
-// @ts-ignore
-// (function build_map() {
-// data.fronts.forEach((front) => {
-// const { id, top, left } = front;
-// const element = (ui.fronts[front.id] = document.createElement('div'));
-// element.classList.add('front');
-// element.style.left = `${left}px`;
-// element.style.top = `${top}px`;
-// element.setAttribute('data-front-id', `${id}`);
-// ui.map.appendChild(element);
-// register_action(element, 'front', id);
-
-// // Contribution tokens
-
-// // Value of front
-// const front_value_element = (ui.front_values[front.id] =
-// document.createElement('span'));
-// front_value_element.classList.add('value');
-// element.appendChild(front_value_element);
-// });
-// })();
-
-function register_action(
- e: HTMLElement & { my_action?: string; my_id?: string | number },
- action: string,
- id: string | number
-) {
- e.my_action = action;
- e.my_id = id;
- e.onmousedown = on_click_action;
- action_register.push(e);
-}
-
-function on_click_action(evt) {
- if (evt.button === 0)
- if (send_action(evt.target.my_action, evt.target.my_id))
- evt.stopPropagation();
-}
-
-function on_click_tab(evt) {
- evt.stopPropagation();
- const { id } = evt.target as HTMLElement;
-
- Object.entries(ui.player_area_tabs).forEach(([tab, elt]) => {
- const cards_area = ui.player_area_cards[tab.split('_')[0]];
- if (tab === id) {
- elt.setAttribute('data-active', 'active');
- cards_area.setAttribute('data-active', 'active');
- } else {
- elt.setAttribute('data-active', 'inactive');
- cards_area.setAttribute('data-active', 'inactive');
- }
- });
-}
-
-function is_action(action, arg) {
- if (arg === undefined) return !!(view.actions && view.actions[action] === 1);
- return !!(
- view.actions &&
- view.actions[action] &&
- view.actions[action].includes(arg)
- );
-}
-
-let on_init_once = false;
-
-function on_init() {
- if (on_init_once) return;
- on_init_once = true;
-
- Object.values(ui.player_area_tabs).forEach((element) => {
- element.addEventListener('click', on_click_tab);
- });
-
- // Reorder tableaus and roles based on player order
- for (const player of view.player_order) {
- ui.player_areas.container.insertAdjacentElement(
- 'beforeend',
- ui.player_areas[player]
- );
- ui.roles.container.insertAdjacentElement('beforeend', ui.roles[player]);
- }
- ui.roles.container.insertAdjacentElement('beforeend', ui.turn_info);
-
- if (view.current === 'Observer') {
- ui.player_area.style.display = 'none';
- } else {
- document
- .getElementById('player_area_header')
- .setAttribute('data-faction-id', view.current_player_faction);
- }
-
- // Create blank_markers
- for (let t = 0; t < TRACK_COUNT; ++t) {
- for (let s = 0; s < TRACK_LENGTH; ++s) {
- const bm = t * 11 + s;
- let e = (ui.blank_markers[bm] = document.createElement('div'));
- e.className = 'blank_marker';
- register_action(e, 'blank_marker', bm);
- }
- }
-
- // create bonus markers
- for (let b = 0; b < BONUSES_COUNT; ++b) {
- let e = (ui.bonuses[b] = document.createElement('div'));
- e.className = 'bonus';
- e.setAttribute('data-bonus-id', '' + b);
- e.style.left = LAYOUT_BONUSES[b][0] + 'px';
- e.style.top = LAYOUT_BONUSES[b][1] + 'px';
- LAYOUT_BONUSES;
- register_action(e, 'bonus', b);
- ui.map.appendChild(ui.bonuses[b]);
- }
-
- // Create glory
- for (let g = 0; g < GLORY_COUNT; ++g) {
- let e = (ui.glory[g] = document.createElement('div'));
- e.className = 'faction_token';
- ui.glory_container.appendChild(ui.glory[g]);
- e.style.left = LAYOUT_GLORY[g][0] + 'px';
- e.style.top = LAYOUT_GLORY[g][1] + 'px';
- }
-
- // create track medallions
- for (let m = 0; m < medallionS_COUNT; ++m) {
- let e = (ui.medallions[m] = document.createElement('div'));
- e.className = 'medallion';
- e.setAttribute('data-medallion-id', '' + m);
- register_action(e, 'medallion', m);
- }
-
- // create track standees
- for (let s = 0; s < STANDEES_COUNT; ++s) {
- let e = (ui.standees[s] = document.createElement('div'));
- e.className = 'standee';
- e.setAttribute('data-standee-id', '' + s);
- register_action(e, 'standee', s);
- ui.tracks.appendChild(ui.standees[s]);
- }
-
- // create card elements
- for (let c = 1; c < CARD_COUNT; ++c) {
- let e = (ui.cards[c] = document.createElement('div'));
- e.className = 'card';
- e.setAttribute('data-card-id', '' + data.cards[c].id);
- register_action(e, 'card', c);
- }
-
- // create initiative marker
- let e = (ui.initiative_token = document.createElement('div'));
- e.className = 'initiative_token';
-
- // create tokens to add to fronts
- data.fronts.forEach((front) => {
- ui.tokens_on_front[front.id] = {};
- FACTIONS.forEach((faction_id) => {
- let e = (ui.tokens_on_front[front.id][faction_id] =
- document.createElement('div'));
- e.className = 'faction_token';
- e.setAttribute('data-faction-id', faction_id);
- });
- });
-
- Object.keys(ui.fronts).forEach((front_id) => {
- register_action(ui.fronts[front_id].front, 'front', front_id);
- });
-}
-
-function place_cards(e: HTMLElement, cards: number[]) {
- e.replaceChildren();
- for (let c of cards) {
- ui.cards[c].classList.remove('selected');
- e.appendChild(ui.cards[c]);
- if (view.selected_cards.includes(c)) {
- ui.cards[c].classList.add('selected');
- }
- }
-}
-
-// @ts-ignore
-function on_update() {
- console.log('on_update', view);
- on_init();
-
- for (let key of Object.keys(view.hero_points)) {
- ui.roles[key].hero_points.replaceChildren(
- `Hero Points: ${view.hero_points[key]}`
- );
- }
- ui.bag_of_glory.replaceChildren(`Bag of Glory: ${view.bag_of_glory_count}`);
-
- // for (let s = 0; s < SPACE_COUNT; ++s) ui.spaces[s].replaceChildren();
-
- // for (let p = 0; p < PIECE_COUNT; ++p) {
- // let s = view.location[p];
- // ui.spaces[s].appendChild(ui.pieces[p]);
- // }
- ui.current_events.replaceChildren();
- for (let i = 0; i < view.current_events.length; i++) {
- const cardId = view.current_events[i];
- ui.current_events.appendChild(ui.cards[cardId]);
- ui.cards[cardId].classList.add('event');
- ui.cards[cardId].style.left = LAYOUT_CURRENT_EVENTS[i][0] + 'px';
- ui.cards[cardId].style.top = LAYOUT_CURRENT_EVENTS[i][1] + 'px';
- }
-
- ui.markers.replaceChildren();
- for (let bm of view.triggered_track_effects) {
- const s = bm % 11;
- const t = Math.floor(bm / 11);
- ui.markers.appendChild(ui.blank_markers[bm]);
- ui.blank_markers[bm].style.left = LAYOUT_TRACKS[t][s][0] + 'px';
- ui.blank_markers[bm].style.top = LAYOUT_TRACKS[t][s][1] + 'px';
- }
-
- for (let bonus_id of Object.keys(view.bonuses)) {
- ui.bonuses[bonus_id].setAttribute(
- 'data-bonus-on',
- view.bonuses[bonus_id] + 0
- );
- }
-
- place_cards(ui.hand, view.hand);
- ui.player_area_tabs.hand_tab.replaceChildren(`Hand (${view.hand.length})`);
- place_cards(ui.player_area_cards.deck, view.deck);
- ui.player_area_tabs.deck_tab.replaceChildren(`Deck (${view.deck.length})`);
- place_cards(ui.player_area_cards.discard, view.discard);
- ui.player_area_tabs.discard_tab.replaceChildren(`Discard (${view.discard.length})`);
- place_cards(ui.player_area_cards.trash, view.trash);
- ui.player_area_tabs.trash_tab.replaceChildren(`Trash (${view.trash.length})`);
-
- place_cards(ui.selectable_cards, view.selectable_cards);
-
- for (let faction_id of FACTIONS) {
- place_cards(ui.tableaus[faction_id], view.tableaus[faction_id]);
- }
-
- for (let i = 0; i < view.tracks.length; ++i) {
- // ui.tracks.appendChild(ui.standees[i]);
- ui.standees[i].style.left = LAYOUT_TRACKS[i][view.tracks[i]][0] + 'px';
- ui.standees[i].style.top = LAYOUT_TRACKS[i][view.tracks[i]][1] + 'px';
- }
-
- for (let front_id of Object.keys(view.fronts)) {
- const front_data = view.fronts[front_id];
- // ui.fronts[front_id].value.replaceChildren(front_data.value);
- ui.fronts[front_id].value.replaceChildren(
- front_data.status !== null ? front_data.status : front_data.value
- );
- ui.fronts[front_id].contributions.replaceChildren();
- for (let faction_id of front_data.contributions) {
- ui.fronts[front_id].contributions.appendChild(
- ui.tokens_on_front[front_id][faction_id]
- );
- }
- }
-
- ui.medallions_container.replaceChildren();
- for (let i = 0; i < view.medallions.pool.length; ++i) {
- if (view.medallions.pool[i] !== null) {
- const id = view.medallions.pool[i];
- ui.medallions[id].style.left = LAYOUT_medallionS[i][0] + 'px';
- ui.medallions[id].style.top = LAYOUT_medallionS[i][1] + 'px';
- ui.medallions_container.appendChild(ui.medallions[id]);
- }
- }
- for (let f of FACTIONS) {
- ui.roles[f].medallions.replaceChildren();
- for (let m of view.medallions[f]) {
- ui.roles[f].medallions.appendChild(ui.medallions[m]);
- }
- }
- ui.roles[view.initiative].medallions.appendChild(ui.initiative_token);
- ui.initiative_token.setAttribute('data-year', view.year);
-
- if (view.played_card === null) {
- ui.turn_info.style.display = 'none';
- } else {
- ui.turn_info.style.display = '';
- ui.turn_info_card.setAttribute('data-card-id', view.played_card + '');
- }
-
- Object.values(ui.glory).forEach((elt: HTMLElement) => elt.removeAttribute('data-faction-id'));
- for (let g = 0; g < view.glory.length; ++g) {
- ui.glory[g].setAttribute('data-faction-id', view.glory[g]);
- }
-
- for (let e of action_register)
- e.classList.toggle('action', is_action(e.my_action, e.my_id));
-
- ui.year.replaceChildren(`Year ${view.year}`);
-
- action_button('add_to_front', '+1 to a Front');
- action_button('d_liberty', 'Decrease Liberty');
- action_button('soviet_support', 'Soviet Support');
- action_button('collectivization', 'Collectivization');
-
- action_button('d_collectivization', 'Decrease Collectivization');
- action_button('d_foreign_aid', 'Decrease Foreign Aid');
- action_button('d_government', 'Decrease Government');
-
- action_button('d_soviet_support', 'Decrease Soviet Support');
-
- action_button('foreign_aid', 'Foreign Aid');
- action_button('government', 'Government');
- action_button('liberty', 'Liberty');
- action_button('government_to_center', 'Government towards center');
-
- action_button('teamwork_on', 'Teamwork Bonus On');
- action_button('Anarchist', 'Anarchist');
- action_button('Communist', 'Communist');
- action_button('Moderate', 'Moderate');
- action_button('gain_hp', 'Gain Hero Points');
- action_button('lose_hp', 'Lose Hero Points');
- action_button('draw_card', 'Draw a card');
- action_button('draw_cards', 'Draw cards');
- // action_button('draw_card', 'Draw card');
- action_button('play_to_tableau', 'Play card to Tableau');
- action_button('play_for_event', 'Play card for Event');
- action_button('use_ap', 'Use Action Points');
- action_button('use_morale_bonus', 'Use Morale Bonus');
-
- action_button('move_track', 'Move a Track');
- action_button('turn_on_bonus', 'Turn on a Bonus');
-
- action_button('add_glory', 'Add to Bag of Glory');
- action_button('draw_glory', 'Draw from Bag of Glory');
- action_button('up', 'Up');
- action_button('down', 'Down');
- action_button('next', 'Next');
- action_button('remove_blank_marker', 'Remove Blank marker');
- action_button('confirm', 'Confirm');
- action_button('yes', 'Yes');
- action_button('no', 'No');
- action_button('skip', 'Skip');
- action_button('spend_hp', 'Spend Hero Points');
- action_button('use_momentum', 'Play second card (Momentum)');
- action_button('done', 'Done');
- action_button('end_turn', 'End turn');
- action_button('undo', 'Undo');
-}
-
-const IMG_FTA = '<span class="faction_token" data-faction-id="a"></span>';
-const IMG_FTC = '<span class="faction_token" data-faction-id="c"></span>';
-const IMG_FTM = '<span class="faction_token" data-faction-id="m"></span>';
-
-// @ts-ignore
-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_name)
- // text = text.replace(/S(\d+)/g, sub_space_name)
- // text = text.replace(/U(\d+)/g, sub_unit_name)
-
- // TODO dice icons
- // text = text.replace(/\bD\d\b/g, sub_icon)
-
- if (text.match(/^\.h1/)) {
- text = text.substring(4);
- p.className = 'h1';
- } else if (text.match(/^\.h2\.Moderate/)) {
- text = text.substring(13);
- p.className = 'h2 moderate';
- } else if (text.match(/^\.h2\.Anarchist/)) {
- text = text.substring(14);
- p.className = 'h2 anarchist';
- } else if (text.match(/^\.h2\.Communist/)) {
- text = text.substring(14);
- p.className = 'h2 communist';
- } else if (text.match(/^\.h2\.fascist/)) {
- text = text.substring(11);
- p.className = 'h2 fascist';
- } else if (text.match(/^\.h2\.glory/)) {
- text = text.substring(9);
- p.className = 'h2 glory';
- } else if (text.match(/^\.h2/)) {
- text = text.substring(4);
- p.className = 'h2';
- } else if (text.match(/^\.h3/)) {
- text = text.substring(4);
- p.className = 'h3';
- }
-
- text = text.replace(/<fta>/g, IMG_FTA)
- text = text.replace(/<ftc>/g, IMG_FTC)
- text = text.replace(/<ftm>/g, IMG_FTM)
-
- p.innerHTML = text;
- return p;
-}
diff --git a/rules.js b/rules.js
index 46a9176..25d80b0 100644
--- a/rules.js
+++ b/rules.js
@@ -80,7 +80,7 @@ function gen_spend_hero_points() {
}
function action(state, player, action, arg) {
game = state;
- if (action !== 'undo' && game.state !== 'choose_card' && !game.fascist) {
+ if (action !== 'undo' && game.state !== 'choose_card') {
push_undo();
}
let S = states[game.state];
diff --git a/rules.ts b/rules.ts
index cc35bdf..e509a17 100644
--- a/rules.ts
+++ b/rules.ts
@@ -174,7 +174,7 @@ export function action(
) {
game = state;
- if (action !== 'undo' && game.state !== 'choose_card' && !game.fascist) {
+ if (action !== 'undo' && game.state !== 'choose_card') {
push_undo();
}
diff --git a/tools/colors.js b/tools/colors.js
new file mode 100644
index 0000000..16378f7
--- /dev/null
+++ b/tools/colors.js
@@ -0,0 +1,143 @@
+"use strict"
+
+function rgb_from_any(color) {
+ if (typeof color === "string")
+ color = parse_hex(color)
+ switch (color.mode) {
+ case "rgb": return color
+ case "lrgb": return rgb_from_lrgb(color)
+ case "oklab": return rgb_from_oklab(color)
+ }
+}
+
+function lrgb_from_any(color) {
+ switch (color.mode) {
+ case "rgb": return lrgb_from_rgb(color)
+ case "lrgb": return color
+ case "oklab": return lrgb_from_oklab(color)
+ }
+}
+
+function oklab_from_any(color) {
+ switch (color.mode) {
+ case "rgb": return oklab_from_rgb(color)
+ case "lrgb": return oklab_from_lrgb(color)
+ case "oklab": return color
+ }
+}
+
+function format_hex(color) {
+ let { r, g, b } = rgb_from_any(color)
+ let adj = 1
+ r = Math.round(Math.max(0, Math.min(1, r)) * 255)
+ g = Math.round(Math.max(0, Math.min(1, g)) * 255)
+ b = Math.round(Math.max(0, Math.min(1, b)) * 255)
+ let x = (r << 16) | (g << 8) | b
+ return "#" + x.toString(16).padStart(6, "0")
+}
+
+function parse_hex(str) {
+ let x = parseInt(str.substring(1), 16)
+ return {
+ mode: "rgb",
+ r: ((x >> 16) & 255) / 255.0,
+ g: ((x >> 8) & 255) / 255.0,
+ b: ((x) & 255) / 255.0
+ }
+}
+
+function lrgb_from_rgb({ r, g, b }) {
+ function to_linear(c) {
+ let ac = Math.abs(c)
+ if (ac < 0.04045)
+ return c / 12.92
+ return (Math.sign(c) || 1) * Math.pow((ac + 0.055) / 1.055, 2.4)
+ }
+ return {
+ mode: "lrgb",
+ r: to_linear(r),
+ g: to_linear(g),
+ b: to_linear(b)
+ }
+}
+
+function rgb_from_lrgb({ r, g, b }) {
+ function from_linear(c) {
+ let ac = Math.abs(c)
+ if (ac > 0.0031308)
+ return (Math.sign(c) || 1) * (1.055 * Math.pow(ac, 1 / 2.4) - 0.055)
+ return c * 12.92
+ }
+ return {
+ mode: "rgb",
+ r: from_linear(r),
+ g: from_linear(g),
+ b: from_linear(b)
+ }
+}
+
+function oklab_from_lrgb({ r, g, b }) {
+ let L = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b)
+ let M = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b)
+ let S = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b)
+ return {
+ mode: "oklab",
+ l: 0.2104542553 * L + 0.793617785 * M - 0.0040720468 * S,
+ a: 1.9779984951 * L - 2.428592205 * M + 0.4505937099 * S,
+ b: 0.0259040371 * L + 0.7827717662 * M - 0.808675766 * S
+ }
+}
+
+function lrgb_from_oklab({ l, a, b }) {
+ let L = Math.pow(l + 0.3963377774 * a + 0.2158037573 * b, 3)
+ let M = Math.pow(l - 0.1055613458 * a - 0.0638541728 * b, 3)
+ let S = Math.pow(l - 0.0894841775 * a - 1.291485548 * b, 3)
+ return {
+ mode: "lrgb",
+ r: +4.0767416621 * L - 3.3077115913 * M + 0.2309699292 * S,
+ g: -1.2684380046 * L + 2.6097574011 * M - 0.3413193965 * S,
+ b: -0.0041960863 * L - 0.7034186147 * M + 1.707614701 * S
+ }
+}
+
+function oklab_from_rgb(rgb) {
+ return oklab_from_lrgb(lrgb_from_rgb(rgb))
+}
+
+function rgb_from_oklab(oklab) {
+ return rgb_from_lrgb(lrgb_from_oklab(oklab))
+}
+
+function format_hsl(rgb) {
+ let { r, g, b } = rgb_from_any(color)
+ let cmin = Math.min(r, g, b)
+ let cmax = Math.max(r, g, b)
+ let delta = cmax - cmin
+ let h = 0, s = 0, l = 0
+
+ if (delta == 0)
+ h = 0
+ else if (cmax == r)
+ h = ((g - b) / delta) % 6
+ else if (cmax == g)
+ h = (b - r) / delta + 2
+ else
+ h = (r - g) / delta + 4
+
+ h = Math.round(h * 60)
+
+ if (h < 0)
+ h += 360
+
+ l = (cmax + cmin) / 2
+
+ s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1))
+
+ s = Math.round(s * 100)
+ l = Math.round(l * 100)
+
+ return "hsl(" + h + "," + s + "%," + l + "%)"
+}
+
+if (typeof module === "object")
+ module.exports = { format_hex, parse_hex, rgb_from_any, lrgb_from_any, oklab_from_any, format_hsl }
diff --git a/tools/gencolor.js b/tools/gencolor.js
new file mode 100644
index 0000000..ea804e6
--- /dev/null
+++ b/tools/gencolor.js
@@ -0,0 +1,27 @@
+"use strict"
+
+const colors = require("./colors.js")
+
+function lerp(v0, v1, t) {
+ return v0 + t * (v1 - v0)
+}
+
+function lerp_rgb(rgb, x=0, t=0) {
+ let lab = colors.oklab_from_any(colors.parse_hex(rgb))
+ lab.l = lerp(lab.l, x, t)
+ return colors.format_hex(lab)
+}
+
+function make_colors(rgb, sel) {
+ let bg = lerp_rgb(rgb, 1.0, 0.0)
+ let hi = lerp_rgb(rgb, 1.0, 0.4)
+ let sh = lerp_rgb(rgb, 0.2, 0.4)
+ let bd = lerp_rgb(rgb, 0.2, 0.8)
+ console.log(sel + ` { background-color: ${bg}; border-color: ${hi} ${sh} ${sh} ${hi}; box-shadow: 0 0 0 1px ${bd}, 0px 1px 4px #0008; }`)
+}
+
+make_colors("#bbbbbb", ".token.white")
+make_colors("#ef0500", ".token.red")
+make_colors("#da997b", ".token.pink")
+make_colors("#bbb079", ".token.brown")
+make_colors("#a6a6a8", ".token.gray")
diff --git a/tools/gentextures.sh b/tools/gentextures.sh
new file mode 100644
index 0000000..b9eabb9
--- /dev/null
+++ b/tools/gentextures.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+pngtopnm tools/rock_overlay@2x.png | pnmnorm | pgmtoppm "#000000-#111111" | ppmquant 2 | pnmtopng > tools/texture_mask.png
+convert tools/texture_mask.png -alpha copy -fx '#fff' images/texture.png
+zopflipng -m -y images/texture.png images/texture.png
diff --git a/tools/parse-layout.js b/tools/parse-layout.js
new file mode 100644
index 0000000..5dcf4f8
--- /dev/null
+++ b/tools/parse-layout.js
@@ -0,0 +1,167 @@
+const fs = require("fs")
+
+const { round, floor, ceil } = Math
+
+let boxes = {}
+let nodes = []
+let edges = []
+let mode, name, x, y, w, h, cx, cy, rx, ry, x2, y2
+let scale = 1
+
+function flush() {
+ if (mode === 'path') {
+ edges.push({ x1: x, y1: y, x2, y2 })
+ }
+ if (mode === 'rect') {
+ boxes[name] = [ x * scale |0, y * scale |0, w * scale |0, h * scale |0 ]
+ nodes.push({ name, x: x + w/2, y: y + h/2 })
+ }
+ if (mode === 'circle') {
+ x = cx - rx
+ y = cy - ry
+ w = rx * 2
+ h = ry * 2
+ boxes[name] = [ x * scale |0, y * scale |0, w * scale |0, h * scale |0 ]
+ nodes.push({ name, x: cx, y: cy })
+ }
+ x = y = x2 = y2 = w = h = cx = cy = rx = ry = 0
+ name = null
+}
+
+function parse_path_data(path) {
+ let cx = 0
+ let cy = 0
+ let abs = 0
+ for (let i = 0; i < path.length;) {
+ switch (path[i]) {
+ case 'M':
+ x = cx = Number(path[i+1])
+ y = cy = Number(path[i+2])
+ i += 3
+ abs = true
+ break
+ case 'm':
+ x = cx = cx + Number(path[i+1])
+ y = cy = cy + Number(path[i+2])
+ i += 3
+ abs = false
+ break
+ case 'C':
+ x2 = cx = Number(path[i+5])
+ y2 = cy = Number(path[i+6])
+ i += 7
+ abs = true
+ break
+ case 'L':
+ i += 1
+ abs = true
+ break
+ case 'H':
+ x2 = cx = Number(path[i+1])
+ i += 2
+ abs = true
+ break
+ case 'V':
+ y2 = cy = Number(path[i+1])
+ i += 2
+ abs = true
+ break
+ case 'c':
+ x2 = cx = cx + Number(path[i+5])
+ y2 = cy = cy + Number(path[i+6])
+ i += 7
+ break
+ case 'l':
+ i += 1
+ abs = false
+ break
+ case 'h':
+ x2 = cx = cx + Number(path[i+1])
+ i += 2
+ abs = false
+ break
+ case 'v':
+ y2 = cy = cy + Number(path[i+1])
+ i += 2
+ abs = false
+ break
+ default:
+ if (abs) {
+ x2 = cx = Number(path[i+0])
+ y2 = cy = Number(path[i+1])
+ } else {
+ x2 = cx = cx + Number(path[i+0])
+ y2 = cy = cy + Number(path[i+1])
+ }
+ i += 2
+ break
+ }
+ }
+}
+
+for (let line of fs.readFileSync("tools/layout.svg", "utf-8").split("\n")) {
+ line = line.trim()
+ if (line.startsWith("<rect")) {
+ flush()
+ mode = "rect"
+ x = y = w = h = 0
+ } else if (line.startsWith("<ellipse") || line.startsWith("<circle")) {
+ flush()
+ mode = "circle"
+ cx = cy = rx = ry = 0
+ } else if (line.startsWith("<path")) {
+ flush()
+ mode = "path"
+ } else if (line.startsWith('x="'))
+ x = round(Number(line.split('"')[1]))
+ else if (line.startsWith('y="'))
+ y = round(Number(line.split('"')[1]))
+ else if (line.startsWith('width="'))
+ w = round(Number(line.split('"')[1]))
+ else if (line.startsWith('height="'))
+ h = round(Number(line.split('"')[1]))
+ else if (line.startsWith('cx="'))
+ cx = round(Number(line.split('"')[1]))
+ else if (line.startsWith('cy="'))
+ cy = round(Number(line.split('"')[1]))
+ else if (line.startsWith('r="'))
+ rx = ry = round(Number(line.split('"')[1]))
+ else if (line.startsWith('rx="'))
+ rx = round(Number(line.split('"')[1]))
+ else if (line.startsWith('ry="'))
+ ry = round(Number(line.split('"')[1]))
+ else if (line.startsWith('inkscape:label="'))
+ name = line.split('"')[1]
+ else if (line.startsWith('d="'))
+ parse_path_data(line.split('"')[1].split(/[ ,]/))
+}
+
+flush()
+
+console.log("const boxes = {")
+for (let key in boxes)
+ console.log("\t\"" + key + "\": " + JSON.stringify(boxes[key]) + ",")
+console.log("}")
+
+function find_closest_node(x, y) {
+ let nd = Infinity, nn = null
+
+ for (let n of nodes) {
+ let d = Math.hypot(n.x - x, n.y - y)
+ if (d < nd) {
+ nd = d
+ nn = n.name
+ }
+ }
+
+ if (!nn)
+ console.log("NOT FOUND", x, y)
+
+ return nn
+}
+
+for (let e of edges) {
+ let n1 = find_closest_node(e.x1, e.y1)
+ let n2 = find_closest_node(e.x2, e.y2)
+ console.log(`edge("${n1}", "${n2}", ${e.x1|0}, ${e.y1|0}, ${e.x2|0}, ${e.y2|0})`)
+}
diff --git a/tools/rock_overlay@2x.png b/tools/rock_overlay@2x.png
new file mode 100644
index 0000000..8710ab6
--- /dev/null
+++ b/tools/rock_overlay@2x.png
Binary files differ