Skip to content

Gallery

Every image on this page is a file showcase-kit wrote, committed exactly as the kit produced it. The app in them is Nightjar, a made-up observing planner that lives in the kit’s repository under site/gallery/: plain HTML, CSS and JavaScript with fixed data (no clock, no randomness, no network), served by a small node:http server that the kit starts through target.start. Its terminal side, nightjar queue, is a Node script without dependencies.

One command regenerates everything below, from empty output folders:

Terminal window
bun run build # the kit's CLI, dist/cli.js
bun run docs:gallery # site/scripts/gallery.ts

Each caption names the file and its real size. Select an image to open the file itself.

The web config captures three views at 1280x720 CSS pixels and device scale factor 2, in English and Polish. The app takes its language from the browser locale, and the kit gives each entry in langs its own browser context with that locale, so the config needs no setup. frame puts each capture in a window on a gradient, and maxWidth: 1600 scales the 2816 pixel wide result down for a lighter README. The frames guide explains each option.

The whole web config. The portfolio and the hero below come from it too:

site/gallery/showcase.config.mjs
// The web half of the docs gallery: Nightjar, a static fixture app in app/, served by server.mjs.
// In your own project, wrap the object in `defineConfig` from '@noctcore/showcase-kit' for editor
// completion. This file sits inside the kit's repo, where the package cannot import itself, so it
// exports the plain object (defineConfig only returns what it is given).
export default {
name: 'Nightjar',
target: {
mode: 'url',
url: 'http://127.0.0.1:47219/',
start: 'node server.mjs --port 47219',
// Always start this copy, never a server something else left on the port.
reuseExisting: false,
readyTimeoutMs: 20000,
},
ready: '#app[data-ready]',
viewport: { width: 1280, height: 720 },
deviceScaleFactor: 2,
colorScheme: 'dark',
// The app reads navigator.language, and each language gets a browser context with that locale.
langs: ['en', 'pl'],
shots: [
{
id: 'tonight',
title: 'Tonight',
caption: 'Plan the night: conditions, targets and a sky chart.',
alt: "Nightjar's Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart.",
nav: '[data-view="tonight"]',
},
{
id: 'log',
title: 'Log',
caption: 'Every session, with seeing and notes.',
alt: "Nightjar's Log view: session totals and a table of eight observations with seeing dots, star ratings and notes.",
nav: '[data-view="log"]',
},
{
id: 'gear',
title: 'Gear',
caption: 'The kit that goes in the car.',
alt: "Nightjar's Gear view: six equipment cards with their specs, and a packing checklist.",
nav: '[data-view="gear"]',
},
],
frame: {
style: 'window',
theme: 'dark',
title: '{name}: {title}',
background: { type: 'gradient', from: '#2a1f6b', to: '#0b3b4c', angle: 135 },
padding: 64,
maxWidth: 1600,
quality: 85,
},
outputs: {
raw: 'showcase-out/raw/{lang}/{id}.png',
readme: '../public/gallery/readme/{lang}/{id}.webp',
portfolio: {
dir: '../public/gallery/portfolio',
size: [1600, 900],
quality: 85,
thumbnail: 'tonight',
publicPath: '/showcase-kit/gallery/portfolio',
// Next to this config, not in public/: the docs page imports it at build time.
gallery: 'showcase.gallery.json',
},
},
hero: {
tagline: 'Plan the night, log what you saw.',
logo: '../src/assets/mark.svg',
shots: ['gear', 'log', 'tonight'],
output: '../public/gallery/hero.webp',
quality: 85,
},
};

outputs.portfolio exports every shot at exactly 1600x900 pixels, the framed window contained on the background and never cropped, plus thumbnail.webp (a copy of the tonight image) and showcase.gallery.json. The images below are rendered from that JSON at build time, which is how a portfolio site is meant to use it. Its publicPath is the URL this site serves the images from, so each src works as written. See the portfolio guide.

site/gallery/showcase.config.mjs (the portfolio block)
portfolio: {
dir: '../public/gallery/portfolio',
size: [1600, 900],
quality: 85,
thumbnail: 'tonight',
publicPath: '/showcase-kit/gallery/portfolio',
// Next to this config, not in public/: the docs page imports it at build time.
gallery: 'showcase.gallery.json',
},

The gallery JSON the export wrote. It sits next to the config rather than in the served folder, because this page imports it:

