KUWAIT'S CAKEPICNIC

Branding + Creative coding

Cake Picnic Kuwait posters installed along a palm-lined street

ABOUT THE PROJECT

Cake Picnic Kuwait is a speculative branding project for a cultural gathering imagined in Al Shaheed Park: a picnic where cakes are shared, conversations unfold outdoors, and a workshop takes place alongside the event. The concept highlights why community matters and how food can bring people closer through something made to be shared.

The identity is generative: ornaments and visual systems are drawn in code with p5.js, so the graphics can stay fluid and unique rather than fixed as a static pattern.

Live generative ornament drawn in p5.js. Hint: hit to re-generate.
ornament.p5.js
1// ORNAMENT GENERATOR
2// KEYS: R = new drawing · S = save PNG
3
4let inkLayer;
5let backgroundColor = "#FFFAFC";
6let inkBlue = "#095ce7";
7
8let penX, penY;
9let directionAngle = 0;
10let startTime;
11let durationMs = 15000;
12
13let trail = [];
14let maxTrail = 14;
15let symmetryCopies = 2;
16
17let thinStroke = 2.5;
18let thickStroke = 9;
19let nibAngle = -Math.PI / 6;
20let noiseTime = 0;
21
22let canvasWidth = 1920;
23let canvasHeight = 1080;
24let drawingMarginX = 260;
25let drawingMarginY = 170;
26
27function setup() {
28 createCanvas(canvasWidth, canvasHeight);
29 pixelDensity(2);
30
31 inkLayer = createGraphics(width, height);
32 inkLayer.pixelDensity(2);
33 inkLayer.clear();
34
35 resetOrnament();
36}
37
38function draw() {
39 clear();
40 image(inkLayer, 0, 0);
41
42 if (millis() - startTime > durationMs) {
43 resetOrnament();
44 return;
45 }
46
47 let slowTurn = map(noise(noiseTime), 0, 1, -0.09, 0.09);
48 let wobbleTurn = sin(frameCount * 0.07) * 0.05;
49 directionAngle += slowTurn + wobbleTurn;
50
51 let moveSpeed = map(noise(noiseTime + 500), 0, 1, 2.2, 4.4);
52 penX += cos(directionAngle) * moveSpeed;
53 penY += sin(directionAngle) * moveSpeed;
54 noiseTime += 0.01;
55
56 if (penX < drawingMarginX || penX > width - drawingMarginX) {
57 directionAngle += random(0.8, 1.6);
58 }
59 if (penY < drawingMarginY || penY > height - drawingMarginY) {
60 directionAngle += random(0.8, 1.6);
61 }
62
63 penX = constrain(penX, drawingMarginX, width - drawingMarginX);
64 penY = constrain(penY, drawingMarginY, height - drawingMarginY);
65
66 trail.push({ x: penX, y: penY });
67 if (trail.length > maxTrail) trail.shift();
68
69 if (frameCount % 3 === 0 && trail.length >= 7) {
70 drawSymmetricCurve();
71 }
72}
73
74function drawSymmetricCurve() {
75 let centerX = width / 2;
76 let centerY = height / 2;
77
78 inkLayer.push();
79 inkLayer.translate(centerX, centerY);
80 inkLayer.noFill();
81 inkLayer.stroke(inkBlue);
82 inkLayer.strokeCap(ROUND);
83 inkLayer.strokeJoin(ROUND);
84
85 for (let copyIndex = 0; copyIndex < symmetryCopies; copyIndex++) {
86 let rotationAngle = (TWO_PI / symmetryCopies) * copyIndex;
87
88 inkLayer.push();
89 inkLayer.rotate(rotationAngle);
90 drawCalligraphicTrail(centerX, centerY);
91 inkLayer.pop();
92
93 inkLayer.push();
94 inkLayer.rotate(rotationAngle);
95 inkLayer.scale(-1, 1);
96 drawCalligraphicTrail(centerX, centerY);
97 inkLayer.pop();
98 }
99
100 inkLayer.pop();
101}
102
103function drawCalligraphicTrail(centerX, centerY) {
104 for (let pointIndex = 1; pointIndex < trail.length - 2; pointIndex++) {
105 let previousPoint = trail[pointIndex - 1];
106 let currentPoint = trail[pointIndex];
107 let nextPoint = trail[pointIndex + 1];
108 let nextNextPoint = trail[pointIndex + 2];
109
110 let currentX = currentPoint.x - centerX;
111 let currentY = currentPoint.y - centerY;
112 let nextX = nextPoint.x - centerX;
113 let nextY = nextPoint.y - centerY;
114
115 let segmentAngle = atan2(nextY - currentY, nextX - currentX);
116 let thicknessAmount = abs(sin(segmentAngle - nibAngle));
117 let segmentStroke = lerp(thinStroke, thickStroke, thicknessAmount);
118
119 inkLayer.strokeWeight(segmentStroke);
120 inkLayer.beginShape();
121 inkLayer.curveVertex(previousPoint.x - centerX, previousPoint.y - centerY);
122 inkLayer.curveVertex(currentX, currentY);
123 inkLayer.curveVertex(nextX, nextY);
124 inkLayer.curveVertex(nextNextPoint.x - centerX, nextNextPoint.y - centerY);
125 inkLayer.endShape();
126 }
127}
128
129function resetOrnament() {
130 randomSeed(floor(random(1000000)));
131 noiseSeed(floor(random(1000000)));
132 inkLayer.clear();
133
134 penX = random(drawingMarginX + 40, width - drawingMarginX - 40);
135 penY = random(drawingMarginY + 40, height - drawingMarginY - 40);
136 directionAngle = random(TWO_PI);
137 noiseTime = random(1000);
138
139 trail = [];
140 for (let pointIndex = 0; pointIndex < 10; pointIndex++) {
141 trail.push({ x: penX, y: penY });
142 }
143
144 startTime = millis();
145 loop();
146}
147
148function keyPressed() {
149 if (key === "r" || key === "R") resetOrnament();
150 if (key === "s" || key === "S") saveCanvas("ornament", "png");
151}

