This is a submission for Frontend Challenge - Comfort Food Edition, CSS Art.

Inspiration

My other entry in this challenge is Still Warm, a museum where comfort food is the art. Its catalogue runs to CAT. 007, and number 006 is missing from the walls. This is that exhibit. It could not hang in a frame because it refuses to sit still: borscht is not a dish so much as a patience ritual, the comfort food that colors itself while you wait. CAT. 006 - THE POT lives outside the museum, on its own pedestal, cooking.

Demo

If the embed does not load, open the pen directly. Press "Cook it again" to replay. If your system prefers reduced motion, the pot is served already cooked.

Journey

The idea

Every CSS Art entry I have seen draws the finished plate. I wanted the cooking. The piece runs three acts in about eight seconds: a pale broth at a quiet simmer, three pieces of beet falling in, and the color spreading through the liquid until it is unmistakably borscht, with potatoes plopping in after it, a dollop of smetana, and a pinch of dill from above. Then it rests on low heat.

Act I, pale broth with the simmer already going

The spread

The part I care most about is the dye. Four registered custom properties drive the gradient stops of the broth surface:

@property --spread-a { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
@property --spread-b { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
@property --broth-shift { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
@property --spread-edge { syntax: "<percentage>"; inherits: false; initial-value: 0%; }

.broth {
  --spread-a: 100%; /* at rest, the dye has reached every wall */
  background: radial-gradient(
    130% 118% at 62% 38%,
    var(--bordeaux) calc(var(--spread-a) * 0.58),
    color-mix(in oklab, var(--bordeaux) 56%, var(--ink))
      calc(var(--spread-a) + var(--spread-edge)),
    transparent calc(var(--spread-a) + var(--spread-edge) * 2 + 14%)
  );
}
Enter fullscreen mode Exit fullscreen mode

Three dyed layers sit over the pale Act I base, each with its own center near the beet's entry point and its own duration and delay. The fourth property, --spread-edge, starts wide and tightens to nothing, so the traveling front is always softer than the core. Different centers plus different clocks plus a softening edge is what makes it read as ink in water instead of a radar sweep. The pale broth and its fat eyes are still there underneath, permanently, waiting to be dyed over on every replay.

the spread mid-travel, about 4.5 seconds in

The architecture

The default values of every property are the finished borscht. Keyframes only declare "from"; where a "to" is missing, the resting rule already knows the end. So a browser without @property support, and any visitor with prefers-reduced-motion, lands on the finished dish instead of a broken pale one. The replay is one button and an :active sibling selector: while the button is held, every animation is removed, and letting go starts the kitchen over. Still zero JavaScript.

37 DOM elements, 59 animations, one style block.

the full sequence, beet to borscht

Honest fails

  • The smetana became a cartoon face three times (a swirl ring, then an eyeball, then two dill flecks landing side by side). My worklog now contains the rule "at most one round dot on any white surface".
  • The first beet froze mid-air. Three easings met at zero velocity and the anticipation beat read as a glitch. It is now a moving hold: it keeps sinking and turning through the pause.
  • The simmer was invisible twice. Once for starting too late (fixed with negative delays, so the pot was simmering before we arrived), and once for losing contrast when the broth turned bordeaux (a bubble recipe tuned on pale gold vanishes on dark red).
  • The beet batons spent one whole review as kidney beans, and the fat eyes as tan cookies. The fix was value relationships, not shapes: the color source must sit deeper than the liquid it dyes.

The small print

The figure is role="img" with an aria-label that reads the dish to screen readers, placard line included. No images, no SVG, no fonts, no libraries; the palette is eight named colors and every other paint is a color-mix of them. MIT licensed.