site/gallery/showcase.gallery.json
[
{
"src": "/showcase-kit/gallery/portfolio/tonight.webp",
"alt": "Nightjar's Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart.",
"caption": "Plan the night: conditions, targets and a sky chart."
},
{
"src": "/showcase-kit/gallery/portfolio/log.webp",
"alt": "Nightjar's Log view: session totals and a table of eight observations with seeing dots, star ratings and notes.",
"caption": "Every session, with seeing and notes."
},
{
"src": "/showcase-kit/gallery/portfolio/gear.webp",
"alt": "Nightjar's Gear view: six equipment cards with their specs, and a packing checklist.",
"caption": "The kit that goes in the car."
}
]

showcase hero in its default stack layout stacks up to three framed shots, tilted, next to the logo, the name and the tagline, at the 1280x640 GitHub social preview size. It renders from the raw captures, like the portfolio. The logo is this site’s own mark. See the hero guide.

site/gallery/showcase.config.mjs (the hero block)
hero: {
tagline: 'Plan the night, log what you saw.',
logo: '../src/assets/mark.svg',
shots: ['gear', 'log', 'tonight'],
output: '../public/gallery/hero.webp',
quality: 85,
},

The same banner in each of the six hero.layout values. Every image from here down comes from a variant config in site/gallery/variants/: the web config above with one block changed, and root: '..' so its paths still resolve from site/gallery/. showcase hero and showcase frame render from the raw captures, so the variants capture nothing again. The block under each image is cut from the file that made it. The layouts that show one window also pick the one shot, since the config lists three. See layouts in the hero guide.

hero.background takes every form frame.background does and defaults to it, which is the gradient in the banner above. Here is the default stack on the three newer types.

frame.style sets the window chrome and frame.background what it sits on, for the README images, the portfolio, the hero and clips alike. These variants frame only the English tonight shot, at maxWidth: 1200 and quality: 80 for a tile here (the README images above are 1600 pixels wide). See the frames guide.

The terminal shot from the tty config, framed three ways. browser is not among them: a terminal app has no address, so a tty config with style: 'browser' fails to load with "browser" needs a page with an address; a terminal app has none (use "terminal" or "window").

The web config’s own background is a gradient, the same type as the kit’s default (which is teal to indigo). The other forms are below; transparent is left out, since here it would only show this page’s background.

showcase readme --layout prints the same images, captions and alt text in five arrangements. The gallery script runs it once per layout with the web config, from the repo root:

Terminal window
showcase readme --config site/gallery/showcase.config.mjs --layout <name> --base ../..

Each box below renders the snippet the kit printed, committed as it came out under site/gallery/readme/, with its image paths pointing at this gallery’s files. The box is styled like a GitHub README, and the HTML is under it. GitHub strips CSS and most attributes from a README, so every layout uses only the tags and attributes it keeps. Each layout was checked through GitHub’s own Markdown renderer (its markdown API, in gfm mode) when it was added, and again for this page: every tag and attribute survives. See layouts in the README guide.

A row of images and a row of <sub> captions under it, two per row unless --cols says otherwise.

Nightjar's Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart. Nightjar's Log view: session totals and a table of eight observations with seeing dots, star ratings and notes.
Plan the night: conditions, targets and a sky chart. Every session, with seeing and notes.
Nightjar's Gear view: six equipment cards with their specs, and a packing checklist.
The kit that goes in the car.
site/gallery/readme/table.html (--layout table)
<table>
<tr>
<td width="50%"><img src="site/public/gallery/readme/en/tonight.webp" alt="Nightjar&#39;s Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart." /></td>
<td width="50%"><img src="site/public/gallery/readme/en/log.webp" alt="Nightjar&#39;s Log view: session totals and a table of eight observations with seeing dots, star ratings and notes." /></td>
</tr>
<tr>
<td align="center"><sub>Plan the night: conditions, targets and a sky chart.</sub></td>
<td align="center"><sub>Every session, with seeing and notes.</sub></td>
</tr>
<tr>
<td width="50%"><img src="site/public/gallery/readme/en/gear.webp" alt="Nightjar&#39;s Gear view: six equipment cards with their specs, and a packing checklist." /></td>
</tr>
<tr>
<td align="center"><sub>The kit that goes in the car.</sub></td>
</tr>
</table>

One small table per image: the image in a 60% cell beside its title (as <h3>) and caption, alternating sides.

Nightjar's Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart.

Tonight

Plan the night: conditions, targets and a sky chart.

Log

Every session, with seeing and notes.

Nightjar's Log view: session totals and a table of eight observations with seeing dots, star ratings and notes.
Nightjar's Gear view: six equipment cards with their specs, and a packing checklist.

Gear

The kit that goes in the car.

