Innovations
heroes /

Practice Portrait Hero (Medical / Dental / Professional)

Two-column hero with eyebrow pill + headline + 2 CTAs on the left, oval-cropped portrait on tinted background on the right. Below: 3-card trust strip with years-experience stat, team-avatar stack + appointment CTA, and a

Preview

Source

tsx
import { ArrowRight, Search, Heart, Stethoscope } from "lucide-react";

export interface PracticePortraitProps {
  brand?: string;
  navItems?: { label: string; href: string }[];
  signUpCta?: { label: string; href: string };
  loginCta?: { label: string; href: string };
  eyebrowLink?: { label: string; href: string };
  headlinePart1?: string; // "Your health, our Priority."
  headlinePart2?: string; // "Caring for youth"
  highlightedText?: string; // "excellence." (in accent color)
  description?: string;
  primaryCta?: { label: string; href: string };
  secondaryCta?: { label: string; href: string };
  /** Doctor portrait — sits inside the oval. */
  imageSrc?: string;
  imageAlt?: string;
  /** Trust strip — team avatars (3-4 small circular portraits). */
  teamAvatars?: { src: string; alt: string }[];
  experienceYears?: string;
  experienceLabel?: string;
  legacyTitle?: string;
  legacyDescription?: string;
  meetTitle?: string;
  bookCta?: { label: string; href: string };
  /** Default = Crypictys medical primary blue #2496c0. */
  primaryColor?: string;
  /** Default = light tint behind oval portrait. */
  ovalBgColor?: string;
}

const defaultTeamAvatars = [
  { src: "https://images.unsplash.com/photo-1559839734-2b71ea197ec2?w=160&h=160&fit=crop&crop=face", alt: "Team member" },
  { src: "https://images.unsplash.com/photo-1612349317150-e413f6a5b16d?w=160&h=160&fit=crop&crop=face", alt: "Team member" },
  { src: "https://images.unsplash.com/photo-1594824476967-48c8b964273f?w=160&h=160&fit=crop&crop=face", alt: "Team member" },
];

