SaaS Landing Page Design: 9 Clean Trust Sections
Good SaaS landing page design leans on two neutral backgrounds — white and a barely-there off-white — plus exactly one brand blue, gray shadows, and corners
Unlike the pastel or candy-gradient look, the brightness here comes from empty space and restraint, not from more colors.
Below is a full one-page landing built from that recipe, the kind of screen people save from Stripe or Linear without knowing what to call it. Scroll from the nav to the footer and count how many colors you actually see.

The nine pieces that make up that page are pulled apart and laid out in the grid below, in the order a visitor's eye actually moves — get noticed, browse features, check pricing, feel trust, act. Blue is the only accent color in the whole kit; everything else is white, off-white, or gray.
01 hero-trust
02 nav-trust
03 card-grid-trust
04 feature-list-trust
05 pricing-trust
06 testimonial-trust
07 cta-band-trust
08 footer-trust
09 buttons-inputs-trust
Auto-plays · click a tile to jump to its section · all nine in one zip
- 01 Soft-grid hero
- 02 Underline nav
- 03 Icon card grid
- 04 Checkmark feature list
- 05 Simple pricing
- 06 Logo-trust testimonial
- 07 Solid CTA band
- 08 Minimal 4-column footer
- 09 Clean buttons & inputs
The whole kit runs on eight colors. Never using the brand blue for small body text is the one rule most saas landing page design best practices agree on, and it is what keeps this palette readable. Raising the color count toward fruit-bright gives Citrus Web Design Kit in the same design pillar, and cutting the surfaces out of paper gives Paper Cutout Design.
| Role | Color | Used for |
|---|---|---|
| Off-white | #f7f9fc |
Page background |
| Blue tint | #eaf1ff |
Card-grid stage, icon backgrounds |
| Gray tint | #f1f5f9 |
Nav and logo-strip stage |
| Mint tint | #eaf6f1 |
Checkmark icons, input stage |
| Brand blue | #2f6df6 |
Buttons, icons, borders, accents |
| Blue deep | #1d4ed8 |
CTA band background, pressed buttons |
| Slate | #475569 |
Secondary text |
| Ink | #17141a |
Headings and body text |
01Soft-grid hero
What you see in this b2b saas landing page design
A faint dot grid sits over an off-white background while the white card in front drifts up and down at an easy pace.

