Skip to main content

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 GeneratorDefault OptionsDescription
paths.box(){ radius: 12 }Rounded rectangle (also available via Card).
paths.circle(){ fit: "contain" }Uniform circle or proportional ellipse ("contain" or "fill").
paths.pill()NoneCapsule 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()NoneSymmetrical 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.

API Reference​