03요금제 카드 — 추천 카드만 확대 비율 값이 더 크게 계산되는 목록
추천 요금제 카드는 주변 카드보다 확대 비율 값이 더 크게 계산되어 떠 보이고, 결제 주기가 바뀐 상태가 되면 표시 금액이 그 값까지 다시 계산됩니다.
쓰임요금제 비교 섹션, SaaS 플랜 안내
classList.togglenth-child(2)steps(1, end)

<main class="stage" id="stage">
<div class="pricing is-demo" id="pricing">
<div class="toggle" role="group" aria-label="결제 주기">
<button class="toggle__btn toggle__btn--m is-active" id="btn-monthly" type="button" aria-pressed="true">월간</button>
<button class="toggle__btn toggle__btn--y" id="btn-yearly" type="button" aria-pressed="false">연간 -20%</button>
</div>
<div class="plans">
<article class="plan">
<p class="plan__name">스타터</p>
<div class="plan__price">
<span class="plan__amount plan__amount--m">9,000</span>
<span class="plan__amount plan__amount--y">7,200</span>
</div>
<div class="plan__unit-row">
<span class="plan__unit plan__unit--m">원/월</span>
<span class="plan__unit plan__unit--y">원/월(연 결제)</span>
</div>
</article>
<article class="plan plan--featured">
<p class="plan__badge">추천</p>
<p class="plan__name">프로</p>
<div class="plan__price">
<span class="plan__amount plan__amount--m">19,000</span>
<span class="plan__amount plan__amount--y">15,200</span>
</div>
<div class="plan__unit-row">
<span class="plan__unit plan__unit--m">원/월</span>
<span class="plan__unit plan__unit--y">원/월(연 결제)</span>
</div>
</article>
<article class="plan">
<p class="plan__name">비즈니스</p>
<div class="plan__price">
<span class="plan__amount plan__amount--m">39,000</span>
<span class="plan__amount plan__amount--y">31,200</span>
</div>
<div class="plan__unit-row">
<span class="plan__unit plan__unit--m">원/월</span>
<span class="plan__unit plan__unit--y">원/월(연 결제)</span>
</div>
</article>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// 계산 상수: 추천 카드와 주변 카드의 scale 값 차이
$featured-scale: 1.08;
$duration: $dur-loop;
$easing: steps(1, end);
$color: $subject-cream;
@include stage($stage-orange);
.pricing {
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-4;
width: 100%;
}
.toggle {
display: inline-flex;
gap: $sp-1;
padding: $sp-1;
border-radius: $r-pill;
background: rgba($subject-ink, .18);
}
.toggle__btn {
border: 0;
border-radius: $r-pill;
padding: $sp-1 $sp-4;
font-family: inherit;
font-weight: 600;
font-size: 11px;
line-height: 1.8;
cursor: pointer;
transition: background $dur-base $ease-out, color $dur-base $ease-out;
}
.toggle__btn.is-active {
background: $color;
color: $subject-orange-deep;
}
.toggle__btn:not(.is-active) {
background: transparent;
color: $color;
}
.plans {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: $sp-3;
width: 100%;
max-width: 420px;
}
.plan {
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-1;
padding: $sp-3 $sp-2;
border-radius: $r-card;
background: $stage-paper;
box-shadow: $shadow-press;
}
.plan--featured {
z-index: 1;
background: $subject-ink;
transform: scale(#{$featured-scale});
}
.is-demo .plan--featured {
animation: featured-scale-in $dur-enter $ease-spring both;
}
@keyframes featured-scale-in {
0% { transform: scale(1); }
100% { transform: scale(#{$featured-scale}); }
}
.plan__badge {
margin: 0;
padding: $sp-1 $sp-2;
border-radius: $r-xs;
background: $color;
color: $subject-orange-deep;
font-weight: 700;
font-size: 9px;
line-height: 1.4;
}
.plan__name {
margin: 0;
color: $subject-ink;
font-weight: 600;
font-size: 12px;
line-height: 1.4;
}
.plan--featured .plan__name { color: $stage-paper; }
.plan__price {
position: relative;
margin: 0;
min-height: 20px;
width: 100%;
text-align: center;
}
.plan__amount {
position: absolute;
inset: 0;
color: $subject-ink;
font-weight: 700;
font-size: 15px;
line-height: 1.4;
white-space: nowrap;
opacity: 0;
transition: opacity $dur-base $ease-out;
}
.plan--featured .plan__amount { color: $stage-paper; }
.plan__amount--m { opacity: 1; }
.plan__unit-row {
position: relative;
width: 100%;
height: 13px;
text-align: center;
}
.plan__unit {
position: absolute;
inset: 0;
color: rgba($subject-ink, .72);
font-weight: 400;
font-size: 9px;
line-height: 1.4;
white-space: nowrap;
opacity: 0;
transition: opacity $dur-base $ease-out;
}
.plan--featured .plan__unit { color: rgba($stage-paper, .78); }
.plan__unit--m { opacity: 1; }
.pricing.is-yearly .plan__amount--m,
.pricing.is-yearly .plan__unit--m { opacity: 0; }
.pricing.is-yearly .plan__amount--y,
.pricing.is-yearly .plan__unit--y { opacity: 1; }
.is-demo .plans > .plan:nth-child(1) .plan__amount--m,
.is-demo .plans > .plan:nth-child(1) .plan__unit--m {
animation: fade-m-1 $duration $easing infinite;
}
.is-demo .plans > .plan:nth-child(1) .plan__amount--y,
.is-demo .plans > .plan:nth-child(1) .plan__unit--y {
animation: fade-y-1 $duration $easing infinite;
}
.is-demo .plans > .plan:nth-child(2) .plan__amount--m,
.is-demo .plans > .plan:nth-child(2) .plan__unit--m {
animation: fade-m-2 $duration $easing infinite;
}
.is-demo .plans > .plan:nth-child(2) .plan__amount--y,
.is-demo .plans > .plan:nth-child(2) .plan__unit--y {
animation: fade-y-2 $duration $easing infinite;
}
.is-demo .plans > .plan:nth-child(3) .plan__amount--m,
.is-demo .plans > .plan:nth-child(3) .plan__unit--m {
animation: fade-m-3 $duration $easing infinite;
}
.is-demo .plans > .plan:nth-child(3) .plan__amount--y,
.is-demo .plans > .plan:nth-child(3) .plan__unit--y {
animation: fade-y-3 $duration $easing infinite;
}
@keyframes fade-m-1 { 0%, 25% { opacity: 1; } 30%, 100% { opacity: 0; } }
@keyframes fade-y-1 { 0%, 25% { opacity: 0; } 30%, 100% { opacity: 1; } }
@keyframes fade-m-2 { 0%, 45% { opacity: 1; } 50%, 100% { opacity: 0; } }
@keyframes fade-y-2 { 0%, 45% { opacity: 0; } 50%, 100% { opacity: 1; } }
@keyframes fade-m-3 { 0%, 65% { opacity: 1; } 70%, 100% { opacity: 0; } }
@keyframes fade-y-3 { 0%, 65% { opacity: 0; } 70%, 100% { opacity: 1; } }
@media (max-width: 340px) {
.plans { gap: $sp-1; max-width: 300px; }
.plan { padding: $sp-2 $sp-1; }
.plan__amount { font-size: 12px; }
}
@media (prefers-reduced-motion: reduce) {
.is-demo .plans > .plan:nth-child(1) .plan__amount--m,
.is-demo .plans > .plan:nth-child(1) .plan__amount--y,
.is-demo .plans > .plan:nth-child(1) .plan__unit--m,
.is-demo .plans > .plan:nth-child(1) .plan__unit--y,
.is-demo .plans > .plan:nth-child(2) .plan__amount--m,
.is-demo .plans > .plan:nth-child(2) .plan__amount--y,
.is-demo .plans > .plan:nth-child(2) .plan__unit--m,
.is-demo .plans > .plan:nth-child(2) .plan__unit--y,
.is-demo .plans > .plan:nth-child(3) .plan__amount--m,
.is-demo .plans > .plan:nth-child(3) .plan__amount--y,
.is-demo .plans > .plan:nth-child(3) .plan__unit--m,
.is-demo .plans > .plan:nth-child(3) .plan__unit--y,
.is-demo .plan--featured {
animation: none;
}
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.pricing {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
width: 100%;
}
.toggle {
display: inline-flex;
gap: 4px;
padding: 4px;
border-radius: 999px;
background: rgba(23, 20, 26, 0.18);
}
.toggle__btn {
border: 0;
border-radius: 999px;
padding: 4px 16px;
font-family: inherit;
font-weight: 600;
font-size: 11px;
line-height: 1.8;
cursor: pointer;
transition: background 300ms ease-out, color 300ms ease-out;
}
.toggle__btn.is-active {
background: #fff7e6;
color: #c93000;
}
.toggle__btn:not(.is-active) {
background: transparent;
color: #fff7e6;
}
.plans {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
width: 100%;
max-width: 420px;
}
.plan {
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 12px 8px;
border-radius: 16px;
background: #ffffff;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
.plan--featured {
z-index: 1;
background: #17141a;
transform: scale(1.08);
}
.is-demo .plan--featured {
animation: featured-scale-in 600ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
@keyframes featured-scale-in {
0% {
transform: scale(1);
}
100% {
transform: scale(1.08);
}
}
.plan__badge {
margin: 0;
padding: 4px 8px;
border-radius: 4px;
background: #fff7e6;
color: #c93000;
font-weight: 700;
font-size: 9px;
line-height: 1.4;
}
.plan__name {
margin: 0;
color: #17141a;
font-weight: 600;
font-size: 12px;
line-height: 1.4;
}
.plan--featured .plan__name {
color: #ffffff;
}
.plan__price {
position: relative;
margin: 0;
min-height: 20px;
width: 100%;
text-align: center;
}
.plan__amount {
position: absolute;
inset: 0;
color: #17141a;
font-weight: 700;
font-size: 15px;
line-height: 1.4;
white-space: nowrap;
opacity: 0;
transition: opacity 300ms ease-out;
}
.plan--featured .plan__amount {
color: #ffffff;
}
.plan__amount--m {
opacity: 1;
}
.plan__unit-row {
position: relative;
width: 100%;
height: 13px;
text-align: center;
}
.plan__unit {
position: absolute;
inset: 0;
color: rgba(23, 20, 26, 0.72);
font-weight: 400;
font-size: 9px;
line-height: 1.4;
white-space: nowrap;
opacity: 0;
transition: opacity 300ms ease-out;
}
.plan--featured .plan__unit {
color: rgba(255, 255, 255, 0.78);
}
.plan__unit--m {
opacity: 1;
}
.pricing.is-yearly .plan__amount--m,
.pricing.is-yearly .plan__unit--m {
opacity: 0;
}
.pricing.is-yearly .plan__amount--y,
.pricing.is-yearly .plan__unit--y {
opacity: 1;
}
.is-demo .plans > .plan:nth-child(1) .plan__amount--m,
.is-demo .plans > .plan:nth-child(1) .plan__unit--m {
animation: fade-m-1 2s steps(1, end) infinite;
}
.is-demo .plans > .plan:nth-child(1) .plan__amount--y,
.is-demo .plans > .plan:nth-child(1) .plan__unit--y {
animation: fade-y-1 2s steps(1, end) infinite;
}
.is-demo .plans > .plan:nth-child(2) .plan__amount--m,
.is-demo .plans > .plan:nth-child(2) .plan__unit--m {
animation: fade-m-2 2s steps(1, end) infinite;
}
.is-demo .plans > .plan:nth-child(2) .plan__amount--y,
.is-demo .plans > .plan:nth-child(2) .plan__unit--y {
animation: fade-y-2 2s steps(1, end) infinite;
}
.is-demo .plans > .plan:nth-child(3) .plan__amount--m,
.is-demo .plans > .plan:nth-child(3) .plan__unit--m {
animation: fade-m-3 2s steps(1, end) infinite;
}
.is-demo .plans > .plan:nth-child(3) .plan__amount--y,
.is-demo .plans > .plan:nth-child(3) .plan__unit--y {
animation: fade-y-3 2s steps(1, end) infinite;
}
@keyframes fade-m-1 {
0%, 25% {
opacity: 1;
}
30%, 100% {
opacity: 0;
}
}
@keyframes fade-y-1 {
0%, 25% {
opacity: 0;
}
30%, 100% {
opacity: 1;
}
}
@keyframes fade-m-2 {
0%, 45% {
opacity: 1;
}
50%, 100% {
opacity: 0;
}
}
@keyframes fade-y-2 {
0%, 45% {
opacity: 0;
}
50%, 100% {
opacity: 1;
}
}
@keyframes fade-m-3 {
0%, 65% {
opacity: 1;
}
70%, 100% {
opacity: 0;
}
}
@keyframes fade-y-3 {
0%, 65% {
opacity: 0;
}
70%, 100% {
opacity: 1;
}
}
@media (max-width: 340px) {
.plans {
gap: 4px;
max-width: 300px;
}
.plan {
padding: 8px 4px;
}
.plan__amount {
font-size: 12px;
}
}
@media (prefers-reduced-motion: reduce) {
.is-demo .plans > .plan:nth-child(1) .plan__amount--m,
.is-demo .plans > .plan:nth-child(1) .plan__amount--y,
.is-demo .plans > .plan:nth-child(1) .plan__unit--m,
.is-demo .plans > .plan:nth-child(1) .plan__unit--y,
.is-demo .plans > .plan:nth-child(2) .plan__amount--m,
.is-demo .plans > .plan:nth-child(2) .plan__amount--y,
.is-demo .plans > .plan:nth-child(2) .plan__unit--m,
.is-demo .plans > .plan:nth-child(2) .plan__unit--y,
.is-demo .plans > .plan:nth-child(3) .plan__amount--m,
.is-demo .plans > .plan:nth-child(3) .plan__amount--y,
.is-demo .plans > .plan:nth-child(3) .plan__unit--m,
.is-demo .plans > .plan:nth-child(3) .plan__unit--y,
.is-demo .plan--featured {
animation: none;
}
}// 결제 주기 버튼을 누르면 요금제 상태가 연간 또는 월간 상태가 되고, 그 상태에 따라 화면에 보이는 금액 값을 다시 계산해 표시한다.
// 계산 겹: 결제 주기 상태 -> 표시 금액 값 재계산. 실사용 클릭 리스너 있음.
(function () {
var root = document.getElementById('pricing');
var mBtn = document.getElementById('btn-monthly');
var yBtn = document.getElementById('btn-yearly');
if (!root || !mBtn || !yBtn) return;
function setYearly(yearly) {
root.classList.remove('is-demo');
root.classList.toggle('is-yearly', yearly);
mBtn.classList.toggle('is-active', !yearly);
yBtn.classList.toggle('is-active', yearly);
mBtn.setAttribute('aria-pressed', String(!yearly));
yBtn.setAttribute('aria-pressed', String(yearly));
}
mBtn.addEventListener('click', function () { setYearly(false); });
yBtn.addEventListener('click', function () { setYearly(true); });
})();import { useEffect, useRef } from "react";
import styles from "./PricingCards.module.scss";
// 결제 주기 버튼을 누르면 요금제 상태가 연간 또는 월간 상태가 되고, 그 상태에 따라 화면에 보이는 금액 값을 다시 계산해 표시한다.
// 계산 겹: 결제 주기 상태 -> 표시 금액 값 재계산. 실사용 클릭 리스너 있음.
const PLANS = [
{ name: "스타터", amountM: "9,000", amountY: "7,200", featured: false },
{ name: "프로", amountM: "19,000", amountY: "15,200", featured: true },
{ name: "비즈니스", amountM: "39,000", amountY: "31,200", featured: false },
];
export default function PricingCards() {
const rootRef = useRef<HTMLDivElement>(null);
const mBtnRef = useRef<HTMLButtonElement>(null);
const yBtnRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
const root = rootRef.current;
const mBtn = mBtnRef.current;
const yBtn = yBtnRef.current;
if (!root || !mBtn || !yBtn) return;
function setYearly(yearly: boolean) {
root!.classList.remove(styles["is-demo"]);
root!.classList.toggle(styles["is-yearly"], yearly);
mBtn!.classList.toggle(styles["is-active"], !yearly);
yBtn!.classList.toggle(styles["is-active"], yearly);
mBtn!.setAttribute("aria-pressed", String(!yearly));
yBtn!.setAttribute("aria-pressed", String(yearly));
}
function onMonthly() { setYearly(false); }
function onYearly() { setYearly(true); }
mBtn.addEventListener("click", onMonthly);
yBtn.addEventListener("click", onYearly);
return () => {
mBtn.removeEventListener("click", onMonthly);
yBtn.removeEventListener("click", onYearly);
};
}, []);
return (
<main className={styles.stage}>
<div ref={rootRef} className={`${styles.pricing} ${styles["is-demo"]}`}>
<div className={styles.toggle} role="group" aria-label="결제 주기">
<button
ref={mBtnRef}
className={`${styles.toggle__btn} ${styles["toggle__btn--m"]} ${styles["is-active"]}`}
type="button"
aria-pressed="true"
>
월간
</button>
<button
ref={yBtnRef}
className={`${styles.toggle__btn} ${styles["toggle__btn--y"]}`}
type="button"
aria-pressed="false"
>
연간 -20%
</button>
</div>
<div className={styles.plans}>
{PLANS.map((plan) => (
<article
key={plan.name}
className={plan.featured ? `${styles.plan} ${styles["plan--featured"]}` : styles.plan}
>
{plan.featured && <p className={styles.plan__badge}>추천</p>}
<p className={styles.plan__name}>{plan.name}</p>
<div className={styles.plan__price}>
<span className={`${styles.plan__amount} ${styles["plan__amount--m"]}`}>{plan.amountM}</span>
<span className={`${styles.plan__amount} ${styles["plan__amount--y"]}`}>{plan.amountY}</span>
</div>
<div className={styles["plan__unit-row"]}>
<span className={`${styles.plan__unit} ${styles["plan__unit--m"]}`}>원/월</span>
<span className={`${styles.plan__unit} ${styles["plan__unit--y"]}`}>원/월(연 결제)</span>
</div>
</article>
))}
</div>
</div>
</main>
);
}03 요금제 카드 — 추천 카드만 확대 비율 값이 더 크게 계산되는 목록 — 측정 누적면적 5.337% · 움직인 프레임 9/23GODRICH 원작 · zip 에 SCSS·React 포함 · 랜딩페이지 히어로섹션 디자인 9종 — 통계 카운터까지
이 데모가 속한 글
랜딩페이지 히어로섹션 디자인 9종 — 통계 카운터까지랜딩페이지 히어로섹션 아홉 개 중 하나는 카드가 보이는 비율 값이 기준을 넘을 때마다 순서 값에 간격을 곱해 나타나는 시점을 다시 구하고, 하나는 열린 질문의 답변 높이 값을 내용 길이로 다시 구해 펼치며, 또 하나 이 글의 데모 전부 보기


