<div class="device" id="device">
<div class="number" id="number">42</div>
<div class="thermostat">
<div class="bar"></div>
<div class="glass-container">
<div class="glass"></div>
<div class="liquid">
<div class="bg"></div>
<div class="bubbles"></div>
</div>
<div class="glass-reflection"></div>
</div>
<div class="slider" id="slider"></div>
</div>
</div>
// CSS Future Tip 🔮 https://developer.mozilla.org/en-US/docs/Web/CSS/@property
@supports (background: paint(houdini)) {
@property --progress {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
@property --x {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
@property --y {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
}
:root {
--c1: #00adff;
--c2: #39d469;
--c3: #ffeb00;
--c4: #ff8100;
--c5: #b50f0f;
--surface: #fffeff;
--on-surface: #636571;
--outline: #000001;
--background: #112;
--v: 1;
}
.thermostat {
height: 24vmin;
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1vmin;
position: relative;
place-items: center;
}
.liquid {
position: absolute;
inset: 0;
border-radius: 10vmin;
overflow: hidden;
isolation: isolate; // fix for Safari's clip path + overflow issue
.bg {
position: absolute;
inset: 0;
&:before,
&:after {
content: "";
position: absolute;
inset: 0;
--gp: calc(100% - var(--progress));
background: linear-gradient(
to bottom,
var(--c5) calc(5% - var(--gp)),
var(--c4) calc(25% - var(--gp)),
var(--c3) calc(50% - var(--gp)),
var(--c2) calc(75% - var(--gp)),
var(--c1) calc(100% - var(--gp))
);
transform: translateY(calc(100% - var(--progress)));
animation: wave 1s linear infinite;
transition: all 0.3s linear;
filter: saturate(0.65);
}
&:after {
transform: scaleX(-1) translateY(calc(100% - var(--progress)));
opacity: 0; // initial state before animation with delay
--o: 0.6; // opacity during animation
animation-delay: 0.55s;
}
}
}
// Amazing tool to generate different polygons: https://wave.novoselski.net/
@keyframes wave {
from {
opacity: var(--o, 1);
clip-path: polygon(
100% 100%,
0% 100%,
0% 27.83%,
7.14% 28.61%,
14.29% 28.98%,
21.43% 28.9%,
28.57% 28.4%,
35.71% 27.49%,
42.86% 26.22%,
50% 24.65%,
57.14% 22.86%,
64.29% 20.95%,
71.43% 19%,
78.57% 17.12%,
85.71% 15.4%,
92.86% 13.92%,
100% 12.77%
);
}
25% {
clip-path: polygon(
100% 100%,
0% 100%,
0% 17.77%,
7.14% 16.02%,
14.29% 14%,
21.43% 11.82%,
28.57% 9.58%,
35.71% 7.4%,
42.86% 5.38%,
50% 3.63%,
57.14% 2.23%,
64.29% 1.26%,
71.43% 0.76%,
78.57% 0.76%,
85.71% 1.26%,
92.86% 2.23%,
100% 3.63%
);
}
50% {
clip-path: polygon(
100% 100%,
0% 100%,
0% 2.21%,
7.14% 1.34%,
14.29% 0.93%,
21.43% 1.01%,
28.57% 1.58%,
35.71% 2.6%,
42.86% 4.03%,
50% 5.8%,
57.14% 7.81%,
64.29% 9.97%,
71.43% 12.16%,
78.57% 14.28%,
85.71% 16.22%,
92.86% 17.88%,
100% 19.19%
);
}
to {
opacity: var(--o, 1);
clip-path: polygon(
100% 100%,
0% 100%,
0% 31.33%,
7.14% 32.11%,
14.29% 32.48%,
21.43% 32.4%,
28.57% 31.9%,
35.71% 30.99%,
42.86% 29.72%,
50% 28.15%,
57.14% 26.36%,
64.29% 24.45%,
71.43% 22.5%,
78.57% 20.62%,
85.71% 18.9%,
92.86% 17.42%,
100% 16.27%
);
}
}
@keyframes b1 {
from {
--x: 50%;
--y: 60%;
opacity: 0;
}
50% {
--x: 60%;
--y: 45%;
opacity: 1;
transform: scale(1.1);
}
to {
--x: 50%;
--y: 30%;
opacity: 0;
}
}
@keyframes b2 {
from {
--x: 50%;
--y: 80%;
opacity: 0;
}
50% {
--x: 10%;
--y: 55%;
opacity: 1;
transform: scale(1.1);
}
to {
--x: 60%;
--y: 30%;
opacity: 0;
}
}
@supports (background: paint(houdini)) {
.bubbles {
position: absolute;
inset: 0;
&:before {
content: "";
position: absolute;
inset: 0;
--x: 50%;
--y: 80%;
background: radial-gradient(
0.02vmin at var(--x) var(--y),
hsla(0, 0, 100%, 0.8) .2vmin,
transparent .4vmin,
transparent
)
center center no-repeat,
radial-gradient(
0.03vmin at calc(var(--x) * 1.9) calc(var(--y) * 1.9),
hsla(0, 0, 100%, 0.8) .2vmin,
transparent .4vmin,
transparent
)
center center no-repeat;
mix-blend-mode: soft-light;
animation: b2 linear 3.5s infinite;
}
&:after {
content: "";
position: absolute;
inset: 0;
--x: 30%;
--y: 60%;
background: radial-gradient(
0.02vmin at var(--x) var(--y),
hsla(0, 0, 100%, 0.1) 0.1vmin,
transparent .4vmin,
transparent
)
center center no-repeat,
radial-gradient(
0.03vmin at calc(var(--x) * 1.2) calc(var(--y) * 1.5),
hsla(0, 0, 100%, 0.1) .1vmin,
transparent .4vmin,
transparent
)
center center no-repeat;
mix-blend-mode: soft-light;
animation: b1 linear 6s infinite;
}
}
}
.glass {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.1);
border-radius: 10vmin;
}
.glass-container {
height: 24vmin;
width: 6vmin;
border-radius: 10vmin;
position: relative;
border: 0.1vmin solid hsla(0, 0, 100%, 0.2);
box-shadow: -1px 0.1vmin 0 1px hsla(0, 0, 100%, 0.5),
1px 0.1vmin 0 1px hsla(0, 0, 100%, 0.2);
&:before {
border-radius: 10vmin;
content: "";
position: absolute;
--size: 2vmin;
inset: calc(var(--size) * -1);
border-left: var(--size) solid hsla(0, 0, 100%, 0.5);
border-right: var(--size) solid hsla(0, 0, 50%, 0.2);
filter: blur(0.5vmin);
}
}
.glass-reflection {
pointer-events: none;
mix-blend-mode: overlay;
position: absolute;
border-radius: 10vmin;
overflow: hidden;
inset: 0;
// blurred soft lights
&:before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
to right,
hsla(0, 0, 100%, 0.5),
hsla(0, 0, 100%, 0.5)
)
no-repeat,
linear-gradient(to right, hsla(0, 0, 100%, 0.5), hsla(0, 0, 100%, 0.1))
no-repeat;
background-size: 0.5vmin 19vmin, 1.8vmin 21vmin;
background-position: 1vmin 3vmin, 3vmin 2vmin;
filter: blur(0.4vmin);
}
// hard light
&:after {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(
4vmin 14vmin at 1vmin 20%,
hsla(0, 0, 100%, 0.3) 10%,
transparent 50%,
transparent
),
radial-gradient(
2vmin 2vmin at 2vmin 5%,
hsla(0, 0, 100%, 0.9),
transparent,
transparent
),
radial-gradient(
7vmin 7vmin at 2vmin 5%,
hsla(0, 0, 100%, 0.9),
transparent,
transparent
);
}
}
.number {
font-size: 7vmin;
font-weight: bold;
color: #e8eaec;
position: relative;
text-shadow: -0.5vmin -0.5vmin 1vmin hsla(0, 0, 100%, 0.4),
0.5vmin 0.5vmin 0.8vmin hsla(0, 0, 0, 0.2);
font-variant-numeric: tabular-nums;
&:after {
content: "°";
position: absolute;
}
}
.slider {
place-self: center;
--gp: calc(100% - var(--progress));
--pos: 0px;
background: linear-gradient(to bottom, #333 var(--gp), white var(--gp));
box-shadow: -0.1vmin 0 0 0.1vmin hsla(0, 0, 0, 0.08);
width: 0.1vmin;
border-radius: 1vmin;
height: 20vmin;
position: relative;
// click area
&:before {
content: "";
position: absolute;
inset: -1vmin -2vmin;
}
&:after {
content: "";
position: absolute;
top: calc(10vmin * var(--gp) / 100);
width: 3vmin;
height: 3vmin;
background: radial-gradient(30% 30% at 50% 50%, white, transparent),
radial-gradient(70% 70% at 50% 50%, #f6f0f0, #bfc1cd), white;
border-radius: 50%;
transform: translate(-50%, var(--pos, 0));
border-left: 0.1vmin solid white;
box-shadow: -0.5vmin -0.5vmin 1vmin hsla(0, 0, 100%, 0.8),
0.5vmin 0.5vmin 1vmin hsla(0, 0, 10%, 0.2);
}
}
.device {
background: transparent;
width: 25svmin;
aspect-ratio: 3/ 5.5;
border-radius: 4vmin;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2.5vmin;
--progress: 100%;
transform: scale(1.5);
}
body {
height: 100vh;
width: 100vw;
background: linear-gradient(135deg, #f7fbfc, #bcc7d3);
display: grid;
place-items: center;
position: relative;
font-family: "Inter", "Helvetica Neue", "Helvetica", sans-serif;
&:after {
content: "";
position: absolute;
inset: 0;
background: url(https://assets.codepen.io/907471/noise.svg);
mix-blend-mode: overlay;
pointer-events: none;
}
}
* {
box-sizing: border-box;
}
View Compiled
const number = document.getElementById("number");
const device = document.getElementById("device");
const slider = document.getElementById("slider");
const random = (min, max) => {
return Math.random() * (max - min) + min;
};
const interval = setInterval(() => {
const offsetY = random(0, slider.offsetHeight - 20);
requestAnimationFrame(() => update(offsetY));
}, 2000);
const update = (offsetY) => {
const pos = Math.max(0, Math.min(slider.offsetHeight - 20, offsetY));
slider.style.setProperty("--pos", `${pos}px`);
const progress = Math.max(
0,
Math.min(100, 100 - (offsetY / slider.offsetHeight) * 100)
);
number.innerText = Math.round(progress * 0.42);
device.style.setProperty("--progress", `${progress}%`);
};
let pointerdown = false;
slider.addEventListener("pointerdown", () => {
pointerdown = true;
clearInterval(interval);
});
slider.addEventListener("pointermove", ({ offsetY }) => {
if (pointerdown) {
update(offsetY);
}
});
slider.addEventListener("pointerup", () => {
pointerdown = false;
});
slider.addEventListener("pointerleave", () => {
pointerdown = false;
clearInterval(interval);
});
This Pen doesn't use any external CSS resources.