site/gallery/readme/rows.html (--layout rows)
<table>
<tr>
<td width="60%"><img src="site/public/gallery/readme/en/tonight.webp" alt="Nightjar&#39;s Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart." /></td>
<td width="40%"><h3>Tonight</h3><p>Plan the night: conditions, targets and a sky chart.</p></td>
</tr>
</table>
<table>
<tr>
<td width="40%"><h3>Log</h3><p>Every session, with seeing and notes.</p></td>
<td width="60%"><img src="site/public/gallery/readme/en/log.webp" alt="Nightjar&#39;s Log view: session totals and a table of eight observations with seeing dots, star ratings and notes." /></td>
</tr>
</table>
<table>
<tr>
<td width="60%"><img src="site/public/gallery/readme/en/gear.webp" alt="Nightjar&#39;s Gear view: six equipment cards with their specs, and a packing checklist." /></td>
<td width="40%"><h3>Gear</h3><p>The kit that goes in the car.</p></td>
</tr>
</table>

The first image full width with its caption, then the others in a table, two per row unless --cols says otherwise.

Nightjar's Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart.
Plan the night: conditions, targets and a sky chart.

Nightjar's Log view: session totals and a table of eight observations with seeing dots, star ratings and notes. Nightjar's Gear view: six equipment cards with their specs, and a packing checklist.
Every session, with seeing and notes. The kit that goes in the car.
site/gallery/readme/featured.html (--layout featured)
<p align="center">
<img width="100%" src="site/public/gallery/readme/en/tonight.webp" alt="Nightjar&#39;s Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart." />
<br /><sub>Plan the night: conditions, targets and a sky chart.</sub>
</p>
<table>
<tr>
<td width="50%"><img src="site/public/gallery/readme/en/log.webp" alt="Nightjar&#39;s Log view: session totals and a table of eight observations with seeing dots, star ratings and notes." /></td>
<td width="50%"><img src="site/public/gallery/readme/en/gear.webp" alt="Nightjar&#39;s Gear view: six equipment cards with their specs, and a packing checklist." /></td>
</tr>
<tr>
<td align="center"><sub>Every session, with seeing and notes.</sub></td>
<td align="center"><sub>The kit that goes in the car.</sub></td>
</tr>
</table>

One collapsible <details> per image, with the title and caption as its summary; the first starts open.

Tonight: Plan the night: conditions, targets and a sky chart.

Nightjar's Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart.

Log: Every session, with seeing and notes.

Nightjar's Log view: session totals and a table of eight observations with seeing dots, star ratings and notes.

Gear: The kit that goes in the car.

Nightjar's Gear view: six equipment cards with their specs, and a packing checklist.

site/gallery/readme/details.html (--layout details)
<details open>
<summary><b>Tonight</b>: Plan the night: conditions, targets and a sky chart.</summary>
<p align="center"><img width="100%" src="site/public/gallery/readme/en/tonight.webp" alt="Nightjar&#39;s Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart." /></p>
</details>
<details>
<summary><b>Log</b>: Every session, with seeing and notes.</summary>
<p align="center"><img width="100%" src="site/public/gallery/readme/en/log.webp" alt="Nightjar&#39;s Log view: session totals and a table of eight observations with seeing dots, star ratings and notes." /></p>
</details>
<details>
<summary><b>Gear</b>: The kit that goes in the car.</summary>
<p align="center"><img width="100%" src="site/public/gallery/readme/en/gear.webp" alt="Nightjar&#39;s Gear view: six equipment cards with their specs, and a packing checklist." /></p>
</details>

Every image full width, one under the other, each with its caption.

Nightjar's Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart.
Plan the night: conditions, targets and a sky chart.

Nightjar's Log view: session totals and a table of eight observations with seeing dots, star ratings and notes.
Every session, with seeing and notes.

Nightjar's Gear view: six equipment cards with their specs, and a packing checklist.
The kit that goes in the car.

site/gallery/readme/list.html (--layout list)
<p align="center">
<img width="100%" src="site/public/gallery/readme/en/tonight.webp" alt="Nightjar&#39;s Tonight view: four condition cards, a table of six targets with altitude bars, and a sky chart." />
<br /><sub>Plan the night: conditions, targets and a sky chart.</sub>
</p>
<p align="center">
<img width="100%" src="site/public/gallery/readme/en/log.webp" alt="Nightjar&#39;s Log view: session totals and a table of eight observations with seeing dots, star ratings and notes." />
<br /><sub>Every session, with seeing and notes.</sub>
</p>
<p align="center">
<img width="100%" src="site/public/gallery/readme/en/gear.webp" alt="Nightjar&#39;s Gear view: six equipment cards with their specs, and a packing checklist." />
<br /><sub>The kit that goes in the car.</sub>
</p>