CAKE PICNIC

The picnic is the heart of the day. People bring cakes from home kitchens and local bakeries, then share the stories behind them: who made each one, what inspired it, how it came together. Visitors try unfamiliar flavors, meet people they have not met before, and sit in Al Shaheed Park to enjoy the afternoon with Kuwait City behind them.

What the picnic celebrates is not spectacle. It is craftsmanship and curiosity: the patience of frosting, the quiet pride of bringing something you made, and the ease of leaving space for someone else to take a slice. Creativity here is practical and social. It lives in conversation as much as in decoration.

The visual identity is generative. Abstract cake elements, like frosting lace, piping, and decoration, are coded to animate around each design piece. On every refresh, the system draws a new random pattern, so no two outputs feel the same while the language still reads as cake craft rather than a fixed illustration.

Street poster installation with generative ornament animation.
Street poster detail.
Tote bag application.
Apron with logo lockup and ornament.
Shopping bag mockup.
Ornament as wearable object.

CAKE WORKSHOP

The Cake Workshop celebrates Kuwait’s flavors, heritage, and culture through a hands-on experience devoted to decorating, frosting, piping, and creative experimentation.

Its visual identity reinterprets Bedouin Sadu weaving through a modern generative system. The code transforms the rhythm, movement, and structure of Sadu into patterns that feel contemporary while remaining connected to the city’s cultural heritage. Across its combinations of outer structures, cores, arms, satellites, scale, spacing, and color, the code can generate roughly 49 million possible motif configurations.

Each ticket is generated individually, giving every holder a unique pattern to discover. No two tickets are the same, making each one a personal part of the workshop experience.

