05오브 배지 요금제
세 요금제 중 추천 플랜 머리 위에 작은 오브 배지가 1초 주기로 떠올랐다 내려앉고(translateY), 해당 카드에만 옅은 테두리와 깊은 그림자가 남습니다. 색을 바꾸지 않고 테두리와 그림자만으로 위계를 만듭니다.
쓰임3단 요금제, 플랜 비교
translateYbox-shadowborder-color

<main class="stage">
<section class="pr is-demo" id="pr">
<header class="pr__head">
<h2 class="pr__title">요금제</h2>
<p class="pr__sub">쓰는 만큼만 냅니다.</p>
</header>
<div class="pr__grid">
<article class="pr__card">
<p class="pr__plan">스타터</p>
<p class="pr__price">₩0<span class="pr__per">/월</span></p>
<ul class="pr__feats">
<li class="pr__feat"><span class="pr__ico" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"/><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/></svg></span>하루 20회</li>
<li class="pr__feat"><span class="pr__ico" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"/><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/></svg></span>모델 1종</li>
<li class="pr__feat"><span class="pr__ico" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"/><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/></svg></span>이메일 문의</li>
</ul>
<button type="button" class="pr__pick">고르기</button>
</article>
<article class="pr__card pr__card--pick">
<span class="pr__orb" aria-hidden="true"></span>
<p class="pr__plan">프로 <span class="pr__flag">추천</span></p>
<p class="pr__price">₩19,000<span class="pr__per">/월</span></p>
<ul class="pr__feats">
<li class="pr__feat"><span class="pr__ico" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"/><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/></svg></span>하루 500회</li>
<li class="pr__feat"><span class="pr__ico" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"/><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/></svg></span>모델 4종</li>
<li class="pr__feat"><span class="pr__ico" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"/><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/></svg></span>우선 응답</li>
</ul>
<button type="button" class="pr__pick">고르기</button>
</article>
<article class="pr__card">
<p class="pr__plan">팀</p>
<p class="pr__price">₩49,000<span class="pr__per">/월</span></p>
<ul class="pr__feats">
<li class="pr__feat"><span class="pr__ico" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"/><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/></svg></span>무제한 호출</li>
<li class="pr__feat"><span class="pr__ico" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"/><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/></svg></span>팀 자리 10</li>
<li class="pr__feat"><span class="pr__ico" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"/><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/></svg></span>전담 담당자</li>
</ul>
<button type="button" class="pr__pick">고르기</button>
</article>
</div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// 변형은 이 셋만 바꾼다
$duration: $dur-loop * .5; // 1s — 2초 캡처 창의 약수라 gif 가 두 바퀴 온전히 담긴다
$easing: $ease-spring;
$color: $subject-blue;
@include stage($stage-paper);
.pr { width: min(100%, 432px); }
.pr__head { margin: 0; text-align: center; }
.pr__title {
margin: 0; font-weight: 800; font-size: clamp(13px, 3.6vw, 16px); line-height: 1.25;
color: $subject-ink; // 흰 무대 위 18.24:1
}
.pr__sub {
margin: $sp-1 0 0; font-weight: 500; font-size: clamp(8px, 2.4vw, 10px); line-height: 1.4;
color: rgba($subject-ink, .62); // 흰 무대 위 5.12:1
}
.pr__grid {
display: grid; grid-template-columns: repeat(3, 1fr); gap: $sp-3;
margin: $sp-3 0 0; padding-top: $sp-5; // 오브 배지가 카드 위로 나오므로 그만큼 자리를 비워 둔다
}
.pr__card {
position: relative; box-sizing: border-box; display: flex; flex-direction: column;
padding: $sp-5 $sp-3 $sp-3; border: 1px solid rgba($subject-ink, .12); border-radius: $r-card;
background: $stage-paper;
}
// 추천 카드는 색이 아니라 테두리와 그림자로만 앞에 선다
.pr__card--pick { border-color: rgba($subject-ink, .32); box-shadow: $shadow-raised; }
.pr__plan {
display: flex; align-items: center; gap: $sp-1; margin: 0;
font-weight: 700; font-size: clamp(8px, 2.4vw, 10px); line-height: 1.2;
color: rgba($subject-ink, .62); // 흰 무대 위 5.12:1
}
.pr__flag {
padding: 0 $sp-1; border: 1px solid rgba($subject-ink, .28); border-radius: $r-pill;
// 행간 1.2 — 이보다 크면 알약이 플랜 이름 줄보다 높아져 추천 카드만 값이 아래로 밀린다 (480 실측 3.5px)
font-weight: 700; font-size: 8px; line-height: 1.2; color: rgba($subject-ink, .62);
}
.pr__price {
margin: $sp-1 0 0; font-weight: 800; font-size: clamp(14px, 4.4vw, 20px); line-height: 1.1;
color: $subject-ink; // 흰 무대 위 18.24:1
}
.pr__per { font-weight: 600; font-size: clamp(7px, 2vw, 9px); line-height: 1; color: rgba($subject-ink, .62); }
.pr__feats { list-style: none; margin: $sp-3 0 0; padding: 0; display: grid; gap: $sp-1; }
.pr__feat {
display: grid; grid-template-columns: 12px 1fr; align-items: center; gap: $sp-1;
font-weight: 500; font-size: clamp(7px, 2.2vw, 9px); line-height: 1.4;
color: rgba($subject-ink, .78); // 흰 무대 위 9.04:1
}
.pr__ico { display: inline-flex; width: 12px; height: 12px; color: $color; } // 장식 아이콘, 흰 무대 위 4.53:1
.pr__ico svg { width: 100%; height: 100%; }
.pr__pick {
margin: $sp-3 0 0; padding: $sp-1 0; width: 100%;
border: 1px solid rgba($subject-ink, .28); border-radius: $r-pill; background: none;
font-family: inherit; font-weight: 700; font-size: clamp(8px, 2.2vw, 10px); line-height: 1.4;
color: $subject-ink; cursor: pointer; // 세 카드가 같은 버튼 — 위계는 테두리·그림자만 진다
}
// 오브 배지 — 추천 카드 머리 위. 흰 바탕이라 가장자리를 잉크로 내려 경계를 세운다
.pr__orb {
position: absolute; top: -14px; left: 50%; width: 28px; height: 28px; border-radius: 50%;
background:
radial-gradient(circle at 34% 26%, rgba(255, 255, 255, .95) 0%, rgba(255, 255, 255, 0) 46%),
radial-gradient(circle at 52% 56%, $color 0%, rgba($color, .9) 54%, rgba($subject-ink, .82) 100%);
box-shadow: 0 4px 12px rgba($color, .45);
transform: translateX(-50%); // 가로 가운데 맞춤은 고정, 세로만 루프가 움직인다
}
// ── 미리보기 루프: 배지가 1초에 한 번 떠올랐다 내려앉는다
.pr.is-demo .pr__orb {
animation-name: prBob; animation-duration: $duration; animation-timing-function: $easing;
animation-iteration-count: infinite;
}
@keyframes prBob {
0% { transform: translateX(-50%) translateY(0); }
50% { transform: translateX(-50%) translateY(-6px); }
100% { transform: translateX(-50%) translateY(0); }
}
// ── 320×200 폰 압축 (BD9). 이 안에서는 간격을 올리지 않는다
@media (max-width: 340px), (max-height: 220px) {
.pr__title { font-size: 11px; }
.pr__sub { font-size: 8px; margin-top: 0; }
.pr__grid { gap: $sp-2; margin-top: $sp-2; padding-top: $sp-4; }
.pr__card { padding: $sp-4 $sp-2 $sp-2; }
.pr__plan { font-size: 8px; }
.pr__flag { font-size: 7px; }
// 320×200 실측 207px(7px 넘침) → 아래 세 간격만 내려 195px 로 맞췄다 (올리지 않는다, BD9)
.pr__price { font-size: 13px; margin-top: 0; }
.pr__feats { margin-top: $sp-1; }
.pr__feat { grid-template-columns: 10px 1fr; font-size: 7px; }
.pr__ico { width: 10px; height: 10px; }
.pr__pick { margin-top: $sp-1; font-size: 8px; }
.pr__orb { top: -10px; width: 20px; height: 20px; }
}
@media (prefers-reduced-motion: reduce) {
.pr.is-demo .pr__orb { animation: none !important; transform: translateX(-50%) !important; }
}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);
}
.pr {
width: min(100%, 432px);
}
.pr__head {
margin: 0;
text-align: center;
}
.pr__title {
margin: 0;
font-weight: 800;
font-size: clamp(13px, 3.6vw, 16px);
line-height: 1.25;
color: #17141a;
}
.pr__sub {
margin: 4px 0 0;
font-weight: 500;
font-size: clamp(8px, 2.4vw, 10px);
line-height: 1.4;
color: rgba(23, 20, 26, 0.62);
}
.pr__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin: 12px 0 0;
padding-top: 20px;
}
.pr__card {
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: column;
padding: 20px 12px 12px;
border: 1px solid rgba(23, 20, 26, 0.12);
border-radius: 16px;
background: #ffffff;
}
.pr__card--pick {
border-color: rgba(23, 20, 26, 0.32);
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
.pr__plan {
display: flex;
align-items: center;
gap: 4px;
margin: 0;
font-weight: 700;
font-size: clamp(8px, 2.4vw, 10px);
line-height: 1.2;
color: rgba(23, 20, 26, 0.62);
}
.pr__flag {
padding: 0 4px;
border: 1px solid rgba(23, 20, 26, 0.28);
border-radius: 999px;
font-weight: 700;
font-size: 8px;
line-height: 1.2;
color: rgba(23, 20, 26, 0.62);
}
.pr__price {
margin: 4px 0 0;
font-weight: 800;
font-size: clamp(14px, 4.4vw, 20px);
line-height: 1.1;
color: #17141a;
}
.pr__per {
font-weight: 600;
font-size: clamp(7px, 2vw, 9px);
line-height: 1;
color: rgba(23, 20, 26, 0.62);
}
.pr__feats {
list-style: none;
margin: 12px 0 0;
padding: 0;
display: grid;
gap: 4px;
}
.pr__feat {
display: grid;
grid-template-columns: 12px 1fr;
align-items: center;
gap: 4px;
font-weight: 500;
font-size: clamp(7px, 2.2vw, 9px);
line-height: 1.4;
color: rgba(23, 20, 26, 0.78);
}
.pr__ico {
display: inline-flex;
width: 12px;
height: 12px;
color: #2f6df6;
}
.pr__ico svg {
width: 100%;
height: 100%;
}
.pr__pick {
margin: 12px 0 0;
padding: 4px 0;
width: 100%;
border: 1px solid rgba(23, 20, 26, 0.28);
border-radius: 999px;
background: none;
font-family: inherit;
font-weight: 700;
font-size: clamp(8px, 2.2vw, 10px);
line-height: 1.4;
color: #17141a;
cursor: pointer;
}
.pr__orb {
position: absolute;
top: -14px;
left: 50%;
width: 28px;
height: 28px;
border-radius: 50%;
background: radial-gradient(circle at 34% 26%, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0) 46%), radial-gradient(circle at 52% 56%, #2f6df6 0%, rgba(47, 109, 246, 0.9) 54%, rgba(23, 20, 26, 0.82) 100%);
box-shadow: 0 4px 12px rgba(47, 109, 246, 0.45);
transform: translateX(-50%);
}
.pr.is-demo .pr__orb {
animation-name: prBob;
animation-duration: 1s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
}
@keyframes prBob {
0% {
transform: translateX(-50%) translateY(0);
}
50% {
transform: translateX(-50%) translateY(-6px);
}
100% {
transform: translateX(-50%) translateY(0);
}
}
@media (max-width: 340px), (max-height: 220px) {
.pr__title {
font-size: 11px;
}
.pr__sub {
font-size: 8px;
margin-top: 0;
}
.pr__grid {
gap: 8px;
margin-top: 8px;
padding-top: 16px;
}
.pr__card {
padding: 16px 8px 8px;
}
.pr__plan {
font-size: 8px;
}
.pr__flag {
font-size: 7px;
}
.pr__price {
font-size: 13px;
margin-top: 0;
}
.pr__feats {
margin-top: 4px;
}
.pr__feat {
grid-template-columns: 10px 1fr;
font-size: 7px;
}
.pr__ico {
width: 10px;
height: 10px;
}
.pr__pick {
margin-top: 4px;
font-size: 8px;
}
.pr__orb {
top: -10px;
width: 20px;
height: 20px;
}
}
@media (prefers-reduced-motion: reduce) {
.pr.is-demo .pr__orb {
animation: none !important;
transform: translateX(-50%) !important;
}
}import styles from "./PricingOrb.module.scss";
export type PricingOrbProps = {
/** 배지가 한 번 떠올랐다 내려앉는 시간(ms). 2000 의 약수로 둔다 */
duration?: number;
easing?: string;
/** 오브 배지 색. 6자리 hex 로 준다 (#2f6df6) */
color?: string;
};
type Plan = { plan: string; price: string; feats: string[]; recommended: boolean };
const PLANS: Plan[] = [
{ plan: "스타터", price: "₩0", feats: ["하루 20회", "모델 1종", "이메일 문의"], recommended: false },
{ plan: "프로", price: "₩19,000", feats: ["하루 500회", "모델 4종", "우선 응답"], recommended: true },
{ plan: "팀", price: "₩49,000", feats: ["무제한 호출", "팀 자리 10", "전담 담당자"], recommended: false },
];
// 6자리 hex 에 알파 두 자리를 붙인다 — #2f6df6 + "73" 꼴
function withAlpha(hex: string, alpha: number): string {
const n = Math.round(alpha * 255);
return hex + (n < 16 ? "0" : "") + n.toString(16);
}
function CheckIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"/><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/></svg>
);
}
export default function PricingOrb({
duration = 1000,
easing = "cubic-bezier(.2, .8, .2, 1)",
color = "#2f6df6",
}: PricingOrbProps) {
const orbStyle = {
background:
"radial-gradient(circle at 34% 26%, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0) 46%), " +
"radial-gradient(circle at 52% 56%, " + color + " 0%, " + withAlpha(color, 0.9) + " 54%, rgba(23, 20, 26, 0.82) 100%)",
boxShadow: "0 4px 12px " + withAlpha(color, 0.45),
animationDuration: duration + "ms",
animationTimingFunction: easing,
};
return (
<main className={styles.stage}>
<section className={`${styles.pr} ${styles.demoLoop}`}>
<header className={styles.head}>
<h2 className={styles.title}>요금제</h2>
<p className={styles.sub}>쓰는 만큼만 냅니다.</p>
</header>
<div className={styles.grid}>
{PLANS.map((p) => (
<article key={p.plan} className={`${styles.card} ${p.recommended ? styles.cardPick : ""}`}>
{p.recommended ? <span className={styles.orb} aria-hidden="true" style={orbStyle} /> : null}
<p className={styles.plan}>
{p.plan}
{p.recommended ? <span className={styles.flag}>추천</span> : null}
</p>
<p className={styles.price}>
{p.price}
<span className={styles.per}>/월</span>
</p>
<ul className={styles.feats}>
{p.feats.map((f) => (
<li key={f} className={styles.feat}>
<span className={styles.ico} aria-hidden="true"><CheckIcon /></span>
{f}
</li>
))}
</ul>
<button type="button" className={styles.pick} onClick={(e: { preventDefault: () => void }) => { e.preventDefault(); }}>
고르기
</button>
</article>
))}
</div>
</section>
</main>
);
}05 오브 배지 요금제 — 측정 누적면적 0.681% · 움직인 프레임 16/23GODRICH 원작 · zip 에 SCSS·React 포함 · ai 랜딩페이지 오브 디자인 키트, 빛나는 섹션 9개
이 데모가 속한 글
ai 랜딩페이지 오브 디자인 키트, 빛나는 섹션 9개ai 랜딩페이지 오브 디자인은 화면 전체를 물들이는 대신 짙은 단색 무대 위에 빛나는 구슬 하나만 올려 제품의 성격을 말하는 방식입니다. 배경 그라디언트 없이도 AI 다워 보인다는 이 계산을 섹션 아홉 개로 잘라 정 이 글의 데모 전부 보기


