Skip to content

PNG window skins

Start with a radius ("22px", "50%", or "9999px") or SVG path shape and background: "transparent". Use a PNG when you need a hole in the window or an outline those shapes cannot describe. PNG skins are fixed-size and cannot resize. A washer with a transparent center is a useful first test.

Add an RGBA PNG under assets/. Its pixel dimensions must be exactly 1× or 2× the window’s logical width and height. Alpha defines both the native backing and where the window accepts pointer input. Import it in slop.ts and use the skin window:

import skin from "./assets/window-mask.png";
// Inside defineSlop({ ... })
window: { kind: "skin", width: 480, height: 480, image: skin },

A skin window contains kind, width, height, and image. It cannot also specify shape, background, or resizable, and the native window stays at the skin’s fixed size.

The host draws the PNG behind the page, supplies window sizing, and overrides ordinary html/body backgrounds with transparency. Avoid overriding that transparency with more-specific or !important page backgrounds. The generated Svelte entry provides the app root automatically; mark a framework-neutral root with data-hitslop-root; its mount ancestors fill the window too. No sizing/transparency reset or duplicated pixel constants are needed; use 100% or --slop-window-width/--slop-window-height. Align controls with opaque areas of the PNG using ordinary CSS.

The native host exposes initial dimensions as --slop-window-width and --slop-window-height, and sets data-slop-presentation="skin". These layout rules are disabled for capture.

Pixels below 10% alpha are click-through. Feathered edge pixels may still receive or reject clicks depending on their alpha, so keep important controls well inside opaque regions. Transparent holes can intentionally expose and click the content behind the window.

  • Confirm the PNG is RGBA and is exactly 1× or 2× the logical window dimensions.
  • Test clicks around curved edges, transparent holes, and soft shadows.
  • Move the window with its native toolbar handle; guest controls do not drag it.
  • Check that focus rings and menus stay inside the usable silhouette.
  • Preview at native size; a skin is not a responsive window treatment.

Vite includes the imported PNG as an immutable content-addressed asset, even if no component renders it. Rust validates the PNG stream and infers its scale; the native mask uses that scale for crisp rendering. Document images imported by a user are separate attachments served by app.attachments.url(id).