export default function PracticePortraitHero({
  brand = "Crypictys",
  navItems = [
    { label: "Pages", href: "#" },
    { label: "Service", href: "#" },
    { label: "Blog", href: "#" },
    { label: "About us", href: "#" },
  ],
  signUpCta = { label: "Sign Up", href: "#" },
  loginCta = { label: "Login", href: "#" },
  eyebrowLink = { label: "Learn More About Our Team", href: "#about" },
  headlinePart1 = "Your health, our Priority.",
  headlinePart2 = "Caring for youth",
  highlightedText = "excellence.",
  description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed diam nonumy eiusmod tincidunt ut labore et dolore magna aliquyam erat.",
  primaryCta = { label: "Get Started", href: "#start" },
  secondaryCta = { label: "About Us", href: "#about" },
  imageSrc = "/heroes/practice-portrait/doctor.webp",
  imageAlt = "Lead physician",
  teamAvatars = defaultTeamAvatars,
  experienceYears = "10",
  experienceLabel = "Years Experience",
  legacyTitle = "Building a legacy of Care and Compassion",
  legacyDescription = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed diam nonumy eirmod tincidunt ut labore.",
  meetTitle = "Meet Our Expert Physicians",
  bookCta = { label: "Book Your Appointment", href: "#book" },
  primaryColor = "#2496c0",
  ovalBgColor = "#cfe9f1",
}: PracticePortraitProps) {
  return (
    <section className="relative bg-white text-slate-900">
      <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
        {/* Nav */}
        <nav className="flex items-center gap-4 py-5">
          <a href="/" className="flex items-center gap-2">
            <BrandIcon color={primaryColor} />
            <span className="text-xl font-bold text-slate-900">{brand}</span>
          </a>

          {/* Search pill */}
          <div className="hidden flex-1 max-w-xs rounded-full border border-slate-200 px-3 py-1.5 lg:flex items-center gap-2 ml-4">
            <Search className="h-4 w-4 text-slate-400" />
            <input
              type="text"
              placeholder="Search"
              className="flex-1 bg-transparent text-sm outline-none placeholder:text-slate-400"
            />
          </div>

          <div className="hidden flex-1 items-center justify-end gap-6 md:flex">
            {navItems.map((item) => (
              <a
                key={item.label}
                href={item.href}
                className="text-sm font-medium text-slate-700 transition-colors hover:text-slate-900"
              >
                {item.label}
              </a>
            ))}
          </div>

          <div className="flex items-center gap-2">
            <a
              href={signUpCta.href}
              className="hidden rounded-full border border-slate-200 px-4 py-2 text-sm font-medium text-slate-900 transition-colors hover:bg-slate-50 sm:inline-flex"
            >
              {signUpCta.label}
            </a>
            <a
              href={loginCta.href}
              className="inline-flex rounded-full px-4 py-2 text-sm font-semibold text-white transition-all hover:-translate-y-0.5"
              style={{ background: primaryColor }}
            >
              {loginCta.label}
            </a>
          </div>
        </nav>

        {/* Hero grid */}
        <div className="grid items-center gap-10 py-10 lg:grid-cols-2 lg:gap-12 lg:py-16">
          {/* Left: copy */}
          <div className="max-w-xl">
            <a
              href={eyebrowLink.href}
              className="inline-flex items-center gap-2 rounded-full border px-4 py-1.5 text-sm font-medium transition-colors hover:bg-slate-50 mb-6"
              style={{ borderColor: primaryColor, color: primaryColor }}
            >
              {eyebrowLink.label}
              <ArrowRight className="h-4 w-4" />
            </a>
            <h1 className="text-4xl sm:text-5xl lg:text-[3.25rem] font-bold leading-[1.15] tracking-tight text-slate-900 mb-2">
              {headlinePart1}
            </h1>
            <h1 className="text-4xl sm:text-5xl lg:text-[3.25rem] font-bold leading-[1.15] tracking-tight text-slate-900">
              {headlinePart2}{" "}
              <span style={{ color: primaryColor }}>{highlightedText}</span>
            </h1>
            <p className="mt-6 max-w-md text-base leading-relaxed text-slate-500">
              {description}
            </p>
            <div className="mt-7 flex flex-wrap items-center gap-4">
              <a
                href={primaryCta.href}
                className="inline-flex items-center justify-center rounded-full px-7 py-3 text-sm font-semibold text-white transition-all hover:-translate-y-0.5"
                style={{ background: primaryColor }}
              >
                {primaryCta.label}
              </a>
              <a
                href={secondaryCta.href}
                className="inline-flex items-center justify-center rounded-full border px-7 py-3 text-sm font-semibold transition-colors hover:bg-slate-50"
                style={{ borderColor: primaryColor, color: primaryColor }}
              >
                {secondaryCta.label}
              </a>
            </div>
          </div>

          {/* Right: oval portrait on tinted background */}
          <div className="relative mx-auto w-full max-w-[500px]">
            <div
              className="absolute inset-0 -z-10 rounded-[50%]"
              style={{ background: ovalBgColor }}
            />
            <img
              src={imageSrc}
              alt={imageAlt}
              width={500}
              height={620}
              loading="eager"
              fetchPriority="high"
              decoding="async"
              className="relative z-10 mx-auto w-full max-w-[440px] rounded-b-[50%] object-cover"
              style={{ aspectRatio: "4/5" }}
            />
          </div>
        </div>

        {/* Trust strip — 3 cards */}
        <div className="grid grid-cols-1 md:grid-cols-3 gap-4 pb-16 lg:pb-20">
          {/* Card 1: experience stat */}
          <div className="rounded-2xl bg-slate-50 p-6 flex items-center gap-4">
            <div className="flex-shrink-0">
              <div
                className="text-5xl font-extrabold tabular-nums leading-none"
                style={{ color: primaryColor }}
              >
                {experienceYears}
              </div>
            </div>
            <div className="text-sm font-medium text-slate-700">
              {experienceLabel}
            </div>
          </div>

          {/* Card 2: team + meet physicians + book CTA */}
          <div className="rounded-2xl bg-slate-50 p-6">
            <div className="flex items-center gap-3 mb-3">
              <div className="flex -space-x-2">
                {teamAvatars.map((a, i) => (
                  <img
                    key={i}
                    src={a.src}
                    alt={a.alt}
                    width={36}
                    height={36}
                    loading="lazy"
                    className="h-9 w-9 rounded-full border-2 border-white object-cover"
                  />
                ))}
              </div>
            </div>
            <h3 className="text-base font-bold text-slate-900 mb-3">
              {meetTitle}
            </h3>
            <a
              href={bookCta.href}
              className="inline-flex items-center justify-center rounded-full px-5 py-2 text-xs font-semibold text-white transition-all hover:-translate-y-0.5"
              style={{ background: primaryColor }}
            >
              {bookCta.label}
            </a>
          </div>

          {/* Card 3: legacy section */}
          <div className="rounded-2xl bg-slate-50 p-6">
            <div
              className="mb-3 inline-flex h-10 w-10 items-center justify-center rounded-xl"
              style={{ background: ovalBgColor, color: primaryColor }}
            >
              <Heart className="h-5 w-5" />
            </div>
            <h3 className="text-base font-bold text-slate-900 mb-2">
              {legacyTitle}
            </h3>
            <p className="text-xs leading-relaxed text-slate-500">
              {legacyDescription}
            </p>
          </div>
        </div>
      </div>
    </section>
  );
}

