StealThis .dev

Doctor Intro Video (Remotion)

A 6-second vertical intro card for a healthcare provider — circular avatar with initials springs into view on a teal glow, doctor name and specialty fade up in layers, three credential pills stagger in with spring scale, a shimmer sweep crosses the card at mid-point, and a Book a Consultation CTA button scales in at the end.

Open Remotion
remotion react typescript
Targets: TS React

Preview

Code

import React from "react";
import {
  AbsoluteFill,
  Composition,
  interpolate,
  spring,
  useCurrentFrame,
  useVideoConfig,
} from "remotion";

// ── Config ─────────────────────────────────────────────────────────────────
const DOCTOR_NAME = "Dr. Elena Reyes";
const DOCTOR_INITIALS = "ER";
const SPECIALTY = "Cardiologist · MD, PhD";
const CLINIC_NAME = "Greenfield Medical Center";

const CREDENTIALS = [
  { label: "15+ Years Exp.", delay: 100 },
  { label: "Board Certified", delay: 118 },
  { label: "500+ Reviews ⭐", delay: 136 },
];

const CTA_TEXT = "Book a Consultation";

// ── Palette ────────────────────────────────────────────────────────────────
const BG = "#0a1a18";
const TEAL = "#12b5a8";
const TEAL_SOFT = "#e7f5f3";
const WHITE = "#ffffff";
const MUTED = "#6b9e99";

// ── Spring helper ──────────────────────────────────────────────────────────
const SP_CONFIG = { damping: 14, stiffness: 120 };

// ── Background glow layer ──────────────────────────────────────────────────
const BackgroundGlow: React.FC = () => (
  <div
    style={{
      position: "absolute",
      inset: 0,
      background: `radial-gradient(ellipse 700px 700px at 50% 38%, ${TEAL}22 0%, transparent 70%)`,
    }}
  />
);

