청량한 디자인 랜딩 UI 키트, 시트러스 컬러 섹션 9개
청량한 디자인은 오렌지·라임·자몽·레몬처럼 채도 높은 과일 색을 크림 바탕 위에 얹고, 카드마다 같은 계열의 진한 색 그림자를 드리워 파스텔보다 더 쨍하고 여름 음료 브랜드처럼 싱그럽게 보이게 만드는 스타일입니다.
핀터레스트에 스크린샷으로만 저장해 두던 스무디·주스 브랜드 화면을 실제로 만든 원페이지로 아래에 그대로 올렸습니다. 맨 위 내비 바부터 맨 아래 웨이브 푸터까지 직접 펼쳐 놓았으니 통째로 훑어보세요.

이 원페이지를 만드는 조각 아홉 개를 낱개로 떼어내 아래 격자에 다시 펼쳤습니다. 방문자 눈이 실제로 움직이는 차례 — 시선을 끌기, 둘러보기, 가격 확인, 신뢰, 행동 유도, 가입 — 을 그대로 따랐습니다. 그중 다섯(히어로·카드 격자·기능 목록·CTA 밴드·푸터)은 물방울 모양과 색 그림자가 파스텔과 갈라지는 지점을 애니메이션으로 직접 보여줍니다.
01 hero-citrus
02 nav-citrus
03 card-grid-citrus
04 feature-list-citrus
05 pricing-citrus
06 testimonial-citrus
07 cta-band-citrus
08 footer-citrus
09 buttons-inputs-citrus
자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip
- 01 쥬스 스플래시 히어로
- 02 슬라이스 인디케이터 내비
- 03 웨지 배지 카드 격자
- 04 스퀴즈 드롭 아이콘 목록
- 05 스무디 요금제
- 06 드롭 말풍선 후기
- 07 시트러스 그라디언트 CTA 밴드
- 08 웨이브 가장자리 푸터
- 09 쥬시 버튼·인풋
이 키트 전체의 색은 웹디자인 트렌드 2026 기준으로도 아홉 개면 끝납니다. 카드 표면에 올리는 톤과 그림자·그라디언트 전용 진한 톤을 나눈 것이 파스텔 캔디 계열과 다른 점입니다. 같은 design 기둥에서 채도를 눌러 부드럽게 가라앉은 쪽이 손그림 디자인 랜딩 UI 키트에 이어 파스텔 톤 랜딩 UI 키트이고, 잉크를 걷어내고 광택으로 바꾼 쪽이 saas 랜딩 신뢰감 디자인 키트입니다.
| 역할 | 색 | 대비(잉크 기준) | 쓰는 곳 |
|---|---|---|---|
| 크림 | #fffaf0 |
9.83:1 | 페이지 바탕 전체 |
| 오렌지(카드) | #ff9a52 |
4.87:1 | 히어로 배지·카드 |
| 라임(카드) | #c8e84a |
7.35:1 | 카드·인풋 링 |
| 자몽(카드) | #ff8fa3 |
4.73:1 | 카드·말풍선 |
| 레몬(카드) | #ffe066 |
7.84:1 | 카드·요금제 |
| 오렌지(진한) | #ff6a00 |
3.56:1 | 그림자·그라디언트·버튼 배경 전용 |
| 잉크 | #204a17 |
— | 모든 글자 (흰 배경 대비 10.23:1) |
01쥬스 스플래시 히어로
시트러스 디자인에서 무엇이 보이나
크림 바탕 위에 오렌지·라임·자몽 세 개의 물방울(비대칭 반경을 45도 돌린 모양)이 튀어 오르며 자리를 바꾸고, 그 앞의 흰 카드가 방울이 튈 때마다 살짝 눌렸다 뜹니다.

