티스토리 뷰

728x90
반응형

안녕하세요. 일요일의 21입니다. 처음엔 가볍게 생각했다. 로또 번호 생성기 정도야, 코드 하나 붙여 넣으면 버튼 하나 뜨고 끝일 줄 알았다. HTML이나 CSS를 모르는 처지에서도 이 정도는 복사해서 붙여 넣기만 하면 해결될 거라 믿었다. 하지만 티스토리는 그렇게 호락호락하지 않았다. 가장 먼저 마주친 문제는 버튼이 눌리지 않는 현상이었다. 코드상에는 분명 클릭 이벤트가 걸려 있는데, 미리 보기에서는 아무 반응이 없었다. 처음엔 코드가 틀린 줄 알았다. 그런데 문제는 코드가 아니라 티스토리 미리 보기 구조였다. 미리 보기 화면은 실제 게시 환경이 아니다. 자바스크립트 실행이 제한되어 있고, 클릭 이벤트도 정상적으로 동작하지 않는다. 즉, 미리 보기에서 안 된다고 해서 실제 글에서도 안 되는 건 아니라는 점을 그제야 알았다.

더 황당한 상황도 있었다. <button> 태그를 쓰면 버튼 자체가 미리 보기에서 사라지는 현상이 발생했다. 이건 단순한 오류가 아니라 티스토리 에디터가 특정 태그를 필터링하거나 변형시키는 구조 때문이었다. 여기서 깨달았다. 정말 빡세겠다고. 해결은 의외로 단순했다. <button> 태그를 고집하지 말 것, 클릭 이벤트가 가능한 다른 HTML 요소로 우회할 것, 형태는 버튼이 아니어도 된다. 중요한 건 클릭 이벤트가 살아 있고, 실제 게시 환경에서 동작하느냐였다. 이 방식으로 바꾸자 미리 보기에서도 잘 돌아갔고, 실제 게시글에서도 정상 작동했다. 다만, 모바일 환경에서는 레이아웃 문제가 발생했지만, 작동은 잘 되니까 딱히 상관이 없었다.

<div id="lottoBtn" style="padding:12px;background:#222;color:#fff;cursor:pointer;">
  로또 번호 생성
</div>

<div id="lottoResult" style="margin-top:10px;font-size:18px;"></div>

<script>
  document.getElementById("lottoBtn").onclick = function () {

    let numbers = [];
    for (let i = 1; i <= 45; i++) numbers.push(i);

    numbers.sort(() => Math.random() - 0.5);

    let main = numbers.slice(0, 6).sort((a, b) => a - b);
    let bonus = numbers[6];

    document.getElementById("lottoResult").innerText =
      "번호: " + main.join(", ") + " / 보너스: " + bonus;
  };
</script>

조금 다른 방식의 로또 번호 생성기를 만들어보고 싶었다. HTML이나 CSS는 정말 하나도 모르는 상태였기 때문에 혼자서 끙끙대는 대신 챗GPT에 도움을 요청했다. 처음엔 욕심 없이 단순하게 시작했다. 버튼을 누르면 로또 번호 한 줄만 깔끔하게 나오는 것, 그게 첫 번째 목표였다. 그다음엔 이왕이면 하는 마음이 붙었다. 추천 번호를 5세트, 5줄로 한꺼번에 보여주는 기능도 있으면 좋겠다고 했다. 정리하면 요구사항은 명확했다. 하나는 단일 번호 생성기, 다른 하나는 여러 세트를 한 번에 뽑아주는 추천용 생성기. 복잡한 디자인이나 화려한 기능보다는, 버튼을 누르면 바로 결과가 나오고, 내가 원하는 대로 분리해서 쓸 수 있는 구조면 충분했다. 그렇게 시작한 게, 생각보다 일이 커졌다.

 

로또 번호 생성
 

 

