This is a submission for Frontend Challenge: Comfort Food Edition, CSS Art: Comfort Food prompt
What I Built
A thattu vadai set building itself on a square of newspaper, on an eleven-second loop. No JavaScript.
Thattu vadai set is a street snack from Salem, in Tamil Nadu. Two deep-fried rice-flour crisps β green chutney on one, a fried red chilli-and-tamarind chutney on the other β and between them a fistful of grated beetroot, carrot and raw onion tossed with lemon. It's a sandwich you eat standing up, and it's soft in about ninety seconds.
I didn't want to draw it. A still picture of a thattu vadai set is just a beige disc. The dish isn't the object β it's the twenty seconds where a man at a folding table builds one in front of you. So the art is the assembly:
newspaper β crisp lands β chutney smears β slaw scatters
β peanuts and boondi drop β chutney squeezes out β top crisp closes it
The scene is lit by a single bulb, after dark. That isn't a mood choice. I looked up the opening hours of every thattu vadai kadai I could verify in Salem, and not one of them opens before 4 PM β most shut by eleven, several close Sundays entirely. It is structurally an evening food, so the artwork is set in the evening.
Demo
Journey
Making a fried crisp not look like a wooden bowl
My first version was embarrassing. Concentric fork-prick rings at 0.13 alpha, a radial gradient darkening toward the rim, and an inset shadow along the bottom edge. Individually all reasonable. Together they read unmistakably as wood grain in a salad bowl.
Three fixes, all subtractive:
/* rings dropped to a whisper β thattai do carry them,
but at any real strength they read as grain */
repeating-radial-gradient(circle at 50% 50%,
rgba(120,66,20,.05) 0 1px, transparent 1px 15px),
/* flat dough, barely any edge falloff */
radial-gradient(circle at 44% 40%,
#F0C888 0%, #E2AF60 58%, #D19A48 84%, #BC8138 100%);
And the rim shadow came off entirely, replaced with a drop-shadow() filter so the crisp sits on the paper rather than being a vessel that contains something.
The edge is a 32-point clip-path with jittered radii, not a border-radius. Real thattai are hand-pressed and fry unevenly β a wobbled circle still reads as manufactured, a scalloped polygon doesn't:
clip-path:polygon(
50% 1%, 61% 3%, 68% 8%, 78% 7%, 84% 14%, 92% 19%, 94% 29%,
99% 38%, 96% 48%, 99% 58%, 93% 66%, 92% 76%, 84% 81%, 79% 90%,
/* ...18 more... */
);
The seamless-loop problem
I wanted the thirty-odd shreds of slaw to land one after another, not all at once. The obvious move is a positive animation-delay per shred β but with infinite, that desynchronises everything and the loop visibly tears on every repeat.
The fix is negative delays. Every shred runs identical keyframes at an identical duration, so they share one period forever; the negative delay just phase-shifts each into the cycle:
.bit{
animation: bit-in 11s cubic-bezier(.25,1.3,.4,1)
calc(var(--i) * -0.026s) infinite both;
}
Thirty-four shreds at 26ms apart reads as a scatter, and nothing ever resets, so the loop is seamless.
Positions live in the markup as custom properties, which keeps a single keyframe block driving all of them:
<span class="bit beet" style="--x:-52px;--y:-30px;--r:-24deg;--w:26px;--i:1"></span>
Letting the filling spill
The other thing I got wrong first time: I kept all the slaw inside the crisp. It looked tidy and completely wrong. Grated vegetables in a real set hang over the edge, and the top crisp never quite covers them.
So the shreds now spread wider than the base crisp, and the top crisp is deliberately smaller than the slaw spread β 43% against 46% β so the filling stays visible around the edges after it closes. That one relationship did more for the realism than any amount of texture work.
Palette
Straight off the plate. Beetroot #B01D5C, carrot #E86A10, thattai gold #E2AF60, coriander #4F8F26, kara chutney #B02D1A, newsprint #D5CDBB.
Beetroot is the whole visual identity of this dish β within a minute of assembly, everything on the paper is stained magenta. Setting it against a dark evening background rather than the usual warm-cream food-page look is what makes it carry.
Captions are set in Anek Tamil so the Tamil renders properly instead of dropping to a fallback, and marked lang="ta" so screen readers switch voice rather than trying to pronounce the script as English.
prefers-reduced-motion skips the assembly and holds the finished set.
Thanks for reading. If you've eaten one of these, I want to know which kadai. π½οΈ
Top comments (0)