Ir al contenido
Evaluar mi proyecto
Componentes · HerosDisponible

Hero claymorfismo

Formas de plastilina que flotan y se aplastan al apuntarlas, titular redondeado y botones blandos con relieve interior.

Hecho a mano, también en pantalla

Interfaces blandas, redondas y amables para productos que quieren caer bien desde el primer clic.

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

hero-arcilla.tsx73 líneas
Descargar
"use client";

import type { CSSProperties } from "react";

/** Hero claymorfismo: formas de plastilina que flotan y se aplastan al apuntarlas, con titular redondeado y botones blandos. React + TypeScript, sin dependencias. Personaliza con --gchar-*. */
export type HeroArcillaProps = {
  title?: string;
  description?: string;
  primary?: string;
  secondary?: string;
  href?: string;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gchar-root { box-sizing: border-box; position: relative; container-type: inline-size; width: 100%; min-height: 620px; overflow: hidden; color: #40284d; background: var(--gchar-bg, #fff0e6); font: 700 16px/1.5 ui-rounded, "SF Pro Rounded", "Nunito", ui-sans-serif, system-ui, sans-serif; }
.gchar-root *, .gchar-root *::before, .gchar-root *::after { box-sizing: border-box; }
.gchar-root a { -webkit-tap-highlight-color: transparent; }
.gchar-root :focus-visible { outline: 3px solid #40284d; outline-offset: 4px; }
@media (prefers-reduced-motion: reduce) { .gchar-root *, .gchar-root *::before, .gchar-root *::after { animation: none !important; transition-duration: .01ms !important; } }
.gchar-copy { position: relative; z-index: 2; display: grid; gap: 22px; align-content: center; justify-items: start; min-height: 620px; padding: 72px clamp(24px, 7cqi, 90px); max-width: 640px; }
.gchar-title { margin: 0; font: 900 clamp(46px, 8.6cqi, 96px)/.98 ui-rounded, "SF Pro Rounded", "Nunito", ui-sans-serif, system-ui, sans-serif; letter-spacing: -.035em; text-wrap: balance; }
.gchar-desc { margin: 0; max-width: 40ch; font-size: clamp(16px, 1.9cqi, 20px); color: #6a4a78; font-weight: 600; text-wrap: pretty; }
.gchar-actions { display: flex; flex-wrap: wrap; gap: 14px; }
.gchar-btn { display: inline-flex; align-items: center; min-height: 58px; padding: 0 32px; border-radius: 999px; color: #40284d; background: var(--c); text-decoration: none; font-weight: 800; font-size: 17px; box-shadow: inset -7px -9px 14px color-mix(in srgb, var(--c) 62%, #3a1100), inset 7px 9px 14px color-mix(in srgb, var(--c) 40%, #fff), 0 14px 22px -8px color-mix(in srgb, var(--c) 70%, #3a1100); transition: transform .35s cubic-bezier(.34, 1.56, .64, 1); }
.gchar-btn:hover { transform: translateY(-3px) scale(1.04); }
.gchar-btn:active { transform: scale(.95, .9); }
.gchar-shapes { position: absolute; inset: 0; }
.gchar-s { position: absolute; background: var(--c); box-shadow: inset -14px -18px 28px color-mix(in srgb, var(--c) 62%, #3a0a30), inset 12px 14px 24px color-mix(in srgb, var(--c) 35%, #fff), 24px 34px 44px -14px color-mix(in srgb, var(--c) 70%, #3a0a30); animation: gchar-float var(--t) ease-in-out var(--dl) infinite; transition: transform .5s cubic-bezier(.34, 1.56, .64, 1); }
.gchar-s:hover { transform: scale(1.12, .9) !important; }
.gchar-sphere { width: var(--z); height: var(--z); border-radius: 50%; }
.gchar-pillow { width: var(--z); height: var(--z); border-radius: 38%; }
.gchar-ring { width: var(--z); height: var(--z); border-radius: 50%; background: transparent; box-shadow: none; border: calc(var(--z) * .24) solid var(--c); filter: drop-shadow(14px 20px 14px color-mix(in srgb, var(--c) 60%, #3a0a30)); }
.gchar-ring::after { content: ""; position: absolute; inset: calc(var(--z) * -.24); border-radius: 50%; box-shadow: inset -10px -12px 18px color-mix(in srgb, var(--c) 60%, #3a0a30), inset 10px 12px 18px color-mix(in srgb, var(--c) 35%, #fff); -webkit-mask: radial-gradient(circle, transparent calc(50% - var(--z) * .24 + 1px), #000 calc(50% - var(--z) * .24 + 2px)); mask: radial-gradient(circle, transparent calc(50% - var(--z) * .24 + 1px), #000 calc(50% - var(--z) * .24 + 2px)); pointer-events: none; }
@container (max-width: 820px) { .gchar-shapes { opacity: .55; } .gchar-copy { max-width: none; } }
@keyframes gchar-float { 50% { transform: translateY(-22px) rotate(4deg) scale(1.02, .98); } }`;

type Shape = { kind: "sphere" | "pillow" | "ring"; c: string; z: number; x: string; y: string; t: number; dl: number };

const SHAPES: Shape[] = [
  { kind: "sphere", c: "#ff9b85", z: 190, x: "62%", y: "10%", t: 6, dl: 0 },
  { kind: "pillow", c: "#8fd3ff", z: 150, x: "80%", y: "46%", t: 7, dl: -2 },
  { kind: "ring", c: "#ffd45e", z: 140, x: "58%", y: "58%", t: 8, dl: -4 },
  { kind: "sphere", c: "#b79bff", z: 96, x: "86%", y: "12%", t: 5.5, dl: -1 },
  { kind: "pillow", c: "#8be3b5", z: 84, x: "70%", y: "84%", t: 6.5, dl: -3 },
];

export function HeroArcilla({ title = "Hecho a mano, también en pantalla", description = "Interfaces blandas, redondas y amables para productos que quieren caer bien desde el primer clic.", primary = "Probar gratis", secondary = "Ver galería", href = "#", className = "", style }: HeroArcillaProps) {
  return (
    <section className={`gchar-root ${className}`} style={style}>
      <style>{CSS}</style>
      <div className="gchar-shapes" aria-hidden="true">
        {SHAPES.map((shape, index) => (
          <span key={index} className={`gchar-s gchar-${shape.kind}`} style={{ "--c": shape.c, "--z": `${shape.z}px`, "--t": `${shape.t}s`, "--dl": `${shape.dl}s`, left: shape.x, top: shape.y } as CSSProperties} />
        ))}
      </div>
      <div className="gchar-copy">
        <h1 className="gchar-title">{title}</h1>
        <p className="gchar-desc">{description}</p>
        <div className="gchar-actions">
          <a className="gchar-btn" href={href} style={{ "--c": "#ffb08a" } as CSSProperties}>
            {primary}
          </a>
          <a className="gchar-btn" href={href} style={{ "--c": "#c3b1ff" } as CSSProperties}>
            {secondary}
          </a>
        </div>
      </div>
    </section>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/hero-arcilla

Ejemplo de uso de Hero claymorfismo

Después de copiar hero-arcilla.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { HeroArcilla } from "./hero-arcilla";

export default function Ejemplo() {
  return <HeroArcilla title="Ejemplo" />;
}

Propiedades de HeroArcilla

HeroArcilla acepta 7 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
title?stringOpcional.
description?stringOpcional.
primary?stringOpcional.
secondary?stringOpcional.
href?stringOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Hero claymorfismo con variables CSS

Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.

VariableValor por defecto
--gchar-bg#fff0e6

Accesibilidad y rendimiento

El archivo tiene 73 líneas (6 KB) y define 1 animación con @keyframes. Esto es lo que hace Hero claymorfismo por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
  • Expone su estado a tecnologías de apoyo con atributos aria-*.
  • Calcula tonos con color-mix(), así que un solo color base alcanza.
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)