Espresso Machine CSS Art
February 15th, 2024
css, css-artI've always enjoyed CSS art, seeing it as a way to draw on the web, something I've wanted to try.
Starting was the hardest part for me, especially organizing the divs and choosing the initial styles.
But now, with tools like ChatGPT and Copilot, the entry barrier is much lower. These tools help set up the basic boilerplate, and then we can iterate on it together.
They're great for the overall layout, but struggle with animations and positioning, which is when I have to do some manual work.
I'm really happy with the result.
Check out what happens when you click the coffee button! That last part was built with React, but Vanilla JS would do the trick as well.
import styles from './Espresso.module.css'; import clsx from "clsx"; import { Coffee, Heart } from "lucide-react"; import React from "react"; function CloudIcon({ className }: { className?: string }) { return ( <svg xmlns="http://www.w3.org/2000/svg" width="28" height="18" viewBox="0 0 28 18" className={className} > <path d="M21.967.964c-1.429-.283-2.439.219-3.325 1.007C17.838.707 16.458-.051 14.715.245c-1.49.254-2.505 1.236-2.946 2.479-1.867-1.045-4.104-1.532-6.059-.432-1.79 1.009-3.382 3.57-3.406 5.775C1.069 9.22.292 10.531 1.053 12.296c.508 1.18 1.703 1.689 2.894 1.667.826 2.152 3.438 3.405 5.572 3.71 2.844.405 6.026-.628 8.087-2.659.133.116.265.231.393.309.522.314 1.147.491 1.749.557 1.05.115 2.201-.137 2.994-.875 1.007-.937 1.195-2.226.901-3.451 1.257-.395 2.446-1.095 3.247-2.096 1.352-1.692.697-3.811-.434-5.413-1.059-1.499-2.673-2.722-4.489-3.081zm2.872 7.081c-.606.759-1.997 1.283-2.933 1.361a1.236 1.236 0 00-.95.544c-.222.264-.327.61-.236.985.032.264.14.508.321.688.301.602.449 1.299-.23 1.69-.89.513-1.817-.341-2.618-.848-.37-.401-.934-.571-1.409-.22a1.08 1.08 0 00-.383.394c-1.131 1.363-2.546 2.027-4.264 2.456-1.691.421-5.791.001-5.943-2.443a1.226 1.226 0 00-.024-.263c-.047-.779-.704-1.146-1.335-1.098l-.029.002a1.677 1.677 0 00-.287.055c-.038.011-.074.027-.111.042-.037.015-.073.023-.109.042-.455.227-.858.019-1.086-.397-.271-.494.982-1.325 1.254-1.557.443-.377.492-.846.321-1.243-.216-1.202 1.291-3.425 2.323-3.83 1.762-.69 3.567.345 4.83 1.466.669.776 2.152.317 2.195-.757a1.003 1.003 0 00-.068-.55c-.453-2.183 2.647-2.621 2.835-.256.028.354.177.626.384.823.387.455 1.031.66 1.565.235a.98.98 0 00.492-.559c.347-.452.682-.927 1.153-1.242 1.002-.67 2.382.372 3.094.997.863.761 2.168 2.331 1.248 3.483z"></path> </svg> ); } export default function EspressoMachine() { const [isLoading, setIsLoading] = React.useState(false); const [isCoffeeReady, setIsCoffeeReady] = React.useState(false); const handleCoffeeClick = () => { setIsLoading(true); setTimeout(() => { setIsCoffeeReady(true); }, 3500); setTimeout(() => { setIsLoading(false); setIsCoffeeReady(false); }, 6000); }; return ( <div className={styles.espressoMachine}> <div className={`${styles.machineBody} flex flex-col items-center bg-stone-300`} > <div className="mt-1 flex w-full flex-col items-center justify-center gap-3"> <div className="flex w-full items-center justify-center border-b border-dashed border-b-rose-600 p-1 text-rose-700"> <Heart /> <Heart /> <Heart /> </div> <button onClick={handleCoffeeClick} className="rounded border-2 border-dashed border-rose-700 p-1 text-xs text-rose-700" > {!isLoading && <Coffee />} {isLoading && !isCoffeeReady && "Preparing..."} {isCoffeeReady && "Enjoy!"} </button> </div> <div className={`${styles.spout} bg-stone-700`}></div> </div> <div className={`${styles.backgroundConnector} bg-[#4d609d]`}></div> <div className={`${styles.tray} bg-stone-600`}></div> <div className={styles.base}></div> <div className={clsx( styles.coffee, "bg-amber-600", isLoading ? styles.animatePourCoffee : "animate-none", )} ></div> <div className={styles.cup}> <div className={clsx( styles.coffeeInside, "bg-amber-600", isLoading ? styles.animateFillCup : "animate-none", )} ></div> <div className={`${styles.smoke} bottom-[30px] flex flex-col items-center justify-center`} > <CloudIcon className={clsx( styles.smokeLine, isLoading ? styles.animateShowSmoke : "", "h-4 w-8 fill-amber-300", )} /> <CloudIcon className={clsx( styles.smokeLine, isLoading ? styles.animateShowSmoke : "", "h-4 w-4 fill-amber-400", )} /> <CloudIcon className={clsx( styles.smokeLine, isLoading ? styles.animateShowSmoke : "", "h-2 w-3 fill-amber-400", )} /> </div> </div> </div> ); }