05아이콘 한 줄 미니 배너
본문을 거의 가리지 않는 한 줄짜리 띠가 아래에서 올라오고, 왼쪽 쿠키 아이콘만 6도 안쪽에서 좌우로 기웁니다.
쓰임고지 문구가 짧은 블로그·문서 사이트, 모바일에서 화면을 덜 먹어야 하는 배너
높이 44px 고정rotate ±6degradius 12
<main class="stage">
<div class="mb">
<div class="mb__top" aria-hidden="true">
<i class="mb__dot"></i><i class="mb__dot"></i><i class="mb__dot"></i>
</div>
<div class="mb__lines" aria-hidden="true">
<i class="mb__line mb__line--title"></i>
<i class="mb__line"></i>
<i class="mb__line"></i>
<i class="mb__line mb__line--short"></i>
</div>
<div class="mb__bar is-demo" role="region" aria-label="쿠키 안내">
<span class="mb__cookie" aria-hidden="true"><svg viewBox="0 0 256 256" width="20" height="20"><g fill="currentColor"><path d="M224 128a96 96 0 1 1-96-96a48 48 0 0 0 48 48a48 48 0 0 0 48 48" opacity=".2"/><path d="M164.49 163.51a12 12 0 1 1-17 0a12 12 0 0 1 17 0m-81-8a12 12 0 1 0 17 0a12 12 0 0 0-16.98 0Zm9-39a12 12 0 1 0-17 0a12 12 0 0 0 17-.02Zm48-1a12 12 0 1 0 0 17a12 12 0 0 0 0-17M232 128A104 104 0 1 1 128 24a8 8 0 0 1 8 8a40 40 0 0 0 40 40a8 8 0 0 1 8 8a40 40 0 0 0 40 40a8 8 0 0 1 8 8m-16.31 7.39A56.13 56.13 0 0 1 168.5 87.5a56.13 56.13 0 0 1-47.89-47.19a88 88 0 1 0 95.08 95.08"/></g></svg></span>
<p class="mb__text">쿠키를 사용합니다. 계속하면 동의로 봅니다.</p>
<button class="mb__ok" type="button">확인</button>
<button class="mb__x" type="button" aria-label="닫기"><span class="mb__xic" aria-hidden="true"><svg viewBox="0 0 256 256" width="14" height="14"><g fill="currentColor"><path d="M216 56v144a16 16 0 0 1-16 16H56a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16h144a16 16 0 0 1 16 16" opacity=".2"/><path d="M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128L50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z"/></g></svg></span></button>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop;
$easing: $ease-spring;
$color: $subject-cream; // 어두운 무대 — 강조는 파랑 대신 밝은 쪽 대비
@include stage($stage-ink);
// ── 어두운 판 위의 가짜 페이지 목업 (움직이지 않는다) ──
.mb {
position: relative;
box-sizing: border-box;
width: clamp(272px, 90vw, 420px);
height: clamp(170px, 62vh, 232px);
overflow: hidden; // 띠가 내려간 만큼은 판 안에서 자른다
border: 1px solid rgba(255, 247, 230, .12);
border-radius: $r-card;
background: rgba(255, 247, 230, .04);
color: $color;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
}
.mb__top {
display: flex;
align-items: center;
gap: $sp-1;
height: 24px;
padding: 0 $sp-3;
background: rgba(255, 247, 230, .06);
}
.mb__dot { width: 6px; height: 6px; border-radius: 50%; background: rgba(255, 247, 230, .20); }
.mb__lines { display: grid; gap: $sp-3; padding: $sp-4; }
.mb__line { height: 8px; border-radius: $r-xs; background: rgba(255, 247, 230, .06); }
.mb__line--title { height: 12px; width: 56%; background: rgba(255, 247, 230, .12); }
.mb__line--short { width: 62%; }
// ── 맨 아래 44px 고정 띠 ──
.mb__bar {
position: absolute;
left: 0;
right: 0;
bottom: 0;
box-sizing: border-box;
height: 44px;
display: flex;
align-items: center;
gap: $sp-2;
padding: 0 $sp-3;
border-top: 1px solid rgba(255, 247, 230, .22);
background: rgba(255, 247, 230, .14);
transition: transform $dur-base $easing;
}
.mb__bar.is-gone { transform: translateY(100%); }
.mb__cookie { display: inline-flex; flex: 0 0 auto; color: $color; }
.mb__text {
margin: 0;
min-width: 0;
flex: 1 1 auto;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
font-size: clamp(9px, 2.6vw, 12px);
line-height: 1.3;
color: rgba(255, 247, 230, .88);
}
.mb__ok {
flex: 0 0 auto;
border: 0;
border-radius: $r-pill;
padding: $sp-1 $sp-3;
background: $color;
color: $subject-ink;
font-family: inherit;
font-size: clamp(9px, 2.6vw, 11px);
font-weight: 700;
white-space: nowrap;
cursor: pointer;
transition: opacity $dur-quick $ease-out, transform $dur-instant $ease-out;
}
.mb__ok:hover, .mb__ok:focus-visible { opacity: .86; }
.mb__ok:active { transform: scale(.97); }
.mb__x {
flex: 0 0 auto;
display: inline-flex;
border: 0;
border-radius: $r-sm;
padding: $sp-1;
background: none;
color: rgba(255, 247, 230, .64);
cursor: pointer;
transition: color $dur-quick $ease-out, transform $dur-instant $ease-out;
}
.mb__x:hover, .mb__x:focus-visible { color: $color; }
.mb__x:active { transform: scale(.97); }
.mb__xic { display: inline-flex; }
// ── 자동 루프: 0~20% 에 확실히 올라오고 머물다 다시 내려간다 (0% = 100%) ──
.mb__bar.is-demo { animation: mb-up $duration $easing infinite; }
@keyframes mb-up {
0%, 100% { transform: translateY(100%); }
20%, 82% { transform: translateY(0); }
}
// 띠 안 cookie 아이콘만 좌우로 천천히 기운다 (-6 → 6 → -6)
.mb__bar.is-demo .mb__cookie { animation: mb-tilt $duration $ease-out infinite; }
@keyframes mb-tilt {
0% { transform: rotate(-6deg); }
50% { transform: rotate(6deg); }
100% { transform: rotate(-6deg); }
}
@media (max-width: 340px) {
.mb__lines { gap: $sp-2; padding: $sp-3; }
.mb__bar { gap: $sp-1; padding: 0 $sp-2; }
.mb__ok { padding: $sp-1 $sp-2; }
}
@media (prefers-reduced-motion: reduce) {
.mb__bar.is-demo, .mb__bar.is-demo .mb__cookie { animation: none !important; }
.mb__bar.is-demo { transform: translateY(0); } // 올라와 있는 결과 상태로 둔다
.mb__bar, .mb__ok, .mb__x { transition: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.mb {
position: relative;
box-sizing: border-box;
width: clamp(272px, 90vw, 420px);
height: clamp(170px, 62vh, 232px);
overflow: hidden;
border: 1px solid rgba(255, 247, 230, 0.12);
border-radius: 16px;
background: rgba(255, 247, 230, 0.04);
color: #fff7e6;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
}
.mb__top {
display: flex;
align-items: center;
gap: 4px;
height: 24px;
padding: 0 12px;
background: rgba(255, 247, 230, 0.06);
}
.mb__dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(255, 247, 230, 0.2);
}
.mb__lines {
display: grid;
gap: 12px;
padding: 16px;
}
.mb__line {
height: 8px;
border-radius: 4px;
background: rgba(255, 247, 230, 0.06);
}
.mb__line--title {
height: 12px;
width: 56%;
background: rgba(255, 247, 230, 0.12);
}
.mb__line--short {
width: 62%;
}
.mb__bar {
position: absolute;
left: 0;
right: 0;
bottom: 0;
box-sizing: border-box;
height: 44px;
display: flex;
align-items: center;
gap: 8px;
padding: 0 12px;
border-top: 1px solid rgba(255, 247, 230, 0.22);
background: rgba(255, 247, 230, 0.14);
transition: transform 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.mb__bar.is-gone {
transform: translateY(100%);
}
.mb__cookie {
display: inline-flex;
flex: 0 0 auto;
color: #fff7e6;
}
.mb__text {
margin: 0;
min-width: 0;
flex: 1 1 auto;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
font-size: clamp(9px, 2.6vw, 12px);
line-height: 1.3;
color: rgba(255, 247, 230, 0.88);
}
.mb__ok {
flex: 0 0 auto;
border: 0;
border-radius: 999px;
padding: 4px 12px;
background: #fff7e6;
color: #17141a;
font-family: inherit;
font-size: clamp(9px, 2.6vw, 11px);
font-weight: 700;
white-space: nowrap;
cursor: pointer;
transition: opacity 220ms ease-out, transform 140ms ease-out;
}
.mb__ok:hover, .mb__ok:focus-visible {
opacity: 0.86;
}
.mb__ok:active {
transform: scale(0.97);
}
.mb__x {
flex: 0 0 auto;
display: inline-flex;
border: 0;
border-radius: 8px;
padding: 4px;
background: none;
color: rgba(255, 247, 230, 0.64);
cursor: pointer;
transition: color 220ms ease-out, transform 140ms ease-out;
}
.mb__x:hover, .mb__x:focus-visible {
color: #fff7e6;
}
.mb__x:active {
transform: scale(0.97);
}
.mb__xic {
display: inline-flex;
}
.mb__bar.is-demo {
animation: mb-up 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes mb-up {
0%, 100% {
transform: translateY(100%);
}
20%, 82% {
transform: translateY(0);
}
}
.mb__bar.is-demo .mb__cookie {
animation: mb-tilt 2s ease-out infinite;
}
@keyframes mb-tilt {
0% {
transform: rotate(-6deg);
}
50% {
transform: rotate(6deg);
}
100% {
transform: rotate(-6deg);
}
}
@media (max-width: 340px) {
.mb__lines {
gap: 8px;
padding: 12px;
}
.mb__bar {
gap: 4px;
padding: 0 8px;
}
.mb__ok {
padding: 4px 8px;
}
}
@media (prefers-reduced-motion: reduce) {
.mb__bar.is-demo, .mb__bar.is-demo .mb__cookie {
animation: none !important;
}
.mb__bar.is-demo {
transform: translateY(0);
}
.mb__bar, .mb__ok, .mb__x {
transition: none;
}
}// 실제 동작: 확인·닫기 어느 쪽을 눌러도 띠가 아래로 내려가고 선택을 저장한다
var bar = document.querySelector('.mb__bar');
function dismiss(v) {
bar.classList.remove('is-demo');
bar.classList.add('is-gone');
try { localStorage.setItem('gd-consent-05', v); } catch (e) {}
}
document.querySelector('.mb__ok').addEventListener('click', function () { dismiss('accept'); });
document.querySelector('.mb__x').addEventListener('click', function () { dismiss('close'); });import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./MiniLineBar.module.scss";
export type MiniLineBarProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & {
"--duration"?: string;
"--easing"?: string;
"--color"?: string;
};
function CookieIcon() {
return (
<svg viewBox="0 0 256 256" width="20" height="20" aria-hidden="true">
<g fill="currentColor">
<path d="M224 128a96 96 0 1 1-96-96a48 48 0 0 0 48 48a48 48 0 0 0 48 48" opacity=".2" />
<path d="M164.49 163.51a12 12 0 1 1-17 0a12 12 0 0 1 17 0m-81-8a12 12 0 1 0 17 0a12 12 0 0 0-16.98 0Zm9-39a12 12 0 1 0-17 0a12 12 0 0 0 17-.02Zm48-1a12 12 0 1 0 0 17a12 12 0 0 0 0-17M232 128A104 104 0 1 1 128 24a8 8 0 0 1 8 8a40 40 0 0 0 40 40a8 8 0 0 1 8 8a40 40 0 0 0 40 40a8 8 0 0 1 8 8m-16.31 7.39A56.13 56.13 0 0 1 168.5 87.5a56.13 56.13 0 0 1-47.89-47.19a88 88 0 1 0 95.08 95.08" />
</g>
</svg>
);
}
function XIcon() {
return (
<svg viewBox="0 0 256 256" width="14" height="14" aria-hidden="true">
<g fill="currentColor">
<path d="M216 56v144a16 16 0 0 1-16 16H56a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16h144a16 16 0 0 1 16 16" opacity=".2" />
<path d="M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128L50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z" />
</g>
</svg>
);
}
export default function MiniLineBar({
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#fff7e6",
}: MiniLineBarProps) {
const [live, setLive] = useState(true);
const [gone, setGone] = useState(false);
const vars: Vars = {
"--duration": `${duration}ms`,
"--easing": easing,
"--color": color,
};
const dismiss = (value: string): void => {
setLive(false);
setGone(true);
try {
localStorage.setItem("gd-consent-05", value);
} catch (e) {
/* 샌드박스에서 던질 수 있다 */
}
};
return (
<div className={styles.mb} style={vars}>
<div className={styles.top} aria-hidden="true">
<i className={styles.dot} />
<i className={styles.dot} />
<i className={styles.dot} />
</div>
<div className={styles.lines} aria-hidden="true">
<i className={`${styles.line} ${styles.lineTitle}`} />
<i className={styles.line} />
<i className={styles.line} />
<i className={`${styles.line} ${styles.lineShort}`} />
</div>
<div
className={`${styles.bar} ${live ? styles.demo : ""} ${gone ? styles.gone : ""}`}
role="region"
aria-label="Cookie notice"
>
<span className={styles.cookie}>
<CookieIcon />
</span>
<p className={styles.text}>We use cookies. Continuing means you accept them.</p>
<button className={styles.ok} type="button" onClick={() => dismiss("accept")}>
OK
</button>
<button
className={styles.x}
type="button"
aria-label="Close"
onClick={() => dismiss("close")}
>
<span className={styles.xic}>
<XIcon />
</span>
</button>
</div>
</div>
);
}05 아이콘 한 줄 미니 배너 — 측정 누적면적 12.71% · 움직인 프레임 20/23GODRICH 원작 · zip 에 SCSS·React 포함 · 쿠키 동의 배너 UI 9종 — 그대로 붙이는 동의 관리 코드
이 데모가 속한 글
쿠키 동의 배너 UI 9종 — 그대로 붙이는 동의 관리 코드쿠키 동의 배너는 사이트가 쿠키를 쓴다는 사실을 알리고 방문자의 선택을 받아 두는 UI입니다. 여기 아홉 개는 배너·패널·모달·토스트를 모두 실제로 눌러 볼 수 있는 상태로 만든 것입니다. 그중 하나는 끝까지 읽기 이 글의 데모 전부 보기


