Skip to content

.esk bundle format

The canonical reference for what's inside a .esk folder.

Folder layout

myskin.esk/
  manifest.json              required
  document.json              required
  hooks.json                 optional; auto-generated
  textures/                  optional; baked PNG textures
    <placement_id>_albedo.png
    <placement_id>_normal.png
  assets/                    optional; any other resources
    studio.hdr
    masks/<placement>.png
  animations/                optional; exported animation tracks
    wing_flap.json
  shaders/                   optional; custom WGSL
  variants/                  optional; per-theme / per-platform overrides
    dark/document.json
  designer_layout.json       ignored by runtime (Designer's UI state)

manifest.json

{
  "schema_version": "1.0",
  "id": "dev.example.myskin",
  "name": "My Skin",
  "version": "0.1.0",
  "color_space": "srgb",
  "window": {
    "shape": { "kind": "path", "path_d": "M …" },
    "default_size": [720, 480]
  },
  "code_file": "myapp.py"
}

Fields

Field Required Notes
schema_version Yes "1.0" today
id Yes Reverse-DNS identifier
name Yes Human-readable
version Yes Semver
color_space Yes "srgb" / "linear" / "aces" / "rec709"
window.shape.kind No "rect" / "ellipse" / "path"
window.shape.path_d No SVG path (when kind="path")
window.default_size No [w, h] pixels
code_file No Paired Python file (Code Link)

document.json

{
  "placements": [
    { "id": "background", "kind": "ellipse", "x": 0, "y": 0, "width": 360, "height": 360, "fill": "#0f0d1eff" },
    { "id": "time", "kind": "label", "x": 80, "y": 168, "width": 200, "height": 24, "text": "00:00:00", "font_size": 18 }
  ]
}

Each placement is a typed dict with at least id and kind. The kind field dictates which other fields are valid.

Placement kinds

Kind Common fields
ellipse / rectangle / rounded_rect x, y, width, height, fill, stroke, stroke_width, radius
path path_d, fill, stroke, stroke_width
arc cx, cy, radius, start_angle, end_angle, stroke, cap
ticks cx, cy, inner_radius, outer_radius, count, stroke
label / heading x, y, width, height, text, font_family, font_size, fill, align
button / icon_button / orb_button x, y, width, height, label, fill, text_fill, radius
card / panel x, y, width, height, radius, material, background_image
image x, y, width, height, image_path
mesh3d translate, rotate, scale, texture_path, material, mesh_dist
light kind (directional/point/spot), color, intensity, direction/position
canvas x, y, width, height (Python-driven via publish_display_list)

The full type catalog lives in the framework source at python/elysium/skin/schema.py.

hooks.json

{
  "hooks": [
    { "id": "save", "events": ["click", "press"] },
    { "id": "name_input", "events": ["change", "focus", "blur"] }
  ]
}

Auto-generated by the Designer on save. Lists every hook exposed by the skin so the framework's @window.on(...) decorator can validate names. Code Link uses this for handler scaffolding.

Token references

Skin field values can reference theme tokens:

"stroke": "{theme.accent}",
"shadow": { "color": "{theme.accent}66", "blur": 18 }

Resolved on load_skin and on set_theme.

Variants

variants/
  dark/document.json
  high_contrast/document.json
  reduce_motion/document.json

The framework picks the matching variant based on the active theme + accessibility prefs, falling back to the root document.json otherwise.

On disk

Bundles are folders, not zips. Drag the folder between machines; the runtime resolves relative paths against the folder root.

See also