<main class="stage">
<section class="herotrust">
<span class="herotrust__badge" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M216 56v56c0 96-88 120-88 120s-88-24-88-120V56a8 8 0 0 1 8-8h160a8 8 0 0 1 8 8" opacity=".2"/><path d="M208 40H48a16 16 0 0 0-16 16v56c0 52.72 25.52 84.67 46.93 102.19c23.06 18.86 46 25.26 47 25.53a8 8 0 0 0 4.2 0c1-.27 23.91-6.67 47-25.53C198.48 196.67 224 164.72 224 112V56a16 16 0 0 0-16-16m0 72c0 37.07-13.66 67.16-40.6 89.42a129.3 129.3 0 0 1-39.4 22.2a128.3 128.3 0 0 1-38.92-21.81C61.82 179.51 48 149.3 48 112V56h160ZM82.34 141.66a8 8 0 0 1 11.32-11.32L112 148.69l50.34-50.35a8 8 0 0 1 11.32 11.32l-56 56a8 8 0 0 1-11.32 0Z"/></g></svg>
</span>
<div class="herotrust__card">
<p class="herotrust__eyebrow">Trusted by teams</p>
<h1 class="herotrust__title">A faster, safer start</h1>
<p class="herotrust__desc">A SaaS landing built on trust</p>
<button class="herotrust__cta" type="button">Start for free</button>
</div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 카드가 한 번 뜨고 가라앉는 주기
$easing: $ease-out; // 통통 튀지 않는 절제된 곡선
$color: $subject-blue; // 유일한 브랜드 컬러
// 신뢰감 팔레트 — 흰색·오프화이트 바탕에 파란색 하나만 강조. 카드류는 잉크 대비 4.5:1 이상(접근성 섹션 참고), 그림자는 회색 계열로 얕게
$trust-offwhite: #f7f9fc;
$trust-ink: $subject-ink;
$trust-slate: #475569;
$trust-blue: $subject-blue;
@include stage($trust-offwhite);
// 점 격자 배경 — 정적 장식. 애니메이션 대상이 아니다
.stage {
background-image: radial-gradient(circle, rgba($trust-ink, .08) 1px, transparent 1px);
background-size: 20px 20px;
}
.herotrust {
position: relative;
width: clamp(220px, 76vw, 340px);
}
.herotrust__badge {
position: absolute;
top: -16px;
right: 12px;
width: clamp(28px, 8vw, 36px);
aspect-ratio: 1;
display: grid;
place-items: center;
border-radius: 50%;
background: $trust-blue;
color: $stage-paper;
box-shadow: 0 8px 16px rgba($trust-blue, .35);
font-size: clamp(14px, 4vw, 18px);
animation: badgeFloat $duration $easing infinite;
}
.herotrust__card {
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 14px 28px rgba($trust-ink, .1);
animation: cardBob $duration $easing infinite;
}
.herotrust__eyebrow {
margin: 0;
color: $trust-blue;
font-weight: 700; font-size: clamp(10px, 2.6vw, 12px); line-height: 1;
font-family: inherit;
letter-spacing: .08em;
text-transform: uppercase;
}
.herotrust__title {
margin: 0;
color: $trust-ink;
font-weight: 700; font-size: clamp(18px, 4.6vw, 24px); line-height: 1.25;
font-family: inherit;
}
.herotrust__desc {
margin: 0;
color: $trust-slate;
font-weight: 500; font-size: clamp(12px, 2.8vw, 14px); line-height: 1.4;
font-family: inherit;
}
.herotrust__cta {
margin-top: $sp-1;
border: 0;
border-radius: $r-pill;
background: $trust-blue;
color: $stage-paper;
padding: $sp-2 $sp-5;
font-weight: 700; font-size: clamp(12px, 2.8vw, 14px); line-height: 1;
font-family: inherit;
box-shadow: 0 8px 18px rgba($trust-blue, .3);
}
.herotrust__cta:focus-visible {
outline: 3px solid $trust-blue;
outline-offset: 2px;
}
// 데모: 카드가 완만하게 떠올랐다 가라앉고(큰 피사체라 누적면적을 확보), 배지는 다른 위상으로 살짝 흔들린다
@keyframes cardBob {
0%, 100% { transform: translateY(0); box-shadow: 0 14px 28px rgba($trust-ink, .1); }
50% { transform: translateY(-9px); box-shadow: 0 20px 32px rgba($trust-ink, .07); }
}
@keyframes badgeFloat {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(6px) scale(1.06); }
}
@media (max-width: 340px), (max-height: 220px) {
.herotrust__card { padding: $sp-3 $sp-4; gap: $sp-1; }
.herotrust__eyebrow { display: none; }
.herotrust__title { font-size: 15px; line-height: 1.15; }
.herotrust__desc { display: none; }
.herotrust__cta { padding: $sp-1 $sp-4; margin-top: 0; }
.herotrust__badge { width: 22px; top: -8px; right: 6px; }
}
@media (prefers-reduced-motion: reduce) {
.herotrust__badge, .herotrust__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: #f7f9fc;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.stage {
background-image: radial-gradient(circle, rgba(23, 20, 26, 0.08) 1px, transparent 1px);
background-size: 20px 20px;
}
.herotrust {
position: relative;
width: clamp(220px, 76vw, 340px);
}
.herotrust__badge {
position: absolute;
top: -16px;
right: 12px;
width: clamp(28px, 8vw, 36px);
aspect-ratio: 1;
display: grid;
place-items: center;
border-radius: 50%;
background: #2f6df6;
color: #ffffff;
box-shadow: 0 8px 16px rgba(47, 109, 246, 0.35);
font-size: clamp(14px, 4vw, 18px);
animation: badgeFloat 2s ease-out infinite;
}
.herotrust__card {
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 14px 28px rgba(23, 20, 26, 0.1);
animation: cardBob 2s ease-out infinite;
}
.herotrust__eyebrow {
margin: 0;
color: #2f6df6;
font-weight: 700;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1;
font-family: inherit;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.herotrust__title {
margin: 0;
color: #17141a;
font-weight: 700;
font-size: clamp(18px, 4.6vw, 24px);
line-height: 1.25;
font-family: inherit;
}
.herotrust__desc {
margin: 0;
color: #475569;
font-weight: 500;
font-size: clamp(12px, 2.8vw, 14px);
line-height: 1.4;
font-family: inherit;
}
.herotrust__cta {
margin-top: 4px;
border: 0;
border-radius: 999px;
background: #2f6df6;
color: #ffffff;
padding: 8px 20px;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 14px);
line-height: 1;
font-family: inherit;
box-shadow: 0 8px 18px rgba(47, 109, 246, 0.3);
}
.herotrust__cta:focus-visible {
outline: 3px solid #2f6df6;
outline-offset: 2px;
}
@keyframes cardBob {
0%, 100% {
transform: translateY(0);
box-shadow: 0 14px 28px rgba(23, 20, 26, 0.1);
}
50% {
transform: translateY(-9px);
box-shadow: 0 20px 32px rgba(23, 20, 26, 0.07);
}
}
@keyframes badgeFloat {
0%, 100% {
transform: translateY(0) scale(1);
}
50% {
transform: translateY(6px) scale(1.06);
}
}
@media (max-width: 340px), (max-height: 220px) {
.herotrust__card {
padding: 12px 16px;
gap: 4px;
}
.herotrust__eyebrow {
display: none;
}
.herotrust__title {
font-size: 15px;
line-height: 1.15;
}
.herotrust__desc {
display: none;
}
.herotrust__cta {
padding: 4px 16px;
margin-top: 0;
}
.herotrust__badge {
width: 22px;
top: -8px;
right: 6px;
}
}
@media (prefers-reduced-motion: reduce) {
.herotrust__badge, .herotrust__card {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./HeroTrust.module.scss";
export type HeroTrustProps = {
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 HeroTrust({
eyebrow = "Trusted by teams",
title = "더 빠르고 안전한 시작",
desc = "신뢰가 필요한 팀을 위한 SaaS 랜딩",
cta = "무료로 시작",
duration = 2000,
easing = "ease-out",
color = "#2f6df6",
}: HeroTrustProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.herotrust} style={vars}>
<span className={styles.badge} aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M216 56v56c0 96-88 120-88 120s-88-24-88-120V56a8 8 0 0 1 8-8h160a8 8 0 0 1 8 8" opacity=".2"/><path d="M208 40H48a16 16 0 0 0-16 16v56c0 52.72 25.52 84.67 46.93 102.19c23.06 18.86 46 25.26 47 25.53a8 8 0 0 0 4.2 0c1-.27 23.91-6.67 47-25.53C198.48 196.67 224 164.72 224 112V56a16 16 0 0 0-16-16m0 72c0 37.07-13.66 67.16-40.6 89.42a129.3 129.3 0 0 1-39.4 22.2a128.3 128.3 0 0 1-38.92-21.81C61.82 179.51 48 149.3 48 112V56h160ZM82.34 141.66a8 8 0 0 1 11.32-11.32L112 148.69l50.34-50.35a8 8 0 0 1 11.32 11.32l-56 56a8 8 0 0 1-11.32 0Z"/></g></svg>
</span>
<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 · Soft-grid hero — Measured area 11.469% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password
Where to look in the code
The only accent is a small blue badge — every shadow underneath stays a soft, neutral gray.
<section class="dotgrid relative overflow-hidden rounded-card py-20">
<div class="relative mx-auto flex max-w-xl flex-col items-start gap-4
rounded-card bg-white p-10 shadow-soft">
<p class="text-xs font-bold uppercase tracking-widest text-brand">Trusted by teams</p>
<h1 class="text-4xl font-bold leading-tight">Ship faster, safer</h1>
<button class="rounded-pill bg-brand px-6 py-3 text-sm font-bold text-white shadow-brand">Start free</button>
</div>
</section>
02Underline nav
What happens on screen
A single blue underline glides to the active tab as it moves between menu items.

<main class="stage">
<nav class="navtrust">
<span class="navtrust__highlight" aria-hidden="true"></span>
<span class="navtrust__tab">Home</span>
<span class="navtrust__tab">Features</span>
<span class="navtrust__tab">Pricing</span>
<span class="navtrust__underline" aria-hidden="true"></span>
</nav>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: 3s; // 세 칸을 도는 한 바퀴 — 2s 로프($dur-loop)보다 정거장이 많아 별도 토큰(합치지 않는다, blog-design 규칙 2)
$easing: $ease-out;
$color: $subject-blue;
$trust-graytint: #f1f5f9;
$trust-ink: $subject-ink;
$trust-blue: $subject-blue;
@include stage($trust-graytint);
// 탭 3개 — 폭을 고정(flex:0 0 auto)해 인디케이터 translateX 이동량과 어긋나지 않는다 (BD19). 320px 안에 들어가도록 전체 폭을 284px 로 미리 잡는다
$tab-w: 92px;
$tab-h: 48px;
$underline-w: 28px;
$underline-offset: 32px; // ($tab-w - $underline-w) / 2, 나눗셈 없이 상수로
.navtrust {
position: relative;
box-sizing: border-box;
display: flex;
align-items: center;
width: $tab-w * 3 + $sp-2;
height: $tab-h;
padding: 0 $sp-1;
border-radius: $r-pill;
background: $stage-paper;
box-shadow: 0 10px 22px rgba($trust-ink, .08);
}
.navtrust__highlight {
position: absolute;
left: $sp-1;
top: 2px;
width: $tab-w - 4px;
height: $tab-h - 4px;
border-radius: $r-control;
background: rgba($trust-blue, .14);
animation: slideTab $duration $easing infinite;
}
.navtrust__tab {
position: relative;
z-index: 1;
flex: 0 0 $tab-w;
text-align: center;
color: $trust-ink;
font-weight: 600; font-size: clamp(11px, 3vw, 13px); line-height: 1;
font-family: inherit;
}
.navtrust__underline {
position: absolute;
bottom: -$sp-2;
left: 0;
width: $underline-w;
height: 3px;
border-radius: $r-xs;
background: $trust-blue;
transform: translateX($underline-offset);
animation: slideUnderline $duration $easing infinite;
}
// 데모: 하이라이트 블록(넓은 면적)과 밑줄(얇은 신호선)이 같은 박자로 세 칸을 순서대로 돈다
@keyframes slideTab {
0%, 22% { transform: translateX(0); }
33%, 55% { transform: translateX($tab-w); }
66%, 88% { transform: translateX($tab-w * 2); }
100% { transform: translateX(0); }
}
@keyframes slideUnderline {
0%, 22% { transform: translateX($underline-offset); }
33%, 55% { transform: translateX($tab-w + $underline-offset); }
66%, 88% { transform: translateX($tab-w * 2 + $underline-offset); }
100% { transform: translateX($underline-offset); }
}
@media (prefers-reduced-motion: reduce) {
.navtrust__highlight, .navtrust__underline { animation: none !important; }
.navtrust__highlight { transform: translateX(0); }
.navtrust__underline { transform: translateX($underline-offset); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #f1f5f9;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.navtrust {
position: relative;
box-sizing: border-box;
display: flex;
align-items: center;
width: 284px;
height: 48px;
padding: 0 4px;
border-radius: 999px;
background: #ffffff;
box-shadow: 0 10px 22px rgba(23, 20, 26, 0.08);
}
.navtrust__highlight {
position: absolute;
left: 4px;
top: 2px;
width: 88px;
height: 44px;
border-radius: 12px;
background: rgba(47, 109, 246, 0.14);
animation: slideTab 3s ease-out infinite;
}
.navtrust__tab {
position: relative;
z-index: 1;
flex: 0 0 92px;
text-align: center;
color: #17141a;
font-weight: 600;
font-size: clamp(11px, 3vw, 13px);
line-height: 1;
font-family: inherit;
}
.navtrust__underline {
position: absolute;
bottom: -8px;
left: 0;
width: 28px;
height: 3px;
border-radius: 4px;
background: #2f6df6;
transform: translateX(32px);
animation: slideUnderline 3s ease-out infinite;
}
@keyframes slideTab {
0%, 22% {
transform: translateX(0);
}
33%, 55% {
transform: translateX(92px);
}
66%, 88% {
transform: translateX(184px);
}
100% {
transform: translateX(0);
}
}
@keyframes slideUnderline {
0%, 22% {
transform: translateX(32px);
}
33%, 55% {
transform: translateX(124px);
}
66%, 88% {
transform: translateX(216px);
}
100% {
transform: translateX(32px);
}
}
@media (prefers-reduced-motion: reduce) {
.navtrust__highlight, .navtrust__underline {
animation: none !important;
}
.navtrust__highlight {
transform: translateX(0);
}
.navtrust__underline {
transform: translateX(32px);
}
}import type { CSSProperties } from "react";
import styles from "./NavTrust.module.scss";
export type NavTrustProps = {
tabs?: string[];
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function NavTrust({
tabs = ["홈", "기능", "요금제"],
duration = 3000,
easing = "ease-out",
color = "#2f6df6",
}: NavTrustProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<nav className={styles.navtrust} style={vars}>
<span className={styles.highlight} aria-hidden="true" />
{tabs.map((t) => (
<span key={t} className={styles.tab}>{t}</span>
))}
<span className={styles.underline} aria-hidden="true" />
</nav>
);
}02 · Underline nav — Measured area 8.271% · moving frames 9/23Original by GODRICH · SCSS and React ship in the zip · See the password
How to read the code
In the actual demo a pale blue highlight block travels on the same beat as the line — a 3px underline alone barely reads as motion.
<nav class="mx-auto flex max-w-5xl items-center gap-6 rounded-pill bg-white px-5 py-3 shadow-soft-sm">
<span class="relative whitespace-nowrap text-xs font-semibold pb-1
after:absolute after:-bottom-[13px] after:left-0
after:h-[3px] after:w-full after:rounded-control after:bg-brand">Home</span>
<span class="whitespace-nowrap text-xs font-semibold text-slate">Features</span>
</nav>
03Icon card grid
What moves
Four white cards sit in a grid with only a faint gray shadow, and the blue circle holding each icon grows slightly and deepens in tone, one card after another on a staggered delay.

<main class="stage">
<div class="cardgrid">
<div class="cardgrid__card cardgrid__card--a">
<span class="cardgrid__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M216 56v56c0 96-88 120-88 120s-88-24-88-120V56a8 8 0 0 1 8-8h160a8 8 0 0 1 8 8" opacity=".2"/><path d="M208 40H48a16 16 0 0 0-16 16v56c0 52.72 25.52 84.67 46.93 102.19c23.06 18.86 46 25.26 47 25.53a8 8 0 0 0 4.2 0c1-.27 23.91-6.67 47-25.53C198.48 196.67 224 164.72 224 112V56a16 16 0 0 0-16-16m0 72c0 37.07-13.66 67.16-40.6 89.42a129.3 129.3 0 0 1-39.4 22.2a128.3 128.3 0 0 1-38.92-21.81C61.82 179.51 48 149.3 48 112V56h160ZM82.34 141.66a8 8 0 0 1 11.32-11.32L112 148.69l50.34-50.35a8 8 0 0 1 11.32 11.32l-56 56a8 8 0 0 1-11.32 0Z"/></g></svg></span>
<p class="cardgrid__label">Security</p>
</div>
<div class="cardgrid__card cardgrid__card--b">
<span class="cardgrid__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="m96 240l16-80l-64-24L160 16l-16 80l64 24Z" opacity=".2"/><path d="M215.79 118.17a8 8 0 0 0-5-5.66L153.18 90.9l14.66-73.33a8 8 0 0 0-13.69-7l-112 120a8 8 0 0 0 3 13l57.63 21.61l-14.62 73.25a8 8 0 0 0 13.69 7l112-120a8 8 0 0 0 1.94-7.26M109.37 214l10.47-52.38a8 8 0 0 0-5-9.06L62 132.71l84.62-90.66l-10.46 52.38a8 8 0 0 0 5 9.06l52.8 19.8Z"/></g></svg></span>
<p class="cardgrid__label">Speed</p>
</div>
<div class="cardgrid__card cardgrid__card--c">
<span class="cardgrid__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M224 64v144H32V48h176a16 16 0 0 1 16 16" opacity=".2"/><path d="M232 208a8 8 0 0 1-8 8H32a8 8 0 0 1-8-8V48a8 8 0 0 1 16 0v108.69l50.34-50.35a8 8 0 0 1 11.32 0L128 132.69L180.69 80H160a8 8 0 0 1 0-16h40a8 8 0 0 1 8 8v40a8 8 0 0 1-16 0V91.31l-58.34 58.35a8 8 0 0 1-11.32 0L96 123.31l-56 56V200h184a8 8 0 0 1 8 8"/></g></svg></span>
<p class="cardgrid__label">Growth</p>
</div>
<div class="cardgrid__card cardgrid__card--d">
<span class="cardgrid__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M208 88H48a8 8 0 0 0-8 8v112a8 8 0 0 0 8 8h160a8 8 0 0 0 8-8V96a8 8 0 0 0-8-8m-80 72a20 20 0 1 1 20-20a20 20 0 0 1-20 20" opacity=".2"/><path d="M208 80h-32V56a48 48 0 0 0-96 0v24H48a16 16 0 0 0-16 16v112a16 16 0 0 0 16 16h160a16 16 0 0 0 16-16V96a16 16 0 0 0-16-16M96 56a32 32 0 0 1 64 0v24H96Zm112 152H48V96h160zm-80-96a28 28 0 0 0-8 54.83V184a8 8 0 0 0 16 0v-17.17a28 28 0 0 0-8-54.83m0 40a12 12 0 1 1 12-12a12 12 0 0 1-12 12"/></g></svg></span>
<p class="cardgrid__label">Security certified</p>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop;
$easing: $ease-out;
$color: $subject-blue;
$trust-bluetint: #eaf1ff;
$trust-ink: $subject-ink;
$trust-blue: $subject-blue;
@include stage($trust-bluetint);
.cardgrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: $sp-2;
width: 200px;
}
.cardgrid__card {
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: $sp-1;
padding: $sp-2;
border-radius: $r-card;
background: $stage-paper;
box-shadow: 0 8px 16px rgba($trust-ink, .08);
animation: cardLift $duration $easing infinite;
}
.cardgrid__card--a { animation-delay: 0s; }
.cardgrid__card--b { animation-delay: $duration * -0.75; }
.cardgrid__card--c { animation-delay: $duration * -0.5; }
.cardgrid__card--d { animation-delay: $duration * -0.25; }
.cardgrid__icon {
display: grid;
place-items: center;
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba($trust-blue, .14);
color: $trust-blue;
font-size: 18px;
animation: iconScale $duration $easing infinite;
}
.cardgrid__card--a .cardgrid__icon { animation-delay: 0s; }
.cardgrid__card--b .cardgrid__icon { animation-delay: $duration * -0.75; }
.cardgrid__card--c .cardgrid__icon { animation-delay: $duration * -0.5; }
.cardgrid__card--d .cardgrid__icon { animation-delay: $duration * -0.25; }
.cardgrid__label {
margin: 0;
color: $trust-ink;
font-weight: 600; font-size: 12px; line-height: 1.2;
font-family: inherit;
}
// 데모: 카드 네 장이 시작 시각을 어긋내 순서대로 살짝 뜨고(호버 시 들리는 느낌), 아이콘 원은 배경이 진해지며 함께 커진다
@keyframes cardLift {
0%, 60%, 100% { transform: translateY(0); box-shadow: 0 8px 16px rgba(23, 20, 26, .08); }
20% { transform: translateY(-5px); box-shadow: 0 14px 22px rgba(23, 20, 26, .12); }
}
@keyframes iconScale {
0%, 60%, 100% { transform: scale(1); background: rgba(47, 109, 246, .14); }
20% { transform: scale(1.12); background: rgba(47, 109, 246, .22); }
}
@media (max-width: 340px), (max-height: 220px) {
.cardgrid { width: 176px; gap: $sp-1; }
.cardgrid__card { padding: $sp-1; }
.cardgrid__icon { width: 28px; height: 28px; font-size: 14px; }
.cardgrid__label { font-size: 10px; }
}
@media (prefers-reduced-motion: reduce) {
.cardgrid__card, .cardgrid__icon { 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: #eaf1ff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.cardgrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
width: 200px;
}
.cardgrid__card {
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
padding: 8px;
border-radius: 16px;
background: #ffffff;
box-shadow: 0 8px 16px rgba(23, 20, 26, 0.08);
animation: cardLift 2s ease-out infinite;
}
.cardgrid__card--a {
animation-delay: 0s;
}
.cardgrid__card--b {
animation-delay: -1.5s;
}
.cardgrid__card--c {
animation-delay: -1s;
}
.cardgrid__card--d {
animation-delay: -0.5s;
}
.cardgrid__icon {
display: grid;
place-items: center;
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(47, 109, 246, 0.14);
color: #2f6df6;
font-size: 18px;
animation: iconScale 2s ease-out infinite;
}
.cardgrid__card--a .cardgrid__icon {
animation-delay: 0s;
}
.cardgrid__card--b .cardgrid__icon {
animation-delay: -1.5s;
}
.cardgrid__card--c .cardgrid__icon {
animation-delay: -1s;
}
.cardgrid__card--d .cardgrid__icon {
animation-delay: -0.5s;
}
.cardgrid__label {
margin: 0;
color: #17141a;
font-weight: 600;
font-size: 12px;
line-height: 1.2;
font-family: inherit;
}
@keyframes cardLift {
0%, 60%, 100% {
transform: translateY(0);
box-shadow: 0 8px 16px rgba(23, 20, 26, 0.08);
}
20% {
transform: translateY(-5px);
box-shadow: 0 14px 22px rgba(23, 20, 26, 0.12);
}
}
@keyframes iconScale {
0%, 60%, 100% {
transform: scale(1);
background: rgba(47, 109, 246, 0.14);
}
20% {
transform: scale(1.12);
background: rgba(47, 109, 246, 0.22);
}
}
@media (max-width: 340px), (max-height: 220px) {
.cardgrid {
width: 176px;
gap: 4px;
}
.cardgrid__card {
padding: 4px;
}
.cardgrid__icon {
width: 28px;
height: 28px;
font-size: 14px;
}
.cardgrid__label {
font-size: 10px;
}
}
@media (prefers-reduced-motion: reduce) {
.cardgrid__card, .cardgrid__icon {
animation: none !important;
transform: none;
}
}import type { CSSProperties, ReactNode } from "react";
import styles from "./CardGridTrust.module.scss";
type Item = { label: string; icon: ReactNode };
export type CardGridTrustProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const ICONS: Item[] = [
{
label: "보안",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M216 56v56c0 96-88 120-88 120s-88-24-88-120V56a8 8 0 0 1 8-8h160a8 8 0 0 1 8 8" opacity=".2"/><path d="M208 40H48a16 16 0 0 0-16 16v56c0 52.72 25.52 84.67 46.93 102.19c23.06 18.86 46 25.26 47 25.53a8 8 0 0 0 4.2 0c1-.27 23.91-6.67 47-25.53C198.48 196.67 224 164.72 224 112V56a16 16 0 0 0-16-16m0 72c0 37.07-13.66 67.16-40.6 89.42a129.3 129.3 0 0 1-39.4 22.2a128.3 128.3 0 0 1-38.92-21.81C61.82 179.51 48 149.3 48 112V56h160ZM82.34 141.66a8 8 0 0 1 11.32-11.32L112 148.69l50.34-50.35a8 8 0 0 1 11.32 11.32l-56 56a8 8 0 0 1-11.32 0Z"/></g></svg>
),
},
{
label: "속도",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="m96 240l16-80l-64-24L160 16l-16 80l64 24Z" opacity=".2"/><path d="M215.79 118.17a8 8 0 0 0-5-5.66L153.18 90.9l14.66-73.33a8 8 0 0 0-13.69-7l-112 120a8 8 0 0 0 3 13l57.63 21.61l-14.62 73.25a8 8 0 0 0 13.69 7l112-120a8 8 0 0 0 1.94-7.26M109.37 214l10.47-52.38a8 8 0 0 0-5-9.06L62 132.71l84.62-90.66l-10.46 52.38a8 8 0 0 0 5 9.06l52.8 19.8Z"/></g></svg>
),
},
{
label: "성장",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M224 64v144H32V48h176a16 16 0 0 1 16 16" opacity=".2"/><path d="M232 208a8 8 0 0 1-8 8H32a8 8 0 0 1-8-8V48a8 8 0 0 1 16 0v108.69l50.34-50.35a8 8 0 0 1 11.32 0L128 132.69L180.69 80H160a8 8 0 0 1 0-16h40a8 8 0 0 1 8 8v40a8 8 0 0 1-16 0V91.31l-58.34 58.35a8 8 0 0 1-11.32 0L96 123.31l-56 56V200h184a8 8 0 0 1 8 8"/></g></svg>
),
},
{
label: "보안 인증",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M208 88H48a8 8 0 0 0-8 8v112a8 8 0 0 0 8 8h160a8 8 0 0 0 8-8V96a8 8 0 0 0-8-8m-80 72a20 20 0 1 1 20-20a20 20 0 0 1-20 20" opacity=".2"/><path d="M208 80h-32V56a48 48 0 0 0-96 0v24H48a16 16 0 0 0-16 16v112a16 16 0 0 0 16 16h160a16 16 0 0 0 16-16V96a16 16 0 0 0-16-16M96 56a32 32 0 0 1 64 0v24H96Zm112 152H48V96h160zm-80-96a28 28 0 0 0-8 54.83V184a8 8 0 0 0 16 0v-17.17a28 28 0 0 0-8-54.83m0 40a12 12 0 1 1 12-12a12 12 0 0 1-12 12"/></g></svg>
),
},
];
export default function CardGridTrust({ duration = 2000, easing = "ease-out", color = "#2f6df6" }: CardGridTrustProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
const delays = ["a", "b", "c", "d"] as const;
return (
<div className={styles.grid} style={vars}>
{ICONS.map((it, i) => (
<div key={it.label} className={`${styles.card} ${styles[delays[i]]}`}>
<span className={`${styles.icon} ${styles[delays[i]]}`}>{it.icon}</span>
<p className={styles.label}>{it.label}</p>
</div>
))}
</div>
);
}03 · Icon card grid — Measured area 7.384% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password
What to change
Every card shares the exact same blue.
<div class="flex flex-col items-start gap-3 rounded-card bg-white p-5 shadow-soft-sm">
<span class="grid h-9 w-9 place-items-center rounded-full bg-bluetint text-brand">
<svg><!-- ph:shield-check-duotone --></svg>
</span>
<p class="text-sm font-bold">Security</p>
</div>
04Checkmark feature list
What you see
Each row carries a blue checkmark in a pale mint circle, and the rows slide down into place from top to bottom, then fade back out and repeat.

