<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:media="http://search.yahoo.com/mrss/">

<channel>
	<title>유용한 도구 &#8211; SNSnote</title>
	<atom:link href="https://snsnote.com/category/%EC%9C%A0%EC%9A%A9%ED%95%9C-%EB%8F%84%EA%B5%AC/feed/" rel="self" type="application/rss+xml" />
	<link>https://snsnote.com</link>
	<description>인스타그램 정지, 기기밴, 계정 복구, 알고리즘, 성장 전략까지 20만 팔로워를 직접 운영하며 쌓은 실전 노하우를 모두 공유합니다. 초보자도 쉽게 따라할 수 있는 SNS 팁을 확인하세요.</description>
	<lastBuildDate>Fri, 11 Sep 2026 14:55:03 +0000</lastBuildDate>
	<language>ko-KR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://snsnote.com/wp-content/uploads/2026/06/sns-thumbnail-logo-32x32.png</url>
	<title>유용한 도구 &#8211; SNSnote</title>
	<link>https://snsnote.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">254946779</site>	<item>
		<title>SNS 게시물 위험도 검사기｜Instagram·Threads·X 이미지 사전 점검</title>
		<link>https://snsnote.com/sns-post-risk-checker/</link>
					<comments>https://snsnote.com/sns-post-risk-checker/#respond</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Fri, 11 Sep 2026 14:45:03 +0000</pubDate>
				<category><![CDATA[인스타그램]]></category>
		<category><![CDATA[유용한 도구]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1617</guid>

					<description><![CDATA[SNS 게시 전, 이미지 위험요소를 미리 확인하세요 SNSNOTE의 SNS 게시물 위험도 검사기는 Instagram, Threads, X 등에 이미지를 게시하기 전 민감 콘텐츠로 판단될 가능성을 사전에 확인할...]]></description>
										<content:encoded><![CDATA[<div id="sns-risk-v8">

<style>
#sns-risk-v8,
#sns-risk-v8 * {
  box-sizing: border-box;
}

#sns-risk-v8 {
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Noto Sans KR", Arial, sans-serif;
  color: #172033;
}

#sns-risk-v8 .sr-card {
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 6px 28px rgba(15,23,42,.06);
}

#sns-risk-v8 .sr-head {
  padding: 24px;
  background: linear-gradient(135deg,#f8faff,#fffaf4);
  border-bottom: 1px solid #eef2f7;
}

#sns-risk-v8 .sr-title-box {
  position: relative;
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 17px 20px 17px 25px;
  background: #fff;
  border: 1px solid #e2e8f0;
  border-radius: 14px;
  box-shadow: 0 4px 16px rgba(15,23,42,.06);
  overflow: hidden;
}

#sns-risk-v8 .sr-title-box::before {
  content: "";
  position: absolute;
  left: 0;
  top: 12px;
  bottom: 12px;
  width: 6px;
  border-radius: 0 6px 6px 0;
  background: linear-gradient(180deg,#3b82f6,#6366f1,#8b5cf6);
}

#sns-risk-v8 .sr-title-icon {
  width: 40px;
  height: 40px;
  flex: 0 0 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 11px;
  background: #eef2ff;
  color: #4f46e5;
  font-size: 20px;
  font-weight: 900;
}

#sns-risk-v8 .sr-title {
  margin: 0 !important;
  padding: 0 !important;
  background: none !important;
  border: 0 !important;
  box-shadow: none !important;
  color: #172033 !important;
  font-size: 25px !important;
  font-weight: 800 !important;
  line-height: 1.35 !important;
  letter-spacing: -.5px;
}

#sns-risk-v8 .sr-desc {
  margin: 16px 0 0;
  color: #64748b;
  font-size: 14px;
  line-height: 1.7;
}

#sns-risk-v8 .sr-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}

#sns-risk-v8 .sr-badge {
  padding: 7px 10px;
  border: 1px solid #e2e8f0;
  border-radius: 999px;
  background: #fff;
  color: #475569;
  font-size: 11px;
  font-weight: 700;
}

#sns-risk-v8 .sr-body {
  padding: 24px;
}

#sns-risk-v8 .sr-drop {
  min-height: 190px;
  border: 2px dashed #cbd5e1;
  border-radius: 16px;
  background: #f8fafc;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  padding: 24px;
  cursor: pointer;
  transition: .2s;
}

#sns-risk-v8 .sr-drop:hover {
  border-color: #6366f1;
  background: #f5f3ff;
}

#sns-risk-v8 .sr-drop strong {
  display: block;
  font-size: 16px;
  color: #1e293b;
}

#sns-risk-v8 .sr-drop span {
  display: block;
  margin-top: 7px;
  color: #64748b;
  font-size: 12px;
  line-height: 1.65;
}

#sns-risk-v8 #sr8-file {
  display: none;
}

#sns-risk-v8 .sr-count {
  display: none;
  margin-top: 14px;
  padding: 11px 13px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #f8fafc;
  color: #334155;
  font-size: 13px;
  font-weight: 700;
}

#sns-risk-v8 .sr-preview {
  display: grid;
  grid-template-columns: repeat(5,minmax(0,1fr));
  gap: 10px;
  margin-top: 16px;
}

#sns-risk-v8 .sr-thumb {
  position: relative !important;
  aspect-ratio: 1/1;
  overflow: hidden;
  border-radius: 12px;
  background: #f1f5f9;
  isolation: isolate;
}

#sns-risk-v8 .sr-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

#sns-risk-v8 .sr-num {
  position: absolute !important;
  top: 8px !important;
  left: 8px !important;
  min-width: 28px !important;
  height: 28px !important;
  padding: 0 7px !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  border-radius: 8px !important;
  background: rgba(15,23,42,.9) !important;
  color: #fff !important;
  font-size: 11px !important;
  font-weight: 800 !important;
  z-index: 10 !important;
}

#sns-risk-v8 button.sr-remove {
  position: absolute !important;
  top: 8px !important;
  right: 8px !important;
  left: auto !important;
  bottom: auto !important;
  width: 28px !important;
  height: 28px !important;
  min-width: 28px !important;
  min-height: 28px !important;
  max-width: 28px !important;
  max-height: 28px !important;
  margin: 0 !important;
  padding: 0 !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  border: 0 !important;
  border-radius: 50% !important;
  background: rgba(15,23,42,.9) !important;
  color: #fff !important;
  font: 900 18px/1 Arial,sans-serif !important;
  cursor: pointer !important;
  box-shadow: 0 2px 7px rgba(0,0,0,.22) !important;
  transform: none !important;
  z-index: 20 !important;
}

#sns-risk-v8 button.sr-remove:hover {
  background: #ef4444 !important;
}

#sns-risk-v8 .sr-actions {
  display: flex;
  gap: 10px;
  margin-top: 18px;
}

#sns-risk-v8 .sr-start {
  flex: 1;
  min-height: 52px;
  border: 0;
  border-radius: 12px;
  background: linear-gradient(135deg,#4f46e5,#7c3aed);
  color: #fff;
  font-size: 15px;
  font-weight: 800;
  cursor: pointer;
}

#sns-risk-v8 .sr-start:disabled {
  opacity: .5;
  cursor: not-allowed;
}

#sns-risk-v8 .sr-reset {
  min-width: 95px;
  border: 1px solid #cbd5e1;
  border-radius: 12px;
  background: #fff;
  color: #475569;
  font-weight: 800;
  cursor: pointer;
}

#sns-risk-v8 .sr-status {
  display: none;
  margin-top: 16px;
  padding: 13px 14px;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  background: #f8fafc;
  color: #334155;
  font-size: 12px;
  font-weight: 700;
}

#sns-risk-v8 .sr-error {
  display: none;
  margin-top: 14px;
  padding: 13px 14px;
  border: 1px solid #fecaca;
  border-radius: 12px;
  background: #fef2f2;
  color: #b91c1c;
  font-size: 12px;
}

#sns-risk-v8 .sr-results {
  display: grid;
  gap: 14px;
  margin-top: 20px;
}

#sns-risk-v8 .sr-result {
  padding: 16px;
  border: 1px solid #e2e8f0;
  border-radius: 16px;
  background: #fff;
}

#sns-risk-v8 .sr-result-top {
  display: grid;
  grid-template-columns: 115px 1fr;
  gap: 16px;
}

#sns-risk-v8 .sr-result-img {
  width: 115px;
  height: 115px;
  object-fit: cover;
  border-radius: 11px;
}

#sns-risk-v8 .sr-result-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
}

#sns-risk-v8 .sr-name {
  font-size: 14px;
  font-weight: 800;
  color: #334155;
}

#sns-risk-v8 .sr-score {
  padding: 7px 11px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 900;
  white-space: nowrap;
}

#sns-risk-v8 .sr-low {
  background: #ecfdf5;
  color: #047857;
}

#sns-risk-v8 .sr-mid {
  background: #fffbeb;
  color: #b45309;
}

#sns-risk-v8 .sr-high {
  background: #fff1f2;
  color: #be123c;
}

#sns-risk-v8 .sr-critical {
  background: #be123c;
  color: #fff;
}

#sns-risk-v8 .sr-models {
  margin-top: 14px;
  border-top: 1px solid #eef2f7;
  padding-top: 14px;
}

#sns-risk-v8 .sr-model-title {
  margin-bottom: 10px;
  font-size: 12px;
  font-weight: 800;
  color: #334155;
}

#sns-risk-v8 .sr-model-row {
  display: grid;
  grid-template-columns: 72px 1fr 48px;
  gap: 10px;
  align-items: center;
  margin-top: 9px;
}

#sns-risk-v8 .sr-model-name {
  font-size: 11px;
  font-weight: 800;
  color: #475569;
}

#sns-risk-v8 .sr-bar-bg {
  height: 10px;
  border-radius: 999px;
  background: #e2e8f0;
  overflow: hidden;
}

#sns-risk-v8 .sr-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg,#3b82f6,#7c3aed);
}

#sns-risk-v8 .sr-model-score {
  font-size: 11px;
  font-weight: 800;
  text-align: right;
  color: #334155;
}

#sns-risk-v8 .sr-summary {
  margin-top: 14px;
  padding: 11px 12px;
  border-radius: 10px;
  font-size: 11px;
  font-weight: 800;
  line-height: 1.6;
}

#sns-risk-v8 .sr-summary-low {
  background: #ecfdf5;
  color: #047857;
}

#sns-risk-v8 .sr-summary-mid {
  background: #fffbeb;
  color: #b45309;
}

#sns-risk-v8 .sr-summary-high {
  background: #fff1f2;
  color: #be123c;
}

#sns-risk-v8 .sr-note {
  margin-top: 18px;
  padding: 15px 16px;
  border: 1px solid #dbeafe;
  border-radius: 12px;
  background: #f8fbff;
  color: #475569;
  font-size: 11px;
  line-height: 1.75;
}

#sns-risk-v8 .sr-note strong {
  display: block;
  margin-bottom: 3px;
  color: #1d4ed8;
  font-size: 12px;
  font-weight: 800;
}


/* =========================
   모바일 최적화
   ========================= */

@media(max-width:700px) {

  /* 숏코드 내부 좌우 여백 축소 */
  #sns-risk-v8 .sr-head,
  #sns-risk-v8 .sr-body {
    padding-left: 12px !important;
    padding-right: 12px !important;
  }

  #sns-risk-v8 .sr-title-box {
    padding: 14px 14px 14px 18px;
    gap: 10px;
  }

  #sns-risk-v8 .sr-title {
    font-size: 21px !important;
  }

  #sns-risk-v8 .sr-title-icon {
    width: 36px;
    height: 36px;
    flex: 0 0 36px;
    font-size: 17px;
  }

  #sns-risk-v8 .sr-drop {
    padding-left: 12px;
    padding-right: 12px;
  }

  #sns-risk-v8 .sr-preview {
    grid-template-columns: repeat(4,minmax(0,1fr));
    gap: 8px;
  }

  #sns-risk-v8 .sr-result {
    padding: 12px;
  }

  #sns-risk-v8 .sr-result-top {
    grid-template-columns: 90px 1fr;
    gap: 12px;
  }

  #sns-risk-v8 .sr-result-img {
    width: 90px;
    height: 90px;
  }

  #sns-risk-v8 .sr-model-row {
    grid-template-columns: 62px 1fr 40px;
    gap: 7px;
  }

  #sns-risk-v8 .sr-model-name,
  #sns-risk-v8 .sr-model-score {
    font-size: 10px;
  }

  #sns-risk-v8 .sr-note {
    padding-left: 12px;
    padding-right: 12px;
  }
}


@media(max-width:480px) {

  #sns-risk-v8 .sr-head,
  #sns-risk-v8 .sr-body {
    padding-left: 10px !important;
    padding-right: 10px !important;
  }

  #sns-risk-v8 .sr-title-box {
    padding: 13px 11px 13px 16px;
    gap: 9px;
  }

  #sns-risk-v8 .sr-title {
    font-size: 19px !important;
    line-height: 1.3 !important;
  }

  #sns-risk-v8 .sr-title-icon {
    width: 34px;
    height: 34px;
    flex: 0 0 34px;
    font-size: 16px;
  }

  #sns-risk-v8 .sr-desc {
    font-size: 12px;
  }

  #sns-risk-v8 .sr-badges {
    gap: 5px;
  }

  #sns-risk-v8 .sr-badge {
    padding: 6px 8px;
    font-size: 10px;
  }

  #sns-risk-v8 .sr-drop {
    min-height: 160px;
    padding: 16px 8px;
  }

  #sns-risk-v8 .sr-drop strong {
    font-size: 14px;
  }

  #sns-risk-v8 .sr-drop span {
    font-size: 11px;
  }

  #sns-risk-v8 .sr-preview {
    grid-template-columns: repeat(3,minmax(0,1fr));
    gap: 7px;
  }

  #sns-risk-v8 .sr-actions {
    gap: 7px;
  }

  #sns-risk-v8 .sr-start {
    min-height: 48px;
    font-size: 13px;
  }

  #sns-risk-v8 .sr-reset {
    min-width: 72px;
    font-size: 12px;
  }

  #sns-risk-v8 .sr-result {
    padding: 10px;
  }

  #sns-risk-v8 .sr-result-top {
    grid-template-columns: 72px 1fr;
    gap: 9px;
  }

  #sns-risk-v8 .sr-result-img {
    width: 72px;
    height: 72px;
  }

  #sns-risk-v8 .sr-name {
    font-size: 12px;
  }

  #sns-risk-v8 .sr-score {
    padding: 5px 7px;
    font-size: 10px;
  }

  #sns-risk-v8 .sr-models {
    margin-top: 10px;
    padding-top: 10px;
  }

  #sns-risk-v8 .sr-model-title {
    font-size: 11px;
  }

  #sns-risk-v8 .sr-model-row {
    grid-template-columns: 52px 1fr 36px;
    gap: 5px;
    margin-top: 7px;
  }

  #sns-risk-v8 .sr-summary {
    padding: 9px 10px;
    font-size: 10px;
  }

  #sns-risk-v8 .sr-note {
    padding: 12px 10px;
  }
}
</style>


<div class="sr-card">

  <div class="sr-head">

    <div class="sr-title-box">

      <div class="sr-title-icon">
        AI
      </div>

      <h2 class="sr-title">
        SNS 게시물 위험도 검사기
      </h2>

    </div>

    <p class="sr-desc">
      서로 다른 3개의 이미지 분석 AI가 같은 이미지를 각각 검사하고,
      결과를 비교해 종합 위험도를 표시합니다.
    </p>

    <div class="sr-badges">
      <span class="sr-badge">최대 20장</span>
      <span class="sr-badge">3중 AI 분석</span>
      <span class="sr-badge">브라우저에서 처리</span>
      <span class="sr-badge">서버 업로드 없음</span>
    </div>

  </div>


  <div class="sr-body">

    <label class="sr-drop" for="sr8-file">

      <div>
        <strong>
          검사할 SNS 이미지를 선택하세요
        </strong>

        <span>
          JPG · PNG · WebP<br>
          카드뉴스·피드 이미지 등 최대 20장까지 한 번에 선택할 수 있습니다.
        </span>
      </div>

    </label>


    <input
      id="sr8-file"
      type="file"
      accept="image/jpeg,image/png,image/webp"
      multiple
    >


    <div
      class="sr-count"
      id="sr8-count"
    ></div>


    <div
      class="sr-preview"
      id="sr8-preview"
    ></div>


    <div class="sr-actions">

      <button
        type="button"
        class="sr-start"
        id="sr8-start"
        disabled
      >
        3중 AI 위험도 검사 시작
      </button>

      <button
        type="button"
        class="sr-reset"
        id="sr8-reset"
      >
        초기화
      </button>

    </div>


    <div
      class="sr-status"
      id="sr8-status"
    ></div>


    <div
      class="sr-error"
      id="sr8-error"
    ></div>


    <div
      class="sr-results"
      id="sr8-results"
    ></div>


    <div class="sr-note">

      <strong>
        검사 기준은 계속 업데이트됩니다.
      </strong>

      Instagram, Threads, X 등 주요 SNS의 정책 변화와 실제 사례를 참고해
      위험도 기준을 지속적으로 개선하고 있습니다.

    </div>

  </div>

</div>


<script type="module">
(async function () {

  const MAX_FILES = 20;

  const MODEL_A =
    "onnx-community/nsfw_image_detection-ONNX";

  const MODEL_B =
    "onnx-community/nsfw-classifier-ONNX";


  const state = {
    files: [],
    running: false,

    transformers: null,
    RawImage: null,

    modelA: null,
    modelB: null,
    modelC: null
  };


  const root =
    document.getElementById(
      "sns-risk-v8"
    );

  if (!root) return;


  const fileInput =
    root.querySelector("#sr8-file");

  const count =
    root.querySelector("#sr8-count");

  const preview =
    root.querySelector("#sr8-preview");

  const startBtn =
    root.querySelector("#sr8-start");

  const resetBtn =
    root.querySelector("#sr8-reset");

  const status =
    root.querySelector("#sr8-status");

  const errorBox =
    root.querySelector("#sr8-error");

  const results =
    root.querySelector("#sr8-results");


  function clamp(value) {
    return Math.max(
      0,
      Math.min(100,value)
    );
  }


  function showStatus(text) {
    status.style.display =
      "block";

    status.textContent =
      text;
  }


  function showError(text) {
    errorBox.style.display =
      "block";

    errorBox.textContent =
      text;
  }


  function clearError() {
    errorBox.style.display =
      "none";

    errorBox.textContent =
      "";
  }


  function getLevel(score) {

    if (score >= 85) {
      return {
        text:"매우 높음",
        cls:"sr-critical",
        summaryCls:"sr-summary-high",
        message:
          "3개 AI의 종합 결과에서 높은 민감 콘텐츠 위험 신호가 확인되었습니다."
      };
    }

    if (score >= 65) {
      return {
        text:"높음",
        cls:"sr-high",
        summaryCls:"sr-summary-high",
        message:
          "여러 AI 모델에서 민감 콘텐츠 위험 신호가 비교적 높게 감지되었습니다."
      };
    }

    if (score >= 35) {
      return {
        text:"주의",
        cls:"sr-mid",
        summaryCls:"sr-summary-mid",
        message:
          "AI 모델별 판단이 엇갈리거나 일부 민감 신호가 감지되었습니다."
      };
    }

    return {
      text:"낮음",
      cls:"sr-low",
      summaryCls:"sr-summary-low",
      message:
        "현재 3개 AI의 종합 결과에서는 강한 민감 콘텐츠 신호가 많지 않습니다."
    };
  }


  function renderPreview() {

    preview.innerHTML = "";


    state.files.forEach(
      function(item,index) {

        const box =
          document.createElement("div");

        box.className =
          "sr-thumb";


        box.innerHTML = `
          <img decoding="async" src="${item.url}" alt="">

          <span class="sr-num">
            ${index + 1}
          </span>

          <button
            type="button"
            class="sr-remove"
            data-id="${item.id}"
            aria-label="이미지 삭제"
          >
            ×
          </button>
        `;


        preview.appendChild(box);

      }
    );


    if (state.files.length) {

      count.style.display =
        "block";

      count.textContent =
        state.files.length +
        " / 20장 선택됨";

    } else {

      count.style.display =
        "none";

    }


    startBtn.disabled =
      !state.files.length ||
      state.running;


    preview
      .querySelectorAll(".sr-remove")
      .forEach(
        function(btn) {

          btn.addEventListener(
            "click",
            function(event) {

              event.preventDefault();
              event.stopPropagation();

              if (state.running) {
                return;
              }

              const id =
                btn.dataset.id;

              const found =
                state.files.find(
                  item =>
                    item.id === id
                );

              if (found) {
                URL.revokeObjectURL(
                  found.url
                );
              }

              state.files =
                state.files.filter(
                  item =>
                    item.id !== id
                );

              renderPreview();

            }
          );

        }
      );
  }


  fileInput.addEventListener(
    "change",
    function() {

      let files =
        Array.from(
          fileInput.files || []
        );


      files =
        files.filter(
          file =>
            /^image\/(jpeg|png|webp)$/i
              .test(file.type)
        );


      const remain =
        MAX_FILES -
        state.files.length;


      files =
        files.slice(
          0,
          remain
        );


      files.forEach(
        function(file) {

          state.files.push({

            id:
              Date.now().toString(36) +
              Math.random()
                .toString(36)
                .slice(2),

            file:file,

            url:
              URL.createObjectURL(file)

          });

        }
      );


      fileInput.value = "";

      renderPreview();

    }
  );


  async function loadModels() {

    if (
      state.modelA &&
      state.modelB &&
      state.modelC
    ) {
      return;
    }


    showStatus(
      "3개의 AI 모델을 처음 준비하는 중입니다..."
    );


    const transformers =
      await import(
        "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1/+esm"
      );


    state.transformers =
      transformers;

    state.RawImage =
      transformers.RawImage;


    if (!state.modelA) {

      showStatus(
        "AI 모델 A를 준비하는 중..."
      );

      state.modelA =
        await transformers.pipeline(
          "image-classification",
          MODEL_A,
          {
            device:"wasm"
          }
        );

    }


    if (!state.modelB) {

      showStatus(
        "AI 모델 B를 준비하는 중..."
      );

      state.modelB =
        await transformers.pipeline(
          "image-classification",
          MODEL_B,
          {
            device:"wasm"
          }
        );

    }


    if (!state.modelC) {

      showStatus(
        "AI 모델 C를 준비하는 중..."
      );


      const nsfwjs =
        await import(
          "https://cdn.jsdelivr.net/npm/nsfwjs@4.4.0/+esm"
        );


      state.modelC =
        await nsfwjs.load();

    }

  }


  async function getRawImage(file) {

    return await state.RawImage
      .fromBlob(file);

  }


  async function getCanvas(file) {

    return new Promise(
      function(resolve,reject) {

        const url =
          URL.createObjectURL(file);


        const img =
          new Image();


        img.onload =
          function() {

            let width =
              img.naturalWidth;

            let height =
              img.naturalHeight;


            const maxSide =
              768;


            if (
              Math.max(
                width,
                height
              ) > maxSide
            ) {

              const ratio =
                maxSide /
                Math.max(
                  width,
                  height
                );

              width =
                Math.round(
                  width * ratio
                );

              height =
                Math.round(
                  height * ratio
                );

            }


            const canvas =
              document.createElement(
                "canvas"
              );


            canvas.width =
              width;

            canvas.height =
              height;


            const ctx =
              canvas.getContext("2d");


            ctx.drawImage(
              img,
              0,
              0,
              width,
              height
            );


            URL.revokeObjectURL(url);


            resolve(canvas);

          };


        img.onerror =
          function() {

            URL.revokeObjectURL(url);

            reject(
              new Error(
                "이미지를 읽지 못했습니다."
              )
            );

          };


        img.src =
          url;

      }
    );

  }


  function parseModelA(output) {

    let nsfw = 0;


    (output || [])
      .forEach(
        function(item) {

          const label =
            String(
              item.label || ""
            ).toLowerCase();


          if (label === "nsfw") {

            nsfw =
              Number(
                item.score || 0
              ) * 100;

          }

        }
      );


    return Math.round(
      clamp(nsfw)
    );

  }


  function parseModelB(output) {

    let sexy = 0;
    let porn = 0;
    let hentai = 0;


    (output || [])
      .forEach(
        function(item) {

          const label =
            String(
              item.label || ""
            ).toLowerCase();


          const score =
            Number(
              item.score || 0
            ) * 100;


          if (label === "sexy") {
            sexy = score;
          }

          else if (label === "porn") {
            porn = score;
          }

          else if (label === "hentai") {
            hentai = score;
          }

        }
      );


    const risk =
      porn * 1.0 +
      sexy * 0.65 +
      hentai * 0.85;


    return Math.round(
      clamp(risk)
    );

  }


  function parseModelC(output) {

    let sexy = 0;
    let porn = 0;
    let hentai = 0;


    (output || [])
      .forEach(
        function(item) {

          const label =
            String(
              item.className || ""
            ).toLowerCase();


          const score =
            Number(
              item.probability || 0
            ) * 100;


          if (label === "sexy") {
            sexy = score;
          }

          else if (label === "porn") {
            porn = score;
          }

          else if (label === "hentai") {
            hentai = score;
          }

        }
      );


    const risk =
      porn * 1.0 +
      sexy * 0.55 +
      hentai * 0.8;


    return Math.round(
      clamp(risk)
    );

  }


  function median(values) {

    const arr =
      [...values]
        .sort(
          (a,b) => a - b
        );


    return arr[1];

  }


  function combinedScore(a,b,c) {

    const values =
      [a,b,c];


    const avg =
      (
        a +
        b +
        c
      ) / 3;


    const mid =
      median(values);


    let score =
      mid * 0.65 +
      avg * 0.35;


    const highCount =
      values.filter(
        v => v >= 70
      ).length;


    if (highCount >= 2) {

      score =
        Math.max(
          score,
          72
        );

    }


    const lowCount =
      values.filter(
        v => v <= 20
      ).length;


    if (lowCount >= 2) {

      score =
        Math.min(
          score,
          32
        );

    }


    return Math.round(
      clamp(score)
    );

  }


  function renderModelRow(
    name,
    score
  ) {

    return `
      <div class="sr-model-row">

        <div class="sr-model-name">
          ${name}
        </div>

        <div class="sr-bar-bg">

          <div
            class="sr-bar-fill"
            style="width:${score}%"
          ></div>

        </div>

        <div class="sr-model-score">
          ${score}%
        </div>

      </div>
    `;

  }


  function renderResult(
    fileItem,
    index,
    a,
    b,
    c
  ) {

    const total =
      combinedScore(
        a,
        b,
        c
      );


    const info =
      getLevel(total);


    const box =
      document.createElement(
        "div"
      );


    box.className =
      "sr-result";


    box.innerHTML = `

      <div class="sr-result-top">

        <img decoding="async"
          class="sr-result-img"
          src="${fileItem.url}"
          alt=""
        >


        <div>

          <div class="sr-result-head">

            <div class="sr-name">
              ${index + 1}번 이미지
            </div>


            <div
              class="sr-score ${info.cls}"
            >
              ${total}점 ·
              ${info.text}
            </div>

          </div>


          <div class="sr-models">

            <div class="sr-model-title">
              AI 모델별 검사 결과
            </div>


            ${renderModelRow(
              "모델 A",
              a
            )}


            ${renderModelRow(
              "모델 B",
              b
            )}


            ${renderModelRow(
              "모델 C",
              c
            )}

          </div>

        </div>

      </div>


      <div
        class="sr-summary ${info.summaryCls}"
      >
        ${info.message}
      </div>

    `;


    results.appendChild(box);

  }


  async function analyze() {

    if (
      state.running ||
      !state.files.length
    ) {
      return;
    }


    state.running =
      true;


    startBtn.disabled =
      true;

    resetBtn.disabled =
      true;


    results.innerHTML =
      "";


    clearError();


    try {

      await loadModels();


      for (
        let i = 0;
        i < state.files.length;
        i++
      ) {

        showStatus(
          (i + 1) +
          " / " +
          state.files.length +
          "장 3중 AI 분석 중..."
        );


        const file =
          state.files[i].file;


        const rawImage =
          await getRawImage(file);


        const outputA =
          await state.modelA(
            rawImage,
            {
              topk:2
            }
          );


        const scoreA =
          parseModelA(
            outputA
          );


        const outputB =
          await state.modelB(
            rawImage,
            {
              topk:5
            }
          );


        const scoreB =
          parseModelB(
            outputB
          );


        const canvas =
          await getCanvas(file);


        const outputC =
          await state.modelC
            .classify(canvas);


        const scoreC =
          parseModelC(
            outputC
          );


        renderResult(
          state.files[i],
          i,
          scoreA,
          scoreB,
          scoreC
        );


        canvas.width = 1;
        canvas.height = 1;

      }


      showStatus(
        "모든 이미지의 3중 AI 분석이 완료되었습니다."
      );

    }


    catch(error) {

      console.error(error);


      showError(
        "검사 중 문제가 발생했습니다. " +
        (
          error.message ||
          String(error)
        )
      );


      showStatus(
        "검사가 중단되었습니다."
      );

    }


    finally {

      state.running =
        false;


      startBtn.disabled =
        !state.files.length;


      resetBtn.disabled =
        false;

    }

  }


  startBtn.addEventListener(
    "click",
    analyze
  );


  resetBtn.addEventListener(
    "click",
    function() {

      if (state.running) {
        return;
      }


      state.files.forEach(
        function(item) {

          URL.revokeObjectURL(
            item.url
          );

        }
      );


      state.files = [];


      preview.innerHTML =
        "";


      results.innerHTML =
        "";


      count.style.display =
        "none";


      status.style.display =
        "none";


      clearError();


      startBtn.disabled =
        true;

    }
  );

})();
</script>

</div>



<h2 class="wp-block-heading">SNS 게시 전, 이미지 위험요소를 미리 확인하세요</h2>



<p class="wp-block-paragraph">SNSNOTE의 <strong>SNS 게시물 위험도 검사기</strong>는 Instagram, Threads, X 등에 이미지를 게시하기 전 민감 콘텐츠로 판단될 가능성을 사전에 확인할 수 있도록 제공하는 이미지 분석 도구입니다.<br>SNSNOTE는 Instagram과 X 등에서 <strong>총 팔로워 약 40만 규모의 SNS 계정을 운영하며 축적한 실제 게시 경험과 콘텐츠 사례</strong>를 바탕으로 서비스 기준을 지속적으로 점검하고 있습니다.<br>단순한 이미지 분석 결과만 제공하는 것이 아니라, 실제 계정 운영 과정에서 확인된 콘텐츠 유형과 주요 SNS 플랫폼의 정책 변화를 함께 참고하여 위험도 안내 기준을 지속적으로 개선합니다.<br><br>공식 정책은 아래 자료를 기준으로 지속적으로 확인하고 있습니다.<br>SNSNOTE는 Instagram과 X의 공식 정책 문서를 정기적으로 검토하며, 정책 변경이나 새로운 제한 기준이 확인될 경우 위험도 안내 기준과 서비스 설명을 지속적으로 점검·개선하고 있습니다.<br><strong>Instagram 커뮤니티 가이드라인</strong><br><a href="https://www.facebook.com/help/477434105621119" target="_blank" rel="noreferrer noopener">https://www.facebook.com/help/477434105621119</a><br><strong>X 콘텐츠 정책</strong><br><a href="https://help.x.com/ko/rules-and-policies" target="_blank" rel="noreferrer noopener">https://help.x.com/ko/rules-and-policies</a><br>SNS 플랫폼의 정책은 수시로 변경될 수 있기 때문에, SNSNOTE는 위 공식 정책과 실제 운영 사례를 함께 참고하여 검사 기준을 계속 업데이트하고 있습니다.</p>



<h2 class="wp-block-heading">SNS 게시물 위험도 검사기란?</h2>



<p class="wp-block-paragraph">SNS 게시물 위험도 검사기는 이미지에 포함된 민감 콘텐츠 가능성을 AI로 분석하여 <strong>0~100점의 위험도 점수</strong>로 표시합니다.<br>JPG, PNG, WebP 이미지를 지원하며 한 번에 <strong>최대 20장</strong>까지 검사할 수 있습니다.<br>Instagram 피드, 카드뉴스, 매거진 콘텐츠, Threads 및 X 게시용 이미지처럼 여러 장을 한 번에 게시하는 콘텐츠를 사전에 점검할 때 활용할 수 있습니다.<br>검사 결과는 각 이미지별로 구분하여 표시되므로 여러 장 중 위험도가 상대적으로 높은 이미지를 빠르게 확인할 수 있습니다.</p>



<h2 class="wp-block-heading">주요 기능</h2>



<ul class="wp-block-list">
<li>최대 20장 이미지 일괄 검사</li>



<li>JPG·PNG·WebP 지원</li>



<li>이미지별 민감 콘텐츠 위험도 표시</li>



<li>0~100점 위험도 점수 제공</li>



<li>낮음·주의·높음·매우 높음 단계별 표시</li>



<li>브라우저에서 직접 AI 분석</li>



<li>SNSNOTE 서버로 이미지 업로드 없음</li>



<li>별도 회원가입 없이 이용 가능</li>
</ul>



<h2 class="wp-block-heading">사용 방법</h2>



<h3 class="wp-block-heading">1. 검사할 이미지를 선택합니다</h3>



<p class="wp-block-paragraph">검사 영역을 눌러 SNS에 게시할 이미지를 선택합니다.<br>한 번에 최대 20장까지 추가할 수 있으며, 잘못 선택한 이미지는 오른쪽 상단의 삭제 버튼을 눌러 제외할 수 있습니다.</p>



<h3 class="wp-block-heading">2. 이미지 위험도 검사를 시작합니다</h3>



<p class="wp-block-paragraph"><strong>이미지 위험도 검사 시작</strong> 버튼을 누르면 선택한 이미지가 순서대로 분석됩니다.<br>첫 검사에서는 브라우저가 AI 분석에 필요한 데이터를 준비하기 때문에 이후 검사보다 조금 더 시간이 걸릴 수 있습니다.</p>



<h3 class="wp-block-heading">3. 이미지별 위험도를 확인합니다</h3>



<p class="wp-block-paragraph">분석이 완료되면 각 이미지에 다음과 같이 위험도가 표시됩니다.<br><strong>낮음</strong><br>강한 민감 콘텐츠 신호가 적게 감지된 이미지입니다.<br><strong>주의</strong><br>일부 민감 콘텐츠 가능성이 감지된 이미지입니다. 게시 전 직접 확인을 권장합니다.<br><strong>높음</strong><br>민감하거나 선정적인 콘텐츠로 분류될 가능성이 높게 감지된 이미지입니다.<br><strong>매우 높음</strong><br>강한 민감 콘텐츠 신호가 감지된 이미지입니다. 게시 전 이미지 재검토를 권장합니다.</p>



<h2 class="wp-block-heading">검사 기준은 계속 업데이트됩니다</h2>



<p class="wp-block-paragraph">Instagram, Threads, X를 비롯한 주요 SNS 플랫폼은 콘텐츠 정책과 추천 기준을 지속적으로 변경하고 있습니다.<br>SNSNOTE는 <strong>실제 SNS 계정 운영 과정에서 축적된 사례와 주요 플랫폼의 정책 변화를 참고하여 위험도 기준을 지속적으로 점검하고 개선하고 있습니다.</strong><br>정책 변화 또는 새로운 콘텐츠 유형이 확인되는 경우 서비스 안내 기준에도 순차적으로 반영합니다.</p>



<h2 class="wp-block-heading">이미지가 서버에 저장되나요?</h2>



<p class="wp-block-paragraph">아닙니다.<br>선택한 이미지는 SNSNOTE 서버에 업로드하여 분석하지 않습니다.<br>AI 분석에 필요한 프로그램과 모델은 브라우저에서 실행되며 실제 이미지 처리는 사용자의 기기에서 진행됩니다.<br>따라서 검사하기 위해 선택한 이미지가 SNSNOTE 서버에 별도로 저장되는 구조가 아닙니다.</p>



<h2 class="wp-block-heading">AI 분석 결과가 낮으면 반드시 안전한가요?</h2>



<p class="wp-block-paragraph">아닙니다.<br>AI 이미지 분석에는 기술적인 한계가 있습니다.<br>특히 다음과 같은 경우 민감 요소를 정확하게 인식하지 못할 수 있습니다.</p>



<ul class="wp-block-list">
<li>화면에서 매우 작게 표현된 노출 부위</li>



<li>신체 일부가 다른 물체나 문자로 가려진 이미지</li>



<li>스티커 또는 모자이크가 적용된 이미지</li>



<li>복잡한 배경과 인물이 겹쳐 있는 이미지</li>



<li>특수한 촬영 각도나 크롭 이미지</li>



<li>AI 학습 데이터에서 충분히 다뤄지지 않은 형태의 이미지</li>
</ul>



<p class="wp-block-paragraph">따라서 <strong>작은 노출 부위나 가려진 민감 요소는 위험도 점수에 충분히 반영되지 않을 수 있습니다.</strong><br>0점 또는 낮음이 표시되더라도 게시 전에는 실제 이미지를 직접 확인하는 것을 권장합니다.</p>



<h2 class="wp-block-heading">Instagram과 X의 기준은 동일한가요?</h2>



<p class="wp-block-paragraph">동일하지 않습니다.<br>각 SNS 플랫폼은 민감 콘텐츠에 대해 서로 다른 정책을 적용합니다.<br>예를 들어 특정 이미지가 한 플랫폼에서는 제한될 수 있지만 다른 플랫폼에서는 민감 콘텐츠 표시 등의 조건을 충족하면 허용될 수도 있습니다.<br>따라서 본 검사기의 점수는 여러 SNS에 공통적으로 적용되는 <strong>사전 위험 신호를 확인하기 위한 참고용 지표</strong>로 활용하는 것이 적절합니다.<br>최종 게시 여부는 반드시 이용하려는 플랫폼의 최신 정책을 함께 확인해야 합니다.</p>



<h2 class="wp-block-heading">카드뉴스도 검사할 수 있나요?</h2>



<p class="wp-block-paragraph">가능합니다.<br>한 번에 최대 20장까지 선택할 수 있기 때문에 여러 장으로 구성된 Instagram 카드뉴스나 매거진형 콘텐츠를 게시하기 전에 일괄 점검할 수 있습니다.<br>여러 이미지 중 일부만 높은 위험도로 표시되는 경우 해당 이미지를 우선적으로 재검토하는 방식으로 활용할 수 있습니다.</p>



<h2 class="wp-block-heading">검사 결과가 실제 계정 제재를 의미하나요?</h2>



<p class="wp-block-paragraph">아닙니다.<br>SNSNOTE의 위험도 점수는 Instagram, Threads, X 등 플랫폼 내부에서 사용하는 공식 심사 점수가 아닙니다.<br>실제 게시물의 삭제, 노출 제한, 추천 제외, 민감 콘텐츠 표시 또는 계정 제재 여부는 각 플랫폼이 자체 정책과 시스템에 따라 결정합니다.<br>콘텐츠의 맥락, 게시 위치, 계정 상태, 반복 게시 여부 등 여러 요소가 함께 영향을 줄 수 있습니다.</p>



<h2 class="wp-block-heading">이용 전 참고사항</h2>



<p class="wp-block-paragraph">본 서비스는 SNS 게시 전 콘텐츠를 한 번 더 확인할 수 있도록 제공되는 <strong>사전 점검 도구</strong>입니다.<br>AI 분석 결과만으로 게시 가능 여부를 확정하지 마시고, 점수가 낮더라도 중요한 게시물은 직접 확인하는 것을 권장합니다.<br>SNSNOTE는 서비스 품질 향상을 위해 주요 SNS의 정책 변화와 실제 운영 사례를 지속적으로 검토하고 위험도 안내 기준을 개선하고 있습니다.</p>



<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/sns-post-risk-checker/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/09/sns-post-risk-checker.webp" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1617</post-id>	</item>
		<item>
		<title>PDF JPG 변환 무료 사이트, 이미지 PDF 변환과 PNG·WebP 저장</title>
		<link>https://snsnote.com/pdf-jpg-converter/</link>
					<comments>https://snsnote.com/pdf-jpg-converter/#respond</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Wed, 09 Sep 2026 10:00:22 +0000</pubDate>
				<category><![CDATA[유용한 도구]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1607</guid>

					<description><![CDATA[PDF JPG 변환 무료 도구 사용 방법 PDF 문서를 이미지로 바꿔야 하는데 프로그램을 설치하거나 회원가입을 해야 한다면 번거로울 수 있습니다. 특히 PDF의 특정 페이지만 JPG로...]]></description>
										<content:encoded><![CDATA[<div id="sns-pdf-image-tool">
<style>
#sns-pdf-image-tool {
  --spic-blue:#2563eb;
  --spic-blue2:#4f46e5;
  --spic-text:#172033;
  --spic-muted:#64748b;
  --spic-line:#dfe6f1;
  --spic-soft:#f8fafc;
  --spic-green:#15803d;
  --spic-red:#b91c1c;
  width:100%;
  max-width:1120px;
  margin:0 auto;
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto Sans KR",sans-serif;
  color:var(--spic-text);
}
#sns-pdf-image-tool *{box-sizing:border-box}
#sns-pdf-image-tool [hidden]{display:none!important}
#sns-pdf-image-tool button,#sns-pdf-image-tool input,#sns-pdf-image-tool select{font:inherit}

#sns-pdf-image-tool .spic-shell{
  width:100%;
  background:#fff;
  border:1px solid var(--spic-line);
  border-radius:18px;
  overflow:hidden;
  box-shadow:0 8px 28px rgba(15,23,42,.055);
}
#sns-pdf-image-tool .spic-tabs{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:12px;
  padding:14px;
  background:#f8fafc;
  border-bottom:1px solid var(--spic-line);
}
#sns-pdf-image-tool .spic-tab{
  min-height:72px;
  display:flex;
  align-items:center;
  justify-content:center;
  gap:11px;
  padding:12px 16px;
  border:1px solid #d7e0ec;
  border-radius:14px;
  background:#fff;
  color:#334155;
  text-align:left;
  cursor:pointer;
  box-shadow:0 2px 7px rgba(15,23,42,.045);
  transition:transform .16s ease,box-shadow .16s ease,border-color .16s ease,background .16s ease,color .16s ease;
}
#sns-pdf-image-tool .spic-tab:hover:not(.active){
  transform:translateY(-1px);
  border-color:#93c5fd;
  background:#eff6ff;
  color:#1d4ed8;
  box-shadow:0 5px 14px rgba(37,99,235,.09);
}
#sns-pdf-image-tool .spic-tab:focus-visible{
  outline:3px solid rgba(59,130,246,.28);
  outline-offset:2px;
}
#sns-pdf-image-tool .spic-tab.active{
  border-color:transparent;
  background:linear-gradient(105deg,var(--spic-blue),var(--spic-blue2));
  color:#fff;
  box-shadow:0 8px 20px rgba(37,99,235,.22);
}
#sns-pdf-image-tool .spic-tab-icon{
  flex:0 0 38px;
  width:38px;
  height:38px;
  display:flex;
  align-items:center;
  justify-content:center;
  border-radius:11px;
  background:#eef2ff;
  color:#4f46e5;
}
#sns-pdf-image-tool .spic-tab-icon svg{
  width:21px;
  height:21px;
}
#sns-pdf-image-tool .spic-tab.active .spic-tab-icon{
  background:rgba(255,255,255,.18);
  color:#fff;
}
#sns-pdf-image-tool .spic-tab-copy{
  display:flex;
  flex-direction:column;
  min-width:0;
}
#sns-pdf-image-tool .spic-tab-copy strong{
  font-size:16px;
  font-weight:850;
  line-height:1.25;
  color:inherit;
}
#sns-pdf-image-tool .spic-tab-copy small{
  display:block;
  margin-top:3px;
  font-size:11px;
  font-weight:650;
  line-height:1.3;
  color:#64748b;
}
#sns-pdf-image-tool .spic-tab.active .spic-tab-copy small{
  color:rgba(255,255,255,.86);
}
#sns-pdf-image-tool .spic-content{padding:22px}
#sns-pdf-image-tool .spic-grid{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:18px;
  align-items:start;
}
#sns-pdf-image-tool .spic-card{
  min-width:0;
  border:1px solid var(--spic-line);
  border-radius:15px;
  background:#fff;
  padding:17px;
}
#sns-pdf-image-tool .spic-card-title{
  display:flex;
  align-items:center;
  gap:8px;
  margin:0 0 14px;
  font-size:16px;
  font-weight:800;
}
#sns-pdf-image-tool .spic-num{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:24px;height:24px;
  border-radius:999px;
  background:#eff6ff;
  color:var(--spic-blue);
  font-size:12px;
  font-weight:900;
}
#sns-pdf-image-tool .spic-drop{
  width:100%;
  min-height:174px;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:6px;
  padding:20px;
  border:2px dashed #cbd5e1;
  border-radius:14px;
  background:#f8fbff;
  text-align:center;
  cursor:pointer;
  transition:.18s ease;
}
#sns-pdf-image-tool .spic-drop:hover,
#sns-pdf-image-tool .spic-drop.dragover{
  border-color:var(--spic-blue);
  background:#eff6ff;
}
#sns-pdf-image-tool .spic-drop-icon{font-size:34px;line-height:1}
#sns-pdf-image-tool .spic-drop strong{font-size:16px}
#sns-pdf-image-tool .spic-drop span{font-size:13px;color:var(--spic-muted);line-height:1.55}
#sns-pdf-image-tool .spic-file-info{
  margin-top:11px;
  padding:10px 11px;
  border-radius:10px;
  background:#f8fafc;
  color:#475569;
  font-size:13px;
  line-height:1.55;
  overflow-wrap:anywhere;
}
#sns-pdf-image-tool .spic-fields{display:grid;gap:13px}
#sns-pdf-image-tool .spic-field label{
  display:block;
  margin-bottom:6px;
  font-size:13px;
  font-weight:750;
  color:#334155;
}
#sns-pdf-image-tool .spic-field input[type="text"],
#sns-pdf-image-tool .spic-field input[type="password"],
#sns-pdf-image-tool .spic-field select{
  width:100%;
  min-height:44px;
  padding:9px 11px;
  border:1px solid #cbd5e1;
  border-radius:10px;
  background:#fff;
  color:#111827;
  outline:none;
}
#sns-pdf-image-tool .spic-field input:focus,
#sns-pdf-image-tool .spic-field select:focus{border-color:#60a5fa;box-shadow:0 0 0 3px rgba(59,130,246,.11)}
#sns-pdf-image-tool .spic-help{margin-top:5px;color:var(--spic-muted);font-size:12px;line-height:1.5}
#sns-pdf-image-tool .spic-two{display:grid;grid-template-columns:1fr 1fr;gap:11px}
#sns-pdf-image-tool .spic-btn-row{display:grid;grid-template-columns:1fr auto;gap:9px;margin-top:15px}
#sns-pdf-image-tool .spic-btn{
  min-height:46px;
  padding:11px 15px;
  border:0;
  border-radius:11px;
  font-weight:800;
  font-size:14px;
  cursor:pointer;
  transition:.15s ease;
}
#sns-pdf-image-tool .spic-btn:disabled{opacity:.46;cursor:not-allowed}
#sns-pdf-image-tool .spic-primary{background:linear-gradient(100deg,var(--spic-blue),var(--spic-blue2));color:#fff}
#sns-pdf-image-tool .spic-primary:hover:not(:disabled){filter:brightness(.97)}
#sns-pdf-image-tool .spic-secondary{background:#edf2f7;color:#334155}
#sns-pdf-image-tool .spic-danger{background:#fee2e2;color:#991b1b}
#sns-pdf-image-tool .spic-status{
  margin-top:14px;
  padding:11px 12px;
  border:1px solid #bfdbfe;
  border-radius:11px;
  background:#eff6ff;
  color:#1d4ed8;
  font-size:13px;
  line-height:1.6;
  overflow-wrap:anywhere;
}
#sns-pdf-image-tool .spic-status.success{background:#ecfdf5;border-color:#a7f3d0;color:#047857}
#sns-pdf-image-tool .spic-status.error{background:#fef2f2;border-color:#fecaca;color:var(--spic-red)}
#sns-pdf-image-tool .spic-status.warn{background:#fffbeb;border-color:#fde68a;color:#92400e}
#sns-pdf-image-tool .spic-progress{width:100%;height:10px;margin-top:11px;accent-color:var(--spic-blue)}
#sns-pdf-image-tool .spic-note{
  margin-top:15px;
  padding:12px 13px;
  border-radius:11px;
  background:#f8fafc;
  color:#64748b;
  font-size:12px;
  line-height:1.7;
}
#sns-pdf-image-tool .spic-summary{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:9px;
  margin-top:13px;
}
#sns-pdf-image-tool .spic-summary-item{
  padding:10px 8px;
  border:1px solid #e2e8f0;
  border-radius:10px;
  background:#fff;
  text-align:center;
}
#sns-pdf-image-tool .spic-summary-item strong{display:block;font-size:15px;color:#0f172a}
#sns-pdf-image-tool .spic-summary-item span{display:block;margin-top:2px;font-size:11px;color:#64748b}

#sns-pdf-image-tool .spic-image-list{display:grid;gap:9px;margin-top:12px}
#sns-pdf-image-tool .spic-image-item{
  display:grid;
  grid-template-columns:50px minmax(0,1fr) auto;
  gap:10px;
  align-items:center;
  padding:8px;
  border:1px solid #e2e8f0;
  border-radius:11px;
  background:#fff;
}
#sns-pdf-image-tool .spic-thumb{
  width:50px;height:50px;
  object-fit:cover;
  border-radius:8px;
  background:#f1f5f9;
}
#sns-pdf-image-tool .spic-image-meta{min-width:0}
#sns-pdf-image-tool .spic-image-meta strong{display:block;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#sns-pdf-image-tool .spic-image-meta span{display:block;margin-top:3px;font-size:11px;color:#64748b}
#sns-pdf-image-tool .spic-item-actions{display:flex;gap:4px}
#sns-pdf-image-tool .spic-mini{
  width:30px;height:30px;
  padding:0;
  border:1px solid #dbe3ee;
  border-radius:8px;
  background:#f8fafc;
  color:#334155;
  cursor:pointer;
  font-size:13px;
  font-weight:800;
}
#sns-pdf-image-tool .spic-empty{
  padding:34px 15px;
  border:1px dashed #d7dee9;
  border-radius:12px;
  color:#94a3b8;
  text-align:center;
  font-size:13px;
  line-height:1.6;
}
#sns-pdf-image-tool .spic-inline{display:flex;gap:8px;align-items:center}
#sns-pdf-image-tool .spic-inline input{flex:1;min-width:0}
#sns-pdf-image-tool .spic-small-btn{
  flex:none;
  min-height:44px;
  padding:8px 11px;
  border:1px solid #dbe3ee;
  border-radius:10px;
  background:#f8fafc;
  color:#334155;
  font-size:12px;
  font-weight:800;
  cursor:pointer;
}
#sns-pdf-image-tool .spic-library-badge{
  display:inline-block;
  margin-left:5px;
  padding:2px 6px;
  border-radius:999px;
  background:#ecfdf5;
  color:#047857;
  font-size:10px;
  font-weight:800;
  vertical-align:1px;
}

@media(max-width:820px){
  #sns-pdf-image-tool .spic-grid{grid-template-columns:1fr}
}
@media(max-width:560px){
  #sns-pdf-image-tool .spic-content{padding:14px}
  #sns-pdf-image-tool .spic-card{padding:14px}
  #sns-pdf-image-tool .spic-two{grid-template-columns:1fr}
  #sns-pdf-image-tool .spic-btn-row{grid-template-columns:1fr}
  #sns-pdf-image-tool .spic-summary{grid-template-columns:1fr 1fr 1fr}
  #sns-pdf-image-tool .spic-tabs{grid-template-columns:1fr;gap:8px;padding:10px}
  #sns-pdf-image-tool .spic-tab{min-height:62px;padding:10px 13px;justify-content:flex-start}
  #sns-pdf-image-tool .spic-tab-icon{flex-basis:34px;width:34px;height:34px}
  #sns-pdf-image-tool .spic-tab-copy strong{font-size:15px}
  #sns-pdf-image-tool .spic-tab-copy small{font-size:11px}
  #sns-pdf-image-tool .spic-image-item{grid-template-columns:46px minmax(0,1fr);}
  #sns-pdf-image-tool .spic-thumb{width:46px;height:46px}
  #sns-pdf-image-tool .spic-item-actions{grid-column:1/-1;justify-content:flex-end}
}
</style>

<div class="spic-shell">
  <div class="spic-tabs" role="tablist" aria-label="PDF 이미지 변환 방향">
    <button type="button" class="spic-tab active" id="spic-tab-pdf" role="tab" aria-selected="true">
      <span class="spic-tab-icon" aria-hidden="true">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <path d="M6 2h8l4 4v16H6z"></path>
          <path d="M14 2v5h5"></path>
          <path d="M8.5 14h7"></path>
          <path d="M8.5 18h5"></path>
        </svg>
      </span>
      <span class="spic-tab-copy">
        <strong>PDF → 이미지 변환</strong>
        <small>JPG · PNG · WebP로 저장</small>
      </span>
    </button>

    <button type="button" class="spic-tab" id="spic-tab-image" role="tab" aria-selected="false">
      <span class="spic-tab-icon" aria-hidden="true">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <rect x="3" y="4" width="18" height="16" rx="2"></rect>
          <circle cx="8.5" cy="9" r="1.5"></circle>
          <path d="m3 17 5-5 4 4 2-2 7 6"></path>
        </svg>
      </span>
      <span class="spic-tab-copy">
        <strong>이미지 → PDF 변환</strong>
        <small>JPG · PNG · WebP를 PDF로 저장</small>
      </span>
    </button>
  </div>

  <!-- PDF -> IMAGE -->
  <section class="spic-content" id="spic-panel-pdf" role="tabpanel" aria-labelledby="spic-tab-pdf">
    <div class="spic-grid">
      <div class="spic-card">
        <h3 class="spic-card-title"><span class="spic-num">1</span> PDF 파일 선택</h3>
        <input id="spic-pdf-file" type="file" accept="application/pdf,.pdf" hidden>
        <label class="spic-drop" id="spic-pdf-drop" for="spic-pdf-file">
          <div class="spic-drop-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
          <strong>PDF를 여기 놓으세요</strong>
          <span>또는 클릭해서 파일 선택 · 최대 100MB<br>파일은 SNSNOTE 서버로 업로드하지 않습니다.</span>
        </label>
        <div class="spic-file-info" id="spic-pdf-info">선택된 PDF 없음</div>

        <div class="spic-field" style="margin-top:13px;">
          <label for="spic-pdf-password">PDF 비밀번호 <span style="font-weight:500;color:#94a3b8;">(암호가 있을 때만)</span></label>
          <div class="spic-inline">
            <input id="spic-pdf-password" type="password" autocomplete="off" placeholder="비밀번호 입력">
            <button type="button" class="spic-small-btn" id="spic-pdf-reload" disabled>다시 불러오기</button>
          </div>
        </div>

        <div class="spic-summary" id="spic-pdf-summary" hidden>
          <div class="spic-summary-item"><strong id="spic-page-count">-</strong><span>전체 페이지</span></div>
          <div class="spic-summary-item"><strong id="spic-pdf-size">-</strong><span>파일 용량</span></div>
          <div class="spic-summary-item"><strong id="spic-selected-count">-</strong><span>변환 페이지</span></div>
        </div>
      </div>

      <div class="spic-card">
        <h3 class="spic-card-title"><span class="spic-num">2</span> 변환 설정</h3>
        <div class="spic-fields">
          <div class="spic-field">
            <label for="spic-pages">변환할 페이지</label>
            <div class="spic-inline">
              <input id="spic-pages" type="text" value="전체" placeholder="예: 1-3, 5, 8-10" disabled>
              <button type="button" class="spic-small-btn" id="spic-pages-all" disabled>전체</button>
            </div>
            <div class="spic-help">전체 또는 <b>1-3, 5, 8-10</b>처럼 필요한 페이지만 지정할 수 있습니다.</div>
          </div>

          <div class="spic-two">
            <div class="spic-field">
              <label for="spic-format">이미지 형식</label>
              <select id="spic-format" disabled>
                <option value="jpeg">JPG</option>
                <option value="png">PNG</option>
                <option value="webp">WebP</option>
              </select>
            </div>
            <div class="spic-field">
              <label for="spic-scale">출력 해상도</label>
              <select id="spic-scale" disabled>
                <option value="1.5">빠른 변환 (1.5x)</option>
                <option value="2" selected>표준 화질 (2x)</option>
                <option value="3">고화질 (3x)</option>
                <option value="4">최고화질 (4x)</option>
              </select>
            </div>
          </div>

          <div class="spic-field" id="spic-quality-wrap">
            <label for="spic-quality">JPG / WebP 품질</label>
            <select id="spic-quality" disabled>
              <option value="0.8">80% · 용량 절약</option>
              <option value="0.9" selected>90% · 권장</option>
              <option value="0.95">95% · 고화질</option>
              <option value="1">100% · 최대 품질</option>
            </select>
          </div>

          <div class="spic-field">
            <label for="spic-output-name">저장 파일명</label>
            <input id="spic-output-name" type="text" value="pdf-images" disabled>
            <div class="spic-help">여러 페이지는 ZIP으로 저장되며 각 이미지는 page-001 형식으로 이름이 붙습니다.</div>
          </div>
        </div>

        <div class="spic-btn-row">
          <button type="button" class="spic-btn spic-primary" id="spic-convert-pdf" disabled>PDF 이미지 변환 시작</button>
          <button type="button" class="spic-btn spic-danger" id="spic-cancel-pdf" hidden>중지</button>
        </div>
        <progress class="spic-progress" id="spic-pdf-progress" max="100" hidden></progress>
        <div class="spic-status" id="spic-pdf-status" role="status" aria-live="polite">PDF를 선택하면 페이지 수를 확인합니다.</div>
      </div>
    </div>

    <div class="spic-note">
      <b>브라우저 처리 방식:</b> PDF 원본과 변환된 이미지는 SNSNOTE 서버로 전송하지 않습니다. PDF.js 라이브러리는 외부 CDN에서 불러오며 실제 렌더링은 사용자의 브라우저에서 진행됩니다.<br>
      페이지 수 자체는 제한하지 않지만, 수백 페이지를 한꺼번에 고화질 ZIP으로 만들면 사용 중인 기기의 메모리를 많이 사용할 수 있습니다. 이 경우 필요한 페이지만 나누어 변환하는 것을 권장합니다.
    </div>
  </section>

  <!-- IMAGE -> PDF -->
  <section class="spic-content" id="spic-panel-image" role="tabpanel" aria-labelledby="spic-tab-image" hidden>
    <div class="spic-grid">
      <div class="spic-card">
        <h3 class="spic-card-title"><span class="spic-num">1</span> 이미지 선택</h3>
        <input id="spic-image-files" type="file" accept="image/jpeg,image/png,image/webp,.jpg,.jpeg,.png,.webp" multiple hidden>
        <label class="spic-drop" id="spic-image-drop" for="spic-image-files">
          <div class="spic-drop-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f5bc.png" alt="🖼" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
          <strong>JPG · PNG · WebP 사진을 넣으세요</strong>
          <span>여러 장을 한 번에 선택 가능 · 총 100MB까지<br>위아래 버튼으로 PDF 페이지 순서를 바꿀 수 있습니다.</span>
        </label>
        <div class="spic-file-info" id="spic-image-info">선택된 이미지 없음</div>
        <div class="spic-image-list" id="spic-image-list">
          <div class="spic-empty">이미지를 선택하면 여기에서 순서를 확인할 수 있습니다.</div>
        </div>
      </div>

      <div class="spic-card">
        <h3 class="spic-card-title"><span class="spic-num">2</span> PDF 만들기 설정</h3>
        <div class="spic-fields">
          <div class="spic-two">
            <div class="spic-field">
              <label for="spic-page-size">PDF 페이지 크기</label>
              <select id="spic-page-size" disabled>
                <option value="auto" selected>A4 자동 방향</option>
                <option value="a4p">A4 세로</option>
                <option value="a4l">A4 가로</option>
                <option value="image">이미지 비율에 맞춤</option>
              </select>
            </div>
            <div class="spic-field">
              <label for="spic-margin">페이지 여백</label>
              <select id="spic-margin" disabled>
                <option value="0">없음</option>
                <option value="5">5mm</option>
                <option value="10" selected>10mm</option>
                <option value="15">15mm</option>
              </select>
            </div>
          </div>

          <div class="spic-two">
            <div class="spic-field">
              <label for="spic-image-max">이미지 해상도</label>
              <select id="spic-image-max" disabled>
                <option value="2000">빠른 처리 · 최대 2000px</option>
                <option value="3000" selected>권장 · 최대 3000px</option>
                <option value="4000">고화질 · 최대 4000px</option>
              </select>
            </div>
            <div class="spic-field">
              <label for="spic-pdf-quality">이미지 품질</label>
              <select id="spic-pdf-quality" disabled>
                <option value="0.8">80% · 파일 작게</option>
                <option value="0.9" selected>90% · 권장</option>
                <option value="0.95">95% · 고화질</option>
              </select>
            </div>
          </div>

          <div class="spic-field">
            <label for="spic-pdf-name">PDF 파일명</label>
            <input id="spic-pdf-name" type="text" value="images-to-pdf" disabled>
          </div>
        </div>

        <div class="spic-btn-row">
          <button type="button" class="spic-btn spic-primary" id="spic-create-pdf" disabled>이미지로 PDF 만들기</button>
          <button type="button" class="spic-btn spic-danger" id="spic-cancel-image" hidden>중지</button>
        </div>
        <progress class="spic-progress" id="spic-image-progress" max="100" hidden></progress>
        <div class="spic-status" id="spic-image-status" role="status" aria-live="polite">이미지를 선택하면 PDF로 묶을 수 있습니다.</div>
      </div>
    </div>

    <div class="spic-note">
      <b>이미지 → PDF도 브라우저에서 처리됩니다.</b> 사진을 SNSNOTE 서버로 업로드하지 않습니다. WebP와 투명 PNG는 PDF 호환성을 위해 브라우저에서 흰색 배경의 JPG 데이터로 변환한 뒤 PDF에 넣습니다.
    </div>
  </section>
</div>

<script type="module">
/*
  SNSNOTE PDF <-> Image Converter
  - PDF.js 5.7.284 (Apache-2.0): https://github.com/mozilla/pdf.js
  - pdf-lib 1.17.1 (MIT): https://github.com/Hopding/pdf-lib
  - JSZip 3.10.1 (MIT or GPL-3.0): https://stuk.github.io/jszip/
  External libraries are version-pinned and loaded from CDN with fallback.
*/
(async function(){
  const root=document.getElementById('sns-pdf-image-tool');
  if(!root || root.dataset.ready==='true') return;
  root.dataset.ready='true';
  const $=id=>root.querySelector('#'+id);

  const MAX_FILE_BYTES=100*1024*1024;
  const MAX_CANVAS_PIXELS=16_000_000;
  const MAX_CANVAS_DIMENSION=8192;
  const A4_P=[595.28,841.89];
  const A4_L=[841.89,595.28];
  const MM_TO_PT=72/25.4;

  const el={
    tabPdf:$('spic-tab-pdf'),tabImage:$('spic-tab-image'),panelPdf:$('spic-panel-pdf'),panelImage:$('spic-panel-image'),
    pdfFile:$('spic-pdf-file'),pdfDrop:$('spic-pdf-drop'),pdfInfo:$('spic-pdf-info'),pdfPassword:$('spic-pdf-password'),pdfReload:$('spic-pdf-reload'),
    pdfSummary:$('spic-pdf-summary'),pageCount:$('spic-page-count'),pdfSize:$('spic-pdf-size'),selectedCount:$('spic-selected-count'),pages:$('spic-pages'),pagesAll:$('spic-pages-all'),format:$('spic-format'),scale:$('spic-scale'),quality:$('spic-quality'),qualityWrap:$('spic-quality-wrap'),outputName:$('spic-output-name'),convertPdf:$('spic-convert-pdf'),cancelPdf:$('spic-cancel-pdf'),pdfProgress:$('spic-pdf-progress'),pdfStatus:$('spic-pdf-status'),
    imageFiles:$('spic-image-files'),imageDrop:$('spic-image-drop'),imageInfo:$('spic-image-info'),imageList:$('spic-image-list'),pageSize:$('spic-page-size'),margin:$('spic-margin'),imageMax:$('spic-image-max'),pdfQuality:$('spic-pdf-quality'),pdfName:$('spic-pdf-name'),createPdf:$('spic-create-pdf'),cancelImage:$('spic-cancel-image'),imageProgress:$('spic-image-progress'),imageStatus:$('spic-image-status')
  };

  let pdfFile=null,pdfDoc=null,pdfBytes=null,pdfjsLib=null,pdfJsBase='';
  let pdfBusy=false,pdfCancel=false;
  let imageItems=[];
  let imageBusy=false,imageCancel=false;

  function setTab(which){
    const pdf=which==='pdf';
    el.tabPdf.classList.toggle('active',pdf);el.tabPdf.setAttribute('aria-selected',String(pdf));
    el.tabImage.classList.toggle('active',!pdf);el.tabImage.setAttribute('aria-selected',String(!pdf));
    el.panelPdf.hidden=!pdf;el.panelImage.hidden=pdf;
  }
  el.tabPdf.addEventListener('click',()=>setTab('pdf'));
  el.tabImage.addEventListener('click',()=>setTab('image'));

  function sizeText(bytes){
    if(bytes<1024) return bytes+' B';
    if(bytes<1024*1024) return (bytes/1024).toFixed(bytes<10240?1:0)+' KB';
    return (bytes/1024/1024).toFixed(1)+' MB';
  }
  function safeBaseName(name,fallback){
    const s=(name||fallback||'file').trim().replace(/\.[^.]+$/,'').replace(/[\\/:*?"<>|]+/g,'_').replace(/\s+/g,'-').replace(/-+/g,'-').replace(/^[-_.]+|[-_.]+$/g,'');
    return s||fallback||'file';
  }
  function downloadBlob(blob,filename){
    const url=URL.createObjectURL(blob);
    const a=document.createElement('a');
    a.href=url;a.download=filename;document.body.appendChild(a);a.click();a.remove();
    setTimeout(()=>URL.revokeObjectURL(url),30000);
  }
  function status(target,message,type=''){
    target.textContent=message;
    target.className='spic-status'+(type?' '+type:'');
  }
  function loadScript(urls,test){
    return new Promise((resolve,reject)=>{
      if(test()) return resolve();
      let i=0;
      const next=()=>{
        if(i>=urls.length) return reject(new Error('외부 라이브러리를 불러오지 못했습니다. 네트워크 또는 콘텐츠 차단 설정을 확인해주세요.'));
        const src=urls[i++];
        const s=document.createElement('script');
        s.src=src;s.async=true;
        s.onload=()=>test()?resolve():next();
        s.onerror=()=>{s.remove();next();};
        document.head.appendChild(s);
      };
      next();
    });
  }
  async function loadPdfJs(){
    if(pdfjsLib) return pdfjsLib;
    const candidates=[
      'https://cdn.jsdelivr.net/npm/pdfjs-dist@5.7.284',
      'https://unpkg.com/pdfjs-dist@5.7.284'
    ];
    let lastError=null;
    for(const base of candidates){
      try{
        const mod=await import(base+'/build/pdf.mjs');
        mod.GlobalWorkerOptions.workerSrc=base+'/build/pdf.worker.min.mjs';
        pdfjsLib=mod;pdfJsBase=base;return mod;
      }catch(e){lastError=e;}
    }
    throw new Error('PDF 변환 라이브러리를 불러오지 못했습니다. '+(lastError?.message||''));
  }
  async function loadJsZip(){
    await loadScript([
      'https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js',
      'https://unpkg.com/jszip@3.10.1/dist/jszip.min.js'
    ],()=>typeof window.JSZip==='function');
    return window.JSZip;
  }
  async function loadPdfLib(){
    await loadScript([
      'https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/dist/pdf-lib.min.js',
      'https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js'
    ],()=>!!window.PDFLib?.PDFDocument);
    return window.PDFLib;
  }

  function setPdfControls(enabled){
    [el.pages,el.pagesAll,el.format,el.scale,el.quality,el.outputName].forEach(x=>x.disabled=!enabled || pdfBusy);
    el.convertPdf.disabled=!enabled || pdfBusy;
    el.pdfReload.disabled=!pdfFile || pdfBusy;
  }
  function setPdfBusy(busy){
    pdfBusy=busy;
    setPdfControls(!!pdfDoc);
    el.pdfFile.disabled=busy;el.pdfPassword.disabled=busy;
    el.cancelPdf.hidden=!busy;el.convertPdf.hidden=busy;
  }

  function parsePages(text,total){
    const raw=(text||'').trim().toLowerCase();
    if(!raw || raw==='전체' || raw==='all') return Array.from({length:total},(_,i)=>i+1);
    const out=[];const seen=new Set();
    for(const part0 of raw.split(',')){
      const part=part0.trim();if(!part) continue;
      let start,end;
      if(/^\d+$/.test(part)){start=end=Number(part);} else {
        const m=part.match(/^(\d+)\s*-\s*(\d+)$/);
        if(!m) throw new Error('페이지 입력 형식을 확인해주세요. 예: 1-3, 5, 8-10');
        start=Number(m[1]);end=Number(m[2]);
        if(start>end) throw new Error('페이지 범위는 작은 번호부터 입력해주세요: '+part);
      }
      if(start<1 || end>total) throw new Error(`페이지는 1부터 ${total} 사이로 입력해주세요.`);
      for(let p=start;p<=end;p++) if(!seen.has(p)){seen.add(p);out.push(p);}
    }
    if(!out.length) throw new Error('변환할 페이지를 입력해주세요.');
    return out;
  }
  function updateSelectedCount(){
    if(!pdfDoc){el.selectedCount.textContent='-';return;}
    try{el.selectedCount.textContent=parsePages(el.pages.value,pdfDoc.numPages).length.toLocaleString();}
    catch{el.selectedCount.textContent='!';}
  }
  el.pages.addEventListener('input',updateSelectedCount);
  el.pagesAll.addEventListener('click',()=>{el.pages.value='전체';updateSelectedCount();});
  el.format.addEventListener('change',()=>{el.qualityWrap.hidden=el.format.value==='png';});

  async function openPdf(file){
    if(!file) return;
    if(file.size>MAX_FILE_BYTES){status(el.pdfStatus,'PDF는 최대 100MB까지 선택할 수 있습니다.','error');return;}
    if(!(file.type==='application/pdf' || /\.pdf$/i.test(file.name))){status(el.pdfStatus,'PDF 파일만 선택해주세요.','error');return;}
    pdfFile=file;pdfDoc=null;pdfBytes=null;el.pdfSummary.hidden=true;setPdfControls(false);
    el.pdfInfo.textContent=`${file.name} · ${sizeText(file.size)}`;
    el.outputName.value=safeBaseName(file.name,'pdf-images')+'-images';
    await reloadPdf();
  }
  async function reloadPdf(){
    if(!pdfFile || pdfBusy) return;
    setPdfBusy(true);el.pdfProgress.hidden=false;el.pdfProgress.removeAttribute('value');
    try{
      status(el.pdfStatus,'PDF 정보를 읽는 중입니다...');
      const lib=await loadPdfJs();
      pdfBytes=new Uint8Array(await pdfFile.arrayBuffer());
      const opts={
        data:pdfBytes,
        password:el.pdfPassword.value||undefined,
        cMapUrl:pdfJsBase+'/cmaps/',cMapPacked:true,
        standardFontDataUrl:pdfJsBase+'/standard_fonts/',
        wasmUrl:pdfJsBase+'/wasm/',
        isEvalSupported:false
      };
      const task=lib.getDocument(opts);
      pdfDoc=await task.promise;
      el.pdfSummary.hidden=false;el.pageCount.textContent=pdfDoc.numPages.toLocaleString();el.pdfSize.textContent=sizeText(pdfFile.size);el.pages.value='전체';updateSelectedCount();
      status(el.pdfStatus,`PDF를 불러왔습니다. 총 ${pdfDoc.numPages.toLocaleString()}페이지입니다.`,'success');
    }catch(e){
      pdfDoc=null;
      const isPassword=e?.name==='PasswordException' || /password/i.test(e?.message||'');
      status(el.pdfStatus,isPassword?'암호가 걸린 PDF입니다. 비밀번호를 입력한 뒤 “다시 불러오기”를 눌러주세요.':'PDF를 열지 못했습니다. 손상되었거나 지원되지 않는 PDF일 수 있습니다.','error');
      console.error(e);
    }finally{el.pdfProgress.hidden=true;setPdfBusy(false);}
  }
  el.pdfReload.addEventListener('click',reloadPdf);
  el.pdfPassword.addEventListener('keydown',e=>{if(e.key==='Enter') reloadPdf();});

  function makeDrop(drop,input,onFiles){
    ['dragenter','dragover'].forEach(n=>drop.addEventListener(n,e=>{e.preventDefault();drop.classList.add('dragover');}));
    ['dragleave','drop'].forEach(n=>drop.addEventListener(n,e=>{e.preventDefault();drop.classList.remove('dragover');}));
    drop.addEventListener('drop',e=>onFiles(e.dataTransfer?.files));
    input.addEventListener('change',()=>onFiles(input.files));
  }
  makeDrop(el.pdfDrop,el.pdfFile,files=>openPdf(files?.[0]));

  function getSafeViewport(page,requestedScale){
    const base=page.getViewport({scale:requestedScale});
    let factor=1;
    if(Math.max(base.width,base.height)>MAX_CANVAS_DIMENSION) factor=Math.min(factor,MAX_CANVAS_DIMENSION/Math.max(base.width,base.height));
    if(base.width*base.height>MAX_CANVAS_PIXELS) factor=Math.min(factor,Math.sqrt(MAX_CANVAS_PIXELS/(base.width*base.height)));
    return {viewport:page.getViewport({scale:requestedScale*factor}),reduced:factor<.999};
  }
  function canvasBlob(canvas,type,quality){
    return new Promise((resolve,reject)=>canvas.toBlob(b=>b?resolve(b):reject(new Error('이미지 파일 생성에 실패했습니다.')),type,quality));
  }

  el.convertPdf.addEventListener('click',async()=>{
    if(!pdfDoc || pdfBusy) return;
    let pages;
    try{pages=parsePages(el.pages.value,pdfDoc.numPages);}catch(e){status(el.pdfStatus,e.message,'error');return;}
    const fmt=el.format.value;
    const mime=fmt==='jpeg'?'image/jpeg':fmt==='png'?'image/png':'image/webp';
    const ext=fmt==='jpeg'?'jpg':fmt;
    const q=Number(el.quality.value);
    const requestedScale=Number(el.scale.value);
    const baseName=safeBaseName(el.outputName.value,'pdf-images');
    pdfCancel=false;setPdfBusy(true);el.pdfProgress.hidden=false;el.pdfProgress.value=0;
    try{
      if(pages.length>100) status(el.pdfStatus,`${pages.length.toLocaleString()}페이지를 변환합니다. 페이지가 많아 기기 메모리를 많이 사용할 수 있습니다.`,'warn');
      const pad=Math.max(3,String(pdfDoc.numPages).length);
      let zip=null;
      if(pages.length>1){const JSZip=await loadJsZip();zip=new JSZip();}
      let reducedAny=false;
      for(let i=0;i<pages.length;i++){
        if(pdfCancel) throw new Error('사용자가 변환을 중지했습니다.');
        const n=pages[i];
        status(el.pdfStatus,`페이지 ${n.toLocaleString()} 변환 중 · ${i+1}/${pages.length}`);
        const page=await pdfDoc.getPage(n);
        const {viewport,reduced}=getSafeViewport(page,requestedScale);reducedAny=reducedAny||reduced;
        const canvas=document.createElement('canvas');
        canvas.width=Math.max(1,Math.ceil(viewport.width));canvas.height=Math.max(1,Math.ceil(viewport.height));
        const ctx=canvas.getContext('2d',{alpha:false});
        ctx.save();ctx.fillStyle='#fff';ctx.fillRect(0,0,canvas.width,canvas.height);ctx.restore();
        await page.render({canvas,viewport,background:'rgb(255,255,255)'}).promise;
        const blob=await canvasBlob(canvas,mime,fmt==='png'?undefined:q);
        const filename=`${baseName}-page-${String(n).padStart(pad,'0')}.${ext}`;
        if(zip) zip.file(filename,blob); else downloadBlob(blob,filename);
        canvas.width=1;canvas.height=1;page.cleanup?.();
        el.pdfProgress.value=Math.round(((i+1)/pages.length)*90);
        await new Promise(r=>setTimeout(r,0));
      }
      if(zip){
        status(el.pdfStatus,'ZIP 파일을 만드는 중입니다...');
        const zipBlob=await zip.generateAsync({type:'blob',compression:'DEFLATE',compressionOptions:{level:5}},meta=>{el.pdfProgress.value=90+Math.round(meta.percent*.1);});
        if(pdfCancel) throw new Error('사용자가 변환을 중지했습니다.');
        downloadBlob(zipBlob,baseName+'.zip');
      }
      el.pdfProgress.value=100;
      status(el.pdfStatus,`변환 완료! ${pages.length.toLocaleString()}페이지를 ${ext.toUpperCase()} 형식으로 만들었습니다.${reducedAny?' 일부 매우 큰 페이지는 브라우저 안정성을 위해 자동으로 해상도를 조정했습니다.':''}`,'success');
    }catch(e){
      status(el.pdfStatus,e.message==='사용자가 변환을 중지했습니다.'?e.message:'변환 중 오류가 발생했습니다: '+(e.message||e),'error');
      console.error(e);
    }finally{setPdfBusy(false);el.pdfProgress.hidden=true;pdfCancel=false;}
  });
  el.cancelPdf.addEventListener('click',()=>{pdfCancel=true;el.cancelPdf.disabled=true;status(el.pdfStatus,'현재 페이지 처리가 끝나는 즉시 중지합니다.','warn');setTimeout(()=>el.cancelPdf.disabled=false,1000);});

  // IMAGE -> PDF
  function totalImageBytes(){return imageItems.reduce((s,x)=>s+x.file.size,0);}
  function revokeImageItems(){imageItems.forEach(x=>URL.revokeObjectURL(x.url));}
  function setImageControls(enabled){
    [el.pageSize,el.margin,el.imageMax,el.pdfQuality,el.pdfName].forEach(x=>x.disabled=!enabled || imageBusy);
    el.createPdf.disabled=!enabled || imageBusy;
  }
  function setImageBusy(busy){
    imageBusy=busy;el.imageFiles.disabled=busy;setImageControls(imageItems.length>0);
    el.cancelImage.hidden=!busy;el.createPdf.hidden=busy;
    root.querySelectorAll('.spic-mini').forEach(b=>b.disabled=busy);
  }
  function renderImageList(){
    el.imageList.innerHTML='';
    if(!imageItems.length){
      el.imageList.innerHTML='<div class="spic-empty">이미지를 선택하면 여기에서 순서를 확인할 수 있습니다.</div>';
      el.imageInfo.textContent='선택된 이미지 없음';setImageControls(false);return;
    }
    el.imageInfo.textContent=`${imageItems.length.toLocaleString()}장 · 총 ${sizeText(totalImageBytes())}`;
    imageItems.forEach((item,index)=>{
      const row=document.createElement('div');row.className='spic-image-item';
      const img=document.createElement('img');img.className='spic-thumb';img.src=item.url;img.alt='';
      const meta=document.createElement('div');meta.className='spic-image-meta';
      const strong=document.createElement('strong');strong.textContent=item.file.name;
      const span=document.createElement('span');span.textContent=sizeText(item.file.size)+' · '+(index+1)+'번째 페이지';
      meta.append(strong,span);
      const acts=document.createElement('div');acts.className='spic-item-actions';
      const up=document.createElement('button');up.type='button';up.className='spic-mini';up.textContent='▲';up.title='위로';up.disabled=index===0||imageBusy;
      const down=document.createElement('button');down.type='button';down.className='spic-mini';down.textContent='▼';down.title='아래로';down.disabled=index===imageItems.length-1||imageBusy;
      const del=document.createElement('button');del.type='button';del.className='spic-mini';del.textContent='×';del.title='삭제';del.disabled=imageBusy;
      up.addEventListener('click',()=>{[imageItems[index-1],imageItems[index]]=[imageItems[index],imageItems[index-1]];renderImageList();});
      down.addEventListener('click',()=>{[imageItems[index+1],imageItems[index]]=[imageItems[index],imageItems[index+1]];renderImageList();});
      del.addEventListener('click',()=>{URL.revokeObjectURL(item.url);imageItems.splice(index,1);renderImageList();});
      acts.append(up,down,del);row.append(img,meta,acts);el.imageList.appendChild(row);
    });
    setImageControls(true);
  }
  function addImages(files){
    if(!files?.length || imageBusy) return;
    const accepted=[];
    for(const f of Array.from(files)){
      if(['image/jpeg','image/png','image/webp'].includes(f.type) || /\.(jpe?g|png|webp)$/i.test(f.name)) accepted.push(f);
    }
    if(!accepted.length){status(el.imageStatus,'JPG, PNG, WebP 이미지만 선택할 수 있습니다.','error');return;}
    const newTotal=totalImageBytes()+accepted.reduce((s,f)=>s+f.size,0);
    if(newTotal>MAX_FILE_BYTES){status(el.imageStatus,'선택한 이미지의 총 용량은 100MB까지 가능합니다.','error');return;}
    accepted.forEach(file=>imageItems.push({file,url:URL.createObjectURL(file)}));
    if(imageItems.length===accepted.length) el.pdfName.value=safeBaseName(accepted[0].name,'images')+'-pdf';
    renderImageList();status(el.imageStatus,`${accepted.length.toLocaleString()}장을 추가했습니다. 순서를 확인한 뒤 PDF 만들기를 눌러주세요.`,'success');
    el.imageFiles.value='';
  }
  makeDrop(el.imageDrop,el.imageFiles,addImages);

  async function imageFileToJpeg(file,maxDim,quality){
    let bmp;
    if('createImageBitmap' in window){bmp=await createImageBitmap(file);}else{
      const url=URL.createObjectURL(file);try{bmp=await new Promise((res,rej)=>{const im=new Image();im.onload=()=>res(im);im.onerror=rej;im.src=url;});}finally{URL.revokeObjectURL(url);}
    }
    const sw=bmp.width||bmp.naturalWidth,sh=bmp.height||bmp.naturalHeight;
    if(!sw||!sh) throw new Error(file.name+' 이미지 크기를 읽을 수 없습니다.');
    const ratio=Math.min(1,maxDim/Math.max(sw,sh));
    const w=Math.max(1,Math.round(sw*ratio)),h=Math.max(1,Math.round(sh*ratio));
    const c=document.createElement('canvas');c.width=w;c.height=h;
    const cx=c.getContext('2d',{alpha:false});cx.fillStyle='#fff';cx.fillRect(0,0,w,h);cx.drawImage(bmp,0,0,w,h);
    if(typeof bmp.close==='function') bmp.close();
    const blob=await canvasBlob(c,'image/jpeg',quality);c.width=1;c.height=1;
    return {bytes:new Uint8Array(await blob.arrayBuffer()),width:w,height:h};
  }
  function pageDimensions(mode,imgW,imgH){
    if(mode==='a4p') return A4_P.slice();
    if(mode==='a4l') return A4_L.slice();
    if(mode==='auto') return imgW>=imgH?A4_L.slice():A4_P.slice();
    const longest=841.89;
    if(imgW>=imgH) return [longest,Math.max(72,longest*imgH/imgW)];
    return [Math.max(72,longest*imgW/imgH),longest];
  }
  el.createPdf.addEventListener('click',async()=>{
    if(!imageItems.length || imageBusy) return;
    imageCancel=false;setImageBusy(true);el.imageProgress.hidden=false;el.imageProgress.value=0;
    try{
      status(el.imageStatus,'PDF 생성 라이브러리를 불러오는 중입니다...');
      const {PDFDocument}=await loadPdfLib();
      const doc=await PDFDocument.create();
      const maxDim=Number(el.imageMax.value),quality=Number(el.pdfQuality.value),marginMm=Number(el.margin.value);
      for(let i=0;i<imageItems.length;i++){
        if(imageCancel) throw new Error('사용자가 PDF 생성을 중지했습니다.');
        const item=imageItems[i];status(el.imageStatus,`${i+1}/${imageItems.length} · ${item.file.name} 처리 중`);
        const data=await imageFileToJpeg(item.file,maxDim,quality);
        const jpg=await doc.embedJpg(data.bytes);
        const [pw,ph]=pageDimensions(el.pageSize.value,data.width,data.height);
        const page=doc.addPage([pw,ph]);
        let margin=marginMm*MM_TO_PT;
        if(el.pageSize.value==='image') margin=Math.min(margin,Math.min(pw,ph)*.15);
        const aw=Math.max(1,pw-margin*2),ah=Math.max(1,ph-margin*2);
        const scale=Math.min(aw/data.width,ah/data.height);
        const dw=data.width*scale,dh=data.height*scale;
        page.drawImage(jpg,{x:(pw-dw)/2,y:(ph-dh)/2,width:dw,height:dh});
        el.imageProgress.value=Math.round(((i+1)/imageItems.length)*88);
        await new Promise(r=>setTimeout(r,0));
      }
      status(el.imageStatus,'PDF 파일을 저장하는 중입니다...');
      const bytes=await doc.save({useObjectStreams:true,addDefaultPage:false});
      el.imageProgress.value=100;
      downloadBlob(new Blob([bytes],{type:'application/pdf'}),safeBaseName(el.pdfName.value,'images-to-pdf')+'.pdf');
      status(el.imageStatus,`완료! 이미지 ${imageItems.length.toLocaleString()}장을 하나의 PDF로 만들었습니다.`,'success');
    }catch(e){status(el.imageStatus,e.message==='사용자가 PDF 생성을 중지했습니다.'?e.message:'PDF 생성 중 오류가 발생했습니다: '+(e.message||e),'error');console.error(e);}
    finally{setImageBusy(false);el.imageProgress.hidden=true;imageCancel=false;}
  });
  el.cancelImage.addEventListener('click',()=>{imageCancel=true;el.cancelImage.disabled=true;status(el.imageStatus,'현재 이미지 처리가 끝나는 즉시 중지합니다.','warn');setTimeout(()=>el.cancelImage.disabled=false,1000);});

  window.addEventListener('pagehide',()=>{revokeImageItems();});
  setPdfControls(false);setImageControls(false);el.qualityWrap.hidden=false;
})();
</script>
</div>



<h2 class="wp-block-heading">PDF JPG 변환 무료 도구 사용 방법</h2>



<p class="wp-block-paragraph">PDF 문서를 이미지로 바꿔야 하는데 프로그램을 설치하거나 회원가입을 해야 한다면 번거로울 수 있습니다. 특히 PDF의 특정 페이지만 JPG로 저장하거나, 여러 장의 사진을 하나의 PDF로 묶어 제출해야 할 때는 간단한 브라우저 도구가 편리합니다.</p>



<p class="wp-block-paragraph">SNSNOTE의 PDF JPG 변환기는 PDF를 JPG·PNG·WebP 이미지로 변환하는 기능과, JPG·PNG·WebP 이미지를 PDF로 만드는 기능을 함께 제공합니다. 파일을 서버에 업로드하지 않고 브라우저에서 처리하도록 구성되어 있으며, 필요한 페이지 선택과 출력 화질 조절도 지원합니다.</p>



<h3 class="wp-block-heading">1. PDF 파일을 선택하세요</h3>



<p class="wp-block-paragraph">도구 상단에서 <strong>PDF → 이미지 변환</strong> 버튼을 선택한 뒤, PDF 파일을 업로드합니다. 파일을 클릭해서 선택하거나 업로드 영역으로 끌어다 놓을 수 있습니다.</p>



<p class="wp-block-paragraph">PDF 파일은 최대 100MB까지 선택할 수 있습니다. 파일을 불러오면 전체 페이지 수를 확인할 수 있으며, 문서에 암호가 설정되어 있다면 비밀번호를 입력해 다시 불러올 수 있습니다. 단, 모든 종류의 암호화 PDF가 지원되는 것은 아닙니다.</p>



<h3 class="wp-block-heading">2. 전체 또는 필요한 페이지만 지정하세요</h3>



<p class="wp-block-paragraph">변환할 페이지 입력란에 <code>전체</code>를 선택하면 문서의 모든 페이지를 이미지로 변환합니다. 특정 페이지만 필요하다면 아래와 같이 입력할 수 있습니다.</p>



<ul class="wp-block-list">
<li><code>1-3</code> : 1페이지부터 3페이지까지</li>



<li><code>5</code> : 5페이지만</li>



<li><code>1-3, 5, 8-10</code> : 여러 페이지 범위를 함께 선택</li>
</ul>



<p class="wp-block-paragraph">예를 들어 100페이지짜리 PDF에서 2페이지와 10~12페이지만 필요하다면, 전체 문서를 변환할 필요 없이 원하는 페이지만 지정하면 됩니다. PDF 내부 페이지 수에 별도의 고정 제한은 없지만, 페이지가 많을수록 처리 시간과 기기 메모리 사용량이 증가할 수 있습니다.</p>



<h3 class="wp-block-heading">3. JPG·PNG·WebP 형식과 화질 선택</h3>



<p class="wp-block-paragraph">이미지 형식에서 JPG, PNG, WebP 중 원하는 형식을 선택합니다. 출력 해상도는 1.5배부터 4배까지 설정할 수 있으며, JPG와 WebP는 이미지 품질도 조절할 수 있습니다.</p>



<p class="wp-block-paragraph">일반적인 블로그 업로드나 문서 공유 목적이라면 기본 설정부터 사용해보세요. 작은 글씨나 도표를 선명하게 저장해야 한다면 해상도를 높여볼 수 있지만, 해상도가 높을수록 파일 크기와 메모리 사용량이 늘어납니다.</p>



<p class="wp-block-paragraph">설정이 끝나면 <strong>PDF 이미지 변환 시작</strong> 버튼을 누르면 됩니다. 여러 페이지를 선택한 경우 결과 이미지는 ZIP 파일로 한 번에 다운로드할 수 있으며, 각 이미지에는 페이지 번호가 포함된 파일명이 붙습니다.</p>



<h2 class="wp-block-heading">JPG·PNG 이미지를 PDF로 변환하는 방법</h2>



<p class="wp-block-paragraph">반대로 여러 장의 사진을 하나의 PDF로 만들어야 한다면 상단의 <strong>이미지 → PDF 변환</strong> 버튼을 선택하세요. JPG, PNG, WebP 이미지를 여러 장 추가한 뒤 원하는 순서로 정리할 수 있습니다.</p>



<h3 class="wp-block-heading">사진 여러 장을 하나의 PDF로 합치기</h3>



<p class="wp-block-paragraph">이미지를 선택하면 목록에서 순서를 확인하고 위·아래 버튼으로 변경할 수 있습니다. 필요 없는 이미지는 제거하고 다른 사진을 추가할 수도 있습니다.</p>



<p class="wp-block-paragraph">예를 들어 신분증 사본, 신청서 사진, 영수증 이미지 등을 순서대로 정리해 하나의 PDF로 만들 수 있습니다. 단, 개인정보가 포함된 서류는 공유하기 전에 제출 대상과 필요한 정보만 포함되어 있는지 확인하는 것이 좋습니다.</p>



<h3 class="wp-block-heading">A4 용지 방향과 여백 설정</h3>



<p class="wp-block-paragraph">PDF를 만들 때는 A4 자동 방향, A4 세로, A4 가로 또는 이미지 비율에 맞춘 페이지 설정을 선택할 수 있습니다. 여백도 조절할 수 있어 문서를 출력하거나 제출할 목적에 맞게 구성할 수 있습니다.</p>



<p class="wp-block-paragraph">사진을 PDF로 변환할 때는 원본 이미지의 비율과 용지 비율이 다를 수 있습니다. 이미지가 잘리거나 지나치게 작게 보이지 않도록 미리보기와 용지 설정을 확인한 뒤 저장하는 것이 좋습니다.</p>



<h2 class="wp-block-heading">PDF를 JPG로 변환하면 언제 유용할까요?</h2>



<h3 class="wp-block-heading">블로그와 SNS에 문서 내용을 이미지로 올릴 때</h3>



<p class="wp-block-paragraph">블로그나 SNS에 PDF 문서의 일부 내용을 이미지로 소개하고 싶을 때 사용할 수 있습니다. 필요한 페이지만 JPG 또는 PNG로 변환하면 문서의 특정 부분을 이미지 형태로 게시할 수 있습니다.</p>



<p class="wp-block-paragraph">다만 PDF를 이미지로 바꾸면 텍스트가 픽셀로 렌더링되기 때문에, 변환된 JPG에서 원래 PDF의 텍스트를 그대로 선택하거나 수정할 수 있는 것은 아닙니다. 글자를 편집해야 한다면 원본 문서나 별도의 텍스트 추출 기능을 사용하는 것이 좋습니다.</p>



<h3 class="wp-block-heading">업무 서류를 이미지로 공유할 때</h3>



<p class="wp-block-paragraph">PDF로 받은 안내문, 도표, 신청서 등을 메신저나 문서에 이미지로 삽입해야 할 때 편리합니다. 여러 페이지 중 필요한 부분만 선택할 수 있어 불필요한 페이지를 함께 변환하지 않아도 됩니다.</p>



<h3 class="wp-block-heading">스캔 사진을 PDF 한 개로 정리할 때</h3>



<p class="wp-block-paragraph">휴대폰으로 촬영한 서류 사진이 여러 장이라면 이미지 → PDF 변환 기능으로 하나의 파일로 묶을 수 있습니다. 페이지 순서를 정리하고 A4 설정을 적용하면 제출용 또는 보관용 PDF를 만들기 편리합니다.</p>



<h2 class="wp-block-heading">PDF JPG 변환 시 화질이 달라지는 이유</h2>



<p class="wp-block-paragraph">PDF는 문서 안에 텍스트, 이미지, 도형 등을 포함할 수 있는 형식입니다. PDF를 JPG로 변환할 때는 각 페이지를 이미지로 렌더링하므로 출력 해상도에 따라 글자와 도형의 선명도가 달라집니다.</p>



<p class="wp-block-paragraph">출력 해상도를 높이면 작은 글씨나 선이 더 선명하게 보일 수 있지만, 원본 PDF에 들어 있는 저화질 사진의 실제 세부 정보까지 새롭게 만들어지는 것은 아닙니다. 또한 JPG는 손실 압축 형식이기 때문에 품질 설정에 따라 이미지의 세부 표현이 달라질 수 있습니다.</p>



<h3 class="wp-block-heading">JPG, PNG, WebP 중 어떤 형식을 선택할까요?</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>형식</th><th>특징</th><th>추천 용도</th></tr></thead><tbody><tr><td>JPG</td><td>사진에 적합한 손실 압축, 투명도 미지원</td><td>일반 문서 이미지, 블로그 업로드</td></tr><tr><td>PNG</td><td>무손실 압축, 투명도 지원</td><td>글자·도표·선이 많은 이미지</td></tr><tr><td>WebP</td><td>손실·무손실 압축 및 투명도 지원</td><td>웹사이트용 이미지, 용량 고려</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">문서의 글자와 도표를 선명하게 보존하고 싶다면 PNG를 선택해 비교해볼 수 있습니다. 일반적인 사진 위주의 PDF라면 JPG나 WebP가 파일 용량 측면에서 유리할 수 있습니다. 다만 실제 용량은 원본 내용과 출력 설정에 따라 달라집니다.</p>



<h2 class="wp-block-heading">PDF 파일을 서버에 업로드하나요?</h2>



<p class="wp-block-paragraph">SNSNOTE PDF 이미지 변환기는 사용자가 선택한 PDF와 이미지 파일을 브라우저에서 처리하도록 구성되어 있습니다. 변환에 필요한 JavaScript 라이브러리는 외부 CDN에서 불러오지만, 문서 변환 자체를 위해 SNSNOTE 서버에 파일을 업로드하는 방식은 아닙니다.</p>



<p class="wp-block-paragraph">따라서 파일 변환 연산은 방문자의 컴퓨터나 휴대폰에서 실행됩니다. 다만 웹페이지 접속에 따른 일반적인 서버 부하와 라이브러리 다운로드는 발생하며, 대용량 PDF 또는 고해상도 변환은 사용자의 기기 메모리와 성능에 영향을 받을 수 있습니다.</p>



<p class="wp-block-paragraph">중요한 계약서나 개인정보가 포함된 문서를 다룬다면 작업 후 다운로드한 파일의 내용을 확인하고, 공용 컴퓨터에서는 다운로드 폴더에 파일이 남지 않도록 주의하세요.</p>



<h2 class="wp-block-heading">자주 묻는 질문 (FAQ)</h2>



<h3 class="wp-block-heading">Q. PDF JPG 변환은 무료인가요?</h3>



<p class="wp-block-paragraph">네. SNSNOTE에서 제공하는 PDF 이미지 변환 도구는 별도의 회원가입이나 유료 API 키 입력 없이 사용할 수 있습니다. 파일 변환은 브라우저에서 처리되며, 처음 실행할 때 필요한 라이브러리 다운로드가 발생할 수 있습니다.</p>



<h3 class="wp-block-heading">Q. PDF 파일은 몇 MB까지 업로드할 수 있나요?</h3>



<p class="wp-block-paragraph">현재 도구에서는 PDF 파일을 최대 100MB까지 선택할 수 있습니다. 이는 브라우저의 기술적 최대 용량이 아니라 도구에서 정한 운영 제한입니다. PDF 내부 페이지 수에는 별도의 고정 제한을 두지 않았지만, 페이지가 많으면 필요한 페이지만 선택해 변환하는 것을 권장합니다.</p>



<h3 class="wp-block-heading">Q. PDF 여러 페이지를 JPG로 한 번에 저장할 수 있나요?</h3>



<p class="wp-block-paragraph">가능합니다. 전체 페이지 또는 원하는 페이지 범위를 지정한 뒤 변환하면 됩니다. 여러 페이지의 결과는 ZIP 파일로 다운로드할 수 있어 각각의 이미지를 한꺼번에 보관하기 편리합니다.</p>



<h3 class="wp-block-heading">Q. PDF에서 특정 페이지만 JPG로 저장할 수 있나요?</h3>



<p class="wp-block-paragraph">네. 페이지 입력란에 <code>2</code>, <code>3-5</code>, <code>1-3, 7</code>처럼 원하는 페이지 번호와 범위를 입력하면 선택한 페이지만 변환할 수 있습니다.</p>



<h3 class="wp-block-heading">Q. JPG 여러 장을 PDF 하나로 만들 수 있나요?</h3>



<p class="wp-block-paragraph">가능합니다. 이미지 → PDF 변환 탭에서 JPG, PNG, WebP 이미지를 여러 장 추가하고 순서를 정한 뒤 하나의 PDF로 저장할 수 있습니다.</p>



<h3 class="wp-block-heading">Q. 암호가 걸린 PDF도 변환할 수 있나요?</h3>



<p class="wp-block-paragraph">도구에서 비밀번호 입력 기능을 제공합니다. 정상적인 비밀번호를 입력하면 지원되는 암호화 PDF를 불러올 수 있습니다. 다만 일부 보안 설정이나 특수한 PDF는 브라우저 라이브러리에서 처리하지 못할 수 있으며, 비밀번호를 모르는 PDF의 암호를 해제하는 기능은 아닙니다.</p>



<h3 class="wp-block-heading">Q. PDF를 JPG로 바꾸면 글자를 수정할 수 있나요?</h3>



<p class="wp-block-paragraph">아니요. PDF 페이지를 JPG로 변환하면 해당 페이지가 이미지로 저장됩니다. 이미지 안의 글자를 편집 가능한 텍스트로 바꾸는 기능과는 다릅니다. 글자 추출이 필요하다면 별도의 OCR 도구를 사용해야 합니다.</p>



<h3 class="wp-block-heading">Q. 휴대폰에서도 PDF 이미지 변환이 가능한가요?</h3>



<p class="wp-block-paragraph">브라우저에서 필요한 라이브러리를 실행할 수 있는 기기라면 모바일에서도 사용할 수 있습니다. 다만 대용량 PDF, 많은 페이지, 높은 출력 해상도는 메모리 부족이나 느린 처리 속도의 원인이 될 수 있습니다. 문제가 발생하면 필요한 페이지만 선택하거나 해상도를 낮춰보세요.</p>



<h2 class="wp-block-heading">함께 사용하면 좋은 무료 이미지 도구</h2>



<p class="wp-block-paragraph">PDF를 JPG로 변환한 뒤 파일 형식이나 크기를 추가로 변경하고 싶다면 <a href="https://snsnote.com/image-converter/">무료 이미지 변환기</a>를 이용할 수 있습니다. 사진을 공유하기 전 촬영정보나 위치정보를 확인하고 제거하고 싶다면 <a href="https://snsnote.com/photo-privacy-remover/">사진 개인정보 제거 도구</a>도 활용해보세요.</p>



<p class="wp-block-paragraph">이미지의 배경을 제거해 투명 PNG로 만들고 싶다면 <a href="https://snsnote.com/ai-background-remover/">AI 이미지 배경 제거기</a>를 사용할 수 있습니다. PDF 변환과 이미지 편집 도구를 목적에 맞게 함께 이용하면 별도의 프로그램 설치 없이 간단한 문서·이미지 작업을 처리하는 데 도움이 됩니다.</p>



<h2 class="wp-block-heading">마무리</h2>



<p class="wp-block-paragraph">PDF JPG 변환은 문서의 일부를 이미지로 공유하거나 블로그에 삽입할 때 유용하고, 이미지 PDF 변환은 여러 장의 사진을 하나의 문서로 정리할 때 편리합니다. SNSNOTE에서는 두 방향의 변환을 하나의 도구에서 제공하므로 필요한 작업에 맞춰 상단 버튼을 선택하면 됩니다.</p>



<p class="wp-block-paragraph">먼저 작은 PDF나 이미지 몇 장으로 변환 결과를 확인한 뒤, 필요한 페이지와 화질을 설정해 사용해보세요. 중요한 원본 문서는 별도로 보관하고, 다운로드한 결과가 원하는 순서와 형식으로 저장되었는지 확인하는 것을 권장합니다.</p>



<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/pdf-jpg-converter/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/09/pdf-jpg-converter.jpg" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1607</post-id>	</item>
		<item>
		<title>AI 이미지 배경 제거 무료 누끼 따기, 투명 PNG 만들기와 수동 보정</title>
		<link>https://snsnote.com/ai-background-remover/</link>
					<comments>https://snsnote.com/ai-background-remover/#respond</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Wed, 09 Sep 2026 08:54:31 +0000</pubDate>
				<category><![CDATA[유용한 도구]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1601</guid>

					<description><![CDATA[AI 이미지 배경 제거 무료 도구 사용 방법 사진에서 인물이나 사물만 남기고 배경을 없애고 싶을 때, 별도의 이미지 편집 프로그램을 설치하지 않아도 됩니다. SNSNOTE의 AI...]]></description>
										<content:encoded><![CDATA[<div id="sns-bg-remover-v3">
<style>
#sns-bg-remover-v3 {
  --br-blue:#2563eb;
  --br-purple:#7c4df4;
  --br-text:#172033;
  --br-muted:#64748b;
  --br-border:#dfe6f1;
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  width:100%!important;
  max-width:none!important;
  min-width:0;
  display:block;
  box-sizing:border-box;
  color:var(--br-text);
}
#sns-bg-remover-v3 * {box-sizing:border-box}
#sns-bg-remover-v3 [hidden] {display:none!important}
#sns-bg-remover-v3 .br-layout {
  display:grid;
  grid-template-columns:minmax(0,340px) minmax(0,1fr);
  gap:22px;
  width:100%;
  align-items:stretch;
}
#sns-bg-remover-v3 .br-panel {
  min-width:0;
  width:100%;
  border:1px solid var(--br-border);
  border-radius:18px;
  padding:20px;
  background:#fff;
}
#sns-bg-remover-v3 .br-heading {
  margin:0 0 20px;
  padding:10px 16px;
  border-left:5px solid #2563eb;
  border-radius:10px;
  background:linear-gradient(100deg,#3477ed,#7c4df4);
  color:#fff;
  font-size:20px;
  font-weight:800;
  line-height:1.4;
}
#sns-bg-remover-v3 .br-drop {
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  min-height:185px;
  width:100%;
  padding:18px;
  border:2px dashed #cbd5e1;
  border-radius:14px;
  background:#f8fbff;
  text-align:center;
  cursor:pointer;
}
#sns-bg-remover-v3 .br-drop:hover,
#sns-bg-remover-v3 .br-drop.dragover {
  border-color:#2563eb;
  background:#eff6ff;
}
#sns-bg-remover-v3 .br-drop strong {font-size:16px}
#sns-bg-remover-v3 .br-drop span {
  margin-top:7px;
  color:var(--br-muted);
  font-size:13px;
  line-height:1.6;
}
#sns-bg-remover-v3 .br-drop-icon {font-size:34px;margin-bottom:10px}
#sns-bg-remover-v3 .br-file {
  margin:12px 0 18px;
  color:var(--br-muted);
  font-size:13px;
  overflow-wrap:anywhere;
}
#sns-bg-remover-v3 .br-field {margin-bottom:14px}
#sns-bg-remover-v3 .br-field label {
  display:block;
  margin-bottom:7px;
  font-size:14px;
  font-weight:700;
}
#sns-bg-remover-v3 select,
#sns-bg-remover-v3 input[type="color"],
#sns-bg-remover-v3 input[type="number"] {
  width:100%;
  min-height:44px;
  border:1px solid #cbd5e1;
  border-radius:10px;
  padding:9px 11px;
  background:#fff;
  color:var(--br-text);
  font-size:14px;
}
#sns-bg-remover-v3 input[type="color"] {padding:5px}
#sns-bg-remover-v3 input[type="range"] {
  width:100%;
  accent-color:#2563eb;
}
#sns-bg-remover-v3 .br-row {
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:10px;
}
#sns-bg-remover-v3 .br-buttons {
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:10px;
  margin-top:18px;
}
#sns-bg-remover-v3 .br-btn {
  min-height:45px;
  border:0;
  border-radius:11px;
  padding:11px 13px;
  font-size:14px;
  font-weight:700;
  cursor:pointer;
}
#sns-bg-remover-v3 .br-btn:disabled {
  opacity:.43;
  cursor:not-allowed;
}
#sns-bg-remover-v3 .br-primary {background:#2563eb;color:#fff}
#sns-bg-remover-v3 .br-secondary {background:#edf1f7;color:#172033}
#sns-bg-remover-v3 .br-download {
  width:100%;
  margin-top:10px;
  background:#15803d;
  color:#fff;
}
#sns-bg-remover-v3 .br-status {
  margin-top:15px;
  padding:12px;
  border:1px solid #bfdbfe;
  border-radius:11px;
  background:#eff6ff;
  color:#1d4ed8;
  font-size:13px;
  line-height:1.6;
  overflow-wrap:anywhere;
}
#sns-bg-remover-v3 .br-status.error {
  background:#fef2f2;
  border-color:#fecaca;
  color:#b91c1c;
}
#sns-bg-remover-v3 .br-status.success {
  background:#ecfdf5;
  border-color:#a7f3d0;
  color:#047857;
}
#sns-bg-remover-v3 .br-progress {
  width:100%;
  height:9px;
  margin-top:12px;
  accent-color:#2563eb;
}
#sns-bg-remover-v3 .br-note {
  margin-top:14px;
  color:var(--br-muted);
  font-size:12px;
  line-height:1.7;
}
#sns-bg-remover-v3 .br-previews {
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:14px;
  width:100%;
}
#sns-bg-remover-v3 .br-preview {
  min-width:0;
  width:100%;
  border:1px solid var(--br-border);
  border-radius:14px;
  overflow:hidden;
}
#sns-bg-remover-v3 .br-preview-title {
  padding:13px 14px;
  border-bottom:1px solid var(--br-border);
  background:#f8fafc;
  font-size:14px;
  font-weight:700;
}
#sns-bg-remover-v3 .br-preview-body {
  display:flex;
  align-items:center;
  justify-content:center;
  min-height:410px;
  padding:12px;
  background:#fff;
}
#sns-bg-remover-v3 .br-preview img {
  display:block;
  max-width:100%;
  max-height:520px;
  width:auto;
  height:auto;
  object-fit:contain;
}
#sns-bg-remover-v3 .br-empty {
  text-align:center;
  color:var(--br-muted);
  font-size:13px;
  line-height:1.7;
}
#sns-bg-remover-v3 .br-checker {
  display:flex;
  align-items:center;
  justify-content:center;
  width:100%;
  min-height:386px;
  background-color:#fff;
  background-image:
    linear-gradient(45deg,#e5e7eb 25%,transparent 25%),
    linear-gradient(-45deg,#e5e7eb 25%,transparent 25%),
    linear-gradient(45deg,transparent 75%,#e5e7eb 75%),
    linear-gradient(-45deg,transparent 75%,#e5e7eb 75%);
  background-size:20px 20px;
  background-position:0 0,0 10px,10px -10px,-10px 0;
}
#sns-bg-remover-v3 .br-checker canvas {
  display:block;
  max-width:100%;
  max-height:520px;
  width:auto;
  height:auto;
}
#sns-bg-remover-v3 .br-editor {
  margin-top:20px;
  border:1px solid var(--br-border);
  border-radius:14px;
  overflow:hidden;
}
#sns-bg-remover-v3 .br-editor-head {
  padding:14px 16px;
  border-bottom:1px solid var(--br-border);
  background:#f8fafc;
  font-weight:800;
  font-size:15px;
}
#sns-bg-remover-v3 .br-editor-controls {
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:14px;
  padding:16px;
}
#sns-bg-remover-v3 .br-tool-buttons {
  display:flex;
  flex-wrap:wrap;
  gap:8px;
  padding:0 16px 14px;
}
#sns-bg-remover-v3 .br-tool {
  min-height:40px;
  padding:9px 13px;
  border:1px solid #cbd5e1;
  border-radius:9px;
  background:#fff;
  color:#172033;
  font-size:13px;
  font-weight:700;
  cursor:pointer;
}
#sns-bg-remover-v3 .br-tool.active {
  background:#2563eb;
  border-color:#2563eb;
  color:#fff;
}
#sns-bg-remover-v3 .br-tool:disabled {opacity:.4;cursor:not-allowed}
#sns-bg-remover-v3 .br-stage {
  position:relative;
  width:100%;
  min-height:340px;
  max-height:620px;
  overflow:auto;
  padding:12px;
  display:flex;
  align-items:flex-start;
  justify-content:flex-start;
  background:#e8edf4;
}
#sns-bg-remover-v3 .br-stage-inner {
  position:relative;
  flex:none;
  margin:auto;
  line-height:0;
}
#sns-bg-remover-v3 .br-edit-canvas {
  display:block;
  height:auto;
  max-width:none;
  touch-action:pan-x pan-y;
  cursor:crosshair;
  background-color:#fff;
  background-image:
    linear-gradient(45deg,#e5e7eb 25%,transparent 25%),
    linear-gradient(-45deg,#e5e7eb 25%,transparent 25%),
    linear-gradient(45deg,transparent 75%,#e5e7eb 75%),
    linear-gradient(-45deg,transparent 75%,#e5e7eb 75%);
  background-size:20px 20px;
  background-position:0 0,0 10px,10px -10px,-10px 0;
}
#sns-bg-remover-v3 .br-brush-cursor {
  position:absolute;
  border:1.5px solid #2563eb;
  border-radius:50%;
  pointer-events:none;
  display:none;
  box-shadow:0 0 0 1px white;
}
#sns-bg-remover-v3 .br-editor-note {
  padding:12px 16px 16px;
  color:var(--br-muted);
  font-size:12px;
  line-height:1.7;
}
#sns-bg-remover-v3 .br-error-details {
  margin-top:9px;
  font-size:12px;
}
#sns-bg-remover-v3 .br-error-details summary {cursor:pointer}
#sns-bg-remover-v3 .br-error-details pre {
  white-space:pre-wrap;
  overflow-wrap:anywhere;
  padding:10px;
  border-radius:8px;
  background:#f1f5f9;
  font-size:11px;
}

@media(max-width:900px) {
  #sns-bg-remover-v3 .br-layout {grid-template-columns:1fr}
}
@media(max-width:600px) {
  #sns-bg-remover-v3 .br-panel {padding:15px}
  #sns-bg-remover-v3 .br-previews {grid-template-columns:1fr}
  #sns-bg-remover-v3 .br-preview-body {min-height:270px}
  #sns-bg-remover-v3 .br-checker {min-height:246px}
  #sns-bg-remover-v3 .br-editor-controls {grid-template-columns:1fr 1fr}
  #sns-bg-remover-v3 .br-heading {font-size:18px}
}
</style>

<div class="br-layout">
  <section class="br-panel">
    <h2 class="br-heading">이미지 업로드</h2>

    <input id="snsbr3-file" type="file"
      accept="image/jpeg,image/png,image/webp" hidden>

    <label class="br-drop" for="snsbr3-file" id="snsbr3-drop">
      <div class="br-drop-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f5bc.png" alt="🖼" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
      <strong>사진을 여기 놓으세요</strong>
      <span>또는 클릭해서 파일을 선택하세요<br>JPG, PNG, WebP 지원</span>
    </label>

    <div class="br-file" id="snsbr3-fileinfo">선택된 파일 없음</div>

    <div class="br-field">
      <label for="snsbr3-quality">AI 처리 품질</label>
      <select id="snsbr3-quality">
        <option value="isnet">고품질 · 큰 모델 (느릴 수 있음)</option>
        <option value="isnet_fp16">표준 · 균형 잡힌 모델</option>
        <option value="isnet_quint8">빠른 처리 · 경량 모델</option>
      </select>
    </div>

    <div class="br-field">
      <label for="snsbr3-size">처리 이미지 크기</label>
      <select id="snsbr3-size">
        <option value="2400">최대 2400px · 권장</option>
        <option value="1600">최대 1600px · 빠른 처리</option>
        <option value="4096">최대 4096px · 고해상도</option>
      </select>
    </div>

    <div class="br-field">
      <label for="snsbr3-bg">배경 옵션</label>
      <select id="snsbr3-bg">
        <option value="transparent">투명 배경</option>
        <option value="white">흰색 배경</option>
        <option value="black">검은색 배경</option>
        <option value="custom">사용자 색상</option>
      </select>
    </div>

    <div class="br-field" id="snsbr3-colorbox" hidden>
      <label for="snsbr3-color">배경 색상</label>
      <input id="snsbr3-color" type="color" value="#ffffff">
    </div>

    <div class="br-field">
      <label for="snsbr3-format">다운로드 형식</label>
      <select id="snsbr3-format">
        <option value="png">PNG (투명 배경 지원)</option>
        <option value="jpg">JPG</option>
        <option value="webp">WebP</option>
      </select>
    </div>

    <div class="br-buttons">
      <button type="button" class="br-btn br-primary"
        id="snsbr3-start" disabled>배경 제거 시작</button>
      <button type="button" class="br-btn br-secondary"
        id="snsbr3-reset">초기화</button>
    </div>

    <button type="button" class="br-btn br-download"
      id="snsbr3-download" disabled>결과 다운로드</button>

    <progress id="snsbr3-progress" class="br-progress"
      max="100" hidden></progress>

    <div id="snsbr3-status" class="br-status" role="status" aria-live="polite">
      사진을 선택한 뒤 배경 제거 시작 버튼을 눌러주세요.
    </div>

    <details id="snsbr3-errorbox" class="br-error-details" hidden>
      <summary>오류 자세히 보기</summary>
      <pre id="snsbr3-error"></pre>
    </details>

    <div class="br-note">
      ※ 서버에 업로드하지 않고 브라우저에서 처리합니다.<br>
      ※ 첫 실행 시 외부 CDN에서 AI 모델을 다운로드합니다.<br>
      ※ 고품질 모델과 고해상도는 기기에 따라 느리거나 메모리가 부족할 수 있습니다.<br>
      ※ JPG는 투명도를 지원하지 않아 흰색 배경으로 저장됩니다.
    </div>
  </section>

  <section class="br-panel">
    <h2 class="br-heading">미리보기</h2>

    <div class="br-previews">
      <div class="br-preview">
        <div class="br-preview-title">원본 이미지</div>
        <div class="br-preview-body">
          <div class="br-empty" id="snsbr3-original-empty">
            업로드한 사진이<br>여기에 표시됩니다.
          </div>
          <img id="snsbr3-original" alt="원본 이미지" hidden>
        </div>
      </div>

      <div class="br-preview">
        <div class="br-preview-title">배경 제거 결과</div>
        <div class="br-preview-body">
          <div class="br-empty" id="snsbr3-result-empty">
            배경 제거 후 결과가<br>여기에 표시됩니다.
          </div>
          <div class="br-checker" id="snsbr3-checker" hidden>
            <canvas id="snsbr3-preview"></canvas>
          </div>
        </div>
      </div>
    </div>

    <div class="br-editor" id="snsbr3-editor" hidden>
      <div class="br-editor-head">세밀한 수동 보정</div>

      <div class="br-editor-controls">
        <div class="br-field">
          <label for="snsbr3-brush">브러시 크기: <span id="snsbr3-brush-value">30</span>px</label>
          <input id="snsbr3-brush" type="range" min="1" max="200" value="30">
        </div>

        <div class="br-field">
          <label for="snsbr3-hardness">브러시 선명도: <span id="snsbr3-hardness-value">80</span>%</label>
          <input id="snsbr3-hardness" type="range" min="0" max="100" value="80">
        </div>

        <div class="br-field">
          <label for="snsbr3-softness">경계 부드러움: <span id="snsbr3-softness-value">0</span>px</label>
          <input id="snsbr3-softness" type="range" min="0" max="4" step="0.5" value="0">
        </div>

        <div class="br-field">
          <label for="snsbr3-zoom">확대</label>
          <select id="snsbr3-zoom">
            <option value="1">화면 맞춤</option>
            <option value="2">2배 확대</option>
            <option value="4">4배 확대</option>
          </select>
        </div>
      </div>

      <div class="br-tool-buttons">
        <button type="button" class="br-tool active" id="snsbr3-restore">
          + 복원
        </button>
        <button type="button" class="br-tool" id="snsbr3-erase">
          − 지우기
        </button>
        <button type="button" class="br-tool" id="snsbr3-undo" disabled>
          실행 취소
        </button>
        <button type="button" class="br-tool" id="snsbr3-redo" disabled>
          다시 실행
        </button>
        <button type="button" class="br-tool" id="snsbr3-auto-reset">
          AI 결과로 되돌리기
        </button>
      </div>

      <div class="br-stage" id="snsbr3-stage">
        <div class="br-stage-inner" id="snsbr3-stage-inner">
          <canvas class="br-edit-canvas" id="snsbr3-edit"
            tabindex="0"
            role="img"
            aria-label="수동 보정 캔버스. 클릭하거나 드래그하여 보정합니다. 방향키로 위치를 이동하고 스페이스 또는 엔터로 칠할 수 있습니다."></canvas>
          <div class="br-brush-cursor" id="snsbr3-cursor"></div>
        </div>
      </div>

      <div class="br-editor-note">
        복원은 지워진 부분을 원본 사진에서 되살리고, 지우기는 남은 배경을 제거합니다.
        클릭하거나 드래그하여 칠할 수 있습니다. 확대해서 머리카락과 옷 가장자리를 보정하세요.
        실행 취소와 다시 실행을 사용할 수 있으며, AI 결과로 되돌리면 수동 보정이 초기화됩니다.
        키보드로는 방향키로 위치 이동, 스페이스 또는 엔터로 한 번 칠하기가 가능합니다.
      </div>
    </div>
  </section>
</div>

<script type="module">
(async function () {
  const root = document.getElementById("sns-bg-remover-v3");
  if (!root || root.dataset.initialized === "true") return;
  root.dataset.initialized = "true";

  const $ = id => root.querySelector("#" + id);
  const el = {
    file:$("snsbr3-file"), drop:$("snsbr3-drop"),
    fileinfo:$("snsbr3-fileinfo"), quality:$("snsbr3-quality"),
    size:$("snsbr3-size"), bg:$("snsbr3-bg"),
    colorbox:$("snsbr3-colorbox"), color:$("snsbr3-color"),
    format:$("snsbr3-format"), start:$("snsbr3-start"),
    reset:$("snsbr3-reset"), download:$("snsbr3-download"),
    progress:$("snsbr3-progress"), status:$("snsbr3-status"),
    errorbox:$("snsbr3-errorbox"), error:$("snsbr3-error"),
    original:$("snsbr3-original"),
    originalEmpty:$("snsbr3-original-empty"),
    resultEmpty:$("snsbr3-result-empty"),
    checker:$("snsbr3-checker"), preview:$("snsbr3-preview"),
    editor:$("snsbr3-editor"), edit:$("snsbr3-edit"),
    stage:$("snsbr3-stage"), stageInner:$("snsbr3-stage-inner"),
    cursor:$("snsbr3-cursor"), brush:$("snsbr3-brush"),
    brushValue:$("snsbr3-brush-value"),
    hardness:$("snsbr3-hardness"),
    hardnessValue:$("snsbr3-hardness-value"),
    softness:$("snsbr3-softness"),
    softnessValue:$("snsbr3-softness-value"),
    zoom:$("snsbr3-zoom"), restore:$("snsbr3-restore"),
    erase:$("snsbr3-erase"), undo:$("snsbr3-undo"),
    redo:$("snsbr3-redo"), autoReset:$("snsbr3-auto-reset")
  };

  let selectedFile = null;
  let originalUrl = "";
  let busy = false;
  let removeFn = null;
  let libraryPromise = null;
  let sourceCanvas = null;
  let maskCanvas = null;
  let maskCtx = null;
  let autoMask = null;
  let history = [];
  let historyIndex = -1;
  let tool = "restore";
  let painting = false;
  let lastPoint = null;
  let strokeChanged = false;
  let keyboardPoint = {x:0,y:0};

  const previewCtx = el.preview.getContext("2d");
  const editCtx = el.edit.getContext("2d");

  function setStatus(message, type = "") {
    el.status.textContent = message;
    el.status.className = "br-status" + (type ? " " + type : "");
  }

  function clearError() {
    el.errorbox.hidden = true;
    el.error.textContent = "";
  }

  function showError(error, message) {
    console.error("SNSNOTE 배경 제거 오류:", error);
    el.error.textContent = error && error.message
      ? error.message : String(error);
    el.errorbox.hidden = false;
    setStatus(message || "오류가 발생했습니다.", "error");
  }

  function setBusy(value) {
    busy = value;
    el.file.disabled = value;
    el.start.disabled = value || !selectedFile;
    el.reset.disabled = value;
    el.download.disabled = value || !sourceCanvas;
    el.quality.disabled = value;
    el.size.disabled = value;
  }

  function clearResult() {
    sourceCanvas = null;
    maskCanvas = null;
    maskCtx = null;
    autoMask = null;
    history = [];
    historyIndex = -1;
    painting = false;
    el.editor.hidden = true;
    el.checker.hidden = true;
    el.resultEmpty.hidden = false;
    el.download.disabled = true;
    el.preview.width = 0;
    el.preview.height = 0;
    updateHistoryButtons();
  }

  function resetAll() {
    if (busy) return;
    selectedFile = null;
    el.file.value = "";
    if (originalUrl) URL.revokeObjectURL(originalUrl);
    originalUrl = "";
    el.original.removeAttribute("src");
    el.original.hidden = true;
    el.originalEmpty.hidden = false;
    el.fileinfo.textContent = "선택된 파일 없음";
    el.quality.value = "isnet";
    el.size.value = "2400";
    el.bg.value = "transparent";
    el.color.value = "#ffffff";
    el.colorbox.hidden = true;
    el.format.value = "png";
    el.progress.hidden = true;
    el.progress.removeAttribute("value");
    clearResult();
    clearError();
    setBusy(false);
    setStatus("사진을 선택한 뒤 배경 제거 시작 버튼을 눌러주세요.");
  }

  function loadImage(url) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => resolve(img);
      img.onerror = () => reject(new Error("이미지를 읽을 수 없습니다."));
      img.src = url;
    });
  }

  function toBlob(canvas, type, quality) {
    return new Promise((resolve, reject) => {
      canvas.toBlob(blob => blob
        ? resolve(blob)
        : reject(new Error("이미지 파일 생성에 실패했습니다.")),
        type, quality);
    });
  }

  function fileSize(n) {
    return n < 1048576
      ? Math.round(n / 1024) + " KB"
      : (n / 1048576).toFixed(1) + " MB";
  }

  async function selectFile(file) {
    if (!file || busy) return;

    if (!["image/jpeg","image/png","image/webp"].includes(file.type)) {
      setStatus("JPG, PNG, WebP 이미지만 선택할 수 있습니다.", "error");
      return;
    }
    if (file.size > 30 * 1024 * 1024) {
      setStatus("30MB 이하의 이미지를 선택해주세요.", "error");
      return;
    }

    clearResult();
    clearError();
    if (originalUrl) URL.revokeObjectURL(originalUrl);

    selectedFile = file;
    originalUrl = URL.createObjectURL(file);
    el.original.src = originalUrl;
    el.original.hidden = false;
    el.originalEmpty.hidden = true;
    el.fileinfo.textContent = file.name + " · " + fileSize(file.size);
    setBusy(false);
    setStatus("이미지 선택 완료. 배경 제거 시작 버튼을 눌러주세요.");
  }

  async function prepareImage(file) {
    const url = URL.createObjectURL(file);
    try {
      const img = await loadImage(url);
      const limit = Number(el.size.value);
      const ratio = Math.min(1, limit / Math.max(img.naturalWidth,img.naturalHeight));
      const w = Math.max(1, Math.round(img.naturalWidth * ratio));
      const h = Math.max(1, Math.round(img.naturalHeight * ratio));

      const canvas = document.createElement("canvas");
      canvas.width = w;
      canvas.height = h;
      const ctx = canvas.getContext("2d");
      if (!ctx) throw new Error("Canvas를 사용할 수 없습니다.");
      ctx.drawImage(img,0,0,w,h);

      const blob = await toBlob(canvas,"image/png");
      return {canvas,blob};
    } finally {
      URL.revokeObjectURL(url);
    }
  }

  async function loadLibrary() {
    if (removeFn) return removeFn;
    if (!libraryPromise) {
      libraryPromise = (async () => {
        const mod = await import(
          "https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/+esm"
        );
        const fn = mod.removeBackground ||
          mod.default?.removeBackground || mod.default;
        if (typeof fn !== "function") {
          throw new Error("AI 배경 제거 함수를 찾지 못했습니다.");
        }
        removeFn = fn;
        return fn;
      })().catch(error => {
        libraryPromise = null;
        throw error;
      });
    }
    return libraryPromise;
  }

  function getBackground() {
    if (el.bg.value === "white") return "#ffffff";
    if (el.bg.value === "black") return "#000000";
    if (el.bg.value === "custom") return el.color.value;
    return null;
  }

  // 원본의 RGB를 유지하고 AI 마스크의 투명도만 사용한다.
  function createMask(resultImage) {
    maskCanvas = document.createElement("canvas");
    maskCanvas.width = sourceCanvas.width;
    maskCanvas.height = sourceCanvas.height;
    maskCtx = maskCanvas.getContext("2d",{willReadFrequently:true});
    maskCtx.drawImage(resultImage,0,0,maskCanvas.width,maskCanvas.height);
    maskCtx.globalCompositeOperation = "source-in";
    maskCtx.fillStyle = "#ffffff";
    maskCtx.fillRect(0,0,maskCanvas.width,maskCanvas.height);
    maskCtx.globalCompositeOperation = "source-over";
    autoMask = snapshotMask();
    history = [autoMask.slice()];
    historyIndex = 0;
    keyboardPoint = {x:sourceCanvas.width/2,y:sourceCanvas.height/2};
    updateHistoryButtons();
  }

  function snapshotMask() {
    const data = maskCtx.getImageData(
      0,0,maskCanvas.width,maskCanvas.height
    ).data;
    const alpha = new Uint8Array(maskCanvas.width * maskCanvas.height);
    for (let i=0;i<alpha.length;i++) alpha[i] = data[i*4+3];
    return alpha;
  }

  function restoreMask(alpha) {
    const image = maskCtx.createImageData(maskCanvas.width,maskCanvas.height);
    const data = image.data;
    for (let i=0;i<alpha.length;i++) {
      const j = i*4;
      data[j] = 255;
      data[j+1] = 255;
      data[j+2] = 255;
      data[j+3] = alpha[i];
    }
    maskCtx.putImageData(image,0,0);
    renderAll();
  }

  function historyLimit() {
    const bytes = maskCanvas.width * maskCanvas.height;
    return Math.max(2,Math.min(10,Math.floor(32*1024*1024/bytes)));
  }

  function pushHistory() {
    history = history.slice(0,historyIndex+1);
    history.push(snapshotMask());
    while (history.length > historyLimit()) history.shift();
    historyIndex = history.length-1;
    updateHistoryButtons();
  }

  function updateHistoryButtons() {
    el.undo.disabled = busy || historyIndex <= 0;
    el.redo.disabled = busy || historyIndex >= history.length-1;
    el.autoReset.disabled = busy || !autoMask;
  }

  function makeForeground(w,h,softness) {
    const fg = document.createElement("canvas");
    fg.width=w;
    fg.height=h;
    const ctx=fg.getContext("2d");
    ctx.drawImage(sourceCanvas,0,0,w,h);
    ctx.globalCompositeOperation="destination-in";
    const scale=w/sourceCanvas.width;
    const blur=softness*scale;
    if (blur>0) ctx.filter="blur("+blur+"px)";
    ctx.drawImage(maskCanvas,0,0,w,h);
    ctx.filter="none";
    ctx.globalCompositeOperation="source-over";
    return fg;
  }

  function compose(target,w,h,background,softness) {
    target.width=w;
    target.height=h;
    const ctx=target.getContext("2d");
    if (background) {
      ctx.fillStyle=background;
      ctx.fillRect(0,0,w,h);
    }
    const fg=makeForeground(w,h,softness);
    ctx.drawImage(fg,0,0);
    fg.width=0;
    fg.height=0;
  }

  function renderAll() {
    if (!sourceCanvas || !maskCanvas) return;

    const ratio=Math.min(1,1200/Math.max(sourceCanvas.width,sourceCanvas.height));
    const w=Math.max(1,Math.round(sourceCanvas.width*ratio));
    const h=Math.max(1,Math.round(sourceCanvas.height*ratio));
    const softness=Number(el.softness.value);

    compose(el.preview,w,h,getBackground(),softness);
    compose(el.edit,w,h,getBackground(),softness);

    el.checker.hidden=false;
    el.resultEmpty.hidden=true;
    el.editor.hidden=false;
    el.download.disabled=busy;
    updateZoom();
  }

  function updateZoom() {
    if (!sourceCanvas || !el.edit.width) return;
    const available=Math.max(180,el.stage.clientWidth-24);
    const fit=Math.min(available,el.edit.width);
    const width=fit*Number(el.zoom.value);
    el.edit.style.width=width+"px";
    el.stageInner.style.width=width+"px";
    el.cursor.style.display="none";
  }

  function setTool(value) {
    tool=value;
    el.restore.classList.toggle("active",value==="restore");
    el.erase.classList.toggle("active",value==="erase");
  }

  function pointFromEvent(event) {
    const rect=el.edit.getBoundingClientRect();
    return {
      x:Math.max(0,Math.min(sourceCanvas.width,
        (event.clientX-rect.left)/rect.width*sourceCanvas.width)),
      y:Math.max(0,Math.min(sourceCanvas.height,
        (event.clientY-rect.top)/rect.height*sourceCanvas.height))
    };
  }

  function stamp(x,y) {
    const radius=Number(el.brush.value)/2;
    const hardness=Number(el.hardness.value)/100;

    maskCtx.save();
    maskCtx.globalCompositeOperation =
      tool==="erase" ? "destination-out" : "source-over";

    if (hardness >= 0.99) {
      maskCtx.fillStyle="#ffffff";
    } else {
      const gradient=maskCtx.createRadialGradient(
        x,y,Math.max(0,radius*hardness),x,y,radius
      );
      gradient.addColorStop(0,"rgba(255,255,255,1)");
      gradient.addColorStop(1,"rgba(255,255,255,0)");
      maskCtx.fillStyle=gradient;
    }

    maskCtx.beginPath();
    maskCtx.arc(x,y,radius,0,Math.PI*2);
    maskCtx.fill();
    maskCtx.restore();
    strokeChanged=true;
  }

  function paintSegment(from,to) {
    const distance=Math.hypot(to.x-from.x,to.y-from.y);
    const step=Math.max(1,Number(el.brush.value)/6);
    const count=Math.max(1,Math.ceil(distance/step));
    for (let i=1;i<=count;i++) {
      const t=i/count;
      stamp(from.x+(to.x-from.x)*t,from.y+(to.y-from.y)*t);
    }
  }

  function updateCursor(event) {
    if (!sourceCanvas) return;
    const point=pointFromEvent(event);
    const rect=el.edit.getBoundingClientRect();
    const size=Number(el.brush.value)*rect.width/sourceCanvas.width;
    const x=(event.clientX-rect.left);
    const y=(event.clientY-rect.top);
    el.cursor.style.width=size+"px";
    el.cursor.style.height=size+"px";
    el.cursor.style.left=(x-size/2)+"px";
    el.cursor.style.top=(y-size/2)+"px";
    el.cursor.style.display="block";
  }

  function finishStroke() {
    if (!painting) return;
    painting=false;
    lastPoint=null;
    el.edit.style.touchAction="pan-x pan-y";
    if (strokeChanged) {
      pushHistory();
      renderAll();
      strokeChanged=false;
    }
  }

  async function runRemoval() {
    if (!selectedFile || busy) return;

    clearResult();
    clearError();
    setBusy(true);
    el.progress.hidden=false;
    el.progress.removeAttribute("value");

    try {
      if (typeof WebAssembly==="undefined") {
        throw new Error("이 브라우저는 WebAssembly를 지원하지 않습니다.");
      }

      setStatus("AI 라이브러리를 불러오는 중입니다...");
      const remove=await loadLibrary();

      setStatus("이미지를 준비하는 중입니다...");
      const prepared=await prepareImage(selectedFile);

      setStatus("AI 모델을 불러오고 있습니다. 첫 실행은 시간이 걸릴 수 있습니다.");

      const resultBlob=await remove(prepared.blob,{
        model:el.quality.value,
        device:"cpu",
        proxyToWorker:false,
        debug:false,
        output:{format:"image/png"},
        progress:(key,current,total)=>{
          if (total>0) {
            const percent=Math.min(100,Math.round(current/total*100));
            el.progress.value=percent;
            setStatus("AI 모델 다운로드 중: "+percent+"%");
          }
        }
      });

      if (!(resultBlob instanceof Blob) || !resultBlob.size) {
        throw new Error("AI가 유효한 결과 이미지를 반환하지 않았습니다.");
      }

      const url=URL.createObjectURL(resultBlob);
      let resultImage;
      try {
        resultImage=await loadImage(url);
      } finally {
        URL.revokeObjectURL(url);
      }

      sourceCanvas=prepared.canvas;
      createMask(resultImage);
      renderAll();

      el.progress.hidden=true;
      setStatus(
        "배경 제거 완료! 아래에서 복원·지우기 브러시로 세밀하게 보정할 수 있습니다.",
        "success"
      );
    } catch(error) {
      el.progress.hidden=true;
      clearResult();
      showError(error,
        "배경 제거에 실패했습니다. 오류 자세히 보기를 열면 원인을 확인할 수 있습니다.");
    } finally {
      setBusy(false);
      updateHistoryButtons();
    }
  }

  async function downloadResult() {
    if (!sourceCanvas || busy) return;

    try {
      const format=el.format.value;
      let mime="image/png";
      if (format==="jpg") mime="image/jpeg";
      if (format==="webp") mime="image/webp";

      let bg=getBackground();
      if (format==="jpg" && !bg) bg="#ffffff";

      const out=document.createElement("canvas");
      compose(out,sourceCanvas.width,sourceCanvas.height,
        bg,Number(el.softness.value));

      const blob=await toBlob(out,mime,
        format==="png" ? undefined : 0.92);

      out.width=0;
      out.height=0;

      let ext="png";
      if (blob.type==="image/jpeg") ext="jpg";
      if (blob.type==="image/webp") ext="webp";

      const name=(selectedFile.name || "image")
        .replace(/\.[^.]+$/,"")
        .replace(/[\\/:*?"<>|]/g,"_");

      const url=URL.createObjectURL(blob);
      const a=document.createElement("a");
      a.href=url;
      a.download=name+"-bg-removed."+ext;
      document.body.appendChild(a);
      a.click();
      a.remove();
      setTimeout(()=>URL.revokeObjectURL(url),20000);

      setStatus("파일 다운로드를 요청했습니다.","success");
    } catch(error) {
      showError(error,"다운로드 파일 생성에 실패했습니다.");
    }
  }

  el.file.addEventListener("change",event=>{
    selectFile(event.target.files?.[0]);
  });

  ["dragenter","dragover"].forEach(name=>{
    el.drop.addEventListener(name,event=>{
      event.preventDefault();
      if (!busy) el.drop.classList.add("dragover");
    });
  });

  ["dragleave","dragend","drop"].forEach(name=>{
    el.drop.addEventListener(name,event=>{
      event.preventDefault();
      el.drop.classList.remove("dragover");
    });
  });

  el.drop.addEventListener("drop",event=>{
    if (!busy) selectFile(event.dataTransfer?.files?.[0]);
  });

  el.start.addEventListener("click",runRemoval);
  el.reset.addEventListener("click",resetAll);
  el.download.addEventListener("click",downloadResult);

  el.bg.addEventListener("change",()=>{
    el.colorbox.hidden=el.bg.value!=="custom";
    renderAll();
  });
  el.color.addEventListener("input",renderAll);

  el.brush.addEventListener("input",()=>{
    el.brushValue.textContent=el.brush.value;
  });
  el.hardness.addEventListener("input",()=>{
    el.hardnessValue.textContent=el.hardness.value;
  });
  el.softness.addEventListener("input",()=>{
    el.softnessValue.textContent=el.softness.value;
    renderAll();
  });
  el.zoom.addEventListener("change",updateZoom);
  el.restore.addEventListener("click",()=>setTool("restore"));
  el.erase.addEventListener("click",()=>setTool("erase"));

  el.undo.addEventListener("click",()=>{
    if (historyIndex<=0 || busy) return;
    historyIndex--;
    restoreMask(history[historyIndex]);
    updateHistoryButtons();
  });

  el.redo.addEventListener("click",()=>{
    if (historyIndex>=history.length-1 || busy) return;
    historyIndex++;
    restoreMask(history[historyIndex]);
    updateHistoryButtons();
  });

  el.autoReset.addEventListener("click",()=>{
    if (!autoMask || busy) return;
    restoreMask(autoMask);
    history=[autoMask.slice()];
    historyIndex=0;
    updateHistoryButtons();
    setStatus("AI 자동 제거 결과로 되돌렸습니다.","success");
  });

  el.edit.addEventListener("pointerdown",event=>{
    if (!sourceCanvas || busy) return;
    if (event.pointerType==="mouse" && event.button!==0) return;
    event.preventDefault();
    painting=true;
    strokeChanged=false;
    lastPoint=pointFromEvent(event);
    stamp(lastPoint.x,lastPoint.y);
    el.edit.style.touchAction="none";
    el.edit.setPointerCapture(event.pointerId);
    updateCursor(event);
    renderAll();
  });

  el.edit.addEventListener("pointermove",event=>{
    if (!sourceCanvas || busy) return;
    updateCursor(event);
    if (!painting) return;
    const point=pointFromEvent(event);
    paintSegment(lastPoint,point);
    lastPoint=point;
    renderAll();
  });

  el.edit.addEventListener("pointerup",finishStroke);
  el.edit.addEventListener("pointercancel",finishStroke);
  el.edit.addEventListener("lostpointercapture",finishStroke);
  el.edit.addEventListener("pointerleave",()=>{
    if (!painting) el.cursor.style.display="none";
  });

  // 키보드로 위치 이동 및 한 번 칠하기
  el.edit.addEventListener("keydown",event=>{
    if (!sourceCanvas || busy) return;
    const step=event.shiftKey ? 20 : 5;
    let moved=true;

    if (event.key==="ArrowLeft") keyboardPoint.x-=step;
    else if (event.key==="ArrowRight") keyboardPoint.x+=step;
    else if (event.key==="ArrowUp") keyboardPoint.y-=step;
    else if (event.key==="ArrowDown") keyboardPoint.y+=step;
    else moved=false;

    if (moved) {
      event.preventDefault();
      keyboardPoint.x=Math.max(0,Math.min(sourceCanvas.width,keyboardPoint.x));
      keyboardPoint.y=Math.max(0,Math.min(sourceCanvas.height,keyboardPoint.y));
    }

    if (event.key===" " || event.key==="Enter") {
      event.preventDefault();
      stamp(keyboardPoint.x,keyboardPoint.y);
      pushHistory();
      renderAll();
      strokeChanged=false;
    }
  });

  if (typeof ResizeObserver!=="undefined") {
    new ResizeObserver(updateZoom).observe(el.stage);
  } else {
    window.addEventListener("resize",updateZoom);
  }

  resetAll();
})();
</script>
</div>

<!-- SNSNOTE 배경 제거기: 편집 화면 안정화 및 수동 보정 상시 표시 -->
<style>
/* 편집 영역은 고정된 크기 안에서 사진 전체를 표시 */
#sns-bg-remover-v3 .br-stage {
  height: clamp(300px, 46vw, 560px) !important;
  min-height: 0 !important;
  max-height: none !important;
  overflow: hidden !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  padding: 14px !important;
}

/* 기존 JavaScript의 확대 너비 계산을 무시 */
#sns-bg-remover-v3 .br-stage-inner {
  width: 100% !important;
  height: 100% !important;
  min-width: 0 !important;
  min-height: 0 !important;
  max-width: 100% !important;
  max-height: 100% !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  position: relative !important;
  flex: 0 0 auto !important;
}

/* 원본 비율을 유지하면서 가로·세로 모두 화면 안에 맞춤 */
#sns-bg-remover-v3 .br-edit-canvas {
  width: auto !important;
  height: auto !important;
  max-width: 100% !important;
  max-height: 100% !important;
  min-width: 0 !important;
  min-height: 0 !important;
  flex: 0 1 auto !important;
  object-fit: contain !important;
  display: block !important;
}

/* 브러시 원형 표시를 실제 캔버스 위치에 맞춤 */
#sns-bg-remover-v3 .br-brush-cursor {
  transform: translate(
    var(--br-offset-x, 0px),
    var(--br-offset-y, 0px)
  ) !important;
}

/* 사진 처리 전에도 수동 보정 기능을 보여줌 */
#sns-bg-remover-v3 .br-editor {
  display: block !important;
}

/* 처리 전 안내문 */
#sns-bg-remover-v3:not([data-editor-ready="true"]) .br-stage::after {
  content: "사진을 업로드하고 배경을 제거하면\A이곳에서 세밀하게 수동 보정할 수 있습니다.";
  white-space: pre-line;
  position: absolute;
  z-index: 2;
  max-width: 280px;
  padding: 20px;
  border-radius: 12px;
  background: rgba(255,255,255,.92);
  border: 1px solid #dbe4f0;
  color: #64748b;
  text-align: center;
  font-size: 13px;
  line-height: 1.8;
  pointer-events: none;
}

/* 처리 전 캔버스는 숨기고 기능 안내를 표시 */
#sns-bg-remover-v3:not([data-editor-ready="true"]) .br-edit-canvas,
#sns-bg-remover-v3:not([data-editor-ready="true"]) .br-brush-cursor {
  visibility: hidden;
}

#sns-bg-remover-v3:not([data-editor-ready="true"]) .br-editor-controls,
#sns-bg-remover-v3:not([data-editor-ready="true"]) .br-tool-buttons {
  opacity: .55;
}

/* 확대 선택 항목 제거 */
#sns-bg-remover-v3 .br-field:has(#snsbr3-zoom) {
  display: none !important;
}

@media (max-width: 600px) {
  #sns-bg-remover-v3 .br-stage {
    height: 340px !important;
    padding: 10px !important;
  }
}
</style>

<script>
(function () {
  const root = document.getElementById("sns-bg-remover-v3");
  if (!root || root.dataset.editorFitPatch === "true") return;
  root.dataset.editorFitPatch = "true";

  const editor = root.querySelector("#snsbr3-editor");
  const checker = root.querySelector("#snsbr3-checker");
  const stage = root.querySelector("#snsbr3-stage");
  const canvas = root.querySelector("#snsbr3-edit");
  const cursor = root.querySelector("#snsbr3-cursor");
  const zoom = root.querySelector("#snsbr3-zoom");

  if (!editor || !checker || !stage || !canvas) return;

  // 확대는 사용하지 않고 항상 화면 맞춤으로 고정
  if (zoom) {
    zoom.value = "1";
    zoom.disabled = true;
    const zoomField = zoom.closest(".br-field");
    if (zoomField) zoomField.hidden = true;
  }

  // 기존 코드가 편집 영역을 숨겨도 다시 표시
  let lastReady = null;

  const basicControls = editor.querySelectorAll(
    ".br-editor-controls input, " +
    ".br-editor-controls select, " +
    "#snsbr3-restore, #snsbr3-erase"
  );

  const historyButtons = editor.querySelectorAll(
    "#snsbr3-undo, #snsbr3-redo, #snsbr3-auto-reset"
  );

  function syncEditor() {
    const ready = !checker.hidden && canvas.width > 0;

    if (editor.hidden) {
      editor.hidden = false;
    }

    if (ready !== lastReady) {
      lastReady = ready;
      root.dataset.editorReady = String(ready);

      basicControls.forEach(function (control) {
        if (control !== zoom) {
          control.disabled = !ready;
        }
      });

      // 실행 취소 등은 기존 코드의 활성화 상태를 유지
      if (!ready) {
        historyButtons.forEach(function (button) {
          button.disabled = true;
        });
      }
    }

    if (zoom && zoom.value !== "1") {
      zoom.value = "1";
    }
  }

  // 캔버스가 가운데 배치되어도 브러시 원형 위치가 맞도록 보정
  function syncCursorOffset() {
    if (!cursor || !canvas.width) return;

    const x = canvas.offsetLeft;
    const y = canvas.offsetTop;

    const xValue = x + "px";
    const yValue = y + "px";

    if (root.style.getPropertyValue("--br-offset-x") !== xValue) {
      root.style.setProperty("--br-offset-x", xValue);
    }

    if (root.style.getPropertyValue("--br-offset-y") !== yValue) {
      root.style.setProperty("--br-offset-y", yValue);
    }
  }

  // 결과가 나타나거나 초기화될 때만 상태 동기화
  const observer = new MutationObserver(syncEditor);

  observer.observe(editor, {
    attributes: true,
    attributeFilter: ["hidden"]
  });

  observer.observe(checker, {
    attributes: true,
    attributeFilter: ["hidden"]
  });

  // 편집 영역의 실제 크기가 바뀔 때만 커서 위치를 조정
  if (typeof ResizeObserver !== "undefined") {
    const resizeObserver = new ResizeObserver(syncCursorOffset);
    resizeObserver.observe(stage);
    resizeObserver.observe(canvas);
  } else {
    window.addEventListener("resize", syncCursorOffset);
  }

  syncEditor();
  syncCursorOffset();
})();
</script>



<h2 class="wp-block-heading">AI 이미지 배경 제거 무료 도구 사용 방법</h2>



<p class="wp-block-paragraph">사진에서 인물이나 사물만 남기고 배경을 없애고 싶을 때, 별도의 이미지 편집 프로그램을 설치하지 않아도 됩니다. SNSNOTE의 AI 이미지 배경 제거 도구는 사진을 브라우저에서 분석해 배경을 분리하고, 투명 PNG 저장과 배경색 변경, 수동 보정까지 지원합니다.</p>



<p class="wp-block-paragraph">프로필 사진, 쇼핑몰 상품 이미지, 블로그 썸네일, 발표 자료에 사용할 사진 등 다양한 이미지에 활용할 수 있습니다. 특히 자동 누끼 따기 결과가 완벽하지 않을 때는 복원·지우기 브러시로 남은 부분을 직접 수정할 수 있습니다.</p>



<h3 class="wp-block-heading">1. 배경을 제거할 사진 업로드</h3>



<p class="wp-block-paragraph">상단의 <strong>이미지 업로드</strong> 영역을 클릭하거나 사진을 끌어다 놓으세요. JPG, PNG, WebP 파일을 선택할 수 있습니다. 사진을 선택하면 오른쪽 미리보기에서 원본 이미지를 확인할 수 있습니다.</p>



<p class="wp-block-paragraph">인물 사진뿐 아니라 제품, 반려동물, 소품 등 배경과 피사체가 구분되는 사진에 사용할 수 있습니다. 다만 투명한 유리, 복잡한 머리카락, 배경과 색상이 비슷한 물체는 AI가 일부 영역을 잘못 구분할 수 있습니다.</p>



<h3 class="wp-block-heading">2. AI 처리 품질과 이미지 크기 선택</h3>



<p class="wp-block-paragraph">AI 처리 품질에서 고품질, 표준, 빠른 처리 중 원하는 옵션을 선택합니다. 고품질 모델은 더 많은 기기 자원과 처리 시간이 필요할 수 있으며, 사진에 따라 표준 모델의 결과가 더 자연스러울 수도 있습니다.</p>



<p class="wp-block-paragraph">처리 이미지 크기는 기본적으로 최대 2400px가 권장됩니다. 고해상도 이미지가 필요한 경우 4096px 옵션을 선택할 수 있지만, 휴대폰이나 저사양 컴퓨터에서는 처리 시간이 길어지거나 메모리 부족이 발생할 수 있습니다. 처음에는 기본 설정으로 결과를 확인한 뒤 필요할 때 높은 설정을 사용하는 편이 좋습니다.</p>



<h3 class="wp-block-heading">3. 배경 제거 시작</h3>



<p class="wp-block-paragraph"><strong>배경 제거 시작</strong> 버튼을 누르면 AI 모델이 사진을 분석합니다. 처음 실행할 때는 외부 CDN에서 AI 모델을 다운로드하기 때문에 시간이 조금 걸릴 수 있습니다. 이후에도 이미지 크기와 기기 성능에 따라 처리 속도가 달라집니다.</p>



<p class="wp-block-paragraph">완료되면 원본 이미지와 배경 제거 결과를 나란히 비교할 수 있습니다. 결과 뒤에 표시되는 회색 체크무늬는 실제 배경이 아니라 투명한 영역을 알아보기 쉽게 보여주는 미리보기 표시입니다.</p>



<h3 class="wp-block-heading">4. 투명 배경 또는 원하는 배경색 선택</h3>



<p class="wp-block-paragraph">배경 옵션에서 투명 배경, 흰색, 검은색, 사용자 색상을 선택할 수 있습니다. 투명 배경은 다른 디자인 위에 사진을 합성할 때 편리하고, 흰색 배경은 상품 사진이나 깔끔한 프로필 이미지에 활용하기 좋습니다.</p>



<p class="wp-block-paragraph">사용자 색상을 선택하면 원하는 색상으로 배경을 변경할 수 있습니다. 블로그 썸네일이나 SNS 콘텐츠의 색상을 통일하고 싶을 때 유용합니다. 이미지를 잘라내는 것이 아니라 AI가 분리한 피사체 뒤에 새로운 배경을 적용하는 방식입니다.</p>



<h3 class="wp-block-heading">5. 수동 보정으로 누끼를 더 깔끔하게 수정하기</h3>



<p class="wp-block-paragraph">자동 배경 제거가 완료되면 미리보기 아래의 <strong>세밀한 수동 보정</strong> 영역을 사용할 수 있습니다. 자동 처리 결과에서 머리카락이 일부 사라졌거나, 옷 가장자리에 배경이 남아 있다면 브러시로 수정해보세요.</p>



<p class="wp-block-paragraph"><strong>+ 복원</strong>은 지워진 부분을 원본 사진에서 되살리는 기능입니다. 반대로 <strong>− 지우기</strong>는 남아 있는 불필요한 배경을 제거합니다. 브러시 크기와 선명도를 조절하면서 작은 부분부터 수정하는 것이 좋습니다.</p>



<p class="wp-block-paragraph">실수로 잘못 칠했다면 실행 취소를 사용하거나 AI 결과로 되돌릴 수 있습니다. 수동 보정은 원본 사진의 픽셀을 복원하는 방식이므로, 원본에 존재하지 않는 머리카락이나 사물을 새롭게 생성하는 기능은 아닙니다.</p>



<h3 class="wp-block-heading">6. PNG, JPG, WebP로 결과 다운로드</h3>



<p class="wp-block-paragraph">수정이 끝나면 원하는 다운로드 형식을 선택하고 <strong>결과 다운로드</strong> 버튼을 누르세요. 투명한 배경을 유지하려면 PNG를 선택하는 것이 가장 편리합니다. JPG는 투명도를 지원하지 않으므로 투명 배경을 선택하더라도 흰색 배경으로 저장됩니다.</p>



<p class="wp-block-paragraph">저장하기 전에 미리보기에서 피사체 가장자리와 배경색을 한 번 더 확인해보세요. 특히 상품 사진이나 발표 자료에 사용할 이미지는 밝은 배경과 어두운 배경에서 모두 확인하면 남아 있는 테두리를 발견하기 쉽습니다.</p>



<h2 class="wp-block-heading">PNG란? 배경 제거 후 PNG로 저장해야 하는 이유</h2>



<p class="wp-block-paragraph">PNG는 이미지의 투명도를 저장할 수 있는 파일 형식입니다. 배경을 제거한 뒤 투명 PNG로 저장하면 인물이나 상품만 남기고 나머지 영역을 투명하게 유지할 수 있습니다.</p>



<p class="wp-block-paragraph">예를 들어 흰색 배경의 상품 사진을 JPG로 저장하면 흰색 바탕이 이미지에 포함됩니다. 반면 투명 PNG로 저장하면 배경이 없는 상태로 다른 색상의 배너, 카드뉴스, 웹사이트 디자인 위에 배치할 수 있습니다.</p>



<p class="wp-block-paragraph">PNG는 무손실 압축을 사용하는 형식이지만, 사진의 크기와 내용에 따라 JPG나 WebP보다 파일 용량이 커질 수 있습니다. 따라서 투명도가 꼭 필요한지, 최종적으로 어디에 사용할지를 기준으로 형식을 선택하는 것이 좋습니다.</p>



<h3 class="wp-block-heading">PNG, JPG, WebP 차이</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>형식</th><th>투명 배경</th><th>주요 특징</th><th>추천 용도</th></tr></thead><tbody><tr><td>PNG</td><td>지원</td><td>무손실 압축, 투명도 보존</td><td>누끼 이미지, 로고, 합성용 사진</td></tr><tr><td>JPG</td><td>지원하지 않음</td><td>사진에 적합한 손실 압축</td><td>일반 사진, 흰색 배경 상품 이미지</td></tr><tr><td>WebP</td><td>지원</td><td>손실·무손실 압축 및 투명도 지원</td><td>웹사이트 이미지, 용량을 고려한 이미지</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">투명 배경이 필요하다면 PNG 또는 투명도를 지원하는 WebP를 선택하세요. 일반적인 사진을 작은 용량으로 저장하려는 목적이라면 JPG나 WebP가 적합할 수 있습니다. 형식별 자세한 기술 정보는 <a href="https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Image_types" target="_blank" rel="noopener">MDN 이미지 파일 형식 안내</a>에서도 확인할 수 있습니다.</p>



<h2 class="wp-block-heading">AI 누끼 따기를 활용하기 좋은 상황</h2>



<h3 class="wp-block-heading">블로그와 SNS 썸네일 제작</h3>



<p class="wp-block-paragraph">인물이나 제품의 배경을 제거하면 텍스트와 배경색을 자유롭게 배치할 수 있습니다. 블로그 대표 이미지, 인스타그램 게시물, 유튜브 썸네일 등에서 피사체를 강조할 때 유용합니다. 배경을 제거한 뒤 원하는 색상을 적용하면 여러 이미지의 디자인을 통일하기도 쉽습니다.</p>



<h3 class="wp-block-heading">쇼핑몰 상품 사진 정리</h3>



<p class="wp-block-paragraph">상품 주변의 복잡한 배경을 제거하고 흰색이나 단색 배경으로 바꾸면 제품을 더 명확하게 보여줄 수 있습니다. 다만 상품의 실제 색상과 형태를 정확하게 전달해야 하므로, AI가 제품의 일부를 삭제하지 않았는지 가장자리를 확인하는 것이 중요합니다.</p>



<h3 class="wp-block-heading">발표 자료와 문서 이미지</h3>



<p class="wp-block-paragraph">파워포인트나 업무 문서에 사용할 사진도 투명 PNG로 만들면 배경과 자연스럽게 합성할 수 있습니다. 인물 사진을 발표 자료에 넣거나, 제품만 따로 배치할 때 활용할 수 있습니다.</p>



<h2 class="wp-block-heading">배경 제거가 잘되지 않을 때 확인할 점</h2>



<p class="wp-block-paragraph">AI 배경 제거는 사진마다 결과가 달라질 수 있습니다. 배경과 피사체의 색상이 비슷하거나, 머리카락이 복잡하게 흩어져 있거나, 피사체가 너무 작게 촬영된 경우에는 일부 영역이 제대로 분리되지 않을 수 있습니다.</p>



<p class="wp-block-paragraph">이럴 때는 원본 사진의 밝기와 선명도를 확인하고, 다른 품질 옵션으로 다시 처리해보세요. 자동 제거 후에는 복원·지우기 브러시로 필요한 부분만 수정하는 것이 효율적입니다. 고품질 모델을 선택하더라도 모든 사진에서 완벽한 결과가 보장되는 것은 아닙니다.</p>



<p class="wp-block-paragraph">AI 모델 로딩이 실패한다면 브라우저를 새로고침하거나 다른 브라우저에서 시도해볼 수 있습니다. 대용량 사진으로 기기 메모리가 부족한 경우에는 처리 크기를 낮춰보세요. 문제가 반복된다면 오류 자세히 보기의 메시지를 확인하면 원인을 파악하는 데 도움이 됩니다.</p>



<h2 class="wp-block-heading">사진은 서버에 업로드되나요?</h2>



<p class="wp-block-paragraph">이 도구는 사진을 SNSNOTE 서버에 업로드하여 처리하는 방식이 아니라, 사용자의 브라우저에서 AI 모델을 실행하는 방식으로 구성되어 있습니다. 처음 실행 시 필요한 AI 모델 파일은 외부 CDN에서 다운로드되며, 배경 제거 연산은 사용자의 기기에서 수행됩니다.</p>



<p class="wp-block-paragraph">따라서 사진 처리 자체를 위해 SNSNOTE의 VPS CPU나 RAM을 사용하는 구조는 아닙니다. 다만 웹페이지 접속에 따른 일반적인 서버 부하와 외부 모델 다운로드에 필요한 인터넷 연결은 발생합니다. 처리 속도는 사용자의 컴퓨터 또는 휴대폰 성능에 영향을 받습니다.</p>



<p class="wp-block-paragraph">민감한 사진을 다룰 때는 결과 이미지를 다운로드하기 전에 원본의 위치정보나 촬영정보 등 메타데이터가 필요한지 확인하는 것도 좋습니다. 사진을 공유하기 전에 개인정보를 별도로 확인하고 싶다면 <a href="https://snsnote.com/photo-privacy-remover/">사진 개인정보 제거 도구</a>를 함께 사용할 수 있습니다.</p>



<h2 class="wp-block-heading">자주 묻는 질문 (FAQ)</h2>



<h3 class="wp-block-heading">Q. AI 이미지 배경 제거는 무료인가요?</h3>



<p class="wp-block-paragraph">현재 SNSNOTE에서 제공하는 브라우저 배경 제거 도구는 별도의 회원가입이나 유료 API 키 입력 없이 사용할 수 있습니다. 다만 AI 모델을 처음 다운로드할 때 인터넷 데이터가 사용될 수 있으며, 기기 성능에 따라 처리 시간이 달라집니다.</p>



<h3 class="wp-block-heading">Q. 사진 배경을 제거하면 화질이 떨어지나요?</h3>



<p class="wp-block-paragraph">AI가 배경을 분리하는 과정에서 머리카락이나 피사체 가장자리의 일부 픽셀이 변경될 수 있습니다. 또한 선택한 처리 이미지 크기에 따라 원본보다 해상도가 줄어들 수 있습니다. 고해상도 결과가 필요하다면 처리 크기를 확인하고, 저장 전 미리보기에서 가장자리를 점검하세요.</p>



<h3 class="wp-block-heading">Q. 투명 PNG로 저장했는데 배경이 흰색으로 보이는 이유는 무엇인가요?</h3>



<p class="wp-block-paragraph">이미지 뷰어 또는 파일 탐색기가 투명한 영역을 흰색으로 표시하는 경우가 있습니다. 실제로 투명도가 저장되었는지 확인하려면 투명도를 지원하는 이미지 편집기에서 열어보세요. JPG로 저장했다면 투명도가 유지되지 않으므로 PNG를 선택해야 합니다.</p>



<h3 class="wp-block-heading">Q. JPG 사진도 누끼를 딸 수 있나요?</h3>



<p class="wp-block-paragraph">네. JPG 사진을 업로드하여 AI 배경 제거를 실행한 뒤, 결과를 PNG나 WebP로 저장할 수 있습니다. 원본이 JPG였더라도 결과 형식에서 PNG를 선택하면 투명 배경을 저장할 수 있습니다.</p>



<h3 class="wp-block-heading">Q. 머리카락이나 옷 일부가 잘못 지워졌을 때는 어떻게 하나요?</h3>



<p class="wp-block-paragraph">수동 보정 영역에서 복원 브러시를 선택하고 지워진 부분을 칠하면 원본 사진의 해당 영역을 되살릴 수 있습니다. 배경이 남아 있다면 지우기 브러시를 사용하세요. 브러시 크기를 작게 설정하면 좁은 가장자리를 수정하기 편리합니다.</p>



<h3 class="wp-block-heading">Q. 휴대폰에서도 사용할 수 있나요?</h3>



<p class="wp-block-paragraph">브라우저에서 AI 모델을 실행할 수 있는 기기라면 모바일에서도 사용할 수 있습니다. 다만 AI 처리에는 메모리와 연산 성능이 필요하므로 기기에 따라 속도가 느리거나 실행이 실패할 수 있습니다. 큰 사진은 처리 크기를 낮춰 시도하는 것이 좋습니다.</p>



<h3 class="wp-block-heading">Q. 배경 제거 후 다른 이미지 형식으로 변환할 수 있나요?</h3>



<p class="wp-block-paragraph">가능합니다. 이 도구에서 PNG, JPG, WebP 형식으로 저장할 수 있으며, 추가적인 크기 조절이나 다른 이미지 형식이 필요하다면 <a href="https://snsnote.com/image-converter/">무료 이미지 변환기</a>를 이용할 수 있습니다.</p>



<h2 class="wp-block-heading">함께 사용하면 좋은 SNSNOTE 무료 도구</h2>



<p class="wp-block-paragraph">배경 제거가 끝난 이미지는 사용 목적에 맞게 추가로 편집하면 더욱 편리합니다. 파일 형식이나 이미지 크기를 변경하고 싶다면 <a href="https://snsnote.com/image-converter/">이미지 변환기</a>를, 사진을 공유하기 전에 촬영정보와 위치정보를 확인하고 제거하고 싶다면 <a href="https://snsnote.com/photo-privacy-remover/">사진 개인정보 제거 도구</a>를 활용해보세요.</p>



<p class="wp-block-paragraph">SNSNOTE는 별도의 프로그램 설치 없이 브라우저에서 사용할 수 있는 이미지·업무 도구를 계속 추가하고 있습니다. 필요한 기능을 선택해 사용하고, 중요한 사진은 편집 전 원본을 별도로 보관해두는 것을 권장합니다.</p>



<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/ai-background-remover/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/09/ai-background-remover.jpg" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1601</post-id>	</item>
		<item>
		<title>화상회의 사전 점검, 마이크·웹캠·스피커 테스트 무료 도구</title>
		<link>https://snsnote.com/meeting-device-test/</link>
					<comments>https://snsnote.com/meeting-device-test/#respond</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Sun, 06 Sep 2026 22:14:28 +0000</pubDate>
				<category><![CDATA[유용한 도구]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1590</guid>

					<description><![CDATA[화상회의 사전 점검, 회의 시작 전에 미리 확인하세요 온라인 회의나 면접에 접속했는데 카메라가 켜지지 않거나, 말을 해도 상대방이 목소리를 듣지 못하는 상황은 생각보다 자주 발생합니다....]]></description>
										<content:encoded><![CDATA[<div id="sns-meeting-preflight">
<style>
#sns-meeting-preflight{
  --mc-bg:#f8fafc;--mc-card:#fff;--mc-text:#0f172a;
  --mc-muted:#64748b;--mc-line:#e2e8f0;--mc-blue:#2563eb;
  --mc-blue-soft:#eff6ff;--mc-green:#15803d;--mc-green-soft:#f0fdf4;
  --mc-red:#b91c1c;--mc-red-soft:#fef2f2;--mc-amber:#a16207;
  --mc-amber-soft:#fffbeb;
  width:100%;max-width:920px;margin:24px auto;
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto Sans KR",Arial,sans-serif;
  color:var(--mc-text);line-height:1.6;box-sizing:border-box;
}
#sns-meeting-preflight *,#sns-meeting-preflight *::before,
#sns-meeting-preflight *::after{box-sizing:border-box}
#sns-meeting-preflight .mc-wrap{
  background:var(--mc-bg);border:1px solid var(--mc-line);
  border-radius:18px;padding:22px;
}
#sns-meeting-preflight .mc-title{
  margin:0;padding:14px 18px;border-radius:11px;color:#fff;
  font-size:25px;font-weight:800;letter-spacing:-.03em;
  background:linear-gradient(90deg,#3b82f6,#7c3aed);
}
#sns-meeting-preflight .mc-desc{margin:10px 0 0;color:var(--mc-muted);font-size:14px}
#sns-meeting-preflight .mc-privacy{
  margin-top:14px;padding:11px 13px;border-radius:10px;
  background:#ecfdf5;color:#166534;font-size:12px;line-height:1.7;
}
#sns-meeting-preflight .mc-summary{
  margin-top:18px;padding:18px;border:1px solid #c7d2fe;
  border-radius:14px;background:linear-gradient(135deg,#eff6ff,#f5f3ff);
}
#sns-meeting-preflight .mc-summary-head{
  display:flex;align-items:center;justify-content:space-between;
  gap:12px;flex-wrap:wrap;
}
#sns-meeting-preflight .mc-summary-title{font-size:18px;font-weight:800}
#sns-meeting-preflight .mc-progress-text{font-size:13px;font-weight:800;color:#4338ca}
#sns-meeting-preflight .mc-progress{
  height:8px;background:#dbeafe;border-radius:99px;overflow:hidden;margin:13px 0;
}
#sns-meeting-preflight .mc-progress-fill{
  height:100%;width:0;background:#2563eb;border-radius:99px;transition:width .2s;
}
#sns-meeting-preflight .mc-summary-result{
  padding:11px 13px;border-radius:10px;background:#fff;
  font-size:13px;font-weight:700;
}
#sns-meeting-preflight .mc-summary-result.ok{background:var(--mc-green-soft);color:var(--mc-green)}
#sns-meeting-preflight .mc-summary-result.warn{background:var(--mc-amber-soft);color:var(--mc-amber)}
#sns-meeting-preflight .mc-summary-sub{margin:10px 0 0;color:var(--mc-muted);font-size:12px}
#sns-meeting-preflight .mc-overview{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;margin-top:14px;
}
#sns-meeting-preflight .mc-overview-item{
  background:#fff;border:1px solid var(--mc-line);border-radius:10px;
  padding:11px 12px;display:flex;justify-content:space-between;align-items:center;gap:10px;
  font-size:13px;font-weight:700;
}
#sns-meeting-preflight .mc-badge{
  display:inline-flex;align-items:center;justify-content:center;
  padding:5px 10px;border-radius:99px;white-space:nowrap;
  font-size:12px;font-weight:800;background:#f1f5f9;color:#475569;
}
#sns-meeting-preflight .mc-badge.ok{background:var(--mc-green-soft);color:var(--mc-green)}
#sns-meeting-preflight .mc-badge.fail{background:var(--mc-red-soft);color:var(--mc-red)}
#sns-meeting-preflight .mc-badge.warn{background:var(--mc-amber-soft);color:var(--mc-amber)}
#sns-meeting-preflight .mc-badge.info{background:var(--mc-blue-soft);color:#1d4ed8}
#sns-meeting-preflight .mc-section{
  margin-top:18px;padding:18px;background:var(--mc-card);
  border:1px solid var(--mc-line);border-radius:14px;
}
#sns-meeting-preflight .mc-section-head{
  display:flex;align-items:flex-start;justify-content:space-between;gap:12px;
}
#sns-meeting-preflight .mc-section-title{
  display:flex;align-items:center;gap:9px;margin:0;font-size:18px;font-weight:800;
}
#sns-meeting-preflight .mc-number{
  display:inline-flex;align-items:center;justify-content:center;
  width:29px;height:29px;border-radius:9px;background:#eff6ff;color:#2563eb;
  font-size:14px;flex-shrink:0;
}
#sns-meeting-preflight .mc-section-desc{margin:8px 0 15px;color:var(--mc-muted);font-size:13px}
#sns-meeting-preflight .mc-controls{display:flex;flex-wrap:wrap;align-items:end;gap:9px}
#sns-meeting-preflight .mc-field{flex:1 1 220px;min-width:0}
#sns-meeting-preflight .mc-label{
  display:block;margin-bottom:6px;font-size:12px;font-weight:700;color:#475569;
}
#sns-meeting-preflight .mc-select{
  display:block;width:100%;min-width:0;height:42px;padding:0 12px;
  border:1px solid #cbd5e1;border-radius:9px;background:#fff;
  color:var(--mc-text);font-family:inherit;font-size:13px;
}
#sns-meeting-preflight .mc-btn{
  appearance:none;border:1px solid transparent;border-radius:9px;
  min-height:42px;padding:10px 14px;font-family:inherit;
  font-size:13px;font-weight:800;cursor:pointer;line-height:1.4;
}
#sns-meeting-preflight .mc-btn.primary{background:var(--mc-blue);color:#fff}
#sns-meeting-preflight .mc-btn.secondary{background:#fff;color:var(--mc-text);border-color:#cbd5e1}
#sns-meeting-preflight .mc-btn.good{background:var(--mc-green-soft);color:var(--mc-green);border-color:#bbf7d0}
#sns-meeting-preflight .mc-btn.bad{background:var(--mc-red-soft);color:var(--mc-red);border-color:#fecaca}
#sns-meeting-preflight .mc-btn:disabled{opacity:.45;cursor:not-allowed}
#sns-meeting-preflight .mc-btn:focus-visible,
#sns-meeting-preflight .mc-select:focus-visible{
  outline:3px solid #93c5fd;outline-offset:2px;
}
#sns-meeting-preflight .mc-preview{
  margin-top:14px;background:#0f172a;border-radius:12px;overflow:hidden;
  min-height:180px;display:flex;align-items:center;justify-content:center;position:relative;
}
#sns-meeting-preflight .mc-preview video{
  display:block;width:100%;max-height:300px;object-fit:contain;
  background:#0f172a;aspect-ratio:16/9;
}
#sns-meeting-preflight .mc-preview-placeholder{
  position:absolute;inset:0;display:flex;align-items:center;
  justify-content:center;padding:18px;text-align:center;
  color:#cbd5e1;font-size:13px;pointer-events:none;
}
#sns-meeting-preflight .mc-preview-placeholder[hidden]{display:none}
#sns-meeting-preflight .mc-message{
  margin-top:12px;padding:11px 13px;border-radius:10px;
  background:#f1f5f9;color:#475569;font-size:12px;line-height:1.7;
}
#sns-meeting-preflight .mc-message.ok{background:var(--mc-green-soft);color:var(--mc-green)}
#sns-meeting-preflight .mc-message.fail{background:var(--mc-red-soft);color:var(--mc-red)}
#sns-meeting-preflight .mc-message.warn{background:var(--mc-amber-soft);color:var(--mc-amber)}
#sns-meeting-preflight .mc-confirm{
  display:flex;flex-wrap:wrap;align-items:center;gap:8px;
  margin-top:12px;padding-top:12px;border-top:1px solid var(--mc-line);
}
#sns-meeting-preflight .mc-confirm-label{font-size:12px;font-weight:700;margin-right:3px}
#sns-meeting-preflight .mc-permission{margin:10px 0 0;font-size:12px;color:var(--mc-muted)}
#sns-meeting-preflight .mc-info{margin:9px 0 0;color:var(--mc-muted);font-size:11px}
#sns-meeting-preflight .mc-meter{
  margin-top:14px;padding:14px;background:#f8fafc;border:1px solid var(--mc-line);
  border-radius:11px;
}
#sns-meeting-preflight .mc-meter-head{display:flex;justify-content:space-between;gap:10px;font-size:12px;font-weight:700}
#sns-meeting-preflight .mc-meter-track{
  height:15px;margin-top:10px;background:#e2e8f0;border-radius:99px;overflow:hidden;
}
#sns-meeting-preflight .mc-meter-fill{
  height:100%;width:0;background:linear-gradient(90deg,#22c55e,#84cc16);
  border-radius:99px;transition:width .08s;
}
#sns-meeting-preflight .mc-meter-note{margin:8px 0 0;color:var(--mc-muted);font-size:11px}
#sns-meeting-preflight .mc-footer{display:flex;flex-wrap:wrap;gap:9px;margin-top:18px}
#sns-meeting-preflight .mc-note{margin:13px 0 0;font-size:11px;color:var(--mc-muted);line-height:1.8}
#sns-meeting-preflight .mc-live{font-size:12px;margin-top:9px;color:var(--mc-muted)}
@media(max-width:760px){
  #sns-meeting-preflight .mc-wrap{padding:15px;border-radius:14px}
  #sns-meeting-preflight .mc-title{font-size:21px}
  #sns-meeting-preflight .mc-overview{grid-template-columns:1fr}
  #sns-meeting-preflight .mc-section{padding:15px}
}
@media(max-width:460px){
  #sns-meeting-preflight .mc-section-head{flex-direction:column;gap:8px}
  #sns-meeting-preflight .mc-controls .mc-field{flex-basis:100%}
  #sns-meeting-preflight .mc-controls .mc-btn{flex:1 1 auto}
  #sns-meeting-preflight .mc-confirm .mc-confirm-label{flex-basis:100%}
  #sns-meeting-preflight .mc-confirm .mc-btn{flex:1 1 auto}
  #sns-meeting-preflight .mc-footer{display:grid;grid-template-columns:1fr}
}
</style>

<div class="mc-wrap">
  <h2 class="mc-title">화상회의 사전 점검</h2>
  <p class="mc-desc">회의 시작 전 카메라·마이크·스피커를 직접 확인하고, 화면 공유까지 점검하세요.</p>
  <div class="mc-privacy"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f512.png" alt="🔒" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 카메라 영상과 마이크 음성은 현재 브라우저 안에서만 처리됩니다. 녹음·녹화·업로드하지 않으며, 장치 권한은 테스트 버튼을 누를 때만 요청합니다.</div>

  <div class="mc-summary">
    <div class="mc-summary-head">
      <div class="mc-summary-title">회의 준비 상태</div>
      <div class="mc-progress-text" data-mc="progress-text">0 / 3 완료</div>
    </div>
    <div class="mc-progress" role="progressbar" aria-label="기본 점검 진행률" aria-valuemin="0" aria-valuemax="3" aria-valuenow="0" data-mc="progress">
      <div class="mc-progress-fill" data-mc="progress-fill"></div>
    </div>
    <div class="mc-summary-result" data-mc="summary-result" aria-live="polite">카메라, 마이크, 스피커를 차례대로 테스트해 주세요.</div>
    <div class="mc-overview">
      <div class="mc-overview-item"><span>카메라</span><span class="mc-badge" data-mc="overview-camera">미완료</span></div>
      <div class="mc-overview-item"><span>마이크</span><span class="mc-badge" data-mc="overview-mic">미완료</span></div>
      <div class="mc-overview-item"><span>스피커</span><span class="mc-badge" data-mc="overview-speaker">미완료</span></div>
      <div class="mc-overview-item"><span>화면 공유</span><span class="mc-badge" data-mc="overview-screen">미완료</span></div>
    </div>
    <p class="mc-summary-sub" data-mc="environment">브라우저 기능을 확인하고 있습니다.</p>
  </div>

  <section class="mc-section">
    <div class="mc-section-head">
      <h3 class="mc-section-title"><span class="mc-number">1</span> 카메라 테스트</h3>
      <span class="mc-badge" data-mc="badge-camera">미완료</span>
    </div>
    <p class="mc-section-desc">카메라를 켜서 실제 화면이 보이는지 확인합니다. 노트북 내장 카메라와 외장 웹캠, iPad 전·후면 카메라를 선택할 수 있습니다.</p>
    <div class="mc-controls">
      <div class="mc-field">
        <label class="mc-label" for="mc-camera-choice">카메라 선택</label>
        <select class="mc-select" id="mc-camera-choice" data-mc="camera-choice">
          <option value="default">기본 카메라 (자동)</option>
          <option value="front">전면 카메라 우선</option>
          <option value="rear">후면 카메라 우선</option>
        </select>
      </div>
      <button type="button" class="mc-btn primary" data-mc="camera-start">카메라 켜기</button>
      <button type="button" class="mc-btn secondary" data-mc="camera-stop" disabled>카메라 끄기</button>
    </div>
    <p class="mc-permission" data-mc="permission-camera">권한 상태: 확인 중...</p>
    <div class="mc-preview">
      <video data-mc="camera-video" autoplay muted playsinline></video>
      <div class="mc-preview-placeholder" data-mc="camera-placeholder">카메라를 켜면 이곳에 내 화면이 표시됩니다.</div>
    </div>
    <p class="mc-info" data-mc="camera-info">카메라 영상은 다른 사람에게 전송되지 않습니다.</p>
    <div class="mc-message" data-mc="camera-message" aria-live="polite">카메라 켜기 버튼을 눌러 테스트를 시작하세요.</div>
    <div class="mc-confirm">
      <span class="mc-confirm-label">내 화면이 정상적으로 보이나요?</span>
      <button type="button" class="mc-btn good" data-mc="camera-yes" disabled>네, 잘 보여요</button>
      <button type="button" class="mc-btn bad" data-mc="camera-no" disabled>아니요, 문제가 있어요</button>
    </div>
  </section>

  <section class="mc-section">
    <div class="mc-section-head">
      <h3 class="mc-section-title"><span class="mc-number">2</span> 마이크 테스트</h3>
      <span class="mc-badge" data-mc="badge-mic">미완료</span>
    </div>
    <p class="mc-section-desc">마이크에 대고 말해보세요. 실제 입력이 감지되면 초록색 게이지가 움직입니다. 소리는 재생하거나 저장하지 않습니다.</p>
    <div class="mc-controls">
      <div class="mc-field">
        <label class="mc-label" for="mc-mic-choice">마이크 선택</label>
        <select class="mc-select" id="mc-mic-choice" data-mc="mic-choice">
          <option value="default">기본 마이크 (자동)</option>
        </select>
      </div>
      <button type="button" class="mc-btn primary" data-mc="mic-start">마이크 켜기</button>
      <button type="button" class="mc-btn secondary" data-mc="mic-stop" disabled>마이크 끄기</button>
    </div>
    <p class="mc-permission" data-mc="permission-mic">권한 상태: 확인 중...</p>
    <div class="mc-meter">
      <div class="mc-meter-head"><span>마이크 입력 레벨</span><span data-mc="mic-level-text">대기 중</span></div>
      <div class="mc-meter-track" role="progressbar" aria-label="마이크 입력 레벨" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" data-mc="mic-meter">
        <div class="mc-meter-fill" data-mc="mic-meter-fill"></div>
      </div>
      <p class="mc-meter-note">이 게이지는 상대적인 입력 크기이며 데시벨(dB) 측정값은 아닙니다.</p>
    </div>
    <div class="mc-message" data-mc="mic-message" aria-live="polite">마이크 켜기 버튼을 누른 뒤 평소 말하는 목소리로 테스트해 주세요.</div>
    <div class="mc-confirm">
      <span class="mc-confirm-label">말할 때 게이지가 움직이나요?</span>
      <button type="button" class="mc-btn good" data-mc="mic-yes" disabled>네, 입력이 감지돼요</button>
      <button type="button" class="mc-btn bad" data-mc="mic-no" disabled>아니요, 움직이지 않아요</button>
    </div>
  </section>

  <section class="mc-section">
    <div class="mc-section-head">
      <h3 class="mc-section-title"><span class="mc-number">3</span> 스피커 테스트</h3>
      <span class="mc-badge" data-mc="badge-speaker">미완료</span>
    </div>
    <p class="mc-section-desc">짧은 테스트 음을 재생합니다. 실제로 소리가 들리는지는 직접 확인해야 합니다. 헤드폰이나 블루투스 이어폰을 사용한다면 먼저 연결해 주세요.</p>
    <div class="mc-controls">
      <button type="button" class="mc-btn primary" data-mc="speaker-play">테스트 음 재생</button>
      <button type="button" class="mc-btn secondary" data-mc="speaker-choose" hidden>출력 장치 선택</button>
      <button type="button" class="mc-btn secondary" data-mc="speaker-stop">소리 중지</button>
    </div>
    <audio data-mc="tone" preload="none"></audio>
    <p class="mc-info" data-mc="speaker-device">출력 장치: 시스템 기본 스피커 / 헤드폰</p>
    <div class="mc-message" data-mc="speaker-message" aria-live="polite">재생 버튼을 누르면 짧은 테스트 음이 나옵니다. 음량을 적당히 조절해 주세요.</div>
    <div class="mc-confirm">
      <span class="mc-confirm-label">테스트 음이 실제로 들렸나요?</span>
      <button type="button" class="mc-btn good" data-mc="speaker-yes" disabled>네, 소리가 들려요</button>
      <button type="button" class="mc-btn bad" data-mc="speaker-no" disabled>아니요, 안 들려요</button>
    </div>
  </section>

  <section class="mc-section">
    <div class="mc-section-head">
      <h3 class="mc-section-title"><span class="mc-number">4</span> 화면 공유 테스트</h3>
      <span class="mc-badge" data-mc="badge-screen">미완료</span>
    </div>
    <p class="mc-section-desc">지원되는 브라우저에서 화면, 창 또는 탭을 선택해 실제 공유 미리보기를 확인합니다. 이 검사는 선택 사항이며, 지원되지 않아도 카메라·마이크·스피커 점검에는 영향을 주지 않습니다.</p>
    <div class="mc-controls">
      <button type="button" class="mc-btn primary" data-mc="screen-start">화면 공유 테스트</button>
      <button type="button" class="mc-btn secondary" data-mc="screen-stop" disabled>공유 중지</button>
    </div>
    <div class="mc-preview">
      <video data-mc="screen-video" autoplay muted playsinline></video>
      <div class="mc-preview-placeholder" data-mc="screen-placeholder">화면 공유를 시작하면 선택한 화면의 미리보기가 표시됩니다.</div>
    </div>
    <div class="mc-message" data-mc="screen-message" aria-live="polite">테스트 버튼을 누른 뒤 브라우저의 화면 선택창에서 공유할 대상을 직접 선택하세요.</div>
    <div class="mc-confirm">
      <span class="mc-confirm-label">선택한 화면이 정상적으로 보이나요?</span>
      <button type="button" class="mc-btn good" data-mc="screen-yes" disabled>네, 잘 보여요</button>
      <button type="button" class="mc-btn bad" data-mc="screen-no" disabled>아니요, 문제가 있어요</button>
    </div>
    <p class="mc-note">※ 화면 공유는 브라우저와 운영체제에 따라 지원 범위가 다릅니다. iPad에서 웹 화면 공유를 지원하지 않는 경우에는 실제 화상회의 앱의 화면 공유 기능을 확인해 주세요. 민감한 정보가 있는 화면은 선택하지 않는 것이 좋습니다.</p>
  </section>

  <div class="mc-footer">
    <button type="button" class="mc-btn primary" data-mc="copy">점검 결과 복사</button>
    <button type="button" class="mc-btn secondary" data-mc="reset">결과 초기화</button>
  </div>
  <div class="mc-live" data-mc="copy-message" aria-live="polite"></div>
  <p class="mc-note">※ 이 도구는 현재 브라우저의 장치 입력·출력을 확인하는 사전 점검입니다. 실제 Zoom, Google Meet, Teams 등의 서비스 연결, 상대방에게 전달되는 영상·음성, 인터넷 속도 및 회사 네트워크 정책까지 보장하지는 않습니다. 카메라·마이크에는 HTTPS와 사용자 권한이 필요합니다.</p>
</div>

<script>
(function(){
'use strict';

var root=document.getElementById('sns-meeting-preflight');
if(!root||root.dataset.mcReady==='1')return;
root.dataset.mcReady='1';

function $(name){return root.querySelector('[data-mc="'+name+'"]');}
function text(name,value){var el=$(name);if(el)el.textContent=value;}
function message(kind,value,mode){
  var el=$(kind+'-message');
  if(!el)return;
  el.textContent=value;
  el.className='mc-message'+(mode?' '+mode:'');
}
function badge(name,value,mode){
  var el=$(name);
  if(!el)return;
  el.textContent=value;
  el.className='mc-badge'+(mode?' '+mode:'');
}

var state={
  results:{camera:'pending',mic:'pending',speaker:'pending',screen:'pending'},
  streams:{camera:null,mic:null,screen:null},
  serial:{camera:0,mic:0,screen:0},
  micContext:null,micAnalyser:null,micData:null,micFrame:0,
  micSignal:false,toneStarted:false,toneURL:null,
  outputLabel:'시스템 기본 스피커 / 헤드폰',
  outputId:'',closing:false
};

var labels={
  pending:['미완료',''],
  pass:['완료','ok'],
  fail:['확인 필요','fail'],
  unavailable:['테스트 제한','warn']
};

function result(kind,value){
  state.results[kind]=value;
  var item=labels[value]||labels.pending;
  badge('badge-'+kind,item[0],item[1]);
  badge('overview-'+kind,item[0],item[1]);
  updateSummary();
}

function updateSummary(){
  var required=['camera','mic','speaker'];
  var done=required.filter(function(k){return state.results[k]==='pass';}).length;
  var issues=required.filter(function(k){
    return state.results[k]==='fail'||state.results[k]==='unavailable';
  });
  var share=state.results.screen;
  var el=$('summary-result');
  text('progress-text',done+' / 3 완료');
  $('progress-fill').style.width=(done/3*100)+'%';
  $('progress').setAttribute('aria-valuenow',String(done));

  if(done===3){
    el.className='mc-summary-result'+(share==='fail'?' warn':' ok');
    el.textContent=share==='fail'
      ?'카메라·마이크·스피커 점검은 완료됐습니다. 화면 공유는 추가 확인이 필요합니다.'
      :'기본 장치 3가지 점검 완료! 실제 회의에 들어가기 전 사용할 앱과 인터넷 연결도 확인해 주세요.';
  }else if(issues.length){
    el.className='mc-summary-result warn';
    el.textContent='확인이 필요한 항목이 있습니다. 아래 안내에 따라 다시 테스트해 주세요.';
  }else{
    el.className='mc-summary-result';
    el.textContent=done
      ?'기본 장치 '+done+'개 완료. 남은 항목도 직접 확인해 주세요.'
      :'카메라, 마이크, 스피커를 차례대로 테스트해 주세요.';
  }
  text('environment',window.isSecureContext
    ?'보안 연결 사용 중 · 카메라/마이크 권한은 사용자가 직접 허용합니다.'
    :'HTTPS가 아닌 환경입니다. 카메라·마이크 기능이 제한될 수 있습니다.');
}

function stopTracks(stream){
  if(stream)stream.getTracks().forEach(function(track){
    try{track.stop();}catch(e){}
  });
}

function controls(kind,active,ready){
  $(kind+'-start').disabled=active;
  $(kind+'-stop').disabled=!active;
  $(kind+'-yes').disabled=!ready;
  $(kind+'-no').disabled=!ready;
  var select=$(kind+'-choice');
  if(select)select.disabled=active;
}

function stopCapture(kind,note){
  state.serial[kind]++;
  var stream=state.streams[kind];
  state.streams[kind]=null;
  stopTracks(stream);

  if(kind==='mic'){
    if(state.micFrame)cancelAnimationFrame(state.micFrame);
    state.micFrame=0;
    state.micAnalyser=null;
    state.micData=null;
    state.micSignal=false;
    var ctx=state.micContext;
    state.micContext=null;
    if(ctx&&ctx.state!=='closed')ctx.close().catch(function(){});
    setMeter(0,'대기 중');
  }else{
    var video=$(kind+'-video');
    video.pause();
    video.srcObject=null;
    $(kind+'-placeholder').hidden=false;
  }

  controls(kind,false,false);
  if(note)message(kind,note);
}

function stopOther(except){
  ['camera','mic','screen'].forEach(function(kind){
    if(kind!==except&&state.streams[kind]){
      stopCapture(kind,'다른 장치 테스트를 시작하여 이전 테스트를 종료했습니다.');
    }
  });
}

function captureError(error,kind){
  var name=error&&error.name||'';
  var label=kind==='camera'?'카메라':kind==='mic'?'마이크':'화면 공유';
  if(name==='NotAllowedError'||name==='PermissionDeniedError'){
    return label+' 권한 요청이 거부되거나 취소되었습니다. 주소창의 사이트 권한과 운영체제 개인정보 설정을 확인한 뒤 다시 시도하세요.';
  }
  if(name==='NotFoundError'||name==='DevicesNotFoundError'){
    return label+' 장치를 찾을 수 없습니다. 장치 연결과 Windows/iPad 설정을 확인하세요.';
  }
  if(name==='NotReadableError'||name==='TrackStartError'){
    return '장치를 시작하지 못했습니다. 다른 앱이 사용 중일 수 있으니 회의 앱을 종료하거나 장치를 다시 연결해 보세요.';
  }
  if(name==='OverconstrainedError'){
    return '선택한 장치를 사용할 수 없습니다. 기본 장치로 변경하여 다시 테스트하세요.';
  }
  if(name==='AbortError'){
    return '장치 시작이 중단되었습니다. 다시 시도해 주세요.';
  }
  if(name==='InvalidStateError'){
    return '현재 탭이 활성화되어 있는지 확인한 뒤 다시 시도해 주세요.';
  }
  if(name==='TypeError'||name==='NotSupportedError'){
    return '현재 브라우저에서 이 기능을 사용할 수 없습니다. 최신 브라우저 또는 실제 화상회의 앱에서 확인해 주세요.';
  }
  return label+' 테스트에 실패했습니다. 장치 연결과 브라우저 권한을 확인한 뒤 다시 시도해 주세요.';
}

async function readPermission(kind){
  var target='permission-'+kind;
  if(!navigator.permissions||!navigator.permissions.query){
    text(target,'권한 상태: 브라우저에서 확인 제한 · 테스트 버튼으로 확인');
    return;
  }
  try{
    var p=await navigator.permissions.query({name:kind==='camera'?'camera':'microphone'});
    var names={granted:'허용됨',denied:'차단됨',prompt:'아직 선택 안 함'};
    text(target,'이 사이트의 권한: '+(names[p.state]||'확인 제한'));
  }catch(e){
    text(target,'권한 상태: 브라우저에서 확인 제한 · 테스트 버튼으로 확인');
  }
}

function fillSelect(name,items,base){
  var el=$(name),previous=el.value;
  el.replaceChildren();
  base.forEach(function(item){
    var o=document.createElement('option');
    o.value=item[0];o.textContent=item[1];el.appendChild(o);
  });
  items.forEach(function(device,index){
    if(!device.deviceId)return;
    var o=document.createElement('option');
    o.value='dev:'+device.deviceId;
    o.textContent=device.label||(name==='camera-choice'?'카메라 ':'마이크 ')+(index+1);
    el.appendChild(o);
  });
  if(Array.from(el.options).some(function(o){return o.value===previous;}))el.value=previous;
}

async function refreshDevices(){
  if(!navigator.mediaDevices||!navigator.mediaDevices.enumerateDevices)return;
  try{
    var devices=await navigator.mediaDevices.enumerateDevices();
    fillSelect('camera-choice',devices.filter(function(d){return d.kind==='videoinput';}),[
      ['default','기본 카메라 (자동)'],['front','전면 카메라 우선'],['rear','후면 카메라 우선']
    ]);
    fillSelect('mic-choice',devices.filter(function(d){return d.kind==='audioinput';}),[
      ['default','기본 마이크 (자동)']
    ]);
  }catch(e){}
}

function getConstraint(kind){
  var choice=$(kind+'-choice').value;
  if(choice.indexOf('dev:')===0)return {deviceId:{exact:choice.slice(4)}};
  if(kind==='camera'){
    if(choice==='front')return {facingMode:{ideal:'user'},width:{ideal:1280},height:{ideal:720}};
    if(choice==='rear')return {facingMode:{ideal:'environment'},width:{ideal:1280},height:{ideal:720}};
    return {width:{ideal:1280},height:{ideal:720}};
  }
  return {echoCancellation:true,noiseSuppression:true,autoGainControl:true};
}

async function startCamera(){
  if(!window.isSecureContext||!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia){
    result('camera','unavailable');
    message('camera','HTTPS 또는 카메라 API가 필요합니다. 최신 Safari, Chrome, Edge 등에서 이 페이지를 다시 열어 주세요.','warn');
    return;
  }
  stopCapture('camera');
  stopOther('camera');
  result('camera','pending');
  var ticket=++state.serial.camera;
  $('camera-start').disabled=true;
  $('camera-stop').disabled=false;
  $('camera-choice').disabled=true;
  message('camera','카메라 권한을 요청하고 있습니다. 브라우저의 허용 버튼을 눌러 주세요.','warn');
  var stream=null;
  try{
    stream=await navigator.mediaDevices.getUserMedia({video:getConstraint('camera'),audio:false});
    if(ticket!==state.serial.camera){stopTracks(stream);return;}
    state.streams.camera=stream;
    var video=$('camera-video');
    video.srcObject=stream;
    await video.play();
    if(ticket!==state.serial.camera)return;
    $('camera-placeholder').hidden=true;
    controls('camera',true,true);
    var track=stream.getVideoTracks()[0];
    var settings=track.getSettings?track.getSettings():{};
    text('camera-info','현재 영상: '+video.videoWidth+' × '+video.videoHeight+
      (settings.frameRate?' · 약 '+Math.round(settings.frameRate)+'fps':'')+
      ' · 영상은 업로드되지 않습니다.');
    message('camera','미리보기가 시작됐습니다. 얼굴과 주변 화면이 정상적으로 보이는지 확인해 주세요.','ok');
    track.onended=function(){
      if(state.streams.camera===stream){
        result('camera','fail');
        stopCapture('camera','카메라 연결이 종료되었습니다. 장치 연결을 확인하고 다시 테스트해 주세요.');
      }
    };
    refreshDevices();
    readPermission('camera');
  }catch(e){
    if(stream)stopTracks(stream);
    if(ticket!==state.serial.camera)return;
    stopCapture('camera');
    result('camera',e.name==='NotSupportedError'?'unavailable':'fail');
    message('camera',captureError(e,'camera'),'fail');
    readPermission('camera');
  }
}

function setMeter(level,label){
  $('mic-meter-fill').style.width=level+'%';
  $('mic-meter').setAttribute('aria-valuenow',String(level));
  text('mic-level-text',label);
}

function meterLoop(){
  var analyser=state.micAnalyser;
  if(!analyser||!state.streams.mic)return;
  analyser.getFloatTimeDomainData(state.micData);
  var sum=0;
  for(var i=0;i<state.micData.length;i++)sum+=state.micData[i]*state.micData[i];
  var rms=Math.sqrt(sum/state.micData.length);
  var level=Math.min(100,Math.round(rms*450));
  setMeter(level,level+' / 100');
  if(rms>0.012&&!state.micSignal){
    state.micSignal=true;
    $('mic-yes').disabled=false;
    message('mic','마이크 입력이 감지됐습니다. 말할 때 게이지가 움직이는지 확인해 주세요.','ok');
  }
  state.micFrame=requestAnimationFrame(meterLoop);
}

async function startMic(){
  if(!window.isSecureContext||!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia){
    result('mic','unavailable');
    message('mic','HTTPS 또는 마이크 API가 필요합니다. 지원되는 최신 브라우저에서 다시 열어 주세요.','warn');
    return;
  }
  if(!(window.AudioContext||window.webkitAudioContext)){
    result('mic','unavailable');
    message('mic','현재 브라우저에서 오디오 입력 분석 기능을 사용할 수 없습니다.','warn');
    return;
  }
  stopCapture('mic');
  stopOther('mic');
  result('mic','pending');
  var ticket=++state.serial.mic;
  $('mic-start').disabled=true;
  $('mic-stop').disabled=false;
  $('mic-choice').disabled=true;
  message('mic','마이크 권한을 요청하고 있습니다. 허용한 뒤 평소 목소리로 말해 주세요.','warn');
  var stream=null;
  try{
    stream=await navigator.mediaDevices.getUserMedia({audio:getConstraint('mic'),video:false});
    if(ticket!==state.serial.mic){stopTracks(stream);return;}
    state.streams.mic=stream;
    var Ctx=window.AudioContext||window.webkitAudioContext;
    var ctx=new Ctx();
    state.micContext=ctx;
    var analyser=ctx.createAnalyser();
    analyser.fftSize=2048;
    var source=ctx.createMediaStreamSource(stream);
    source.connect(analyser);
    /* 마이크 소리를 스피커로 연결하지 않아 피드백을 방지합니다. */
    state.micAnalyser=analyser;
    state.micData=new Float32Array(analyser.fftSize);
    state.micSignal=false;
    await ctx.resume();
    if(ticket!==state.serial.mic)return;
    controls('mic',true,true);
    $('mic-yes').disabled=true;
    setMeter(0,'입력 대기 중');
    message('mic','마이크가 시작됐습니다. 평소 목소리로 말하면 입력 게이지가 움직입니다.');
    state.micFrame=requestAnimationFrame(meterLoop);
    stream.getAudioTracks()[0].onended=function(){
      if(state.streams.mic===stream){
        result('mic','fail');
        stopCapture('mic','마이크 연결이 종료되었습니다. 다른 앱의 사용 여부와 장치 연결을 확인해 주세요.');
      }
    };
    refreshDevices();
    readPermission('mic');
  }catch(e){
    if(stream)stopTracks(stream);
    if(ticket!==state.serial.mic)return;
    stopCapture('mic');
    result('mic',e.name==='NotSupportedError'?'unavailable':'fail');
    message('mic',captureError(e,'mic'),'fail');
    readPermission('mic');
  }
}

function confirmCapture(kind,good){
  if(!state.streams[kind])return;
  if(kind==='mic'&&good&&!state.micSignal)return;
  result(kind,good?'pass':'fail');
  var goodText={
    camera:'카메라 미리보기를 정상적으로 확인했습니다.',
    mic:'실제 마이크 입력이 감지되고 게이지가 움직이는 것을 확인했습니다.',
    screen:'선택한 화면의 공유 미리보기를 정상적으로 확인했습니다.'
  };
  var badText={
    camera:'카메라 화면에 문제가 있습니다. 다른 카메라를 선택하거나 장치 권한을 확인해 주세요.',
    mic:'마이크 입력을 확인하지 못했습니다. 음소거, 입력 장치, 운영체제 마이크 권한을 확인해 주세요.',
    screen:'화면 공유 미리보기에 문제가 있습니다. 다른 창 또는 화면을 선택해 다시 테스트해 주세요.'
  };
  stopCapture(kind);
  message(kind,good?goodText[kind]+' 장치를 종료했습니다.':badText[kind],good?'ok':'fail');
}

function makeTone(){
  var rate=44100,duration=1.35,count=Math.floor(rate*duration);
  var buffer=new ArrayBuffer(44+count*2),view=new DataView(buffer);
  function str(pos,s){for(var i=0;i<s.length;i++)view.setUint8(pos+i,s.charCodeAt(i));}
  str(0,'RIFF');view.setUint32(4,36+count*2,true);str(8,'WAVE');
  str(12,'fmt ');view.setUint32(16,16,true);view.setUint16(20,1,true);
  view.setUint16(22,1,true);view.setUint32(24,rate,true);
  view.setUint32(28,rate*2,true);view.setUint16(32,2,true);
  view.setUint16(34,16,true);str(36,'data');view.setUint32(40,count*2,true);
  var notes=[523.25,659.25,783.99];
  for(var n=0;n<count;n++){
    var t=n/rate,segment=Math.floor(t/0.43),local=t-segment*0.43,sample=0;
    if(segment<3&&local<0.30){
      var fade=Math.min(1,local/0.015,(0.30-local)/0.025);
      sample=0.20*Math.max(0,fade)*Math.sin(2*Math.PI*notes[segment]*local);
    }
    view.setInt16(44+n*2,Math.round(sample*32767),true);
  }
  return URL.createObjectURL(new Blob([buffer],{type:'audio/wav'}));
}

async function playTone(){
  var audio=$('tone');
  if(!state.toneURL)state.toneURL=makeTone();
  if(!audio.src)audio.src=state.toneURL;
  try{
    audio.pause();
    audio.currentTime=0;
    await audio.play();
    state.toneStarted=true;
    $('speaker-yes').disabled=false;
    $('speaker-no').disabled=false;
    message('speaker','테스트 음을 재생했습니다. 실제로 소리가 들리는지 확인한 뒤 아래 버튼을 눌러 주세요.');
  }catch(e){
    state.toneStarted=false;
    message('speaker','소리를 재생하지 못했습니다. 브라우저 음소거, 시스템 음량, 출력 장치와 자동재생 차단 여부를 확인해 주세요.','fail');
  }
}

function stopTone(){
  var audio=$('tone');
  audio.pause();
  try{audio.currentTime=0;}catch(e){}
}

async function chooseSpeaker(){
  if(!navigator.mediaDevices||!navigator.mediaDevices.selectAudioOutput||!$('tone').setSinkId)return;
  try{
    var device=await navigator.mediaDevices.selectAudioOutput();
    await $('tone').setSinkId(device.deviceId);
    state.outputId=device.deviceId;
    state.outputLabel=device.label||'선택한 출력 장치';
    text('speaker-device','출력 장치: '+state.outputLabel);
    state.toneStarted=false;
    $('speaker-yes').disabled=true;
    $('speaker-no').disabled=true;
    result('speaker','pending');
    message('speaker','출력 장치를 선택했습니다. 테스트 음을 다시 재생하여 실제로 들리는지 확인해 주세요.');
  }catch(e){
    message('speaker','출력 장치 선택이 취소되었거나 브라우저에서 허용되지 않았습니다. 시스템 기본 출력 장치로도 테스트할 수 있습니다.','warn');
  }
}

function confirmSpeaker(good){
  if(!state.toneStarted)return;
  stopTone();
  result('speaker',good?'pass':'fail');
  message('speaker',good
    ?'테스트 음을 실제로 들었다고 확인했습니다. 스피커 점검 완료!'
    :'소리가 들리지 않았습니다. 시스템 음량, 브라우저 탭 음소거, 블루투스 연결, 출력 장치를 확인하고 다시 테스트해 주세요.',
    good?'ok':'fail');
}

async function startScreen(){
  if(!window.isSecureContext||!navigator.mediaDevices||!navigator.mediaDevices.getDisplayMedia){
    result('screen','unavailable');
    message('screen','현재 브라우저에서는 웹페이지 내 화면 공유 테스트를 지원하지 않습니다. iPad에서는 실제 화상회의 앱에서 화면 공유를 확인해 주세요.','warn');
    return;
  }
  stopCapture('screen');
  stopOther('screen');
  result('screen','pending');
  var ticket=++state.serial.screen;
  $('screen-start').disabled=true;
  $('screen-stop').disabled=false;
  message('screen','브라우저의 화면 선택창에서 공유할 화면이나 창을 선택해 주세요.','warn');
  var stream=null;
  try{
    stream=await navigator.mediaDevices.getDisplayMedia({video:true,audio:false});
    if(ticket!==state.serial.screen){stopTracks(stream);return;}
    state.streams.screen=stream;
    var video=$('screen-video');
    video.srcObject=stream;
    await video.play();
    if(ticket!==state.serial.screen)return;
    $('screen-placeholder').hidden=true;
    controls('screen',true,true);
    message('screen','공유 미리보기가 시작됐습니다. 선택한 화면이 정상적으로 표시되는지 확인해 주세요.','ok');
    stream.getVideoTracks()[0].onended=function(){
      if(state.streams.screen===stream){
        stopCapture('screen','화면 공유가 종료되었습니다. 다시 테스트하려면 버튼을 눌러 주세요.');
      }
    };
  }catch(e){
    if(stream)stopTracks(stream);
    if(ticket!==state.serial.screen)return;
    stopCapture('screen');
    if(e.name==='NotAllowedError'||e.name==='AbortError'){
      result('screen','pending');
      message('screen','화면 선택이 취소되었거나 권한이 거부되었습니다. 공유하려면 다시 버튼을 눌러 주세요.','warn');
    }else{
      result('screen',e.name==='NotSupportedError'?'unavailable':'fail');
      message('screen',captureError(e,'screen'),'fail');
    }
  }
}

function report(){
  var names={camera:'카메라',mic:'마이크',speaker:'스피커',screen:'화면 공유'};
  var lines=['화상회의 사전 점검 결과','점검 시각: '+new Date().toLocaleString('ko-KR'),''];
  ['camera','mic','speaker','screen'].forEach(function(k){
    lines.push(names[k]+': '+(labels[state.results[k]]||labels.pending)[0]);
  });
  lines.push('','화면 공유는 선택 검사입니다.','실제 회의 서비스 연결 및 인터넷 품질은 검사하지 않았습니다.');
  return lines.join('\n');
}

async function copyReport(){
  var value=report();
  try{
    if(navigator.clipboard&&window.isSecureContext){
      await navigator.clipboard.writeText(value);
    }else{
      var ta=document.createElement('textarea');
      ta.value=value;ta.style.position='fixed';ta.style.opacity='0';
      document.body.appendChild(ta);ta.select();
      var ok=document.execCommand('copy');ta.remove();
      if(!ok)throw new Error('copy');
    }
    text('copy-message','점검 결과를 복사했습니다.');
  }catch(e){
    text('copy-message','자동 복사가 제한되었습니다. 브라우저 권한을 확인한 뒤 다시 시도해 주세요.');
  }
}

function resetAll(){
  ['camera','mic','screen'].forEach(function(k){stopCapture(k);result(k,'pending');});
  stopTone();
  state.toneStarted=false;
  $('speaker-yes').disabled=true;$('speaker-no').disabled=true;
  result('speaker','pending');
  message('camera','카메라 켜기 버튼을 눌러 테스트를 시작하세요.');
  message('mic','마이크 켜기 버튼을 누른 뒤 평소 말하는 목소리로 테스트해 주세요.');
  message('speaker','재생 버튼을 누르면 짧은 테스트 음이 나옵니다.');
  message('screen','테스트 버튼을 누른 뒤 공유할 대상을 직접 선택하세요.');
  text('copy-message','점검 결과를 초기화하고 사용 중이던 장치를 종료했습니다.');
  updateCapabilities();
}

function updateCapabilities(){
  var media=!!(window.isSecureContext&&navigator.mediaDevices&&navigator.mediaDevices.getUserMedia);
  var share=!!(window.isSecureContext&&navigator.mediaDevices&&navigator.mediaDevices.getDisplayMedia);
  if(!media){
    ['camera','mic'].forEach(function(k){
      result(k,'unavailable');
      $(k+'-start').disabled=true;
      message(k,'현재 페이지에서 장치 접근 API를 사용할 수 없습니다. HTTPS로 접속했는지 확인해 주세요.','warn');
    });
  }
  if(!share){
    $('screen-start').disabled=true;
    result('screen','unavailable');
    message('screen','현재 브라우저에서는 웹 화면 공유 테스트가 지원되지 않습니다. 실제 회의 앱에서 확인해 주세요.','warn');
  }
  if(navigator.mediaDevices&&navigator.mediaDevices.selectAudioOutput&&$('tone').setSinkId){
    $('speaker-choose').hidden=false;
  }
  var rtc=typeof RTCPeerConnection==='function';
  text('environment',(window.isSecureContext?'HTTPS 보안 연결':'HTTPS 필요')+
    ' · WebRTC 기본 API: '+(rtc?'지원':'확인 제한')+
    ' · 실제 회의 연결은 별도 확인');
}

function cleanup(){
  ['camera','mic','screen'].forEach(function(k){stopCapture(k);});
  stopTone();
  if(state.toneURL){URL.revokeObjectURL(state.toneURL);state.toneURL=null;}
}

$('camera-start').addEventListener('click',startCamera);
$('camera-stop').addEventListener('click',function(){stopCapture('camera','카메라를 종료했습니다.');});
$('camera-yes').addEventListener('click',function(){confirmCapture('camera',true);});
$('camera-no').addEventListener('click',function(){confirmCapture('camera',false);});
$('mic-start').addEventListener('click',startMic);
$('mic-stop').addEventListener('click',function(){stopCapture('mic','마이크를 종료했습니다.');});
$('mic-yes').addEventListener('click',function(){confirmCapture('mic',true);});
$('mic-no').addEventListener('click',function(){confirmCapture('mic',false);});
$('speaker-play').addEventListener('click',playTone);
$('speaker-stop').addEventListener('click',function(){stopTone();message('speaker','테스트 음을 중지했습니다.');});
$('speaker-choose').addEventListener('click',chooseSpeaker);
$('speaker-yes').addEventListener('click',function(){confirmSpeaker(true);});
$('speaker-no').addEventListener('click',function(){confirmSpeaker(false);});
$('screen-start').addEventListener('click',startScreen);
$('screen-stop').addEventListener('click',function(){stopCapture('screen','화면 공유를 종료했습니다.');});
$('screen-yes').addEventListener('click',function(){confirmCapture('screen',true);});
$('screen-no').addEventListener('click',function(){confirmCapture('screen',false);});
$('copy').addEventListener('click',copyReport);
$('reset').addEventListener('click',resetAll);

if(navigator.mediaDevices&&navigator.mediaDevices.addEventListener){
  navigator.mediaDevices.addEventListener('devicechange',refreshDevices);
}
document.addEventListener('visibilitychange',function(){
  if(document.hidden){
    ['camera','mic','screen'].forEach(function(k){
      if(state.streams[k])stopCapture(k,'페이지가 숨겨져 장치 사용을 종료했습니다. 다시 테스트하려면 버튼을 눌러 주세요.');
    });
    stopTone();
  }
});
window.addEventListener('pagehide',cleanup,{once:true});

updateSummary();
updateCapabilities();
refreshDevices();
readPermission('camera');
readPermission('mic');

})();
</script>
</div>



<h2 class="wp-block-heading">화상회의 사전 점검, 회의 시작 전에 미리 확인하세요</h2>



<p class="wp-block-paragraph">온라인 회의나 면접에 접속했는데 카메라가 켜지지 않거나, 말을 해도 상대방이 목소리를 듣지 못하는 상황은 생각보다 자주 발생합니다. 특히 회의 시작 직전에 장치 문제를 발견하면 Windows 설정과 브라우저 권한을 찾느라 시간을 허비하기 쉽습니다.</p>



<p class="wp-block-paragraph">이럴 때는 회의에 입장하기 전에 <strong>카메라, 마이크, 스피커가 실제로 작동하는지 미리 확인</strong>해 두는 것이 좋습니다.</p>



<p class="wp-block-paragraph">위의 화상회의 사전 점검 도구는 별도의 프로그램 설치나 회원가입 없이 브라우저에서 바로 사용할 수 있습니다. Windows 노트북과 데스크톱은 물론, 지원되는 iPad 브라우저에서도 카메라와 마이크, 스피커를 테스트할 수 있습니다. 화면 공유 기능은 브라우저에서 지원하는 경우에만 별도로 확인할 수 있습니다.</p>



<p class="wp-block-paragraph">단순히 장치가 연결되어 있는지만 표시하는 것이 아니라, 사용자가 직접 화면을 보고, 목소리를 입력하고, 테스트 음을 들어본 뒤 정상 작동 여부를 확인하는 방식입니다.</p>



<h2 class="wp-block-heading">화상회의 사전 점검 사용 방법</h2>



<p class="wp-block-paragraph">처음 사용하는 분이라도 순서대로 진행하면 어렵지 않습니다.</p>



<ol class="wp-block-list">
<li>위의 도구에서 <strong>카메라 켜기</strong> 버튼을 누르고 브라우저의 카메라 권한을 허용합니다.</li>



<li>내 얼굴이나 주변 화면이 미리보기에 정상적으로 표시되는지 확인한 뒤 <strong>네, 잘 보여요</strong>를 선택합니다.</li>



<li><strong>마이크 켜기</strong> 버튼을 누르고 권한을 허용한 다음 평소 목소리로 말해봅니다.</li>



<li>입력 게이지가 움직이는 것을 확인하고 <strong>네, 입력이 감지돼요</strong>를 선택합니다.</li>



<li><strong>테스트 음 재생</strong> 버튼을 눌러 실제로 소리가 들리는지 확인합니다.</li>



<li>필요한 경우 화면 공유 테스트를 실행하고 선택한 화면이 정상적으로 표시되는지 확인합니다.</li>



<li>점검이 끝나면 상단의 완료 상태를 확인하고, 필요하다면 <strong>점검 결과 복사</strong> 버튼으로 결과를 복사합니다.</li>
</ol>



<p class="wp-block-paragraph">카메라·마이크·스피커는 기본 점검 항목이며, 화면 공유는 선택 항목입니다. 화면 공유를 지원하지 않는 브라우저라고 해서 카메라나 마이크까지 사용할 수 없는 것은 아닙니다.</p>



<h2 class="wp-block-heading">1. 웹캠 테스트로 내 화면이 실제로 보이는지 확인</h2>



<p class="wp-block-paragraph">웹캠 테스트는 카메라 장치가 연결되어 있다는 정보만 확인하는 것이 아니라 <strong>실제 카메라 영상을 브라우저에 표시하는 방식</strong>입니다.</p>



<p class="wp-block-paragraph">카메라 켜기 버튼을 누르면 브라우저에서 권한 허용을 요청할 수 있습니다. 권한을 허용하면 내장 웹캠이나 연결된 카메라의 미리보기가 나타납니다.</p>



<p class="wp-block-paragraph">화면이 정상적으로 보이는지, 카메라가 가려져 있지는 않은지, 화상회의에서 사용할 구도가 적절한지 직접 확인할 수 있습니다.</p>



<p class="wp-block-paragraph">노트북에 내장 카메라와 외장 웹캠이 모두 연결되어 있다면 장치 선택 메뉴에서 원하는 카메라를 선택할 수 있습니다. iPad처럼 전면과 후면 카메라가 있는 기기에서는 지원되는 환경에서 카메라 방향을 우선 선택할 수도 있습니다.</p>



<p class="wp-block-paragraph">테스트를 마치면 <strong>네, 잘 보여요</strong> 버튼을 눌러 카메라 점검을 완료할 수 있습니다. 문제가 있다면 다른 카메라를 선택하거나 장치 연결 및 권한 설정을 확인해 보세요.</p>



<h2 class="wp-block-heading">2. 마이크 테스트로 내 목소리가 입력되는지 확인</h2>



<p class="wp-block-paragraph">마이크는 연결되어 있다고 해서 반드시 정상적으로 입력되는 것은 아닙니다. Windows에서 다른 입력 장치가 선택되어 있거나, 브라우저 권한이 차단되어 있거나, 다른 프로그램이 장치를 사용 중일 수도 있습니다.</p>



<p class="wp-block-paragraph">위의 마이크 테스트는 실제 마이크 입력을 분석해 <strong>말할 때 움직이는 입력 게이지</strong>를 보여줍니다.</p>



<p class="wp-block-paragraph">마이크 켜기 버튼을 누른 뒤 권한을 허용하고 평소 회의에서 말하는 목소리로 이야기해 보세요. 소리가 입력되면 게이지가 움직입니다.</p>



<p class="wp-block-paragraph">게이지가 전혀 움직이지 않는다면 마이크가 음소거되어 있는지, 올바른 입력 장치를 선택했는지, 운영체제와 브라우저에서 마이크 사용을 허용했는지 확인하는 것이 좋습니다.</p>



<p class="wp-block-paragraph">이 테스트는 마이크 소리를 스피커로 되돌려 재생하지 않으므로 하울링을 방지할 수 있습니다. 또한 목소리를 녹음하거나 서버에 저장하지 않고 현재 브라우저 안에서 입력 크기만 확인합니다.</p>



<h2 class="wp-block-heading">3. 스피커 테스트로 소리가 실제로 들리는지 확인</h2>



<p class="wp-block-paragraph">마이크가 정상이어도 스피커나 헤드폰에서 소리가 나오지 않으면 회의에 참여하기 어렵습니다.</p>



<p class="wp-block-paragraph">스피커 테스트에서는 <strong>테스트 음 재생</strong> 버튼을 누르면 짧은 소리가 재생됩니다. 소리가 실제로 들리는지 확인한 뒤 <strong>네, 소리가 들려요</strong>를 선택하면 됩니다.</p>



<p class="wp-block-paragraph">브라우저에서 소리 재생이 성공했다는 사실만으로 사용자가 실제로 소리를 들었다고 판단하지는 않습니다. 따라서 직접 듣고 확인하는 과정이 중요합니다.</p>



<p class="wp-block-paragraph">소리가 들리지 않는다면 컴퓨터의 시스템 음량, 브라우저 탭 음소거, 헤드폰 연결 상태, 블루투스 이어폰의 출력 장치 설정 등을 확인해 보세요. 지원되는 브라우저에서는 출력 장치를 직접 선택하는 기능도 사용할 수 있습니다.</p>



<h2 class="wp-block-heading">4. 화면 공유 테스트로 발표 화면을 미리 확인</h2>



<p class="wp-block-paragraph">화면 공유를 자주 사용하는 직장인이라면 발표 자료나 브라우저 창이 제대로 공유되는지도 미리 확인해 두면 좋습니다.</p>



<p class="wp-block-paragraph">지원되는 브라우저에서는 <strong>화면 공유 테스트</strong> 버튼을 누른 뒤 전체 화면, 특정 창 또는 브라우저 탭 중 공유할 대상을 선택할 수 있습니다.</p>



<p class="wp-block-paragraph">공유를 시작하면 선택한 화면이 미리보기에 표시됩니다. 발표 자료가 정상적으로 보이는지 확인한 뒤 테스트를 종료하면 됩니다.</p>



<p class="wp-block-paragraph">다만 화면 공유 기능은 운영체제와 브라우저에 따라 지원 범위가 다릅니다. 특히 iPad에서는 웹페이지 내부의 화면 공유 테스트가 제한될 수 있으므로, 지원되지 않는다는 안내가 나오면 실제로 사용할 화상회의 앱에서 화면 공유 기능을 확인해 주세요.</p>



<p class="wp-block-paragraph">화면 공유를 테스트할 때는 개인정보나 업무상 민감한 정보가 표시된 창을 선택하지 않는 것이 좋습니다.</p>



<h2 class="wp-block-heading">Windows 노트북과 iPad에서도 사용할 수 있나요?</h2>



<p class="wp-block-paragraph">네. 최신 브라우저에서 필요한 웹 기능을 지원하고 권한을 허용한다면 Windows 노트북, 데스크톱, iPad 등에서 사용할 수 있습니다.</p>



<p class="wp-block-paragraph">Windows에서는 Chrome이나 Edge 등의 브라우저에서 카메라·마이크·스피커를 테스트할 수 있으며, 지원되는 환경에서는 화면 공유와 출력 장치 선택도 가능합니다.</p>



<p class="wp-block-paragraph">iPad에서는 Safari를 통해 카메라와 마이크 권한을 허용한 뒤 실제 미리보기와 입력 게이지를 확인할 수 있습니다. 스피커 테스트 역시 테스트 음이 들리는지 직접 확인하는 방식입니다.</p>



<p class="wp-block-paragraph">다만 모든 기기와 브라우저에서 동일한 기능을 제공하는 것은 아닙니다. 화면 공유나 출력 장치 선택처럼 지원이 제한되는 기능은 해당 브라우저에서 사용할 수 없는 것으로 안내됩니다.</p>



<h2 class="wp-block-heading">카메라나 마이크가 작동하지 않을 때 확인할 것</h2>



<p class="wp-block-paragraph">화상회의 사전 점검 중 장치가 작동하지 않는다면 다음과 같은 원인을 확인해 보세요.</p>



<p class="wp-block-paragraph"><strong>브라우저 권한이 차단된 경우</strong></p>



<p class="wp-block-paragraph">주소창 왼쪽의 사이트 정보 또는 권한 설정에서 카메라와 마이크가 허용되어 있는지 확인하세요. 권한을 다시 허용한 뒤 페이지를 새로고침하고 테스트할 수 있습니다.</p>



<p class="wp-block-paragraph"><strong>Windows 개인정보 설정이 차단된 경우</strong></p>



<p class="wp-block-paragraph">Windows 설정의 개인정보 및 보안 메뉴에서 카메라 또는 마이크에 대한 앱 접근 권한이 허용되어 있는지 확인해야 합니다. 브라우저 자체가 장치 접근을 허용받지 못하면 웹페이지에서도 사용할 수 없습니다.</p>



<p class="wp-block-paragraph"><strong>다른 프로그램이 장치를 사용 중인 경우</strong></p>



<p class="wp-block-paragraph">일부 환경에서는 다른 회의 프로그램이나 카메라 앱이 장치를 점유하고 있어 테스트가 실패할 수 있습니다. 사용 중인 앱을 종료하거나 장치를 다시 연결한 뒤 확인해 보세요.</p>



<p class="wp-block-paragraph"><strong>잘못된 장치를 선택한 경우</strong></p>



<p class="wp-block-paragraph">외장 웹캠, USB 마이크, 블루투스 헤드셋을 사용하는 경우 기본 장치가 다른 장치로 설정되어 있을 수 있습니다. 도구의 장치 선택 메뉴에서 원하는 장치를 선택해 보세요.</p>



<p class="wp-block-paragraph"><strong>HTTPS 또는 브라우저 지원 문제</strong></p>



<p class="wp-block-paragraph">카메라와 마이크에 접근하는 웹 기능은 보안 연결이 필요합니다. 지원되는 최신 브라우저에서 HTTPS 주소로 접속했는지 확인해 주세요.</p>



<h2 class="wp-block-heading">점검 결과는 어떤 의미인가요?</h2>



<p class="wp-block-paragraph">상단의 회의 준비 상태는 카메라, 마이크, 스피커의 기본 점검 진행 상황을 보여줍니다.</p>



<p class="wp-block-paragraph">세 가지 모두 직접 정상 작동을 확인하면 <strong>기본 장치 점검 완료</strong>로 표시됩니다. 화면 공유는 선택 검사이므로 별도로 확인할 수 있습니다.</p>



<p class="wp-block-paragraph">다만 이 결과가 실제 Zoom, Google Meet, Microsoft Teams 등의 회의 연결까지 보장하는 것은 아닙니다. 각 서비스의 권한 설정, 회사 네트워크 정책, 인터넷 연결 상태, 상대방에게 전달되는 영상과 음성은 별도로 영향을 받을 수 있습니다.</p>



<p class="wp-block-paragraph">따라서 중요한 온라인 면접이나 발표가 있다면 이 도구로 장치를 확인한 뒤, 실제 사용할 회의 서비스에서도 미리 입장 테스트를 진행하는 것이 좋습니다.</p>



<h2 class="wp-block-heading">개인정보는 어떻게 처리되나요?</h2>



<p class="wp-block-paragraph">이 도구는 카메라와 마이크를 자동으로 켜지 않습니다. 사용자가 직접 테스트 버튼을 누르고 브라우저 권한을 허용한 경우에만 장치에 접근합니다.</p>



<p class="wp-block-paragraph">카메라 영상은 현재 페이지의 미리보기에 사용되고, 마이크 음성은 입력 게이지를 표시하는 데 사용됩니다. <strong>진단 도구 자체에서는 영상이나 음성을 녹음·녹화하거나 서버로 업로드하지 않습니다.</strong></p>



<p class="wp-block-paragraph">테스트가 끝나거나 장치를 끄면 사용 중인 카메라·마이크 스트림을 종료하도록 구성되어 있습니다. 화면 공유도 사용자가 직접 시작하고 중지할 수 있습니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">자주 묻는 질문 FAQ</h2>



<h3 class="wp-block-heading">화상회의 사전 점검은 무료인가요?</h3>



<p class="wp-block-paragraph">네. 별도의 프로그램 설치나 회원가입 없이 무료로 사용할 수 있습니다. 카메라, 마이크, 스피커 및 지원되는 환경의 화면 공유를 브라우저에서 직접 테스트할 수 있습니다.</p>



<h3 class="wp-block-heading">웹캠 테스트를 하면 영상이 서버에 저장되나요?</h3>



<p class="wp-block-paragraph">아니요. 이 도구의 카메라 미리보기는 사용자의 브라우저 안에서 처리됩니다. 영상 파일을 생성하거나 서버로 업로드하는 기능은 포함되어 있지 않습니다.</p>



<h3 class="wp-block-heading">마이크 테스트에서 제 목소리가 녹음되나요?</h3>



<p class="wp-block-paragraph">아니요. 마이크 입력 신호의 크기를 분석해 게이지로 보여주는 방식이며, 음성을 녹음하거나 저장하지 않습니다.</p>



<h3 class="wp-block-heading">마이크 게이지가 움직이면 정상이라고 봐도 되나요?</h3>



<p class="wp-block-paragraph">마이크 입력이 감지된다는 뜻입니다. 다만 실제 회의에서 상대방에게 목소리가 전달되는지, 음질이 적절한지까지 보장하지는 않습니다. 중요한 회의라면 실제 회의 서비스에서도 확인하는 것이 좋습니다.</p>



<h3 class="wp-block-heading">스피커 테스트를 눌렀는데 소리가 안 들립니다. 어떻게 해야 하나요?</h3>



<p class="wp-block-paragraph">시스템 음량과 브라우저 탭 음소거 여부를 먼저 확인하세요. 헤드폰이나 블루투스 이어폰을 사용한다면 현재 출력 장치가 올바르게 선택되어 있는지도 확인해야 합니다.</p>



<h3 class="wp-block-heading">iPad에서도 카메라와 마이크 테스트가 가능한가요?</h3>



<p class="wp-block-paragraph">지원되는 최신 Safari 등에서 카메라와 마이크 접근을 허용하면 사용할 수 있습니다. 다만 iPadOS와 브라우저 버전에 따라 일부 장치 선택 또는 화면 공유 기능은 제한될 수 있습니다.</p>



<h3 class="wp-block-heading">화면 공유가 지원되지 않으면 화상회의도 할 수 없나요?</h3>



<p class="wp-block-paragraph">아닙니다. 웹페이지의 화면 공유 테스트가 제한된다는 것은 이 도구에서 해당 API를 사용할 수 없다는 의미입니다. 카메라·마이크·스피커 사용 가능 여부와는 별개이며, 실제 화상회의 앱에서는 화면 공유가 가능할 수도 있습니다.</p>



<h3 class="wp-block-heading">카메라 권한을 차단했는데 다시 허용할 수 있나요?</h3>



<p class="wp-block-paragraph">네. 브라우저 주소창의 사이트 권한 설정에서 카메라 접근을 허용한 뒤 다시 테스트할 수 있습니다. Windows나 iPad의 운영체제 설정에서도 카메라 접근이 허용되어 있어야 합니다.</p>



<h3 class="wp-block-heading">Zoom, Google Meet, Teams에서도 정상 작동하는지 확인할 수 있나요?</h3>



<p class="wp-block-paragraph">이 도구는 브라우저의 기본 장치 입력·출력을 점검하는 용도입니다. 특정 화상회의 서비스의 로그인, 서버 연결, 회사 정책 또는 상대방에게 전달되는 품질까지 직접 검사하는 것은 아니므로 실제 서비스에서도 최종 확인이 필요합니다.</p>



<h3 class="wp-block-heading">인터넷 속도나 Ping도 측정하나요?</h3>



<p class="wp-block-paragraph">아니요. 이 도구는 카메라·마이크·스피커·화면 공유에 초점을 맞춘 장치 점검 도구입니다. 다운로드 속도, 업로드 속도, Ping 등의 인터넷 품질은 측정하지 않습니다.</p>



<h3 class="wp-block-heading">점검 결과를 다른 사람에게 전달할 수 있나요?</h3>



<p class="wp-block-paragraph">네. <strong>점검 결과 복사</strong> 버튼을 이용하면 각 장치의 완료 또는 확인 필요 상태를 텍스트로 복사할 수 있습니다. 온라인 면접 담당자나 회사 IT 지원팀에 현재 점검 상황을 전달할 때 활용할 수 있습니다.</p>



<h2 class="wp-block-heading">회의 시작 전에 3분만 확인해 보세요</h2>



<p class="wp-block-paragraph">온라인 회의에서 가장 난감한 문제는 입장한 뒤 카메라와 마이크가 작동하지 않는다는 사실을 알게 되는 것입니다. 회의 전에 웹캠 화면, 마이크 입력, 스피커 소리를 한 번씩 확인해 두면 이런 상황을 줄이는 데 도움이 됩니다.</p>



<p class="wp-block-paragraph">위의 <strong>화상회의 사전 점검 무료 도구</strong>를 이용해 기본 장치 세 가지를 확인하고, 발표가 예정되어 있다면 화면 공유도 미리 테스트해 보세요. 설치 없이 브라우저에서 바로 사용할 수 있어 재택근무, 온라인 수업, 화상 면접을 준비할 때 간편하게 활용할 수 있습니다.</p>



<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img loading="lazy" decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/meeting-device-test/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/09/meeting-device-test.jpg" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1590</post-id>	</item>
		<item>
		<title>윈도우 32비트 64비트 확인, 내 PC·브라우저 환경 진단 무료 도구</title>
		<link>https://snsnote.com/windows-32bit-64bit-check/</link>
					<comments>https://snsnote.com/windows-32bit-64bit-check/#respond</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Sun, 06 Sep 2026 19:49:07 +0000</pubDate>
				<category><![CDATA[유용한 도구]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1584</guid>

					<description><![CDATA[윈도우 32비트 64비트 확인 방법 프로그램을 다운로드하다 보면 x86, x64, 32-bit, 64-bit, ARM64처럼 여러 종류의 설치 파일이 표시되는 경우가 있습니다. 평소에는 신경 쓸 일이 거의...]]></description>
										<content:encoded><![CDATA[<div id="sns-browser-diagnostic">

<style>
#sns-browser-diagnostic {
  --bg:#f8fafc;
  --card:#ffffff;
  --text:#0f172a;
  --muted:#64748b;
  --line:#e2e8f0;
  --blue:#2563eb;
  --blue-soft:#eff6ff;
  --green:#15803d;
  --green-soft:#f0fdf4;
  --red:#b91c1c;
  --red-soft:#fef2f2;
  --amber:#a16207;
  --amber-soft:#fffbeb;

  width:100%;
  max-width:920px;
  margin:24px auto;
  color:var(--text);
  line-height:1.55;
  box-sizing:border-box;

  font-family:
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    Roboto,
    "Noto Sans KR",
    Arial,
    sans-serif;
}

#sns-browser-diagnostic *,
#sns-browser-diagnostic *::before,
#sns-browser-diagnostic *::after {
  box-sizing:border-box;
}

#sns-browser-diagnostic .bd-wrap {
  background:var(--bg);
  border:1px solid var(--line);
  border-radius:18px;
  padding:22px;
}

#sns-browser-diagnostic .bd-title {
  margin:0;
  padding:13px 18px;
  border-radius:10px;
  color:#fff;
  font-size:25px;
  font-weight:800;
  letter-spacing:-.03em;
  background:linear-gradient(90deg,#3b82f6,#7c3aed);
  box-shadow:0 5px 15px rgba(37,99,235,.2);
}

#sns-browser-diagnostic .bd-desc {
  margin:8px 0 0;
  color:var(--muted);
  font-size:14px;
}

#sns-browser-diagnostic .bd-summary {
  margin-top:18px;
  padding:18px;
  border:1px solid #c7d2fe;
  border-radius:14px;
  background:linear-gradient(135deg,#eff6ff,#f5f3ff);
}

#sns-browser-diagnostic .bd-summary-title {
  margin:0 0 12px;
  font-size:18px;
  font-weight:800;
}

#sns-browser-diagnostic .bd-summary-main {
  font-size:20px;
  font-weight:800;
  line-height:1.5;
}

#sns-browser-diagnostic .bd-summary-sub {
  margin-top:6px;
  color:var(--muted);
  font-size:13px;
}

#sns-browser-diagnostic .bd-summary-result {
  margin-top:12px;
  padding:10px 12px;
  border-radius:9px;
  font-size:13px;
  font-weight:700;
}

#sns-browser-diagnostic .summary-ok {
  color:var(--green);
  background:var(--green-soft);
}

#sns-browser-diagnostic .summary-warn {
  color:var(--amber);
  background:var(--amber-soft);
}

#sns-browser-diagnostic .bd-main {
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:12px;
  margin:18px 0;
}

#sns-browser-diagnostic .bd-main-card {
  background:var(--card);
  border:1px solid var(--line);
  border-radius:14px;
  padding:16px;
  min-height:112px;
}

#sns-browser-diagnostic .bd-main-card.highlight {
  border-color:#bfdbfe;
  background:var(--blue-soft);
}

#sns-browser-diagnostic .bd-label {
  display:block;
  color:var(--muted);
  font-size:12px;
  font-weight:700;
  margin-bottom:7px;
}

#sns-browser-diagnostic .bd-main-value {
  display:block;
  font-size:19px;
  font-weight:800;
  line-height:1.35;
}

#sns-browser-diagnostic .bd-main-sub {
  display:block;
  margin-top:6px;
  color:var(--muted);
  font-size:12px;
}

#sns-browser-diagnostic .bd-download-guide {
  display:none;
  margin-bottom:18px;
  padding:17px;
  border-radius:14px;
  border:1px solid #bfdbfe;
  background:var(--blue-soft);
}

#sns-browser-diagnostic .bd-download-title {
  margin:0 0 8px;
  font-size:16px;
  font-weight:800;
  color:#1d4ed8;
}

#sns-browser-diagnostic .bd-download-main {
  margin-bottom:10px;
  font-size:20px;
  font-weight:900;
}

#sns-browser-diagnostic .bd-download-tags {
  display:flex;
  flex-wrap:wrap;
  gap:6px;
  margin-top:8px;
}

#sns-browser-diagnostic .bd-tag {
  display:inline-flex;
  padding:5px 9px;
  border-radius:999px;
  background:#fff;
  border:1px solid #bfdbfe;
  color:#1d4ed8;
  font-size:12px;
  font-weight:800;
}

#sns-browser-diagnostic .bd-download-note {
  margin:10px 0 0;
  color:#475569;
  font-size:12px;
}

#sns-browser-diagnostic .bd-section-title {
  margin:26px 0 10px;
  padding:7px 0 7px 13px;
  border-left:5px solid #6366f1;
  border-bottom:1px solid #a78bfa;
  font-size:17px;
  font-weight:800;
}

#sns-browser-diagnostic .bd-grid {
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:10px;
}

#sns-browser-diagnostic .bd-row {
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:14px;
  min-height:64px;
  padding:13px 14px;
  background:var(--card);
  border:1px solid var(--line);
  border-radius:12px;
}

#sns-browser-diagnostic .bd-row-left {
  min-width:0;
}

#sns-browser-diagnostic .bd-row-name {
  display:block;
  font-size:14px;
  font-weight:700;
}

#sns-browser-diagnostic .bd-row-help {
  display:block;
  margin-top:2px;
  color:var(--muted);
  font-size:11px;
}

#sns-browser-diagnostic .bd-value {
  flex:0 1 auto;
  text-align:right;
  font-size:13px;
  font-weight:700;
  word-break:break-word;
}

#sns-browser-diagnostic .bd-status {
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width:72px;
  padding:5px 9px;
  border-radius:999px;
  font-size:12px;
  font-weight:800;
  white-space:nowrap;
}

#sns-browser-diagnostic .is-ok {
  color:var(--green);
  background:var(--green-soft);
}

#sns-browser-diagnostic .is-no {
  color:var(--red);
  background:var(--red-soft);
}

#sns-browser-diagnostic .is-warn {
  color:var(--amber);
  background:var(--amber-soft);
}

#sns-browser-diagnostic .is-info {
  color:#1d4ed8;
  background:var(--blue-soft);
}

#sns-browser-diagnostic .bd-meaning-grid {
  display:grid;
  grid-template-columns:1fr;
  gap:9px;
}

#sns-browser-diagnostic .bd-meaning {
  display:flex;
  gap:12px;
  align-items:flex-start;
  padding:14px;
  border:1px solid var(--line);
  border-radius:12px;
  background:#fff;
}

#sns-browser-diagnostic .bd-meaning-icon {
  flex:0 0 30px;
  width:30px;
  height:30px;
  display:flex;
  align-items:center;
  justify-content:center;
  border-radius:50%;
  font-size:15px;
  font-weight:900;
}

#sns-browser-diagnostic .meaning-ok .bd-meaning-icon {
  background:var(--green-soft);
  color:var(--green);
}

#sns-browser-diagnostic .meaning-warn .bd-meaning-icon {
  background:var(--amber-soft);
  color:var(--amber);
}

#sns-browser-diagnostic .meaning-no .bd-meaning-icon {
  background:var(--red-soft);
  color:var(--red);
}

#sns-browser-diagnostic .bd-meaning-name {
  margin:0;
  font-size:14px;
  font-weight:800;
}

#sns-browser-diagnostic .bd-meaning-desc {
  margin:3px 0 0;
  color:var(--muted);
  font-size:12px;
}

#sns-browser-diagnostic .bd-actions {
  display:flex;
  flex-wrap:wrap;
  gap:9px;
  margin-top:18px;
}

#sns-browser-diagnostic .bd-btn {
  appearance:none;
  border:0;
  border-radius:10px;
  padding:11px 15px;
  font-family:inherit;
  font-size:13px;
  font-weight:800;
  cursor:pointer;
}

#sns-browser-diagnostic .bd-btn.primary {
  color:#fff;
  background:var(--blue);
}

#sns-browser-diagnostic .bd-btn.secondary {
  color:var(--text);
  background:#fff;
  border:1px solid var(--line);
}

#sns-browser-diagnostic .bd-note {
  margin:14px 0 0;
  color:var(--muted);
  font-size:11px;
}

#sns-browser-diagnostic .bd-noscript {
  margin-top:15px;
  padding:14px;
  border-radius:12px;
  background:var(--red-soft);
  color:var(--red);
  font-weight:700;
}

@media(max-width:760px) {

  #sns-browser-diagnostic .bd-wrap {
    padding:16px;
    border-radius:14px;
  }

  #sns-browser-diagnostic .bd-title {
    font-size:21px;
  }

  #sns-browser-diagnostic .bd-main,
  #sns-browser-diagnostic .bd-grid {
    grid-template-columns:1fr;
  }

  #sns-browser-diagnostic .bd-main-card {
    min-height:auto;
  }

  #sns-browser-diagnostic .bd-summary-main {
    font-size:18px;
  }
}

@media(max-width:460px) {

  #sns-browser-diagnostic .bd-row {
    align-items:flex-start;
  }

  #sns-browser-diagnostic .bd-value {
    max-width:54%;
  }

  #sns-browser-diagnostic .bd-actions {
    display:grid;
    grid-template-columns:1fr;
  }

  #sns-browser-diagnostic .bd-btn {
    width:100%;
  }
}
</style>


<div class="bd-wrap">

  <div class="bd-head">

    <h2 class="bd-title">
      내 브라우저·PC 환경 진단
    </h2>

    <p class="bd-desc">
      Windows 32·64비트, 브라우저 버전과 주요 웹 기능을
      설치 없이 한 번에 확인합니다.
    </p>

  </div>


  <noscript>
    <div class="bd-noscript">
      JavaScript가 꺼져 있어 진단을 실행할 수 없습니다.
    </div>
  </noscript>


  <div class="bd-summary">

    <div class="bd-summary-title">
      내 PC 한눈에 보기
    </div>

    <div
      class="bd-summary-main"
      id="bd-summary-main"
    >
      환경을 확인하고 있습니다...
    </div>

    <div
      class="bd-summary-sub"
      id="bd-summary-sub"
    >
      잠시만 기다려 주세요.
    </div>

    <div
      class="bd-summary-result summary-ok"
      id="bd-summary-result"
    >
      브라우저 기능을 진단하고 있습니다.
    </div>

  </div>


  <div class="bd-main">

    <div class="bd-main-card">

      <span class="bd-label">
        운영체제
      </span>

      <strong
        class="bd-main-value"
        id="bd-os"
      >
        확인 중...
      </strong>

      <span
        class="bd-main-sub"
        id="bd-os-detail"
      >
        시스템 정보 확인 중
      </span>

    </div>


    <div class="bd-main-card highlight">

      <span class="bd-label">
        시스템 종류
      </span>

      <strong
        class="bd-main-value"
        id="bd-bitness"
      >
        확인 중...
      </strong>

      <span
        class="bd-main-sub"
        id="bd-arch"
      >
        CPU 아키텍처 확인 중
      </span>

    </div>


    <div class="bd-main-card">

      <span class="bd-label">
        브라우저
      </span>

      <strong
        class="bd-main-value"
        id="bd-browser"
      >
        확인 중...
      </strong>

      <span
        class="bd-main-sub"
        id="bd-browser-detail"
      >
        버전 확인 중
      </span>

    </div>

  </div>


  <div
    class="bd-download-guide"
    id="bd-download-guide"
  ></div>


  <h3 class="bd-section-title">
    브라우저 기능
  </h3>


  <div class="bd-grid">

    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">JavaScript</span>
        <span class="bd-row-help">웹페이지 기능 실행 가능 여부</span>
      </div>
      <div class="bd-value">
        <span class="bd-status is-ok">정상 작동</span>
      </div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">쿠키</span>
        <span class="bd-row-help">로그인·설정 저장 등에 사용</span>
      </div>
      <div class="bd-value" id="bd-cookie">확인 중...</div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">LocalStorage</span>
        <span class="bd-row-help">브라우저 데이터 저장 기능</span>
      </div>
      <div class="bd-value" id="bd-local">확인 중...</div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">SessionStorage</span>
        <span class="bd-row-help">현재 탭의 임시 데이터 저장</span>
      </div>
      <div class="bd-value" id="bd-session">확인 중...</div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">팝업</span>
        <span class="bd-row-help">결제·로그인·본인인증 창 확인</span>
      </div>
      <div class="bd-value" id="bd-popup">
        <span class="bd-status is-warn">테스트 필요</span>
      </div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">WebGL</span>
        <span class="bd-row-help">웹 3D·그래픽 기능 지원</span>
      </div>
      <div class="bd-value" id="bd-webgl">확인 중...</div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">WebP 이미지</span>
        <span class="bd-row-help">최신 웹 이미지 형식 지원</span>
      </div>
      <div class="bd-value" id="bd-webp">확인 중...</div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">AVIF 이미지</span>
        <span class="bd-row-help">고효율 이미지 형식 지원</span>
      </div>
      <div class="bd-value" id="bd-avif">확인 중...</div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">클립보드</span>
        <span class="bd-row-help">웹페이지 복사 기능 지원</span>
      </div>
      <div class="bd-value" id="bd-clipboard">확인 중...</div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">PDF 브라우저 보기</span>
        <span class="bd-row-help">PDF를 브라우저에서 바로 열 수 있는지 확인</span>
      </div>
      <div class="bd-value" id="bd-pdf">확인 중...</div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">웹사이트 알림</span>
        <span class="bd-row-help">브라우저 알림 권한 상태</span>
      </div>
      <div class="bd-value" id="bd-notification">확인 중...</div>
    </div>

  </div>


  <h3 class="bd-section-title">
    웹사이트 사용 환경
  </h3>


  <div class="bd-meaning-grid">

    <div class="bd-meaning" id="meaning-storage">

      <div class="bd-meaning-icon">·</div>

      <div>
        <p class="bd-meaning-name">로그인·설정 저장</p>
        <p class="bd-meaning-desc" id="meaning-storage-text">
          확인 중...
        </p>
      </div>

    </div>


    <div class="bd-meaning" id="meaning-copy">

      <div class="bd-meaning-icon">·</div>

      <div>
        <p class="bd-meaning-name">웹페이지 복사 기능</p>
        <p class="bd-meaning-desc" id="meaning-copy-text">
          확인 중...
        </p>
      </div>

    </div>


    <div class="bd-meaning" id="meaning-popup">

      <div class="bd-meaning-icon">!</div>

      <div>
        <p class="bd-meaning-name">결제·본인인증·새 창</p>
        <p class="bd-meaning-desc" id="meaning-popup-text">
          팝업 차단 테스트 버튼을 눌러 확인할 수 있습니다.
        </p>
      </div>

    </div>


    <div class="bd-meaning" id="meaning-graphics">

      <div class="bd-meaning-icon">·</div>

      <div>
        <p class="bd-meaning-name">지도·3D·웹 그래픽</p>
        <p class="bd-meaning-desc" id="meaning-graphics-text">
          확인 중...
        </p>
      </div>

    </div>


    <div class="bd-meaning" id="meaning-images">

      <div class="bd-meaning-icon">·</div>

      <div>
        <p class="bd-meaning-name">최신 이미지 표시</p>
        <p class="bd-meaning-desc" id="meaning-images-text">
          확인 중...
        </p>
      </div>

    </div>

  </div>


  <h3 class="bd-section-title">
    화면·언어 정보
  </h3>


  <div class="bd-grid">

    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">브라우저 화면 크기</span>
        <span class="bd-row-help">운영체제의 화면 배율이 적용된 크기</span>
      </div>
      <div class="bd-value" id="bd-screen">확인 중...</div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">화면 픽셀 비율</span>
        <span class="bd-row-help">화면 배율·브라우저 확대/축소의 영향을 받습니다</span>
      </div>
      <div class="bd-value" id="bd-pixel-ratio">확인 중...</div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">현재 창 크기</span>
        <span class="bd-row-help">현재 웹페이지가 사용하는 영역</span>
      </div>
      <div class="bd-value" id="bd-viewport">확인 중...</div>
    </div>


    <div class="bd-row">
      <div class="bd-row-left">
        <span class="bd-row-name">브라우저 언어</span>
        <span class="bd-row-help">브라우저에 설정된 기본 언어</span>
      </div>
      <div class="bd-value" id="bd-language">확인 중...</div>
    </div>

  </div>


  <div class="bd-actions">

    <button
      type="button"
      class="bd-btn primary"
      id="bd-popup-test">
      팝업 차단 테스트
    </button>

    <button
      type="button"
      class="bd-btn secondary"
      id="bd-copy">
      진단 결과 복사
    </button>

    <button
      type="button"
      class="bd-btn secondary"
      id="bd-refresh">
      다시 진단
    </button>

  </div>


  <p class="bd-note">
    ※ 브라우저 보안 정책에 따라 운영체제 버전,
    CPU 아키텍처, 32·64비트 등의 일부 정보는 제한될 수 있습니다.
    표시되는 검사는 사용자의 브라우저에서 처리됩니다.
  </p>

</div>


<script>
(function(){

'use strict';

var root=document.getElementById('sns-browser-diagnostic');

if(!root){
  return;
}


var diagnosticState={
  cookie:null,
  local:null,
  session:null,
  webgl:null,
  webp:null,
  avif:null,
  clipboard:null,
  pdf:null,
  notification:null,
  popup:null,

  os:'',
  bitness:'',
  arch:'',
  browser:'',
  browserVersion:''
};


function $(id){
  return document.getElementById(id);
}


function esc(str){

  return String(str)
    .replace(/&/g,'&amp;')
    .replace(/</g,'&lt;')
    .replace(/>/g,'&gt;')
    .replace(/"/g,'&quot;')
    .replace(/'/g,'&#039;');

}


function status(text,type){

  return (
    '<span class="bd-status '+
    (type||'is-info')+
    '">'+
    esc(text)+
    '</span>'
  );

}


function setHtml(id,html){

  var el=$(id);

  if(el){
    el.innerHTML=html;
  }

}


function setText(id,text){

  var el=$(id);

  if(el){
    el.textContent=text;
  }

}


function testStorage(storage){

  try{

    var key=
      '__sns_diag_'+
      Date.now()+
      '_'+
      Math.random();

    storage.setItem(key,'1');

    var ok=
      storage.getItem(key)==='1';

    storage.removeItem(key);

    return ok;

  }catch(e){

    return false;

  }

}


function testCookies(){

  try{

    var key=
      'sns_diag_cookie_'+
      Date.now();

    document.cookie=
      key+
      '=1; path=/; SameSite=Lax';

    var ok=
      document.cookie
        .split('; ')
        .some(function(part){

          return (
            part.indexOf(
              key+'='
            )===0
          );

        });

    document.cookie=
      key+
      '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax';

    return ok;

  }catch(e){

    return false;

  }

}


function detectWebGL(){

  try{

    var canvas=
      document.createElement(
        'canvas'
      );

    if(
      canvas.getContext(
        'webgl2'
      )
    ){
      return 'WebGL 2';
    }

    if(
      canvas.getContext('webgl') ||
      canvas.getContext(
        'experimental-webgl'
      )
    ){
      return 'WebGL 1';
    }

    return '';

  }catch(e){

    return '';

  }

}


function detectWebP(){

  try{

    var canvas=
      document.createElement(
        'canvas'
      );

    canvas.width=1;
    canvas.height=1;

    var data=
      canvas.toDataURL(
        'image/webp'
      );

    return (
      data.indexOf(
        'data:image/webp'
      )===0
    );

  }catch(e){

    return false;

  }

}


function detectImage(dataUri){

  return new Promise(
    function(resolve){

      var img=new Image();
      var done=false;

      function finish(result){

        if(done){
          return;
        }

        done=true;
        resolve(result);

      }

      img.onload=function(){
        finish(true);
      };

      img.onerror=function(){
        finish(false);
      };

      img.src=dataUri;

      setTimeout(
        function(){
          finish(false);
        },
        2000
      );

    }
  );

}


async function detectAVIF(){

  try{

    if(
      window.ImageDecoder &&
      typeof window.ImageDecoder
        .isTypeSupported==='function'
    ){

      var supported=
        await window.ImageDecoder
          .isTypeSupported(
            'image/avif'
          );

      if(supported){
        return true;
      }

    }

  }catch(e){}


  var data=
    'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=';

  return await detectImage(data);

}


function detectPDF(){

  if(
    'pdfViewerEnabled' in navigator
  ){

    return navigator.pdfViewerEnabled;

  }

  return null;

}


function detectNotification(){

  if(
    !('Notification' in window)
  ){

    return 'unsupported';

  }

  return Notification.permission;

}


function fallbackBrowser(){

  var ua=
    navigator.userAgent||'';

  var m;


  m=ua.match(
    /Edg\/([\d.]+)/
  );

  if(m){
    return {
      name:'Microsoft Edge',
      version:m[1]
    };
  }


  m=ua.match(
    /OPR\/([\d.]+)/
  );

  if(m){
    return {
      name:'Opera',
      version:m[1]
    };
  }


  m=ua.match(
    /Firefox\/([\d.]+)/
  );

  if(m){
    return {
      name:'Firefox',
      version:m[1]
    };
  }


  m=ua.match(
    /Chrome\/([\d.]+)/
  );

  if(
    m &&
    !/Edg\//.test(ua) &&
    !/OPR\//.test(ua)
  ){

    return {
      name:'Google Chrome',
      version:m[1]
    };

  }


  m=ua.match(
    /Version\/([\d.]+).*Safari/
  );

  if(
    m &&
    !/Chrome\//.test(ua)
  ){

    return {
      name:'Safari',
      version:m[1]
    };

  }


  return {
    name:'알 수 없는 브라우저',
    version:''
  };

}


function browserFromBrands(list){

  if(
    !Array.isArray(list)
  ){
    return null;
  }


  var priority=[
    'Microsoft Edge',
    'Google Chrome',
    'Opera',
    'Chromium'
  ];


  for(
    var i=0;
    i<priority.length;
    i++
  ){

    for(
      var j=0;
      j<list.length;
      j++
    ){

      if(
        list[j].brand===
        priority[i]
      ){

        return {
          name:list[j].brand,
          version:
            list[j].version||''
        };

      }

    }

  }

  return null;

}


function fallbackOS(){

  var ua=
    navigator.userAgent||'';

  var platform=
    navigator.platform||'';


  if(
    /Windows NT 10\.0/.test(
      ua
    )
  ){
    return 'Windows 10/11';
  }

  if(
    /Windows NT 6\.3/.test(
      ua
    )
  ){
    return 'Windows 8.1';
  }

  if(
    /Windows NT 6\.1/.test(
      ua
    )
  ){
    return 'Windows 7';
  }

  if(
    /Android/.test(
      ua
    )
  ){
    return 'Android';
  }

  if(
    /iPhone|iPad|iPod/.test(
      ua
    )
  ){
    return 'iOS/iPadOS';
  }

  if(
    platform==='MacIntel' &&
    navigator.maxTouchPoints>1
  ){
    return 'iPadOS';
  }

  if(
    /Mac OS X/.test(
      ua
    )
  ){
    return 'macOS';
  }

  if(
    /CrOS/.test(
      ua
    )
  ){
    return 'ChromeOS';
  }

  if(
    /Linux/.test(
      ua
    )
  ){
    return 'Linux';
  }

  return (
    platform||
    '확인 제한'
  );

}


function fallbackBitness(){

  var combined=
    (navigator.userAgent||'')+
    ' '+
    (navigator.platform||'');


  if(
    /ARM64|AArch64/i.test(
      combined
    )
  ){

    return {
      bits:'64',
      arch:'ARM64'
    };

  }


  if(
    /Win64|x64|x86_64|WOW64|amd64/i.test(
      combined
    )
  ){

    return {
      bits:'64',
      arch:'x64'
    };

  }


  if(
    /Win32|i[3-6]86|x86/i.test(
      combined
    )
  ){

    return {
      bits:'32',
      arch:'x86'
    };

  }


  return {
    bits:'',
    arch:''
  };

}


function windowsName(version){

  if(!version){
    return 'Windows';
  }

  var major=
    parseInt(
      String(version)
        .split('.')[0],
      10
    );

  if(isNaN(major)){
    return 'Windows';
  }

  if(major>=13){
    return 'Windows 11';
  }

  if(major>0){
    return 'Windows 10';
  }

  return 'Windows';

}


function normalizeArch(
  arch,
  bits
){

  arch=
    (arch||'')
      .toLowerCase();

  bits=
    String(
      bits||''
    );


  if(
    arch.indexOf('arm')!==-1
  ){

    return (
      bits==='64'
        ? 'ARM64'
        : 'ARM'
    );

  }


  if(
    arch.indexOf('x86')!==-1 ||
    arch.indexOf('amd')!==-1
  ){

    return (
      bits==='64'
        ? 'x64'
        : 'x86'
    );

  }


  return (
    arch
      ? arch.toUpperCase()
      : ''
  );

}


function downloadGuide(
  os,
  bits,
  arch
){

  var guide=
    $('bd-download-guide');

  if(!guide){
    return;
  }


  var a=
    String(
      arch||''
    ).toUpperCase();

  var html='';


  if(
    /Windows/i.test(os)
  ){

    if(
      a==='ARM64'
    ){

      html=
        '<div class="bd-download-title">내 PC에 맞는 다운로드 파일</div>'+

        '<div class="bd-download-main">Windows ARM64</div>'+

        '<div class="bd-download-tags">'+
        '<span class="bd-tag">✓ ARM64</span>'+
        '<span class="bd-tag">✓ Windows ARM</span>'+
        '</div>'+

        '<p class="bd-download-note">'+
        '프로그램 다운로드 페이지에서 ARM64 버전을 우선 선택하세요. '+
        'ARM64 버전이 없다면 해당 프로그램의 호환 정보를 확인하는 것이 좋습니다.'+
        '</p>';

    }else if(
      bits==='64'
    ){

      html=
        '<div class="bd-download-title">내 PC에 맞는 다운로드 파일</div>'+

        '<div class="bd-download-main">Windows x64 / 64-bit</div>'+

        '<div class="bd-download-tags">'+
        '<span class="bd-tag">✓ x64</span>'+
        '<span class="bd-tag">✓ 64-bit</span>'+
        '<span class="bd-tag">✓ Windows 64-bit</span>'+
        '</div>'+

        '<p class="bd-download-note">'+
        '일반적인 Intel·AMD 64비트 Windows PC에서는 위 표기의 파일을 선택하면 됩니다. '+
        'x86 또는 32-bit 버전은 보통 선택할 필요가 없습니다.'+
        '</p>';

    }else if(
      bits==='32'
    ){

      html=
        '<div class="bd-download-title">내 PC에 맞는 다운로드 파일</div>'+

        '<div class="bd-download-main">Windows x86 / 32-bit</div>'+

        '<div class="bd-download-tags">'+
        '<span class="bd-tag">✓ x86</span>'+
        '<span class="bd-tag">✓ 32-bit</span>'+
        '<span class="bd-tag">✓ Windows 32-bit</span>'+
        '</div>'+

        '<p class="bd-download-note">'+
        '프로그램 다운로드 페이지에서 x86 또는 32-bit 버전을 선택하세요.'+
        '</p>';

    }

  }


  if(html){

    guide.innerHTML=html;
    guide.style.display='block';

  }else{

    guide.innerHTML='';
    guide.style.display='none';

  }

}


function updateScreen(){

  setText(
    'bd-screen',
    screen.width+
    ' × '+
    screen.height
  );


  var ratio=
    Number(
      (
        window.devicePixelRatio||
        1
      ).toFixed(2)
    );


  setText(
    'bd-pixel-ratio',
    ratio+'배'
  );


  setText(
    'bd-viewport',
    window.innerWidth+
    ' × '+
    window.innerHeight
  );

}


function updateNotificationDisplay(){

  var value=
    diagnosticState.notification;


  if(
    value==='granted'
  ){

    setHtml(
      'bd-notification',
      status(
        '허용됨',
        'is-ok'
      )
    );

  }else if(
    value==='denied'
  ){

    setHtml(
      'bd-notification',
      status(
        '차단됨',
        'is-no'
      )
    );

  }else if(
    value==='default'
  ){

    setHtml(
      'bd-notification',
      status(
        '아직 선택 안 함',
        'is-info'
      )
    );

  }else{

    setHtml(
      'bd-notification',
      status(
        '미지원',
        'is-warn'
      )
    );

  }

}


function setMeaning(
  id,
  textId,
  mode,
  text
){

  var el=$(id);

  if(!el){
    return;
  }


  el.classList.remove(
    'meaning-ok',
    'meaning-warn',
    'meaning-no'
  );


  var icon=
    el.querySelector(
      '.bd-meaning-icon'
    );


  if(mode==='ok'){

    el.classList.add(
      'meaning-ok'
    );

    if(icon){
      icon.textContent='✓';
    }

  }else if(
    mode==='no'
  ){

    el.classList.add(
      'meaning-no'
    );

    if(icon){
      icon.textContent='!';
    }

  }else{

    el.classList.add(
      'meaning-warn'
    );

    if(icon){
      icon.textContent='!';
    }

  }


  setText(
    textId,
    text
  );

}


function updateMeanings(){

  if(
    diagnosticState.cookie &&
    diagnosticState.local &&
    diagnosticState.session
  ){

    setMeaning(
      'meaning-storage',
      'meaning-storage-text',
      'ok',
      '쿠키와 브라우저 저장 기능을 정상적으로 사용할 수 있습니다.'
    );

  }else{

    setMeaning(
      'meaning-storage',
      'meaning-storage-text',
      'no',
      '일부 저장 기능이 제한되어 있습니다. 로그인 유지나 사이트 설정 저장에 영향을 줄 수 있습니다.'
    );

  }


  if(
    diagnosticState.clipboard
  ){

    setMeaning(
      'meaning-copy',
      'meaning-copy-text',
      'ok',
      '웹사이트의 복사 버튼과 클립보드 기능을 사용할 수 있는 환경입니다.'
    );

  }else{

    setMeaning(
      'meaning-copy',
      'meaning-copy-text',
      'warn',
      '브라우저 보안 설정에 따라 웹페이지 복사 기능이 제한될 수 있습니다.'
    );

  }


  if(
    diagnosticState.popup===true
  ){

    setMeaning(
      'meaning-popup',
      'meaning-popup-text',
      'ok',
      '팝업 창이 허용되어 있습니다.'
    );

  }else if(
    diagnosticState.popup===false
  ){

    setMeaning(
      'meaning-popup',
      'meaning-popup-text',
      'no',
      '팝업이 차단되어 있습니다. 결제·본인인증·로그인 창이 열리지 않을 수 있습니다.'
    );

  }else{

    setMeaning(
      'meaning-popup',
      'meaning-popup-text',
      'warn',
      '팝업 차단 테스트 버튼을 눌러 실제 허용 여부를 확인하세요.'
    );

  }


  if(
    diagnosticState.webgl
  ){

    setMeaning(
      'meaning-graphics',
      'meaning-graphics-text',
      'ok',
      diagnosticState.webgl+
      '을 사용할 수 있어 지도·3D·웹 그래픽 기능을 사용할 수 있습니다.'
    );

  }else{

    setMeaning(
      'meaning-graphics',
      'meaning-graphics-text',
      'no',
      'WebGL을 사용할 수 없어 일부 지도·3D·웹 그래픽 기능이 정상 작동하지 않을 수 있습니다.'
    );

  }


  if(
    diagnosticState.webp &&
    diagnosticState.avif===true
  ){

    setMeaning(
      'meaning-images',
      'meaning-images-text',
      'ok',
      'WebP와 AVIF를 모두 지원해 최신 웹 이미지 형식을 표시할 수 있습니다.'
    );

  }else if(
    diagnosticState.webp
  ){

    setMeaning(
      'meaning-images',
      'meaning-images-text',
      'warn',
      'WebP는 지원하지만 AVIF는 지원되지 않거나 확인이 제한될 수 있습니다.'
    );

  }else{

    setMeaning(
      'meaning-images',
      'meaning-images-text',
      'no',
      '일부 최신 이미지 형식을 표시하지 못할 수 있습니다.'
    );

  }

}


function updateSummary(){

  var parts=[];


  if(
    diagnosticState.os
  ){
    parts.push(
      diagnosticState.os
    );
  }


  if(
    diagnosticState.bitness
  ){

    parts.push(
      diagnosticState.bitness+
      '비트'
    );

  }


  if(
    diagnosticState.arch
  ){

    parts.push(
      diagnosticState.arch
    );

  }


  setText(
    'bd-summary-main',

    parts.length
      ? parts.join(' · ')
      : 'PC 환경 확인 제한'
  );


  var browserText=
    diagnosticState.browser||
    '브라우저';


  if(
    diagnosticState.browserVersion
  ){

    browserText+=
      ' '+
      diagnosticState.browserVersion;

  }


  var downloadText='';


  if(
    /Windows/i.test(
      diagnosticState.os
    )
  ){

    if(
      String(
        diagnosticState.arch
      ).toUpperCase()==='ARM64'
    ){

      downloadText=
        ' · 다운로드 권장: Windows ARM64';

    }else if(
      diagnosticState.bitness==='64'
    ){

      downloadText=
        ' · 다운로드 권장: Windows x64 / 64-bit';

    }else if(
      diagnosticState.bitness==='32'
    ){

      downloadText=
        ' · 다운로드 권장: Windows x86 / 32-bit';

    }

  }


  setText(
    'bd-summary-sub',
    browserText+
    downloadText
  );


  var issueCount=0;


  if(
    diagnosticState.cookie===false
  ){
    issueCount++;
  }

  if(
    diagnosticState.local===false
  ){
    issueCount++;
  }

  if(
    diagnosticState.session===false
  ){
    issueCount++;
  }

  if(
    diagnosticState.webgl===false
  ){
    issueCount++;
  }

  if(
    diagnosticState.webp===false
  ){
    issueCount++;
  }


  var result=
    $('bd-summary-result');


  if(!result){
    return;
  }


  if(issueCount===0){

    result.className=
      'bd-summary-result summary-ok';

    result.textContent=
      '✓ 현재 검사에서 특별히 발견된 브라우저 환경 문제는 없습니다.';

  }else{

    result.className=
      'bd-summary-result summary-warn';

    result.textContent=
      '&#x26a0; 일부 브라우저 기능에서 확인이 필요한 항목이 있습니다. 아래 상세 결과를 확인하세요.';

  }

}


function basicChecks(){

  diagnosticState.cookie=
    testCookies();

  diagnosticState.local=
    testStorage(
      window.localStorage
    );

  diagnosticState.session=
    testStorage(
      window.sessionStorage
    );


  setHtml(
    'bd-cookie',

    diagnosticState.cookie
      ? status(
          '사용 가능',
          'is-ok'
        )
      : status(
          '사용 불가',
          'is-no'
        )
  );


  setHtml(
    'bd-local',

    diagnosticState.local
      ? status(
          '사용 가능',
          'is-ok'
        )
      : status(
          '사용 불가',
          'is-no'
        )
  );


  setHtml(
    'bd-session',

    diagnosticState.session
      ? status(
          '사용 가능',
          'is-ok'
        )
      : status(
          '사용 불가',
          'is-no'
        )
  );


  var gl=
    detectWebGL();


  diagnosticState.webgl=
    gl||false;


  setHtml(
    'bd-webgl',

    gl
      ? status(
          gl+' 지원',
          'is-ok'
        )
      : status(
          '미지원',
          'is-no'
        )
  );


  diagnosticState.webp=
    detectWebP();


  setHtml(
    'bd-webp',

    diagnosticState.webp
      ? status(
          '지원',
          'is-ok'
        )
      : status(
          '미지원',
          'is-no'
        )
  );


  if(
    navigator.clipboard &&
    window.isSecureContext
  ){

    diagnosticState.clipboard=true;

    setHtml(
      'bd-clipboard',
      status(
        '지원',
        'is-ok'
      )
    );

  }else{

    diagnosticState.clipboard=false;

    setHtml(
      'bd-clipboard',
      status(
        '제한적 지원',
        'is-warn'
      )
    );

  }


  diagnosticState.pdf=
    detectPDF();


  if(
    diagnosticState.pdf===true
  ){

    setHtml(
      'bd-pdf',
      status(
        '지원',
        'is-ok'
      )
    );

  }else if(
    diagnosticState.pdf===false
  ){

    setHtml(
      'bd-pdf',
      status(
        '미지원',
        'is-no'
      )
    );

  }else{

    setHtml(
      'bd-pdf',
      status(
        '확인 제한',
        'is-warn'
      )
    );

  }


  diagnosticState.notification=
    detectNotification();

  updateNotificationDisplay();


  updateScreen();


  setText(
    'bd-language',
    navigator.language||
    '확인 제한'
  );


  updateMeanings();
  updateSummary();

}


async function imageChecks(){

  try{

    var avif=
      await detectAVIF();


    diagnosticState.avif=
      avif;


    setHtml(
      'bd-avif',

      avif
        ? status(
            '지원',
            'is-ok'
          )
        : status(
            '미지원',
            'is-no'
          )
    );

  }catch(e){

    diagnosticState.avif=
      null;


    setHtml(
      'bd-avif',
      status(
        '확인 제한',
        'is-warn'
      )
    );

  }


  updateMeanings();
  updateSummary();

}


async function systemChecks(){

  var browser=
    fallbackBrowser();

  var os=
    fallbackOS();

  var bit=
    fallbackBitness();

  var detail=
    '브라우저 기본 정보 기준';


  if(
    navigator.userAgentData &&
    navigator.userAgentData
      .getHighEntropyValues
  ){

    try{

      var ua=
        await navigator
          .userAgentData
          .getHighEntropyValues([
            'architecture',
            'bitness',
            'platformVersion',
            'fullVersionList',
            'wow64'
          ]);


      var b=
        browserFromBrands(
          ua.fullVersionList
        );


      if(b){
        browser=b;
      }


      var platform=
        navigator.userAgentData
          .platform||'';


      if(
        platform==='Windows'
      ){

        os=
          windowsName(
            ua.platformVersion
          );


        detail=
          ua.platformVersion
            ? 'Windows 플랫폼 버전 '+
              ua.platformVersion
            : 'Windows';

      }else if(platform){

        os=platform;

        detail=
          '브라우저 제공 정보';

      }


      if(
        ua.bitness
      ){

        bit.bits=
          String(
            ua.bitness
          );

      }


      if(
        ua.architecture
      ){

        bit.arch=
          normalizeArch(
            ua.architecture,
            bit.bits
          );

      }


      if(
        ua.wow64===true
      ){

        bit.bits='64';

        if(
          !bit.arch
        ){
          bit.arch='x64';
        }

      }

    }catch(e){}

  }


  diagnosticState.os=os;
  diagnosticState.bitness=bit.bits;
  diagnosticState.arch=bit.arch;
  diagnosticState.browser=browser.name;
  diagnosticState.browserVersion=browser.version;


  setText(
    'bd-os',
    os||
    '확인 제한'
  );


  setText(
    'bd-os-detail',
    detail
  );


  if(
    bit.bits
  ){

    setText(
      'bd-bitness',
      bit.bits+
      '비트'
    );


    setText(
      'bd-arch',

      bit.arch
        ? 'CPU 아키텍처: '+
          bit.arch
        : 'CPU 아키텍처 확인 제한'
    );

  }else{

    setText(
      'bd-bitness',
      '확인 제한'
    );


    setText(
      'bd-arch',
      '현재 브라우저가 세부 정보를 제공하지 않습니다.'
    );

  }


  setText(
    'bd-browser',
    browser.name
  );


  setText(
    'bd-browser-detail',

    browser.version
      ? '버전 '+
        browser.version
      : '세부 버전 확인 제한'
  );


  downloadGuide(
    os,
    bit.bits,
    bit.arch
  );


  updateSummary();

}


function popupTest(){

  var popup=null;


  try{

    popup=
      window.open(
        '',
        '_blank',
        'width=340,height=220'
      );

  }catch(e){}


  if(!popup){

    diagnosticState.popup=false;


    setHtml(
      'bd-popup',
      status(
        '차단됨',
        'is-no'
      )
    );


    updateMeanings();

    return;

  }


  diagnosticState.popup=true;


  setHtml(
    'bd-popup',
    status(
      '허용됨',
      'is-ok'
    )
  );


  updateMeanings();


  try{

    popup.document.write(
      '<!doctype html>'+
      '<html lang="ko">'+
      '<head>'+
      '<meta charset="utf-8">'+
      '<title>팝업 테스트</title>'+
      '</head>'+
      '<body style="font-family:sans-serif;padding:24px">'+
      '<strong>팝업이 정상적으로 허용되어 있습니다.</strong>'+
      '<p>이 창은 자동으로 닫힙니다.</p>'+
      '</body>'+
      '</html>'
    );

    popup.document.close();

  }catch(e){}


  setTimeout(
    function(){

      try{
        popup.close();
      }catch(e){}

    },
    700
  );

}


function getCopyText(){

  return [

    '브라우저·PC 환경 진단 결과',

    '',

    '운영체제: '+
    $('bd-os')
      .textContent
      .trim(),

    '시스템 종류: '+
    $('bd-bitness')
      .textContent
      .trim(),

    'CPU 아키텍처: '+
    $('bd-arch')
      .textContent
      .replace(
        'CPU 아키텍처:',
        ''
      )
      .trim(),

    '브라우저: '+
    $('bd-browser')
      .textContent
      .trim()+
    ' / '+
    $('bd-browser-detail')
      .textContent
      .trim(),

    '',

    '쿠키: '+
    $('bd-cookie')
      .textContent
      .trim(),

    'LocalStorage: '+
    $('bd-local')
      .textContent
      .trim(),

    'SessionStorage: '+
    $('bd-session')
      .textContent
      .trim(),

    '팝업: '+
    $('bd-popup')
      .textContent
      .trim(),

    'WebGL: '+
    $('bd-webgl')
      .textContent
      .trim(),

    'WebP: '+
    $('bd-webp')
      .textContent
      .trim(),

    'AVIF: '+
    $('bd-avif')
      .textContent
      .trim(),

    '클립보드: '+
    $('bd-clipboard')
      .textContent
      .trim(),

    'PDF 브라우저 보기: '+
    $('bd-pdf')
      .textContent
      .trim(),

    '웹사이트 알림: '+
    $('bd-notification')
      .textContent
      .trim(),

    '',

    '브라우저 화면 크기: '+
    $('bd-screen')
      .textContent
      .trim(),

    '화면 픽셀 비율: '+
    $('bd-pixel-ratio')
      .textContent
      .trim(),

    '현재 창 크기: '+
    $('bd-viewport')
      .textContent
      .trim(),

    '브라우저 언어: '+
    $('bd-language')
      .textContent
      .trim()

  ].join('\n');

}


async function copyResults(){

  var btn=
    $('bd-copy');

  var original=
    btn.textContent;


  try{

    if(
      navigator.clipboard &&
      window.isSecureContext
    ){

      await navigator.clipboard
        .writeText(
          getCopyText()
        );


      btn.textContent=
        '복사 완료';

    }else{

      var ta=
        document.createElement(
          'textarea'
        );


      ta.value=
        getCopyText();

      ta.style.position=
        'fixed';

      ta.style.opacity=
        '0';


      document.body
        .appendChild(ta);


      ta.select();


      var copied=
        document.execCommand(
          'copy'
        );


      ta.remove();


      btn.textContent=
        copied
          ? '복사 완료'
          : '복사 실패';

    }

  }catch(e){

    btn.textContent=
      '복사 실패';

  }


  setTimeout(
    function(){

      btn.textContent=
        original;

    },
    1400
  );

}


function runAll(){

  diagnosticState.popup=null;

  basicChecks();

  imageChecks();

  systemChecks();

}


$('bd-popup-test')
  .addEventListener(
    'click',
    popupTest
  );


$('bd-copy')
  .addEventListener(
    'click',
    copyResults
  );


$('bd-refresh')
  .addEventListener(
    'click',
    runAll
  );


window.addEventListener(
  'resize',
  updateScreen
);


runAll();

})();
</script>

</div>



<h2 class="wp-block-heading">윈도우 32비트 64비트 확인 방법</h2>



<p class="wp-block-paragraph">프로그램을 다운로드하다 보면 <strong>x86, x64, 32-bit, 64-bit, ARM64</strong>처럼 여러 종류의 설치 파일이 표시되는 경우가 있습니다. 평소에는 신경 쓸 일이 거의 없지만, 막상 프로그램을 설치하려고 하면 내 컴퓨터가 32비트인지 64비트인지 몰라 어떤 파일을 받아야 할지 헷갈릴 수 있습니다.</p>



<p class="wp-block-paragraph">위의 무료 진단 도구를 이용하면 별도의 프로그램을 설치하지 않아도 <strong>현재 Windows 버전과 32비트·64비트 여부, CPU 아키텍처, 사용 중인 브라우저와 버전</strong>을 바로 확인할 수 있습니다.</p>



<p class="wp-block-paragraph">특히 Windows PC에서는 확인 가능한 경우 현재 환경에 맞춰 <strong>x64, 64-bit, x86, ARM64 중 어떤 설치 파일을 선택해야 하는지도 함께 안내</strong>합니다.</p>



<h2 class="wp-block-heading">사용 방법</h2>



<p class="wp-block-paragraph">별도의 프로그램 설치나 회원가입은 필요하지 않습니다.</p>



<ol class="wp-block-list">
<li>페이지에 접속하면 자동으로 PC와 브라우저 환경을 진단합니다.</li>



<li>상단의 <strong>내 PC 한눈에 보기</strong>에서 Windows 버전과 32비트·64비트 여부를 확인합니다.</li>



<li><strong>내 PC에 맞는 다운로드 파일</strong> 항목에서 프로그램을 받을 때 선택해야 할 파일 종류를 확인합니다.</li>



<li>아래의 브라우저 기능에서 쿠키, WebGL, WebP, AVIF, PDF 보기 등의 지원 여부를 확인할 수 있습니다.</li>



<li>팝업 차단 여부가 궁금하다면 <strong>팝업 차단 테스트</strong> 버튼을 누릅니다.</li>



<li>문제가 발생했을 때 다른 사람에게 환경 정보를 전달해야 한다면 <strong>진단 결과 복사</strong> 버튼을 이용하면 됩니다.</li>
</ol>



<p class="wp-block-paragraph">검사 결과는 현재 사용 중인 브라우저가 제공하는 정보를 기준으로 표시됩니다. 브라우저 보안 정책에 따라 일부 운영체제 또는 시스템 정보는 제한될 수 있습니다.</p>



<h2 class="wp-block-heading">x86, x64, 32비트, 64비트는 무엇이 다른가요?</h2>



<p class="wp-block-paragraph">프로그램 다운로드 페이지에서 가장 많이 볼 수 있는 표기가 <code>x86</code>과 <code>x64</code>입니다.</p>



<p class="wp-block-paragraph"><strong>x86 또는 32-bit</strong>는 일반적으로 32비트 환경용 프로그램을 의미합니다.</p>



<p class="wp-block-paragraph"><strong>x64 또는 64-bit</strong>는 64비트 Windows 환경용 프로그램을 의미합니다.</p>



<p class="wp-block-paragraph">요즘 사용되는 대부분의 Windows PC는 64비트 환경이기 때문에 진단 결과가 <strong>64비트 / x64</strong>로 나온다면 프로그램 다운로드 페이지에서 보통 다음과 같은 파일을 선택하면 됩니다.</p>



<ul class="wp-block-list">
<li>x64</li>



<li>64-bit</li>



<li>Windows 64-bit</li>
</ul>



<p class="wp-block-paragraph">반대로 결과가 32비트라면 <code>x86</code>, <code>32-bit</code>, <code>Windows 32-bit</code>라고 표시된 버전을 선택하는 것이 일반적입니다.</p>



<h2 class="wp-block-heading">ARM64는 무엇인가요?</h2>



<p class="wp-block-paragraph">ARM64는 일반적인 x64 Windows PC와 다른 CPU 아키텍처입니다. Windows용 ARM 기기에서는 프로그램 다운로드 페이지에 <strong>ARM64 전용 설치 파일</strong>이 제공되는 경우가 있습니다.</p>



<p class="wp-block-paragraph">진단 결과가 ARM64로 표시된다면 가능한 경우 <strong>Windows ARM64 버전</strong>을 선택하는 것이 좋습니다.</p>



<p class="wp-block-paragraph">단, 모든 프로그램이 ARM64 전용 버전을 제공하는 것은 아닙니다. 이 경우 프로그램 제작사가 x64 버전 실행을 지원하는지 확인해야 합니다.</p>



<h2 class="wp-block-heading">브라우저 버전도 함께 확인할 수 있습니다</h2>



<p class="wp-block-paragraph">이 도구에서는 Windows 32비트 64비트 확인뿐 아니라 현재 사용 중인 <strong>Chrome, Edge, Firefox, Safari 등의 브라우저와 버전 정보</strong>도 확인할 수 있습니다.</p>



<p class="wp-block-paragraph">웹사이트가 정상적으로 열리지 않거나 특정 기능이 작동하지 않을 때 고객센터에서 다음과 같은 정보를 요청하는 경우가 있습니다.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">사용 중인 운영체제와 브라우저 버전을 알려주세요.</p>
</blockquote>



<p class="wp-block-paragraph">이럴 때 진단 결과를 확인한 뒤 <strong>진단 결과 복사</strong> 기능을 사용하면 일일이 정보를 찾아 입력할 필요가 없습니다.</p>



<h2 class="wp-block-heading">웹사이트가 정상 작동하는지도 확인할 수 있습니다</h2>



<p class="wp-block-paragraph">현재 브라우저가 웹사이트의 주요 기능을 정상적으로 사용할 수 있는지도 함께 진단합니다.</p>



<p class="wp-block-paragraph"><strong>쿠키</strong></p>



<p class="wp-block-paragraph">웹사이트 로그인 유지, 설정 저장 등에 사용됩니다. 쿠키가 차단되어 있으면 일부 사이트에서 로그인이 풀리거나 기능이 정상 작동하지 않을 수 있습니다.</p>



<p class="wp-block-paragraph"><strong>LocalStorage / SessionStorage</strong></p>



<p class="wp-block-paragraph">웹사이트가 브라우저 안에 간단한 정보를 저장하는 기능입니다. 사이트 설정, 임시 작업 내용 등 다양한 웹 기능에 사용됩니다.</p>



<p class="wp-block-paragraph"><strong>팝업</strong></p>



<p class="wp-block-paragraph">결제, 본인인증, 로그인 또는 새 창을 사용하는 사이트에서는 팝업 허용이 필요할 수 있습니다. 위의 <strong>팝업 차단 테스트</strong> 버튼으로 현재 상태를 직접 확인할 수 있습니다.</p>



<p class="wp-block-paragraph"><strong>WebGL</strong></p>



<p class="wp-block-paragraph">웹브라우저에서 3D 그래픽과 일부 지도·시각화 기능을 표시할 때 사용됩니다. WebGL을 지원하지 않거나 차단되어 있으면 일부 웹사이트 화면이 제대로 표시되지 않을 수 있습니다.</p>



<p class="wp-block-paragraph"><strong>WebP / AVIF</strong></p>



<p class="wp-block-paragraph">최근 웹사이트에서 많이 사용되는 고효율 이미지 형식입니다. 현재 브라우저가 해당 이미지 파일을 정상적으로 표시할 수 있는지 확인합니다.</p>



<p class="wp-block-paragraph"><strong>PDF 브라우저 보기</strong></p>



<p class="wp-block-paragraph">PDF 파일을 별도의 프로그램으로 열지 않고 브라우저 안에서 바로 표시할 수 있는 환경인지 확인합니다.</p>



<h2 class="wp-block-heading">진단 결과가 실제 PC 설정과 다르게 보일 수도 있나요?</h2>



<p class="wp-block-paragraph">가능합니다. 웹사이트는 보안과 개인정보 보호 때문에 PC의 모든 하드웨어 정보를 직접 읽을 수 없습니다.</p>



<p class="wp-block-paragraph">특히 일부 브라우저에서는 <strong>Windows 세부 버전, CPU 아키텍처, 32비트·64비트 정보</strong>를 제한할 수 있습니다. 이 경우 도구에서는 임의로 값을 추측하지 않고 확인 가능한 정보만 표시합니다.</p>



<p class="wp-block-paragraph">또한 화면 크기와 화면 픽셀 비율은 Windows의 디스플레이 배율이나 브라우저 확대·축소 설정에 따라 달라질 수 있습니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">자주 묻는 질문 FAQ</h2>



<h3 class="wp-block-heading">내 컴퓨터가 32비트인지 64비트인지 어떻게 확인하나요?</h3>



<p class="wp-block-paragraph">위의 진단 도구에 접속하면 별도의 프로그램 설치 없이 자동으로 확인합니다. 지원되는 브라우저에서는 상단에 <strong>32비트 또는 64비트</strong>와 함께 x64, x86, ARM64 등의 아키텍처 정보가 표시됩니다.</p>



<h3 class="wp-block-heading">64비트라고 나오면 어떤 프로그램을 다운로드해야 하나요?</h3>



<p class="wp-block-paragraph">일반적인 Windows 64비트 PC라면 다운로드 페이지에서 <strong>x64, 64-bit, Windows 64-bit</strong>라고 표시된 파일을 선택하면 됩니다.</p>



<h3 class="wp-block-heading">x86과 x64 중 어떤 것을 받아야 하나요?</h3>



<p class="wp-block-paragraph">진단 결과가 64비트·x64라면 <strong>x64</strong> 버전을 선택하면 됩니다. 32비트 환경에서는 일반적으로 <strong>x86</strong> 버전을 선택합니다.</p>



<h3 class="wp-block-heading">Windows 11도 64비트인가요?</h3>



<p class="wp-block-paragraph">현재 Windows 11은 64비트 환경을 기반으로 제공됩니다. 다만 프로그램 다운로드 시에는 CPU 아키텍처에 따라 일반적인 x64와 ARM64 버전이 나뉠 수 있으므로 진단 결과의 아키텍처도 함께 확인하는 것이 좋습니다.</p>



<h3 class="wp-block-heading">x64와 ARM64는 같은 것인가요?</h3>



<p class="wp-block-paragraph">아닙니다. 둘 다 64비트 환경이지만 CPU 아키텍처가 다릅니다. 일반적인 Intel 또는 AMD Windows PC는 주로 x64를 사용하며, 일부 ARM 기반 Windows 기기는 ARM64를 사용합니다.</p>



<h3 class="wp-block-heading">브라우저 버전은 어디에서 확인할 수 있나요?</h3>



<p class="wp-block-paragraph">도구 상단의 <strong>브라우저</strong> 항목에서 현재 사용 중인 브라우저 이름과 버전을 바로 확인할 수 있습니다.</p>



<h3 class="wp-block-heading">WebP와 AVIF 지원 여부는 왜 확인하나요?</h3>



<p class="wp-block-paragraph">WebP와 AVIF는 웹사이트에서 사용하는 최신 이미지 형식입니다. 브라우저가 해당 형식을 지원하지 않으면 일부 이미지가 정상적으로 표시되지 않을 수 있습니다.</p>



<h3 class="wp-block-heading">팝업이 차단되어 있는지도 확인할 수 있나요?</h3>



<p class="wp-block-paragraph">가능합니다. <strong>팝업 차단 테스트</strong> 버튼을 누르면 실제 새 창을 열어보고 현재 브라우저가 팝업을 허용하는지 확인합니다.</p>



<h3 class="wp-block-heading">검사하려면 프로그램을 설치해야 하나요?</h3>



<p class="wp-block-paragraph">아니요. 별도의 프로그램이나 확장 프로그램을 설치할 필요가 없습니다. 페이지에 접속하면 브라우저에서 바로 검사할 수 있습니다.</p>



<h3 class="wp-block-heading">내 PC 정보가 서버로 전송되나요?</h3>



<p class="wp-block-paragraph">이 도구의 진단 기능은 페이지에 포함된 JavaScript를 이용해 <strong>현재 브라우저 안에서 확인 가능한 정보를 검사하는 방식</strong>입니다. 별도의 PC 진단 프로그램을 설치하거나 파일을 업로드할 필요가 없습니다.</p>



<p class="wp-block-paragraph">프로그램을 설치할 때 x86, x64, 32-bit, 64-bit 중 무엇을 선택해야 할지 헷갈렸다면 위의 진단 결과에서 <strong>시스템 종류와 권장 다운로드 파일</strong>을 먼저 확인해 보세요. Windows 버전과 브라우저 환경까지 함께 확인할 수 있어 웹사이트 오류나 프로그램 설치 전 환경을 점검할 때도 활용할 수 있습니다.</p>



<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img loading="lazy" decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/windows-32bit-64bit-check/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/09/windows-32bit-64bit-check.jpg" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1584</post-id>	</item>
		<item>
		<title>사진 개인정보 제거 도구, GPS·EXIF 메타데이터 정보 한 번에 삭제</title>
		<link>https://snsnote.com/photo-privacy-remover/</link>
					<comments>https://snsnote.com/photo-privacy-remover/#respond</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Sat, 05 Sep 2026 14:02:22 +0000</pubDate>
				<category><![CDATA[유용한 도구]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1561</guid>

					<description><![CDATA[사진 개인정보 제거 도구 사용 방법 사용 방법은 간단합니다. 원본 사진은 수정되지 않으며 개인정보가 제거된 새로운 이미지 파일이 별도로 만들어집니다. 또한 사진은 SNSNOTE 서버로 전송되지...]]></description>
										<content:encoded><![CDATA[<iframe
  id="snsnote-photo-privacy-frame"
  title="사진 개인정보 제거 도구"
  srcdoc="&lt;!doctype html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1&quot;&gt;
&lt;style&gt;
*{box-sizing:border-box}
[hidden]{display:none!important}
:root{
  --border:#e5e7eb;--text:#111827;--muted:#6b7280;--soft:#f8fafc;
  --primary:#2563eb;--primary-dark:#1d4ed8
}
html,body{margin:0;padding:0;background:transparent;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,&quot;Segoe UI&quot;,Roboto,&quot;Noto Sans KR&quot;,sans-serif}
.tool{width:100%;padding:2px}
.card{width:100%;background:#fff;border:1px solid var(--border);border-radius:20px;padding:26px;box-shadow:0 10px 35px rgba(15,23,42,.06)}
.head{display:flex;align-items:flex-start;gap:14px;margin-bottom:24px}
.icon{display:grid;place-items:center;flex:0 0 46px;width:46px;height:46px;border-radius:14px;background:#eff6ff;color:var(--primary);font-size:27px;font-weight:800}
.title{display:inline-flex;align-items:center;margin:0 0 8px;padding:8px 14px;border:1px solid #dbe7ff;border-left:4px solid var(--primary);border-radius:9px;background:#f5f8ff;font-size:24px;line-height:1.3;font-weight:800}
.head p{margin:0;color:var(--muted);font-size:14px;line-height:1.65}
.panel{width:100%;border:1px solid var(--border);border-radius:16px;padding:18px;margin-top:18px;background:#fff}
.head+.panel{margin-top:0}
.section-title{margin-bottom:13px;font-size:15px;font-weight:800}

.layout{display:grid;grid-template-columns:1fr;gap:14px;align-items:start}
.layout.has-photo{grid-template-columns:minmax(0,1.18fr) minmax(340px,.82fr)}

.drop{width:100%;min-height:360px;display:flex;align-items:center;justify-content:center;padding:18px;border:2px dashed #cbd5e1;border-radius:14px;background:var(--soft)}
.drop.drag{border-color:var(--primary);background:#eff6ff}
.empty{width:100%;display:flex;flex-direction:column;align-items:center;gap:9px;text-align:center;color:var(--muted)}
.empty-picker{cursor:pointer;user-select:none}
.empty-picker:hover .upload-icon{background:#bfdbfe}
.empty-picker:hover strong{color:#1d4ed8}
.empty strong{color:var(--text);font-size:15px}
.empty span,.empty small{font-size:13px}
.upload-icon{display:grid;place-items:center;width:52px;height:52px;border-radius:50%;background:#dbeafe;color:var(--primary);font-size:31px}

/* 실제 브라우저 기본 file input을 그대로 사용 */
.file-input{display:block;max-width:340px;width:100%;margin:5px auto 0;font:inherit;font-size:13px;color:#374151}
.file-input::file-selector-button{
  margin-right:10px;padding:10px 16px;border:1px solid #d1d5db;border-radius:10px;
  background:#fff;color:#111827;font-weight:800;cursor:pointer
}

.preview-wrap{text-align:center;width:100%}
.preview,.clean-preview{display:block;max-width:100%;max-height:560px;margin:0 auto 12px;object-fit:contain;border-radius:10px;box-shadow:0 3px 15px rgba(15,23,42,.08)}
.file-meta{display:flex;flex-direction:column;gap:3px;margin-bottom:10px}
.file-meta strong{font-size:13px;overflow-wrap:anywhere}
.file-meta span{color:var(--muted);font-size:12px}
.change-input{max-width:260px}

.info-panel{min-width:0;padding:14px;border:1px solid #dbe7ff;border-radius:14px;background:#f8fbff}
.clean-panel{border-color:#bbf7d0;background:#f8fffa}
.info-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:11px}
.info-head&gt;div{display:flex;flex-direction:column;gap:3px}
.info-head strong{font-size:14px}
.info-head span{color:#6b7280;font-size:10px;line-height:1.45}
.badge{flex:0 0 auto;padding:5px 8px;border-radius:999px;border:1px solid #dbeafe;background:#eff6ff;color:#1d4ed8;font-size:10px;font-weight:800;white-space:nowrap}
.badge.found{border-color:#fde68a;background:#fffbeb;color:#b45309}
.badge.ok{border-color:#bbf7d0;background:#f0fdf4;color:#166534}
.badge.fail{border-color:#fecaca;background:#fef2f2;color:#b91c1c}

.meta-grid,.clean-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}
.meta-item,.clean-row{min-width:0;padding:9px 10px;border:1px solid #e5e7eb;border-radius:9px;background:#fff}
.meta-item small,.clean-row small{display:block;margin-bottom:3px;color:#6b7280;font-size:9px;font-weight:700}
.meta-item strong,.clean-row strong{display:block;color:#111827;font-size:10px;line-height:1.4;overflow-wrap:anywhere}
.meta-item strong.empty{color:#94a3b8;font-weight:700}
.note{margin-top:9px;color:#6b7280;font-size:9px;line-height:1.5}

.primary{width:100%;min-height:50px;border:0;border-radius:11px;background:var(--primary);color:#fff;font:inherit;font-weight:800;cursor:pointer}
.primary:hover{background:var(--primary-dark)}
.primary:disabled{opacity:.5;cursor:not-allowed}
.remove{margin-top:14px}
.error{margin-top:12px;padding:11px 12px;border:1px solid #fecaca;border-radius:10px;background:#fef2f2;color:#b91c1c;font-size:12px;line-height:1.55}

.success{display:flex;align-items:center;gap:12px;margin-bottom:14px;padding:15px;border:1px solid #bbf7d0;border-radius:13px;background:#f0fdf4;color:#166534}
.success-icon{display:grid;place-items:center;flex:0 0 38px;width:38px;height:38px;border-radius:50%;background:#dcfce7;font-size:22px;font-weight:900}
.success div{display:flex;flex-direction:column;gap:3px}
.success strong{font-size:13px}.success span{font-size:11px;line-height:1.5}

.clean-layout{display:grid;grid-template-columns:minmax(0,1.18fr) minmax(340px,.82fr);gap:14px;align-items:start}
.clean-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;border-color:#dcfce7}
.clean-status{color:#166534;font-size:10px;font-weight:900;white-space:nowrap}
.clean-row.remain{border-color:#fecaca;background:#fff7f7}
.clean-row.remain .clean-status{color:#b91c1c}
.clean-row.absent{border-color:#e5e7eb;background:#f8fafc}
.clean-row.absent .clean-status{color:#64748b}

.actions{display:grid;grid-template-columns:minmax(0,2fr) minmax(160px,.8fr);gap:10px;margin-top:14px}
.reset{width:100%;min-height:50px;padding:10px 16px;border:1px solid #cbd5e1;border-radius:10px;background:#fff;color:#374151;font:inherit;font-weight:700;cursor:pointer}

.privacy{display:flex;align-items:flex-start;gap:9px;margin-top:18px;padding:12px 14px;border:1px solid #dcfce7;border-radius:12px;background:#f0fdf4;color:#166534}
.privacy div{display:flex;flex-direction:column;gap:2px}
.privacy strong{font-size:12px}.privacy span{font-size:11px;line-height:1.5}

@media(max-width:900px){
  .layout.has-photo,.clean-layout{grid-template-columns:1fr}
}
@media(max-width:760px){
  .card{padding:15px;border-radius:16px}.head{gap:10px;margin-bottom:17px}
  .icon{width:40px;height:40px;flex-basis:40px}.title{font-size:20px;padding:7px 11px}
  .head p{font-size:12px}.panel{padding:14px}.drop{min-height:245px;padding:14px}
  .preview,.clean-preview{max-height:360px}.actions{grid-template-columns:1fr}
}
@media(max-width:500px){
  .meta-grid,.clean-grid{grid-template-columns:1fr}.info-head{flex-direction:column}
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div class=&quot;tool&quot;&gt;
  &lt;div class=&quot;card&quot;&gt;
    &lt;div class=&quot;head&quot;&gt;
      &lt;div class=&quot;icon&quot;&gt;⌖&lt;/div&gt;
      &lt;div&gt;
        &lt;div class=&quot;title&quot; role=&quot;heading&quot; aria-level=&quot;2&quot;&gt;사진 개인정보 제거 도구&lt;/div&gt;
        &lt;p&gt;사진에 포함될 수 있는 &lt;strong&gt;GPS 위치정보·촬영일·기기정보&lt;/strong&gt; 등의 메타데이터를 확인하고 제거합니다.&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;section class=&quot;panel&quot;&gt;
      &lt;div class=&quot;section-title&quot;&gt;1. 사진 넣기&lt;/div&gt;

      &lt;div class=&quot;layout&quot; id=&quot;originalLayout&quot;&gt;
        &lt;div class=&quot;drop&quot; id=&quot;dropZone&quot;&gt;
          &lt;label class=&quot;empty empty-picker&quot; id=&quot;emptyState&quot;&gt;
            &lt;div class=&quot;upload-icon&quot;&gt;＋&lt;/div&gt;
            &lt;strong&gt;사진을 선택하거나 여기에 놓으세요&lt;/strong&gt;
            &lt;span&gt;사진 선택 후 원본 메타데이터를 확인합니다.&lt;/span&gt;
            &lt;input id=&quot;fileInput&quot; class=&quot;file-input&quot; type=&quot;file&quot;
              accept=&quot;image/jpeg,image/png,image/webp,.jpg,.jpeg,.png,.webp&quot;
              onchange=&quot;window.SNSPHOTO &amp;&amp; window.SNSPHOTO.handleFile(this.files &amp;&amp; this.files[0])&quot;&gt;
            &lt;small&gt;JPG, PNG, WebP · 최대 30MB&lt;/small&gt;
          &lt;/label&gt;

          &lt;div class=&quot;preview-wrap&quot; id=&quot;previewWrap&quot; hidden&gt;
            &lt;img class=&quot;preview&quot; id=&quot;preview&quot; alt=&quot;선택한 사진 미리보기&quot;&gt;
            &lt;div class=&quot;file-meta&quot;&gt;
              &lt;strong id=&quot;fileName&quot;&gt;&lt;/strong&gt;
              &lt;span id=&quot;fileInfo&quot;&gt;&lt;/span&gt;
            &lt;/div&gt;
            &lt;input id=&quot;changeInput&quot; class=&quot;file-input change-input&quot; type=&quot;file&quot;
              accept=&quot;image/jpeg,image/png,image/webp,.jpg,.jpeg,.png,.webp&quot;
              onchange=&quot;window.SNSPHOTO &amp;&amp; window.SNSPHOTO.handleFile(this.files &amp;&amp; this.files[0])&quot;&gt;
          &lt;/div&gt;
        &lt;/div&gt;

        &lt;aside class=&quot;info-panel&quot; id=&quot;metaPanel&quot; hidden&gt;
          &lt;div class=&quot;info-head&quot;&gt;
            &lt;div&gt;
              &lt;strong&gt;원본 사진 정보&lt;/strong&gt;
              &lt;span&gt;사진 파일에서 실제로 확인된 메타데이터입니다.&lt;/span&gt;
            &lt;/div&gt;
            &lt;span class=&quot;badge&quot; id=&quot;metaBadge&quot;&gt;확인 중&lt;/span&gt;
          &lt;/div&gt;
          &lt;div class=&quot;meta-grid&quot; id=&quot;metaGrid&quot;&gt;&lt;/div&gt;
          &lt;div class=&quot;note&quot;&gt;카카오톡·SNS 등에서 다시 저장한 사진은 원본 메타데이터가 이미 제거되어 있을 수 있습니다.&lt;/div&gt;
        &lt;/aside&gt;
      &lt;/div&gt;

      &lt;button id=&quot;removeBtn&quot; class=&quot;primary remove&quot; type=&quot;button&quot; disabled&gt;사진 개인정보 제거하기&lt;/button&gt;
      &lt;div class=&quot;error&quot; id=&quot;errorBox&quot; hidden&gt;&lt;/div&gt;
    &lt;/section&gt;

    &lt;section class=&quot;panel&quot; id=&quot;donePanel&quot; hidden&gt;
      &lt;div class=&quot;section-title&quot;&gt;2. 제거 완료&lt;/div&gt;

      &lt;div class=&quot;success&quot;&gt;
        &lt;span class=&quot;success-icon&quot;&gt;✓&lt;/span&gt;
        &lt;div&gt;
          &lt;strong&gt;개인정보 메타데이터 제거가 완료되었습니다.&lt;/strong&gt;
          &lt;span id=&quot;successText&quot;&gt;원본 사진은 그대로 두고 새로운 이미지 파일을 만들었습니다.&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class=&quot;clean-layout&quot;&gt;
        &lt;img class=&quot;clean-preview&quot; id=&quot;cleanPreview&quot; alt=&quot;개인정보 제거된 사진 미리보기&quot;&gt;

        &lt;aside class=&quot;info-panel clean-panel&quot;&gt;
          &lt;div class=&quot;info-head&quot;&gt;
            &lt;div&gt;
              &lt;strong&gt;없어진 정보&lt;/strong&gt;
              &lt;span&gt;원본 정보와 제거된 새 사진을 다시 비교한 결과입니다.&lt;/span&gt;
            &lt;/div&gt;
            &lt;span class=&quot;badge&quot; id=&quot;cleanBadge&quot;&gt;검사 중&lt;/span&gt;
          &lt;/div&gt;
          &lt;div class=&quot;clean-grid&quot; id=&quot;cleanGrid&quot;&gt;&lt;/div&gt;
        &lt;/aside&gt;
      &lt;/div&gt;

      &lt;div class=&quot;actions&quot;&gt;
        &lt;button id=&quot;downloadBtn&quot; class=&quot;primary&quot; type=&quot;button&quot;&gt;개인정보 제거된 사진 저장&lt;/button&gt;
        &lt;button id=&quot;resetBtn&quot; class=&quot;reset&quot; type=&quot;button&quot;&gt;초기화&lt;/button&gt;
      &lt;/div&gt;
    &lt;/section&gt;

    &lt;div class=&quot;privacy&quot;&gt;
      &lt;span&gt;🔒&lt;/span&gt;
      &lt;div&gt;
        &lt;strong&gt;사진은 SNSNOTE 서버에 업로드되지 않습니다.&lt;/strong&gt;
        &lt;span&gt;사진 정보 확인과 개인정보 제거는 사용 중인 브라우저 안에서 진행됩니다.&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;script&gt;
(function(){
  &#x27;use strict&#x27;;

  var $ = function(id){ return document.getElementById(id); };
  var state = {
    file:null, previewURL:null, cleanURL:null, cleanBlob:null, meta:null
  };

  var FIELDS = [
    [&#x27;제조사&#x27;,&#x27;make&#x27;],[&#x27;기기 / 카메라 모델&#x27;,&#x27;model&#x27;],[&#x27;GPS 위치정보&#x27;,&#x27;gps&#x27;],
    [&#x27;GPS 고도&#x27;,&#x27;altitude&#x27;],[&#x27;촬영 날짜&#x27;,&#x27;date&#x27;],[&#x27;ISO&#x27;,&#x27;iso&#x27;],
    [&#x27;초점거리&#x27;,&#x27;focal&#x27;],[&#x27;35mm 환산 초점거리&#x27;,&#x27;focal35&#x27;],[&#x27;조리개&#x27;,&#x27;aperture&#x27;],
    [&#x27;노출시간&#x27;,&#x27;exposure&#x27;],[&#x27;렌즈 제조사&#x27;,&#x27;lensMake&#x27;],[&#x27;렌즈 모델&#x27;,&#x27;lens&#x27;],
    [&#x27;카메라 시리얼&#x27;,&#x27;cameraSerial&#x27;],[&#x27;렌즈 시리얼&#x27;,&#x27;lensSerial&#x27;],
    [&#x27;편집 프로그램&#x27;,&#x27;software&#x27;],[&#x27;작성자&#x27;,&#x27;author&#x27;],[&#x27;저작권 정보&#x27;,&#x27;copyright&#x27;]
  ];

  function blankMeta(){
    var o={}; FIELDS.forEach(function(r){o[r[1]]=null}); return o;
  }

  function inferMime(file){
    if(file &amp;&amp; /^(image\/jpeg|image\/png|image\/webp)$/.test(file.type)) return file.type;
    var ext=(file &amp;&amp; file.name ? file.name.split(&#x27;.&#x27;).pop() : &#x27;&#x27;).toLowerCase();
    if(ext===&#x27;jpg&#x27;||ext===&#x27;jpeg&#x27;)return&#x27;image/jpeg&#x27;;
    if(ext===&#x27;png&#x27;)return&#x27;image/png&#x27;;
    if(ext===&#x27;webp&#x27;)return&#x27;image/webp&#x27;;
    return&#x27;&#x27;;
  }

  function formatBytes(n){
    if(!n)return&#x27;&#x27;;
    var u=[&#x27;B&#x27;,&#x27;KB&#x27;,&#x27;MB&#x27;,&#x27;GB&#x27;], i=Math.min(Math.floor(Math.log(n)/Math.log(1024)),u.length-1);
    return (n/Math.pow(1024,i)).toFixed(i?1:0)+&#x27; &#x27;+u[i];
  }

  function showError(msg){ $(&#x27;errorBox&#x27;).textContent=msg; $(&#x27;errorBox&#x27;).hidden=false; }
  function hideError(){ $(&#x27;errorBox&#x27;).textContent=&#x27;&#x27;; $(&#x27;errorBox&#x27;).hidden=true; }

  function revoke(url){ if(url) URL.revokeObjectURL(url); }

  function resizeFrame(){
    try{
      var f=parent.document.getElementById(&quot;snsnote-photo-privacy-frame&quot;);
      if(f){
        var h=Math.max(520, document.documentElement.scrollHeight+8);
        f.style.height=h+&#x27;px&#x27;;
      }
    }catch(e){}
  }

  function renderMeta(meta){
    state.meta=meta||blankMeta();
    var grid=$(&#x27;metaGrid&#x27;); grid.innerHTML=&#x27;&#x27;;
    var count=0;

    FIELDS.forEach(function(row){
      var value=state.meta[row[1]];
      if(value)count++;

      var item=document.createElement(&#x27;div&#x27;); item.className=&#x27;meta-item&#x27;;
      var small=document.createElement(&#x27;small&#x27;); small.textContent=row[0];
      var strong=document.createElement(&#x27;strong&#x27;); strong.className=value?&#x27;&#x27;:&#x27;empty&#x27;;
      strong.textContent=value||&#x27;원본에서 확인되지 않음&#x27;;
      item.appendChild(small); item.appendChild(strong); grid.appendChild(item);
    });

    var badge=$(&#x27;metaBadge&#x27;);
    badge.className=&#x27;badge &#x27;+(count?&#x27;found&#x27;:&#x27;ok&#x27;);
    badge.textContent=count?count+&#x27;개 정보 확인&#x27;:&#x27;주요 메타데이터 없음&#x27;;
    $(&#x27;metaPanel&#x27;).hidden=false;
    $(&#x27;originalLayout&#x27;).classList.add(&#x27;has-photo&#x27;);
    resizeFrame();
  }

  function renderMetaFail(){
    state.meta=blankMeta();
    var grid=$(&#x27;metaGrid&#x27;); grid.innerHTML=&#x27;&#x27;;
    var item=document.createElement(&#x27;div&#x27;); item.className=&#x27;meta-item&#x27;; item.style.gridColumn=&#x27;1 / -1&#x27;;
    var small=document.createElement(&#x27;small&#x27;); small.textContent=&#x27;정보 분석&#x27;;
    var strong=document.createElement(&#x27;strong&#x27;); strong.textContent=&#x27;상세 메타데이터를 읽지 못했습니다. 사진 개인정보 제거 기능은 정상적으로 사용할 수 있습니다.&#x27;;
    item.appendChild(small); item.appendChild(strong); grid.appendChild(item);
    $(&#x27;metaBadge&#x27;).className=&#x27;badge fail&#x27;; $(&#x27;metaBadge&#x27;).textContent=&#x27;분석 불가&#x27;;
    $(&#x27;metaPanel&#x27;).hidden=false; $(&#x27;originalLayout&#x27;).classList.add(&#x27;has-photo&#x27;);
    resizeFrame();
  }

  function analyze(file){
    if(window.SNSMETA &amp;&amp; typeof window.SNSMETA.analyze===&#x27;function&#x27;){
      return window.SNSMETA.analyze(file);
    }
    return Promise.reject(new Error(&#x27;metadata parser unavailable&#x27;));
  }

  function handleFile(file){
    hideError();
    if(!file)return;

    var mime=inferMime(file);
    if(!mime){ showError(&#x27;현재 JPG, PNG, WebP 사진을 지원합니다.&#x27;); return; }
    if(file.size&gt;30*1024*1024){ showError(&#x27;파일이 너무 큽니다. 30MB 이하의 사진을 사용해주세요.&#x27;); return; }

    state.file=file; state.cleanBlob=null; state.meta=null;
    $(&#x27;donePanel&#x27;).hidden=true; $(&#x27;cleanGrid&#x27;).innerHTML=&#x27;&#x27;;

    revoke(state.previewURL);
    state.previewURL=URL.createObjectURL(file);
    $(&#x27;preview&#x27;).src=state.previewURL;
    $(&#x27;fileName&#x27;).textContent=file.name||&#x27;선택한 사진&#x27;;
    $(&#x27;fileInfo&#x27;).textContent=formatBytes(file.size);

    $(&#x27;emptyState&#x27;).hidden=true; $(&#x27;previewWrap&#x27;).hidden=false;
    $(&#x27;removeBtn&#x27;).disabled=false;
    $(&#x27;originalLayout&#x27;).classList.add(&#x27;has-photo&#x27;);
    $(&#x27;metaPanel&#x27;).hidden=false;
    $(&#x27;metaGrid&#x27;).innerHTML=&#x27;&lt;div class=&quot;meta-item&quot; style=&quot;grid-column:1/-1&quot;&gt;&lt;small&gt;사진 정보&lt;/small&gt;&lt;strong&gt;확인 중...&lt;/strong&gt;&lt;/div&gt;&#x27;;
    $(&#x27;metaBadge&#x27;).className=&#x27;badge&#x27;; $(&#x27;metaBadge&#x27;).textContent=&#x27;확인 중&#x27;;

    var img=new Image();
    var temp=URL.createObjectURL(file);
    img.onload=function(){
      $(&#x27;fileInfo&#x27;).textContent=(img.naturalWidth||0)+&#x27; × &#x27;+(img.naturalHeight||0)+&#x27;px · &#x27;+formatBytes(file.size);
      URL.revokeObjectURL(temp); resizeFrame();
    };
    img.onerror=function(){ URL.revokeObjectURL(temp); };
    img.src=temp;

    setTimeout(function(){
      analyze(file).then(renderMeta).catch(renderMetaFail);
    },0);

    resizeFrame();
  }

  function loadImage(file){
    return new Promise(function(resolve,reject){
      var url=URL.createObjectURL(file), img=new Image();
      img.onload=function(){URL.revokeObjectURL(url);resolve(img)};
      img.onerror=function(){URL.revokeObjectURL(url);reject(new Error(&#x27;사진을 불러오지 못했습니다.&#x27;))};
      img.src=url;
    });
  }

  function canvasToBlob(canvas,mime,quality){
    return new Promise(function(resolve,reject){
      canvas.toBlob(function(blob){blob?resolve(blob):reject(new Error(&#x27;새 이미지 파일을 만들지 못했습니다.&#x27;))},mime,quality);
    });
  }

  function renderClean(before,after){
    var grid=$(&#x27;cleanGrid&#x27;); grid.innerHTML=&#x27;&#x27;;
    var removed=0, remain=0, targets=0;

    FIELDS.forEach(function(row){
      var oldV=before&amp;&amp;before[row[1]], newV=after&amp;&amp;after[row[1]];
      if(!oldV)return;
      targets++;

      var box=document.createElement(&#x27;div&#x27;); box.className=&#x27;clean-row&#x27;;
      var left=document.createElement(&#x27;div&#x27;);
      var small=document.createElement(&#x27;small&#x27;); small.textContent=row[0];
      var strong=document.createElement(&#x27;strong&#x27;); strong.textContent=oldV;
      var status=document.createElement(&#x27;span&#x27;); status.className=&#x27;clean-status&#x27;;

      if(newV){box.classList.add(&#x27;remain&#x27;);status.textContent=&#x27;⚠ 남아 있음&#x27;;remain++}
      else{status.textContent=&#x27;✓ 제거됨&#x27;;removed++}

      left.appendChild(small);left.appendChild(strong);box.appendChild(left);box.appendChild(status);grid.appendChild(box);
    });

    if(!targets){
      var empty=document.createElement(&#x27;div&#x27;); empty.className=&#x27;clean-row absent&#x27;; empty.style.gridColumn=&#x27;1 / -1&#x27;;
      var left2=document.createElement(&#x27;div&#x27;);
      var s2=document.createElement(&#x27;small&#x27;); s2.textContent=&#x27;원본 사진&#x27;;
      var st2=document.createElement(&#x27;strong&#x27;); st2.textContent=&#x27;주요 개인정보 메타데이터가 확인되지 않았습니다.&#x27;;
      var status2=document.createElement(&#x27;span&#x27;); status2.className=&#x27;clean-status&#x27;; status2.textContent=&#x27;원본에 없음&#x27;;
      left2.appendChild(s2);left2.appendChild(st2);empty.appendChild(left2);empty.appendChild(status2);grid.appendChild(empty);
    }

    var badge=$(&#x27;cleanBadge&#x27;);
    if(remain){
      badge.className=&#x27;badge fail&#x27;; badge.textContent=remain+&#x27;개 확인 필요&#x27;;
      $(&#x27;successText&#x27;).textContent=&#x27;일부 메타데이터가 남아 있습니다. 오른쪽 결과를 확인해주세요.&#x27;;
    }else{
      badge.className=&#x27;badge ok&#x27;; badge.textContent=removed?removed+&#x27;개 제거 확인&#x27;:&#x27;제거 대상 없음&#x27;;
      $(&#x27;successText&#x27;).textContent=removed
        ?&#x27;원본에서 확인된 메타데이터 &#x27;+removed+&#x27;개가 새 사진에서는 확인되지 않습니다.&#x27;
        :&#x27;새 이미지 파일을 메타데이터 없이 다시 생성했습니다.&#x27;;
    }
    resizeFrame();
  }

  $(&#x27;removeBtn&#x27;).addEventListener(&#x27;click&#x27;,function(){
    if(!state.file)return;
    hideError();
    this.disabled=true; this.textContent=&#x27;개인정보 제거 중...&#x27;;

    loadImage(state.file).then(function(img){
      var canvas=document.createElement(&#x27;canvas&#x27;);
      canvas.width=img.naturalWidth||img.width;
      canvas.height=img.naturalHeight||img.height;
      var ctx=canvas.getContext(&#x27;2d&#x27;);
      if(!ctx)throw new Error(&#x27;브라우저에서 이미지 처리를 시작할 수 없습니다.&#x27;);
      ctx.drawImage(img,0,0,canvas.width,canvas.height);
      var mime=inferMime(state.file), quality=mime===&#x27;image/png&#x27;?undefined:.98;
      return canvasToBlob(canvas,mime,quality);
    }).then(function(blob){
      state.cleanBlob=blob;
      revoke(state.cleanURL); state.cleanURL=URL.createObjectURL(blob);
      $(&#x27;cleanPreview&#x27;).src=state.cleanURL;
      $(&#x27;donePanel&#x27;).hidden=false;
      $(&#x27;cleanGrid&#x27;).innerHTML=&#x27;&lt;div class=&quot;clean-row&quot; style=&quot;grid-column:1/-1&quot;&gt;&lt;div&gt;&lt;small&gt;제거 결과&lt;/small&gt;&lt;strong&gt;재검사 중...&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&#x27;;
      $(&#x27;cleanBadge&#x27;).className=&#x27;badge&#x27;; $(&#x27;cleanBadge&#x27;).textContent=&#x27;검사 중&#x27;;

      return analyze(blob).then(function(after){renderClean(state.meta||blankMeta(),after)}).catch(function(){
        renderClean(state.meta||blankMeta(),blankMeta());
      });
    }).catch(function(e){
      showError(e&amp;&amp;e.message?e.message:&#x27;개인정보 제거 중 오류가 발생했습니다.&#x27;);
    }).finally(function(){
      $(&#x27;removeBtn&#x27;).disabled=false; $(&#x27;removeBtn&#x27;).textContent=&#x27;사진 개인정보 제거하기&#x27;;
      resizeFrame();
      if(!$(&#x27;donePanel&#x27;).hidden)$(&#x27;donePanel&#x27;).scrollIntoView({behavior:&#x27;smooth&#x27;,block:&#x27;nearest&#x27;});
    });
  });

  $(&#x27;downloadBtn&#x27;).addEventListener(&#x27;click&#x27;,function(){
    if(!state.cleanBlob||!state.cleanURL||!state.file)return;
    var mime=state.cleanBlob.type||inferMime(state.file)||&#x27;image/jpeg&#x27;;
    var ext=mime===&#x27;image/png&#x27;?&#x27;.png&#x27;:mime===&#x27;image/webp&#x27;?&#x27;.webp&#x27;:&#x27;.jpg&#x27;;
    var name=state.file.name||&#x27;photo&#x27;, dot=name.lastIndexOf(&#x27;.&#x27;), base=dot&gt;0?name.slice(0,dot):name;
    var a=document.createElement(&#x27;a&#x27;);a.href=state.cleanURL;a.download=base+&#x27;-privacy-removed&#x27;+ext;
    document.body.appendChild(a);a.click();a.remove();
  });

  $(&#x27;resetBtn&#x27;).addEventListener(&#x27;click&#x27;,function(){
    revoke(state.previewURL);revoke(state.cleanURL);
    state={file:null,previewURL:null,cleanURL:null,cleanBlob:null,meta:null};
    $(&#x27;fileInput&#x27;).value=&#x27;&#x27;;$(&#x27;changeInput&#x27;).value=&#x27;&#x27;;
    $(&#x27;preview&#x27;).removeAttribute(&#x27;src&#x27;);$(&#x27;cleanPreview&#x27;).removeAttribute(&#x27;src&#x27;);
    $(&#x27;fileName&#x27;).textContent=&#x27;&#x27;;$(&#x27;fileInfo&#x27;).textContent=&#x27;&#x27;;
    $(&#x27;emptyState&#x27;).hidden=false;$(&#x27;previewWrap&#x27;).hidden=true;
    $(&#x27;originalLayout&#x27;).classList.remove(&#x27;has-photo&#x27;);$(&#x27;metaPanel&#x27;).hidden=true;
    $(&#x27;removeBtn&#x27;).disabled=true;$(&#x27;donePanel&#x27;).hidden=true;
    $(&#x27;metaGrid&#x27;).innerHTML=&#x27;&#x27;;$(&#x27;cleanGrid&#x27;).innerHTML=&#x27;&#x27;;hideError();resizeFrame();
    window.scrollTo({top:0,behavior:&#x27;smooth&#x27;});
  });

  [&#x27;dragenter&#x27;,&#x27;dragover&#x27;].forEach(function(n){
    $(&#x27;dropZone&#x27;).addEventListener(n,function(e){e.preventDefault();this.classList.add(&#x27;drag&#x27;)});
  });
  [&#x27;dragleave&#x27;,&#x27;drop&#x27;].forEach(function(n){
    $(&#x27;dropZone&#x27;).addEventListener(n,function(e){e.preventDefault();this.classList.remove(&#x27;drag&#x27;)});
  });
  $(&#x27;dropZone&#x27;).addEventListener(&#x27;drop&#x27;,function(e){
    var f=e.dataTransfer&amp;&amp;e.dataTransfer.files&amp;&amp;e.dataTransfer.files[0];
    if(f)handleFile(f);
  });

  window.SNSPHOTO={handleFile:handleFile,inferMime:inferMime};
  window.addEventListener(&#x27;load&#x27;,resizeFrame);
  if(window.ResizeObserver)new ResizeObserver(resizeFrame).observe(document.body);
})();
&lt;/script&gt;

&lt;script&gt;
/* 메타데이터 파서: 이 스크립트에 문제가 생겨도 위 사진 선택/미리보기/제거 기능은 이미 연결되어 있습니다. */
(function(){
  &#x27;use strict&#x27;;

  function blank(){
    return {make:null,model:null,gps:null,altitude:null,date:null,iso:null,focal:null,focal35:null,aperture:null,exposure:null,lensMake:null,lens:null,cameraSerial:null,lensSerial:null,software:null,author:null,copyright:null};
  }
  function ascii(b,s,n){var o=&#x27;&#x27;,e=Math.min(b.length,s+n);for(var i=s;i&lt;e;i++){if(b[i]===0)break;o+=String.fromCharCode(b[i])}return o.trim()}
  function mime(blob){
    if(blob&amp;&amp;/^(image\/jpeg|image\/png|image\/webp)$/.test(blob.type))return blob.type;
    var ext=(blob&amp;&amp;blob.name?blob.name.split(&#x27;.&#x27;).pop():&#x27;&#x27;).toLowerCase();
    if(ext===&#x27;jpg&#x27;||ext===&#x27;jpeg&#x27;)return&#x27;image/jpeg&#x27;;if(ext===&#x27;png&#x27;)return&#x27;image/png&#x27;;if(ext===&#x27;webp&#x27;)return&#x27;image/webp&#x27;;return&#x27;&#x27;;
  }
  function tiffStart(buffer,m){
    var b=new Uint8Array(buffer),v=new DataView(buffer);
    if(m===&#x27;image/jpeg&#x27;){
      if(b.length&lt;4||b[0]!==255||b[1]!==216)return-1;
      var p=2;
      while(p+4&lt;=b.length){
        if(b[p]!==255){p++;continue}
        var marker=b[p+1];if(marker===218||marker===217)break;
        if(marker===0||marker===1||(marker&gt;=208&amp;&amp;marker&lt;=215)){p+=2;continue}
        var len=v.getUint16(p+2,false);if(len&lt;2||p+2+len&gt;b.length)break;
        var d=p+4;
        if(marker===225&amp;&amp;d+6&lt;=b.length&amp;&amp;b[d]===69&amp;&amp;b[d+1]===120&amp;&amp;b[d+2]===105&amp;&amp;b[d+3]===102&amp;&amp;b[d+4]===0&amp;&amp;b[d+5]===0)return d+6;
        p+=2+len;
      }
    }
    if(m===&#x27;image/png&#x27;){
      var sig=[137,80,78,71,13,10,26,10];if(b.length&lt;8)return-1;
      for(var i=0;i&lt;8;i++)if(b[i]!==sig[i])return-1;
      var q=8;while(q+12&lt;=b.length){var n=v.getUint32(q,false),type=String.fromCharCode(b[q+4],b[q+5],b[q+6],b[q+7]);if(type===&#x27;eXIf&#x27;)return q+8;q+=12+n}
    }
    if(m===&#x27;image/webp&#x27;){
      if(b.length&lt;12||ascii(b,0,4)!==&#x27;RIFF&#x27;||ascii(b,8,4)!==&#x27;WEBP&#x27;)return-1;
      var w=12;while(w+8&lt;=b.length){var f=String.fromCharCode(b[w],b[w+1],b[w+2],b[w+3]),size=v.getUint32(w+4,true),s=w+8;
        if(f===&#x27;EXIF&#x27;){if(s+6&lt;=b.length&amp;&amp;b[s]===69&amp;&amp;b[s+1]===120&amp;&amp;b[s+2]===105&amp;&amp;b[s+3]===102&amp;&amp;b[s+4]===0&amp;&amp;b[s+5]===0)return s+6;return s}
        w=s+size+(size%2);
      }
    }
    return-1;
  }
  function parse(buffer,m){
    var out=blank(),t=tiffStart(buffer,m);if(t&lt;0)return out;
    try{
      var b=new Uint8Array(buffer),v=new DataView(buffer);if(t+8&gt;b.length)return out;
      var le;if(b[t]===73&amp;&amp;b[t+1]===73)le=true;else if(b[t]===77&amp;&amp;b[t+1]===77)le=false;else return out;
      function u16(o){return v.getUint16(o,le)}function u32(o){return v.getUint32(o,le)}function i16(o){return v.getInt16(o,le)}function i32(o){return v.getInt32(o,le)}
      if(u16(t+2)!==42)return out;
      var sizes={1:1,2:1,3:2,4:4,5:8,6:1,7:1,8:2,9:4,10:8};
      function valid(o,n){return o&gt;=0&amp;&amp;n&gt;=0&amp;&amp;o+n&lt;=b.length}
      function value(e){
        var type=u16(e+2),count=u32(e+4),unit=sizes[type];if(!unit||count&gt;20000)return null;
        var total=unit*count,pos=total&lt;=4?e+8:t+u32(e+8);if(!valid(pos,total))return null;
        if(type===2)return ascii(b,pos,count);
        function one(p){
          if(type===1||type===7)return b[p];if(type===6)return v.getInt8(p);
          if(type===3)return u16(p);if(type===8)return i16(p);if(type===4)return u32(p);
          if(type===5){var d=u32(p+4);return d?u32(p)/d:null}
          if(type===9)return i32(p);if(type===10){var ds=i32(p+4);return ds?i32(p)/ds:null}return null;
        }
        if(count===1)return one(pos);var a=[];for(var j=0;j&lt;count;j++){var x=one(pos+j*unit);if(x!==null)a.push(x)}return a;
      }
      function ifd(abs){var o={};if(!valid(abs,2))return o;var count=u16(abs);if(count&gt;700)return o;for(var i=0;i&lt;count;i++){var e=abs+2+i*12;if(!valid(e,12))break;o[u16(e)]=value(e)}return o}
      function txt(x){if(typeof x!==&#x27;string&#x27;)return null;x=x.replace(/\0/g,&#x27;&#x27;).trim();return x||null}
      function num(x){return typeof x===&#x27;number&#x27;&amp;&amp;Number.isFinite(x)?x:null}
      function pretty(x,d){x=num(x);return x===null?null:Number(x.toFixed(d===undefined?2:d)).toString()}
      var i0=ifd(t+u32(t+4)),ex={},gps={};
      if(typeof i0[0x8769]===&#x27;number&#x27;)ex=ifd(t+i0[0x8769]);if(typeof i0[0x8825]===&#x27;number&#x27;)gps=ifd(t+i0[0x8825]);
      out.make=txt(i0[0x010F]);out.model=txt(i0[0x0110]);out.date=txt(ex[0x9003])||txt(ex[0x9004])||txt(i0[0x0132]);
      out.software=txt(i0[0x0131]);out.author=txt(i0[0x013B]);out.copyright=txt(i0[0x8298]);
      var iso=num(ex[0x8827]);if(iso!==null)out.iso=&#x27;ISO &#x27;+Math.round(iso);
      var focal=num(ex[0x920A]);if(focal!==null)out.focal=pretty(focal,1)+&#x27; mm&#x27;;
      var f35=num(ex[0xA405]);if(f35!==null)out.focal35=Math.round(f35)+&#x27; mm&#x27;;
      var ap=num(ex[0x829D]);if(ap!==null)out.aperture=&#x27;f/&#x27;+pretty(ap,1);
      var exp=num(ex[0x829A]);if(exp!==null)out.exposure=exp&gt;0&amp;&amp;exp&lt;1?&#x27;1/&#x27;+Math.round(1/exp)+&#x27;초&#x27;:pretty(exp,3)+&#x27;초&#x27;;
      out.lensMake=txt(ex[0xA433]);out.lens=txt(ex[0xA434]);out.cameraSerial=txt(ex[0xA431]);out.lensSerial=txt(ex[0xA435]);
      function coord(a,r){if(!Array.isArray(a)||a.length&lt;3)return null;var c=Number(a[0])+Number(a[1])/60+Number(a[2])/3600;if(r===&#x27;S&#x27;||r===&#x27;W&#x27;)c*=-1;return Number.isFinite(c)?c:null}
      var lat=coord(gps[2],txt(gps[1])),lon=coord(gps[4],txt(gps[3]));if(lat!==null&amp;&amp;lon!==null)out.gps=lat.toFixed(6)+&#x27;, &#x27;+lon.toFixed(6);
      var alt=num(gps[6]);if(alt!==null){if(gps[5]===1)alt*=-1;out.altitude=pretty(alt,1)+&#x27; m&#x27;}
    }catch(e){return blank()}
    return out;
  }
  window.SNSMETA={
    analyze:function(blob){
      return blob.arrayBuffer().then(function(buffer){return parse(buffer,mime(blob))}).catch(function(){return blank()});
    }
  };
})();
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;"
  style="display:block;width:100%;height:620px;border:0;overflow:hidden;background:transparent"
  loading="eager">
</iframe>



<h2 class="wp-block-heading">사진 개인정보 제거 도구 사용 방법</h2>



<p class="wp-block-paragraph">사용 방법은 간단합니다.</p>



<ol class="wp-block-list">
<li>위 도구에서 <strong>사진 선택</strong>을 눌러 JPG, PNG, WebP 이미지를 불러옵니다.</li>



<li>사진이 선택되면 오른쪽에서 <strong>기기 모델, GPS 위치정보, 촬영 날짜, ISO, 초점거리 등 사진에 저장된 메타데이터</strong>를 확인할 수 있습니다.</li>



<li><strong>사진 개인정보 제거하기</strong> 버튼을 누릅니다.</li>



<li>제거가 완료되면 원본 사진에서 어떤 정보가 사라졌는지 확인할 수 있습니다.</li>



<li>마지막으로 <strong>개인정보 제거된 사진 저장</strong>을 눌러 새 이미지를 저장하면 됩니다.</li>
</ol>



<p class="wp-block-paragraph">원본 사진은 수정되지 않으며 개인정보가 제거된 <strong>새로운 이미지 파일</strong>이 별도로 만들어집니다.</p>



<p class="wp-block-paragraph">또한 사진은 SNSNOTE 서버로 전송되지 않고 <strong>현재 사용 중인 브라우저 안에서 처리됩니다.</strong></p>



<h2 class="wp-block-heading">사진 개인정보 제거 FAQ</h2>


<div id="rank-math-faq" class="rank-math-block">
<div class="rank-math-list ">
<div id="faq-question-1788620885123" class="rank-math-list-item">
<h3 class="rank-math-question ">사진에는 어떤 개인정보가 저장될 수 있나요?</h3>
<div class="rank-math-answer ">

<p>스마트폰이나 디지털카메라로 촬영한 사진에는 기기 제조사와 모델명, 촬영 날짜, GPS 위치정보, ISO, 초점거리, 조리개, 노출시간, 렌즈 정보 등이 저장될 수 있습니다. 이러한 정보를 일반적으로 EXIF 또는 사진 메타데이터라고 부릅니다.</p>

</div>
</div>
<div id="faq-question-1788620893550" class="rank-math-list-item">
<h3 class="rank-math-question ">GPS 위치정보도 제거되나요?</h3>
<div class="rank-math-answer ">

<p>원본 사진에 GPS 좌표가 저장되어 있다면 사진을 새로 생성하는 과정에서 함께 제거됩니다. 도구에서는 원본 사진에서 GPS 정보가 확인되는지 먼저 확인할 수 있습니다.</p>

</div>
</div>
<div id="faq-question-1788620901159" class="rank-math-list-item">
<h3 class="rank-math-question ">카카오톡으로 받은 사진은 정보가 안 나오는 이유가 뭔가요?</h3>
<div class="rank-math-answer ">

<p>카카오톡, 인스타그램, 일부 메신저나 SNS는 사진을 전송하거나 다시 저장하는 과정에서 EXIF 정보를 자동으로 제거하는 경우가 있습니다. 이런 사진은 원본 사진 정보에서 <strong>확인되지 않음</strong>으로 표시될 수 있습니다.</p>

</div>
</div>
<div id="faq-question-1788620908226" class="rank-math-list-item">
<h3 class="rank-math-question ">원본 사진도 변경되나요?</h3>
<div class="rank-math-answer ">

<p>아닙니다. 원본 파일은 그대로 유지됩니다. 개인정보가 제거된 이미지를 별도의 새 파일로 만들어 저장하는 방식입니다.</p>

</div>
</div>
<div id="faq-question-1788620915348" class="rank-math-list-item">
<h3 class="rank-math-question ">사진 화질이 떨어질 수 있나요?</h3>
<div class="rank-math-answer ">

<p>JPG와 WebP 사진은 새로운 이미지로 다시 생성되는 과정에서 아주 미세한 재압축이 발생할 수 있습니다. 일반적인 웹 업로드나 SNS 사용에서는 눈에 띄는 차이가 거의 없지만, 원본 보관용 사진은 따로 보관하는 것을 권장합니다.</p>

</div>
</div>
<div id="faq-question-1788620924992" class="rank-math-list-item">
<h3 class="rank-math-question ">아이폰 사진도 사용할 수 있나요?</h3>
<div class="rank-math-answer ">

<p>현재 도구는 <strong>JPG, PNG, WebP</strong> 형식을 지원합니다. 아이폰에서 HEIC 형식으로 저장된 사진은 JPG 등 지원되는 형식으로 변환한 뒤 사용할 수 있습니다.</p>

</div>
</div>
<div id="faq-question-1788620934531" class="rank-math-list-item">
<h3 class="rank-math-question ">사진이 서버에 업로드되나요?<br></h3>
<div class="rank-math-answer ">

<p>아닙니다. 사진 선택, 메타데이터 확인, 개인정보 제거 과정은 모두 사용자의 브라우저 안에서 처리됩니다.</p>

</div>
</div>
</div>
</div>


<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img loading="lazy" decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/photo-privacy-remover/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/09/photo-privacy-remover.jpg" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1561</post-id>	</item>
		<item>
		<title>스크린샷 글자 추출기, 이미지 속 글자를 초간단 텍스트로 변환</title>
		<link>https://snsnote.com/screenshot-text-extractor/</link>
					<comments>https://snsnote.com/screenshot-text-extractor/#respond</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Sat, 05 Sep 2026 08:42:50 +0000</pubDate>
				<category><![CDATA[유용한 도구]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1540</guid>

					<description><![CDATA[스크린샷이나 이미지 속 글자를 초간단으로 텍스트로 변환해보세요. 이미지 업로드, 드래그앤드롭, Ctrl+V 붙여넣기를 지원하며 한국어·영어·일본어 글자를 바로 추출할 수 있습니다.]]></description>
										<content:encoded><![CDATA[
<!-- SNSNOTE 스크린샷 글자 추출기 -->
<div id="snsnote-ocr-tool">
  <div class="ocr-card">
    <div class="ocr-head">
      <div class="ocr-icon" aria-hidden="true">⌁</div>
      <div>
        <div class="ocr-main-title" role="heading" aria-level="2">스크린샷 글자 추출기</div>
        <p>이미지를 올리거나 캡처한 화면을 <strong>Ctrl + V</strong>로 붙여넣으면 사진 속 글자를 텍스트로 변환합니다.</p>
      </div>
    </div>

    <div class="ocr-grid">
      <section class="ocr-panel">
        <div class="ocr-section-title">1. 이미지 넣기</div>

        <input id="ocrFileInput" class="ocr-file-input" type="file" accept="image/*">

        <div id="ocrDropZone" class="ocr-drop-zone" tabindex="0" role="button" aria-label="이미지 선택 또는 붙여넣기">
          <div id="ocrEmptyState" class="ocr-empty-state">
            <div class="ocr-upload-icon" aria-hidden="true">＋</div>
            <strong>이미지를 선택하거나 여기에 놓으세요</strong>
            <span>스크린샷은 Ctrl + V로 바로 붙여넣을 수 있습니다.</span>
            <button id="ocrChooseBtn" class="ocr-secondary-btn" type="button">이미지 선택</button>
            <small>JPG, PNG, WebP 등 · 최대 20MB</small>
          </div>

          <div id="ocrPreviewWrap" class="ocr-preview-wrap" hidden>
            <img id="ocrPreview" alt="OCR 분석 이미지 미리보기">
            <div class="ocr-file-meta">
              <strong id="ocrFileName"></strong>
              <span id="ocrFileInfo"></span>
            </div>
            <button id="ocrChangeBtn" class="ocr-mini-btn" type="button">다른 이미지</button>
          </div>
        </div>

        <div class="ocr-options">
          <label for="ocrLang">인식 언어</label>
          <select id="ocrLang">
            <option value="kor+eng" selected>한국어 + 영어</option>
            <option value="kor">한국어</option>
            <option value="eng">영어</option>
            <option value="jpn">일본어</option>
            <option value="jpn+eng">일본어 + 영어</option>
            <option value="kor+jpn+eng">한국어 + 일본어 + 영어</option>
          </select>
        </div>

        <button id="ocrStartBtn" class="ocr-primary-btn" type="button" disabled>
          글자 추출하기
        </button>

        <div id="ocrProgressBox" class="ocr-progress-box" hidden aria-live="polite">
          <div class="ocr-progress-head">
            <span id="ocrProgressText">OCR 준비 중&#8230;</span>
            <strong id="ocrProgressPct">0%</strong>
          </div>
          <div class="ocr-progress-track">
            <div id="ocrProgressBar" class="ocr-progress-bar"></div>
          </div>
          <small>첫 실행에서는 OCR 언어 데이터를 불러오느라 조금 더 걸릴 수 있습니다.</small>
        </div>

        <div id="ocrError" class="ocr-error" hidden></div>
      </section>

      <section class="ocr-panel ocr-result-panel">
        <div class="ocr-result-head">
          <div class="ocr-section-title">2. 추출 결과</div>
          <div id="ocrStats" class="ocr-stats" hidden>
            <span id="ocrConfidence"></span>
            <span id="ocrCharCount"></span>
          </div>
        </div>

        <div id="ocrResultEmpty" class="ocr-result-empty" hidden></div>

        <textarea id="ocrResult" class="ocr-result" placeholder="추출된 텍스트" hidden></textarea>

        <div class="ocr-result-actions">
          <button id="ocrCopyBtn" class="ocr-copy-btn" type="button" disabled>전체 복사</button>
          <button id="ocrClearBtn" class="ocr-clear-btn" type="button" disabled>초기화</button>
        </div>
      </section>
    </div>

    <div class="ocr-privacy">
      <span aria-hidden="true"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f512.png" alt="🔒" class="wp-smiley" style="height: 1em; max-height: 1em;" /></span>
      <div>
        <strong>이미지는 SNSNOTE 서버에 업로드되지 않습니다.</strong>
        <span>OCR 처리는 사용 중인 브라우저에서 진행되며, OCR 프로그램과 언어 데이터는 외부 CDN에서 불러옵니다.</span>
      </div>
    </div>
  </div>
</div>

<style>
#snsnote-ocr-tool,
#snsnote-ocr-tool * { box-sizing: border-box; }

#snsnote-ocr-tool {
  --ocr-border: #e5e7eb;
  --ocr-text: #111827;
  --ocr-muted: #6b7280;
  --ocr-soft: #f8fafc;
  --ocr-primary: #2563eb;
  --ocr-primary-dark: #1d4ed8;
  width: 100%;
  margin: 24px auto;
  font-family: inherit;
  color: var(--ocr-text);
}

#snsnote-ocr-tool .ocr-card {
  width: 100%;
  background: #fff;
  border: 1px solid var(--ocr-border);
  border-radius: 20px;
  padding: 26px;
  box-shadow: 0 10px 35px rgba(15, 23, 42, .06);
}

#snsnote-ocr-tool .ocr-head {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  margin-bottom: 24px;
}

#snsnote-ocr-tool .ocr-icon {
  display: grid;
  place-items: center;
  flex: 0 0 46px;
  width: 46px;
  height: 46px;
  border-radius: 14px;
  background: #eff6ff;
  color: var(--ocr-primary);
  font-size: 28px;
  font-weight: 800;
}

#snsnote-ocr-tool .ocr-main-title {
  display: inline-flex;
  align-items: center;
  margin: 0 0 8px;
  padding: 8px 14px;
  border: 1px solid #dbe7ff;
  border-left: 4px solid var(--ocr-primary);
  border-radius: 9px;
  background: #f5f8ff;
  color: var(--ocr-text);
  font-size: 24px;
  line-height: 1.3;
  font-weight: 800;
  box-shadow: none;
}

#snsnote-ocr-tool .ocr-head p {
  margin: 0 !important;
  color: var(--ocr-muted);
  font-size: 14px;
  line-height: 1.65;
}

#snsnote-ocr-tool .ocr-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
}

#snsnote-ocr-tool .ocr-panel {
  min-width: 0;
  border: 1px solid var(--ocr-border);
  border-radius: 16px;
  padding: 18px;
  background: #fff;
}

#snsnote-ocr-tool .ocr-section-title {
  font-size: 15px;
  line-height: 1.4;
  font-weight: 800;
  margin-bottom: 13px;
}

#snsnote-ocr-tool .ocr-file-input { display: none; }

#snsnote-ocr-tool .ocr-drop-zone {
  width: 100%;
  min-height: 360px;
  border: 2px dashed #cbd5e1;
  border-radius: 14px;
  background: var(--ocr-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
  transition: border-color .2s ease, background .2s ease, transform .2s ease;
  cursor: pointer;
  outline: none;
}

#snsnote-ocr-tool .ocr-drop-zone:hover,
#snsnote-ocr-tool .ocr-drop-zone:focus-visible,
#snsnote-ocr-tool .ocr-drop-zone.is-dragover {
  border-color: var(--ocr-primary);
  background: #eff6ff;
}

#snsnote-ocr-tool .ocr-drop-zone.is-dragover { transform: translateY(-2px); }

#snsnote-ocr-tool .ocr-empty-state {
  text-align: center;
  display: flex;
  width: 100%;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: var(--ocr-muted);
}

#snsnote-ocr-tool .ocr-empty-state strong {
  font-size: 15px;
  color: var(--ocr-text);
}

#snsnote-ocr-tool .ocr-empty-state span,
#snsnote-ocr-tool .ocr-empty-state small { font-size: 13px; }

#snsnote-ocr-tool .ocr-upload-icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin-bottom: 2px;
  border-radius: 50%;
  background: #dbeafe;
  color: var(--ocr-primary);
  font-size: 31px;
  line-height: 1;
}

#snsnote-ocr-tool button,
#snsnote-ocr-tool select,
#snsnote-ocr-tool textarea { font: inherit; }

#snsnote-ocr-tool button { cursor: pointer; }

#snsnote-ocr-tool button:disabled {
  cursor: not-allowed;
  opacity: .5;
}

#snsnote-ocr-tool .ocr-secondary-btn,
#snsnote-ocr-tool .ocr-mini-btn,
#snsnote-ocr-tool .ocr-copy-btn,
#snsnote-ocr-tool .ocr-clear-btn {
  border: 1px solid var(--ocr-border);
  background: #fff;
  color: var(--ocr-text);
  border-radius: 10px;
  font-weight: 700;
}

#snsnote-ocr-tool .ocr-secondary-btn {
  margin-top: 3px;
  padding: 10px 16px;
}

#snsnote-ocr-tool .ocr-preview-wrap {
  width: 100%;
  text-align: center;
}

#snsnote-ocr-tool .ocr-preview-wrap img {
  display: block;
  max-width: 100%;
  max-height: 560px;
  margin: 0 auto 12px;
  object-fit: contain;
  border-radius: 10px;
  box-shadow: 0 3px 15px rgba(15, 23, 42, .08);
}

#snsnote-ocr-tool .ocr-file-meta {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  margin-bottom: 10px;
}

#snsnote-ocr-tool .ocr-file-meta strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
}

#snsnote-ocr-tool .ocr-file-meta span {
  font-size: 12px;
  color: var(--ocr-muted);
}

#snsnote-ocr-tool .ocr-mini-btn { padding: 8px 12px; font-size: 12px; }

#snsnote-ocr-tool .ocr-options {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 14px;
}

#snsnote-ocr-tool .ocr-options label {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 800;
}

#snsnote-ocr-tool .ocr-options select {
  width: 230px;
  max-width: 68%;
  height: 42px;
  border: 1px solid #d1d5db;
  border-radius: 10px;
  background: #fff;
  color: var(--ocr-text);
  padding: 0 34px 0 11px;
}

#snsnote-ocr-tool .ocr-primary-btn {
  width: 100%;
  height: 48px;
  margin-top: 14px;
  border: 0;
  border-radius: 11px;
  background: var(--ocr-primary);
  color: #fff;
  font-weight: 800;
  transition: background .2s ease, transform .1s ease;
}

#snsnote-ocr-tool .ocr-primary-btn:hover:not(:disabled) { background: var(--ocr-primary-dark); }
#snsnote-ocr-tool .ocr-primary-btn:active:not(:disabled) { transform: translateY(1px); }

#snsnote-ocr-tool .ocr-progress-box {
  margin-top: 14px;
  padding: 13px;
  border-radius: 11px;
  background: #f8fafc;
  border: 1px solid var(--ocr-border);
}

#snsnote-ocr-tool .ocr-progress-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
  font-size: 12px;
}

#snsnote-ocr-tool .ocr-progress-track {
  width: 100%;
  height: 8px;
  overflow: hidden;
  border-radius: 999px;
  background: #e5e7eb;
}

#snsnote-ocr-tool .ocr-progress-bar {
  width: 0%;
  height: 100%;
  border-radius: inherit;
  background: var(--ocr-primary);
  transition: width .18s ease;
}

#snsnote-ocr-tool .ocr-progress-box small {
  display: block;
  margin-top: 8px;
  color: var(--ocr-muted);
  font-size: 11px;
  line-height: 1.5;
}

#snsnote-ocr-tool .ocr-error {
  margin-top: 12px;
  border-radius: 10px;
  background: #fef2f2;
  border: 1px solid #fecaca;
  color: #b91c1c;
  padding: 11px 12px;
  font-size: 12px;
  line-height: 1.55;
}

#snsnote-ocr-tool .ocr-result-panel {
  display: flex;
  flex-direction: column;
}

#snsnote-ocr-tool .ocr-result-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

#snsnote-ocr-tool .ocr-stats {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 13px;
  color: var(--ocr-muted);
  font-size: 11px;
}

#snsnote-ocr-tool .ocr-stats span {
  padding: 4px 7px;
  background: #f3f4f6;
  border-radius: 999px;
}

#snsnote-ocr-tool .ocr-result-empty {
  display: none !important;
}

#snsnote-ocr-tool .ocr-result {
  display: block;
  width: 100%;
  min-height: 220px;
  height: 220px;
  max-height: 560px;
  resize: vertical;
  border: 1px solid #d1d5db;
  border-radius: 13px;
  padding: 14px;
  background: #fff;
  color: var(--ocr-text);
  font-size: 14px;
  line-height: 1.7;
  outline: none;
}

#snsnote-ocr-tool .ocr-result:focus {
  border-color: var(--ocr-primary);
  box-shadow: 0 0 0 3px rgba(37, 99, 235, .10);
}

#snsnote-ocr-tool .ocr-result-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 12px;
}

#snsnote-ocr-tool .ocr-copy-btn,
#snsnote-ocr-tool .ocr-clear-btn {
  width: 100%;
  min-height: 48px;
  padding: 10px 16px;
  font-size: 13px;
  font-weight: 800;
}

#snsnote-ocr-tool .ocr-copy-btn:not(:disabled) {
  background: #111827;
  border-color: #111827;
  color: #fff;
}

#snsnote-ocr-tool .ocr-clear-btn:not(:disabled) {
  background: #fff;
  border-color: #cbd5e1;
  color: #374151;
}

#snsnote-ocr-tool .ocr-clear-btn:hover:not(:disabled) {
  background: #f8fafc;
  border-color: #94a3b8;
}

#snsnote-ocr-tool .ocr-privacy {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin-top: 18px;
  padding: 12px 14px;
  border-radius: 12px;
  background: #f0fdf4;
  border: 1px solid #dcfce7;
  color: #166534;
}

#snsnote-ocr-tool .ocr-privacy div {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

#snsnote-ocr-tool .ocr-privacy strong { font-size: 12px; }
#snsnote-ocr-tool .ocr-privacy span { font-size: 11px; line-height: 1.5; }

@media (max-width: 760px) {
  #snsnote-ocr-tool { margin: 16px auto; }
  #snsnote-ocr-tool .ocr-card { padding: 15px; border-radius: 16px; }
  #snsnote-ocr-tool .ocr-head { gap: 10px; margin-bottom: 17px; }
  #snsnote-ocr-tool .ocr-icon { width: 40px; height: 40px; flex-basis: 40px; border-radius: 12px; font-size: 24px; }
  #snsnote-ocr-tool .ocr-main-title { font-size: 20px; padding: 7px 11px; border-radius: 8px; }
  #snsnote-ocr-tool .ocr-head p { font-size: 12px; }
  #snsnote-ocr-tool .ocr-grid { grid-template-columns: 1fr; }
  #snsnote-ocr-tool .ocr-panel { padding: 14px; }
  #snsnote-ocr-tool .ocr-drop-zone { min-height: 245px; padding: 14px; }
  #snsnote-ocr-tool .ocr-preview-wrap img { max-height: 360px; }
  #snsnote-ocr-tool .ocr-options {
    align-items: center;
    justify-content: center;
    flex-direction: row;
    gap: 10px;
  }
  #snsnote-ocr-tool .ocr-options select {
    width: min(210px, 68vw);
    max-width: calc(100% - 80px);
  }
  #snsnote-ocr-tool .ocr-result { min-height: 190px; height: 190px; max-height: 460px; }
  #snsnote-ocr-tool .ocr-result-head { align-items: flex-start; flex-direction: column; gap: 0; }
  #snsnote-ocr-tool .ocr-stats { margin-top: -5px; }
  #snsnote-ocr-tool .ocr-privacy { padding: 11px; }
}
</style>

<script src="https://cdn.jsdelivr.net/npm/tesseract.js@7.0.0/dist/tesseract.min.js"></script>
<script>
(function () {
  'use strict';

  const root = document.getElementById('snsnote-ocr-tool');
  if (!root) return;

  const fileInput = root.querySelector('#ocrFileInput');
  const chooseBtn = root.querySelector('#ocrChooseBtn');
  const changeBtn = root.querySelector('#ocrChangeBtn');
  const dropZone = root.querySelector('#ocrDropZone');
  const emptyState = root.querySelector('#ocrEmptyState');
  const previewWrap = root.querySelector('#ocrPreviewWrap');
  const preview = root.querySelector('#ocrPreview');
  const fileName = root.querySelector('#ocrFileName');
  const fileInfo = root.querySelector('#ocrFileInfo');
  const langSelect = root.querySelector('#ocrLang');
  const startBtn = root.querySelector('#ocrStartBtn');
  const progressBox = root.querySelector('#ocrProgressBox');
  const progressText = root.querySelector('#ocrProgressText');
  const progressPct = root.querySelector('#ocrProgressPct');
  const progressBar = root.querySelector('#ocrProgressBar');
  const errorBox = root.querySelector('#ocrError');
  const resultEmpty = root.querySelector('#ocrResultEmpty');
  const result = root.querySelector('#ocrResult');
  const stats = root.querySelector('#ocrStats');
  const confidence = root.querySelector('#ocrConfidence');
  const charCount = root.querySelector('#ocrCharCount');
  const copyBtn = root.querySelector('#ocrCopyBtn');
  const clearBtn = root.querySelector('#ocrClearBtn');

  let selectedFile = null;
  let previewURL = null;
  let isRunning = false;

  const MAX_FILE_SIZE = 20 * 1024 * 1024;

  function formatBytes(bytes) {
    if (!Number.isFinite(bytes) || bytes <= 0) return '';
    const units = ['B', 'KB', 'MB', 'GB'];
    const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
    return (bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1) + ' ' + units[i];
  }

  function showError(message) {
    errorBox.textContent = message;
    errorBox.hidden = false;
  }

  function hideError() {
    errorBox.hidden = true;
    errorBox.textContent = '';
  }

  function setProgress(percent, text) {
    const safe = Math.max(0, Math.min(100, Math.round(percent || 0)));
    progressBar.style.width = safe + '%';
    progressPct.textContent = safe + '%';
    if (text) progressText.textContent = text;
  }

  function autoResizeResult() {
    if (result.hidden) return;
    result.style.height = 'auto';
    const minHeight = window.innerWidth <= 760 ? 190 : 220;
    const maxHeight = window.innerWidth <= 760 ? 460 : 560;
    const nextHeight = Math.max(minHeight, Math.min(result.scrollHeight + 2, maxHeight));
    result.style.height = nextHeight + 'px';
  }

  function statusToKorean(status) {
    const map = {
      'loading tesseract core': 'OCR 엔진 불러오는 중...',
      'loaded tesseract core': 'OCR 엔진 준비 완료',
      'initializing tesseract': 'OCR 엔진 초기화 중...',
      'initialized tesseract': 'OCR 엔진 초기화 완료',
      'loading language traineddata': '언어 데이터 불러오는 중...',
      'loaded language traineddata': '언어 데이터 준비 완료',
      'initializing api': '문자 인식 준비 중...',
      'initialized api': '문자 인식 준비 완료',
      'recognizing text': '이미지에서 글자 찾는 중...'
    };
    return map[status] || '글자를 분석하는 중...';
  }

  function resetResult() {
    result.value = '';
    result.hidden = true;
    resultEmpty.hidden = false;
    stats.hidden = true;
    copyBtn.disabled = true;
  }

  function resetAll() {
    if (isRunning) return;
    selectedFile = null;
    fileInput.value = '';
    if (previewURL) {
      URL.revokeObjectURL(previewURL);
      previewURL = null;
    }
    preview.removeAttribute('src');
    emptyState.hidden = false;
    previewWrap.hidden = true;
    startBtn.disabled = true;
    clearBtn.disabled = true;
    progressBox.hidden = true;
    setProgress(0, 'OCR 준비 중...');
    hideError();
    resetResult();
  }

  function loadImageDimensions(file) {
    return new Promise((resolve) => {
      const url = URL.createObjectURL(file);
      const img = new Image();
      img.onload = function () {
        const dimensions = { width: img.naturalWidth || 0, height: img.naturalHeight || 0 };
        URL.revokeObjectURL(url);
        resolve(dimensions);
      };
      img.onerror = function () {
        URL.revokeObjectURL(url);
        resolve({ width: 0, height: 0 });
      };
      img.src = url;
    });
  }

  async function selectFile(file) {
    hideError();

    if (!file || !file.type || !file.type.startsWith('image/')) {
      showError('이미지 파일만 사용할 수 있습니다. JPG, PNG, WebP 등의 파일을 선택해 주세요.');
      return;
    }

    if (file.size > MAX_FILE_SIZE) {
      showError('파일이 너무 큽니다. 20MB 이하의 이미지를 사용해 주세요.');
      return;
    }

    selectedFile = file;
    resetResult();

    if (previewURL) URL.revokeObjectURL(previewURL);
    previewURL = URL.createObjectURL(file);
    preview.src = previewURL;

    const dimensions = await loadImageDimensions(file);
    const dimText = dimensions.width && dimensions.height ? dimensions.width + ' × ' + dimensions.height + 'px · ' : '';

    fileName.textContent = file.name || '붙여넣은 스크린샷';
    fileInfo.textContent = dimText + formatBytes(file.size);
    emptyState.hidden = true;
    previewWrap.hidden = false;
    startBtn.disabled = false;
    clearBtn.disabled = false;
    progressBox.hidden = true;
    setProgress(0, 'OCR 준비 중...');
  }

  function openFilePicker(event) {
    if (event) event.stopPropagation();
    if (!isRunning) fileInput.click();
  }

  chooseBtn.addEventListener('click', openFilePicker);
  changeBtn.addEventListener('click', openFilePicker);

  dropZone.addEventListener('click', function (event) {
    if (event.target.closest('button')) return;
    if (!selectedFile) openFilePicker(event);
  });

  dropZone.addEventListener('keydown', function (event) {
    if ((event.key === 'Enter' || event.key === ' ') && !isRunning) {
      event.preventDefault();
      openFilePicker(event);
    }
  });

  fileInput.addEventListener('change', function () {
    if (fileInput.files && fileInput.files[0]) selectFile(fileInput.files[0]);
  });

  ['dragenter', 'dragover'].forEach(function (eventName) {
    dropZone.addEventListener(eventName, function (event) {
      event.preventDefault();
      if (!isRunning) dropZone.classList.add('is-dragover');
    });
  });

  ['dragleave', 'drop'].forEach(function (eventName) {
    dropZone.addEventListener(eventName, function (event) {
      event.preventDefault();
      dropZone.classList.remove('is-dragover');
    });
  });

  dropZone.addEventListener('drop', function (event) {
    if (isRunning) return;
    const files = event.dataTransfer && event.dataTransfer.files;
    if (files && files[0]) selectFile(files[0]);
  });

  document.addEventListener('paste', function (event) {
    if (isRunning || !event.clipboardData || !event.clipboardData.items) return;

    const items = Array.from(event.clipboardData.items);
    const imageItem = items.find(function (item) {
      return item.type && item.type.startsWith('image/');
    });

    if (!imageItem) return;

    const target = event.target;
    if (target && (target.tagName === 'TEXTAREA' || target.tagName === 'INPUT')) return;

    const blob = imageItem.getAsFile();
    if (!blob) return;

    event.preventDefault();
    const ext = (blob.type.split('/')[1] || 'png').replace('jpeg', 'jpg');
    const pastedFile = new File([blob], 'screenshot-' + Date.now() + '.' + ext, { type: blob.type });
    selectFile(pastedFile);
    root.scrollIntoView({ behavior: 'smooth', block: 'center' });
  });

  function prepareImageForOCR(file) {
    return new Promise((resolve, reject) => {
      const url = URL.createObjectURL(file);
      const img = new Image();

      img.onload = function () {
        try {
          let width = img.naturalWidth || img.width;
          let height = img.naturalHeight || img.height;

          if (!width || !height) throw new Error('이미지 크기를 확인할 수 없습니다.');

          const maxSide = Math.max(width, height);
          let scale = 1;

          // 작은 스크린샷은 OCR 정확도를 위해 최대 2배 확대합니다.
          if (maxSide < 1600) scale = Math.min(2, 2000 / maxSide);
          // 지나치게 큰 이미지는 브라우저 메모리 사용량을 줄입니다.
          if (maxSide > 3200) scale = 3200 / maxSide;

          width = Math.max(1, Math.round(width * scale));
          height = Math.max(1, Math.round(height * scale));

          const canvas = document.createElement('canvas');
          canvas.width = width;
          canvas.height = height;

          const ctx = canvas.getContext('2d', { alpha: false });
          if (!ctx) throw new Error('브라우저에서 이미지 처리를 시작할 수 없습니다.');

          ctx.fillStyle = '#ffffff';
          ctx.fillRect(0, 0, width, height);
          ctx.imageSmoothingEnabled = true;
          ctx.imageSmoothingQuality = 'high';
          ctx.drawImage(img, 0, 0, width, height);

          canvas.toBlob(function (blob) {
            URL.revokeObjectURL(url);
            if (blob) resolve(blob);
            else reject(new Error('이미지를 OCR용으로 변환하지 못했습니다.'));
          }, 'image/png', 1);
        } catch (error) {
          URL.revokeObjectURL(url);
          reject(error);
        }
      };

      img.onerror = function () {
        URL.revokeObjectURL(url);
        reject(new Error('이미지를 불러오지 못했습니다. 다른 이미지로 다시 시도해 주세요.'));
      };

      img.src = url;
    });
  }

  async function runOCR() {
    if (!selectedFile || isRunning) return;

    if (typeof window.Tesseract === 'undefined') {
      showError('OCR 프로그램을 불러오지 못했습니다. 인터넷 연결을 확인한 뒤 페이지를 새로고침해 주세요.');
      return;
    }

    isRunning = true;
    hideError();
    resetResult();
    startBtn.disabled = true;
    changeBtn.disabled = true;
    langSelect.disabled = true;
    clearBtn.disabled = true;
    progressBox.hidden = false;
    setProgress(1, '이미지 준비 중...');
    startBtn.textContent = '글자 추출 중...';

    let worker = null;

    try {
      const preparedImage = await prepareImageForOCR(selectedFile);
      const langValue = langSelect.value;
      const languages = langValue.indexOf('+') > -1 ? langValue.split('+') : langValue;

      setProgress(5, 'OCR 엔진 준비 중...');

      worker = await Tesseract.createWorker(languages, 1, {
        logger: function (message) {
          const p = typeof message.progress === 'number' ? message.progress : 0;
          let percent = Math.round(p * 100);

          // 언어 로딩과 실제 인식 진행률이 사용자에게 자연스럽게 보이도록 보정합니다.
          if (message.status === 'recognizing text') {
            percent = 35 + Math.round(p * 64);
          } else if (percent > 0) {
            percent = Math.min(34, Math.max(5, Math.round(percent * 0.34)));
          }

          setProgress(percent, statusToKorean(message.status));
        }
      });

      const output = await worker.recognize(preparedImage, { rotateAuto: true });
      const text = (output && output.data && output.data.text ? output.data.text : '').trim();
      const score = output && output.data && Number.isFinite(output.data.confidence)
        ? Math.round(output.data.confidence)
        : null;

      setProgress(100, '글자 추출 완료');

      if (!text) {
        result.value = '';
        result.hidden = false;
        resultEmpty.hidden = true;
        autoResizeResult();
        stats.hidden = true;
        copyBtn.disabled = true;
        showError('인식된 글자가 없습니다. 글자가 더 크고 선명한 이미지를 사용하거나 인식 언어를 바꿔 다시 시도해 주세요.');
      } else {
        result.value = text;
        result.hidden = false;
        resultEmpty.hidden = true;
        autoResizeResult();
        confidence.textContent = score !== null ? '인식도 ' + score + '%' : '인식 완료';
        charCount.textContent = text.length.toLocaleString() + '자';
        stats.hidden = false;
        copyBtn.disabled = false;
      }
    } catch (error) {
      console.error('[SNSNOTE OCR]', error);
      const message = error && error.message ? error.message : String(error || '알 수 없는 오류');
      showError('글자 추출 중 오류가 발생했습니다. 인터넷 연결을 확인하거나 이미지 크기를 줄여 다시 시도해 주세요. (' + message + ')');
      progressBox.hidden = true;
    } finally {
      if (worker) {
        try { await worker.terminate(); } catch (e) { /* ignore */ }
      }

      isRunning = false;
      startBtn.disabled = !selectedFile;
      changeBtn.disabled = false;
      langSelect.disabled = false;
      clearBtn.disabled = !selectedFile;
      startBtn.textContent = '글자 추출하기';
    }
  }

  startBtn.addEventListener('click', runOCR);

  copyBtn.addEventListener('click', async function () {
    const text = result.value;
    if (!text) return;

    try {
      if (navigator.clipboard && window.isSecureContext) {
        await navigator.clipboard.writeText(text);
      } else {
        result.focus();
        result.select();
        document.execCommand('copy');
      }
      const original = copyBtn.textContent;
      copyBtn.textContent = '복사 완료 ✓';
      setTimeout(function () { copyBtn.textContent = original; }, 1400);
    } catch (error) {
      showError('자동 복사에 실패했습니다. 결과 영역을 선택해 직접 복사해 주세요.');
    }
  });

  clearBtn.addEventListener('click', resetAll);

  result.addEventListener('input', function () {
    autoResizeResult();
    const text = result.value.trim();
    copyBtn.disabled = !text;
    charCount.textContent = text.length.toLocaleString() + '자';
  });

  window.addEventListener('resize', autoResizeResult);
})();
</script>



<h2 class="wp-block-heading">이미지 파일 형식도 바꾸고 싶다면?</h2>



<p class="wp-block-paragraph">PNG, JPG, WebP 등 이미지 파일 형식을 변경하거나 이미지 크기를 조절해야 한다면 <strong>SNSNOTE 이미지 변환기</strong>를 함께 이용해보세요.</p>



<p class="wp-block-paragraph"><a href="https://snsnote.com/image-converter/">무료 이미지 변환기 – JPG·PNG·WebP 변환 및 크기 조절</a> 바로가기</p>



<h2 class="wp-block-heading">스크린샷 글자 추출기 사용 방법</h2>



<p class="wp-block-paragraph">스크린샷이나 이미지 속 글자를 직접 타이핑할 필요 없이 위 도구를 이용하면 간단하게 텍스트로 변환할 수 있습니다.</p>



<ol class="wp-block-list">
<li><strong>이미지 선택</strong> 버튼을 눌러 스크린샷이나 사진을 불러옵니다.</li>



<li>윈도우에서 방금 캡처한 이미지라면 파일로 저장하지 않고 <strong>Ctrl + V</strong>로 바로 붙여넣을 수도 있습니다.</li>



<li>이미지에 포함된 글자에 맞게 한국어, 영어, 일본어 등 <strong>인식 언어를 선택</strong>합니다.</li>



<li><strong>글자 추출하기</strong>를 누르면 이미지 속 문자가 텍스트로 변환됩니다.</li>



<li>추출된 글자는 필요한 부분을 직접 수정한 뒤 <strong>전체 복사</strong> 버튼으로 복사할 수 있습니다.</li>
</ol>



<h2 class="wp-block-heading">이미지 글자 추출 정확도를 높이는 방법</h2>



<p class="wp-block-paragraph">OCR은 이미지에 있는 문자를 분석해 텍스트로 변환하기 때문에 원본 이미지 상태에 따라 결과가 조금씩 달라질 수 있습니다.</p>



<p class="wp-block-paragraph">가능하면 글자가 선명하고 해상도가 높은 이미지를 사용하는 것이 좋습니다. 글씨가 너무 작거나 흐릿한 사진, 심하게 기울어진 이미지, 글자 뒤에 복잡한 배경이 있는 경우에는 일부 문자가 다르게 인식될 수 있습니다.</p>



<p class="wp-block-paragraph">또한 한국어 이미지라면 한국어가 포함된 인식 언어를 선택하고, 일본어 이미지라면 일본어가 포함된 옵션을 선택하면 좀 더 정확한 결과를 얻을 수 있습니다.</p>



<h2 class="wp-block-heading">이런 경우에 유용합니다</h2>



<p class="wp-block-paragraph">복사가 되지 않는 웹페이지나 프로그램 화면을 캡처했을 때, 이미지로 받은 안내문이나 문서에서 글자만 복사하고 싶을 때 활용할 수 있습니다.</p>



<p class="wp-block-paragraph">특히 긴 문장을 직접 입력하기 번거로운 경우 스크린샷을 붙여넣은 뒤 텍스트만 추출하면 훨씬 빠르게 내용을 복사할 수 있습니다.</p>



<h2 class="wp-block-heading">자주 묻는 질문</h2>


<div id="rank-math-faq" class="rank-math-block">
<div class="rank-math-list ">
<div id="faq-question-1788597144227" class="rank-math-list-item">
<h3 class="rank-math-question ">스크린샷을 저장하지 않고 바로 글자를 추출할 수 있나요?</h3>
<div class="rank-math-answer ">

<p>가능합니다. 윈도우 캡처 도구 등으로 화면을 캡처한 후 이 페이지에서 <strong>Ctrl + V</strong>를 누르면 클립보드에 저장된 이미지를 바로 불러올 수 있습니다.</p>

</div>
</div>
<div id="faq-question-1788597154586" class="rank-math-list-item">
<h3 class="rank-math-question ">한국어와 영어가 함께 있는 이미지도 가능한가요?</h3>
<div class="rank-math-answer ">

<p>가능합니다. <strong>한국어 + 영어</strong> 옵션을 선택하면 두 언어가 함께 포함된 스크린샷도 한 번에 인식할 수 있습니다.</p>

</div>
</div>
<div id="faq-question-1788597164475" class="rank-math-list-item">
<h3 class="rank-math-question ">일본어 이미지도 글자를 추출할 수 있나요?</h3>
<div class="rank-math-answer ">

<p>지원합니다. 일본어가 포함된 인식 언어를 선택하면 일본어 스크린샷이나 이미지 속 문자를 텍스트로 변환할 수 있습니다.</p>

</div>
</div>
<div id="faq-question-1788597175739" class="rank-math-list-item">
<h3 class="rank-math-question ">이미지 속 글자가 100% 정확하게 변환되나요?</h3>
<div class="rank-math-answer ">

<p>OCR 특성상 항상 100% 정확하지는 않습니다. 작은 글씨, 흐릿한 이미지, 특수한 폰트, 복잡한 배경에서는 일부 문자가 잘못 인식될 수 있으므로 추출 후 결과를 한 번 확인하는 것이 좋습니다.</p>

</div>
</div>
<div id="faq-question-1788597185585" class="rank-math-list-item">
<h3 class="rank-math-question ">추출된 글자를 수정할 수 있나요?</h3>
<div class="rank-math-answer ">

<p>네. 결과창에 표시된 텍스트를 직접 수정한 후 <strong>전체 복사</strong> 버튼을 이용하면 됩니다.</p>

</div>
</div>
<div id="faq-question-1788597195217" class="rank-math-list-item">
<h3 class="rank-math-question ">사진이 서버에 저장되나요?</h3>
<div class="rank-math-answer ">

<p>이 도구는 이미지의 OCR 처리를 <strong>사용자의 브라우저에서 실행하는 방식</strong>으로 만들어져 있어 이미지 파일 자체를 SNSNOTE 서버에 업로드해서 보관하지 않습니다.</p>

</div>
</div>
</div>
</div>


<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img loading="lazy" decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/screenshot-text-extractor/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/09/screenshot-text-extractor.jpg" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1540</post-id>	</item>
		<item>
		<title>인스타그램 폰트 글씨체 변환기</title>
		<link>https://snsnote.com/%ec%9d%b8%ec%8a%a4%ed%83%80%ea%b7%b8%eb%9e%a8-%ed%8f%b0%ed%8a%b8-%ea%b8%80%ec%94%a8%ec%b2%b4-%eb%b3%80%ed%99%98%ea%b8%b0/</link>
					<comments>https://snsnote.com/%ec%9d%b8%ec%8a%a4%ed%83%80%ea%b7%b8%eb%9e%a8-%ed%8f%b0%ed%8a%b8-%ea%b8%80%ec%94%a8%ec%b2%b4-%eb%b3%80%ed%99%98%ea%b8%b0/#respond</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Tue, 01 Sep 2026 05:13:48 +0000</pubDate>
				<category><![CDATA[유용한 도구]]></category>
		<category><![CDATA[인스타그램]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1525</guid>

					<description><![CDATA[인스타그램, Threads, X(트위터)에서 예쁘게 보이는 특수 글씨체를 무료로 변환해드립니다.
볼드, 이탤릭, 필기체, 더블, 전각 등 6가지 스타일 지원]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center has-small-font-size wp-block-paragraph">인스타그램, Threads, X(트위터)에서 예쁘게 보이는 특수 글씨체를 무료로 변환해드립니다.<br>볼드, 이탤릭, 필기체, 더블, 전각 등 6가지 스타일 지원</p>



<!-- 왼쪽 쏠림을 방지하고 테마 틀 안에서 최대폭을 100% 채우며 중앙 정렬하는 최상위 박스 -->
<div style="width: 100%; max-width: 1200px; margin: 30px auto; padding: 30px 20px; background: #0f172a; border-radius: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.6); color: white; box-sizing: border-box; clear: both;">
  
  <h2 style="text-align:center; font-size:28px; margin-bottom:8px; color:#ffffff !important;">인스타 글씨체 변환기</h2>
  <p style="text-align:center; margin-bottom:30px; opacity:0.85; color:#e0f2fe;">영어·숫자만 예쁘게 바뀝니다.</p>

  <textarea id="inputText" rows="3" style="width:100%; background:#1e2937; border:2px solid #60a5fa; border-radius:12px; padding:16px; color:white; font-size:17px; box-sizing:border-box; display:block;" placeholder="Hello 2026"></textarea>

  <h3 style="text-align:center; margin:30px 0 20px; color:#60a5fa;"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3a8.png" alt="🎨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 글씨체 미리보기</h3>
  
  <!-- 가로 공간을 꽉 채우면서 PC 화면에서 바둑판(Grid) 형태로 정렬되는 결과창 -->
  <div id="results" style="display:grid; grid-template-columns:repeat(auto-fit, minmax(300px, 1fr)); gap:20px; width:100%; box-sizing:border-box;"></div>

  <!-- 사용 방법 -->
  <div style="margin-top:50px; background:#1e2937; padding:25px; border-radius:16px; border:1px solid #334155; box-sizing:border-box;">
    <h4 style="color:#60a5fa; margin-bottom:15px; font-size:20px;"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 사용 방법</h4>
    <ol style="line-height:1.7; color:#cbd5e1; padding-left:20px; font-size:15.5px;">
      <li>위 입력란에 영문 텍스트를 입력하세요.</li>
      <li>아래에 다양한 글씨체로 자동 변환됩니다.</li>
      <li>원하는 스타일의 <strong>복사하기</strong> 버튼을 클릭하세요.</li>
      <li>인스타그램, Threads, X 등에 바로 붙여넣기 하세요! (Ctrl + V)</li>
    </ol>
  </div>

  <!-- 자주 묻는 질문 -->
  <div style="margin-top:25px; background:#1e2937; padding:25px; border-radius:16px; border:1px solid #334155; box-sizing:border-box;">
    <h4 style="color:#60a5fa; margin-bottom:15px; font-size:20px;"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 자주 묻는 질문</h4>
    <details style="margin-bottom:12px;"><summary style="cursor:pointer; font-weight:bold; color:#e0f2fe; font-size:15.5px;">인스타그램에서 사용할 수 있나요?</summary><p style="margin-top:8px; color:#cbd5e1; font-size:15px;">네, 대부분의 SNS(인스타, Threads, X, 유튜브 등)에서 정상적으로 사용 가능합니다.</p></details>
    <details style="margin-bottom:12px;"><summary style="cursor:pointer; font-weight:bold; color:#e0f2fe; font-size:15.5px;">한글은 왜 안 바뀌나요?</summary><p style="margin-top:8px; color:#cbd5e1; font-size:15px;">한글은 유니코드 지원이 제한적이어서 현재 영문과 숫자만 변환됩니다.</p></details>
    <details><summary style="cursor:pointer; font-weight:bold; color:#e0f2fe; font-size:15.5px;">모바일에서도 되나요?</summary><p style="margin-top:8px; color:#cbd5e1; font-size:15px;">네, 모바일에서도 완벽하게 작동합니다.</p></details>
  </div>
</div>

<script>
const styles = [
  {name: "Bold (볼드)", map: {'A':'𝐀','B':'𝐁','C':'𝐂','D':'𝐃','E':'𝐄','F':'𝐅','G':'𝐆','H':'𝐇','I':'𝐈','J':'𝐉','K':'𝐊','L':'𝐋','M':'𝐌','N':'𝐍','O':'𝐎','P':'𝐏','Q':'𝐐','R':'𝐑','S':'𝐒','T':'𝐓','U':'𝐔','V':'𝐕','W':'𝐖','X':'𝐗','Y':'𝐘','Z':'𝐙','a':'𝐚','b':'𝐛','c':'𝐜','d':'𝐝','e':'𝐞','f':'𝐟','g':'𝐠','h':'𝐡','i':'𝐢','j':'𝐣','k':'𝐤','l':'𝐥','m':'𝐦','n':'𝐧','o':'𝐨','p':'𝐩','q':'𝐪','r':'𝐫','s':'𝐬','t':'𝐭','u':'𝐮','v':'𝐯','w':'𝐰','x':'𝐱','y':'𝐲','z':'𝐳','1':'𝟏','2':'𝟐','3':'𝟑','4':'𝟒','5':'𝟓','6':'𝟔','7':'𝟕','8':'𝟖','9':'𝟗','0':'𝟎'}},
  {name: "Sans Italic (산스 이탤릭)", map: {'A':'𝘈','B':'𝘉','C':'𝘊','D':'𝘋','E':'𝘌','F':'𝘍','G':'𝘎','H':'𝘏','I':'𝘐','J':'𝘑','K':'𝘒','L':'𝘓','M':'𝘔','N':'𝘕','O':'𝘖','P':'𝘗','Q':'𝘘','R':'𝘙','S':'𝘚','T':'𝘛','U':'𝘜','V':'𝘝','W':'𝘞','X':'𝘟','Y':'𝘠','Z':'𝘡','a':'𝘢','b':'𝘣','c':'𝘤','d':'𝘥','e':'𝘦','f':'𝘲','g':'𝘨','h':'𝘩','i':'𝘪','j':'𝘫','k':'𝘬','l':'𝘭','m':'𝘮','n':'𝘯','o':'𝘰','p':'𝘱','q':'𝘲','r':'𝘳','s':'𝘴','t':'𝘵','u':'𝘶','v':'𝘷','w':'𝘸','x':'𝘹','y':'𝘺','z':'𝘻','1':'𝟣','2':'𝟤','3':'𝟥','4':'𝟦','5':'𝟧','6':'𝟨','7':'𝟩','8':'𝟪','9':'𝟫','0':'𝟢'}},
  {name: "Bold Italic (볼드 이탤릭)", map: {'A':'𝘼','B':'𝘽','C':'𝘾','D':'𝘿','E':'𝙀','F':'𝙁','G':'𝙂','H':'𝙃','I':'𝙄','J':'𝙅','K':'𝙆','L':'𝙇','M':'𝙈','N':'𝙉','O':'𝙊','P':'𝙋','Q':'𝙌','R':'𝙍','S':'𝙎','T':'𝙏','U':'𝙐','V':'𝙑','W':'𝙒','X':'𝙓','Y':'𝙔','Z':'𝙕','a':'𝙖','b':'𝙗','c':'𝙘','d':'𝙙','e':'𝙚','f':'𝙛','g':'𝙜','h':'𝙝','i':'𝙞','j':'𝙟','k':'𝙠','l':'𝙡','m':'𝙢','n':'𝙣','o':'𝙤','p':'𝙥','q':'𝙦','r':'𝙧','s':'𝙨','t':'𝙩','u':'𝙪','v':'𝙫','w':'𝙬','x':'𝙭','y':'𝙮','z':'𝙯','1':'𝟭','2':'𝟮','3':'𝟯','4':'𝟰','5':'𝟱','6':'𝟲','7':'𝟳','8':'𝟴','9':'𝟵','0':'𝟬'}},
{name: "Script (필기체)", map: {'A':'𝒜','B':'ℬ','C':'𝒞','D':'𝒟','E':'ℰ','F':'ℱ','G':'𝒢','H':'ℋ','I':'ℐ','J':'𝒥','K':'𝒦','L':'ℒ','M':'ℳ','N':'𝒩','O':'𝒪','P':'𝒫','Q':'𝒬','R':'ℛ','S':'𝒮','T':'𝒯','U':'𝒰','V':'𝒱','W':'𝒲','X':'𝒳','Y':'𝒴','Z':'𝒵','a':'𝒶','b':'𝒷','c':'𝒸','d':'𝒹','e':'ℯ','f':'𝒻','g':'ℊ','h':'𝒽','i':'𝒾','j':'𝒿','k':'𝓀','l':'𝓁','m':'𝓂','n':'𝓃','o':'ℴ','p':'𝓅','q':'𝓆','r':'𝓇','s':'𝓈','t':'𝓉','u':'𝓊','v':'𝓋','w':'𝓌','x':'𝓍','y':'𝓎','z':'𝓏','1':'𝟷','2':'𝟸','3':'𝟹','4':'𝟺','5':'𝟻','6':'𝟼','7':'𝟽','8':'𝟾','9':'𝟿','0':'𝟶'}},
  {name: "Double Struck (더블)", map: {'A':'𝔸','B':'𝔹','C':'ℂ','D':'𝔻','E':'𝔼','F':'𝔽','G':'𝔾','H':'ℍ','I':'𝕀','J':'𝕁','K':'𝕂','L':'𝕃','M':'𝕄','N':'ℕ','O':'𝕆','P':'ℙ','Q':'ℚ','R':'ℝ','S':'𝕊','T':'𝕋','U':'𝕌','V':'𝕍','W':'𝕎','X':'𝕏','Y':'𝕐','Z':'ℤ','a':'𝕒','b':'𝕓','c':'𝕔','d':'𝕕','e':'𝕖','f':'𝕗','g':'𝕘','h':'𝕙','i':'𝕚','j':'𝕛','k':'𝕜','l':'𝕝','m':'𝕞','n':'𝕟','o':'𝕠','p':'𝕡','q':'𝕢','r':'𝕣','s':'𝕤','t':'𝕥','u':'𝕦','v':'𝕧','w':'𝕨','x':'𝕩','y':'𝕪','z':'𝕫','1':'𝟙','2':'𝟚','3':'𝟛','4':'𝟜','5':'𝟝','6':'𝟞','7':'𝟟','8':'𝟠','9':'𝟡','0':'𝟘'}},
  {name: "Fullwidth (전각 문자)", map: {'A':'Ａ','B':'Ｂ','C':'Ｃ','D':'Ｄ','E':'Ｅ','F':'Ｆ','G':'Ｇ','H':'Ｈ','I':'Ｉ','J':'Ｊ','K':'Ｋ','L':'Ｌ','M':'Ｍ','N':'Ｎ','O':'Ｏ','P':'Ｐ','Q':'Ｑ','R':'Ｒ','S':'Ｓ','T':'Ｔ','U':'Ｕ','V':'Ｖ','W':'Ｗ','X':'Ｘ','Y':'Ｙ','Z':'Ｚ','a':'ａ','b':'ｂ','c':'ｃ','d':'ｄ','e':'ｅ','f':'ｆ','g':'ｇ','h':'ｈ','i':'ｉ','j':'ｊ','k':'ｋ','l':'ｌ','m':'ｍ','n':'ｎ','o':'ｏ','p':'ｐ','q':'ｑ','r':'ｒ','s':'ｓ','t':'ｔ','u':'ｕ','v':'ｖ','w':'ｗ','x':'ｘ','y':'ｙ','z':'ｚ','1':'１','2':'２','3':'３','4':'４','5':'５','6':'６','7':'７','8':'８','9':'９','0':'０'}}
];

function convert(text, map) {
  return text.split('').map(c => map[c] || c).join('');
}

function generateResults(text) {
  if (!text.trim()) text = "1234567890qwertyuiopasdfghjklzxcvbnm";
  let html = '';
  styles.forEach(s => {
    const converted = convert(text, s.map);
    html += `
      <div style="background:#1e2937; color:#e2e8f0; padding:20px; border-radius:14px; border:2px solid #60a5fa; box-sizing:border-box; width:100%; display:flex; flex-direction:column; justify-content:space-between;">
        <div>
          <strong style="color:#60a5fa; display:block; margin-bottom:10px;">${s.name}</strong>
          <span style="font-size:19px; line-height:1.5; word-break:break-all; display:block; min-height:40px;">${converted}</span>
        </div>
        <button onclick="navigator.clipboard.writeText('${converted.replace(/'/g,"\\'")}')" 
                style="width:100%; background:#3b82f6; color:white; padding:14px; border:none; border-radius:10px; cursor:pointer; font-weight:bold; font-size:16px; transition:background 0.2s; box-sizing:border-box; margin-top:15px;"
                onmouseover="this.style.background='#2563eb'"
                onmouseout="this.style.background='#3b82f6'">
          &#x1f4cb; 복사하기
        </button>
      </div>`;
  });
  document.getElementById('results').innerHTML = html;
}

document.getElementById('inputText').addEventListener('input', () => generateResults(document.getElementById('inputText').value));
window.onload = () => generateResults(document.getElementById('inputText').value || 'hello 2026');
</script>



<h2 class="wp-block-heading has-text-align-center has-small-font-size" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--40)">──── SNSnote 인기 글 ────</h2>



<div class="wp-block-query is-layout-flow wp-block-query-is-layout-flow"><ul class="columns-3 wp-block-post-template is-layout-grid wp-container-core-post-template-is-layout-0fed8f92 wp-block-post-template-is-layout-grid"><li class="wp-block-post post-703 post type-post status-publish format-standard has-post-thumbnail hentry category-3 category-pc-tips">
<figure style="padding-top:var(--wp--preset--spacing--50);margin-right:0;margin-left:0" class="alignwide wp-block-post-featured-image"><a href="https://snsnote.com/%ec%ba%a1%ec%bb%b7-%ec%95%a0%eb%8d%a4%ea%b3%bc-%ec%83%a4%ea%b0%88-%eb%aa%a9%ec%86%8c%eb%a6%ac-%eb%93%9c%eb%94%94%ec%96%b4-pc%eb%b2%84%ec%a0%84-%ea%b8%b0%eb%8a%a5-%ec%97%85%eb%8d%b0%ec%9d%b4%ed%8a%b8/" target="_self"><img loading="lazy" decoding="async" width="772" height="435" src="https://snsnote.com/wp-content/uploads/2026/07/capcut-adam-and-chagall-voice-finally-pc-version-feature-update-2026.jpg" class="attachment-full size-full wp-post-image" alt="캡컷 애덤과 샤갈 목소리 드디어 PC버전 기능 업데이트 2026" style="border-top-left-radius:5px;border-top-right-radius:5px;border-bottom-left-radius:5px;border-bottom-right-radius:5px;object-fit:cover;" srcset="https://snsnote.com/wp-content/uploads/2026/07/capcut-adam-and-chagall-voice-finally-pc-version-feature-update-2026.jpg 772w, https://snsnote.com/wp-content/uploads/2026/07/capcut-adam-and-chagall-voice-finally-pc-version-feature-update-2026-300x169.jpg 300w, https://snsnote.com/wp-content/uploads/2026/07/capcut-adam-and-chagall-voice-finally-pc-version-feature-update-2026-768x433.jpg 768w" sizes="auto, (max-width: 772px) 100vw, 772px" /></a></figure>

<h3 style="padding-bottom:var(--wp--preset--spacing--30);margin-right:var(--wp--preset--spacing--20);margin-left:var(--wp--preset--spacing--20)" class="wp-block-post-title has-small-font-size"><a href="https://snsnote.com/%ec%ba%a1%ec%bb%b7-%ec%95%a0%eb%8d%a4%ea%b3%bc-%ec%83%a4%ea%b0%88-%eb%aa%a9%ec%86%8c%eb%a6%ac-%eb%93%9c%eb%94%94%ec%96%b4-pc%eb%b2%84%ec%a0%84-%ea%b8%b0%eb%8a%a5-%ec%97%85%eb%8d%b0%ec%9d%b4%ed%8a%b8/" target="_self">캡컷 애덤과 샤갈 목소리 드디어 PC버전 기능 업데이트 2026</a></h3>
</li><li class="wp-block-post post-626 post type-post status-publish format-standard has-post-thumbnail hentry category-3">
<figure style="padding-top:var(--wp--preset--spacing--50);margin-right:0;margin-left:0" class="alignwide wp-block-post-featured-image"><a href="https://snsnote.com/%ec%9d%b8%ec%8a%a4%ed%83%80%ea%b7%b8%eb%9e%a8-%ea%b3%84%ec%a0%95-%eb%b3%b5%ea%b5%ac-%eb%b0%a9%eb%b2%95/" target="_self"><img loading="lazy" decoding="async" width="772" height="435" src="https://snsnote.com/wp-content/uploads/2026/06/instagram-account-recovery-method-after-10-meta-support-attempts-review-2026.png" class="attachment-full size-full wp-post-image" alt="인스타그램 계정 복구 방법, 10번의 메타 상담원 시도 후기 2026" style="border-top-left-radius:5px;border-top-right-radius:5px;border-bottom-left-radius:5px;border-bottom-right-radius:5px;object-fit:cover;" srcset="https://snsnote.com/wp-content/uploads/2026/06/instagram-account-recovery-method-after-10-meta-support-attempts-review-2026.png 772w, https://snsnote.com/wp-content/uploads/2026/06/instagram-account-recovery-method-after-10-meta-support-attempts-review-2026-300x169.png 300w, https://snsnote.com/wp-content/uploads/2026/06/instagram-account-recovery-method-after-10-meta-support-attempts-review-2026-768x433.png 768w" sizes="auto, (max-width: 772px) 100vw, 772px" /></a></figure>

<h3 style="padding-bottom:var(--wp--preset--spacing--30);margin-right:var(--wp--preset--spacing--20);margin-left:var(--wp--preset--spacing--20)" class="wp-block-post-title has-small-font-size"><a href="https://snsnote.com/%ec%9d%b8%ec%8a%a4%ed%83%80%ea%b7%b8%eb%9e%a8-%ea%b3%84%ec%a0%95-%eb%b3%b5%ea%b5%ac-%eb%b0%a9%eb%b2%95/" target="_self">인스타그램 계정 복구 방법, 10번의 메타 상담원 시도 후기 2026</a></h3>
</li><li class="wp-block-post post-507 post type-post status-publish format-standard has-post-thumbnail hentry category-x- tag-x- tag-x---">
<figure style="padding-top:var(--wp--preset--spacing--50);margin-right:0;margin-left:0" class="alignwide wp-block-post-featured-image"><a href="https://snsnote.com/x-%ec%84%9c%ec%b9%98%eb%b0%b4-%ea%b3%a0%ec%8a%a4%ed%8a%b8%eb%b0%b4-%ed%99%95%ec%9d%b8-%ec%82%ac%ec%9d%b4%ed%8a%b8-24%ec%8b%9c%ea%b0%84-%ed%95%b4%ea%b2%b0%eb%b0%a9%eb%b2%95/" target="_self"><img loading="lazy" decoding="async" width="772" height="435" src="https://snsnote.com/wp-content/uploads/2026/06/how-to-remove-x-search-ban-ghost-ban-in-24-hours-real-methods-2026.webp" class="attachment-full size-full wp-post-image" alt="X 서치밴·고스트밴 확인 사이트와 24시간 푸는 방법 (2026년 최신)" style="border-top-left-radius:5px;border-top-right-radius:5px;border-bottom-left-radius:5px;border-bottom-right-radius:5px;object-fit:cover;" srcset="https://snsnote.com/wp-content/uploads/2026/06/how-to-remove-x-search-ban-ghost-ban-in-24-hours-real-methods-2026.webp 772w, https://snsnote.com/wp-content/uploads/2026/06/how-to-remove-x-search-ban-ghost-ban-in-24-hours-real-methods-2026-300x169.webp 300w, https://snsnote.com/wp-content/uploads/2026/06/how-to-remove-x-search-ban-ghost-ban-in-24-hours-real-methods-2026-768x433.webp 768w" sizes="auto, (max-width: 772px) 100vw, 772px" /></a></figure>

<h3 style="padding-bottom:var(--wp--preset--spacing--30);margin-right:var(--wp--preset--spacing--20);margin-left:var(--wp--preset--spacing--20)" class="wp-block-post-title has-small-font-size"><a href="https://snsnote.com/x-%ec%84%9c%ec%b9%98%eb%b0%b4-%ea%b3%a0%ec%8a%a4%ed%8a%b8%eb%b0%b4-%ed%99%95%ec%9d%b8-%ec%82%ac%ec%9d%b4%ed%8a%b8-24%ec%8b%9c%ea%b0%84-%ed%95%b4%ea%b2%b0%eb%b0%a9%eb%b2%95/" target="_self">X 서치밴·고스트밴 확인 사이트와 24시간 푸는 방법 (2026년 최신)</a></h3>
</li></ul></div>



<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img loading="lazy" decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/%ec%9d%b8%ec%8a%a4%ed%83%80%ea%b7%b8%eb%9e%a8-%ed%8f%b0%ed%8a%b8-%ea%b8%80%ec%94%a8%ec%b2%b4-%eb%b3%80%ed%99%98%ea%b8%b0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/06/instagram-font-changer-text-style-converter.webp" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1525</post-id>	</item>
		<item>
		<title>무료 다이어트 칼로리 &#038; 비만도(BMI) 계산기</title>
		<link>https://snsnote.com/diet-calorie-calculator/</link>
					<comments>https://snsnote.com/diet-calorie-calculator/#respond</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Tue, 01 Sep 2026 05:06:22 +0000</pubDate>
				<category><![CDATA[유용한 도구]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1521</guid>

					<description><![CDATA[다이어트 칼로리 및 비만도(BMI)를 무료로 계산해보세요. 나이, 키, 체중, 활동량을 입력하면 기초대사량(BMR), 유지 칼로리(TDEE), 권장 감량 칼로리와 1시간 운동별 소모 칼로리까지 즉시 안내해 드립니다.]]></description>
										<content:encoded><![CDATA[
<style>
  /* 이미지 및 CalZen 테마 기반 다크 모드 스타일 */
  .diet-calc-container {
    max-width: 520px;
    margin: 20px auto;
    padding: 30px 24px;
    background-color: #111a2e; 
    border: 2px solid #1f2d4d;
    border-radius: 16px;
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    box-sizing: border-box;
    color: #ffffff;
  }
  
  .diet-calc-container h2 {
    text-align: center;
    margin-top: 0;
    margin-bottom: 24px;
    color: #ffffff;
    font-size: 1.5rem;
    font-weight: 800;
    letter-spacing: -0.5px;
  }
  
  .form-group {
    margin-bottom: 18px;
  }
  
  .form-group label {
    display: block;
    margin-bottom: 8px;
    font-weight: 600;
    color: #94a3b8;
    font-size: 0.95rem;
  }
  
  /* 텍스트 입력창 & 셀렉트박스 글자색 강제 고정 */
  .form-group input, .form-group select {
    width: 100%;
    padding: 14px;
    background-color: rgba(10, 17, 32, 0.8) !important;
    border: 1px solid #1f2d4d;
    border-radius: 10px;
    box-sizing: border-box;
    font-size: 1rem;
    color: #ffffff !important; /* 글자색 흰색 고정 */
    -webkit-text-fill-color: #ffffff !important; /* 모바일 Safari/Chrome 글자색 고정 */
    outline: none;
    transition: all 0.2s;
  }

  /* PC 셀렉트 박스 드롭다운 목록(option) 글자색 및 배경색 수정 */
  .form-group select option {
    background-color: #0a1120 !important; /* 옵션 목록 배경 어둡게 */
    color: #ffffff !important;            /* 옵션 글자색 흰색 고정 */
    padding: 10px;
  }
  
  /* 모바일/PC 자동완성시 배경 및 글자색 덮어쓰기 방지 */
  .form-group input:-webkit-autofill,
  .form-group input:-webkit-autofill:hover, 
  .form-group input:-webkit-autofill:focus {
    -webkit-text-fill-color: #ffffff !important;
    -webkit-box-shadow: 0 0 0px 1000px #0a1120 inset !important;
    transition: background-color 5000s ease-in-out 0s;
  }

  /* 입력 placeholder 색상 설정 */
  .form-group input::placeholder {
    color: #64748b;
    opacity: 1;
  }
  
  .form-group input:focus, .form-group select:focus {
    border-color: #3b82f6;
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
    color: #ffffff !important;
  }
  
  .gender-group {
    display: flex;
    gap: 10px;
  }
  
  .gender-btn {
    flex: 1;
    padding: 14px;
    background-color: rgba(10, 17, 32, 0.8);
    border: 1px solid #1f2d4d;
    border-radius: 10px;
    cursor: pointer;
    text-align: center;
    font-weight: 700;
    color: #94a3b8;
    transition: all 0.2s;
  }
  
  .gender-btn.active {
    background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
    color: #ffffff;
    border-color: #3b82f6;
    box-shadow: 0 4px 12px rgba(37, 99, 235, 0.25);
  }
  
  .calc-btn {
    width: 100%;
    padding: 16px;
    background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
    color: #ffffff;
    border: none;
    border-radius: 10px;
    font-size: 1.1rem;
    font-weight: 700;
    cursor: pointer;
    margin-top: 10px;
    box-shadow: 0 4px 12px rgba(37, 99, 235, 0.25);
    transition: transform 0.1s, opacity 0.2s;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
  }
  
  .calc-btn:hover, .gender-btn:hover {
    opacity: 0.92;
  }
  
  .calc-btn:active, .gender-btn:active {
    transform: scale(0.98);
  }
  
  .result-box {
    margin-top: 24px;
    padding: 22px;
    background-color: #0a1120;
    border: 1px solid #1f2d4d;
    border-radius: 12px;
    display: block; 
  }
  
  .result-header {
    text-align: center;
    color: #60a5fa;
    margin-bottom: 16px;
    font-weight: 700;
    font-size: 1.1rem;
  }

  .result-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
    font-size: 0.98rem;
    color: #cbd5e1;
  }
  
  .result-item:last-child {
    margin-bottom: 0;
  }
  
  .result-item strong {
    font-size: 1.1rem;
  }

  /* BMI 게이지 디자인 (CalZen 스타일) */
  .bmi-container {
    margin: 12px 0 18px 0;
    padding: 12px 14px;
    background: rgba(15, 23, 42, 0.5);
    border: 1px solid #1f2d4d;
    border-radius: 10px;
  }

  .bmi-bar {
    display: flex;
    height: 6px;
    border-radius: 3px;
    overflow: hidden;
    margin-bottom: 6px;
  }

  .bmi-seg {
    flex: 1;
    height: 100%;
  }

  .bmi-seg.under { background-color: #38bdf8; } /* 저체중 (파랑) */
  .bmi-seg.normal { background-color: #4ade80; } /* 정상 (초록) */
  .bmi-seg.over { background-color: #fb923c; }   /* 과체중 (주황) */
  .bmi-seg.obese { background-color: #f87171; }  /* 비만 (빨강) */

  .bmi-labels {
    display: flex;
    justify-content: space-between;
    font-size: 0.75rem;
    color: #94a3b8;
    text-align: center;
  }

  .bmi-labels span {
    flex: 1;
  }

  .bmi-values {
    display: flex;
    justify-content: space-between;
    font-size: 0.7rem;
    color: #64748b;
    margin-top: 4px;
    padding: 0 22%;
  }

  /* 서브 타이틀 스타일 */
  .sub-title {
    font-size: 0.93rem;
    font-weight: 700;
    color: #94a3b8;
    margin-top: 16px;
    margin-bottom: 10px;
    display: flex;
    align-items: center;
    gap: 6px;
  }

  /* 탄단지 카드 레이아웃 */
  .macro-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
    margin-bottom: 16px;
  }

  .macro-card {
    background: rgba(15, 23, 42, 0.6);
    border: 1px solid #1f2d4d;
    border-radius: 10px;
    padding: 12px 8px;
    text-align: center;
  }

  .macro-card .macro-label {
    font-size: 0.82rem;
    color: #94a3b8;
    margin-bottom: 4px;
  }

  .macro-card .macro-val {
    font-size: 1.05rem;
    font-weight: 800;
  }

  /* 주의사항 박스 */
  .warning-box {
    margin-top: 18px;
    padding: 14px 16px;
    background-color: rgba(239, 68, 68, 0.1);
    border: 1px solid rgba(239, 68, 68, 0.3);
    border-radius: 8px;
    font-size: 0.88rem;
    color: #fca5a5;
    line-height: 1.5;
  }

  .warning-box strong {
    display: block;
    margin-bottom: 4px;
    color: #ef4444;
    font-size: 0.93rem;
  }

  .workout-box {
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px dashed #1f2d4d;
  }
</style>

<div class="diet-calc-container">
  <h2>다이어트 칼로리 &amp; 비만도(BMI) 계산기</h2>
  
  <div class="form-group">
    <label>성별</label>
    <div class="gender-group">
      <div class="gender-btn active" id="btn-female" onclick="selectGender('female')">여성</div>
      <div class="gender-btn" id="btn-male" onclick="selectGender('male')">남성</div>
    </div>
  </div>

  <div class="form-group">
    <label>나이 (세)</label>
    <input type="number" id="calc-age" placeholder="예: 30">
  </div>

  <div class="form-group">
    <label>키 (cm)</label>
    <input type="number" id="calc-height" placeholder="예: 165">
  </div>

  <div class="form-group">
    <label>체중 (kg)</label>
    <input type="number" id="calc-weight" placeholder="예: 55">
  </div>

  <div class="form-group">
    <label>활동량</label>
    <select id="calc-activity">
      <option value="1.2">매우 적음 (주로 앉아있음)</option>
      <option value="1.375" selected="">보통 (주 1~3회 가벼운 운동)</option>
      <option value="1.55">활동적 (주 3~5회 보통 운동)</option>
      <option value="1.725">매우 활동적 (주 6~7회 강한 운동)</option>
    </select>
  </div>

  <button class="calc-btn" onclick="calculateKcal()">
    <span><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /></span> 칼로리 계산하기
  </button>

  <!-- 결과 박스 -->
  <div class="result-box" id="calc-result">
    <div class="result-header"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4ca.png" alt="📊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 계산 결과</div>
    
    <!-- BMI 항목 및 슬림 바 -->
    <div class="result-item">
      <span>비만도 (BMI)</span>
      <strong id="res-bmi" style="color:#a855f7;">&#8211;</strong>
    </div>

    <div class="bmi-container">
      <div class="bmi-bar">
        <div class="bmi-seg under"></div>
        <div class="bmi-seg normal"></div>
        <div class="bmi-seg over"></div>
        <div class="bmi-seg obese"></div>
      </div>
      <div class="bmi-labels">
        <span>저체중</span>
        <span>정상</span>
        <span>과체중</span>
        <span>비만</span>
      </div>
      <div class="bmi-values">
        <span>18.5</span>
        <span>23.0</span>
        <span>25.0</span>
      </div>
    </div>

    <div class="result-item">
      <span>기초대사량 (BMR)</span>
      <strong id="res-bmr" style="color:#38bdf8;">&#8211; kcal</strong>
    </div>
    <div class="result-item">
      <span>유지 칼로리 (TDEE)</span>
      <strong id="res-tdee" style="color:#facc15;">&#8211; kcal</strong>
    </div>

    <hr style="border: 0; border-top: 1px solid #1f2d4d; margin: 15px 0;">

    <div class="result-item">
      <span><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3af.png" alt="🎯" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 다이어트 칼로리(-20%)</span>
      <strong id="res-cut" style="color:#ef4444;">&#8211; kcal</strong>
    </div>
    <div class="result-item">
      <span><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4aa.png" alt="💪" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 벌크업 칼로리 (+15%)</span>
      <strong id="res-bulk" style="color:#4ade80;">&#8211; kcal</strong>
    </div>

    <!-- 탄단지 추천 영양소 -->
    <div class="sub-title"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f957.png" alt="🥗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 권장 영양소 (탄 4 : 단 3 : 지 3)</div>
    <div class="macro-grid">
      <div class="macro-card">
        <div class="macro-label">탄수화물(40%)</div>
        <div class="macro-val" id="res-carb" style="color:#f97316;">&#8211; g</div>
      </div>
      <div class="macro-card">
        <div class="macro-label">단백질(30%)</div>
        <div class="macro-val" id="res-protein" style="color:#3b82f6;">&#8211; g</div>
      </div>
      <div class="macro-card">
        <div class="macro-label">지방(30%)</div>
        <div class="macro-val" id="res-fat" style="color:#eab308;">&#8211; g</div>
      </div>
    </div>

    <!-- 주의사항 -->
    <div class="warning-box">
      <strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 주의사항</strong>
      다이어트 칼로리보다 낮게 먹으면 대사량이 떨어지고 요요나 건강 이상이 올 수 있습니다.
    </div>

    <!-- 운동 소모 칼로리 -->
    <div class="workout-box">
      <div class="sub-title" style="margin-top:0;"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3c3-200d-2642-fe0f.png" alt="🏃‍♂️" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 1시간 운동 시 예상 소모 칼로리</div>
      <div class="result-item">
        <span><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6b6-200d-2640-fe0f.png" alt="🚶‍♀️" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 걷기 (1시간)</span>
        <strong id="res-walk" style="color:#38bdf8;">&#8211; kcal</strong>
      </div>
      <div class="result-item">
        <span><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3cb-fe0f-200d-2642-fe0f.png" alt="🏋️‍♂️" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 웨이트(1시간)</span>
        <strong id="res-weight" style="color:#38bdf8;">&#8211; kcal</strong>
      </div>
      <div class="result-item">
        <span><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3c3-200d-2642-fe0f.png" alt="🏃‍♂️" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 달리기 (1시간)</span>
        <strong id="res-run" style="color:#38bdf8;">&#8211; kcal</strong>
      </div>
    </div>
  </div>
</div>

<script>
  let selectedGender = 'female';

  function selectGender(gender) {
    selectedGender = gender;
    document.getElementById('btn-female').classList.toggle('active', gender === 'female');
    document.getElementById('btn-male').classList.toggle('active', gender === 'male');
  }

  function calculateKcal() {
    const age = parseFloat(document.getElementById('calc-age').value);
    const height = parseFloat(document.getElementById('calc-height').value);
    const weight = parseFloat(document.getElementById('calc-weight').value);
    const activity = parseFloat(document.getElementById('calc-activity').value);

    if (!age || !height || !weight) {
      alert('모든 입력 값을 올바르게 입력해주세요.');
      return;
    }

    // 1. BMI 계산 및 색상 매칭
    const heightM = height / 100;
    const bmi = weight / (heightM * heightM);
    let bmiState = '';
    let bmiColor = '#a855f7';

    if (bmi < 18.5) {
      bmiState = '저체중';
      bmiColor = '#38bdf8';
    } else if (bmi < 23) {
      bmiState = '정상';
      bmiColor = '#4ade80';
    } else if (bmi < 25) {
      bmiState = '과체중';
      bmiColor = '#fb923c';
    } else {
      bmiState = '비만';
      bmiColor = '#f87171';
    }

    // 2. BMR 계산
    let bmr = 0;
    if (selectedGender === 'male') {
      bmr = 66.47 + (13.75 * weight) + (5.003 * height) - (6.755 * age);
    } else {
      bmr = 655.1 + (9.563 * weight) + (1.85 * height) - (4.676 * age);
    }

    // 3. TDEE 및 목표 칼로리
    const tdee = bmr * activity;
    const cutKcal = tdee * 0.8;
    const bulkKcal = tdee * 1.15;

    // 4. 탄단지 계산 (탄40%, 단30%, 지30%)
    const carbG = (cutKcal * 0.4) / 4;
    const proteinG = (cutKcal * 0.3) / 4;
    const fatG = (cutKcal * 0.3) / 9;

    // 5. MET 계산
    const walkKcal = weight * 3.5;
    const weightKcal = weight * 6.0;
    const runKcal = weight * 8.0;

    // 대입
    const resBmiEl = document.getElementById('res-bmi');
    resBmiEl.innerText = bmi.toFixed(1) + ' (' + bmiState + ')';
    resBmiEl.style.color = bmiColor;

    document.getElementById('res-bmr').innerText = Math.round(bmr).toLocaleString() + ' kcal';
    document.getElementById('res-tdee').innerText = Math.round(tdee).toLocaleString() + ' kcal';
    document.getElementById('res-cut').innerText = Math.round(cutKcal).toLocaleString() + ' kcal';
    document.getElementById('res-bulk').innerText = Math.round(bulkKcal).toLocaleString() + ' kcal';

    document.getElementById('res-carb').innerText = Math.round(carbG) + ' g';
    document.getElementById('res-protein').innerText = Math.round(proteinG) + ' g';
    document.getElementById('res-fat').innerText = Math.round(fatG) + ' g';

    document.getElementById('res-walk').innerText = '약 ' + Math.round(walkKcal).toLocaleString() + ' kcal';
    document.getElementById('res-weight').innerText = '약 ' + Math.round(weightKcal).toLocaleString() + ' kcal';
    document.getElementById('res-run').innerText = '약 ' + Math.round(runKcal).toLocaleString() + ' kcal';
  }
</script>



<p class="has-text-align-center wp-block-paragraph">──── 다이어트 함께 보면 좋은 글 ────</p>



<div class="wp-block-query is-layout-flow wp-block-query-is-layout-flow"><ul class="columns-3 wp-block-post-template is-layout-grid wp-container-core-post-template-is-layout-0fed8f92 wp-block-post-template-is-layout-grid"><li class="wp-block-post post-1071 post type-post status-publish format-standard has-post-thumbnail hentry category-60 tag-61">
<figure style="padding-top:var(--wp--preset--spacing--50);margin-right:0;margin-left:0" class="alignwide wp-block-post-featured-image"><a href="https://snsnote.com/mounjaro-diet-10kg/" target="_self"><img loading="lazy" decoding="async" width="772" height="435" src="https://snsnote.com/wp-content/uploads/2026/07/mounjaro-diet-10kg.jpg" class="attachment-full size-full wp-post-image" alt="마운자로 식단 운동 없이 -10kg 도와준 3달 실제 후기" style="border-top-left-radius:5px;border-top-right-radius:5px;border-bottom-left-radius:5px;border-bottom-right-radius:5px;object-fit:cover;" srcset="https://snsnote.com/wp-content/uploads/2026/07/mounjaro-diet-10kg.jpg 772w, https://snsnote.com/wp-content/uploads/2026/07/mounjaro-diet-10kg-300x169.jpg 300w, https://snsnote.com/wp-content/uploads/2026/07/mounjaro-diet-10kg-768x433.jpg 768w" sizes="auto, (max-width: 772px) 100vw, 772px" /></a></figure>

<h3 style="padding-bottom:var(--wp--preset--spacing--30);margin-right:var(--wp--preset--spacing--20);margin-left:var(--wp--preset--spacing--20)" class="wp-block-post-title has-small-font-size"><a href="https://snsnote.com/mounjaro-diet-10kg/" target="_self">마운자로 식단 운동 없이 -10kg 도와준 3달 실제 후기</a></h3>
</li><li class="wp-block-post post-1006 post type-post status-publish format-standard has-post-thumbnail hentry category-60 tag-61">
<figure style="padding-top:var(--wp--preset--spacing--50);margin-right:0;margin-left:0" class="alignwide wp-block-post-featured-image"><a href="https://snsnote.com/mounjaro-hospital-guide/" target="_self"><img loading="lazy" decoding="async" width="1179" height="663" src="https://snsnote.com/wp-content/uploads/2026/07/mounjaro-hospital-guide.jpg" class="attachment-full size-full wp-post-image" alt="전국 마운자로 성지 찾아 싸게 처방하는 병원 찾는 방법 3가지" style="border-top-left-radius:5px;border-top-right-radius:5px;border-bottom-left-radius:5px;border-bottom-right-radius:5px;object-fit:cover;" srcset="https://snsnote.com/wp-content/uploads/2026/07/mounjaro-hospital-guide.jpg 1179w, https://snsnote.com/wp-content/uploads/2026/07/mounjaro-hospital-guide-300x169.jpg 300w, https://snsnote.com/wp-content/uploads/2026/07/mounjaro-hospital-guide-1024x576.jpg 1024w, https://snsnote.com/wp-content/uploads/2026/07/mounjaro-hospital-guide-768x432.jpg 768w" sizes="auto, (max-width: 1179px) 100vw, 1179px" /></a></figure>

<h3 style="padding-bottom:var(--wp--preset--spacing--30);margin-right:var(--wp--preset--spacing--20);margin-left:var(--wp--preset--spacing--20)" class="wp-block-post-title has-small-font-size"><a href="https://snsnote.com/mounjaro-hospital-guide/" target="_self">전국 마운자로 성지 찾아 싸게 처방하는 병원 찾는 방법 3가지</a></h3>
</li><li class="wp-block-post post-837 post type-post status-publish format-standard has-post-thumbnail hentry category-60 tag-61">
<figure style="padding-top:var(--wp--preset--spacing--50);margin-right:0;margin-left:0" class="alignwide wp-block-post-featured-image"><a href="https://snsnote.com/%eb%a7%88%ec%9a%b4%ec%9e%90%eb%a1%9c-%ec%8b%9d%eb%8b%a8%ed%91%9c-%ec%b9%bc%eb%a1%9c%eb%a6%ac-%ea%b3%84%ec%82%b0%eb%b2%95-%ec%b4%9d%ec%a0%95%eb%a6%ac/" target="_self"><img loading="lazy" decoding="async" width="1280" height="720" src="https://snsnote.com/wp-content/uploads/2026/07/마운자로-식단표-영양성분적으로-최적의-식단.jpg" class="attachment-full size-full wp-post-image" alt="마운자로 식단표 칼로리 계산법 나의 기록 3끼 총정리" style="border-top-left-radius:5px;border-top-right-radius:5px;border-bottom-left-radius:5px;border-bottom-right-radius:5px;object-fit:cover;" srcset="https://snsnote.com/wp-content/uploads/2026/07/마운자로-식단표-영양성분적으로-최적의-식단.jpg 1280w, https://snsnote.com/wp-content/uploads/2026/07/마운자로-식단표-영양성분적으로-최적의-식단-300x169.jpg 300w, https://snsnote.com/wp-content/uploads/2026/07/마운자로-식단표-영양성분적으로-최적의-식단-1024x576.jpg 1024w, https://snsnote.com/wp-content/uploads/2026/07/마운자로-식단표-영양성분적으로-최적의-식단-768x432.jpg 768w" sizes="auto, (max-width: 1280px) 100vw, 1280px" /></a></figure>

<h3 style="padding-bottom:var(--wp--preset--spacing--30);margin-right:var(--wp--preset--spacing--20);margin-left:var(--wp--preset--spacing--20)" class="wp-block-post-title has-small-font-size"><a href="https://snsnote.com/%eb%a7%88%ec%9a%b4%ec%9e%90%eb%a1%9c-%ec%8b%9d%eb%8b%a8%ed%91%9c-%ec%b9%bc%eb%a1%9c%eb%a6%ac-%ea%b3%84%ec%82%b0%eb%b2%95-%ec%b4%9d%ec%a0%95%eb%a6%ac/" target="_self">마운자로 식단표 칼로리 계산법 나의 기록 3끼 총정리</a></h3>
</li></ul></div>



<p class="wp-block-paragraph"><a href="https://snsnote.com/category/%ec%a0%95%eb%b3%b4/%ea%b1%b4%ea%b0%95-%eb%8b%a4%ec%9d%b4%ec%96%b4%ed%8a%b8/">다이어트 관련 카테고리 모든 글 바로가기&lt;</a></p>



<h2 class="wp-block-heading">칼로리 계산기 작동 원리</h2>



<ul class="wp-block-list">
<li><strong>Harris-Benedict 공식 안내:</strong> 기초대사량(BMR) 산출을 위해 검증된 공식을 사용한다는 점을 설명합니다.</li>
</ul>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>활동 수준</td><td>설명 및 생활 패턴</td><td class="has-text-align-center" data-align="center">활동 계수</td></tr><tr><td>매우 적음</td><td>주로 앉아서 생활함, 운동을 거의 하지 않음</td><td class="has-text-align-center" data-align="center">1.2</td></tr><tr><td>보통 활동</td><td>주 1~3회 가벼운 운동 또는 산책</td><td class="has-text-align-center" data-align="center">1.375</td></tr><tr><td>활동적</td><td>주 3~5회 중간 강도 이상의 운동 (웨이트, 러닝 등)</td><td class="has-text-align-center" data-align="center">1.55</td></tr><tr><td>매우 활동적</td><td>주 6~7회 고강도 운동 또는 육체 노동</td><td class="has-text-align-center" data-align="center">1.725</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">BMI(체질량지수) 판정 기준</h2>



<p class="wp-block-paragraph">한국인(아시아) 성인 기준 BMI 범위를 표 형태로 안내해 줍니다.</p>



<ul class="wp-block-list">
<li><strong>18.5 미만:</strong> 저체중</li>



<li><strong>18.5 ~ 22.9:</strong> 정상 체중</li>



<li><strong>23.0 ~ 24.9:</strong> 비만 전 단계 (과체중)</li>



<li><strong>25.0 이상:</strong> 비만</li>
</ul>



<h2 class="wp-block-heading">칼로리 &amp; 탄단지 기록이 다이어트에 효과적인 이유</h2>



<ul class="wp-block-list">
<li><strong>시각화의 힘:</strong> 내가 먹는 양을 정확히 인지함으로써 과식을 방지.</li>



<li><strong>영양 균형 유지:</strong> 무작정 굶는 다이어트가 아닌 단백질과 적정 탄수화물/지방을 챙겨 근손실 예방.</li>



<li><strong>지속 가능성:</strong> 요요 현상 없이 장기적인 체중 관리가 가능.</li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 자주 묻는 질문 (FAQ)</h2>


<div id="rank-math-faq" class="rank-math-block">
<div class="rank-math-list ">
<div id="faq-question-1785139908166" class="rank-math-list-item">
<h3 class="rank-math-question "><strong>Q1. 기초대사량(BMR)과 유지 칼로리(TDEE)의 차이는 무엇인가요?</strong></h3>
<div class="rank-math-answer ">

<p>- <strong>기초대사량(BMR):</strong> 생명 유지에 필요한 최소한의 에너지량으로, 하루 종일 아무것도 하지 않고 누워만 있어도 소모되는 칼로리입니다.<br /><strong>- 유지 칼로리(TDEE):</strong> 기초대사량에 하루 활동량과 운동량을 반영한 수치로, 현재 체중을 유지하기 위해 하루 동안 소비하는 총 칼로리입니다.</p>

</div>
</div>
<div id="faq-question-1785139918563" class="rank-math-list-item">
<h3 class="rank-math-question "><strong>Q2. 감량 권장 칼로리는 어떻게 설정되었나요?</strong></h3>
<div class="rank-math-answer ">

<p>- 건강한 체중 감량을 위해 일일 총 에너지 소비량(TDEE)에서 약 20%를 줄인 수치입니다. 급격한 단식이나 극단적인 칼로리 제한은 요요 현상과 대사 저하를 일으킬 수 있으므로 권장 칼로리를 준수하는 것이 좋습니다.</p>

</div>
</div>
<div id="faq-question-1785139924863" class="rank-math-list-item">
<h3 class="rank-math-question "><strong>Q3. 권장 탄단지(탄수화물·단백질·지방) 비율은 어떻게 계산되나요?</strong></h3>
<div class="rank-math-answer ">

<p>- 감량 목표 칼로리를 기준으로 <strong>탄수화물 40%, 단백질 30%, 지방 30%</strong> 비율로 산출됩니다. 근손실을 방지하고 지속 가능한 다이어트를 돕기 위한 보편적인 영양소 배분 방식입니다.</p>

</div>
</div>
</div>
</div>


<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 이용 안내</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">본 다이어트 칼로리 및 BMI 계산기는 표준 공식(<a href="https://en.wikipedia.org/wiki/Harris%E2%80%93Benedict_equation" target="_blank" rel="noreferrer noopener">Harris-Benedict </a>및 MET)을 기반으로 산출된 추정치를 제공합니다. 개개인의 근육량, 체지방률, 건강 상태 및 대사율에 따라 실제 소모 칼로리와 필요 영양소는 차이가 있을 수 있습니다. 본 결과는 참고용 자료이며, 정확한 식단 및 운동 처방은 전문 의료진 또는 영양사와 상담하시기 바랍니다.</p>
</blockquote>



<p class="has-text-align-center wp-block-paragraph">──── 다이어트 함께 보면 좋은 글 ────</p>



<div class="wp-block-query is-layout-flow wp-block-query-is-layout-flow"><ul class="wp-block-post-template is-layout-flow wp-block-post-template-is-layout-flow"><li class="wp-block-post post-1509 post type-post status-publish format-standard has-post-thumbnail hentry category-60">

<div class="wp-block-columns are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:25%"><figure class="wp-block-post-featured-image"><a href="https://snsnote.com/mounjaro-baby-pregnancy-birth-control/" target="_self"><img loading="lazy" decoding="async" width="772" height="435" src="https://snsnote.com/wp-content/uploads/2026/08/mounjaro-baby-pregnancy-birth-control.jpg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="마운자로 베이비 뜻은? 피임약 먹어도 임신할 수 있는 이유와 주의사항" style="object-fit:cover;" srcset="https://snsnote.com/wp-content/uploads/2026/08/mounjaro-baby-pregnancy-birth-control.jpg 772w, https://snsnote.com/wp-content/uploads/2026/08/mounjaro-baby-pregnancy-birth-control-300x169.jpg 300w, https://snsnote.com/wp-content/uploads/2026/08/mounjaro-baby-pregnancy-birth-control-768x433.jpg 768w" sizes="auto, (max-width: 772px) 100vw, 772px" /></a></figure></div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:75%"><h2 class="wp-block-post-title"><a href="https://snsnote.com/mounjaro-baby-pregnancy-birth-control/" target="_self">마운자로 베이비 뜻은? 피임약 먹어도 임신할 수 있는 이유와 주의사항</a></h2></div>
</div>

</li><li class="wp-block-post post-1500 post type-post status-publish format-standard has-post-thumbnail hentry category-60">

<div class="wp-block-columns are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:25%"><figure class="wp-block-post-featured-image"><a href="https://snsnote.com/mounjaro-blood-donation/" target="_self"><img loading="lazy" decoding="async" width="772" height="435" src="https://snsnote.com/wp-content/uploads/2026/08/mounjaro-blood-donation-guide.jpg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="2.5mg 5mg 마운자로 헌혈해도 될까? 헌혈 가능 여부와 기준 정리" style="object-fit:cover;" srcset="https://snsnote.com/wp-content/uploads/2026/08/mounjaro-blood-donation-guide.jpg 772w, https://snsnote.com/wp-content/uploads/2026/08/mounjaro-blood-donation-guide-300x169.jpg 300w, https://snsnote.com/wp-content/uploads/2026/08/mounjaro-blood-donation-guide-768x433.jpg 768w" sizes="auto, (max-width: 772px) 100vw, 772px" /></a></figure></div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:75%"><h2 class="wp-block-post-title"><a href="https://snsnote.com/mounjaro-blood-donation/" target="_self">2.5mg 5mg 마운자로 헌혈해도 될까? 헌혈 가능 여부와 기준 정리</a></h2></div>
</div>

</li><li class="wp-block-post post-1471 post type-post status-publish format-standard has-post-thumbnail hentry category-60">

<div class="wp-block-columns are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:25%"><figure class="wp-block-post-featured-image"><a href="https://snsnote.com/mounjaro-expiration-date-storage/" target="_self"><img loading="lazy" decoding="async" width="772" height="435" src="https://snsnote.com/wp-content/uploads/2026/08/mounjaro-expiration-date-storage.jpg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="마운자로 유통기한 보는 법, 냉장·실온 보관기간과 사용 전 확인할 것" style="object-fit:cover;" srcset="https://snsnote.com/wp-content/uploads/2026/08/mounjaro-expiration-date-storage.jpg 772w, https://snsnote.com/wp-content/uploads/2026/08/mounjaro-expiration-date-storage-300x169.jpg 300w, https://snsnote.com/wp-content/uploads/2026/08/mounjaro-expiration-date-storage-768x433.jpg 768w" sizes="auto, (max-width: 772px) 100vw, 772px" /></a></figure></div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:75%"><h2 class="wp-block-post-title"><a href="https://snsnote.com/mounjaro-expiration-date-storage/" target="_self">마운자로 유통기한 보는 법, 냉장·실온 보관기간과 사용 전 확인할 것</a></h2></div>
</div>

</li></ul></div>



<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img loading="lazy" decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/diet-calorie-calculator/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/07/diet-calorie-calculator.jpg" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1521</post-id>	</item>
		<item>
		<title>사진에서 마음에 든 색 그대로 찾기, 이미지 HEX·RGB·HSL 색상 추출기</title>
		<link>https://snsnote.com/image-color-picker/</link>
					<comments>https://snsnote.com/image-color-picker/#respond</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Mon, 24 Aug 2026 11:18:34 +0000</pubDate>
				<category><![CDATA[유용한 도구]]></category>
		<category><![CDATA[인스타그램]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1483</guid>

					<description><![CDATA[이미지 색상 추출이 필요할 때 사진의 원하는 위치를 클릭해 HEX·RGB·HSL 색상 코드를 바로 확인하세요. 서버에 이미지를 저장하지 않고 브라우저에서만 처리되며, 추출한 색상값은 클릭 한 번으로 간편하게 복사할 수 있습니다.]]></description>
										<content:encoded><![CDATA[<div id="sns-color-picker">
  <div class="scp-card">
    <h2>이미지 색상 코드 추출기</h2>

    <p class="scp-desc">
      이미지를 선택한 뒤 사진에서 원하는 위치를 클릭하세요.
      클릭한 지점의 HEX, RGB, HSL 값을 바로 확인하고 복사할 수 있습니다.
    </p>

    <div class="scp-privacy">
      업로드한 이미지는 서버로 전송되거나 저장되지 않습니다.
      모든 색상 추출 작업은 현재 브라우저 안에서만 처리됩니다.
    </div>

    <div class="scp-upload">
      <label for="snsColorFile" class="scp-file-btn">
        이미지 선택
      </label>

      <input id="snsColorFile" type="file" accept="image/*">

      <button id="snsColorReset" type="button" class="scp-reset-btn">
        초기화
      </button>
    </div>

    <div class="scp-grid">
      <div id="snsCanvasBox" class="scp-canvas-box">
        <div id="snsPlaceholder" class="scp-placeholder">
          이미지를 선택하면 여기에 표시됩니다.<br>
          원하는 색상 부분을 클릭해보세요.
        </div>

        <canvas id="snsColorCanvas"></canvas>
      </div>

      <div class="scp-panel">
        <div id="snsSwatch" class="scp-swatch"></div>

        <div class="scp-row">
          <div class="scp-label">HEX</div>

          <div class="scp-value-row">
            <div id="snsHex" class="scp-value">
              #FFFFFF
            </div>

            <button type="button" class="scp-copy" data-copy-target="snsHex">
              복사
            </button>
          </div>
        </div>

        <div class="scp-row">
          <div class="scp-label">RGB</div>

          <div class="scp-value-row">
            <div id="snsRgb" class="scp-value">
              rgb(255, 255, 255)
            </div>

            <button type="button" class="scp-copy" data-copy-target="snsRgb">
              복사
            </button>
          </div>
        </div>

        <div class="scp-row">
          <div class="scp-label">HSL</div>

          <div class="scp-value-row">
            <div id="snsHsl" class="scp-value">
              hsl(0, 0%, 100%)
            </div>

            <button type="button" class="scp-copy" data-copy-target="snsHsl">
              복사
            </button>
          </div>
        </div>

        <div class="scp-history-wrap">
          <div class="scp-history-title">
            최근 선택한 색상
          </div>

          <div id="snsHistory" class="scp-history"></div>
        </div>
      </div>
    </div>
  </div>
</div>

<style>
#sns-color-picker,
#sns-color-picker * {
  box-sizing: border-box;
}

#sns-color-picker {
  width: 100%;
  max-width: 1000px;
  margin: 30px auto;
  color: #1f2937;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans KR", sans-serif;
}

#sns-color-picker .scp-card {
  width: 100%;
  padding: 24px;
  background: #ffffff;
  border: 1px solid #e5e7eb;
  border-radius: 18px;
  box-shadow: 0 8px 24px rgba(0,0,0,.05);
}

#sns-color-picker h2 {
  margin: 0 0 8px;
  font-size: 28px;
  line-height: 1.4;
}

#sns-color-picker .scp-desc {
  margin: 0 0 18px;
  color: #6b7280;
  line-height: 1.7;
}

#sns-color-picker .scp-privacy {
  margin-bottom: 20px;
  padding: 13px 15px;
  background: #f3f4f6;
  border-radius: 12px;
  color: #4b5563;
  font-size: 14px;
  line-height: 1.6;
}


/* 이미지 선택 / 초기화 */

#sns-color-picker .scp-upload {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 20px;
}

#sns-color-picker #snsColorFile {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

#sns-color-picker .scp-file-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 12px 24px;
  background: #111827;
  color: #ffffff;
  border-radius: 10px;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  text-align: center;
}

#sns-color-picker .scp-file-btn:hover {
  opacity: .9;
}

#sns-color-picker .scp-reset-btn {
  min-height: 42px;
  padding: 9px 16px;
  background: #f3f4f6;
  color: #4b5563;
  border: 1px solid #d1d5db;
  border-radius: 10px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

#sns-color-picker .scp-reset-btn:hover {
  background: #e5e7eb;
}


/* 본문 */

#sns-color-picker .scp-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 20px;
  align-items: start;
  width: 100%;
}

#sns-color-picker .scp-canvas-box {
  display: grid;
  place-items: center;
  width: 100%;
  min-width: 0;
  min-height: 360px;
  overflow: hidden;
  background: #fafafa;
  border: 2px dashed #d1d5db;
  border-radius: 16px;
}

#sns-color-picker .scp-placeholder {
  padding: 30px;
  color: #9ca3af;
  text-align: center;
  line-height: 1.7;
}

#snsColorCanvas {
  display: none;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 700px;
  cursor: crosshair;
}


/* 색상 정보 패널 */

#sns-color-picker .scp-panel {
  width: 100%;
  min-width: 0;
  padding: 18px;
  background: #ffffff;
  border: 1px solid #e5e7eb;
  border-radius: 16px;
}

#sns-color-picker .scp-swatch {
  width: 100%;
  height: 90px;
  margin-bottom: 18px;
  background: #ffffff;
  border: 1px solid #d1d5db;
  border-radius: 12px;
}

#sns-color-picker .scp-row {
  margin: 14px 0;
}

#sns-color-picker .scp-label {
  margin-bottom: 6px;
  color: #6b7280;
  font-size: 13px;
  font-weight: 600;
}

#sns-color-picker .scp-value-row {
  display: flex;
  gap: 8px;
  width: 100%;
  min-width: 0;
}

#sns-color-picker .scp-value {
  flex: 1 1 auto;
  min-width: 0;
  padding: 10px 12px;
  overflow-x: auto;
  background: #f9fafb;
  border: 1px solid #d1d5db;
  border-radius: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 14px;
  white-space: nowrap;
}

#sns-color-picker .scp-copy {
  flex: 0 0 auto;
  padding: 10px 12px;
  background: #111827;
  color: #ffffff;
  border: 0;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}

#sns-color-picker .scp-copy:hover {
  opacity: .9;
}


/* 최근 색상 */

#sns-color-picker .scp-history-wrap {
  margin-top: 20px;
}

#sns-color-picker .scp-history-title {
  margin-bottom: 10px;
  font-size: 14px;
  font-weight: 700;
}

#sns-color-picker .scp-history {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

#sns-color-picker .scp-history button {
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid #d1d5db;
  border-radius: 9px;
  cursor: pointer;
}


/* 모바일 */

@media (max-width: 800px) {

  #sns-color-picker {
    width: 100%;
    max-width: 100%;
    margin: 20px 0;
  }

  #sns-color-picker .scp-card {
    padding: 16px;
    border-radius: 14px;
  }

  #sns-color-picker h2 {
    font-size: 23px;
  }

  #sns-color-picker .scp-desc {
    font-size: 15px;
  }


  /* 핵심: 이미지 선택을 가장 눈에 띄게 */

  #sns-color-picker .scp-upload {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    width: 100%;
  }

  #sns-color-picker .scp-file-btn {
    width: 100%;
    min-height: 54px;
    padding: 14px;
    font-size: 17px;
    font-weight: 700;
  }

  #sns-color-picker .scp-reset-btn {
    width: 100%;
    min-height: 38px;
    padding: 8px;
    background: #f8f9fa;
    color: #6b7280;
    font-size: 13px;
    font-weight: 500;
  }


  #sns-color-picker .scp-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }

  #sns-color-picker .scp-canvas-box {
    min-height: 240px;
  }

  #snsColorCanvas {
    width: 100%;
    max-width: 100%;
    height: auto;
  }

  #sns-color-picker .scp-panel {
    padding: 14px;
  }

  #sns-color-picker .scp-value {
    padding: 9px 8px;
    font-size: 12px;
  }

  #sns-color-picker .scp-copy {
    padding: 9px 11px;
    font-size: 13px;
  }
}


@media (max-width: 420px) {

  #sns-color-picker .scp-card {
    padding: 14px;
  }

  #sns-color-picker h2 {
    font-size: 21px;
  }

  #sns-color-picker .scp-privacy {
    padding: 11px 12px;
    font-size: 13px;
  }

  #sns-color-picker .scp-value-row {
    gap: 6px;
  }

  #sns-color-picker .scp-value {
    font-size: 11px;
  }
}
</style>

<script>
(function () {

  const root = document.getElementById('sns-color-picker');

  if (!root) return;

  const fileInput = document.getElementById('snsColorFile');
  const resetBtn = document.getElementById('snsColorReset');

  const canvas = document.getElementById('snsColorCanvas');

  const ctx = canvas.getContext(
    '2d',
    { willReadFrequently: true }
  );

  const placeholder =
    document.getElementById('snsPlaceholder');

  const swatch =
    document.getElementById('snsSwatch');

  const hexBox =
    document.getElementById('snsHex');

  const rgbBox =
    document.getElementById('snsRgb');

  const hslBox =
    document.getElementById('snsHsl');

  const historyBox =
    document.getElementById('snsHistory');

  const history = [];

  let objectUrl = null;


  fileInput.addEventListener('change', function () {

    const file =
      this.files &&
      this.files[0];

    if (!file) return;


    if (!file.type.startsWith('image/')) {

      alert('이미지 파일만 선택할 수 있습니다.');

      this.value = '';

      return;
    }


    if (objectUrl) {

      URL.revokeObjectURL(objectUrl);

    }


    objectUrl =
      URL.createObjectURL(file);


    const img =
      new Image();


    img.onload = function () {

      canvas.width =
        img.naturalWidth;

      canvas.height =
        img.naturalHeight;


      ctx.clearRect(
        0,
        0,
        canvas.width,
        canvas.height
      );


      ctx.drawImage(
        img,
        0,
        0
      );


      canvas.style.display =
        'block';

      placeholder.style.display =
        'none';


      URL.revokeObjectURL(
        objectUrl
      );


      objectUrl =
        null;

    };


    img.onerror = function () {

      alert('이미지를 불러오지 못했습니다.');

    };


    img.src =
      objectUrl;

  });


  canvas.addEventListener('click', function (e) {

    const rect =
      canvas.getBoundingClientRect();


    const scaleX =
      canvas.width /
      rect.width;


    const scaleY =
      canvas.height /
      rect.height;


    let x =
      Math.floor(
        (e.clientX - rect.left) *
        scaleX
      );


    let y =
      Math.floor(
        (e.clientY - rect.top) *
        scaleY
      );


    x =
      Math.max(
        0,
        Math.min(
          canvas.width - 1,
          x
        )
      );


    y =
      Math.max(
        0,
        Math.min(
          canvas.height - 1,
          y
        )
      );


    const pixel =
      ctx.getImageData(
        x,
        y,
        1,
        1
      ).data;


    const r =
      pixel[0];

    const g =
      pixel[1];

    const b =
      pixel[2];


    updateColor(
      r,
      g,
      b
    );

  });


  function updateColor(r, g, b) {

    const hex =
      rgbToHex(
        r,
        g,
        b
      );


    const hsl =
      rgbToHsl(
        r,
        g,
        b
      );


    swatch.style.background =
      hex;


    hexBox.textContent =
      hex;


    rgbBox.textContent =
      `rgb(${r}, ${g}, ${b})`;


    hslBox.textContent =
      `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;


    addHistory(
      hex,
      r,
      g,
      b,
      hsl
    );

  }


  function rgbToHex(r, g, b) {

    return '#' +

      [r, g, b]

      .map(function (v) {

        return v
          .toString(16)
          .padStart(2, '0');

      })

      .join('')

      .toUpperCase();

  }


  function rgbToHsl(r, g, b) {

    r /= 255;

    g /= 255;

    b /= 255;


    const max =
      Math.max(r, g, b);


    const min =
      Math.min(r, g, b);


    let h = 0;

    let s = 0;


    const l =
      (max + min) / 2;


    if (max !== min) {

      const d =
        max - min;


      s =
        l > 0.5

        ? d /
          (2 - max - min)

        : d /
          (max + min);


      switch (max) {

        case r:

          h =
            (g - b) / d +
            (g < b ? 6 : 0);

          break;


        case g:

          h =
            (b - r) / d + 2;

          break;


        case b:

          h =
            (r - g) / d + 4;

          break;

      }


      h /= 6;

    }


    return {

      h:
        Math.round(h * 360),

      s:
        Math.round(s * 100),

      l:
        Math.round(l * 100)

    };

  }


  function addHistory(
    hex,
    r,
    g,
    b,
    hsl
  ) {

    if (
      history.length &&
      history[0].hex === hex
    ) {
      return;
    }


    history.unshift({

      hex: hex,

      r: r,

      g: g,

      b: b,

      hsl: hsl

    });


    if (
      history.length > 8
    ) {

      history.pop();

    }


    historyBox.innerHTML =
      '';


    history.forEach(function (item) {

      const btn =
        document.createElement(
          'button'
        );


      btn.type =
        'button';


      btn.title =
        item.hex;


      btn.style.background =
        item.hex;


      btn.addEventListener(
        'click',
        function () {

          swatch.style.background =
            item.hex;


          hexBox.textContent =
            item.hex;


          rgbBox.textContent =
            `rgb(${item.r}, ${item.g}, ${item.b})`;


          hslBox.textContent =
            `hsl(${item.hsl.h}, ${item.hsl.s}%, ${item.hsl.l}%)`;

        }
      );


      historyBox.appendChild(
        btn
      );

    });

  }


  root
  .querySelectorAll(
    '[data-copy-target]'
  )
  .forEach(function (button) {

    button.addEventListener(
      'click',
      async function () {

        const target =
          document.getElementById(
            this.dataset.copyTarget
          );


        const text =
          target.textContent;


        const oldText =
          this.textContent;


        try {

          await navigator
            .clipboard
            .writeText(text);

        }

        catch (e) {

          const textarea =
            document.createElement(
              'textarea'
            );


          textarea.value =
            text;


          textarea.style.position =
            'fixed';


          textarea.style.opacity =
            '0';


          document.body.appendChild(
            textarea
          );


          textarea.select();


          document.execCommand(
            'copy'
          );


          textarea.remove();

        }


        this.textContent =
          '완료';


        setTimeout(() => {

          this.textContent =
            oldText;

        }, 1000);

      }
    );

  });


  resetBtn.addEventListener(
    'click',
    function () {

      fileInput.value =
        '';


      canvas.style.display =
        'none';


      placeholder.style.display =
        'block';


      ctx.clearRect(
        0,
        0,
        canvas.width,
        canvas.height
      );


      canvas.width =
        0;


      canvas.height =
        0;


      swatch.style.background =
        '#FFFFFF';


      hexBox.textContent =
        '#FFFFFF';


      rgbBox.textContent =
        'rgb(255, 255, 255)';


      hslBox.textContent =
        'hsl(0, 0%, 100%)';


      history.length =
        0;


      historyBox.innerHTML =
        '';


      if (objectUrl) {

        URL.revokeObjectURL(
          objectUrl
        );


        objectUrl =
          null;

      }

    }
  );

})();
</script>



<p class="wp-block-paragraph">사진이나 이미지에서 정확한 색상 코드를 확인하고 싶을 때는 위의 이미지 색상 코드 추출기를 이용하면 됩니다.</p>



<p class="wp-block-paragraph">사진에서 원하는 색상 코드를 찾았다면 이미지 파일 형식이나 크기도 바로 변경할 수 있습니다.<br>JPG·PNG·WebP 변환이나 이미지 크기 조절이 필요하다면 <a href="https://snsnote.com/image-converter/">무료 이미지 변환기</a>도 함께 이용해보세요.</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-snsnote wp-block-embed-snsnote"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="Omq2sjtJYm"><a href="https://snsnote.com/image-converter/">무료 이미지 변환기 – JPG·PNG·WebP 변환 및 크기 조절</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="“무료 이미지 변환기 – JPG·PNG·WebP 변환 및 크기 조절” — SNSnote" src="https://snsnote.com/image-converter/embed/#?secret=M2NDsmwJSl#?secret=Omq2sjtJYm" data-secret="Omq2sjtJYm" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p class="wp-block-paragraph">별도의 프로그램을 설치하거나 이미지를 서버에 업로드할 필요 없이, 브라우저에서 이미지를 선택한 뒤 원하는 부분을 클릭하면 해당 위치의 색상값을 바로 확인할 수 있습니다.</p>



<ol class="wp-block-list">
<li>색상을 확인할 이미지를 선택합니다.</li>



<li>화면에 표시된 이미지에서 원하는 위치를 클릭합니다.</li>



<li>클릭한 지점의 HEX, RGB, HSL 색상 코드가 자동으로 표시됩니다.</li>



<li>필요한 색상 코드 옆의 복사 버튼을 누르면 클립보드에 바로 복사됩니다.</li>



<li>다른 위치의 색상을 확인하고 싶다면 이미지의 원하는 부분을 다시 클릭하면 됩니다.</li>
</ol>



<p class="wp-block-paragraph">최근 선택한 색상도 함께 표시되기 때문에 한 이미지에서 여러 색상을 비교할 때 편리합니다.</p>



<h2 class="wp-block-heading">HEX, RGB, HSL은 어떤 차이가 있나요?</h2>



<p class="wp-block-paragraph">사진에서 색상을 추출하면 같은 색상이라도 HEX, RGB, HSL처럼 여러 방식으로 표시할 수 있습니다.</p>



<h3 class="wp-block-heading">HEX 색상 코드</h3>



<p class="wp-block-paragraph">HEX는 웹사이트나 디자인 작업에서 가장 많이 사용되는 색상 표현 방식 중 하나입니다.</p>



<p class="wp-block-paragraph">예를 들어 흰색은 <code>#FFFFFF</code>, 검은색은 <code>#000000</code>처럼 표시됩니다.</p>



<p class="wp-block-paragraph">CSS나 웹디자인에서 색상을 지정할 때 간단하게 사용할 수 있어 블로그 꾸미기나 웹사이트 디자인 작업에서 특히 많이 사용됩니다.</p>



<h3 class="wp-block-heading">RGB 색상 코드</h3>



<p class="wp-block-paragraph">RGB는 Red, Green, Blue의 세 가지 색상값을 조합해서 색상을 표현하는 방식입니다.</p>



<p class="wp-block-paragraph">예를 들어 흰색은 <code>rgb(255, 255, 255)</code>로 표시되고 검은색은 <code>rgb(0, 0, 0)</code>으로 표시됩니다.</p>



<p class="wp-block-paragraph">이미지 편집이나 웹디자인 프로그램에서 색상을 직접 입력해야 할 때 자주 사용됩니다.</p>



<h3 class="wp-block-heading">HSL 색상 코드</h3>



<p class="wp-block-paragraph">HSL은 Hue(색상), Saturation(채도), Lightness(명도)를 기준으로 색상을 표현합니다.</p>



<p class="wp-block-paragraph">RGB나 HEX보다 사람이 색상의 밝기나 채도를 조절하기 쉽게 이해할 수 있다는 장점이 있습니다.</p>



<p class="wp-block-paragraph">예를 들어 같은 색상을 조금 더 밝게 만들거나 채도를 낮추고 싶을 때 HSL 값을 이용하면 편리합니다.</p>



<h2 class="wp-block-heading">사진에서 색상 코드를 추출하면 언제 유용할까?</h2>



<p class="wp-block-paragraph">이미지 색상 추출 기능은 단순히 사진의 색을 확인하는 용도 외에도 여러 상황에서 사용할 수 있습니다.</p>



<p class="wp-block-paragraph">예를 들어 마음에 드는 사진에서 배경색과 비슷한 색상을 웹사이트에 적용하거나, 로고 이미지에서 정확한 브랜드 색상을 확인할 때 유용합니다.</p>



<p class="wp-block-paragraph">인스타그램 피드나 블로그 디자인을 꾸밀 때도 사진 속 색상을 추출해 제목이나 버튼, 배경색에 적용하면 전체적인 분위기를 자연스럽게 맞출 수 있습니다.</p>



<p class="wp-block-paragraph">또한 디자인 작업 중 참고 이미지에서 원하는 색상을 발견했지만 정확한 HEX 코드나 RGB 값을 모를 때도 이미지에서 직접 클릭해 빠르게 확인할 수 있습니다.</p>



<h2 class="wp-block-heading">이미지는 서버에 업로드되나요?</h2>



<p class="wp-block-paragraph">이 색상 코드 추출기는 이미지를 서버로 전송하지 않습니다.</p>



<p class="wp-block-paragraph">사용자가 선택한 이미지는 현재 사용 중인 브라우저에서만 불러오며, 색상 추출 과정 역시 브라우저 내부에서 처리됩니다.</p>



<p class="wp-block-paragraph">이미지를 클릭하면 브라우저가 해당 위치의 픽셀 색상값을 읽어 HEX, RGB, HSL 형식으로 변환해서 보여주는 방식입니다.</p>



<p class="wp-block-paragraph">따라서 선택한 사진과 색상 추출 결과가 SNSnote 서버에 저장되지 않습니다.</p>



<p class="wp-block-paragraph">페이지를 닫거나 새로고침하면 선택했던 이미지도 브라우저 화면에서 사라집니다.</p>



<h2 class="wp-block-heading">자주 묻는 질문</h2>



<h3 class="wp-block-heading">이미지 색상 코드 추출은 무료인가요?</h3>



<p class="wp-block-paragraph">네. 별도의 회원가입이나 프로그램 설치 없이 무료로 사용할 수 있습니다.</p>



<p class="wp-block-paragraph">이미지를 선택하고 원하는 부분을 클릭하면 HEX, RGB, HSL 색상값을 바로 확인할 수 있습니다.</p>



<h3 class="wp-block-heading">업로드한 사진이 서버에 저장되나요?</h3>



<p class="wp-block-paragraph">아니요.</p>



<p class="wp-block-paragraph">이미지는 서버로 업로드되거나 저장되지 않으며 사용자의 브라우저에서만 처리됩니다.</p>



<h3 class="wp-block-heading">사진의 원하는 위치를 직접 선택할 수 있나요?</h3>



<p class="wp-block-paragraph">네.</p>



<p class="wp-block-paragraph">이미지가 화면에 표시되면 색상을 알고 싶은 위치를 직접 클릭하면 됩니다.</p>



<p class="wp-block-paragraph">클릭한 픽셀의 색상을 기준으로 HEX, RGB, HSL 값이 자동으로 표시됩니다.</p>



<h3 class="wp-block-heading">휴대폰에서도 사용할 수 있나요?</h3>



<p class="wp-block-paragraph">네.</p>



<p class="wp-block-paragraph">모바일 브라우저에서도 이미지를 선택한 뒤 화면에 표시된 사진의 원하는 부분을 터치해서 색상을 확인할 수 있습니다.</p>



<p class="wp-block-paragraph">다만 작은 영역의 색상을 정확하게 선택하려면 화면을 크게 사용하는 PC 환경이 조금 더 편할 수 있습니다.</p>



<h3 class="wp-block-heading">JPG, PNG, WebP 이미지도 사용할 수 있나요?</h3>



<p class="wp-block-paragraph">브라우저에서 정상적으로 표시할 수 있는 일반적인 이미지 파일이라면 사용할 수 있습니다.</p>



<p class="wp-block-paragraph">JPG, PNG, WebP 등 대부분의 이미지 형식을 브라우저에서 바로 불러와 색상을 확인할 수 있습니다.</p>



<h3 class="wp-block-heading">이미지 품질이 낮으면 색상 코드가 달라질 수 있나요?</h3>



<p class="wp-block-paragraph">가능합니다.</p>



<p class="wp-block-paragraph">특히 JPG처럼 압축이 적용된 이미지는 원본 이미지와 픽셀 색상이 조금 달라질 수 있습니다.</p>



<p class="wp-block-paragraph">정확한 색상을 확인해야 한다면 가능한 한 원본에 가까운 이미지 파일을 사용하는 것이 좋습니다.</p>



<h3 class="wp-block-heading">같은 이미지에서 여러 색상을 확인할 수 있나요?</h3>



<p class="wp-block-paragraph">네.</p>



<p class="wp-block-paragraph">원하는 위치를 계속 클릭하면 색상값이 즉시 변경됩니다.</p>



<p class="wp-block-paragraph">최근 선택한 색상이 표시되기 때문에 이미지 안에서 여러 색상을 비교하기에도 편리합니다.</p>



<h3 class="wp-block-heading">HEX 색상 코드만 복사할 수 있나요?</h3>



<p class="wp-block-paragraph">아니요.</p>



<p class="wp-block-paragraph">HEX뿐만 아니라 RGB와 HSL 값도 각각 복사할 수 있습니다.</p>



<p class="wp-block-paragraph">사용하려는 프로그램이나 작업 환경에 맞는 형식을 선택하면 됩니다.</p>



<h2 class="wp-block-heading">마무리</h2>



<p class="wp-block-paragraph">사진이나 이미지에서 정확한 색상 코드를 확인해야 할 때 별도의 그래픽 프로그램을 실행할 필요는 없습니다.</p>



<p class="wp-block-paragraph">위의 이미지 색상 코드 추출기를 이용하면 원하는 위치를 클릭하는 것만으로 HEX, RGB, HSL 값을 바로 확인할 수 있습니다.</p>



<p class="wp-block-paragraph">특히 웹사이트 디자인, 블로그 꾸미기, SNS 이미지 제작처럼 기존 이미지와 비슷한 색상을 찾아야 할 때 간단하게 활용할 수 있습니다.</p>



<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img loading="lazy" decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/image-color-picker/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/08/image-color-picker.jpg" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1483</post-id>	</item>
		<item>
		<title>무료 이미지 변환기 – JPG·PNG·WebP 변환 및 크기 조절</title>
		<link>https://snsnote.com/image-converter/</link>
					<comments>https://snsnote.com/image-converter/#comments</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Fri, 21 Aug 2026 10:42:31 +0000</pubDate>
				<category><![CDATA[유용한 도구]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1423</guid>

					<description><![CDATA[이미지 형식·크기 변환기 이미지는 서버에 저장되지 않습니다.모든 변환은 브라우저에서 처리됩니다. 이미지를 선택하거나 여기 끌어놓으세요 JPG, PNG, WebP, BMP, SVG한 번에 최대 5장 이미지를 선택해 주세요....]]></description>
										<content:encoded><![CDATA[
<div id="sic-app" class="sic-wrap">
  <style>
    #sic-app, #sic-app * { box-sizing: border-box; }
    #sic-app { width:100%; max-width:1200px; margin:24px auto; font-family:system-ui,-apple-system,"Segoe UI",sans-serif; color:#172033; line-height:1.65; }
    #sic-app .sic-panel { border:1px solid #dfe4ea; border-radius:16px; padding:22px; background:#fff; box-shadow:0 8px 28px rgba(20,35,60,.07); }
    #sic-app h2 { margin:0 0 8px; font-size:26px; }
    #sic-app .sic-note { margin:0 0 20px; color:#526071; font-size:14px; line-height:1.7; }
    #sic-app .sic-drop { display:block; border:2px dashed #8aa4c8; border-radius:14px; padding:28px 16px; text-align:center; background:#f7faff; cursor:pointer; }
    #sic-app .sic-drop:hover, #sic-app .sic-drop.is-over { background:#edf5ff; border-color:#246bce; }
    #sic-app .sic-drop strong { display:block; margin-bottom:6px; font-size:17px; }
    #sic-app input[type=file] { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
    #sic-app .sic-options { display:grid; grid-template-columns:repeat(4,minmax(120px,1fr)); gap:12px; margin-top:18px; }
    #sic-app label.sic-field { display:flex; flex-direction:column; gap:6px; font-size:13px; font-weight:700; }
    #sic-app select, #sic-app input[type=number] { width:100%; padding:10px; border:1px solid #cbd4df; border-radius:9px; background:#fff; font:inherit; }
    #sic-app .sic-quality { grid-column:span 2; }
    #sic-app input[type=range] {
      width:100%;
      height:8px !important;
      margin:8px 0;
      padding:0 !important;
      border:0 !important;
      border-radius:999px;
      background:#cfe0f5 !important;
      box-shadow:none !important;
      outline:none;
      cursor:pointer;
      accent-color:#246bce;
      -webkit-appearance:none !important;
      appearance:none !important;
    }
    #sic-app input[type=range]::-webkit-slider-thumb {
      width:22px;
      height:22px;
      border:3px solid #ffffff;
      border-radius:50%;
      background:#246bce;
      box-shadow:0 0 0 2px #246bce;
      -webkit-appearance:none;
      appearance:none;
    }
    #sic-app input[type=range]::-moz-range-thumb {
      width:18px;
      height:18px;
      border:3px solid #ffffff;
      border-radius:50%;
      background:#246bce;
      box-shadow:0 0 0 2px #246bce;
    }
    #sic-app input[type=range]::-moz-range-track {
      height:8px;
      border:0;
      border-radius:999px;
      background:#cfe0f5;
    }
    #sic-app .sic-actions { display:flex; gap:10px; margin-top:18px; flex-wrap:wrap; }
    #sic-app button, #sic-app .sic-download { border:0; border-radius:9px; padding:11px 16px; font-weight:800; cursor:pointer; text-decoration:none; text-align:center; }
    #sic-app .sic-primary { background:#246bce; color:#fff; }
    #sic-app .sic-secondary { background:#eef2f6; color:#243248; }
    #sic-app button:disabled { opacity:.5; cursor:not-allowed; }
    #sic-app .sic-status { margin:16px 0 0; min-height:22px; padding:14px 16px; border-radius:10px; background:#f3f5f8; color:#526071; font-size:16px; font-weight:800; text-align:center; }
    #sic-app .sic-status.has-files { background:#eaf4ff; color:#1359a0; border:1px solid #acd2f7; font-size:20px; }
    #sic-app .sic-results { display:grid; gap:14px; margin-top:18px; }
    #sic-app .sic-card { display:grid; grid-template-columns:120px 1fr auto; gap:16px; align-items:center; border:1px solid #e1e6ec; border-radius:12px; padding:12px; }
    #sic-app .sic-card img { width:120px; height:90px; object-fit:contain; background:#f2f4f7; border-radius:8px; }
    #sic-app .sic-meta strong { display:block; overflow-wrap:anywhere; }
    #sic-app .sic-meta span { display:block; margin-top:5px; color:#617083; font-size:13px; }
    #sic-app .sic-download { display:inline-block; background:#167447; color:#fff; white-space:nowrap; }
    #sic-app .sic-error { color:#b42318; }
    @media (max-width:700px) {
      #sic-app { margin:14px auto; }
      #sic-app .sic-panel { padding:16px; border-radius:12px; }
      #sic-app h2 { font-size:22px; line-height:1.35; }
      #sic-app .sic-drop { padding:22px 12px; }
      #sic-app .sic-drop strong { font-size:clamp(14px,4vw,17px); white-space:nowrap; }
      #sic-app .sic-options { grid-template-columns:1fr; gap:11px; }
      #sic-app .sic-quality { grid-column:auto; }
      #sic-app .sic-actions { flex-direction:column; }
      #sic-app .sic-actions button { width:100%; min-height:46px; }
      #sic-app .sic-status.has-files { font-size:18px; }
      #sic-app .sic-card { grid-template-columns:86px 1fr; }
      #sic-app .sic-card img { width:86px; height:70px; }
      #sic-app .sic-download { grid-column:1 / -1; }
    }
    @media (max-width:420px) {
      #sic-app .sic-card { grid-template-columns:1fr; }
      #sic-app .sic-card img { width:100%; height:180px; }
      #sic-app .sic-download { grid-column:auto; width:100%; }
    }
  </style>

  <div class="sic-panel">
    <h2>이미지 형식·크기 변환기</h2>
    <p class="sic-note">이미지는 서버에 저장되지 않습니다.<br>모든 변환은 브라우저에서 처리됩니다.</p>

    <label class="sic-drop" id="sic-drop" for="sic-files">
      <strong>이미지를 선택하거나 여기 끌어놓으세요</strong>
      <span>JPG, PNG, WebP, BMP, SVG<br>한 번에 최대 5장</span>
    </label>
    <input id="sic-files" type="file" multiple accept="image/jpeg,image/png,image/webp,image/bmp,image/svg+xml">
    <p id="sic-status" class="sic-status" aria-live="polite">이미지를 선택해 주세요.</p>

    <div class="sic-options">
      <label class="sic-field">변환 형식
        <select id="sic-format">
          <option value="image/webp">WebP</option>
          <option value="image/jpeg">JPG</option>
          <option value="image/png">PNG</option>
        </select>
      </label>
      <label class="sic-field">출력 너비(px)
        <input id="sic-width" type="number" min="1" max="12000" placeholder="원본 유지">
      </label>
      <label class="sic-field">출력 높이(px)
        <input id="sic-height" type="number" min="1" max="12000" placeholder="원본 유지">
      </label>
      <label class="sic-field sic-quality">품질 <span id="sic-quality-value">82%</span>
        <input id="sic-quality" type="range" min="10" max="100" value="82">
      </label>
    </div>

    <div class="sic-actions">
      <button id="sic-convert" class="sic-primary" type="button" disabled>선택한 이미지 변환</button>
      <button id="sic-clear" class="sic-secondary" type="button">초기화</button>
    </div>
    <div id="sic-results" class="sic-results"></div>
  </div>

  <script>
  (() => {
    const root = document.getElementById('sic-app');
    if (!root || root.dataset.ready) return;
    root.dataset.ready = '1';

    const $ = (selector) => root.querySelector(selector);
    const fileInput = $('#sic-files');
    const drop = $('#sic-drop');
    const format = $('#sic-format');
    const widthInput = $('#sic-width');
    const heightInput = $('#sic-height');
    const quality = $('#sic-quality');
    const qualityValue = $('#sic-quality-value');
    const convertButton = $('#sic-convert');
    const clearButton = $('#sic-clear');
    const status = $('#sic-status');
    const results = $('#sic-results');
    const maxFiles = 5;
    let files = [];
    let resultUrls = [];
    let webpEncoderPromise = null;

    const nativeWebpSupported = (() => {
      try {
        const testCanvas = document.createElement('canvas');
        testCanvas.width = 1;
        testCanvas.height = 1;
        return testCanvas.toDataURL('image/webp').startsWith('data:image/webp');
      } catch (error) {
        return false;
      }
    })();

    const formatBytes = (bytes) => {
      if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
      const units = ['B', 'KB', 'MB', 'GB'];
      const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
      return `${(bytes / Math.pow(1024, index)).toFixed(index ? 1 : 0)} ${units[index]}`;
    };

    const safeBaseName = (name) => {
      const base = name.replace(/\.[^.]+$/, '').replace(/[\\/:*?\"<>|]+/g, '-').trim();
      return base || 'converted-image';
    };

    const extensionFor = (mime) => mime === 'image/jpeg' ? 'jpg' : mime.split('/')[1];

    const revokeResults = () => {
      resultUrls.forEach(URL.revokeObjectURL);
      resultUrls = [];
    };

    const updateSelection = (selected) => {
      const imageFiles = Array.from(selected).filter(file => file.type.startsWith('image/'));
      files = imageFiles.slice(0, maxFiles);
      convertButton.disabled = files.length === 0;
      status.classList.toggle('has-files', files.length > 0);
      status.textContent = imageFiles.length > maxFiles
        ? `최대 5장까지 가능합니다. 처음 5장이 선택되었습니다.`
        : files.length ? `${files.length}개 이미지가 선택되었습니다.` : '이미지를 선택해 주세요.';
    };

    fileInput.addEventListener('change', () => updateSelection(fileInput.files));
    quality.addEventListener('input', () => qualityValue.textContent = `${quality.value}%`);
    format.addEventListener('change', () => {
      quality.disabled = format.value === 'image/png';
      qualityValue.textContent = format.value === 'image/png' ? 'PNG는 무손실' : `${quality.value}%`;
    });

    ['dragenter', 'dragover'].forEach(type => drop.addEventListener(type, event => {
      event.preventDefault();
      drop.classList.add('is-over');
    }));
    ['dragleave', 'drop'].forEach(type => drop.addEventListener(type, event => {
      event.preventDefault();
      drop.classList.remove('is-over');
    }));
    drop.addEventListener('drop', event => updateSelection(event.dataTransfer.files));

    const loadImage = (file) => new Promise((resolve, reject) => {
      const url = URL.createObjectURL(file);
      const image = new Image();
      image.onload = () => { URL.revokeObjectURL(url); resolve(image); };
      image.onerror = () => { URL.revokeObjectURL(url); reject(new Error('이 브라우저에서 읽을 수 없는 형식입니다.')); };
      image.src = url;
    });

    const canvasToBlob = (canvas, mime, imageQuality) => new Promise((resolve, reject) => {
      canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('변환에 실패했습니다.')), mime, imageQuality);
    });

    const encodeWebpWithWasm = async (context, imageQuality) => {
      if (!webpEncoderPromise) {
        webpEncoderPromise = import('https://esm.sh/@jsquash/webp@1.5.0')
          .then(module => module.encode);
      }
      status.textContent = 'iPhone용 WebP 압축 엔진으로 변환 중입니다…';
      const encodeWebp = await webpEncoderPromise;
      const imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
      const encoded = await encodeWebp(imageData, { quality: Math.round(imageQuality * 100) });
      const blob = new Blob([encoded], { type: 'image/webp' });
      if (!blob.size) throw new Error('WebP 압축 결과를 만들 수 없습니다.');
      return blob;
    };

    const encodeCanvas = async (canvas, context, mime, imageQuality) => {
      if (mime === 'image/webp') {
        if (nativeWebpSupported) {
          const nativeBlob = await canvasToBlob(canvas, mime, imageQuality);
          if (nativeBlob.type === 'image/webp') return nativeBlob;
        }
        return encodeWebpWithWasm(context, imageQuality);
      }

      const blob = await canvasToBlob(canvas, mime, imageQuality);
      if (blob.type !== mime) {
        throw new Error(`이 브라우저에서 ${extensionFor(mime).toUpperCase()} 변환을 지원하지 않습니다.`);
      }
      return blob;
    };

    const convertOne = async (file) => {
      const image = await loadImage(file);
      const requestedWidth = Math.max(0, Number(widthInput.value) || 0);
      const requestedHeight = Math.max(0, Number(heightInput.value) || 0);
      const ratios = [];
      if (requestedWidth) ratios.push(requestedWidth / image.naturalWidth);
      if (requestedHeight) ratios.push(requestedHeight / image.naturalHeight);
      const scale = ratios.length ? Math.min(...ratios) : 1;
      const outputWidth = Math.max(1, Math.round(image.naturalWidth * scale));
      const outputHeight = Math.max(1, Math.round(image.naturalHeight * scale));
      const canvas = document.createElement('canvas');
      canvas.width = outputWidth;
      canvas.height = outputHeight;
      const context = canvas.getContext('2d');
      if (format.value === 'image/jpeg') {
        context.fillStyle = '#ffffff';
        context.fillRect(0, 0, outputWidth, outputHeight);
      }
      context.imageSmoothingEnabled = true;
      context.imageSmoothingQuality = 'high';
      context.drawImage(image, 0, 0, outputWidth, outputHeight);
      const blob = await encodeCanvas(canvas, context, format.value, Number(quality.value) / 100);
      return { blob, originalWidth: image.naturalWidth, originalHeight: image.naturalHeight, outputWidth, outputHeight };
    };

    const addResult = (file, converted) => {
      const url = URL.createObjectURL(converted.blob);
      resultUrls.push(url);
      const change = file.size ? ((1 - converted.blob.size / file.size) * 100) : 0;
      const card = document.createElement('div');
      card.className = 'sic-card';

      const preview = document.createElement('img');
      preview.src = url;
      preview.alt = `${file.name} 변환 결과`;

      const meta = document.createElement('div');
      meta.className = 'sic-meta';
      const title = document.createElement('strong');
      title.textContent = file.name;
      const original = document.createElement('span');
      original.textContent = `원본: ${converted.originalWidth}×${converted.originalHeight} · ${formatBytes(file.size)}`;
      const output = document.createElement('span');
      output.textContent = `결과: ${converted.outputWidth}×${converted.outputHeight} · ${formatBytes(converted.blob.size)} (${change >= 0 ? `${change.toFixed(1)}% 감소` : `${Math.abs(change).toFixed(1)}% 증가`})`;
      meta.append(title, original, output);

      const download = document.createElement('a');
      download.className = 'sic-download';
      download.href = url;
      download.download = `${safeBaseName(file.name)}.${extensionFor(format.value)}`;
      download.textContent = '다운로드';
      card.append(preview, meta, download);
      results.appendChild(card);
    };

    convertButton.addEventListener('click', async () => {
      revokeResults();
      results.replaceChildren();
      convertButton.disabled = true;
      status.textContent = '변환 중입니다…';
      let successCount = 0;
      for (const file of files) {
        try {
          const converted = await convertOne(file);
          addResult(file, converted);
          successCount += 1;
        } catch (error) {
          const message = document.createElement('p');
          message.className = 'sic-error';
          message.textContent = `${file.name}: ${error.message}`;
          results.appendChild(message);
        }
      }
      status.textContent = `${successCount}/${files.length}개 이미지 변환이 완료되었습니다.`;
      convertButton.disabled = files.length === 0;
    });

    clearButton.addEventListener('click', () => {
      revokeResults();
      files = [];
      fileInput.value = '';
      results.replaceChildren();
      convertButton.disabled = true;
      status.classList.remove('has-files');
      status.textContent = '이미지를 선택해 주세요.';
    });
  })();
  </script>
</div>



<p class="wp-block-paragraph">JPG, PNG, WebP 등의 이미지를 원하는 형식과 크기로 간편하게 변환할 수 있습니다. 프로그램 설치나 회원가입 없이 이미지를 선택하고<br>설정한 뒤 바로 다운로드하세요.</p>



<h2 class="wp-block-heading">이미지 변환기 사용 방법</h2>



<ol class="wp-block-list">
<li>변환할 이미지를 선택하거나 입력 영역에 끌어놓습니다. 한 번에 최대 5장까지 선택할 수 있습니다.</li>



<li>WebP, JPG, PNG 중 원하는 변환 형식을 선택합니다.</li>



<li>필요하면 출력 너비와 높이, 이미지 품질을 설정합니다.</li>



<li>&#8216;선택한 이미지 변환&#8217;을 누른 뒤 결과 이미지를 다운로드합니다.</li>
</ol>



<h2 class="wp-block-heading">이미지 형식을 변환하면 좋은 점</h2>



<p class="wp-block-paragraph">WebP 형식은 비교적 작은 용량으로 좋은 화질을 유지할 수 있어 웹페이지 로딩 속도를 개선하는 데 유용합니다. JPG는 사진처럼 색상이<br>많은 이미지에 적합하며, PNG는 투명 배경이나 선명한 그래픽을 보존할 때 유용합니다. 용도에 맞는 형식을 선택하면 저장 공간과 전송<br>용량을 줄일 수 있습니다.</p>



<h2 class="wp-block-heading">자주 묻는 질문</h2>


<div id="rank-math-faq" class="rank-math-block">
<div class="rank-math-list ">
<div id="faq-question-1787308543605" class="rank-math-list-item">
<h3 class="rank-math-question ">선택한 이미지가 서버에 저장되나요?</h3>
<div class="rank-math-answer ">

<p>아니요. 이미지 변환은 사용 중인 브라우저에서 처리되며 원본 이미지와 변환 결과가 서버로 전송되거나 저장되지 않습니다.</p>

</div>
</div>
<div id="faq-question-1787308554384" class="rank-math-list-item">
<h3 class="rank-math-question ">이미지 크기를 변경해도 비율이 유지되나요?</h3>
<div class="rank-math-answer ">

<p>네. 너비나 높이 중 하나만 입력하면 원본 비율에 맞춰 나머지 크기가 자동으로 계산됩니다. 두 값을 모두 입력하면 지정한 범위 안에서<br />원본 비율을 유지합니다.</p>

</div>
</div>
<div id="faq-question-1787308560615" class="rank-math-list-item">
<h3 class="rank-math-question ">이미지 크기를 변경해도 비율이 유지되나요?</h3>
<div class="rank-math-answer ">

<p>네. 너비나 높이 중 하나만 입력하면 원본 비율에 맞춰 나머지 크기가 자동으로 계산됩니다. 두 값을 모두 입력하면 지정한 범위 안에서<br />원본 비율을 유지합니다.</p>

</div>
</div>
</div>
</div>


<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img loading="lazy" decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/image-converter/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/08/image-converter.jpg" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1423</post-id>	</item>
		<item>
		<title>포토샵 색상 코드 매트릭스표 HEX, RGB, HSL, HSV 변환기</title>
		<link>https://snsnote.com/photoshop-color-code/</link>
					<comments>https://snsnote.com/photoshop-color-code/#respond</comments>
		
		<dc:creator><![CDATA[JH]]></dc:creator>
		<pubDate>Thu, 06 Aug 2026 15:54:37 +0000</pubDate>
				<category><![CDATA[유용한 도구]]></category>
		<guid isPermaLink="false">https://snsnote.com/?p=1234</guid>

					<description><![CDATA[포토샵 색상 코드 매트릭스표를 통해 HEX, RGB, HSL, HSV 색상 코드를 실시간으로 확인하고 변환해 보세요. 클릭 한 번으로 색상 코드가 바로 복사됩니다.]]></description>
										<content:encoded><![CDATA[
<CENTER>
<style>
  .color-map-container {
    width: 100%;
    max-width: 320px; /* 모바일 기본 너비 */
    background-color: #ffffff;
    border: 1px solid #e2e8f0;
    border-radius: 12px;
    padding: 16px;
    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    box-sizing: border-box;
    margin: 20px 0;
    transition: all 0.2s ease-in-out;
  }

  /* &#x1f4bb; PC / 태블릿 화면 (화면 폭 600px 이상) */
  @media (min-width: 600px) {
    .color-map-container {
      max-width: 500px; /* PC에서 너비 확대 */
      padding: 20px;
    }
    .color-map-wrapper {
      height: 260px !important; /* PC에서 높이 확대 */
    }
    .color-map-container .title {
      font-size: 17px;
    }
    .code-input {
      font-size: 13px;
    }
  }

  .color-map-container .title {
    font-size: 15px;
    font-weight: 700;
    color: #0f172a;
    margin-bottom: 12px;
    text-align: center;
  }

  /* 2D 색상 맵 */
  .color-map-wrapper {
    position: relative;
    width: 100%;
    height: 180px; /* 모바일 기본 높이 */
    border-radius: 8px;
    overflow: hidden;
    cursor: crosshair;
    margin-bottom: 12px;
    border: 1px solid #cbd5e1;
  }

  #colorCanvas {
    width: 100%;
    height: 100%;
    display: block;
  }

  .map-pointer {
    position: absolute;
    width: 14px;
    height: 14px;
    border: 2px solid #ffffff;
    border-radius: 50%;
    box-shadow: 0 0 3px rgba(0,0,0,0.6);
    pointer-events: none;
    transform: translate(-50%, -50%);
    top: 50%;
    left: 50%;
  }

  /* 색상 띠 (Hue Slider) */
  .hue-slider-wrapper {
    margin-bottom: 12px;
  }

  #hueRange {
    -webkit-appearance: none;
    width: 100%;
    height: 14px;
    border-radius: 7px;
    outline: none;
    background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
    cursor: pointer;
  }

  #hueRange::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #ffffff;
    border: 2px solid #0f172a;
    cursor: pointer;
    box-shadow: 0 1px 3px rgba(0,0,0,0.3);
  }

  /* 미리보기 바 */
  .preview-bar {
    width: 100%;
    height: 38px;
    border-radius: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #ffffff;
    font-weight: 700;
    font-size: 14px;
    text-shadow: 0 1px 2px rgba(0,0,0,0.5);
    margin-bottom: 12px;
    border: 1px solid rgba(0,0,0,0.1);
  }

  /* 코드 입력 영역 */
  .color-code-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }

  .code-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    background-color: #f8fafc;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    padding: 4px 8px;
  }

  .code-label {
    font-size: 11px;
    font-weight: 700;
    color: #64748b;
    width: 35px;
  }

  .code-input {
    flex: 1;
    border: 1px solid #cbd5e1;
    border-radius: 4px;
    padding: 5px 6px;
    font-size: 12px;
    font-weight: 600;
    color: #0f172a;
    font-family: monospace;
    text-align: center;
    margin: 0 6px;
    background-color: #ffffff;
    outline: none;
  }

  .code-input:focus {
    border-color: #2563eb;
    box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
  }

  .copy-btn {
    background-color: #e2e8f0;
    border: none;
    border-radius: 4px;
    padding: 5px 10px;
    font-size: 11px;
    font-weight: 600;
    color: #334155;
    cursor: pointer;
    transition: all 0.15s;
  }

  .copy-btn:hover { background-color: #2563eb; color: #ffffff; }

  #color-toast {
    display: none;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: #0f172a;
    color: #ffffff;
    padding: 10px 18px;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 600;
    z-index: 9999;
  }
  #color-toast.show { display: block; }
</style>

<div class="color-map-container">
  <div class="title"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3a8.png" alt="🎨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> HTML 색상 매트릭스 표</div>

  <div class="color-map-wrapper" id="mapWrapper">
    <canvas id="colorCanvas"></canvas>
    <div class="map-pointer" id="pointer"></div>
  </div>

  <div class="hue-slider-wrapper">
    <input type="range" id="hueRange" min="0" max="360" value="0">
  </div>

  <div class="preview-bar" id="previewBar">#FF0000</div>

  <div class="color-code-group">
    <div class="code-item">
      <span class="code-label">HEX</span>
      <input type="text" class="code-input" id="hexVal" value="#FF0000">
      <button class="copy-btn" onclick="copyMapCode('hexVal')">복사</button>
    </div>
    <div class="code-item">
      <span class="code-label">RGB</span>
      <input type="text" class="code-input" id="rgbVal" value="rgb(255, 0, 0)">
      <button class="copy-btn" onclick="copyMapCode('rgbVal')">복사</button>
    </div>
    <div class="code-item">
      <span class="code-label">HSL</span>
      <input type="text" class="code-input" id="hslVal" value="hsl(0, 100%, 50%)">
      <button class="copy-btn" onclick="copyMapCode('hslVal')">복사</button>
    </div>
    <div class="code-item">
      <span class="code-label">HSV</span>
      <input type="text" class="code-input" id="hsvVal" value="hsv(0, 100%, 100%)">
      <button class="copy-btn" onclick="copyMapCode('hsvVal')">복사</button>
    </div>
  </div>
</div>

<div id="color-toast">복사되었습니다!</div>

<script>
(function() {
  const canvas = document.getElementById('colorCanvas');
  const ctx = canvas.getContext('2d');
  const wrapper = document.getElementById('mapWrapper');
  const pointer = document.getElementById('pointer');
  const hueRange = document.getElementById('hueRange');
  const previewBar = document.getElementById('previewBar');

  const hexInput = document.getElementById('hexVal');
  const rgbInput = document.getElementById('rgbVal');
  const hslInput = document.getElementById('hslVal');
  const hsvInput = document.getElementById('hsvVal');

  let currentHue = 0;
  let isDragging = false;
  let currentX = 0;
  let currentY = 0;

  function resizeCanvas() {
    canvas.width = wrapper.clientWidth;
    canvas.height = wrapper.clientHeight;
    currentX = canvas.width;
    currentY = 0;
    drawCanvas();
  }

  function drawCanvas() {
    const width = canvas.width;
    const height = canvas.height;

    ctx.fillStyle = `hsl(${currentHue}, 100%, 50%)`;
    ctx.fillRect(0, 0, width, height);

    const whiteGrad = ctx.createLinearGradient(0, 0, width, 0);
    whiteGrad.addColorStop(0, 'rgba(255,255,255,1)');
    whiteGrad.addColorStop(1, 'rgba(255,255,255,0)');
    ctx.fillStyle = whiteGrad;
    ctx.fillRect(0, 0, width, height);

    const blackGrad = ctx.createLinearGradient(0, 0, 0, height);
    blackGrad.addColorStop(0, 'rgba(0,0,0,0)');
    blackGrad.addColorStop(1, 'rgba(0,0,0,1)');
    ctx.fillStyle = blackGrad;
    ctx.fillRect(0, 0, width, height);

    pickColorFromCoords(currentX, currentY);
  }

  /* 색상 상호 변환 알고리즘 */
  function rgbToHsl(r, g, b) {
    r /= 255; g /= 255; b /= 255;
    const max = Math.max(r, g, b), min = Math.min(r, g, b);
    let h, s, l = (max + min) / 2;

    if (max === min) { h = s = 0; } 
    else {
      const d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      switch (max) {
        case r: h = (g - b) / d + (g < b ? 6 : 0); break;
        case g: h = (b - r) / d + 2; break;
        case b: h = (r - g) / d + 4; break;
      }
      h /= 6;
    }
    return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
  }

  function hslToRgb(h, s, l) {
    s /= 100; l /= 100;
    const k = n => (n + h / 30) % 12;
    const a = s * Math.min(l, 1 - l);
    const f = n => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
    return {
      r: Math.round(255 * f(0)),
      g: Math.round(255 * f(8)),
      b: Math.round(255 * f(4))
    };
  }

  function rgbToHsv(r, g, b) {
    r /= 255; g /= 255; b /= 255;
    const max = Math.max(r, g, b), min = Math.min(r, g, b);
    const d = max - min;
    let h, s = max === 0 ? 0 : d / max, v = max;

    if (max === min) { h = 0; } 
    else {
      switch (max) {
        case r: h = (g - b) / d + (g < b ? 6 : 0); break;
        case g: h = (b - r) / d + 2; break;
        case b: h = (r - g) / d + 4; break;
      }
      h /= 6;
    }
    return { h: Math.round(h * 360), s: Math.round(s * 100), v: Math.round(v * 100) };
  }

  function hsvToRgb(h, s, v) {
    s /= 100; v /= 100;
    let r, g, b;
    let i = Math.floor((h / 60) % 6);
    let f = h / 60 - Math.floor(h / 60);
    let p = v * (1 - s);
    let q = v * (1 - f * s);
    let t = v * (1 - (1 - f) * s);
    switch (i) {
      case 0: r = v; g = t; b = p; break;
      case 1: r = q; g = v; b = p; break;
      case 2: r = p; g = v; b = t; break;
      case 3: r = p; g = q; b = v; break;
      case 4: r = t; g = p; b = v; break;
      case 5: r = v; g = p; b = q; break;
    }
    return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };
  }

  /* 화면 UI 및 포인터 업데이트 */
  function updateAllUI(r, g, b, activeSource = null) {
    const hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
    const hsl = rgbToHsl(r, g, b);
    const hsv = rgbToHsv(r, g, b);

    previewBar.style.backgroundColor = hex;
    previewBar.textContent = hex;

    if (activeSource !== 'hex') hexInput.value = hex;
    if (activeSource !== 'rgb') rgbInput.value = `rgb(${r}, ${g}, ${b})`;
    if (activeSource !== 'hsl') hslInput.value = `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;
    if (activeSource !== 'hsv') hsvInput.value = `hsv(${hsv.h}, ${hsv.s}%, ${hsv.v}%)`;

    currentHue = hsv.h;
    hueRange.value = currentHue;

    redrawCanvasWithoutPicker();

    const width = canvas.width;
    const height = canvas.height;
    currentX = (hsv.s / 100) * width;
    currentY = (1 - hsv.v / 100) * height;

    pointer.style.left = currentX + 'px';
    pointer.style.top = currentY + 'px';
  }

  function pickColorFromCoords(x, y) {
    x = Math.max(0, Math.min(x, canvas.width - 1));
    y = Math.max(0, Math.min(y, canvas.height - 1));
    currentX = x; currentY = y;

    pointer.style.left = x + 'px';
    pointer.style.top = y + 'px';

    const pixel = ctx.getImageData(x, y, 1, 1).data;
    updateAllUI(pixel[0], pixel[1], pixel[2]);
  }

  function redrawCanvasWithoutPicker() {
    const width = canvas.width, height = canvas.height;
    ctx.fillStyle = `hsl(${currentHue}, 100%, 50%)`;
    ctx.fillRect(0, 0, width, height);

    const whiteGrad = ctx.createLinearGradient(0, 0, width, 0);
    whiteGrad.addColorStop(0, 'rgba(255,255,255,1)');
    whiteGrad.addColorStop(1, 'rgba(255,255,255,0)');
    ctx.fillStyle = whiteGrad;
    ctx.fillRect(0, 0, width, height);

    const blackGrad = ctx.createLinearGradient(0, 0, 0, height);
    blackGrad.addColorStop(0, 'rgba(0,0,0,0)');
    blackGrad.addColorStop(1, 'rgba(0,0,0,1)');
    ctx.fillStyle = blackGrad;
    ctx.fillRect(0, 0, width, height);
  }

  /* 입력 필드별 실시간 반영 이벤트 */
  hexInput.addEventListener('input', (e) => {
    let val = e.target.value.trim();
    if (!val.startsWith('#')) val = '#' + val;
    if (/^#([0-9A-F]{3}){1,2}$/i.test(val)) {
      if (val.length === 4) {
        val = '#' + val[1] + val[1] + val[2] + val[2] + val[3] + val[3];
      }
      const num = parseInt(val.slice(1), 16);
      const r = (num >> 16) & 255, g = (num >> 8) & 255, b = num & 255;
      updateAllUI(r, g, b, 'hex');
    }
  });

  rgbInput.addEventListener('input', (e) => {
    const match = e.target.value.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i) || 
                  e.target.value.match(/^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)$/);
    if (match) {
      const r = Math.min(255, parseInt(match[1]));
      const g = Math.min(255, parseInt(match[2]));
      const b = Math.min(255, parseInt(match[3]));
      updateAllUI(r, g, b, 'rgb');
    }
  });

  hslInput.addEventListener('input', (e) => {
    const match = e.target.value.match(/hsl\(\s*(\d+)\s*,\s*(\d+)%?\s*,\s*(\d+)%?\s*\)/i) ||
                  e.target.value.match(/^(\d+)\s*,\s*(\d+)%?\s*,\s*(\d+)%?$/);
    if (match) {
      const h = Math.min(360, parseInt(match[1]));
      const s = Math.min(100, parseInt(match[2]));
      const l = Math.min(100, parseInt(match[3]));
      const { r, g, b } = hslToRgb(h, s, l);
      updateAllUI(r, g, b, 'hsl');
    }
  });

  hsvInput.addEventListener('input', (e) => {
    const match = e.target.value.match(/hsv\(\s*(\d+)\s*,\s*(\d+)%?\s*,\s*(\d+)%?\s*\)/i) ||
                  e.target.value.match(/^(\d+)\s*,\s*(\d+)%?\s*,\s*(\d+)%?$/);
    if (match) {
      const h = Math.min(360, parseInt(match[1]));
      const s = Math.min(100, parseInt(match[2]));
      const v = Math.min(100, parseInt(match[3]));
      const { r, g, b } = hsvToRgb(h, s, v);
      updateAllUI(r, g, b, 'hsv');
    }
  });

  /* 드래그 및 슬라이더 이벤트 */
  function handleMove(e) {
    const rect = canvas.getBoundingClientRect();
    const clientX = e.touches ? e.touches[0].clientX : e.clientX;
    const clientY = e.touches ? e.touches[0].clientY : e.clientY;
    pickColorFromCoords(clientX - rect.left, clientY - rect.top);
  }

  wrapper.addEventListener('mousedown', (e) => { isDragging = true; handleMove(e); });
  window.addEventListener('mousemove', (e) => { if (isDragging) handleMove(e); });
  window.addEventListener('mouseup', () => { isDragging = false; });

  wrapper.addEventListener('touchstart', (e) => { isDragging = true; handleMove(e); }, {passive: true});
  window.addEventListener('touchmove', (e) => { if (isDragging) handleMove(e); }, {passive: true});
  window.addEventListener('touchend', () => { isDragging = false; });

  hueRange.addEventListener('input', (e) => {
    currentHue = e.target.value;
    drawCanvas();
  });

  /* 클립보드 복사 함수 */
  window.copyMapCode = function(id) {
    const inputEl = document.getElementById(id);
    const textToCopy = inputEl.value;

    function showToast() {
      const toast = document.getElementById('color-toast');
      toast.textContent = `"${textToCopy}" 복사되었습니다!`;
      toast.classList.add('show');
      setTimeout(() => toast.classList.remove('show'), 1500);
    }

    if (navigator.clipboard && window.isSecureContext) {
      navigator.clipboard.writeText(textToCopy).then(showToast).catch(() => fallbackCopy());
    } else {
      fallbackCopy();
    }

    function fallbackCopy() {
      const tempTextArea = document.createElement('textarea');
      tempTextArea.value = textToCopy;
      tempTextArea.style.position = 'fixed';
      tempTextArea.style.left = '-999999px';
      tempTextArea.style.top = '-999999px';
      document.body.appendChild(tempTextArea);
      tempTextArea.focus();
      tempTextArea.select();
      try {
        document.execCommand('copy');
        showToast();
      } catch (err) {
        alert('복사에 실패했습니다.');
      }
      document.body.removeChild(tempTextArea);
    }
  };

  setTimeout(resizeCanvas, 50);
  window.addEventListener('resize', resizeCanvas);
})();
</script>
</CENTER>



<h2 class="wp-block-heading">포토샵 색상 코드와 2D 매트릭스표의 이해</h2>



<p class="wp-block-paragraph">웹 디자인, 그래픽 작업, 인쇄물 제작 등 다양한 디자인 작업에서 원하는 색상을 정확하게 선택하고 공유하는 것은 매우 중요합니다. <strong>색상 코드</strong>는 컴퓨터와 그래픽 프로그램이 특정 컬러를 인식할 수 있도록 표준화한 기호입니다.</p>



<p class="wp-block-paragraph">특히 <strong>포토샵 색상 코드</strong>는 작업 시 자주 사용되는 HEX, RGB, HSL, HSV 등 여러 체계로 표현됩니다. 본 페이지에서 제공하는 2D 색상 매트릭스표 도구를 사용하면 수동으로 수치를 입력하거나 화면의 포인터를 움직여 원하는 <strong>색상 코드</strong>를 실시간으로 확인하고 변환할 수 있습니다.</p>



<h2 class="wp-block-heading">주요 색상 코드 종류 및 특징 (HEX, RGB, HSL, HSV)</h2>



<p class="wp-block-paragraph">디자인 환경에 따라 사용하는 <strong>색상 코드</strong> 방식이 달라집니다. 각 표기법의 특징은 다음과 같습니다.</p>



<ul class="wp-block-list">
<li><strong>HEX 코드 (16진수 색상 코드):</strong> <code>#</code> 기호 뒤에 6자리 문자와 숫자로 조합된 표기법입니다. 웹 사이트(HTML/CSS)에서 가장 널리 쓰이는 표준 <strong>색상 코드</strong>입니다. (예: <code>#4A140A</code>)</li>



<li><strong>RGB 코드:</strong> Red, Green, Blue 3가지 빛의 삼원색을 0부터 255까지의 수치로 나타냅니다. 디지털 모니터 화면 표현에 최적화되어 있습니다. (예: <code>rgb(74, 20, 10)</code>)</li>



<li><strong>HSL 코드:</strong> 색상(Hue), 채도(Saturation), 명도(Lightness)의 3가지 요소로 <strong>색상 코드</strong>를 구성합니다. 직관적으로 색조와 밝기를 조정할 때 유리합니다.</li>



<li><strong>HSV 코드:</strong> 색상(Hue), 채도(Saturation), 명도/가치(Value)를 뜻하며, 포토샵의 컬러 피커(Color Picker) 방식과 가장 유사한 형태입니다.</li>
</ul>



<h2 class="wp-block-heading">포토샵 색상 코드 변환기 사용 방법</h2>



<p class="wp-block-paragraph">본 <strong>색상 코드</strong> 변환기는 PC와 모바일 환경 모두에서 손쉽게 동작하도록 최적화되어 있습니다.</p>



<ol start="1" class="wp-block-list">
<li><strong>2D 매트릭스 조절:</strong> 상단 판에서 마우스나 손가락으로 포인터를 이동하여 채도와 명도를 변경합니다.</li>



<li><strong>색상 띠(Hue Slider) 변경:</strong> 슬라이더를 움직여 기본 색상톤(Hue)을 빠르게 전환합니다.</li>



<li><strong>수동 입력 및 실시간 변환:</strong> HEX, RGB, HSL, HSV 입력창에 직접 <strong>색상 코드</strong> 값을 입력해도 매트릭스 포인터와 나머지 수치가 자동으로 즉시 업데이트됩니다.</li>



<li><strong>원클릭 복사:</strong> 각 코드 오른쪽에 위치한 <strong>[복사]</strong> 버튼을 클릭하면 클립보드에 해당 <strong>색상 코드</strong>가 저장되어 포토샵이나 CSS 코드에 바로 붙여넣을 수 있습니다.</li>
</ol>



<h2 class="wp-block-heading">웹 디자인 및 포토샵 작업 시 색상 코드 활용 팁</h2>



<p class="wp-block-paragraph">포토샵 작업을 진행할 때 정확한 <strong>색상 코드</strong>를 공유하는 것은 일관된 브랜딩과 디자인 품질 유지에 필수적입니다. 본 도구에서 추출한 HEX나 RGB <strong>색상 코드</strong>를 활용하면 스타일시트(CSS) 작성 시 오차 없이 디자이너의 의도대로 웹페이지 요소를 구현할 수 있습니다.</p>



<p class="wp-block-paragraph">필요할 때마다 이 <strong>색상 코드</strong> 변환기를 활용해 쉽고 빠르게 원하는 컬러를 추출해 보세요!</p>



<p class="wp-block-paragraph"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 더 자세한 웹 표준 색상 가이드 보기:</strong></p>



<p class="wp-block-paragraph">다양한 색상 코드의 정확한 웹 표준 규격과 표현 방식은 <a target="_blank" rel="noreferrer noopener" href="https://developer.mozilla.org/ko/docs/Web/CSS/color_value">MDN CSS 색상 값 공식 문서</a>에서 확인하실 수 있습니다.</p>



<p class="wp-block-paragraph">그 외의 유용한 도구</p>



<ul class="wp-block-list">
<li><a href="https://snsnote.com/free-qr-code-generator/">자영업자 전용 무료 QR 코드 생성기</a></li>



<li><a href="https://snsnote.com/emoticon-list/">2000년대 느낌 텍스트 이모티콘 표정 특수문자 모음</a></li>
</ul>



<style>
  /* 이 글에서만 본문 상단 대표 이미지 숨기기 */
  .featured-image, 
  .post-thumbnail, 
  .entry-header .post-image,
  .single-media {
    display: none !important;
  }
</style>
<div class="saboxplugin-wrap" itemtype="http://schema.org/Person" itemscope itemprop="author"><div class="saboxplugin-tab"><div class="saboxplugin-gravatar"><img loading="lazy" decoding="async" src="https://snsnote.com/wp-content/uploads/2026/06/5555.png" width="100"  height="100" alt="" itemprop="image"></div><div class="saboxplugin-authorname"><a href="https://snsnote.com/author/admin/" class="vcard author" rel="author"><span class="fn">JH</span></a></div><div class="saboxplugin-desc"><div itemprop="description"><p>SNSnote 편집 담당 JH입니다.<br />
2020년부터 SNS를 본격적으로 운영하기 시작해 현재 인스타그램, 유튜브, X를 합쳐 총 40만 팔로워를 보유하고 있습니다.<br />
영구정지 복구, 계정 성장, 수익화까지 직접 경험한 실패와 성공 사례를 바탕으로<br />
실전에서 바로 써먹을 수 있는 최신 정보를 전달해드리고 있습니다.</p>
</div></div><div class="saboxplugin-web "><a href="https://snsnote.com" target="_self">snsnote.com</a></div><div class="clearfix"></div></div></div>]]></content:encoded>
					
					<wfw:commentRss>https://snsnote.com/photoshop-color-code/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://snsnote.com/wp-content/uploads/2026/08/photoshop-color-code.jpg" medium="image" /><post-id xmlns="com-wordpress:feed-additions:1">1234</post-id>	</item>
	</channel>
</rss>
