믿음 가는 랜딩 페이지 섹션 템플릿 9종
랜딩 페이지 섹션 템플릿은 통계·로고 바·비교표부터 문의 폼까지, 랜딩 페이지 여기저기 흩어져 있던 신뢰 요소를 한 번에 붙일 수 있게 모은 코드 모음입니다. 랜딩 페이지에 바로 붙이는 아홉 개 중 하나는 숫자가 실제로 0에서 목표값까지 올라갑니다.
자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip
- 01 핵심 지표 카운터
- 02 고객사 로고 트러스트 바
- 03 기능 비교표
- 04 팀 소개 그리드
- 05 뉴스레터 구독 밴드
- 06 연동 서비스 그리드
- 07 로드맵 타임라인
- 08 수상·언론 보도 섹션
- 09 문의 폼 스플릿 레이아웃
아홉 개는 인기순이 아니라 방문자가 신뢰를 쌓아가는 순서대로 놓았습니다. 먼저 숫자로 규모를 보여주고(핵심 지표 카운터), 이미 쓰는 회사 이름을 흘려보내고(고객사 로고 바), 요금제 차이를 표로 정리하고(기능 비교표), 실제로 일하는 사람을 보여주고(팀 소개 그리드), 다음 소식을 받게 하고(뉴스레터 구독 밴드), 연결되는 도구를 나열하고(연동 서비스 그리드), 지금까지 온 길과 앞으로 갈 길을 보여주고(로드맵 타임라인), 제3자가 인정했다는 근거를 대고(수상·언론 보도), 마지막으로 실제 문의를 받습니다(문의 폼). 로고·아바타·매체 이름은 전부 이 사이트의 먹·파랑·크림 토큰으로 그린 자리표시자라, 실제 로고 파일만 바꿔 끼워도 격자와 정렬은 그대로 유지됩니다.
01핵심 지표 카운터
숫자 세 개는 페이지가 뜨자마자 0에서 실제 목표값까지 requestAnimationFrame으로 실측 카운트업됩니다. 아이콘은 계속 은은하게 위아래로 스치며 눈길을 붙잡습니다.
document.querySelectorAll('.stats__n').forEach(function (el, i) {
var target = Number(el.getAttribute('data-target'));
var DUR = 1100, start = null, delay = i * 140;
function frame(ts) {
if (!start) start = ts;
var t = Math.max(0, ts - start - delay);
var p = Math.min(1, t / DUR);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = Math.round(eased * target).toLocaleString('ko-KR');
if (p < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
});
02고객사 로고 트러스트 바
로고 줄은 끊김 없이 계속 흐르고, 버튼을 누르면 aria-pressed가 바뀌며 실제로 멈추고 다시 흐릅니다. 흐름 폭은 로고 6개를 그대로 한 번 더 붙인 구조라 이어 붙는 자리가 안 보입니다.
.lc__track {
display: flex;
width: max-content;
animation: lc-scroll 9s linear infinite;
}
.lc__track li {
flex: 0 0 auto;
margin-right: 24px; // gap 대신 margin-right
}
@keyframes lc-scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
03기능 비교표
요금제 이름을 클릭하면 aria-pressed가 바뀌며 그 열의 체크·엑스 아이콘이 실제로 커지고 배경이 강조됩니다. 두 열은 1초마다 자동으로도 번갈아 강조돼, 누르지 않아도 어떤 항목이 어느 요금제에 있는지 눈으로 따라갈 수 있습니다.
function select(plan) {
document.querySelectorAll('.fc__plan').forEach(function (b) {
var on = b.getAttribute('data-plan') === plan;
b.classList.toggle('is-on', on);
b.setAttribute('aria-pressed', String(on));
});
document.querySelectorAll('.fc__cell').forEach(function (c) {
c.classList.toggle('is-on', c.getAttribute('data-plan') === plan);
});
}
04팀 소개 그리드
멤버 카드를 클릭하거나 Enter를 누르면 aria-expanded가 바뀌며 한줄 소개가 grid-template-rows로 실제로 펼쳐집니다. 아바타는 펼쳐질 때마다 살짝 커지고 기울어져 지금 누가 펼쳐졌는지 바로 보입니다.
document.querySelectorAll('.tm__head').forEach(function (btn) {
btn.addEventListener('click', function () {
var card = btn.closest('.tm__card');
var open = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!open));
card.classList.toggle('is-open', !open);
});
});
05뉴스레터 구독 밴드
이메일을 넣고 구독하면 정규식으로 실제 형식을 검사해, 통과하면 체크가 stroke-dashoffset으로 그려지고 버튼 글자가 "구독 완료"로 바뀝니다. 형식이 틀리면 입력칸이 좌우로 짧게 흔들려 무엇이 잘못됐는지 바로 알 수 있습니다.
var RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
form.addEventListener('submit', function (e) {
e.preventDefault();
if (RE.test(input.value)) {
form.classList.add('is-done');
msg.textContent = input.value + ' 주소로 확인 메일을 보냈어요.';
} else {
input.classList.add('is-shake');
msg.textContent = '이메일 형식을 다시 확인해 주세요.';
}
});
06연동 서비스 그리드
연결 버튼을 누르면 aria-pressed가 바뀌며 점이 실제로 오른쪽으로 슬라이드되고 글자가 "연결됨"으로 바뀝니다. 점은 자기 트랙 안에서만 움직여 "연결" 글자와 겹치지 않습니다.
document.querySelectorAll('.ig__toggle').forEach(function (btn) {
btn.addEventListener('click', function () {
var on = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', String(!on));
btn.querySelector('.ig__state').textContent = on ? '연결' : '연결됨';
});
});
07로드맵 타임라인
스포트라이트는 자동으로 다음 분기로 옮겨가고, 항목을 클릭하거나 Tab으로 포커스를 옮기면 실측한 위치로 translateY만큼 바로 이동합니다. 완료된 분기와 예정 분기는 서로 다른 아이콘으로 구분됩니다.
function moveTo(idx) {
i = idx;
var top = steps[idx].getBoundingClientRect().top - list.getBoundingClientRect().top;
spot.style.transform = 'translateY(' + top + 'px)';
steps.forEach(function (s, n) { s.classList.toggle('is-active', n === idx); });
}
08수상·언론 보도 섹션
매체 이름을 클릭하면 aria-expanded가 바뀌며 그 매체의 인용 문구가 실제로 펼쳐지고, 한 번에 하나만 열립니다. 자동으로도 매체가 하나씩 돌아가며 열려, 방문자가 아무것도 누르지 않아도 인용 문구를 전부 볼 수 있습니다.
function openOnly(idx) {
btns.forEach(function (b, n) {
var on = n === idx;
b.setAttribute('aria-expanded', String(on));
b.closest('.ap__badge').classList.toggle('is-open', on);
});
}
09문의 폼 스플릿 레이아웃
이름·이메일·문의 내용을 다 채우고 보내면 종이비행기 아이콘이 실제로 날아가며 사라지고 버튼 글자가 "전송 완료"로 바뀝니다. 빈 칸이 있거나 이메일 형식이 틀리면 그 칸만 짧게 흔들립니다.
inputs.forEach(function (inp) {
if (!inp.value.trim() || (inp.type === 'email' && !RE.test(inp.value))) {
ok = false;
inp.classList.add('is-shake');
}
});
if (ok) {
form.classList.add('is-sent');
msg.textContent = '문의가 접수됐어요. 1영업일 안에 답장 드려요.';
}
어디서 깨지는가 — 함정
아홉 개를 만드는 동안 실제로 세 번 발을 헛디뎠습니다. 첫 번째는 고객사 로고 바(02)입니다. 로고 여섯 개를 두 번 이어 붙여 translateX(-50%)로 무한히 흐르게 했는데, 처음엔 항목 사이 간격을 gap으로 줬습니다. 로고가 열두 개면 사이 간격은 열한 개뿐이라 -50% 지점이 간격의 절반만큼 모자라, 한 바퀴 돌 때마다 로고 줄이 살짝 튀었습니다. gap을 버리고 항목마다 margin-right를 주자 이어 붙는 자리가 완전히 사라졌습니다.
두 번째는 320px 폭·200px 높이 화면 확인에서 나왔습니다. 팀 소개 그리드(04)와 문의 폼(09)은 처음엔 좁은 화면에서 세로로 쌓이게(flex-direction: column) 했는데, 오히려 세로로 더 길어져 실측값이 화면 높이를 넘겼습니다. 쌓는 대신 안쪽 글자 크기와 여백만 줄이자 두 화면 모두 넘치지 않고 들어갔습니다.
세 번째는 연동 서비스 그리드(06)의 토글 스위치입니다. 점을 "연결"·"연결됨" 글자와 같은 줄에 나란히 두고 transform: translateX로만 옮겼더니, 점이 켜질 때 옆 글자 위로 실제로 겹쳤습니다. 점을 자기만의 작은 트랙 안에 가두고 트랙째로 배치하자 겹침이 사라졌습니다.
접근성
zip을 rghhpe4v으로 열면 받는 코드 아홉 개가 전부 prefers-reduced-motion: reduce 환경에서 장식만 끄고 상태는 그대로 남기도록 이미 짜여 있습니다. 핵심 지표 카운터(01)는 아이콘 흔들림만 끄고 숫자 카운트업은 정보라서 그대로 유지하며, 고객사 로고 바(02)의 흐름도 멈추지만 일시정지 버튼은 그대로 눌립니다. 기능 비교표(03)와 연동 서비스 그리드(06)의 상태는 각각 aria-pressed로, 팀 소개 그리드(04)와 수상·언론 보도(08)의 펼침 상태는 aria-expanded로 전달합니다. 뉴스레터 구독 밴드(05)와 문의 폼(09)의 결과 문구는 role="status"라 스크린 리더가 성공·실패를 바로 읽습니다. 펼침 애니메이션에 쓴 grid-template-rows: 0fr → 1fr 전환은 MDN의 grid-template-rows 문서에 나온 표준 트랙 크기 문법을 그대로 쓰고, prefers-reduced-motion 자체는 MDN 문서를 따릅니다.
같은 기둥의 다른 랜딩 조합은 템플릿 카테고리에 모아 뒀고, 이 코드를 누가 만드는지는 소개 페이지에 적어 뒀습니다.
FAQ
고객사 로고 바(02)는 실제로 자동으로 흐르나요, 정지 이미지인가요?
실제로 흐릅니다. translateX(-50%) CSS 애니메이션으로 무한 반복되고, 로고 목록을 정확히 한 번 더 이어 붙여 이음매가 보이지 않게 만들었습니다. 일시정지 버튼을 누르면 animation-play-state가 실제로 paused로 바뀝니다.
React 버전도 CSS 버전과 동작이 같나요?
네, 같습니다. 열림·연결·강조 같은 상태는 전부 useState로 관리하고, 변수 3개(duration·easing·color)는 props로 받아 CSS 변수로 그대로 넘깁니다. 로드맵(07)의 자동 이동과 문의 폼(09)의 자동 포커스도 useEffect 안에서 같은 로직을 씁니다.
문의 폼(09)이 실제로 메일을 어딘가로 보내나요?
아니요, 데모 자체는 형식 검사와 전송 완료 표시까지만 실제로 동작하고 실제 발송 서버는 연결돼 있지 않습니다. form의 submit 이벤트를 가로채 정규식으로 검사하는 부분까지가 이 템플릿의 역할이고, 실제로 쓸 때는 그 자리에 실제 API 호출만 추가하면 검증·표시 로직은 그대로 재사용할 수 있습니다.