06Taped testimonial note
Masking tape pins the note's two top corners, and the whole sheet sways 1.6 degrees each way around a pivot above it.
Best forTestimonial cards and pinned notices
transform-origin 50% -12px테이프 두 장radius 8

<main class="stage">
<figure class="tapenote">
<span class="tapenote__tape tapenote__tape--l" aria-hidden="true"></span>
<span class="tapenote__tape tapenote__tape--r" aria-hidden="true"></span>
<span class="tapenote__q" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M108 72v72H40a8 8 0 0 1-8-8V72a8 8 0 0 1 8-8h60a8 8 0 0 1 8 8m108-8h-60a8 8 0 0 0-8 8v64a8 8 0 0 0 8 8h68V72a8 8 0 0 0-8-8" opacity=".2"/><path d="M100 56H40a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h60v8a32 32 0 0 1-32 32a8 8 0 0 0 0 16a48.05 48.05 0 0 0 48-48V72a16 16 0 0 0-16-16m0 80H40V72h60Zm116-80h-60a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h60v8a32 32 0 0 1-32 32a8 8 0 0 0 0 16a48.05 48.05 0 0 0 48-48V72a16 16 0 0 0-16-16m0 80h-60V72h60Z"/></g></svg>
</span>
<blockquote class="tapenote__body">Three sheets of paper, and the hero is done.</blockquote>
<figcaption class="tapenote__by">
<span class="tapenote__ava" aria-hidden="true">ㅂ</span>
<span class="tapenote__who">Vibe coder · solo builder</span>
</figcaption>
</figure>
</main>@use "../../../../site/static/demo-tokens" as *;
// 페이퍼컷 팔레트
$paper-base: #fbf6ec;
$paper-sheet: #ffffff;
$paper-ink: #2a2320;
$paper-stone: #5c5048;
$paper-coral: #b97b71;
$paper-sky: #6a8db5;
$paper-leaf: #2b7d5a;
$paper-butter: #d6c9ab;
$paper-edge: #e7dccb;
$duration: $dur-loop; // 메모가 좌우로 한 번 흔들리는 주기
$easing: $ease-spring; // 테이프에 매달린 종이의 감쇠
$color: $paper-butter; // 마스킹 테이프 색
@include stage($paper-sheet);
.tapenote {
box-sizing: border-box;
position: relative;
margin: 0;
width: clamp(224px, 74vw, 308px);
padding: $sp-6 $sp-5 $sp-5;
border-radius: $r-sm;
background: $paper-base;
box-shadow: 0 8px 16px rgba($paper-ink, .14);
transform-origin: 50% -12px; // 규칙8: 종이 밖 축 흔들림(테이프)
animation: noteSway $duration $easing infinite;
}
// 위쪽 두 귀퉁이를 눌러 붙인 마스킹 테이프
.tapenote__tape {
position: absolute;
top: -10px;
width: 56px;
height: $sp-5;
background: $color;
opacity: .92;
border-radius: $r-xs;
}
.tapenote__tape--l { left: $sp-4; transform: rotate(-6deg); }
.tapenote__tape--r { right: $sp-4; transform: rotate(5deg); }
.tapenote__q {
display: block;
color: $paper-coral;
font-size: 22px;
line-height: 1;
margin-bottom: $sp-2;
}
.tapenote__body {
margin: 0 0 $sp-4;
color: $paper-ink;
font-weight: 600; font-size: 15px; line-height: 1.45;
font-family: inherit;
}
.tapenote__by {
display: flex;
align-items: center;
gap: $sp-2;
}
.tapenote__ava {
width: 26px;
height: 26px;
display: grid;
place-items: center;
border-radius: 50%;
background: $paper-leaf;
color: $paper-sheet;
font-weight: 700; font-size: 12px; line-height: 1;
font-family: inherit;
}
.tapenote__who {
color: $paper-stone;
font-weight: 500; font-size: 11px; line-height: 1;
font-family: inherit;
}
// 데모: 테이프 위쪽을 축으로 좌우로 흔들린다. 축이 종이 밖(-12px)이라 아래쪽이 크게 움직인다
@keyframes noteSway {
0%, 100% { transform: rotate(-1.6deg); }
50% { transform: rotate(1.6deg); }
}
@media (max-width: 340px), (max-height: 220px) {
.tapenote { width: 232px; padding: $sp-5 $sp-3 $sp-3; }
.tapenote__tape { width: 40px; height: $sp-4; top: -8px; }
.tapenote__q { font-size: 16px; margin-bottom: $sp-1; }
.tapenote__body { font-size: 12px; margin-bottom: $sp-2; }
.tapenote__ava { width: 20px; height: 20px; font-size: 10px; }
.tapenote__who { font-size: 10px; }
}
@media (prefers-reduced-motion: reduce) {
.tapenote { animation: none !important; transform: none; }
}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);
}
.tapenote {
box-sizing: border-box;
position: relative;
margin: 0;
width: clamp(224px, 74vw, 308px);
padding: 24px 20px 20px;
border-radius: 8px;
background: #fbf6ec;
box-shadow: 0 8px 16px rgba(42, 35, 32, 0.14);
transform-origin: 50% -12px;
animation: noteSway 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.tapenote__tape {
position: absolute;
top: -10px;
width: 56px;
height: 20px;
background: #d6c9ab;
opacity: 0.92;
border-radius: 4px;
}
.tapenote__tape--l {
left: 16px;
transform: rotate(-6deg);
}
.tapenote__tape--r {
right: 16px;
transform: rotate(5deg);
}
.tapenote__q {
display: block;
color: #b97b71;
font-size: 22px;
line-height: 1;
margin-bottom: 8px;
}
.tapenote__body {
margin: 0 0 16px;
color: #2a2320;
font-weight: 600;
font-size: 15px;
line-height: 1.45;
font-family: inherit;
}
.tapenote__by {
display: flex;
align-items: center;
gap: 8px;
}
.tapenote__ava {
width: 26px;
height: 26px;
display: grid;
place-items: center;
border-radius: 50%;
background: #2b7d5a;
color: #ffffff;
font-weight: 700;
font-size: 12px;
line-height: 1;
font-family: inherit;
}
.tapenote__who {
color: #5c5048;
font-weight: 500;
font-size: 11px;
line-height: 1;
font-family: inherit;
}
@keyframes noteSway {
0%, 100% {
transform: rotate(-1.6deg);
}
50% {
transform: rotate(1.6deg);
}
}
@media (max-width: 340px), (max-height: 220px) {
.tapenote {
width: 232px;
padding: 20px 12px 12px;
}
.tapenote__tape {
width: 40px;
height: 16px;
top: -8px;
}
.tapenote__q {
font-size: 16px;
margin-bottom: 4px;
}
.tapenote__body {
font-size: 12px;
margin-bottom: 8px;
}
.tapenote__ava {
width: 20px;
height: 20px;
font-size: 10px;
}
.tapenote__who {
font-size: 10px;
}
}
@media (prefers-reduced-motion: reduce) {
.tapenote {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./TapeNote.module.scss";
export type TapeNoteProps = {
quote?: string;
initial?: string;
who?: string;
/** 한 주기(기본 2000ms) */
duration?: number;
easing?: string;
/** 마스킹 테이프 색 */
color?: string;
/** 좌우로 기우는 각도(deg) */
tilt?: number;
};
type Vars = CSSProperties & {
"--duration"?: string; "--easing"?: string; "--color"?: string; "--tilt"?: string;
};
const QUOTES = (
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="M108 72v72H40a8 8 0 0 1-8-8V72a8 8 0 0 1 8-8h60a8 8 0 0 1 8 8m108-8h-60a8 8 0 0 0-8 8v64a8 8 0 0 0 8 8h68V72a8 8 0 0 0-8-8" opacity=".2"/><path d="M100 56H40a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h60v8a32 32 0 0 1-32 32a8 8 0 0 0 0 16a48.05 48.05 0 0 0 48-48V72a16 16 0 0 0-16-16m0 80H40V72h60Zm116-80h-60a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h60v8a32 32 0 0 1-32 32a8 8 0 0 0 0 16a48.05 48.05 0 0 0 48-48V72a16 16 0 0 0-16-16m0 80h-60V72h60Z"/></g></svg>
);
export default function TapeNote({
quote = "색종이 세 장으로 첫 화면이 끝났습니다.",
initial = "ㅂ",
who = "바이브코더 · 1인 개발",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#f6c453",
tilt = 1.6,
}: TapeNoteProps) {
const vars: Vars = {
"--duration": `${duration}ms`, "--easing": easing, "--color": color, "--tilt": `${tilt}deg`,
};
return (
<figure className={styles.tapenote} style={vars}>
<span className={`${styles.tape} ${styles.tapeL}`} aria-hidden="true" />
<span className={`${styles.tape} ${styles.tapeR}`} aria-hidden="true" />
<span className={styles.q} aria-hidden="true">{QUOTES}</span>
<blockquote className={styles.body}>{quote}</blockquote>
<figcaption className={styles.by}>
<span className={styles.ava} aria-hidden="true">{initial}</span>
<span className={styles.who}>{who}</span>
</figcaption>
</figure>
);
}06 Taped testimonial note — Measured area 6.52% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · Paper Cutout Design: 9 Layered Landing Sections
The post this demo lives in
Paper Cutout Design: 9 Layered Landing SectionsPaper cutout design is a layout style where every surface looks like it was cut with scissors and glued down: each panel See all demos in this post


