08Confetti strip footer
Below three link columns, a strip holding two identical sets of four paper chips slides exactly one set, so the loop has no visible seam.
Best forThe closing strip of a bright-toned site
두 벌 복제margin-right 간격translateX(-50%)

<main class="stage">
<footer class="pfoot">
<div class="pfoot__cols">
<div class="pfoot__col">
<p class="pfoot__h">Craft Room</p>
<p class="pfoot__l">About</p>
<p class="pfoot__l">Blog</p>
</div>
<div class="pfoot__col">
<p class="pfoot__h">Materials</p>
<p class="pfoot__l">Paper</p>
<p class="pfoot__l">Stickers</p>
</div>
<div class="pfoot__col">
<p class="pfoot__h">Help</p>
<p class="pfoot__l">Contact</p>
<p class="pfoot__l">Terms</p>
</div>
</div>
<div class="pfoot__strip" aria-hidden="true">
<div class="pfoot__track">
<span class="pfoot__bit pfoot__bit--coral"></span>
<span class="pfoot__bit pfoot__bit--butter"></span>
<span class="pfoot__bit pfoot__bit--leaf"></span>
<span class="pfoot__bit pfoot__bit--sky"></span>
<span class="pfoot__bit pfoot__bit--coral"></span>
<span class="pfoot__bit pfoot__bit--butter"></span>
<span class="pfoot__bit pfoot__bit--leaf"></span>
<span class="pfoot__bit pfoot__bit--sky"></span>
</div>
</div>
</footer>
</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: linear; // 흐름은 일정 속도라야 이음매가 안 보인다
$color: $paper-edge; // 푸터 종이의 아래 두께
@include stage($paper-butter);
.pfoot {
box-sizing: border-box;
width: clamp(264px, 86vw, 388px);
padding: $sp-5 $sp-5 0;
border-radius: $r-card;
background: $paper-sheet;
box-shadow: 0 6px 0 $color;
overflow: hidden;
}
.pfoot__cols {
display: flex;
gap: $sp-6;
margin-bottom: $sp-4;
}
.pfoot__col { flex: 1 1 0; }
.pfoot__h {
margin: 0 0 $sp-2;
color: $paper-ink;
font-weight: 800; font-size: 12px; line-height: 1;
font-family: inherit;
}
.pfoot__l {
margin: 0 0 $sp-1;
color: $paper-stone;
font-weight: 500; font-size: 11px; line-height: 1.3;
font-family: inherit;
}
// 아래 색종이 띠 — 같은 네 조각을 두 벌 넣고 한 벌만큼(-50%) 민다. 끝에서 처음으로 튀는 순간이 없다
.pfoot__strip {
overflow: hidden;
margin: 0 #{-$sp-5};
padding: $sp-2 0;
background: $paper-base;
}
.pfoot__track {
display: flex;
width: max-content;
animation: stripFlow $duration $easing infinite;
}
.pfoot__bit {
flex: 0 0 auto;
width: 36px;
height: $sp-5;
margin-right: $sp-3; // 간격은 gap 이 아니라 margin — 2N 조각 사이 gap 은 2N-1 개라 -50% 가 어긋난다
border-radius: $r-xs;
}
.pfoot__bit--coral { background: $paper-coral; }
.pfoot__bit--butter { background: $paper-butter; }
.pfoot__bit--leaf { background: $paper-leaf; }
.pfoot__bit--sky { background: $paper-sky; }
// 데모: 트랙 전체가 정확히 한 벌(=전체 폭의 50%)만큼 왼쪽으로 흐른 뒤 처음 상태와 완전히 같아진다
@keyframes stripFlow {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@media (max-width: 340px), (max-height: 220px) {
.pfoot { width: 276px; padding: $sp-3 $sp-3 0; }
.pfoot__cols { gap: $sp-3; margin-bottom: $sp-2; }
.pfoot__h { font-size: 10px; margin-bottom: $sp-1; }
.pfoot__l { font-size: 9px; }
.pfoot__strip { margin: 0 #{-$sp-3}; padding: $sp-1 0; }
.pfoot__bit { width: 24px; height: $sp-4; margin-right: $sp-2; }
}
@media (prefers-reduced-motion: reduce) {
.pfoot__track { 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: #d6c9ab;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.pfoot {
box-sizing: border-box;
width: clamp(264px, 86vw, 388px);
padding: 20px 20px 0;
border-radius: 16px;
background: #ffffff;
box-shadow: 0 6px 0 #e7dccb;
overflow: hidden;
}
.pfoot__cols {
display: flex;
gap: 24px;
margin-bottom: 16px;
}
.pfoot__col {
flex: 1 1 0;
}
.pfoot__h {
margin: 0 0 8px;
color: #2a2320;
font-weight: 800;
font-size: 12px;
line-height: 1;
font-family: inherit;
}
.pfoot__l {
margin: 0 0 4px;
color: #5c5048;
font-weight: 500;
font-size: 11px;
line-height: 1.3;
font-family: inherit;
}
.pfoot__strip {
overflow: hidden;
margin: 0 -20px;
padding: 8px 0;
background: #fbf6ec;
}
.pfoot__track {
display: flex;
width: max-content;
animation: stripFlow 2s linear infinite;
}
.pfoot__bit {
flex: 0 0 auto;
width: 36px;
height: 20px;
margin-right: 12px;
border-radius: 4px;
}
.pfoot__bit--coral {
background: #b97b71;
}
.pfoot__bit--butter {
background: #d6c9ab;
}
.pfoot__bit--leaf {
background: #2b7d5a;
}
.pfoot__bit--sky {
background: #6a8db5;
}
@keyframes stripFlow {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
@media (max-width: 340px), (max-height: 220px) {
.pfoot {
width: 276px;
padding: 12px 12px 0;
}
.pfoot__cols {
gap: 12px;
margin-bottom: 8px;
}
.pfoot__h {
font-size: 10px;
margin-bottom: 4px;
}
.pfoot__l {
font-size: 9px;
}
.pfoot__strip {
margin: 0 -12px;
padding: 4px 0;
}
.pfoot__bit {
width: 24px;
height: 16px;
margin-right: 8px;
}
}
@media (prefers-reduced-motion: reduce) {
.pfoot__track {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./PaperFooter.module.scss";
export type FooterColumn = { head: string; links: string[] };
export type PaperFooterProps = {
columns?: FooterColumn[];
/** 띠에 놓을 색종이 색 한 벌. 컴포넌트가 두 벌로 복제한다 */
chips?: string[];
/** 한 벌만큼 흐르는 시간(기본 2000ms) */
duration?: number;
};
type Vars = CSSProperties & { "--duration"?: string };
export default function PaperFooter({
columns = [
{ head: "공작실", links: ["소개", "기록"] },
{ head: "재료", links: ["색종이", "스티커"] },
{ head: "도움말", links: ["문의", "약관"] },
],
chips = ["#cc3d26", "#f6c453", "#2b7d5a", "#2f6fb8"],
duration = 2000,
}: PaperFooterProps) {
const vars: Vars = { "--duration": `${duration}ms` };
// 한 벌을 그대로 두 번 깔아야 -50% 지점이 첫 프레임과 완전히 같아진다 (이음매 없음)
const track = [...chips, ...chips];
return (
<footer className={styles.pfoot} style={vars}>
<div className={styles.cols}>
{columns.map((c) => (
<div key={c.head} className={styles.col}>
<p className={styles.h}>{c.head}</p>
{c.links.map((l) => <p key={l} className={styles.l}>{l}</p>)}
</div>
))}
</div>
<div className={styles.strip} aria-hidden="true">
<div className={styles.track}>
{track.map((c, i) => <span key={`${c}-${i}`} className={styles.bit} style={{ background: c }} />)}
</div>
</div>
</footer>
);
}08 Confetti strip footer — Measured area 5.163% · moving frames 23/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


