By Shehab Waleed, Founder, Cairo Studio 3 min read
In October we started making Instagram carousels for the studio: short series on Arabic and Latin type, on why a design looks cheap, and on our own case studies. We began in Figma, through a plugin we drive with scripts, and hit a wall on the first slide.
Type is the point of these carousels, and our typefaces are files in our repository: Youth for headlines, Satoshi for text, Thmanyah Display for Arabic. The plugin could only load fonts that Figma hosts. Everything else fell back to a default face, which defeats a carousel about type.
A slide is a page
So each carousel became one HTML file. The fonts load with @font-face from the same files our website uses, Tailwind handles layout, and a query parameter decides which slide is showing. A carousel file looks roughly like this:
<body class="w-[1080px] h-[1350px]">
<section data-slide="1">...</section>
<section data-slide="2">...</section>
</body>
<script>
const s = new URL(location.href).searchParams.get('s');
for (const el of document.querySelectorAll('[data-slide]'))
el.hidden = el.dataset.slide !== s;
</script>
Rendering is one command per slide, using the headless Chrome that Playwright already keeps on our machines.
chrome-headless-shell --headless \
--window-size=1080,1350 \
--virtual-time-budget=8000 \
--screenshot=out/slide-01.png \
'file:///work/carousel.html?s=1'
The virtual time budget gives fonts and images eight seconds of simulated time to settle before the screenshot is taken. Figma still holds the finished slides as images, so the team reviews everything in one place.
What a browser gives you
Once the slides were pages, the tools of the web came with them.
- Real font metrics. When a slide compares two typefaces, the measurements are taken from the fonts, not judged by eye.
- Effects as code. Halftone photos, misregistered ink and canvas type effects are functions, so one change updates every slide that uses them.
- Real work. Screens from our projects are cropped out of mockup images automatically, by finding long straight edges.
- Speed. We went through seven visual directions in four days, from an editorial grid to riso posters to clay still lifes, because each one was a new stylesheet and a render, not a redraw.
The traps
Canvas text measurement has one catch that cost us an afternoon. The bounding box measureText returns depends on the alignment set at that moment, so set textAlign and direction first, or right to left text gets boxes in the wrong place.
ctx.direction = 'rtl';
ctx.textAlign = 'right';
const m = ctx.measureText(text);
const left = x - m.actualBoundingBoxLeft;
Fitting type to the height of a slide also failed with wide display faces, which overflowed sideways. We set fixed pixel sizes per slide instead.
What a browser cannot tell you
Instagram compresses every image, and fine detail like grain and thin misregistration may not survive it. A browser preview cannot tell you how much will. The Arabic and Latin series grew from the same thinking as Arabic is not a translation.
