Skip to content

Growing Reveal

Stopped
The complete example source and integration notes are available below.

Read as Markdown

Full-page experience · Open playground

Paint a living alpha mask that reveals illustrated landscapes and colorful typography.

Paint the artwork, pause the mask, and switch between landscape and typography.

  • Reuse procedural texture output in Canvas composition.
  • Keep texture generation independent of display size.
  • Uses the optional @pibbl/core/textures package and the public render-layer lifecycle.
  • Texture renderer sources are borrowed for the synchronous callback; Pibbl owns presentation.

This is a Pibbl Canvas example. Its mount owns its runtime resources; call its controller’s dispose method when removing it. The preview starts automatically unless reduced motion is enabled; Stop disposes the mount. Preserve the source’s logical canvas size and aspect ratio. Native HTML is not captured by Canvas filters.

Host setup for this source: use a canvas with width 880 and height 630, compile with jsx: "react-jsx" and jsxImportSource: "@pibbl/core", import its default mount, and call const controller = mount(canvas) after attaching the canvas. Call controller.dispose() before removing it.

/** @jsxImportSource @pibbl/core */
import {
pibbl,
Group,
Rectangle,
Text,
FocusManagement,
useSignal,
useReaction,
type PibblController,
} from "@pibbl/core";
import { ReactionDiffusion, type TextureRenderer } from "@pibbl/core/textures";
// The artwork and raster alpha mask are composed locally. No new core mask API.
const landscape: TextureRenderer = (ctx, texture) => {
const { width: w, height: h } = texture;
const sky = ctx.createLinearGradient(0, 0, 0, h);
sky.addColorStop(0, "#f2b1a1");
sky.addColorStop(0.5, "#efdbc0");
sky.addColorStop(1, "#5fa398");
ctx.fillStyle = sky;
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "#fff2ba";
ctx.beginPath();
ctx.arc(w * 0.72, h * 0.28, h * 0.19, 0, Math.PI * 2);
ctx.fill();
for (let layer = 0; layer < 4; layer++) {
ctx.fillStyle = ["#a36e88", "#6b6882", "#366572", "#184e5b"][layer];
ctx.beginPath();
ctx.moveTo(0, h);
for (let x = 0; x <= w; x += 4)
ctx.lineTo(
x,
h * (0.47 + layer * 0.12) +
Math.sin((x / w) * 9 + layer * 2) * h * 0.13 +
Math.sin((x / w) * 21 + layer) * h * 0.025,
);
ctx.lineTo(w, h);
ctx.closePath();
ctx.fill();
}
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(texture.mask, 0, 0, w, h);
};
const typography: TextureRenderer = (ctx, texture) => {
const gradient = ctx.createLinearGradient(
0,
0,
texture.width,
texture.height,
);
gradient.addColorStop(0, "#ecb984");
gradient.addColorStop(0.5, "#ef8da8");
gradient.addColorStop(1, "#8cbbbc");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, texture.width, texture.height);
ctx.fillStyle = "#203148";
ctx.font = "900 100px Georgia";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("STAY", texture.width / 2, texture.height * 0.36);
ctx.fillText("CURIOUS", texture.width / 2, texture.height * 0.68);
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(texture.mask, 0, 0, texture.width, texture.height);
};
function Button({
x,
label,
active,
onClick,
}: {
x: number;
label: string;
active?: boolean;
onClick: () => void;
}) {
return (
<Group style={{ translateX: x }}>
<Rectangle
keyboardFocusable
onClick={onClick}
style={{
width: 146,
height: 38,
cornerRadius: 19,
fill: active ? "#edbea7" : "#303343",
cursor: "pointer",
}}
/>
<Text
pointerEvents="none"
style={{
left: 73,
top: 24,
textBaseline: "alphabetic",
textAlign: "center",
font: "600 12px Arial",
fill: active ? "#2c2732" : "#ede5e2",
}}
>
{label}
</Text>
</Group>
);
}
function GrowingReveal({ canvas }: { canvas: HTMLCanvasElement }) {
useReaction(() => {
const previous = canvas.style.touchAction;
canvas.style.touchAction = "none";
return () => {
if (canvas.style.touchAction === "none")
canvas.style.touchAction = previous;
};
}, [canvas]);
const state = useSignal({ paused: false, reset: 0, artwork: 0, brush: 12 });
const s = state.get();
return (
<FocusManagement>
<Group>
<Rectangle
pointerEvents="none"
style={{ width: 880, height: 630, fill: "#171c2b" }}
/>
<Text
pointerEvents="none"
style={{
left: 28,
top: 30,
textBaseline: "alphabetic",
font: "600 11px Arial",
fill: "#d3a9b3",
}}
>
TEXTURE STUDIES / 03
</Text>
<Text
pointerEvents="none"
style={{
left: 28,
top: 70,
textBaseline: "alphabetic",
font: "600 34px Georgia",
fill: "#f5e5dc",
}}
>
Growing Reveal
</Text>
<Text
pointerEvents="none"
style={{
left: 28,
top: 95,
textBaseline: "alphabetic",
font: "13px Arial",
fill: "#aeb0c4",
}}
>
Paint a window into another world. The living mask grows, divides, and
recedes.
</Text>
<Group style={{ translateX: 24, translateY: 118 }}>
<Rectangle
pointerEvents="none"
style={{ width: 832, height: 388, fill: "#0d1422" }}
/>
<ReactionDiffusion
seed={18}
preset="stripes"
paused={s.paused}
resetRevision={s.reset}
render={s.artwork === 0 ? landscape : typography}
resolution={{ width: 256, height: 144 }}
brush={{ radius: s.brush, strength: 1 }}
style={{ width: 832, height: 388 }}
/>
</Group>
<Group style={{ translateX: 24, translateY: 526 }}>
<Button
x={0}
label="Landscape"
active={s.artwork === 0}
onClick={() => state.update((v) => ({ ...v, artwork: 0 }))}
/>
<Button
x={160}
label="Stay curious"
active={s.artwork === 1}
onClick={() => state.update((v) => ({ ...v, artwork: 1 }))}
/>
<Button
x={352}
label={s.paused ? "Resume" : "Pause"}
active={s.paused}
onClick={() => state.update((v) => ({ ...v, paused: !v.paused }))}
/>
<Button
x={512}
label={`Brush ${s.brush}`}
onClick={() =>
state.update((v) => ({ ...v, brush: v.brush === 12 ? 24 : 12 }))
}
/>
<Button
x={672}
label="Begin again"
onClick={() => state.update((v) => ({ ...v, reset: v.reset + 1 }))}
/>
</Group>
<Text
pointerEvents="none"
style={{
left: 28,
top: 602,
textBaseline: "alphabetic",
font: "11px Arial",
fill: "#929bae",
}}
>
One simulation → one alpha mask → your artwork. Switching artwork
preserves the living pattern.
</Text>
</Group>
</FocusManagement>
);
}
export default function mount(canvas: HTMLCanvasElement): PibblController {
return pibbl(canvas, <GrowingReveal canvas={canvas} />);
}

Documentation built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.