Trade Book Central · June 20, 2026 · 7 min read

Shipping a 15-day build-in-public carousel series

Built a data-driven LinkedIn carousel generator for Trade Book Central and produced a full 15-day build-in-public series: 90 on-brand slides and 15 print-ready PDFs rendered from JSON specs with node-canvas.

By Andy D — Founder, Creative Brain Inc. — Brampton, Ontario

Day 1 cover slide for Trade Book Central: a dark navy portrait card with a bold headline, the candlestick app icon as a logo chip, faint node-network and a candlestick chart band at the bottom

Every slide is generated from a JSON spec. The look (brand hex, app icon, candlestick chart band, node-network) is pulled straight from the product, not eyeballed.

TL;DR

  • Built a data-driven carousel generator: a single node-canvas renderer turns a per-post slides.json into six 1080x1350 PNG slides plus one multi-page PDF for LinkedIn document posts.
  • Produced a complete 15-day build-in-public series for Trade Book Central documenting its MEAN to Next.js, Supabase and Stripe migration: 15 posts, 90 slides, 15 carousel PDFs, each with a copy-paste caption.
  • Pulled the visual system from the product itself: exact brand hex from the app's globals.css, the real app icon as the logo chip, and a static port of the marketing homepage's animated candlestick, EMA and node-network background.
  • Seven reusable slide layouts (cover, bullets, stat, code, quote, CTA, statement) driven entirely by JSON. Re-rendering any day is one command.
  • Locked a writing style for every caption and slide: short, human, first person, no emojis, no em-dashes.

Wiring: no new services. The renderer reuses the node-canvas build and font library shipped with an existing local design skill. No DB, no env vars.

Why we shipped this

Trade Book Central is being rebuilt from a MEAN stack onto Next.js, Supabase and Stripe. That migration is a good story, and the plan was to document it as a daily build-in-public series on LinkedIn to grow an audience around the product while the work happens.

The problem is volume and consistency. A 15-day carousel series is 90 individual slides. Hand-designing that in Canva is slow, and by slide 40 the brand has quietly drifted: spacing wanders, the greens stop matching, the candlestick motif from the product never makes it in. We wanted the carousels to look like they came out of the app, to be trivially editable, and to be reproducible if a number or a line of copy changed.

So instead of designing 90 slides, we built one renderer and wrote 15 small content files. The design lives in code, the words live in JSON and Markdown, and any slide is one command away from being regenerated.

Files added or modified

All paths are inside the Trade Book Central repo, under Marketing/Social Media Posts/MEAN-to-NextJS Build Series/.

New

  • _engine/render.js — the node-canvas renderer: seven layouts, the "Market Cartography" graphics layer, and PDF bundling.
  • CONTENT-PLAN.md — the 15-post narrative arc with per-day angle, real detail and accent colour.
  • PROGRESS.md — a resumable build tracker so a future session can pick up cold.
  • DESIGN-PHILOSOPHY.md — the visual philosophy the graphics layer expresses.
  • Day NN - <Title>/slides.json (x15) — the six-slide content spec per post.
  • Day NN - <Title>/post.md (x15) — metadata table, copy-paste caption, slide summary, posting checklist.
  • Day NN - <Title>/slide-1..6.png + <slug>-carousel.pdf (x15) — the rendered assets.

Reused (not modified)

  • The canvas-design local skill's node-canvas build and canvas-fonts library (Big Shoulders, Work Sans, JetBrains Mono).
  • The product's apps/web/app/globals.css (brand tokens) and components/marketing/hero-background.tsx (the homepage scene we ported).

Design rationale

One renderer, seven layouts, zero hand-design

Each post is a slides.json file: an array of slides, each with a layout and a few fields. The renderer owns all the visual decisions; the JSON owns only the words and the accent. The rejected alternative was bespoke code per slide, which is just hand-design with extra steps and the same drift problem.

{
  "day": 8,
  "accent": "#0fcc5d",
  "slides": [
    { "layout": "stat", "value": "1000s", "unit": "of trades",
      "label": "Every position, every user, carried across." },
    { "layout": "code", "title": "A rule the database enforces itself",
      "lines": ["create policy own_trades on trades", "  using ( user_id = auth.uid() );"] }
  ]
}

