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

postext-folio

v0.18.2

Published

A 3D book viewer for Postext documents and page images: three.js leaves that curl, cast real shadows and turn by hand, for left- and right-bound books.

Readme

postext-folio

A 3D book viewer for postext documents and page images.

postext-folio sets a book out on the screen in spreads and lets the reader turn its pages: with the ‹ › buttons, the arrow keys, a swipe, a click on a page, or by taking a page by its edge and dragging it over. Each leaf curls in three.js and casts a real shadow on the pages under it. The WebGL canvas draws the book still and turning alike, so a page never changes look when it lands; the DOM pages under it (images or canvases) are the textures' sources and the pages' text alternatives. A right-bound book (Chinese, Japanese, Arabic) lies mirrored and turns leftward. Without WebGL2, or when the reader asks for reduced motion, the spreads simply change.

Website: postext.dev · Docs: A 3D book

Install

npm install postext postext-folio three

postext and three are peer dependencies. Or import everything straight from a CDN:

import { buildDocument } from 'https://esm.sh/postext';
import { createFolioFromDocument } from 'https://esm.sh/postext-folio';

A postext document as a book

import { buildDocument } from 'postext';
import { createFolioFromDocument } from 'postext-folio';

// Postext measures text with the fonts the browser has loaded.
await document.fonts.load('16px "EB Garamond"');

const doc = buildDocument({ markdown }, {
  page: { sizePreset: '17x24' },
  bodyText: { fontFamily: 'EB Garamond' },
});

const book = createFolioFromDocument(document.getElementById('book')!, doc, {
  onChange: ({ pages }) => console.log('showing pages', pages),
});

// After an edit: the same viewer, on the same page.
book.setDocument(buildDocument({ markdown: edited }, config));

Pages are painted with renderPageToCanvas at exactly the device pixels of a page slot, so WebGL shows them texel for pixel, as sharp as the canvas preview, and only around the open spread (window, three spreads either side by default). Pages that fall out of that window are freed, so a book of a thousand pages costs the memory of a few. Turning to a far page paints that spread first. Pages a long turn sweeps past show as blank paper while they are in the air.

The container sets the size: the book fills it, with the ‹ › buttons and the page count in the margins. Give it a height. A resize paints the pages again at the new size.

Videos on the pages

A video resource of the document plays on its page. A click on the poster starts it there, in any pointer mode: the page's shader draws the frames over the poster, so the picture curls with the leaf and keeps playing while it turns. Another click pauses it, and it stops when the book comes to rest on a spread that no longer shows it. Space or Enter plays or pauses the video on the open spread.

Several videos play at once, up to eight drawn on the pages on show (each frame goes into one shared texture). player.exclusive (the default) keeps to one at a time: starting such a video stops the other exclusive ones and holds those playing alongside until it pauses or ends. A video with exclusive: false plays alongside the others, and starting it pauses only the exclusive ones: the silent loops of a living page. A video with player.autoplay starts by itself, muted: an exclusive one the first time its spread is shown (with its sound once the reader has interacted with the page), one with exclusive: false each time its page comes into view, looping when player.loop says so. Readers who ask for less motion (prefers-reduced-motion) get no loops starting on their own; a click still plays them.

const book = createFolioFromDocument(container, doc, {
  videoUrl: (fileId) => objectUrls.get(fileId), // an uploaded file's URL; else the video's address
  onVideo: ({ resourceId, state }) => console.log(resourceId, state),
});

Files and web addresses play (MP4, WebM, and HLS streams through hls.js, an optional peer dependency loaded on the first play and capped at the picture's size on screen; the browser's own HLS is the fallback). The server must allow cross-origin reads (CORS) for WebGL to draw the video; YouTube and Vimeo play in iframes, which WebGL cannot draw, so their posters turn the page as before. videos: false turns it off.

The book on the desk

The book lies open on a desk, seen from in front and a little above (the foot of the pages comes closer), lit by an environment that glossy paper reflects as a leaf curls, and by a key light that casts the shadows. The pages already read and those still to come form two blocks of real thickness: leaf count × the paper's caliper, with the book's other chapters counted in (extraPages). They are never painted, only counted. The open pages curve down into the gutter, and the darker gutter comes from the occlusion of that shape, not from a painted gradient. Each leaf bends according to its paper's stiffness: bible paper rolls tight, card turns in a wide arc, and board turns as a rigid plate on its hinge.

All of this is set with postext's folio config, read from doc.config.folio or passed as appearance:

const book = createFolioFromDocument(container, doc, {
  appearance: {
    folio: {
      tilt: 22, // degrees from straight above, 0 … 70
      yaw: 0, // degrees round the book, −180 … 180
      paper: { type: 'bookWove', grammage: 80, texture: 'laid' },
      binding: { type: 'hardcover', coverColor: { hex: '#5a1f1f', model: 'hex' } },
      surface: { type: 'walnut' },
      lighting: { environment: 'lamp', intensity: 1 },
    },
  },
});
book.setAppearance({ folio: { lighting: { environment: 'daylight' } } });

A document on a newspaper trim (page.sizePreset broadsheet, berliner, tabloid or compact) lies as a newspaper when its settings, or the host's appearance.folio, name no stock and no binding: newsprint folded once, the sheets nested with nothing round them. A stock or binding that is set wins. Newsprint shows the reverse page more than any stock but bible paper, as its ink soaks into the sheet.

A :::paper{type=coatedGloss} run in the markdown (a plate section, say) stamps its pages with their own stock; the viewer gives those leaves that paper's look, thickness and stiffness. With createFolio, a page may carry its own paper the same way: { src, paper: { type: 'coatedGloss' } }.

Page images

createFolio takes any pages: image URLs, <canvas> or <img> elements, "" for a blank page.

import { createFolio } from 'postext-folio';

const book = createFolio(container, {
  pages: ['p01.webp', 'p02.webp', 'p03.webp', { src: 'p04.webp', alt: 'Plate II' }],
  firstPageRecto: true, // page 1 opens alone, on the right
  binding: 'left',
});
book.goToPage(2);

API

The viewer takes the keyboard when focused: ←/→ (mirrored for a right-bound book), Page Up/Down, Home and End. Its labels default to English; pass labels (region, prev, next, count) to translate them.

Desk textures

The photographed desk surfaces served from postext.dev (/folio/textures/, with a manifest.json and a CREDITS file) are CC0 texture sets, resized and re-encoded for the web. No attribution is required; thanks to their authors:

License

MIT (code). The desk textures above are CC0.