03紙目の機能カード
細かい目の入った紙が3枚重なり、一番上の紙が48pxだけ上へ滑って下の紙の目を現し、元の位置へ戻ります。
使いどころ機能紹介カードの3枚重ね、並べ替えカード
1px 결 겹침translateY 시트radius 16

<main class="stage">
<section class="paperstack" aria-label="紙目のカード3枚">
<article class="paperstack__sheet paperstack__sheet--3" aria-hidden="true">
<span class="paperstack__line"></span><span class="paperstack__line"></span>
</article>
<article class="paperstack__sheet paperstack__sheet--2" aria-hidden="true">
<span class="paperstack__tag">2番目</span>
<span class="paperstack__line"></span>
</article>
<article class="paperstack__sheet paperstack__sheet--1">
<p class="paperstack__eyebrow">機能カード</p>
<h2 class="paperstack__title">スムーズな決済</h2>
<p class="paperstack__desc">目の見える紙のカード</p>
</article>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// 스큐어모픽 종이 — base64 노이즈 없이 repeating-linear-gradient 세 겹으로 미세한 결을 만든다.
// 결 방향을 섞는다(0deg·90deg·48deg) — 한 방향만 쓰면 줄무늬로 보인다
$grain-a: rgba(122, 105, 78, .06); // 어두운 결 — 세로
$grain-b: rgba(122, 105, 78, .05); // 어두운 결 — 가로
$grain-c: rgba(255, 255, 255, .5); // 밝은 결 — 비스듬한 실
$paper-face: $subject-cream; // 종이 면 (토큰)
$paper-ink: #574a38; // 종이 위 글자 — cream 대비 7.1:1
$duration: $dur-loop; // 맨 위 시트가 올라갔다 돌아오는 주기
$easing: $ease-spring; // 종이가 살짝 튀어 오른다
$color: $subject-cream;
@include stage($stage-orange);
.paperstack {
position: relative;
width: clamp(200px, 62vw, 280px);
}
// 시트 공통 — 결 그라디언트 + 아래 두 장은 살짝 비틀려 겹침을 보여준다
.paperstack__sheet {
position: absolute;
top: 0; left: 0;
box-sizing: border-box;
width: 100%;
padding: $sp-4;
border-radius: $r-card;
background:
repeating-linear-gradient(0deg, $grain-a 0 1px, transparent 1px 3px),
repeating-linear-gradient(90deg, $grain-b 0 1px, transparent 1px 4px),
repeating-linear-gradient(48deg, $grain-c 0 1px, transparent 1px 7px),
$paper-face;
box-shadow: 0 6px 10px rgba(23, 20, 26, .3);
}
.paperstack__sheet--2 {
top: 10px; left: 8px;
transform: rotate(1.6deg);
height: 152px;
}
.paperstack__sheet--3 {
top: 20px; left: -6px;
transform: rotate(-2.2deg);
height: 148px;
}
// 맨 위 시트 — 48px 위로 미끄러져 아래 시트의 결을 드러낸다
.paperstack__sheet--1 {
position: relative;
height: 156px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: $sp-1;
z-index: 1;
animation: sheetSlide $duration $easing infinite;
}
.paperstack__eyebrow {
margin: 0;
color: #8a6d3f;
font-weight: 700; font-size: clamp(9px, 2.4vw, 11px); line-height: 1;
font-family: inherit;
letter-spacing: .12em;
text-transform: uppercase;
}
.paperstack__title {
margin: 0;
color: $paper-ink;
font-weight: 800; font-size: clamp(16px, 4.2vw, 22px); line-height: 1.2;
font-family: inherit;
}
.paperstack__desc {
margin: 0;
color: $paper-ink;
font-weight: 500; font-size: clamp(11px, 2.8vw, 13px); line-height: 1.4;
font-family: inherit;
}
.paperstack__tag {
margin: $sp-2 0 0;
border-radius: $r-xs;
padding: $sp-1 $sp-2;
background: #e9dcc2;
color: #574a38;
font-weight: 700; font-size: 10px; line-height: 1;
font-family: inherit;
}
.paperstack__line {
display: block;
height: 8px;
margin: $sp-2 0 0;
border-radius: $r-pill;
background: rgba(87, 74, 56, .16);
}
// 0%·12% 정지 → 46%까지 48px 상승 → 잠시 멈춤 → 90%까지 복귀. 텔레포트 없다
@keyframes sheetSlide {
0%, 10% { transform: translateY(0); }
46%, 58% { transform: translateY(-48px); }
88%, 100% { transform: translateY(0); }
}
@media (max-width: 340px), (max-height: 220px) {
.paperstack { width: 208px; }
.paperstack__sheet--1 { height: 128px; padding: $sp-3; }
.paperstack__sheet--2, .paperstack__sheet--3 { height: 124px; }
.paperstack__sheet--2 { top: 8px; }
.paperstack__sheet--3 { top: 16px; }
.paperstack__desc { display: none; }
@keyframes sheetSlide {
0%, 10% { transform: translateY(0); }
46%, 58% { transform: translateY(-20px); }
88%, 100% { transform: translateY(0); }
}
}
@media (prefers-reduced-motion: reduce) {
.paperstack__sheet--1 { 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: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.paperstack {
position: relative;
width: clamp(200px, 62vw, 280px);
}
.paperstack__sheet {
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
width: 100%;
padding: 16px;
border-radius: 16px;
background: repeating-linear-gradient(0deg, rgba(122, 105, 78, 0.06) 0 1px, transparent 1px 3px), repeating-linear-gradient(90deg, rgba(122, 105, 78, 0.05) 0 1px, transparent 1px 4px), repeating-linear-gradient(48deg, rgba(255, 255, 255, 0.5) 0 1px, transparent 1px 7px), #fff7e6;
box-shadow: 0 6px 10px rgba(23, 20, 26, 0.3);
}
.paperstack__sheet--2 {
top: 10px;
left: 8px;
transform: rotate(1.6deg);
height: 152px;
}
.paperstack__sheet--3 {
top: 20px;
left: -6px;
transform: rotate(-2.2deg);
height: 148px;
}
.paperstack__sheet--1 {
position: relative;
height: 156px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
z-index: 1;
animation: sheetSlide 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.paperstack__eyebrow {
margin: 0;
color: #8a6d3f;
font-weight: 700;
font-size: clamp(9px, 2.4vw, 11px);
line-height: 1;
font-family: inherit;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.paperstack__title {
margin: 0;
color: #574a38;
font-weight: 800;
font-size: clamp(16px, 4.2vw, 22px);
line-height: 1.2;
font-family: inherit;
}
.paperstack__desc {
margin: 0;
color: #574a38;
font-weight: 500;
font-size: clamp(11px, 2.8vw, 13px);
line-height: 1.4;
font-family: inherit;
}
.paperstack__tag {
margin: 8px 0 0;
border-radius: 4px;
padding: 4px 8px;
background: #e9dcc2;
color: #574a38;
font-weight: 700;
font-size: 10px;
line-height: 1;
font-family: inherit;
}
.paperstack__line {
display: block;
height: 8px;
margin: 8px 0 0;
border-radius: 999px;
background: rgba(87, 74, 56, 0.16);
}
@keyframes sheetSlide {
0%, 10% {
transform: translateY(0);
}
46%, 58% {
transform: translateY(-48px);
}
88%, 100% {
transform: translateY(0);
}
}
@media (max-width: 340px), (max-height: 220px) {
.paperstack {
width: 208px;
}
.paperstack__sheet--1 {
height: 128px;
padding: 12px;
}
.paperstack__sheet--2, .paperstack__sheet--3 {
height: 124px;
}
.paperstack__sheet--2 {
top: 8px;
}
.paperstack__sheet--3 {
top: 16px;
}
.paperstack__desc {
display: none;
}
@keyframes sheetSlide {
0%, 10% {
transform: translateY(0);
}
46%, 58% {
transform: translateY(-20px);
}
88%, 100% {
transform: translateY(0);
}
}
}
@media (prefers-reduced-motion: reduce) {
.paperstack__sheet--1 {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./PaperGrainCard.module.scss";
export type PaperGrainCardProps = {
eyebrow?: string;
title?: string;
desc?: string;
tag?: string;
/** 한 주기(기본 2000ms) */
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function PaperGrainCard({
eyebrow = "기능 카드",
title = "빠른 결제",
desc = "결이 보이는 종이 카드",
tag = "두 번째",
duration = 2000,
easing = "cubic-bezier(.2, .8, .2, 1)",
color = "#fff7e6",
}: PaperGrainCardProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.paperstack} style={vars} aria-label="종이 결 카드 세 장">
<article className={`${styles.sheet} ${styles.sheet3}`} aria-hidden="true">
<span className={styles.line}></span><span className={styles.line}></span>
</article>
<article className={`${styles.sheet} ${styles.sheet2}`} aria-hidden="true">
<span className={styles.tag}>{tag}</span>
<span className={styles.line}></span>
</article>
<article className={`${styles.sheet} ${styles.sheet1}`}>
<p className={styles.eyebrow}>{eyebrow}</p>
<h2 className={styles.title}>{title}</h2>
<p className={styles.desc}>{desc}</p>
</article>
</section>
);
}03 紙目の機能カード — 計測 累積面積 34.744% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · スキューモーフィズムデザインの質感パーツ9選
このデモが属する記事
スキューモーフィズムデザインの質感パーツ9選スキューモーフィズムデザインは、革・金属・木目・ガラスといった実物の質感を画面に持ち込むスタイルです。革・金属・木目をCSSの値だけで作り、テクスチャ写真を1枚も使わずに、2024年以降の製品サイトに少しずつ戻ってきた「選択的な質感」を貼っ この記事のデモをすべて見る


