Medication Guide Video (Remotion)
A light-mode 16:9 Remotion explainer for a fictional medication — split-screen layout with a teal left panel (pill icon, drug name, dose) that slides in from the left, and a right panel where three staggered instruction cards fade up with spring-animated check circles, accent line, and subtle dot-grid background. Ideal for patient education, pharmacy onboarding, or clinic app demos.
Preview
Code
import React from "react";
import {
AbsoluteFill,
Composition,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from "remotion";
// ── Config constants (swap these to rebrand) ───────────────────────────
const CLINIC_NAME = "Greenfield Medical Center";
const DRUG_NAME = "Cardomax";
const DRUG_DOSE = "10 mg";
const DRUG_SCHEDULE = "Once daily";
const DISCLAIMER = "Consult your doctor before making any changes.";
// ── Color palette ──────────────────────────────────────────────────────
const BG = "#f1f7f6";
const INK = "#0d2b27";
const TEAL = "#12b5a8";
const TEAL_SOFT = "#e7f5f3";
const MUTED = "#6b9e99";
const WHITE = "#ffffff";
const TEAL_DARK = "#0a8f85";
// ── Spring config (shared) ─────────────────────────────────────────────
const SP = { damping: 14, stiffness: 120 };
// ── Helpers ────────────────────────────────────────────────────────────
function fadeIn(frame: number, start: number, end: number): number {
return interpolate(frame, [start, end], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
}
function slideX(frame: number, start: number, end: number, from: number): number {
return interpolate(frame, [start, end], [from, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
}
// ── PillIcon: decorative SVG pill ─────────────────────────────────────
const PillIcon: React.FC<{ size: number; color: string }> = ({ size, color }) => (
<svg
width={size}
height={size * 0.46}
viewBox="0 0 120 55"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
{/* Capsule body */}
<rect x="1" y="1" width="118" height="53" rx="26.5" fill={WHITE} stroke={color} strokeWidth="3" />
{/* Left half fill */}
<path d="M1 27.5C1 14.0 13.0 1 26.5 1H60V54H26.5C13.0 54 1 41.0 1 27.5Z" fill={color} />
{/* Center divider */}
<line x1="60" y1="3" x2="60" y2="52" stroke={color} strokeWidth="2.5" />
{/* Shine on left half */}
<ellipse cx="30" cy="18" rx="12" ry="6" fill="rgba(255,255,255,0.25)" />
</svg>
);
// ── ClockIcon ─────────────────────────────────────────────────────────
const ClockIcon: React.FC<{ size: number; color: string }> = ({ size, color }) => (
<svg width={size} height={size} viewBox="0 0 48 48" fill="none">
<circle cx="24" cy="24" r="21" stroke={color} strokeWidth="3" fill={TEAL_SOFT} />
<line x1="24" y1="12" x2="24" y2="24" stroke={color} strokeWidth="3" strokeLinecap="round" />
<line x1="24" y1="24" x2="33" y2="29" stroke={color} strokeWidth="3" strokeLinecap="round" />
<circle cx="24" cy="24" r="2.5" fill={color} />
</svg>
);
// ── WaterDropIcon ─────────────────────────────────────────────────────
const WaterDropIcon: React.FC<{ size: number; color: string }> = ({ size, color }) => (
<svg width={size} height={size} viewBox="0 0 48 48" fill="none">
<path
d="M24 6C24 6 10 20 10 30C10 38 16.3 44 24 44C31.7 44 38 38 38 30C38 20 24 6 24 6Z"
fill={TEAL_SOFT}
stroke={color}
strokeWidth="3"
strokeLinejoin="round"
/>
<ellipse cx="19" cy="28" rx="3.5" ry="6" fill="rgba(255,255,255,0.55)" />
</svg>
);
// ── CalendarCheckIcon ─────────────────────────────────────────────────
const CalendarCheckIcon: React.FC<{ size: number; color: string }> = ({ size, color }) => (
<svg width={size} height={size} viewBox="0 0 48 48" fill="none">
<rect x="5" y="9" width="38" height="34" rx="5" fill={TEAL_SOFT} stroke={color} strokeWidth="3" />
<line x1="5" y1="20" x2="43" y2="20" stroke={color} strokeWidth="2.5" />
<line x1="16" y1="5" x2="16" y2="14" stroke={color} strokeWidth="3" strokeLinecap="round" />
<line x1="32" y1="5" x2="32" y2="14" stroke={color} strokeWidth="3" strokeLinecap="round" />
{/* Check mark */}
<polyline points="15,30 21,36 33,26" stroke={color} strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round" fill="none" />
</svg>
);
// ── CheckCircle: animating teal check badge ────────────────────────────
const CheckCircle: React.FC<{ frame: number; fps: number; delay: number }> = ({
frame,
fps,
delay,
}) => {
const f = Math.max(0, frame - delay);
const scale = spring({ frame: f, fps, from: 0, to: 1, config: SP });
const opacity = fadeIn(f, 0, 8);
return (
<div
style={{
width: 28,
height: 28,
borderRadius: "50%",
background: TEAL,
display: "flex",
alignItems: "center",
justifyContent: "center",
transform: `scale(${scale})`,
opacity,
flexShrink: 0,
boxShadow: `0 0 10px ${TEAL}55`,
}}
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<polyline
points="3,8 6.5,11.5 13,5"
stroke={WHITE}
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
/>
</svg>
</div>
);
};
// ── Step data ─────────────────────────────────────────────────────────
interface Step {
icon: React.FC<{ size: number; color: string }>;
text: string;
sub: string;
}
const STEPS: Step[] = [
{
icon: ClockIcon,
text: "Take with food in the morning",
sub: "Best absorbed with breakfast",
},
{
icon: WaterDropIcon,
text: "Drink a full glass of water",
sub: "At least 240 mL (8 fl oz)",
},
{
icon: CalendarCheckIcon,
text: "Do not skip doses",
sub: "Same time every day for best results",
},
];
// ── LeftPanel: pill + drug identity ───────────────────────────────────
const LeftPanel: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
const panelX = slideX(frame, 0, 22, -340);
const opacity = fadeIn(frame, 0, 18);
// Inner items stagger
const pillScale = spring({ frame: Math.max(0, frame - 4), fps, from: 0.7, to: 1, config: SP });
const labelOpacity = fadeIn(Math.max(0, frame - 10), 0, 14);
const doseOpacity = fadeIn(Math.max(0, frame - 18), 0, 14);
const tagScale = spring({ frame: Math.max(0, frame - 24), fps, from: 0, to: 1, config: SP });
return (
<div
style={{
width: 420,
height: "100%",
background: `linear-gradient(160deg, ${TEAL} 0%, ${TEAL_DARK} 100%)`,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
padding: "0 40px",
gap: 0,
transform: `translateX(${panelX}px)`,
opacity,
position: "relative",
overflow: "hidden",
flexShrink: 0,
}}
>
{/* Background circle decoration */}
<div
style={{
position: "absolute",
top: -80,
left: -80,
width: 300,
height: 300,
borderRadius: "50%",
background: "rgba(255,255,255,0.06)",
pointerEvents: "none",
}}
/>
<div
style={{
position: "absolute",
bottom: -60,
right: -60,
width: 220,
height: 220,
borderRadius: "50%",
background: "rgba(255,255,255,0.05)",
pointerEvents: "none",
}}
/>
{/* Clinic name badge */}
<div
style={{
position: "absolute",
top: 32,
left: 0,
right: 0,
textAlign: "center",
opacity: labelOpacity,
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
fontWeight: 500,
fontSize: 12,
letterSpacing: "0.14em",
textTransform: "uppercase",
color: "rgba(255,255,255,0.65)",
}}
>
{CLINIC_NAME}
</div>
{/* Pill icon */}
<div
style={{
transform: `scale(${pillScale})`,
marginBottom: 32,
filter: "drop-shadow(0 8px 24px rgba(0,0,0,0.22))",
}}
>
<PillIcon size={160} color={WHITE} />
</div>
{/* Drug name */}
<div
style={{
opacity: labelOpacity,
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
fontWeight: 700,
fontSize: 52,
color: WHITE,
letterSpacing: "-0.02em",
lineHeight: 1,
textAlign: "center",
}}
>
{DRUG_NAME}
</div>
{/* Dose line */}
<div
style={{
opacity: doseOpacity,
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
fontWeight: 400,
fontSize: 20,
color: "rgba(255,255,255,0.80)",
marginTop: 10,
letterSpacing: "0.04em",
textAlign: "center",
}}
>
{DRUG_DOSE} · {DRUG_SCHEDULE}
</div>
{/* "Rx" tag */}
<div
style={{
marginTop: 28,
transform: `scale(${tagScale})`,
background: "rgba(255,255,255,0.15)",
border: "1.5px solid rgba(255,255,255,0.35)",
borderRadius: 24,
padding: "6px 18px",
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
fontWeight: 600,
fontSize: 13,
letterSpacing: "0.1em",
color: WHITE,
textTransform: "uppercase",
}}
>
Prescription Only
</div>
</div>
);
};
// ── SingleStep: one animated instruction row ───────────────────────────
const SingleStep: React.FC<{
step: Step;
index: number;
frame: number;
fps: number;
baseDelay: number;
}> = ({ step, index, frame, fps, baseDelay }) => {
const delay = baseDelay + index * 20;
const f = Math.max(0, frame - delay);
const rowOpacity = fadeIn(f, 0, 14);
const rowY = interpolate(f, [0, 18], [20, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const iconScale = spring({ frame: Math.max(0, f - 4), fps, from: 0.6, to: 1, config: SP });
return (
<div
style={{
opacity: rowOpacity,
transform: `translateY(${rowY}px)`,
display: "flex",
alignItems: "center",
gap: 20,
padding: "18px 22px",
background: WHITE,
borderRadius: 16,
boxShadow: "0 2px 16px rgba(18,181,168,0.10), 0 1px 4px rgba(0,0,0,0.06)",
border: `1.5px solid ${TEAL_SOFT}`,
}}
>
{/* Step number badge */}
<div
style={{
width: 32,
height: 32,
borderRadius: "50%",
background: TEAL_SOFT,
border: `2px solid ${TEAL}`,
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
fontWeight: 700,
fontSize: 14,
color: TEAL_DARK,
}}
>
{index + 1}
</div>
{/* Icon */}
<div style={{ transform: `scale(${iconScale})`, flexShrink: 0 }}>
<step.icon size={40} color={TEAL} />
</div>
{/* Text block */}
<div style={{ flex: 1 }}>
<div
style={{
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
fontWeight: 600,
fontSize: 17,
color: INK,
lineHeight: 1.2,
marginBottom: 4,
}}
>
{step.text}
</div>
<div
style={{
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
fontWeight: 400,
fontSize: 13,
color: MUTED,
}}
>
{step.sub}
</div>
</div>
{/* Check circle */}
<CheckCircle frame={f} fps={fps} delay={20} />
</div>
);
};
// ── RightPanel: header + three steps + disclaimer ─────────────────────
const RightPanel: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
const RIGHT_DELAY = 14;
const panelOpacity = fadeIn(Math.max(0, frame - RIGHT_DELAY), 0, 18);
const headingY = interpolate(Math.max(0, frame - RIGHT_DELAY), [0, 20], [-18, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const headingOpacity = fadeIn(Math.max(0, frame - RIGHT_DELAY), 0, 18);
// Divider line
const lineWidth = interpolate(Math.max(0, frame - (RIGHT_DELAY + 8)), [0, 22], [0, 100], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Disclaimer
const disclaimerOpacity = fadeIn(Math.max(0, frame - 200), 0, 18);
return (
<div
style={{
flex: 1,
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "center",
padding: "40px 52px",
gap: 0,
opacity: panelOpacity,
}}
>
{/* Section header */}
<div
style={{
opacity: headingOpacity,
transform: `translateY(${headingY}px)`,
marginBottom: 6,
}}
>
<div
style={{
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
fontWeight: 500,
fontSize: 13,
letterSpacing: "0.18em",
textTransform: "uppercase",
color: TEAL,
marginBottom: 6,
}}
>
How to take it
</div>
<div
style={{
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
fontWeight: 700,
fontSize: 32,
color: INK,
letterSpacing: "-0.02em",
lineHeight: 1.1,
}}
>
Your Daily Guide
</div>
</div>
{/* Accent line */}
<div
style={{
height: 3,
width: `${lineWidth}%`,
background: `linear-gradient(90deg, ${TEAL} 0%, ${TEAL_SOFT} 100%)`,
borderRadius: 2,
marginBottom: 28,
}}
/>
{/* Steps */}
<div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
{STEPS.map((step, i) => (
<SingleStep
key={step.text}
step={step}
index={i}
frame={frame}
fps={fps}
baseDelay={30 + i * 20}
/>
))}
</div>
{/* Disclaimer */}
<div
style={{
marginTop: 24,
opacity: disclaimerOpacity,
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
fontWeight: 400,
fontSize: 12,
color: MUTED,
textAlign: "center",
letterSpacing: "0.01em",
lineHeight: 1.5,
}}
>
{DISCLAIMER}
</div>
</div>
);
};
// ── Background decorations ─────────────────────────────────────────────
const BackgroundDecor: React.FC = () => (
<>
{/* Subtle top-right blob */}
<div
style={{
position: "absolute",
top: -120,
right: -80,
width: 380,
height: 380,
borderRadius: "50%",
background: `radial-gradient(ellipse, ${TEAL_SOFT} 0%, transparent 70%)`,
pointerEvents: "none",
opacity: 0.6,
}}
/>
{/* Bottom-left faint blob */}
<div
style={{
position: "absolute",
bottom: -100,
left: 360,
width: 280,
height: 280,
borderRadius: "50%",
background: `radial-gradient(ellipse, ${TEAL_SOFT} 0%, transparent 70%)`,
pointerEvents: "none",
opacity: 0.45,
}}
/>
{/* Fine dot grid pattern via repeating-linear-gradient */}
<div
style={{
position: "absolute",
inset: 0,
backgroundImage:
"radial-gradient(circle, rgba(18,181,168,0.08) 1px, transparent 1px)",
backgroundSize: "28px 28px",
pointerEvents: "none",
}}
/>
</>
);
// ── Main composition ───────────────────────────────────────────────────
export const MedicationGuide: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
return (
<AbsoluteFill
style={{
backgroundColor: BG,
overflow: "hidden",
display: "flex",
flexDirection: "row",
}}
>
<BackgroundDecor />
{/* Left panel */}
<LeftPanel frame={frame} fps={fps} />
{/* Thin separator */}
<div
style={{
width: 1,
height: "100%",
background: `linear-gradient(180deg, transparent 0%, ${TEAL}44 30%, ${TEAL}44 70%, transparent 100%)`,
flexShrink: 0,
opacity: interpolate(frame, [16, 30], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
/>
{/* Right panel */}
<RightPanel frame={frame} fps={fps} />
</AbsoluteFill>
);
};
// ── Remotion Root ──────────────────────────────────────────────────────
export const RemotionRoot: React.FC = () => (
<Composition
id="MedicationGuide"
component={MedicationGuide}
durationInFrames={270}
fps={30}
width={1280}
height={720}
/>
);Medication Guide Video
A polished 9-second 16:9 Remotion explainer designed for patient-facing healthcare content. The composition uses a split-screen layout: a rich teal left panel carries the medication identity (pill SVG, drug name, dose, and a prescription badge), while the right panel reveals a three-step instruction list with staggered spring entrances. All motion is driven by spring() and interpolate() — no CSS transitions — giving the animation a physical, organic feel across clinic apps or social health content.
The left panel slides in from off-screen left over the first 22 frames, then each instruction card fades up with a 20-frame stagger starting at frame 30. Each card features a custom SVG icon (clock, water drop, calendar check) plus a teal check circle that scales in via spring() with the shared { damping: 14, stiffness: 120 } config. An accent gradient line wipes across below the section header, and soft radial blobs plus a repeating dot-grid layer add depth to the #f1f7f6 light background without cluttering the layout.
All text and branding live in typed constants at the top of react.tsx: CLINIC_NAME, DRUG_NAME, DRUG_DOSE, DRUG_SCHEDULE, and the three STEPS objects. Swap those values to adapt the video to any medication, clinic brand, or language — no layout changes needed.
Composition specs
| Property | Value |
|---|---|
| Resolution | 1280 × 720 |
| FPS | 30 |
| Duration | 9.0 s (270 frames) |
Timeline
| Time | Frame | Action |
|---|---|---|
| 0:00 | 0 | Left panel begins sliding in from left; pill icon scales up |
| 0:00–0:07 | 0–22 | Left panel slide-in + pill spring scale completes |
| 0:00–0:06 | 0–18 | Right panel fades in; heading slides up; accent line wipes |
| 0:01 | 30 | Step 1 card fades up + icon springs in + check circle springs in |
| 0:01.7 | 50 | Step 2 card fades up |
| 0:02.3 | 70 | Step 3 card fades up |
| 0:06.7 | 200 | Disclaimer text fades in |
| 0:09 | 270 | Composition ends (all elements fully visible) |
Customization
CLINIC_NAME— replaces the badge at the top of the left panelDRUG_NAME/DRUG_DOSE/DRUG_SCHEDULE— controls the pill identity blockSTEPS— array of{ icon, text, sub }objects; add or remove rows to change step countDISCLAIMER— small print at the bottom of the right panelSP— shared spring config{ damping, stiffness }for all spring animationsBG/TEAL/INK— top-level color constants for full palette rebrand
Illustrative UI only — not intended for real medical use.