// ── Subtle grid pattern overlay ────────────────────────────────────────────
const GridOverlay: React.FC<{ frame: number }> = ({ frame }) => {
  const opacity = interpolate(frame, [0, 40], [0, 0.06], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  return (
    <div
      style={{
        position: "absolute",
        inset: 0,
        opacity,
        backgroundImage: `
          linear-gradient(${TEAL}80 1px, transparent 1px),
          linear-gradient(90deg, ${TEAL}80 1px, transparent 1px)
        `,
        backgroundSize: "80px 80px",
      }}
    />
  );
};

// ── Avatar circle ──────────────────────────────────────────────────────────
const Avatar: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
  const scale = spring({
    frame,
    fps,
    from: 0,
    to: 1,
    config: SP_CONFIG,
  });

  const ringOpacity = interpolate(frame, [20, 50], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const ringScale = spring({
    frame: Math.max(0, frame - 10),
    fps,
    from: 0.6,
    to: 1,
    config: { damping: 12, stiffness: 80 },
  });

  return (
    <div
      style={{
        position: "relative",
        width: 280,
        height: 280,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        transform: `scale(${scale})`,
      }}
    >
      {/* Outer pulse ring */}
      <div
        style={{
          position: "absolute",
          width: 296,
          height: 296,
          borderRadius: "50%",
          border: `3px solid ${TEAL}`,
          opacity: ringOpacity * 0.4,
          transform: `scale(${ringScale})`,
        }}
      />
      {/* Inner ring */}
      <div
        style={{
          position: "absolute",
          width: 310,
          height: 310,
          borderRadius: "50%",
          border: `1.5px solid ${TEAL}`,
          opacity: ringOpacity * 0.2,
        }}
      />
      {/* Avatar body */}
      <div
        style={{
          width: 260,
          height: 260,
          borderRadius: "50%",
          background: `linear-gradient(145deg, ${TEAL} 0%, #0d8a80 100%)`,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          boxShadow: `0 0 60px ${TEAL}55, 0 0 120px ${TEAL}22`,
          border: `4px solid ${TEAL}66`,
        }}
      >
        <span
          style={{
            fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
            fontWeight: 800,
            fontSize: 80,
            color: WHITE,
            letterSpacing: -2,
            lineHeight: 1,
          }}
        >
          {DOCTOR_INITIALS}
        </span>
      </div>
    </div>
  );
};

// ── Clinic badge ───────────────────────────────────────────────────────────
const ClinicBadge: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
  const delayed = Math.max(0, frame - 25);
  const opacity = interpolate(delayed, [0, 18], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const translateY = spring({
    frame: delayed,
    fps,
    from: -12,
    to: 0,
    config: SP_CONFIG,
  });

  return (
    <div
      style={{
        opacity,
        transform: `translateY(${translateY}px)`,
        display: "flex",
        alignItems: "center",
        gap: 8,
        backgroundColor: `${TEAL}18`,
        border: `1px solid ${TEAL}44`,
        borderRadius: 100,
        paddingInline: 20,
        paddingBlock: 8,
        marginBottom: 32,
      }}
    >
      {/* Heart-pulse icon (SVG inline) */}
      <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
        <path
          d="M3 12h4l3-7 4 14 3-7h4"
          stroke={TEAL}
          strokeWidth="2.2"
          strokeLinecap="round"
          strokeLinejoin="round"
        />
      </svg>
      <span
        style={{
          fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
          fontWeight: 600,
          fontSize: 22,
          color: TEAL,
          letterSpacing: 0.3,
        }}
      >
        {CLINIC_NAME}
      </span>
    </div>
  );
};

// ── Doctor name & specialty ────────────────────────────────────────────────
const DoctorNameBlock: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
  const nameDelayed = Math.max(0, frame - 38);
  const specialtyDelayed = Math.max(0, frame - 55);

  const nameOpacity = interpolate(nameDelayed, [0, 20], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const nameY = spring({
    frame: nameDelayed,
    fps,
    from: 24,
    to: 0,
    config: SP_CONFIG,
  });

  const specOpacity = interpolate(specialtyDelayed, [0, 20], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const specY = spring({
    frame: specialtyDelayed,
    fps,
    from: 20,
    to: 0,
    config: SP_CONFIG,
  });

  return (
    <div style={{ textAlign: "center", marginBottom: 40 }}>
      <div
        style={{
          opacity: nameOpacity,
          transform: `translateY(${nameY}px)`,
          fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
          fontWeight: 800,
          fontSize: 64,
          color: WHITE,
          letterSpacing: -1.5,
          lineHeight: 1.1,
          marginBottom: 10,
        }}
      >
        {DOCTOR_NAME}
      </div>
      <div
        style={{
          opacity: specOpacity,
          transform: `translateY(${specY}px)`,
          fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
          fontWeight: 500,
          fontSize: 32,
          color: MUTED,
          letterSpacing: 0.5,
        }}
      >
        {SPECIALTY}
      </div>
    </div>
  );
};

// ── Credential pill ────────────────────────────────────────────────────────
const CredentialPill: React.FC<{
  label: string;
  frame: number;
  fps: number;
  delay: number;
}> = ({ label, frame, fps, delay }) => {
  const delayed = Math.max(0, frame - delay);

  const scale = spring({
    frame: delayed,
    fps,
    from: 0,
    to: 1,
    config: { damping: 13, stiffness: 160 },
  });

  const opacity = interpolate(delayed, [0, 10], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  return (
    <div
      style={{
        opacity,
        transform: `scale(${scale})`,
        backgroundColor: `${TEAL_SOFT}12`,
        border: `1.5px solid ${TEAL}55`,
        borderRadius: 100,
        paddingInline: 28,
        paddingBlock: 12,
        fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
        fontWeight: 600,
        fontSize: 24,
        color: TEAL_SOFT,
        whiteSpace: "nowrap",
        letterSpacing: 0.2,
      }}
    >
      {label}
    </div>
  );
};

// ── Credential pills row ───────────────────────────────────────────────────
const CredentialPills: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => (
  <div
    style={{
      display: "flex",
      flexWrap: "wrap",
      gap: 16,
      justifyContent: "center",
      marginBottom: 60,
    }}
  >
    {CREDENTIALS.map((cred) => (
      <CredentialPill
        key={cred.label}
        label={cred.label}
        frame={frame}
        fps={fps}
        delay={cred.delay}
      />
    ))}
  </div>
);

// ── Divider line ───────────────────────────────────────────────────────────
const Divider: React.FC<{ frame: number }> = ({ frame }) => {
  const width = interpolate(frame, [70, 110], [0, 600], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const opacity = interpolate(frame, [70, 90], [0, 0.35], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  return (
    <div
      style={{
        width,
        height: 1,
        background: `linear-gradient(90deg, transparent, ${TEAL}, transparent)`,
        opacity,
        marginBottom: 60,
      }}
    />
  );
};

// ── Shimmer sweep across card ──────────────────────────────────────────────
const ShimmerSweep: React.FC<{ frame: number }> = ({ frame }) => {
  // Sweeps between frame 80 and 130
  const rawX = interpolate(frame, [80, 130], [-120, 120], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const opacity = interpolate(frame, [80, 88, 122, 130], [0, 0.6, 0.6, 0], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  return (
    <div
      style={{
        position: "absolute",
        inset: 0,
        pointerEvents: "none",
        overflow: "hidden",
      }}
    >
      <div
        style={{
          position: "absolute",
          top: 0,
          bottom: 0,
          width: 200,
          left: "50%",
          transform: `translateX(calc(-50% + ${rawX}%))`,
          background: `linear-gradient(105deg, transparent 20%, ${WHITE}14 50%, transparent 80%)`,
          opacity,
        }}
      />
    </div>
  );
};

// ── CTA button ────────────────────────────────────────────────────────────
const CTAButton: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
  const delayed = Math.max(0, frame - 155);

  const scale = spring({
    frame: delayed,
    fps,
    from: 0.7,
    to: 1,
    config: SP_CONFIG,
  });

  const opacity = interpolate(delayed, [0, 14], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  return (
    <div
      style={{
        opacity,
        transform: `scale(${scale})`,
        background: `linear-gradient(135deg, ${TEAL} 0%, #0d9e93 100%)`,
        borderRadius: 100,
        paddingInline: 72,
        paddingBlock: 26,
        boxShadow: `0 8px 40px ${TEAL}55`,
        display: "flex",
        alignItems: "center",
        gap: 14,
      }}
    >
      {/* Calendar icon */}
      <svg width="30" height="30" viewBox="0 0 24 24" fill="none">
        <rect x="3" y="4" width="18" height="18" rx="3" stroke={WHITE} strokeWidth="2" />
        <path d="M3 9h18" stroke={WHITE} strokeWidth="2" />
        <path d="M8 2v4M16 2v4" stroke={WHITE} strokeWidth="2" strokeLinecap="round" />
        <path d="M7 13h4M7 17h6" stroke={WHITE} strokeWidth="2" strokeLinecap="round" />
      </svg>
      <span
        style={{
          fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
          fontWeight: 700,
          fontSize: 32,
          color: WHITE,
          letterSpacing: 0.3,
        }}
      >
        {CTA_TEXT}
      </span>
    </div>
  );
};

// ── Main composition ───────────────────────────────────────────────────────
export const DoctorIntro: React.FC = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  // Overall card fade-in
  const cardOpacity = interpolate(frame, [0, 12], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  return (
    <AbsoluteFill style={{ backgroundColor: BG, overflow: "hidden" }}>
      <BackgroundGlow />
      <GridOverlay frame={frame} />

      {/* Main card */}
      <AbsoluteFill
        style={{
          opacity: cardOpacity,
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          justifyContent: "center",
          paddingInline: 80,
          paddingBlock: 60,
          gap: 0,
        }}
      >
        {/* Top clinic badge */}
        <ClinicBadge frame={frame} fps={fps} />

        {/* Avatar */}
        <div style={{ marginBottom: 40 }}>
          <Avatar frame={frame} fps={fps} />
        </div>

        {/* Name + specialty */}
        <DoctorNameBlock frame={frame} fps={fps} />

        {/* Animated divider */}
        <Divider frame={frame} />

        {/* Credential pills */}
        <CredentialPills frame={frame} fps={fps} />

        {/* CTA */}
        <CTAButton frame={frame} fps={fps} />
      </AbsoluteFill>

      {/* Shimmer sweep (layered above content) */}
      <ShimmerSweep frame={frame} />
    </AbsoluteFill>
  );
};

// ── Remotion Root ──────────────────────────────────────────────────────────
export const RemotionRoot: React.FC = () => (
  <Composition
    id="DoctorIntro"
    component={DoctorIntro}
    durationInFrames={180}
    fps={30}
    width={1080}
    height={1920}
  />
);

Doctor Intro Video

A vertical 9:16 “Meet Your Doctor” card designed for short-form video platforms and in-app onboarding flows. A large circular avatar placeholder — dark teal background with bold initials “ER” — springs onto screen driven by Remotion’s spring() function. Below it, the doctor’s name slides up with a fade, followed by the specialty line one beat later. A subtle teal glow pulsates behind the avatar, and a thin grid overlay adds depth to the dark background.

Three credential pills — “15+ Years Exp.”, “Board Certified”, and “500+ Reviews ⭐” — appear in staggered spring-scale bursts between frames 100–150, giving the composition a punchy rhythm. At frame 80–130 a shimmer light sweep (wide white-to-transparent gradient translated horizontally) passes over the full card, adding the polish of a premium broadcast lower-third. The composition closes with a teal “Book a Consultation” button that scales in from 70% with spring easing, complete with a calendar SVG icon and a soft teal drop shadow.

All motion is driven exclusively by spring() and interpolate() — no CSS transitions or keyframe animations. Every visual element (avatar, glow, grid, icons) is rendered with inline styles, SVG, or CSS gradients, so the composition is fully self-contained with zero external assets.

Composition specs

PropertyValue
Resolution1080 × 1920
FPS30
Duration6.0 s (180 frames)

Timeline

TimeFrameAction
0.0 s0Card fades in (opacity 0 → 1 over 12 frames)
0.0 s0Avatar springs in from scale 0 with SP_CONFIG
0.8 s25Clinic badge slides down and fades in
1.3 s38Doctor name slides up and fades in
1.8 s55Specialty line slides up and fades in
2.3 s70Divider line sweeps outward
3.3 s100Credential pill 1 scales in
3.9 s118Credential pill 2 scales in
4.5 s136Credential pill 3 scales in
2.7 s80Shimmer sweep begins (ends frame 130)
5.2 s155CTA button scales in from 0.7 with spring
6.0 s180Hold

Customization

  • DOCTOR_NAME / DOCTOR_INITIALS — Replace with any provider name; initials auto-render in the avatar.
  • SPECIALTY — Change specialty and credentials (e.g. Orthopedic Surgeon · MD).
  • CLINIC_NAME — Shown in the top badge with a pulse-line icon.
  • CREDENTIALS — Array of { label, delay } objects; add or remove pills freely.
  • CTA_TEXT — Swap for “Request an Appointment”, “View Profile”, etc.
  • SP_CONFIG — Adjust damping and stiffness to change the spring feel across all animations.
  • TEAL / BG — Swap the accent and background colors to match your brand palette.

Illustrative UI only — not intended for real medical use.