ui/gamehud.razor.scss
$sand: #e8bd7a;
$sand-dark: #cf9a4c;
$panel: rgba( 18, 22, 32, 0.72 );
$gold: #f5c542;
$water: #4fb0e8;
.hud
{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-direction: column;
font-family: Poppins;
color: white;
overflow: hidden;
// s&box does not inherit pointer-events down the tree and the screen root starts
// as "none", so every clickable element sets "all" for itself. Setting it here
// only would leave the whole UI dead to the mouse.
pointer-events: all;
}
// ---------------------------------------------------------------- background
// Backdrop layers never take the mouse - they sit under the whole screen and would
// otherwise swallow clicks meant for the pump.
.sky,
.sun,
.dune,
.floor,
.clouds,
.motes,
.shimmer,
.fx
{
pointer-events: none;
}
// The rows that hold clickable things, in case the engine wants the whole ancestor
// chain live rather than inheriting from .hud.
.topbar,
.stage,
.actions,
.collection
{
pointer-events: all;
}
.sky
{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: linear-gradient( to bottom, #4aa3d8 0%, #8fd0ea 38%, #f4d69c 70%, #e9b264 100% );
}
.sun
{
position: absolute;
top: 70px;
right: 160px;
width: 170px;
height: 170px;
border-radius: 100%;
background-color: #ffd84d;
box-shadow: 0px 0px 90px 30px rgba( 255, 216, 77, 0.35 );
align-items: center;
justify-content: center;
animation: sun-breathe 9s ease-in-out infinite;
}
// A slowly turning square behind the disc. One panel, and it reads as light bending
// off something very bright - eight individual ray divs looked like a cartwheel.
.sun-rays
{
position: absolute;
width: 230%;
height: 230%;
border-radius: 22%;
background-color: rgba( 255, 240, 180, 0.10 );
animation: sun-turn 70s linear infinite;
}
@keyframes sun-turn
{
0% { transform: rotate( 0deg ); }
100% { transform: rotate( 360deg ); }
}
// Scale, not blur radius. The disc and its shadow are drawn once and the transform
// moves the result, where animating the shadow re-blurs a 170px quad every frame for
// as long as the game is running - and the sun is on screen the whole time.
@keyframes sun-breathe
{
0% { transform: scale( 1 ); }
50% { transform: scale( 1.07 ); }
100% { transform: scale( 1 ); }
}
// ---------------------------------------------------------------- clouds
.clouds
{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 55%;
overflow: hidden;
}
// Soft ellipses with a wide shadow rather than drawn puffs - at this scale the shadow
// is what sells it, and it costs one panel each.
.cloud
{
position: absolute;
border-radius: 50%;
background-color: rgba( 255, 255, 255, 0.5 );
box-shadow: 0px 0px 50px 24px rgba( 255, 255, 255, 0.3 );
&.c1
{
top: 12%;
width: 260px;
height: 54px;
animation: drift-a 96s linear infinite;
}
&.c2
{
top: 30%;
width: 180px;
height: 38px;
opacity: 0.7;
animation: drift-b 138s linear infinite;
}
&.c3
{
top: 5%;
width: 340px;
height: 62px;
opacity: 0.45;
animation: drift-c 176s linear infinite;
}
}
@keyframes drift-a
{
0% { left: -22%; }
100% { left: 108%; }
}
@keyframes drift-b
{
0% { left: -16%; }
100% { left: 104%; }
}
@keyframes drift-c
{
0% { left: -30%; }
100% { left: 112%; }
}
// ---------------------------------------------------------------- dust
.motes
{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
// Specks of sand on the wind. Purely atmosphere, and the one thing on screen that
// keeps moving when the player has stopped.
.mote
{
position: absolute;
width: 5px;
height: 5px;
border-radius: 100%;
background-color: rgba( 255, 244, 214, 0.55 );
&.m1 { top: 42%; width: 4px; height: 4px; animation: mote-a 17s linear infinite; }
&.m2 { top: 58%; animation: mote-b 23s linear infinite; }
&.m3 { top: 71%; width: 3px; height: 3px; animation: mote-c 14s linear infinite; }
&.m4 { top: 36%; animation: mote-b 29s linear infinite; }
&.m5 { top: 64%; width: 6px; height: 6px; animation: mote-a 21s linear infinite; }
&.m6 { top: 79%; width: 4px; height: 4px; animation: mote-c 26s linear infinite; }
&.m7 { top: 50%; width: 3px; height: 3px; animation: mote-b 19s linear infinite; }
&.m8 { top: 86%; animation: mote-a 31s linear infinite; }
}
@keyframes mote-a
{
0% { left: -3%; opacity: 0; transform: translateY( 0px ); }
14% { opacity: 0.8; }
86% { opacity: 0.8; }
100% { left: 103%; opacity: 0; transform: translateY( -46px ); }
}
@keyframes mote-b
{
0% { left: -3%; opacity: 0; transform: translateY( 0px ); }
18% { opacity: 0.6; }
82% { opacity: 0.6; }
100% { left: 103%; opacity: 0; transform: translateY( 30px ); }
}
@keyframes mote-c
{
0% { left: -3%; opacity: 0; transform: translateY( 0px ); }
20% { opacity: 0.9; }
80% { opacity: 0.9; }
100% { left: 103%; opacity: 0; transform: translateY( -18px ); }
}
// ---------------------------------------------------------------- heat haze
// A band of light sitting on the horizon, breathing. Off by default and turned up by
// the scenes where the air would actually be shaking.
.shimmer
{
position: absolute;
bottom: 8%;
left: 0;
width: 100%;
height: 22%;
opacity: 0;
background-image: linear-gradient( to top, rgba( 255, 255, 255, 0.22 ) 0%, rgba( 255, 255, 255, 0 ) 100% );
animation: haze 7s ease-in-out infinite;
}
// Deliberately does not touch opacity - that is the switch each scene uses to turn the
// haze on, and a keyframe writing it would override the switch and show it everywhere.
@keyframes haze
{
0% { transform: scaleY( 1 ); }
50% { transform: scaleY( 1.3 ); }
100% { transform: scaleY( 1 ); }
}
// Wide flat ellipses - only the top curve shows, which reads as a dune ridge.
.dune
{
position: absolute;
border-radius: 50%;
&.far
{
left: -34%;
width: 105%;
bottom: 14%;
height: 26%;
background-color: $sand;
}
&.near
{
left: 28%;
width: 105%;
bottom: 12%;
height: 24%;
background-color: $sand;
}
}
// A very wide ellipse, so the horizon is a gentle curve. As a plain rectangle its
// top edge cut a hard straight line across the sky wherever no dune covered it.
.floor
{
position: absolute;
bottom: -34%;
left: -25%;
width: 150%;
height: 70%;
border-radius: 50%;
background-color: $sand-dark;
}
// ---------------------------------------------------------------- top bar
.topbar
{
height: 96px;
align-items: center;
padding-left: 28px;
padding-right: 28px;
flex-shrink: 0;
}
.stat
{
background-color: $panel;
border-radius: 18px;
padding: 10px 18px;
align-items: center;
margin-right: 16px;
}
.coins
{
position: relative;
transition: transform 0.12s ease-out;
.coin
{
width: 32px;
height: 32px;
border-radius: 100%;
background-color: $gold;
border: 3px solid #a8791a;
margin-right: 10px;
}
.value
{
font-size: 30px;
font-weight: 700;
}
// Mounted only for the moment after a sale, so it starts its animation fresh every
// time. Toggling a class on a panel that is already there does not reliably restart
// one, and a payout that doesn't visibly land is the whole point of this.
.coinflash
{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 18px;
border: 2px solid $gold;
animation: coin-flash 0.36s ease-out;
}
&.pop
{
transform: scale( 1.06 );
}
}
@keyframes coin-flash
{
0% { opacity: 0.95; transform: scale( 1 ); }
100% { opacity: 0; transform: scale( 1.18 ); }
}
.tankstat
{
.tank
{
width: 26px;
height: 54px;
border-radius: 8px;
background-color: rgba( 255, 255, 255, 0.16 );
border: 2px solid rgba( 255, 255, 255, 0.45 );
margin-right: 12px;
flex-direction: column;
justify-content: flex-end;
overflow: hidden;
position: relative;
}
.tank-fill
{
width: 100%;
background-color: $water;
transition: height 0.12s linear;
position: relative;
}
// A pale disc riding the surface, wider than the tank so only its curve shows. It
// rocks side to side, which turns a rectangle sliding up into something with liquid in it.
.tank-wave
{
position: absolute;
top: -5px;
left: -30%;
width: 160%;
height: 10px;
border-radius: 50%;
background-color: #8fd8ff;
animation: tank-rock 1.9s ease-in-out infinite;
}
// Fixed glass highlight down the left, so the tank reads as a cylinder.
.tank-shine
{
position: absolute;
top: 8%;
left: 14%;
width: 5px;
height: 60%;
border-radius: 999px;
background-color: rgba( 255, 255, 255, 0.35 );
}
.tank-text
{
flex-direction: column;
}
.value
{
font-size: 22px;
font-weight: 600;
}
.unit
{
font-size: 15px;
opacity: 0.7;
}
.sub
{
font-size: 13px;
opacity: 0.65;
}
&.full .tank-fill
{
background-color: #ff8b4a;
}
&.full .tank-wave
{
background-color: #ffb98a;
}
&.full .sub
{
color: #ffb37a;
opacity: 1;
}
// A full tank is wasted pumping, so the whole readout nags rather than sitting there.
&.full
{
animation: tank-nag 1.1s ease-in-out infinite;
}
}
@keyframes tank-rock
{
0% { transform: translateX( -4px ) rotate( -2deg ); }
50% { transform: translateX( 4px ) rotate( 2deg ); }
100% { transform: translateX( -4px ) rotate( -2deg ); }
}
@keyframes tank-nag
{
0% { box-shadow: 0px 0px 0px 0px rgba( 255, 139, 74, 0.5 ); }
50% { box-shadow: 0px 0px 18px 3px rgba( 255, 139, 74, 0.55 ); }
100% { box-shadow: 0px 0px 0px 0px rgba( 255, 139, 74, 0.5 ); }
}
.spacer
{
flex-grow: 1;
}
.iconbutton
{
width: 56px;
height: 56px;
border-radius: 14px;
background-color: $panel;
align-items: center;
justify-content: center;
cursor: pointer;
pointer-events: all;
position: relative;
margin-left: 10px;
transition: transform 0.09s ease-out, background-color 0.12s linear;
i
{
font-family: Material Icons;
font-size: 30px;
}
&:hover
{
background-color: rgba( 40, 48, 64, 0.9 );
transform: translateY( -2px );
}
&:active
{
transform: translateY( 1px );
}
&.muted
{
background-color: rgba( 200, 110, 40, 0.85 );
}
// Something in the shop is affordable. A dot in the corner rather than a colour
// change on the button, so it reads the way an unread badge does everywhere else.
.dot
{
position: absolute;
top: -4px;
right: -4px;
width: 16px;
height: 16px;
border-radius: 100%;
background-color: $gold;
border: 2px solid rgba( 18, 22, 32, 0.9 );
}
&.highlight
{
animation: btn-nudge 2.4s ease-in-out infinite;
}
}
@keyframes btn-nudge
{
0% { transform: translateY( 0px ); }
8% { transform: translateY( -5px ); }
16% { transform: translateY( 0px ); }
24% { transform: translateY( -3px ); }
32% { transform: translateY( 0px ); }
100% { transform: translateY( 0px ); }
}
// ---------------------------------------------------------------- stage
.stage
{
flex-grow: 1;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
// Wraps the pump so the effects layer has something the exact size and place of the
// pump to anchor to. Anchoring to .stage instead put every burst too low, because the
// nameplate under the pump pushes the pump above the stage's centre.
.pumpwrap
{
position: relative;
align-items: center;
justify-content: center;
}
.pumparea
{
width: 330px;
height: 330px;
align-items: center;
justify-content: center;
position: relative;
cursor: pointer;
pointer-events: all;
transition: transform 0.07s ease-out;
.pumpart
{
width: 100%;
height: 100%;
}
// Size and brightness come in as an inline style - they track pressure.
.glow
{
position: absolute;
border-radius: 100%;
background-color: rgba( 255, 255, 255, 0.14 );
transition: width 0.2s ease-out, height 0.2s ease-out;
}
// Only shows once pressure is pinned. The ring, not the glow, is what tells you
// you're at the top - a glow that just keeps brightening has no ceiling to read.
.heatring
{
position: absolute;
width: 86%;
height: 86%;
border-radius: 100%;
border: 3px solid rgba( 255, 190, 90, 0.85 );
opacity: 0;
}
.ground
{
position: absolute;
bottom: 2%;
width: 70%;
height: 8%;
border-radius: 50%;
background-color: rgba( 90, 60, 20, 0.22 );
}
&:hover
{
transform: scale( 1.03 );
}
&.pumping
{
transform: scale( 0.93 );
}
&.full .glow
{
background-color: rgba( 255, 140, 60, 0.25 );
}
&.hot
{
.heatring
{
opacity: 1;
animation: heat-ring 1.05s ease-out infinite;
}
.pumpart
{
animation: hot-throb 0.62s ease-in-out infinite;
}
}
}
@keyframes heat-ring
{
0% { transform: scale( 0.94 ); opacity: 0.9; }
100% { transform: scale( 1.18 ); opacity: 0; }
}
@keyframes hot-throb
{
0% { transform: scale( 1 ); }
50% { transform: scale( 1.035 ); }
100% { transform: scale( 1 ); }
}
// ---------------------------------------------------------------- effects layer
// A zero-size anchor dead centre on the pump. Everything in here positions itself off
// it with margins, which is why the particle code can work in plain pixel offsets.
.fx
{
position: absolute;
left: 50%;
top: 50%;
width: 0px;
height: 0px;
align-items: center;
justify-content: center;
}
.particle
{
position: absolute;
pointer-events: none;
// Water thrown up out of the pump head.
&.drop
{
border-radius: 50%;
background-color: #9fe0ff;
border: 2px solid rgba( 255, 255, 255, 0.75 );
}
// Coins off a sale. Round, gold, and spinning - the spin comes in inline.
&.coinbit
{
border-radius: 100%;
background-color: $gold;
border: 3px solid #a8791a;
}
// The flat radial burst on a surge or a new pump.
&.spark
{
border-radius: 100%;
background-color: #fff6d8;
box-shadow: 0px 0px 14px 4px rgba( 255, 214, 120, 0.75 );
}
}
// A ring thrown off the pump on every click. Mounted fresh each time so it always
// plays from the start.
// Centred by hand with half its size off the top and left. The layout engine will
// centre an absolutely positioned child off the parent's alignment, but only while no
// inset is set - too subtle a rule to hang the whole effects layer on.
.clickring
{
position: absolute;
left: 50%;
top: 50%;
margin-left: -105px;
margin-top: -105px;
width: 210px;
height: 210px;
border-radius: 100%;
border: 4px solid rgba( 190, 235, 255, 0.75 );
pointer-events: none;
animation: click-ring 0.42s ease-out;
}
@keyframes click-ring
{
0% { transform: scale( 0.42 ); opacity: 0.85; }
100% { transform: scale( 1.3 ); opacity: 0; }
}
.surgering
{
position: absolute;
left: 50%;
top: 50%;
margin-left: -150px;
margin-top: -150px;
width: 300px;
height: 300px;
border-radius: 100%;
border: 6px solid rgba( 255, 226, 150, 0.9 );
box-shadow: 0px 0px 40px 10px rgba( 255, 200, 90, 0.5 );
pointer-events: none;
animation: surge-ring 0.6s ease-out;
}
@keyframes surge-ring
{
0% { transform: scale( 0.3 ); opacity: 1; }
100% { transform: scale( 1.7 ); opacity: 0; }
}
// A wash of warm light over the whole screen for a fifth of a second.
.surgeflash
{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba( 255, 220, 150, 0.4 );
pointer-events: none;
animation: surge-flash 0.4s ease-out;
}
@keyframes surge-flash
{
0% { opacity: 0.75; }
100% { opacity: 0; }
}
// ---------------------------------------------------------------- shake
// The stage shakes, not the whole HUD. Moving the root would drag the sky and the
// dunes with it and show bare edges at the screen border.
.hud.shake-soft .stage
{
animation: shake-soft 0.32s ease-out;
}
.hud.shake-hard .stage
{
animation: shake-hard 0.32s ease-out;
}
@keyframes shake-soft
{
0% { transform: translateX( 0px ) translateY( 0px ); }
25% { transform: translateX( -4px ) translateY( 2px ); }
50% { transform: translateX( 3px ) translateY( -2px ); }
75% { transform: translateX( -2px ) translateY( 1px ); }
100% { transform: translateX( 0px ) translateY( 0px ); }
}
@keyframes shake-hard
{
0% { transform: translateX( 0px ) translateY( 0px ); }
15% { transform: translateX( -9px ) translateY( 4px ); }
32% { transform: translateX( 8px ) translateY( -5px ); }
50% { transform: translateX( -6px ) translateY( 3px ); }
70% { transform: translateX( 4px ) translateY( -2px ); }
85% { transform: translateX( -2px ) translateY( 1px ); }
100% { transform: translateX( 0px ) translateY( 0px ); }
}
// ---------------------------------------------------------------- pressure
// Sits between the pump and the nameplate. Always laid out, faded out when cold, so
// showing and hiding it never shunts the nameplate up and down the screen.
.pressure
{
flex-direction: column;
align-items: center;
margin-top: 6px;
transition: opacity 0.25s linear;
&.cold { opacity: 0.25; }
.pressure-bar
{
width: 240px;
height: 10px;
border-radius: 999px;
background-color: rgba( 0, 0, 0, 0.45 );
border: 1px solid rgba( 255, 255, 255, 0.22 );
overflow: hidden;
}
.pressure-fill
{
height: 100%;
background-color: $water;
transition: width 0.1s linear, background-color 0.3s linear;
}
.pressure-label
{
margin-top: 4px;
align-items: center;
}
.pressure-name
{
font-size: 11px;
letter-spacing: 3px;
opacity: 0.7;
margin-right: 8px;
}
.pressure-value
{
font-size: 14px;
font-weight: 800;
color: #bfe9ff;
}
// Four bands rather than a continuous ramp - a colour you can name is a colour you
// can chase, and the game tells you which band you're in without a number.
&.s1 .pressure-fill { background-color: #6fd3a0; }
&.s1 .pressure-value { color: #9ff0c6; }
&.s2 .pressure-fill { background-color: #ffb54a; }
&.s2 .pressure-value { color: #ffd08a; }
&.s3
{
.pressure-fill
{
background-color: #fff0c0;
box-shadow: 0px 0px 14px 3px rgba( 255, 200, 90, 0.8 );
}
.pressure-value
{
color: #fff2cc;
}
.pressure-bar
{
border-color: rgba( 255, 214, 120, 0.9 );
animation: pressure-max 0.8s ease-in-out infinite;
}
}
}
@keyframes pressure-max
{
0% { box-shadow: 0px 0px 0px 0px rgba( 255, 200, 90, 0.6 ); }
50% { box-shadow: 0px 0px 16px 3px rgba( 255, 200, 90, 0.6 ); }
100% { box-shadow: 0px 0px 0px 0px rgba( 255, 200, 90, 0.6 ); }
}
.nameplate
{
flex-direction: column;
align-items: center;
margin-top: 4px;
padding: 10px 30px 14px 30px;
border-radius: 22px;
background-color: rgba( 16, 20, 30, 0.38 );
.tierno
{
font-size: 14px;
letter-spacing: 3px;
opacity: 1;
text-shadow: 0px 2px 0px rgba( 0, 0, 0, 0.6 );
}
.name
{
font-size: 40px;
font-weight: 800;
text-shadow: 0px 3px 0px rgba( 0, 0, 0, 0.35 );
}
.tagline
{
font-size: 16px;
opacity: 1;
text-shadow: 0px 2px 0px rgba( 0, 0, 0, 0.55 );
}
.perclick
{
margin-top: 8px;
font-size: 15px;
background-color: rgba( 18, 22, 32, 0.55 );
padding: 4px 16px;
border-radius: 999px;
}
}
// Lives in .fx now, so left/top come in inline off the pump's centre. The fixed width
// with centred text is what lets the code place it without measuring the string - keep
// it in step with FloaterWidth in GameHud.razor.
.floater
{
position: absolute;
width: 300px;
text-align: center;
justify-content: center;
font-size: 30px;
font-weight: 800;
color: #d3f0ff;
text-shadow: 0px 2px 0px rgba( 0, 0, 0, 0.45 );
pointer-events: none;
&.coin
{
color: $gold;
}
&.surge
{
font-size: 40px;
color: #fff4d2;
letter-spacing: 2px;
text-shadow: 0px 3px 0px rgba( 120, 60, 0, 0.55 );
}
}
.toast
{
position: absolute;
bottom: 13%;
background-color: rgba( 18, 22, 32, 0.85 );
padding: 10px 22px;
border-radius: 999px;
font-size: 18px;
pointer-events: none;
}
.idlenote
{
position: absolute;
top: 5%;
background-color: rgba( 30, 90, 130, 0.85 );
padding: 8px 20px;
border-radius: 999px;
font-size: 16px;
pointer-events: none;
align-items: center;
border: 2px solid rgba( 140, 210, 255, 0.5 );
i
{
font-family: Material Icons;
font-size: 20px;
margin-right: 8px;
}
}
// Scoped to the stage on purpose - as a bare ".hint" this rule reached into the
// settings panel and threw its sub-labels to the bottom of the screen.
.stage .hint
{
position: absolute;
bottom: 4%;
font-size: 14px;
letter-spacing: 2px;
opacity: 1;
padding: 5px 16px;
border-radius: 999px;
background-color: rgba( 16, 20, 30, 0.38 );
pointer-events: none;
}
// ---------------------------------------------------------------- actions
.actions
{
height: 130px;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.bigbutton
{
width: 420px;
height: 92px;
border-radius: 22px;
align-items: center;
padding: 0px 24px;
margin: 0px 12px;
cursor: pointer;
pointer-events: all;
transition: transform 0.07s ease-out;
border-bottom: 6px solid rgba( 0, 0, 0, 0.35 );
position: relative;
overflow: hidden;
.btn-icon i
{
font-family: Material Icons;
font-size: 40px;
margin-right: 16px;
}
// A band of light that sweeps across the face. Parked off the left edge and only
// sent across when the button is actually worth pressing.
.shine
{
position: absolute;
top: 0;
left: -40%;
width: 30%;
height: 100%;
background-image: linear-gradient( to right, rgba( 255, 255, 255, 0 ) 0%, rgba( 255, 255, 255, 0.35 ) 50%, rgba( 255, 255, 255, 0 ) 100% );
opacity: 0;
}
.btn-text
{
flex-direction: column;
}
.btn-title
{
font-size: 23px;
font-weight: 800;
}
.btn-sub
{
font-size: 16px;
opacity: 0.85;
}
&:hover
{
transform: translateY( -3px );
}
&:active
{
transform: translateY( 2px );
}
&.sell
{
background-color: #3aa7e0;
&:not(.disabled) .shine
{
opacity: 1;
animation: shine-sweep 3.4s linear infinite;
}
}
&.buy
{
background-color: #f0a92c;
}
// Affordable. This is the moment the whole loop is pointed at, so it gets the
// brightest thing on screen after the pump itself.
&.buy.ready
{
animation: buy-ready 1.6s ease-in-out infinite;
.shine
{
opacity: 1;
animation: shine-sweep 2.2s linear infinite;
}
}
&.maxed
{
background-color: #7a5cc4;
cursor: default;
}
&.disabled
{
background-color: #5f6675;
opacity: 0.65;
cursor: default;
&:hover
{
transform: translateY( 0px );
}
}
}
@keyframes shine-sweep
{
0% { left: -40%; }
40% { left: 110%; }
100% { left: 110%; }
}
@keyframes buy-ready
{
0% { box-shadow: 0px 0px 0px 0px rgba( 245, 197, 66, 0.55 ); }
50% { box-shadow: 0px 0px 30px 6px rgba( 245, 197, 66, 0.5 ); }
100% { box-shadow: 0px 0px 0px 0px rgba( 245, 197, 66, 0.55 ); }
}
// ---------------------------------------------------------------- collection
.collection
{
height: 170px;
align-items: center;
justify-content: center;
padding-bottom: 14px;
flex-shrink: 0;
}
.slot
{
width: 118px;
height: 142px;
margin: 0px 6px;
padding: 8px;
flex-direction: column;
align-items: center;
justify-content: flex-end;
border-radius: 16px;
background-color: rgba( 18, 22, 32, 0.4 );
border: 3px solid rgba( 0, 0, 0, 0 );
position: relative;
transition: transform 0.12s ease-out, background-color 0.2s linear;
&:hover
{
transform: translateY( -4px );
}
.tick
{
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
border-radius: 100%;
background-color: #4f9a45;
align-items: center;
justify-content: center;
i
{
font-family: Material Icons;
font-size: 15px;
}
}
.slotart
{
width: 100%;
height: 92px;
position: relative;
align-items: center;
justify-content: center;
}
.question
{
position: absolute;
font-size: 38px;
font-weight: 800;
color: rgba( 255, 255, 255, 0.7 );
}
.slotlabel
{
margin-top: 4px;
font-size: 12px;
text-align: center;
opacity: 0.9;
}
&.owned
{
background-color: rgba( 42, 92, 60, 0.5 );
}
&.next
{
border-color: $gold;
box-shadow: 0px 0px 16px 2px rgba( 245, 197, 66, 0.4 );
animation: slot-next 2s ease-in-out infinite;
}
}
// The glow is a fixed shadow and the pulse is a colour. Animating the blur radius
// instead makes the renderer rebuild a blurred quad on every frame of every second the
// game is open, and this one is always on because there is always a next pump.
//
// The border, specifically. .slot animates neither - it transitions transform for its
// hover lift and background-color for its owned state, and a keyframe on either of
// those would fight something: an animation outranks the hover for as long as it runs,
// which would have quietly killed the lift on the one slot the player is most likely
// to point at.
@keyframes slot-next
{
0% { border-color: rgba( 245, 197, 66, 0.45 ); }
50% { border-color: rgba( 255, 226, 140, 1 ); }
100% { border-color: rgba( 245, 197, 66, 0.45 ); }
}
// ---------------------------------------------------------------- fallback
.missing
{
position: absolute;
top: 40%;
left: 50%;
margin-left: -260px;
width: 520px;
flex-direction: column;
align-items: center;
background-color: rgba( 18, 22, 32, 0.85 );
border-radius: 18px;
padding: 24px;
.missing-title
{
font-size: 22px;
font-weight: 700;
}
.missing-body
{
font-size: 15px;
opacity: 0.8;
margin-top: 6px;
}
}
// ---------------------------------------------------------------- scenery
// Extra layers the default scene doesn't use - each background switches them on.
.stars,
.scenery
{
position: absolute;
pointer-events: none;
}
.stars
{
top: 0;
left: 0;
width: 100%;
height: 60%;
opacity: 0;
}
.scenery
{
bottom: 0;
left: 0;
width: 100%;
height: 40%;
opacity: 0;
}
// ---- Blood Moon: night dunes under something large and red
.scene-dusk
{
.sky { background-image: linear-gradient( to bottom, #150a24 0%, #241436 40%, #5c2536 78%, #8c3a35 100% ); }
.sun
{
background-color: #d94f43;
box-shadow: 0px 0px 120px 40px rgba( 217, 79, 67, 0.4 );
width: 210px;
height: 210px;
}
.dune.far { background-color: #45283f; }
.dune.near { background-color: #45283f; }
.floor { background-color: #2c1a2c; }
// a scatter of stars, faked with a few hard-stop gradients
.stars
{
opacity: 0.9;
background-image: linear-gradient( to bottom, rgba( 255, 255, 255, 0.16 ) 0%, rgba( 255, 255, 255, 0 ) 70% );
}
.nameplate .name { text-shadow: 0px 3px 0px rgba( 0, 0, 0, 0.55 ); }
}
// ---- Green Oasis: you pumped enough that things started growing
.scene-oasis
{
.sky { background-image: linear-gradient( to bottom, #2f8fbf 0%, #6fc6c2 45%, #a9d98d 80%, #6ea84e 100% ); }
.sun
{
background-color: #fff2a8;
box-shadow: 0px 0px 100px 34px rgba( 255, 242, 168, 0.45 );
}
.dune.far { background-color: #67ab55; }
.dune.near { background-color: #4f8f3a; }
.floor { background-color: #3d7330; }
.scenery
{
opacity: 1;
background-image: linear-gradient( to top, rgba( 40, 90, 40, 0.55 ) 0%, rgba( 40, 90, 40, 0 ) 55% );
}
}
// ---- Neon Wastes: the desert, some time after everyone left
.scene-neon
{
.sky { background-image: linear-gradient( to bottom, #12042a 0%, #2a0b45 42%, #7b2062 76%, #ff5fa2 100% ); }
.sun
{
background-color: #ff7ac6;
box-shadow: 0px 0px 140px 46px rgba( 255, 95, 162, 0.45 );
width: 220px;
height: 220px;
}
.dune.far { background-color: #35114f; }
.dune.near { background-color: #24093a; }
.floor { background-color: #160526; }
.stars
{
opacity: 0.8;
background-image: linear-gradient( to bottom, rgba( 255, 255, 255, 0.12 ) 0%, rgba( 255, 255, 255, 0 ) 65% );
}
// horizon glow standing in for a synth grid
.scenery
{
opacity: 1;
background-image: linear-gradient( to top, rgba( 255, 95, 162, 0.35 ) 0%, rgba( 255, 95, 162, 0 ) 40% );
}
.nameplate .name { color: #ffd9ec; }
}
// ---------------------------------------------------------------- tier bar
.tierbar
{
width: 260px;
height: 8px;
border-radius: 999px;
background-color: rgba( 0, 0, 0, 0.45 );
border: 1px solid rgba( 255, 255, 255, 0.25 );
margin-top: 10px;
overflow: hidden;
}
.tierfill
{
height: 100%;
background-color: $gold;
transition: width 0.2s ease-out;
}
// ---------------------------------------------------------------- gusher
.gusher
{
position: absolute;
width: 96px;
height: 96px;
margin-left: -48px;
margin-top: -48px;
align-items: center;
justify-content: center;
cursor: pointer;
pointer-events: all;
animation: gusher-bob 1.6s ease-in-out infinite;
// Teardrop rather than a disc, and blue-white rather than yellow - as a glowing
// yellow circle it was indistinguishable from the sun behind it.
.gusher-drop
{
width: 46px;
height: 58px;
border-radius: 50% 50% 50% 50%;
background-image: linear-gradient( to bottom, #eaf9ff, #4fb0e8 );
border: 3px solid #ffffff;
box-shadow: 0px 0px 34px 10px rgba( 79, 176, 232, 0.6 );
}
.gusher-ring
{
position: absolute;
width: 92px;
height: 92px;
border-radius: 100%;
border: 3px solid rgba( 160, 225, 255, 0.7 );
animation: gusher-pulse 1.6s ease-out infinite;
}
&:hover .gusher-drop { transform: scale( 1.12 ); }
}
@keyframes gusher-bob
{
0% { transform: translateY( 0px ); }
50% { transform: translateY( -12px ); }
100% { transform: translateY( 0px ); }
}
@keyframes gusher-pulse
{
0% { transform: scale( 0.7 ); opacity: 0.9; }
100% { transform: scale( 1.25 ); opacity: 0; }
}
// ---------------------------------------------------------------- daily
.daily
{
position: absolute;
top: 110px;
left: 28px;
align-items: center;
background-color: rgba( 24, 30, 44, 0.9 );
border: 3px solid $gold;
border-radius: 18px;
padding: 12px 20px 12px 14px;
cursor: pointer;
pointer-events: all;
animation: daily-glow 2.2s ease-in-out infinite;
.daily-icon
{
margin-right: 12px;
i
{
font-family: Material Icons;
font-size: 34px;
color: $gold;
}
}
.daily-text { flex-direction: column; }
.daily-title { font-size: 16px; font-weight: 800; letter-spacing: 1px; }
.daily-sub { font-size: 13px; opacity: 0.8; }
&:hover { transform: scale( 1.03 ); }
}
@keyframes daily-glow
{
0% { box-shadow: 0px 0px 0px 0px rgba( 245, 197, 66, 0.5 ); }
50% { box-shadow: 0px 0px 26px 4px rgba( 245, 197, 66, 0.45 ); }
100% { box-shadow: 0px 0px 0px 0px rgba( 245, 197, 66, 0.5 ); }
}
// ---------------------------------------------------------------- the tree
.treearea
{
position: absolute;
left: 34px;
bottom: 14%;
align-items: flex-end;
pointer-events: all;
}
.tree
{
width: 118px;
height: 150px;
position: relative;
cursor: pointer;
pointer-events: all;
background-repeat: no-repeat;
background-position: bottom center;
background-size: contain;
transition: transform 0.25s ease-out;
&:hover { transform: scale( 1.06 ); }
&.growing { animation: tree-sway 3.4s ease-in-out infinite; }
}
@keyframes tree-sway
{
0% { transform: rotate( -1.5deg ); }
50% { transform: rotate( 1.5deg ); }
100% { transform: rotate( -1.5deg ); }
}
.treeinfo
{
flex-direction: column;
margin-left: 10px;
margin-bottom: 8px;
background-color: rgba( 18, 22, 32, 0.7 );
border-radius: 14px;
padding: 10px 12px;
.treeheight
{
font-size: 15px;
font-weight: 700;
align-items: center;
i
{
font-family: Material Icons;
font-size: 18px;
margin-right: 6px;
color: #7fd06f;
}
}
.treebar
{
width: 130px;
height: 7px;
border-radius: 999px;
background-color: rgba( 0, 0, 0, 0.35 );
margin-top: 6px;
overflow: hidden;
}
.treefill
{
height: 100%;
background-color: #7fd06f;
transition: width 0.2s ease-out;
}
.treestatus
{
font-size: 12px;
opacity: 0.75;
margin-top: 4px;
&.growing { color: #7fd06f; opacity: 1; }
}
.treebtn
{
margin-top: 8px;
padding: 6px 12px;
border-radius: 10px;
background-color: #4f9a45;
font-size: 13px;
font-weight: 700;
align-items: center;
justify-content: center;
cursor: pointer;
pointer-events: all;
i
{
font-family: Material Icons;
font-size: 16px;
margin-right: 6px;
}
&:hover { background-color: #5cae50; }
&.board { background-color: #3aa7e0; }
&.board:hover { background-color: #4bb8f0; }
&.disabled
{
background-color: #5f6675;
opacity: 0.6;
cursor: default;
}
}
}
// ---- Sandstorm: ochre haze, the horizon eaten by dust
.scene-sandstorm
{
.sky { background-image: linear-gradient( to bottom, #d9ae6b 0%, #c99a52 45%, #a97c3c 78%, #7a5326 100% ); }
.sun { background-color: #f0d9a6; box-shadow: 0px 0px 130px 50px rgba( 240, 217, 166, 0.3 ); }
.dune.far { background-color: #a9803f; }
.dune.near { background-color: #96702f; }
.floor { background-color: #7a5326; }
.scenery
{
opacity: 1;
background-image: linear-gradient( to top, rgba( 200, 160, 90, 0.4 ) 0%, rgba( 200, 160, 90, 0 ) 60% );
}
}
// ---- Golden Hour: the twenty minutes a day the desert looks kind
.scene-golden
{
.sky { background-image: linear-gradient( to bottom, #5b3a86 0%, #c1476a 38%, #f2884b 74%, #f7b167 100% ); }
.sun { background-color: #fff0c4; box-shadow: 0px 0px 120px 44px rgba( 255, 200, 120, 0.5 ); }
.dune.far { background-color: #a4553f; }
.dune.near { background-color: #8c4436; }
.floor { background-color: #6d3630; }
}
// ---- Starfield: no moon, no cloud, rather a lot of sky
.scene-starfield
{
.sky { background-image: linear-gradient( to bottom, #04060f 0%, #070b1c 40%, #18213f 75%, #2b3560 100% ); }
.sun
{
background-color: #dfe6ff;
width: 90px;
height: 90px;
box-shadow: 0px 0px 70px 22px rgba( 223, 230, 255, 0.35 );
}
.dune.far { background-color: #1b2340; }
.dune.near { background-color: #141a30; }
.floor { background-color: #0d1122; }
.stars
{
opacity: 1;
background-image: linear-gradient( to bottom, rgba( 255, 255, 255, 0.22 ) 0%, rgba( 255, 255, 255, 0 ) 75% );
}
}
// ---- Aurora: green light over the ice
.scene-aurora
{
.sky { background-image: linear-gradient( to bottom, #04121f 0%, #0b2c3a 35%, #1c6b63 70%, #3fd6a0 100% ); }
.sun { background-color: #b9f5e2; box-shadow: 0px 0px 110px 40px rgba( 100, 230, 190, 0.4 ); }
.dune.far { background-color: #1d5a5a; }
.dune.near { background-color: #16414a; }
.floor { background-color: #0f2c35; }
.stars
{
opacity: 1;
background-image: linear-gradient( to bottom, rgba( 120, 255, 210, 0.18 ) 0%, rgba( 120, 255, 210, 0 ) 70% );
}
.scenery
{
opacity: 1;
background-image: linear-gradient( to top, rgba( 63, 214, 160, 0.3 ) 0%, rgba( 63, 214, 160, 0 ) 45% );
}
}
// ---- Volcanic: warm rock, water comes up boiling
.scene-volcanic
{
.sky { background-image: linear-gradient( to bottom, #120404 0%, #1a0806 40%, #7d2412 78%, #e2542a 100% ); }
.sun { background-color: #ff8a4a; box-shadow: 0px 0px 130px 46px rgba( 226, 84, 42, 0.5 ); }
.dune.far { background-color: #3a1410; }
.dune.near { background-color: #2a0d0a; }
.floor { background-color: #1c0806; }
.scenery
{
opacity: 1;
background-image: linear-gradient( to top, rgba( 226, 84, 42, 0.45 ) 0%, rgba( 226, 84, 42, 0 ) 40% );
}
}
// ---- Crystal Cavern: deep enough that the walls glow
.scene-crystal
{
.sky { background-image: linear-gradient( to bottom, #0a0620 0%, #160d33 40%, #3f2a70 76%, #7f6ae0 100% ); }
.sun { background-color: #cfc2ff; box-shadow: 0px 0px 120px 42px rgba( 160, 140, 255, 0.45 ); }
.dune.far { background-color: #2e2059; }
.dune.near { background-color: #241947; }
.floor { background-color: #170f2e; }
.stars
{
opacity: 1;
background-image: linear-gradient( to bottom, rgba( 190, 170, 255, 0.2 ) 0%, rgba( 190, 170, 255, 0 ) 70% );
}
}
// ---------------------------------------------------------------- new pump
// Buying a pump is the reward the whole loop points at, and it used to be a line of
// toast text. Two and a half seconds of the screen stopping to say so instead.
.celebrate
{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
pointer-events: none;
animation: celebrate-fade 2.6s ease-out;
.celebrate-ring
{
position: absolute;
left: 50%;
top: 50%;
margin-left: -210px;
margin-top: -210px;
width: 420px;
height: 420px;
border-radius: 100%;
border: 8px solid rgba( 255, 226, 150, 0.85 );
animation: celebrate-ring 1.1s ease-out;
&.two
{
margin-left: -130px;
margin-top: -130px;
width: 260px;
height: 260px;
border-width: 5px;
border-color: rgba( 255, 255, 255, 0.8 );
animation: celebrate-ring 1.5s ease-out;
}
}
.celebrate-card
{
flex-direction: column;
align-items: center;
background-color: rgba( 16, 20, 30, 0.86 );
border: 3px solid $gold;
border-radius: 24px;
padding: 20px 46px;
animation: celebrate-card 2.6s ease-out;
}
.celebrate-kicker
{
font-size: 14px;
letter-spacing: 5px;
color: $gold;
font-weight: 700;
}
.celebrate-name
{
font-size: 46px;
font-weight: 800;
margin-top: 2px;
text-shadow: 0px 3px 0px rgba( 0, 0, 0, 0.45 );
}
.celebrate-sub
{
font-size: 16px;
opacity: 0.8;
margin-top: 2px;
}
}
@keyframes celebrate-fade
{
0% { opacity: 1; }
80% { opacity: 1; }
100% { opacity: 0; }
}
@keyframes celebrate-ring
{
0% { transform: scale( 0.2 ); opacity: 0.95; }
100% { transform: scale( 1.6 ); opacity: 0; }
}
@keyframes celebrate-card
{
0% { transform: scale( 0.6 ); opacity: 0; }
9% { transform: scale( 1.08 ); opacity: 1; }
16% { transform: scale( 1 ); opacity: 1; }
86% { transform: scale( 1 ); opacity: 1; }
100% { transform: scale( 1 ); opacity: 0; }
}
// ---------------------------------------------------------------- scene weather
// The ambient layers are shared markup, so each scene only has to say what its own
// air is doing. Dawn is the baseline: clouds, a little haze off the sand.
.scene-dawn
{
.shimmer { opacity: 0.7; }
}
.scene-dusk
{
.cloud { background-color: rgba( 120, 70, 90, 0.5 ); box-shadow: 0px 0px 50px 24px rgba( 120, 70, 90, 0.3 ); }
.mote { background-color: rgba( 255, 190, 190, 0.4 ); }
.shimmer { opacity: 0.25; }
}
.scene-oasis
{
.cloud { background-color: rgba( 255, 255, 255, 0.7 ); }
// Seeds and pollen rather than sand, drifting rather than blowing through.
.mote { background-color: rgba( 220, 255, 190, 0.7 ); }
.shimmer { opacity: 0; }
}
.scene-neon
{
.cloud { background-color: rgba( 255, 120, 190, 0.28 ); box-shadow: 0px 0px 60px 30px rgba( 255, 95, 162, 0.22 ); }
.mote { background-color: rgba( 255, 150, 220, 0.75 ); }
.shimmer { opacity: 0.4; }
}
// Enough dust in the air that the clouds have gone and the haze has taken over.
.scene-sandstorm
{
.cloud { background-color: rgba( 220, 190, 140, 0.4 ); box-shadow: 0px 0px 70px 40px rgba( 200, 160, 90, 0.3 ); }
.mote { background-color: rgba( 255, 230, 180, 0.85 ); width: 7px; height: 7px; }
.shimmer { opacity: 1; }
}
.scene-golden
{
.cloud { background-color: rgba( 255, 210, 180, 0.55 ); box-shadow: 0px 0px 60px 26px rgba( 255, 170, 120, 0.35 ); }
.mote { background-color: rgba( 255, 220, 170, 0.7 ); }
.shimmer { opacity: 0.6; }
}
// No cloud and no wind - the motes hold still enough to read as stars.
.scene-starfield
{
.clouds { opacity: 0; }
.mote { background-color: rgba( 235, 240, 255, 0.9 ); }
.shimmer { opacity: 0; }
}
.scene-aurora
{
.cloud { background-color: rgba( 120, 255, 210, 0.2 ); box-shadow: 0px 0px 70px 34px rgba( 100, 230, 190, 0.22 ); }
.mote { background-color: rgba( 235, 255, 250, 0.85 ); width: 6px; height: 6px; }
.shimmer { opacity: 0.3; }
}
// Embers, and air you can see moving.
.scene-volcanic
{
.cloud { background-color: rgba( 90, 40, 30, 0.5 ); box-shadow: 0px 0px 70px 34px rgba( 226, 84, 42, 0.25 ); }
.mote { background-color: rgba( 255, 160, 90, 0.9 ); box-shadow: 0px 0px 8px 2px rgba( 255, 120, 50, 0.6 ); }
.shimmer { opacity: 1; }
}
.scene-crystal
{
.clouds { opacity: 0; }
.mote { background-color: rgba( 210, 190, 255, 0.85 ); box-shadow: 0px 0px 8px 2px rgba( 170, 140, 255, 0.5 ); }
.shimmer { opacity: 0.35; }
}