npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@liebstoeckel/thumbnails

v0.3.6

Published

Slide thumbnail capture for liebstoeckel decks via headless Chromium.

Readme

@liebstoeckel/thumbnails

Slide thumbnail capture for liebstoeckel decks. Headless Chromium screenshots embedded into the deck.

Part of liebstoeckel, a code-first presentation framework. You write decks in MDX and TSX and build them into a single self-contained HTML file with no server or runtime dependencies. The same file works offline, and when you host it the deck runs a live session between the presenter and the audience. Built on Bun, React 19, Motion, and Tailwind v4.

Status: experimental, pre-1.0. liebstoeckel is an evolving experiment, not yet production-ready. Before 1.0, breaking changes can land in any release without a major-version bump, so pin an exact version if you depend on it.

It renders each slide of a built deck with headless Chromium and embeds the screenshots back into the HTML, so the overview grid, presenter view, and link previews have real thumbnails. It encodes to WebP through Bun.Image. If no Chromium is available, it skips the step and the deck still builds.

Install

bun add -d @liebstoeckel/thumbnails
bun add react   # peer

Needs a real Chromium binary. It's resolved from opts.executablePath, then $LIEBSTOECKEL_CHROMIUM, then Playwright's installed Chromium. The package ships playwright-core only, so install a browser or point at one you already have.

Usage

The common case is to swap your engine bundleDeck call for this thumbnail-aware buildDeck:

// build.ts
import { buildDeck } from "@liebstoeckel/thumbnails/build";

await buildDeck({ entry: "./index.html", outdir: "./dist" });
// writes dist/index.html with per-slide thumbnails embedded

Or capture against an already-built file:

import { addThumbnailsToFile } from "@liebstoeckel/thumbnails";

await addThumbnailsToFile("dist/index.html");

Exports

| Entry | What | |---|---| | @liebstoeckel/thumbnails | captureThumbnails, addThumbnailsToFile, resolveChromium, hasChromium, thumbnailsEnabled, the manifest re-exports, and the CaptureOptions / ThumbnailFormat / ThumbnailManifest types | | @liebstoeckel/thumbnails/build | buildDeck(build?, capture?), the standard deck build.ts one-liner | | @liebstoeckel/thumbnails/cli | thumbsCommand / exportCommand (citty commands; thumbsCommand powers the liebstoeckel-thumbnails bin) |

CaptureOptions defaults: width 640, format webp, quality 80, scale 2, settleMs 250, timeoutMs 15000.

Gotchas

  • It only works on a built <Present> deck, not raw source.
  • It skips silently when no Chromium is found or LIEBSTOECKEL_NO_THUMBS is set, which is handy in CI where you don't want to install a browser.

Architecture

It drives a built single-file deck through its own capture protocol (re-exported from @liebstoeckel/engine/build) and screenshots each slide. The deck cooperates by rendering CaptureView instead of the normal presenter when the capture flag is present.

| File | Role | |---|---| | src/capture.ts | The browser side. captureThumbnails(html, opts) launches headless Chromium through playwright-core, injects the CAPTURE_FLAG as a classic inline <script> (so it runs before the deferred deck bundle boots), waits for SLIDE_COUNT, then for each slide dispatches CAPTURE_EVENT and waits for CAPTURE_READY to match before a settle delay and a PNG page.screenshot(). It also holds resolveChromium, hasChromium, thumbnailsEnabled, and the PNG-to-WebP/JPEG transcode through Bun.Image. | | src/index.ts | addThumbnailsToFile(path) reads the HTML, runs captureThumbnails, then re-embeds in place with embedThumbnails (from the engine). It's idempotent, since a prior block is stripped first. It re-exports the engine manifest helpers (embedThumbnails / extractThumbnails / stripThumbnails). | | src/build.ts | buildDeck() calls the engine's bundleDeck, then addThumbnailsToFile on the output. It wraps capture in a guard (thumbnailsEnabled) and a try/catch, so the optional step never fails the build. | | src/cli.ts | thumbsCommand powers the liebstoeckel-thumbnails bin and the CLI's thumbs command. It parses --width/--quality/--scale/--format and calls addThumbnailsToFile. |

A few sequencing notes:

  • Chromium launches with container-friendly flags (--no-sandbox, --single-process, --no-zygote, …) so it runs without a GPU or a user namespace.
  • It renders at deviceScaleFactor scale (default 2) for crisp text, and the manifest stores the scaled w/h.
  • The binary resolution order is opts.executablePath, then $LIEBSTOECKEL_CHROMIUM, then Playwright's installed Chromium, each with an existsSync check so hasChromium() stays honest in CI.

Links

Licensed under MPL-2.0.