hyapp-h5/game/touzi/index.html
2026-06-11 16:10:32 +08:00

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>