Innovations

Soft Organic

Cream backdrop with sage / peach blurred blobs, a blob-shaped avatar, Cormorant italic name, pill-shaped link buttons with soft shadows, and squiggle dividers. Renders one latest-post block from rich blocks.

Preview

Source

tsx
"use client";

import {
  Instagram,
  Twitter,
  Youtube,
  Mail,
  Globe,
  ShoppingBag,
  PlayCircle,
  Linkedin,
  Music2,
  ArrowUpRight,
  type LucideIcon,
} from "lucide-react";
import type { LinkIcon, LinksInBioData, RichBlock } from "../types";
import { defaultData } from "../defaultData";

const ICONS: Record<LinkIcon, LucideIcon> = {
  instagram: Instagram,
  twitter: Twitter,
  tiktok: Music2,
  youtube: Youtube,
  spotify: Music2,
  apple: Music2,
  linkedin: Linkedin,
  email: Mail,
  globe: Globe,
  shop: ShoppingBag,
  play: PlayCircle,
};

const CREAM = "#fefcf8";
const SAGE = "#cfd8c2";
const PEACH = "#f6c9b1";
const INK = "#28201a";
const INK_SOFT = "#5b4f44";

function Squiggle({ color = INK }: { color?: string }) {
  return (
    <svg
      aria-hidden
      width="120"
      height="14"
      viewBox="0 0 120 14"
      fill="none"
      className="opacity-30"
    >
      <path
        d="M2 7 Q 12 1, 22 7 T 42 7 T 62 7 T 82 7 T 102 7 T 118 7"
        stroke={color}
        strokeWidth="1.5"
        strokeLinecap="round"
        fill="none"
      />
    </svg>
  );
}

function LatestPostBlock({ block }: { block: Extract<RichBlock, { kind: "latest-post" }> }) {
  return (
    <a
      href={block.href}
      className="group block overflow-hidden rounded-2xl border border-black/5 bg-white shadow-[0_8px_30px_rgba(60,40,20,0.06)] transition-all hover:-translate-y-0.5 hover:shadow-[0_14px_40px_rgba(60,40,20,0.10)]"
    >
      <div className="aspect-[16/10] overflow-hidden">
        <img
          src={block.image}
          alt={block.title}
          width={800}
          height={500}
          loading="lazy"
          className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-[1.03]"
        />
      </div>
      <div className="flex items-center justify-between gap-3 p-4">
        <div className="min-w-0">
          {block.meta && (
            <p className="text-[11px] uppercase tracking-[0.18em]" style={{ color: INK_SOFT }}>
              {block.meta}
            </p>
          )}
          <p
            className="mt-1 truncate text-base"
            style={{
              fontFamily: "'Cormorant Garamond', serif",
              fontStyle: "italic",
              color: INK,
            }}
          >
            {block.title}
          </p>
        </div>
        <ArrowUpRight className="h-4 w-4 shrink-0" style={{ color: INK }} />
      </div>
    </a>
  );
}

export interface LinksInBioSoftOrganicProps {
  data?: LinksInBioData;
}

