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

@aotoki/slidev-theme-terraforming

v0.7.2

Published

[![NPM version](https://img.shields.io/npm/v/@aotoki/slidev-theme-terraforming?color=3AB9D4&label=)](https://www.npmjs.com/package/@aotoki/slidev-theme-terraforming)

Readme

Slidev Theme - Terraforming

NPM version

A personal theme for Slidev by Aotokitsuruya.

Install

Add the following frontmatter to your slides.md. Start Slidev then it will prompt you to install the theme automatically.

Learn more about how to use a theme.

Canvas

The theme draws on a 1920×1080 canvas, matching the design file it is derived from. A deck can go back to another size from its own headmatter:

---
canvasWidth: 980
---

The root font size is 16px, so a px measurement from the design file divided by 16 is its rem value, and divided by 4 is its UnoCSS spacing number — 72px is p-18.

Mermaid sizes its diagrams from its own text metrics, so its base type is set to the body step to keep a diagram proportionate to the canvas. An individual block can still take {scale: n} when it needs to be larger or smaller than that.

Transition

Slides cross over with tf-fade, the theme's own transition. Only the content fades: the sidebar is the same on every slide, so fading the whole page would dissolve it into an identical copy of itself and put motion in the one part of the canvas that never changes. It runs for --slidev-transition-duration, the same variable Slidev's own transitions read.

A deck can name any of Slidev's builtins instead:

---
transition: slide-left
---

Note that the builtins fade or slide the whole page, sidebar included.

Design Tokens

Tokens are defined in two layers in styles/tokens.css. The first layer names values for what they are, using the Tailwind v4 @theme namespaces:

| Namespace | Tokens | |----------------|----------------------------------------------------------------| | --color-* | kon-500 gunJyo-500 tamago-500 jinZamOmi-500 gray-200 gray-500 white black | | --text-* | title 64 · heading 48 · body 36 · sub 32 · note 16 | | --leading-* | none 1 · heading 1.25 · body 1.5 | | --font-* | sans mono | | --tf-motion-*| travel 600ms · settle 500ms · touch 200ms · delay 300ms · stagger 100ms · ease-move · ease-fade · rise 20 | | --spacing-* | line 5 · shadow 10 · sidebar 296 · slash 100 · block-w 270 · block-h 162 | | --container-*| content 1380 |

Leading answers a question about the text rather than about its size. none is for a line laid out as a single line, where the line box has to equal the type size for the box around it to measure out; body is for anything that may wrap; heading sits between and hands the extra back through a negative margin, so a one-line heading still lands where the design file's 100% put it. Space between blocks is always a margin, never leading.

The second layer names them for the role they play: --slidev-theme-primary, --slidev-theme-secondary, --slidev-theme-accent, --slidev-theme-neutral, --slidev-theme-text, --slidev-theme-surface, --slidev-theme-on-surface.

Both layers are wired into the UnoCSS theme, so slides can use them as utilities:

<div class="bg-gunJyo-500 text-white text-sub p-8">A block of 群青</div>
<p class="text-note text-neutral">A source link</p>

The utilities emit var() references rather than literals, so overriding a token in a deck's own style.css changes both the stylesheet and the utilities:

:root {
  --color-gunJyo-500: #3a8fc4;
}

Fonts

The sans stack lives in --font-sans and resolves to locally installed faces, so no webfont is fetched for it:

  • Futura / Futura PT for Latin
  • PingFang TC / LiHei Pro / Heiti TC / Microsoft JhengHei for CJK

Fetched from Google Fonts:

  • JetBrains Mono for code

Bundled with the theme:

Loaded from a CDN when Mermaid is used:

Layouts

  • default — content starts at the top edge
  • cover
  • intro
  • section
  • statement
  • center — centred vertically, text centred
  • diagram — centred on both axes, for a page whose content is the figure itself

Components

What the audience reads is written as children; what it does not — geometry, data, a resource, a presentational choice — is a prop. So a deck writes its content in the order it is read, and where each part lands is the theme's to decide.

About

The speaker introduction, written as the lines it is made of.

<About>
  <Name>蒼時弦也</Name>
  <Title>Software Architect</Title>
  <Contact>https://blog.aotoki.me/</Contact>
  <Contact>@elct9620</Contact>
</About>

| Component | Props | |-----------|-------| | About | avatar, alt | | Name | — | | Title | — | | Contact | — |

Write a Title for each role the talk gives the speaker and they are taken in turn, a click apart — the opening that says what the speaker used to be. The page learns its length from them, so there is no clicks: to write. All of them are laid out in the same place, so the column stays as wide as the longest and neither it nor the portrait beside it moves; the role fades rather than cuts.

<About>
  <Name>蒼時弦也</Name>
  <Title>Associate AI Engineer</Title>
  <Title>AI Engineer</Title>
  <Contact>https://blog.aotoki.me/</Contact>
</About>

Each kind counts its own turns, so a Title that changes on the first click is a sequence of its own — nothing about it decides when any other part of the introduction may change.

avatar is a prop because a portrait is a resource the page is given rather than something read off it. It carries no alt of its own: the name is written beside it, and repeating it would have a screen reader say it twice. alt is there for the deck that puts a face beside no name at all.

Diagram vocabulary

Every diagram is built from four ideas — a flat fill, a focus frame, a drawn stroke, and absence expressed as opacity. Nothing scales or bolds, so a figure can animate across clicks without the layout shifting underneath it.

| Component | Props | |-----------|-------| | Stage | column, gap, fit | | Block | color (gunJyo | tamago | jinZamOmi | gray), name, sub, hidden | | Group | name, column, gap, hidden | | Stroke | dir (right | left | both | up | down | both-y | none), label, labels, name, flip, length, hidden | | Focus | steps, of, color | | Caption | — | | Line | — |

Focus is the selection box. It is positioned absolutely and takes no space, so it never disturbs what it frames, and it transitions its position and size rather than fading — moving the focus reads as one rectangle travelling and resizing.

Name the pieces and let steps say what the frame holds at each click. Several names give the box that contains them all, so neighbours read as a range; null frames nothing, which is how the box waits offstage until the click it belongs to. There are no coordinates and no indices, so inserting a block renumbers nothing.

<Stage>
  <Block name="proxy" color="tamago">Proxy</Block>
  <Stroke :label="$clicks === 1 ? 'call' : ''" :labels="['call', 'result']" />
  <Block name="object" color="gunJyo">Object</Block>

  <Focus :steps="['proxy', ['proxy', 'object']]" />
</Stage>

<Caption>The real <strong>object</strong> lives on the far side.</Caption>

The list is what tells the slide how long it is — see Clicks — so the walk through a diagram is written in one place instead of being split between a frontmatter count and a chain of comparisons. Entries are addressed by absolute click number, so steps[0] is the state before the first click.

A caption that says something new at each click writes a Line for each. They are taken in turn a click apart, and all of them occupy one place, so the narration fades from one to the next and the diagram above it never moves:

<Caption>
  <Line>The real <strong>object</strong> lives on the far side.</Line>
  <Line>Calling the proxy sends the request <strong>across the wire</strong>.</Line>
</Caption>

of is the alternative, for a box driven from a $clicks expression of your own. It takes the same names as one entry of steps and the page then has to declare its own clicks. To frame something the stage does not own, leave both out and place the box by hand — Slidev's v-drag can position it in the preview and write the result back into the markdown:

<Focus v-drag="[420, 180, 660, 220]" />

Stroke takes its length from the widest entry in labels, so changing or clearing the label never resizes the line. Supply labels whenever the text changes across clicks. It is drawn as SVG so its weight survives any transform the slide applies; Slidev's own <Arrow> remains available for annotating a slide by coordinates.

Stage spaces its pieces with gap — the pieces keep the size the design file gives them and the breathing room is what adapts. A row holds about four blocks before it runs past the content width; past that, prefer column or a second page. fit is an escape hatch that scales one stage down to fit, at the cost of that page's blocks no longer matching the rest of the deck.

Charts

| Component | Props | |-----------|-------| | Bars | max, steps, active, reveal, log, axisStart, axisEnd | | Bar | value, text, via, name | | Axis | start, end | | Map2D | xStart, xEnd, yStart, yEnd, steps, active, reveal | | Point | x, y, name, tone (gunJyo | gray) |

Both charts are written as the marks on them. What a mark is of is its content; value and x/y say where it goes, and the rest qualifies that one datum — text is the figure to be read aloud, units included, and via is the annotation saying how it came about, the second line a Block's sub is.

<Bars log axis-start="1 μs" axis-end="10 ms" :steps="[null, 'wasm']">
  <Bar :value="1200" text="1.2 ms" via="fork">Process</Bar>
  <Bar name="wasm" :value="12" text="12 μs" via="linear memory">WebAssembly</Bar>
</Bars>

A bar has no length of its own — it is a share of the track, and no row can know its share until every other row has said how long it is. That is what the chart is for: it collects what its rows say and answers each of them, so a row added anywhere remeasures the lot.

The two ends of an axis are start and end. Where the axis is one part of a chart rather than the whole of it, they say which axis: axisStart and axisEnd on Bars, xStart and yStart on Map2D. They stay props because they name the space a chart is read in rather than anything plotted in it.

A Map2D is written as the marks on it. x and y are relative positions from 0 to 100 and there are no ticks: the chart is an argument about where things sit in relation to each other, and printed coordinates would be taken for measurements.

<Map2D x-start="Hard to use" x-end="Easy to use"
       y-start="Weak isolation" y-end="Strong isolation"
       :steps="[null, 'wasm']">
  <Point :x="88" :y="4">eval</Point>
  <Point name="wasm" :x="66" :y="80" tone="gunJyo">WebAssembly</Point>
</Map2D>

Both charts take the same red frame as a block, and move it with steps the way a Focus does — each entry naming the row or mark under discussion, null for none. A Bar and a Point are named the way a Block is, so steps refers to one without depending on what it says.

active is the alternative for a chart driven from a $clicks expression, taking the row's or mark's place and -1 for none.

Bars accepts log for data spanning orders of magnitude; using it obliges you to label both ends of the axis, since lengths on a log scale otherwise invite a linear reading. Every row shares one set of columns through subgrid, so each text column ends up as wide as the widest entry in it — long labels need nothing said about them, and the annotation column takes no more than its own gap when no row carries a via.

Arrival

A figure's pieces land in the order they are read rather than all at once, so the narration has a moment on each before the next one arrives. A Stage deals its pieces out along the row; Bars and Map2D deal out their rows and points. A Group arrives whole — it exists so that several pieces read as one thing.

Nothing is asked of the deck: the arrival plays when the slide is reached, and the first click ends it — asking for the next step says you are past being shown the slide, so whatever is still on its way is simply there. Export and print show every figure whole; nothing arrives on a page being captured on a timer.

The focus frame waits for the pieces it frames, since a frame around a space nothing has arrived in reads as a mistake rather than as emphasis.

reveal hands the pacing to the speaker instead, one row or point per click:

<Bars reveal>
  <Bar :value="1200" text="1.2 ms">Process</Bar>
  <Bar :value="220" text="220 μs">Container</Bar>
</Bars>

The prop is on the charts alone because they own their pieces. A Stage's pieces are written by the deck, so a deck paces those with v-click or hidden — see Clicks.

Motion

How long a change takes is a question about the change rather than about what is changing, so there are three answers and every rule in the theme picks one. The lengths are Material 3's duration tokens rather than numbers chosen by eye; a slide is read from further away and with less urgency than an interface, so each takes the slower end of what M3 offers.

| Token | | | |-------|--|--| | --tf-motion-travel | 600ms · M3 long4 | something moving or resizing, which has ground to cover — the focus frame crossing a diagram, the red frame stepping down a chart | | --tf-motion-settle | 500ms · M3 long2 | something arriving or leaving in place, which has none. Slidev's slide transition follows it | | --tf-motion-touch | 200ms · M3 short4 | direct feedback to the pointer, where anything slower reads as lag | | --tf-motion-delay | 300ms · M3 medium2 | how long a figure waits before arriving, so it does not run while the page is still fading in | | --tf-motion-stagger | 100ms · M3 short2 | the wait between one arriving piece and the next | | --tf-motion-rise | 20 | how far an arriving piece travels — set it to 0 for a plain fade |

There are two curves, because a curve describes how ground is covered and not every change covers any:

| Token | | | |-------|--|--| | --tf-motion-ease-move | M3 emphasized.decelerate | position, size, the rise a piece makes — off the mark quickly and then settling, so it reads as coming to rest where it belongs | | --tf-motion-ease-fade | ease-in-out | opacity and colour, which have no ground to cover. The move curve spends a fade in a twentieth of the time it was given, so the duration stops meaning anything |

They reach UnoCSS too, so motion a deck adds itself keeps time with the theme's: duration-travel, duration-settle, duration-touch, ease-move, ease-fade.

Clicks

Slidev works out how long a slide is from what registers with it — v-click, v-clicks, v-switch, v-click-gap — while reading $clicks in an expression registers nothing. Slidev's own v-motion reads clicks the same way, so this is the division of labour rather than an oversight in it.

steps puts a component on the declaring side: giving one to a Focus, Bars or Map2D is enough on its own, and a page whose only content is that figure will walk through every entry before moving on.

Anything driven from a $clicks expression — of, active, hidden, a Stroke's label — only reads, so a page built that way has to say how long it is. Often a v-switch in the narration already covers it; otherwise the frontmatter says so:

---
clicks: 3
---

That count overrides every registration on the page rather than adding to it, so a slide already carrying steps should leave it out — declaring both cuts the slide to whatever the frontmatter says.

Contributing

  • pnpm install
  • pnpm dev to preview example.md, which exercises every layout and component
  • pnpm test for the click and arrival protocols, pnpm test:watch while working on them
  • pnpm test:deck to walk example.md through Slidev in a real browser
  • pnpm typecheck for the theme's own files
  • pnpm lint, or pnpm lint:fix to have the style applied for you
  • pnpm export to generate the preview PDF
  • pnpm screenshot to generate the preview PNG

What a test can answer here stops short of what a slide looks like. The unit suite covers what a component decides — the length it declares, whether it thinks it is arriving, where it puts its frame — and the deck suite covers what Slidev makes of that, by walking example.md in a browser and asking how long each page turned out to be. How a rule cascades and how long a transition takes are still read off a running deck.

package.json lists what ships. A new file at the top level that Slidev is meant to load has to be added there, or it will be missing from the published package while working perfectly in this repository.