03クレイモーフィズム
内側にくぼんだ影と外に広がる影を重ねることでカードがふくらんだ粘土のように見え、アバターが呼吸するように大きくなったり小さくなったりします。
使いどころ子ども向け・教育・ヘルスケアなど、やわらかくもちっとした印象を出したいサービスのユーザーカード
box-shadowinsetaria-pressed

<main class="stage">
<article class="cp is-demo">
<div class="cp__head">
<div class="cp__avatar">HY</div>
<div class="cp__who">
<p class="cp__name">チョン・ハユン</p>
<p class="cp__role">イラストレーター</p>
</div>
</div>
<div class="cp__stats">
<div class="cp__stat"><strong class="cp__num">5,108</strong><span class="cp__lab">フォロワー</span></div>
<div class="cp__stat"><strong class="cp__num">94</strong><span class="cp__lab">投稿</span></div>
</div>
<button class="cp__btn" type="button" id="cp-btn" aria-pressed="false">
<svg viewBox="0 0 256 256" width="16" height="16" aria-hidden="true"><g fill="currentColor"><path d="m229.06 108.79l-48.7 42l14.88 62.79a8.4 8.4 0 0 1-12.52 9.17L128 189.09l-54.72 33.65a8.4 8.4 0 0 1-12.52-9.17l14.88-62.79l-48.7-42A8.46 8.46 0 0 1 31.73 94l63.91-5.2l24.62-59.6a8.36 8.36 0 0 1 15.48 0l24.62 59.6l63.91 5.2a8.46 8.46 0 0 1 4.79 14.79" opacity=".2"/><path d="M239.18 97.26A16.38 16.38 0 0 0 224.92 86l-59-4.76l-22.78-55.09a16.36 16.36 0 0 0-30.27 0L90.11 81.23L31.08 86a16.46 16.46 0 0 0-9.37 28.86l45 38.83L53 211.75a16.38 16.38 0 0 0 24.5 17.82l50.5-31.08l50.53 31.08A16.4 16.4 0 0 0 203 211.75l-13.76-58.07l45-38.83a16.43 16.43 0 0 0 4.94-17.59m-15.34 5.47l-48.7 42a8 8 0 0 0-2.56 7.91l14.88 62.8a.37.37 0 0 1-.17.48c-.18.14-.23.11-.38 0l-54.72-33.65a8 8 0 0 0-8.38 0l-54.72 33.67c-.15.09-.19.12-.38 0a.37.37 0 0 1-.17-.48l14.88-62.8a8 8 0 0 0-2.56-7.91l-48.7-42c-.12-.1-.23-.19-.13-.5s.18-.27.33-.29l63.92-5.16a8 8 0 0 0 6.72-4.94l24.62-59.61c.08-.17.11-.25.35-.25s.27.08.35.25L153 91.86a8 8 0 0 0 6.75 4.92l63.92 5.16c.15 0 .24 0 .33.29s0 .4-.16.5"/></g></svg>
<svg viewBox="0 0 256 256" width="16" height="16" aria-hidden="true" hidden><g fill="currentColor"><path d="M232 56v144a16 16 0 0 1-16 16H40a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16h176a16 16 0 0 1 16 16" opacity=".2"/><path d="m205.66 85.66l-96 96a8 8 0 0 1-11.32 0l-40-40a8 8 0 0 1 11.32-11.32L104 164.69l90.34-90.35a8 8 0 0 1 11.32 11.32"/></g></svg>
<span id="cp-label">フォロー</span>
</button>
</article>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-base;
$easing: $ease-pop;
$color: $subject-lilac;
@include stage($stage-paper);
// 클레이모피즘 — 파스텔 바탕 + 겹친 인셋 그림자로 부풀린 카드. 아바타가 숨 쉰다.
.cp {
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-3;
width: 300px;
padding: $sp-4;
border-radius: $r-card;
background: rgba($subject-pink, .28);
box-shadow:
0 18px 30px rgba(23, 20, 26, .14),
inset 6px 6px 12px rgba(255, 255, 255, .9),
inset -6px -6px 12px rgba(23, 20, 26, .10);
color: $subject-ink;
user-select: none;
-webkit-user-select: none;
}
.cp__head { display: flex; align-items: center; gap: $sp-3; }
.cp__avatar {
display: flex; align-items: center; justify-content: center;
width: 56px; height: 56px;
border-radius: 50%;
background: $color;
box-shadow:
inset 4px 4px 10px rgba(255, 255, 255, .85),
inset -4px -4px 10px rgba(23, 20, 26, .12);
color: $subject-ink;
font-size: 18px; line-height: 22px; font-weight: 800;
}
.cp__who { display: flex; flex-direction: column; gap: $sp-1; }
.cp__name { margin: 0; font-size: 20px; line-height: 24px; font-weight: 800; }
.cp__role { margin: 0; font-size: 12px; line-height: 16px; font-weight: 600; color: rgba(23, 20, 26, .62); }
.cp__stats { display: flex; align-items: center; gap: $sp-4; }
.cp__stat { display: flex; flex-direction: column; align-items: center; gap: $sp-1; }
.cp__stat + .cp__stat { border-left: 1px solid rgba(23, 20, 26, .14); padding-left: $sp-4; }
.cp__num { font-size: 17px; line-height: 20px; font-weight: 800; font-variant-numeric: tabular-nums; }
.cp__lab { font-size: 11px; line-height: 13px; font-weight: 600; color: rgba(23, 20, 26, .62); }
.cp__btn {
display: inline-flex; align-items: center; justify-content: center;
gap: $sp-1;
height: 36px;
padding: 0 $sp-5;
border: 0;
border-radius: $r-pill;
background: rgba($subject-lilac, .6);
box-shadow:
0 8px 16px rgba(23, 20, 26, .14),
inset 3px 3px 8px rgba(255, 255, 255, .85),
inset -3px -3px 8px rgba(23, 20, 26, .10);
color: $subject-ink;
font-size: 13px; line-height: 16px; font-weight: 800;
cursor: pointer;
transition: background $duration $easing, transform $duration $easing;
}
.cp__btn svg { display: block; }
.cp__btn svg[hidden] { display: none; }
.cp__btn:hover { background: rgba($subject-lilac, .78); }
.cp__btn:focus-visible { outline: 2px solid $color; outline-offset: 2px; }
.cp__btn:active { transform: scale(.95); }
.cp__btn[aria-pressed="true"] { background: rgba($subject-mint, .55); }
// ── 자동 루프: 아바타가 숨 쉬듯 부풀고(복귀 88% 안), 버튼은 시차를 두고 살짝 부풀린다 ──
.cp.is-demo .cp__avatar {
animation-name: cp-breathe;
animation-duration: $dur-loop;
animation-timing-function: $easing;
animation-iteration-count: infinite;
}
.cp.is-demo .cp__btn {
animation-name: cp-puff;
animation-duration: $dur-loop;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-delay: -600ms; // 시차 — 단축 속성은 delay 를 리셋하므로 낱개 속성으로 쓴다
}
@keyframes cp-breathe {
0%, 100% { transform: scale(1); }
44% { transform: scale(1.12); }
88% { transform: scale(1); }
}
@keyframes cp-puff {
0%, 100% { transform: scale(1); }
44% { transform: scale(1.07); }
88% { transform: scale(1); }
}
@media (max-width: 340px) {
.cp { width: 272px; gap: $sp-1; padding: $sp-3; }
.cp__head { gap: $sp-2; }
.cp__avatar { width: 44px; height: 44px; font-size: 14px; line-height: 18px; }
.cp__who { gap: 0; }
.cp__name { font-size: 14px; line-height: 18px; }
.cp__role { font-size: 11px; line-height: 13px; }
.cp__stats { gap: $sp-3; }
.cp__stat { gap: 0; }
.cp__stat + .cp__stat { padding-left: $sp-3; }
.cp__num { font-size: 13px; line-height: 16px; }
.cp__lab { font-size: 11px; line-height: 13px; }
.cp__btn { height: 32px; padding: 0 $sp-4; font-size: 12px; line-height: 14px; }
}
@media (prefers-reduced-motion: reduce) {
.cp.is-demo .cp__avatar,
.cp.is-demo .cp__btn { animation: none !important; }
.cp__btn { transition: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.cp {
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
width: 300px;
padding: 16px;
border-radius: 16px;
background: rgba(255, 158, 187, 0.28);
box-shadow: 0 18px 30px rgba(23, 20, 26, 0.14), inset 6px 6px 12px rgba(255, 255, 255, 0.9), inset -6px -6px 12px rgba(23, 20, 26, 0.1);
color: #17141a;
user-select: none;
-webkit-user-select: none;
}
.cp__head {
display: flex;
align-items: center;
gap: 12px;
}
.cp__avatar {
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
border-radius: 50%;
background: #b9a0ff;
box-shadow: inset 4px 4px 10px rgba(255, 255, 255, 0.85), inset -4px -4px 10px rgba(23, 20, 26, 0.12);
color: #17141a;
font-size: 18px;
line-height: 22px;
font-weight: 800;
}
.cp__who {
display: flex;
flex-direction: column;
gap: 4px;
}
.cp__name {
margin: 0;
font-size: 20px;
line-height: 24px;
font-weight: 800;
}
.cp__role {
margin: 0;
font-size: 12px;
line-height: 16px;
font-weight: 600;
color: rgba(23, 20, 26, 0.62);
}
.cp__stats {
display: flex;
align-items: center;
gap: 16px;
}
.cp__stat {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.cp__stat + .cp__stat {
border-left: 1px solid rgba(23, 20, 26, 0.14);
padding-left: 16px;
}
.cp__num {
font-size: 17px;
line-height: 20px;
font-weight: 800;
font-variant-numeric: tabular-nums;
}
.cp__lab {
font-size: 11px;
line-height: 13px;
font-weight: 600;
color: rgba(23, 20, 26, 0.62);
}
.cp__btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
height: 36px;
padding: 0 20px;
border: 0;
border-radius: 999px;
background: rgba(185, 160, 255, 0.6);
box-shadow: 0 8px 16px rgba(23, 20, 26, 0.14), inset 3px 3px 8px rgba(255, 255, 255, 0.85), inset -3px -3px 8px rgba(23, 20, 26, 0.1);
color: #17141a;
font-size: 13px;
line-height: 16px;
font-weight: 800;
cursor: pointer;
transition: background 300ms cubic-bezier(0.2, 1.4, 0.4, 1), transform 300ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.cp__btn svg {
display: block;
}
.cp__btn svg[hidden] {
display: none;
}
.cp__btn:hover {
background: rgba(185, 160, 255, 0.78);
}
.cp__btn:focus-visible {
outline: 2px solid #b9a0ff;
outline-offset: 2px;
}
.cp__btn:active {
transform: scale(0.95);
}
.cp__btn[aria-pressed=true] {
background: rgba(76, 212, 166, 0.55);
}
.cp.is-demo .cp__avatar {
animation-name: cp-breathe;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 1.4, 0.4, 1);
animation-iteration-count: infinite;
}
.cp.is-demo .cp__btn {
animation-name: cp-puff;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 1.4, 0.4, 1);
animation-iteration-count: infinite;
animation-delay: -600ms;
}
@keyframes cp-breathe {
0%, 100% {
transform: scale(1);
}
44% {
transform: scale(1.12);
}
88% {
transform: scale(1);
}
}
@keyframes cp-puff {
0%, 100% {
transform: scale(1);
}
44% {
transform: scale(1.07);
}
88% {
transform: scale(1);
}
}
@media (max-width: 340px) {
.cp {
width: 272px;
gap: 4px;
padding: 12px;
}
.cp__head {
gap: 8px;
}
.cp__avatar {
width: 44px;
height: 44px;
font-size: 14px;
line-height: 18px;
}
.cp__who {
gap: 0;
}
.cp__name {
font-size: 14px;
line-height: 18px;
}
.cp__role {
font-size: 11px;
line-height: 13px;
}
.cp__stats {
gap: 12px;
}
.cp__stat {
gap: 0;
}
.cp__stat + .cp__stat {
padding-left: 12px;
}
.cp__num {
font-size: 13px;
line-height: 16px;
}
.cp__lab {
font-size: 11px;
line-height: 13px;
}
.cp__btn {
height: 32px;
padding: 0 16px;
font-size: 12px;
line-height: 14px;
}
}
@media (prefers-reduced-motion: reduce) {
.cp.is-demo .cp__avatar,
.cp.is-demo .cp__btn {
animation: none !important;
}
.cp__btn {
transition: none;
}
}// 실제 동작: フォロー 버튼 토글. 첫 조작에 자동 루프가 끊긴다.
var root = document.querySelector('.cp');
var btn = document.getElementById('cp-btn');
var label = document.getElementById('cp-label');
var icons = btn.querySelectorAll('svg');
root.addEventListener('pointerdown', function () { root.classList.remove('is-demo'); });
btn.addEventListener('click', function () {
root.classList.remove('is-demo');
var on = btn.getAttribute('aria-pressed') !== 'true';
btn.setAttribute('aria-pressed', String(on));
icons[0].hidden = on;
icons[1].hidden = !on;
label.textContent = on ? 'フォロー中' : 'フォロー';
});import { useState } from "react";
import type { CSSProperties, ReactNode } from "react";
import styles from "./ClayPuffCard.module.scss";
export type ClayPuffCardProps = {
duration?: number;
easing?: string;
color?: string;
};
type ClickEvt = { currentTarget: { blur(): void } };
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const IconStar = (): ReactNode => (
<svg viewBox="0 0 256 256" width="16" height="16" aria-hidden="true"><g fill="currentColor"><path d="m229.06 108.79l-48.7 42l14.88 62.79a8.4 8.4 0 0 1-12.52 9.17L128 189.09l-54.72 33.65a8.4 8.4 0 0 1-12.52-9.17l14.88-62.79l-48.7-42A8.46 8.46 0 0 1 31.73 94l63.91-5.2l24.62-59.6a8.36 8.36 0 0 1 15.48 0l24.62 59.6l63.91 5.2a8.46 8.46 0 0 1 4.79 14.79" opacity=".2"/><path d="M239.18 97.26A16.38 16.38 0 0 0 224.92 86l-59-4.76l-22.78-55.09a16.36 16.36 0 0 0-30.27 0L90.11 81.23L31.08 86a16.46 16.46 0 0 0-9.37 28.86l45 38.83L53 211.75a16.38 16.38 0 0 0 24.5 17.82l50.5-31.08l50.53 31.08A16.4 16.4 0 0 0 203 211.75l-13.76-58.07l45-38.83a16.43 16.43 0 0 0 4.94-17.59m-15.34 5.47l-48.7 42a8 8 0 0 0-2.56 7.91l14.88 62.8a.37.37 0 0 1-.17.48c-.18.14-.23.11-.38 0l-54.72-33.65a8 8 0 0 0-8.38 0l-54.72 33.67c-.15.09-.19.12-.38 0a.37.37 0 0 1-.17-.48l14.88-62.8a8 8 0 0 0-2.56-7.91l-48.7-42c-.12-.1-.23-.19-.13-.5s.18-.27.33-.29l63.92-5.16a8 8 0 0 0 6.72-4.94l24.62-59.61c.08-.17.11-.25.35-.25s.27.08.35.25L153 91.86a8 8 0 0 0 6.75 4.92l63.92 5.16c.15 0 .24 0 .33.29s0 .4-.16.5"/></g></svg>
);
const IconCheck = (): ReactNode => (
<svg viewBox="0 0 256 256" width="16" height="16" aria-hidden="true"><g fill="currentColor"><path d="M232 56v144a16 16 0 0 1-16 16H40a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16h176a16 16 0 0 1 16 16" opacity=".2"/><path d="m205.66 85.66l-96 96a8 8 0 0 1-11.32 0l-40-40a8 8 0 0 1 11.32-11.32L104 164.69l90.34-90.35a8 8 0 0 1 11.32 11.32"/></g></svg>
);
export default function ClayPuffCard({
duration = 300,
easing = "cubic-bezier(.2, 1.4, .4, 1)",
color = "#b9a0ff",
}: ClayPuffCardProps) {
const [on, setOn] = useState(false);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
function toggle(e: ClickEvt): void {
setOn((v: boolean): boolean => !v);
e.currentTarget.blur();
}
return (
<article className={styles.cp}>
<div className={styles.head}>
<div className={styles.avatar}>HY</div>
<div className={styles.who}>
<p className={styles.name}>정하윤</p>
<p className={styles.role}>일러스트레이터</p>
</div>
</div>
<div className={styles.stats}>
<div className={styles.stat}><strong className={styles.num}>5,108</strong><span className={styles.lab}>팔로워</span></div>
<div className={styles.stat}><strong className={styles.num}>94</strong><span className={styles.lab}>게시물</span></div>
</div>
<button type="button" className={styles.btn} aria-pressed={on} onClick={toggle}>
{on ? <IconCheck /> : <IconStar />}
<span>{on ? "팔로잉" : "팔로우"}</span>
</button>
</article>
);
}03 クレイモーフィズム — 計測 累積面積 1.766% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · プロフィールカード デザイン 9選 — コピペOKのスタイル集
このデモが属する記事
プロフィールカード デザイン 9選 — コピペOKのスタイル集プロフィールカード デザインとは、アバター・名前・肩書き・数値・フォローボタンという決まった中身に、どんな質感を着せるかを決める作業です。9つは内側の構造がすべて同じで、表面と動きだけが違います。 この記事のデモをすべて見る


