07지그재그 구분선
두 섹션 사이의 톱니 두 줄이 서로 반대 방향으로 흘러 맞물리는데, 무늬 폭이 같아 한 치도 어긋나지 않고 물립니다.
쓰임섹션 경계, 배경색이 바뀌는 지점
conic-gradienttranslateXrepeat-x

<main class="stage">
<section class="zzm">
<header class="zzm__sec zzm__sec--top">
<p class="zzm__kicker">윗 섹션</p>
<h2 class="zzm__head">레몬 배경이 끝나는 자리</h2>
</header>
<div class="zzm__zip" aria-hidden="true">
<span class="zzm__teeth zzm__teeth--down"></span>
<span class="zzm__teeth zzm__teeth--up"></span>
<span class="zzm__chip">경계</span>
</div>
<footer class="zzm__sec zzm__sec--bottom">
<h2 class="zzm__head">민트 배경이 시작되는 자리</h2>
<p class="zzm__kicker">아래 섹션</p>
</footer>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// 멤피스 팔레트 — 01 과 같은 값
$mem-base: #fdf4ec;
$mem-sheet: #ffffff;
$mem-ink: #241f26;
$mem-stone: #5d5560;
$mem-coral: #d5aea9;
$mem-lemon: #cec29c;
$mem-sky: #82adc2;
$mem-mint: #71b9ab;
$mem-pink: #ebd6db;
$tile: 16px; // 톱니 하나의 폭 — 두 줄이 같아야 맞물린다
$duration: $dur-loop; // 톱니가 타일 두 칸만큼 흐르는 주기
$easing: linear; // 맞물림은 일정 속도로 흘러야 쉬지 않고 물려 있다
$color: $mem-lemon; // 윗 섹션 색
@include stage($mem-base);
.zzm {
box-sizing: border-box;
width: clamp(280px, 92vw, 448px);
border: 3px solid $mem-ink;
border-radius: $r-card;
overflow: hidden;
box-shadow: 0 6px 0 $mem-ink;
}
.zzm__sec {
box-sizing: border-box;
padding: $sp-4 $sp-5;
}
.zzm__sec--top { background: $mem-lemon; }
.zzm__sec--bottom { background: $mem-mint; }
.zzm__kicker {
margin: 0 0 $sp-1;
color: $mem-ink;
font-weight: 700; font-size: clamp(9px, 2.2vw, 10.5px); line-height: 1;
font-family: inherit;
letter-spacing: .14em;
}
.zzm__head {
margin: 0;
color: $mem-ink;
font-weight: 800; font-size: clamp(13px, 3vw, 16px); line-height: 1.3;
font-family: inherit;
}
// 지퍼 띠 — 높이 32px(톱니 16 + 겹침 16). 두 줄이 반대 방향으로 흘러 맞물린다
.zzm__zip {
position: relative;
height: 32px;
background: $mem-base; // 맞물림 사이로 보이는 바탕
}
.zzm__teeth {
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 32px;
}
// 윗 색의 이 — 아래로 뻗는 쐐기(꼭짓점이 타일 아래쪽 변). 타일 16px
.zzm__teeth--down {
background: conic-gradient(from 315deg at 50% 100%, $mem-lemon 0 90deg, transparent 90deg 360deg);
background-size: $tile $tile;
background-repeat: repeat-x;
animation: teethFlow $duration $easing infinite;
}
// 아랫 색의 이 — 위로 뻗되 타일 절반(8px) 옆으로 밀려 옆 이와 빈틈없이 물린다
.zzm__teeth--up {
top: 16px;
background: conic-gradient(from 135deg at 50% 0%, $mem-mint 0 90deg, transparent 90deg 360deg);
background-size: $tile $tile;
background-repeat: repeat-x;
background-position: 8px 0;
animation: teethFlow $duration $easing infinite reverse; // reverse = 반대 방향
}
// 가운데 알약 — 경계임을 알리는 표식(정지)
.zzm__chip {
position: absolute;
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: $sp-1 $sp-3;
border-radius: $r-pill;
background: $mem-sheet;
border: 3px solid $mem-ink;
color: $mem-ink;
font-weight: 800; font-size: 11px; line-height: 1;
font-family: inherit;
}
// 타일 두 칸(32px)만큼 흐른 뒤 처음과 같은 무늬 — 이동 거리가 주기의 배수라 텔레포트가 없다
@keyframes teethFlow {
from { transform: translateX(0); }
to { transform: translateX(-2 * $tile); }
}
@media (max-width: 340px), (max-height: 220px) {
.zzm { width: 244px; }
.zzm__sec { padding: $sp-3 $sp-4; }
.zzm__head { font-size: 12px; }
}
@media (prefers-reduced-motion: reduce) {
.zzm__teeth { 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: #fdf4ec;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.zzm {
box-sizing: border-box;
width: clamp(280px, 92vw, 448px);
border: 3px solid #241f26;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 6px 0 #241f26;
}
.zzm__sec {
box-sizing: border-box;
padding: 16px 20px;
}
.zzm__sec--top {
background: #cec29c;
}
.zzm__sec--bottom {
background: #71b9ab;
}
.zzm__kicker {
margin: 0 0 4px;
color: #241f26;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10.5px);
line-height: 1;
font-family: inherit;
letter-spacing: 0.14em;
}
.zzm__head {
margin: 0;
color: #241f26;
font-weight: 800;
font-size: clamp(13px, 3vw, 16px);
line-height: 1.3;
font-family: inherit;
}
.zzm__zip {
position: relative;
height: 32px;
background: #fdf4ec;
}
.zzm__teeth {
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 32px;
}
.zzm__teeth--down {
background: conic-gradient(from 315deg at 50% 100%, #cec29c 0 90deg, transparent 90deg 360deg);
background-size: 16px 16px;
background-repeat: repeat-x;
animation: teethFlow 2s linear infinite;
}
.zzm__teeth--up {
top: 16px;
background: conic-gradient(from 135deg at 50% 0%, #71b9ab 0 90deg, transparent 90deg 360deg);
background-size: 16px 16px;
background-repeat: repeat-x;
background-position: 8px 0;
animation: teethFlow 2s linear infinite reverse;
}
.zzm__chip {
position: absolute;
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 4px 12px;
border-radius: 999px;
background: #ffffff;
border: 3px solid #241f26;
color: #241f26;
font-weight: 800;
font-size: 11px;
line-height: 1;
font-family: inherit;
}
@keyframes teethFlow {
from {
transform: translateX(0);
}
to {
transform: translateX(-32px);
}
}
@media (max-width: 340px), (max-height: 220px) {
.zzm {
width: 244px;
}
.zzm__sec {
padding: 12px 16px;
}
.zzm__head {
font-size: 12px;
}
}
@media (prefers-reduced-motion: reduce) {
.zzm__teeth {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./ZigzagDividerMemphis.module.scss";
export type ZigzagDividerMemphisProps = {
topKicker?: string;
topHead?: string;
bottomHead?: string;
bottomKicker?: string;
chip?: string;
/** 한 주기(기본 2000ms) */
duration?: number;
easing?: string;
/** 윗 섹션 색(데모 기준값 — 톱니 색은 고정) */
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function ZigzagDividerMemphis({
topKicker = "윗 섹션",
topHead = "레몬 배경이 끝나는 자리",
bottomHead = "민트 배경이 시작되는 자리",
bottomKicker = "아래 섹션",
chip = "경계",
duration = 2000,
easing = "linear",
color = "#ffcf33",
}: ZigzagDividerMemphisProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.zzm} style={vars}>
<header className={`${styles.sec} ${styles.secTop}`}>
<p className={styles.kicker}>{topKicker}</p>
<h2 className={styles.head}>{topHead}</h2>
</header>
<div className={styles.zip} aria-hidden="true">
<span className={`${styles.teeth} ${styles.teethDown}`} />
<span className={`${styles.teeth} ${styles.teethUp}`} />
<span className={styles.chip}>{chip}</span>
</div>
<footer className={`${styles.sec} ${styles.secBottom}`}>
<h2 className={styles.head}>{bottomHead}</h2>
<p className={styles.kicker}>{bottomKicker}</p>
</footer>
</section>
);
}07 지그재그 구분선 — 측정 누적면적 3.781% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 멤피스 디자인 패턴 랜딩 키트, 점·물결 섹션 9개
이 데모가 속한 글
멤피스 디자인 패턴 랜딩 키트, 점·물결 섹션 9개멤피스 디자인은 1980년대 밀라노에서 시작된, 점·물결선·지그재그 같은 기하 형상을 밝은 색 위에 물들이듯 반복하는 스타일입니다. 이 글은 이미지 한 장 없이 CSS 그라디언트 타일과 도형만으로 멤피스 패턴을 재현 이 글의 데모 전부 보기