<main class="stage">
<section class="herocitrus">
<span class="herocitrus__drop herocitrus__drop--a" aria-hidden="true"></span>
<span class="herocitrus__drop herocitrus__drop--b" aria-hidden="true"></span>
<span class="herocitrus__drop herocitrus__drop--c" aria-hidden="true"></span>
<div class="herocitrus__card">
<p class="herocitrus__eyebrow">스퀴즈</p>
<h1 class="herocitrus__title">오늘도 상큼하게</h1>
<p class="herocitrus__desc">과즙 가득한 랜딩 키트</p>
<button class="herocitrus__cta" type="button">무료로 시작</button>
</div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 방울 세 개가 한 바퀴 튀어 도는 주기
$easing: $ease-spring; // 통통 튀어 오르는 곡선
$color: $stage-ink; // 글자 기본(토큰) — 아래 시트러스 잉크로 덮는다
// 시트러스 팔레트 — 값은 tailwind.config.js · tokens.css 와 같다. 카드류는 잉크 대비 4.5:1 이상 확보한 밝은 톤, 진한 톤은 그림자·그라디언트 전용(접근성 섹션 참고)
$citrus-cream: #fffaf0; // 크림 바탕
$citrus-orange: #d8c1b1; // 오렌지 — 카드 표면용(대비 4.9:1)
$citrus-orange-deep: #bd967a; // 오렌지 — 그림자·그라디언트 전용(진한 톤)
$citrus-lime: #c4ce9c; // 라임 — 카드 표면용
$citrus-grapefruit: #ebd6da; // 자몽 — 카드 표면용
$citrus-lemon: #dfd8be; // 레몬 — 카드 표면용
$citrus-ink: #204a17; // 잎사귀 잉크 — 글자는 항상 이 색(흰 배경 대비 10:1)
@include stage($citrus-cream);
.herocitrus {
position: relative;
width: clamp(220px, 76vw, 360px);
}
// 물방울 — 비대칭 반경(물방울 모양)을 45도 돌려 만든다. 위치·크기는 부모가, 모양 각도는 고정
.herocitrus__drop {
position: absolute;
width: clamp(46px, 16vw, 64px);
aspect-ratio: 1;
pointer-events: none;
}
.herocitrus__drop::before {
content: "";
display: block;
width: 100%;
height: 100%;
border-radius: 50% 50% 50% $r-xs;
transform: rotate(45deg);
}
.herocitrus__drop--a {
top: -18px;
left: -14px;
animation: dropA $duration $easing infinite;
}
.herocitrus__drop--a::before { background: $citrus-orange-deep; box-shadow: 0 8px 16px rgba($citrus-orange-deep, .55); }
.herocitrus__drop--b {
right: -18px;
top: 30%;
animation: dropB $duration $easing infinite;
animation-delay: calc($duration / -3);
}
.herocitrus__drop--b::before { background: mix($citrus-lime, $citrus-ink, 85%); box-shadow: 0 8px 16px rgba($citrus-lime, .6); }
.herocitrus__drop--c {
left: 10%;
bottom: -20px;
animation: dropA $duration $easing infinite;
animation-delay: calc($duration / -3 * 2);
}
.herocitrus__drop--c::before { background: $citrus-grapefruit; box-shadow: 0 8px 16px rgba($citrus-grapefruit, .55); }
.herocitrus__card {
position: relative;
box-sizing: border-box;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: $sp-2;
padding: $sp-5;
border-radius: $r-card;
background: $stage-paper;
box-shadow: 0 10px 24px rgba($citrus-orange-deep, .4);
animation: cardBob $duration $easing infinite;
}
.herocitrus__eyebrow {
margin: 0;
color: $citrus-ink;
font-weight: 700; font-size: clamp(10px, 2.6vw, 12px); line-height: 1;
font-family: inherit;
letter-spacing: .08em;
text-transform: uppercase;
}
.herocitrus__title {
margin: 0;
color: $citrus-ink;
font-weight: 700; font-size: clamp(18px, 4.6vw, 24px); line-height: 1.2;
font-family: inherit;
}
.herocitrus__desc {
margin: 0;
color: rgba($citrus-ink, .72);
font-weight: 500; font-size: clamp(12px, 2.8vw, 14px); line-height: 1.4;
font-family: inherit;
}
.herocitrus__cta {
margin-top: $sp-1;
border: 0;
border-radius: $r-pill;
background: $citrus-orange-deep;
color: $subject-ink;
padding: $sp-2 $sp-5;
font-weight: 700; font-size: clamp(12px, 2.8vw, 14px); line-height: 1;
font-family: inherit;
box-shadow: 0 6px 14px rgba($citrus-orange-deep, .6);
}
.herocitrus__cta:focus-visible {
outline: 3px solid $citrus-ink;
outline-offset: 2px;
}
// 데모: 물방울 세 개가 각자 다른 위상으로 튀어 오르고, 카드는 그때마다 살짝 눌렸다 뜬다 (WAAPI 시계가 잡을 수 있게 keyframes)
@keyframes dropA {
0%, 100% { transform: translate(0, 0) scale(1) rotate(0deg); }
50% { transform: translate(8%, -10%) scale(1.15) rotate(-8deg); }
}
@keyframes dropB {
0%, 100% { transform: translate(0, 0) scale(1) rotate(0deg); }
50% { transform: translate(-7%, 9%) scale(.86) rotate(10deg); }
}
@keyframes cardBob {
0%, 100% { transform: translateY(0); box-shadow: 0 10px 24px rgba($citrus-orange-deep, .4); }
50% { transform: translateY(-7px); box-shadow: 0 16px 30px rgba($citrus-orange-deep, .32); }
}
// 반응형: 320×200 처럼 세로가 짧은 화면에선 방울 크기와 카드 속을 줄인다(BD9 — 217 실측대로 카드 자체 높이가 넘침의 주 원인이라
// 패딩·글자·버튼까지 함께 줄인다. 217-01 은 mist 를 blur 원으로만 그려 카드 밖으로 안 삐져나왔지만 이 편은 물방울을 카드 모서리
// 밖으로 튀어나오게 그려 bottom 오프셋이 남으면 213px 로 넘쳤다 — 실측)
@media (max-width: 340px), (max-height: 220px) {
.herocitrus__drop { width: clamp(22px, 8vw, 30px); }
.herocitrus__drop--a { top: -4px; left: -4px; }
.herocitrus__drop--b { right: -4px; top: 12%; }
.herocitrus__drop--c { left: 8%; bottom: -4px; }
.herocitrus__card { padding: $sp-2 $sp-3; gap: $sp-1; }
.herocitrus__eyebrow { display: none; }
.herocitrus__title { font-size: 15px; line-height: 1.15; }
.herocitrus__desc { display: none; }
.herocitrus__cta { padding: $sp-1 $sp-4; margin-top: 0; }
}
@media (prefers-reduced-motion: reduce) {
.herocitrus__drop, .herocitrus__card { animation: none !important; transform: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #fffaf0;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.herocitrus {
position: relative;
width: clamp(220px, 76vw, 360px);
}
.herocitrus__drop {
position: absolute;
width: clamp(46px, 16vw, 64px);
aspect-ratio: 1;
pointer-events: none;
}
.herocitrus__drop::before {
content: "";
display: block;
width: 100%;
height: 100%;
border-radius: 50% 50% 50% 4px;
transform: rotate(45deg);
}
.herocitrus__drop--a {
top: -18px;
left: -14px;
animation: dropA 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.herocitrus__drop--a::before {
background: #bd967a;
box-shadow: 0 8px 16px rgba(189, 150, 122, 0.55);
}
.herocitrus__drop--b {
right: -18px;
top: 30%;
animation: dropB 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
animation-delay: -0.6666666667s;
}
.herocitrus__drop--b::before {
background: rgb(67.2156862745%, 73.0196078431%, 53.3529411765%);
box-shadow: 0 8px 16px rgba(196, 206, 156, 0.6);
}
.herocitrus__drop--c {
left: 10%;
bottom: -20px;
animation: dropA 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
animation-delay: -1.3333333333s;
}
.herocitrus__drop--c::before {
background: #ebd6da;
box-shadow: 0 8px 16px rgba(235, 214, 218, 0.55);
}
.herocitrus__card {
position: relative;
box-sizing: border-box;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
padding: 20px;
border-radius: 16px;
background: #ffffff;
box-shadow: 0 10px 24px rgba(189, 150, 122, 0.4);
animation: cardBob 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.herocitrus__eyebrow {
margin: 0;
color: #204a17;
font-weight: 700;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1;
font-family: inherit;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.herocitrus__title {
margin: 0;
color: #204a17;
font-weight: 700;
font-size: clamp(18px, 4.6vw, 24px);
line-height: 1.2;
font-family: inherit;
}
.herocitrus__desc {
margin: 0;
color: rgba(32, 74, 23, 0.72);
font-weight: 500;
font-size: clamp(12px, 2.8vw, 14px);
line-height: 1.4;
font-family: inherit;
}
.herocitrus__cta {
margin-top: 4px;
border: 0;
border-radius: 999px;
background: #bd967a;
color: #17141a;
padding: 8px 20px;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 14px);
line-height: 1;
font-family: inherit;
box-shadow: 0 6px 14px rgba(189, 150, 122, 0.6);
}
.herocitrus__cta:focus-visible {
outline: 3px solid #204a17;
outline-offset: 2px;
}
@keyframes dropA {
0%, 100% {
transform: translate(0, 0) scale(1) rotate(0deg);
}
50% {
transform: translate(8%, -10%) scale(1.15) rotate(-8deg);
}
}
@keyframes dropB {
0%, 100% {
transform: translate(0, 0) scale(1) rotate(0deg);
}
50% {
transform: translate(-7%, 9%) scale(0.86) rotate(10deg);
}
}
@keyframes cardBob {
0%, 100% {
transform: translateY(0);
box-shadow: 0 10px 24px rgba(189, 150, 122, 0.4);
}
50% {
transform: translateY(-7px);
box-shadow: 0 16px 30px rgba(189, 150, 122, 0.32);
}
}
@media (max-width: 340px), (max-height: 220px) {
.herocitrus__drop {
width: clamp(22px, 8vw, 30px);
}
.herocitrus__drop--a {
top: -4px;
left: -4px;
}
.herocitrus__drop--b {
right: -4px;
top: 12%;
}
.herocitrus__drop--c {
left: 8%;
bottom: -4px;
}
.herocitrus__card {
padding: 8px 12px;
gap: 4px;
}
.herocitrus__eyebrow {
display: none;
}
.herocitrus__title {
font-size: 15px;
line-height: 1.15;
}
.herocitrus__desc {
display: none;
}
.herocitrus__cta {
padding: 4px 16px;
margin-top: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.herocitrus__drop, .herocitrus__card {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./HeroCitrus.module.scss";
export type HeroCitrusProps = {
eyebrow?: string;
title?: string;
desc?: string;
cta?: string;
/** 한 주기(기본 2000ms) */
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function HeroCitrus({
eyebrow = "스퀴즈",
title = "오늘도 상큼하게",
desc = "과즙 가득한 랜딩 키트",
cta = "무료로 시작",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ff6a00",
}: HeroCitrusProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.herocitrus} style={vars}>
<span className={`${styles.drop} ${styles.dropA}`} aria-hidden="true" />
<span className={`${styles.drop} ${styles.dropB}`} aria-hidden="true" />
<span className={`${styles.drop} ${styles.dropC}`} aria-hidden="true" />
<div className={styles.card}>
<p className={styles.eyebrow}>{eyebrow}</p>
<h1 className={styles.title}>{title}</h1>
<p className={styles.desc}>{desc}</p>
<button type="button" className={styles.cta}>{cta}</button>
</div>
</section>
);
}01 · 쥬스 스플래시 히어로 — 측정 누적면적 10.797% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기
코드에서 볼 곳
안개처럼 흐린 원이 아니라 또렷한 물방울을 쓴 것이 이 스타일이 파스텔 안개 히어로와 갈라지는 지점입니다.
.drop::before {
content: "";
width: 100%; height: 100%;
border-radius: 50% 50% 50% 4px; /* 물방울 모양 */
transform: rotate(45deg);
}
@keyframes dropA {
0%, 100% { transform: translate(0, 0) scale(1) rotate(0deg); }
50% { transform: translate(8%, -10%) scale(1.15) rotate(-8deg); }
}
02슬라이스 인디케이터 내비
화면에서 일어나는 일
알약 메뉴에서 활성 탭을 알려주는 라임색 웨지가 옆 칸으로 옮겨 갈 때 가로로 늘어났다가 도착하며 탄력 있게 줄어듭니다.

<main class="stage">
<nav class="navcitrus" aria-label="주요 메뉴">
<span class="navcitrus__brand">스퀴즈</span>
<div class="navcitrus__track">
<span class="navcitrus__wedge" aria-hidden="true"></span>
<span class="navcitrus__tab navcitrus__tab--on">홈</span>
<span class="navcitrus__tab">기능</span>
<span class="navcitrus__tab">요금제</span>
<span class="navcitrus__tab">문의</span>
</div>
<button class="navcitrus__cta" type="button">시작하기</button>
</nav>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 웨지가 네 칸을 한 바퀴 도는 주기
$easing: $ease-spring; // 도착할 때 탄력 있게 줄어드는 곡선
$color: $stage-ink; // 글자 기본(토큰) — 아래 잉크로 덮는다
$citrus-cream: #fffaf0;
$citrus-orange: #d8c1b1;
$citrus-orange-deep: #bd967a;
$citrus-lime: #c4ce9c;
$citrus-lime-deep: #98af5c;
$citrus-grapefruit: #ebd6da;
$citrus-ink: #204a17;
// 무대 — 라임 물색
@include stage(mix($citrus-lime, $stage-paper, 26%));
.navcitrus {
box-sizing: border-box;
width: clamp(280px, 92vw, 440px);
display: flex;
align-items: center;
gap: $sp-2;
padding: $sp-2;
border-radius: $r-pill;
background: $stage-paper;
box-shadow: 0 8px 20px rgba($citrus-lime-deep, .4);
}
.navcitrus__brand {
flex: 0 0 auto;
padding-left: $sp-3;
color: $citrus-ink;
font-weight: 700; font-size: clamp(11px, 2.6vw, 13px); line-height: 1;
font-family: inherit;
}
.navcitrus__track {
position: relative;
flex: 1 1 auto;
min-width: 0;
display: flex;
border-radius: $r-pill;
background: mix($citrus-cream, $stage-paper, 60%);
}
.navcitrus__wedge {
position: absolute;
top: $sp-1;
bottom: $sp-1;
left: $sp-1;
width: calc(25% - $sp-1);
box-sizing: border-box;
border-radius: $r-pill;
background: $citrus-lime-deep;
box-shadow: 0 4px 10px rgba($citrus-lime-deep, .8);
// 네 칸을 순서대로 옮겨 다니며 도착할 때마다 조각(웨지)처럼 세로로 살짝 눌린다
animation: wedgeWalk $duration $easing infinite;
}
.navcitrus__tab {
position: relative;
z-index: 1;
flex: 1 1 auto;
min-width: 0;
padding: $sp-2 0;
text-align: center;
color: rgba($citrus-ink, .78);
font-weight: 600; font-size: clamp(10px, 2.4vw, 12px); line-height: 1;
font-family: inherit;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.navcitrus__tab--on {
color: $citrus-ink;
font-weight: 700;
}
.navcitrus__cta {
flex: 0 0 auto;
border: 0;
border-radius: $r-pill;
background: $citrus-orange-deep;
color: $subject-ink;
padding: $sp-2 $sp-4;
font-weight: 700; font-size: clamp(10px, 2.4vw, 12px); line-height: 1;
font-family: inherit;
box-shadow: 0 5px 12px rgba($citrus-orange-deep, .6);
}
.navcitrus__cta:focus-visible {
outline: 3px solid $citrus-ink;
outline-offset: 2px;
}
// 데모: 칸 폭은 25% — translateX(100%) 가 정확히 한 칸. 도착 순간 scaleY 로 눌렸다 펴진다
// 데모: % 이동은 이 요소 자기 폭 기준 — 폭이 calc(25% - 4px) 라 한 칸(트랙의 25%)보다 4px 모자라
// 이동량을 calc(100% + 4px) 씩 보정한다(보정 없으면 세 칸을 지나며 12px 밀린다 — 217 nav-candy 와 같은 식으로 실측)
@keyframes wedgeWalk {
0%, 24% { transform: translateX(0) scaleY(1); }
30% { transform: translateX(0) scaleY(.82); }
36%, 49% { transform: translateX(calc(100% + 4px)) scaleY(1); }
55% { transform: translateX(calc(100% + 4px)) scaleY(.82); }
61%, 74% { transform: translateX(calc(200% + 8px)) scaleY(1); }
80% { transform: translateX(calc(200% + 8px)) scaleY(.82); }
86%, 100% { transform: translateX(calc(300% + 12px)) scaleY(1); }
}
@media (prefers-reduced-motion: reduce) {
.navcitrus__wedge { animation: none !important; transform: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: rgb(93.9843137255%, 95.0039215686%, 89.9058823529%);
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.navcitrus {
box-sizing: border-box;
width: clamp(280px, 92vw, 440px);
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 999px;
background: #ffffff;
box-shadow: 0 8px 20px rgba(152, 175, 92, 0.4);
}
.navcitrus__brand {
flex: 0 0 auto;
padding-left: 12px;
color: #204a17;
font-weight: 700;
font-size: clamp(11px, 2.6vw, 13px);
line-height: 1;
font-family: inherit;
}
.navcitrus__track {
position: relative;
flex: 1 1 auto;
min-width: 0;
display: flex;
border-radius: 999px;
background: #fffcf6;
}
.navcitrus__wedge {
position: absolute;
top: 4px;
bottom: 4px;
left: 4px;
width: calc(25% - 4px);
box-sizing: border-box;
border-radius: 999px;
background: #98af5c;
box-shadow: 0 4px 10px rgba(152, 175, 92, 0.8);
animation: wedgeWalk 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.navcitrus__tab {
position: relative;
z-index: 1;
flex: 1 1 auto;
min-width: 0;
padding: 8px 0;
text-align: center;
color: rgba(32, 74, 23, 0.78);
font-weight: 600;
font-size: clamp(10px, 2.4vw, 12px);
line-height: 1;
font-family: inherit;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.navcitrus__tab--on {
color: #204a17;
font-weight: 700;
}
.navcitrus__cta {
flex: 0 0 auto;
border: 0;
border-radius: 999px;
background: #bd967a;
color: #17141a;
padding: 8px 16px;
font-weight: 700;
font-size: clamp(10px, 2.4vw, 12px);
line-height: 1;
font-family: inherit;
box-shadow: 0 5px 12px rgba(189, 150, 122, 0.6);
}
.navcitrus__cta:focus-visible {
outline: 3px solid #204a17;
outline-offset: 2px;
}
@keyframes wedgeWalk {
0%, 24% {
transform: translateX(0) scaleY(1);
}
30% {
transform: translateX(0) scaleY(0.82);
}
36%, 49% {
transform: translateX(calc(100% + 4px)) scaleY(1);
}
55% {
transform: translateX(calc(100% + 4px)) scaleY(0.82);
}
61%, 74% {
transform: translateX(calc(200% + 8px)) scaleY(1);
}
80% {
transform: translateX(calc(200% + 8px)) scaleY(0.82);
}
86%, 100% {
transform: translateX(calc(300% + 12px)) scaleY(1);
}
}
@media (prefers-reduced-motion: reduce) {
.navcitrus__wedge {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./NavCitrus.module.scss";
export type NavCitrusProps = {
brand?: string;
tabs?: string[];
active?: number;
cta?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function NavCitrus({
brand = "스퀴즈",
tabs = ["홈", "기능", "요금제", "문의"],
active = 0,
cta = "시작하기",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#9ad200",
}: NavCitrusProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<nav className={styles.navcitrus} style={vars} aria-label="주요 메뉴">
<span className={styles.brand}>{brand}</span>
<div className={styles.track}>
<span className={styles.wedge} aria-hidden="true" style={{ "--activeIndex": active } as CSSProperties} />
{tabs.map((t, i) => (
<span key={t} className={`${styles.tab} ${i === active ? styles.tabOn : ""}`}>{t}</span>
))}
</div>
<button type="button" className={styles.cta}>{cta}</button>
</nav>
);
}02 · 슬라이스 인디케이터 내비 — 측정 누적면적 6.292% · 움직인 프레임 12/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기
코드 읽는 법
칸 폭이 트랙의 4분의 1에서 좌측 여백 4px를 뺀 값이라 translateX도 그만큼 보정해야 밀리지 않습니다.
.wedge {
width: calc(25% - 4px);
animation: wedgeWalk 2s cubic-bezier(.2,.8,.2,1) infinite;
}
@keyframes wedgeWalk {
0%, 24% { transform: translateX(0) scaleY(1); }
36%, 49% { transform: translateX(calc(100% + 4px)) scaleY(1); }
}
03웨지 배지 카드 격자
어떤 움직임인가
카드마다 오렌지·라임·자몽·레몬 저마다 다른 톤에 같은 색으로 물든 그림자를 얹고, 모서리에는 과일 조각 모양 배지가 붙습니다.

<main class="stage">
<div class="gridcitrus">
<div class="gridcitrus__card gridcitrus__card--org">
<span class="gridcitrus__badge">NEW</span>
<span class="gridcitrus__dot" aria-hidden="true"></span>
<p class="gridcitrus__name">오렌지 알림</p>
</div>
<div class="gridcitrus__card gridcitrus__card--lim">
<span class="gridcitrus__badge">무료</span>
<span class="gridcitrus__dot" aria-hidden="true"></span>
<p class="gridcitrus__name">라임 위젯</p>
</div>
<div class="gridcitrus__card gridcitrus__card--grp">
<span class="gridcitrus__badge">인기</span>
<span class="gridcitrus__dot" aria-hidden="true"></span>
<p class="gridcitrus__name">자몽 카드</p>
</div>
<div class="gridcitrus__card gridcitrus__card--lem">
<span class="gridcitrus__badge">신규</span>
<span class="gridcitrus__dot" aria-hidden="true"></span>
<p class="gridcitrus__name">레몬 기록</p>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 카드 네 장이 한 바퀴 튀어 오르는 주기
$easing: $ease-spring;
$color: $stage-ink;
$citrus-cream: #fffaf0;
$citrus-orange: #d8c1b1;
$citrus-orange-deep: #bd967a;
$citrus-lime: #c4ce9c;
$citrus-grapefruit: #ebd6da;
$citrus-grapefruit-deep: #d1a3ad;
$citrus-lemon: #dfd8be;
$citrus-ink: #204a17;
// 무대 — 자몽 물색
@include stage(mix($citrus-grapefruit, $stage-paper, 24%));
.gridcitrus {
box-sizing: border-box;
width: clamp(280px, 92vw, 430px);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(clamp(120px, 40vw, 160px), 1fr));
gap: $sp-3;
}
.gridcitrus__card {
position: relative;
box-sizing: border-box;
min-height: clamp(92px, 26vw, 116px);
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: $sp-2;
padding: $sp-4;
border-radius: $r-card;
animation: cardPop $duration $easing infinite;
}
.gridcitrus__card--org { background: $citrus-orange; box-shadow: 0 10px 20px rgba($citrus-orange-deep, .45); }
.gridcitrus__card--lim { background: $citrus-lime; box-shadow: 0 10px 20px rgba($citrus-lime, .8); animation-delay: calc($duration / -4); }
.gridcitrus__card--grp { background: $citrus-grapefruit; box-shadow: 0 10px 20px rgba($citrus-grapefruit-deep, .4); animation-delay: calc($duration / -2); }
.gridcitrus__card--lem { background: $citrus-lemon; box-shadow: 0 10px 20px rgba($citrus-lemon, .85); animation-delay: calc($duration / -4 * 3); }
// 웨지 배지 — 카드 모서리에 붙는 과일 조각 모양(삼각형 위쪽을 둥글린 모양)
.gridcitrus__badge {
position: absolute;
top: calc($sp-2 * -1);
right: $sp-3;
padding: $sp-1 $sp-2;
border-radius: $sp-1 $r-pill $r-pill $r-pill;
background: $stage-paper;
color: $citrus-ink;
font-weight: 700; font-size: clamp(9px, 2.2vw, 11px); line-height: 1;
font-family: inherit;
box-shadow: 0 4px 10px rgba($citrus-orange-deep, .35);
}
.gridcitrus__dot {
width: $sp-6;
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, $stage-paper, rgba($stage-paper, .4) 75%);
}
.gridcitrus__name {
margin: 0;
color: $citrus-ink;
font-weight: 700; font-size: clamp(12px, 3vw, 14px); line-height: 1.3;
font-family: inherit;
}
// 데모: 네 카드가 시작 시각을 1/4 주기씩 어긋내 순서대로 톡톡 튀어 오른다
@keyframes cardPop {
0%, 100% { transform: translateY(0) scale(1); }
30% { transform: translateY(-8px) scale(1.02); }
}
// 반응형: 320×200 처럼 세로가 짧은 화면에선 카드 속을 줄인다(BD9)
@media (max-width: 340px), (max-height: 220px) {
.gridcitrus { gap: $sp-2; }
.gridcitrus__card { min-height: 64px; padding: $sp-2 $sp-3; gap: $sp-1; }
.gridcitrus__badge { display: none; }
.gridcitrus__dot { width: $sp-4; }
.gridcitrus__name { font-size: 11px; }
}
@media (prefers-reduced-motion: reduce) {
.gridcitrus__card { animation: none !important; transform: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: rgb(98.1176470588%, 96.1411764706%, 96.5176470588%);
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.gridcitrus {
box-sizing: border-box;
width: clamp(280px, 92vw, 430px);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(clamp(120px, 40vw, 160px), 1fr));
gap: 12px;
}
.gridcitrus__card {
position: relative;
box-sizing: border-box;
min-height: clamp(92px, 26vw, 116px);
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 8px;
padding: 16px;
border-radius: 16px;
animation: cardPop 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.gridcitrus__card--org {
background: #d8c1b1;
box-shadow: 0 10px 20px rgba(189, 150, 122, 0.45);
}
.gridcitrus__card--lim {
background: #c4ce9c;
box-shadow: 0 10px 20px rgba(196, 206, 156, 0.8);
animation-delay: -0.5s;
}
.gridcitrus__card--grp {
background: #ebd6da;
box-shadow: 0 10px 20px rgba(209, 163, 173, 0.4);
animation-delay: -1s;
}
.gridcitrus__card--lem {
background: #dfd8be;
box-shadow: 0 10px 20px rgba(223, 216, 190, 0.85);
animation-delay: -1.5s;
}
.gridcitrus__badge {
position: absolute;
top: -8px;
right: 12px;
padding: 4px 8px;
border-radius: 4px 999px 999px 999px;
background: #ffffff;
color: #204a17;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 11px);
line-height: 1;
font-family: inherit;
box-shadow: 0 4px 10px rgba(189, 150, 122, 0.35);
}
.gridcitrus__dot {
width: 24px;
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, #ffffff, rgba(255, 255, 255, 0.4) 75%);
}
.gridcitrus__name {
margin: 0;
color: #204a17;
font-weight: 700;
font-size: clamp(12px, 3vw, 14px);
line-height: 1.3;
font-family: inherit;
}
@keyframes cardPop {
0%, 100% {
transform: translateY(0) scale(1);
}
30% {
transform: translateY(-8px) scale(1.02);
}
}
@media (max-width: 340px), (max-height: 220px) {
.gridcitrus {
gap: 8px;
}
.gridcitrus__card {
min-height: 64px;
padding: 8px 12px;
gap: 4px;
}
.gridcitrus__badge {
display: none;
}
.gridcitrus__dot {
width: 16px;
}
.gridcitrus__name {
font-size: 11px;
}
}
@media (prefers-reduced-motion: reduce) {
.gridcitrus__card {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./CardGridCitrus.module.scss";
export type CardGridCitrusItem = { badge: string; name: string; tone: "org" | "lim" | "grp" | "lem" };
export type CardGridCitrusProps = {
items?: CardGridCitrusItem[];
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const DEFAULT_ITEMS: CardGridCitrusItem[] = [
{ badge: "NEW", name: "오렌지 알림", tone: "org" },
{ badge: "무료", name: "라임 위젯", tone: "lim" },
{ badge: "인기", name: "자몽 카드", tone: "grp" },
{ badge: "신규", name: "레몬 기록", tone: "lem" },
];
const TONE_CLASS: Record<CardGridCitrusItem["tone"], string> = {
org: "cardOrg", lim: "cardLim", grp: "cardGrp", lem: "cardLem",
};
export default function CardGridCitrus({
items = DEFAULT_ITEMS,
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ff9a52",
}: CardGridCitrusProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.gridcitrus} style={vars}>
{items.map((it, i) => (
<div
key={it.name}
className={`${styles.card} ${styles[TONE_CLASS[it.tone]]}`}
style={{ animationDelay: `${(-duration / 4) * i}ms` } as CSSProperties}
>
<span className={styles.badge}>{it.badge}</span>
<span className={styles.dot} aria-hidden="true" />
<p className={styles.name}>{it.name}</p>
</div>
))}
</div>
);
}03 · 웨지 배지 카드 격자 — 측정 누적면적 15.898% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기
어디를 바꾸면 되나
네 카드가 시작 시각을 4분의 1주기씩 어긋내 순서대로 톡톡 튀어 오릅니다.
.card { animation: cardPop 2s cubic-bezier(.2,.8,.2,1) infinite; }
.card--lim { background: #c8e84a; animation-delay: -500ms; }
@keyframes cardPop {
0%, 100% { transform: translateY(0) scale(1); }
30% { transform: translateY(-8px) scale(1.02); }
}
04스퀴즈 드롭 아이콘 목록
무엇이 보이나
행마다 흰 알약 카드가 있고 그 안의 동그란 아이콘은 repeating-conic-gradient로 그린 과육 무늬입니다. 아이콘마다 시작 시각을 어긋내 놓으면 방울이 각자 다른 박자로 눌렸다 튑니다.

<main class="stage">
<ul class="listcitrus">
<li class="listcitrus__row">
<span class="listcitrus__drop listcitrus__drop--a" aria-hidden="true"></span>
<span class="listcitrus__label">끌어다 놓기</span>
</li>
<li class="listcitrus__row">
<span class="listcitrus__drop listcitrus__drop--b" aria-hidden="true"></span>
<span class="listcitrus__label">템플릿 고르기</span>
</li>
<li class="listcitrus__row">
<span class="listcitrus__drop listcitrus__drop--c" aria-hidden="true"></span>
<span class="listcitrus__label">색 바꾸기</span>
</li>
<li class="listcitrus__row">
<span class="listcitrus__drop listcitrus__drop--d" aria-hidden="true"></span>
<span class="listcitrus__label">공유하기</span>
</li>
</ul>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 방울이 한 번 튀고 쉬는 주기
$easing: $ease-pop; // 튀어 오르는 감
$color: $stage-ink;
$citrus-cream: #fffaf0;
$citrus-orange: #d8c1b1;
$citrus-lime: #c4ce9c;
$citrus-grapefruit: #ebd6da;
$citrus-lemon: #dfd8be;
$citrus-lemon-deep: #bdb27a;
$citrus-ink: #204a17;
// 무대 — 레몬 물색
@include stage(mix($citrus-lemon, $stage-paper, 24%));
.listcitrus {
box-sizing: border-box;
width: clamp(260px, 88vw, 400px);
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: $sp-2;
}
.listcitrus__row {
display: flex;
align-items: center;
gap: $sp-3;
padding: $sp-2 $sp-4 $sp-2 $sp-2;
border-radius: $r-pill;
background: $stage-paper;
box-shadow: 0 6px 16px rgba($citrus-lemon-deep, .45);
}
// 과육 무늬 — conic gradient 를 원으로 잘라 쓰고 하이라이트 점을 얹는다
.listcitrus__drop {
flex: 0 0 auto;
width: clamp(28px, 8vw, 36px);
aspect-ratio: 1;
border-radius: 50%;
position: relative;
box-shadow: inset 0 -3px 6px rgba($citrus-ink, .18);
}
.listcitrus__drop::after {
content: "";
position: absolute;
top: 18%;
left: 18%;
width: 26%;
aspect-ratio: 1;
border-radius: 50%;
background: rgba($stage-paper, .85);
}
.listcitrus__drop--a {
background: repeating-conic-gradient($citrus-orange 0deg 45deg, $citrus-lemon 45deg 90deg);
animation: dropSqueeze $duration $easing infinite;
}
.listcitrus__drop--b {
background: repeating-conic-gradient($citrus-lime 0deg 45deg, $citrus-lemon 45deg 90deg);
animation: dropSqueeze $duration $easing infinite;
animation-delay: calc($duration / -4);
}
.listcitrus__drop--c {
background: repeating-conic-gradient($citrus-grapefruit 0deg 45deg, $citrus-orange 45deg 90deg);
animation: dropSqueeze $duration $easing infinite;
animation-delay: calc($duration / -2);
}
.listcitrus__drop--d {
background: repeating-conic-gradient($citrus-lemon 0deg 45deg, $citrus-lime 45deg 90deg);
animation: dropSqueeze $duration $easing infinite;
animation-delay: calc($duration / -4 * 3);
}
.listcitrus__label {
color: $citrus-ink;
font-weight: 600; font-size: clamp(12px, 3vw, 14px); line-height: 1;
font-family: inherit;
}
// 데모: 방울 네 개가 시작 시각을 어긋내 각자 다른 박자로 눌렸다 튄다(스퀴즈)
@keyframes dropSqueeze {
0%, 40%, 100% { transform: scale(1, 1); }
15% { transform: scale(1.18, .78); }
28% { transform: scale(.92, 1.1); }
34% { transform: scale(1.05, .96); }
}
// 반응형: 320×200 처럼 세로가 짧은 화면에선 행 속을 줄인다(BD9)
@media (max-width: 340px), (max-height: 220px) {
.listcitrus { gap: $sp-1; }
.listcitrus__row { padding: $sp-1 $sp-3 $sp-1 $sp-1; gap: $sp-2; }
.listcitrus__drop { width: 20px; }
.listcitrus__label { font-size: 11px; }
}
@media (prefers-reduced-motion: reduce) {
.listcitrus__drop { animation: none !important; transform: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: rgb(96.9882352941%, 96.3294117647%, 93.8823529412%);
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.listcitrus {
box-sizing: border-box;
width: clamp(260px, 88vw, 400px);
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
}
.listcitrus__row {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 16px 8px 8px;
border-radius: 999px;
background: #ffffff;
box-shadow: 0 6px 16px rgba(189, 178, 122, 0.45);
}
.listcitrus__drop {
flex: 0 0 auto;
width: clamp(28px, 8vw, 36px);
aspect-ratio: 1;
border-radius: 50%;
position: relative;
box-shadow: inset 0 -3px 6px rgba(32, 74, 23, 0.18);
}
.listcitrus__drop::after {
content: "";
position: absolute;
top: 18%;
left: 18%;
width: 26%;
aspect-ratio: 1;
border-radius: 50%;
background: rgba(255, 255, 255, 0.85);
}
.listcitrus__drop--a {
background: repeating-conic-gradient(#d8c1b1 0deg 45deg, #dfd8be 45deg 90deg);
animation: dropSqueeze 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
.listcitrus__drop--b {
background: repeating-conic-gradient(#c4ce9c 0deg 45deg, #dfd8be 45deg 90deg);
animation: dropSqueeze 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
animation-delay: -0.5s;
}
.listcitrus__drop--c {
background: repeating-conic-gradient(#ebd6da 0deg 45deg, #d8c1b1 45deg 90deg);
animation: dropSqueeze 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
animation-delay: -1s;
}
.listcitrus__drop--d {
background: repeating-conic-gradient(#dfd8be 0deg 45deg, #c4ce9c 45deg 90deg);
animation: dropSqueeze 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
animation-delay: -1.5s;
}
.listcitrus__label {
color: #204a17;
font-weight: 600;
font-size: clamp(12px, 3vw, 14px);
line-height: 1;
font-family: inherit;
}
@keyframes dropSqueeze {
0%, 40%, 100% {
transform: scale(1, 1);
}
15% {
transform: scale(1.18, 0.78);
}
28% {
transform: scale(0.92, 1.1);
}
34% {
transform: scale(1.05, 0.96);
}
}
@media (max-width: 340px), (max-height: 220px) {
.listcitrus {
gap: 4px;
}
.listcitrus__row {
padding: 4px 12px 4px 4px;
gap: 8px;
}
.listcitrus__drop {
width: 20px;
}
.listcitrus__label {
font-size: 11px;
}
}
@media (prefers-reduced-motion: reduce) {
.listcitrus__drop {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./FeatureListCitrus.module.scss";
export type FeatureListCitrusItem = { label: string; tone: "a" | "b" | "c" | "d" };
export type FeatureListCitrusProps = {
items?: FeatureListCitrusItem[];
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const DEFAULT_ITEMS: FeatureListCitrusItem[] = [
{ label: "끌어다 놓기", tone: "a" },
{ label: "템플릿 고르기", tone: "b" },
{ label: "색 바꾸기", tone: "c" },
{ label: "공유하기", tone: "d" },
];
export default function FeatureListCitrus({
items = DEFAULT_ITEMS,
duration = 2000,
easing = "cubic-bezier(.2,1.4,.4,1)",
color = "#ff9a52",
}: FeatureListCitrusProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<ul className={styles.listcitrus} style={vars}>
{items.map((it, i) => (
<li key={it.label} className={styles.row}>
<span
className={`${styles.drop} ${styles[`drop${it.tone.toUpperCase()}`]}`}
aria-hidden="true"
style={{ animationDelay: `${(-duration / 4) * i}ms` } as CSSProperties}
/>
<span className={styles.label}>{it.label}</span>
</li>
))}
</ul>
);
}04 · 스퀴즈 드롭 아이콘 목록 — 측정 누적면적 2.135% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기
코드에서 볼 곳
아이콘 하나만 바뀌는 항목이라 누적면적이 2.1%로 아홉 개 중 가장 작습니다 — 그래도 강도는 35.4로 잉크 대비만큼 또렷합니다.
.drop {
background: repeating-conic-gradient(#ff9a52 0deg 45deg, #ffe066 45deg 90deg);
animation: dropSqueeze 2s cubic-bezier(.2,1.4,.4,1) infinite;
}
@keyframes dropSqueeze {
0%, 40%, 100% { transform: scale(1, 1); }
15% { transform: scale(1.18, .78); }
}
05스무디 요금제
화면에서 일어나는 일
세 요금제를 새싹(라임)·스퀴즈(추천)·자몽으로 두고, 추천 플랜만 오렌지→레몬 그라디언트 테두리와 인기 배지를 얹어 살짝 크고 통통거립니다.

<main class="stage">
<div class="pricecitrus">
<div class="pricecitrus__plan pricecitrus__plan--lime">
<p class="pricecitrus__name">새싹</p>
<p class="pricecitrus__price">무료</p>
<p class="pricecitrus__per">영원히</p>
</div>
<div class="pricecitrus__plan pricecitrus__plan--pick">
<span class="pricecitrus__badge">인기</span>
<div class="pricecitrus__inner">
<p class="pricecitrus__name">스퀴즈</p>
<p class="pricecitrus__price">월 19,000원</p>
<p class="pricecitrus__per">첫 달 무료</p>
</div>
</div>
<div class="pricecitrus__plan pricecitrus__plan--grp">
<p class="pricecitrus__name">자몽</p>
<p class="pricecitrus__price">월 9,000원</p>
<p class="pricecitrus__per">연간 결제</p>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 추천 플랜이 한 번 통통거리는 주기
$easing: $ease-spring;
$color: $stage-ink;
$citrus-cream: #fffaf0;
$citrus-orange: #d8c1b1;
$citrus-orange-deep: #bd967a;
$citrus-lime: #c4ce9c;
$citrus-grapefruit: #ebd6da;
$citrus-lemon: #dfd8be;
$citrus-ink: #204a17;
// 무대 — 오렌지 물색
@include stage(mix($citrus-orange, $stage-paper, 22%));
.pricecitrus {
box-sizing: border-box;
width: clamp(280px, 94vw, 450px);
display: flex;
align-items: center;
gap: $sp-2;
}
.pricecitrus__plan {
position: relative;
box-sizing: border-box;
flex: 1 1 0;
min-width: 0;
display: flex;
flex-direction: column;
gap: $sp-1;
padding: $sp-4 $sp-3;
border-radius: $r-card;
text-align: center;
}
.pricecitrus__plan--lime {
background: $citrus-lime;
box-shadow: 0 8px 18px rgba($citrus-lime, .8);
}
.pricecitrus__plan--grp {
background: $citrus-grapefruit;
box-shadow: 0 8px 18px rgba($citrus-grapefruit, .75);
}
// 추천 플랜 — 오렌지 그라디언트 테두리(겉을 감싼 알약) + 살짝 크게
.pricecitrus__plan--pick {
flex: 1.2 1 0;
padding: $sp-1;
background: linear-gradient(135deg, $citrus-orange-deep, $citrus-lemon);
box-shadow: 0 10px 22px rgba($citrus-orange-deep, .5);
animation: pickJiggle $duration $easing infinite;
}
.pricecitrus__inner {
display: flex;
flex-direction: column;
gap: $sp-1;
padding: $sp-4 $sp-3;
border-radius: $r-card;
background: mix($citrus-lemon, $stage-paper, 55%);
}
.pricecitrus__badge {
position: absolute;
top: calc($sp-2 * -1);
left: 50%;
transform: translateX(-50%);
z-index: 1;
padding: $sp-1 $sp-3;
border-radius: $r-pill;
background: $stage-paper;
color: $citrus-ink;
font-weight: 700; font-size: clamp(9px, 2.2vw, 11px); line-height: 1;
font-family: inherit;
box-shadow: 0 4px 10px rgba($citrus-orange-deep, .45);
}
.pricecitrus__name {
margin: 0;
color: $citrus-ink;
font-weight: 700; font-size: clamp(12px, 3vw, 15px); line-height: 1;
font-family: inherit;
}
.pricecitrus__price {
margin: 0;
color: $citrus-ink;
font-weight: 700; font-size: clamp(12px, 3.2vw, 16px); line-height: 1.15;
font-family: inherit;
}
.pricecitrus__per {
margin: 0;
color: $citrus-ink;
font-weight: 500; font-size: clamp(10px, 2.4vw, 12px); line-height: 1;
font-family: inherit;
}
// 데모: 추천 플랜만 살짝 흔들리며 통통거린다 — 색과 크기가 이미 주인공임을 움직임도 돕는다
@keyframes pickJiggle {
0%, 100% { transform: rotate(0deg) translateY(0); }
25% { transform: rotate(-1.6deg) translateY(-5px); }
50% { transform: rotate(0deg) translateY(0); }
75% { transform: rotate(1.6deg) translateY(-5px); }
}
@media (prefers-reduced-motion: reduce) {
.pricecitrus__plan--pick { animation: none !important; transform: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: rgb(96.6352941176%, 94.6509803922%, 93.2705882353%);
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.pricecitrus {
box-sizing: border-box;
width: clamp(280px, 94vw, 450px);
display: flex;
align-items: center;
gap: 8px;
}
.pricecitrus__plan {
position: relative;
box-sizing: border-box;
flex: 1 1 0;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
padding: 16px 12px;
border-radius: 16px;
text-align: center;
}
.pricecitrus__plan--lime {
background: #c4ce9c;
box-shadow: 0 8px 18px rgba(196, 206, 156, 0.8);
}
.pricecitrus__plan--grp {
background: #ebd6da;
box-shadow: 0 8px 18px rgba(235, 214, 218, 0.75);
}
.pricecitrus__plan--pick {
flex: 1.2 1 0;
padding: 4px;
background: linear-gradient(135deg, #bd967a, #dfd8be);
box-shadow: 0 10px 22px rgba(189, 150, 122, 0.5);
animation: pickJiggle 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.pricecitrus__inner {
display: flex;
flex-direction: column;
gap: 4px;
padding: 16px 12px;
border-radius: 16px;
background: rgb(93.0980392157%, 91.5882352941%, 85.9803921569%);
}
.pricecitrus__badge {
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
z-index: 1;
padding: 4px 12px;
border-radius: 999px;
background: #ffffff;
color: #204a17;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 11px);
line-height: 1;
font-family: inherit;
box-shadow: 0 4px 10px rgba(189, 150, 122, 0.45);
}
.pricecitrus__name {
margin: 0;
color: #204a17;
font-weight: 700;
font-size: clamp(12px, 3vw, 15px);
line-height: 1;
font-family: inherit;
}
.pricecitrus__price {
margin: 0;
color: #204a17;
font-weight: 700;
font-size: clamp(12px, 3.2vw, 16px);
line-height: 1.15;
font-family: inherit;
}
.pricecitrus__per {
margin: 0;
color: #204a17;
font-weight: 500;
font-size: clamp(10px, 2.4vw, 12px);
line-height: 1;
font-family: inherit;
}
@keyframes pickJiggle {
0%, 100% {
transform: rotate(0deg) translateY(0);
}
25% {
transform: rotate(-1.6deg) translateY(-5px);
}
50% {
transform: rotate(0deg) translateY(0);
}
75% {
transform: rotate(1.6deg) translateY(-5px);
}
}
@media (prefers-reduced-motion: reduce) {
.pricecitrus__plan--pick {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./PricingCitrus.module.scss";
export type PricingCitrusProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function PricingCitrus({
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ff6a00",
}: PricingCitrusProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.pricecitrus} style={vars}>
<div className={`${styles.plan} ${styles.planLime}`}>
<p className={styles.name}>새싹</p>
<p className={styles.price}>무료</p>
<p className={styles.per}>영원히</p>
</div>
<div className={`${styles.plan} ${styles.planPick}`}>
<span className={styles.badge}>인기</span>
<div className={styles.inner}>
<p className={styles.name}>스퀴즈</p>
<p className={styles.price}>월 19,000원</p>
<p className={styles.per}>첫 달 무료</p>
</div>
</div>
<div className={`${styles.plan} ${styles.planGrp}`}>
<p className={styles.name}>자몽</p>
<p className={styles.price}>월 9,000원</p>
<p className={styles.per}>연간 결제</p>
</div>
</div>
);
}05 · 스무디 요금제 — 측정 누적면적 4.617% · 움직인 프레임 21/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기
코드 읽는 법
표 없이 색과 크기만으로 어느 플랜이 주인공인지 보입니다.
.plan--pick {
flex: 1.2 1 0;
background: linear-gradient(135deg, #ff6a00, #ffe066);
animation: pickJiggle 2s cubic-bezier(.2,.8,.2,1) infinite;
}
@keyframes pickJiggle {
0%, 100% { transform: rotate(0deg) translateY(0); }
25% { transform: rotate(-1.6deg) translateY(-5px); }
}
06드롭 말풍선 후기
어떤 움직임인가
후기는 물방울 모양 꼬리가 달린 둥근 말풍선에 담고, 옆의 아바타는 과육 무늬 동그라미입니다.

<main class="stage">
<figure class="quotecitrus">
<span class="quotecitrus__avatar" aria-hidden="true"></span>
<div class="quotecitrus__bubble">
<blockquote class="quotecitrus__text">
<p class="quotecitrus__line">이 사이트 진짜 상큼해요</p>
</blockquote>
<figcaption class="quotecitrus__who">박모찌 · 스무디 가게 운영</figcaption>
</div>
</figure>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 말풍선이 한 번 눌렸다 펴지는 주기
$easing: $ease-spring;
$color: $stage-ink;
$citrus-cream: #fffaf0;
$citrus-orange: #d8c1b1;
$citrus-orange-deep: #bd967a;
$citrus-lime: #c4ce9c;
$citrus-grapefruit: #ebd6da;
$citrus-lemon: #dfd8be;
$citrus-ink: #204a17;
// 무대 — 자몽 물색
@include stage(mix($citrus-grapefruit, $stage-paper, 22%));
.quotecitrus {
box-sizing: border-box;
width: clamp(280px, 90vw, 420px);
margin: 0;
display: flex;
align-items: flex-start;
gap: $sp-3;
}
// 과육 무늬 아바타
.quotecitrus__avatar {
flex: 0 0 auto;
width: clamp(44px, 12vw, 56px);
aspect-ratio: 1;
border-radius: 50%;
background: repeating-conic-gradient($citrus-orange 0deg 60deg, $citrus-lemon 60deg 120deg);
box-shadow: inset 0 -4px 8px rgba($citrus-ink, .16), 0 6px 14px rgba($citrus-orange-deep, .45);
animation: avatarBob $duration $easing infinite;
animation-delay: calc($duration / -2);
}
.quotecitrus__bubble {
position: relative;
flex: 1 1 auto;
min-width: 0;
box-sizing: border-box;
padding: $sp-4;
border-radius: $r-card;
background: $stage-paper;
box-shadow: 0 8px 20px rgba($citrus-grapefruit, .55);
animation: bubbleSquish $duration $easing infinite;
}
// 말풍선 꼬리 — 물방울 모양(비대칭 반경) 하나를 45도 돌려 붙인다
.quotecitrus__bubble::before {
content: "";
position: absolute;
top: $sp-3;
left: calc($sp-2 * -1);
width: $sp-4;
aspect-ratio: 1;
border-radius: 50% 50% 50% $r-xs;
background: $stage-paper;
transform: rotate(45deg);
}
.quotecitrus__text {
margin: 0;
}
.quotecitrus__line {
margin: 0;
color: $citrus-ink;
font-weight: 700; font-size: clamp(13px, 3.4vw, 16px); line-height: 1.4;
font-family: inherit;
}
.quotecitrus__who {
margin: $sp-2 0 0;
color: rgba($citrus-ink, .78);
font-weight: 500; font-size: clamp(10px, 2.6vw, 12px); line-height: 1;
font-family: inherit;
}
// 데모: 말풍선은 젤리처럼 세로로 살짝 눌렸다 펴지고, 아바타는 반대 위상으로 떠오른다
@keyframes bubbleSquish {
0%, 100% { transform: scaleY(1); }
50% { transform: scaleY(.94); }
}
@keyframes avatarBob {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
@media (prefers-reduced-motion: reduce) {
.quotecitrus__bubble, .quotecitrus__avatar { animation: none !important; transform: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: rgb(98.2745098039%, 96.462745098%, 96.8078431373%);
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.quotecitrus {
box-sizing: border-box;
width: clamp(280px, 90vw, 420px);
margin: 0;
display: flex;
align-items: flex-start;
gap: 12px;
}
.quotecitrus__avatar {
flex: 0 0 auto;
width: clamp(44px, 12vw, 56px);
aspect-ratio: 1;
border-radius: 50%;
background: repeating-conic-gradient(#d8c1b1 0deg 60deg, #dfd8be 60deg 120deg);
box-shadow: inset 0 -4px 8px rgba(32, 74, 23, 0.16), 0 6px 14px rgba(189, 150, 122, 0.45);
animation: avatarBob 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
animation-delay: -1s;
}
.quotecitrus__bubble {
position: relative;
flex: 1 1 auto;
min-width: 0;
box-sizing: border-box;
padding: 16px;
border-radius: 16px;
background: #ffffff;
box-shadow: 0 8px 20px rgba(235, 214, 218, 0.55);
animation: bubbleSquish 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.quotecitrus__bubble::before {
content: "";
position: absolute;
top: 12px;
left: -8px;
width: 16px;
aspect-ratio: 1;
border-radius: 50% 50% 50% 4px;
background: #ffffff;
transform: rotate(45deg);
}
.quotecitrus__text {
margin: 0;
}
.quotecitrus__line {
margin: 0;
color: #204a17;
font-weight: 700;
font-size: clamp(13px, 3.4vw, 16px);
line-height: 1.4;
font-family: inherit;
}
.quotecitrus__who {
margin: 8px 0 0;
color: rgba(32, 74, 23, 0.78);
font-weight: 500;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1;
font-family: inherit;
}
@keyframes bubbleSquish {
0%, 100% {
transform: scaleY(1);
}
50% {
transform: scaleY(0.94);
}
}
@keyframes avatarBob {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-8px);
}
}
@media (prefers-reduced-motion: reduce) {
.quotecitrus__bubble, .quotecitrus__avatar {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./TestimonialCitrus.module.scss";
export type TestimonialCitrusProps = {
quote?: string;
who?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function TestimonialCitrus({
quote = "이 사이트 진짜 상큼해요",
who = "박모찌 · 스무디 가게 운영",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ff9a52",
}: TestimonialCitrusProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<figure className={styles.quotecitrus} style={vars}>
<span className={styles.avatar} aria-hidden="true" />
<div className={styles.bubble}>
<blockquote className={styles.text}>
<p className={styles.line}>{quote}</p>
</blockquote>
<figcaption className={styles.who}>{who}</figcaption>
</div>
</figure>
);
}06 · 드롭 말풍선 후기 — 측정 누적면적 3.087% · 움직인 프레임 12/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기
어디를 바꾸면 되나
말풍선 세로 폭이 숨쉬듯 줄었다 늘었다를 반복해 젤리를 쥔 듯한 탄력이 페이지 전체에 배어납니다.
.bubble::before {
content: "";
width: 16px; height: 16px;
border-radius: 50% 50% 50% 4px; /* 말풍선 꼬리도 물방울 모양 */
background: #fff;
transform: rotate(45deg);
}
07시트러스 그라디언트 CTA 밴드
무엇이 보이나
밴드 하나에 오렌지에서 레몬, 라임으로 지나가는 그라디언트를 깔고 그 위치가 천천히 흐릅니다. 위의 흰 버튼은 눌렸다가 통통 튕겨 돌아오는 것을 반복합니다.

<main class="stage">
<section class="ctacitrus">
<div class="ctacitrus__scrim">
<h2 class="ctacitrus__title">지금 상큼하게 시작하기</h2>
<p class="ctacitrus__sub">무료로 시작해 보세요</p>
</div>
<button class="ctacitrus__btn" type="button">시작하기</button>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 그라디언트가 한 바퀴 흐르는 주기
$easing: $ease-spring; // 버튼이 눌렸다 튕겨 돌아오는 곡선
$color: $stage-ink;
$citrus-cream: #fffaf0;
$citrus-orange-deep: #bd967a;
$citrus-lime-deep: #98af5c;
$citrus-lemon-deep: #bdb27a;
$citrus-ink: #204a17;
// 무대 — 크림
@include stage($citrus-cream);
.ctacitrus {
box-sizing: border-box;
width: clamp(280px, 90vw, 430px);
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-2;
padding: clamp($sp-5, 6vw, $sp-8) $sp-5;
border-radius: $r-card;
// 시트러스 그라디언트 — 색이 이어지도록 양 끝을 같은 색으로 두고 300% 폭으로 깔아 위치를 흘린다
background: linear-gradient(90deg, $citrus-orange-deep, $citrus-lemon-deep, $citrus-lime-deep, $citrus-lemon-deep, $citrus-orange-deep);
background-size: 300% 100%;
box-shadow: 0 10px 24px rgba($citrus-orange-deep, .45);
animation: gradFlow $duration linear infinite;
}
// 스크림 — 그라디언트가 흐르며 레몬 구간을 지날 때 흰 글자 대비가 1.4:1까지 떨어진다(실측, 접근성 섹션 참고).
// 잉크 65% 스크림을 글자 뒤에 깔면 세 색 전 구간에서 흰 글자 대비가 4.7:1 이상으로 유지된다
.ctacitrus__scrim {
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-1;
padding: $sp-2 $sp-4;
border-radius: $r-card;
background: rgba($citrus-ink, .65);
}
.ctacitrus__title {
margin: 0;
color: $stage-paper;
font-weight: 700; font-size: clamp(16px, 4.2vw, 21px); line-height: 1.25;
font-family: inherit;
text-align: center;
}
.ctacitrus__sub {
margin: 0;
color: rgba($stage-paper, .92);
font-weight: 500; font-size: clamp(11px, 2.8vw, 13px); line-height: 1.4;
font-family: inherit;
text-align: center;
}
.ctacitrus__btn {
margin-top: $sp-2;
border: 0;
border-radius: $r-pill;
background: $stage-paper;
color: $citrus-ink;
padding: $sp-2 $sp-6;
font-weight: 700; font-size: clamp(12px, 3vw, 14px); line-height: 1;
font-family: inherit;
box-shadow: 0 8px 18px rgba($citrus-ink, .28);
animation: btnPress $duration $easing infinite;
}
.ctacitrus__btn:focus-visible {
outline: 3px solid $stage-paper;
outline-offset: 2px;
}
// 데모: 그라디언트는 왼쪽으로 천천히 흐르고, 버튼은 살짝 눌렸다 통통 튕겨 돌아온다
@keyframes gradFlow {
0% { background-position: 0% 50%; }
100% { background-position: -300% 50%; }
}
@keyframes btnPress {
0%, 55%, 100% { transform: translateY(0); box-shadow: 0 8px 18px rgba($citrus-ink, .28); }
65% { transform: translateY(4px) scaleY(.96); box-shadow: 0 2px 6px rgba($citrus-ink, .2); }
82% { transform: translateY(-3px); box-shadow: 0 11px 22px rgba($citrus-ink, .3); }
}
@media (prefers-reduced-motion: reduce) {
.ctacitrus, .ctacitrus__btn { animation: none !important; transform: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #fffaf0;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.ctacitrus {
box-sizing: border-box;
width: clamp(280px, 90vw, 430px);
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: clamp(20px, 6vw, 32px) 20px;
border-radius: 16px;
background: linear-gradient(90deg, #bd967a, #bdb27a, #98af5c, #bdb27a, #bd967a);
background-size: 300% 100%;
box-shadow: 0 10px 24px rgba(189, 150, 122, 0.45);
animation: gradFlow 2s linear infinite;
}
.ctacitrus__scrim {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 8px 16px;
border-radius: 16px;
background: rgba(32, 74, 23, 0.65);
}
.ctacitrus__title {
margin: 0;
color: #ffffff;
font-weight: 700;
font-size: clamp(16px, 4.2vw, 21px);
line-height: 1.25;
font-family: inherit;
text-align: center;
}
.ctacitrus__sub {
margin: 0;
color: rgba(255, 255, 255, 0.92);
font-weight: 500;
font-size: clamp(11px, 2.8vw, 13px);
line-height: 1.4;
font-family: inherit;
text-align: center;
}
.ctacitrus__btn {
margin-top: 8px;
border: 0;
border-radius: 999px;
background: #ffffff;
color: #204a17;
padding: 8px 24px;
font-weight: 700;
font-size: clamp(12px, 3vw, 14px);
line-height: 1;
font-family: inherit;
box-shadow: 0 8px 18px rgba(32, 74, 23, 0.28);
animation: btnPress 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.ctacitrus__btn:focus-visible {
outline: 3px solid #ffffff;
outline-offset: 2px;
}
@keyframes gradFlow {
0% {
background-position: 0% 50%;
}
100% {
background-position: -300% 50%;
}
}
@keyframes btnPress {
0%, 55%, 100% {
transform: translateY(0);
box-shadow: 0 8px 18px rgba(32, 74, 23, 0.28);
}
65% {
transform: translateY(4px) scaleY(0.96);
box-shadow: 0 2px 6px rgba(32, 74, 23, 0.2);
}
82% {
transform: translateY(-3px);
box-shadow: 0 11px 22px rgba(32, 74, 23, 0.3);
}
}
@media (prefers-reduced-motion: reduce) {
.ctacitrus, .ctacitrus__btn {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./CtaBandCitrus.module.scss";
export type CtaBandCitrusProps = {
title?: string;
sub?: string;
cta?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function CtaBandCitrus({
title = "지금 상큼하게 시작하기",
sub = "무료로 시작해 보세요",
cta = "시작하기",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ff6a00",
}: CtaBandCitrusProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.ctacitrus} style={vars}>
<div className={styles.scrim}>
<h2 className={styles.title}>{title}</h2>
<p className={styles.sub}>{sub}</p>
</div>
<button type="button" className={styles.btn}>{cta}</button>
</section>
);
}07 · 시트러스 그라디언트 CTA 밴드 — 측정 누적면적 41.17% · 움직인 프레임 21/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기
코드에서 볼 곳
이 항목은 나중에 함정 섹션에서 다시 나옵니다 — 원래 흰 글자를 그라디언트 위에 바로 얹었더니 레몬 구간에서 대비가 무너졌습니다.
.ctacitrus {
background: linear-gradient(90deg, #ff6a00, #ffd400, #9ad200, #ffd400, #ff6a00);
background-size: 300% 100%;
animation: gradFlow 2s linear infinite;
}
@keyframes gradFlow {
0% { background-position: 0% 50%; }
100% { background-position: -300% 50%; }
}
08웨이브 가장자리 푸터
화면에서 일어나는 일
푸터의 윗변을 물결이 죽 이어진 웨이브 무늬로 띄우는데, 물결 하나하나를 요소로 그리지 않고 배경 반복 하나로 그립니다.

<main class="stage">
<footer class="footercitrus">
<span class="footercitrus__wave" aria-hidden="true"></span>
<div class="footercitrus__cols">
<div class="footercitrus__col footercitrus__col--org">
<p class="footercitrus__head">제품</p>
<p class="footercitrus__link">기능</p>
</div>
<div class="footercitrus__col footercitrus__col--grp">
<p class="footercitrus__head">회사</p>
<p class="footercitrus__link">소개</p>
</div>
<div class="footercitrus__col footercitrus__col--lem">
<p class="footercitrus__head">도움말</p>
<p class="footercitrus__link">가이드</p>
</div>
</div>
<p class="footercitrus__copy">
<span class="footercitrus__spark" aria-hidden="true">✦</span>
스퀴즈 · 상큼한 하루
<span class="footercitrus__spark footercitrus__spark--b" aria-hidden="true">✦</span>
</p>
</footer>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 방울이 한 번 반짝이는 주기
$easing: $ease-out;
$color: $stage-ink;
$citrus-cream: #fffaf0;
$citrus-orange-deep: #bd967a;
$citrus-lime: #c4ce9c;
$citrus-grapefruit: #ebd6da;
$citrus-lemon: #dfd8be;
$citrus-ink: #204a17;
// 푸터 패널 색 — 크림보다 살짝 진한 라임 우유크림
$panel: mix($citrus-lime, $stage-paper, 18%);
@include stage($citrus-cream);
.footercitrus {
position: relative;
box-sizing: border-box;
width: clamp(300px, 94vw, 460px);
padding: $sp-4 $sp-3 $sp-3;
border-radius: $r-card;
background: $panel;
box-shadow: 0 -6px 20px rgba($citrus-lime, .4);
}
// 웨이브 가장자리 — 물결 하나하나를 요소로 그리지 않고 배경 반복 하나로 그린다.
// 각 타일(30×15px) 아래변 중심에 반원을 그리면 윗반원만 남아 물결치는 가장자리가 된다
.footercitrus__wave {
position: absolute;
top: calc($sp-3 * -1);
left: 0;
width: 100%;
height: $sp-3;
background-image: radial-gradient(circle at 50% 100%, $panel 0 64%, transparent 66%);
background-size: 30px 15px;
background-repeat: repeat-x;
background-position: bottom center;
}
.footercitrus__cols {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(clamp(84px, 26vw, 120px), 1fr));
gap: $sp-2;
}
.footercitrus__col {
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: $sp-1;
padding: $sp-3;
border-radius: $r-control;
}
.footercitrus__col--org { background: mix($citrus-orange-deep, $stage-paper, 30%); animation: colRipple $duration $easing infinite; }
.footercitrus__col--grp { background: mix($citrus-grapefruit, $stage-paper, 45%); animation: colRipple $duration $easing infinite; animation-delay: calc($duration / -3); }
.footercitrus__col--lem { background: mix($citrus-lemon, $stage-paper, 45%); animation: colRipple $duration $easing infinite; animation-delay: calc($duration / -3 * 2); }
.footercitrus__head {
margin: 0;
color: $citrus-ink;
font-weight: 700; font-size: clamp(10px, 2.6vw, 12px); line-height: 1;
font-family: inherit;
letter-spacing: .06em;
}
.footercitrus__link {
margin: 0;
color: $citrus-ink;
font-weight: 500; font-size: clamp(11px, 2.8vw, 13px); line-height: 1.5;
font-family: inherit;
}
.footercitrus__copy {
display: flex;
align-items: center;
justify-content: center;
gap: $sp-2;
margin: $sp-3 0 0;
color: rgba($citrus-ink, .78);
font-weight: 500; font-size: clamp(10px, 2.6vw, 12px); line-height: 1;
font-family: inherit;
}
.footercitrus__spark {
color: $citrus-orange-deep;
animation: dropTwinkle $duration $easing infinite;
}
.footercitrus__spark--b {
animation-delay: calc($duration / -2);
color: mix($citrus-lime, $citrus-ink, 70%);
}
// 데모: 세 칸이 순서대로 살짝 들썩이는 물결을 만들고, 방울 두 개는 어긋나게 반짝인다
@keyframes colRipple {
0%, 40%, 100% { transform: translateY(0); }
20% { transform: translateY(-6px); }
}
@keyframes dropTwinkle {
0%, 100% { opacity: .35; transform: scale(.8) rotate(0deg); }
50% { opacity: 1; transform: scale(1.3) rotate(25deg); }
}
// 반응형: 320×200 처럼 세로가 짧은 화면에선 칸 속과 웨이브 번짐을 줄인다(BD9)
@media (max-width: 340px), (max-height: 220px) {
.footercitrus { padding: $sp-3 $sp-2 $sp-2; }
.footercitrus__wave { top: calc($sp-2 * -1); height: $sp-2; background-size: 22px 11px; }
.footercitrus__cols { gap: $sp-1; }
.footercitrus__col { padding: $sp-2; }
.footercitrus__link { display: none; }
.footercitrus__copy { margin-top: $sp-2; font-size: 9px; }
}
@media (prefers-reduced-motion: reduce) {
.footercitrus__col, .footercitrus__spark { animation: none !important; transform: none; }
.footercitrus__spark { opacity: .8; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #fffaf0;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.footercitrus {
position: relative;
box-sizing: border-box;
width: clamp(300px, 94vw, 460px);
padding: 16px 12px 12px;
border-radius: 16px;
background: rgb(95.8352941176%, 96.5411764706%, 93.0117647059%);
box-shadow: 0 -6px 20px rgba(196, 206, 156, 0.4);
}
.footercitrus__wave {
position: absolute;
top: -12px;
left: 0;
width: 100%;
height: 12px;
background-image: radial-gradient(circle at 50% 100%, rgb(95.8352941176%, 96.5411764706%, 93.0117647059%) 0 64%, transparent 66%);
background-size: 30px 15px;
background-repeat: repeat-x;
background-position: bottom center;
}
.footercitrus__cols {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(clamp(84px, 26vw, 120px), 1fr));
gap: 8px;
}
.footercitrus__col {
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px;
border-radius: 12px;
}
.footercitrus__col--org {
background: rgb(92.2352941176%, 87.6470588235%, 84.3529411765%);
animation: colRipple 2s ease-out infinite;
}
.footercitrus__col--grp {
background: rgb(96.4705882353%, 92.7647058824%, 93.4705882353%);
animation: colRipple 2s ease-out infinite;
animation-delay: -0.6666666667s;
}
.footercitrus__col--lem {
background: rgb(94.3529411765%, 93.1176470588%, 88.5294117647%);
animation: colRipple 2s ease-out infinite;
animation-delay: -1.3333333333s;
}
.footercitrus__head {
margin: 0;
color: #204a17;
font-weight: 700;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1;
font-family: inherit;
letter-spacing: 0.06em;
}
.footercitrus__link {
margin: 0;
color: #204a17;
font-weight: 500;
font-size: clamp(11px, 2.8vw, 13px);
line-height: 1.5;
font-family: inherit;
}
.footercitrus__copy {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin: 12px 0 0;
color: rgba(32, 74, 23, 0.78);
font-weight: 500;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1;
font-family: inherit;
}
.footercitrus__spark {
color: #bd967a;
animation: dropTwinkle 2s ease-out infinite;
}
.footercitrus__spark--b {
animation-delay: -1s;
color: rgb(57.568627451%, 65.2549019608%, 45.5294117647%);
}
@keyframes colRipple {
0%, 40%, 100% {
transform: translateY(0);
}
20% {
transform: translateY(-6px);
}
}
@keyframes dropTwinkle {
0%, 100% {
opacity: 0.35;
transform: scale(0.8) rotate(0deg);
}
50% {
opacity: 1;
transform: scale(1.3) rotate(25deg);
}
}
@media (max-width: 340px), (max-height: 220px) {
.footercitrus {
padding: 12px 8px 8px;
}
.footercitrus__wave {
top: -8px;
height: 8px;
background-size: 22px 11px;
}
.footercitrus__cols {
gap: 4px;
}
.footercitrus__col {
padding: 8px;
}
.footercitrus__link {
display: none;
}
.footercitrus__copy {
margin-top: 8px;
font-size: 9px;
}
}
@media (prefers-reduced-motion: reduce) {
.footercitrus__col, .footercitrus__spark {
animation: none !important;
transform: none;
}
.footercitrus__spark {
opacity: 0.8;
}
}import type { CSSProperties } from "react";
import styles from "./FooterCitrus.module.scss";
export type FooterCitrusProps = {
brand?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function FooterCitrus({
brand = "스퀴즈 · 상큼한 하루",
duration = 2000,
easing = "ease-out",
color = "#ff6a00",
}: FooterCitrusProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<footer className={styles.footercitrus} style={vars}>
<span className={styles.wave} aria-hidden="true" />
<div className={styles.cols}>
<div className={`${styles.col} ${styles.colOrg}`}>
<p className={styles.head}>제품</p>
<p className={styles.link}>기능</p>
</div>
<div className={`${styles.col} ${styles.colGrp}`}>
<p className={styles.head}>회사</p>
<p className={styles.link}>소개</p>
</div>
<div className={`${styles.col} ${styles.colLem}`}>
<p className={styles.head}>도움말</p>
<p className={styles.link}>가이드</p>
</div>
</div>
<p className={styles.copy}>
<span className={styles.spark} aria-hidden="true">✦</span>
{brand}
<span className={`${styles.spark} ${styles.sparkB}`} aria-hidden="true">✦</span>
</p>
</footer>
);
}08 · 웨이브 가장자리 푸터 — 측정 누적면적 2.668% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기
코드 읽는 법
안의 세 칸은 오렌지·자몽·레몬으로 나뉘어 순서대로 살짝 들썩이고, 방울 두 개가 어긋나게 반짝입니다.
.wave {
height: 12px;
background-image: radial-gradient(circle at 50% 100%, #eef7d3 0 64%, transparent 66%);
background-size: 30px 15px;
background-repeat: repeat-x;
}
09쥬시 버튼·인풋
어떤 움직임인가
버튼은 눌릴 때 몸이 살짝 눌리고 오렌지색 그림자도 함께 얕아져 손맛이 나고, 입력창은 포커스를 받으면 라임색 두꺼운 링이 부드럽게 퍼집니다.

<main class="stage">
<div class="ctrlcitrus">
<button class="ctrlcitrus__btn" type="button">저장하기</button>
<label class="ctrlcitrus__field">
<span class="ctrlcitrus__label">이메일</span>
<input class="ctrlcitrus__input" type="email" placeholder="이메일을 적어보세요">
</label>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 버튼이 눌렸다 돌아오는 주기
$easing: $ease-spring;
$color: $stage-ink;
$citrus-cream: #fffaf0;
$citrus-orange-deep: #bd967a;
$citrus-lime: #c4ce9c;
$citrus-lime-deep: #98af5c;
$citrus-ink: #204a17;
// 무대 — 라임 물색
@include stage(mix($citrus-lime, $stage-paper, 26%));
.ctrlcitrus {
box-sizing: border-box;
width: clamp(260px, 84vw, 340px);
display: flex;
flex-direction: column;
gap: $sp-4;
}
.ctrlcitrus__btn {
align-self: flex-start;
border: 0;
border-radius: $r-control;
background: $citrus-orange-deep;
color: $subject-ink;
padding: $sp-3 $sp-6;
font-weight: 700; font-size: clamp(12px, 3vw, 14px); line-height: 1;
font-family: inherit;
box-shadow: 0 8px 18px rgba($citrus-orange-deep, .5);
// 데모에서는 눌림을 보여주기 위해 자동 루프를 건다
animation: citrusPress $duration $easing infinite;
}
.ctrlcitrus__btn:focus-visible {
outline: 3px solid $citrus-ink;
outline-offset: 2px;
}
.ctrlcitrus__field {
display: flex;
flex-direction: column;
gap: $sp-1;
}
.ctrlcitrus__label {
color: rgba($citrus-ink, .78);
font-weight: 600; font-size: clamp(10px, 2.6vw, 12px); line-height: 1;
font-family: inherit;
}
.ctrlcitrus__input {
box-sizing: border-box;
width: 100%;
border: 0;
border-radius: $r-control;
background: $stage-paper;
color: $citrus-ink;
padding: $sp-3 $sp-4;
font-weight: 500; font-size: clamp(12px, 3vw, 14px); line-height: 1;
font-family: inherit;
box-shadow: 0 6px 14px rgba($citrus-lime-deep, .5);
// 데모: 포커스를 받은 것처럼 라임 링이 부드럽게 퍼졌다 줄어들기를 반복
animation: ringPulse $duration $easing infinite;
}
.ctrlcitrus__input::placeholder {
color: rgba($citrus-ink, .45);
}
// 데모: 버튼은 몸과 그림자가 함께 눌렸다 튕기고, 입력창은 라임 링이 숨쉰다
@keyframes citrusPress {
0%, 55%, 100% { transform: translateY(0) scaleY(1); box-shadow: 0 8px 18px rgba($citrus-orange-deep, .5); }
65% { transform: translateY(4px) scaleY(.95); box-shadow: 0 2px 6px rgba($citrus-orange-deep, .35); }
82% { transform: translateY(-3px); box-shadow: 0 12px 24px rgba($citrus-orange-deep, .55); }
}
@keyframes ringPulse {
0%, 100% { box-shadow: 0 6px 14px rgba($citrus-lime-deep, .5); }
50% { box-shadow: 0 6px 14px rgba($citrus-lime-deep, .5), 0 0 0 5px rgba($citrus-lime-deep, .4); }
}
@media (prefers-reduced-motion: reduce) {
.ctrlcitrus__btn, .ctrlcitrus__input { animation: none !important; transform: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: rgb(93.9843137255%, 95.0039215686%, 89.9058823529%);
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.ctrlcitrus {
box-sizing: border-box;
width: clamp(260px, 84vw, 340px);
display: flex;
flex-direction: column;
gap: 16px;
}
.ctrlcitrus__btn {
align-self: flex-start;
border: 0;
border-radius: 12px;
background: #bd967a;
color: #17141a;
padding: 12px 24px;
font-weight: 700;
font-size: clamp(12px, 3vw, 14px);
line-height: 1;
font-family: inherit;
box-shadow: 0 8px 18px rgba(189, 150, 122, 0.5);
animation: citrusPress 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.ctrlcitrus__btn:focus-visible {
outline: 3px solid #204a17;
outline-offset: 2px;
}
.ctrlcitrus__field {
display: flex;
flex-direction: column;
gap: 4px;
}
.ctrlcitrus__label {
color: rgba(32, 74, 23, 0.78);
font-weight: 600;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1;
font-family: inherit;
}
.ctrlcitrus__input {
box-sizing: border-box;
width: 100%;
border: 0;
border-radius: 12px;
background: #ffffff;
color: #204a17;
padding: 12px 16px;
font-weight: 500;
font-size: clamp(12px, 3vw, 14px);
line-height: 1;
font-family: inherit;
box-shadow: 0 6px 14px rgba(152, 175, 92, 0.5);
animation: ringPulse 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.ctrlcitrus__input::placeholder {
color: rgba(32, 74, 23, 0.45);
}
@keyframes citrusPress {
0%, 55%, 100% {
transform: translateY(0) scaleY(1);
box-shadow: 0 8px 18px rgba(189, 150, 122, 0.5);
}
65% {
transform: translateY(4px) scaleY(0.95);
box-shadow: 0 2px 6px rgba(189, 150, 122, 0.35);
}
82% {
transform: translateY(-3px);
box-shadow: 0 12px 24px rgba(189, 150, 122, 0.55);
}
}
@keyframes ringPulse {
0%, 100% {
box-shadow: 0 6px 14px rgba(152, 175, 92, 0.5);
}
50% {
box-shadow: 0 6px 14px rgba(152, 175, 92, 0.5), 0 0 0 5px rgba(152, 175, 92, 0.4);
}
}
@media (prefers-reduced-motion: reduce) {
.ctrlcitrus__btn, .ctrlcitrus__input {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./ButtonsInputsCitrus.module.scss";
export type ButtonsInputsCitrusProps = {
buttonLabel?: string;
fieldLabel?: string;
placeholder?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function ButtonsInputsCitrus({
buttonLabel = "저장하기",
fieldLabel = "이메일",
placeholder = "이메일을 적어보세요",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ff6a00",
}: ButtonsInputsCitrusProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.ctrlcitrus} style={vars}>
<button type="button" className={styles.btn}>{buttonLabel}</button>
<label className={styles.field}>
<span className={styles.label}>{fieldLabel}</span>
<input className={styles.input} type="email" placeholder={placeholder} />
</label>
</div>
);
}09 · 쥬시 버튼·인풋 — 측정 누적면적 4.938% · 움직인 프레임 15/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기
어디를 바꾸면 되나
시트러스 키트의 손맛을 하나로 묶은 항목입니다.
.btn { animation: citrusPress 2s cubic-bezier(.2,.8,.2,1) infinite; }
@keyframes citrusPress {
0%, 55%, 100% { transform: translateY(0) scaleY(1); }
65% { transform: translateY(4px) scaleY(.95); }
82% { transform: translateY(-3px); }
}
tailwind.config 커스텀
카드 표면용 네 색과 그림자·그라디언트 전용 진한 색을 나눠 이 한 곳에 적어 두면 원페이지와 부품 아홉 개가 전부 같은 대비 규칙을 따라갑니다. -deep 접미사가 붙은 색은 글자를 얹지 않는다는 약속을 이름에 담았습니다.
theme: {
extend: {
colors: {
cream: "#fffaf0", orange: "#ff9a52", "orange-deep": "#ff6a00",
lime: "#c8e84a", grapefruit: "#ff8fa3", lemon: "#ffe066",
ink: "#204a17",
},
borderRadius: { citrus: "24px", control: "12px", pill: "999px" },
boxShadow: {
"citrus-orange": "0 10px 24px rgba(255,106,0,.4)",
"citrus-lime": "0 10px 24px rgba(200,232,74,.8)",
},
},
},
바이브코딩 프롬프트
아래 지시만 AI 코딩 도구에 붙여 넣으면 이 config 를 바로 적용해 화면을 다시 짭니다. 채도 높은 색일수록 대비가 빠듯해진다는 규칙을 프롬프트에 명시해 두면, 도구가 카드 배경에 진한 톤을 골라 글자를 못 읽게 만드는 실수를 줄일 수 있습니다. 여기 옮긴 것은 발췌본이고, 번호 붙은 규칙 열한 개짜리 원본 지시문은 zip 안 프롬프트 파일에서 통째로 읽을 수 있습니다.
배경을 cream(#fffaf0) 하나로 통일한다. 카드마다 orange·lime·grapefruit·lemon 중
하나를 준다(진한 -deep 톤은 그림자·그라디언트 전용, 글자를 얹지 않는다). 그림자는
회색이 아니라 카드와 같은 계열 색 그림자를 쓴다. 글자는 항상 ink(#204a17)다.
위 지시를 실제 page/index.html 에 적용한 뒤 1280px 뷰포트로 전체 페이지를 캡처한 결과가 아래 그림입니다.

어디서 깨지는가 — 함정
첫 번째로 실제로 겪은 것은 320px 렌더에서 히어로 카드가 화면을 넘긴 것이었습니다. 물방울 세 개를 카드 모서리 밖으로 튀어나오게 배치했더니, 480×300 무대에서는 괜찮다가 320×200으로 좁히자 document.documentElement.scrollHeight가 213px로 잡혀 innerHeight 200px를 넘겼습니다. 카드 패딩과 제목 글자 크기, 물방울의 음수 오프셋을 phone 미디어쿼리에서 함께 줄이고서야 213px가 정확히 200px로 떨어졌습니다. type F(디자인 트렌드) 편은 check.py의 BD9 게이트가 항목별 모음 글에만 적용돼 자동으로 잡히지 않아, Playwright로 문서·body 양쪽의 scrollWidth·scrollHeight를 직접 재서 확인해야 했습니다.
두 번째는 07번 CTA 밴드의 글자 대비였습니다. 흰 글자를 오렌지→레몬→라임 그라디언트 위에 바로 올렸더니, 그라디언트가 레몬 구간(#ffd400)을 지날 때 흰 글자 대비가 1.43:1까지 떨어졌습니다 — 라임 구간에서도 1.81:1로 WCAG 2.1 최소 대비 기준 4.5:1에 한참 못 미쳤습니다. 글자 뒤에 잉크색 65% 반투명 스크림을 깔아 계산해 보니 세 색 전 구간에서 최소 4.7:1로 올라왔고, 그 값으로 실제 데모를 고쳤습니다. 이 과일 색이 메일 본문에서 어떤 얼굴을 하는지는 이메일 템플릿 디자인에서 따로 쟀습니다. 이 두 가지를 다 고친 완성본을, 이 비밀번호 52ppc9vv이 걸린 zip을 받아 그대로 열어 확인할 수 있습니다.
아홉 개 데모 전부 MDN prefers-reduced-motion 문서가 정의한 미디어 쿼리를 감지하면 동작을 멈추고 각 요소를 애니메이션이 시작되기 전의 정지 상태로 되돌려 고정합니다.
접근성
시트러스 스타일이 파스텔보다 먼저 부딪히는 문제는 채도입니다. 카드 표면 색을 그대로 진하게 두면 잉크 글자 대비가 3.56:1(오렌지 진한 톤)이나 2.98:1(자몽 진한 톤)까지 떨어져 본문 기준 4.5:1을 통과하지 못합니다. 이 편은 그래서 카드 표면용 색 네 개를 잉크 대비 4.73~7.84:1 범위로 따로 밝혀 두고, 진한 원색은 그림자·그라디언트·작은 아이콘에만 씁니다. 07번 CTA 밴드처럼 흰 글자를 그라디언트 위에 얹어야 하는 경우는 함정 섹션에서 설명한 65% 잉크 스크림으로 보정했습니다. 색 그림자는 MDN box-shadow 문서가 안내하는 문법 그대로 쓰되 투명도를 0.4~0.85로 낮췄고, 키보드 초점은 색 그림자에 의존하지 않고 :focus-visible 아웃라인을 잉크색으로 별도 그렸습니다.
시트러스 프레시 말고 다른 홈페이지 유행이 궁금하면 디자인 트렌드 카테고리를 훑어보시고, 이 블로그가 통째로 뭘 다루는지는 소개 페이지에 정리해 뒀습니다.
FAQ
시트러스 프레시와 파스텔 캔디는 뭐가 다른가요?
파스텔 캔디는 라벤더·민트처럼 채도를 낮춘 부드러운 톤을 쓰고 그림자도 연하게 깔아 말랑한 느낌을 냅니다. 시트러스 프레시는 오렌지·라임·자몽처럼 채도를 그대로 살린 톤을 쓰되 카드 표면과 그림자용 색의 밝기를 나눠 대비를 지킵니다. 같은 '색 그림자' 원칙을 쓰지만 파스텔은 부드럽게, 시트러스는 쨍하게 가는 것이 차이입니다. 색을 아예 종이로 옮겨 층을 쌓는 쪽은 종이 질감 랜딩 키트가 그 옆 자리입니다.
최신 웹디자인 트렌드인데 카드 배경이 진하면 왜 안 되나요?
카드 배경이 진해질수록(채도·명도가 낮아질수록) 그 위에 얹는 잉크 글자와의 밝기 차이가 줄어들어 대비 비율이 떨어집니다. 이 키트에서 실측한 오렌지 진한 톤(#ff6a00)은 잉크와 대비가 3.56:1로 본문 기준 4.5:1에 못 미칩니다. 카드 표면은 항상 그 색 계열 안에서 밝은 쪽을, 진한 쪽은 그림자나 그라디언트처럼 글자가 없는 곳에만 씁니다.
그라디언트 위에 글자를 놓아야 하면 어떻게 하나요?
그라디언트는 위치에 따라 색이 계속 바뀌므로 한 지점만 보고 대비를 맞추면 다른 구간에서 실패합니다. 이 편의 CTA 밴드처럼 글자 뒤에 반투명한 어두운 스크림(이 편은 잉크 65%)을 깔면 그라디언트의 모든 구간에서 최소 대비를 보장할 수 있습니다. 스크림 없이 그라디언트 위에 흰 글자를 직접 얹는 방식은 밝은 색 구간을 지날 때마다 거의 확실히 실패합니다.









