ui/gamehud.razor.scss
@import "Theme.scss";
GameHud {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-direction: column;
justify-content: space-between; // status at the top, command bar at the bottom
align-items: center;
pointer-events: none; // only buttons opt back in (via .cg-* classes)
.top {
flex-direction: column;
align-items: flex-start;
align-self: flex-start; // pin to the top-left rather than centring with the rest of the HUD
gap: 8px;
margin-top: $deadzone;
margin-left: $deadzone;
// Rounded status container, with the turn-timer bar across its bottom.
.statuscard {
flex-direction: column;
align-items: stretch;
background-color: $panel;
border: 0.15px solid $line;
border-radius: $r-md;
overflow: hidden;
@include elevate;
.cg-status {
padding: 11px 30px;
}
.timerbar {
position: relative; // containing block for the fill
height: 4px;
background-color: rgba(255, 255, 255, .08);
// Absolutely positioned so its inline width is honoured (a flex child would stretch full-width).
.fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
background-color: $gold;
transition: width 1s linear; // drains smoothly between the ~1/s repaints
}
}
}
// Secondary line under the card (e.g. the poker pot): a small muted label and a big white value.
.subtitle {
flex-direction: row;
align-items: center;
gap: 9px;
text-shadow: 0 2px 8px black;
.sub-label {
font-family: $font;
font-size: 13px;
font-weight: 800;
letter-spacing: 2px;
text-transform: uppercase;
color: $text-mut;
}
.sub-value {
font-family: $font;
font-size: 30px;
font-weight: 800;
color: #fff;
}
}
}
.bottom {
flex-direction: column;
align-items: center;
gap: 14px;
margin-bottom: $deadzone;
}
// System controls (Forfeit) - tucked in the top-right corner, away from the action buttons.
.system {
position: absolute;
top: $deadzone;
right: $deadzone;
flex-direction: row;
gap: 8px; // space the How-to-play and Leave buttons apart
}
// Modal scrim + centred panel (the forfeit confirm and the end-of-game overlay). Both fade the scrim in
// and scale the dialog up as they appear, and play the reverse on `.closing` before they're removed.
.cg-modal {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, .6);
pointer-events: all;
animation: cg-modal-fade-in .35s ease-out;
&.closing { animation: cg-modal-fade-out .3s ease-in forwards; pointer-events: none; }
.dialog {
flex-direction: column;
align-items: center;
gap: 18px;
padding: 32px 36px;
max-width: 560px;
animation: cg-modal-scale-in .35s ease-out;
.cg-body {
text-align: center;
}
.row {
flex-direction: row;
align-items: stretch;
gap: 10px;
margin-top: 6px;
}
}
&.closing .dialog { animation: cg-modal-scale-out .3s ease-in forwards; }
// End-of-game overlay: a bigger, centred title with a win/loss tone.
.dialog.end {
.cg-eyebrow,
.cg-display { text-align: center; }
.cg-display { font-size: 64px; }
&.win .cg-display,
&.win .cg-eyebrow { color: $green; }
&.loss .cg-display,
&.loss .cg-eyebrow { color: $red-text; }
}
}
// Transient centre-screen announcement banner (any game can flash one via CardGame.Announce).
// Centred vertically so it reads clearly over the table; plays a one-shot pop-in/fade-out.
.announce {
position: absolute;
top: 32%;
left: 0;
right: 0;
justify-content: center;
align-items: center;
pointer-events: none;
.banner {
padding: 16px 30px;
background-color: $panel;
border: 2px solid $gold;
border-radius: 12px;
font-family: $font;
font-size: 30px;
font-weight: 900;
letter-spacing: 1px;
text-align: center;
color: #fff;
text-shadow: 0px 2px 6px rgba(0, 0, 0, 0.6);
@include elevate;
// ~matches AnnounceSeconds on the game; the element is removed when the timer ends.
animation: cg-announce 2.6s ease-out forwards;
}
}
// Shown on a client while a game's cloud package is still downloading.
.loading-cover
{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
background-color: rgba(8, 13, 11, .55);
backdrop-filter: blur(6px);
}
}
@keyframes cg-announce {
0% { opacity: 0; transform: scale(0.8) translateY(-12px); }
10% { opacity: 1; transform: scale(1.0) translateY(0px); }
80% { opacity: 1; transform: scale(1.0) translateY(0px); }
100% { opacity: 0; transform: scale(1.04) translateY(-8px); }
}
@keyframes cg-modal-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes cg-modal-fade-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes cg-modal-scale-in { from { transform: scale(0.85); } to { transform: scale(1); } }
@keyframes cg-modal-scale-out { from { transform: scale(1); } to { transform: scale(0.9); } }