무료 도구

쿠키 동의 배너 생성기

쿠키 카테고리를 고르고, 문구를 쓰고, 모양을 선택하면 어떤 사이트에나 붙여넣을 수 있는 의존성 없는 동의 배너가 만들어집니다. 방문자의 선택을 저장하고, 태깅된 스크립트는 해당 카테고리가 허용될 때까지 차단하며, 푸터 링크로 다시 열 수 있습니다.

사이트 작동에 반드시 필요합니다. 항상 켜져 있으며 동의를 묻지 않습니다.
사이트 이용 방식을 측정해 개선에 활용합니다.
광고 개인화와 캠페인 측정에 사용합니다.
언어, 지역, 레이아웃 같은 선택을 기억합니다.

내 동의 배너

모든 페이지에서 닫는 body 태그 바로 앞에 붙여넣으세요.

<!-- Cookie consent banner - paste this just before </body> -->
<script>
(function () {
  var CFG = {
  "storageKey": "cookie_consent",
  "position": "bar",
  "theme": "light",
  "accent": "#a6144d",
  "title": "쿠키를 사용합니다",
  "message": "이 사이트를 운영하고, 이용 방식을 이해하고, 개선하기 위해 쿠키를 사용합니다. 모두 허용하거나, 선택 항목을 거부하거나, 카테고리별로 고를 수 있습니다.",
  "policyUrl": "https://example.com/privacy",
  "policyLabel": "개인정보 처리방침 보기",
  "accept": "모두 허용",
  "reject": "선택 항목 거부",
  "prefs": "환경설정",
  "save": "선택 저장",
  "categories": [
    {
      "id": "necessary",
      "label": "필수",
      "desc": "사이트 작동에 반드시 필요합니다. 항상 켜져 있으며 동의를 묻지 않습니다.",
      "required": true
    },
    {
      "id": "analytics",
      "label": "분석",
      "desc": "사이트 이용 방식을 측정해 개선에 활용합니다.",
      "required": false
    },
    {
      "id": "marketing",
      "label": "마케팅",
      "desc": "광고 개인화와 캠페인 측정에 사용합니다.",
      "required": false
    }
  ]
};
  var KEY = CFG.storageKey;
  var doc = document;
  var box = null;

  function read() {
    try { return JSON.parse(localStorage.getItem(KEY) || 'null'); } catch (e) { return null; }
  }

  function save(consent) {
    consent.necessary = true;
    consent.date = new Date().toISOString();
    try { localStorage.setItem(KEY, JSON.stringify(consent)); } catch (e) {}
    unblock(consent);
    window.dispatchEvent(new CustomEvent('consentchange', { detail: consent }));
    if (typeof window.onConsentChange === 'function') window.onConsentChange(consent);
  }

  // Scripts the site parked behind a category run only once it is granted. Park
  // one by giving its tag type="text/plain" and data-consent="analytics".
  // (Deliberately described, not shown: a literal script tag written out here
  // would close this one early and truncate everything below it.)
  function unblock(consent) {
    var parked = doc.querySelectorAll('script[type="text/plain"][data-consent]');
    for (var i = 0; i < parked.length; i++) {
      var old = parked[i];
      if (!consent[old.getAttribute('data-consent')]) continue;
      var next = doc.createElement('script');
      for (var a = 0; a < old.attributes.length; a++) {
        var at = old.attributes[a];
        if (at.name !== 'type' && at.name !== 'data-consent') next.setAttribute(at.name, at.value);
      }
      next.text = old.text;
      old.parentNode.replaceChild(next, old);
    }
  }

  function styles() {
    var dark = CFG.theme === 'dark';
    var bg = dark ? '#161622' : '#ffffff';
    var fg = dark ? '#f4f4f8' : '#141a47';
    var muted = dark ? 'rgba(244,244,248,.72)' : 'rgba(20,26,71,.7)';
    var line = dark ? 'rgba(255,255,255,.14)' : 'rgba(20,26,71,.12)';
    var el = doc.createElement('style');
    el.textContent = [
      '.cc-root{position:fixed;z-index:2147483000;font:400 14px/1.55 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;color:' + fg + ';box-sizing:border-box}',
      '.cc-root *,.cc-root *::before,.cc-root *::after{box-sizing:inherit}',
      '.cc-card{background:' + bg + ';border:1px solid ' + line + ';box-shadow:0 18px 48px rgba(0,0,0,.18);border-radius:14px;padding:20px}',
      '.cc-bar{left:0;right:0;bottom:0;padding:16px}',
      '.cc-bar .cc-card{display:flex;flex-wrap:wrap;gap:16px;align-items:center;justify-content:space-between;max-width:1120px;margin:0 auto}',
      '.cc-box{left:16px;bottom:16px;max-width:400px}',
      '.cc-modal{inset:0;display:flex;align-items:center;justify-content:center;padding:16px;background:rgba(10,10,20,.5)}',
      '.cc-modal .cc-card{max-width:520px;width:100%}',
      '.cc-title{margin:0 0 6px;font-size:16px;font-weight:700}',
      '.cc-text{margin:0;color:' + muted + '}',
      '.cc-text a{color:' + CFG.accent + ';font-weight:600}',
      '.cc-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}',
      '.cc-bar .cc-actions{margin-top:0}',
      '.cc-btn{appearance:none;cursor:pointer;border:1px solid ' + line + ';border-radius:999px;padding:9px 18px;font:600 14px inherit;background:transparent;color:' + fg + '}',
      '.cc-btn-main{background:' + CFG.accent + ';border-color:' + CFG.accent + ';color:#fff}',
      '.cc-btn:focus-visible{outline:2px solid ' + CFG.accent + ';outline-offset:2px}',
      '.cc-cats{display:none;margin:14px 0 0;padding:14px 0 0;border-top:1px solid ' + line + '}',
      '.cc-cats.cc-open{display:block}',
      '.cc-cat{display:flex;gap:10px;align-items:flex-start;margin-bottom:12px}',
      '.cc-cat input{margin:3px 0 0;accent-color:' + CFG.accent + '}',
      '.cc-cat strong{display:block;font-size:14px}',
      '.cc-cat span{display:block;color:' + muted + ';font-size:13px}',
      '@media(max-width:640px){.cc-bar .cc-card{display:block}.cc-bar .cc-actions{margin-top:14px}.cc-btn{flex:1 1 auto}}'
    ].join('');
    doc.head.appendChild(el);
  }

  function build() {
    var stored = read() || {};
    var root = doc.createElement('div');
    root.className = 'cc-root cc-' + CFG.position;
    root.setAttribute('role', CFG.position === 'modal' ? 'dialog' : 'region');
    root.setAttribute('aria-label', CFG.title);
    if (CFG.position === 'modal') root.setAttribute('aria-modal', 'true');

    var card = doc.createElement('div');
    card.className = 'cc-card';

    var copy = doc.createElement('div');
    var h = doc.createElement('p');
    h.className = 'cc-title';
    h.textContent = CFG.title;
    var p = doc.createElement('p');
    p.className = 'cc-text';
    p.textContent = CFG.message + ' ';
    if (CFG.policyUrl) {
      var link = doc.createElement('a');
      link.href = CFG.policyUrl;
      link.textContent = CFG.policyLabel;
      p.appendChild(link);
    }
    copy.appendChild(h);
    copy.appendChild(p);

    var cats = doc.createElement('div');
    cats.className = 'cc-cats';
    var inputs = {};
    for (var i = 0; i < CFG.categories.length; i++) {
      var c = CFG.categories[i];
      var row = doc.createElement('label');
      row.className = 'cc-cat';
      var cb = doc.createElement('input');
      cb.type = 'checkbox';
      cb.checked = c.required || stored[c.id] === true;
      cb.disabled = !!c.required;
      var txt = doc.createElement('div');
      var name = doc.createElement('strong');
      name.textContent = c.label;
      var desc = doc.createElement('span');
      desc.textContent = c.desc;
      txt.appendChild(name);
      txt.appendChild(desc);
      row.appendChild(cb);
      row.appendChild(txt);
      cats.appendChild(row);
      inputs[c.id] = cb;
    }
    copy.appendChild(cats);

    var actions = doc.createElement('div');
    actions.className = 'cc-actions';
    function button(label, main, onClick) {
      var b = doc.createElement('button');
      b.type = 'button';
      b.className = 'cc-btn' + (main ? ' cc-btn-main' : '');
      b.textContent = label;
      b.addEventListener('click', onClick);
      actions.appendChild(b);
      return b;
    }
    function decide(granted) {
      var consent = {};
      for (var i = 0; i < CFG.categories.length; i++) {
        var c = CFG.categories[i];
        consent[c.id] = c.required ? true : granted === null ? !!inputs[c.id].checked : granted;
      }
      save(consent);
      close();
    }
    var prefsBtn = button(CFG.prefs, false, function () {
      var open = cats.classList.toggle('cc-open');
      prefsBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
      saveBtn.style.display = open ? '' : 'none';
    });
    prefsBtn.setAttribute('aria-expanded', 'false');
    button(CFG.reject, false, function () { decide(false); });
    button(CFG.accept, true, function () { decide(true); });
    var saveBtn = button(CFG.save, false, function () { decide(null); });
    saveBtn.style.display = 'none';

    card.appendChild(copy);
    card.appendChild(actions);
    root.appendChild(card);
    doc.body.appendChild(root);
    box = root;
  }

  function close() {
    if (box && box.parentNode) box.parentNode.removeChild(box);
    box = null;
  }

  function open() {
    if (!box) build();
  }

  function start() {
    styles();
    var stored = read();
    if (stored) unblock(stored);
    else build();
    window.showConsentPreferences = open;
  }

  if (doc.readyState === 'loading') doc.addEventListener('DOMContentLoaded', start);
  else start();
})();
</script>

