Shapes & Frames
StageRoutine separates geometric silhouettes from visual presentation.
Geometry is defined by reusable path generators in paths. You apply geometry to stage elements using two complementary primitives:
Shape: Renders surface backgrounds, cards, diagram nodes, and text labels.Frame: Clips visual media, webcam feeds, images, or DOM elements to the path silhouette.
Shape
Shape requires a path generator as its first argument, followed by optional child content and configuration options:
Shape(path, content?, options?)
The example below uses paths.circle():
import { paths, Shape } from "stageroutine";
const circle = Shape(paths.circle(), "Circle", {
position: [80, 45],
origin: "center",
size: 15,
});
Surface Variants
Shapes support three surface treatments via the variant option:
"surface"(default): Translucent background fill."ghost": Transparent interior with vector border outline."solid": Opaque background fill.
const outline = Shape(paths.circle(), "Ghost", {
size: 10,
variant: "ghost",
});
Frame
Frame is a dedicated clipping container. It masks its children to an SVG path geometry contour without applying a background fill:
Frame(path, media, options?)
Wrap any element inside a Frame to clip it to that path. This example clips an image to a heart with paths.heart():
import { Frame, Image, paths } from "stageroutine";
const gradient = `data:image/svg+xml;utf8,${encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#38bdf8"/><stop offset="1" stop-color="#f43f5e"/></linearGradient></defs><rect width="400" height="400" fill="url(#g)"/></svg>',
)}`;
const heart = Frame(paths.heart(), Image(gradient, { fit: "cover" }), {
position: [80, 45],
origin: "center",
size: 18.33,
borderColor: "#38bdf8",
strokeWidth: 2,
});
For a live presenter bubble, swap Image for Webcam({ mirror: true }). See Video & Camera.
Optional Borders
Frame includes an optional vector stroke overlay on top of the clipped media:
borderColor: Border color string (default:"transparent").strokeWidth: Stroke width in virtual pixels (default:0).active: Highlighted glow state (default:false).
When no border color or stroke width is specified, Frame functions as pure clipping.
Built-in Geometries
The paths object provides generators for the standard diagram shapes:
| Path Generator | Default Options | Description |
|---|---|---|
paths.box() | { radius: 12 } | Rounded rectangle (also available via Card). |
paths.circle() | { fit: "contain" } | Uniform circle or proportional ellipse ("contain" or "fill"). |
paths.pill() | None | Capsule with fully rounded semicircle ends. |
paths.diamond() | { radius: 10.5 } | Diamond decision node with softly rounded tips. |
paths.triangle() | { direction: "up", radius: 10 } | Directional triangle ("up", "down", "left", "right"). |
paths.hexagon() | { orientation: "pointy" } | Regular hexagon ("pointy" or "flat" orientation). |
paths.star() | { points: 5, innerRadius: 0.45 } | Symmetrical multi-pointed star. |
paths.squircle() | { curvature: 0.82 } | Continuous G2 curvature superellipse. |
paths.heart() | None | Symmetrical heart shape. |
paths.polygon() | { sides: 5 } | Regular N-sided polygon. |
paths.speechBubble() | { radius: 12, tail: "bottom", tailWidth: 24 } | Rounded bubble with a pointy pointer tail. |
paths.thoughtBubble() | { tail: "bottom-left", lobes: 9 } | Scalloped cloud with a curved trail of shrinking bubbles. |
The same geometry powers every primitive. Pass extra options to change the silhouette:
const rating = Shape(paths.star({ points: 6 }), "Featured", { size: 11.67 });
const gateway = Shape(paths.hexagon({ orientation: "flat" }), "Gateway", { size: 10.83 });
The bubble generators accept a tail length, a local anchor preset ("bottom-left", "top-right", etc.), or a live target:
import { layout, paths, Shape } from "stageroutine";
const question = Shape(paths.speechBubble({ tail: "bottom-left" }), "Hello, world!", {
width: 18.33,
height: 11.67,
borderColor: "#38bdf8",
strokeWidth: 2,
});
const idea = Shape(paths.thoughtBubble({ tail: "bottom-right" }), "Thinking...", {
width: 18.33,
height: 11.67,
borderColor: "#38bdf8",
strokeWidth: 2,
});
layout.hstack([question, idea], { x: "center", y: 22, align: "center", gap: 10 });
Pass a { to, anchor } object or an element directly to connect the tail to a live target. The tip re-aims every frame as the target moves and lands on the target outline:
const agent = Card("Agent Alpha", { width: 14.17, height: 9.17 });
const bubble = Shape(
paths.speechBubble({ tail: { to: agent, anchor: "top" } }),
"Following the agent",
{ width: 20.83, height: 10 },
);
agent.to({ x: 96 }).ease("cubicInOut"); // the tail follows live
Targets also accept an attachment mode ("auto" (default), "closest", "ray"), a clearance padding, or a fixed stage point via { to: [x, y] }.
Connector Perimeter Tracking
Connectors automatically calculate intersection points against the outer boundary of any Shape or Frame:
import { Connector, Frame, paths, Shape } from "stageroutine";
const source = Shape(paths.circle(), "Source", { size: 8.33 });
const target = Frame(paths.squircle(), photo, { size: 10 });
const arrow = Connector(source, target, { routing: "bezier" });
The layout engine uses raycast bisection to attach connector endpoints directly to the perimeter of the shape.
Custom Paths
You can pass any function that calculates an SVG path d string to either Shape or Frame.
For complete instructions and tutorial examples on building custom geometries, see the Custom Paths guide.