3641 lines
132 KiB
HTML
3641 lines
132 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="diceMatch.pageTitle">Dice Match</title>
|
|
<link rel="stylesheet" href="../../common/theme.css" />
|
|
<link rel="stylesheet" href="../common/game-match.css" />
|
|
<style>
|
|
:root {
|
|
color-scheme: dark;
|
|
--dice-frame-width: 375px;
|
|
--dice-frame-height: 812px;
|
|
--dice-scale: 1;
|
|
--dice-playfield-offset: 24px;
|
|
--dice-topbar-offset: -24px;
|
|
}
|
|
|
|
* {
|
|
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: flex-start;
|
|
}
|
|
|
|
button {
|
|
padding: 0;
|
|
border: 0;
|
|
color: inherit;
|
|
font: inherit;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
|
|
.dice-stage {
|
|
position: relative;
|
|
width: calc(var(--dice-frame-width) * var(--dice-scale));
|
|
height: calc(var(--dice-frame-height) * var(--dice-scale));
|
|
overflow: hidden;
|
|
background: #030623;
|
|
}
|
|
|
|
.dice-frame {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: var(--dice-frame-width);
|
|
height: var(--dice-frame-height);
|
|
overflow: hidden;
|
|
background: #030623;
|
|
transform: scale(var(--dice-scale));
|
|
transform-origin: top left;
|
|
}
|
|
|
|
.dice-frame.is-matching {
|
|
animation: screen-impact-shake 520ms ease-out 420ms both;
|
|
}
|
|
|
|
.dice-frame.is-self-win-compare {
|
|
animation: you-win-screen-smash 780ms
|
|
cubic-bezier(0.14, 0.84, 0.18, 1) 430ms both;
|
|
}
|
|
|
|
.game-content {
|
|
position: absolute;
|
|
inset: 0;
|
|
transform: translateY(
|
|
calc(-44px + var(--dice-playfield-offset))
|
|
);
|
|
}
|
|
|
|
.layer {
|
|
position: absolute;
|
|
}
|
|
|
|
.vs-badge {
|
|
transform-origin: 50% 50%;
|
|
will-change: transform, filter;
|
|
}
|
|
|
|
.vs-badge::after {
|
|
position: absolute;
|
|
inset: 14px 10px;
|
|
content: '';
|
|
border: 2px solid rgba(255, 213, 87, 0.68);
|
|
border-radius: 50%;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transform: scale(0.62);
|
|
filter: drop-shadow(0 0 10px rgba(255, 96, 243, 0.68));
|
|
}
|
|
|
|
.dice-frame.is-matching .vs-badge {
|
|
animation: vs-drop-impact 900ms
|
|
cubic-bezier(0.13, 0.92, 0.18, 1) both;
|
|
}
|
|
|
|
.dice-frame.is-matching .vs-badge::after {
|
|
animation: vs-impact-ring 580ms ease-out 430ms both;
|
|
}
|
|
|
|
.layer-img {
|
|
position: absolute;
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
max-width: none;
|
|
pointer-events: none;
|
|
user-select: none;
|
|
}
|
|
|
|
.cover {
|
|
object-fit: cover;
|
|
}
|
|
|
|
.fill {
|
|
object-fit: fill;
|
|
}
|
|
|
|
.status-time {
|
|
position: absolute;
|
|
top: 13px;
|
|
left: 33.87px;
|
|
width: 54px;
|
|
height: 15px;
|
|
color: #ffffff;
|
|
font-family: Helvetica, Arial, sans-serif;
|
|
font-size: 15px;
|
|
line-height: 15px;
|
|
text-align: center;
|
|
}
|
|
|
|
.top-bar {
|
|
position: absolute;
|
|
top: 60px;
|
|
left: 16px;
|
|
z-index: 10;
|
|
width: 343px;
|
|
height: 36px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
transform: translateY(var(--dice-topbar-offset));
|
|
}
|
|
|
|
.top-back {
|
|
position: relative;
|
|
width: 46px;
|
|
height: 36px;
|
|
flex: 0 0 46px;
|
|
transform: translateY(-17px);
|
|
}
|
|
|
|
.top-game-icon {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 36px;
|
|
height: 36px;
|
|
overflow: hidden;
|
|
border-radius: 6px;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
.top-coin {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
width: max-content;
|
|
min-width: 102px;
|
|
max-width: 176px;
|
|
height: 32px;
|
|
flex: 0 1 auto;
|
|
gap: 4px;
|
|
overflow: visible;
|
|
padding: 0 7px;
|
|
/* border: 1px solid rgba(108, 113, 194, 0.96);
|
|
border-radius: 4px; */
|
|
/* background: linear-gradient(
|
|
180deg,
|
|
rgba(15, 18, 58, 0.92) 0%,
|
|
rgba(4, 6, 31, 0.86) 100%
|
|
);
|
|
box-shadow:
|
|
inset 0 0 0 1px rgba(255, 255, 255, 0.05),
|
|
0 0 11px rgba(79, 93, 202, 0.22); */
|
|
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: block;
|
|
min-width: max-content;
|
|
max-width: none;
|
|
flex: 0 0 auto;
|
|
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 {
|
|
color: #ffd15a;
|
|
font-size: 21px;
|
|
font-weight: 900;
|
|
/* line-height: 8px; */
|
|
text-shadow:
|
|
0 1px 1px rgba(65, 23, 0, 0.86),
|
|
0 0 8px rgba(255, 178, 47, 0.74);
|
|
align-items: center;
|
|
/* font-size: 0; */
|
|
display: inline-block;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.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: 900;
|
|
line-height: 14px;
|
|
letter-spacing: 0;
|
|
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 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;
|
|
}
|
|
|
|
html[dir='rtl'] .language-button::after {
|
|
margin-right: 4px;
|
|
margin-left: 0;
|
|
}
|
|
|
|
.lobby-avatar-glow {
|
|
top: 163px;
|
|
left: 151px;
|
|
width: 73px;
|
|
height: 73px;
|
|
}
|
|
|
|
.lobby-player-card {
|
|
top: 146px;
|
|
left: 131px;
|
|
width: 113px;
|
|
height: 158px;
|
|
}
|
|
|
|
.dice-avatar,
|
|
.dice-avatar-frame {
|
|
position: absolute;
|
|
display: block;
|
|
pointer-events: none;
|
|
user-select: none;
|
|
}
|
|
|
|
.dice-avatar {
|
|
z-index: 4;
|
|
width: 71px;
|
|
height: 71px;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
clip-path: circle(50% at 50% 50%);
|
|
background: transparent;
|
|
}
|
|
|
|
.dice-avatar-frame {
|
|
z-index: 5;
|
|
width: 84px;
|
|
height: 84px;
|
|
object-fit: contain;
|
|
opacity: 0;
|
|
}
|
|
|
|
.dice-avatar-frame.has-frame {
|
|
opacity: 1;
|
|
}
|
|
|
|
.avatar-hole-mask {
|
|
position: absolute;
|
|
z-index: 3;
|
|
width: 74px;
|
|
height: 74px;
|
|
border-radius: 50%;
|
|
background: radial-gradient(
|
|
circle at 50% 44%,
|
|
rgba(27, 19, 36, 0.98) 0%,
|
|
rgba(13, 10, 22, 0.98) 68%,
|
|
rgba(6, 5, 15, 0.98) 100%
|
|
);
|
|
box-shadow:
|
|
inset 0 0 16px rgba(0, 0, 0, 0.72),
|
|
0 0 10px rgba(255, 174, 58, 0.12);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.avatar-hole-mask-lobby {
|
|
top: 163px;
|
|
left: 150px;
|
|
}
|
|
|
|
.avatar-hole-mask-self {
|
|
top: 163px;
|
|
left: 40px;
|
|
}
|
|
|
|
.dice-avatar.is-empty,
|
|
.dice-avatar-frame.is-empty {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
}
|
|
|
|
.dice-avatar-lobby {
|
|
top: 163px;
|
|
left: 152px;
|
|
}
|
|
|
|
.dice-avatar-frame-lobby {
|
|
top: 158px;
|
|
left: 145px;
|
|
}
|
|
|
|
.dice-avatar-self {
|
|
top: 168px;
|
|
left: 45px;
|
|
}
|
|
|
|
.dice-avatar-frame-self {
|
|
top: 158px;
|
|
left: 35px;
|
|
}
|
|
|
|
.dice-avatar-opponent {
|
|
top: 168px;
|
|
left: 266px;
|
|
}
|
|
|
|
.dice-avatar-frame-opponent {
|
|
top: 158px;
|
|
left: 256px;
|
|
}
|
|
|
|
.lobby-player-name {
|
|
position: absolute;
|
|
top: 247px;
|
|
left: 147px;
|
|
width: 81px;
|
|
height: 32px;
|
|
overflow: hidden;
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
line-height: 16px;
|
|
text-align: center;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.player-name {
|
|
position: absolute;
|
|
top: 247px;
|
|
left: 37px;
|
|
width: 81px;
|
|
height: 32px;
|
|
overflow: hidden;
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
line-height: 16px;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.opponent-mark {
|
|
position: absolute;
|
|
top: 247px;
|
|
left: 258px;
|
|
width: 81px;
|
|
height: 16px;
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
line-height: 16px;
|
|
text-align: center;
|
|
}
|
|
|
|
.opponent-name {
|
|
position: absolute;
|
|
top: 247px;
|
|
left: 258px;
|
|
z-index: 6;
|
|
width: 81px;
|
|
height: 32px;
|
|
overflow: hidden;
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
line-height: 16px;
|
|
text-align: center;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.matching-label {
|
|
position: absolute;
|
|
top: 270px;
|
|
left: 282px;
|
|
width: 54px;
|
|
height: 16px;
|
|
overflow: hidden;
|
|
color: #b9defb;
|
|
font-size: 10px;
|
|
line-height: 16px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.winner-label {
|
|
position: absolute;
|
|
top: 608px;
|
|
left: 123px;
|
|
width: 130px;
|
|
height: 14px;
|
|
color: transparent;
|
|
font-size: 14px;
|
|
line-height: 14px;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
background-image: url('assets/figma/winner-text-fill.png');
|
|
background-repeat: no-repeat;
|
|
background-position: center;
|
|
background-size: cover;
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
}
|
|
|
|
.stake {
|
|
position: absolute;
|
|
top: 634px;
|
|
width: 77px;
|
|
height: 28px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
overflow: hidden;
|
|
padding-left: 8px;
|
|
color: #ffe293;
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
line-height: 16px;
|
|
text-shadow: 0 0 1px #d5942b;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.stake:active {
|
|
filter: brightness(1.15);
|
|
}
|
|
|
|
.stake[data-selected='true'] {
|
|
filter: drop-shadow(0 0 5px rgba(255, 197, 47, 0.4));
|
|
}
|
|
|
|
.stake-bg {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 77px;
|
|
height: 28px;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.stake[data-selected='true'] .stake-bg {
|
|
display: none;
|
|
}
|
|
|
|
.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-fill.png');
|
|
background-repeat: no-repeat;
|
|
background-size: 105px 28px;
|
|
}
|
|
|
|
.stake[data-selected='true'] .stake-selected-slice {
|
|
display: block;
|
|
}
|
|
|
|
.stake-selected-left {
|
|
left: 0;
|
|
background-position: left top;
|
|
}
|
|
|
|
.stake-selected-right {
|
|
right: 0;
|
|
background-position: right top;
|
|
}
|
|
|
|
.stake-coin {
|
|
position: relative;
|
|
z-index: 1;
|
|
width: 16px;
|
|
height: 16px;
|
|
flex: 0 0 16px;
|
|
}
|
|
|
|
.stake-value {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.match-button {
|
|
position: absolute;
|
|
top: 690px;
|
|
left: 74px;
|
|
width: 227px;
|
|
height: 50px;
|
|
}
|
|
|
|
.match-button img {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.match-button span {
|
|
position: absolute;
|
|
top: 13px;
|
|
left: 0;
|
|
width: 227px;
|
|
color: #3e1f04;
|
|
font-size: 24px;
|
|
font-weight: 900;
|
|
line-height: 24px;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.match-button:active {
|
|
transform: translateY(1px);
|
|
filter: brightness(0.96);
|
|
}
|
|
|
|
.victory-button {
|
|
position: absolute;
|
|
}
|
|
|
|
.victory-button img {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.victory-button:active {
|
|
filter: brightness(1.12);
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
.fee-note {
|
|
position: absolute;
|
|
top: 752px;
|
|
left: 62px;
|
|
width: 252px;
|
|
height: 12px;
|
|
overflow: hidden;
|
|
color: rgba(255, 255, 255, 0.4);
|
|
font-size: 12px;
|
|
line-height: 12px;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.waiting-only {
|
|
opacity: 1;
|
|
transition:
|
|
opacity 260ms ease,
|
|
visibility 0s linear 0s;
|
|
will-change: opacity;
|
|
}
|
|
|
|
.dice-frame.is-matching .waiting-only {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition:
|
|
opacity 260ms ease 560ms,
|
|
visibility 0s linear 830ms;
|
|
}
|
|
|
|
.dice-frame.is-searching .waiting-only {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition:
|
|
opacity 240ms ease,
|
|
visibility 0s linear 260ms;
|
|
}
|
|
|
|
.lobby-only {
|
|
transition:
|
|
opacity 180ms ease,
|
|
visibility 0s linear 0s,
|
|
transform 820ms ease-in-out;
|
|
will-change: opacity, transform;
|
|
}
|
|
|
|
.dice-frame.is-searching .lobby-only,
|
|
.dice-frame.is-matching .lobby-only {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
transition:
|
|
opacity 180ms ease,
|
|
visibility 0s linear 0s,
|
|
transform 820ms ease-in-out;
|
|
}
|
|
|
|
.dice-frame.is-searching .lobby-avatar-glow,
|
|
.dice-frame.is-matching .lobby-avatar-glow,
|
|
.dice-frame.is-searching .lobby-player-card,
|
|
.dice-frame.is-matching .lobby-player-card,
|
|
.dice-frame.is-searching .avatar-hole-mask-lobby,
|
|
.dice-frame.is-matching .avatar-hole-mask-lobby,
|
|
.dice-frame.is-searching .dice-avatar-lobby,
|
|
.dice-frame.is-matching .dice-avatar-lobby,
|
|
.dice-frame.is-searching .dice-avatar-frame-lobby,
|
|
.dice-frame.is-matching .dice-avatar-frame-lobby,
|
|
.dice-frame.is-searching .lobby-player-name,
|
|
.dice-frame.is-matching .lobby-player-name {
|
|
transform: translateX(-110px);
|
|
}
|
|
|
|
.matching-card-only {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
transition:
|
|
opacity 180ms ease,
|
|
visibility 0s linear 180ms;
|
|
}
|
|
|
|
.dice-frame.is-searching .matching-card-only {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
transition:
|
|
opacity 220ms ease 760ms,
|
|
visibility 0s linear 760ms;
|
|
}
|
|
|
|
.search-indicator img {
|
|
transform-origin: 50% 50%;
|
|
}
|
|
|
|
.search-indicator img {
|
|
--search-orbit-radius: 5px;
|
|
}
|
|
|
|
.dice-frame.is-searching .search-indicator img {
|
|
animation: search-orbit 1100ms linear infinite;
|
|
}
|
|
|
|
.searching-only {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
transition:
|
|
opacity 180ms ease,
|
|
visibility 0s linear 180ms;
|
|
}
|
|
|
|
.dice-frame.is-searching .searching-only {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
transition:
|
|
opacity 220ms ease 820ms,
|
|
visibility 0s linear 820ms;
|
|
}
|
|
|
|
.center-search {
|
|
position: absolute;
|
|
top: 157px;
|
|
left: 122px;
|
|
z-index: 8;
|
|
width: 132px;
|
|
height: 132px;
|
|
filter: drop-shadow(0 0 12px rgba(77, 224, 255, 0.72));
|
|
}
|
|
|
|
.center-search-bg,
|
|
.center-search-icon {
|
|
position: absolute;
|
|
display: block;
|
|
pointer-events: none;
|
|
user-select: none;
|
|
}
|
|
|
|
.center-search-bg {
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
transform-origin: 50% 50%;
|
|
will-change: transform;
|
|
}
|
|
|
|
.dice-frame.is-searching .center-search-bg {
|
|
animation: matching-center-spin 3000ms linear infinite;
|
|
}
|
|
|
|
.center-search-icon {
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 44px;
|
|
height: 44px;
|
|
filter: drop-shadow(0 0 8px rgba(126, 220, 255, 0.88));
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
.matching-action {
|
|
position: absolute;
|
|
top: 676px;
|
|
left: 74px;
|
|
width: 264px;
|
|
height: 24px;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
transition:
|
|
opacity 260ms ease 180ms,
|
|
visibility 0s linear 440ms;
|
|
will-change: opacity;
|
|
}
|
|
|
|
.dice-frame.is-searching .matching-action {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
transition:
|
|
opacity 260ms ease 860ms,
|
|
visibility 0s linear 860ms;
|
|
}
|
|
|
|
.matching-action img {
|
|
position: absolute;
|
|
top: 7px;
|
|
width: 36px;
|
|
height: 7px;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition:
|
|
opacity 180ms ease 260ms,
|
|
transform 260ms ease 240ms;
|
|
}
|
|
|
|
.matching-action-left {
|
|
left: 0;
|
|
transform: translateX(18px);
|
|
}
|
|
|
|
.matching-action-right {
|
|
left: 228px;
|
|
transform: translateX(-18px);
|
|
}
|
|
|
|
.dice-frame.is-searching .matching-action img {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
transition:
|
|
opacity 180ms ease 940ms,
|
|
transform 260ms ease 920ms;
|
|
}
|
|
|
|
.matching-action-text {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 44px;
|
|
width: 140px;
|
|
opacity: 0;
|
|
color: transparent;
|
|
font-size: 24px;
|
|
font-weight: 900;
|
|
line-height: 24px;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
background: linear-gradient(180deg, #fef8ca 27.76%, #ffb36c);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
text-shadow: 0 0 3px #bc0003;
|
|
transition: opacity 220ms ease 260ms;
|
|
}
|
|
|
|
.dice-frame.is-searching .matching-action-text {
|
|
opacity: 1;
|
|
transition: opacity 220ms ease 920ms;
|
|
}
|
|
|
|
.dice-phase-text {
|
|
position: absolute;
|
|
top: 318px;
|
|
left: 52px;
|
|
z-index: 12;
|
|
width: 271px;
|
|
min-height: 42px;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
color: #fff1a8;
|
|
font-size: 36px;
|
|
font-weight: 900;
|
|
line-height: 42px;
|
|
text-align: center;
|
|
text-shadow:
|
|
0 2px 0 rgba(96, 16, 0, 0.78),
|
|
0 0 14px rgba(255, 88, 0, 0.82);
|
|
transition: opacity 180ms ease;
|
|
}
|
|
|
|
.dice-frame.is-searching .dice-phase-text,
|
|
.dice-frame.is-matching .dice-phase-text {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
}
|
|
|
|
.dice-frame.is-countdown .dice-phase-text {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
}
|
|
|
|
.countdown-layer {
|
|
position: absolute;
|
|
top: 268px;
|
|
left: 0;
|
|
z-index: 18;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 375px;
|
|
height: 158px;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 120ms ease;
|
|
}
|
|
|
|
.dice-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: 23;
|
|
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%;
|
|
}
|
|
|
|
.dice-frame.is-self-win-compare .you-win-smash-layer {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
}
|
|
|
|
.dice-frame.is-self-win-compare .you-win-smash-layer::before {
|
|
animation: you-win-screen-flash 760ms ease-out 430ms both;
|
|
}
|
|
|
|
.dice-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: 25;
|
|
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;
|
|
}
|
|
|
|
.dice-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;
|
|
}
|
|
|
|
.dice-score {
|
|
position: absolute;
|
|
top: 302px;
|
|
z-index: 12;
|
|
width: 88px;
|
|
min-height: 34px;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
color: #ffffff;
|
|
font-size: 18px;
|
|
font-weight: 900;
|
|
line-height: 22px;
|
|
text-align: center;
|
|
text-shadow: 0 2px 5px rgba(0, 0, 0, 0.82);
|
|
transition: opacity 180ms ease;
|
|
}
|
|
|
|
.dice-score-self {
|
|
left: 34px;
|
|
}
|
|
|
|
.dice-score-opponent {
|
|
left: 255px;
|
|
}
|
|
|
|
.dice-frame.has-scores .dice-score {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
}
|
|
|
|
.dice-roll-effect {
|
|
position: absolute;
|
|
z-index: 11;
|
|
width: 132px;
|
|
height: 132px;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 120ms ease;
|
|
}
|
|
|
|
.dice-roll-effect canvas,
|
|
.dice-roll-effect video {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.dice-roll-effect-self {
|
|
left: 8px;
|
|
top: 379px;
|
|
}
|
|
|
|
.dice-roll-effect-opponent {
|
|
left: 230px;
|
|
top: 379px;
|
|
}
|
|
|
|
.dice-roll-effect.is-playing,
|
|
.dice-roll-effect.is-fallback {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
}
|
|
|
|
.dice-roll-effect.is-fallback::before {
|
|
position: absolute;
|
|
inset: 28px;
|
|
content: '';
|
|
border-radius: 50%;
|
|
background: radial-gradient(
|
|
circle at 45% 38%,
|
|
rgba(255, 255, 255, 0.95) 0%,
|
|
rgba(255, 230, 124, 0.72) 32%,
|
|
rgba(255, 108, 26, 0.38) 58%,
|
|
rgba(255, 108, 26, 0) 78%
|
|
);
|
|
filter: blur(1px) drop-shadow(0 0 14px rgba(255, 196, 69, 0.82));
|
|
animation: dice-roll-fallback 520ms linear infinite;
|
|
}
|
|
|
|
.battle-only {
|
|
opacity: 1;
|
|
transition:
|
|
opacity 240ms ease,
|
|
visibility 0s linear 0s;
|
|
}
|
|
|
|
.match-battle-only {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition:
|
|
opacity 180ms ease,
|
|
visibility 0s linear 180ms;
|
|
}
|
|
|
|
.dice-frame.is-matching .match-battle-only {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
transition:
|
|
opacity 220ms ease,
|
|
visibility 0s linear 0s;
|
|
}
|
|
|
|
.player-copy-only {
|
|
display: none;
|
|
}
|
|
|
|
.opponent-battle-only,
|
|
.versus-only {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition:
|
|
opacity 180ms ease,
|
|
visibility 0s linear 180ms;
|
|
}
|
|
|
|
.dice-frame.is-searching .opponent-battle-only,
|
|
.dice-frame.is-matching .opponent-battle-only {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
transition:
|
|
opacity 220ms ease 760ms,
|
|
visibility 0s linear 760ms;
|
|
}
|
|
|
|
.opponent-name {
|
|
display: none;
|
|
}
|
|
|
|
.dice-frame.is-matching .opponent-name {
|
|
display: block;
|
|
}
|
|
|
|
.dice-frame.has-opponent .opponent-default-avatar {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.dice-frame.is-matching .versus-only {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
transition:
|
|
opacity 80ms ease,
|
|
visibility 0s linear 0s;
|
|
}
|
|
|
|
.dice-frame .dice-avatar.is-empty,
|
|
.dice-frame .dice-avatar-frame.is-empty,
|
|
.dice-frame.is-searching .dice-avatar.is-empty,
|
|
.dice-frame.is-searching .dice-avatar-frame.is-empty,
|
|
.dice-frame.is-matching .dice-avatar.is-empty,
|
|
.dice-frame.is-matching .dice-avatar-frame.is-empty {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.dice-frame.is-victory .battle-only,
|
|
.dice-frame.is-victory .waiting-only,
|
|
.dice-frame.is-victory .matching-card-only,
|
|
.dice-frame.is-victory .matching-action,
|
|
.dice-frame.is-victory .searching-only,
|
|
.dice-frame.is-victory .opponent-battle-only,
|
|
.dice-frame.is-victory .dice-roll-effect,
|
|
.dice-frame.is-victory .versus-only {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition:
|
|
opacity 240ms ease,
|
|
visibility 0s linear 260ms;
|
|
}
|
|
|
|
.victory-only {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
transform: translateY(18px) scale(0.98);
|
|
transition:
|
|
opacity 320ms ease 120ms,
|
|
transform 420ms cubic-bezier(0.16, 1, 0.3, 1) 120ms,
|
|
visibility 0s linear 440ms;
|
|
will-change: opacity, transform;
|
|
}
|
|
|
|
.dice-frame.is-victory .victory-only {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
transition:
|
|
opacity 320ms ease 120ms,
|
|
transform 420ms cubic-bezier(0.16, 1, 0.3, 1) 120ms,
|
|
visibility 0s linear 120ms;
|
|
}
|
|
|
|
.winner-avatar-mask,
|
|
.winner-avatar {
|
|
position: absolute;
|
|
top: 268px;
|
|
left: 134px;
|
|
width: 108px;
|
|
height: 108px;
|
|
border-radius: 50%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.winner-avatar-mask {
|
|
z-index: 3;
|
|
background: radial-gradient(
|
|
circle at 50% 44%,
|
|
rgba(27, 19, 36, 0.98) 0%,
|
|
rgba(13, 10, 22, 0.98) 68%,
|
|
rgba(6, 5, 15, 0.98) 100%
|
|
);
|
|
box-shadow: inset 0 0 24px rgba(0, 0, 0, 0.72);
|
|
}
|
|
|
|
.winner-avatar {
|
|
z-index: 4;
|
|
object-fit: cover;
|
|
user-select: none;
|
|
}
|
|
|
|
.winner-avatar.is-empty {
|
|
opacity: 0;
|
|
}
|
|
|
|
.victory-name {
|
|
position: absolute;
|
|
top: 392px;
|
|
left: 121px;
|
|
width: 133px;
|
|
height: 52px;
|
|
overflow: hidden;
|
|
color: #ffffff;
|
|
font-size: 24px;
|
|
line-height: 26px;
|
|
text-align: center;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.victory-panel-label,
|
|
.victory-panel-amount,
|
|
.victory-panel-currency {
|
|
position: absolute;
|
|
left: 201px;
|
|
width: 65px;
|
|
color: #f7cf58;
|
|
font-weight: 700;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.victory-panel-label {
|
|
top: 540px;
|
|
height: 20px;
|
|
font-size: 18px;
|
|
line-height: 20px;
|
|
}
|
|
|
|
.victory-panel-amount {
|
|
top: 566px;
|
|
height: 24px;
|
|
font-size: 24px;
|
|
line-height: 24px;
|
|
}
|
|
|
|
.victory-panel-currency {
|
|
top: 597px;
|
|
height: 20px;
|
|
font-size: 18px;
|
|
line-height: 20px;
|
|
text-transform: uppercase;
|
|
background: linear-gradient(180deg, #f9d76f 0%, #f1bb3c 95%);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.waiting-only,
|
|
.lobby-only,
|
|
.searching-only,
|
|
.matching-action,
|
|
.matching-action img,
|
|
.matching-action-text,
|
|
.battle-only,
|
|
.match-battle-only,
|
|
.opponent-battle-only,
|
|
.dice-roll-effect,
|
|
.versus-only,
|
|
.victory-only {
|
|
transition: none;
|
|
}
|
|
|
|
.dice-frame.is-matching .vs-badge,
|
|
.dice-frame.is-matching .vs-badge::after,
|
|
.dice-frame.is-self-win-compare,
|
|
.dice-frame.is-self-win-compare .you-win-smash-layer::before,
|
|
.dice-frame.is-self-win-compare .you-win-smash-floor,
|
|
.dice-frame.is-self-win-compare .you-win-impact,
|
|
.dice-frame.is-matching,
|
|
.dice-frame.is-searching .center-search-bg,
|
|
.dice-frame.is-searching .search-indicator img,
|
|
.dice-roll-effect.is-fallback::before {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
@keyframes screen-impact-shake {
|
|
0%,
|
|
100% {
|
|
transform: translate3d(0, 0, 0) scale(var(--dice-scale));
|
|
}
|
|
12% {
|
|
transform: translate3d(0, 9px, 0) scale(var(--dice-scale));
|
|
}
|
|
24% {
|
|
transform: translate3d(-7px, -5px, 0)
|
|
scale(var(--dice-scale));
|
|
}
|
|
36% {
|
|
transform: translate3d(6px, 4px, 0) scale(var(--dice-scale));
|
|
}
|
|
50% {
|
|
transform: translate3d(-4px, 2px, 0)
|
|
scale(var(--dice-scale));
|
|
}
|
|
66% {
|
|
transform: translate3d(3px, -2px, 0)
|
|
scale(var(--dice-scale));
|
|
}
|
|
82% {
|
|
transform: translate3d(-1px, 1px, 0)
|
|
scale(var(--dice-scale));
|
|
}
|
|
}
|
|
|
|
@keyframes vs-drop-impact {
|
|
0% {
|
|
opacity: 0;
|
|
filter: brightness(1.9) drop-shadow(0 0 34px #ff79ff);
|
|
transform: translateY(-640px) scale(4.8) rotate(-12deg);
|
|
}
|
|
34% {
|
|
opacity: 1;
|
|
filter: brightness(1.75) drop-shadow(0 0 32px #4cc9ff);
|
|
transform: translateY(-120px) scale(2.35) rotate(5deg);
|
|
}
|
|
58% {
|
|
opacity: 1;
|
|
filter: brightness(1.62) drop-shadow(0 0 28px #ffd557);
|
|
transform: translateY(22px) scale(0.78) rotate(2deg);
|
|
}
|
|
72% {
|
|
filter: brightness(1.48) drop-shadow(0 0 24px #ff79ff);
|
|
transform: translateY(-12px) scale(1.18) rotate(-1deg);
|
|
}
|
|
86% {
|
|
filter: brightness(1.18) drop-shadow(0 0 14px #4cc9ff);
|
|
transform: translateY(4px) scale(0.96) rotate(0.5deg);
|
|
}
|
|
100% {
|
|
opacity: 1;
|
|
filter: brightness(1);
|
|
transform: translateY(0) scale(1) rotate(0);
|
|
}
|
|
}
|
|
|
|
@keyframes vs-impact-ring {
|
|
0% {
|
|
opacity: 0.95;
|
|
transform: scale(0.36);
|
|
}
|
|
100% {
|
|
opacity: 0;
|
|
transform: scale(1.92);
|
|
}
|
|
}
|
|
|
|
@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(--dice-scale));
|
|
filter: none;
|
|
}
|
|
12% {
|
|
transform: translate3d(0, 14px, 0) scale(var(--dice-scale));
|
|
filter: brightness(1.22) contrast(1.08);
|
|
}
|
|
24% {
|
|
transform: translate3d(-12px, -8px, 0)
|
|
scale(var(--dice-scale));
|
|
}
|
|
36% {
|
|
transform: translate3d(10px, 7px, 0)
|
|
scale(var(--dice-scale));
|
|
}
|
|
50% {
|
|
transform: translate3d(-7px, 4px, 0)
|
|
scale(var(--dice-scale));
|
|
}
|
|
66% {
|
|
transform: translate3d(5px, -3px, 0)
|
|
scale(var(--dice-scale));
|
|
filter: brightness(1.08) contrast(1.03);
|
|
}
|
|
82% {
|
|
transform: translate3d(-2px, 1px, 0)
|
|
scale(var(--dice-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 search-orbit {
|
|
0% {
|
|
transform: rotate(0deg)
|
|
translateX(var(--search-orbit-radius, 12px))
|
|
rotate(0deg);
|
|
}
|
|
100% {
|
|
transform: rotate(360deg)
|
|
translateX(var(--search-orbit-radius, 12px))
|
|
rotate(-360deg);
|
|
}
|
|
}
|
|
|
|
@keyframes matching-center-spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
@keyframes dice-roll-fallback {
|
|
0% {
|
|
transform: scale(0.88) rotate(0deg);
|
|
opacity: 0.78;
|
|
}
|
|
50% {
|
|
transform: scale(1.08) rotate(180deg);
|
|
opacity: 1;
|
|
}
|
|
100% {
|
|
transform: scale(0.88) rotate(360deg);
|
|
opacity: 0.78;
|
|
}
|
|
}
|
|
|
|
@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="dice-stage"
|
|
role="application"
|
|
aria-label="Dice match lobby"
|
|
data-i18n-aria="diceMatch.pageLabel"
|
|
>
|
|
<main class="dice-frame" data-node-id="466:61" data-name="dice">
|
|
<div
|
|
class="layer"
|
|
style="left: -47px; top: 0; width: 470px; height: 834px"
|
|
data-node-id="466:64"
|
|
data-name="arena background"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/bg-arena.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
|
|
<div class="game-content">
|
|
<div class="top-bar" data-name="top bar">
|
|
<button
|
|
class="top-back"
|
|
type="button"
|
|
aria-label="Back"
|
|
data-node-id="466:68"
|
|
data-action="back"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/back-button.png"
|
|
alt=""
|
|
/>
|
|
</button>
|
|
|
|
<div
|
|
class="top-game-icon"
|
|
data-node-id="466:87"
|
|
data-name="game icon"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/game-icon.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
class="top-coin"
|
|
type="button"
|
|
data-name="coin balance"
|
|
data-action="recharge"
|
|
aria-label="Recharge"
|
|
>
|
|
<img src="assets/figma/coin.png" alt="" />
|
|
<span>8,000</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="diceMatch.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>
|
|
|
|
<div
|
|
class="layer waiting-only lobby-only lobby-avatar-glow"
|
|
data-name="lobby avatar glow"
|
|
>
|
|
<img
|
|
class="layer-img fill"
|
|
src="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="layer waiting-only lobby-only lobby-player-card"
|
|
data-name="lobby player card"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/player-card.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="avatar-hole-mask avatar-hole-mask-lobby waiting-only lobby-only"
|
|
data-name="lobby avatar empty mask"
|
|
></div>
|
|
<img
|
|
class="dice-avatar dice-avatar-lobby waiting-only lobby-only is-empty"
|
|
data-role="self-avatar"
|
|
src="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E"
|
|
alt=""
|
|
/>
|
|
<img
|
|
class="dice-avatar-frame dice-avatar-frame-lobby waiting-only lobby-only is-empty"
|
|
data-role="self-avatar-frame"
|
|
src="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E"
|
|
alt=""
|
|
/>
|
|
<div
|
|
class="lobby-player-name waiting-only lobby-only"
|
|
data-name="lobby player name"
|
|
>
|
|
<div>NameName</div>
|
|
<div>Name...</div>
|
|
</div>
|
|
|
|
<div
|
|
class="layer battle-only match-battle-only player-copy-only"
|
|
style="
|
|
left: 41px;
|
|
top: 163px;
|
|
width: 73px;
|
|
height: 73px;
|
|
"
|
|
data-node-id="466:89"
|
|
>
|
|
<img
|
|
class="layer-img fill"
|
|
src="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="layer battle-only match-battle-only player-copy-only"
|
|
style="
|
|
left: 21px;
|
|
top: 146px;
|
|
width: 113px;
|
|
height: 158px;
|
|
"
|
|
data-node-id="466:90"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/player-card.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="avatar-hole-mask avatar-hole-mask-self battle-only match-battle-only"
|
|
data-name="self avatar empty mask"
|
|
></div>
|
|
<img
|
|
class="dice-avatar dice-avatar-self battle-only match-battle-only is-empty"
|
|
data-role="self-battle-avatar"
|
|
src="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E"
|
|
alt=""
|
|
/>
|
|
<img
|
|
class="dice-avatar-frame dice-avatar-frame-self battle-only match-battle-only is-empty"
|
|
data-role="self-battle-avatar-frame"
|
|
src="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E"
|
|
alt=""
|
|
/>
|
|
<div
|
|
class="player-name battle-only match-battle-only player-copy-only"
|
|
data-node-id="466:91"
|
|
>
|
|
<div>NameName</div>
|
|
<div>Name...</div>
|
|
</div>
|
|
|
|
<div
|
|
class="layer vs-badge battle-only versus-only"
|
|
style="
|
|
left: 120px;
|
|
top: 159px;
|
|
width: 133px;
|
|
height: 128px;
|
|
"
|
|
data-node-id="466:122"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/vs.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
class="center-search searching-only"
|
|
aria-hidden="true"
|
|
>
|
|
<img
|
|
class="center-search-bg"
|
|
src="assets/figma/matching-center-bg.svg"
|
|
alt=""
|
|
/>
|
|
<img
|
|
class="center-search-icon"
|
|
src="assets/figma/search-icon.svg"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
class="layer waiting-only"
|
|
style="
|
|
left: -51px;
|
|
top: 311px;
|
|
width: 258px;
|
|
height: 258px;
|
|
"
|
|
data-node-id="466:124"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/red-ring.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="layer waiting-only"
|
|
style="
|
|
left: 32px;
|
|
top: 400px;
|
|
width: 84px;
|
|
height: 89px;
|
|
"
|
|
data-node-id="466:125"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/red-dice.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="layer waiting-only"
|
|
style="
|
|
left: -44px;
|
|
top: 288px;
|
|
width: 244px;
|
|
height: 236px;
|
|
"
|
|
data-node-id="466:126"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/red-flare.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
class="layer waiting-only"
|
|
style="
|
|
left: 170px;
|
|
top: 311px;
|
|
width: 258px;
|
|
height: 258px;
|
|
"
|
|
data-node-id="466:128"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/blue-ring.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="layer waiting-only"
|
|
style="
|
|
left: 254px;
|
|
top: 400px;
|
|
width: 83px;
|
|
height: 87px;
|
|
"
|
|
data-node-id="466:129"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/blue-dice.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="layer waiting-only"
|
|
style="
|
|
left: 177px;
|
|
top: 288px;
|
|
width: 244px;
|
|
height: 236px;
|
|
"
|
|
data-node-id="466:130"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/blue-flare.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
class="dice-roll-effect dice-roll-effect-self"
|
|
data-role="self-roll-effect"
|
|
aria-hidden="true"
|
|
></div>
|
|
<div
|
|
class="dice-roll-effect dice-roll-effect-opponent"
|
|
data-role="opponent-roll-effect"
|
|
aria-hidden="true"
|
|
></div>
|
|
|
|
<div
|
|
class="layer battle-only opponent-battle-only"
|
|
style="
|
|
left: 262px;
|
|
top: 163px;
|
|
width: 73px;
|
|
height: 73px;
|
|
"
|
|
data-node-id="466:132"
|
|
>
|
|
<img
|
|
class="layer-img fill"
|
|
src="assets/figma/opponent-avatar-glow.svg"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="layer battle-only opponent-battle-only opponent-default-avatar"
|
|
style="
|
|
left: 262px;
|
|
top: 163px;
|
|
width: 73px;
|
|
height: 73px;
|
|
"
|
|
data-node-id="466:133"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/opponent-question.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="layer battle-only opponent-battle-only"
|
|
style="
|
|
left: 242px;
|
|
top: 146px;
|
|
width: 113px;
|
|
height: 158px;
|
|
"
|
|
data-node-id="466:134"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/opponent-card.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<img
|
|
class="dice-avatar dice-avatar-opponent battle-only opponent-battle-only is-empty"
|
|
data-role="opponent-avatar"
|
|
src="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E"
|
|
alt=""
|
|
/>
|
|
<img
|
|
class="dice-avatar-frame dice-avatar-frame-opponent battle-only opponent-battle-only is-empty"
|
|
data-role="opponent-avatar-frame"
|
|
src="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E"
|
|
alt=""
|
|
/>
|
|
<div
|
|
class="opponent-name battle-only opponent-battle-only"
|
|
data-role="opponent-name"
|
|
>
|
|
<div>?</div>
|
|
<div></div>
|
|
</div>
|
|
<div
|
|
class="opponent-mark matching-card-only"
|
|
data-node-id="466:135"
|
|
data-i18n="diceMatch.unknownOpponent"
|
|
>
|
|
?
|
|
</div>
|
|
<div
|
|
class="layer matching-card-only search-indicator"
|
|
style="
|
|
left: 262px;
|
|
top: 269px;
|
|
width: 18px;
|
|
height: 18px;
|
|
"
|
|
data-node-id="466:137"
|
|
>
|
|
<img
|
|
class="layer-img fill"
|
|
src="assets/figma/search-icon.svg"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="matching-label matching-card-only"
|
|
data-node-id="466:136"
|
|
data-i18n="diceMatch.matching"
|
|
>
|
|
Matching...
|
|
</div>
|
|
|
|
<div
|
|
class="layer waiting-only"
|
|
style="
|
|
left: 13px;
|
|
top: 596px;
|
|
width: 350px;
|
|
height: 78px;
|
|
"
|
|
data-node-id="466:94"
|
|
>
|
|
<img
|
|
class="layer-img fill"
|
|
src="assets/figma/prize-panel.svg"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="winner-label waiting-only"
|
|
data-node-id="466:95"
|
|
data-i18n="diceMatch.winnerReceives"
|
|
>
|
|
The winner receives
|
|
</div>
|
|
<button
|
|
class="stake waiting-only"
|
|
type="button"
|
|
style="left: 21px"
|
|
data-node-id="466:96"
|
|
data-action="stake"
|
|
data-stake="8000"
|
|
aria-label="8,000"
|
|
aria-pressed="false"
|
|
>
|
|
<img
|
|
class="stake-bg"
|
|
src="assets/figma/stake-outline.svg"
|
|
alt=""
|
|
/>
|
|
<span
|
|
class="stake-selected-slice stake-selected-left"
|
|
></span>
|
|
<span
|
|
class="stake-selected-slice stake-selected-right"
|
|
></span>
|
|
<img
|
|
class="stake-coin"
|
|
src="assets/figma/coin.png"
|
|
alt=""
|
|
/>
|
|
<span class="stake-value">8,000</span>
|
|
</button>
|
|
<button
|
|
class="stake waiting-only"
|
|
type="button"
|
|
style="left: 107px"
|
|
data-node-id="466:99"
|
|
data-action="stake"
|
|
data-stake="8000"
|
|
data-selected="true"
|
|
aria-label="8,000"
|
|
aria-pressed="true"
|
|
>
|
|
<img
|
|
class="stake-bg"
|
|
src="assets/figma/stake-fill.png"
|
|
alt=""
|
|
/>
|
|
<span
|
|
class="stake-selected-slice stake-selected-left"
|
|
></span>
|
|
<span
|
|
class="stake-selected-slice stake-selected-right"
|
|
></span>
|
|
<img
|
|
class="stake-coin"
|
|
src="assets/figma/coin.png"
|
|
alt=""
|
|
/>
|
|
<span class="stake-value">8,000</span>
|
|
</button>
|
|
<button
|
|
class="stake waiting-only"
|
|
type="button"
|
|
style="left: 192px"
|
|
data-node-id="466:97"
|
|
data-action="stake"
|
|
data-stake="8000"
|
|
aria-label="8,000"
|
|
aria-pressed="false"
|
|
>
|
|
<img
|
|
class="stake-bg"
|
|
src="assets/figma/stake-outline.svg"
|
|
alt=""
|
|
/>
|
|
<span
|
|
class="stake-selected-slice stake-selected-left"
|
|
></span>
|
|
<span
|
|
class="stake-selected-slice stake-selected-right"
|
|
></span>
|
|
<img
|
|
class="stake-coin"
|
|
src="assets/figma/coin.png"
|
|
alt=""
|
|
/>
|
|
<span class="stake-value">8,000</span>
|
|
</button>
|
|
<button
|
|
class="stake waiting-only"
|
|
type="button"
|
|
style="left: 278px"
|
|
data-node-id="466:98"
|
|
data-action="stake"
|
|
data-stake="8000"
|
|
aria-label="8,000"
|
|
aria-pressed="false"
|
|
>
|
|
<img
|
|
class="stake-bg"
|
|
src="assets/figma/stake-outline.svg"
|
|
alt=""
|
|
/>
|
|
<span
|
|
class="stake-selected-slice stake-selected-left"
|
|
></span>
|
|
<span
|
|
class="stake-selected-slice stake-selected-right"
|
|
></span>
|
|
<img
|
|
class="stake-coin"
|
|
src="assets/figma/coin.png"
|
|
alt=""
|
|
/>
|
|
<span class="stake-value">8,000</span>
|
|
</button>
|
|
<div
|
|
class="layer waiting-only"
|
|
style="
|
|
left: 335px;
|
|
top: 602px;
|
|
width: 20px;
|
|
height: 20px;
|
|
"
|
|
data-node-id="466:118"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/badge-help.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
class="match-button waiting-only"
|
|
type="button"
|
|
data-node-id="466:119"
|
|
data-action="match"
|
|
aria-label="Match"
|
|
data-i18n-aria="diceMatch.match"
|
|
>
|
|
<img src="assets/figma/match-button.png" alt="" />
|
|
<span data-node-id="466:121" data-i18n="diceMatch.match"
|
|
>Match</span
|
|
>
|
|
</button>
|
|
|
|
<div
|
|
class="fee-note waiting-only"
|
|
data-node-id="466:92"
|
|
data-i18n="diceMatch.feeNote"
|
|
>
|
|
The winning player pays a 5% transaction fee
|
|
</div>
|
|
|
|
<div
|
|
class="matching-action"
|
|
data-node-id="466:255"
|
|
aria-live="polite"
|
|
>
|
|
<img
|
|
class="matching-action-left"
|
|
src="assets/figma/matching-left.svg"
|
|
alt=""
|
|
/>
|
|
<span
|
|
class="matching-action-text"
|
|
data-node-id="466:262"
|
|
data-i18n="diceMatch.matching"
|
|
>Matching...</span
|
|
>
|
|
<img
|
|
class="matching-action-right"
|
|
src="assets/figma/matching-right.svg"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="dice-phase-text"
|
|
data-role="phase-text"
|
|
aria-live="polite"
|
|
></div>
|
|
<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"
|
|
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>
|
|
<div
|
|
class="dice-score dice-score-self"
|
|
data-role="self-score"
|
|
></div>
|
|
<div
|
|
class="dice-score dice-score-opponent"
|
|
data-role="opponent-score"
|
|
></div>
|
|
|
|
<div
|
|
class="layer victory-only"
|
|
style="
|
|
left: 29px;
|
|
top: 90px;
|
|
width: 316px;
|
|
height: 124px;
|
|
"
|
|
data-node-id="466:297"
|
|
data-name="victory light"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/win-victory-light.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="winner-avatar-mask victory-only"
|
|
data-name="winner avatar empty mask"
|
|
></div>
|
|
<img
|
|
class="winner-avatar victory-only is-empty"
|
|
data-role="winner-avatar"
|
|
src="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E"
|
|
alt=""
|
|
/>
|
|
<div
|
|
class="layer victory-only"
|
|
style="
|
|
left: 104px;
|
|
top: 243px;
|
|
width: 167px;
|
|
height: 233px;
|
|
"
|
|
data-node-id="466:293"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
data-role="winner-card"
|
|
src="assets/figma/win-player-card.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="victory-name victory-only"
|
|
data-node-id="466:294"
|
|
>
|
|
<div>NameName</div>
|
|
<div>Name...</div>
|
|
</div>
|
|
<div
|
|
class="layer victory-only"
|
|
style="
|
|
left: 38px;
|
|
top: 528px;
|
|
width: 300px;
|
|
height: 103px;
|
|
"
|
|
data-node-id="466:299"
|
|
>
|
|
<img
|
|
class="layer-img cover"
|
|
src="assets/figma/win-reward-panel.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div
|
|
class="victory-panel-label victory-only"
|
|
data-node-id="466:301"
|
|
data-i18n="diceMatch.win"
|
|
>
|
|
Win
|
|
</div>
|
|
<div
|
|
class="victory-panel-amount victory-only"
|
|
data-node-id="466:300"
|
|
>
|
|
1,900
|
|
</div>
|
|
<div
|
|
class="victory-panel-currency victory-only"
|
|
data-node-id="466:302"
|
|
data-i18n="diceMatch.coins"
|
|
>
|
|
Coins
|
|
</div>
|
|
<button
|
|
class="victory-button victory-only"
|
|
type="button"
|
|
style="
|
|
left: 12px;
|
|
top: 692px;
|
|
width: 168px;
|
|
height: 38px;
|
|
"
|
|
data-node-id="466:295"
|
|
data-action="rematch"
|
|
aria-label="Re-match"
|
|
>
|
|
<img src="assets/figma/win-rematch-button.png" alt="" />
|
|
</button>
|
|
<button
|
|
class="victory-button victory-only"
|
|
type="button"
|
|
style="
|
|
left: 193px;
|
|
top: 692px;
|
|
width: 170px;
|
|
height: 38px;
|
|
"
|
|
data-node-id="466:296"
|
|
data-action="play-again"
|
|
aria-label="Play again"
|
|
>
|
|
<img
|
|
src="assets/figma/win-play-again-button.png"
|
|
alt=""
|
|
/>
|
|
</button>
|
|
</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=dice-lang-20260611"></script>
|
|
<script src="../../common/effect-player.js"></script>
|
|
<script>
|
|
(function () {
|
|
var DESIGN_WIDTH = 375;
|
|
var DESIGN_HEIGHT = 812;
|
|
var SETTLEMENT_AUTO_CLOSE_MS = 30000;
|
|
var DICE_ROLL_EFFECT_URL = 'assets/figma/dice-roll.svga';
|
|
var root = document.documentElement;
|
|
var transparentPixel =
|
|
'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E';
|
|
var frame = document.querySelector('.dice-frame');
|
|
var timers = [];
|
|
var pollTimer = 0;
|
|
var roundSequence = 0;
|
|
var rollEffectPlayers = {
|
|
self: null,
|
|
opponent: null,
|
|
};
|
|
var state = {
|
|
profile: {},
|
|
appearance: {},
|
|
balanceCoin: 0,
|
|
config: null,
|
|
selectedStake: 0,
|
|
match: null,
|
|
busy: false,
|
|
roundStarted: false,
|
|
serverTimeMS: 0,
|
|
};
|
|
|
|
function updateScale() {
|
|
var scale = Math.min(
|
|
window.innerWidth / DESIGN_WIDTH,
|
|
window.innerHeight / DESIGN_HEIGHT
|
|
);
|
|
root.style.setProperty(
|
|
'--dice-scale',
|
|
String(Math.max(scale, 0.1))
|
|
);
|
|
}
|
|
|
|
function postBridge(action, payload) {
|
|
if (window.HyAppBridge && window.HyAppBridge.post) {
|
|
window.HyAppBridge.post(action, payload || {});
|
|
}
|
|
}
|
|
|
|
function openRecharge() {
|
|
var payload = { source: 'dice', game_code: 'touzi' };
|
|
if (
|
|
window.HyAppBridge &&
|
|
typeof window.HyAppBridge.recharge === 'function'
|
|
) {
|
|
window.HyAppBridge.recharge(payload);
|
|
return;
|
|
}
|
|
postBridge('gameRecharge', payload);
|
|
postBridge('recharge', payload);
|
|
}
|
|
|
|
function each(nodes, iteratee) {
|
|
Array.prototype.forEach.call(nodes, iteratee);
|
|
}
|
|
|
|
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 delay(ms) {
|
|
return new Promise(function (resolve) {
|
|
schedule(resolve, ms);
|
|
});
|
|
}
|
|
|
|
function clearPollTimer() {
|
|
if (!pollTimer) return;
|
|
window.clearTimeout(pollTimer);
|
|
pollTimer = 0;
|
|
}
|
|
|
|
function clearMatchTimers() {
|
|
clearPollTimer();
|
|
timers.forEach(function (timer) {
|
|
window.clearTimeout(timer);
|
|
});
|
|
timers = [];
|
|
}
|
|
|
|
function api() {
|
|
return window.HyAppAPI || {};
|
|
}
|
|
|
|
function diceAPI() {
|
|
return api().game && api().game.dice;
|
|
}
|
|
|
|
function role(name) {
|
|
return document.querySelector('[data-role="' + name + '"]');
|
|
}
|
|
|
|
function rollEffectNode(side) {
|
|
return role(
|
|
side === 'opponent'
|
|
? 'opponent-roll-effect'
|
|
: 'self-roll-effect'
|
|
);
|
|
}
|
|
|
|
function createRollEffectPlayer(side) {
|
|
var node = rollEffectNode(side);
|
|
if (!node || !window.HyAppEffectPlayer) return null;
|
|
if (!rollEffectPlayers[side]) {
|
|
rollEffectPlayers[side] =
|
|
window.HyAppEffectPlayer.create(node, {
|
|
activeClass: 'is-playing',
|
|
fit: 'contain',
|
|
loop: true,
|
|
svgaScript: '../../common/vendor/svga.min.js',
|
|
waitForWindowLoad: false,
|
|
});
|
|
}
|
|
return rollEffectPlayers[side];
|
|
}
|
|
|
|
function playSingleRollEffect(side) {
|
|
var node = rollEffectNode(side);
|
|
var player = createRollEffectPlayer(side);
|
|
if (!node) return Promise.resolve();
|
|
node.classList.remove('is-fallback');
|
|
if (!player) {
|
|
node.classList.add('is-fallback');
|
|
return Promise.resolve();
|
|
}
|
|
player.stop();
|
|
return player
|
|
.play(DICE_ROLL_EFFECT_URL, {
|
|
type: 'svga',
|
|
waitForWindowLoad: false,
|
|
})
|
|
.catch(function () {
|
|
node.innerHTML = '';
|
|
node.classList.remove('is-playing');
|
|
node.classList.add('is-fallback');
|
|
});
|
|
}
|
|
|
|
function playRollEffects() {
|
|
return Promise.all([
|
|
playSingleRollEffect('self'),
|
|
playSingleRollEffect('opponent'),
|
|
]);
|
|
}
|
|
|
|
function stopSingleRollEffect(side) {
|
|
var node = rollEffectNode(side);
|
|
var player = rollEffectPlayers[side];
|
|
if (player) player.stop();
|
|
if (node) {
|
|
node.innerHTML = '';
|
|
node.classList.remove('is-playing', 'is-fallback');
|
|
}
|
|
}
|
|
|
|
function stopRollEffects() {
|
|
stopSingleRollEffect('self');
|
|
stopSingleRollEffect('opponent');
|
|
}
|
|
|
|
function setText(node, value) {
|
|
if (node) node.textContent = value == null ? '' : value;
|
|
}
|
|
|
|
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 matchText() {
|
|
return i18nText('diceMatch.match', 'Match');
|
|
}
|
|
|
|
function matchingText() {
|
|
return i18nText('diceMatch.matching', 'Matching...');
|
|
}
|
|
|
|
function setPhaseText(value) {
|
|
setText(role('phase-text'), value || '');
|
|
}
|
|
|
|
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 setActionText(value) {
|
|
setText(
|
|
document.querySelector('.matching-action-text'),
|
|
value || matchingText()
|
|
);
|
|
}
|
|
|
|
function showMessage(message) {
|
|
var text =
|
|
message ||
|
|
i18nText('diceMatch.requestFailed', 'Request failed');
|
|
if (window.HyAppToast && window.HyAppToast.show) {
|
|
window.HyAppToast.show(text);
|
|
return;
|
|
}
|
|
setPhaseText(text);
|
|
}
|
|
|
|
function formatCoin(value) {
|
|
var number = Number(value || 0);
|
|
if (!Number.isFinite(number)) number = 0;
|
|
return Math.trunc(number).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 setCoin(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 displayName(user) {
|
|
user = user || {};
|
|
return (
|
|
user.nickname ||
|
|
user.username ||
|
|
user.name ||
|
|
user.display_user_id ||
|
|
user.user_id ||
|
|
'NameName'
|
|
);
|
|
}
|
|
|
|
function displayID(user) {
|
|
user = user || {};
|
|
return user.display_user_id || user.user_id || '';
|
|
}
|
|
|
|
function setNameGroup(selector, user) {
|
|
var nodes = document.querySelectorAll(selector + ' div');
|
|
var name = displayName(user);
|
|
var sub = displayID(user);
|
|
if (nodes[0]) nodes[0].textContent = name;
|
|
if (nodes[1]) nodes[1].textContent = sub || 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 ||
|
|
normalized.indexOf('cdn.example.com/avatar/default') !==
|
|
-1
|
|
) {
|
|
return '';
|
|
}
|
|
return url;
|
|
}
|
|
|
|
function frameURL(frameData) {
|
|
frameData = frameData || {};
|
|
return (
|
|
frameData.asset_url ||
|
|
frameData.preview_url ||
|
|
frameData.animation_url ||
|
|
frameData.url ||
|
|
''
|
|
);
|
|
}
|
|
|
|
function setImage(node, url, fallbackURL) {
|
|
if (!node) return;
|
|
var nextURL = url || fallbackURL || '';
|
|
if (nextURL) {
|
|
node.onerror = function () {
|
|
node.onerror = null;
|
|
node.src = transparentPixel;
|
|
node.classList.add('is-empty');
|
|
};
|
|
node.src = nextURL;
|
|
node.classList.remove('is-empty');
|
|
} else {
|
|
node.onerror = null;
|
|
node.src = transparentPixel;
|
|
node.classList.add('is-empty');
|
|
}
|
|
}
|
|
|
|
function setFrame(node, frameData) {
|
|
var url = frameURL(frameData);
|
|
setImage(node, url, '');
|
|
if (url) {
|
|
node.classList.add('has-frame');
|
|
node.classList.remove('is-empty');
|
|
} else {
|
|
node.classList.remove('has-frame');
|
|
node.classList.add('is-empty');
|
|
}
|
|
}
|
|
|
|
function renderSelfProfile() {
|
|
var profile = state.profile || {};
|
|
var avatar = avatarURL(profile);
|
|
var frameData = state.appearance.avatar_frame || {};
|
|
setNameGroup('.lobby-player-name', profile);
|
|
setNameGroup('.player-name', profile);
|
|
setImage(role('self-avatar'), avatar, '');
|
|
setImage(role('self-battle-avatar'), avatar, '');
|
|
setFrame(role('self-avatar-frame'), frameData);
|
|
setFrame(role('self-battle-avatar-frame'), frameData);
|
|
}
|
|
|
|
function balanceFromResponse(data) {
|
|
var balances = (data && data.balances) || [];
|
|
for (var i = 0; i < balances.length; i += 1) {
|
|
if (balances[i].asset_type === 'COIN') {
|
|
return balances[i].available_amount || 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function enabledStakeOptions(config) {
|
|
var options = (config && config.stake_options) || [];
|
|
return options
|
|
.filter(function (item) {
|
|
return (
|
|
item &&
|
|
item.enabled !== false &&
|
|
item.stake_coin > 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 selectStake(button) {
|
|
if (!button) return;
|
|
each(document.querySelectorAll('.stake'), function (stake) {
|
|
var selected = stake === button;
|
|
var img = stake.querySelector('img');
|
|
if (selected) {
|
|
stake.setAttribute('data-selected', 'true');
|
|
state.selectedStake = Number(
|
|
stake.getAttribute('data-stake') || 0
|
|
);
|
|
} else {
|
|
stake.removeAttribute('data-selected');
|
|
}
|
|
stake.setAttribute(
|
|
'aria-pressed',
|
|
selected ? 'true' : 'false'
|
|
);
|
|
if (img) {
|
|
img.src = selected
|
|
? 'assets/figma/stake-fill.png'
|
|
: 'assets/figma/stake-outline.svg';
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderStakeOptions(config) {
|
|
var options = enabledStakeOptions(config);
|
|
if (!options.length) {
|
|
selectStake(
|
|
document.querySelector(
|
|
'.stake[data-selected="true"]'
|
|
) || document.querySelector('.stake')
|
|
);
|
|
return;
|
|
}
|
|
var selected = state.selectedStake;
|
|
var fallbackSelected = options[0] && options[0].stake_coin;
|
|
var selectedButton = null;
|
|
each(
|
|
document.querySelectorAll('.stake'),
|
|
function (button, index) {
|
|
var option = options[index];
|
|
if (!option) {
|
|
button.style.display = 'none';
|
|
return;
|
|
}
|
|
button.style.display = '';
|
|
button.setAttribute(
|
|
'data-stake',
|
|
String(option.stake_coin)
|
|
);
|
|
button.setAttribute(
|
|
'aria-label',
|
|
formatCoin(option.stake_coin)
|
|
);
|
|
setText(
|
|
button.querySelector('.stake-value'),
|
|
formatCoin(option.stake_coin)
|
|
);
|
|
if (option.stake_coin === selected) {
|
|
selectedButton = button;
|
|
}
|
|
if (
|
|
!selectedButton &&
|
|
option.stake_coin === fallbackSelected
|
|
) {
|
|
selectedButton = button;
|
|
}
|
|
}
|
|
);
|
|
selectStake(
|
|
selectedButton || document.querySelector('.stake')
|
|
);
|
|
if (config) {
|
|
var totalFee =
|
|
(Number(config.fee_bps || 0) +
|
|
Number(config.pool_bps || 0)) /
|
|
100;
|
|
setText(
|
|
document.querySelector('.fee-note'),
|
|
i18nFormat(
|
|
'diceMatch.configFeeNote',
|
|
'Fee {fee}%',
|
|
{
|
|
fee: totalFee,
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
function resetFrame() {
|
|
stopRollEffects();
|
|
frame.classList.remove(
|
|
'is-searching',
|
|
'is-matching',
|
|
'is-countdown',
|
|
'is-victory',
|
|
'is-self-win-compare',
|
|
'has-scores'
|
|
);
|
|
setCountdownNumber('');
|
|
}
|
|
|
|
function setMatchButtonBusy(busy, label) {
|
|
var button = document.querySelector('.match-button');
|
|
if (!button) return;
|
|
button.disabled = !!busy;
|
|
setText(button.querySelector('span'), label || matchText());
|
|
}
|
|
|
|
function currentRoomID() {
|
|
var params =
|
|
window.HyAppParams &&
|
|
window.HyAppParams.current &&
|
|
window.HyAppParams.current.raw;
|
|
if (params && params.get) {
|
|
return (
|
|
params.get('room_id') || params.get('roomId') || ''
|
|
);
|
|
}
|
|
return (
|
|
new URLSearchParams(window.location.search).get(
|
|
'room_id'
|
|
) || ''
|
|
);
|
|
}
|
|
|
|
function selfUserID() {
|
|
return String(
|
|
(state.profile && state.profile.user_id) || ''
|
|
);
|
|
}
|
|
|
|
function participantUser(participant) {
|
|
participant = participant || {};
|
|
var user = Object.assign(
|
|
{
|
|
user_id: participant.user_id || '',
|
|
display_user_id: participant.display_user_id || '',
|
|
nickname:
|
|
participant.nickname ||
|
|
participant.username ||
|
|
'',
|
|
username: participant.username || '',
|
|
avatar:
|
|
participant.avatar ||
|
|
participant.avatar_url ||
|
|
participant.avatarUrl ||
|
|
'',
|
|
},
|
|
participant.user || {}
|
|
);
|
|
if (!user.user_id) user.user_id = participant.user_id || '';
|
|
if (!user.display_user_id) {
|
|
user.display_user_id =
|
|
participant.display_user_id || '';
|
|
}
|
|
if (!user.nickname && participant.nickname) {
|
|
user.nickname = participant.nickname;
|
|
}
|
|
if (!user.username && participant.username) {
|
|
user.username = participant.username;
|
|
}
|
|
if (!user.avatar) {
|
|
user.avatar =
|
|
participant.avatar ||
|
|
participant.avatar_url ||
|
|
participant.avatarUrl ||
|
|
'';
|
|
}
|
|
return user;
|
|
}
|
|
|
|
function findSelfParticipant(match) {
|
|
var participants = (match && match.participants) || [];
|
|
var id = selfUserID();
|
|
for (var i = 0; i < participants.length; i += 1) {
|
|
if (String(participants[i].user_id || '') === id) {
|
|
return participants[i];
|
|
}
|
|
}
|
|
for (var j = 0; j < participants.length; j += 1) {
|
|
if (!participants[j].is_robot) return participants[j];
|
|
}
|
|
return participants[0] || null;
|
|
}
|
|
|
|
function findOpponentParticipant(match, selfParticipant) {
|
|
var participants = (match && match.participants) || [];
|
|
for (var i = 0; i < participants.length; i += 1) {
|
|
if (participants[i] !== selfParticipant) {
|
|
return participants[i];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function sameParticipant(left, right) {
|
|
if (!left || !right) return false;
|
|
var leftID = String(left.user_id || '');
|
|
var rightID = String(right.user_id || '');
|
|
return leftID !== '' && leftID === rightID;
|
|
}
|
|
|
|
function findWinnerParticipant(match) {
|
|
var participants = (match && match.participants) || [];
|
|
for (var i = 0; i < participants.length; i += 1) {
|
|
if (
|
|
participants[i] &&
|
|
participants[i].result === 'win'
|
|
) {
|
|
return participants[i];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function scoreText(participant) {
|
|
var points = (participant && participant.dice_points) || [];
|
|
if (!points.length) return '';
|
|
return String(Number(points[0] || 0));
|
|
}
|
|
|
|
function isDrawReroll(match) {
|
|
return (
|
|
match &&
|
|
match.result === 'draw' &&
|
|
match.status !== 'settled'
|
|
);
|
|
}
|
|
|
|
function isSelfWinner(match) {
|
|
var self = findSelfParticipant(match);
|
|
var winner = findWinnerParticipant(match);
|
|
if (!self) return false;
|
|
if (winner && sameParticipant(winner, self)) return true;
|
|
return self.result === 'win';
|
|
}
|
|
|
|
function setSelfWinCompare(visible) {
|
|
frame.classList.remove('is-self-win-compare');
|
|
if (!visible) return;
|
|
// 每一局 Compare 都要重新播放“砸下”效果,因此先读一次布局强制浏览器提交移除状态。
|
|
void frame.offsetWidth;
|
|
frame.classList.add('is-self-win-compare');
|
|
}
|
|
|
|
function renderMatchParticipants(match) {
|
|
var self = findSelfParticipant(match);
|
|
var opponent = findOpponentParticipant(match, self);
|
|
if (self && self.user) {
|
|
setNameGroup('.player-name', participantUser(self));
|
|
}
|
|
if (opponent) {
|
|
var opponentUser = participantUser(opponent);
|
|
var opponentAvatar = avatarURL(opponentUser);
|
|
var hasOpponentProfile =
|
|
displayName(opponentUser) !== '?' ||
|
|
!!opponentAvatar ||
|
|
!!displayID(opponentUser);
|
|
frame.classList.toggle(
|
|
'has-opponent',
|
|
hasOpponentProfile
|
|
);
|
|
setText(
|
|
document.querySelector('.opponent-mark'),
|
|
displayName(opponentUser)
|
|
);
|
|
setNameGroup(
|
|
'[data-role="opponent-name"]',
|
|
opponentUser
|
|
);
|
|
setText(
|
|
document.querySelector('.matching-label'),
|
|
i18nText('diceMatch.ready', 'Ready')
|
|
);
|
|
setImage(role('opponent-avatar'), opponentAvatar, '');
|
|
setFrame(
|
|
role('opponent-avatar-frame'),
|
|
opponent.avatar_frame || {}
|
|
);
|
|
} else {
|
|
setText(document.querySelector('.opponent-mark'), '?');
|
|
setText(
|
|
document.querySelector('.matching-label'),
|
|
matchingText()
|
|
);
|
|
setNameGroup('[data-role="opponent-name"]', {
|
|
nickname: '?',
|
|
});
|
|
frame.classList.remove('has-opponent');
|
|
setImage(role('opponent-avatar'), '', '');
|
|
setFrame(role('opponent-avatar-frame'), {});
|
|
}
|
|
var selfScore = scoreText(self);
|
|
var opponentScore = scoreText(opponent);
|
|
setText(role('self-score'), selfScore);
|
|
setText(role('opponent-score'), opponentScore);
|
|
if (selfScore && opponentScore) {
|
|
frame.classList.add('has-scores');
|
|
} else {
|
|
frame.classList.remove('has-scores');
|
|
}
|
|
}
|
|
|
|
function matchFromData(data) {
|
|
state.serverTimeMS = Number(
|
|
(data && data.server_time_ms) || 0
|
|
);
|
|
return (data && data.match) || data || {};
|
|
}
|
|
|
|
function isWaitingMatch(match) {
|
|
if (!match) return false;
|
|
return (
|
|
match.phase === 'waiting' ||
|
|
(match.status === 'waiting' &&
|
|
Number(match.current_players || 0) <
|
|
Number(match.min_players || 2))
|
|
);
|
|
}
|
|
|
|
function phaseDeadlineLeft(match) {
|
|
var deadline = Number(
|
|
(match && match.phase_deadline_ms) || 0
|
|
);
|
|
var serverTime = state.serverTimeMS || Date.now();
|
|
if (!deadline) return 3000;
|
|
return Math.max(deadline - serverTime, 0);
|
|
}
|
|
|
|
function pollMatch(matchID) {
|
|
clearPollTimer();
|
|
pollTimer = window.setTimeout(function () {
|
|
pollTimer = 0;
|
|
var game = diceAPI();
|
|
if (!game || !matchID || !state.busy) return;
|
|
game.getMatch(matchID)
|
|
.then(handleMatchData)
|
|
.catch(function (error) {
|
|
showMessage(error.message);
|
|
schedule(function () {
|
|
pollMatch(matchID);
|
|
}, 1000);
|
|
});
|
|
}, 1000);
|
|
}
|
|
|
|
function enterWaiting(match) {
|
|
state.match = match;
|
|
state.roundStarted = false;
|
|
resetFrame();
|
|
frame.classList.add('is-searching');
|
|
setActionText(matchingText());
|
|
setPhaseText(matchingText());
|
|
setMatchButtonBusy(true, matchingText());
|
|
renderMatchParticipants(match);
|
|
pollMatch(match.match_id);
|
|
}
|
|
|
|
function runCountdown(match, sequence) {
|
|
var duration = Math.min(phaseDeadlineLeft(match), 3000);
|
|
var endAt = Date.now() + Math.max(duration, 0);
|
|
function tick() {
|
|
if (sequence !== roundSequence) return;
|
|
var left = Math.max(endAt - Date.now(), 0);
|
|
if (left <= 0) {
|
|
frame.classList.remove('is-countdown');
|
|
setCountdownNumber('');
|
|
setPhaseText(i18nText('diceMatch.roll', 'Roll'));
|
|
rollAndAnimate(sequence);
|
|
return;
|
|
}
|
|
setPhaseText('');
|
|
setCountdownNumber(Math.ceil(left / 1000));
|
|
schedule(tick, 160);
|
|
}
|
|
tick();
|
|
}
|
|
|
|
function beginRound(match) {
|
|
if (state.roundStarted) {
|
|
renderMatchParticipants(match);
|
|
return;
|
|
}
|
|
clearPollTimer();
|
|
state.roundStarted = true;
|
|
state.busy = true;
|
|
state.match = match;
|
|
roundSequence += 1;
|
|
resetFrame();
|
|
frame.classList.add('is-matching', 'is-countdown');
|
|
setMatchButtonBusy(true, matchText());
|
|
renderMatchParticipants(match);
|
|
postBridge('gameMatchReady', {
|
|
game_code: 'touzi',
|
|
match_id: match.match_id,
|
|
});
|
|
runCountdown(match, roundSequence);
|
|
}
|
|
|
|
function rollAndAnimate(sequence) {
|
|
var game = diceAPI();
|
|
var matchID = state.match && state.match.match_id;
|
|
if (!game || !matchID) {
|
|
showMessage(
|
|
i18nText(
|
|
'diceMatch.apiUnavailable',
|
|
'Dice API unavailable'
|
|
)
|
|
);
|
|
resetToLobby(false);
|
|
return;
|
|
}
|
|
frame.classList.remove('is-countdown');
|
|
setCountdownNumber('');
|
|
setPhaseText(i18nText('diceMatch.rolling', 'Rolling...'));
|
|
frame.classList.remove('has-scores');
|
|
playRollEffects();
|
|
postBridge('gameDiceRolling', {
|
|
game_code: 'touzi',
|
|
match_id: matchID,
|
|
});
|
|
var rollPromise = game
|
|
.roll(matchID)
|
|
.then(function (data) {
|
|
return matchFromData(data);
|
|
})
|
|
.catch(function () {
|
|
return game.getMatch(matchID).then(function (data) {
|
|
return matchFromData(data);
|
|
});
|
|
});
|
|
Promise.all([rollPromise, delay(3000)])
|
|
.then(function (results) {
|
|
if (sequence !== roundSequence) {
|
|
stopRollEffects();
|
|
return;
|
|
}
|
|
var match = results[0];
|
|
state.match = match;
|
|
stopRollEffects();
|
|
renderMatchParticipants(match);
|
|
frame.classList.add('has-scores');
|
|
var drawReroll = isDrawReroll(match);
|
|
var selfWinCompare =
|
|
!drawReroll && isSelfWinner(match);
|
|
setPhaseText(
|
|
selfWinCompare
|
|
? ''
|
|
: drawReroll
|
|
? i18nText('diceMatch.draw', 'Draw')
|
|
: i18nText('diceMatch.compare', 'Compare')
|
|
);
|
|
setSelfWinCompare(selfWinCompare);
|
|
if (drawReroll) {
|
|
return delay(2000).then(function () {
|
|
if (sequence !== roundSequence) return;
|
|
rollAndAnimate(sequence);
|
|
});
|
|
}
|
|
return delay(3000).then(function () {
|
|
showSettlement(match, sequence);
|
|
});
|
|
})
|
|
.catch(function (error) {
|
|
stopRollEffects();
|
|
showMessage(error.message);
|
|
resetToLobby(false);
|
|
});
|
|
}
|
|
|
|
function renderVictory(match) {
|
|
var self = findSelfParticipant(match);
|
|
var winner = findWinnerParticipant(match) || self;
|
|
var winnerIsSelf = sameParticipant(winner, self);
|
|
var winnerUser = winnerIsSelf
|
|
? Object.assign(
|
|
{},
|
|
participantUser(winner),
|
|
state.profile || {}
|
|
)
|
|
: participantUser(winner);
|
|
var winnerCard = role('winner-card');
|
|
if (winnerCard) {
|
|
winnerCard.src = winnerIsSelf
|
|
? 'assets/figma/win-player-card.png'
|
|
: 'assets/figma/opponent-card.png';
|
|
}
|
|
setImage(role('winner-avatar'), avatarURL(winnerUser), '');
|
|
setNameGroup('.victory-name', winnerUser);
|
|
|
|
setText(
|
|
document.querySelector('.victory-panel-label'),
|
|
i18nText('diceMatch.win', 'Win')
|
|
);
|
|
setText(
|
|
document.querySelector('.victory-panel-amount'),
|
|
formatCoin((winner && winner.payout_coin) || 0)
|
|
);
|
|
if (self && self.balance_after > 0) {
|
|
setCoin(self.balance_after);
|
|
}
|
|
}
|
|
|
|
function refreshBalance() {
|
|
if (!api().wallet || !api().wallet.balances) return;
|
|
api()
|
|
.wallet.balances('COIN')
|
|
.then(function (data) {
|
|
setCoin(balanceFromResponse(data));
|
|
})
|
|
.catch(function () {});
|
|
}
|
|
|
|
function showSettlement(match, sequence) {
|
|
if (sequence !== roundSequence) return;
|
|
state.match = match;
|
|
renderMatchParticipants(match);
|
|
resetFrame();
|
|
frame.classList.add('is-victory');
|
|
renderVictory(match);
|
|
refreshBalance();
|
|
postBridge('gameDiceSettled', {
|
|
game_code: 'touzi',
|
|
match_id: match.match_id,
|
|
result: match.result,
|
|
});
|
|
delay(SETTLEMENT_AUTO_CLOSE_MS).then(function () {
|
|
if (sequence === roundSequence) resetToLobby(true);
|
|
});
|
|
}
|
|
|
|
function handleMatchData(data) {
|
|
var match = matchFromData(data);
|
|
if (!match || !match.match_id) return;
|
|
state.match = match;
|
|
renderMatchParticipants(match);
|
|
if (
|
|
match.status === 'settled' ||
|
|
match.phase === 'settled'
|
|
) {
|
|
roundSequence += 1;
|
|
showSettlement(match, roundSequence);
|
|
return;
|
|
}
|
|
if (
|
|
match.status === 'canceled' ||
|
|
match.phase === 'canceled'
|
|
) {
|
|
resetToLobby(true);
|
|
return;
|
|
}
|
|
if (isWaitingMatch(match)) {
|
|
enterWaiting(match);
|
|
return;
|
|
}
|
|
beginRound(match);
|
|
}
|
|
|
|
function selectedStakeCoin() {
|
|
var selected = document.querySelector(
|
|
'.stake[data-selected="true"]'
|
|
);
|
|
return Number(
|
|
(selected && selected.getAttribute('data-stake')) ||
|
|
state.selectedStake ||
|
|
0
|
|
);
|
|
}
|
|
|
|
function startMatch() {
|
|
var game = diceAPI();
|
|
var stake = selectedStakeCoin();
|
|
if (!game) {
|
|
showMessage(
|
|
i18nText(
|
|
'diceMatch.apiUnavailable',
|
|
'Dice API unavailable'
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
if (!stake) {
|
|
showMessage(
|
|
i18nText('diceMatch.selectStake', 'Select stake')
|
|
);
|
|
return;
|
|
}
|
|
clearMatchTimers();
|
|
state.busy = true;
|
|
state.roundStarted = false;
|
|
state.match = null;
|
|
roundSequence += 1;
|
|
resetFrame();
|
|
frame.classList.add('is-searching');
|
|
setActionText(matchingText());
|
|
setPhaseText(matchingText());
|
|
setMatchButtonBusy(true, matchingText());
|
|
postBridge('gameMatchRequested', {
|
|
game_code: 'touzi',
|
|
stake: stake,
|
|
});
|
|
game.match({
|
|
game_id: 'dice',
|
|
room_id: currentRoomID(),
|
|
stake_coin: stake,
|
|
})
|
|
.then(handleMatchData)
|
|
.catch(function (error) {
|
|
showMessage(error.message);
|
|
resetToLobby(true);
|
|
});
|
|
}
|
|
|
|
function cancelWaitingMatch() {
|
|
var game = diceAPI();
|
|
var match = state.match;
|
|
if (
|
|
!game ||
|
|
!match ||
|
|
!match.match_id ||
|
|
!isWaitingMatch(match)
|
|
) {
|
|
return Promise.resolve();
|
|
}
|
|
return game.cancel(match.match_id).catch(function () {});
|
|
}
|
|
|
|
function resetToLobby(keepStake) {
|
|
clearMatchTimers();
|
|
state.busy = false;
|
|
state.roundStarted = false;
|
|
state.match = null;
|
|
roundSequence += 1;
|
|
resetFrame();
|
|
setPhaseText('');
|
|
setActionText(matchingText());
|
|
setText(role('self-score'), '');
|
|
setText(role('opponent-score'), '');
|
|
setText(document.querySelector('.opponent-mark'), '?');
|
|
setText(
|
|
document.querySelector('.matching-label'),
|
|
matchingText()
|
|
);
|
|
setNameGroup('[data-role="opponent-name"]', {
|
|
nickname: '?',
|
|
});
|
|
frame.classList.remove('has-opponent');
|
|
setImage(role('opponent-avatar'), '', '');
|
|
setFrame(role('opponent-avatar-frame'), {});
|
|
setMatchButtonBusy(false, matchText());
|
|
if (!keepStake) state.selectedStake = 0;
|
|
renderStakeOptions(state.config);
|
|
}
|
|
|
|
function refreshLocalizedRuntimeText() {
|
|
renderStakeOptions(state.config);
|
|
if (state.match) {
|
|
renderMatchParticipants(state.match);
|
|
}
|
|
if (frame.classList.contains('is-victory')) {
|
|
renderVictory(state.match || {});
|
|
}
|
|
if (frame.classList.contains('is-searching')) {
|
|
setActionText(matchingText());
|
|
setPhaseText(matchingText());
|
|
setMatchButtonBusy(true, matchingText());
|
|
return;
|
|
}
|
|
if (!state.busy) {
|
|
setActionText(matchingText());
|
|
setMatchButtonBusy(false, matchText());
|
|
}
|
|
}
|
|
|
|
function loadLobby() {
|
|
if (!window.HyAppAPI || !diceAPI()) {
|
|
showMessage(
|
|
i18nText(
|
|
'diceMatch.apiUnavailable',
|
|
'Dice API unavailable'
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
setMatchButtonBusy(
|
|
true,
|
|
i18nText('diceMatch.loading', 'Loading')
|
|
);
|
|
Promise.all([
|
|
api()
|
|
.user.me()
|
|
.catch(function () {
|
|
return null;
|
|
}),
|
|
api()
|
|
.user.appearance()
|
|
.catch(function () {
|
|
return null;
|
|
}),
|
|
api()
|
|
.wallet.balances('COIN')
|
|
.catch(function () {
|
|
return null;
|
|
}),
|
|
diceAPI()
|
|
.config('dice')
|
|
.catch(function () {
|
|
return null;
|
|
}),
|
|
]).then(function (results) {
|
|
state.profile = results[0] || {};
|
|
state.appearance = results[1] || {};
|
|
state.config =
|
|
(results[3] && results[3].config) || null;
|
|
if (results[2])
|
|
setCoin(balanceFromResponse(results[2]));
|
|
renderSelfProfile();
|
|
renderStakeOptions(state.config);
|
|
setMatchButtonBusy(false, matchText());
|
|
});
|
|
}
|
|
|
|
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') {
|
|
cancelWaitingMatch().then(function () {
|
|
postBridge('back');
|
|
});
|
|
return;
|
|
}
|
|
if (action === 'recharge') {
|
|
openRecharge();
|
|
return;
|
|
}
|
|
if (action === 'stake') {
|
|
if (state.busy) return;
|
|
selectStake(target);
|
|
return;
|
|
}
|
|
if (action === 'match') {
|
|
if (!state.busy) startMatch();
|
|
return;
|
|
}
|
|
if (action === 'rematch') {
|
|
cancelWaitingMatch().then(function () {
|
|
resetToLobby(true);
|
|
postBridge('gameRematch', {
|
|
game_code: 'touzi',
|
|
});
|
|
startMatch();
|
|
});
|
|
return;
|
|
}
|
|
if (action === 'play-again') {
|
|
cancelWaitingMatch().then(function () {
|
|
resetToLobby(true);
|
|
postBridge('gamePlayAgain', {
|
|
game_code: 'touzi',
|
|
});
|
|
});
|
|
}
|
|
});
|
|
window.addEventListener('beforeunload', function () {
|
|
cancelWaitingMatch();
|
|
});
|
|
}
|
|
|
|
updateScale();
|
|
initActions();
|
|
window.addEventListener(
|
|
'hyapp:i18n-ready',
|
|
refreshLocalizedRuntimeText
|
|
);
|
|
loadLobby();
|
|
window.addEventListener('resize', updateScale);
|
|
window.addEventListener('orientationchange', updateScale);
|
|
if (window.HyAppBridge && window.HyAppBridge.ready) {
|
|
window.HyAppBridge.ready({
|
|
page: 'game/touzi',
|
|
figma_node_id: '466:61',
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|