07Torn-edge CTA band
Inside a coral band with zigzag torn edges, the white button floats 8px up and settles down as its shadow thickness shrinks.
Best forBottom-of-page conversion bands
conic-gradient 마스크그림자=두께radius 12

<main class="stage">
<section class="tornband">
<div class="tornband__in">
<p class="tornband__t">Start cutting and pasting today</p>
<button class="tornband__btn" type="button">
<span>Get the pieces</span>
<span class="tornband__ic" 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="m216 128l-72 72V56Z" opacity=".2"/><path d="m221.66 122.34l-72-72A8 8 0 0 0 136 56v64H40a8 8 0 0 0 0 16h96v64a8 8 0 0 0 13.66 5.66l72-72a8 8 0 0 0 0-11.32M152 180.69V75.31L204.69 128Z"/></g></svg>
</span>
</button>
</div>
</section>
</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-coral; // 띠의 색종이 색. 흰 글자 대비 4.93:1
@include stage($paper-base);
.tornband {
box-sizing: border-box;
width: clamp(252px, 84vw, 372px);
padding: $sp-6 $sp-5;
background: $color;
// 위아래를 지그재그로 찢어낸 가장자리 — 마스크 세 겹을 겹쳐 만든다 (애니메이션 대상 아님)
-webkit-mask:
conic-gradient(from -45deg at bottom, #0000, #000 1deg 89deg, #0000 90deg) bottom / 14px 7px repeat-x,
conic-gradient(from 135deg at top, #0000, #000 1deg 89deg, #0000 90deg) top / 14px 7px repeat-x,
linear-gradient(#000 0 0) center / 100% calc(100% - 14px) no-repeat;
mask:
conic-gradient(from -45deg at bottom, #0000, #000 1deg 89deg, #0000 90deg) bottom / 14px 7px repeat-x,
conic-gradient(from 135deg at top, #0000, #000 1deg 89deg, #0000 90deg) top / 14px 7px repeat-x,
linear-gradient(#000 0 0) center / 100% calc(100% - 14px) no-repeat;
}
.tornband__in {
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-4;
}
.tornband__t {
margin: 0;
color: $paper-sheet;
font-weight: 800; font-size: clamp(15px, 3.6vw, 19px); line-height: 1.25;
font-family: inherit;
text-align: center;
}
// 종이 버튼 — 그림자가 두께다. 눌리면 두께가 사라진다
.tornband__btn {
display: inline-flex;
align-items: center;
gap: $sp-2;
border: 0;
border-radius: $r-control;
background: $paper-sheet;
color: $paper-ink;
padding: $sp-3 $sp-6;
font-weight: 800; font-size: 14px; line-height: 1;
font-family: inherit;
box-shadow: 0 6px 0 rgba($paper-ink, .3);
animation: paperPress $duration $easing infinite;
}
.tornband__ic { font-size: 16px; line-height: 1; color: $color; }
.tornband__btn:focus-visible { outline: 3px solid $paper-sheet; outline-offset: 3px; }
// 데모: 버튼이 8px 떠올랐다가 바닥에 붙는다. 그림자 두께가 6px→0 으로 같이 줄어든다
@keyframes paperPress {
0%, 100% { transform: translateY(-8px); box-shadow: 0 14px 0 rgba($paper-ink, .3); }
50% { transform: translateY(0); box-shadow: 0 2px 0 rgba($paper-ink, .3); }
}
@media (max-width: 340px), (max-height: 220px) {
.tornband { width: 268px; padding: $sp-5 $sp-3; }
.tornband__in { gap: $sp-3; }
.tornband__t { font-size: 13px; }
.tornband__btn { padding: $sp-2 $sp-4; font-size: 12px; }
.tornband__ic { font-size: 13px; }
}
@media (prefers-reduced-motion: reduce) {
.tornband__btn { 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: #fbf6ec;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.tornband {
box-sizing: border-box;
width: clamp(252px, 84vw, 372px);
padding: 24px 20px;
background: #b97b71;
-webkit-mask: conic-gradient(from -45deg at bottom, rgba(0, 0, 0, 0), #000 1deg 89deg, rgba(0, 0, 0, 0) 90deg) bottom/14px 7px repeat-x, conic-gradient(from 135deg at top, rgba(0, 0, 0, 0), #000 1deg 89deg, rgba(0, 0, 0, 0) 90deg) top/14px 7px repeat-x, linear-gradient(#000 0 0) center/100% calc(100% - 14px) no-repeat;
mask: conic-gradient(from -45deg at bottom, rgba(0, 0, 0, 0), #000 1deg 89deg, rgba(0, 0, 0, 0) 90deg) bottom/14px 7px repeat-x, conic-gradient(from 135deg at top, rgba(0, 0, 0, 0), #000 1deg 89deg, rgba(0, 0, 0, 0) 90deg) top/14px 7px repeat-x, linear-gradient(#000 0 0) center/100% calc(100% - 14px) no-repeat;
}
.tornband__in {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.tornband__t {
margin: 0;
color: #ffffff;
font-weight: 800;
font-size: clamp(15px, 3.6vw, 19px);
line-height: 1.25;
font-family: inherit;
text-align: center;
}
.tornband__btn {
display: inline-flex;
align-items: center;
gap: 8px;
border: 0;
border-radius: 12px;
background: #ffffff;
color: #2a2320;
padding: 12px 24px;
font-weight: 800;
font-size: 14px;
line-height: 1;
font-family: inherit;
box-shadow: 0 6px 0 rgba(42, 35, 32, 0.3);
animation: paperPress 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.tornband__ic {
font-size: 16px;
line-height: 1;
color: #b97b71;
}
.tornband__btn:focus-visible {
outline: 3px solid #ffffff;
outline-offset: 3px;
}
@keyframes paperPress {
0%, 100% {
transform: translateY(-8px);
box-shadow: 0 14px 0 rgba(42, 35, 32, 0.3);
}
50% {
transform: translateY(0);
box-shadow: 0 2px 0 rgba(42, 35, 32, 0.3);
}
}
@media (max-width: 340px), (max-height: 220px) {
.tornband {
width: 268px;
padding: 20px 12px;
}
.tornband__in {
gap: 12px;
}
.tornband__t {
font-size: 13px;
}
.tornband__btn {
padding: 8px 16px;
font-size: 12px;
}
.tornband__ic {
font-size: 13px;
}
}
@media (prefers-reduced-motion: reduce) {
.tornband__btn {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./TornBand.module.scss";
export type TornBandProps = {
title?: string;
cta?: string;
/** 한 주기(기본 2000ms) */
duration?: number;
easing?: string;
/** 띠의 색종이 색. 흰 글자와 4.5:1 이상이어야 한다 */
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const ARROW = (
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><g fill="currentColor"><path d="m216 128l-72 72V56Z" opacity=".2"/><path d="m221.66 122.34l-72-72A8 8 0 0 0 136 56v64H40a8 8 0 0 0 0 16h96v64a8 8 0 0 0 13.66 5.66l72-72a8 8 0 0 0 0-11.32M152 180.69V75.31L204.69 128Z"/></g></svg>
);
export default function TornBand({
title = "오늘 오려 붙이세요",
cta = "부품 받기",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#cc3d26",
}: TornBandProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.tornband} style={vars}>
<div className={styles.in}>
<p className={styles.t}>{title}</p>
<button className={styles.btn} type="button">
<span>{cta}</span>
<span className={styles.ic} aria-hidden="true">{ARROW}</span>
</button>
</div>
</section>
);
}07 Torn-edge CTA band — Measured area 2.563% · moving frames 21/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


