ui/characterpicker.razor.scss
@import "Theme.scss";
CharacterPicker {
position: relative;
flex-direction: column;
align-items: center;
pointer-events: none;
font-family: $font;
font-smooth: never;
transform-origin: 50% 50%;
&.locked-entry-bounce {
animation: picker-lock-bounce 0.46s ease-out;
}
.picker-trigger {
position: relative;
align-items: center;
justify-content: center;
padding: 2px;
background-color: #1c2229;
border: 3px solid #343b45;
border-radius: 0;
pointer-events: all;
cursor: blank-hand;
transition: background-color 0.1s ease, border-color 0.14s ease-out, box-shadow 0.14s ease-out, transform 0.06s ease;
&:hover {
background-color: #252c34;
border-color: #59626e;
transform: scale( 1.06 );
}
&:active {
transform: scale( 0.98 );
}
&.locked-visuals {
border-color: rgba( #df5c65, 0.82 );
box-shadow: 0 0 28px 9px rgba( #df3f4d, 0.48 );
animation: picker-lock-pulse 1.15s ease-in-out infinite;
}
&.locked-fade-in {
animation: picker-lock-fade-in 0.34s ease-out, picker-lock-pulse 1.15s 0.34s ease-in-out infinite;
}
&.locked-shake-a {
animation: picker-locked-shake-a 0.24s ease-out, picker-lock-pulse 1.15s 0.24s ease-in-out infinite;
}
&.locked-shake-b {
animation: picker-locked-shake-b 0.24s ease-out, picker-lock-pulse 1.15s 0.24s ease-in-out infinite;
}
.trigger-lock {
position: absolute;
right: -12px;
top: -12px;
width: 48px;
height: 48px;
z-index: 3;
pointer-events: none;
background-image: url( "sprites/ui/level_lock.png" );
background-tint: #aeb6c2;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
image-rendering: pixelated;
}
.new-badge {
position: absolute;
left: -10px;
top: -10px;
min-width: 32px;
height: 32px;
align-items: center;
justify-content: center;
padding: 0 7px;
background-color: #c98a8a;
border: 2px solid #f8f5e6;
z-index: 3;
pointer-events: none;
transform-origin: 50% 50%;
animation: new-badge-pulse 0.5s ease-in-out infinite;
label {
width: 100%;
color: #3a404c;
font-size: 22px;
line-height: 0.8;
text-align: center;
}
}
}
.preview {
position: relative;
overflow: hidden;
pointer-events: none;
}
.preview.paired .rear {
transform: translate( 11%, -9% );
}
.sprite-layer {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-size: contain;
background-position: center;
image-rendering: pixelated;
}
.preview.overall .sprite-layer {
filter: brightness( 0 ) saturate( 0 );
opacity: 1;
}
&.leaderboard {
.clear-selection {
position: absolute;
top: -6px;
right: -6px;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
padding: 0;
background-color: #c98a8a;
border: 2px solid #f8f5e6;
pointer-events: all;
cursor: blank-hand;
z-index: 2;
transition: transform 0.08s ease, background-color 0.1s ease;
.icon {
font-family: "Material Icons";
font-size: 22px;
color: #3a404c;
}
&:hover {
background-color: #d99a9a;
transform: scale( 1.08 );
}
&:active {
transform: scale( 0.94 );
}
}
}
.paired .rear {
transform: translate( 11%, -9% ) scale( 0.94 );
}
.paired .front {
transform: translate( -11%, 9% );
}
.overlay {
position: absolute;
width: 100vw;
height: 100vh;
justify-content: center;
align-items: center;
pointer-events: all;
z-index: 900;
.dismiss {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba( $black, 0.18 );
pointer-events: all;
}
.panel {
width: 900px;
height: 900px;
flex-direction: column;
align-items: center;
padding: 42px 54px;
background-color: $black;
border: 5px solid #59626e;
pointer-events: all;
box-shadow: 0 12px 40px rgba( #000000, 0.55 );
.title {
font-size: 38px;
color: $white;
margin-bottom: 30px;
letter-spacing: 3px;
}
}
.character-grid {
width: 100%;
flex-grow: 1;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
}
.character-row {
width: 620px;
height: 185px;
flex-direction: row;
justify-content: center;
gap: 16px;
}
.character {
width: 118px;
height: 180px;
flex-shrink: 0;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding: 4px;
background-color: transparent;
border: 0;
border-radius: 0;
pointer-events: all;
cursor: blank-hand;
transition: transform 0.06s ease;
.character-sprite {
width: 110px;
height: 120px;
position: relative;
// Soft white oval behind the sprite so the character's dark outer pixels
// still read against the dark panel. Sized 8:10 (a touch taller than wide)
// as a share of the viewport, so it scales with PreviewScale like the art.
// Pre-blurred by tools/bake_glow.py rather than a live filter: blur(), which
// cost a render target and two blur passes per character per frame. The box
// is exactly the baked canvas (244x265px at base scale) so background-size
// 100% 100% is 1:1 — edit the shape in the baker, not here.
// The PNG's alpha peaks at 255; opacity is the brightness knob.
.icon-glow {
position: absolute;
left: -61%;
top: -60.5%;
width: 222%;
height: 221%;
z-index: 0;
pointer-events: none;
opacity: 0.3;
background-image: url( "sprites/ui/icon_glow.png" );
background-repeat: no-repeat;
background-size: 100% 100%;
}
.sprite-layer {
z-index: 1;
}
&.new-character .sprite-layer {
animation: new-character-pulse 1.05s ease-in-out infinite;
}
}
.character-name {
height: 36px;
font-size: 25px;
color: $white;
margin-top: 3px;
}
&.unlocked {
&:hover {
transform: scale( 1.06 );
}
&:active {
transform: scale( 0.98 );
}
}
&.locked {
// Locked entries render no glow, so the whole sprite box can be silhouetted.
.character-sprite {
filter: brightness( 0 ) saturate( 0 );
opacity: 1;
}
&:hover {
transform: scale( 1.035 );
}
&:active {
transform: scale( 0.995 );
}
}
}
}
}
@keyframes picker-lock-fade-in {
0% {
border-color: #343b45;
box-shadow: 0 0 0 0 rgba( #df3f4d, 0 );
}
}
@keyframes picker-lock-bounce {
0% { transform: scale( 0.86 ); }
42% { transform: scale( 1.2 ); }
66% { transform: scale( 0.94 ); }
84% { transform: scale( 1.07 ); }
100% { transform: scale( 1 ); }
}
@keyframes picker-lock-pulse {
0%, 100% {
border-color: rgba( #c94c57, 0.72 );
box-shadow: 0 0 18px 5px rgba( #df3f4d, 0.28 );
}
50% {
border-color: rgba( #ff7880, 0.86 );
box-shadow: 0 0 34px 12px rgba( #ef4655, 0.58 );
}
}
@keyframes picker-locked-shake-a {
0%, 100% { transform: translate( 0, 0 ) rotate( 0deg ); }
16% { transform: translate( -7px, 4px ) rotate( -2deg ); }
33% { transform: translate( 5px, -6px ) rotate( 1deg ); }
50% { transform: translate( -3px, -4px ) rotate( 2deg ); }
67% { transform: translate( 8px, 3px ) rotate( -1deg ); }
84% { transform: translate( -4px, 6px ) rotate( 1deg ); }
}
@keyframes picker-locked-shake-b {
0%, 100% { transform: translate( 0, 0 ) rotate( 0deg ); }
16% { transform: translate( 6px, 5px ) rotate( 2deg ); }
33% { transform: translate( -8px, -3px ) rotate( -1deg ); }
50% { transform: translate( 4px, -7px ) rotate( -2deg ); }
67% { transform: translate( -5px, 4px ) rotate( 1deg ); }
84% { transform: translate( 7px, -2px ) rotate( -1deg ); }
}
@keyframes new-badge-pulse {
0%, 100% {
transform: scale( 0.94 );
background-color: #b86f6f;
border-color: #e7dfd2;
box-shadow: 0 0 2px rgba( #c98a8a, 0.22 );
}
50% {
transform: scale( 1.08 );
background-color: #e2a2a2;
border-color: #fff3c4;
box-shadow: 0 0 9px rgba( #e2a2a2, 0.62 );
}
}
@keyframes new-character-pulse {
0%, 100% {
filter: brightness( 1.04 ) drop-shadow( 0 0 7px rgba( #dddd99, 0.58 ) );
}
50% {
filter: brightness( 1.26 ) drop-shadow( 0 0 18px rgba( #fff3c4, 0.95 ) );
}
}