사용 방법

작동하는 배너까지 세 단계

1. 스니펫 붙여넣기

생성된 코드를 모든 페이지의 닫는 body 태그 바로 앞에 넣으세요. 의존성이 없고 네트워크 요청도 늘지 않습니다.

2. 추적 스크립트 대기시키기

추적 태그의 type을 text/plain으로 바꾸고 data-consent="analytics"를 추가하세요. 배너는 해당 카테고리가 허용되는 순간 실행하며, 그 전에는 절대 실행하지 않습니다.

3. 마음을 바꿀 수 있게 하기

window.showConsentPreferences()를 호출하는 링크를 푸터에 추가하면 방문자가 패널을 다시 열 수 있습니다. 모든 결정은 consentchange 이벤트도 발생시키므로 구독할 수 있습니다.

자주 묻는 질문

쿠키 동의에 대하여

이 배너만으로 GDPR을 준수할 수 있나요?

규제 기관이 기대하는 작동 방식은 갖추고 있습니다. 동의 전에는 선택 쿠키를 두지 않고, 거부를 허용만큼 쉽게 하며, 카테고리별로 선택할 수 있고, 나중에 마음을 바꿀 수 있습니다. 다만 준수 여부는 실제로 무엇을 실행하는지, 방침에 무엇을 적었는지, 방문자가 어디에 있는지에도 달려 있으므로 생성된 코드는 법률 자문이 아니라 견고한 출발점으로 보시기 바랍니다.

