App definition and windows
Read slop.ts first. It declares a slop’s app: its identity, author, catalog
metadata, initial native window, theme colors and a new document’s values. The build stores catalog fields and a typed definition, with initial values in its
checkpoint. The embedded app and its defaults are
immutable in a built template or document, so make changes in the source project and
rebuild. Writable state and imported attachments belong to the document.
Start with a complete slop.ts
Section titled “Start with a complete slop.ts”The document field takes a defineDocument value; initial must fit it:
import { defineDocument, s } from "hitslop";
export default defineDocument({ count: s.counter() });import { defineSlop } from "hitslop";import schema from "./schema";import App from "./App.svelte";
export default defineSlop({ slug: "tiny-counter", view: App, title: "Tiny Counter", description: "A small counter that stays on your desktop.", author: { name: "Your Name", url: "https://example.com" }, categories: ["utilities"], window: { kind: "standard", width: 420, height: 520, resizable: true, shape: "22px" }, theme: { surface: "#fff7e6", ink: "#382d24", accent: "#28634b" }, document: schema, initial: { count: 0 },});Vite compiles the declaration and its imports. A restricted native evaluator initializes
it, then Rust checks the resulting definition and resource inventory. Metadata and
initial values do not ship in ui.js. The explicit slug is independent of the source
folder’s name. Import CSS in this entry or in a component; it is never discovered by name.
Identity and discovery fields
Section titled “Identity and discovery fields”| Field | Rule |
|---|---|
slug |
Explicit identity: 2–64 lowercase letters or numbers in kebab case, such as tiny-counter. |
author.name |
Required, 1–80 characters with at least one non-space character. This attribution travels with the template. |
author.url |
Optional HTTP or HTTPS URL, up to 2,048 characters. |
title |
Required, 1–80 characters. |
description |
Required, 1–240 characters. Explain the slop’s specific job. |
categories |
One or two unique catalog categories. |
Categories are productivity, utilities, finance, media, games,
developer-tools, education, business, personal, health, creative, music,
and other.
Set the initial window
Section titled “Set the initial window”window.width must be an integer from 240 to 4,096 pixels. Height must
be an integer from 180 to 4,096 pixels. Choose the smallest size at which the
main job feels complete; this becomes the initial native window and the basis
for capture work.
Standard windows support these options:
| Option | Values | Behavior |
|---|---|---|
resizable |
true or false |
Whether the user can resize the window. The host defaults to true. |
shape |
CSS radius string or SVG path object | Defines the native silhouette; defaults to "22px". |
lockAspect |
Boolean, default false | Preserves the initial width/height ratio when resizing. |
background |
transparent or glass |
transparent shows the desktop behind a custom HTML surface; glass frosts it. |
Choose a window shape
Section titled “Choose a window shape”shape is either a CSS radius string or a vector path. The host builds one native
silhouette from it and uses that silhouette for clipping, click-through, and the
mask on window-sized fallback PNG captures.
Radius strings accept one to four nonnegative px or % values, unitless 0,
and an optional / followed by vertical radii. Corners run top-left, top-right,
bottom-right, bottom-left, and overlapping radii shrink as in CSS. Use "50%" for an
ellipse or "9999px" for a pill. Radius strings are limited to 256 ASCII characters.
shape: "48px 8px 72px 0 / 24px 32px 18px 0",Vector paths use { path, viewBox, fillRule }. Coordinates are top-left
and y-down. viewBox defaults to the window’s width and height, so the path scales when
the window resizes, and fillRule defaults to nonzero. All SVG path commands are
supported. Paths are limited to 4,096 ASCII characters, 512 commands (implicit
repetitions count) and 2,048 expanded segments; each viewBox side is 1–16,384.
Arcs target 0.1 logical-point accuracy at the declared window size and must fit the
expanded-segment limit. Numerically unstable arcs are refused; nonzero arc radii must
exceed 0.00001 viewBox units. A concave notch on the right edge:
shape: { path: "M24 0 H456 Q480 0 480 24 V132 L432 156 L480 180 V336 Q480 360 456 360 H0 V48 Z", viewBox: [480, 360],},Set fillRule: "evenodd" and add a second subpath to cut a hole. Clicks in the
hole reach whatever is underneath the window:
shape: { path: "M24 0 H456 Q480 0 480 24 V336 Q480 360 456 360 H0 V48 Z M350 56 a44 32 0 1 0 0 64 a44 32 0 1 0 0 -64 Z", viewBox: [480, 360], fillRule: "evenodd",},Add lockAspect: true to keep the window at its initial ratio while resizing.
The build refuses invalid geometry. Dedicated exports and icons remain
independent of the window silhouette. Radius shapes expose their radius to the
page as --slop-window-radius; path shapes set it to 0.
The window is the stage in every mode. The host resets the page margins, and
the automatic app root (or a framework-neutral data-hitslop-root element) and its mount
ancestors fill the window. Prefer height: 100%, grid, or flex inside it rather
than repeating page sizing or using 100vh; deliberate overrides remain possible.
For a transparent window, draw the visible surface inside your app: the host
overrides ordinary html/body backgrounds with transparency, and a more specific or
!important page background defeats it.
A glass window puts the system’s frosted material behind the page and keeps your page
background. Give it a translucent theme color, such as paper: "#f6f3ee99" used as
body { background: var(--slop-paper) }: the desktop shows through blurred, and
anyone can make the window clearer or more solid from the theme panel. An opaque
background hides the glass. The frost stays light in dark mode, as your palette does;
a dark tint makes smoked glass. With Reduce Transparency on, macOS draws the frost as a
solid light surface, so keep text readable over it as well. Exports and icons can’t
capture the blur, so a translucent area stays translucent there; paint a solid surface
in Export.svelte when the export should look finished.
The native host exposes the presentation to your CSS:
| Host value | Meaning |
|---|---|
data-slop-presentation |
standard, transparent, glass, or skin |
--slop-window-radius |
The radius shape; 0 for paths, absent for PNG skins |
data-slop-resizable |
Present when the person can resize the window |
--slop-window-width, --slop-window-height |
Initial dimensions, not live viewport measurements |
data-slop-controls |
visible or hidden, following the native hover toolbar |
slop dev previews the window at its declared window size with the same shape or skin, but
without these host values; lay out from the actual viewport rather than assuming them.
Keep controls inside the visible shape and test focus, resizing, and dragging at the smallest size you support.
Resize the app and move the window
Section titled “Resize the app and move the window”An unskinned app can request a new bounded native size at runtime. The host
keeps the result on the visible screen and returns the applied size; window’s size
remains the next launch’s initial size. Browser preview has no native window: resize
it with the preview’s handle or arrow keys.
import { resizeWindow } from "hitslop/svelte";
// Ignored in browser preview, which has no native window.await resizeWindow({ width: 560, height: 680 });Use the native toolbar handle to move the window. There is no guest window-drag API; buttons, sliders, and text selection retain their ordinary behavior.
Show controls on hover
Section titled “Show controls on hover”For actions that should appear only on hover, follow html[data-slop-controls="visible"].
The host starts it at hidden and updates it with the native toolbar, including the
toolbar’s hit testing and its 0.8-second hide delay. Focus, Tab and disabled controls
don’t reveal these actions. Hide them with visibility: hidden and
pointer-events: none, not opacity alone, so they also leave keyboard navigation.
.my-slop-actions { visibility: hidden; pointer-events: none; }html[data-slop-controls="visible"] .my-slop-actions { visibility: visible; pointer-events: auto; }/* Browser preview has no attribute; fall back to :hover there only. */html:not([data-slop-controls]) .my-slop-shell:hover .my-slop-actions { visibility: visible; pointer-events: auto; }Mark these controls data-slop-export="hide" too, or leave them out of your export
view.
Use a PNG skin for a custom silhouette
Section titled “Use a PNG skin for a custom silhouette”A skinned window, { kind: "skin", width, height, image }, imports its PNG as image. It is fixed
at that size and cannot also use shape, background, resizable or lockAspect.
See PNG window skins for the full asset and
hit-testing workflow.