A second config runs tui.mjs in a pseudo terminal at 100x22 cells. The shot presses j once and waits for the selected target’s details; the clip starts a fresh process, moves down the queue and switches to the log. Unchanged frames merge, so 4.9 seconds of recording is five distinct frames. The clip is written as WebP and GIF (the default formats). Two frame choices keep it small: a solid background, and no maxWidth. Downscaling blends pixels, and with maxWidth: 1600 this clip’s lossless WebP was 351 KB instead of 121 KB at full size. inheritEnv: false keeps your environment out of the images. See terminal apps and clips.

The same clip as a GIF: terminal/tour.gif.

site/gallery/showcase.tty.config.mjs
// The terminal half of the docs gallery: tui.mjs, a dependency-free TUI with fixed data and a frozen clock.
// A tty config is its own file (a config captures either a browser page or a terminal), so the gallery
// script passes it with --config. As in showcase.config.mjs, wrap it in `defineConfig` in your own project.
export default {
name: 'nightjar queue',
target: {
mode: 'tty',
command: ['node', 'tui.mjs'],
// Only what a program needs to start: nothing from your environment can end up in the images.
inheritEnv: false,
cols: 100,
rows: 22,
},
ready: /Queue \(\d+\)/,
deviceScaleFactor: 2,
terminal: { theme: 'dark', font: { size: 15 } },
shots: [
{
id: 'queue',
title: 'Imaging queue',
caption: 'The imaging queue, one target selected.',
alt: 'A terminal showing the nightjar imaging queue: six targets with their progress, M27 selected, and its details with an altitude graph.',
keys: 'j',
waitFor: 'M27 Dumbbell',
},
],
clips: [
{
id: 'tour',
title: 'Queue tour',
caption: 'Moving down the queue, then over to the session log.',
alt: 'An animation of the nightjar queue: the selection moves down three targets, then the view switches to the session log.',
steps: [
{ sleep: 700 },
{ keys: 'j' },
{ sleep: 500 },
{ keys: 'j' },
{ sleep: 500 },
{ keys: 'j' },
{ sleep: 900 },
{ keys: '{Tab}' },
{ waitFor: 'Session log' },
],
tailMs: 1800,
durationMs: 10000,
// formats defaults to WebP and GIF.
},
],
frame: {
style: 'window',
theme: 'dark',
title: '{name}',
// A solid background keeps a lossless clip small; a gradient does not compress.
background: '#141a3a',
padding: 48,
// No maxWidth: downscaling blends pixels, and this clip came out three times larger with maxWidth: 1600.
},
outputs: {
raw: 'showcase-out/raw-tty/{lang}/{id}.png',
readme: '../public/gallery/terminal/{id}.webp',
clips: '../public/gallery/terminal/{id}.{ext}',
},
};

The icon set needs no config. The gallery script renders this site’s mark.svg to a 1024 pixel square PNG with sharp, then runs:

Terminal window
showcase icons --source site/gallery/showcase-out/icon-source.png --preset web --out site/public/gallery/icons

Every size is fitted onto a transparent square. See the icons guide.

FilePixelsSize
icons/apple-touch-icon.png180x18010.0 KB
icons/favicon.icoICO, several sizes4.4 KB
icons/icon-192.png192x19210.7 KB
icons/icon-512.png512x51228.8 KB

site/scripts/gallery.ts stops with a clear message when dist/cli.js is missing or when something already answers on the fixture server’s port. Otherwise it empties the output folders, renders the icon source, and runs the built CLI under Node with an argument list (no shell), one command at a time:

  1. all with the web config: capture, frame, and the portfolio export;
  2. hero with the web config;
  3. all --only queue with the tty config: the terminal shot;
  4. record with the tty config: the clip;
  5. icons with the web preset;
  6. hero or frame with each config in site/gallery/variants/, from the raw captures of steps 1 and 3;
  7. readme --layout <name> with the web config, once per layout, each written to site/gallery/readme/<name>.html;
  8. readme with site/gallery/readme.config.mjs, a config that only lists gallery images: the table in the kit’s own README, written to site/gallery/readme/kit-readme.html.

The kit starts the fixture server itself and stops its whole process tree when a command ends, and the script checks that the port is free again afterwards. Last, it writes site/gallery/gallery.manifest.json (the size, pixel size and SHA-256 hash of every output, which this page reads for image dimensions) and fails when the committed files together pass 4 MB. Raw captures stay in site/gallery/showcase-out/, which git ignores.

Two runs on the same machine write the same bytes. Another operating system rasterizes fonts differently, so expect small pixel differences in images regenerated there: regenerate on one OS if the committed files must not churn. The config reference lists every key the two configs use.