3927 lines
148 KiB
HTML
3927 lines
148 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta
|
||
name="viewport"
|
||
content="width=device-width, initial-scale=1, viewport-fit=cover"
|
||
/>
|
||
<title data-i18n="rockGame.pageTitle">Rock Paper Scissors</title>
|
||
<link rel="stylesheet" href="../../common/theme.css" />
|
||
<link rel="stylesheet" href="../common/game-match.css" />
|
||
<link rel="stylesheet" href="../common/game-loading.css" />
|
||
<style>
|
||
:root {
|
||
color-scheme: dark;
|
||
--dice-topbar-offset: 0px;
|
||
}
|
||
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
html,
|
||
body {
|
||
width: 100%;
|
||
min-width: 100%;
|
||
height: 100%;
|
||
min-height: 100%;
|
||
margin: 0;
|
||
overflow: hidden;
|
||
background: #030623;
|
||
font-family:
|
||
'Source Han Sans SC', 'Helvetica Neue', Arial, sans-serif;
|
||
-webkit-font-smoothing: antialiased;
|
||
text-rendering: geometricprecision;
|
||
}
|
||
|
||
body {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: center;
|
||
}
|
||
|
||
button {
|
||
padding: 0;
|
||
border: 0;
|
||
color: inherit;
|
||
font: inherit;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.rps-frame {
|
||
--game-screen-scale: var(--game-scale);
|
||
}
|
||
|
||
.game-stage {
|
||
width: 100%;
|
||
max-width: 768px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.rps-bg {
|
||
position: absolute;
|
||
top: -145px;
|
||
left: -53px;
|
||
width: 471px;
|
||
height: 836px;
|
||
object-fit: cover;
|
||
pointer-events: none;
|
||
user-select: none;
|
||
}
|
||
|
||
.rps-bg-overlay {
|
||
mix-blend-mode: multiply;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.rps-home-indicator {
|
||
position: absolute;
|
||
left: 120px;
|
||
bottom: 9px;
|
||
z-index: 50;
|
||
width: 135px;
|
||
height: 5px;
|
||
border-radius: 3px;
|
||
background: #fff;
|
||
}
|
||
|
||
.top-bar {
|
||
position: absolute;
|
||
top: 60px;
|
||
left: 16px;
|
||
z-index: 30;
|
||
display: grid;
|
||
grid-template-columns: 46px minmax(0, 1fr) max-content;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
width: 343px;
|
||
height: 36px;
|
||
transform: translateY(var(--dice-topbar-offset));
|
||
}
|
||
|
||
.top-back {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 46px;
|
||
height: 36px;
|
||
flex: 0 0 46px;
|
||
}
|
||
|
||
.top-back .layer-img {
|
||
position: static;
|
||
width: 46px;
|
||
height: 36px;
|
||
object-fit: fill;
|
||
}
|
||
|
||
.top-game-icon {
|
||
position: relative;
|
||
justify-self: center;
|
||
width: 36px;
|
||
height: 36px;
|
||
overflow: hidden;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.top-coin {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
justify-self: end;
|
||
width: max-content;
|
||
min-width: 102px;
|
||
max-width: 176px;
|
||
height: 36px;
|
||
flex: 0 1 auto;
|
||
gap: 4px;
|
||
overflow: visible;
|
||
padding: 0 7px;
|
||
color: #ffe38e;
|
||
font-family: inherit;
|
||
font-size: 18px;
|
||
font-weight: 900;
|
||
line-height: 20px;
|
||
margin: 0;
|
||
text-align: center;
|
||
text-shadow:
|
||
0 1px 1px rgba(65, 23, 0, 0.86),
|
||
0 0 6px rgba(255, 204, 74, 0.34);
|
||
white-space: nowrap;
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
border-image: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.top-coin span {
|
||
display: flex;
|
||
align-items: center;
|
||
min-width: max-content;
|
||
max-width: none;
|
||
flex: 0 0 auto;
|
||
height: 100%;
|
||
overflow: visible;
|
||
line-height: 20px;
|
||
}
|
||
|
||
.top-coin.is-compact span {
|
||
max-width: none;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.top-coin img {
|
||
display: block;
|
||
width: 23px;
|
||
height: 23px;
|
||
flex: 0 0 23px;
|
||
filter: drop-shadow(0 0 5px rgba(255, 197, 49, 0.45));
|
||
}
|
||
|
||
.top-coin-plus {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100%;
|
||
color: #ffd15a;
|
||
font-size: 21px;
|
||
font-weight: 900;
|
||
line-height: 1;
|
||
text-shadow:
|
||
0 1px 1px rgba(65, 23, 0, 0.86),
|
||
0 0 8px rgba(255, 178, 47, 0.74);
|
||
}
|
||
|
||
.top-coin.is-crediting {
|
||
animation: top-coin-credit-pulse 720ms ease-out both;
|
||
}
|
||
|
||
.coin-credit-layer {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 60;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.coin-credit-flyer {
|
||
position: absolute;
|
||
left: var(--coin-credit-x);
|
||
top: var(--coin-credit-y);
|
||
width: 24px;
|
||
height: 24px;
|
||
opacity: 0;
|
||
filter: drop-shadow(0 0 6px rgba(255, 203, 67, 0.86));
|
||
transform: translate3d(0, 0, 0) scale(0.78);
|
||
animation: coin-credit-fly 920ms
|
||
cubic-bezier(0.2, 0.82, 0.16, 1) forwards;
|
||
animation-delay: var(--coin-credit-delay, 0ms);
|
||
will-change: transform, opacity;
|
||
}
|
||
|
||
.language-switcher {
|
||
position: absolute;
|
||
top: 41px;
|
||
right: 0;
|
||
z-index: 20;
|
||
width: 102px;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.language-button {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
height: 22px;
|
||
border: 1px solid rgba(108, 113, 194, 0.68);
|
||
border-radius: 4px;
|
||
background: rgba(7, 10, 40, 0.72);
|
||
color: #ffe38e;
|
||
font-size: 12px;
|
||
font-weight: 800;
|
||
line-height: 14px;
|
||
text-shadow: 0 0 5px rgba(255, 204, 74, 0.42);
|
||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
|
||
}
|
||
|
||
.language-button::after {
|
||
margin-left: 4px;
|
||
color: #ffd15a;
|
||
content: 'v';
|
||
font-size: 10px;
|
||
line-height: 10px;
|
||
}
|
||
|
||
.language-menu {
|
||
position: absolute;
|
||
top: 26px;
|
||
right: 0;
|
||
width: 102px;
|
||
padding: 4px;
|
||
border: 1px solid rgba(108, 113, 194, 0.78);
|
||
border-radius: 6px;
|
||
background: rgba(5, 8, 35, 0.94);
|
||
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.42);
|
||
}
|
||
|
||
.language-menu[hidden] {
|
||
display: none;
|
||
}
|
||
|
||
.language-menu button {
|
||
display: block;
|
||
width: 100%;
|
||
min-height: 24px;
|
||
border-radius: 4px;
|
||
color: rgba(255, 227, 142, 0.78);
|
||
font-size: 12px;
|
||
font-weight: 800;
|
||
line-height: 14px;
|
||
text-align: center;
|
||
}
|
||
|
||
.language-menu button.is-active {
|
||
background: rgba(255, 209, 90, 0.16);
|
||
color: #ffd15a;
|
||
}
|
||
|
||
.rps-screen {
|
||
position: absolute;
|
||
inset: 0;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 180ms ease;
|
||
}
|
||
|
||
.rps-frame[data-state='lobby'] .rps-lobby,
|
||
.rps-frame[data-state='searching'] .rps-matching,
|
||
.rps-frame[data-state='vs'] .rps-vs-screen,
|
||
.rps-frame[data-state='battle'] .rps-battle,
|
||
.rps-frame[data-state='victory'] .rps-victory {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.rps-profile {
|
||
position: absolute;
|
||
top: 116px;
|
||
left: 102px;
|
||
z-index: 6;
|
||
width: 170px;
|
||
text-align: center;
|
||
}
|
||
|
||
.rps-avatar-ring {
|
||
position: relative;
|
||
width: 96px;
|
||
height: 96px;
|
||
margin: 0 auto;
|
||
border: 3px solid #f8bc39;
|
||
border-radius: 50%;
|
||
background:
|
||
radial-gradient(circle, #74421f 0%, #2c1235 66%), #35153d;
|
||
box-shadow:
|
||
0 0 0 4px rgba(76, 35, 4, 0.9),
|
||
0 0 0 7px rgba(255, 205, 61, 0.45),
|
||
0 0 22px rgba(255, 175, 40, 0.76);
|
||
}
|
||
|
||
.rps-avatar-ring::before {
|
||
position: absolute;
|
||
top: -18px;
|
||
left: 27px;
|
||
width: 42px;
|
||
height: 26px;
|
||
content: '';
|
||
background: linear-gradient(180deg, #ffe777 0%, #c06d12 100%);
|
||
clip-path: polygon(
|
||
0 100%,
|
||
14% 38%,
|
||
29% 72%,
|
||
50% 0,
|
||
71% 72%,
|
||
86% 38%,
|
||
100% 100%
|
||
);
|
||
filter: drop-shadow(0 2px 0 rgba(92, 31, 0, 0.72));
|
||
}
|
||
|
||
.rps-profile .rps-avatar-ring {
|
||
width: 90px;
|
||
height: 98px;
|
||
border: 0;
|
||
border-radius: 0;
|
||
background: transparent;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.rps-profile .rps-avatar-ring::before {
|
||
display: none;
|
||
}
|
||
|
||
.rps-lobby-avatar-fallback {
|
||
position: absolute;
|
||
left: 7px;
|
||
top: 15px;
|
||
width: 75px;
|
||
height: 75px;
|
||
border-radius: 50%;
|
||
background:
|
||
radial-gradient(circle, #74421f 0%, #2c1235 66%), #35153d;
|
||
}
|
||
|
||
.rps-lobby-avatar-frame {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 90px;
|
||
height: 98px;
|
||
object-fit: fill;
|
||
pointer-events: none;
|
||
z-index: 2;
|
||
}
|
||
|
||
.rps-avatar-img {
|
||
position: absolute;
|
||
inset: 5px;
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.rps-profile .rps-avatar-img {
|
||
left: 7px;
|
||
top: 15px;
|
||
width: 75px;
|
||
height: 75px;
|
||
z-index: 1;
|
||
}
|
||
|
||
.rps-avatar-img.is-empty {
|
||
display: none;
|
||
}
|
||
|
||
.rps-profile-name,
|
||
.rps-victory-name {
|
||
color: #ffe189;
|
||
font-weight: 700;
|
||
text-shadow:
|
||
0 1px 0 #7a2e09,
|
||
0 0 4px #692808;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.rps-profile-name {
|
||
margin-top: 13px;
|
||
font-size: 16px;
|
||
line-height: 16px;
|
||
}
|
||
|
||
.rps-showcase {
|
||
position: absolute;
|
||
top: 195px;
|
||
left: 0;
|
||
width: 375px;
|
||
height: 236px;
|
||
}
|
||
|
||
.rps-hand-zone {
|
||
position: absolute;
|
||
width: 258px;
|
||
height: 258px;
|
||
}
|
||
|
||
.rps-hand-zone.is-left {
|
||
left: -52px;
|
||
top: 0;
|
||
}
|
||
|
||
.rps-hand-zone.is-right {
|
||
left: 169px;
|
||
top: 0;
|
||
}
|
||
|
||
.rps-ring {
|
||
position: absolute;
|
||
top: 23px;
|
||
width: 258px;
|
||
height: 258px;
|
||
opacity: 0.88;
|
||
animation: matching-center-spin 10s linear infinite;
|
||
}
|
||
|
||
.rps-hand-zone.is-left .rps-ring {
|
||
left: -7px;
|
||
}
|
||
|
||
.rps-hand-zone.is-right .rps-ring {
|
||
right: -7px;
|
||
}
|
||
|
||
.rps-flare {
|
||
position: absolute;
|
||
top: 0;
|
||
width: 244px;
|
||
height: 236px;
|
||
opacity: 0.95;
|
||
}
|
||
|
||
.rps-hand-zone.is-left .rps-flare {
|
||
left: 0;
|
||
}
|
||
|
||
.rps-hand-zone.is-right .rps-flare {
|
||
right: 0;
|
||
}
|
||
|
||
.rps-hand {
|
||
position: absolute;
|
||
object-fit: contain;
|
||
filter: drop-shadow(0 16px 11px rgba(0, 0, 0, 0.42));
|
||
}
|
||
|
||
.rps-hand-zone.is-left .rps-hand {
|
||
left: 89px;
|
||
top: 90px;
|
||
width: 78px;
|
||
height: 101px;
|
||
}
|
||
|
||
.rps-hand-zone.is-right .rps-hand {
|
||
left: 68px;
|
||
top: 72px;
|
||
width: 121px;
|
||
height: 131px;
|
||
}
|
||
|
||
.rps-hand[data-gesture='paper'] {
|
||
width: 118px;
|
||
height: 132px;
|
||
}
|
||
|
||
.rps-hand[data-gesture='scissors'] {
|
||
width: 92px;
|
||
height: 132px;
|
||
}
|
||
|
||
.rps-lobby-panel {
|
||
position: absolute;
|
||
left: 12px;
|
||
top: 488px;
|
||
width: 350px;
|
||
height: 292px;
|
||
background: url('assets/figma/lobby-panel.svg') center/100% 100%
|
||
no-repeat;
|
||
}
|
||
|
||
.rps-panel-title,
|
||
.rps-stake-title {
|
||
position: absolute;
|
||
left: 0;
|
||
width: 100%;
|
||
color: #f8edc7;
|
||
font-size: 14px;
|
||
line-height: 14px;
|
||
text-align: center;
|
||
text-shadow: 0 0 6px rgba(255, 217, 115, 0.38);
|
||
}
|
||
|
||
.rps-panel-title {
|
||
top: 12px;
|
||
}
|
||
|
||
.rps-stake-title {
|
||
top: 155px;
|
||
}
|
||
|
||
.rps-help {
|
||
position: absolute;
|
||
top: 9px;
|
||
right: 8px;
|
||
width: 20px;
|
||
height: 20px;
|
||
overflow: hidden;
|
||
color: transparent;
|
||
background: url('assets/figma/help-badge.png') center/100% 100%
|
||
no-repeat;
|
||
}
|
||
|
||
.rps-gesture-row {
|
||
position: absolute;
|
||
top: 38px;
|
||
left: 8px;
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 105px);
|
||
gap: 10px;
|
||
}
|
||
|
||
.rps-gesture {
|
||
position: relative;
|
||
width: 105px;
|
||
height: 103px;
|
||
background: url('assets/figma/gesture-card.png') center/100%
|
||
100% no-repeat;
|
||
}
|
||
|
||
.rps-gesture[aria-pressed='true'] {
|
||
background-image: url('assets/figma/gesture-selected-rock.png');
|
||
filter: drop-shadow(0 0 8px rgba(22, 102, 255, 0.95));
|
||
}
|
||
|
||
.rps-gesture img {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 20px;
|
||
width: 40px;
|
||
height: 48px;
|
||
object-fit: contain;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
.rps-gesture[data-gesture='paper'] img {
|
||
top: 18px;
|
||
width: 42px;
|
||
height: 50px;
|
||
}
|
||
|
||
.rps-gesture[data-gesture='scissors'] img {
|
||
top: 17px;
|
||
width: 46px;
|
||
height: 52px;
|
||
}
|
||
|
||
.rps-gesture span {
|
||
position: absolute;
|
||
left: 0;
|
||
bottom: 10px;
|
||
width: 100%;
|
||
color: #ffd982;
|
||
font-size: 14px;
|
||
font-weight: 800;
|
||
line-height: 14px;
|
||
text-align: center;
|
||
text-shadow:
|
||
0 1px 0 #532203,
|
||
0 0 5px rgba(255, 216, 105, 0.58);
|
||
}
|
||
|
||
.rps-stake-row {
|
||
position: absolute;
|
||
top: 181px;
|
||
left: 8px;
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 77px);
|
||
gap: 9px;
|
||
overflow: visible;
|
||
}
|
||
|
||
.rps-stake {
|
||
position: relative;
|
||
width: 77px;
|
||
height: 28px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
color: #ffe189;
|
||
font-size: 16px;
|
||
font-weight: 800;
|
||
line-height: 16px;
|
||
text-shadow: 0 0 4px rgba(213, 148, 43, 0.82);
|
||
overflow: hidden;
|
||
padding-left: 8px;
|
||
white-space: nowrap;
|
||
z-index: 1;
|
||
}
|
||
|
||
.rps-stake-bg {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 77px;
|
||
height: 28px;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.rps-stake[aria-pressed='true'] .rps-stake-bg {
|
||
display: none;
|
||
}
|
||
|
||
.rps-stake[aria-pressed='true'] {
|
||
filter: drop-shadow(0 0 5px rgba(255, 197, 47, 0.4));
|
||
}
|
||
|
||
.rps-stake-selected-slice {
|
||
position: absolute;
|
||
top: 0;
|
||
z-index: 0;
|
||
display: none;
|
||
width: 45px;
|
||
height: 28px;
|
||
pointer-events: none;
|
||
background-image: url('assets/figma/stake-selected.png');
|
||
background-repeat: no-repeat;
|
||
background-size: 105px 28px;
|
||
}
|
||
|
||
.rps-stake[aria-pressed='true'] .rps-stake-selected-slice {
|
||
display: block;
|
||
}
|
||
|
||
.rps-stake-selected-left {
|
||
left: 0;
|
||
background-position: left top;
|
||
}
|
||
|
||
.rps-stake-selected-right {
|
||
right: 0;
|
||
background-position: right top;
|
||
}
|
||
|
||
.rps-stake-coin {
|
||
position: relative;
|
||
width: 14px;
|
||
height: 14px;
|
||
flex: 0 0 14px;
|
||
z-index: 1;
|
||
}
|
||
|
||
.rps-stake-value {
|
||
position: relative;
|
||
font-size: 12px;
|
||
line-height: 12px;
|
||
z-index: 1;
|
||
}
|
||
|
||
.rps-start {
|
||
position: absolute;
|
||
left: 73px;
|
||
top: 225px;
|
||
width: 201px;
|
||
height: 40px;
|
||
background: url('assets/figma/start-button.png') center/100%
|
||
100% no-repeat;
|
||
color: #fcf7cb;
|
||
filter: brightness(1.35) saturate(1.35)
|
||
drop-shadow(0 0 18px rgba(0, 136, 255, 0.96));
|
||
font-size: 18px;
|
||
font-weight: 800;
|
||
line-height: 40px;
|
||
text-align: center;
|
||
text-shadow:
|
||
0 1px 1px rgba(52, 18, 0, 0.9),
|
||
0 0 8px rgba(255, 231, 121, 0.76);
|
||
}
|
||
|
||
.rps-start::before {
|
||
content: none;
|
||
}
|
||
|
||
.rps-refund-note {
|
||
position: absolute;
|
||
left: 49px;
|
||
bottom: 11px;
|
||
width: 252px;
|
||
overflow: hidden;
|
||
color: rgba(255, 255, 255, 0.4);
|
||
font-size: 12px;
|
||
line-height: 12px;
|
||
text-align: center;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.rps-duel-card {
|
||
position: absolute;
|
||
top: 126px;
|
||
width: 160px;
|
||
height: 80px;
|
||
}
|
||
|
||
.rps-duel-card.is-self {
|
||
left: 12px;
|
||
}
|
||
|
||
.rps-duel-card.is-opponent {
|
||
left: 203px;
|
||
}
|
||
|
||
.rps-duel-card > img {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 160px;
|
||
height: 80px;
|
||
object-fit: fill;
|
||
}
|
||
|
||
.rps-duel-avatar {
|
||
position: absolute;
|
||
top: 10px;
|
||
width: 62px;
|
||
height: 62px;
|
||
border: 2px solid #f6bc43;
|
||
border-radius: 50%;
|
||
background: radial-gradient(circle, #5c2d48 0%, #180b32 76%);
|
||
box-shadow: 0 0 12px rgba(255, 192, 58, 0.68);
|
||
}
|
||
|
||
.rps-duel-card.is-self .rps-duel-avatar {
|
||
left: 6px;
|
||
}
|
||
|
||
.rps-duel-card.is-opponent .rps-duel-avatar {
|
||
right: 7px;
|
||
border-color: #54bcff;
|
||
box-shadow: 0 0 17px rgba(72, 142, 255, 0.92);
|
||
}
|
||
|
||
.rps-duel-avatar img {
|
||
position: absolute;
|
||
inset: 3px;
|
||
width: 52px;
|
||
height: 52px;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.rps-duel-avatar img.is-empty {
|
||
display: none;
|
||
}
|
||
|
||
.rps-duel-card.is-opponent.has-opponent-avatar .rps-question {
|
||
display: none;
|
||
}
|
||
|
||
.rps-duel-card.is-opponent.has-opponent .rps-duel-status {
|
||
display: none;
|
||
}
|
||
|
||
.rps-question {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: grid;
|
||
place-items: center;
|
||
color: #8fd5ff;
|
||
font-size: 28px;
|
||
font-weight: 900;
|
||
text-shadow: 0 0 12px rgba(52, 134, 255, 1);
|
||
}
|
||
|
||
.rps-duel-name {
|
||
position: absolute;
|
||
top: 29px;
|
||
width: 58px;
|
||
color: #fff;
|
||
font-size: 10px;
|
||
line-height: 14px;
|
||
}
|
||
|
||
.rps-duel-card.is-self .rps-duel-name {
|
||
left: 79px;
|
||
}
|
||
|
||
.rps-duel-card.is-opponent .rps-duel-name {
|
||
left: 21px;
|
||
text-align: left;
|
||
}
|
||
|
||
.rps-duel-status {
|
||
position: absolute;
|
||
top: 45px;
|
||
left: 37px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
color: #b9defb;
|
||
font-size: 8px;
|
||
line-height: 14px;
|
||
}
|
||
|
||
.rps-duel-status img {
|
||
width: 12px;
|
||
height: 12px;
|
||
}
|
||
|
||
.rps-search-hud {
|
||
position: absolute;
|
||
top: 134px;
|
||
left: 152px;
|
||
width: 72px;
|
||
height: 72px;
|
||
}
|
||
|
||
.rps-search-hud img:first-child {
|
||
width: 72px;
|
||
height: 72px;
|
||
animation: matching-center-spin 3000ms linear infinite;
|
||
}
|
||
|
||
.rps-search-hud img:last-child {
|
||
position: absolute;
|
||
top: 29px;
|
||
left: 29px;
|
||
width: 15px;
|
||
height: 15px;
|
||
}
|
||
|
||
.rps-frame.has-opponent .rps-search-hud {
|
||
display: none;
|
||
}
|
||
|
||
.rps-bottom-status {
|
||
position: absolute;
|
||
left: 74px;
|
||
top: 676px;
|
||
width: 228px;
|
||
color: #c9e8ff;
|
||
font-size: 24px;
|
||
font-weight: 900;
|
||
line-height: 24px;
|
||
text-align: center;
|
||
text-shadow:
|
||
0 0 6px #0039bc,
|
||
0 0 16px rgba(70, 157, 255, 0.78);
|
||
}
|
||
|
||
.rps-bottom-status::before,
|
||
.rps-bottom-status::after {
|
||
position: absolute;
|
||
top: 11px;
|
||
width: 36px;
|
||
height: 1px;
|
||
content: '';
|
||
background: linear-gradient(
|
||
90deg,
|
||
transparent,
|
||
rgba(180, 224, 255, 0.95)
|
||
);
|
||
}
|
||
|
||
.rps-bottom-status::before {
|
||
left: 0;
|
||
}
|
||
|
||
.rps-bottom-status::after {
|
||
right: 0;
|
||
transform: scaleX(-1);
|
||
}
|
||
|
||
.rps-vs-badge {
|
||
position: absolute;
|
||
top: 112px;
|
||
left: 156px;
|
||
width: 68px;
|
||
height: 112px;
|
||
z-index: 12;
|
||
}
|
||
|
||
.rps-battle .rps-showcase {
|
||
top: 244px;
|
||
}
|
||
|
||
.rps-battle .rps-hand-zone.is-left .rps-hand {
|
||
animation: rps-left-clash 900ms
|
||
cubic-bezier(0.21, 0.92, 0.22, 1) 260ms both;
|
||
}
|
||
|
||
.rps-battle .rps-hand-zone.is-right .rps-hand {
|
||
animation: rps-right-clash 900ms
|
||
cubic-bezier(0.21, 0.92, 0.22, 1) 260ms both;
|
||
}
|
||
|
||
.rps-frame.is-self-win-compare {
|
||
animation: you-win-screen-smash 780ms
|
||
cubic-bezier(0.14, 0.84, 0.18, 1) 430ms both;
|
||
}
|
||
|
||
.countdown-layer {
|
||
position: absolute;
|
||
top: 268px;
|
||
left: 0;
|
||
z-index: 28;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 375px;
|
||
height: 158px;
|
||
visibility: hidden;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 120ms ease;
|
||
}
|
||
|
||
.rps-frame.is-countdown .countdown-layer {
|
||
visibility: visible;
|
||
opacity: 1;
|
||
}
|
||
|
||
.you-win-smash-layer,
|
||
.you-win-impact {
|
||
position: absolute;
|
||
visibility: hidden;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.you-win-smash-layer {
|
||
inset: 0;
|
||
z-index: 31;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.you-win-smash-layer::before {
|
||
position: absolute;
|
||
inset: 0;
|
||
content: '';
|
||
background:
|
||
radial-gradient(
|
||
circle at 50% 39%,
|
||
rgba(255, 248, 179, 0.72) 0%,
|
||
rgba(255, 126, 20, 0.3) 18%,
|
||
rgba(255, 73, 0, 0.08) 35%,
|
||
rgba(255, 73, 0, 0) 58%
|
||
),
|
||
rgba(255, 255, 255, 0.2);
|
||
opacity: 0;
|
||
}
|
||
|
||
.you-win-smash-floor {
|
||
position: absolute;
|
||
top: 362px;
|
||
left: 50%;
|
||
width: 312px;
|
||
height: 92px;
|
||
border-radius: 50%;
|
||
background: radial-gradient(
|
||
ellipse at center,
|
||
rgba(255, 215, 75, 0.68) 0%,
|
||
rgba(255, 103, 15, 0.34) 30%,
|
||
rgba(120, 16, 0, 0.28) 54%,
|
||
rgba(0, 0, 0, 0) 76%
|
||
);
|
||
opacity: 0;
|
||
filter: blur(1px) drop-shadow(0 0 18px rgba(255, 118, 22, 0.76));
|
||
transform: translateX(-50%) scale(0.38, 0.2);
|
||
transform-origin: 50% 50%;
|
||
}
|
||
|
||
.rps-frame.is-self-win-compare .you-win-smash-layer {
|
||
visibility: visible;
|
||
opacity: 1;
|
||
}
|
||
|
||
.rps-frame.is-self-win-compare .you-win-smash-layer::before {
|
||
animation: you-win-screen-flash 760ms ease-out 430ms both;
|
||
}
|
||
|
||
.rps-frame.is-self-win-compare .you-win-smash-floor {
|
||
animation: you-win-floor-impact 900ms ease-out 430ms both;
|
||
}
|
||
|
||
.you-win-impact {
|
||
top: 300px;
|
||
left: 0;
|
||
z-index: 32;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 375px;
|
||
height: 92px;
|
||
color: transparent;
|
||
font-size: 56px;
|
||
font-weight: 1000;
|
||
line-height: 62px;
|
||
text-align: center;
|
||
background: linear-gradient(
|
||
180deg,
|
||
#ffffff 0%,
|
||
#fff5b6 19%,
|
||
#ffcf44 48%,
|
||
#ff7a00 74%,
|
||
#9b2d00 100%
|
||
);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
-webkit-text-stroke: 2px #7d2700;
|
||
filter: drop-shadow(0 5px 0 rgba(94, 17, 0, 0.9))
|
||
drop-shadow(0 0 22px rgba(255, 205, 46, 0.9))
|
||
drop-shadow(0 0 36px rgba(255, 77, 0, 0.78));
|
||
transform-origin: 50% 78%;
|
||
will-change: opacity, transform, filter;
|
||
}
|
||
|
||
.rps-frame.is-self-win-compare .you-win-impact {
|
||
visibility: visible;
|
||
opacity: 1;
|
||
animation: you-win-drop-impact 980ms
|
||
cubic-bezier(0.13, 0.92, 0.18, 1) both;
|
||
}
|
||
|
||
.countdown-number {
|
||
min-width: 132px;
|
||
color: transparent;
|
||
font-size: 132px;
|
||
font-weight: 1000;
|
||
line-height: 132px;
|
||
text-align: center;
|
||
background: linear-gradient(
|
||
180deg,
|
||
#ffffff 0%,
|
||
#fff5b6 17%,
|
||
#ffcf44 43%,
|
||
#ff7a00 71%,
|
||
#9b2d00 100%
|
||
);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
-webkit-text-stroke: 2px #7d2700;
|
||
filter: drop-shadow(0 5px 0 rgba(94, 17, 0, 0.9))
|
||
drop-shadow(0 0 18px rgba(255, 205, 46, 0.92))
|
||
drop-shadow(0 0 30px rgba(255, 77, 0, 0.72));
|
||
opacity: 0;
|
||
transform: scale(0.72);
|
||
}
|
||
|
||
.countdown-number.is-ticking {
|
||
animation: countdown-number-pop 940ms ease-out both;
|
||
}
|
||
|
||
.rps-victory-title {
|
||
position: absolute;
|
||
top: 90px;
|
||
left: 29px;
|
||
width: 316px;
|
||
height: 124px;
|
||
object-fit: contain;
|
||
z-index: 1;
|
||
}
|
||
|
||
.rps-victory .rps-avatar-ring {
|
||
position: absolute;
|
||
top: 246px;
|
||
left: 102px;
|
||
width: 170px;
|
||
height: 186px;
|
||
border: 0;
|
||
background: transparent;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.rps-victory .rps-avatar-ring::before {
|
||
display: none;
|
||
}
|
||
|
||
.rps-victory-avatar-bg {
|
||
position: absolute;
|
||
left: 15px;
|
||
top: 28px;
|
||
width: 144px;
|
||
height: 144px;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.rps-victory-avatar-frame {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 170px;
|
||
height: 186px;
|
||
object-fit: fill;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.rps-victory .rps-avatar-img {
|
||
left: 15px;
|
||
top: 28px;
|
||
width: 144px;
|
||
height: 144px;
|
||
z-index: 1;
|
||
}
|
||
|
||
.rps-victory-name {
|
||
position: absolute;
|
||
top: 448px;
|
||
left: 72px;
|
||
width: 232px;
|
||
overflow: hidden;
|
||
font-size: 24px;
|
||
line-height: 26px;
|
||
text-align: center;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.rps-reward-panel {
|
||
position: absolute;
|
||
top: 514px;
|
||
left: 38px;
|
||
width: 300px;
|
||
height: 103px;
|
||
background: url('assets/figma/victory-reward-frame.png')
|
||
center/100% 100% no-repeat;
|
||
}
|
||
|
||
.rps-reward-panel img {
|
||
position: absolute;
|
||
left: 38px;
|
||
top: 16px;
|
||
width: 75px;
|
||
height: 70px;
|
||
object-fit: contain;
|
||
}
|
||
|
||
.rps-reward-copy {
|
||
position: absolute;
|
||
top: 13px;
|
||
left: 145px;
|
||
width: 170px;
|
||
color: #ffe189;
|
||
text-align: center;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.rps-reward-copy strong {
|
||
display: block;
|
||
font-size: 18px;
|
||
line-height: 20px;
|
||
}
|
||
|
||
.rps-reward-copy b {
|
||
display: block;
|
||
margin-top: 4px;
|
||
font-size: 24px;
|
||
line-height: 24px;
|
||
}
|
||
|
||
.rps-reward-copy span {
|
||
display: block;
|
||
margin-top: 4px;
|
||
font-size: 18px;
|
||
line-height: 20px;
|
||
}
|
||
|
||
.rps-victory-actions {
|
||
position: absolute;
|
||
top: 690px;
|
||
left: 12px;
|
||
width: 351px;
|
||
height: 40px;
|
||
}
|
||
|
||
.rps-victory-button {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 168px;
|
||
height: 40px;
|
||
color: #fcf7cb;
|
||
font-size: 18px;
|
||
font-weight: 800;
|
||
line-height: 40px;
|
||
text-align: center;
|
||
text-shadow: 0 0 2px #d5942b;
|
||
}
|
||
|
||
.rps-victory-button::before {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: -1;
|
||
content: '';
|
||
background: url('assets/figma/victory-action-blue.png')
|
||
center/100% 100% no-repeat;
|
||
filter: brightness(1.22) saturate(1.14)
|
||
drop-shadow(0 0 10px rgba(0, 111, 255, 0.9));
|
||
}
|
||
|
||
.rps-victory-button.is-secondary {
|
||
left: 183px;
|
||
}
|
||
|
||
.rps-victory-button.is-secondary::before {
|
||
background-image: url('assets/figma/victory-action-secondary.png');
|
||
filter: drop-shadow(0 0 5px rgba(255, 227, 142, 0.34));
|
||
}
|
||
|
||
.rps-message {
|
||
position: absolute;
|
||
left: 36px;
|
||
top: 636px;
|
||
z-index: 35;
|
||
width: 303px;
|
||
color: #f4f8ff;
|
||
font-size: 13px;
|
||
line-height: 16px;
|
||
text-align: center;
|
||
text-shadow: 0 0 8px rgba(31, 130, 255, 0.95);
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.rps-ring,
|
||
.rps-search-hud img:first-child,
|
||
.rps-battle .rps-hand-zone .rps-hand,
|
||
.coin-credit-flyer,
|
||
.rps-frame.is-self-win-compare,
|
||
.rps-frame.is-self-win-compare .you-win-smash-layer::before,
|
||
.rps-frame.is-self-win-compare .you-win-smash-floor,
|
||
.rps-frame.is-self-win-compare .you-win-impact,
|
||
.top-coin.is-crediting {
|
||
animation: none !important;
|
||
}
|
||
}
|
||
|
||
@keyframes rps-left-clash {
|
||
0% {
|
||
transform: translateX(-34px) scale(0.94);
|
||
}
|
||
46% {
|
||
transform: translateX(42px) scale(1.08) rotate(4deg);
|
||
}
|
||
62% {
|
||
transform: translateX(26px) scale(0.98) rotate(-2deg);
|
||
}
|
||
100% {
|
||
transform: translateX(0) scale(1) rotate(0deg);
|
||
}
|
||
}
|
||
|
||
@keyframes rps-right-clash {
|
||
0% {
|
||
transform: translateX(34px) scale(0.94);
|
||
}
|
||
46% {
|
||
transform: translateX(-42px) scale(1.08) rotate(-4deg);
|
||
}
|
||
62% {
|
||
transform: translateX(-26px) scale(0.98) rotate(2deg);
|
||
}
|
||
100% {
|
||
transform: translateX(0) scale(1) rotate(0deg);
|
||
}
|
||
}
|
||
|
||
@keyframes you-win-drop-impact {
|
||
0% {
|
||
opacity: 0;
|
||
filter: brightness(1.9) drop-shadow(0 0 36px #ff79ff);
|
||
transform: translateY(-650px) scale(3.9) rotate(-7deg);
|
||
}
|
||
34% {
|
||
opacity: 1;
|
||
filter: brightness(1.75) drop-shadow(0 0 32px #4cc9ff);
|
||
transform: translateY(-122px) scale(2.05) rotate(4deg);
|
||
}
|
||
58% {
|
||
opacity: 1;
|
||
filter: brightness(1.55) drop-shadow(0 0 30px #ffd557);
|
||
transform: translateY(28px) scale(0.78) rotate(1deg);
|
||
}
|
||
72% {
|
||
filter: brightness(1.42) drop-shadow(0 0 24px #ff79ff);
|
||
transform: translateY(-16px) scale(1.16) rotate(-0.8deg);
|
||
}
|
||
86% {
|
||
filter: brightness(1.18) drop-shadow(0 0 16px #4cc9ff);
|
||
transform: translateY(4px) scale(0.96) rotate(0.3deg);
|
||
}
|
||
100% {
|
||
opacity: 1;
|
||
filter: drop-shadow(0 5px 0 rgba(94, 17, 0, 0.9))
|
||
drop-shadow(0 0 22px rgba(255, 205, 46, 0.9))
|
||
drop-shadow(0 0 36px rgba(255, 77, 0, 0.78));
|
||
transform: translateY(0) scale(1) rotate(0);
|
||
}
|
||
}
|
||
|
||
@keyframes you-win-screen-smash {
|
||
0%,
|
||
100% {
|
||
transform: translate3d(0, 0, 0)
|
||
scale(var(--game-screen-scale));
|
||
filter: none;
|
||
}
|
||
12% {
|
||
transform: translate3d(0, 14px, 0)
|
||
scale(var(--game-screen-scale));
|
||
filter: brightness(1.22) contrast(1.08);
|
||
}
|
||
24% {
|
||
transform: translate3d(-12px, -8px, 0)
|
||
scale(var(--game-screen-scale));
|
||
}
|
||
36% {
|
||
transform: translate3d(10px, 7px, 0)
|
||
scale(var(--game-screen-scale));
|
||
}
|
||
50% {
|
||
transform: translate3d(-7px, 4px, 0)
|
||
scale(var(--game-screen-scale));
|
||
}
|
||
66% {
|
||
transform: translate3d(5px, -3px, 0)
|
||
scale(var(--game-screen-scale));
|
||
filter: brightness(1.08) contrast(1.03);
|
||
}
|
||
82% {
|
||
transform: translate3d(-2px, 1px, 0)
|
||
scale(var(--game-screen-scale));
|
||
}
|
||
}
|
||
|
||
@keyframes you-win-screen-flash {
|
||
0% {
|
||
opacity: 0;
|
||
}
|
||
8% {
|
||
opacity: 1;
|
||
}
|
||
22% {
|
||
opacity: 0.46;
|
||
}
|
||
100% {
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
@keyframes you-win-floor-impact {
|
||
0% {
|
||
opacity: 0;
|
||
transform: translateX(-50%) scale(0.38, 0.2);
|
||
}
|
||
16% {
|
||
opacity: 1;
|
||
transform: translateX(-50%) scale(1.05, 0.52);
|
||
}
|
||
58% {
|
||
opacity: 0.62;
|
||
}
|
||
100% {
|
||
opacity: 0;
|
||
transform: translateX(-50%) scale(1.32, 0.72);
|
||
}
|
||
}
|
||
|
||
@keyframes coin-credit-fly {
|
||
0% {
|
||
opacity: 0;
|
||
transform: translate3d(0, 0, 0) scale(0.78) rotate(0deg);
|
||
}
|
||
12% {
|
||
opacity: 1;
|
||
}
|
||
58% {
|
||
opacity: 1;
|
||
transform: translate3d(
|
||
calc(var(--coin-credit-dx) * 0.72),
|
||
calc(var(--coin-credit-dy) * 0.58 - 34px),
|
||
0
|
||
)
|
||
scale(1.05) rotate(220deg);
|
||
}
|
||
100% {
|
||
opacity: 0;
|
||
transform: translate3d(
|
||
var(--coin-credit-dx),
|
||
var(--coin-credit-dy),
|
||
0
|
||
)
|
||
scale(0.36) rotate(520deg);
|
||
}
|
||
}
|
||
|
||
@keyframes top-coin-credit-pulse {
|
||
0% {
|
||
transform: scale(1);
|
||
filter: drop-shadow(0 0 0 rgba(255, 211, 79, 0));
|
||
}
|
||
42% {
|
||
transform: scale(1.12);
|
||
filter: drop-shadow(0 0 13px rgba(255, 211, 79, 0.86));
|
||
}
|
||
100% {
|
||
transform: scale(1);
|
||
filter: drop-shadow(0 0 0 rgba(255, 211, 79, 0));
|
||
}
|
||
}
|
||
|
||
@keyframes countdown-number-pop {
|
||
0% {
|
||
opacity: 0;
|
||
transform: scale(0.32);
|
||
filter: drop-shadow(0 5px 0 rgba(94, 17, 0, 0.9))
|
||
drop-shadow(0 0 8px rgba(255, 205, 46, 0.55));
|
||
}
|
||
|
||
24% {
|
||
opacity: 1;
|
||
transform: scale(1.18);
|
||
filter: drop-shadow(0 5px 0 rgba(94, 17, 0, 0.9))
|
||
drop-shadow(0 0 24px rgba(255, 229, 86, 1))
|
||
drop-shadow(0 0 42px rgba(255, 77, 0, 0.86));
|
||
}
|
||
|
||
70% {
|
||
opacity: 1;
|
||
transform: scale(1);
|
||
}
|
||
|
||
100% {
|
||
opacity: 0;
|
||
transform: scale(0.86);
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div
|
||
class="game-stage"
|
||
role="application"
|
||
aria-label="Rock paper scissors"
|
||
data-i18n-aria="rockGame.pageLabel"
|
||
>
|
||
<main
|
||
class="game-frame rps-frame is-preloading"
|
||
data-state="lobby"
|
||
data-node-id="491:135"
|
||
data-name="rock-paper-scissors"
|
||
>
|
||
<div
|
||
class="loading-screen"
|
||
data-role="loading-screen"
|
||
aria-label="Loading"
|
||
role="status"
|
||
>
|
||
<img
|
||
class="loading-bg"
|
||
src="assets/figma/loading-bg.jpg"
|
||
alt=""
|
||
/>
|
||
<div
|
||
class="loading-progress"
|
||
data-role="loading-progress"
|
||
role="progressbar"
|
||
aria-valuemin="0"
|
||
aria-valuemax="100"
|
||
aria-valuenow="0"
|
||
>
|
||
<span
|
||
class="loading-progress-bar"
|
||
data-role="loading-progress-bar"
|
||
></span>
|
||
</div>
|
||
</div>
|
||
|
||
<img class="rps-bg" src="assets/figma/bg-arena.jpg" alt="" />
|
||
<img
|
||
class="rps-bg rps-bg-overlay"
|
||
src="assets/figma/bg-arena.jpg"
|
||
alt=""
|
||
/>
|
||
<div class="rps-home-indicator" aria-hidden="true"></div>
|
||
|
||
<div class="top-bar" data-name="top bar">
|
||
<button
|
||
class="top-back"
|
||
type="button"
|
||
aria-label="Back"
|
||
data-action="back"
|
||
>
|
||
<img
|
||
class="layer-img cover"
|
||
src="assets/figma/back-button.png"
|
||
alt=""
|
||
/>
|
||
</button>
|
||
<div class="top-game-icon">
|
||
<img
|
||
class="layer-img cover"
|
||
src="assets/figma/game-icon.png"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<button
|
||
class="top-coin"
|
||
type="button"
|
||
aria-label="Recharge"
|
||
data-action="recharge"
|
||
>
|
||
<img src="assets/figma/coin.png" alt="" />
|
||
<span>159</span>
|
||
<strong class="top-coin-plus" aria-hidden="true"
|
||
>+</strong
|
||
>
|
||
</button>
|
||
<div class="language-switcher">
|
||
<button
|
||
class="language-button"
|
||
type="button"
|
||
data-language-toggle
|
||
data-i18n-aria="rockGame.changeLanguage"
|
||
aria-label="Change language"
|
||
aria-expanded="false"
|
||
>
|
||
<span data-current-lang>EN</span>
|
||
</button>
|
||
<div class="language-menu" data-language-menu hidden>
|
||
<button type="button" data-lang-option="en">
|
||
EN
|
||
</button>
|
||
<button type="button" data-lang-option="ar">
|
||
AR
|
||
</button>
|
||
<button type="button" data-lang-option="es">
|
||
ES
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<section class="rps-screen rps-lobby">
|
||
<div class="rps-profile">
|
||
<div class="rps-avatar-ring">
|
||
<div
|
||
class="rps-lobby-avatar-fallback"
|
||
aria-hidden="true"
|
||
></div>
|
||
<img
|
||
class="rps-avatar-img is-empty"
|
||
data-role="self-avatar"
|
||
alt=""
|
||
/>
|
||
<img
|
||
class="rps-lobby-avatar-frame"
|
||
src="assets/figma/lobby-avatar-frame.png"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<div class="rps-profile-name" data-role="self-name">
|
||
NameNameName...
|
||
</div>
|
||
</div>
|
||
|
||
<div class="rps-showcase">
|
||
<div class="rps-hand-zone is-left">
|
||
<img
|
||
class="rps-ring"
|
||
src="assets/figma/red-ring.png"
|
||
alt=""
|
||
/>
|
||
<img
|
||
class="rps-flare"
|
||
src="assets/figma/red-flare.png"
|
||
alt=""
|
||
/>
|
||
<img
|
||
class="rps-hand"
|
||
data-role="self-lobby-hand"
|
||
data-gesture="rock"
|
||
src="assets/figma/rock.png"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<div class="rps-hand-zone is-right">
|
||
<img
|
||
class="rps-ring"
|
||
src="assets/figma/blue-ring.png"
|
||
alt=""
|
||
/>
|
||
<img
|
||
class="rps-flare"
|
||
src="assets/figma/blue-flare.png"
|
||
alt=""
|
||
/>
|
||
<img
|
||
class="rps-hand"
|
||
data-role="preview-opponent-hand"
|
||
data-gesture="paper"
|
||
src="assets/figma/paper.png"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="rps-lobby-panel">
|
||
<div
|
||
class="rps-panel-title"
|
||
data-i18n="rockGame.chooseGesture"
|
||
>
|
||
Choose your gesture
|
||
</div>
|
||
<div class="rps-help">?</div>
|
||
<div class="rps-gesture-row">
|
||
<button
|
||
class="rps-gesture"
|
||
type="button"
|
||
data-action="gesture"
|
||
data-gesture="rock"
|
||
aria-pressed="true"
|
||
>
|
||
<img src="assets/figma/rock.png" alt="" />
|
||
<span data-i18n="rockGame.rock">Rock</span>
|
||
</button>
|
||
<button
|
||
class="rps-gesture"
|
||
type="button"
|
||
data-action="gesture"
|
||
data-gesture="paper"
|
||
aria-pressed="false"
|
||
>
|
||
<img src="assets/figma/paper.png" alt="" />
|
||
<span data-i18n="rockGame.paper">Paper</span>
|
||
</button>
|
||
<button
|
||
class="rps-gesture"
|
||
type="button"
|
||
data-action="gesture"
|
||
data-gesture="scissors"
|
||
aria-pressed="false"
|
||
>
|
||
<img src="assets/figma/scissors.png" alt="" />
|
||
<span data-i18n="rockGame.scissors"
|
||
>Scissors</span
|
||
>
|
||
</button>
|
||
</div>
|
||
<div
|
||
class="rps-stake-title"
|
||
data-i18n="rockGame.winnerReceives"
|
||
>
|
||
The winner receives
|
||
</div>
|
||
<div class="rps-stake-row">
|
||
<button
|
||
class="rps-stake"
|
||
type="button"
|
||
data-action="stake"
|
||
data-stake="8000"
|
||
aria-pressed="false"
|
||
>
|
||
<img
|
||
class="rps-stake-bg"
|
||
src="assets/figma/stake-outline.svg"
|
||
alt=""
|
||
/>
|
||
<span
|
||
class="rps-stake-selected-slice rps-stake-selected-left"
|
||
></span>
|
||
<span
|
||
class="rps-stake-selected-slice rps-stake-selected-right"
|
||
></span>
|
||
<img
|
||
class="rps-stake-coin"
|
||
src="assets/figma/coin.png"
|
||
alt=""
|
||
/>
|
||
<span class="rps-stake-value">8,000</span>
|
||
</button>
|
||
<button
|
||
class="rps-stake"
|
||
type="button"
|
||
data-action="stake"
|
||
data-stake="8000"
|
||
aria-pressed="true"
|
||
>
|
||
<img
|
||
class="rps-stake-bg"
|
||
src="assets/figma/stake-outline.svg"
|
||
alt=""
|
||
/>
|
||
<span
|
||
class="rps-stake-selected-slice rps-stake-selected-left"
|
||
></span>
|
||
<span
|
||
class="rps-stake-selected-slice rps-stake-selected-right"
|
||
></span>
|
||
<img
|
||
class="rps-stake-coin"
|
||
src="assets/figma/coin.png"
|
||
alt=""
|
||
/>
|
||
<span class="rps-stake-value">8,000</span>
|
||
</button>
|
||
<button
|
||
class="rps-stake"
|
||
type="button"
|
||
data-action="stake"
|
||
data-stake="8000"
|
||
aria-pressed="false"
|
||
>
|
||
<img
|
||
class="rps-stake-bg"
|
||
src="assets/figma/stake-outline.svg"
|
||
alt=""
|
||
/>
|
||
<span
|
||
class="rps-stake-selected-slice rps-stake-selected-left"
|
||
></span>
|
||
<span
|
||
class="rps-stake-selected-slice rps-stake-selected-right"
|
||
></span>
|
||
<img
|
||
class="rps-stake-coin"
|
||
src="assets/figma/coin.png"
|
||
alt=""
|
||
/>
|
||
<span class="rps-stake-value">8,000</span>
|
||
</button>
|
||
<button
|
||
class="rps-stake"
|
||
type="button"
|
||
data-action="stake"
|
||
data-stake="8000"
|
||
aria-pressed="false"
|
||
>
|
||
<img
|
||
class="rps-stake-bg"
|
||
src="assets/figma/stake-outline.svg"
|
||
alt=""
|
||
/>
|
||
<span
|
||
class="rps-stake-selected-slice rps-stake-selected-left"
|
||
></span>
|
||
<span
|
||
class="rps-stake-selected-slice rps-stake-selected-right"
|
||
></span>
|
||
<img
|
||
class="rps-stake-coin"
|
||
src="assets/figma/coin.png"
|
||
alt=""
|
||
/>
|
||
<span class="rps-stake-value">8,000</span>
|
||
</button>
|
||
</div>
|
||
<button
|
||
class="rps-start"
|
||
type="button"
|
||
data-action="start"
|
||
data-i18n="rockGame.start"
|
||
>
|
||
START
|
||
</button>
|
||
<div
|
||
class="rps-refund-note"
|
||
style="display: none"
|
||
></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="rps-screen rps-matching">
|
||
<div class="rps-duel-card is-self">
|
||
<img src="assets/figma/player-card.png" alt="" />
|
||
<div class="rps-duel-avatar">
|
||
<img
|
||
class="is-empty"
|
||
data-role="self-card-avatar"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<div class="rps-duel-name" data-role="self-card-name">
|
||
<div>NameName</div>
|
||
<div>Name...</div>
|
||
</div>
|
||
</div>
|
||
<div class="rps-duel-card is-opponent">
|
||
<img src="assets/figma/opponent-card.png" alt="" />
|
||
<div class="rps-duel-avatar">
|
||
<img
|
||
class="is-empty"
|
||
data-role="opponent-card-avatar"
|
||
alt=""
|
||
/>
|
||
<div class="rps-question">?</div>
|
||
</div>
|
||
<div
|
||
class="rps-duel-name"
|
||
data-role="opponent-card-name"
|
||
>
|
||
<div>?</div>
|
||
<div></div>
|
||
</div>
|
||
<div class="rps-duel-status">
|
||
<img src="assets/figma/search-icon.svg" alt="" />
|
||
<span data-i18n="rockGame.matching"
|
||
>Matching...</span
|
||
>
|
||
</div>
|
||
</div>
|
||
<div class="rps-search-hud">
|
||
<img src="assets/figma/search-hud.png" alt="" />
|
||
<img src="assets/figma/search-icon.svg" alt="" />
|
||
</div>
|
||
<div
|
||
class="rps-bottom-status"
|
||
data-role="bottom-status"
|
||
data-i18n="rockGame.matching"
|
||
>
|
||
Matching...
|
||
</div>
|
||
</section>
|
||
|
||
<section class="rps-screen rps-vs-screen">
|
||
<div class="rps-duel-card is-self">
|
||
<img src="assets/figma/player-card.png" alt="" />
|
||
<div class="rps-duel-avatar">
|
||
<img
|
||
class="is-empty"
|
||
data-role="self-vs-avatar"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<div class="rps-duel-name" data-role="self-vs-name">
|
||
<div>NameName</div>
|
||
<div>Name...</div>
|
||
</div>
|
||
</div>
|
||
<div class="rps-duel-card is-opponent">
|
||
<img src="assets/figma/opponent-card.png" alt="" />
|
||
<div class="rps-duel-avatar">
|
||
<img
|
||
class="is-empty"
|
||
data-role="opponent-vs-avatar"
|
||
alt=""
|
||
/>
|
||
<div class="rps-question">?</div>
|
||
</div>
|
||
<div class="rps-duel-name" data-role="opponent-vs-name">
|
||
<div>?</div>
|
||
<div></div>
|
||
</div>
|
||
<div class="rps-duel-status">
|
||
<img src="assets/figma/search-icon.svg" alt="" />
|
||
<span data-i18n="rockGame.matched">Matched</span>
|
||
</div>
|
||
</div>
|
||
<div class="game-vs-badge rps-vs-badge">
|
||
<img
|
||
class="game-layer-img game-contain"
|
||
src="assets/figma/vs.png"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<div class="rps-bottom-status" data-i18n="rockGame.matched">
|
||
Matched
|
||
</div>
|
||
</section>
|
||
|
||
<div
|
||
class="you-win-smash-layer"
|
||
data-role="you-win-smash"
|
||
aria-hidden="true"
|
||
>
|
||
<span class="you-win-smash-floor"></span>
|
||
</div>
|
||
<div
|
||
class="you-win-impact"
|
||
data-role="you-win-impact"
|
||
data-i18n="rockGame.youWin"
|
||
aria-hidden="true"
|
||
>
|
||
YOU WIN
|
||
</div>
|
||
<div
|
||
class="countdown-layer"
|
||
data-role="countdown-layer"
|
||
aria-live="polite"
|
||
aria-hidden="true"
|
||
>
|
||
<div
|
||
class="countdown-number"
|
||
data-role="countdown-number"
|
||
></div>
|
||
</div>
|
||
|
||
<section class="rps-screen rps-battle">
|
||
<div class="rps-duel-card is-self">
|
||
<img src="assets/figma/player-card.png" alt="" />
|
||
<div class="rps-duel-avatar">
|
||
<img
|
||
class="is-empty"
|
||
data-role="self-battle-avatar"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<div class="rps-duel-name" data-role="self-battle-name">
|
||
<div>NameName</div>
|
||
<div>Name...</div>
|
||
</div>
|
||
</div>
|
||
<div class="rps-duel-card is-opponent">
|
||
<img src="assets/figma/opponent-card.png" alt="" />
|
||
<div class="rps-duel-avatar">
|
||
<img
|
||
class="is-empty"
|
||
data-role="opponent-battle-avatar"
|
||
alt=""
|
||
/>
|
||
<div class="rps-question">?</div>
|
||
</div>
|
||
<div
|
||
class="rps-duel-name"
|
||
data-role="opponent-battle-name"
|
||
>
|
||
<div>NameName</div>
|
||
<div>Name...</div>
|
||
</div>
|
||
</div>
|
||
<div class="game-vs-badge rps-vs-badge">
|
||
<img
|
||
class="game-layer-img game-contain"
|
||
src="assets/figma/vs.png"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<div class="rps-showcase">
|
||
<div class="rps-hand-zone is-left">
|
||
<img
|
||
class="rps-ring"
|
||
src="assets/figma/red-ring.png"
|
||
alt=""
|
||
/>
|
||
<img
|
||
class="rps-flare"
|
||
src="assets/figma/red-flare.png"
|
||
alt=""
|
||
/>
|
||
<img
|
||
class="rps-hand"
|
||
data-role="self-battle-hand"
|
||
data-gesture="rock"
|
||
src="assets/figma/rock.png"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<div class="rps-hand-zone is-right">
|
||
<img
|
||
class="rps-ring"
|
||
src="assets/figma/blue-ring.png"
|
||
alt=""
|
||
/>
|
||
<img
|
||
class="rps-flare"
|
||
src="assets/figma/blue-flare.png"
|
||
alt=""
|
||
/>
|
||
<img
|
||
class="rps-hand"
|
||
data-role="opponent-battle-hand"
|
||
data-gesture="paper"
|
||
src="assets/figma/paper.png"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="rps-screen rps-victory">
|
||
<img
|
||
class="rps-victory-title"
|
||
src="assets/figma/victory-title.png"
|
||
alt=""
|
||
/>
|
||
<div class="rps-avatar-ring">
|
||
<img
|
||
class="rps-victory-avatar-bg"
|
||
src="assets/figma/victory-avatar-bg.png"
|
||
alt=""
|
||
/>
|
||
<img
|
||
class="rps-avatar-img is-empty"
|
||
data-role="winner-avatar"
|
||
alt=""
|
||
/>
|
||
<img
|
||
class="rps-victory-avatar-frame"
|
||
src="assets/figma/victory-avatar-frame.png"
|
||
alt=""
|
||
/>
|
||
</div>
|
||
<div class="rps-victory-name" data-role="winner-name">
|
||
NameNameName...
|
||
</div>
|
||
<div class="rps-reward-panel">
|
||
<img src="assets/figma/win-coins.png" alt="" />
|
||
<div class="rps-reward-copy">
|
||
<strong data-role="result-label">Win</strong>
|
||
<b data-role="win-amount">1,900,000</b>
|
||
<span data-i18n="rockGame.coins">Coins</span>
|
||
</div>
|
||
</div>
|
||
<div class="rps-victory-actions">
|
||
<button
|
||
class="rps-victory-button"
|
||
type="button"
|
||
data-action="rematch"
|
||
data-i18n="rockGame.rematch"
|
||
>
|
||
Re-match
|
||
</button>
|
||
<button
|
||
class="rps-victory-button is-secondary"
|
||
type="button"
|
||
data-action="play-again"
|
||
data-i18n="rockGame.playAgain"
|
||
>
|
||
Play again
|
||
</button>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="rps-message" data-role="message"></div>
|
||
</main>
|
||
</div>
|
||
|
||
<script src="../../common/api.js"></script>
|
||
<script src="../../common/params.js"></script>
|
||
<script src="../../common/jsbridge.js"></script>
|
||
<script>
|
||
window.HyAppI18nSupported = ['en', 'ar', 'es'];
|
||
</script>
|
||
<script src="../../common/i18n.js?v=rock-game-20260611"></script>
|
||
<script src="../../common/game-preloader.js?v=game-preloader-20260611"></script>
|
||
<script>
|
||
(function () {
|
||
var DESIGN_WIDTH = 375;
|
||
var DESIGN_HEIGHT = 812;
|
||
var MAX_STAGE_WIDTH = 768;
|
||
var GAME_ID = 'rock';
|
||
var GAME_CODE = 'game_rock';
|
||
var RPS_PRELOAD_ASSET_URLS = [
|
||
'assets/figma/loading-bg.jpg',
|
||
'assets/figma/back-button.png',
|
||
'assets/figma/bg-arena.jpg',
|
||
'assets/figma/blue-flare.png',
|
||
'assets/figma/blue-ring.png',
|
||
'assets/figma/coin.png',
|
||
'assets/figma/game-icon.png',
|
||
'assets/figma/gesture-card.png',
|
||
'assets/figma/gesture-selected-rock.png',
|
||
'assets/figma/help-badge.png',
|
||
'assets/figma/lobby-avatar-frame.png',
|
||
'assets/figma/lobby-panel.svg',
|
||
'assets/figma/opponent-card.png',
|
||
'assets/figma/opponent-question.png',
|
||
'assets/figma/paper.png',
|
||
'assets/figma/player-card.png',
|
||
'assets/figma/red-flare.png',
|
||
'assets/figma/red-ring.png',
|
||
'assets/figma/rock.png',
|
||
'assets/figma/scissors.png',
|
||
'assets/figma/search-hud.png',
|
||
'assets/figma/search-icon.svg',
|
||
'assets/figma/stake-outline.svg',
|
||
'assets/figma/stake-selected.png',
|
||
'assets/figma/start-button.png',
|
||
'assets/figma/victory-action-blue.png',
|
||
'assets/figma/victory-action-secondary.png',
|
||
'assets/figma/victory-action-secondary.svg',
|
||
'assets/figma/victory-avatar-bg.png',
|
||
'assets/figma/victory-avatar-frame.png',
|
||
'assets/figma/victory-coin-stack.png',
|
||
'assets/figma/victory-reward-frame.png',
|
||
'assets/figma/victory-title.png',
|
||
'assets/figma/victory-title.svg',
|
||
'assets/figma/victory.png',
|
||
'assets/figma/vs.png',
|
||
'assets/figma/win-coins.png',
|
||
];
|
||
var root = document.documentElement;
|
||
var frame = document.querySelector('.rps-frame');
|
||
var timers = [];
|
||
var pollTimer = 0;
|
||
var pageStartedAt = performance.now();
|
||
var coinCreditFrame = 0;
|
||
var coinCreditLayer = null;
|
||
var transparentPixel =
|
||
'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E';
|
||
var gestures = {
|
||
rock: {
|
||
labelKey: 'rockGame.rock',
|
||
label: 'Rock',
|
||
image: 'assets/figma/rock.png',
|
||
beats: 'scissors',
|
||
},
|
||
paper: {
|
||
labelKey: 'rockGame.paper',
|
||
label: 'Paper',
|
||
image: 'assets/figma/paper.png',
|
||
beats: 'rock',
|
||
},
|
||
scissors: {
|
||
labelKey: 'rockGame.scissors',
|
||
label: 'Scissors',
|
||
image: 'assets/figma/scissors.png',
|
||
beats: 'paper',
|
||
},
|
||
};
|
||
var state = {
|
||
preloading: true,
|
||
profile: {},
|
||
balanceCoin: 159,
|
||
selectedGesture: 'rock',
|
||
selectedStake: 8000,
|
||
opponentGesture: 'paper',
|
||
opponent: {},
|
||
config: null,
|
||
match: null,
|
||
busy: false,
|
||
roundID: 0,
|
||
serverTimeMS: 0,
|
||
};
|
||
|
||
function trackSelfGameEvent(name, extra) {
|
||
var api =
|
||
window.HyAppAPI &&
|
||
window.HyAppAPI.game &&
|
||
window.HyAppAPI.game.reportEvent;
|
||
if (typeof api !== 'function') return;
|
||
var payload = Object.assign(
|
||
{
|
||
event_name: name,
|
||
game_id: GAME_ID,
|
||
page: 'game/rock',
|
||
h5_version: 'rock-h5-20260611',
|
||
entry_source: currentRoomID() ? 'room' : 'direct',
|
||
success: true,
|
||
},
|
||
extra || {}
|
||
);
|
||
api(payload).catch(function () {});
|
||
}
|
||
|
||
function updateScale() {
|
||
// 游戏区域宽度始终吃满可用宽度,但最多放大到 768px;缩放只跟最终宽度走,避免大屏时被高度压窄后露出右侧空条。
|
||
var viewportWidth = Math.min(
|
||
window.innerWidth,
|
||
MAX_STAGE_WIDTH
|
||
);
|
||
var scale = viewportWidth / DESIGN_WIDTH;
|
||
root.style.setProperty(
|
||
'--game-scale',
|
||
String(Math.max(scale, 0.1))
|
||
);
|
||
}
|
||
|
||
function postBridge(action, payload) {
|
||
if (window.HyAppBridge && window.HyAppBridge.post) {
|
||
window.HyAppBridge.post(action, payload || {});
|
||
}
|
||
}
|
||
|
||
function schedule(callback, ms) {
|
||
var timer = window.setTimeout(function () {
|
||
timers = timers.filter(function (item) {
|
||
return item !== timer;
|
||
});
|
||
callback();
|
||
}, ms);
|
||
timers.push(timer);
|
||
return timer;
|
||
}
|
||
|
||
function clearTimers() {
|
||
clearPollTimer();
|
||
timers.forEach(function (timer) {
|
||
window.clearTimeout(timer);
|
||
});
|
||
timers = [];
|
||
}
|
||
|
||
function clearPollTimer() {
|
||
if (!pollTimer) return;
|
||
window.clearTimeout(pollTimer);
|
||
pollTimer = 0;
|
||
}
|
||
|
||
function api() {
|
||
return window.HyAppAPI || {};
|
||
}
|
||
|
||
function rpsAPI() {
|
||
return api().game && api().game.rps;
|
||
}
|
||
|
||
function role(name) {
|
||
return document.querySelector('[data-role="' + name + '"]');
|
||
}
|
||
|
||
function each(nodes, iteratee) {
|
||
Array.prototype.forEach.call(nodes, iteratee);
|
||
}
|
||
|
||
function i18nText(key, fallback) {
|
||
return window.HyAppI18n && window.HyAppI18n.t
|
||
? window.HyAppI18n.t(key, fallback)
|
||
: fallback;
|
||
}
|
||
|
||
function i18nFormat(key, fallback, values) {
|
||
var text = i18nText(key, fallback);
|
||
return text.replace(
|
||
/\{([a-zA-Z0-9_]+)\}/g,
|
||
function (match, name) {
|
||
return Object.prototype.hasOwnProperty.call(
|
||
values || {},
|
||
name
|
||
)
|
||
? values[name]
|
||
: match;
|
||
}
|
||
);
|
||
}
|
||
|
||
function formatCoin(value) {
|
||
var number = Number(value || 0);
|
||
if (!Number.isFinite(number)) number = 0;
|
||
return Math.trunc(Math.max(number, 0)).toLocaleString(
|
||
'en-US'
|
||
);
|
||
}
|
||
|
||
function formatTopCoin(value) {
|
||
var number = Number(value || 0);
|
||
if (!Number.isFinite(number)) number = 0;
|
||
number = Math.max(0, Math.trunc(number));
|
||
if (number < 1000000) return formatCoin(number);
|
||
return Math.floor(number / 1000) + 'K';
|
||
}
|
||
|
||
function currentRoomID() {
|
||
var raw = currentParams();
|
||
return raw && raw.get
|
||
? raw.get('room_id') || raw.get('roomId') || ''
|
||
: '';
|
||
}
|
||
|
||
function entryMatchID() {
|
||
var raw = currentParams();
|
||
return raw && raw.get
|
||
? raw.get('match_id') || raw.get('matchId') || ''
|
||
: '';
|
||
}
|
||
|
||
function matchRequestMode() {
|
||
var raw = currentParams();
|
||
var mode =
|
||
raw && raw.get
|
||
? String(
|
||
raw.get('rps_mode') ||
|
||
raw.get('match_mode') ||
|
||
raw.get('mode') ||
|
||
''
|
||
)
|
||
.trim()
|
||
.toLowerCase()
|
||
: '';
|
||
if (entryMatchID()) return 'join';
|
||
if (mode === 'create' || mode === 'waiting') {
|
||
return 'create';
|
||
}
|
||
return 'match';
|
||
}
|
||
|
||
function currentParams() {
|
||
var params =
|
||
(window.HyAppParams && window.HyAppParams.current) ||
|
||
{};
|
||
if (params.raw && params.raw.get) return params.raw;
|
||
var raw = new URLSearchParams(window.location.search);
|
||
var hash = window.location.hash || '';
|
||
if (hash.indexOf('?') >= 0) {
|
||
new URLSearchParams(
|
||
hash.split('?').slice(1).join('?')
|
||
).forEach(function (value, key) {
|
||
if (!raw.has(key)) raw.set(key, value);
|
||
});
|
||
}
|
||
return raw;
|
||
}
|
||
|
||
function shouldUseMockFlow() {
|
||
var params = currentParams();
|
||
return (
|
||
params.get('mock') === '1' ||
|
||
params.get('demo') === '1' ||
|
||
params.get('rps_mock') === '1'
|
||
);
|
||
}
|
||
|
||
function displayName(user) {
|
||
user = user || {};
|
||
return (
|
||
user.nickname ||
|
||
user.username ||
|
||
user.name ||
|
||
user.display_user_id ||
|
||
user.user_id ||
|
||
'NameNameName...'
|
||
);
|
||
}
|
||
|
||
function displaySubName(user) {
|
||
user = user || {};
|
||
return user.display_user_id || user.user_id || 'Name...';
|
||
}
|
||
|
||
function avatarURL(user) {
|
||
user = user || {};
|
||
var url =
|
||
user.avatar || user.avatar_url || user.avatarUrl || '';
|
||
url = String(url || '').trim();
|
||
if (!url) return '';
|
||
var normalized = url.toLowerCase();
|
||
if (
|
||
normalized.indexOf('/avatar/default') !== -1 ||
|
||
normalized.indexOf('avatar/default.') !== -1 ||
|
||
normalized.indexOf('/default-avatar') !== -1 ||
|
||
normalized.indexOf('default_avatar') !== -1
|
||
) {
|
||
return '';
|
||
}
|
||
return url;
|
||
}
|
||
|
||
function setText(node, value) {
|
||
if (node) node.textContent = value == null ? '' : value;
|
||
}
|
||
|
||
function setImage(node, url) {
|
||
if (!node) return;
|
||
var next = String(url || '').trim();
|
||
if (!next) {
|
||
node.removeAttribute('src');
|
||
node.classList.add('is-empty');
|
||
return;
|
||
}
|
||
node.onerror = function () {
|
||
node.onerror = null;
|
||
node.src = transparentPixel;
|
||
node.classList.add('is-empty');
|
||
};
|
||
node.src = next;
|
||
node.classList.remove('is-empty');
|
||
}
|
||
|
||
function setNameLines(selector, user) {
|
||
var nodes = document.querySelectorAll(selector + ' div');
|
||
if (nodes[0]) nodes[0].textContent = displayName(user);
|
||
if (nodes[1]) nodes[1].textContent = displaySubName(user);
|
||
}
|
||
|
||
function setOpponentNameLines(selector, user, hasDetails) {
|
||
var nodes = document.querySelectorAll(selector + ' div');
|
||
if (!hasDetails) {
|
||
if (nodes[0]) nodes[0].textContent = '?';
|
||
if (nodes[1]) nodes[1].textContent = '';
|
||
return;
|
||
}
|
||
if (nodes[0]) nodes[0].textContent = displayName(user);
|
||
if (nodes[1]) nodes[1].textContent = displaySubName(user);
|
||
}
|
||
|
||
function setHand(node, gesture) {
|
||
var meta = gestures[gesture] || gestures.rock;
|
||
if (!node) return;
|
||
node.src = meta.image;
|
||
node.setAttribute('data-gesture', gesture);
|
||
node.alt = i18nText(meta.labelKey, meta.label);
|
||
}
|
||
|
||
function showMessage(message) {
|
||
setText(role('message'), message || '');
|
||
if (!message) return;
|
||
schedule(function () {
|
||
setText(role('message'), '');
|
||
}, 2200);
|
||
}
|
||
|
||
function setCountdownNumber(value) {
|
||
var node = role('countdown-number');
|
||
var layer = role('countdown-layer');
|
||
var next = value == null ? '' : String(value);
|
||
if (layer) {
|
||
layer.setAttribute(
|
||
'aria-hidden',
|
||
next ? 'false' : 'true'
|
||
);
|
||
}
|
||
if (!node) return;
|
||
if (
|
||
(node.getAttribute('data-countdown-value') || '') ===
|
||
next
|
||
) {
|
||
return;
|
||
}
|
||
if (next) {
|
||
node.setAttribute('data-countdown-value', next);
|
||
} else {
|
||
node.removeAttribute('data-countdown-value');
|
||
}
|
||
node.textContent = next;
|
||
node.classList.remove('is-ticking');
|
||
if (!next) return;
|
||
// 每个数字都要重新触发弹出动画,保证 3、2、1 是三次清晰的反馈。
|
||
void node.offsetWidth;
|
||
node.classList.add('is-ticking');
|
||
}
|
||
|
||
function setSelfWinCompare(visible) {
|
||
frame.classList.remove('is-self-win-compare');
|
||
if (!visible) return;
|
||
// 自己赢时复用 dice 的“YOU WIN 从天而降”节奏;先强制提交移除状态,下一局才能再次播放。
|
||
void frame.offsetWidth;
|
||
frame.classList.add('is-self-win-compare');
|
||
}
|
||
|
||
function setScreen(nextState) {
|
||
frame.setAttribute('data-state', nextState);
|
||
}
|
||
|
||
function renderCoin(value) {
|
||
var number = Number(value || 0);
|
||
if (!Number.isFinite(number)) number = 0;
|
||
number = Math.max(0, Math.trunc(number));
|
||
state.balanceCoin = number;
|
||
var fullLabel = formatCoin(number);
|
||
var topCoin = document.querySelector('.top-coin');
|
||
if (topCoin) {
|
||
topCoin.classList.toggle(
|
||
'is-compact',
|
||
number >= 1000000
|
||
);
|
||
topCoin.setAttribute('title', fullLabel);
|
||
topCoin.setAttribute(
|
||
'aria-label',
|
||
'Recharge ' + fullLabel
|
||
);
|
||
}
|
||
setText(
|
||
document.querySelector('.top-coin span'),
|
||
formatTopCoin(number)
|
||
);
|
||
}
|
||
|
||
function cancelCoinCreditAnimation() {
|
||
if (coinCreditFrame) {
|
||
window.cancelAnimationFrame(coinCreditFrame);
|
||
coinCreditFrame = 0;
|
||
}
|
||
if (coinCreditLayer && coinCreditLayer.parentNode) {
|
||
coinCreditLayer.parentNode.removeChild(coinCreditLayer);
|
||
}
|
||
coinCreditLayer = null;
|
||
var topCoin = document.querySelector('.top-coin');
|
||
if (topCoin) topCoin.classList.remove('is-crediting');
|
||
}
|
||
|
||
function framePointFromRect(rect, anchorX, anchorY) {
|
||
var frameRect = frame.getBoundingClientRect();
|
||
var scale = frameRect.width / DESIGN_WIDTH || 1;
|
||
return {
|
||
x:
|
||
(rect.left +
|
||
rect.width * anchorX -
|
||
frameRect.left) /
|
||
scale,
|
||
y:
|
||
(rect.top + rect.height * anchorY - frameRect.top) /
|
||
scale,
|
||
};
|
||
}
|
||
|
||
function createCoinCreditLayer() {
|
||
cancelCoinCreditAnimation();
|
||
coinCreditLayer = document.createElement('div');
|
||
coinCreditLayer.className = 'coin-credit-layer';
|
||
frame.appendChild(coinCreditLayer);
|
||
return coinCreditLayer;
|
||
}
|
||
|
||
function burstCreditCoins(start, end) {
|
||
var layer = createCoinCreditLayer();
|
||
var offsets = [
|
||
[-8, 6],
|
||
[12, -4],
|
||
[3, 16],
|
||
[20, 10],
|
||
[-16, -3],
|
||
[8, -17],
|
||
[-2, -12],
|
||
[17, 22],
|
||
];
|
||
offsets.forEach(function (offset, index) {
|
||
var coin = document.createElement('img');
|
||
coin.className = 'coin-credit-flyer';
|
||
coin.src = 'assets/figma/coin.png';
|
||
coin.alt = '';
|
||
coin.draggable = false;
|
||
coin.setAttribute('aria-hidden', 'true');
|
||
coin.style.setProperty(
|
||
'--coin-credit-x',
|
||
start.x + offset[0] + 'px'
|
||
);
|
||
coin.style.setProperty(
|
||
'--coin-credit-y',
|
||
start.y + offset[1] + 'px'
|
||
);
|
||
coin.style.setProperty(
|
||
'--coin-credit-dx',
|
||
end.x - start.x - offset[0] + 'px'
|
||
);
|
||
coin.style.setProperty(
|
||
'--coin-credit-dy',
|
||
end.y - start.y - offset[1] + 'px'
|
||
);
|
||
coin.style.setProperty(
|
||
'--coin-credit-delay',
|
||
index * 64 + 'ms'
|
||
);
|
||
layer.appendChild(coin);
|
||
});
|
||
schedule(function () {
|
||
if (coinCreditLayer === layer) {
|
||
layer.remove();
|
||
coinCreditLayer = null;
|
||
}
|
||
}, 1500);
|
||
}
|
||
|
||
function animateCoinNumber(startValue, targetValue) {
|
||
var start = Math.max(
|
||
0,
|
||
Math.trunc(Number(startValue) || 0)
|
||
);
|
||
var target = Math.max(
|
||
0,
|
||
Math.trunc(Number(targetValue) || 0)
|
||
);
|
||
var startedAt = performance.now();
|
||
var duration = 1180;
|
||
function tick(now) {
|
||
var progress = Math.min(
|
||
Math.max((now - startedAt) / duration, 0),
|
||
1
|
||
);
|
||
var eased = 1 - Math.pow(1 - progress, 3);
|
||
renderCoin(start + (target - start) * eased);
|
||
if (progress < 1) {
|
||
coinCreditFrame =
|
||
window.requestAnimationFrame(tick);
|
||
return;
|
||
}
|
||
coinCreditFrame = 0;
|
||
renderCoin(target);
|
||
}
|
||
if (coinCreditFrame) {
|
||
window.cancelAnimationFrame(coinCreditFrame);
|
||
}
|
||
coinCreditFrame = window.requestAnimationFrame(tick);
|
||
}
|
||
|
||
function playVictoryCoinCredit(startValue, targetValue) {
|
||
var topCoin = document.querySelector('.top-coin');
|
||
var topCoinIcon = topCoin && topCoin.querySelector('img');
|
||
if (!topCoinIcon || targetValue <= startValue) {
|
||
renderCoin(targetValue);
|
||
return false;
|
||
}
|
||
var start = { x: 114, y: 566 };
|
||
var end = framePointFromRect(
|
||
topCoinIcon.getBoundingClientRect(),
|
||
0.5,
|
||
0.5
|
||
);
|
||
burstCreditCoins(start, end);
|
||
animateCoinNumber(startValue, targetValue);
|
||
topCoin.classList.remove('is-crediting');
|
||
void topCoin.offsetWidth;
|
||
topCoin.classList.add('is-crediting');
|
||
schedule(function () {
|
||
topCoin.classList.remove('is-crediting');
|
||
}, 780);
|
||
return true;
|
||
}
|
||
|
||
function renderProfile() {
|
||
var profile = state.profile || {};
|
||
var avatar = avatarURL(profile);
|
||
setText(role('self-name'), displayName(profile));
|
||
setNameLines('[data-role="self-card-name"]', profile);
|
||
setNameLines('[data-role="self-vs-name"]', profile);
|
||
setNameLines('[data-role="self-battle-name"]', profile);
|
||
setImage(role('self-avatar'), avatar);
|
||
setImage(role('self-card-avatar'), avatar);
|
||
setImage(role('self-vs-avatar'), avatar);
|
||
setImage(role('self-battle-avatar'), avatar);
|
||
}
|
||
|
||
function renderSelection() {
|
||
each(
|
||
document.querySelectorAll('[data-action="gesture"]'),
|
||
function (button) {
|
||
var selected =
|
||
button.getAttribute('data-gesture') ===
|
||
state.selectedGesture;
|
||
button.setAttribute(
|
||
'aria-pressed',
|
||
selected ? 'true' : 'false'
|
||
);
|
||
}
|
||
);
|
||
var matchedStake = false;
|
||
each(
|
||
document.querySelectorAll('[data-action="stake"]'),
|
||
function (button) {
|
||
var selected =
|
||
!matchedStake &&
|
||
Number(
|
||
button.getAttribute('data-stake') || 0
|
||
) === state.selectedStake;
|
||
if (selected) matchedStake = true;
|
||
button.setAttribute(
|
||
'aria-pressed',
|
||
selected ? 'true' : 'false'
|
||
);
|
||
}
|
||
);
|
||
setHand(role('self-lobby-hand'), state.selectedGesture);
|
||
setHand(role('self-battle-hand'), state.selectedGesture);
|
||
setHand(
|
||
role('preview-opponent-hand'),
|
||
counterGestureFor(state.selectedGesture)
|
||
);
|
||
}
|
||
|
||
function hasOpponentDetails(user) {
|
||
user = user || {};
|
||
return Boolean(
|
||
avatarURL(user) ||
|
||
user.nickname ||
|
||
user.username ||
|
||
user.name ||
|
||
user.display_user_id ||
|
||
user.user_id
|
||
);
|
||
}
|
||
|
||
function renderOpponent() {
|
||
var opponent = state.opponent || {};
|
||
var avatar = avatarURL(opponent);
|
||
var hasDetails = hasOpponentDetails(opponent);
|
||
frame.classList.toggle('has-opponent', hasDetails);
|
||
// 匹配成功后同一份对手资料要同步到等待卡、VS 卡和对战卡;否则某个屏幕仍停留在搜索态,会出现头像未替换或搜索图标残留。
|
||
setOpponentNameLines(
|
||
'[data-role="opponent-card-name"]',
|
||
opponent,
|
||
hasDetails
|
||
);
|
||
setOpponentNameLines(
|
||
'[data-role="opponent-vs-name"]',
|
||
opponent,
|
||
hasDetails
|
||
);
|
||
setOpponentNameLines(
|
||
'[data-role="opponent-battle-name"]',
|
||
opponent,
|
||
hasDetails
|
||
);
|
||
setImage(role('opponent-card-avatar'), avatar);
|
||
setImage(role('opponent-vs-avatar'), avatar);
|
||
setImage(role('opponent-battle-avatar'), avatar);
|
||
each(
|
||
document.querySelectorAll('.rps-duel-card.is-opponent'),
|
||
function (card) {
|
||
// 有对手资料就隐藏 Matching/Search 状态;只有头像真实可用时才隐藏问号兜底,避免无头像用户显示成空圆。
|
||
card.classList.toggle('has-opponent', hasDetails);
|
||
card.classList.toggle(
|
||
'has-opponent-avatar',
|
||
Boolean(avatar)
|
||
);
|
||
}
|
||
);
|
||
setHand(
|
||
role('opponent-battle-hand'),
|
||
state.opponentGesture
|
||
);
|
||
}
|
||
|
||
function renderVictory(result, match) {
|
||
var self = findSelfParticipant(match);
|
||
var winner = findWinnerParticipant(match);
|
||
var winnerIsSelf =
|
||
result === 'draw' ||
|
||
!winner ||
|
||
sameParticipant(winner, self);
|
||
var winnerUser = winnerIsSelf
|
||
? state.profile
|
||
: Object.assign(
|
||
{},
|
||
participantUser(winner),
|
||
state.opponent
|
||
);
|
||
var amount = payoutFromMatch(match, result);
|
||
var label =
|
||
result === 'draw'
|
||
? i18nText('rockGame.draw', 'Draw')
|
||
: result === 'lose'
|
||
? i18nText('rockGame.lose', 'Lose')
|
||
: i18nText('rockGame.win', 'Win');
|
||
setImage(role('winner-avatar'), avatarURL(winnerUser));
|
||
setText(role('winner-name'), displayName(winnerUser));
|
||
setText(role('result-label'), label);
|
||
setText(role('win-amount'), formatCoin(amount));
|
||
if (self && self.balance_after > 0) {
|
||
var balanceAfter = Math.trunc(
|
||
Number(self.balance_after) || 0
|
||
);
|
||
if (
|
||
result === 'win' &&
|
||
winnerIsSelf &&
|
||
balanceAfter > state.balanceCoin
|
||
) {
|
||
return playVictoryCoinCredit(
|
||
state.balanceCoin,
|
||
balanceAfter
|
||
);
|
||
}
|
||
renderCoin(balanceAfter);
|
||
} else if (result === 'win' && !match) {
|
||
return playVictoryCoinCredit(
|
||
state.balanceCoin,
|
||
state.balanceCoin + state.selectedStake
|
||
);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function losingGestureFor(gesture) {
|
||
return (
|
||
(gestures[gesture] && gestures[gesture].beats) ||
|
||
'scissors'
|
||
);
|
||
}
|
||
|
||
function counterGestureFor(gesture) {
|
||
if (gesture === 'rock') return 'paper';
|
||
if (gesture === 'paper') return 'scissors';
|
||
return 'rock';
|
||
}
|
||
|
||
function resultFor(selfGesture, opponentGesture) {
|
||
if (selfGesture === opponentGesture) return 'draw';
|
||
return losingGestureFor(selfGesture) === opponentGesture
|
||
? 'win'
|
||
: 'lose';
|
||
}
|
||
|
||
function chooseOpponentGesture() {
|
||
return losingGestureFor(state.selectedGesture);
|
||
}
|
||
|
||
function matchFromData(data) {
|
||
return (
|
||
(data && (data.match || data.rps_match)) || data || null
|
||
);
|
||
}
|
||
|
||
function participantUser(participant) {
|
||
participant = participant || {};
|
||
return Object.assign(
|
||
{},
|
||
participant.user || {},
|
||
participant.profile || {}
|
||
);
|
||
}
|
||
|
||
function readGesture(participant) {
|
||
participant = participant || {};
|
||
var gesture = String(
|
||
participant.gesture ||
|
||
participant.rps_gesture ||
|
||
participant.choice ||
|
||
participant.selected_gesture ||
|
||
''
|
||
)
|
||
.trim()
|
||
.toLowerCase();
|
||
return gestures[gesture] ? gesture : '';
|
||
}
|
||
|
||
function readResult(participant) {
|
||
participant = participant || {};
|
||
return String(participant.result || '')
|
||
.trim()
|
||
.toLowerCase();
|
||
}
|
||
|
||
function selfUserID() {
|
||
var profile = state.profile || {};
|
||
return String(
|
||
profile.user_id ||
|
||
profile.userId ||
|
||
profile.id ||
|
||
profile.display_user_id ||
|
||
''
|
||
);
|
||
}
|
||
|
||
function sameParticipant(left, right) {
|
||
if (!left || !right) return false;
|
||
var leftID = String(
|
||
left.user_id ||
|
||
left.userId ||
|
||
left.display_user_id ||
|
||
''
|
||
);
|
||
var rightID = String(
|
||
right.user_id ||
|
||
right.userId ||
|
||
right.display_user_id ||
|
||
''
|
||
);
|
||
if (leftID && rightID) return leftID === rightID;
|
||
return left === right;
|
||
}
|
||
|
||
function findSelfParticipant(match) {
|
||
var participants = (match && match.participants) || [];
|
||
var id = selfUserID();
|
||
for (var i = 0; i < participants.length; i += 1) {
|
||
var candidateID = String(
|
||
participants[i].user_id ||
|
||
participants[i].userId ||
|
||
participants[i].display_user_id ||
|
||
''
|
||
);
|
||
if (id && candidateID === id) return participants[i];
|
||
}
|
||
return participants[0] || null;
|
||
}
|
||
|
||
function findOpponentParticipant(match, self) {
|
||
var participants = (match && match.participants) || [];
|
||
for (var i = 0; i < participants.length; i += 1) {
|
||
if (!sameParticipant(participants[i], self)) {
|
||
return participants[i];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function findWinnerParticipant(match) {
|
||
var participants = (match && match.participants) || [];
|
||
var winnerID = String(
|
||
(match &&
|
||
(match.winner_user_id || match.winnerUserId)) ||
|
||
''
|
||
);
|
||
for (var i = 0; i < participants.length; i += 1) {
|
||
if (
|
||
winnerID &&
|
||
String(participants[i].user_id || '') === winnerID
|
||
) {
|
||
return participants[i];
|
||
}
|
||
if (readResult(participants[i]) === 'win') {
|
||
return participants[i];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function isWaitingMatch(match) {
|
||
var status = String(
|
||
(match && match.status) || ''
|
||
).toLowerCase();
|
||
var phase = String(
|
||
(match && match.phase) || ''
|
||
).toLowerCase();
|
||
return (
|
||
status === 'waiting' ||
|
||
status === 'pending' ||
|
||
phase === 'waiting' ||
|
||
phase === 'matching'
|
||
);
|
||
}
|
||
|
||
function isSettledMatch(match) {
|
||
var status = String(
|
||
(match && match.status) || ''
|
||
).toLowerCase();
|
||
var phase = String(
|
||
(match && match.phase) || ''
|
||
).toLowerCase();
|
||
return (
|
||
status === 'settled' ||
|
||
status === 'finished' ||
|
||
phase === 'settled' ||
|
||
phase === 'finished'
|
||
);
|
||
}
|
||
|
||
function renderMatch(match) {
|
||
if (!match) return;
|
||
var self = findSelfParticipant(match);
|
||
var opponent = findOpponentParticipant(match, self);
|
||
if (match.stake_coin > 0) {
|
||
state.selectedStake = Number(match.stake_coin);
|
||
}
|
||
if (readGesture(self)) {
|
||
state.selectedGesture = readGesture(self);
|
||
}
|
||
if (opponent) {
|
||
state.opponent = Object.assign(
|
||
{},
|
||
participantUser(opponent),
|
||
{
|
||
user_id: opponent.user_id,
|
||
display_user_id:
|
||
participantUser(opponent).display_user_id ||
|
||
opponent.display_user_id ||
|
||
opponent.user_id,
|
||
}
|
||
);
|
||
if (readGesture(opponent)) {
|
||
state.opponentGesture = readGesture(opponent);
|
||
}
|
||
} else {
|
||
// 等待匹配时必须清空上一局对手资料,否则搜索页会复用旧头像并提前隐藏搜索状态。
|
||
state.opponent = {};
|
||
}
|
||
renderSelection();
|
||
renderOpponent();
|
||
}
|
||
|
||
function resultFromMatch(match) {
|
||
var self = findSelfParticipant(match);
|
||
var explicit = readResult(self);
|
||
if (
|
||
explicit === 'win' ||
|
||
explicit === 'lose' ||
|
||
explicit === 'draw'
|
||
) {
|
||
return explicit;
|
||
}
|
||
var opponent = findOpponentParticipant(match, self);
|
||
var selfGesture =
|
||
readGesture(self) || state.selectedGesture;
|
||
var opponentGesture =
|
||
readGesture(opponent) || state.opponentGesture;
|
||
return resultFor(selfGesture, opponentGesture);
|
||
}
|
||
|
||
function numberFromFields(source, fields) {
|
||
if (!source) return null;
|
||
for (var i = 0; i < fields.length; i += 1) {
|
||
if (
|
||
!Object.prototype.hasOwnProperty.call(
|
||
source,
|
||
fields[i]
|
||
)
|
||
) {
|
||
continue;
|
||
}
|
||
var raw = source[fields[i]];
|
||
if (raw === null || raw === undefined || raw === '') {
|
||
continue;
|
||
}
|
||
var value = Number(raw);
|
||
if (Number.isFinite(value)) return value;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function totalFeeBPSForMatch(match) {
|
||
var config = state.config || {};
|
||
var feeBPS = numberFromFields(match, ['fee_bps', 'feeBps']);
|
||
var poolBPS = numberFromFields(match, [
|
||
'pool_bps',
|
||
'poolBps',
|
||
]);
|
||
if (feeBPS === null) {
|
||
feeBPS = numberFromFields(config, [
|
||
'fee_bps',
|
||
'feeBps',
|
||
]);
|
||
}
|
||
if (poolBPS === null) {
|
||
poolBPS = numberFromFields(config, [
|
||
'pool_bps',
|
||
'poolBps',
|
||
]);
|
||
}
|
||
if (feeBPS === null && poolBPS === null) return null;
|
||
return Math.max(
|
||
0,
|
||
Math.min(10000, (feeBPS || 0) + (poolBPS || 0))
|
||
);
|
||
}
|
||
|
||
function estimatedPayoutFromFee(match) {
|
||
var stakeCoin =
|
||
numberFromFields(match, ['stake_coin', 'stakeCoin']) ||
|
||
state.selectedStake ||
|
||
0;
|
||
var totalFeeBPS = totalFeeBPSForMatch(match);
|
||
if (!stakeCoin || totalFeeBPS === null) return 0;
|
||
return Math.max(
|
||
0,
|
||
Math.trunc(
|
||
(stakeCoin * 2 * (10000 - totalFeeBPS)) / 10000
|
||
)
|
||
);
|
||
}
|
||
|
||
function payoutFromMatch(match, result) {
|
||
var self = findSelfParticipant(match);
|
||
var payoutCoin = numberFromFields(self, [
|
||
'payout_coin',
|
||
'payoutCoin',
|
||
]);
|
||
if (payoutCoin !== null) {
|
||
return Math.max(0, Math.trunc(payoutCoin));
|
||
}
|
||
if (result !== 'win') return 0;
|
||
// 真实局缺少 payout_coin 时不能再展示未扣点的 2 倍押注。
|
||
// 兜底只能按本局或配置里的 fee_bps + pool_bps 估算。
|
||
if (match) return estimatedPayoutFromFee(match);
|
||
return state.selectedStake * 2;
|
||
}
|
||
|
||
function enabledStakeOptions(config) {
|
||
var options = (config && config.stake_options) || [];
|
||
return options
|
||
.filter(function (item) {
|
||
return (
|
||
item &&
|
||
item.enabled !== false &&
|
||
Number(item.stake_coin || 0) > 0
|
||
);
|
||
})
|
||
.sort(function (left, right) {
|
||
return (
|
||
Number(left.sort_order || 0) -
|
||
Number(right.sort_order || 0) ||
|
||
Number(left.stake_coin || 0) -
|
||
Number(right.stake_coin || 0)
|
||
);
|
||
});
|
||
}
|
||
|
||
function renderStakeOptions(config) {
|
||
var options = enabledStakeOptions(config);
|
||
var buttons = document.querySelectorAll(
|
||
'[data-action="stake"]'
|
||
);
|
||
renderFeeNote(config);
|
||
if (!options.length) {
|
||
renderSelection();
|
||
return;
|
||
}
|
||
if (!state.selectedStake) {
|
||
state.selectedStake = Number(
|
||
options[0].stake_coin || 0
|
||
);
|
||
}
|
||
var matchedSelected = false;
|
||
each(buttons, function (button, index) {
|
||
var option = options[index];
|
||
if (!option) {
|
||
button.style.display = 'none';
|
||
return;
|
||
}
|
||
button.style.display = '';
|
||
button.setAttribute('data-stake', option.stake_coin);
|
||
button.setAttribute(
|
||
'aria-label',
|
||
formatCoin(option.stake_coin)
|
||
);
|
||
setText(
|
||
button.querySelector('.rps-stake-value'),
|
||
formatCoin(option.stake_coin)
|
||
);
|
||
var selected =
|
||
!matchedSelected &&
|
||
Number(option.stake_coin) === state.selectedStake;
|
||
if (selected) matchedSelected = true;
|
||
button.setAttribute(
|
||
'aria-pressed',
|
||
selected ? 'true' : 'false'
|
||
);
|
||
});
|
||
}
|
||
|
||
function renderFeeNote(config) {
|
||
var note = document.querySelector('.rps-refund-note');
|
||
if (!note) return;
|
||
var feeBPS = numberFromFields(config, [
|
||
'fee_bps',
|
||
'feeBps',
|
||
]);
|
||
var poolBPS = numberFromFields(config, [
|
||
'pool_bps',
|
||
'poolBps',
|
||
]);
|
||
// 费率必须来自后端配置;没有明确字段时隐藏,避免把历史兜底 5% 当成真实规则展示。
|
||
if (feeBPS === null && poolBPS === null) {
|
||
setText(note, '');
|
||
note.style.display = 'none';
|
||
return;
|
||
}
|
||
var totalFee =
|
||
Math.max(
|
||
0,
|
||
Math.min(10000, (feeBPS || 0) + (poolBPS || 0))
|
||
) / 100;
|
||
setText(
|
||
note,
|
||
i18nFormat('rockGame.configFeeNote', 'Tickets {fee}%', {
|
||
fee: totalFee,
|
||
})
|
||
);
|
||
note.style.display = '';
|
||
}
|
||
|
||
function pollMatch(matchID) {
|
||
clearPollTimer();
|
||
pollTimer = window.setTimeout(function () {
|
||
pollTimer = 0;
|
||
if (!state.busy || !matchID || !rpsAPI()) return;
|
||
rpsAPI()
|
||
.getMatch(matchID)
|
||
.then(handleMatchData)
|
||
.catch(function (error) {
|
||
showMessage(error.message);
|
||
pollMatch(matchID);
|
||
});
|
||
}, 1000);
|
||
}
|
||
|
||
function enterWaiting(match) {
|
||
state.match = match;
|
||
state.busy = true;
|
||
renderMatch(match);
|
||
setScreen('searching');
|
||
pollMatch(match.match_id || match.matchId);
|
||
}
|
||
|
||
function phaseDeadlineLeft(match) {
|
||
var deadline = Number(
|
||
(match && match.phase_deadline_ms) ||
|
||
(match && match.phaseDeadlineMs) ||
|
||
0
|
||
);
|
||
var serverTime = state.serverTimeMS || Date.now();
|
||
if (!deadline) return 3000;
|
||
return Math.max(deadline - serverTime, 0);
|
||
}
|
||
|
||
function runCountdown(roundID, match) {
|
||
var duration = Math.min(phaseDeadlineLeft(match), 3000);
|
||
var endAt = Date.now() + Math.max(duration, 0);
|
||
frame.classList.add('is-countdown');
|
||
function tick() {
|
||
if (roundID !== state.roundID) return;
|
||
var left = Math.max(endAt - Date.now(), 0);
|
||
if (left <= 0) {
|
||
frame.classList.remove('is-countdown');
|
||
setCountdownNumber('');
|
||
if (match && !isSettledMatch(match)) {
|
||
rollAndAnimate(roundID, match);
|
||
return;
|
||
}
|
||
showBattle(roundID, match || null);
|
||
return;
|
||
}
|
||
setCountdownNumber(Math.ceil(left / 1000));
|
||
schedule(tick, 160);
|
||
}
|
||
tick();
|
||
}
|
||
|
||
function beginRound(match) {
|
||
clearPollTimer();
|
||
state.match = match;
|
||
state.busy = true;
|
||
state.roundID += 1;
|
||
var roundID = state.roundID;
|
||
renderMatch(match);
|
||
showVS(roundID, match);
|
||
}
|
||
|
||
function rollAndAnimate(roundID, match) {
|
||
var matchID = match && (match.match_id || match.matchId);
|
||
if (!rpsAPI() || !matchID) {
|
||
showMessage(
|
||
i18nText(
|
||
'rockGame.apiUnavailable',
|
||
'RPS API unavailable'
|
||
)
|
||
);
|
||
resetToLobby();
|
||
return;
|
||
}
|
||
trackSelfGameEvent('reveal_enter', {
|
||
match_id: matchID,
|
||
gesture: state.selectedGesture,
|
||
});
|
||
var rollStartedAt = performance.now();
|
||
rpsAPI()
|
||
.roll(matchID, {
|
||
game_id: GAME_ID,
|
||
gesture: state.selectedGesture,
|
||
})
|
||
.then(function (data) {
|
||
trackSelfGameEvent('roll_api', {
|
||
match_id: matchID,
|
||
gesture: state.selectedGesture,
|
||
duration_ms: Math.round(
|
||
performance.now() - rollStartedAt
|
||
),
|
||
});
|
||
var nextMatch = matchFromData(data);
|
||
if (roundID !== state.roundID) return;
|
||
state.match = nextMatch || match;
|
||
renderMatch(state.match);
|
||
showBattle(roundID, state.match);
|
||
})
|
||
.catch(function (error) {
|
||
trackSelfGameEvent('roll_api', {
|
||
match_id: matchID,
|
||
gesture: state.selectedGesture,
|
||
duration_ms: Math.round(
|
||
performance.now() - rollStartedAt
|
||
),
|
||
success: false,
|
||
error_code: error && error.message,
|
||
});
|
||
if (isInsufficientBalanceError(error)) {
|
||
resetToLobby();
|
||
handleInsufficientBalance(error);
|
||
return;
|
||
}
|
||
showMessage(error.message);
|
||
resetToLobby();
|
||
});
|
||
}
|
||
|
||
function handleMatchData(data) {
|
||
var match = matchFromData(data);
|
||
if (!match || !(match.match_id || match.matchId)) return;
|
||
state.serverTimeMS =
|
||
(data && data.server_time_ms) || Date.now();
|
||
state.match = match;
|
||
renderMatch(match);
|
||
if (isSettledMatch(match)) {
|
||
beginRound(match);
|
||
return;
|
||
}
|
||
if (isWaitingMatch(match)) {
|
||
enterWaiting(match);
|
||
return;
|
||
}
|
||
beginRound(match);
|
||
}
|
||
|
||
function startMatch() {
|
||
if (state.preloading) return;
|
||
if (state.busy) return;
|
||
if (shouldUseMockFlow()) {
|
||
startMockMatch();
|
||
return;
|
||
}
|
||
if (!rpsAPI()) {
|
||
showMessage(
|
||
i18nText(
|
||
'rockGame.apiUnavailable',
|
||
'RPS API unavailable'
|
||
)
|
||
);
|
||
return;
|
||
}
|
||
clearTimers();
|
||
state.busy = true;
|
||
state.roundID += 1;
|
||
state.match = null;
|
||
state.opponent = {};
|
||
renderOpponent();
|
||
setScreen('searching');
|
||
postBridge('gameRPSMatchRequested', {
|
||
game_code: GAME_CODE,
|
||
game_id: GAME_ID,
|
||
room_id: currentRoomID(),
|
||
match_id: entryMatchID(),
|
||
mode: matchRequestMode(),
|
||
stake_coin: state.selectedStake,
|
||
gesture: state.selectedGesture,
|
||
});
|
||
trackSelfGameEvent('match_click', {
|
||
stake_coin: state.selectedStake,
|
||
gesture: state.selectedGesture,
|
||
mode: matchRequestMode(),
|
||
});
|
||
var payload = {
|
||
game_id: GAME_ID,
|
||
room_id: currentRoomID(),
|
||
stake_coin: state.selectedStake,
|
||
gesture: state.selectedGesture,
|
||
};
|
||
var mode = matchRequestMode();
|
||
var request;
|
||
// H5 默认走快速匹配;分享或指定局入口带 match_id 时只加入该局,后台仍会校验 rock 边界和预选手势。
|
||
if (mode === 'join') {
|
||
request = rpsAPI().join(entryMatchID(), {
|
||
game_id: GAME_ID,
|
||
gesture: state.selectedGesture,
|
||
});
|
||
} else if (mode === 'create') {
|
||
request = rpsAPI().create(payload);
|
||
} else {
|
||
request = rpsAPI().match(payload);
|
||
}
|
||
var matchStartedAt = performance.now();
|
||
request
|
||
.then(function (data) {
|
||
var match = matchFromData(data);
|
||
trackSelfGameEvent('match_success', {
|
||
match_id:
|
||
match && (match.match_id || match.matchId),
|
||
stake_coin: state.selectedStake,
|
||
gesture: state.selectedGesture,
|
||
duration_ms: Math.round(
|
||
performance.now() - matchStartedAt
|
||
),
|
||
});
|
||
handleMatchData(data);
|
||
})
|
||
.catch(function (error) {
|
||
trackSelfGameEvent('match_api', {
|
||
stake_coin: state.selectedStake,
|
||
gesture: state.selectedGesture,
|
||
duration_ms: Math.round(
|
||
performance.now() - matchStartedAt
|
||
),
|
||
success: false,
|
||
error_code: error && error.message,
|
||
});
|
||
state.busy = false;
|
||
if (isInsufficientBalanceError(error)) {
|
||
setScreen('lobby');
|
||
handleInsufficientBalance(error);
|
||
return;
|
||
}
|
||
showMessage(error.message);
|
||
setScreen('lobby');
|
||
});
|
||
}
|
||
|
||
function startMockMatch() {
|
||
clearTimers();
|
||
state.busy = true;
|
||
state.roundID += 1;
|
||
var roundID = state.roundID;
|
||
state.opponent = {};
|
||
state.opponentGesture = chooseOpponentGesture();
|
||
state.opponent = {
|
||
nickname: 'NameName',
|
||
display_user_id: 'Name...',
|
||
avatar: 'assets/figma/game-icon.png',
|
||
};
|
||
renderOpponent();
|
||
setScreen('searching');
|
||
postBridge('gameRPSMatchRequested', {
|
||
game_code: GAME_CODE,
|
||
game_id: GAME_ID,
|
||
room_id: currentRoomID(),
|
||
stake_coin: state.selectedStake,
|
||
gesture: state.selectedGesture,
|
||
mock: true,
|
||
});
|
||
schedule(function () {
|
||
if (roundID !== state.roundID) return;
|
||
showVS(roundID, null);
|
||
}, 1200);
|
||
}
|
||
|
||
function showVS(roundID, match) {
|
||
renderOpponent();
|
||
frame.classList.remove('is-vs-impact', 'is-screen-impact');
|
||
setScreen('vs');
|
||
void frame.offsetWidth;
|
||
frame.classList.add('is-vs-impact', 'is-screen-impact');
|
||
postBridge('gameRPSMatched', {
|
||
game_code: GAME_CODE,
|
||
game_id: GAME_ID,
|
||
room_id: currentRoomID(),
|
||
});
|
||
schedule(function () {
|
||
frame.classList.remove(
|
||
'is-vs-impact',
|
||
'is-screen-impact'
|
||
);
|
||
if (roundID !== state.roundID) return;
|
||
runCountdown(roundID, match || null);
|
||
}, 1050);
|
||
}
|
||
|
||
function showBattle(roundID, match) {
|
||
if (match) renderMatch(match);
|
||
else {
|
||
renderSelection();
|
||
renderOpponent();
|
||
}
|
||
var result = match
|
||
? resultFromMatch(match)
|
||
: resultFor(
|
||
state.selectedGesture,
|
||
state.opponentGesture
|
||
);
|
||
setScreen('battle');
|
||
setSelfWinCompare(result === 'win');
|
||
postBridge('gameRPSRevealing', {
|
||
game_code: GAME_CODE,
|
||
game_id: GAME_ID,
|
||
self_gesture: state.selectedGesture,
|
||
opponent_gesture: state.opponentGesture,
|
||
});
|
||
schedule(function () {
|
||
if (roundID !== state.roundID) return;
|
||
finishRound(result, match || null);
|
||
}, 1800);
|
||
}
|
||
|
||
function finishRound(result, match) {
|
||
state.busy = false;
|
||
if (match) renderMatch(match);
|
||
frame.classList.remove(
|
||
'is-countdown',
|
||
'is-self-win-compare'
|
||
);
|
||
setCountdownNumber('');
|
||
setScreen('victory');
|
||
renderVictory(result, match);
|
||
trackSelfGameEvent('settlement_show', {
|
||
match_id: match && (match.match_id || match.matchId),
|
||
result: result,
|
||
gesture: state.selectedGesture,
|
||
stake_coin: state.selectedStake,
|
||
});
|
||
postBridge('gameRPSSettled', {
|
||
game_code: GAME_CODE,
|
||
game_id: GAME_ID,
|
||
match_id: match && (match.match_id || match.matchId),
|
||
result: result,
|
||
self_gesture: state.selectedGesture,
|
||
opponent_gesture: state.opponentGesture,
|
||
stake_coin: state.selectedStake,
|
||
});
|
||
}
|
||
|
||
function resetToLobby() {
|
||
clearTimers();
|
||
state.busy = false;
|
||
state.roundID += 1;
|
||
state.match = null;
|
||
state.opponent = {};
|
||
cancelCoinCreditAnimation();
|
||
frame.classList.remove(
|
||
'is-vs-impact',
|
||
'is-screen-impact',
|
||
'is-countdown',
|
||
'is-self-win-compare'
|
||
);
|
||
setCountdownNumber('');
|
||
setScreen('lobby');
|
||
renderSelection();
|
||
renderProfile();
|
||
renderOpponent();
|
||
}
|
||
|
||
function cancelMatch() {
|
||
var match = state.match;
|
||
var matchID = match && (match.match_id || match.matchId);
|
||
if (!rpsAPI() || !matchID || isSettledMatch(match)) {
|
||
return Promise.resolve();
|
||
}
|
||
return rpsAPI()
|
||
.cancel(matchID)
|
||
.then(function () {
|
||
trackSelfGameEvent('match_cancel', {
|
||
match_id: matchID,
|
||
});
|
||
})
|
||
.catch(function () {
|
||
trackSelfGameEvent('cancel_api', {
|
||
match_id: matchID,
|
||
success: false,
|
||
error_code: 'cancel_failed',
|
||
});
|
||
});
|
||
}
|
||
|
||
function openRecharge() {
|
||
var payload = {
|
||
source: 'rock',
|
||
game_code: GAME_CODE,
|
||
game_id: GAME_ID,
|
||
};
|
||
if (
|
||
window.HyAppBridge &&
|
||
typeof window.HyAppBridge.recharge === 'function'
|
||
) {
|
||
window.HyAppBridge.recharge(payload);
|
||
return;
|
||
}
|
||
postBridge('gameRecharge', payload);
|
||
postBridge('recharge', payload);
|
||
}
|
||
|
||
function isInsufficientBalanceError(error) {
|
||
var code = String(
|
||
(error && error.code) || ''
|
||
).toUpperCase();
|
||
var message = String(
|
||
(error && error.message) || ''
|
||
).toLowerCase();
|
||
return (
|
||
code === 'INSUFFICIENT_BALANCE' ||
|
||
message === 'insufficient balance'
|
||
);
|
||
}
|
||
|
||
function handleInsufficientBalance() {
|
||
// 余额不足是需要用户立即补币的业务错误:先给固定英文提示,再复用现有 Flutter 充值桥接拉起支付页。
|
||
showMessage('insufficient balance');
|
||
openRecharge();
|
||
}
|
||
|
||
function setLoadingProgress(progress) {
|
||
window.HyAppGamePreloader.setProgress({
|
||
root: root,
|
||
progress: progress,
|
||
});
|
||
}
|
||
|
||
function completeLoadingScreen() {
|
||
state.preloading = false;
|
||
window.HyAppGamePreloader.complete({
|
||
root: root,
|
||
frame: frame,
|
||
loadingScreen: role('loading-screen'),
|
||
progressNode: role('loading-progress'),
|
||
});
|
||
}
|
||
|
||
function collectRPSPreloadURLs() {
|
||
return window.HyAppGamePreloader.collectResourceURLs(
|
||
RPS_PRELOAD_ASSET_URLS
|
||
);
|
||
}
|
||
|
||
function preloadResourceOnce(url) {
|
||
return window.HyAppGamePreloader.preloadResource(url);
|
||
}
|
||
|
||
function preloadProfileImages() {
|
||
var urls = [];
|
||
var seen = {};
|
||
window.HyAppGamePreloader.addURL(
|
||
urls,
|
||
seen,
|
||
avatarURL(state.profile)
|
||
);
|
||
if (!urls.length) return Promise.resolve();
|
||
return Promise.all(
|
||
urls.map(function (url) {
|
||
return window.HyAppGamePreloader.preloadImage(url);
|
||
})
|
||
);
|
||
}
|
||
|
||
function runPreloadTasks(tasks) {
|
||
return window.HyAppGamePreloader.runTasks(
|
||
tasks,
|
||
setLoadingProgress
|
||
);
|
||
}
|
||
|
||
function loadProfile() {
|
||
if (shouldUseMockFlow()) {
|
||
renderProfile();
|
||
return Promise.resolve();
|
||
}
|
||
if (!api().user || !api().user.me) {
|
||
renderProfile();
|
||
return Promise.resolve();
|
||
}
|
||
return api()
|
||
.user.me()
|
||
.then(function (profile) {
|
||
state.profile = profile || {};
|
||
renderProfile();
|
||
})
|
||
.catch(function () {
|
||
renderProfile();
|
||
});
|
||
}
|
||
|
||
function loadBalance() {
|
||
if (shouldUseMockFlow()) {
|
||
renderCoin(state.balanceCoin);
|
||
return Promise.resolve();
|
||
}
|
||
if (!api().wallet || !api().wallet.balances) {
|
||
renderCoin(state.balanceCoin);
|
||
return Promise.resolve();
|
||
}
|
||
return api()
|
||
.wallet.balances('COIN')
|
||
.then(function (data) {
|
||
var balances = (data && data.balances) || [];
|
||
for (var i = 0; i < balances.length; i += 1) {
|
||
if (balances[i].asset_type === 'COIN') {
|
||
renderCoin(
|
||
balances[i].available_amount || 0
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
renderCoin(state.balanceCoin);
|
||
})
|
||
.catch(function () {
|
||
renderCoin(state.balanceCoin);
|
||
});
|
||
}
|
||
|
||
function loadConfig() {
|
||
if (!rpsAPI() || shouldUseMockFlow()) {
|
||
renderStakeOptions(state.config);
|
||
return Promise.resolve();
|
||
}
|
||
var configStartedAt = performance.now();
|
||
return rpsAPI()
|
||
.config(GAME_ID)
|
||
.then(function (data) {
|
||
trackSelfGameEvent('config_load_success', {
|
||
duration_ms: Math.round(
|
||
performance.now() - configStartedAt
|
||
),
|
||
});
|
||
state.config =
|
||
(data && (data.config || data.rps_config)) ||
|
||
data ||
|
||
null;
|
||
renderStakeOptions(state.config);
|
||
})
|
||
.catch(function () {
|
||
trackSelfGameEvent('config_load_fail', {
|
||
duration_ms: Math.round(
|
||
performance.now() - configStartedAt
|
||
),
|
||
success: false,
|
||
error_code: 'config_failed',
|
||
});
|
||
renderStakeOptions(state.config);
|
||
});
|
||
}
|
||
|
||
function loadStartup() {
|
||
var profileTask =
|
||
window.HyAppGamePreloader.once(loadProfile);
|
||
var tasks = [
|
||
profileTask,
|
||
window.HyAppGamePreloader.once(loadBalance),
|
||
window.HyAppGamePreloader.once(loadConfig),
|
||
];
|
||
collectRPSPreloadURLs().forEach(function (url) {
|
||
tasks.push(function () {
|
||
return preloadResourceOnce(url);
|
||
});
|
||
});
|
||
tasks.push(function () {
|
||
return profileTask().then(preloadProfileImages);
|
||
});
|
||
// 启动期统一等用户、余额、配置和当前页面图片都进入缓存,再放开大厅点击,避免刚进页面时边操作边抖动加载。
|
||
return runPreloadTasks(tasks)
|
||
.then(function () {
|
||
return window.HyAppGamePreloader.delay(180);
|
||
})
|
||
.then(function () {
|
||
completeLoadingScreen();
|
||
trackSelfGameEvent('preload_complete', {
|
||
duration_ms: Math.round(
|
||
performance.now() - pageStartedAt
|
||
),
|
||
asset_count: tasks.length,
|
||
});
|
||
postBridge('gameRPSReady', {
|
||
game_code: GAME_CODE,
|
||
game_id: GAME_ID,
|
||
room_id: currentRoomID(),
|
||
});
|
||
});
|
||
}
|
||
|
||
function refreshLocalizedRuntimeText() {
|
||
renderSelection();
|
||
renderStakeOptions(state.config);
|
||
if (
|
||
!state.busy &&
|
||
frame.getAttribute('data-state') === 'lobby'
|
||
) {
|
||
setText(role('message'), '');
|
||
}
|
||
}
|
||
|
||
function initActions() {
|
||
document.addEventListener('click', function (event) {
|
||
var target = event.target.closest('[data-action]');
|
||
if (!target) return;
|
||
var action = target.getAttribute('data-action');
|
||
if (action === 'back') {
|
||
if (state.busy) {
|
||
cancelMatch().then(resetToLobby);
|
||
return;
|
||
}
|
||
postBridge('back');
|
||
return;
|
||
}
|
||
if (action === 'recharge') {
|
||
openRecharge();
|
||
return;
|
||
}
|
||
if (state.preloading) return;
|
||
if (action === 'gesture') {
|
||
if (state.busy) return;
|
||
state.selectedGesture =
|
||
target.getAttribute('data-gesture') || 'rock';
|
||
renderSelection();
|
||
trackSelfGameEvent('gesture_select', {
|
||
gesture: state.selectedGesture,
|
||
});
|
||
return;
|
||
}
|
||
if (action === 'stake') {
|
||
if (state.busy) return;
|
||
state.selectedStake = Number(
|
||
target.getAttribute('data-stake') || 0
|
||
);
|
||
each(
|
||
document.querySelectorAll(
|
||
'[data-action="stake"]'
|
||
),
|
||
function (button) {
|
||
button.setAttribute(
|
||
'aria-pressed',
|
||
button === target ? 'true' : 'false'
|
||
);
|
||
}
|
||
);
|
||
trackSelfGameEvent('stake_select', {
|
||
stake_coin: state.selectedStake,
|
||
});
|
||
return;
|
||
}
|
||
if (action === 'start') {
|
||
if (!state.selectedStake) {
|
||
showMessage(
|
||
i18nText(
|
||
'rockGame.selectStake',
|
||
'Select a stake'
|
||
)
|
||
);
|
||
return;
|
||
}
|
||
startMatch();
|
||
return;
|
||
}
|
||
if (action === 'rematch') {
|
||
cancelMatch().then(function () {
|
||
resetToLobby();
|
||
trackSelfGameEvent('play_again', {
|
||
source: 'rematch',
|
||
stake_coin: state.selectedStake,
|
||
gesture: state.selectedGesture,
|
||
});
|
||
startMatch();
|
||
});
|
||
return;
|
||
}
|
||
if (action === 'play-again') {
|
||
trackSelfGameEvent('play_again', {
|
||
source: 'settlement',
|
||
stake_coin: state.selectedStake,
|
||
gesture: state.selectedGesture,
|
||
});
|
||
resetToLobby();
|
||
}
|
||
});
|
||
}
|
||
|
||
window.HyAppRPSGame = {
|
||
reset: resetToLobby,
|
||
start: startMatch,
|
||
finish: function (payload) {
|
||
payload = payload || {};
|
||
if (
|
||
payload.self_gesture &&
|
||
gestures[payload.self_gesture]
|
||
) {
|
||
state.selectedGesture = payload.self_gesture;
|
||
}
|
||
if (
|
||
payload.opponent_gesture &&
|
||
gestures[payload.opponent_gesture]
|
||
) {
|
||
state.opponentGesture = payload.opponent_gesture;
|
||
}
|
||
if (payload.opponent) state.opponent = payload.opponent;
|
||
renderSelection();
|
||
renderOpponent();
|
||
finishRound(
|
||
payload.result ||
|
||
resultFor(
|
||
state.selectedGesture,
|
||
state.opponentGesture
|
||
)
|
||
);
|
||
},
|
||
};
|
||
|
||
updateScale();
|
||
initActions();
|
||
trackSelfGameEvent('page_open', {
|
||
duration_ms: Math.round(performance.now() - pageStartedAt),
|
||
});
|
||
window.addEventListener('beforeunload', function () {
|
||
cancelMatch();
|
||
});
|
||
renderCoin(state.balanceCoin);
|
||
renderSelection();
|
||
renderOpponent();
|
||
loadStartup().catch(function (error) {
|
||
showMessage(error && error.message);
|
||
completeLoadingScreen();
|
||
postBridge('gameRPSReady', {
|
||
game_code: GAME_CODE,
|
||
game_id: GAME_ID,
|
||
room_id: currentRoomID(),
|
||
});
|
||
});
|
||
window.addEventListener(
|
||
'hyapp:i18n-ready',
|
||
refreshLocalizedRuntimeText
|
||
);
|
||
window.addEventListener('resize', updateScale);
|
||
window.addEventListener('orientationchange', updateScale);
|
||
if (window.HyAppBridge && window.HyAppBridge.ready) {
|
||
window.HyAppBridge.ready({
|
||
page: 'game/rock',
|
||
figma_node_id: '491:135',
|
||
});
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|