Logo sting
Draws a real SVG mark in stroke order, snaps to a filled mark with one sound at the moment the last path finishes, then holds fully still for at least a second.
LogoSting, defaults
Installation
npx shadcn add https://www.reelcn.dev/r/logo-sting.jsonFiles land in src/reelcn/ with the items it builds on. Dependencies: remotion.
Usage
<Center>
<LogoSting viewBox="0 0 48 48" paths={["M8 24 L24 8 L40 24 L24 40 Z"]} size={160} sfx="chime" />
</Center>Props
PropTypeDescription
delaynumberFrames to wait before entering.
durationnumberEnter duration in frames. Defaults to 0.6s. Some items reuse
duration for an unrelated local timing. See that item's own prop docs.exitnumber | booleanExit at the end of the parent `<Sequence>`.
false keeps it on screen, a number sets the exit length in frames.motionMotionPersonalityOverride the theme's motion personality.
posterbooleanRenders fully entered, with no exit: for a still frame or thumbnail. Defaults to
false.holdFramesnumberDelays the start of the exit window by this many frames, for a minimum on-screen hold. Defaults to
0.
Minimum static hold after the snap, in frames. Defaults to fps (a hard ≥1s floor, M3).viewBox*string
paths*string[]
sizenumber Default
200.colorstring
sfxfalse | "success" | "error" | "pop" | "rise" | "drop" | "typing" | "bell" | "bubble" | "camera-shutter" | "chime" | "click" | "click-soft" | "coin" | "ding" | "glitch" | "impact" | "keypress" | "notification" | "pop-high" | "riser-short" | "sparkle" | "swipe" | "swish" | "swoosh" | "tap" | "thud" | "tick" | "transition-hit" | "whoosh" | "whoosh-soft"Fires once, the moment the last path finishes drawing.
false for silence. Default "chime".styleCSSProperties
classNamestring
Built from
Use
- A brand's own logo mark drawing itself, not a generic "LOGO" placeholder
- The opening or closing beat of `brand-reel`
Avoid
- A logo that needs to keep moving after it lands. Pair with
svg-drawdirectly instead
Guides
Components take their colors from a theme, their timing from motion props and their layout from the canvas.
Source
registry/items/logo-sting.tsx
/**
* @title Logo Sting
* @category motion
* @description Draws a real SVG mark in stroke order, snaps to a filled mark with one sound at the moment the last path finishes, then holds fully still for at least a second.
* @duration data-driven
* @use A brand's own logo mark drawing itself, not a generic "LOGO" placeholder
* @use The opening or closing beat of `brand-reel`
* @avoid A logo that needs to keep moving after it lands. Pair with `svg-draw` directly instead
* @tags logo, sting, draw, brand, sfx
* @example
* <Center>
* <LogoSting viewBox="0 0 48 48" paths={["M8 24 L24 8 L40 24 L24 40 Z"]} size={160} sfx="chime" />
* </Center>
*/
import type React from "react";
import { Sequence, useVideoConfig } from "remotion";
import { clamp01, type MotionProps, StrokeOverlay, tween, useMotion, useTheme, useViewport } from "./core";
import { Sfx, type SfxName } from "./sfx";
export type LogoStingProps = MotionProps & {
viewBox: string;
paths: string[];
size?: number;
color?: string;
/** Fires once, the moment the last path finishes drawing. `false` for silence. */
sfx?: SfxName | false;
/** Minimum static hold after the snap, in frames. Defaults to `fps` (a hard ≥1s floor, M3). */
holdFrames?: number;
style?: React.CSSProperties;
className?: string;
};
export function LogoSting({
viewBox,
paths,
size = 200,
color,
sfx = "chime",
holdFrames,
style,
className,
...motion
}: LogoStingProps) {
const theme = useTheme();
const { u } = useViewport();
const { fps } = useVideoConfig();
// A sting's whole point is to snap into place and hold (M3: pixel-static, logo still visible, for the
// final second), `useMotion`'s own default (`exit: true`) would auto-fade it out before that hold ever
// starts, since `holdFrames` there only pulls the exit's own end *earlier*, never suppresses it. Default
// to no exit; a caller who explicitly wants one still gets it (`exit`/`holdFrames` stay public props).
const m = useMotion({ exit: false, ...motion, holdFrames: holdFrames ?? fps });
const fill = color ?? theme.colors.foreground;
const step = Math.round(m.fps * 0.15);
const box = viewBox.split(/[\s,]+/).map(Number);
const vbWidth = box[2];
const vbHeight = box[3];
const renderWidth = u(size);
const renderHeight = vbWidth > 0 ? renderWidth * (vbHeight / vbWidth) : renderWidth;
const lastDrawEnd = m.delay + (paths.length - 1) * step + m.enterFrames;
const snap = clamp01(
tween(m.frame, m.fps, { from: lastDrawEnd, duration: Math.round(m.fps * 0.25), motion: "snappy" }),
);
return (
<>
<svg
aria-hidden="true"
className={className}
width={renderWidth}
height={renderHeight}
viewBox={viewBox}
style={{ opacity: 1 - m.exit, overflow: "visible", ...style }}
>
{paths.map((d, index) => {
const drawn = Math.max(
0,
Math.min(
1,
tween(m.frame, m.fps, { from: m.delay + index * step, duration: m.enterFrames, motion: m.preset }),
),
);
return (
<g key={index} opacity={drawn > 0 ? 1 : 0}>
<path d={d} fill={fill} fillOpacity={snap} stroke="none" />
<StrokeOverlay
d={d}
kind={theme.stroke}
seed={`logo-sting-${index}`}
color={fill}
strokeWidth={3}
drawn={drawn}
extraProps={{ strokeLinecap: "round", strokeLinejoin: "round", strokeOpacity: 1 - snap }}
/>
</g>
);
})}
</svg>
{sfx !== false ? (
<Sequence from={Math.max(0, lastDrawEnd)}>
<Sfx name={sfx} />
</Sequence>
) : null}
</>
);
}