07유영하는 오브 CTA 밴드
밴드 뒤편에서 큰 오브 하나가 천천히 좌우로 왕복하며 유영하고(translateX), 앞의 솔리드 버튼은 마우스를 올리면 그림자가 깊어지며 1px 떠오릅니다. 빛나는 버튼은 쓰지 않습니다.
쓰임가입 유도 띠, 데모 신청 CTA
translateXradial-gradienttranslateY

<main class="stage">
<section class="cb is-demo" id="cb">
<!-- 오브는 순수 장식 — 낭독기에서 숨긴다 -->
<span class="cb__orb" aria-hidden="true"></span>
<p class="cb__eyebrow">
<span class="cb__ico" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M194.82,151.43l-55.09,20.3-20.3,55.09a7.92,7.92,0,0,1-14.86,0l-20.3-55.09-55.09-20.3a7.92,7.92,0,0,1,0-14.86l55.09-20.3,20.3-55.09a7.92,7.92,0,0,1,14.86,0l20.3,55.09,55.09,20.3A7.92,7.92,0,0,1,194.82,151.43Z" opacity="0.2"/><path d="M197.58,129.06,146,110l-19-51.62a15.92,15.92,0,0,0-29.88,0L78,110l-51.62,19a15.92,15.92,0,0,0,0,29.88L78,178l19,51.62a15.92,15.92,0,0,0,29.88,0L146,178l51.62-19a15.92,15.92,0,0,0,0-29.88ZM137,164.22a8,8,0,0,0-4.74,4.74L112,223.85,91.78,169A8,8,0,0,0,87,164.22L32.15,144,87,123.78A8,8,0,0,0,91.78,119L112,64.15,132.22,119a8,8,0,0,0,4.74,4.74L191.85,144ZM144,40a8,8,0,0,1,8-8h16V16a8,8,0,0,1,16,0V32h16a8,8,0,0,1,0,16H184V64a8,8,0,0,1-16,0V48H152A8,8,0,0,1,144,40ZM248,88a8,8,0,0,1-8,8h-8v8a8,8,0,0,1-16,0V96h-8a8,8,0,0,1,0-16h8V72a8,8,0,0,1,16,0v8h8A8,8,0,0,1,248,88Z"/></svg></span>
무료 체험 열림
</p>
<h2 class="cb__title">첫 프롬프트를 오늘 안에 돌려보세요</h2>
<p class="cb__sub">카드 등록 없이 5분이면 팀 작업실이 열립니다.</p>
<button type="button" class="cb__cta" id="cbCta">
무료로 시작하기
<span class="cb__ico cb__ico--sm" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,128l-72,72V56Z" opacity="0.2"/><path d="M221.66,122.34l-72-72A8,8,0,0,0,136,56v64H40a8,8,0,0,0,0,16h96v64a8,8,0,0,0,13.66,5.66l72-72A8,8,0,0,0,221.66,122.34ZM152,180.69V75.31L204.69,128Z"/></svg></span>
</button>
<p class="cb__note">언제든 해지할 수 있어요</p>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 오브가 좌 → 우 → 좌 를 한 바퀴 도는 시간
$easing: $ease-spring; // 버튼이 떠오르는 이징
$color: $subject-lilac; // 오브 핵심 색
@include stage($stage-orange);
.cb {
position: relative; overflow: hidden; // 오브가 밴드 밖으로 새지 않게
box-sizing: border-box; width: min(100%, 420px);
background: $subject-ink; border-radius: $r-card; box-shadow: $shadow-raised;
padding: $sp-5; text-align: center;
}
// ── 오브: 겹친 radial-gradient 한 덩어리. 필터도 이미지도 없이 그라디언트만으로 공처럼 보이게 한다
// 핵심 알파를 .55 로 묶으면 오브 한가운데가 가장 밝을 때도 글자가 AA 를 넘는다 —
// 렌더 픽셀 실측(오브를 가운데 세우고 글자를 감춘 뒤, 요소마다 그 띠에서 가장 밝은 배경을 샘플).
// 네 요소는 오브의 서로 다른 높이에 걸치므로 배경도 각각 다르다 — 한 배경을 공유하지 않는다:
// 제목 크림 100% / #53528a → 6.70:1
// 부제 크림 100% / #6e6096 → 5.22:1 (오브 핵을 정면으로 지나 가장 밝은 자리)
// 눈썹 크림 72% / #475c90 → 6.32:1
// 주석 크림 62% / #1e305f → 5.59:1
// 부제만 크림 72% 로 두었을 때 3.58:1 이라 알파를 100% 로 올렸다 (11px 500 은 큰 글자 예외도 못 받는다).
// 위계는 색이 아니라 크기(19px↔11px)와 굵기(800↔500)가 만든다. (오브 없는 잉크 바탕에서는 17.11:1)
.cb__orb {
position: absolute; left: 50%; top: 50%;
margin-left: -104px; margin-top: -104px; // 자기 크기의 절반만큼 당겨 가운데로
width: 208px; height: 208px; border-radius: 50%; z-index: 0; pointer-events: none;
background: radial-gradient(circle at 50% 50%,
rgba($color, .55) 0%,
rgba($subject-blue, .40) 38%,
rgba($subject-blue, 0) 70%);
// 유영은 부품의 기본 성질 — .is-demo 와 무관하게 항상 돈다
animation-name: orbDrift;
animation-duration: $duration;
animation-timing-function: ease-in-out; // 왕복이라 양 끝에서 느려져야 한다 ($ease-out 은 편도용)
animation-iteration-count: infinite;
}
// 왕복이라 되돌아오는 경로가 그대로 보인다 — 끝에서 처음으로 튀는 구간이 없다
@keyframes orbDrift {
0% { transform: translateX(-34%); }
50% { transform: translateX(34%); }
100% { transform: translateX(-34%); }
}
.cb__eyebrow,
.cb__title,
.cb__sub,
.cb__cta,
.cb__note { position: relative; z-index: 1; }
.cb__eyebrow {
display: inline-flex; align-items: center; gap: $sp-1; margin: 0;
font-weight: 800; font-size: clamp(9px, 2.6vw, 10px); line-height: 1;
letter-spacing: .04em; color: rgba($subject-cream, .72); // 눈썹 띠 최악 배경 #475c90 위 6.32:1
}
.cb__ico { display: inline-flex; width: 14px; height: 14px; flex: 0 0 auto; }
.cb__ico--sm { width: 12px; height: 12px; }
.cb__ico svg { width: 100%; height: 100%; }
.cb__title {
margin: $sp-2 0 0; font-weight: 800; font-size: clamp(14px, 4.2vw, 19px); line-height: 1.25;
color: $subject-cream; // 제목 띠 최악 배경 #53528a 위 6.70:1 (잉크 바탕 17.11:1)
}
.cb__sub {
margin: $sp-2 0 0; font-weight: 500; font-size: clamp(9px, 2.8vw, 11px); line-height: 1.45;
color: $subject-cream; // 부제 띠 최악 배경 #6e6096 위 5.22:1 (72% 였을 때 3.58:1 미달)
}
// ── 솔리드 버튼: 빛나지 않는다. 떠오름은 transform, 깊이는 그림자
.cb__cta {
display: inline-flex; align-items: center; gap: $sp-2; margin-top: $sp-4;
padding: $sp-3 $sp-6; border: 0; border-radius: $r-control; cursor: pointer;
background: $subject-cream; color: $subject-ink; // 17.11:1
font-family: inherit; font-weight: 800; font-size: clamp(11px, 3.2vw, 13px); line-height: 1;
box-shadow: $shadow-press;
transition: transform $dur-quick $easing, box-shadow $dur-quick $easing;
}
.cb__cta:hover,
.cb__cta:focus-visible { transform: translateY(-1px); box-shadow: $shadow-raised; }
.cb__cta:active { transform: translateY(0); box-shadow: $shadow-press; }
.cb__note {
margin: $sp-2 0 0; font-weight: 600; font-size: clamp(8px, 2.4vw, 9px); line-height: 1;
color: rgba($subject-cream, .62); // 주석 띠 최악 배경 #1e305f 위 5.59:1
}
// ── 미리보기 루프: 호버를 못 하는 격자 칸에서도 "떠오르는 버튼" 을 보여준다 (복귀 90% 안)
.cb.is-demo .cb__cta {
animation-name: ctaLift;
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
}
@keyframes ctaLift {
0%, 40% { transform: translateY(0); box-shadow: $shadow-press; }
54%, 84% { transform: translateY(-1px); box-shadow: $shadow-raised; }
90%, 100% { transform: translateY(0); box-shadow: $shadow-press; }
}
// ── 320×200 폰 실측 대비 압축 (BD9). 이 블록 안에서는 간격을 올리지 않는다
@media (max-width: 340px), (max-height: 220px) {
.cb { padding: $sp-3; }
.cb__orb { width: 160px; height: 160px; margin-left: -80px; margin-top: -80px; }
.cb__eyebrow { font-size: 8px; }
.cb__ico { width: 12px; height: 12px; }
.cb__title { font-size: 12px; margin-top: $sp-1; }
.cb__sub { font-size: 8px; margin-top: $sp-1; }
.cb__cta { margin-top: $sp-2; padding: $sp-2 $sp-4; font-size: 10px; }
.cb__note { font-size: 7px; margin-top: $sp-1; }
}
@media (prefers-reduced-motion: reduce) {
.cb__orb { animation: none !important; transform: none !important; }
.cb.is-demo .cb__cta { animation: none !important; }
.cb__cta { transition: none !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.cb {
position: relative;
overflow: hidden;
box-sizing: border-box;
width: min(100%, 420px);
background: #17141a;
border-radius: 16px;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
padding: 20px;
text-align: center;
}
.cb__orb {
position: absolute;
left: 50%;
top: 50%;
margin-left: -104px;
margin-top: -104px;
width: 208px;
height: 208px;
border-radius: 50%;
z-index: 0;
pointer-events: none;
background: radial-gradient(circle at 50% 50%, rgba(185, 160, 255, 0.55) 0%, rgba(47, 109, 246, 0.4) 38%, rgba(47, 109, 246, 0) 70%);
animation-name: orbDrift;
animation-duration: 2s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
@keyframes orbDrift {
0% {
transform: translateX(-34%);
}
50% {
transform: translateX(34%);
}
100% {
transform: translateX(-34%);
}
}
.cb__eyebrow,
.cb__title,
.cb__sub,
.cb__cta,
.cb__note {
position: relative;
z-index: 1;
}
.cb__eyebrow {
display: inline-flex;
align-items: center;
gap: 4px;
margin: 0;
font-weight: 800;
font-size: clamp(9px, 2.6vw, 10px);
line-height: 1;
letter-spacing: 0.04em;
color: rgba(255, 247, 230, 0.72);
}
.cb__ico {
display: inline-flex;
width: 14px;
height: 14px;
flex: 0 0 auto;
}
.cb__ico--sm {
width: 12px;
height: 12px;
}
.cb__ico svg {
width: 100%;
height: 100%;
}
.cb__title {
margin: 8px 0 0;
font-weight: 800;
font-size: clamp(14px, 4.2vw, 19px);
line-height: 1.25;
color: #fff7e6;
}
.cb__sub {
margin: 8px 0 0;
font-weight: 500;
font-size: clamp(9px, 2.8vw, 11px);
line-height: 1.45;
color: #fff7e6;
}
.cb__cta {
display: inline-flex;
align-items: center;
gap: 8px;
margin-top: 16px;
padding: 12px 24px;
border: 0;
border-radius: 12px;
cursor: pointer;
background: #fff7e6;
color: #17141a;
font-family: inherit;
font-weight: 800;
font-size: clamp(11px, 3.2vw, 13px);
line-height: 1;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.cb__cta:hover,
.cb__cta:focus-visible {
transform: translateY(-1px);
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
.cb__cta:active {
transform: translateY(0);
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
.cb__note {
margin: 8px 0 0;
font-weight: 600;
font-size: clamp(8px, 2.4vw, 9px);
line-height: 1;
color: rgba(255, 247, 230, 0.62);
}
.cb.is-demo .cb__cta {
animation-name: ctaLift;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
}
@keyframes ctaLift {
0%, 40% {
transform: translateY(0);
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
54%, 84% {
transform: translateY(-1px);
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
90%, 100% {
transform: translateY(0);
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
}
@media (max-width: 340px), (max-height: 220px) {
.cb {
padding: 12px;
}
.cb__orb {
width: 160px;
height: 160px;
margin-left: -80px;
margin-top: -80px;
}
.cb__eyebrow {
font-size: 8px;
}
.cb__ico {
width: 12px;
height: 12px;
}
.cb__title {
font-size: 12px;
margin-top: 4px;
}
.cb__sub {
font-size: 8px;
margin-top: 4px;
}
.cb__cta {
margin-top: 8px;
padding: 8px 16px;
font-size: 10px;
}
.cb__note {
font-size: 7px;
margin-top: 4px;
}
}
@media (prefers-reduced-motion: reduce) {
.cb__orb {
animation: none !important;
transform: none !important;
}
.cb.is-demo .cb__cta {
animation: none !important;
}
.cb__cta {
transition: none !important;
}
}// 사람이 손을 대면 미리보기 루프를 끄고 진짜 호버·포커스에 넘긴다
var band = document.getElementById('cb');
var cta = document.getElementById('cbCta');
function stopPreview() { band.classList.remove('is-demo'); }
cta.addEventListener('pointerenter', stopPreview);
cta.addEventListener('focus', stopPreview);
cta.addEventListener('click', stopPreview);import { useState } from "react";
import styles from "./CtaBandOrb.module.scss";
export type CtaBandOrbProps = {
/** 오브가 좌 → 우 → 좌 를 한 바퀴 도는 시간 */
duration?: string;
/** 버튼이 떠오르는 이징 */
easing?: string;
/** 오브 핵심 색 — --orbColor 로 내려간다 */
color?: string;
};
export default function CtaBandOrb({
duration = "2s",
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#b9a0ff",
}: CtaBandOrbProps) {
// 사람이 손을 대면 미리보기 루프를 끄고 진짜 호버·포커스에 넘긴다
const [preview, setPreview] = useState(true);
const stopPreview = () => { setPreview(false); };
return (
<main className={styles.stage}>
<section className={styles.cb} style={{ "--orbColor": color }}>
{/* 오브는 순수 장식 — 낭독기에서 숨긴다 */}
<span
className={styles.orb}
aria-hidden="true"
style={{
animationName: "orbDrift",
animationDuration: duration,
animationTimingFunction: "ease-in-out",
animationIterationCount: "infinite",
}}
/>
<p className={styles.eyebrow}>
<span className={styles.ico} aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M194.82,151.43l-55.09,20.3-20.3,55.09a7.92,7.92,0,0,1-14.86,0l-20.3-55.09-55.09-20.3a7.92,7.92,0,0,1,0-14.86l55.09-20.3,20.3-55.09a7.92,7.92,0,0,1,14.86,0l20.3,55.09,55.09,20.3A7.92,7.92,0,0,1,194.82,151.43Z" opacity="0.2"/><path d="M197.58,129.06,146,110l-19-51.62a15.92,15.92,0,0,0-29.88,0L78,110l-51.62,19a15.92,15.92,0,0,0,0,29.88L78,178l19,51.62a15.92,15.92,0,0,0,29.88,0L146,178l51.62-19a15.92,15.92,0,0,0,0-29.88ZM137,164.22a8,8,0,0,0-4.74,4.74L112,223.85,91.78,169A8,8,0,0,0,87,164.22L32.15,144,87,123.78A8,8,0,0,0,91.78,119L112,64.15,132.22,119a8,8,0,0,0,4.74,4.74L191.85,144ZM144,40a8,8,0,0,1,8-8h16V16a8,8,0,0,1,16,0V32h16a8,8,0,0,1,0,16H184V64a8,8,0,0,1-16,0V48H152A8,8,0,0,1,144,40ZM248,88a8,8,0,0,1-8,8h-8v8a8,8,0,0,1-16,0V96h-8a8,8,0,0,1,0-16h8V72a8,8,0,0,1,16,0v8h8A8,8,0,0,1,248,88Z"/></svg></span>
무료 체험 열림
</p>
<h2 className={styles.title}>첫 프롬프트를 오늘 안에 돌려보세요</h2>
<p className={styles.sub}>카드 등록 없이 5분이면 팀 작업실이 열립니다.</p>
<button
type="button"
className={styles.cta}
style={preview ? {
animationName: "ctaLift",
animationDuration: duration,
animationTimingFunction: easing,
animationIterationCount: "infinite",
} : undefined}
onPointerEnter={stopPreview}
onFocus={stopPreview}
onClick={stopPreview}
>
무료로 시작하기
<span className={`${styles.ico} ${styles.icoSm}`} aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,128l-72,72V56Z" opacity="0.2"/><path d="M221.66,122.34l-72-72A8,8,0,0,0,136,56v64H40a8,8,0,0,0,0,16h96v64a8,8,0,0,0,13.66,5.66l72-72A8,8,0,0,0,221.66,122.34ZM152,180.69V75.31L204.69,128Z"/></svg></span>
</button>
<p className={styles.note}>언제든 해지할 수 있어요</p>
</section>
</main>
);
}07 유영하는 오브 CTA 밴드 — 측정 누적면적 33.242% · 움직인 프레임 18/23GODRICH 원작 · zip 에 SCSS·React 포함 · ai 랜딩페이지 오브 디자인 키트, 빛나는 섹션 9개
이 데모가 속한 글
ai 랜딩페이지 오브 디자인 키트, 빛나는 섹션 9개ai 랜딩페이지 오브 디자인은 화면 전체를 물들이는 대신 짙은 단색 무대 위에 빛나는 구슬 하나만 올려 제품의 성격을 말하는 방식입니다. 배경 그라디언트 없이도 AI 다워 보인다는 이 계산을 섹션 아홉 개로 잘라 정 이 글의 데모 전부 보기


