YOUR_SCRIPT_NAME

One-line description of what this script does.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         YOUR_SCRIPT_NAME
// @namespace    https://greasyfork.org/users/your-id
// @version      0.1.0
// @description  One-line description of what this script does.
// @author       You
// @license      MIT
// @match        https://example.com/*
// @run-at       document-idle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

/**
 * GreasyFork-friendly guidelines used here:
 * - No obfuscation, no eval/new Function.
 * - Minimal @grant and permissions.
 * - Clear comments + predictable structure.
 * - Safe DOM operations + mutation observer cleanup.
 */
(() => {
  'use strict';

  // -----------------------------
  // Config / Feature flags
  // -----------------------------

  /** Toggle debug logs (default: false). */
  const DEBUG = false;

  /** Namespaced storage key prefix to avoid collisions with other scripts. */
  const STORE_PREFIX = 'your_script_name:';

  /** Simple logger wrapper to avoid noisy console. */
  const log = (...args) => { if (DEBUG) console.log('[YOUR_SCRIPT_NAME]', ...args); };

  // -----------------------------
  // Utilities
  // -----------------------------

  /**
   * Read value from Greasemonkey/Tampermonkey storage.
   * @template T
   * @param {string} key
   * @param {T} fallback
   * @returns {T}
   */
  function getStore(key, fallback) {
    try {
      return /** @type {any} */ (GM_getValue(STORE_PREFIX + key, fallback));
    } catch (e) {
      log('GM_getValue failed:', e);
      return fallback;
    }
  }

  /**
   * Save value to storage.
   * @param {string} key
   * @param {any} value
   */
  function setStore(key, value) {
    try {
      GM_setValue(STORE_PREFIX + key, value);
    } catch (e) {
      log('GM_setValue failed:', e);
    }
  }

  /**
   * Wait for an element to appear, then resolve.
   * Uses polling with timeout to stay simple & robust.
   * @param {string} selector
   * @param {{timeoutMs?: number, intervalMs?: number, root?: ParentNode}} [opts]
   * @returns {Promise<Element>}
   */
  function waitForSelector(selector, opts = {}) {
    const {
      timeoutMs = 10_000,
      intervalMs = 200,
      root = document
    } = opts;

    return new Promise((resolve, reject) => {
      const start = Date.now();

      const timer = setInterval(() => {
        const el = root.querySelector(selector);
        if (el) {
          clearInterval(timer);
          resolve(el);
          return;
        }
        if (Date.now() - start > timeoutMs) {
          clearInterval(timer);
          reject(new Error(`Timeout waiting for selector: ${selector}`));
        }
      }, intervalMs);
    });
  }

  /**
   * Add CSS safely.
   * @param {string} css
   */
  function addCss(css) {
    try {
      GM_addStyle(css);
    } catch {
      // Fallback for environments without GM_addStyle
      const style = document.createElement('style');
      style.textContent = css;
      document.head.appendChild(style);
    }
  }

  // -----------------------------
  // Core logic (replace with your own)
  // -----------------------------

  /**
   * Perform one-time initialization.
   * Put your main logic here.
   */
  async function main() {
    log('init');

    // Example: inject styles
    addCss(`
      /* YOUR_SCRIPT_NAME styles */
      .ysn-hidden { display: none !important; }
    `);

    // Example: wait for key element
    // (Replace selectors with your real target)
    try {
      const target = await waitForSelector('body');
      log('target ready:', target);

      // Example: apply once
      applyOnce();

      // Example: observe DOM changes if the site is SPA
      startObserver();
    } catch (e) {
      console.warn('[YOUR_SCRIPT_NAME] init failed:', e);
    }
  }

  /**
   * Apply changes once (idempotent).
   * Make sure repeated calls won't duplicate UI or listeners.
   */
  function applyOnce() {
    // Idempotency guard example
    const markerId = 'ysn-marker';
    if (document.getElementById(markerId)) return;

    const marker = document.createElement('div');
    marker.id = markerId;
    marker.style.display = 'none';
    document.documentElement.appendChild(marker);

    // TODO: Your actual one-time modifications
    // - Hide elements
    // - Add buttons
    // - Patch text
    log('applyOnce done');
  }

  /**
   * Mutation observer handler (keep fast!).
   * Avoid heavy querySelectorAll on every mutation.
   * @param {MutationRecord[]} mutations
   */
  function onMutation(mutations) {
    // Example: cheap detection strategy
    for (const m of mutations) {
      if (m.type === 'childList' && (m.addedNodes?.length || m.removedNodes?.length)) {
        // TODO: Your incremental updates
        // Tip: only touch relevant nodes or re-run small idempotent function
        // applyIncremental();
        break;
      }
    }
  }

  /** Keep observer reference for cleanup. */
  let observer = null;

  /** Start observing SPA page updates. */
  function startObserver() {
    if (observer) return;
    observer = new MutationObserver(onMutation);
    observer.observe(document.documentElement, { childList: true, subtree: true });
    log('observer started');
  }

  /** Stop observer if needed. */
  function stopObserver() {
    if (!observer) return;
    observer.disconnect();
    observer = null;
    log('observer stopped');
  }

  // -----------------------------
  // Boot
  // -----------------------------

  // If the site is heavy SPA, document-idle is usually safer.
  // You can also gate by readyState.
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', main, { once: true });
  } else {
    void main();
  }

  // Optional: cleanup when leaving page (rarely needed in userscripts)
  window.addEventListener('beforeunload', () => {
    stopObserver();
  });
})();