Build your first finish
A finish is one package folder and one CSS block. Here is the whole job.
1. Add the package
Section titled “1. Add the package”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 vocabularyCopy 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:
node tools/build-packages-index.jsThat 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.
2. Write the preset
Section titled “2. Write the preset”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.
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.
3. Keep it palette-blind
Section titled “3. Keep it palette-blind”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.
4. Leave the mark empty
Section titled “4. Leave the mark empty”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.
6. Ship a demo deck and export it
Section titled “6. Ship a demo deck and export it”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.
Trying one without registering it
Section titled “Trying one without registering 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.