로또 번호 생성을 누르면 지금 5세트가 5줄로 한꺼번에 나올 텐데 원하는 것은 아니었다. 위에 있는 로또 번호 생성 버튼을 눌렀을 때, 로또 번호 한 줄이 깔끔하게 출력되는 것까지는 비교적 순조로웠다. 원하던 대로 버튼을 누르면 바로 결과가 나오고, 화면도 크게 어지럽지 않았다. 문제는 그다음부터였다.

<div id="lottoBtn" style="padding:12px;background:#111;color:#fff;cursor:pointer;">
  추천 번호 5줄 생성
</div>

<div id="lottoResult" style="margin-top:15px;"></div>

<script>
  document.getElementById("lottoBtn").onclick = function () {

    let output = "";

    for (let r = 0; r < 5; r++) {

      let nums = [];
      for (let i = 1; i <= 45; i++) nums.push(i);
      nums.sort(() => Math.random() - 0.5);

      let line = nums.slice(0, 6).sort((a, b) => a - b);
      output += (r + 1) + "줄 : " + line.join(", ") + "<br>";
    }

    document.getElementById("lottoResult").innerHTML = output;
  };
</script>
추천 번호 5줄 생성

 

하지만 추천 번호 5줄 생성 버튼을 눌렀을 때는 상황이 달랐다. 버튼을 아무리 눌러도 아무 반응이 없었다. 이상하게도, 원래는 한 줄만 나오도록 만든 기능이 오히려 5줄로 출력되는 현상까지 보였다. 한 줄은 한 줄대로, 다섯 줄은 다섯 줄대로 각각 따로 작동해야 하는데, 둘이 서로 엉켜버린 느낌이었다. 도저히 이유를 모르겠어서 다시 챗GPT에 물어봤다. 그러자 돌아온 답변은 의외로 단순했다. 지금 안 되는 이유는 ID가 서로 겹쳐서, 기능이 덮어 쓰이고 있다는 것이었다.

id="lottoBtn"
id="lottoResult"

HTML에서는 id가 문서 안에서 반드시 유일해야 하는데, 같은 id를 두 번 써버리면서 문제가 생긴 거였다. 두 번째 <script>가 실행되는 순간, 앞에 있던 버튼과 결과에 연결된 이벤트가 전부 덮어써졌고, 결국 마지막에 작성한 코드만 살아남는 현상이 발생했다. 그래서 어떤 때는 로또 번호 생성 버튼만 작동하고, 어떤 때는 추천 번호 5줄 생성 버튼만 반응하지 않거나, 상황에 따라 둘 다 어긋난 채로 꼬여 버렸다는 거다. 챗GPT가 그렇다는데, 봐도 뭔 말인지 모르겠다.

해결책은 버튼 / 결과 ID를 서로 다르게만 하면 끝.
lottoBtn → lottoBtnSingle, lottoBtnMulti
lottoResult → lottoResultSingle, lottoResultMulti

<div id="lottoBtnSingle"
     style="padding:12px;background:#222;color:#fff;cursor:pointer;">
  로또 번호 생성
</div>

<div id="lottoResultSingle"
     style="margin-top:10px;font-size:18px;"></div>

<script>
  document.getElementById("lottoBtnSingle").onclick = function () {

    let numbers = [];
    for (let i = 1; i <= 45; i++) numbers.push(i);

    numbers.sort(() => Math.random() - 0.5);

    let main = numbers.slice(0, 6).sort((a, b) => a - b);
    let bonus = numbers[6];

    document.getElementById("lottoResultSingle").innerText =
      "번호: " + main.join(", ") + " / 보너스: " + bonus;
  };
</script>

로또 번호 생성

 

 

이번에는 구조를 다시 정리했다. 로또 번호 생성 버튼을 누르면 1줄 번호와 보너스 번호가 함께 출력되도록 하고, 추천 번호 5줄 생성 버튼을 누르면 5줄이 독립적으로 따로 출력되게 했다. 서로 같은 영역을 건드리지 않도록 분리했기 때문에 이제는 한쪽 버튼을 눌러도 다른 쪽 결과에 전혀 영향을 주지 않는다. 뭐, 하는 방법 같은 건 역시 모르기에 챗GPT가 다 해줬다.