<main class="stage">
<ul class="featurelist">
<li class="featurelist__row featurelist__row--1">
<span class="featurelist__check" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M232 56v144a16 16 0 0 1-16 16H40a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16h176a16 16 0 0 1 16 16" opacity=".2"/><path d="m205.66 85.66l-96 96a8 8 0 0 1-11.32 0l-40-40a8 8 0 0 1 11.32-11.32L104 164.69l90.34-90.35a8 8 0 0 1 11.32 11.32"/></g></svg></span>
<span class="featurelist__text">Unlimited team invites</span>
</li>
<li class="featurelist__row featurelist__row--2">
<span class="featurelist__check" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M232 56v144a16 16 0 0 1-16 16H40a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16h176a16 16 0 0 1 16 16" opacity=".2"/><path d="m205.66 85.66l-96 96a8 8 0 0 1-11.32 0l-40-40a8 8 0 0 1 11.32-11.32L104 164.69l90.34-90.35a8 8 0 0 1 11.32 11.32"/></g></svg></span>
<span class="featurelist__text">90-day audit log retention</span>
</li>
<li class="featurelist__row featurelist__row--3">
<span class="featurelist__check" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M232 56v144a16 16 0 0 1-16 16H40a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16h176a16 16 0 0 1 16 16" opacity=".2"/><path d="m205.66 85.66l-96 96a8 8 0 0 1-11.32 0l-40-40a8 8 0 0 1 11.32-11.32L104 164.69l90.34-90.35a8 8 0 0 1 11.32 11.32"/></g></svg></span>
<span class="featurelist__text">SSO · SAML integration</span>
</li>
</ul>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop;
$easing: $ease-out;
$color: $subject-blue;
$trust-mint: #eaf6f1;
$trust-ink: $subject-ink;
$trust-blue: $subject-blue;
@include stage($trust-mint);
.featurelist {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: $sp-2;
width: clamp(220px, 78vw, 300px);
}
.featurelist__row {
box-sizing: border-box;
display: flex;
align-items: center;
gap: $sp-3;
padding: $sp-2 $sp-4;
border-radius: $r-pill;
background: $stage-paper;
box-shadow: 0 8px 16px rgba($trust-ink, .07);
opacity: 0;
animation: rowIn $duration $easing infinite;
}
.featurelist__row--1 { animation-delay: 0s; }
.featurelist__row--2 { animation-delay: $duration * -0.83; }
.featurelist__row--3 { animation-delay: $duration * -0.66; }
.featurelist__check {
display: grid;
place-items: center;
width: 28px;
height: 28px;
flex: 0 0 auto;
border-radius: 50%;
background: $trust-mint;
color: $trust-blue;
font-size: 15px;
}
.featurelist__text {
color: $trust-ink;
font-weight: 600; font-size: 13px; line-height: 1.3;
font-family: inherit;
}
// 데모: 행마다 시작 시각을 어긋내 위에서 아래로 미끄러져 들어오고, 한 바퀴가 끝나면 다시 사라졌다 나타난다
@keyframes rowIn {
0% { opacity: 0; transform: translateY(-10px); }
16%, 82% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-10px); }
}
@media (max-width: 340px), (max-height: 220px) {
.featurelist__row { padding: $sp-1 $sp-3; gap: $sp-2; }
.featurelist__check { width: 22px; height: 22px; font-size: 12px; }
.featurelist__text { font-size: 11px; }
}
@media (prefers-reduced-motion: reduce) {
.featurelist__row { animation: none !important; opacity: 1; transform: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #eaf6f1;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.featurelist {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
width: clamp(220px, 78vw, 300px);
}
.featurelist__row {
box-sizing: border-box;
display: flex;
align-items: center;
gap: 12px;
padding: 8px 16px;
border-radius: 999px;
background: #ffffff;
box-shadow: 0 8px 16px rgba(23, 20, 26, 0.07);
opacity: 0;
animation: rowIn 2s ease-out infinite;
}
.featurelist__row--1 {
animation-delay: 0s;
}
.featurelist__row--2 {
animation-delay: -1.66s;
}
.featurelist__row--3 {
animation-delay: -1.32s;
}
.featurelist__check {
display: grid;
place-items: center;
width: 28px;
height: 28px;
flex: 0 0 auto;
border-radius: 50%;
background: #eaf6f1;
color: #2f6df6;
font-size: 15px;
}
.featurelist__text {
color: #17141a;
font-weight: 600;
font-size: 13px;
line-height: 1.3;
font-family: inherit;
}
@keyframes rowIn {
0% {
opacity: 0;
transform: translateY(-10px);
}
16%, 82% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-10px);
}
}
@media (max-width: 340px), (max-height: 220px) {
.featurelist__row {
padding: 4px 12px;
gap: 8px;
}
.featurelist__check {
width: 22px;
height: 22px;
font-size: 12px;
}
.featurelist__text {
font-size: 11px;
}
}
@media (prefers-reduced-motion: reduce) {
.featurelist__row {
animation: none !important;
opacity: 1;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./FeatureListTrust.module.scss";
export type FeatureListTrustProps = {
items?: string[];
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const CHECK = (
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M232 56v144a16 16 0 0 1-16 16H40a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16h176a16 16 0 0 1 16 16" opacity=".2"/><path d="m205.66 85.66l-96 96a8 8 0 0 1-11.32 0l-40-40a8 8 0 0 1 11.32-11.32L104 164.69l90.34-90.35a8 8 0 0 1 11.32 11.32"/></g></svg>
);
export default function FeatureListTrust({
items = ["무제한 팀원 초대", "감사 로그 90일 보관", "SSO · SAML 연동"],
duration = 2000,
easing = "ease-out",
color = "#2f6df6",
}: FeatureListTrustProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
const delays = [0, -1660, -1320];
return (
<ul className={styles.list} style={vars}>
{items.map((text, i) => (
<li key={text} className={styles.row} style={{ animationDelay: `${delays[i] ?? 0}ms` }}>
<span className={styles.check} aria-hidden="true">{CHECK}</span>
<span className={styles.text}>{text}</span>
</li>
))}
</ul>
);
}04 · Checkmark feature list — Measured area 29.301% · moving frames 16/23Original by GODRICH · SCSS and React ship in the zip · See the password
Where to look in the code
The check is a real Phosphor Duotone icon, not a hand-drawn shape.
<li class="flex items-center gap-3 rounded-pill bg-white px-5 py-3 shadow-soft-sm">
<span class="grid h-7 w-7 shrink-0 place-items-center rounded-full bg-mint text-brand">
<svg><!-- ph:check-duotone --></svg>
</span>
<span class="text-sm font-semibold">Unlimited team seats</span>
</li>
05Simple pricing
What happens on screen
All three plans share the same white card, but the recommended one gets a thin blue border and a slightly deeper shadow.

<main class="stage">
<div class="pricing">
<div class="pricing__card">
<p class="pricing__name">Starter</p>
<p class="pricing__price">₩0</p>
</div>
<div class="pricing__card pricing__card--pro">
<span class="pricing__badge">Recommended</span>
<p class="pricing__name">Pro</p>
<p class="pricing__price">₩29,000</p>
</div>
<div class="pricing__card">
<p class="pricing__name">Enterprise</p>
<p class="pricing__price">Contact</p>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop;
$easing: $ease-out;
$color: $subject-blue;
$trust-offwhite: #f7f9fc;
$trust-ink: $subject-ink;
$trust-slate: #475569;
$trust-blue: $subject-blue;
@include stage($trust-offwhite);
.pricing {
display: flex;
align-items: center;
gap: $sp-2;
}
.pricing__card {
position: relative;
box-sizing: border-box;
width: 92px;
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-1;
padding: $sp-3 $sp-2;
border-radius: $r-card;
border: 1px solid #e2e8f0;
background: $stage-paper;
box-shadow: 0 6px 12px rgba($trust-ink, .05);
text-align: center;
}
.pricing__card--pro {
width: 100px;
padding: $sp-4 $sp-2;
border: 2px solid $trust-blue;
box-shadow: 0 16px 26px rgba($trust-blue, .18);
animation: proLift $duration $easing infinite;
}
.pricing__badge {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
border-radius: $r-pill;
background: $trust-blue;
color: $stage-paper;
padding: $sp-1 $sp-2;
font-weight: 700; font-size: 10px; line-height: 1.6;
font-family: inherit;
animation: badgePulse $duration $easing infinite;
}
.pricing__name {
margin: 0;
color: $trust-ink;
font-weight: 700; font-size: 12px; line-height: 1.2;
font-family: inherit;
}
.pricing__price {
margin: 0;
color: $trust-slate;
font-weight: 600; font-size: 13px; line-height: 1.2;
font-family: inherit;
}
// 데모: 추천 플랜만 배지가 숨 쉬듯 살짝 커졌다 작아지고, 카드 전체도 아주 완만하게 떠오른다 — 화려한 색 대신 테두리·그림자 차이가 위계를 만든다
@keyframes proLift {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
@keyframes badgePulse {
0%, 100% { transform: translateX(-50%) scale(1); }
50% { transform: translateX(-50%) scale(1.1); }
}
@media (max-width: 340px), (max-height: 220px) {
.pricing { gap: $sp-1; }
.pricing__card { width: 76px; padding: $sp-2 $sp-1; }
.pricing__card--pro { width: 82px; padding: $sp-3 $sp-1; }
.pricing__name { font-size: 10px; }
.pricing__price { font-size: 11px; }
}
@media (prefers-reduced-motion: reduce) {
.pricing__card--pro, .pricing__badge { animation: none !important; transform: none; }
.pricing__badge { transform: translateX(-50%); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #f7f9fc;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.pricing {
display: flex;
align-items: center;
gap: 8px;
}
.pricing__card {
position: relative;
box-sizing: border-box;
width: 92px;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 12px 8px;
border-radius: 16px;
border: 1px solid #e2e8f0;
background: #ffffff;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.05);
text-align: center;
}
.pricing__card--pro {
width: 100px;
padding: 16px 8px;
border: 2px solid #2f6df6;
box-shadow: 0 16px 26px rgba(47, 109, 246, 0.18);
animation: proLift 2s ease-out infinite;
}
.pricing__badge {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
border-radius: 999px;
background: #2f6df6;
color: #ffffff;
padding: 4px 8px;
font-weight: 700;
font-size: 10px;
line-height: 1.6;
font-family: inherit;
animation: badgePulse 2s ease-out infinite;
}
.pricing__name {
margin: 0;
color: #17141a;
font-weight: 700;
font-size: 12px;
line-height: 1.2;
font-family: inherit;
}
.pricing__price {
margin: 0;
color: #475569;
font-weight: 600;
font-size: 13px;
line-height: 1.2;
font-family: inherit;
}
@keyframes proLift {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-6px);
}
}
@keyframes badgePulse {
0%, 100% {
transform: translateX(-50%) scale(1);
}
50% {
transform: translateX(-50%) scale(1.1);
}
}
@media (max-width: 340px), (max-height: 220px) {
.pricing {
gap: 4px;
}
.pricing__card {
width: 76px;
padding: 8px 4px;
}
.pricing__card--pro {
width: 82px;
padding: 12px 4px;
}
.pricing__name {
font-size: 10px;
}
.pricing__price {
font-size: 11px;
}
}
@media (prefers-reduced-motion: reduce) {
.pricing__card--pro, .pricing__badge {
animation: none !important;
transform: none;
}
.pricing__badge {
transform: translateX(-50%);
}
}import type { CSSProperties } from "react";
import styles from "./PricingTrust.module.scss";
export type PricingTrustProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function PricingTrust({ duration = 2000, easing = "ease-out", color = "#2f6df6" }: PricingTrustProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.pricing} style={vars}>
<div className={styles.card}>
<p className={styles.name}>스타터</p>
<p className={styles.price}>₩0</p>
</div>
<div className={`${styles.card} ${styles.pro}`}>
<span className={styles.badge}>추천</span>
<p className={styles.name}>프로</p>
<p className={styles.price}>₩29,000</p>
</div>
<div className={styles.card}>
<p className={styles.name}>기업용</p>
<p className={styles.price}>문의</p>
</div>
</div>
);
}05 · Simple pricing — Measured area 2.276% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password
How to read the code
Hierarchy comes from border weight and shadow depth, never from a different background color.
<div class="relative flex-1 max-w-[240px] rounded-card border-2 border-brand
bg-white p-6 text-center shadow-brand">
<span class="absolute -top-3 left-1/2 -translate-x-1/2 rounded-pill
bg-brand px-3 py-1 text-[11px] font-bold text-white">Popular</span>
<p class="text-sm font-bold">Pro</p>
</div>
06Logo-trust testimonial
What moves
Above the quote, six gray logo chips scroll sideways in an endless loop by stitching an original set to a cloned copy and sliding the whole track by -50%.

<main class="stage">
<div class="logotrust">
<figure class="logotrust__quote">
<blockquote>"Onboarding was done in a single day."</blockquote>
<figcaption>— Nimbus · Operations Lead</figcaption>
</figure>
<div class="logotrust__viewport">
<div class="logotrust__track" aria-hidden="true">
<span class="logotrust__chip">Nimbus</span>
<span class="logotrust__chip">Ledger</span>
<span class="logotrust__chip">Northwind</span>
<span class="logotrust__chip">Vertex</span>
<span class="logotrust__chip">Harborline</span>
<span class="logotrust__chip">Anchorpay</span>
<span class="logotrust__chip">Nimbus</span>
<span class="logotrust__chip">Ledger</span>
<span class="logotrust__chip">Northwind</span>
<span class="logotrust__chip">Vertex</span>
<span class="logotrust__chip">Harborline</span>
<span class="logotrust__chip">Anchorpay</span>
</div>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: 6s; // 열두 칩(원본6+복제6)이 한 바퀴 도는 시간 — $dur-loop 보다 길어야 흐름이 자연스럽다(합치지 않는다, 규칙2)
$easing: linear;
$color: $subject-blue;
$trust-graytint: #f1f5f9;
$trust-ink: $subject-ink;
$trust-slate: #475569;
$trust-blue: $subject-blue;
@include stage($trust-graytint);
.logotrust {
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-4;
width: 320px;
}
.logotrust__quote {
margin: 0;
box-sizing: border-box;
width: 100%;
padding: $sp-3 $sp-4;
border-radius: $r-card;
background: $stage-paper;
box-shadow: 0 8px 18px rgba($trust-ink, .07);
text-align: center;
}
.logotrust__quote blockquote {
margin: 0 0 $sp-1;
color: $trust-ink;
font-weight: 700; font-size: 13px; line-height: 1.4;
font-family: inherit;
}
.logotrust__quote figcaption {
color: $trust-slate;
font-weight: 500; font-size: 11px; line-height: 1.3;
font-family: inherit;
}
.logotrust__viewport {
width: 100%;
overflow: hidden;
}
.logotrust__track {
display: flex;
width: max-content;
animation: scrollLogos $duration $easing infinite;
}
.logotrust__chip {
flex: 0 0 auto;
margin-right: $sp-3;
border-radius: $r-pill;
background: $stage-paper;
color: $trust-slate;
padding: $sp-1 $sp-4;
font-weight: 700; font-size: 12px; line-height: 1.6;
font-family: inherit;
box-shadow: 0 4px 10px rgba($trust-ink, .06);
}
.logotrust__chip:hover {
color: $trust-blue;
box-shadow: 0 6px 14px rgba($trust-blue, .18);
}
// 데모: 원본 6개 + 복제 6개를 이어 붙여 -50% 만큼 옮기면 이음새 없이 무한히 흐른다. 간격은 flex gap 대신 margin-right(BD15)
@keyframes scrollLogos {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@media (max-width: 340px), (max-height: 220px) {
.logotrust { width: 260px; gap: $sp-2; }
.logotrust__quote { padding: $sp-2 $sp-3; }
.logotrust__quote blockquote { font-size: 11px; }
.logotrust__chip { padding: 4px $sp-3; font-size: 11px; margin-right: $sp-2; }
}
@media (prefers-reduced-motion: reduce) {
.logotrust__track { animation: none !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #f1f5f9;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.logotrust {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
width: 320px;
}
.logotrust__quote {
margin: 0;
box-sizing: border-box;
width: 100%;
padding: 12px 16px;
border-radius: 16px;
background: #ffffff;
box-shadow: 0 8px 18px rgba(23, 20, 26, 0.07);
text-align: center;
}
.logotrust__quote blockquote {
margin: 0 0 4px;
color: #17141a;
font-weight: 700;
font-size: 13px;
line-height: 1.4;
font-family: inherit;
}
.logotrust__quote figcaption {
color: #475569;
font-weight: 500;
font-size: 11px;
line-height: 1.3;
font-family: inherit;
}
.logotrust__viewport {
width: 100%;
overflow: hidden;
}
.logotrust__track {
display: flex;
width: max-content;
animation: scrollLogos 6s linear infinite;
}
.logotrust__chip {
flex: 0 0 auto;
margin-right: 12px;
border-radius: 999px;
background: #ffffff;
color: #475569;
padding: 4px 16px;
font-weight: 700;
font-size: 12px;
line-height: 1.6;
font-family: inherit;
box-shadow: 0 4px 10px rgba(23, 20, 26, 0.06);
}
.logotrust__chip:hover {
color: #2f6df6;
box-shadow: 0 6px 14px rgba(47, 109, 246, 0.18);
}
@keyframes scrollLogos {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
@media (max-width: 340px), (max-height: 220px) {
.logotrust {
width: 260px;
gap: 8px;
}
.logotrust__quote {
padding: 8px 12px;
}
.logotrust__quote blockquote {
font-size: 11px;
}
.logotrust__chip {
padding: 4px 12px;
font-size: 11px;
margin-right: 8px;
}
}
@media (prefers-reduced-motion: reduce) {
.logotrust__track {
animation: none !important;
}
}import type { CSSProperties } from "react";
import styles from "./LogoTrust.module.scss";
export type LogoTrustProps = {
quote?: string;
author?: string;
logos?: string[];
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function LogoTrust({
quote = '"온보딩이 하루 만에 끝났습니다"',
author = "— 니임버스 · 운영팀장",
logos = ["Nimbus", "Ledger", "Northwind", "Vertex", "Harborline", "Anchorpay"],
duration = 6000,
easing = "linear",
color = "#2f6df6",
}: LogoTrustProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
const doubled = [...logos, ...logos];
return (
<div className={styles.wrap} style={vars}>
<figure className={styles.quote}>
<blockquote>{quote}</blockquote>
<figcaption>{author}</figcaption>
</figure>
<div className={styles.viewport}>
<div className={styles.track} aria-hidden="true">
{doubled.map((name, i) => (
<span key={`${name}-${i}`} className={styles.chip}>{name}</span>
))}
</div>
</div>
</div>
);
}06 · Logo-trust testimonial — Measured area 5.942% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password
What to change
Only the hovered chip regains full blue color.
<div class="track flex w-max" style="animation: scrollLogos 6s linear infinite">
<span class="mr-3 shrink-0 rounded-pill bg-white px-5 py-2 text-xs
font-bold text-slate shadow-soft-sm">Nimbus</span>
<!-- repeat the same six chips a second time right after -->
</div>
07Solid CTA band
What you see
The band is a single flat brand-blue fill with no gradient, and only the white button inside lifts and deepens its shadow in a loop.

<main class="stage">
<section class="ctaband">
<h2 class="ctaband__title">Start free right now</h2>
<p class="ctaband__desc">14-day trial, no credit card</p>
<button class="ctaband__btn" type="button">Request a demo</button>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop;
$easing: $ease-out;
$color: $subject-blue;
$trust-bluetint: #eaf1ff;
$trust-blue-deep: #7386ba;
@include stage($trust-bluetint);
.ctaband {
box-sizing: border-box;
width: clamp(240px, 82vw, 340px);
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-2;
padding: $sp-6 $sp-4;
border-radius: $r-card;
background: $trust-blue-deep;
text-align: center;
box-shadow: 0 16px 30px rgba($trust-blue-deep, .32);
}
.ctaband__title {
margin: 0;
color: $stage-paper;
font-weight: 700; font-size: clamp(16px, 4.4vw, 21px); line-height: 1.3;
font-family: inherit;
}
.ctaband__desc {
margin: 0;
color: rgba($stage-paper, .82);
font-weight: 500; font-size: clamp(11px, 2.8vw, 13px); line-height: 1.4;
font-family: inherit;
}
.ctaband__btn {
margin-top: $sp-1;
border: 0;
border-radius: $r-pill;
background: $stage-paper;
color: $trust-blue-deep;
padding: $sp-2 $sp-6;
font-weight: 700; font-size: clamp(12px, 2.8vw, 14px); line-height: 1;
font-family: inherit;
box-shadow: 0 6px 14px rgba(23, 20, 26, .18);
animation: btnLift $duration $easing infinite;
}
.ctaband__btn:focus-visible {
outline: 3px solid $stage-paper;
outline-offset: 2px;
}
// 데모: 흰 버튼만 눌렸다 떠오르기를 반복한다 — 배경은 그라디언트 없이 진한 파란 단색 그대로 고정
@keyframes btnLift {
0%, 100% { transform: translateY(0); box-shadow: 0 6px 14px rgba(23, 20, 26, .18); }
50% { transform: translateY(-4px); box-shadow: 0 12px 22px rgba(23, 20, 26, .24); }
}
@media (max-width: 340px), (max-height: 220px) {
.ctaband { padding: $sp-4 $sp-3; gap: $sp-1; }
.ctaband__desc { display: none; }
.ctaband__btn { padding: $sp-1 $sp-4; margin-top: 0; }
}
@media (prefers-reduced-motion: reduce) {
.ctaband__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: #eaf1ff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.ctaband {
box-sizing: border-box;
width: clamp(240px, 82vw, 340px);
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 24px 16px;
border-radius: 16px;
background: #7386ba;
text-align: center;
box-shadow: 0 16px 30px rgba(115, 134, 186, 0.32);
}
.ctaband__title {
margin: 0;
color: #ffffff;
font-weight: 700;
font-size: clamp(16px, 4.4vw, 21px);
line-height: 1.3;
font-family: inherit;
}
.ctaband__desc {
margin: 0;
color: rgba(255, 255, 255, 0.82);
font-weight: 500;
font-size: clamp(11px, 2.8vw, 13px);
line-height: 1.4;
font-family: inherit;
}
.ctaband__btn {
margin-top: 4px;
border: 0;
border-radius: 999px;
background: #ffffff;
color: #7386ba;
padding: 8px 24px;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 14px);
line-height: 1;
font-family: inherit;
box-shadow: 0 6px 14px rgba(23, 20, 26, 0.18);
animation: btnLift 2s ease-out infinite;
}
.ctaband__btn:focus-visible {
outline: 3px solid #ffffff;
outline-offset: 2px;
}
@keyframes btnLift {
0%, 100% {
transform: translateY(0);
box-shadow: 0 6px 14px rgba(23, 20, 26, 0.18);
}
50% {
transform: translateY(-4px);
box-shadow: 0 12px 22px rgba(23, 20, 26, 0.24);
}
}
@media (max-width: 340px), (max-height: 220px) {
.ctaband {
padding: 16px 12px;
gap: 4px;
}
.ctaband__desc {
display: none;
}
.ctaband__btn {
padding: 4px 16px;
margin-top: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ctaband__btn {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./CtaBandTrust.module.scss";
export type CtaBandTrustProps = {
title?: string;
desc?: string;
cta?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function CtaBandTrust({
title = "지금 무료로 시작하세요",
desc = "신용카드 없이 14일 체험",
cta = "데모 신청",
duration = 2000,
easing = "ease-out",
color = "#1d4ed8",
}: CtaBandTrustProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.band} style={vars}>
<h2 className={styles.title}>{title}</h2>
<p className={styles.desc}>{desc}</p>
<button type="button" className={styles.btn}>{cta}</button>
</section>
);
}07 · Solid CTA band — Measured area 1.019% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · See the password
Where to look in the code
Restraint here means one color, not a blend of several.
<div class="rounded-card bg-brand-deep px-10 py-14 shadow-brand-deep text-center">
<h2 class="text-3xl font-bold text-white">Start free today</h2>
<button class="mt-4 rounded-pill bg-white px-8 py-3 text-sm font-bold text-brand-deep">Request a demo</button>
</div>
08Minimal 4-column footer
What happens on screen
A single pale-gray rule sits above the column links, and each social icon circle fills blue and grows a little on hover — cycled automatically in the demo.

<main class="stage">
<footer class="footertrust">
<div class="footertrust__rule" aria-hidden="true"></div>
<div class="footertrust__cols">
<div class="footertrust__col"><p>Product</p><p>Features</p></div>
<div class="footertrust__col"><p>Company</p><p>About</p></div>
<div class="footertrust__col"><p>Help</p><p>Contact</p></div>
</div>
<div class="footertrust__social">
<span class="footertrust__icon footertrust__icon--1" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="m240 72l-32 32c-4.26 66.84-60.08 120-128 120c-32 0-40-12-40-12s32-12 48-36c0 0-55.15-32-47.22-120c0 0 39.66 40 87.22 48V88c0-22 18-40.27 40-40a40.74 40.74 0 0 1 36.67 24Z" opacity=".2"/><path d="M247.39 68.94A8 8 0 0 0 240 64h-30.43a48.65 48.65 0 0 0-41.47-24a46.87 46.87 0 0 0-33.74 13.7A47.87 47.87 0 0 0 120 88v6.09C79.74 83.47 46.81 50.72 46.46 50.37a8 8 0 0 0-13.65 4.92c-4.3 47.79 9.57 79.77 22 98.18a111 111 0 0 0 21.89 24.2c-15.27 17.53-39.25 26.74-39.5 26.84a8 8 0 0 0-3.85 11.93c.74 1.12 3.75 5.05 11.08 8.72C53.51 229.7 65.48 232 80 232c70.68 0 129.72-54.42 135.76-124.44l29.9-29.9a8 8 0 0 0 1.73-8.72m-45 29.41a8 8 0 0 0-2.32 5.14C196 166.58 143.28 216 80 216c-10.56 0-18-1.4-23.22-3.08c11.52-6.25 27.56-17 37.88-32.48a8 8 0 0 0-2.61-11.34c-.13-.08-12.86-7.74-24.48-25.29C54.54 124.11 48 100.38 48 73.09c16 13 45.25 33.18 78.69 38.8A8 8 0 0 0 136 104V88a32 32 0 0 1 9.6-22.92A30.9 30.9 0 0 1 167.9 56c12.66.16 24.49 7.88 29.44 19.21a8 8 0 0 0 7.33 4.79h16Z"/></g></svg></span>
<span class="footertrust__icon footertrust__icon--2" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M224 40v176a8 8 0 0 1-8 8H40a8 8 0 0 1-8-8V40a8 8 0 0 1 8-8h176a8 8 0 0 1 8 8" opacity=".2"/><path d="M216 24H40a16 16 0 0 0-16 16v176a16 16 0 0 0 16 16h176a16 16 0 0 0 16-16V40a16 16 0 0 0-16-16m0 192H40V40h176zM96 112v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0m88 28v36a8 8 0 0 1-16 0v-36a20 20 0 0 0-40 0v36a8 8 0 0 1-16 0v-64a8 8 0 0 1 15.79-1.78A36 36 0 0 1 184 140m-84-56a12 12 0 1 1-12-12a12 12 0 0 1 12 12"/></g></svg></span>
<span class="footertrust__icon footertrust__icon--3" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M208 104v8a48 48 0 0 1-48 48h-24a32 32 0 0 1 32 32v40h-64v-40a32 32 0 0 1 32-32h-24a48 48 0 0 1-48-48v-8a49.3 49.3 0 0 1 8.51-27.3A51.92 51.92 0 0 1 76 32a52 52 0 0 1 43.83 24h32.34A52 52 0 0 1 196 32a51.92 51.92 0 0 1 3.49 44.7A49.3 49.3 0 0 1 208 104" opacity=".2"/><path d="M208.3 75.68A59.74 59.74 0 0 0 202.93 28a8 8 0 0 0-6.93-4a59.75 59.75 0 0 0-48 24h-24a59.75 59.75 0 0 0-48-24a8 8 0 0 0-6.93 4a59.78 59.78 0 0 0-5.38 47.68A58.14 58.14 0 0 0 56 104v8a56.06 56.06 0 0 0 48.44 55.47A39.8 39.8 0 0 0 96 192v8H72a24 24 0 0 1-24-24a40 40 0 0 0-40-40a8 8 0 0 0 0 16a24 24 0 0 1 24 24a40 40 0 0 0 40 40h24v16a8 8 0 0 0 16 0v-40a24 24 0 0 1 48 0v40a8 8 0 0 0 16 0v-40a39.8 39.8 0 0 0-8.44-24.53A56.06 56.06 0 0 0 216 112v-8a58 58 0 0 0-7.7-28.32M200 112a40 40 0 0 1-40 40h-48a40 40 0 0 1-40-40v-8a41.74 41.74 0 0 1 6.9-22.48a8 8 0 0 0 1.1-7.69a43.8 43.8 0 0 1 .79-33.58a43.88 43.88 0 0 1 32.32 20.06a8 8 0 0 0 6.71 3.69h32.35a8 8 0 0 0 6.74-3.69a43.87 43.87 0 0 1 32.32-20.06a43.8 43.8 0 0 1 .77 33.58a8.09 8.09 0 0 0 1 7.65a41.76 41.76 0 0 1 7 22.52Z"/></g></svg></span>
</div>
</footer>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop;
$easing: $ease-out;
$color: $subject-blue;
$trust-offwhite: #f7f9fc;
$trust-ink: $subject-ink;
$trust-slate: #475569;
$trust-blue: $subject-blue;
@include stage($trust-offwhite);
.footertrust {
box-sizing: border-box;
width: 300px;
display: flex;
flex-direction: column;
gap: $sp-3;
}
.footertrust__rule {
height: 1px;
background: #e2e8f0;
}
.footertrust__cols {
display: flex;
justify-content: space-between;
gap: $sp-2;
}
.footertrust__col p {
margin: 0 0 $sp-1;
color: $trust-slate;
font-weight: 600; font-size: 11px; line-height: 1.5;
font-family: inherit;
}
.footertrust__col p:first-child { color: $trust-ink; font-weight: 700; }
.footertrust__social {
display: flex;
gap: $sp-4;
}
.footertrust__icon {
display: grid;
place-items: center;
width: 40px;
height: 40px;
border-radius: 50%;
background: #e2e8f0;
color: $trust-slate;
font-size: 18px;
animation: iconFill $duration $easing infinite;
}
.footertrust__icon--1 { animation-delay: 0s; }
.footertrust__icon--2 { animation-delay: $duration * -0.66; }
.footertrust__icon--3 { animation-delay: $duration * -0.33; }
// 데모: 소셜 아이콘 원이 순서대로 파랗게 채워지며 커졌다 돌아온다. 장식은 구분선 하나뿐이다
@keyframes iconFill {
0%, 60%, 100% { transform: translateY(0) scale(1); background: #e2e8f0; color: #475569; }
20% { transform: translateY(-5px) scale(1.35); background: $trust-blue; color: #ffffff; }
}
@media (max-width: 340px), (max-height: 220px) {
.footertrust { width: 264px; gap: $sp-2; }
.footertrust__icon { width: 32px; height: 32px; font-size: 14px; }
.footertrust__social { gap: $sp-3; }
}
@media (prefers-reduced-motion: reduce) {
.footertrust__icon { animation: none !important; transform: none; background: #e2e8f0; color: #475569; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #f7f9fc;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.footertrust {
box-sizing: border-box;
width: 300px;
display: flex;
flex-direction: column;
gap: 12px;
}
.footertrust__rule {
height: 1px;
background: #e2e8f0;
}
.footertrust__cols {
display: flex;
justify-content: space-between;
gap: 8px;
}
.footertrust__col p {
margin: 0 0 4px;
color: #475569;
font-weight: 600;
font-size: 11px;
line-height: 1.5;
font-family: inherit;
}
.footertrust__col p:first-child {
color: #17141a;
font-weight: 700;
}
.footertrust__social {
display: flex;
gap: 16px;
}
.footertrust__icon {
display: grid;
place-items: center;
width: 40px;
height: 40px;
border-radius: 50%;
background: #e2e8f0;
color: #475569;
font-size: 18px;
animation: iconFill 2s ease-out infinite;
}
.footertrust__icon--1 {
animation-delay: 0s;
}
.footertrust__icon--2 {
animation-delay: -1.32s;
}
.footertrust__icon--3 {
animation-delay: -0.66s;
}
@keyframes iconFill {
0%, 60%, 100% {
transform: translateY(0) scale(1);
background: #e2e8f0;
color: #475569;
}
20% {
transform: translateY(-5px) scale(1.35);
background: #2f6df6;
color: #ffffff;
}
}
@media (max-width: 340px), (max-height: 220px) {
.footertrust {
width: 264px;
gap: 8px;
}
.footertrust__icon {
width: 32px;
height: 32px;
font-size: 14px;
}
.footertrust__social {
gap: 12px;
}
}
@media (prefers-reduced-motion: reduce) {
.footertrust__icon {
animation: none !important;
transform: none;
background: #e2e8f0;
color: #475569;
}
}import type { CSSProperties } from "react";
import styles from "./FooterTrust.module.scss";
export type FooterTrustProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const ICONS = [
<svg key="tw" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="m240 72l-32 32c-4.26 66.84-60.08 120-128 120c-32 0-40-12-40-12s32-12 48-36c0 0-55.15-32-47.22-120c0 0 39.66 40 87.22 48V88c0-22 18-40.27 40-40a40.74 40.74 0 0 1 36.67 24Z" opacity=".2"/><path d="M247.39 68.94A8 8 0 0 0 240 64h-30.43a48.65 48.65 0 0 0-41.47-24a46.87 46.87 0 0 0-33.74 13.7A47.87 47.87 0 0 0 120 88v6.09C79.74 83.47 46.81 50.72 46.46 50.37a8 8 0 0 0-13.65 4.92c-4.3 47.79 9.57 79.77 22 98.18a111 111 0 0 0 21.89 24.2c-15.27 17.53-39.25 26.74-39.5 26.84a8 8 0 0 0-3.85 11.93c.74 1.12 3.75 5.05 11.08 8.72C53.51 229.7 65.48 232 80 232c70.68 0 129.72-54.42 135.76-124.44l29.9-29.9a8 8 0 0 0 1.73-8.72m-45 29.41a8 8 0 0 0-2.32 5.14C196 166.58 143.28 216 80 216c-10.56 0-18-1.4-23.22-3.08c11.52-6.25 27.56-17 37.88-32.48a8 8 0 0 0-2.61-11.34c-.13-.08-12.86-7.74-24.48-25.29C54.54 124.11 48 100.38 48 73.09c16 13 45.25 33.18 78.69 38.8A8 8 0 0 0 136 104V88a32 32 0 0 1 9.6-22.92A30.9 30.9 0 0 1 167.9 56c12.66.16 24.49 7.88 29.44 19.21a8 8 0 0 0 7.33 4.79h16Z"/></g></svg>,
<svg key="li" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M224 40v176a8 8 0 0 1-8 8H40a8 8 0 0 1-8-8V40a8 8 0 0 1 8-8h176a8 8 0 0 1 8 8" opacity=".2"/><path d="M216 24H40a16 16 0 0 0-16 16v176a16 16 0 0 0 16 16h176a16 16 0 0 0 16-16V40a16 16 0 0 0-16-16m0 192H40V40h176zM96 112v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0m88 28v36a8 8 0 0 1-16 0v-36a20 20 0 0 0-40 0v36a8 8 0 0 1-16 0v-64a8 8 0 0 1 15.79-1.78A36 36 0 0 1 184 140m-84-56a12 12 0 1 1-12-12a12 12 0 0 1 12 12"/></g></svg>,
<svg key="gh" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M208 104v8a48 48 0 0 1-48 48h-24a32 32 0 0 1 32 32v40h-64v-40a32 32 0 0 1 32-32h-24a48 48 0 0 1-48-48v-8a49.3 49.3 0 0 1 8.51-27.3A51.92 51.92 0 0 1 76 32a52 52 0 0 1 43.83 24h32.34A52 52 0 0 1 196 32a51.92 51.92 0 0 1 3.49 44.7A49.3 49.3 0 0 1 208 104" opacity=".2"/><path d="M208.3 75.68A59.74 59.74 0 0 0 202.93 28a8 8 0 0 0-6.93-4a59.75 59.75 0 0 0-48 24h-24a59.75 59.75 0 0 0-48-24a8 8 0 0 0-6.93 4a59.78 59.78 0 0 0-5.38 47.68A58.14 58.14 0 0 0 56 104v8a56.06 56.06 0 0 0 48.44 55.47A39.8 39.8 0 0 0 96 192v8H72a24 24 0 0 1-24-24a40 40 0 0 0-40-40a8 8 0 0 0 0 16a24 24 0 0 1 24 24a40 40 0 0 0 40 40h24v16a8 8 0 0 0 16 0v-40a24 24 0 0 1 48 0v40a8 8 0 0 0 16 0v-40a39.8 39.8 0 0 0-8.44-24.53A56.06 56.06 0 0 0 216 112v-8a58 58 0 0 0-7.7-28.32M200 112a40 40 0 0 1-40 40h-48a40 40 0 0 1-40-40v-8a41.74 41.74 0 0 1 6.9-22.48a8 8 0 0 0 1.1-7.69a43.8 43.8 0 0 1 .79-33.58a43.88 43.88 0 0 1 32.32 20.06a8 8 0 0 0 6.71 3.69h32.35a8 8 0 0 0 6.74-3.69a43.87 43.87 0 0 1 32.32-20.06a43.8 43.8 0 0 1 .77 33.58a8.09 8.09 0 0 0 1 7.65a41.76 41.76 0 0 1 7 22.52Z"/></g></svg>,
];
export default function FooterTrust({ duration = 2000, easing = "ease-out", color = "#2f6df6" }: FooterTrustProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
const cols = [
{ h: "제품", items: ["기능"] },
{ h: "회사", items: ["소개"] },
{ h: "도움말", items: ["문의"] },
];
const delays = [0, -1320, -660];
return (
<footer className={styles.footer} style={vars}>
<div className={styles.rule} aria-hidden="true" />
<div className={styles.cols}>
{cols.map((c) => (
<div key={c.h} className={styles.col}>
<p>{c.h}</p>
{c.items.map((it) => <p key={it}>{it}</p>)}
</div>
))}
</div>
<div className={styles.social}>
{ICONS.map((icon, i) => (
<span key={i} className={styles.icon} style={{ animationDelay: `${delays[i]}ms` }} aria-hidden="true">
{icon}
</span>
))}
</div>
</footer>
);
}08 · Minimal 4-column footer — Measured area 4.968% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password
How to read the code
The only ornament on the whole footer is that one line.
<div class="grid h-9 w-9 place-items-center rounded-full bg-graytint text-slate
transition hover:bg-brand hover:text-white">
<svg><!-- ph:twitter-logo-duotone --></svg>
</div>
09Clean buttons & inputs
What moves
The button darkens slightly and lifts by a pixel on hover, while the input's thin gray border softens into a two-layer blue focus ring.

<main class="stage">
<div class="controls">
<label class="controls__field">
<span class="controls__label">Email</span>
<input class="controls__input" type="text" placeholder="[email protected]">
</label>
<button class="controls__btn" type="button">Continue</button>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop;
$easing: $ease-out;
$color: $subject-blue;
$trust-mint: #eaf6f1;
$trust-ink: $subject-ink;
$trust-slate: #475569;
$trust-blue: $subject-blue;
$trust-blue-deep: #7386ba;
@include stage($trust-mint);
.controls {
box-sizing: border-box;
width: clamp(220px, 78vw, 280px);
display: flex;
flex-direction: column;
gap: $sp-4;
}
.controls__field {
display: flex;
flex-direction: column;
gap: $sp-1;
}
.controls__label {
color: $trust-slate;
font-weight: 600; font-size: 11px; line-height: 1.4;
font-family: inherit;
}
.controls__input {
box-sizing: border-box;
width: 100%;
border: 1.5px solid #e2e8f0;
border-radius: $r-sm;
background: $stage-paper;
color: $trust-ink;
padding: $sp-2 $sp-3;
font-weight: 500; font-size: 13px; line-height: 1.4;
font-family: inherit;
outline: none;
animation: ringPulse $duration $easing infinite;
}
.controls__input::placeholder {
color: $trust-slate;
opacity: .65;
}
.controls__btn {
border: 0;
border-radius: $r-control;
background: $trust-blue;
color: $stage-paper;
padding: $sp-2 $sp-5;
font-weight: 700; font-size: 13px; line-height: 1;
font-family: inherit;
box-shadow: 0 6px 14px rgba($trust-blue, .28);
animation: btnDarken $duration $easing infinite;
}
// 데모: 입력창은 포커스를 받은 것처럼 얇은 테두리가 두 겹 파란 링으로 부드럽게 퍼졌다 가라앉고, 버튼은 배경이 살짝 짙어지며 1px 떠오른다
@keyframes ringPulse {
0%, 60%, 100% { border-color: #e2e8f0; box-shadow: none; }
25% { border-color: $trust-blue; box-shadow: 0 0 0 4px rgba(47, 109, 246, .18); }
}
@keyframes btnDarken {
0%, 60%, 100% { transform: translateY(0); background: $trust-blue; box-shadow: 0 6px 14px rgba(47, 109, 246, .28); }
25% { transform: translateY(-1px); background: #7386ba; box-shadow: 0 10px 18px rgba(29, 78, 216, .34); }
}
@media (max-width: 340px), (max-height: 220px) {
.controls { gap: $sp-2; }
.controls__input { padding: $sp-1 $sp-2; }
.controls__btn { padding: $sp-1 $sp-4; }
}
@media (prefers-reduced-motion: reduce) {
.controls__input, .controls__btn { animation: none !important; transform: none; }
.controls__input { border-color: #e2e8f0; box-shadow: none; }
.controls__btn { background: $trust-blue; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #eaf6f1;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.controls {
box-sizing: border-box;
width: clamp(220px, 78vw, 280px);
display: flex;
flex-direction: column;
gap: 16px;
}
.controls__field {
display: flex;
flex-direction: column;
gap: 4px;
}
.controls__label {
color: #475569;
font-weight: 600;
font-size: 11px;
line-height: 1.4;
font-family: inherit;
}
.controls__input {
box-sizing: border-box;
width: 100%;
border: 1.5px solid #e2e8f0;
border-radius: 8px;
background: #ffffff;
color: #17141a;
padding: 8px 12px;
font-weight: 500;
font-size: 13px;
line-height: 1.4;
font-family: inherit;
outline: none;
animation: ringPulse 2s ease-out infinite;
}
.controls__input::placeholder {
color: #475569;
opacity: 0.65;
}
.controls__btn {
border: 0;
border-radius: 12px;
background: #2f6df6;
color: #ffffff;
padding: 8px 20px;
font-weight: 700;
font-size: 13px;
line-height: 1;
font-family: inherit;
box-shadow: 0 6px 14px rgba(47, 109, 246, 0.28);
animation: btnDarken 2s ease-out infinite;
}
@keyframes ringPulse {
0%, 60%, 100% {
border-color: #e2e8f0;
box-shadow: none;
}
25% {
border-color: #2f6df6;
box-shadow: 0 0 0 4px rgba(47, 109, 246, 0.18);
}
}
@keyframes btnDarken {
0%, 60%, 100% {
transform: translateY(0);
background: #2f6df6;
box-shadow: 0 6px 14px rgba(47, 109, 246, 0.28);
}
25% {
transform: translateY(-1px);
background: #7386ba;
box-shadow: 0 10px 18px rgba(29, 78, 216, 0.34);
}
}
@media (max-width: 340px), (max-height: 220px) {
.controls {
gap: 8px;
}
.controls__input {
padding: 4px 8px;
}
.controls__btn {
padding: 4px 16px;
}
}
@media (prefers-reduced-motion: reduce) {
.controls__input, .controls__btn {
animation: none !important;
transform: none;
}
.controls__input {
border-color: #e2e8f0;
box-shadow: none;
}
.controls__btn {
background: #2f6df6;
}
}import type { CSSProperties } from "react";
import styles from "./ButtonsInputsTrust.module.scss";
export type ButtonsInputsTrustProps = {
label?: string;
placeholder?: string;
cta?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function ButtonsInputsTrust({
label = "이메일",
placeholder = "[email protected]",
cta = "계속하기",
duration = 2000,
easing = "ease-out",
color = "#2f6df6",
}: ButtonsInputsTrustProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.controls} style={vars}>
<label className={styles.field}>
<span className={styles.label}>{label}</span>
<input className={styles.input} type="text" placeholder={placeholder} />
</label>
<button type="button" className={styles.btn}>{cta}</button>
</div>
);
}09 · Clean buttons & inputs — Measured area 10.641% · moving frames 12/23Original by GODRICH · SCSS and React ship in the zip · See the password
What to change
This one item bundles the whole kit's tactile feel into a single pair of controls.
<input class="w-full rounded-control border border-border bg-white px-4 py-3
text-sm outline-none focus:border-brand
focus:ring-4 focus:ring-brand/20" placeholder="[email protected]">
<button class="rounded-control bg-brand px-6 py-3 text-sm font-bold text-white
shadow-brand hover:bg-brand-deep hover:-translate-y-px">Continue</button>
Custom tailwind.config
Two backgrounds, one brand blue, and four shadows — two neutral, two brand-tinted — live in this one file so the one-pager and all nine pieces stay equally restrained. The gray shadow-soft and shadow-soft-sm sit under plain cards, while the blue-tinted shadow-brand and shadow-brand-deep mark only the buttons, the pro plan, and the CTA band.
theme: {
extend: {
colors: {
offwhite: "#f7f9fc", bluetint: "#eaf1ff", graytint: "#f1f5f9", mint: "#eaf6f1",
ink: "#17141a", slate: "#475569", brand: "#2f6df6", "brand-deep": "#1d4ed8",
border: "#e2e8f0",
},
borderRadius: { card: "16px", control: "12px", pill: "999px" },
boxShadow: {
soft: "0 14px 28px rgba(23,20,26,.08)",
"soft-sm": "0 8px 16px rgba(23,20,26,.07)",
brand: "0 10px 22px rgba(47,109,246,.28)",
"brand-deep": "0 16px 30px rgba(29,78,216,.32)",
},
},
},
Vibe-Coding Prompt
Once the config is in your project, this is the entire instruction you hand to Claude Code or Cursor. It runs ten rules instead of five because, unlike the candy or citrus kits, it has to spell out what not to use. The unabridged version sits inside the zip as prompt.md.
Use only two backgrounds: white and offwhite (#f7f9fc). The only brand color is
brand (#2f6df6), and every card stays white. Shadows stay a soft gray
(shadow-soft) — never a colored shadow. Text is always ink or slate, never
brand. Hierarchy comes from border weight and shadow depth, not card color.
Here's the full 1280px screenshot of that prompt applied to page/index.html.

Where it breaks (the trap)
The first real snag was the nav indicator's motion. A 3px underline alone barely read as movement once rendered — the changed-pixel area was too small to notice on an 480×300 stage. Adding a pale blue highlight block the width of a tab, moving on the same beat as the underline, is what actually made the motion visible.
The second was viewport width. With three 108px tabs plus side padding, the nav's real layout box came out to 332px — 12px over the 320px phone check even though nothing looked wrong at 480px. Shrinking each tab to 92px and the whole bar to 284px is what cleared the 320×200 overflow check. The third was one word: "Enterprise" wrapped mid-word inside a 92px pricing card until it was shortened to "Team" so it fit on a single line. How far this restrained blue holds up inside a mail body was checked separately in Marketing Email Design. All three fixes are in the zip behind the password 8h4q5uur — open it and you can confirm every one directly.
All nine demos check for the MDN prefers-reduced-motion media feature and, when it's set, freeze every element at its resting state — cards stay put, list rows stay visible, focus rings stay off. Visitors who've asked for less motion lose the drift and the scroll, not the information in the card, button, or input itself.
Accessibility
This style has an easier time with contrast than the pastel or candy kits simply because the background is almost always white or off-white. Measured directly: ink text (#17141a) hits 18.24:1 on white, and the slate (#475569) used for secondary text hits 7.58:1 — both comfortably clear the WCAG 2.1 minimum contrast ratio of 4.5:1 for body text. The brand blue (#2f6df6), though, only reaches 4.53:1 as text on white — right at the line — so this kit's code never uses it for body copy, only for buttons, icons, and borders where it covers enough area or weight to stay legible. Where blue needs to be a background (the CTA band), the code switches to the deeper blue-deep (#1d4ed8), which clears white text at 6.70:1. Shadows follow the MDN box-shadow syntax at low opacity (.07–.32) so they never double as a border, and keyboard focus is drawn separately with a :focus-visible outline and a two-layer blue ring.
For other homepage trends beyond trustworthy SaaS, browse the design trends category, and the about page explains what this whole blog covers.
FAQ
Why don't these saas landing page design examples look like pastel candy?
Because the brightness comes from a different place. Pastel-candy kits get their brightness from many colors — pink, lemon, mint, lavender — one per card. This kit fixes the background at two neutral tones and adds exactly one brand color, so it can be bright while keeping the color count to a minimum. More colors read as playful; fewer read as serious. Building trust out of other people's voices rather than color is what Testimonial Section Design handles.
Can I swap in a different brand color instead of blue?
Yes, but if that color is going to sit as text on a white background, you have to check its 4.5:1 contrast yourself. This kit's blue (#2f6df6) sits right at 4.53:1, which is why it's never used for body copy — a green or purple at the same lightness could land under the line, so don't carry the ratio over without recalculating it.
Does adding a light color to each card still count as this style?
No — once a card's own surface takes on a color, it has moved into the candy or fruit family of bright design. In this kit, cards stay white; light tints only ever sit on the stage behind them or inside small accents like an icon circle.









