chg: usr: re-implement the waiting for opponent dialog - refactor its gfx - & add online user selection dialog #4
This commit is contained in:
@@ -1,137 +1,438 @@
|
||||
#mine-wrapper .game-wrapper .game-overlay {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
z-index: 200;
|
||||
z-index: 200;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay.hide {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window {
|
||||
background: rgba(204, 204, 204, 0.8);
|
||||
border: 5px solid rgba(255, 255, 255, 0.5);
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
text-align: center;
|
||||
color: #354d6a;
|
||||
width: 660px;
|
||||
padding: 10px;
|
||||
background: linear-gradient(135deg, rgba(7, 9, 13, 0.98) 0%, rgba(10, 20, 35, 0.98) 100%);
|
||||
border: 2px solid rgba(35, 111, 135, 0.4);
|
||||
backdrop-filter: blur(12px);
|
||||
font-family: 'Rajdhani', sans-serif;
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
max-width: 680px;
|
||||
padding: 40px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.7), 0 0 40px rgba(35, 111, 135, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
animation: slideUp 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window h1 {
|
||||
font-weight: bold;
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
font-size: 32px;
|
||||
color: #fff;
|
||||
margin: 0 0 50px 0;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window h2 {
|
||||
font-size: 18px;
|
||||
font-size: 14px;
|
||||
color: rgba(149, 207, 245, 0.6);
|
||||
margin: 0 0 30px 0;
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window h3 {
|
||||
font-size: 16px;
|
||||
color: #386e8c;
|
||||
font-size: 16px;
|
||||
color: #236f87;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-invite {
|
||||
padding: 0 4px 4px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-invite-label {
|
||||
font-size: 13px;
|
||||
color: #386e8c;
|
||||
margin: 0 0 8px;
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
color: #386e8c;
|
||||
margin: 0 0 8px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.waiting-options {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
grid-template-rows: auto;
|
||||
gap: 24px;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
animation: fadeInUp 0.6s ease-out 0.2s both;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.waiting-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 24px;
|
||||
background: linear-gradient(135deg, rgba(35, 111, 135, 0.08) 0%, rgba(26, 80, 104, 0.08) 100%);
|
||||
border: 2px solid rgba(35, 111, 135, 0.2);
|
||||
border-radius: 12px;
|
||||
transition: all 350ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
animation: scaleIn 0.5s ease-out;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(35, 111, 135, 0.15), transparent);
|
||||
transition: left 0.5s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(35, 111, 135, 0.45);
|
||||
background: linear-gradient(135deg, rgba(35, 111, 135, 0.12) 0%, rgba(26, 80, 104, 0.12) 100%);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 30px rgba(35, 111, 135, 0.2);
|
||||
|
||||
&::before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.waiting-option-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
font-weight: 800;
|
||||
font-size: 17px;
|
||||
color: #fff;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
background: linear-gradient(135deg, rgba(35, 111, 135, 0.4) 0%, rgba(35, 111, 135, 0.2) 100%);
|
||||
border: 2px solid rgba(35, 111, 135, 0.5);
|
||||
border-radius: 8px;
|
||||
transition: all 400ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&:hover i {
|
||||
background: linear-gradient(135deg, rgba(35, 111, 135, 0.6) 0%, rgba(35, 111, 135, 0.4) 100%);
|
||||
border-color: rgba(35, 111, 135, 0.8);
|
||||
transform: scale(1.15) rotate(-8deg);
|
||||
box-shadow: 0 0 20px rgba(35, 111, 135, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.waiting-option-desc {
|
||||
font: 600 12px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.75);
|
||||
margin: 0;
|
||||
letter-spacing: 0.4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.waiting-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin: 0;
|
||||
animation: slideIn 0.7s ease-out 0.4s both;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
width: 2px;
|
||||
height: 20px;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(35, 111, 135, 0.1),
|
||||
rgba(35, 111, 135, 0.4),
|
||||
rgba(35, 111, 135, 0.1)
|
||||
);
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span {
|
||||
font: 700 11px 'Rajdhani', sans-serif;
|
||||
color: rgba(35, 111, 135, 0.6);
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
flex-direction: row;
|
||||
margin: 8px 0;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
width: auto;
|
||||
height: 1px;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
rgba(35, 111, 135, 0),
|
||||
rgba(35, 111, 135, 0.3),
|
||||
rgba(35, 111, 135, 0)
|
||||
);
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-invite {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-url-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #d0e8f5;
|
||||
border: 1px solid #7ab8d8;
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
margin-bottom: 12px;
|
||||
cursor: text;
|
||||
transition: border-color 200ms ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #d0e8f5 0%, #c5dff0 100%);
|
||||
border: 2px solid #7ab8d8;
|
||||
border-radius: 8px;
|
||||
padding: 0 10px;
|
||||
cursor: text;
|
||||
transition: all 300ms ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
border-color: #236f87;
|
||||
}
|
||||
&:hover {
|
||||
border-color: #236f87;
|
||||
box-shadow: 0 4px 12px rgba(35, 111, 135, 0.2);
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
border-color: #236f87;
|
||||
box-shadow: 0 0 16px rgba(35, 111, 135, 0.35);
|
||||
}
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-url-icon {
|
||||
color: #236f87;
|
||||
font-size: 13px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
opacity: 0.7;
|
||||
color: #236f87;
|
||||
font-size: 13px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-url-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
height: 40px;
|
||||
color: #1a4a6a;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.3px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: text;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
height: 40px;
|
||||
color: #1a4a6a;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.5px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: text;
|
||||
min-width: 0;
|
||||
|
||||
&::selection {
|
||||
background: rgba(35, 111, 135, 0.2);
|
||||
&::selection {
|
||||
background: rgba(35, 111, 135, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .browse-players-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
background: linear-gradient(to bottom, #236f87 0%, #1a5068 100%);
|
||||
border: 2px solid #2e7a9a;
|
||||
color: #e0f4ff;
|
||||
font: 700 13px 'Rajdhani', sans-serif;
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
width: 100%;
|
||||
font-weight: 800;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(35, 111, 135, 0.25);
|
||||
|
||||
i {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.4s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(to bottom, #2d8aa8 0%, #236f87 100%);
|
||||
border-color: #5ba4d4;
|
||||
color: #fff;
|
||||
box-shadow: 0 8px 24px rgba(35, 111, 135, 0.4);
|
||||
transform: translateY(-2px);
|
||||
|
||||
&::before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-copy-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
background: linear-gradient(to bottom, #236f87 0%, #1a5068 100%);
|
||||
border: 1px solid #2e7a9a;
|
||||
color: #e0f4ff;
|
||||
font-family: 'Rajdhani', sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 9px 32px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 220ms ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
background: linear-gradient(to bottom, #236f87 0%, #1a5068 100%);
|
||||
border: 2px solid #2e7a9a;
|
||||
color: #e0f4ff;
|
||||
font-family: 'Rajdhani', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(35, 111, 135, 0.25);
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(to bottom, #2d8aa8 0%, #236f87 100%);
|
||||
border-color: #5ba4d4;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 10px rgba(35, 111, 135, 0.5);
|
||||
}
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.4s ease;
|
||||
}
|
||||
|
||||
&.copied {
|
||||
background: linear-gradient(to bottom, #1a6844 0%, #135233 100%);
|
||||
border-color: #2a9e60;
|
||||
color: #a0f0c0;
|
||||
box-shadow: 0 0 8px rgba(26, 104, 68, 0.5);
|
||||
&:hover {
|
||||
background: linear-gradient(to bottom, #2d8aa8 0%, #236f87 100%);
|
||||
border-color: #5ba4d4;
|
||||
color: #fff;
|
||||
box-shadow: 0 8px 24px rgba(35, 111, 135, 0.4);
|
||||
transform: translateY(-2px);
|
||||
|
||||
&::before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&.copied {
|
||||
background: linear-gradient(to bottom, #1a6844 0%, #135233 100%);
|
||||
border-color: #2a9e60;
|
||||
color: #a0f0c0;
|
||||
box-shadow: 0 4px 12px rgba(26, 104, 68, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
275
assets/css/mineseeker/_waiting-dialog.scss
Normal file
275
assets/css/mineseeker/_waiting-dialog.scss
Normal file
@@ -0,0 +1,275 @@
|
||||
.opd-paper {
|
||||
background: #07090d !important;
|
||||
background-image: linear-gradient(rgba(35, 111, 135, 0.08) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.08) 1px, transparent 1px) !important;
|
||||
background-size: 46px 46px !important;
|
||||
border: 1px solid rgba(35, 111, 135, 0.4) !important;
|
||||
border-radius: 12px !important;
|
||||
box-shadow: 0 0 80px rgba(35, 111, 135, 0.15),
|
||||
0 32px 80px rgba(0, 0, 0, 0.9) !important;
|
||||
width: 500px;
|
||||
max-width: 94vw !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.opd {
|
||||
padding: 28px 28px 22px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.opd-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 22px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.opd-header-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.opd-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.opd-label {
|
||||
display: block;
|
||||
font: 700 11px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 4px;
|
||||
color: rgba(149, 207, 245, 0.55);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.opd-title {
|
||||
font: 800 28px 'Rajdhani', sans-serif;
|
||||
color: #fff;
|
||||
letter-spacing: 0.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
|
||||
i {
|
||||
color: rgba(35, 111, 135, 0.9);
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.opd-refresh,
|
||||
.opd-close {
|
||||
background: transparent;
|
||||
border: 1px solid rgba(35, 111, 135, 0.3);
|
||||
border-radius: 6px;
|
||||
color: rgba(149, 207, 245, 0.55);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 200ms ease;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: rgba(149, 207, 245, 0.5);
|
||||
color: #fff;
|
||||
background: rgba(35, 111, 135, 0.15);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.opd-refresh--spin i {
|
||||
animation: opd-spin 0.7s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes opd-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.opd-search-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(35, 111, 135, 0.07);
|
||||
border: 1px solid rgba(35, 111, 135, 0.28);
|
||||
border-radius: 8px;
|
||||
padding: 0 14px;
|
||||
margin-bottom: 16px;
|
||||
transition: border-color 200ms ease, background 200ms ease;
|
||||
|
||||
&:focus-within {
|
||||
border-color: rgba(35, 111, 135, 0.65);
|
||||
background: rgba(35, 111, 135, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
.opd-search-icon {
|
||||
color: rgba(149, 207, 245, 0.38);
|
||||
font-size: 13px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.opd-search {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
height: 44px;
|
||||
font: 400 14px 'Rajdhani', sans-serif;
|
||||
letter-spacing: 0.3px;
|
||||
color: #fff;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(149, 207, 245, 0.32);
|
||||
}
|
||||
}
|
||||
|
||||
.opd-search-clear {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: rgba(149, 207, 245, 0.4);
|
||||
cursor: pointer;
|
||||
padding: 0 0 0 8px;
|
||||
font-size: 12px;
|
||||
transition: color 150ms ease;
|
||||
|
||||
&:hover {
|
||||
color: rgba(149, 207, 245, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.opd-list {
|
||||
min-height: 110px;
|
||||
}
|
||||
|
||||
.opd-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30px 0 22px;
|
||||
gap: 12px;
|
||||
|
||||
i {
|
||||
font-size: 34px;
|
||||
color: rgba(35, 111, 135, 0.35);
|
||||
}
|
||||
|
||||
p {
|
||||
font: 400 14px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.38);
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
}
|
||||
|
||||
.opd-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 11px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
transition: all 180ms ease;
|
||||
margin-bottom: 5px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(35, 111, 135, 0.1);
|
||||
border-color: rgba(35, 111, 135, 0.28);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.opd-avatar {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, rgba(35, 111, 135, 0.55) 0%, rgba(35, 111, 135, 0.28) 100%);
|
||||
border: 1px solid rgba(35, 111, 135, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font: 700 13px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.9);
|
||||
letter-spacing: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.opd-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.opd-name {
|
||||
font: 700 15px 'Rajdhani', sans-serif;
|
||||
color: #fff;
|
||||
letter-spacing: 0.3px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.opd-since {
|
||||
font: 400 12px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.48);
|
||||
|
||||
i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.opd-join {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: linear-gradient(to bottom, rgba(35, 111, 135, 0.75) 0%, rgba(26, 80, 104, 0.9) 100%);
|
||||
border: 1px solid rgba(35, 111, 135, 0.55);
|
||||
color: rgba(149, 207, 245, 0.9);
|
||||
font: 700 12px 'Rajdhani', sans-serif;
|
||||
letter-spacing: 1.5px;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
padding: 7px 16px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 200ms ease;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(to bottom, rgba(45, 138, 168, 0.9) 0%, rgba(35, 111, 135, 0.95) 100%);
|
||||
border-color: rgba(149, 207, 245, 0.5);
|
||||
color: #fff;
|
||||
box-shadow: 0 0 14px rgba(35, 111, 135, 0.5);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.opd-note {
|
||||
font: 400 11px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.32);
|
||||
text-align: center;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 14px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid rgba(35, 111, 135, 0.14);
|
||||
}
|
||||
@@ -10,4 +10,5 @@
|
||||
@import 'mineseeker/grid';
|
||||
@import 'mineseeker/back-button';
|
||||
@import 'mineseeker/timer';
|
||||
@import 'mineseeker/responsive';
|
||||
@import 'mineseeker/responsive';
|
||||
@import 'mineseeker/waiting-dialog';
|
||||
170
assets/images/waiting-dialog-design.svg
Normal file
170
assets/images/waiting-dialog-design.svg
Normal file
@@ -0,0 +1,170 @@
|
||||
<!---
|
||||
- This file is part of the SplendidBear Websites' projects.
|
||||
-
|
||||
- Copyright (c) 2026 @ www.splendidbear.org
|
||||
-
|
||||
- For the full copyright and license information, please view the LICENSE
|
||||
- file that was distributed with this source code.
|
||||
-->
|
||||
|
||||
<svg viewBox="0 0 800 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style>
|
||||
.bg-dark { fill: #07090d; }
|
||||
.bg-grid { fill: url(#gridPattern); }
|
||||
.border-accent { stroke: rgba(35, 111, 135, 0.4); stroke-width: 2; fill: none; }
|
||||
.shadow { filter: drop-shadow(0 8px 20px rgba(0, 0, 0, 0.4)); }
|
||||
.title-text { font: bold 28px 'Rajdhani', sans-serif; fill: #fff; letter-spacing: 0.5px; }
|
||||
.header-text { font: bold 16px 'Rajdhani', sans-serif; fill: #236f87; letter-spacing: 0.5px; }
|
||||
.desc-text { font: 13px 'Rajdhani', sans-serif; fill: rgba(149, 207, 245, 0.7); letter-spacing: 0.3px; }
|
||||
.divider-line { stroke: rgba(35, 111, 135, 0.25); stroke-width: 1.5; }
|
||||
.divider-text { font: bold 12px 'Rajdhani', sans-serif; fill: rgba(35, 111, 135, 0.5); letter-spacing: 1px; text-transform: uppercase; }
|
||||
.icon-color { fill: rgba(35, 111, 135, 0.9); }
|
||||
.button-gradient { fill: url(#buttonGradient); }
|
||||
.button-text { font: bold 13px 'Rajdhani', sans-serif; fill: #e0f4ff; letter-spacing: 1.5px; text-transform: uppercase; }
|
||||
.glow { filter: drop-shadow(0 0 12px rgba(35, 111, 135, 0.3)); }
|
||||
</style>
|
||||
|
||||
<!-- Grid Pattern Background -->
|
||||
<pattern id="gridPattern" x="0" y="0" width="46" height="46" patternUnits="userSpaceOnUse">
|
||||
<path d="M 46 0 L 0 0 0 46" fill="none" stroke="rgba(35, 111, 135, 0.08)" stroke-width="1"/>
|
||||
</pattern>
|
||||
|
||||
<!-- Button Gradient -->
|
||||
<linearGradient id="buttonGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#236f87;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1a5068;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- Icon Definitions -->
|
||||
<g id="icon-link">
|
||||
<path d="M 12 8 L 20 8 Q 22 8 22 10 L 22 16 Q 22 18 20 18 L 16 18 M 28 12 L 32 12 Q 34 12 34 14 L 34 20 Q 34 22 32 22 L 28 22 M 22 12 L 28 12"
|
||||
stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
|
||||
<g id="icon-users">
|
||||
<circle cx="16" cy="10" r="3.5" fill="currentColor"/>
|
||||
<path d="M 12 15 Q 12 13 16 13 Q 20 13 20 15 L 20 18 L 12 18 Z" fill="currentColor"/>
|
||||
<circle cx="30" cy="10" r="3.5" fill="currentColor"/>
|
||||
<path d="M 26 15 Q 26 13 30 13 Q 34 13 34 15 L 34 18 L 26 18 Z" fill="currentColor"/>
|
||||
</g>
|
||||
|
||||
<g id="icon-search">
|
||||
<circle cx="18" cy="18" r="8" stroke="currentColor" stroke-width="2" fill="none"/>
|
||||
<line x1="26" y1="26" x2="32" y2="32" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</g>
|
||||
|
||||
<g id="icon-clipboard">
|
||||
<rect x="12" y="8" width="16" height="24" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
|
||||
<rect x="16" y="6" width="8" height="3" fill="currentColor"/>
|
||||
<line x1="16" y1="14" x2="28" y2="14" stroke="currentColor" stroke-width="1.5"/>
|
||||
<line x1="16" y1="18" x2="28" y2="18" stroke="currentColor" stroke-width="1.5"/>
|
||||
<line x1="16" y1="22" x2="24" y2="22" stroke="currentColor" stroke-width="1.5"/>
|
||||
</g>
|
||||
</defs>
|
||||
|
||||
<!-- Main Background -->
|
||||
<rect class="bg-dark" width="800" height="500"/>
|
||||
<rect class="bg-grid" width="800" height="500"/>
|
||||
|
||||
<!-- Dialog Container -->
|
||||
<g class="shadow">
|
||||
<rect class="bg-dark" x="60" y="40" width="680" height="420" rx="12" class="shadow"/>
|
||||
<rect class="border-accent" x="60" y="40" width="680" height="420" rx="12"/>
|
||||
</g>
|
||||
|
||||
<!-- Dialog Header -->
|
||||
<g>
|
||||
<text x="100" y="85" class="title-text">We are waiting...</text>
|
||||
<g transform="translate(720, 60)">
|
||||
<!-- Close Button -->
|
||||
<circle cx="0" cy="0" r="16" stroke="rgba(35, 111, 135, 0.3)" stroke-width="1" fill="none"/>
|
||||
<line x1="-6" y1="-6" x2="6" y2="6" stroke="rgba(149, 207, 245, 0.55)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="6" y1="-6" x2="-6" y2="6" stroke="rgba(149, 207, 245, 0.55)" stroke-width="2" stroke-linecap="round"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Option 1: Invite a Friend -->
|
||||
<g>
|
||||
<!-- Option Container -->
|
||||
<rect x="80" y="120" width="280" height="280" rx="8" fill="rgba(35, 111, 135, 0.05)" stroke="rgba(35, 111, 135, 0.15)" stroke-width="1"/>
|
||||
|
||||
<!-- Icon -->
|
||||
<g transform="translate(110, 145)">
|
||||
<circle cx="0" cy="0" r="22" fill="rgba(35, 111, 135, 0.15)"/>
|
||||
<g use="#icon-link" class="icon-color" transform="scale(1.8)"/>
|
||||
</g>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="160" y="165" class="header-text">Invite a Friend</text>
|
||||
|
||||
<!-- Description -->
|
||||
<text x="100" y="195" class="desc-text">Share this link with your opponent</text>
|
||||
|
||||
<!-- URL Box -->
|
||||
<rect x="100" y="210" width="240" height="40" rx="6" fill="#d0e8f5" stroke="#7ab8d8" stroke-width="1"/>
|
||||
<text x="118" y="237" style="font: 12px 'Courier New', monospace; fill: #1a4a6a; letter-spacing: 0.3px;">play.mineseeker.com/game-id</text>
|
||||
|
||||
<!-- Copy Button -->
|
||||
<g class="glow">
|
||||
<rect x="100" y="265" width="240" height="40" rx="5" class="button-gradient" stroke="#2e7a9a" stroke-width="1"/>
|
||||
<g transform="translate(120, 285)">
|
||||
<rect x="0" y="0" width="6" height="6" fill="#e0f4ff"/>
|
||||
<rect x="2" y="2" width="4" height="4" fill="none" stroke="#e0f4ff" stroke-width="0.5"/>
|
||||
</g>
|
||||
<text x="140" y="287" class="button-text">Copy Link</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Divider OR -->
|
||||
<g>
|
||||
<line x1="400" y1="200" x2="400" y2="320" class="divider-line"/>
|
||||
<circle cx="400" cy="260" r="18" fill="#07090d" stroke="rgba(35, 111, 135, 0.25)" stroke-width="1"/>
|
||||
<text x="400" y="267" class="divider-text" text-anchor="middle">OR</text>
|
||||
</g>
|
||||
|
||||
<!-- Option 2: Challenge a Player -->
|
||||
<g>
|
||||
<!-- Option Container -->
|
||||
<rect x="440" y="120" width="280" height="280" rx="8" fill="rgba(35, 111, 135, 0.05)" stroke="rgba(35, 111, 135, 0.15)" stroke-width="1"/>
|
||||
|
||||
<!-- Icon -->
|
||||
<g transform="translate(470, 145)">
|
||||
<circle cx="0" cy="0" r="22" fill="rgba(35, 111, 135, 0.15)"/>
|
||||
<g use="#icon-users" class="icon-color" transform="scale(1.5)"/>
|
||||
</g>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="510" y="165" class="header-text">Challenge a Player</text>
|
||||
|
||||
<!-- Description -->
|
||||
<text x="460" y="195" class="desc-text">Browse online players and challenge</text>
|
||||
|
||||
<!-- Browse Button -->
|
||||
<g class="glow">
|
||||
<rect x="460" y="265" width="240" height="40" rx="5" class="button-gradient" stroke="#2e7a9a" stroke-width="1"/>
|
||||
<g transform="translate(480, 285)">
|
||||
<!-- Search icon simplified -->
|
||||
<circle cx="4" cy="4" r="3" fill="none" stroke="#e0f4ff" stroke-width="1"/>
|
||||
<line x1="7" y1="7" x2="9" y2="9" stroke="#e0f4ff" stroke-width="1" stroke-linecap="round"/>
|
||||
</g>
|
||||
<text x="510" y="287" class="button-text">Browse Players</text>
|
||||
</g>
|
||||
|
||||
<!-- Highlight: Players Online -->
|
||||
<g>
|
||||
<text x="460" y="230" class="desc-text" style="font-weight: bold;">5 players waiting</text>
|
||||
<g transform="translate(680, 220)">
|
||||
<circle r="6" fill="#236f87"/>
|
||||
<circle cx="-8" cy="3" r="4" fill="rgba(35, 111, 135, 0.6)"/>
|
||||
<circle cx="8" cy="3" r="4" fill="rgba(35, 111, 135, 0.6)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Bottom Info -->
|
||||
<g>
|
||||
<text x="100" y="435" style="font: 11px 'Rajdhani', sans-serif; fill: rgba(149, 207, 245, 0.35); letter-spacing: 0.5px;">Choose either option to start playing or find an opponent</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
210
assets/js/mine-seeker/components/OnlinePlayersDialog.jsx
Normal file
210
assets/js/mine-seeker/components/OnlinePlayersDialog.jsx
Normal file
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
|
||||
const DIALOG_SX = {
|
||||
'& .MuiDialog-paper': {
|
||||
background: '#07090d',
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(35, 111, 135, 0.08) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.08) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '46px 46px',
|
||||
border: '1px solid rgba(35, 111, 135, 0.4)',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 0 80px rgba(35, 111, 135, 0.15), 0 32px 80px rgba(0, 0, 0, 0.9)',
|
||||
width: '500px',
|
||||
maxWidth: '94vw',
|
||||
overflow: 'hidden',
|
||||
color: '#fff',
|
||||
},
|
||||
'& .MuiBackdrop-root': {
|
||||
background: 'rgba(2, 4, 8, 0.88)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
},
|
||||
};
|
||||
|
||||
const formatSince = isoStr => {
|
||||
const diff = Math.floor((Date.now() - new Date(isoStr)) / 60000);
|
||||
if (1 > diff) return 'just now';
|
||||
if (1 === diff) return '1 min ago';
|
||||
return `${diff} min ago`;
|
||||
};
|
||||
|
||||
const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
|
||||
const [players, setPlayers] = useState([]);
|
||||
const [search, setSearch] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
const [snapshotLoaded, setSnapshotLoaded] = useState(false);
|
||||
|
||||
const addPlayer = useCallback(entry => {
|
||||
setPlayers(prev =>
|
||||
prev.some(p => p.gameAssoc === entry.gameAssoc)
|
||||
? prev
|
||||
: [...prev, entry],
|
||||
);
|
||||
}, []);
|
||||
|
||||
const removePlayer = useCallback(gameAssoc => {
|
||||
setPlayers(prev => prev.filter(p => p.gameAssoc !== gameAssoc));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setLoading(true);
|
||||
setSnapshotLoaded(false);
|
||||
fetch('/api/game/waiting')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
// Filter out current user's game from the snapshot
|
||||
const filtered = data.filter(p => p.gameAssoc !== currentGameAssoc);
|
||||
setPlayers(filtered);
|
||||
setSnapshotLoaded(true);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setPlayers([]);
|
||||
setSnapshotLoaded(true);
|
||||
setLoading(false);
|
||||
});
|
||||
}, [open, refreshKey, currentGameAssoc]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !snapshotLoaded) return;
|
||||
setSearch('');
|
||||
|
||||
const wrapper = document.getElementById('mine-wrapper');
|
||||
const hubUrl = wrapper?.dataset?.mercureHubUrl;
|
||||
const jwt = wrapper?.dataset?.mercureSubscriberJwt;
|
||||
if (!hubUrl) return;
|
||||
|
||||
const url = new URL(hubUrl, window.location.origin);
|
||||
url.searchParams.append('topic', 'mineseeker/lobby');
|
||||
if (jwt) url.searchParams.append('authorization', jwt);
|
||||
|
||||
const es = new EventSource(url.toString());
|
||||
es.onmessage = e => {
|
||||
const { action, gameAssoc, name, since } = JSON.parse(e.data);
|
||||
// Don't add the current user's game to the list
|
||||
if (gameAssoc === currentGameAssoc) return;
|
||||
if ('join' === action) addPlayer({ gameAssoc, name, since });
|
||||
if ('leave' === action) removePlayer(gameAssoc);
|
||||
};
|
||||
|
||||
return () => es.close();
|
||||
}, [open, snapshotLoaded, addPlayer, removePlayer, currentGameAssoc]);
|
||||
|
||||
const visible = players
|
||||
.filter(p => p.gameAssoc !== currentGameAssoc)
|
||||
.filter(p => p.name.toLowerCase().includes(search.toLowerCase()));
|
||||
|
||||
const shown = visible.slice(0, 5);
|
||||
const hasMore = 5 < visible.length;
|
||||
|
||||
// Debug: log if currentGameAssoc is undefined or if current user appears
|
||||
if ('development' === process.env.NODE_ENV && 0 < players.length) {
|
||||
const userInList = players.find(p => p.gameAssoc === currentGameAssoc);
|
||||
if (userInList) {
|
||||
console.warn('[OnlinePlayersDialog] Current user appears in players list:', { currentGameAssoc, userInList });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} sx={DIALOG_SX}>
|
||||
<div className="opd">
|
||||
<div className="opd-header">
|
||||
<div className="opd-header-text">
|
||||
<span className="opd-label">Multiplayer</span>
|
||||
<h2 className="opd-title">
|
||||
<i className="fa fa-users" />
|
||||
Online Players
|
||||
</h2>
|
||||
</div>
|
||||
<div className="opd-header-actions">
|
||||
<button
|
||||
className={`opd-refresh${loading ? ' opd-refresh--spin' : ''}`}
|
||||
onClick={() => setRefreshKey(k => k + 1)}
|
||||
disabled={loading}
|
||||
aria-label="Refresh"
|
||||
title="Refresh list"
|
||||
>
|
||||
<i className="fa fa-refresh" />
|
||||
</button>
|
||||
<button className="opd-close" onClick={onClose} aria-label="Close">
|
||||
<i className="fa fa-times" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="opd-search-wrap">
|
||||
<i className="fa fa-search opd-search-icon" />
|
||||
<input
|
||||
className="opd-search"
|
||||
placeholder="Search by username…"
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
/>
|
||||
{search && (
|
||||
<button className="opd-search-clear" onClick={() => setSearch('')}>
|
||||
<i className="fa fa-times" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="opd-list">
|
||||
{loading && (
|
||||
<div className="opd-empty">
|
||||
<i className="fa fa-spinner fa-spin" />
|
||||
<p>Loading players…</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && 0 === shown.length && (
|
||||
<div className="opd-empty">
|
||||
<i className="fa fa-hourglass-half" />
|
||||
<p>
|
||||
{search
|
||||
? 'No players match your search.'
|
||||
: 'No other players are waiting right now.'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && shown.map(player => (
|
||||
<div key={player.gameAssoc} className="opd-row">
|
||||
<div className="opd-avatar">
|
||||
{player.name.slice(0, 2).toUpperCase()}
|
||||
</div>
|
||||
<div className="opd-info">
|
||||
<span className="opd-name">{player.name}</span>
|
||||
<span className="opd-since">
|
||||
<i className="fa fa-clock-o" />
|
||||
{' '}Waiting {formatSince(player.since)}
|
||||
</span>
|
||||
</div>
|
||||
<a className="opd-join" href={`/play/${player.gameAssoc}`}>
|
||||
<i className="fa fa-play" />
|
||||
Join
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!loading && hasMore && (
|
||||
<p className="opd-note">
|
||||
Showing 5 of {visible.length} waiting players
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default OnlinePlayersDialog;
|
||||
85
assets/js/mine-seeker/components/WaitingOverlayContent.jsx
Normal file
85
assets/js/mine-seeker/components/WaitingOverlayContent.jsx
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
import { Fragment, useState } from 'react';
|
||||
import { OnlinePlayersDialog } from '@mine-components';
|
||||
|
||||
const WaitingOverlayContent = ({ shareUrl, currentGameAssoc }) => {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="waiting-options">
|
||||
<div className="waiting-option">
|
||||
<div className="waiting-option-header">
|
||||
<i className="fa fa-link" />
|
||||
<span>Invite a Friend</span>
|
||||
</div>
|
||||
<p className="waiting-option-desc">Share this link with your opponent</p>
|
||||
<ShareLinkBox
|
||||
url={shareUrl}
|
||||
/>
|
||||
</div>
|
||||
<div className="waiting-divider">
|
||||
<span>OR</span>
|
||||
</div>
|
||||
<div className="waiting-option">
|
||||
<div className="waiting-option-header">
|
||||
<i className="fa fa-users" />
|
||||
<span>Challenge a Player</span>
|
||||
</div>
|
||||
<p className="waiting-option-desc">Browse online players and challenge them</p>
|
||||
<button
|
||||
className="browse-players-btn"
|
||||
onClick={() => setDialogOpen(true)}
|
||||
>
|
||||
<i className="fa fa-search" />
|
||||
Browse Players
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<OnlinePlayersDialog
|
||||
open={dialogOpen}
|
||||
onClose={() => setDialogOpen(false)}
|
||||
currentGameAssoc={currentGameAssoc}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const ShareLinkBox = ({ url }) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2500);
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="share-invite">
|
||||
<div className="share-url-box" onClick={e => e.currentTarget.querySelector('input').select()}>
|
||||
<i className="fa fa-link share-url-icon" />
|
||||
<input
|
||||
className="share-url-input"
|
||||
readOnly
|
||||
value={url}
|
||||
onClick={e => e.target.select()}
|
||||
/>
|
||||
</div>
|
||||
<button className={`share-copy-btn${copied ? ' copied' : ''}`} onClick={handleCopy}>
|
||||
<i className={`fa ${copied ? 'fa-check' : 'fa-clipboard'}`} />
|
||||
{copied ? 'Copied!' : 'Copy Link'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WaitingOverlayContent;
|
||||
@@ -8,6 +8,8 @@
|
||||
*/
|
||||
|
||||
export { GameBoard } from './GameBoard';
|
||||
export { default as OnlinePlayersDialog } from './OnlinePlayersDialog';
|
||||
export { default as WaitingOverlayContent } from './WaitingOverlayContent';
|
||||
export { default as GameTimer } from './GameTimer';
|
||||
export { default as GridControl } from './grid/GridControl';
|
||||
export { default as GridField } from './grid/GridField';
|
||||
|
||||
@@ -7,41 +7,12 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { useGame } from '@mine-contexts';
|
||||
import { DESC } from '@mine-utils';
|
||||
import useStepTimer from './useStepTimer';
|
||||
|
||||
const ShareLinkBox = ({ url }) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2500);
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="share-invite">
|
||||
<p className="share-invite-label">Share this link with your opponent</p>
|
||||
<div className="share-url-box" onClick={e => e.currentTarget.querySelector('input').select()}>
|
||||
<i className="fa fa-link share-url-icon" />
|
||||
<input
|
||||
className="share-url-input"
|
||||
readOnly
|
||||
value={url}
|
||||
onClick={e => e.target.select()}
|
||||
/>
|
||||
</div>
|
||||
<button className={`share-copy-btn${copied ? ' copied' : ''}`} onClick={handleCopy}>
|
||||
<i className={`fa ${copied ? 'fa-check' : 'fa-clipboard'}`} />
|
||||
{copied ? 'Copied!' : 'Copy link'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import { WaitingOverlayContent } from '@mine-components';
|
||||
|
||||
/** Handles all server communication: SSE (Mercure), REST calls, and the initialization lifecycle. */
|
||||
const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
||||
@@ -106,7 +77,10 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
||||
const wInit = (revealedCells = []) => {
|
||||
setGridReady(true);
|
||||
showOverlay('We are waiting for your opponent...', gameAssoc ? (
|
||||
<ShareLinkBox url={`${window.location.href}/${gameAssoc}`} />
|
||||
<WaitingOverlayContent
|
||||
shareUrl={`${window.location.href}/${gameAssoc}`}
|
||||
currentGameAssoc={gameAssoc}
|
||||
/>
|
||||
) : '');
|
||||
setTimeout(() => revealedCells.forEach(cell => applyRevealedCell(cell, cell.player)), 0);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user