07Trích dẫn nhà sáng lập
Hai trích dẫn của nhà sáng lập chuyền nhau qua hai nút chấm; bấm chấm là crossfade thật sang trích dẫn đó, còn dấu ngoặc kép trang trí khẽ lay.
Dùng choSection thông điệp nhà sáng lập trong trang giới thiệu và press room
opacityclickaria-selected

<main class="stage">
<section class="fq is-demo">
<span class="fq__mark" aria-hidden="true">"</span>
<div class="fq__stage">
<figure class="fq__slide is-active" data-i="0">
<blockquote class="fq__quote">"Chúng tôi tin nhóm nhỏ cũng đưa được tin nhanh như các tờ báo lớn."</blockquote>
<figcaption class="fq__who"><span class="fq__name">Lee Dohyun</span> · Đồng sáng lập & CEO</figcaption>
</figure>
<figure class="fq__slide" data-i="1">
<blockquote class="fq__quote">"Press room không phải chỗ quảng bá — đó là nơi xác lập niềm tin."</blockquote>
<figcaption class="fq__who"><span class="fq__name">Park Seoyeon</span> · Đồng sáng lập & CTO</figcaption>
</figure>
</div>
<div class="fq__dots" role="group" aria-label="Chọn trích dẫn">
<button class="fq__dot is-active" type="button" data-i="0" aria-selected="true" aria-label="Trích dẫn thứ nhất"></button>
<button class="fq__dot" type="button" data-i="1" aria-selected="false" aria-label="Trích dẫn thứ hai"></button>
</div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-enter; // 슬라이드 크로스페이드
$easing: $ease-spring;
$color: $subject-ink; // 인용부호·강조색
@include stage($stage-orange);
.fq {
position: relative;
box-sizing: border-box; // width 에 패딩을 포함 — 아니면 320px 에서 패딩만큼 넘친다(실측 2026-09-10)
width: min(400px, 94vw);
background: $subject-cream;
border-radius: $r-card;
padding: $sp-6 $sp-4 $sp-4;
text-align: center;
overflow: hidden;
}
.fq__mark {
position: absolute;
top: $sp-1; left: 50%;
transform: translateX(-50%);
font: 800 56px/1 Georgia, serif;
color: rgba(47, 109, 246, .32);
animation: fq-wobble 2s $easing infinite;
}
@keyframes fq-wobble {
0%, 100% { transform: translateX(-50%) rotate(0deg) scale(1); }
50% { transform: translateX(-50%) rotate(-10deg) scale(1.35); }
}
.fq__stage { position: relative; min-height: 96px; }
.fq__slide {
position: absolute; inset: 0;
margin: 0;
opacity: 0;
transition: opacity $duration $easing;
pointer-events: none;
}
.fq__slide.is-active { opacity: 1; pointer-events: auto; }
.fq__quote {
margin: 0 0 $sp-2;
font-weight: 700; font-size: clamp(12px, 3.6vw, 14px); line-height: 1.5; font-family: inherit;
color: $subject-ink;
}
.fq__who { font-weight: 600; font-size: 11px; line-height: 1.4; font-family: inherit; color: rgba($subject-ink, .72); }
.fq__name { color: $color; }
.fq__dots { display: flex; justify-content: center; gap: $sp-2; margin-top: $sp-3; }
.fq__dot {
width: 8px; height: 8px;
border-radius: 50%;
border: 0;
padding: 0;
background: rgba(23, 20, 26, .2);
cursor: pointer;
transition: background $duration $easing, transform $duration $easing;
}
.fq__dot.is-active { background: $color; transform: scale(1.3); }
// ── 반응형(BD9) ──────────────────────────────────────────
@media (max-width: 340px), (max-height: 220px) {
.fq { padding: $sp-4 $sp-3 $sp-3; }
.fq__mark { font-size: 28px; }
.fq__stage { min-height: 70px; }
.fq__quote { font-size: 11px; }
}
// ── 접근성 ──────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.fq__mark { animation: none !important; }
.fq__slide, .fq__dot { transition: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.fq {
position: relative;
box-sizing: border-box;
width: min(400px, 94vw);
background: #fff7e6;
border-radius: 16px;
padding: 24px 16px 16px;
text-align: center;
overflow: hidden;
}
.fq__mark {
position: absolute;
top: 4px;
left: 50%;
transform: translateX(-50%);
font: 800 56px/1 Georgia, serif;
color: rgba(47, 109, 246, 0.32);
animation: fq-wobble 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes fq-wobble {
0%, 100% {
transform: translateX(-50%) rotate(0deg) scale(1);
}
50% {
transform: translateX(-50%) rotate(-10deg) scale(1.35);
}
}
.fq__stage {
position: relative;
min-height: 96px;
}
.fq__slide {
position: absolute;
inset: 0;
margin: 0;
opacity: 0;
transition: opacity 600ms cubic-bezier(0.2, 0.8, 0.2, 1);
pointer-events: none;
}
.fq__slide.is-active {
opacity: 1;
pointer-events: auto;
}
.fq__quote {
margin: 0 0 8px;
font-weight: 700;
font-size: clamp(12px, 3.6vw, 14px);
line-height: 1.5;
font-family: inherit;
color: #17141a;
}
.fq__who {
font-weight: 600;
font-size: 11px;
line-height: 1.4;
font-family: inherit;
color: rgba(23, 20, 26, 0.72);
}
.fq__name {
color: #17141a;
}
.fq__dots {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 12px;
}
.fq__dot {
width: 8px;
height: 8px;
border-radius: 50%;
border: 0;
padding: 0;
background: rgba(23, 20, 26, 0.2);
cursor: pointer;
transition: background 600ms cubic-bezier(0.2, 0.8, 0.2, 1), transform 600ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.fq__dot.is-active {
background: #17141a;
transform: scale(1.3);
}
@media (max-width: 340px), (max-height: 220px) {
.fq {
padding: 16px 12px 12px;
}
.fq__mark {
font-size: 28px;
}
.fq__stage {
min-height: 70px;
}
.fq__quote {
font-size: 11px;
}
}
@media (prefers-reduced-motion: reduce) {
.fq__mark {
animation: none !important;
}
.fq__slide, .fq__dot {
transition: none;
}
}// 실제 동작: 점을 클릭하면 그 인용문으로 실제로 크로스페이드 전환된다.
var root = document.querySelector('.fq');
var dots = root.querySelectorAll('.fq__dot');
var slides = root.querySelectorAll('.fq__slide');
dots.forEach(function (dot) {
dot.addEventListener('click', function () {
root.classList.remove('is-demo');
var i = dot.getAttribute('data-i');
dots.forEach(function (d) { d.classList.remove('is-active'); d.setAttribute('aria-selected', 'false'); });
slides.forEach(function (s) { s.classList.remove('is-active'); });
dot.classList.add('is-active'); dot.setAttribute('aria-selected', 'true');
root.querySelector('.fq__slide[data-i="' + i + '"]').classList.add('is-active');
});
});import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./PressFounderQuote.module.scss";
export type PressFounderQuoteProps = { duration?: number; easing?: string; color?: string };
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const QUOTES = [
{ quote: "\"작은 팀도 대형 언론과 같은 속도로 소식을 전할 수 있어야 한다고 믿습니다.\"", name: "이도현", role: "공동창업자 & CEO" },
{ quote: "\"프레스룸은 홍보가 아니라 신뢰를 확인시켜주는 자리입니다.\"", name: "박서연", role: "공동창업자 & CTO" },
];
export default function PressFounderQuote({ duration = 600, easing = "cubic-bezier(.2,.8,.2,1)", color = "#17141a" }: PressFounderQuoteProps) {
const [active, setActive] = useState(0);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.fq} style={vars}>
<span className={styles.mark} aria-hidden="true">"</span>
<div className={styles.stage}>
{QUOTES.map((q, i) => (
<figure key={q.name} className={i === active ? `${styles.slide} ${styles.active}` : styles.slide}>
<blockquote className={styles.quote}>{q.quote}</blockquote>
<figcaption className={styles.who}><span className={styles.name}>{q.name}</span> · {q.role}</figcaption>
</figure>
))}
</div>
<div className={styles.dots} role="group" aria-label="인용문 선택">
{QUOTES.map((q, i) => (
<button
key={q.name}
type="button"
className={i === active ? `${styles.dot} ${styles.active}` : styles.dot}
aria-selected={i === active}
aria-label={`${i + 1}번째 인용`}
onClick={() => setActive(i)}
/>
))}
</div>
</section>
);
}07 Trích dẫn nhà sáng lập — Diện tích đo được 0.366% · khung hình chuyển động 17/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 mẫu press page design — dán vào landing
Bài viết chứa demo này
9 mẫu press page design — dán vào landingPress page design là bộ code gom sẵn từng khối của trang báo chí: press release, media kit, liên hệ người phụ trách. Từ Xem tất cả demo trong bài này