Four random Sadu motifs generated from the same system.
Cake Workshop poster wall.
sadu.p5.js
1// SADU MOTIF GENERATOR
2// KEYS: R = new motif · S = save PNG · G = save GIF
3
4let cell = 16;
5let cols = 40;
6let rows = 40;
7
8let aCol = "#ff7fff";
9let bCol = "#722218";
10
11let pixels = [];
12let drawIndex = 0;
13let pixelsPerFrame = 24;
14let occupied;
15
16function setup() {
17 pixelDensity(1);
18 createCanvas(cols * cell, rows * cell);
19 noStroke();
20 clear();
21 generateMotif();
22}
23
24function draw() {
25 clear();
26
27 let limit = min(drawIndex, pixels.length);
28 for (let i = 0; i < limit; i++) {
29 let p = pixels[i];
30 fill(p.c);
31 rect(p.x * cell, p.y * cell, cell, cell);
32 }
33
34 if (drawIndex < pixels.length) {
35 drawIndex += pixelsPerFrame;
36 }
37}
38
39function keyPressed() {
40 if (key === "r" || key === "R") {
41 generateMotif();
42 }
43
44 if (key === "s" || key === "S") {
45 saveCanvas("sadu-motif", "png");
46 }
47
48 if (key === "g" || key === "G") {
49 // restart animation before recording
50 drawIndex = 0;
51
52 // works in p5 builds that support saveGif()
53 if (typeof saveGif === "function") {
54 let seconds = max(1, ceil(pixels.length / pixelsPerFrame / 60) + 1);
55 saveGif("sadu-motif", seconds);
56 } else {
57 console.log("saveGif() is not available in this p5.js build.");
58 }
59 }
60}
61
62function generateMotif() {
63 pixels = [];
64 drawIndex = 0;
65 occupied = new Map();
66
67 let cx = floor(cols / 2);
68 let cy = floor(rows / 2);
69
70 let outerType = random(["diamondRing", "diamondFilled", "squareRing", "stepDiamond"]);
71 let middleType = random(["crossBlock", "diamondRing", "stepDiamond", "none"]);
72 let coreType = random(["miniCrossCut", "diamondCut", "plusSquareCut", "crossDiamond"]);
73 let armType = random(["none", "cardinalCrosses", "cardinalDiamonds", "steppedArms", "cornerDiamonds", "cornerCrosses"]);
74 let satelliteType = random(["none", "fourDots", "fourDiamonds", "fourCrosses", "edgeHooks"]);
75
76 let outerR = floor(random(5, 9));
77 let middleR = max(2, outerR - floor(random(2, 4)));
78 let innerR = max(1, middleR - floor(random(1, 3)));
79
80 if (outerType === "diamondRing") addDiamondRing(cx, cy, outerR, floor(random(1, 3)), bCol);
81 if (outerType === "diamondFilled") addDiamondFilled(cx, cy, outerR, bCol);
82 if (outerType === "squareRing") addSquareRing(cx, cy, outerR, floor(random(1, 3)), bCol);
83 if (outerType === "stepDiamond") addStepDiamond(cx, cy, outerR, bCol);
84
85 if (middleType === "crossBlock") addBlockCross(cx, cy, middleR, 3, aCol);
86 if (middleType === "diamondRing") addDiamondRing(cx, cy, middleR, 1, aCol);
87 if (middleType === "stepDiamond") addStepDiamond(cx, cy, middleR, aCol);
88
89 if (coreType === "miniCrossCut") cutMiniCross(cx, cy);
90 if (coreType === "diamondCut") cutDiamondFilled(cx, cy, 1);
91 if (coreType === "plusSquareCut") cutPlusSquare(cx, cy);
92 if (coreType === "crossDiamond") {
93 cutMiniCross(cx, cy);
94 addDiamondFilled(cx, cy, 1, aCol);
95 }
96
97 if (random() > 0.45) {
98 let fillChoice = random(["diamond", "square", "cross"]);
99 let fillCol = random([aCol, bCol]);
100
101 if (fillChoice === "diamond") addDiamondFilled(cx, cy, innerR, fillCol);
102 if (fillChoice === "square") addSquareFilled(cx, cy, innerR, fillCol);
103 if (fillChoice === "cross") addBlockCross(cx, cy, innerR, 1, fillCol);
104 }
105
106 let d = outerR + floor(random(2, 5));
107
108 if (armType === "cardinalCrosses") {
109 addMiniCross(cx, cy - d, aCol);
110 addMiniCross(cx, cy + d, aCol);
111 addMiniCross(cx - d, cy, aCol);
112 addMiniCross(cx + d, cy, aCol);
113 }
114
115 if (armType === "cardinalDiamonds") {
116 addDiamondFilled(cx, cy - d, 1, aCol);
117 addDiamondFilled(cx, cy + d, 1, aCol);
118 addDiamondFilled(cx - d, cy, 1, aCol);
119 addDiamondFilled(cx + d, cy, 1, aCol);
120 }
121
122 if (armType === "steppedArms") {
123 let len = floor(random(2, 5));
124 addSteppedArmUp(cx, cy - outerR - 1, len, aCol);
125 addSteppedArmDown(cx, cy + outerR + 1, len, aCol);
126 addSteppedArmLeft(cx - outerR - 1, cy, len, aCol);
127 addSteppedArmRight(cx + outerR + 1, cy, len, aCol);
128 }
129
130 if (armType === "cornerDiamonds") {
131 addDiamondFilled(cx - d, cy - d, 1, aCol);
132 addDiamondFilled(cx + d, cy - d, 1, aCol);
133 addDiamondFilled(cx - d, cy + d, 1, aCol);
134 addDiamondFilled(cx + d, cy + d, 1, aCol);
135 }
136
137 if (armType === "cornerCrosses") {
138 addMiniCross(cx - d, cy - d, aCol);
139 addMiniCross(cx + d, cy - d, aCol);
140 addMiniCross(cx - d, cy + d, aCol);
141 addMiniCross(cx + d, cy + d, aCol);
142 }
143
144 let d2 = d + floor(random(2, 4));
145
146 if (satelliteType === "fourDots") {
147 addPixel(cx, cy - d2, bCol);
148 addPixel(cx, cy + d2, bCol);
149 addPixel(cx - d2, cy, bCol);
150 addPixel(cx + d2, cy, bCol);
151 }
152
153 if (satelliteType === "fourDiamonds") {
154 addDiamondFilled(cx, cy - d2, 1, bCol);
155 addDiamondFilled(cx, cy + d2, 1, bCol);
156 addDiamondFilled(cx - d2, cy, 1, bCol);
157 addDiamondFilled(cx + d2, cy, 1, bCol);
158 }
159
160 if (satelliteType === "fourCrosses") {
161 addMiniCross(cx, cy - d2, bCol);
162 addMiniCross(cx, cy + d2, bCol);
163 addMiniCross(cx - d2, cy, bCol);
164 addMiniCross(cx + d2, cy, bCol);
165 }
166
167 if (satelliteType === "edgeHooks") {
168 addHookUp(cx, cy - d2, bCol);
169 addHookDown(cx, cy + d2, bCol);
170 addHookLeft(cx - d2, cy, bCol);
171 addHookRight(cx + d2, cy, bCol);
172 }
173
174 if (random() > 0.55) {
175 let c = d2 + floor(random(1, 4));
176 let kind = random(["dot", "diamond", "cross"]);
177 let col = random([aCol, bCol]);
178 placeCornerSet(cx, cy, c, kind, col);
179 }
180
181 sortPixelsForAnimation(cx, cy);
182}
183
184function addPixel(x, y, c) {
185 if (x < 0 || y < 0 || x >= cols || y >= rows) return;
186 occupied.set(x + "," + y, c);
187}
188
189function removePixel(x, y) {
190 if (x < 0 || y < 0 || x >= cols || y >= rows) return;
191 occupied.delete(x + "," + y);
192}
193
194function commitOccupied() {
195 pixels = [];
196 for (let [key, c] of occupied.entries()) {
197 let parts = key.split(",");
198 pixels.push({ x: int(parts[0]), y: int(parts[1]), c });
199 }
200}
201
202function addRect(x, y, w, h, c) {
203 for (let yy = 0; yy < h; yy++) {
204 for (let xx = 0; xx < w; xx++) {
205 addPixel(x + xx, y + yy, c);
206 }
207 }
208}
209
210function cutRect(x, y, w, h) {
211 for (let yy = 0; yy < h; yy++) {
212 for (let xx = 0; xx < w; xx++) {
213 removePixel(x + xx, y + yy);
214 }
215 }
216}
217
218function addDiamondFilled(cx, cy, r, c) {
219 for (let yy = -r; yy <= r; yy++) {
220 let span = r - abs(yy);
221 for (let xx = -span; xx <= span; xx++) {
222 addPixel(cx + xx, cy + yy, c);
223 }
224 }
225}
226
227function cutDiamondFilled(cx, cy, r) {
228 for (let yy = -r; yy <= r; yy++) {
229 let span = r - abs(yy);
230 for (let xx = -span; xx <= span; xx++) {
231 removePixel(cx + xx, cy + yy);
232 }
233 }
234}
235
236function addDiamondRing(cx, cy, r, thickness, c) {
237 for (let t = 0; t < thickness; t++) {
238 let rr = r - t;
239 for (let yy = -rr; yy <= rr; yy++) {
240 let span = rr - abs(yy);
241 addPixel(cx - span, cy + yy, c);
242 addPixel(cx + span, cy + yy, c);
243 }
244 }
245}
246
247function addSquareFilled(cx, cy, r, c) {
248 addRect(cx - r, cy - r, r * 2 + 1, r * 2 + 1, c);
249}
250
251function addSquareRing(cx, cy, r, thickness, c) {
252 for (let t = 0; t < thickness; t++) {
253 let rr = r - t;
254 for (let x = cx - rr; x <= cx + rr; x++) {
255 addPixel(x, cy - rr, c);
256 addPixel(x, cy + rr, c);
257 }
258 for (let y = cy - rr; y <= cy + rr; y++) {
259 addPixel(cx - rr, y, c);
260 addPixel(cx + rr, y, c);
261 }
262 }
263}
264
265function addStepDiamond(cx, cy, r, c) {
266 for (let yy = -r; yy <= r; yy++) {
267 let span = r - abs(yy);
268 addRect(cx - span, cy + yy, span * 2 + 1, 1, c);
269 }
270}
271
272function addMiniCross(cx, cy, c) {
273 addPixel(cx, cy - 1, c);
274 addPixel(cx - 1, cy, c);
275 addPixel(cx, cy, c);
276 addPixel(cx + 1, cy, c);
277 addPixel(cx, cy + 1, c);
278}
279
280function cutMiniCross(cx, cy) {
281 removePixel(cx, cy - 1);
282 removePixel(cx - 1, cy);
283 removePixel(cx, cy);
284 removePixel(cx + 1, cy);
285 removePixel(cx, cy + 1);
286}
287
288function addPlusSquare(cx, cy, c) {
289 addRect(cx - 1, cy - 1, 3, 3, c);
290 addPixel(cx, cy - 2, c);
291 addPixel(cx - 2, cy, c);
292 addPixel(cx + 2, cy, c);
293 addPixel(cx, cy + 2, c);
294}
295
296function cutPlusSquare(cx, cy) {
297 cutRect(cx - 1, cy - 1, 3, 3);
298 removePixel(cx, cy - 2);
299 removePixel(cx - 2, cy);
300 removePixel(cx + 2, cy);
301 removePixel(cx, cy + 2);
302}
303
304function addBlockCross(cx, cy, arm, thickness, c) {
305 addRect(cx - floor(thickness / 2), cy - arm, thickness, arm * 2 + 1, c);
306 addRect(cx - arm, cy - floor(thickness / 2), arm * 2 + 1, thickness, c);
307}
308
309function addSteppedArmUp(cx, cy, len, c) {
310 for (let i = 0; i < len; i++) addRect(cx - 1, cy - i * 2, 3, 2, c);
311}
312
313function addSteppedArmDown(cx, cy, len, c) {
314 for (let i = 0; i < len; i++) addRect(cx - 1, cy + i * 2, 3, 2, c);
315}
316
317function addSteppedArmLeft(cx, cy, len, c) {
318 for (let i = 0; i < len; i++) addRect(cx - i * 2, cy - 1, 2, 3, c);
319}
320
321function addSteppedArmRight(cx, cy, len, c) {
322 for (let i = 0; i < len; i++) addRect(cx + i * 2, cy - 1, 2, 3, c);
323}
324
325function addHookUp(cx, cy, c) {
326 addPixel(cx, cy, c);
327 addPixel(cx - 1, cy + 1, c);
328 addPixel(cx, cy + 1, c);
329 addPixel(cx + 1, cy + 1, c);
330}
331
332function addHookDown(cx, cy, c) {
333 addPixel(cx, cy, c);
334 addPixel(cx - 1, cy - 1, c);
335 addPixel(cx, cy - 1, c);
336 addPixel(cx + 1, cy - 1, c);
337}
338
339function addHookLeft(cx, cy, c) {
340 addPixel(cx, cy, c);
341 addPixel(cx + 1, cy - 1, c);
342 addPixel(cx + 1, cy, c);
343 addPixel(cx + 1, cy + 1, c);
344}
345
346function addHookRight(cx, cy, c) {
347 addPixel(cx, cy, c);
348 addPixel(cx - 1, cy - 1, c);
349 addPixel(cx - 1, cy, c);
350 addPixel(cx - 1, cy + 1, c);
351}
352
353function placeCornerSet(cx, cy, d, kind, col) {
354 if (kind === "dot") {
355 addPixel(cx - d, cy - d, col);
356 addPixel(cx + d, cy - d, col);
357 addPixel(cx - d, cy + d, col);
358 addPixel(cx + d, cy + d, col);
359 }
360
361 if (kind === "diamond") {
362 addDiamondFilled(cx - d, cy - d, 1, col);
363 addDiamondFilled(cx + d, cy - d, 1, col);
364 addDiamondFilled(cx - d, cy + d, 1, col);
365 addDiamondFilled(cx + d, cy + d, 1, col);
366 }
367
368 if (kind === "cross") {
369 addMiniCross(cx - d, cy - d, col);
370 addMiniCross(cx + d, cy - d, col);
371 addMiniCross(cx - d, cy + d, col);
372 addMiniCross(cx + d, cy + d, col);
373 }
374}
375
376function sortPixelsForAnimation(cx, cy) {
377 commitOccupied();
378
379 pixels.sort((p1, p2) => {
380 let d1 = abs(p1.x - cx) + abs(p1.y - cy);
381 let d2 = abs(p2.x - cx) + abs(p2.y - cy);
382
383 if (d1 !== d2) return d1 - d2;
384 if (p1.y !== p2.y) return p1.y - p2.y;
385 return p1.x - p2.x;
386 });
387}
Cake Workshop poster wall.

Workshop signup and generated guest ticket with a unique Sadu pattern.

2026 All rights reserved. Designed and developed by Mohsen Alattar.

KUWAIT --:--:--LOS ANGELES --:--:--