<div id="lottoBtnMulti"
     style="padding:12px;background:#111;color:#fff;cursor:pointer;">
  추천 번호 5줄 생성
</div>

<div id="lottoResultMulti"
     style="margin-top:15px;"></div>

<script>
  document.getElementById("lottoBtnMulti").onclick = function () {

    let output = "";

    for (let r = 0; r < 5; r++) {

      let nums = [];
      for (let i = 1; i <= 45; i++) nums.push(i);
      nums.sort(() => Math.random() - 0.5);

      let line = nums.slice(0, 6).sort((a, b) => a - b);
      output += (r + 1) + "줄 : " + line.join(", ") + "<br>";
    }

    document.getElementById("lottoResultMulti").innerHTML = output;
  };
</script>

추천 번호 5줄 생성

 

 

추천 번호 5줄 생성 버튼도 독립적으로 잘 나온다. 한 줄짜리 로또 번호를 먼저 눌러도, 그다음에 다섯 줄 추천을 눌러도 서로의 결과를 건드리지 않는다. 각각의 버튼은 자기 역할만 수행하고, 출력 영역도 정확히 분리되어 있다. 처음에 겪었던 그 문제는 없다. HTML이나 CSS를 모르는 상태에서 시작했지만, 문제를 하나씩 분해하고 원인을 이해하니 생각보다 재밌다. 로또 번호 생성기 하나를 만들었을 뿐인데, 어떻게 작동하는지에 대한 감각을 조금 얻은 느낌이다. 어쨌든 목표는 달성했다. 원하던 대로 버튼을 누르면 결과가 나오고, 각각의 기능은 제자리를 지킨다. 이 정도면 충분하다.

<!-- ===== 카드 컨테이너 ===== -->
<div id="lw_01_wrap" style="
  max-width:520px;
  border:1px solid #e5e7eb;
  border-radius:18px;
  padding:22px;
  background:#fff;
  box-shadow:0 4px 12px rgba(0,0,0,0.04);
">
  <div style="font-size:20px;font-weight:900;margin-bottom:10px;">
    로또 번호 생성기 6/45
  </div>

  <div style="font-size:13px;color:#64748b;margin-bottom:14px;">
    숫자 클릭 → 일반 → 고정 → 제외
  </div>

  <!-- 숫자 선택 그리드 -->
  <div id="lw_01_grid"
       style="display:grid;grid-template-columns:repeat(9,1fr);gap:6px;margin-bottom:16px;"></div>

  <!-- 버튼 -->
  <div id="lw_01_btn"
       style="display:inline-block;padding:10px 16px;background:#0f172a;color:#fff;
              cursor:pointer;border-radius:10px;font-size:15px;font-weight:700;">
    번호 생성
  </div>

  <!-- 결과 -->
  <div id="lw_01_result" style="margin-top:14px;font-size:16px;line-height:1.6;"></div>
</div>