The same seven layouts cover the whole series, so a stat slide on day 8 and a stat slide on day 14 are visually identical except for the numbers.

A stat-layout slide showing a giant green '1000s' with 'of trades' beneath it, a supporting line, and a candlestick chart band at the bottom

The stat layout. The value auto-shrinks to fit the canvas, so "0", "256" and "1000s" all sit correctly without manual tweaking.

A code-layout slide rendering a terminal window with a Postgres row-level-security policy, syntax-coloured, above the candlestick chart band

The code layout renders a terminal window with simple line-prefix colouring, used for SQL, shell and config snippets across the series.

A bullets-layout slide titled 'What I said no to' with four short bullet points, on the dark navy brand background

The bullets layout. Four short lines is the cap, which keeps the copy skimmable and forces tighter writing.

Brand fidelity by reuse, not eyeballing

The brand colours are read from the app's globals.css (brand green #0b9444, profit #0fcc5d, loss #f26d6d, marketing orange #f26522, deep navy base), the logo chip is the actual app icon PNG, and the ambient background is a static port of the homepage's animated hero: candlesticks, two EMA lines, a node-network and drifting "price" ribbons. Nothing here is approximated by eye, which is the only way 90 slides stay on-brand.

Bold colour-block slides for rhythm

Every post includes one saturated colour-block quote slide so a swipe-through has a beat instead of six near-identical dark cards. The accent rotates across the series (green, orange, teal, red, profit), and on those bright slides the candlestick motif persists as a tonal silhouette so the visual system never disappears.

A full-bleed brand-green quote slide with white text and a faint navy candlestick silhouette along the bottom

A green colour-block quote slide. The chart band stays present as a tonal silhouette rather than disappearing on the bright background.

A full-bleed loss-red quote slide with white text, used for a war-story post, with the same tonal candlestick silhouette at the bottom

The same layout on the loss-red accent, used for the "war story" posts. One JSON field (bg) switches the whole palette.

Gotchas we hit (so the next log doesn't repeat them)

  1. node-canvas fontconfig is cwd-sensitive on Windows. Registered fonts silently fell back to a generic sans. Root cause: the bundled fontconfig initialises at require() time, keyed to the process's startup working directory, so registered faces only resolve when the cwd is the skill install. Fix: process.chdir(SKILL_DIR) before require('canvas'), and resolve the post-folder argument against the original cwd so relative paths still work.
  2. Numeric font weights break face matching. Strings like "700 92px Family" fell back to sans. Fix: register each weight under its own short family alias and use the bold keyword in the font string, mirroring the pattern that already worked in the design skill.
  3. Big stat values ran off the canvas. A long value such as "1000s" overflowed because its width was measured with the smaller unit font still active. Fix: measure with the display font, auto-shrink the value to fit the content width, and stack the unit on its own line beneath the number.
  4. The chart band collided with the code terminal. Raising the chart band to sit between the body text and the footer pushed it into the tall terminal box on code slides. Fix: shortened the terminal height so the raised band always clears it.

Verifying the work

# from the series folder: re-render any single day
node "_engine/render.js" "Day 08 - Migration Day"

# confirm each day produced 6 PNGs + 1 PDF, across all 15 days
for d in Day*/; do
  png=$(ls "$d"slide-*.png 2>/dev/null | wc -l)
  pdf=$(ls "$d"*.pdf 2>/dev/null | wc -l)
  printf "%-34s png:%s pdf:%s\n" "$d" "$png" "$pdf"
done

Manual check: open any *-carousel.pdf, swipe through the six pages, and confirm the brand colours, the app-icon chip in the header, and the candlestick-plus-EMA band at the foot of each slide. Honest status: verified locally across all 15 days (90 slides, 15 PDFs, no missing assets); LinkedIn engagement metrics are pending until posting begins.

What's next

  1. Post the series on a cadence — publish each day's PDF and caption to LinkedIn, then fill the Date Posted column in PROGRESS.md and each post.md.
  2. Cross-platform adaptation — generate square and vertical variants for X, Instagram and TikTok from the same JSON specs, no recopy of content.
  3. One combined captions file — concatenate the 15 captions into a single scheduling-friendly document.