export default function LinksInBioSoftOrganic({
  data = defaultData,
}: LinksInBioSoftOrganicProps) {
  const latestPost = data.richBlocks?.find(
    (b): b is Extract<RichBlock, { kind: "latest-post" }> => b.kind === "latest-post"
  );

  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-screen w-full overflow-hidden px-4 py-12 sm:py-16"
        style={{ background: CREAM, fontFamily: "'Inter', sans-serif", color: INK }}
      >
        <div
          aria-hidden
          className="pointer-events-none absolute -left-24 -top-32 h-96 w-96 rounded-full blur-3xl"
          style={{ background: SAGE, opacity: 0.55 }}
        />
        <div
          aria-hidden
          className="pointer-events-none absolute -right-32 top-40 h-80 w-80 rounded-full blur-3xl"
          style={{ background: PEACH, opacity: 0.5 }}
        />

        <div className="relative mx-auto w-full max-w-[440px]">
          <div className="flex flex-col items-center text-center">
            <div className="relative">
              <div
                aria-hidden
                className="absolute inset-[-12%]"
                style={{
                  background: SAGE,
                  borderRadius: "65% 35% 50% 50% / 55% 45% 55% 45%",
                  opacity: 0.85,
                }}
              />
              <div
                className="relative overflow-hidden"
                style={{
                  width: "120px",
                  height: "120px",
                  borderRadius: "55% 45% 60% 40% / 50% 60% 40% 50%",
                  boxShadow: "0 12px 30px rgba(60,40,20,0.15)",
                }}
              >
                <img
                  src={data.avatar}
                  alt={data.name}
                  width={240}
                  height={240}
                  loading="eager"
                  className="h-full w-full object-cover"
                />
              </div>
            </div>

            <h1
              className="mt-6"
              style={{
                fontFamily: "'Cormorant Garamond', serif",
                fontStyle: "italic",
                fontSize: "clamp(1.85rem, 6vw, 2.4rem)",
                lineHeight: 1.1,
                letterSpacing: "-0.01em",
              }}
            >
              {data.name}
            </h1>
            {data.handle && (
              <p className="mt-1 text-sm" style={{ color: INK_SOFT }}>
                {data.handle}
              </p>
            )}
            {data.bio && (
              <p className="mt-4 max-w-[340px] text-[15px] leading-relaxed" style={{ color: INK_SOFT }}>
                {data.bio}
              </p>
            )}

            <div className="mt-5 flex justify-center">
              <Squiggle color={INK} />
            </div>
          </div>

          <div className="mt-6 space-y-3">
            {data.links.map((link) => {
              const Icon = link.icon ? ICONS[link.icon] : null;
              return (
                <a
                  key={link.label}
                  href={link.href}
                  className="flex items-center gap-3 rounded-full bg-white px-5 py-3.5 text-sm font-medium transition-all hover:-translate-y-0.5 hover:shadow-[0_10px_28px_rgba(60,40,20,0.10)]"
                  style={{
                    color: INK,
                    boxShadow: "0 4px 14px rgba(60,40,20,0.06)",
                  }}
                >
                  {Icon && (
                    <span
                      className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full"
                      style={{ background: SAGE, color: INK }}
                    >
                      <Icon className="h-4 w-4" strokeWidth={1.6} />
                    </span>
                  )}
                  <span className="flex-1 truncate text-left">{link.label}</span>
                  {link.badge && (
                    <span
                      className="rounded-full px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider"
                      style={{ background: PEACH, color: INK }}
                    >
                      {link.badge}
                    </span>
                  )}
                </a>
              );
            })}
          </div>

          {latestPost && (
            <>
              <div className="my-8 flex justify-center">
                <Squiggle color={INK} />
              </div>
              <LatestPostBlock block={latestPost} />
            </>
          )}

          {data.socials && data.socials.length > 0 && (
            <div className="mt-10 flex items-center justify-center gap-5" style={{ color: INK_SOFT }}>
              {data.socials.map((s, i) => {
                const Icon = ICONS[s.type] ?? Globe;
                return (
                  <a
                    key={i}
                    href={s.href}
                    aria-label={s.type}
                    className="transition-colors"
                    style={{ color: INK_SOFT }}
                  >
                    <Icon className="h-4 w-4" strokeWidth={1.6} />
                  </a>
                );
              })}
            </div>
          )}

          <p
            className="mt-10 text-center text-[11px] uppercase tracking-[0.2em]"
            style={{ color: INK_SOFT, opacity: 0.6 }}
          >
            Made with care
          </p>
        </div>
      </section>
    </>
  );
}
Claude Code Instructions

CLI Install

npx innovations add soft-organic

Where to use it

An organic, calm link-in-bio page with cream cream/sage/peach palette. Loads Cormorant Garamond and Inter from Google Fonts. Pass a typed 'data' prop (LinksInBioData from src/registry/links-in-bio/types.ts) to swap content. With no prop it renders sample data. Add a richBlocks entry with kind 'latest-post' to feature your most recent post or article — it renders as a card with image + title + meta below the link stack. In Astro: import LinksInBioSoftOrganic from '../components/innovations/links-in-bio/soft-organic'; <LinksInBioSoftOrganic client:load data={myProfile} /> In Next.js: import LinksInBioSoftOrganic from '@/components/innovations/links-in-bio/soft-organic'; Best for: lifestyle creators, wellness brands, writers, photographers — any audience that responds to warmth and softness.