07Founder quote
창업자 인용문 두 개가 점 두 개로 오가며, 점을 클릭하면 그 인용문으로 실제로 크로스페이드 전환되고 따옴표 장식이 은은하게 흔들립니다.
Best for
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">"We believe even a small team can share news as fast as major media."</blockquote>
<figcaption class="fq__who"><span class="fq__name">Lee Dohyun</span> · Co-founder & CEO</figcaption>
</figure>
<figure class="fq__slide" data-i="1">
<blockquote class="fq__quote">"A press room isn't for promotion—it's where trust is confirmed."</blockquote>
<figcaption class="fq__who"><span class="fq__name">Park Seoyeon</span> · Co-founder & CTO</figcaption>
</figure>
</div>
<div class="fq__dots" role="group" aria-label="Select quote">
<button class="fq__dot is-active" type="button" data-i="0" aria-selected="true" aria-label="First quote"></button>
<button class="fq__dot" type="button" data-i="1" aria-selected="false" aria-label="Second quote"></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 Founder quote — Measured area 0.366% · moving frames 17/23Original by GODRICH · SCSS and React ship in the zip · 9 Media Page Templates — Copy-Paste
The post this demo lives in
9 Media Page Templates — Copy-PasteA media page template bundles the sections a press room needs — release feed, downloadable assets, spokesperson contact See all demos in this post


