Hi,👋 we have updated the app and fixed multiple bugs. We are lacking funds, request to free user not to use Adblock. Ads are non intrusive. 😊

Carousel Studio

Repurpose X Threads into LinkedIn & Instagram Carousels

Thread Truncated (Cap Enforced)

Only the first 20 tweets are unrolled into slides to ensure reliable PDF exporting and high server performance.

Canvas & Ratio

Choose your destination platform format


Layout Template

Choose a content structure for your slides


Preset Themes


Typography & Sizing

Title Font Size36px
Body Font Size18px
Header & Footer Size12px

Brand Kit Customization

AGENCY

Configure brand assets for headers & footers

MULTI-PROFILES (AGENCY)
AGENCY
SAVE PRESETS (AGENCY)

Outro Slide CTA

Customize your closing call-to-action slide

#1
#2
#3

Background Pattern

Source Content

Build Your Carousel

Drag and drop any post card below onto a slide, or use the quick buttons to insert content/images instantly!

Drag Post #1
Aswin
@aswincode

I posted this on X yesterday and a lot of folks have been asking how we built it. Here's how.

Apply Image
Drag Post #2
Aswin
@aswincode

<a target="_blank" href="https://x.com/i/status/2051311805926965597" color="blue">https://x.com/i/status/2051311805926965597</a>

Drag Post #3
Aswin
@aswincode

We explored a tiny, fun thing in the <a target="_blank" href="https://x.com/turfsports_" color="blue">Turf</a> iOS app: <b>a dynamic and personalized tab icon</b>. Instead of a static image asset, the News tab shows two stacked thumbnails pulled from the latest news that is actually relevant to <b>you</b>. Tap around the app, your feed changes, your tab icon changes with it.

Drag Post #4
Aswin
@aswincode

This was a fun reminder that good design engineering is a full-stack discipline. What you see is one tab icon. Keeping it <b>personalized, current, and ready before the tab bar paints</b> took work across the backend, the edge, and the client all at once.

Drag Post #5
Aswin
@aswincode

Turf is built around prediction markets and live moments. The News tab is one of the surfaces where personalization shows up most: the articles you see are scoped to the leagues, teams, and topics you care about. We wanted the <b>tab itself</b> to reflect that, not just the screen behind it. A tab icon that hints at "here is what is fresh for you right now" is a small, persistent reminder that the app is paying attention.

Drag Post #6
Aswin
@aswincode

## Constraints

Drag Post #7
Aswin
@aswincode

We use <a target="_blank" href="https://docs.expo.dev/router/introduction/" color="blue">Expo Router</a> and, on iOS, the new <b>native tabs</b> API (<a target="_blank" href="https://docs.expo.dev/router/advanced/native-tabs/" color="blue">expo-router/unstable-native-tabs</a>). Native tabs are great because you get the real UITabBar, liquid glass on iOS 26, system minimize-on-scroll, badges, all of it. The tradeoff is that you live inside UITabBarItem's rules. For an icon, <a target="_blank" href="https://docs.expo.dev/router/advanced/native-tabs/#icon" color="blue">NativeTabs.Trigger.Icon</a> accepts an SF Symbol name, an Android Material Symbol, an image (local bundle or remote URL), or a local Xcode asset. A local Xcode asset would have been the natural fit. It paints instantly because it's already in the bundle, but it's frozen at app-build time and can't be personalized. Anything you swap it out for has to be <b>downloaded while the app is open</b>, which means the user pays for that round-trip with their first glance at the tab bar. We wanted the opposite: the icon ready by the time the user looks at it, dynamic without paying a latency tax for it. So the icon needs to be a <b>URL</b>: one that's cheap enough to resolve that we can prefetch it during normal app warmup and have it sitting in the image cache before the tab bar ever paints. And that URL needs to deterministically render a small PNG that composites two thumbnails into our "stacked rounded rect" look.

Drag Post #8
Aswin
@aswincode

## Render the icon at the edge

Drag Post #9
Aswin
@aswincode

