Home

Espresso Machine CSS Art

February 15th, 2024

css, css-art

I'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.

Code Playground
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>
  );
}