동의 전에 스크립트가 실행되지 않게 하려면 어떻게 하나요?

스크립트 태그에 type="text/plain"과 카테고리 이름을 담은 data-consent 속성을 주세요. 브라우저는 그것을 실행하지 않고, 해당 카테고리가 허용되는 즉시 배너가 진짜 스크립트 태그로 바꿔 줍니다. 이번 방문에서든, 저장된 결정에 따라 다음 방문에서든 마찬가지입니다.

방문자의 선택은 어디에 저장되나요?

선택한 키의 localStorage에 카테고리별 플래그와 날짜를 담은 작은 JSON 객체로 저장됩니다. 배너 자체는 쿠키를 설정하지 않고 서버로 아무것도 보내지 않으므로 순수한 정적 사이트에서도 동작합니다.

디자인을 바꿀 수 있나요?

네. 위치, 테마, 강조 색상은 여기서 선택할 수 있고, 생성되는 CSS의 모든 요소에는 cc- 접두사가 붙어 있어 스니펫을 건드리지 않고도 자체 스타일시트에서 규칙을 덮어쓸 수 있습니다.

그래도 동의 관리 플랫폼이 필요한가요?

카테고리가 몇 개뿐인 사이트 하나라면 이 스니펫으로 충분한 경우가 많습니다. 동의 기록, 여러 도메인, 선택을 자동으로 존중하는 분석이 필요해지면 PulsePanda에 내장된 동의 관리자가 전부 맡습니다.

관련

PulsePanda 더 보기

분석이 실제로 존중하는 동의

PulsePanda에는 동의 관리자가 기본 내장되어 있습니다. 기본적으로 쿠키를 쓰지 않고, 누군가 거부하는 즉시 수집을 멈춥니다. 두 번째 공급업체도, 연결 코드도 필요 없습니다.

무료로 시작하기