<script>
(function () {
  const PREFIX = "lw_01_";
  const grid = document.getElementById(PREFIX + "grid");
  const btn  = document.getElementById(PREFIX + "btn");
  const out  = document.getElementById(PREFIX + "result");

  if (!grid || !btn || !out) return; // 요소가 없으면 조용히 종료(티스토리 변형 대비)

  const STATE_NORMAL = 0, STATE_FIXED = 1, STATE_EXCL = 2;
  const state = Array(46).fill(STATE_NORMAL); // 0~45

  // 동행복권 색상
  function getBallColor(n) {
    if (n <= 10) return "#f5c542";   // 노랑
    if (n <= 20) return "#4a7bd0";   // 파랑
    if (n <= 30) return "#e04b4b";   // 빨강
    if (n <= 40) return "#8c8c8c";   // 회색
    return "#5bb85d";                // 초록
  }

  // 숫자 버튼 스타일 적용
  function paint(el, n) {
    const st = state[n];

    // 항상 숫자는 유지 (X로 바꾸면 헷갈리고, 복구 누락 이슈도 생김)
    el.textContent = n;

    if (st === STATE_NORMAL) {
      el.style.background = "#f8fafc";
      el.style.border = "1px solid #cbd5e1";
      el.style.color = "#111827";
    } else if (st === STATE_FIXED) {
      el.style.background = "#eff6ff";
      el.style.border = "2px solid #2563eb";
      el.style.color = "#2563eb";
    } else {
      el.style.background = "#fef2f2";
      el.style.border = "2px solid #dc2626";
      el.style.color = "#dc2626";
    }
  }

  // 그리드 생성
  for (let n = 1; n <= 45; n++) {
    const el = document.createElement("div");
    el.setAttribute("data-n", String(n));
    el.style.cssText = `
      height:32px; display:flex; align-items:center; justify-content:center;
      border-radius:8px; font-size:12px; font-weight:800;
      cursor:pointer; user-select:none;
    `;
    paint(el, n);

    el.addEventListener("click", function () {
      state[n] = (state[n] + 1) % 3;  // 일반→고정→제외→일반
      paint(el, n);
    });

    grid.appendChild(el);
  }

  // Fisher-Yates 셔플
  function shuffle(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
  }

  btn.addEventListener("click", function () {
    // 고정/제외 반영해서 풀 구성
    const fixed = [];
    const pool = [];
    const bonusPool = [];

    for (let n = 1; n <= 45; n++) {
      if (state[n] === STATE_FIXED) fixed.push(n);
      if (state[n] === STATE_NORMAL) pool.push(n);
      if (state[n] !== STATE_EXCL) bonusPool.push(n); // 보너스는 제외만 피하면 됨
    }

    if (fixed.length > 6) {
      out.innerHTML = "<span style='color:#dc2626;font-weight:700;'>고정수는 최대 6개까지 가능합니다.</span>";
      return;
    }

    const need = 6 - fixed.length;
    if (pool.length < need) {
      out.innerHTML = "<span style='color:#dc2626;font-weight:700;'>제외수가 너무 많아 번호를 만들 수 없습니다.</span>";
      return;
    }

    // 메인 6개 만들기
    shuffle(pool);
    const main = fixed.concat(pool.slice(0, need)).sort((a,b)=>a-b);

    // 보너스: (제외 X) AND (메인과 중복 X)
    const bPool = bonusPool.filter(n => !main.includes(n));
    if (bPool.length === 0) {
      out.innerHTML = "<span style='color:#dc2626;font-weight:700;'>보너스 후보가 없습니다. 제외/고정을 줄여주세요.</span>";
      return;
    }
    shuffle(bPool);
    const bonus = bPool[0];

    // 출력
    let html = "";
    main.forEach(n => {
      html += `
        <span style="
          display:inline-block;width:38px;height:38px;line-height:38px;border-radius:50%;
          background:${getBallColor(n)};
          margin-right:6px;text-align:center;font-weight:900;color:#fff;
          border:2px solid rgba(0,0,0,.35);
        ">${n}</span>
      `;
    });

    html += `
      <span style="display:inline-block;margin:0 6px;font-weight:900;">+</span>
      <span style="
        display:inline-block;width:38px;height:38px;line-height:38px;border-radius:50%;
        background:${getBallColor(bonus)};
        text-align:center;font-weight:900;color:#fff;
        border:3px solid #000;
      ">${bonus}</span>
    `;

    out.innerHTML = html;
  });

})();
</script>

로또 번호 생성기 6/45
숫자 클릭 → 일반 → 고정 → 제외
 
번호 생성
 

 

 

이번 작업에서 내가 한 일은 솔직히 많지 않았다. 내가 한 건 그저 문제 상황을 설명하고, 보이는 에러 증상을 그대로 전달하고, 왜 안 되냐고 계속 묻는 일이 전부였다. 원인 분석이나 구조 설명, 어디서 어떻게 우회해야 하는지에 대한 아이디어는 전부 챗GPT가 정리해 줬다. 나는 그 과정을 따라가며 적용했을 뿐이고, 결과적으로는 성과만 챙긴 셈이었다.

