summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-10-14 16:54:58 +0200
committerTor Andersson <tor@ccxvii.net>2023-10-15 00:35:59 +0200
commit47a375277e0f5ffcd92ee491371c1fe616707b2f (patch)
treebc1ad89ae2d9c34f7864ba30e6165557fbc3e01a
parentd445a6a168cd3948f682df9771a8b8e24418a10c (diff)
downloadpax-pamir-47a375277e0f5ffcd92ee491371c1fe616707b2f.tar.gz
Add animations to cards and pieces.
Show hand cards tucked under player boards when minimized.
-rw-r--r--about.html8
-rw-r--r--play.css39
-rw-r--r--play.html10
-rw-r--r--play.js163
4 files changed, 199 insertions, 21 deletions
diff --git a/about.html b/about.html
index e2b1b18..a7dbb64 100644
--- a/about.html
+++ b/about.html
@@ -4,13 +4,13 @@ authority has collapsed. Rivals both old and new have emerged from the shadows.
It’s up to the players to see if a fledgling Afghan state might come into
being.
-<br clear=left>
-
<p>
-Designer: Cole Wehrle
+Designer: Cole Wehrle.
<br>
Publisher:
-<a href="https://wehrlegig.com/collections/frontpage/products/pax-pamir-second-edition">Wehrlegig Games llc</a>
+<a href="https://wehrlegig.com/collections/frontpage/products/pax-pamir-second-edition">Wehrlegig Games llc</a>.
+<br>
+Programming: Tor Andersson.
<p>
This game is licensed under Creative Commons <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">BY-NC-SA 4.0</a>.
diff --git a/play.css b/play.css
index 02807f3..1a5ebf1 100644
--- a/play.css
+++ b/play.css
@@ -189,6 +189,7 @@ body.shift #tooltip.focus { display: block; }
background-repeat: no-repeat;
border: 1px solid #4d452b;
box-shadow: 0 2px 3px rgba(0,0,0,0.5);
+ transition: filter 500ms ease;
}
#tooltip.card {
@@ -203,6 +204,7 @@ body.shift #tooltip.focus { display: block; }
.card .spyrow {
position: absolute;
+ pointer-events: none;
display: flex;
flex-wrap: wrap;
width: 168px;
@@ -278,9 +280,9 @@ body.shift #tooltip.focus { display: block; }
/* PIECES */
-#board div {
- transition: top 500ms, left 500ms;
-}
+.card { z-index: 10 }
+.block { z-index: 20 }
+.cylinder { z-index: 20 }
.coin {
position: absolute;
@@ -306,6 +308,7 @@ body.shift #tooltip.focus { display: block; }
background-size: contain;
background-repeat: no-repeat;
filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5));
+ pointer-events: auto;
}
#board .cylinder {
@@ -586,12 +589,29 @@ body.shift #tooltip.focus { display: block; }
padding: 15px;
margin: 15px auto 0 auto;
gap: 15px;
- min-height: 260px;
+ min-height: calc(260px + 30px);
max-width: min(calc(100% - 20px), 1260px);
}
-.hand.hide {
- display: none;
+.hand .card {
+ z-index: auto;
+}
+
+.hand.minimize + .player_court {
+ margin-top: calc(-15px - 195px);
+}
+
+/* hide backs when minimized hands */
+/*
+body.open .hand.minimize .card {
+ pointer-events: none;
+ background-image: url(cards/card_back_116.jpg);
+ filter: none;
+}
+*/
+
+body.open .hand.minimize .card {
+ filter: brightness(60%);
}
.player_area {
@@ -603,6 +623,7 @@ body.shift #tooltip.focus { display: block; }
}
.player_court {
+ position: relative;
display: flex;
padding: 10px 15px;
min-height: 283px;
@@ -613,6 +634,7 @@ body.shift #tooltip.focus { display: block; }
background-repeat: no-repeat;
background-size: 100%;
background-position: center bottom;
+ transition: margin-top 500ms ease;
}
#player_court_0 { background-color: #b7b2b0; background-image: url(backgrounds/mountains_gray.jpg) }
@@ -900,3 +922,8 @@ body.shift #tooltip.focus { display: block; }
border-radius: 13px;
}
}
+
+@media (max-width: 1200px) {
+ .hand.minimize + .player_court { margin-top: -290px; }
+ body.open .hand.minimize + .player_court { margin-top: -290px; }
+}
diff --git a/play.html b/play.html
index 7317806..cc4a6e4 100644
--- a/play.html
+++ b/play.html
@@ -346,7 +346,7 @@
<div id="player_area_list">
<div class="hide player_area" id="player_area_0">
-<div class="hide hand" id="player_hand_0"></div>
+<div class="minimize hand" id="player_hand_0"></div>
<div class="player_court" id="player_court_0">
<div class="player_pool" id="player_pool_0">
<div class="player_dial p0" id="player_dial_0">
@@ -362,7 +362,7 @@
</div>
<div class="hide player_area" id="player_area_1">
-<div class="hide hand" id="player_hand_1"></div>
+<div class="minimize hand" id="player_hand_1"></div>
<div class="player_court" id="player_court_1">
<div class="player_pool" id="player_pool_1">
<div class="player_dial p1" id="player_dial_1">
@@ -378,7 +378,7 @@
</div>
<div class="hide player_area" id="player_area_2">
-<div class="hide hand" id="player_hand_2"></div>
+<div class="minimize hand" id="player_hand_2"></div>
<div class="player_court" id="player_court_2">
<div class="player_pool" id="player_pool_2">
<div class="player_dial p2" id="player_dial_2">
@@ -394,7 +394,7 @@
</div>
<div class="hide player_area" id="player_area_3">
-<div class="hide hand" id="player_hand_3"></div>
+<div class="minimize hand" id="player_hand_3"></div>
<div class="player_court" id="player_court_3">
<div class="player_pool" id="player_pool_3">
<div class="player_dial p3" id="player_dial_3">
@@ -410,7 +410,7 @@
</div>
<div class="hide player_area" id="player_area_4">
-<div class="hide hand" id="player_hand_4"></div>
+<div class="minimize hand" id="player_hand_4"></div>
<div class="player_court" id="player_court_4">
<div class="player_pool" id="player_pool_4">
<div class="player_dial p4" id="player_dial_4">
diff --git a/play.js b/play.js
index 8770966..348290b 100644
--- a/play.js
+++ b/play.js
@@ -1,5 +1,88 @@
"use strict"
+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 = e.parentElement
+ 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 cylinders within same card...
+ 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: 350, easing: "ease" }
+ )
+ }
+
+ if (dx !== 0 || dy !== 0) {
+ let dist = Math.sqrt((dx * dx) + (dy * dy))
+ let time = Math.max(350, Math.min(1000, dist / 2))
+ e.animate(
+ [
+ { transform: `translate(${dx}px, ${dy}px)`, },
+ { transform: "translate(0, 0)", },
+ ],
+ { duration: time, easing: "ease" }
+ )
+ }
+ }
+}
+
+function deep_copy(original) {
+ if (Array.isArray(original)) {
+ let n = original.length
+ let copy = new Array(n)
+ for (let i = 0; i < n; ++i) {
+ let v = original[i]
+ if (typeof v === "object" && v !== null)
+ copy[i] = deep_copy(v)
+ else
+ copy[i] = v
+ }
+ return copy
+ } else {
+ let copy = {}
+ for (let i in original) {
+ let v = original[i]
+ if (typeof v === "object" && v !== null)
+ copy[i] = deep_copy(v)
+ else
+ copy[i] = v
+ }
+ return copy
+ }
+}
+
// CONSTANTS
const player_names = [ "Gray", "Blue", "Tan", "Red", "Black", "None" ]
@@ -269,7 +352,7 @@ function toggle_open_hands() {
open_toggle = !open_toggle
for (let p = 0; p < view.players.length; ++p)
if (p !== player_index[player])
- ui.player[p].hand.classList.toggle("hide", open_toggle)
+ ui.player[p].hand.classList.toggle("minimize", open_toggle)
}
function on_blur() {
@@ -313,7 +396,7 @@ function on_click_cylinder(evt) {
}
function toggle_hand(p) {
- ui.player[p].hand.classList.toggle("hide")
+ ui.player[p].hand.classList.toggle("minimize")
}
// CARD MENU
@@ -494,6 +577,32 @@ function layout_border(r, xc, yc, line) {
// UPDATE UI
let once = true
+let old_view = null
+
+function is_card_visible(game, c) {
+ for (let row = 0; row < 2; ++row)
+ for (let col = 0; col < 6; ++col)
+ if (game.market_cards[row][col] === c)
+ return true
+ for (let p = 0; p < game.players.length; ++p) {
+ let pp = game.players[p]
+ if (pp.court.includes(c))
+ return true
+ if (pp.hand.includes(c))
+ return true
+ for (let evt in pp.events)
+ if (event_cards[evt] === c)
+ return true
+ }
+ return false
+}
+
+function is_card_on_market(game, row, c) {
+ for (let col = 0; col < 6; ++col)
+ if (game.market_cards[row][col] === c)
+ return true
+ return false
+}
function on_update() {
if (once) {
@@ -501,6 +610,36 @@ function on_update() {
once = false
}
+ ui.cards.forEach(remember_position)
+ ui.pieces.forEach(remember_position)
+
+ if (old_view) {
+ let anchor = ui.main.getBoundingClientRect()
+ let anchor_a = ui.market_a.getBoundingClientRect()
+ let anchor_b = ui.market_b.getBoundingClientRect()
+ for (let i = 1; i < cards.length; ++i) {
+ // animate from card deck onto market
+ let anchor_x = Math.min(anchor_a.right, anchor.right - 190)
+ if (!is_card_visible(old_view, i)) {
+ if (is_card_on_market(view, 0, i)) {
+ ui.cards[i].my_visible = 1
+ ui.cards[i].my_x = anchor_x
+ ui.cards[i].my_y = anchor_a.bottom - 260
+ }
+ else if (is_card_on_market(view, 1, i)) {
+ ui.cards[i].my_visible = 1
+ ui.cards[i].my_x = anchor_x
+ ui.cards[i].my_y = anchor_b.bottom - 260
+ }
+ }
+ }
+ } else {
+ for (let e of ui.pieces)
+ e.my_visible = 0
+ }
+
+ old_view = deep_copy(view)
+
function update_event_cards(node, events) {
for (let evt in events)
node.appendChild(ui.cards[event_cards[evt]])
@@ -549,6 +688,8 @@ function on_update() {
action_button("cancel", "Cancel")
action_button("undo", "Undo")
+ ui.body.classList.toggle("open", !!view.open)
+
ui.favored1.className = view.favored
ui.favored2.className = view.favored + " icon"
@@ -572,6 +713,10 @@ function on_update() {
ui.market_coin[row][col].textContent = ""
ui.market_coin[row][col].className = "coin hide"
}
+ if (ce)
+ ce.appendChild(ui.market_coin[row][col])
+ else
+ ui.market_coin[row][col].remove()
}
}
@@ -621,7 +766,7 @@ function on_update() {
while (me.firstChild)
me.removeChild(me.firstChild)
if (p === player_index[player])
- me.classList.remove("hide")
+ me.classList.remove("minimize")
for (let i = 0; i < pp.hand.length; ++i) {
let ce = ui.cards[pp.hand[i]]
if (p !== player_index[player] && !view.open)
@@ -668,7 +813,7 @@ function on_update() {
ui.player[p].score.style.left = (VP_OFFSET[p][0] + VP_TRACK[view.players[p].vp][0]) + "px"
ui.player[p].score.style.top = (VP_OFFSET[p][1] + VP_TRACK[view.players[p].vp][1]) + "px"
- for (let i = 0; i < 10; ++i) {
+ for (let i = 9; i >= 0; --i) {
let x = 36 + p * 10 + i
let s = view.pieces[x]
if (s === 0 || s === Safe_House)
@@ -736,6 +881,9 @@ function on_update() {
e.classList.toggle("action", is_card_action(action, c))
}
}
+
+ ui.pieces.forEach(animate_position)
+ ui.cards.forEach(animate_position)
}
// BUILD UI
@@ -824,8 +972,7 @@ function build_ui() {
ui.player[p].dial.addEventListener("click", () => send_action('player_' + p))
- ui.player[p].hand_size.addEventListener("click",
- () => toggle_hand(p))
+ ui.player[p].hand_size.addEventListener("click", () => toggle_hand(p))
for (let i = 0; i < 10; ++i) {
let x = 36 + p * 10 + i
@@ -850,10 +997,14 @@ function build_ui() {
document.querySelector(`#board .rule.Punjab`),
]
+ ui.body = document.querySelector("body")
+ ui.main = document.querySelector("main")
ui.prompt = document.getElementById("prompt")
ui.deck_info = document.getElementById("deck_info")
ui.board = document.getElementById("board")
ui.market = document.getElementById("market")
+ ui.market_a = document.getElementById("market_a")
+ ui.market_b = document.getElementById("market_b")
ui.status = document.getElementById("status")
ui.tooltip = document.getElementById("tooltip")
ui.favored1 = document.getElementById("favored_suit_marker")