Innovations
heroes /

Blob Portrait Hero (Editorial Magazine)

Editorial fashion/blogger hero with cream bg, italic Cormorant Garamond serif headline, body copy, yellow CTA pill, and a blob-shaped portrait on the right (with a softer yellow blob behind it). Three yellow circle accents bleed off the corners. Follow + social links footer. Built from a

Preview

Source

tsx
import { Twitter, Facebook, Menu, Share2 } from "lucide-react";

export interface BlobPortraitHeroProps {
  brand?: string;
  navItems?: { label: string; href: string }[];
  headlineLines?: string[];
  body?: string;
  primaryCta?: { label: string; href: string };
  followLabel?: string;
  socialLinks?: { type: "twitter" | "facebook" | "instagram"; href: string }[];
  imageSrc?: string;
  imageAlt?: string;
  /** Default = Ladie cream bg #fefcf8 */
  bgColor?: string;
  /** Default = Ladie warm yellow #fcc638 */
  accentYellow?: string;
  /** Default = Ladie deep ink #18171c */
  inkColor?: string;
}

export default function BlobPortraitHero({
  brand = "ladie",
  navItems = [
    { label: "About Us", href: "#about" },
    { label: "Blog", href: "#blog" },
    { label: "Contact Us", href: "#contact" },
  ],
  headlineLines = [
    "Be a girl with a mind",
    "A woman with attitude",
    "& a lady with class",
  ],
  body = "Fashion is a language which tells a story about the person who wears it. “Clothes create a wordless means of communication that we all understand.”",
  primaryCta = { label: "Read more", href: "#read" },
  followLabel = "Follow Us",
  socialLinks = [
    { type: "twitter", href: "#" },
    { type: "facebook", href: "#" },
  ],
  imageSrc = "/heroes/blob-portrait/woman.webp",
  imageAlt = "Featured editorial portrait",
  bgColor = "#fefcf8",
  accentYellow = "#fcc638",
  inkColor = "#18171c",
}: BlobPortraitHeroProps) {
  return (
    <>
      <link rel="preconnect" href="https://fonts.googleapis.com" />
      <link
        rel="stylesheet"
        href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400..700;1,400..700&family=Inter:wght@400;500;600&display=swap"
      />
      <section
        className="relative min-h-[680px] overflow-hidden"
        style={{ background: bgColor, fontFamily: "'Inter', sans-serif", color: inkColor }}
      >
        {/* Decorative yellow circles */}
        <div
          aria-hidden
          className="absolute -left-32 -top-20 h-72 w-72 rounded-full"
          style={{ background: accentYellow, opacity: 0.85 }}
        />
        <div
          aria-hidden
          className="absolute -right-24 -top-24 h-56 w-56 rounded-full"
          style={{ background: accentYellow, opacity: 0.85 }}
        />
        <div
          aria-hidden
          className="absolute -left-10 bottom-[-120px] h-72 w-72 rounded-full"
          style={{ background: accentYellow, opacity: 0.85 }}
        />

        <div className="relative mx-auto max-w-7xl px-6 sm:px-10 lg:px-14">
          {/* Nav */}
          <nav className="flex items-center justify-between pt-6">
            <a href="/" className="flex items-center gap-2">
              <span
                className="text-2xl font-bold italic"
                style={{ fontFamily: "'Cormorant Garamond', serif", color: inkColor }}
              >
                {brand}
              </span>
            </a>
            <div className="hidden items-center gap-10 md:flex">
              {navItems.map((item) => (
                <a
                  key={item.label}
                  href={item.href}
                  className="text-sm transition-opacity hover:opacity-60"
                  style={{ color: inkColor }}
                >
                  {item.label}
                </a>
              ))}
            </div>
            <button
              type="button"
              aria-label="Open menu"
              className="rounded-md p-2 transition-opacity hover:opacity-60"
              style={{ color: inkColor }}
            >
              <Menu className="h-5 w-5" />
            </button>
          </nav>

          {/* Hero grid */}
          <div className="grid items-center gap-10 py-12 lg:grid-cols-[1fr_1.05fr] lg:gap-12 lg:py-16">
            {/* Left: copy */}
            <div className="relative z-10 max-w-xl pl-2 sm:pl-6">
              <h1
                className="font-bold leading-[1.15] tracking-tight"
                style={{
                  fontFamily: "'Cormorant Garamond', serif",
                  fontSize: "clamp(2.25rem, 4.4vw, 3.25rem)",
                  color: inkColor,
                }}
              >
                {headlineLines.map((line, i) => (
                  <span key={i} className="block">
                    {line}
                  </span>
                ))}
              </h1>
              <p
                className="mt-6 max-w-sm text-sm leading-relaxed"
                style={{ color: "#5b5763" }}
              >
                {body}
              </p>
              <a
                href={primaryCta.href}
                className="mt-7 inline-flex items-center justify-center rounded-md px-7 py-3 text-sm font-semibold transition-all hover:-translate-y-0.5"
                style={{
                  background: accentYellow,
                  color: inkColor,
                  boxShadow: `0 6px 20px ${accentYellow}55`,
                }}
              >
                {primaryCta.label}
              </a>
            </div>

            {/* Right: blob-shaped photo */}
            <div className="relative">
              {/* Yellow blob accent behind */}
              <div
                aria-hidden
                className="absolute inset-[2%] -left-[6%] -bottom-[4%]"
                style={{
                  background: accentYellow,
                  borderRadius: "65% 35% 50% 50% / 55% 45% 55% 45%",
                  opacity: 0.95,
                }}
              />

              {/* Photo in blob frame */}
              <div
                className="relative overflow-hidden"
                style={{
                  borderRadius: "55% 45% 60% 40% / 50% 60% 40% 50%",
                  aspectRatio: "5/4",
                  boxShadow: "0 20px 60px rgba(0,0,0,0.08)",
                }}
              >
                <img
                  src={imageSrc}
                  alt={imageAlt}
                  width={900}
                  height={720}
                  loading="eager"
                  fetchPriority="high"
                  decoding="async"
                  className="h-full w-full object-cover"
                />
              </div>
            </div>
          </div>

          {/* Footer row: Follow + socials */}
          <div className="flex items-center justify-between pb-10 pt-4">
            <div className="flex items-center gap-4">
              <span className="text-sm font-semibold" style={{ color: inkColor }}>
                {followLabel}
              </span>
              <div className="flex items-center gap-3">
                {socialLinks.map((s, i) => (
                  <a
                    key={i}
                    href={s.href}
                    aria-label={s.type}
                    className="transition-opacity hover:opacity-60"
                    style={{ color: inkColor }}
                  >
                    {s.type === "twitter" && <Twitter className="h-4 w-4" />}
                    {s.type === "facebook" && <Facebook className="h-4 w-4" />}
                  </a>
                ))}
              </div>
            </div>
            <a
              href="#share"
              aria-label="Share"
              className="transition-opacity hover:opacity-60"
              style={{ color: inkColor }}
            >
              <Share2 className="h-5 w-5" />
            </a>
          </div>
        </div>
      </section>
    </>
  );
}
Claude Code Instructions

