05オーブバッジの料金プラン
3プランのうちおすすめの頭上で小さなオーブバッジが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>1日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">¥1,900<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>1日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">¥4,900<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 オリジナル · SCSS と React は zip に同梱 · ai ランディングページ 光るオーブ デザインキット、セクション9選
このデモが属する記事
ai ランディングページ 光るオーブ デザインキット、セクション9選ai ランディングページの光るオーブとは、台になる背景を単色のまま置いて、画面の中で光るものを丸い球ひとつに絞る作り方です。グラデーション背景なしでもAIらしく見えるこの手を、ヒーローからフッターまで貼って使える9つのセクションにまとめまし この記事のデモをすべて見る