function BrandIcon({ color = "#2496c0" }: { color?: string }) {
  return (
    <svg viewBox="0 0 24 24" className="h-7 w-7" fill="none" aria-hidden>
      <path
        d="M12 2 C 7 2, 4 5, 4 10 C 4 14, 7 17, 12 22 C 17 17, 20 14, 20 10 C 20 5, 17 2, 12 2 Z"
        fill={color}
        opacity="0.18"
      />
      <path
        d="M12 6 v 12 M 6 12 h 12"
        stroke={color}
        strokeWidth="2.5"
        strokeLinecap="round"
      />
    </svg>
  );
}
Claude Code Instructions

CLI Install

npx innovations add practice-portrait

Where to use it

Use this for medical, dental, chiropractic, veterinary, or any professional practice where a single confident portrait + trust signals matter. In Astro: --- import PracticePortraitHero from '../components/innovations/heroes/practice-portrait'; --- <PracticePortraitHero /> In Next.js: import PracticePortraitHero from '@/components/innovations/heroes/practice-portrait'; No client:* needed — server-renderable. CUSTOMIZATION: <PracticePortraitHero brand="Your Practice" eyebrowLink={{ label: "Meet Our Team", href: "/about" }} headlinePart1="Your health, our priority." headlinePart2="Caring for" highlightedText="every generation." primaryCta={{ label: "Book Now", href: "/book" }} secondaryCta={{ label: "Our Services", href: "/services" }} imageSrc="/your-doctor.webp" teamAvatars={[ { src: "/team-1.jpg", alt: "Dr. Smith" }, { src: "/team-2.jpg", alt: "Dr. Jones" }, { src: "/team-3.jpg", alt: "Dr. Lee" }, ]} experienceYears="20" experienceLabel="Years Serving the Community" primaryColor="#2496c0" ovalBgColor="#cfe9f1" /> ASSETS: - The doctor portrait is generated and lives at /heroes/practice-portrait/doctor.webp. Replace with your real doctor's portrait — vertical 4:5 ratio works best, with the figure centered and a clean light background that the oval mask will fade against. - Default team avatars use Unsplash CDN URLs; swap with your real team headshots via the teamAvatars prop. OVAL PORTRAIT: the bottom is rounded into a half-oval via rounded-b-[50%]. The tinted background sits behind the photo as a full ellipse. To make the photo into a full oval, change to rounded-[50%]. PALETTE: medical blue/teal. To rebrand: primaryColor (#2496c0) drives all blue accents — eyebrow border, link text, CTAs, big stat number ovalBgColor (#cfe9f1) is the light tint behind the portrait + the legacy icon background NAV: includes a search input pill (lg+ only) and Sign Up / Login pills. To remove search, delete the search-pill block in the nav. To remove Sign Up, delete that <a> tag. TRUST STRIP: 3 cards in a horizontal grid below the hero. Each is independently editable in the JSX. Drop or restructure as needed.