05Objection-handling FAQ that arms the CTA
Opening each of the three buying worries marks that row resolved, and the moment all three are cleared the button below grows while its label changes to say it is ready to press.
Best for
grid-template-rowsaria-expandedscale

<main class="stage is-demo">
<section class="ob">
<p class="ob__title">
<span class="ob__ic" aria-hidden="true"><svg viewBox="0 0 256 256" width="18" height="18"><g fill="currentColor"><path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2"/><path d="M140 180a12 12 0 1 1-12-12a12 12 0 0 1 12 12M128 72c-22.06 0-40 16.15-40 36v4a8 8 0 0 0 16 0v-4c0-11 10.77-20 24-20s24 9 24 20s-10.77 20-24 20a8 8 0 0 0-8 8v8a8 8 0 0 0 16 0v-.72c18.24-3.35 32-17.9 32-35.28c0-19.85-17.94-36-40-36m104 56A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88"/></g></svg></span>
Still on the fence?
</p>
<ul class="ob__list">
<li class="ob__item" style="--i:0">
<button type="button" class="ob__q" aria-expanded="false" aria-controls="ob-a1">
<span class="ob__q-t">The price feels steep</span>
<span class="ob__done">
<svg class="ob__check" viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity=".35"/><polyline points="7 12.5 10.5 16 17 8.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="ob__done-t">Cleared</span>
</span>
</button>
<div class="ob__a" id="ob-a1"><div class="ob__a-in"><p>First month ₩0, then pay only for what you use.</p></div></div>
</li>
<li class="ob__item" style="--i:1">
<button type="button" class="ob__q" aria-expanded="false" aria-controls="ob-a2">
<span class="ob__q-t">Hard to trust without trying it</span>
<span class="ob__done">
<svg class="ob__check" viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity=".35"/><polyline points="7 12.5 10.5 16 17 8.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="ob__done-t">Cleared</span>
</span>
</button>
<div class="ob__a" id="ob-a2"><div class="ob__a-in"><p>All features for 14 days, no card needed.</p></div></div>
</li>
<li class="ob__item" style="--i:2">
<button type="button" class="ob__q" aria-expanded="false" aria-controls="ob-a3">
<span class="ob__q-t">Canceling sounds like a hassle</span>
<span class="ob__done">
<svg class="ob__check" viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity=".35"/><polyline points="7 12.5 10.5 16 17 8.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="ob__done-t">Cleared</span>
</span>
</button>
<div class="ob__a" id="ob-a3"><div class="ob__a-in"><p>One button in settings and you're done.</p></div></div>
</li>
</ul>
<button type="button" class="ob__cta">
<span class="ob__cta-t">Start with no strings</span>
<span class="ob__cta-t ob__cta-t--ready" aria-hidden="true">You're all set — tap here</span>
</button>
<p class="ob__status" role="status"></p>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop;
$easing: $ease-pop;
$color: $subject-blue;
@include stage($stage-ink);
.ob {
width: min(312px, 94vw);
box-sizing: border-box;
background: $subject-cream;
border-radius: $r-card;
box-shadow: $shadow-raised;
padding: $sp-3;
}
.ob__title {
display: flex; align-items: center; gap: $sp-1;
margin: 0 0 $sp-2;
font-family: inherit; font-weight: 800; font-size: 13px; line-height: 1.2;
color: $subject-ink;
}
.ob__ic { display: grid; place-items: center; color: $color; }
.ob__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0; }
.ob__item { border-bottom: 1px solid rgba(23, 20, 26, .1); }
.ob__q {
display: flex; align-items: center; justify-content: space-between; gap: $sp-2;
width: 100%; padding: $sp-1 0;
border: 0; background: transparent; cursor: pointer; text-align: left;
font-family: inherit; font-weight: 700; font-size: 12px; line-height: 1.2;
color: $subject-ink;
}
.ob__q:focus-visible { outline: 2px solid $color; outline-offset: 2px; }
.ob__done {
display: inline-flex; align-items: center; gap: $sp-1; flex: 0 0 auto;
color: #50a26e; font-weight: 800; font-size: 10px; line-height: 1;
opacity: 0; transform: scale(.8);
transition: opacity $dur-quick $ease-out, transform $dur-quick $easing;
}
.ob__item.is-open .ob__done { opacity: 1; transform: scale(1); }
.ob__check polyline { stroke-dasharray: 26; stroke-dashoffset: 26; transition: stroke-dashoffset $dur-base $ease-out; }
.ob__item.is-open .ob__check polyline { stroke-dashoffset: 0; }
// ── 펼침 (grid 0fr → 1fr) ────────────────────────────
.ob__a { display: grid; grid-template-rows: 0fr; transition: grid-template-rows $dur-base $easing; }
.ob__item.is-open .ob__a { grid-template-rows: 1fr; }
.ob__a-in { overflow: hidden; }
.ob__a p {
margin: 0; padding: 0 0 $sp-1;
font-family: inherit; font-weight: 500; font-size: 11px; line-height: 1.5;
color: rgba(23, 20, 26, .66);
}
// ── CTA ──────────────────────────────────────────────
.ob__cta {
display: grid; place-items: center;
width: 100%; margin-top: $sp-2;
padding: $sp-2 $sp-3;
border: 0; border-radius: $r-control;
background: $color; color: $subject-cream;
font-family: inherit; font-weight: 800; font-size: 12px; line-height: 1.2;
cursor: pointer;
}
.ob__cta:focus-visible { outline: 2px solid $subject-cream; outline-offset: 2px; }
.ob__cta-t { grid-area: 1 / 1; }
.ob__cta-t--ready { opacity: 0; }
.ob.is-ready .ob__cta-t { opacity: 0; }
.ob.is-ready .ob__cta-t--ready { opacity: 1; }
.ob.is-ready .ob__cta { animation: ob-pulse $duration $easing infinite; }
@keyframes ob-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.06); }
}
.ob__status { margin: $sp-1 0 0; min-height: 13px; font-family: inherit; font-weight: 600; font-size: 10px; line-height: 1.3; color: rgba(23, 20, 26, .55); }
// ── 측정용 루프 (.is-demo) ───────────────────────────
// 세 줄이 차례로 펼쳐지고(지연 160ms 간격) CTA 글자가 컷으로 바뀐 뒤 펄스한다. 2s 루프의 90% 안에 복귀.
.stage.is-demo .ob__a {
transition: none;
animation-name: ob-open;
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
animation-delay: calc(var(--i) * 160ms);
}
// 닫힘은 열림보다 훨씬 느리게 편다 — 프레임당 변한 면적이 작아져 대표 이미지(poster)가 닫히는 장면으로 뽑히지 않는다
@keyframes ob-open {
0% { grid-template-rows: 0fr; }
16% { grid-template-rows: 1fr; }
66% { grid-template-rows: 1fr; }
100% { grid-template-rows: 0fr; }
}
.stage.is-demo .ob__a p {
animation-name: ob-answer;
animation-duration: $duration;
animation-timing-function: $ease-out;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
animation-delay: calc(var(--i) * 160ms);
}
@keyframes ob-answer {
0% { opacity: 0; transform: translateY(6px); }
20%, 66% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(6px); }
}
.stage.is-demo .ob__done {
transition: none;
animation-name: ob-done;
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
animation-delay: calc(var(--i) * 160ms);
}
@keyframes ob-done {
0% { opacity: 0; transform: scale(.8); }
14% { opacity: 1; transform: scale(1.16); }
24%, 66% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(.8); }
}
.stage.is-demo .ob__check polyline {
transition: none;
animation-name: ob-draw;
animation-duration: $duration;
animation-timing-function: $ease-out;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
animation-delay: calc(var(--i) * 160ms);
}
@keyframes ob-draw {
0% { stroke-dashoffset: 26; }
28%, 66% { stroke-dashoffset: 0; }
100% { stroke-dashoffset: 26; }
}
// 글자 교체는 컷(steps), 커지는 느낌은 버튼 scale 로 따로 준다
.stage.is-demo .ob__cta-t {
animation-name: ob-label-out;
animation-duration: $duration;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
}
.stage.is-demo .ob__cta-t--ready {
animation-name: ob-label-in;
animation-duration: $duration;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
}
// 라벨이 「준비됐어요」인 구간은 세 줄이 전부 열려 있는 동안(41~64%)만 — 한 줄이라도 닫히기 시작하면 원래 라벨로 돌아온다
@keyframes ob-label-out {
0%, 40% { opacity: 1; }
41%, 64% { opacity: 0; }
65%, 100% { opacity: 1; }
}
@keyframes ob-label-in {
0%, 40% { opacity: 0; }
41%, 64% { opacity: 1; }
65%, 100% { opacity: 0; }
}
.stage.is-demo .ob__cta {
animation-name: ob-cta-loop;
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
}
@keyframes ob-cta-loop {
0%, 41% { transform: scale(1); }
48% { transform: scale(1.06); }
55% { transform: scale(1); }
60% { transform: scale(1.04); }
64%, 100% { transform: scale(1); }
}
// ── 접근성 ──────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.ob__a, .ob__done, .ob__check polyline { transition: none !important; }
.stage.is-demo .ob__a,
.stage.is-demo .ob__a p,
.stage.is-demo .ob__done,
.stage.is-demo .ob__check polyline,
.stage.is-demo .ob__cta,
.stage.is-demo .ob__cta-t,
.stage.is-demo .ob__cta-t--ready,
.ob.is-ready .ob__cta { animation: none !important; }
.stage.is-demo .ob__a { grid-template-rows: 1fr !important; }
.stage.is-demo .ob__a p { opacity: 1 !important; transform: none !important; }
.stage.is-demo .ob__done { opacity: 1 !important; transform: none !important; }
.stage.is-demo .ob__check polyline { stroke-dashoffset: 0 !important; }
.stage.is-demo .ob__cta { transform: none !important; }
.stage.is-demo .ob__cta-t { opacity: 0 !important; }
.stage.is-demo .ob__cta-t--ready { opacity: 1 !important; }
}
// ── 반응형(320×200) ──────────────────────────────────
@media (max-width: 340px), (max-height: 220px) {
.ob { width: min(272px, 92vw); padding: $sp-2; }
.ob__title { margin: 0 0 $sp-1; font-size: 11px; }
.ob__ic svg { width: 14px; height: 14px; }
.ob__list { gap: 0; }
.ob__q { padding: $sp-1 0; font-size: 10px; }
.ob__done { font-size: 9px; }
.ob__check { width: 12px; height: 12px; }
// 세 줄이 동시에 열린 순간까지 200px 안에 들어가도록 답 줄을 8px·여백 0 으로 내린다 (실측 2026-09-12)
.ob__a p {
padding: 0; font-size: 8px; line-height: 1.2;
display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}
.ob__cta { margin-top: $sp-2; padding: $sp-1 $sp-2; font-size: 10px; }
.ob__status { margin-top: 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);
}
.ob {
width: min(312px, 94vw);
box-sizing: border-box;
background: #fff7e6;
border-radius: 16px;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
padding: 12px;
}
.ob__title {
display: flex;
align-items: center;
gap: 4px;
margin: 0 0 8px;
font-family: inherit;
font-weight: 800;
font-size: 13px;
line-height: 1.2;
color: #17141a;
}
.ob__ic {
display: grid;
place-items: center;
color: #2f6df6;
}
.ob__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 0;
}
.ob__item {
border-bottom: 1px solid rgba(23, 20, 26, 0.1);
}
.ob__q {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
padding: 4px 0;
border: 0;
background: transparent;
cursor: pointer;
text-align: left;
font-family: inherit;
font-weight: 700;
font-size: 12px;
line-height: 1.2;
color: #17141a;
}
.ob__q:focus-visible {
outline: 2px solid #2f6df6;
outline-offset: 2px;
}
.ob__done {
display: inline-flex;
align-items: center;
gap: 4px;
flex: 0 0 auto;
color: #50a26e;
font-weight: 800;
font-size: 10px;
line-height: 1;
opacity: 0;
transform: scale(0.8);
transition: opacity 220ms ease-out, transform 220ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.ob__item.is-open .ob__done {
opacity: 1;
transform: scale(1);
}
.ob__check polyline {
stroke-dasharray: 26;
stroke-dashoffset: 26;
transition: stroke-dashoffset 300ms ease-out;
}
.ob__item.is-open .ob__check polyline {
stroke-dashoffset: 0;
}
.ob__a {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 300ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.ob__item.is-open .ob__a {
grid-template-rows: 1fr;
}
.ob__a-in {
overflow: hidden;
}
.ob__a p {
margin: 0;
padding: 0 0 4px;
font-family: inherit;
font-weight: 500;
font-size: 11px;
line-height: 1.5;
color: rgba(23, 20, 26, 0.66);
}
.ob__cta {
display: grid;
place-items: center;
width: 100%;
margin-top: 8px;
padding: 8px 12px;
border: 0;
border-radius: 12px;
background: #2f6df6;
color: #fff7e6;
font-family: inherit;
font-weight: 800;
font-size: 12px;
line-height: 1.2;
cursor: pointer;
}
.ob__cta:focus-visible {
outline: 2px solid #fff7e6;
outline-offset: 2px;
}
.ob__cta-t {
grid-area: 1/1;
}
.ob__cta-t--ready {
opacity: 0;
}
.ob.is-ready .ob__cta-t {
opacity: 0;
}
.ob.is-ready .ob__cta-t--ready {
opacity: 1;
}
.ob.is-ready .ob__cta {
animation: ob-pulse 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes ob-pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.06);
}
}
.ob__status {
margin: 4px 0 0;
min-height: 13px;
font-family: inherit;
font-weight: 600;
font-size: 10px;
line-height: 1.3;
color: rgba(23, 20, 26, 0.55);
}
.stage.is-demo .ob__a {
transition: none;
animation-name: ob-open;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 1.4, 0.4, 1);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
animation-delay: calc(var(--i) * 160ms);
}
@keyframes ob-open {
0% {
grid-template-rows: 0fr;
}
16% {
grid-template-rows: 1fr;
}
66% {
grid-template-rows: 1fr;
}
100% {
grid-template-rows: 0fr;
}
}
.stage.is-demo .ob__a p {
animation-name: ob-answer;
animation-duration: 2s;
animation-timing-function: ease-out;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
animation-delay: calc(var(--i) * 160ms);
}
@keyframes ob-answer {
0% {
opacity: 0;
transform: translateY(6px);
}
20%, 66% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(6px);
}
}
.stage.is-demo .ob__done {
transition: none;
animation-name: ob-done;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 1.4, 0.4, 1);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
animation-delay: calc(var(--i) * 160ms);
}
@keyframes ob-done {
0% {
opacity: 0;
transform: scale(0.8);
}
14% {
opacity: 1;
transform: scale(1.16);
}
24%, 66% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.8);
}
}
.stage.is-demo .ob__check polyline {
transition: none;
animation-name: ob-draw;
animation-duration: 2s;
animation-timing-function: ease-out;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
animation-delay: calc(var(--i) * 160ms);
}
@keyframes ob-draw {
0% {
stroke-dashoffset: 26;
}
28%, 66% {
stroke-dashoffset: 0;
}
100% {
stroke-dashoffset: 26;
}
}
.stage.is-demo .ob__cta-t {
animation-name: ob-label-out;
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
}
.stage.is-demo .ob__cta-t--ready {
animation-name: ob-label-in;
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
}
@keyframes ob-label-out {
0%, 40% {
opacity: 1;
}
41%, 64% {
opacity: 0;
}
65%, 100% {
opacity: 1;
}
}
@keyframes ob-label-in {
0%, 40% {
opacity: 0;
}
41%, 64% {
opacity: 1;
}
65%, 100% {
opacity: 0;
}
}
.stage.is-demo .ob__cta {
animation-name: ob-cta-loop;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 1.4, 0.4, 1);
animation-iteration-count: infinite;
}
@keyframes ob-cta-loop {
0%, 41% {
transform: scale(1);
}
48% {
transform: scale(1.06);
}
55% {
transform: scale(1);
}
60% {
transform: scale(1.04);
}
64%, 100% {
transform: scale(1);
}
}
@media (prefers-reduced-motion: reduce) {
.ob__a, .ob__done, .ob__check polyline {
transition: none !important;
}
.stage.is-demo .ob__a,
.stage.is-demo .ob__a p,
.stage.is-demo .ob__done,
.stage.is-demo .ob__check polyline,
.stage.is-demo .ob__cta,
.stage.is-demo .ob__cta-t,
.stage.is-demo .ob__cta-t--ready,
.ob.is-ready .ob__cta {
animation: none !important;
}
.stage.is-demo .ob__a {
grid-template-rows: 1fr !important;
}
.stage.is-demo .ob__a p {
opacity: 1 !important;
transform: none !important;
}
.stage.is-demo .ob__done {
opacity: 1 !important;
transform: none !important;
}
.stage.is-demo .ob__check polyline {
stroke-dashoffset: 0 !important;
}
.stage.is-demo .ob__cta {
transform: none !important;
}
.stage.is-demo .ob__cta-t {
opacity: 0 !important;
}
.stage.is-demo .ob__cta-t--ready {
opacity: 1 !important;
}
}
@media (max-width: 340px), (max-height: 220px) {
.ob {
width: min(272px, 92vw);
padding: 8px;
}
.ob__title {
margin: 0 0 4px;
font-size: 11px;
}
.ob__ic svg {
width: 14px;
height: 14px;
}
.ob__list {
gap: 0;
}
.ob__q {
padding: 4px 0;
font-size: 10px;
}
.ob__done {
font-size: 9px;
}
.ob__check {
width: 12px;
height: 12px;
}
.ob__a p {
padding: 0;
font-size: 8px;
line-height: 1.2;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.ob__cta {
margin-top: 8px;
padding: 4px 8px;
font-size: 10px;
}
.ob__status {
margin-top: 0;
min-height: 0;
font-size: 9px;
}
}// 실제 동작: 질문 줄을 누르면 답이 grid 0fr→1fr 로 펼쳐지고 그 줄에 체크가 그려진다.
// 세 줄을 모두 한 번씩 열면 CTA 글자가 바뀌고 펄스가 시작된다.
var stage = document.querySelector('.stage');
var ob = document.querySelector('.ob');
var items = [].slice.call(document.querySelectorAll('.ob__item'));
var status = document.querySelector('.ob__status');
var label = document.querySelector('.ob__cta-t');
var labelReady = document.querySelector('.ob__cta-t--ready');
var opened = [];
['pointerdown', 'keydown'].forEach(function (t) {
window.addEventListener(t, function () { stage.classList.remove('is-demo'); }, { once: true });
});
items.forEach(function (item, idx) {
var q = item.querySelector('.ob__q');
q.addEventListener('click', function () {
var open = item.classList.toggle('is-open');
q.setAttribute('aria-expanded', String(open));
if (open && opened.indexOf(idx) < 0) opened.push(idx);
if (opened.length === items.length && !ob.classList.contains('is-ready')) {
ob.classList.add('is-ready');
label.setAttribute('aria-hidden', 'true');
labelReady.removeAttribute('aria-hidden');
status.textContent = 'Every concern is cleared.';
}
});
});import { useEffect, useState } from "react";
import type { CSSProperties } from "react";
import styles from "./ObjectionFaqCta.module.scss";
export type ObjectionFaqCtaProps = { duration?: number; easing?: string; color?: string };
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string; "--i"?: string };
const ITEMS = [
{ q: "가격이 부담돼요", a: "첫 달 ₩0, 이후엔 쓴 만큼만 냅니다." },
{ q: "써보지 않고 믿기 어려워요", a: "카드 없이 14일 동안 전체 기능을 씁니다." },
{ q: "해지가 번거로울까 봐요", a: "설정에서 버튼 한 번이면 끝납니다." },
];
export default function ObjectionFaqCta({
duration = 2000,
easing = "cubic-bezier(.2,1.4,.4,1)",
color = "#2f6df6",
}: ObjectionFaqCtaProps) {
const [open, setOpen] = useState<boolean[]>([false, false, false]);
const [seen, setSeen] = useState<boolean[]>([false, false, false]);
// 측정용 루프: 처음 조작(pointerdown·keydown)에서 루프를 끈다 — vanilla 판과 같은 동작
const [demo, setDemo] = useState(true);
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);
};
}, []);
const ready = seen.every(Boolean);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
const cls = [styles.ob, ready ? styles.isReady : "", demo ? styles.isDemo : ""].join(" ");
function toggle(i: number) {
setOpen(open.map((v, k) => (k === i ? !v : v)));
if (!open[i]) setSeen(seen.map((v, k) => (k === i ? true : v)));
}
return (
<section className={cls} style={vars}>
<p className={styles.title}>
<span className={styles.ic} aria-hidden="true">
<svg viewBox="0 0 256 256" width="18" height="18">
<g fill="currentColor">
<path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2" />
<path d="M140 180a12 12 0 1 1-12-12a12 12 0 0 1 12 12M128 72c-22.06 0-40 16.15-40 36v4a8 8 0 0 0 16 0v-4c0-11 10.77-20 24-20s24 9 24 20s-10.77 20-24 20a8 8 0 0 0-8 8v8a8 8 0 0 0 16 0v-.72c18.24-3.35 32-17.9 32-35.28c0-19.85-17.94-36-40-36m104 56A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88" />
</g>
</svg>
</span>
아직 망설이는 부분
</p>
<ul className={styles.list}>
{ITEMS.map((it, i) => {
const itemVars: Vars = { "--i": String(i) };
return (
<li key={it.q} className={`${styles.item} ${open[i] ? styles.isOpen : ""}`} style={itemVars}>
<button
type="button"
className={styles.q}
aria-expanded={open[i]}
aria-controls={`ob-a-${i}`}
onClick={() => toggle(i)}
>
<span>{it.q}</span>
<span className={styles.done}>
<svg className={styles.check} viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" strokeWidth={2} opacity=".35" />
<polyline points="7 12.5 10.5 16 17 8.5" fill="none" stroke="currentColor" strokeWidth={2.4} strokeLinecap="round" strokeLinejoin="round" />
</svg>
<span>해소됨</span>
</span>
</button>
<div className={styles.a} id={`ob-a-${i}`}>
<div className={styles.aIn}>
<p>{it.a}</p>
</div>
</div>
</li>
);
})}
</ul>
<button type="button" className={styles.cta}>
<span className={styles.ctaT} aria-hidden={ready}>자유롭게 시작하기</span>
<span className={`${styles.ctaT} ${styles.ctaTReady}`} aria-hidden={!ready}>준비됐어요, 눌러주세요</span>
</button>
<p className={styles.status} role="status">{ready ? "모든 걸림돌이 풀렸어요" : ""}</p>
</section>
);
}05 Objection-handling FAQ that arms the CTA — Measured area 40.806% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · Landing Page Conversion: 9 Sections That Convert
The post this demo lives in
Landing Page Conversion: 9 Sections That ConvertLanding page conversion is the share of visitors who leave an email, book a slot, or buy, and this post hands you the ni See all demos in this post


