Skip to content

Guides

Embed with <lumorig-player>

One script tag and one element: a live, interactive mascot at about 8 KB gzipped.

<lumorig-player> is a web component that draws your mascot as live SVG and animates it with the same sampler the exporters use. One module script (about 8 KB gzipped), one element, no framework. It loads a bundle: the mascot's spec, every preset fitted to its rig, every saved animation and every canvas, so switching motions never needs another request.

Try it#

This is Pip rendered in the page. Change the controls and the snippet below the stage updates to match.

trigger
palette
index.html
<lumorig-player
  src="https://lumorig.com/api/v1/mascots/pip/bundle"
  animation="wave"
  trigger="auto"
  follow-cursor
></lumorig-player>

Install#

Load the module from /player.js on your Lumorig origin, then drop the element anywhere. It registers itself as lumorig-player when the script runs. Point src at the mascot's bundle_url.

index.html
<script type="module" src="https://lumorig.com/player.js"></script>

<lumorig-player
  src="https://lumorig.com/api/v1/mascots/msc_…/bundle"
  animation="idle"
  trigger="hover"
  follow-cursor
  style="width: 200px; height: 200px"
></lumorig-player>

The element is 240 px wide and square by default; size it with CSS. Get a ready snippet with lumorig embed msc_… -a wave or the get_embed_code MCP tool.

Serving the bundle yourself#

The bundle endpoint sends open CORS headers, so it works from any origin. If you would rather not depend on it at runtime, fetch the JSON once, host it with your site, and point src at your copy, or set the bundle property directly.

bash
curl https://lumorig.com/api/v1/mascots/msc_…/bundle -H "Authorization: Bearer $LUMORIG_API_KEY" -o public/pip.bundle.json

Attributes#

NameTypeDescription
srcURLBundle to load. Changing it loads the new one.
animationstringClip to play: a preset id (wave, think…) or a saved animation's name. Unknown names fall back to the bundle's first clip.
triggerauto | hover | click | noneauto (default) plays at once. hover plays while the pointer is over it and rewinds on leave. click toggles play and pause. none waits for play().
speednumberPlayback rate, default 1. Must be positive.
looptrue | falseOverride the clip's own looping. loop="false" plays once and fires ended; any other value loops. Omit it to use the clip's setting.
paletteJSONRecolor in the browser by token, e.g. palette='{"fur":"#3A5BFF"}'. Token names are in mascot.palette.
follow-cursorbooleanThe pupils (a part named pupils) or else the head drift toward the pointer.
canvasstringRun a canvas from the bundle by name. It takes over from animation while set.
no-shadowbooleanHide the soft floor shadow. Read when the mascot is built, so set it before the bundle loads.

Methods and properties#

MemberDoes
play(name?)Play from the current time. With a name, switches animation first. Restarts a finished one-shot clip.
pause()Stop at the current frame.
seek(t)Jump to t seconds and draw that frame.
setInput(name, value)Set a canvas input (boolean or number).
fire(name)Fire a canvas trigger input.
bundleGet or set the loaded bundle object. Setting it skips the network entirely.
currentTime · duration · pausedRead-only timeline state, in seconds.
stateThe current canvas state id, or null when no canvas is running.
script
const pip = document.querySelector("lumorig-player");

pip.play("celebrate");
pip.pause();
pip.seek(0.4);

// Load a bundle you already have, no request:
pip.bundle = await fetch("/pip.bundle.json").then((r) => r.json());

Events#

EventWhendetail
readyThe bundle loaded and the mascot is drawn.—
endedA non-looping clip reached its end.—
statechangeA canvas moved to a new state.{ state }
errorThe bundle failed to load, or the canvas name isn't in the bundle.The Error
ts
pip.addEventListener("ready", () => pip.play("wave"));
pip.addEventListener("statechange", (e) => console.log("now", e.detail.state));
pip.addEventListener("error", (e) => console.warn("mascot failed", e.detail));

React#

The repository ships a thin React wrapper, MascotPlayer, that renders the element and registers it on mount, so it is safe to server-render. Props map one-to-one onto the attributes (followCursor for follow-cursor, palette as an object), plus bundle, onReady and onEnded.

Helper.tsx
import { MascotPlayer } from "@lumorig/player/react";

export function Helper() {
  return (
    <MascotPlayer
      src="https://lumorig.com/api/v1/mascots/msc_…/bundle"
      animation="idle"
      trigger="hover"
      followCursor
      style={{ width: 200, height: 200 }}
      onReady={(el) => el.play("wave")}
    />
  );
}
today
import { useEffect } from "react";

export function Helper() {
  useEffect(() => {
    import(/* webpackIgnore: true */ "https://lumorig.com/player.js");
  }, []);
  return <lumorig-player src="https://lumorig.com/api/v1/mascots/msc_…/bundle" animation="idle" trigger="hover" />;
}

Private mascots#

Bundles of public mascots load with no key. Private ones need a signed, expiring share link (never put an API key in a web page).

# Option 1: make it public (permanent embed)
curl -X PATCH https://lumorig.com/api/v1/mascots/msc_… -H "Authorization: Bearer $LUMORIG_API_KEY" \
  -H "Content-Type: application/json" -d '{ "public": true }'

# Option 2: a signed link, here for 7 days (default 1 hour, max 30 days)
curl -X POST https://lumorig.com/api/v1/mascots/msc_…/share -H "Authorization: Bearer $LUMORIG_API_KEY" \
  -H "Content-Type: application/json" -d '{ "ttl_seconds": 604800 }'
# → { "token": "…", "expires_at": "…", "bundle_url": "/api/v1/mascots/msc_…/bundle?token=…" }

Share links stop working when they expire (403 forbidden, “Share token expired.”), so refresh them server-side before then, or make the mascot public. Public bundles are cached for 60 seconds, so a new version reaches embeds within a minute.

Accessibility and motion#

  • The SVG carries role="img" and the mascot's name as its label. If the mascot is decorative, add aria-hidden="true" to the element.
  • Under prefers-reduced-motion: reduce nothing plays on its own: auto and hover show the first frame. A click plays the animation once and returns to the first frame, so motion only happens when the visitor asks for it.
  • Nothing animates while it isn't playing: the render loop stops when the clip pauses, ends, or the element leaves the page.