이벤트 페이지 템플릿 9종 — 그대로 붙이는 섹션
이벤트 페이지 템플릿은 컨퍼런스·밋업 랜딩 페이지에 바로 붙이는 히어로·일정·연사·가격 같은 섹션을 통째로 담은 코드 모음입니다. 아홉 개 중 하나는 실제 목표 시각까지 초 단위로 줄어드는 카운트다운을 담았습니다.
자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip
아홉 개는 인기 순이 아니라 방문자가 랜딩 페이지를 훑는 순서대로 놓았습니다. 먼저 눈길을 끌고(히어로) 일정을 확인시키고(타임라인) 신뢰를 쌓은 뒤(연사) 실행 정보를 주고(장소) 구매를 결정시키고(가격) 신뢰를 한 번 더 보강하고(스폰서) 남은 의심을 없앤 다음(FAQ) 전환시키고(등록 CTA) 마지막으로 지금 해야 할 이유를 못 박습니다(카운트다운). 사진·로고 자리는 전부 이 사이트의 먹·파랑·크림 토큰으로 만든 플레이스홀더 도형이라, 실제 로고와 사진만 갈아 끼우면 됩니다.
01이벤트 히어로
무대 뒤 흐릿한 원 세 개가 계속 떠다니는 동안 워드마크(도형 플레이스홀더)·헤드라인·날짜/장소 알약·CTA가 한 화면에 자리 잡습니다. 오른쪽 "관심 등록" 버튼은 클릭마다 aria-pressed가 실제로 바뀌며 아이콘이 채워집니다.
.eh__orb--1 { animation: eh-float-a 2s ease-in-out infinite; }
@keyframes eh-float-a {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate($sp-10, -$sp-8) scale(1.5); }
}
.eh__save[aria-pressed="true"] {
background: rgba(255, 247, 230, .12);
border-color: $subject-cream;
}
.eh__save[aria-pressed="true"] .eh__save-ic {
opacity: 1;
color: $color;
transform: scale(1.15);
}
02일정 타임라인
세로선 위 정류장(등록·기조연설·네트워킹·패널)을 클릭하면 그 지점까지 진행바가 scaleY로 실제로 채워지고, 지금 연 정류장의 점만 펄스 링으로 강조됩니다.
.tl__fill {
position: absolute; inset: 0;
transform: scaleY(.25);
transform-origin: top;
background: $color;
transition: transform $duration $easing;
}
.tl__stop[aria-current="step"] .tl__dot {
background: $color;
transform: scale(1.3);
box-shadow: 0 0 0 4px rgba(23, 20, 26, .14);
}
03연사 그리드
카드 여섯 장이 순서대로 살짝 기울었다 펴지며 떠오릅니다. 카드를 클릭하면 그 카드만 한 줄짜리 소개가 max-height 전환으로 펼쳐지고 나머지는 그대로 닫혀 있습니다.
.sg.is-demo .sg__card { animation: sg-in 2s $easing infinite; }
@keyframes sg-in {
0% { opacity: .7; transform: translateY(6px) rotate(-1.5deg); }
18% { opacity: 1; transform: translateY(0) rotate(0deg); }
86% { opacity: 1; transform: translateY(0) rotate(0deg); }
100% { opacity: .7; transform: translateY(6px) rotate(-1.5deg); }
}
.sg__card.is-open .sg__bio { max-height: 60px; opacity: 1; }
04장소 안내
지하철·버스·자차 탭을 누르면 그 이동수단 설명만 실제로 보이고, 밑줄 인디케이터가 고른 탭 아래까지 미끄러집니다. 오른쪽 지도 플레이스홀더 위 핀은 계속 파문을 그립니다. 여기는 :checked 대신 진짜 버튼 클릭으로 상태가 바뀝니다.
tabs.forEach(function (t) {
t.addEventListener('click', function () {
tabs.forEach(function (o) { o.setAttribute('aria-selected', 'false'); });
t.setAttribute('aria-selected', 'true');
ind.style.transform = 'translateX(' + (Number(t.dataset.i) * 100) + '%)';
panels.forEach(function (p, i) {
p.style.opacity = String(i === Number(t.dataset.i) ? 1 : 0);
});
});
});
05티켓 가격표
가운데 카드에 "인기" 배지와 은은한 글로우가 계속 맴돕니다. 위쪽 얼리버드/정가 스위치를 누르면 세 카드의 가격 숫자가 한 번에 크로스페이드로 바뀝니다.
.tp__price-a, .tp__price-b {
position: absolute; inset: 0;
transition: opacity $duration $easing;
}
.tp__price-b { opacity: 0; }
.tp__switch[aria-checked="true"] ~ .tp__cards .tp__price-a { opacity: 0; }
.tp__switch[aria-checked="true"] ~ .tp__cards .tp__price-b { opacity: 1; }
.tp__card--pop { animation: tp-glow 2.4s ease-in-out infinite; }
06스폰서 로고 바
로고 여덟 개(전부 도형 플레이스홀더, 실제 로고 아님)가 트랙을 이어 붙여 끊김 없이 흐릅니다. 마우스를 올리거나 버튼을 탭하면 그 자리에서 멈춥니다.
// 트랙 자체엔 gap 을 쓰지 않는다 — 항목마다 margin-right 로 간격을 준다
.sb__track { display: flex; width: max-content; animation: sb-scroll 14s linear infinite; }
.sb__logo { flex: 0 0 auto; margin-right: $sp-6; }
@keyframes sb-scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
07이벤트 FAQ
질문 세 개 중 하나만 열려 있습니다. 클릭한 질문의 답변 칸이 grid-template-rows로 실제로 펼쳐지고 나머지는 닫히며, 화살표가 함께 돕니다.
.fq__a-wrap {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows $duration $easing;
}
.fq__item.is-open .fq__a-wrap { grid-template-rows: 1fr; }
.fq__a { min-height: 0; overflow: hidden; }
08등록 CTA
이메일을 넣고 버튼을 누르면 실제로 형식을 검사합니다. 통과하면 버튼에 체크가 그려지고 아래에 "등록 완료" 안내 문구가 뜨며, 형식이 틀리면 입력칸이 좌우로 짧게 흔들립니다.
form.addEventListener('submit', function (e) {
e.preventDefault();
var ok = /^\S+@\S+\.\S+$/.test(input.value);
if (ok) {
form.classList.add('is-done');
done.textContent = '등록 완료 — 확인 메일을 보냈습니다.';
} else {
input.classList.remove('is-shake');
void input.offsetWidth;
input.classList.add('is-shake');
done.textContent = '올바른 이메일을 입력해 주세요.';
}
});
09카운트다운 띠
로드된 시점부터 정확히 7일 4시간 20분 15초 뒤를 목표 시각으로 잡고, 1초마다 실제로 숫자가 줄어듭니다. 초 카드는 매초 살짝 튀어 오릅니다.
var target = Date.now() + ((7 * 24 * 60 * 60) + (4 * 60 * 60) + (20 * 60) + 15) * 1000;
function tick() {
var diff = Math.max(0, target - Date.now());
var s = Math.floor(diff / 1000);
els.d.textContent = pad(Math.floor(s / 86400));
els.h.textContent = pad(Math.floor((s % 86400) / 3600));
els.m.textContent = pad(Math.floor((s % 3600) / 60));
els.s.textContent = pad(s % 60);
}
tick();
setInterval(tick, 1000);
어디서 깨지는가 — 함정
아홉 개를 만드는 동안 진짜 걸린 함정은 화면 크기였습니다. 480×300 캔버스에서는 멀쩡하던 히어로가 처음엔 세로 여백을 너무 많이 잡아먹어 그 안에서도 스크롤바가 떴습니다. 패딩과 줄간격을 한 단계씩 줄여서야 통과했습니다. 진짜 문제는 폭 320px·높이 200px의 좁은 화면이었습니다. 연사 그리드·장소 안내·티켓 가격표·FAQ·등록 CTA 다섯 개가 이 크기에서 키가 260~450px까지 넘쳤습니다. 2열로 접는 게 능사가 아니어서, 연사 그리드는 오히려 3열을 유지하고 역할 설명 같은 2차 정보만 좁은 화면에서 숨기는 쪽으로 고쳤습니다.
카운트다운 초 카드를 처음엔 rotateX(0 → -90deg → 0)로 통째로 뒤집었는데, perspective 없이 90도 지점을 지나는 순간 숫자가 완전히 납작해져 캡처 화면에 선 하나("--")처럼 찍혔습니다. 3D 플립 대신 translateY+scale로 살짝 튀는 정도로 바꾸자 해결됐습니다. 스폰서 로고 바처럼 translateX(-50%)로 무한 스크롤하는 트랙은 항목 사이 간격을 gap이 아니라 margin-right로 줘야 합니다. gap을 쓰면 두 벌(2N개) 사이 이음새가 gap ÷ 2만큼 모자라 트랙이 한 바퀴 돌 때마다 살짝 튑니다. 이 두 가지를 포함해 반응형과 애니메이션을 전부 손본 다음, 5ngtcz6h으로 최신 상태의 zip 을 열어 보면 아홉 개가 위 표대로 정확히 동작합니다.
접근성
아홉 개 전부 prefers-reduced-motion: reduce에서 장식 전환·애니메이션을 끄되 상태 자체는 남깁니다 — 일정 진행바는 채워진 채로, FAQ 답변은 열린 채로 고정되고 카운트다운 숫자 갱신만은 그대로 유지합니다. 장소 안내(04)는 role="tablist"·role="tab"·aria-selected를 실제로 갖춘 탭이고, 등록 CTA(08)의 결과 문구는 role="status"(aria-live)라 스크린 리더가 제출 결과를 바로 읽습니다. 카운트다운(09)은 role="timer"에 aria-live="off"를 줘서 매초 갱신되는 숫자가 스크린 리더를 방해하지 않게 했습니다. 탭·아코디언은 표준 button 요소로 만들어 Tab·Enter로 그대로 조작되고, role과 상태 속성은 W3C ARIA Authoring Practices의 탭 패턴을 참고했습니다.
다른 CSS 템플릿 소재는 템플릿 카테고리에 모여 있고, 이 사이트가 무엇을 다루는지는 소개에 있습니다.
FAQ
이 템플릿에 실제 로고나 사진을 써도 되나요?
네, 그게 목적입니다. 아홉 개 전부 사진·로고 자리는 도형 플레이스홀더라 실제 이미지·로고로 갈아 끼우기만 하면 됩니다. 색은 demo-tokens.scss 값을 그대로 써도 되고, 브랜드 색으로 바꿔도 레이아웃은 그대로 유지됩니다.
React 버전은 CSS 버전과 뭐가 다른가요?
동작은 같습니다. 활성 탭·열린 아코디언 같은 상태는 useState로 들고, 변수 3개(duration·easing·color)는 props로 받아 CSS 변수(--duration --easing --color)로 넘깁니다. 카운트다운은 useEffect 안에서 같은 setInterval 로직을 그대로 씁니다.
카운트다운이 실제로 작동하나요, 아니면 장식인가요?
실제로 작동합니다. 페이지를 연 시점부터 정확히 7일 4시간 20분 15초 뒤를 목표로 잡고 1초마다 갱신됩니다. 목표 시각은 targetMs 값 하나만 실제 행사 날짜로 바꾸면 그대로 쓸 수 있습니다. 자동 재생 GIF에서 보이는 초 카드의 튀는 동작은 별도 @keyframes 장식이고, 숫자 갱신과는 독립적으로 작동합니다.