<div class="welcome">
<h1>Don't forget to spin the can!</h1>
<button id="stop-animation">OMG STOP FLASHING!</button>
</div>
<img class="logo" src="https://beavertownbrewery.co.uk/cdn/shop/files/Text_Logo.png" width="1014" height="125"/>
<form>
<fieldset>
<legend>Select a flavour!</legend>
<div>
<input type="radio" id="neck-oil" name="flavours" value="neck-oil" checked />
<label for="neck-oil">Neck Oil</label>
</div>
<div>
<input type="radio" id="gamma-ray" name="flavours" value="gamma-ray" />
<label for="gamma-ray">Gamma Ray</label>
</div>
<div>
<input type="radio" id="lupuloid" name="flavours" value="lupuloid" />
<label for="lupuloid">Lupuloid</label>
</div>
<div>
<input type="radio" id="smog-rocket" name="flavours" value="smog-rocket" />
<label for="smog-rocket">Smog Rocket</label>
</div>
<div>
<input type="radio" id="bloody-ell" name="flavours" value="bloody-ell" />
<label for="bloody-ell">Bloody 'Ell</label>
</div>
<div>
<input type="radio" id="8-ball" name="flavours" value="8-ball" />
<label for="8-ball">8 Ball Rye</label>
</div>
</fieldset>
</form>
<div class="can">
<div class="can__head">
<svg class="can__topDetail" height="100" viewBox="0 0 100 100" width="100">
<path class="can__topDetailOuter" d="M50,86.39c-39.09,0-22.42-39.67-18.81-57.27,3.6-17.61,18.81-16.97,18.81-16.97,0,0,15.21-.64,18.81,16.97,3.61,17.6,20.28,57.27-18.81,57.27Z"/>
<path class="can__topDetailEngraveOuter" d="M73.32,68.35c0,12.38-15.69,15.12-23.32,15.12s-23.32-2.74-23.32-15.12c0-21.31,15.33-14.69,23.32-14.69s23.32-6.62,23.32,14.69Z"/>
<path class="can__topDetailEngraveCenter" d="M71.85,68.33c0,11.6-14.7,14.17-21.85,14.17s-21.85-2.57-21.85-14.17c0-19.97,14.37-13.77,21.85-13.77s21.85-6.2,21.85,13.77Z"/>
<path class="can__topDetailEngraveInner" d="M63.7,65.28c0,4.71-3.14,8.62-13.71,8.69-10.57-.07-13.7-3.98-13.7-8.69s3.1-1.63,6.68,0c3.11,1.41,4.61,1.54,7.02,1.54s3.92-.13,7.03-1.54c3.58-1.63,6.68-4.76,6.68,0Z"/>
<path class="can__topDetailRingpull" d="M65.63,29.06c0-13.76-15.63-13.11-15.63-13.11,0,0-15.63-.65-15.63,13.11s-2.16,36.74,15.63,36.74,15.63-22.99,15.63-36.74ZM50,53.66c-1.95,0-3.54-1.58-3.54-3.54s1.58-3.54,3.54-3.54,3.54,1.58,3.54,3.54-1.58,3.54-3.54,3.54ZM59.62,51.43c0,.9-.73,1.63-1.63,1.63s-1.64-.73-1.64-1.63c0-9.94-6.35-9.55-6.35-9.55,0,0-6.35-.39-6.35,9.55,0,.9-.73,1.63-1.64,1.63s-1.63-.73-1.63-1.63c0,0-1.56-12.81,9.62-12.81s9.62,12.81,9.62,12.81ZM50,34.93c-11.45,0-11.36-2.67-11.36-8.25s11.36-5.99,11.36-5.99c0,0,11.36.42,11.36,5.99s.09,8.25-11.36,8.25Z"/>
</svg>
</div>
<div class="can__body">
<div style="--bg-x: 0;">
<div style="--bg-x: calc(100% / 35);">
<div style="--bg-x: calc((100% / 35) * 2);">
<div style="--bg-x: calc((100% / 35) * 3);">
<div style="--bg-x: calc((100% / 35) * 4);">
<div style="--bg-x: calc((100% / 35) * 5);">
<div style="--bg-x: calc((100% / 35) * 6);">
<div style="--bg-x: calc((100% / 35) * 7);">
<div style="--bg-x: calc((100% / 35) * 8);">
<div style="--bg-x: calc((100% / 35) * 9);">
<div style="--bg-x: calc((100% / 35) * 10);">
<div style="--bg-x: calc((100% / 35) * 11);">
<div style="--bg-x: calc((100% / 35) * 12);">
<div style="--bg-x: calc((100% / 35) * 13);">
<div style="--bg-x: calc((100% / 35) * 14);">
<div style="--bg-x: calc((100% / 35) * 15);">
<div style="--bg-x: calc((100% / 35) * 16);">
<div style="--bg-x: calc((100% / 35) * 17);">
<div style="--bg-x: calc((100% / 35) * 18);">
<div style="--bg-x: calc((100% / 35) * 19);">
<div style="--bg-x: calc((100% / 35) * 20);">
<div style="--bg-x: calc((100% / 35) * 21);">
<div style="--bg-x: calc((100% / 35) * 22);">
<div style="--bg-x: calc((100% / 35) * 23);">
<div style="--bg-x: calc((100% / 35) * 24);">
<div style="--bg-x: calc((100% / 35) * 25);">
<div style="--bg-x: calc((100% / 35) * 26);">
<div style="--bg-x: calc((100% / 35) * 27);">
<div style="--bg-x: calc((100% / 35) * 28);">
<div style="--bg-x: calc((100% / 35) * 29);">
<div style="--bg-x: calc((100% / 35) * 30);">
<div style="--bg-x: calc((100% / 35) * 31);">
<div style="--bg-x: calc((100% / 35) * 32);">
<div style="--bg-x: calc((100% / 35) * 33);">
<div style="--bg-x: calc((100% / 35) * 34);">
<div style="--bg-x: calc((100% / 35) * 35);">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="can__bottom"></div>
</div>
/* Uncomment below to see what is going on. */
/* *,
*::before,
*::after {
outline: 1px dotted red;
} */
:root {
/* Adjust the size of the can if you want! */
--relative-unit-size: min(0.75dvw, 0.75dvh);
/* Can artwork - shout out to https://readlagom.com/stories/beavertown-the-art-of-the-can.html for the can wrap images. */
--neck-oil-can-bg: url(https://readlagom.com/images/beavertown-Artwork-01.jpg);
--gamma-ray-can-bg: url(https://readlagom.com/images/beavertown-Gamma-Ray.jpg);
--lupuloid-can-bg: url('https://readlagom.com/images/beavertown-Lupuloid-110716.jpg');
--smog-rocket-can-bg: url('https://readlagom.com/images/beavertown-Smog-Rocket-original.jpg');
--bloody-ell-can-bg: url('https://readlagom.com/images/beavertown-Bloody-can-outlines-original.jpg');
--8-ball-can-bg: url(https://readlagom.com/images/beavertown-8ball.jpg);
/* Default can artwork. */
--can-bg: var(--neck-oil-can-bg);
/* Colour vars. */
--c-tin-silver: oklch(84% 0.01 128deg);
--c-neck-oil-tin-accent: oklch(71% 0.15 74deg);
--c-neck-oil-ringpull: oklch(55% 0.2 32deg);
--c-gamma-ray-tin-accent: oklch(55% 0.2 32deg);
--c-gamma-ray-ringpull: oklch(72% 0.09 223deg);
--c-lupuloid-tin-accent: oklch(54% 0.19 12deg);
--c-lupuloid-ringpull: oklch(60% 0.12 157deg);
--c-smog-rocket-tin-accent: oklch(31% 0.01 287deg);
--c-smog-rocket-ringpull: oklch(31% 0.01 287deg);
--c-bloody-ell-tin-accent: oklch(55% 0.2 32deg);
--c-bloody-ell-ringpull: oklch(55% 0.2 32deg);
--c-8-ball-tin-accent: var(--c-tin-silver);
--c-8-ball-ringpull: var(--c-tin-silver);
/* Default colours. */
--c-tin-accent: var(--c-neck-oil-tin-accent);
--c-ringpull: var(--c-neck-oil-ringpull);
/**
* Probably leave these alone or don't. You do you.
*/
/* Dimension vars. */
--pi: 3.1416;
--can-height: 103.2;
--can-head-size: calc((var(--can-body-diamater) / 100) * 86);
--can-upper-lip-height: 12;
--can-bottom-size: calc((var(--can-body-diamater) / 100) * 95);
--can-lower-lip-height: calc(var(--can-upper-lip-height) / 3);
--can-body-diamater: 66.3;
--can-body-circumfrence: calc(var(--can-body-diamater) * var(--pi));
}
/* General layout styles. */
body {
background-color: oklch(80% 0.18 220deg);
background-image: url("https://www.transparenttextures.com/patterns/asfalt-dark.png");
block-size: 100dvh;
display: grid;
font-family: "Road Rage", sans-serif;
font-size: 24px;
font-style: normal;
font-weight: 400;
margin: 0;
overflow: clip;
perspective: 1000px;
place-content: center;
position: relative;
@media (prefers-reduced-motion: no-preference) {
animation: animate-bg 1s step-end infinite;
}
}
body.mNoAnimation {
animation-play-state: paused;
}
.welcome {
inset: 20px auto auto 20px;
position: absolute;
}
.welcome * + * {
margin-top: 10px;
}
h1 {
margin: 0;
}
button {
background-color: oklch(100% 0 0deg);
border: 0;
box-shadow: 5px 5px 0 0 oklch(0% 0 0deg);
cursor: pointer;
padding: 10px 20px;
font-family: inherit;
font-size: 24px;
@media (prefers-reduced-motion: reduce) {
display: none;
}
}
button.clicked {
box-shadow: none;
translate: 5px 5px;
}
form {
inset: auto 20px 20px auto;
position: absolute;
z-index: 1;
}
fieldset {
border: 1px solid oklch(0% 0 0deg);
}
.logo {
block-size: auto;
display: block;
inline-size: 90dvw;
inset: 50% auto auto 50%;
pointer-events: none;
position: absolute;
rotate: 0 0 1 -10deg;
translate: -50% -50%;
user-select: none;
@media (prefers-reduced-motion: no-preference) {
animation: shake 1s step-end infinite;
}
}
body.mNoAnimation .logo {
animation: none;
}
/* Let's make a 3D can in CSS even though we probably shouldn't! */
.can {
cursor: all-scroll;
transform-origin: 50% 50% calc(((var(--can-body-diamater) / 2) * var(--relative-unit-size)) * -1);
transform-style: preserve-3d;
translate: 0 calc((var(--can-upper-lip-height) * var(--relative-unit-size)) * 0.1);
}
.can * {
transform-style: preserve-3d;
}
.can__head {
background:
radial-gradient(
var(--c-tin-accent) 65%,
color-mix(in oklch, var(--c-tin-accent) 80%, oklch(0% 0 0deg)) 65%,
color-mix(in oklch, var(--c-tin-accent) 80%, oklch(0% 0 0deg)) 66%,
var(--c-tin-accent) 67%,
color-mix(in oklch, var(--c-tin-accent) 80%, oklch(0% 0 0deg)) 68%,
color-mix(in oklch, var(--c-tin-accent) 80%, oklch(0% 0 0deg)) 69%,
color-mix(in oklch, var(--c-tin-accent) 80%, oklch(100% 0 0deg)) 70%
);
block-size: calc(var(--can-head-size) * var(--relative-unit-size));
border-radius: 50%;
inline-size: calc(var(--can-head-size) * var(--relative-unit-size));
inset: auto auto 100% 50%;
position: absolute;
rotate: 1 0 0 90deg;
transform-origin: center bottom;
translate: -50% calc(((var(--can-upper-lip-height) - ((var(--can-upper-lip-height) / 100) * 9)) * var(--relative-unit-size)) * -1) calc((var(--can-head-size) * var(--relative-unit-size)) * -0.08);
}
.can__head::after {
background:
radial-gradient(
circle at 57.5% 40%,
oklch(100% 0 0deg / 20%) 0%,
oklch(100% 0 0deg / 10%) 3%,
oklch(0% 0 0deg / 10%) 50%
);
border-radius: 50%;
content: '';
inset: 0;
position: absolute;
z-index: 1;
}
.can__topDetail {
block-size: 75%;
fill: none;
inline-size: 75%;
inset: 50% auto auto 50%;
position: absolute;
translate: -50% -50% 0;
}
.can__topDetailOuter {
fill: var(--c-tin-accent);
filter: drop-shadow(0, 0, 5px, black);
stroke: color-mix(in oklch, var(--c-tin-accent) 95%, oklch(0% 0 0deg));
stroke-width: calc(var(--relative-unit-size) * 0.3);
}
.can__topDetailEngraveOuter,
.can__topDetailEngraveCenter,
.can__topDetailEngraveInner {
stroke: color-mix(in oklch, var(--c-tin-accent) 90%, oklch(0% 0 0deg));
stroke-width: calc(var(--relative-unit-size) * 0.1);
}
.can__topDetailEngraveInner {
fill: color-mix(in oklch, var(--c-tin-accent) 97.5%, oklch(0% 0 0deg));
stroke: color-mix(in oklch, var(--c-tin-accent) 90%, oklch(100% 0 0deg));
}
.can__topDetailRingpull {
fill: var(--c-ringpull);
filter: drop-shadow(0 calc(var(--relative-unit-size) * -0.4) calc(var(--relative-unit-size) * 0.4) oklch(0% 0 0deg / 30%));
}
.can__body {
block-size: calc(var(--can-height) * var(--relative-unit-size));
inline-size: calc(var(--can-body-diamater) * var(--relative-unit-size));
position: relative;
}
.can__body div {
background-color: var(--c-tin-silver);
background-image: var(--can-bg);
background-position: var(--bg-x) calc(((var(--can-upper-lip-height) - 0) * var(--relative-unit-size)) * -1);
background-repeat: no-repeat;
background-size: calc(var(--can-body-circumfrence) * var(--relative-unit-size)) 108%;
inline-size: calc((var(--can-body-circumfrence) * var(--relative-unit-size)) / 36);
inset: 0 auto 0 100%;
position: absolute;
rotate: 0 1 0 10deg;
transform-origin: 0% 50%;
}
.can__body > div {
inset-inline-start: calc(50% + (((var(--can-body-circumfrence) * var(--relative-unit-size)) / 36) / 2));
}
.can__body div::before,
.can__body div::after {
content: '';
inset-inline: 0;
position: absolute;
}
.can__body div::before {
background-image: var(--can-bg);
background-position: var(--bg-x) top;
background-size: calc(var(--can-body-circumfrence) * var(--relative-unit-size)) calc((var(--can-height) + var(--can-upper-lip-height)) * var(--relative-unit-size));
block-size: calc(var(--can-upper-lip-height) * var(--relative-unit-size));
border-block-start: calc((var(--can-upper-lip-height) / 5) * var(--relative-unit-size)) solid color-mix(in oklch, var(--c-tin-accent) 90%, oklch(0% 0 0deg));
box-sizing: content-box;
inset-block-end: 100%;
rotate: 1 0 0 25deg;
transform-origin: center bottom;
}
.can__body div::after {
background:
linear-gradient(
to bottom,
var(--c-tin-silver),
color-mix(in oklch, var(--c-tin-silver) 75%, oklch(0% 0 0deg))
);
block-size: calc(var(--can-lower-lip-height) * var(--relative-unit-size));
inset-block-start: 100%;
rotate: 1 0 0 -35deg;
transform-origin: center top;
}
.can__bottom {
background:
radial-gradient(
color-mix(in oklch, var(--c-tin-silver) 80%, oklch(0% 0 0deg)) 0%,
color-mix(in oklch, var(--c-tin-silver) 80%, oklch(0% 0 0deg)) 10%,
var(--c-tin-silver) 65%,
color-mix(in oklch, var(--c-tin-silver) 80%, oklch(0% 0 0deg)) 65%,
color-mix(in oklch, var(--c-tin-silver) 80%, oklch(0% 0 0deg)) 66%,
var(--c-tin-silver) 67%,
color-mix(in oklch, var(--c-tin-silver) 80%, oklch(0% 0 0deg)) 68%,
color-mix(in oklch, var(--c-tin-silver) 80%, oklch(0% 0 0deg)) 69%,
color-mix(in oklch, var(--c-tin-silver) 80%, oklch(100% 0 0deg)) 70%
);
block-size: calc(var(--can-bottom-size) * var(--relative-unit-size));
border-radius: 50%;
inline-size: calc(var(--can-bottom-size) * var(--relative-unit-size));
inset: 100% auto auto 50%;
position: absolute;
rotate: 1 0 0 90deg;
transform-origin: center top;
translate: -50% calc((var(--can-lower-lip-height) - ((var(--can-lower-lip-height) / 100) * 16)) * var(--relative-unit-size)) calc((var(--can-bottom-size) * var(--relative-unit-size)) * -1.03);
}
@keyframes animate-bg {
0%, 100% {
background-color: oklch(80% 0.18 31deg);
background-position: left top;
}
12.5%,
37.5%,
62.5%,
87.5% {
background-position: center center;
}
25% {
background-color: oklch(80% 0.18 99deg);
background-position: right bottom;
}
50% {
background-color: oklch(80% 0.18 42deg);
background-position: left bottom;
}
75% {
background-color: oklch(80% 0.18 220deg);
background-position: right top;
}
}
@keyframes shake {
2% {
transform: translate(0px, 0px) rotate(0.5deg);
}
4% {
transform: translate(1px, 1px) rotate(0.5deg);
}
6% {
transform: translate(1px, 0px) rotate(0.5deg);
}
8% {
transform: translate(1px, 0px) rotate(0.5deg);
}
10% {
transform: translate(1px, 1px) rotate(0.5deg);
}
12% {
transform: translate(1px, 0px) rotate(0.5deg);
}
14% {
transform: translate(0px, 1px) rotate(0.5deg);
}
16% {
transform: translate(1px, 1px) rotate(0.5deg);
}
18% {
transform: translate(0px, 1px) rotate(0.5deg);
}
20% {
transform: translate(0px, 0px) rotate(0.5deg);
}
22% {
transform: translate(0px, 0px) rotate(0.5deg);
}
24% {
transform: translate(1px, 0px) rotate(0.5deg);
}
26% {
transform: translate(1px, 1px) rotate(0.5deg);
}
28% {
transform: translate(0px, 0px) rotate(0.5deg);
}
30% {
transform: translate(0px, 0px) rotate(0.5deg);
}
32% {
transform: translate(0px, 0px) rotate(0.5deg);
}
34% {
transform: translate(0px, 0px) rotate(0.5deg);
}
36% {
transform: translate(1px, 1px) rotate(0.5deg);
}
38% {
transform: translate(0px, 1px) rotate(0.5deg);
}
40% {
transform: translate(0px, 0px) rotate(0.5deg);
}
42% {
transform: translate(1px, 0px) rotate(0.5deg);
}
44% {
transform: translate(0px, 0px) rotate(0.5deg);
}
46% {
transform: translate(1px, 1px) rotate(0.5deg);
}
48% {
transform: translate(1px, 0px) rotate(0.5deg);
}
50% {
transform: translate(1px, 1px) rotate(0.5deg);
}
52% {
transform: translate(1px, 0px) rotate(0.5deg);
}
54% {
transform: translate(0px, 0px) rotate(0.5deg);
}
56% {
transform: translate(1px, 0px) rotate(0.5deg);
}
58% {
transform: translate(0px, 1px) rotate(0.5deg);
}
60% {
transform: translate(1px, 1px) rotate(0.5deg);
}
62% {
transform: translate(1px, 1px) rotate(0.5deg);
}
64% {
transform: translate(1px, 0px) rotate(0.5deg);
}
66% {
transform: translate(0px, 0px) rotate(0.5deg);
}
68% {
transform: translate(1px, 0px) rotate(0.5deg);
}
70% {
transform: translate(1px, 1px) rotate(0.5deg);
}
72% {
transform: translate(1px, 0px) rotate(0.5deg);
}
74% {
transform: translate(0px, 0px) rotate(0.5deg);
}
76% {
transform: translate(0px, 1px) rotate(0.5deg);
}
78% {
transform: translate(1px, 0px) rotate(0.5deg);
}
80% {
transform: translate(1px, 0px) rotate(0.5deg);
}
82% {
transform: translate(0px, 0px) rotate(0.5deg);
}
84% {
transform: translate(1px, 1px) rotate(0.5deg);
}
86% {
transform: translate(0px, 1px) rotate(0.5deg);
}
88% {
transform: translate(1px, 1px) rotate(0.5deg);
}
90% {
transform: translate(0px, 0px) rotate(0.5deg);
}
92% {
transform: translate(0px, 1px) rotate(0.5deg);
}
94% {
transform: translate(1px, 0px) rotate(0.5deg);
}
96% {
transform: translate(0px, 1px) rotate(0.5deg);
}
98% {
transform: translate(1px, 0px) rotate(0.5deg);
}
0%, 100% {
transform: translate(0, 0) rotate(0);
}
}
const can = document.querySelector('.can');
let isDragging = false;
let previousX = 0;
let previousY = 0;
let rotateX = -20;
let rotateY = 0;
let velocityX = 0;
let velocityY = 0;
let autoRotate = true;
function animate() {
if (autoRotate) {
rotateY += 0.1;
} else {
rotateX -= velocityY;
rotateY += velocityX;
// Apply friction.
velocityX *= 0.95;
velocityY *= 0.95;
// Stop completely if velocity is low.
if (Math.abs(velocityX) < 0.001 && Math.abs(velocityY) < 0.001) {
velocityX = 0;
velocityY = 0;
}
}
can.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
requestAnimationFrame(animate);
}
// Mouse events.
can.addEventListener('mousedown', (e) => {
isDragging = true;
autoRotate = false;
previousX = e.clientX;
previousY = e.clientY;
});
window.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const deltaX = e.clientX - previousX;
const deltaY = e.clientY - previousY;
velocityX = deltaX * 0.1;
velocityY = deltaY * 0.1;
rotateY += velocityX;
rotateX -= velocityY;
previousX = e.clientX;
previousY = e.clientY;
});
window.addEventListener('mouseup', () => {
isDragging = false;
autoRotate = true;
});
// Touch events.
can.addEventListener('touchstart', (e) => {
isDragging = true;
autoRotate = false;
previousX = e.touches[0].clientX;
previousY = e.touches[0].clientY;
});
can.addEventListener('touchmove', (e) => {
const deltaX = e.touches[0].clientX - previousX;
const deltaY = e.touches[0].clientY - previousY;
velocityX = deltaX * 0.1;
velocityY = deltaY * 0.1;
rotateY += velocityX;
rotateX -= velocityY;
previousX = e.touches[0].clientX;
previousY = e.touches[0].clientY;
});
can.addEventListener('touchend', () => {
isDragging = false;
autoRotate = true;
});
// Start animation loop.
requestAnimationFrame(animate);
const inputs = document.querySelectorAll('input');
inputs.forEach(input => {
input.addEventListener('change', (e) => {
document.documentElement.style.setProperty('--can-bg', `var(--${e.target.value}-can-bg)`);
document.documentElement.style.setProperty('--c-tin-accent', `var(--c-${e.target.value}-tin-accent)`);
document.documentElement.style.setProperty('--c-ringpull', `var(--c-${e.target.value}-ringpull)`);
});
});
const animationBtn = document.getElementById('stop-animation');
const body = document.querySelector('body');
animationBtn.addEventListener('click', () => {
body.classList.toggle('mNoAnimation');
animationBtn.classList.add('clicked');
if (animationBtn.classList.contains('clicked')) {
animationBtn.innerText = 'OK FLASH AT ME!';
}
else {
animationBtn.innerText = 'OMG STOP FLASHING!';
}
setTimeout(() => {
animationBtn.classList.remove('clicked');
}, 100);
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.