이번에도 상황은 비슷했다. 겉보기엔 다 갖춰져 있었다. 카드형 테두리까지 넣고, 숫자 선택 그리드도 만들고, 고정 수/제외 수까지 고를 수 있게 해놨다. 이제 버튼만 누르면 메인 6개와 보너스 공이 동행복권 색상으로 예쁘게 뜨는, 로또 번호 생성기가 완성될 줄 알았다. 그런데 또 버튼이 말을 안 들었다. 번호 생성이 안 됐다. 클릭해도 결과가 비어 있거나, 아예 아무 변화가 없었다.

그 순간 아, 또 시작이구나 싶었다. 내가 코드를 잘못 붙여 넣은 건지, 티스토리가 또 뭔가를 막은 건지, 아니면 내가 놓친 규칙이 있는 건지 감이 잡히지 않았다. 결국 다시 챗GPT에 물었다.

"번호 생성이 안 되는데?" 그러자 돌아온 답은 이번에도 깔끔하게 요약됐다. 대부분의 원인은 두 가지 중 하나였다. 아까처럼 ID 충돌. 이전에도 겪었던 문제인데, 글 안에 예전 로또 코드가 남아 있거나, 비슷한 코드를 두 번 붙여 넣으면 id가 겹치면서 버튼이나 결과 영역이 서로를 덮어쓴다. 겉으로는 버튼이 보이는데, 실제로는 다른 요소에 이벤트가 붙거나, 출력 대상이 엉뚱한 곳을 바라보는 식으로 꼬인다.

티스토리 에디터가 스크립트를 변형하는 경우.
특히 <script>가 문단 태그 안에 들어가거나, 편집 과정에서 태그가 감싸지면 예상대로 실행이 안 되는 일이 생길 수 있다는 거다. 미리 보기 환경에서는 더 심하게 제한될 때도 있다. 결국 내가 보고 있는 화면이 실제로 코드가 실행되는 환경과 다를 수 있다는 이야기였다. 솔직히 쫄린다. 지금 미리 보기 화면에서는 되기는 하는데, 올렸을 때 안 되면 그게 무슨 소용이겠는가. 일단 챗GPT가 되는 태그라면서 주긴 줬는데, 제발 잘 되었으면 좋겠다.

결국은 올려봐야 안다. 여기서 더 붙잡고 고민한다고 해서 갑자기 구조가 바뀌는 것도 아니고, 내가 갑자기 HTML이나 CSS를 완벽히 이해하는 것도 아니다. 한 번은 실패해야 다음이 있다. 안 되면 안 되는 대로 또 기록이 남고, 그게 다음 글의 소재가 된다. 그렇게 생각하니 조금 마음이 가벼워진다.

솔직히 말하면, 챗GPT를 쓰는 이유도 이런 순간 때문이다. 정답을 바로 얻어서라기보다는, 혼자 끙끙대며 헤매는 시간을 줄이기 위해서다. 22달러가 싸다고는 할 수 없지만, 밤늦게 혼자 화면 붙잡고 욕하면서 구글 검색하던 시간을 떠올리면 아주 허황한 소비는 아니다. 적어도 지금처럼 이게 맞나? 라는 생각을 글로 바로 정리할 수 있게 해주니까. 이제 남은 건 발행 버튼뿐이다. 잘 되면 다행이고, 안 되면 또 하나 배웠다고 치자. 블로그라는 게 원래 그런 식으로 쌓이는 거니까. 실패한 코드도, 작동 안 한 버튼도, 나중에 보면 다 글감이 된다. 그렇게 생각하며, 오늘도 결국은 올린다.

728x90
반응형
댓글
최근에 달린 댓글
글 보관함
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
최근에 올라온 글
Total
Today
Yesterday