CLI Install

npx innovations add blob-portrait

Where to use it

Use this for editorial / lifestyle / fashion / personal-brand bloggers — anywhere you've got a strong portrait and a thoughtful, considered voice. In Astro: --- import BlobPortraitHero from '../components/innovations/heroes/blob-portrait'; --- <BlobPortraitHero /> In Next.js: import BlobPortraitHero from '@/components/innovations/heroes/blob-portrait'; No client:* needed — server-renderable. CUSTOMIZATION: <BlobPortraitHero brand="yourname" navItems={[{ label: "About", href: "/about" }, ...]} headlineLines={["First line", "Second line", "Third line"]} body="Body paragraph..." primaryCta={{ label: "Read more", href: "/read" }} socialLinks={[ { type: "twitter", href: "#" }, { type: "facebook", href: "#" }, { type: "instagram", href: "#" }, ]} imageSrc="/your-portrait.webp" bgColor="#fefcf8" accentYellow="#fcc638" inkColor="#18171c" /> PALETTE: cream + warm yellow + ink. Easy to rebrand by swapping the 3 color props. BLOB SHAPES: the photo and its accent shadow both use organic border-radius percentages. Adjust the radii in the JSX (look for "borderRadius: '55% 45% 60% 40% / 50% 60% 40% 50%'") to get a different blob silhouette. DECORATIVE CIRCLES: three yellow circles bleed off corners (top-left, top-right, bottom-left). Tune their size or remove them by editing the three "Decorative yellow circles" divs. FONT: Cormorant Garamond + Inter, shipped via baked-in Google Fonts <link>.