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-portraitWhere 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.