08スカラップのフッター
フッターの上辺は半円が連なるスカラップ模様で、一つひとつ要素を置かず背景の繰り返しだけで描きます。中のカラムは薄いラベンダー・ピンク・ミントに分かれ、小さな星が瞬きます。
使いどころマルチカラムフッター・サイトの締め
repeating 반원 가장자리3열→1열반짝임 점

<main class="stage">
<footer class="footercandy">
<span class="footercandy__scallop" aria-hidden="true"></span>
<div class="footercandy__cols">
<div class="footercandy__col footercandy__col--lav">
<p class="footercandy__head">プロダクト</p>
<p class="footercandy__link">機能</p>
<p class="footercandy__link">料金プラン</p>
</div>
<div class="footercandy__col footercandy__col--bub">
<p class="footercandy__head">会社</p>
<p class="footercandy__link">紹介</p>
<p class="footercandy__link">お問い合わせ</p>
</div>
<div class="footercandy__col footercandy__col--min">
<p class="footercandy__head">ヘルプ</p>
<p class="footercandy__link">よくある質問</p>
<p class="footercandy__link">ガイド</p>
</div>
</div>
<p class="footercandy__copy"><span class="footercandy__spark" aria-hidden="true">✦</span> プリン・甘い一日 <span class="footercandy__spark footercandy__spark--b" aria-hidden="true">✦</span></p>
</footer>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 별이 한 번 반짝이는 주기
$easing: $ease-out;
$color: $stage-ink; // 글자 기본(토큰) — 베리 잉크로 덮는다
// 캔디 팔레트 — 값은 tailwind.config.js · tokens.css 와 같다
$candy-cream: #fff6f0;
$candy-bubble: #ffb9cf;
$candy-lemon: #ffe9a3;
$candy-mint: #b5e8d2;
$candy-lavender: #cdbcf2;
$candy-berry: #55395e;
// 푸터 패널 색 — 크림보다 살짝 진한 베리 우유크림
$panel: mix($candy-lavender, $stage-paper, 20%);
@include stage($candy-cream);
.footercandy {
position: relative;
box-sizing: border-box;
width: clamp(300px, 94vw, 460px);
padding: $sp-4 $sp-3 $sp-3;
border-radius: $r-card;
background: $panel;
box-shadow: 0 -6px 20px rgba($candy-lavender, .45);
}
// 스캘럽 가장자리 — 반원 하나하나를 요소로 그리지 않고 배경 반복 하나로 그린다.
// 각 타일(26×13px) 아래변 중심에 반원을 그리면 윗반원만 남아 조개 껍질 가장자리가 된다
.footercandy__scallop {
position: absolute;
top: calc($sp-3 * -1);
left: 0;
width: 100%;
height: $sp-3;
background-image: radial-gradient(circle at 50% 100%, $panel 0 64%, transparent 66%);
background-size: 26px 13px;
background-repeat: repeat-x;
background-position: bottom center;
}
.footercandy__cols {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(clamp(84px, 26vw, 120px), 1fr));
gap: $sp-2;
}
.footercandy__col {
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: $sp-1;
padding: $sp-3;
border-radius: $r-control;
}
.footercandy__col--lav { background: mix($candy-lavender, $stage-paper, 45%); animation: colWave $duration $easing infinite; }
.footercandy__col--bub { background: mix($candy-bubble, $stage-paper, 45%); animation: colWave $duration $easing infinite; animation-delay: calc($duration / -3); }
.footercandy__col--min { background: mix($candy-mint, $stage-paper, 45%); animation: colWave $duration $easing infinite; animation-delay: calc($duration / -3 * 2); }
.footercandy__head {
margin: 0;
color: $candy-berry;
font-weight: 700; font-size: clamp(10px, 2.6vw, 12px); line-height: 1;
letter-spacing: .06em;
}
.footercandy__link {
margin: 0;
color: rgba($candy-berry, .78);
font-weight: 500; font-size: clamp(11px, 2.8vw, 13px); line-height: 1.5;
}
.footercandy__copy {
display: flex;
align-items: center;
justify-content: center;
gap: $sp-2;
margin: $sp-3 0 0;
color: rgba($candy-berry, .78);
font-weight: 500; font-size: clamp(10px, 2.6vw, 12px); line-height: 1;
}
.footercandy__spark {
color: $candy-bubble;
animation: sparkTwinkle $duration $easing infinite;
}
.footercandy__spark--b {
animation-delay: calc($duration / -2);
color: $candy-lavender;
}
// 데모: 세 칸이 순서대로 살짝 들썩이는 물결을 만들고, 별 두 개는 어긋나게 반짝인다
@keyframes colWave {
0%, 40%, 100% { transform: translateY(0); }
20% { transform: translateY(-6px); }
}
@keyframes sparkTwinkle {
0%, 100% { opacity: .35; transform: scale(.8) rotate(0deg); }
50% { opacity: 1; transform: scale(1.3) rotate(25deg); }
}
// 반응형: 320×200 처럼 세로가 짧은 화면에선 칸 속과 스캘럽 번짐을 줄인다(BD9)
@media (max-width: 340px), (max-height: 220px) {
.footercandy { padding: $sp-3 $sp-2 $sp-2; }
.footercandy__scallop { top: calc($sp-2 * -1); height: $sp-2; background-size: 20px 10px; }
.footercandy__cols { gap: $sp-1; }
.footercandy__col { padding: $sp-2; }
.footercandy__link { display: none; }
.footercandy__copy { margin-top: $sp-2; font-size: 9px; }
}
@media (prefers-reduced-motion: reduce) {
.footercandy__col, .footercandy__spark { animation: none !important; transform: none; }
.footercandy__spark { opacity: .8; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #fff6f0;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.footercandy {
position: relative;
box-sizing: border-box;
width: clamp(300px, 94vw, 460px);
padding: 16px 12px 12px;
border-radius: 16px;
background: rgb(96.0784313725%, 94.7450980392%, 98.9803921569%);
box-shadow: 0 -6px 20px rgba(205, 188, 242, 0.45);
}
.footercandy__scallop {
position: absolute;
top: -12px;
left: 0;
width: 100%;
height: 12px;
background-image: radial-gradient(circle at 50% 100%, rgb(96.0784313725%, 94.7450980392%, 98.9803921569%) 0 64%, transparent 66%);
background-size: 26px 13px;
background-repeat: repeat-x;
background-position: bottom center;
}
.footercandy__cols {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(clamp(84px, 26vw, 120px), 1fr));
gap: 8px;
}
.footercandy__col {
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px;
border-radius: 12px;
}
.footercandy__col--lav {
background: rgb(91.1764705882%, 88.1764705882%, 97.7058823529%);
animation: colWave 2s ease-out infinite;
}
.footercandy__col--bub {
background: rgb(100%, 87.6470588235%, 91.5294117647%);
animation: colWave 2s ease-out infinite;
animation-delay: -0.6666666667s;
}
.footercandy__col--min {
background: rgb(86.9411764706%, 95.9411764706%, 92.0588235294%);
animation: colWave 2s ease-out infinite;
animation-delay: -1.3333333333s;
}
.footercandy__head {
margin: 0;
color: #55395e;
font-weight: 700;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1;
letter-spacing: 0.06em;
}
.footercandy__link {
margin: 0;
color: rgba(85, 57, 94, 0.78);
font-weight: 500;
font-size: clamp(11px, 2.8vw, 13px);
line-height: 1.5;
}
.footercandy__copy {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin: 12px 0 0;
color: rgba(85, 57, 94, 0.78);
font-weight: 500;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1;
}
.footercandy__spark {
color: #ffb9cf;
animation: sparkTwinkle 2s ease-out infinite;
}
.footercandy__spark--b {
animation-delay: -1s;
color: #cdbcf2;
}
@keyframes colWave {
0%, 40%, 100% {
transform: translateY(0);
}
20% {
transform: translateY(-6px);
}
}
@keyframes sparkTwinkle {
0%, 100% {
opacity: 0.35;
transform: scale(0.8) rotate(0deg);
}
50% {
opacity: 1;
transform: scale(1.3) rotate(25deg);
}
}
@media (max-width: 340px), (max-height: 220px) {
.footercandy {
padding: 12px 8px 8px;
}
.footercandy__scallop {
top: -8px;
height: 8px;
background-size: 20px 10px;
}
.footercandy__cols {
gap: 4px;
}
.footercandy__col {
padding: 8px;
}
.footercandy__link {
display: none;
}
.footercandy__copy {
margin-top: 8px;
font-size: 9px;
}
}
@media (prefers-reduced-motion: reduce) {
.footercandy__col, .footercandy__spark {
animation: none !important;
transform: none;
}
.footercandy__spark {
opacity: 0.8;
}
}import type { CSSProperties } from "react";
import styles from "./FooterCandy.module.scss";
export type FooterCandyProps = {
columns?: { head: string; links: string[] }[];
copy?: string;
/** 한 주기(기본 2000ms) */
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const TONE = ["lav", "bub", "min"] as const;
export default function FooterCandy({
columns = [
{ head: "제품", links: ["기능", "요금제"] },
{ head: "회사", links: ["소개", "문의"] },
{ head: "도움말", links: ["자주 묻는 질문", "가이드"] },
],
copy = "푸딩 · 달콤한 하루",
duration = 2000,
easing = "ease-out",
color = "#ffb9cf",
}: FooterCandyProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<footer className={styles.footercandy} style={vars}>
<span className={styles.scallop} aria-hidden="true" />
<div className={styles.cols}>
{columns.map((c, i) => (
<div key={c.head} className={`${styles.col} ${styles[TONE[i % 3]]}`}>
<p className={styles.head}>{c.head}</p>
{c.links.map((l) => (
<p key={l} className={styles.link}>{l}</p>
))}
</div>
))}
</div>
<p className={styles.copy}>
<span className={styles.spark} aria-hidden="true">✦</span> {copy} <span className={`${styles.spark} ${styles.sparkB}`} aria-hidden="true">✦</span>
</p>
</footer>
);
}08 スカラップのフッター — 計測 累積面積 6.003% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パステル デザイン LPキット、キャンディカラーのセクション9選
このデモが属する記事
パステル デザイン LPキット、キャンディカラーのセクション9選パステル デザインは、クリーム色の地にバブルガムピンク・ピーチ・レモン・ミント・ラベンダーの淡い色を重ね、影までグレーではなく同系色のパステルで描いて、飴のようにやわらかく、さわやかに見せるスタイルです。名前がなくて検索できなかった、あのデ この記事のデモをすべて見る


