05チケット料金
가운데 카드에 '인기' 배지와 은은한 글로우가 계속 맴돌고, 위쪽 얼리버드/정가 스위치를 누르면 세 카드의 가격 숫자가 opacity 크로스페이드로 한 번에 바뀝니다.
使いどころ
scaleopacityaria-checked

<main class="stage">
<section class="tp is-demo">
<button class="tp__switch" type="button" role="switch" aria-checked="false">
<span class="tp__switch-label">早割</span>
<span class="tp__switch-track" aria-hidden="true"><span class="tp__switch-knob"></span></span>
<span class="tp__switch-label">通常</span>
</button>
<ul class="tp__cards">
<li class="tp__card">
<p class="tp__tier">スタンダード</p>
<p class="tp__price"><span class="tp__price-a">49,000ウォン</span><span class="tp__price-b">69,000ウォン</span></p>
<p class="tp__desc">全セッション・資料集</p>
</li>
<li class="tp__card tp__card--pop">
<span class="tp__badge">人気</span>
<p class="tp__tier">プレミアム</p>
<p class="tp__price"><span class="tp__price-a">89,000ウォン</span><span class="tp__price-b">119,000ウォン</span></p>
<p class="tp__desc">全セッション・ランチ</p>
</li>
<li class="tp__card">
<p class="tp__tier">VIP</p>
<p class="tp__price"><span class="tp__price-a">149,000ウォン</span><span class="tp__price-b">189,000ウォン</span></p>
<p class="tp__desc">全特典・ディナー</p>
</li>
</ul>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-base; // 스위치·가격 전환
$easing: $ease-spring;
$color: $subject-blue; // 스위치·인기 카드 강조색
@include stage($stage-ink);
.tp { width: min(420px, 94vw); }
.tp__switch {
display: flex; align-items: center; gap: $sp-2;
margin: 0 auto $sp-4;
background: none; border: 0; padding: 0;
cursor: pointer; font: inherit;
-webkit-tap-highlight-color: transparent;
}
.tp__switch-label { font-weight: 600; font-size: 11px; line-height: 1; font-family: inherit; color: rgba(255, 247, 230, .55); }
.tp__switch-track {
position: relative;
width: 34px; height: $sp-4;
border-radius: $r-pill;
background: rgba(255, 247, 230, .18);
}
.tp__switch-knob {
position: absolute; top: 2px; left: 2px;
width: 12px; height: 12px;
border-radius: 50%;
background: $subject-cream;
transform: translateX(0);
transition: transform $duration $easing;
}
.tp__switch[aria-checked="true"] .tp__switch-knob { transform: translateX(18px); background: $color; }
.tp__cards { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: $sp-2; }
@media (max-width: 340px) { .tp__cards { grid-template-columns: 1fr; } }
@media (max-width: 340px), (max-height: 220px) {
.tp__switch { margin-bottom: $sp-2; }
.tp__cards { grid-template-columns: repeat(3, 1fr); gap: $sp-1; }
.tp__card { padding: $sp-2 $sp-1; }
.tp__desc { display: none; }
.tp__tier { margin-bottom: $sp-1; font-size: 10px; }
}
.tp__card {
position: relative;
padding: $sp-4 $sp-2;
border-radius: $r-card;
background: rgba(255, 247, 230, .06);
border: 1px solid rgba(255, 247, 230, .12);
text-align: center;
}
.tp__card--pop {
background: rgba(47, 109, 246, .14);
border-color: $color;
animation: tp-glow 2.4s ease-in-out infinite;
}
@keyframes tp-glow {
0%, 100% { box-shadow: 0 0 0 0 rgba(47, 109, 246, .35); }
50% { box-shadow: 0 0 18px 2px rgba(47, 109, 246, .35); }
}
.tp__badge {
position: absolute; top: -10px; left: 50%; transform: translateX(-50%);
padding: $sp-1 $sp-2;
border-radius: $r-pill;
background: $subject-blue-deep;
color: $subject-cream;
font-weight: 700; font-size: 9px; line-height: 1.6; font-family: inherit;
}
.tp__tier { margin: 0 0 $sp-2; font-weight: 700; font-size: clamp(11px, 3vw, 12px); line-height: 1; font-family: inherit; color: $subject-cream; }
.tp__price { position: relative; margin: 0 0 $sp-1; min-height: 18px; }
.tp__price-a, .tp__price-b {
position: absolute; inset: 0;
font-weight: 800; font-size: clamp(12px, 3.4vw, 14px); line-height: 1; font-family: inherit;
color: $subject-cream;
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__desc { margin: $sp-4 0 0; font-weight: 500; font-size: 9px; line-height: 1.4; font-family: inherit; color: rgba(255, 247, 230, .55); }
// ── 데모 루프: 클릭 전까지 얼리버드 ↔ 정가를 스스로 오간다 ──────────────
.tp.is-demo .tp__switch-knob { animation: tp-knob $dur-loop $easing infinite; }
.tp.is-demo .tp__price-a { animation: tp-a $dur-loop $easing infinite; }
.tp.is-demo .tp__price-b { animation: tp-b $dur-loop $easing infinite; }
@keyframes tp-knob {
0%, 40% { transform: translateX(0); background: $subject-cream; }
55%, 95% { transform: translateX(18px); background: $color; }
100% { transform: translateX(0); background: $subject-cream; }
}
@keyframes tp-a { 0%, 40% { opacity: 1; } 55%, 95% { opacity: 0; } 100% { opacity: 1; } }
@keyframes tp-b { 0%, 40% { opacity: 0; } 55%, 95% { opacity: 1; } 100% { opacity: 0; } }
// ── 접근성 ──────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.tp__switch-knob, .tp__price-a, .tp__price-b { transition: none; }
.tp__card--pop { animation: none; }
.tp.is-demo .tp__switch-knob,
.tp.is-demo .tp__price-a,
.tp.is-demo .tp__price-b { animation: none !important; }
}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);
}
.tp {
width: min(420px, 94vw);
}
.tp__switch {
display: flex;
align-items: center;
gap: 8px;
margin: 0 auto 16px;
background: none;
border: 0;
padding: 0;
cursor: pointer;
font: inherit;
-webkit-tap-highlight-color: transparent;
}
.tp__switch-label {
font-weight: 600;
font-size: 11px;
line-height: 1;
font-family: inherit;
color: rgba(255, 247, 230, 0.55);
}
.tp__switch-track {
position: relative;
width: 34px;
height: 16px;
border-radius: 999px;
background: rgba(255, 247, 230, 0.18);
}
.tp__switch-knob {
position: absolute;
top: 2px;
left: 2px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #fff7e6;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.tp__switch[aria-checked=true] .tp__switch-knob {
transform: translateX(18px);
background: #2f6df6;
}
.tp__cards {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
@media (max-width: 340px) {
.tp__cards {
grid-template-columns: 1fr;
}
}
@media (max-width: 340px), (max-height: 220px) {
.tp__switch {
margin-bottom: 8px;
}
.tp__cards {
grid-template-columns: repeat(3, 1fr);
gap: 4px;
}
.tp__card {
padding: 8px 4px;
}
.tp__desc {
display: none;
}
.tp__tier {
margin-bottom: 4px;
font-size: 10px;
}
}
.tp__card {
position: relative;
padding: 16px 8px;
border-radius: 16px;
background: rgba(255, 247, 230, 0.06);
border: 1px solid rgba(255, 247, 230, 0.12);
text-align: center;
}
.tp__card--pop {
background: rgba(47, 109, 246, 0.14);
border-color: #2f6df6;
animation: tp-glow 2.4s ease-in-out infinite;
}
@keyframes tp-glow {
0%, 100% {
box-shadow: 0 0 0 0 rgba(47, 109, 246, 0.35);
}
50% {
box-shadow: 0 0 18px 2px rgba(47, 109, 246, 0.35);
}
}
.tp__badge {
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
padding: 4px 8px;
border-radius: 999px;
background: #0a4fe8;
color: #fff7e6;
font-weight: 700;
font-size: 9px;
line-height: 1.6;
font-family: inherit;
}
.tp__tier {
margin: 0 0 8px;
font-weight: 700;
font-size: clamp(11px, 3vw, 12px);
line-height: 1;
font-family: inherit;
color: #fff7e6;
}
.tp__price {
position: relative;
margin: 0 0 4px;
min-height: 18px;
}
.tp__price-a, .tp__price-b {
position: absolute;
inset: 0;
font-weight: 800;
font-size: clamp(12px, 3.4vw, 14px);
line-height: 1;
font-family: inherit;
color: #fff7e6;
transition: opacity 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.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__desc {
margin: 16px 0 0;
font-weight: 500;
font-size: 9px;
line-height: 1.4;
font-family: inherit;
color: rgba(255, 247, 230, 0.55);
}
.tp.is-demo .tp__switch-knob {
animation: tp-knob 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.tp.is-demo .tp__price-a {
animation: tp-a 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.tp.is-demo .tp__price-b {
animation: tp-b 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes tp-knob {
0%, 40% {
transform: translateX(0);
background: #fff7e6;
}
55%, 95% {
transform: translateX(18px);
background: #2f6df6;
}
100% {
transform: translateX(0);
background: #fff7e6;
}
}
@keyframes tp-a {
0%, 40% {
opacity: 1;
}
55%, 95% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes tp-b {
0%, 40% {
opacity: 0;
}
55%, 95% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.tp__switch-knob, .tp__price-a, .tp__price-b {
transition: none;
}
.tp__card--pop {
animation: none;
}
.tp.is-demo .tp__switch-knob,
.tp.is-demo .tp__price-a,
.tp.is-demo .tp__price-b {
animation: none !important;
}
}// 실제 동작: 스위치를 누르면 세 카드의 가격이 실제로 早割/정가로 바뀐다.
var sw = document.querySelector('.tp__switch');
sw.addEventListener('click', function () {
document.querySelector('.tp').classList.remove('is-demo');
var on = sw.getAttribute('aria-checked') === 'true';
sw.setAttribute('aria-checked', String(!on));
});import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./TicketPricing.module.scss";
const TIERS = [
{ name: "스탠다드", early: "49,000원", regular: "69,000원", desc: "세션 전체 · 자료집", pop: false },
{ name: "프리미엄", early: "89,000원", regular: "119,000원", desc: "세션 전체 · 네트워킹 런치", pop: true },
{ name: "VIP", early: "149,000원", regular: "189,000원", desc: "전 혜택 · 연사 디너", pop: false },
];
export type TicketPricingProps = { duration?: number; easing?: string; color?: string };
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function TicketPricing({ duration = 300, easing = "cubic-bezier(.2,.8,.2,1)", color = "#2f6df6" }: TicketPricingProps) {
const [regular, setRegular] = useState(false);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.tp} style={vars}>
<button
type="button"
role="switch"
aria-checked={regular}
className={styles.switch}
onClick={() => setRegular((v) => !v)}
>
<span className={styles.switchLabel}>얼리버드</span>
<span className={styles.track} aria-hidden="true">
<span className={styles.knob} style={{ transform: regular ? "translateX(18px)" : "translateX(0)" }} />
</span>
<span className={styles.switchLabel}>정가</span>
</button>
<ul className={styles.cards}>
{TIERS.map((t) => (
<li key={t.name} className={t.pop ? `${styles.card} ${styles.pop}` : styles.card}>
{t.pop && <span className={styles.badge}>인기</span>}
<p className={styles.tier}>{t.name}</p>
<p className={styles.price}>{regular ? t.regular : t.early}</p>
<p className={styles.desc}>{t.desc}</p>
</li>
))}
</ul>
</section>
);
}05 チケット料金 — 計測 累積面積 4.375% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · 【コピペOK】カンファレンス LP セクション テンプレート9選
このデモが属する記事
【コピペOK】カンファレンス LP セクション テンプレート9選カンファレンス LP セクションテンプレートは、ヒーロー・スケジュール・登壇者紹介のようなイベントランディングページ用ブロックを、LPにそのまま貼れる形でまとめたコード集です。9つのうち1つは、実際の目標時刻まで秒単位で減っていくカウントダ この記事のデモをすべて見る


