Skip to content

Build your first finish

A finish is one package folder and one CSS block. Here is the whole job.

A finish is a folder in lib/finishes/, named for it, holding two files:

lib/finishes/quarry/
quarry.manifest.json name, label, blurb, order, picker swatch
quarry.recipe.json the look, in the Studio's layer vocabulary

Copy a sibling’s two files and edit them. The manifest’s name is the identity: the folder and both file names must match it, and the build fails if they don’t. Then regenerate:

Terminal window
node tools/build-packages-index.js

That one folder does three things: finish: quarry starts working in front matter, the deck linter accepts it (while still flagging typos like quary), and the Studio’s picker and “Start from preset” row list it. The recipe is what “Start from preset” loads, so keep it as close to your CSS as the vocabulary allows.

The register is deliberately open: adding a finish is adding a folder, never a change to the engine.

In lib/base/base.finish.css:

section.finish-quarry {
/* every layer you use, and the -opaque twin of each */
}

Two disciplines make the difference between a preset that works and one that mostly works.

Declare all four slot families, even the ones you do not use. A finish is a set of custom properties, and an undeclared one inherits, so an unset slot can pick up a stray layer from elsewhere.

The empty value depends on where the slot lands. --fin-wash, --fin-texture, --fin-mark and --fin-edge become background layers, where none is the empty value. --fin-frame is the exception: it composes into a box-shadow list, where none is legal only as the sole value — write it there and the whole declaration goes invalid, taking the tone rail down with it on any tone-* slide. Its empty value is 0 0 transparent.

Write the -opaque twin of every full-bleed layer as you write the layer, not afterwards. Screen and print explains why; doing it in the same edit is how you avoid discovering it after export.

A two-layer preset

Try this. This is a complete finish. Give it a signature: change the texture's first 0deg to 45deg for a diagonal weave, or move the wash to a corner and drop it to 8%. Edit the top copy of each pair — the -opaque twins are the export face.

Every color comes from a token:

color-mix(in srgb, var(--accent) 12%, transparent) /* screen */
color-mix(in srgb, var(--accent) 12%, var(--fin-canvas)) /* export */
color-mix(in srgb, var(--text-heading) 6%, transparent) /* an ink pattern */

No hex. No url(). No mask-image. No margin.

Get this right and your finish works in every palette that ships, in both canvases, for free. Get it wrong once and it works in exactly the palette you tested.

The mark family has two slots. Declare both — the gradient as none, the glyph as an empty string:

--fin-mark: none;
--fin-mark-text: "";

A monogram baked into a shared finish makes it one organization’s finish. Empty is the default; the author opts in per deck.

5. Tell the rest of the tree the name exists

Section titled “5. Tell the rest of the tree the name exists”

Nothing to do. The engine’s register, the Studio’s picker, the reserved-name list that stops a user’s saved finish from shadowing yours, and the skill’s own count are all read from the package now. The one link the build still checks by hand is the CSS: checkFinishPackages fails if your package has no section.finish-quarry rule, or a rule has no package.

examples/quarry.md, six to ten slides, PDF committed. Include a title slide: that is where a finish written against the wrong canvas token shows its failure.

Then export through both paths, in both canvases, and look at all four files. A finish changes exported bytes, so this is not optional and a screen preview does not substitute for it.

You can test a finish on a single slide before touching the register, by putting the class on the slide directly:

<!-- _class: content finish-quarry -->

The engine treats any finish-* class as a finish and wires up the backdrop for it. That is exactly what the labs on these pages do — every one of them is an unregistered finish on one slide.

Everything above renders beautifully on screen. Making it survive a PDF is a separate problem: Screen and print.