We built a small Cloudflare Worker that exposes a single route, roughly:

Drag Post #10
Aswin
@aswincode

<b>GET /tab-feed?left=&lt;url&gt;&amp;right=&lt;url&gt;</b>

Drag Post #11
Aswin
@aswincode

It returns an aggressively cached <b>PNG</b> that stacks two thumbnails with the exact tilt, white rim, shadow, and border-radius our design wanted. The mobile app composes the URL with the user's two latest news thumbnails, prefetches it, and hands the result to NativeTabs.Trigger.Icon as a remote ImageURISource. Two layers, one URL.

Drag Post #12
Aswin
@aswincode

Worker side: Satori + Resvg, all WASM

Drag Post #13
Aswin
@aswincode

The Worker is intentionally simple. On every request it validates inputs (length-capped, http/https only), hashes them into a deterministic cache key alongside a LAYOUT_VERSION env var, and looks up the rendered PNG in the <a target="_blank" href="https://developers.cloudflare.com/workers/runtime-apis/cache/" color="blue">Workers Cache API</a>. If it hits, we return the cached Response. If it misses, we render once and write the result back via waitUntil so the response isn't blocked by the cache write. Cache headers are aggressive (a day at the browser, a week at the CDN, immutable) because the route is a pure function of (version, left, right). To retune the visual (radius, tilt, shadow), we bump LAYOUT_VERSION and every cached PNG invalidates without changing the public URL contract. We also save every rendered PNG to <a target="_blank" href="https://developers.cloudflare.com/r2/" color="blue">R2</a>, Cloudflare's object storage. The edge cache is fast but it lives at each location separately and can drop entries when it fills up. R2 is shared across all locations and keeps the file around. So if a request lands somewhere that hasn't seen this icon before, the worker grabs the PNG straight from R2 instead of re-rendering, and stashes a copy locally for the next visitor. The render path only runs the very first time we see a new (version, left, right) combination. The render itself runs <b><a target="_blank" href="https://github.com/vercel/satori" color="blue">Satori</a></b><a target="_blank" href="https://github.com/vercel/satori" color="blue"></a> for layout and <b>Resvg-WASM</b> for rasterization, with <b>Yoga-WASM</b> providing flexbox under Satori. All three work inside a Worker because <a target="_blank" href="https://developers.cloudflare.com/workers/runtime-apis/webassembly/" color="blue">WASM is a first-class module type there</a>, so we get JSX-flavored layout → SVG → PNG without spinning up Node or any image microservice. WASM init runs once per isolate; everything after that is pure render. If you want to play with this kind of JSX-flavored layout before wiring it into a Worker, <a target="_blank" href="https://og-playground.vercel.app/" color="blue">Vercel's OG Playground</a>, built by <a target="_blank" href="https://x.com/shuding" color="blue">@shuding</a> (who also created Satori), is the fastest way to iterate on it in a browser.

Drag Post #14
Aswin
@aswincode

Apply Image
Drag Post #15
Aswin
@aswincode

The element tree is two &lt;img&gt; nodes with transform: rotate(-8deg) and rotate(6deg), white borders, soft drop shadows, and rounded corners. Rendering at <b>3× the canvas width</b> keeps the PNG crisp on retina screens.

Drag Post #16
Aswin
@aswincode

Mobile side: build URL, prefetch, hand to native tabs

Drag Post #17
Aswin
@aswincode

The client never reaches into the Worker beyond constructing a URL. It composes the request, calls Image.prefetch, and only flips the tab icon source over once prefetch resolves:

Drag Post #18
Aswin
@aswincode

Apply Image
Drag Post #19
Aswin
@aswincode

Two details that mattered in practice:

Drag Post #20
Aswin
@aswincode

• <b>Prefetch gate.</b> The remote source stays null until the PNG is in the RN image cache. Until then, the tab keeps a bundled SF Symbol fallback, so there's no flash of broken icon on first launch and no missing-pixel jank when the URL changes underneath us.