08등 뒤에 커다란 따옴표가 떠오르는 인용구
인용문 등 뒤로 커다란 따옴표가 서서히 떠올라 배경 무늬가 되고, 전경 문장은 그 위에 또렷하게 남습니다.
쓰임에세이 리드 문구, 책 표지 문장
opacityscalez-index

<main class="stage">
<figure class="qw is-demo" aria-label="뒤에 큰 따옴표가 떠오르는 인용구">
<span class="qw__mark" aria-hidden="true">“</span>
<blockquote class="qw__body">느린 문장이<br>빠른 문장을 이긴다.</blockquote>
<figcaption class="qw__cite">— 에세이 여는 글</figcaption>
</figure>
</main>@use "../../../../site/static/demo-tokens" as *;
// 에세이 리드용 인용구 — 등 뒤로 커다란 따옴표 글리프가 서서히 떠올라(scale+opacity) 배경 무늬가 되고,
// 전경 문장은 z-index 로 한 층 위에 또렷하게 남는다. 워터마크는 아주 연하게(라일락 22%) —
// 글자가 아니라 '자리'를 만드는 역할이므로 대비를 낮춘다.
$duration: $dur-loop;
$easing: $ease-out;
$color: $subject-ink;
@include stage($stage-paper);
.qw {
box-sizing: border-box;
position: relative;
width: min(280px, 92vw);
margin: 0;
padding: $sp-8 $sp-4;
display: grid;
justify-items: center;
gap: $sp-3;
text-align: center;
font-family: inherit;
}
.qw * { box-sizing: border-box; }
// 거대 따옴표 — 문장 뒤 한 층 아래
.qw__mark {
position: absolute;
z-index: 0;
top: -8px;
left: 50%;
translate: -50% 0;
font-family: Georgia, "Times New Roman", serif;
font-size: 120px;
font-weight: 700;
line-height: 1;
color: $subject-lilac;
opacity: .22;
pointer-events: none;
}
.qw__body {
position: relative;
z-index: 1;
margin: 0;
font-size: 17px;
font-weight: 800;
line-height: 1.6;
color: $color;
}
.qw__cite {
position: relative;
z-index: 1;
margin: 0;
font-size: 11px;
font-weight: 500;
line-height: 1.4;
color: rgba(23, 20, 26, .6);
}
// ── 미리보기 루프 ─────────────────
.qw.is-demo .qw__mark {
animation-name: qwMark;
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
}
.qw.is-demo .qw__body {
animation-name: qwBody;
animation-duration: $duration;
animation-timing-function: $ease-spring;
animation-iteration-count: infinite;
}
.qw.is-demo .qw__cite {
animation-name: qwCite;
animation-duration: $duration;
animation-timing-function: $ease-spring;
animation-iteration-count: infinite;
}
// 워터마크: 0~46% 천천히 확장하며 떠오름 — 문장 컷 등장과 겹치는 초반 프레임이 최대 변화.
// 문장·출처는 opacity 램프가 아니라 steps 컷으로 등장한 뒤 translateY 로 떠오른다
// (키프레임 안 animation-timing-function 으로 속성 구간별 타이밍을 나눈다 — 텍스트 페이드의
// 중간값이 poster 에 유령으로 남는다, 237 계열 함정).
// 퇴장은 셋을 서로 먼 구간에 12%+ 페이드로 — 통째로 사라지는 컷 프레임이 입장을 이기고
// 빈 화면이 poster 가 되는 것을 막는다(248-09 함정).
@keyframes qwMark {
0% { opacity: 0; transform: scale(.6); }
46%, 70% { opacity: .22; transform: scale(1); }
92%, 100% { opacity: 0; transform: scale(.8); }
}
@keyframes qwBody {
0% { opacity: 0; transform: translateY(10px); animation-timing-function: steps(1, end); }
10% { opacity: 1; transform: translateY(10px); animation-timing-function: $easing; }
30%, 60% { opacity: 1; transform: translateY(0); }
74%, 100% { opacity: 0; }
}
@keyframes qwCite {
0% { opacity: 0; transform: translateY(8px); animation-timing-function: steps(1, end); }
16% { opacity: 1; transform: translateY(8px); animation-timing-function: $easing; }
36%, 58% { opacity: 1; transform: translateY(0); }
70%, 100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.qw.is-demo .qw__mark,
.qw.is-demo .qw__body,
.qw.is-demo .qw__cite {
animation: none !important;
}
}
@media (max-width: 340px) {
.qw { padding: $sp-6 $sp-3; }
.qw__body { font-size: 16px; }
.qw__mark { font-size: 104px; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.qw {
box-sizing: border-box;
position: relative;
width: min(280px, 92vw);
margin: 0;
padding: 32px 16px;
display: grid;
justify-items: center;
gap: 12px;
text-align: center;
font-family: inherit;
}
.qw * {
box-sizing: border-box;
}
.qw__mark {
position: absolute;
z-index: 0;
top: -8px;
left: 50%;
translate: -50% 0;
font-family: Georgia, "Times New Roman", serif;
font-size: 120px;
font-weight: 700;
line-height: 1;
color: #b9a0ff;
opacity: 0.22;
pointer-events: none;
}
.qw__body {
position: relative;
z-index: 1;
margin: 0;
font-size: 17px;
font-weight: 800;
line-height: 1.6;
color: #17141a;
}
.qw__cite {
position: relative;
z-index: 1;
margin: 0;
font-size: 11px;
font-weight: 500;
line-height: 1.4;
color: rgba(23, 20, 26, 0.6);
}
.qw.is-demo .qw__mark {
animation-name: qwMark;
animation-duration: 2s;
animation-timing-function: ease-out;
animation-iteration-count: infinite;
}
.qw.is-demo .qw__body {
animation-name: qwBody;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
}
.qw.is-demo .qw__cite {
animation-name: qwCite;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
}
@keyframes qwMark {
0% {
opacity: 0;
transform: scale(0.6);
}
46%, 70% {
opacity: 0.22;
transform: scale(1);
}
92%, 100% {
opacity: 0;
transform: scale(0.8);
}
}
@keyframes qwBody {
0% {
opacity: 0;
transform: translateY(10px);
animation-timing-function: steps(1, end);
}
10% {
opacity: 1;
transform: translateY(10px);
animation-timing-function: ease-out;
}
30%, 60% {
opacity: 1;
transform: translateY(0);
}
74%, 100% {
opacity: 0;
}
}
@keyframes qwCite {
0% {
opacity: 0;
transform: translateY(8px);
animation-timing-function: steps(1, end);
}
16% {
opacity: 1;
transform: translateY(8px);
animation-timing-function: ease-out;
}
36%, 58% {
opacity: 1;
transform: translateY(0);
}
70%, 100% {
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.qw.is-demo .qw__mark,
.qw.is-demo .qw__body,
.qw.is-demo .qw__cite {
animation: none !important;
}
}
@media (max-width: 340px) {
.qw {
padding: 24px 12px;
}
.qw__body {
font-size: 16px;
}
.qw__mark {
font-size: 104px;
}
}// QuoteWatermarkFade — vanilla 판(run/282/demo/08-quote-watermark-fade) 의 React 포트.
import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./QuoteWatermarkFade.module.scss";
export type QuoteWatermarkFadeProps = {
line1?: string;
line2?: string;
cite?: string;
groupLabel?: string;
demo?: boolean;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function QuoteWatermarkFade({
line1 = "느린 문장이",
line2 = "빠른 문장을 이긴다.",
cite = "— 에세이 여는 글",
groupLabel = "뒤에 큰 따옴표가 떠오르는 인용구",
demo = true,
duration = 2000,
easing = "ease-out",
color = "#17141a",
}: QuoteWatermarkFadeProps) {
const [live, setLive] = useState(demo);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
function onEnter() {
setLive(false);
}
const cls = [styles.qw, live ? styles.isDemo : ""].filter(Boolean).join(" ");
return (
<figure className={cls} style={vars} aria-label={groupLabel} onMouseEnter={onEnter}>
<span className={styles.mark} aria-hidden="true">“</span>
<blockquote className={styles.body}>{line1}<br />{line2}</blockquote>
<figcaption className={styles.cite}>{cite}</figcaption>
</figure>
);
}08 등 뒤에 커다란 따옴표가 떠오르는 인용구 — 측정 누적면적 4.192% · 움직인 프레임 14/23GODRICH 원작 · zip 에 SCSS·React 포함 · 인용구 디자인 CSS 9종 — 블로그에 바로 붙이는 인용구 박스 코드
이 데모가 속한 글
인용구 디자인 CSS 9종 — 블로그에 바로 붙이는 인용구 박스 코드인용구 디자인은 남의 문장을 내 글 사이에 예쁘게 앉히는 인용구 박스를 말합니다. 이 페이지에는 그 박스 아홉 벌이 실제로 돌아가는 채로 들어 있고, 격자 아래 코드를 통째로 복사해 블로그에 바로 붙이는 데 씁니다. 이 글의 데모 전부 보기


