09최종 CTA 이중 선택
무료로 시작과 데모 상담 두 카드가 좌우 화살표와 클릭으로도 선택되는데, 고른 쪽이 앞으로 나오며 각자 다음 단계 미리보기가 그 아래 바뀌어 나타납니다.
쓰임랜딩 페이지 마지막 섹션, 셀프서비스와 영업 상담 갈림길
aria-checkedscaletranslateX

<main class="stage is-demo" id="stage">
<div class="dc">
<p class="dc__title">어떻게 시작할까요?</p>
<div class="dc__cards" role="radiogroup" aria-label="시작 방법" id="dc-group">
<div class="dc__cell">
<input class="dc__radio" type="radio" id="dc-a" name="dc271" value="free" checked>
<label class="dc__label" for="dc-a">
<span class="dc__ring" aria-hidden="true"></span>
<span class="dc__ic" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="22" height="22"><g fill="currentColor"><path d="M184 120v61.65a8 8 0 0 1-2.34 5.65l-34.35 34.35a8 8 0 0 1-13.57-4.53L128 176Zm-48-48H74.35a8 8 0 0 0-5.65 2.34l-34.35 34.35a8 8 0 0 0 4.53 13.57L80 128ZM40 216c37.65 0 50.69-19.69 54.56-28.18l-26.38-26.38C59.69 165.31 40 178.35 40 216" opacity=".2"/><path d="M223.85 47.12a16 16 0 0 0-15-15c-12.58-.75-44.73.4-71.41 27.07L132.69 64H74.36A15.9 15.9 0 0 0 63 68.68L28.7 103a16 16 0 0 0 9.07 27.16l38.47 5.37l44.21 44.21l5.37 38.49a15.94 15.94 0 0 0 10.78 12.92a16.1 16.1 0 0 0 5.1.83a15.9 15.9 0 0 0 11.3-4.68l34.32-34.3a15.9 15.9 0 0 0 4.68-11.36v-58.33l4.77-4.77c26.68-26.68 27.83-58.83 27.08-71.42M74.36 80h42.33l-39.53 39.52L40 114.34Zm74.41-9.45a76.65 76.65 0 0 1 59.11-22.47a76.46 76.46 0 0 1-22.42 59.16L128 164.68L91.32 128ZM176 181.64L141.67 216l-5.19-37.17L176 139.31Zm-74.16 9.5C97.34 201 82.29 224 40 224a8 8 0 0 1-8-8c0-42.29 23-57.34 32.86-61.85a8 8 0 0 1 6.64 14.56c-6.43 2.93-20.62 12.36-23.12 38.91c26.55-2.5 36-16.69 38.91-23.12a8 8 0 1 1 14.56 6.64Z"/></g></svg>
</span>
<span class="dc__name">무료로 시작하기</span>
<span class="dc__sub">가입 없이 30초</span>
<span class="dc__arrow" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="14" height="14"><g fill="currentColor"><path d="m216 128l-72 72V56Z" opacity=".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 0-11.32M152 180.69V75.31L204.69 128Z"/></g></svg>
</span>
</label>
</div>
<div class="dc__cell">
<input class="dc__radio" type="radio" id="dc-b" name="dc271" value="demo">
<label class="dc__label" for="dc-b">
<span class="dc__ring" aria-hidden="true"></span>
<span class="dc__ic" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="22" height="22"><g fill="currentColor"><path d="M80 144v40a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16v-56h32a16 16 0 0 1 16 16m112-16a16 16 0 0 0-16 16v40a16 16 0 0 0 16 16h32v-72Z" opacity=".2"/><path d="M201.89 54.66A104.08 104.08 0 0 0 24 128v56a24 24 0 0 0 24 24h16a24 24 0 0 0 24-24v-40a24 24 0 0 0-24-24H40.36a88.12 88.12 0 0 1 150.18-54.07A87.4 87.4 0 0 1 215.65 120H192a24 24 0 0 0-24 24v40a24 24 0 0 0 24 24h24a24 24 0 0 1-24 24h-56a8 8 0 0 0 0 16h56a40 40 0 0 0 40-40v-80a103.4 103.4 0 0 0-30.11-73.34M64 136a8 8 0 0 1 8 8v40a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8v-48Zm128 56a8 8 0 0 1-8-8v-40a8 8 0 0 1 8-8h24v56Z"/></g></svg>
</span>
<span class="dc__name">데모 상담 잡기</span>
<span class="dc__sub">20분 화상 미팅</span>
<span class="dc__arrow" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="14" height="14"><g fill="currentColor"><path d="m216 128l-72 72V56Z" opacity=".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 0-11.32M152 180.69V75.31L204.69 128Z"/></g></svg>
</span>
</label>
</div>
</div>
<button type="button" class="dc__pv" id="dc-pv">
<span class="dc__pv-t dc__pv-t--a" id="dc-pv-a">다음: 템플릿 골라 첫 화면 만들기</span>
<span class="dc__pv-t dc__pv-t--b" id="dc-pv-b" aria-hidden="true">다음: 편한 시간 골라 20분 잡기</span>
<span class="dc__pv-t dc__pv-t--go" id="dc-pv-go" aria-hidden="true">시작하기 »</span>
</button>
<p class="dc__msg" role="status" id="dc-msg">
<span class="dc__msg-t dc__msg-t--a" id="dc-msg-a">무료 시작을 골랐어요</span>
<span class="dc__msg-t dc__msg-t--b" id="dc-msg-b" aria-hidden="true">상담 예약을 골랐어요</span>
</p>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// 변수 3개 — 이 데모의 지속시간·이징·포인트색
$duration: $dur-loop;
$easing: $ease-pop;
$color: $subject-blue;
@include stage($stage-ink);
.stage * { box-sizing: border-box; }
// ── 정적 기본 화면: ① 선택 상태로 전부 보인다 ──
.dc { width: min(360px, 94vw); background: $subject-cream; color: $subject-ink; border-radius: $r-card; padding: $sp-4; box-shadow: $shadow-raised; }
.dc__title { margin: 0 0 $sp-3; text-align: center; font-weight: 800; font-size: 15px; line-height: 1.3; font-family: inherit; }
.dc__cards { display: grid; grid-template-columns: 1fr 1fr; gap: $sp-3; }
.dc__cell { position: relative; }
.dc__radio { position: absolute; width: 1px; height: 1px; margin: 0; padding: 0; opacity: 0; }
.dc__label { position: relative; display: grid; justify-items: center; gap: $sp-1; padding: $sp-3 $sp-2; border: 1.5px solid rgba(23, 20, 26, .18); border-radius: $r-card; background: $stage-paper; cursor: pointer; transform: scale(.97); transition: transform $dur-quick $easing; }
.dc__ring { position: absolute; inset: 0; border: 2px solid $color; border-radius: $r-card; background: rgba(47, 109, 246, .12); opacity: 0; pointer-events: none; }
.dc__ic { position: relative; display: grid; place-items: center; color: rgba(23, 20, 26, .5); }
.dc__name { position: relative; font-weight: 800; font-size: 12px; line-height: 1.3; text-align: center; }
.dc__sub { position: relative; font-size: 10px; line-height: 1.3; color: rgba(23, 20, 26, .6); text-align: center; }
.dc__arrow { position: relative; display: grid; place-items: center; color: $color; transform: translateX(0); transition: transform $dur-quick $easing; }
.dc__radio:checked + .dc__label { transform: scale(1.02); }
.dc__radio:checked + .dc__label .dc__ring { opacity: 1; }
.dc__radio:checked + .dc__label .dc__ic { color: $color; }
.dc__radio:checked + .dc__label .dc__arrow { transform: translateX(4px); }
.dc__radio:focus-visible + .dc__label { outline: 2px solid $color; outline-offset: 2px; }
.dc__pv { display: grid; width: 100%; margin: $sp-3 0 0; padding: $sp-2 $sp-3; border: 0; border-radius: $r-control; background: $subject-ink; color: $subject-cream; font-weight: 700; font-size: 12px; line-height: 1.4; font-family: inherit; cursor: pointer; }
.dc__pv-t { grid-area: 1 / 1; opacity: 0; }
.dc__pv-t--a { opacity: 1; }
.dc__pv.is-b .dc__pv-t--a { opacity: 0; }
.dc__pv.is-b .dc__pv-t--b { opacity: 1; }
.dc__pv.is-armed .dc__pv-t--a, .dc__pv.is-armed .dc__pv-t--b { opacity: 0; }
.dc__pv.is-armed .dc__pv-t--go { opacity: 1; }
.dc__pv:focus-visible { outline: 2px solid $color; outline-offset: 2px; }
.dc__msg { display: grid; margin: $sp-2 0 0; min-height: 13px; text-align: center; font-size: 11px; line-height: 1.3; color: rgba(23, 20, 26, .6); font-family: inherit; }
.dc__msg-t { grid-area: 1 / 1; opacity: 0; }
.dc__msg-t--a { opacity: 1; }
.dc__msg.is-b .dc__msg-t--a { opacity: 0; }
.dc__msg.is-b .dc__msg-t--b { opacity: 1; }
// ── 측정용 자동 루프: 선택 교체는 steps 컷, 카드 scale·화살표 translateX 가 보조 ──
.is-demo .dc__cell:first-child .dc__ring { animation: dc-ring-a $duration steps(1, end) infinite; }
@keyframes dc-ring-a {
0%, 41% { opacity: 1; }
42%, 83% { opacity: 0; }
84%, 100% { opacity: 1; }
}
.is-demo .dc__cell:last-child .dc__ring { animation: dc-ring-b $duration steps(1, end) infinite; }
@keyframes dc-ring-b {
0%, 41% { opacity: 0; }
42%, 83% { opacity: 1; }
84%, 100% { opacity: 0; }
}
.is-demo .dc__cell:first-child .dc__label { animation: dc-card-a $duration $easing infinite; }
@keyframes dc-card-a {
0%, 36% { transform: scale(1.02); }
48%, 78% { transform: scale(.97); }
88%, 100% { transform: scale(1.02); }
}
.is-demo .dc__cell:last-child .dc__label { animation: dc-card-b $duration $easing infinite; }
@keyframes dc-card-b {
0%, 36% { transform: scale(.97); }
48%, 78% { transform: scale(1.02); }
88%, 100% { transform: scale(.97); }
}
.is-demo .dc__cell:first-child .dc__arrow { animation: dc-arrow-a $duration $ease-spring infinite; }
@keyframes dc-arrow-a {
0%, 36% { transform: translateX(4px); }
48%, 78% { transform: translateX(0); }
88%, 100% { transform: translateX(4px); }
}
.is-demo .dc__cell:last-child .dc__arrow { animation: dc-arrow-b $duration $ease-spring infinite; }
@keyframes dc-arrow-b {
0%, 36% { transform: translateX(0); }
48%, 78% { transform: translateX(4px); }
88%, 100% { transform: translateX(0); }
}
.is-demo .dc__pv-t--a { animation: dc-ring-a $duration steps(1, end) infinite; }
.is-demo .dc__pv-t--b { animation: dc-ring-b $duration steps(1, end) infinite; }
.is-demo .dc__msg-t--a { animation: dc-ring-a $duration steps(1, end) infinite; }
.is-demo .dc__msg-t--b { animation: dc-ring-b $duration steps(1, end) infinite; }
.is-demo .dc__pv { animation: dc-pv-pop $duration $easing infinite; }
@keyframes dc-pv-pop {
0%, 42%, 56%, 84%, 100% { transform: scale(1); }
46% { transform: scale(1.04); }
88% { transform: scale(1.04); }
}
// ── 접근성 ──────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.dc__label, .dc__arrow, .dc__ring, .dc__pv, .dc__pv-t,
.is-demo .dc__cell:first-child .dc__ring, .is-demo .dc__cell:last-child .dc__ring,
.is-demo .dc__cell:first-child .dc__label, .is-demo .dc__cell:last-child .dc__label,
.is-demo .dc__cell:first-child .dc__arrow, .is-demo .dc__cell:last-child .dc__arrow,
.is-demo .dc__pv-t--a, .is-demo .dc__pv-t--b, .is-demo .dc__pv,
.is-demo .dc__msg-t--a, .is-demo .dc__msg-t--b {
animation: none !important; transition: none !important;
}
.is-demo .dc__cell:first-child .dc__ring, .is-demo .dc__pv-t--a, .is-demo .dc__msg-t--a { opacity: 1 !important; }
.is-demo .dc__cell:last-child .dc__ring, .is-demo .dc__pv-t--b, .is-demo .dc__msg-t--b { opacity: 0 !important; }
.is-demo .dc__cell:first-child .dc__label { transform: scale(1.02) !important; }
.is-demo .dc__cell:last-child .dc__label { transform: scale(.97) !important; }
}
// ── 반응형(320×200) ──────────────────────────────────
@media (max-width: 340px), (max-height: 220px) {
.dc { width: min(288px, 92vw); padding: $sp-2; }
.dc__title { margin: 0 0 $sp-2; font-size: 12px; }
.dc__cards { gap: $sp-2; }
.dc__label { gap: 0; padding: $sp-2 $sp-1; }
.dc__ic svg { width: 16px; height: 16px; }
.dc__name { font-size: 10px; }
.dc__sub { font-size: 9px; }
.dc__arrow svg { width: 11px; height: 11px; }
.dc__pv { margin: $sp-2 0 0; padding: $sp-1 $sp-2; font-size: 10px; }
.dc__msg { margin: $sp-1 0 0; min-height: 0; font-size: 9px; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.stage * {
box-sizing: border-box;
}
.dc {
width: min(360px, 94vw);
background: #fff7e6;
color: #17141a;
border-radius: 16px;
padding: 16px;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
.dc__title {
margin: 0 0 12px;
text-align: center;
font-weight: 800;
font-size: 15px;
line-height: 1.3;
font-family: inherit;
}
.dc__cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.dc__cell {
position: relative;
}
.dc__radio {
position: absolute;
width: 1px;
height: 1px;
margin: 0;
padding: 0;
opacity: 0;
}
.dc__label {
position: relative;
display: grid;
justify-items: center;
gap: 4px;
padding: 12px 8px;
border: 1.5px solid rgba(23, 20, 26, 0.18);
border-radius: 16px;
background: #ffffff;
cursor: pointer;
transform: scale(0.97);
transition: transform 220ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.dc__ring {
position: absolute;
inset: 0;
border: 2px solid #2f6df6;
border-radius: 16px;
background: rgba(47, 109, 246, 0.12);
opacity: 0;
pointer-events: none;
}
.dc__ic {
position: relative;
display: grid;
place-items: center;
color: rgba(23, 20, 26, 0.5);
}
.dc__name {
position: relative;
font-weight: 800;
font-size: 12px;
line-height: 1.3;
text-align: center;
}
.dc__sub {
position: relative;
font-size: 10px;
line-height: 1.3;
color: rgba(23, 20, 26, 0.6);
text-align: center;
}
.dc__arrow {
position: relative;
display: grid;
place-items: center;
color: #2f6df6;
transform: translateX(0);
transition: transform 220ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.dc__radio:checked + .dc__label {
transform: scale(1.02);
}
.dc__radio:checked + .dc__label .dc__ring {
opacity: 1;
}
.dc__radio:checked + .dc__label .dc__ic {
color: #2f6df6;
}
.dc__radio:checked + .dc__label .dc__arrow {
transform: translateX(4px);
}
.dc__radio:focus-visible + .dc__label {
outline: 2px solid #2f6df6;
outline-offset: 2px;
}
.dc__pv {
display: grid;
width: 100%;
margin: 12px 0 0;
padding: 8px 12px;
border: 0;
border-radius: 12px;
background: #17141a;
color: #fff7e6;
font-weight: 700;
font-size: 12px;
line-height: 1.4;
font-family: inherit;
cursor: pointer;
}
.dc__pv-t {
grid-area: 1/1;
opacity: 0;
}
.dc__pv-t--a {
opacity: 1;
}
.dc__pv.is-b .dc__pv-t--a {
opacity: 0;
}
.dc__pv.is-b .dc__pv-t--b {
opacity: 1;
}
.dc__pv.is-armed .dc__pv-t--a, .dc__pv.is-armed .dc__pv-t--b {
opacity: 0;
}
.dc__pv.is-armed .dc__pv-t--go {
opacity: 1;
}
.dc__pv:focus-visible {
outline: 2px solid #2f6df6;
outline-offset: 2px;
}
.dc__msg {
display: grid;
margin: 8px 0 0;
min-height: 13px;
text-align: center;
font-size: 11px;
line-height: 1.3;
color: rgba(23, 20, 26, 0.6);
font-family: inherit;
}
.dc__msg-t {
grid-area: 1/1;
opacity: 0;
}
.dc__msg-t--a {
opacity: 1;
}
.dc__msg.is-b .dc__msg-t--a {
opacity: 0;
}
.dc__msg.is-b .dc__msg-t--b {
opacity: 1;
}
.is-demo .dc__cell:first-child .dc__ring {
animation: dc-ring-a 2s steps(1, end) infinite;
}
@keyframes dc-ring-a {
0%, 41% {
opacity: 1;
}
42%, 83% {
opacity: 0;
}
84%, 100% {
opacity: 1;
}
}
.is-demo .dc__cell:last-child .dc__ring {
animation: dc-ring-b 2s steps(1, end) infinite;
}
@keyframes dc-ring-b {
0%, 41% {
opacity: 0;
}
42%, 83% {
opacity: 1;
}
84%, 100% {
opacity: 0;
}
}
.is-demo .dc__cell:first-child .dc__label {
animation: dc-card-a 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes dc-card-a {
0%, 36% {
transform: scale(1.02);
}
48%, 78% {
transform: scale(0.97);
}
88%, 100% {
transform: scale(1.02);
}
}
.is-demo .dc__cell:last-child .dc__label {
animation: dc-card-b 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes dc-card-b {
0%, 36% {
transform: scale(0.97);
}
48%, 78% {
transform: scale(1.02);
}
88%, 100% {
transform: scale(0.97);
}
}
.is-demo .dc__cell:first-child .dc__arrow {
animation: dc-arrow-a 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes dc-arrow-a {
0%, 36% {
transform: translateX(4px);
}
48%, 78% {
transform: translateX(0);
}
88%, 100% {
transform: translateX(4px);
}
}
.is-demo .dc__cell:last-child .dc__arrow {
animation: dc-arrow-b 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes dc-arrow-b {
0%, 36% {
transform: translateX(0);
}
48%, 78% {
transform: translateX(4px);
}
88%, 100% {
transform: translateX(0);
}
}
.is-demo .dc__pv-t--a {
animation: dc-ring-a 2s steps(1, end) infinite;
}
.is-demo .dc__pv-t--b {
animation: dc-ring-b 2s steps(1, end) infinite;
}
.is-demo .dc__msg-t--a {
animation: dc-ring-a 2s steps(1, end) infinite;
}
.is-demo .dc__msg-t--b {
animation: dc-ring-b 2s steps(1, end) infinite;
}
.is-demo .dc__pv {
animation: dc-pv-pop 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes dc-pv-pop {
0%, 42%, 56%, 84%, 100% {
transform: scale(1);
}
46% {
transform: scale(1.04);
}
88% {
transform: scale(1.04);
}
}
@media (prefers-reduced-motion: reduce) {
.dc__label, .dc__arrow, .dc__ring, .dc__pv, .dc__pv-t,
.is-demo .dc__cell:first-child .dc__ring, .is-demo .dc__cell:last-child .dc__ring,
.is-demo .dc__cell:first-child .dc__label, .is-demo .dc__cell:last-child .dc__label,
.is-demo .dc__cell:first-child .dc__arrow, .is-demo .dc__cell:last-child .dc__arrow,
.is-demo .dc__pv-t--a, .is-demo .dc__pv-t--b, .is-demo .dc__pv,
.is-demo .dc__msg-t--a, .is-demo .dc__msg-t--b {
animation: none !important;
transition: none !important;
}
.is-demo .dc__cell:first-child .dc__ring, .is-demo .dc__pv-t--a, .is-demo .dc__msg-t--a {
opacity: 1 !important;
}
.is-demo .dc__cell:last-child .dc__ring, .is-demo .dc__pv-t--b, .is-demo .dc__msg-t--b {
opacity: 0 !important;
}
.is-demo .dc__cell:first-child .dc__label {
transform: scale(1.02) !important;
}
.is-demo .dc__cell:last-child .dc__label {
transform: scale(0.97) !important;
}
}
@media (max-width: 340px), (max-height: 220px) {
.dc {
width: min(288px, 92vw);
padding: 8px;
}
.dc__title {
margin: 0 0 8px;
font-size: 12px;
}
.dc__cards {
gap: 8px;
}
.dc__label {
gap: 0;
padding: 8px 4px;
}
.dc__ic svg {
width: 16px;
height: 16px;
}
.dc__name {
font-size: 10px;
}
.dc__sub {
font-size: 9px;
}
.dc__arrow svg {
width: 11px;
height: 11px;
}
.dc__pv {
margin: 8px 0 0;
padding: 4px 8px;
font-size: 10px;
}
.dc__msg {
margin: 4px 0 0;
min-height: 0;
font-size: 9px;
}
}// 실제 동작: 진짜 radio 라 클릭도 좌우 방향키도 그대로 먹는다. 고른 쪽이 앞으로 나오고 아래 미리보기 줄이 바뀌며, Enter 나 더블클릭이면 미리보기 줄이 시작 버튼으로 바뀐다.
var stage = document.getElementById('stage');
var group = document.getElementById('dc-group');
var a = document.getElementById('dc-a');
var b = document.getElementById('dc-b');
var pv = document.getElementById('dc-pv');
var pvA = document.getElementById('dc-pv-a');
var pvB = document.getElementById('dc-pv-b');
var pvGo = document.getElementById('dc-pv-go');
var msg = document.getElementById('dc-msg');
var msgA = document.getElementById('dc-msg-a');
var msgB = document.getElementById('dc-msg-b');
var armed = false;
function paint() {
a.setAttribute('aria-checked', String(a.checked));
b.setAttribute('aria-checked', String(b.checked));
pv.classList.toggle('is-b', b.checked);
pv.classList.toggle('is-armed', armed);
pvA.setAttribute('aria-hidden', String(armed || b.checked));
pvB.setAttribute('aria-hidden', String(armed || a.checked));
pvGo.setAttribute('aria-hidden', String(!armed));
// 안내 문장도 선택과 같은 시계로 갈린다 — 루프에서는 CSS 가 같은 컷으로 바꾼다
msg.classList.toggle('is-b', b.checked);
msgA.setAttribute('aria-hidden', String(b.checked));
msgB.setAttribute('aria-hidden', String(a.checked));
}
paint();
group.addEventListener('change', function () { armed = false; paint(); });
group.addEventListener('dblclick', function () { armed = true; paint(); });
group.addEventListener('keydown', function (e) {
if (e.key === 'Enter') { armed = true; paint(); pv.focus({preventScroll:true}); }
});
pv.addEventListener('click', function () {
msgA.textContent = '무료 시작으로 넘어갑니다';
msgB.textContent = '상담 예약으로 넘어갑니다';
});
function human() { stage.classList.remove('is-demo'); }
document.addEventListener('pointerdown', human, { once: true });
document.addEventListener('keydown', human, { once: true });import { useEffect, useRef, useState } from "react";
import styles from "./DualChoiceCta.module.scss";
// 이벤트·ref 타입은 이 파일 안에서 최소한으로 선언한다
type ChangeEvt = { target: { value: string } };
type KeyEvt = { key: string };
type Box<T> = { current: T | null };
export type DualChoiceCtaProps = { duration?: number; easing?: string; color?: string };
type Vars = { "--duration": string; "--easing": string; "--color": string };
export default function DualChoiceCta({
duration = 2000,
easing = "cubic-bezier(.2,1.4,.4,1)",
color = "#2f6df6",
}: DualChoiceCtaProps) {
const [demo, setDemo] = useState(true);
const [choice, setChoice] = useState("free");
const [armed, setArmed] = useState(false);
const [went, setWent] = useState(false);
const pvRef: Box<HTMLButtonElement> = useRef<HTMLButtonElement>(null);
const vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color } as Vars;
const isFree = choice === "free";
useEffect(() => {
const off = () => setDemo(false);
window.addEventListener("pointerdown", off, { once: true });
window.addEventListener("keydown", off, { once: true });
return () => {
window.removeEventListener("pointerdown", off);
window.removeEventListener("keydown", off);
};
}, []);
function onChange(e: ChangeEvt) {
setChoice(e.target.value);
setArmed(false);
setWent(false);
}
function onKeyDown(e: KeyEvt) {
if (e.key !== "Enter") return;
setArmed(true);
if (pvRef.current) pvRef.current.focus({ preventScroll: true });
}
return (
<main className={`${styles.stage} ${demo ? styles.demo : ""}`} style={vars}>
<div className={styles.dc}>
<p className={styles.title}>어떻게 시작할까요?</p>
<div
className={styles.cards}
role="radiogroup"
aria-label="시작 방법"
onKeyDown={onKeyDown}
onDoubleClick={() => setArmed(true)}
>
<div className={styles.cell}>
<input
className={styles.radio}
type="radio"
id="dc-a-r"
name="dc271r"
value="free"
checked={isFree}
aria-checked={isFree}
onChange={onChange}
/>
<label className={styles.label} htmlFor="dc-a-r">
<span className={styles.ring} aria-hidden="true" />
<span className={styles.ic} aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="22" height="22"><g fill="currentColor"><path d="M184 120v61.65a8 8 0 0 1-2.34 5.65l-34.35 34.35a8 8 0 0 1-13.57-4.53L128 176Zm-48-48H74.35a8 8 0 0 0-5.65 2.34l-34.35 34.35a8 8 0 0 0 4.53 13.57L80 128ZM40 216c37.65 0 50.69-19.69 54.56-28.18l-26.38-26.38C59.69 165.31 40 178.35 40 216" opacity=".2" /><path d="M223.85 47.12a16 16 0 0 0-15-15c-12.58-.75-44.73.4-71.41 27.07L132.69 64H74.36A15.9 15.9 0 0 0 63 68.68L28.7 103a16 16 0 0 0 9.07 27.16l38.47 5.37l44.21 44.21l5.37 38.49a15.94 15.94 0 0 0 10.78 12.92a16.1 16.1 0 0 0 5.1.83a15.9 15.9 0 0 0 11.3-4.68l34.32-34.3a15.9 15.9 0 0 0 4.68-11.36v-58.33l4.77-4.77c26.68-26.68 27.83-58.83 27.08-71.42M74.36 80h42.33l-39.53 39.52L40 114.34Zm74.41-9.45a76.65 76.65 0 0 1 59.11-22.47a76.46 76.46 0 0 1-22.42 59.16L128 164.68L91.32 128ZM176 181.64L141.67 216l-5.19-37.17L176 139.31Zm-74.16 9.5C97.34 201 82.29 224 40 224a8 8 0 0 1-8-8c0-42.29 23-57.34 32.86-61.85a8 8 0 0 1 6.64 14.56c-6.43 2.93-20.62 12.36-23.12 38.91c26.55-2.5 36-16.69 38.91-23.12a8 8 0 1 1 14.56 6.64Z" /></g></svg>
</span>
<span className={styles.name}>무료로 시작하기</span>
<span className={styles.sub}>가입 없이 30초</span>
<span className={styles.arrow} aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="14" height="14"><g fill="currentColor"><path d="m216 128l-72 72V56Z" opacity=".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 0-11.32M152 180.69V75.31L204.69 128Z" /></g></svg>
</span>
</label>
</div>
<div className={styles.cell}>
<input
className={styles.radio}
type="radio"
id="dc-b-r"
name="dc271r"
value="demo"
checked={!isFree}
aria-checked={!isFree}
onChange={onChange}
/>
<label className={styles.label} htmlFor="dc-b-r">
<span className={styles.ring} aria-hidden="true" />
<span className={styles.ic} aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="22" height="22"><g fill="currentColor"><path d="M80 144v40a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16v-56h32a16 16 0 0 1 16 16m112-16a16 16 0 0 0-16 16v40a16 16 0 0 0 16 16h32v-72Z" opacity=".2" /><path d="M201.89 54.66A104.08 104.08 0 0 0 24 128v56a24 24 0 0 0 24 24h16a24 24 0 0 0 24-24v-40a24 24 0 0 0-24-24H40.36a88.12 88.12 0 0 1 150.18-54.07A87.4 87.4 0 0 1 215.65 120H192a24 24 0 0 0-24 24v40a24 24 0 0 0 24 24h24a24 24 0 0 1-24 24h-56a8 8 0 0 0 0 16h56a40 40 0 0 0 40-40v-80a103.4 103.4 0 0 0-30.11-73.34M64 136a8 8 0 0 1 8 8v40a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8v-48Zm128 56a8 8 0 0 1-8-8v-40a8 8 0 0 1 8-8h24v56Z" /></g></svg>
</span>
<span className={styles.name}>데모 상담 잡기</span>
<span className={styles.sub}>20분 화상 미팅</span>
<span className={styles.arrow} aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="14" height="14"><g fill="currentColor"><path d="m216 128l-72 72V56Z" opacity=".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 0-11.32M152 180.69V75.31L204.69 128Z" /></g></svg>
</span>
</label>
</div>
</div>
<button
type="button"
className={`${styles.pv} ${isFree ? "" : styles.isB} ${armed ? styles.isArmed : ""}`}
ref={pvRef}
onClick={() => setWent(true)}
>
<span className={`${styles.pvT} ${styles.pvA}`} aria-hidden={armed || !isFree}>다음: 템플릿 골라 첫 화면 만들기</span>
<span className={`${styles.pvT} ${styles.pvB}`} aria-hidden={armed || isFree}>다음: 편한 시간 골라 20분 잡기</span>
<span className={`${styles.pvT} ${styles.pvGo}`} aria-hidden={!armed}>시작하기 »</span>
</button>
<p className={`${styles.msg} ${isFree ? "" : styles.isB}`} role="status">
<span className={`${styles.msgT} ${styles.msgA}`} aria-hidden={!isFree}>{went ? "무료 시작으로 넘어갑니다" : "무료 시작을 골랐어요"}</span>
<span className={`${styles.msgT} ${styles.msgB}`} aria-hidden={isFree}>{went ? "상담 예약으로 넘어갑니다" : "상담 예약을 골랐어요"}</span>
</p>
</div>
</main>
);
}09 최종 CTA 이중 선택 — 측정 누적면적 26.074% · 움직인 프레임 12/23GODRICH 원작 · zip 에 SCSS·React 포함 · 랜딩페이지 전환율을 끌어올리는 섹션 9종
이 데모가 속한 글
랜딩페이지 전환율을 끌어올리는 섹션 9종랜딩페이지 전환율이란 방문자가 이메일을 남기거나 예약·구매까지 가는 비율을 말하고, 이 글은 그 전환을 직접 받아내는 섹션 아홉 개를 통째로 주는 코드 모음입니다. 떠나는 방문자를 붙잡는 이탈 팝업부터 읽던 지점에 이 글의 데모 전부 보기


