write a document on how to use this class and how to write a plugin:
"""
class OverlayManager extends EventTarget {
  /**
   * Creates an instance of OverlayManager.
   * @param {HTMLElement} targetElement - The element to be overlaid.
   * @param {Object} [options] - Optional settings.
   * @param {HTMLElement|Function|string} [options.content] - Content to insert into the overlay.
   * @param {number} [options.zIndex=1000] - z-index for the overlay.
   * @param {Object} [options.styles] - Additional styles for the overlay.
   * @param {boolean} [options.focusTrap=true] - Whether to enable focus trapping.
   * @param {boolean} [options.preventBodyScroll=false] - Whether to prevent body scrolling when overlay is active.
   */
  constructor(targetElement, options = {}) {
    super(); // Initialize EventTarget

    if (!(targetElement instanceof HTMLElement)) {
      throw new TypeError('targetElement must be a valid HTMLElement');
    }

    this.targetElement = targetElement;
    this.overlay = null;
    this.isActive = false;
    this.isInitialized = false;
    this.options = {
      zIndex: 1000,
      focusTrap: true,
      preventBodyScroll: false, // New option added
      ...options,
    };

    this.plugins = new Set(); // Using Set to prevent duplicate plugins
    this.previousActiveElement = null;
    this.scrollPosition = { top: 0, left: 0 }; // For scroll preservation
    this.originalBodyStyles = {}; // To store original body styles

    // Cached focusable elements within contentContainer
    this.cachedFocusableElements = [];

    // Bind methods
    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.debouncedStart = this.debounce(this.start.bind(this), 100);
    this.debouncedStop = this.debounce(this.stop.bind(this), 100);
  }

  /**
   * Static registry to ensure only one OverlayManager per targetElement.
   * Uses a WeakMap to allow garbage collection of DOM elements.
   */
  static registry = new WeakMap();

  /**
   * Factory method to create and initialize an OverlayManager instance.
   * Ensures only one instance per targetElement.
   * @param {HTMLElement} targetElement - The element to be overlaid.
   * @param {Object} [options] - Optional settings.
   * @returns {OverlayManager} - The initialized OverlayManager instance.
   */
  static create(targetElement, options = {}) {
    if (OverlayManager.registry.has(targetElement)) {
      console.warn('An OverlayManager instance already exists for this targetElement.');
      return OverlayManager.registry.get(targetElement);
    }

    const manager = new OverlayManager(targetElement, options);
    manager.init();
    OverlayManager.registry.set(targetElement, manager);
    return manager;
  }

  /**
   * Factory method to debounce functions.
   * @param {Function} func - The function to debounce.
   * @param {number} wait - The debounce delay in milliseconds.
   * @returns {Function} - The debounced function.
   */
  debounce(func, wait) {
    let timeout;
    return function (...args) {
      clearTimeout(timeout);
      timeout = setTimeout(() => func.apply(this, args), wait);
    };
  }

  /**
   * Initializes the overlay by creating and appending it to the target element.
   * Guard clause ensures initialization happens only once.
   * @returns {OverlayManager} - The instance for chaining.
   * @throws {Error} - If initialization fails critically.
   */
  init() {
    if (this.isInitialized) {
      console.warn('OverlayManager is already initialized.');
      return this;
    }

    // Check if the overlay already exists by looking for a specific class
    const existingOverlay = this.targetElement.querySelector('.overlay-manager-overlay');
    if (existingOverlay) {
      this.overlay = existingOverlay;
    } else {
      // Ensure the target element has a positioning context
      const computedStyle = window.getComputedStyle(this.targetElement);
      if (!['absolute', 'relative', 'fixed', 'sticky'].includes(computedStyle.position)) {
        this.targetElement.style.position = 'relative';
      }

      // Create the overlay element
      this.overlay = document.createElement('div');
      this.overlay.classList.add('overlay-manager-overlay');

      // Apply default styles
      Object.assign(this.overlay.style, {
        position: 'absolute',
        top: '0',
        left: '0',
        width: '100%',
        height: '100%',
        display: 'none', // Hidden by default
        pointerEvents: 'none', // Let clicks pass through unless in active state
        zIndex: this.options.zIndex.toString(), // Use provided z-index or default
        ...this.options.styles, // Spread additional styles if provided
      });

      // Create a dedicated content container within the overlay


      // Insert content into the contentContainer if provided
      if (this.options.content) {
        this.#setContent(this.options.content);
      }

      // Append the overlay to the target element
      this.targetElement.appendChild(this.overlay);
    }

    this.isInitialized = true;
    this.dispatchEvent(new CustomEvent('init', { detail: { overlay: this.overlay } }));

    // Initialize all registered plugins
    this.plugins.forEach(plugin => {
      if (typeof plugin.init === 'function') {
        try {
          plugin.init(this);
        } catch (error) {
          console.error('Error in plugin init:', error);
        }
      }
    });

    return this;
  }

  /**
   * Starts the overlay by displaying it and entering the active state.
   * Guard clause prevents starting if already active.
   * @returns {OverlayManager} - The instance for chaining.
   * @throws {Error} - If the overlay is not initialized.
   */
  start() {
    if (!this.isInitialized) {
      throw new Error('OverlayManager is not initialized. Call init() or use the create() factory method.');
    }

    if (this.isActive) {
      console.warn('OverlayManager is already active.');
      return this;
    }

    // Prevent body from scrolling if preventBodyScroll is true
    if (this.options.preventBodyScroll) {
      this.scrollPosition = {
        top: window.scrollY || document.documentElement.scrollTop,
        left: window.scrollX || document.documentElement.scrollLeft,
      };

      // Save original body styles to restore later
      this.originalBodyStyles = {
        position: document.body.style.position || '',
        top: document.body.style.top || '',
        left: document.body.style.left || '',
        width: document.body.style.width || '',
        overflow: document.body.style.overflow || '',
      };

      document.body.style.top = `-${this.scrollPosition.top}px`;
      document.body.style.left = `-${this.scrollPosition.left}px`;
      document.body.style.position = 'fixed';
      document.body.style.width = '100%';
      document.body.style.overflow = 'hidden';
    }

    this.overlay.style.display = 'block';
    this.overlay.style.pointerEvents = 'auto'; // Capture events if needed
    this.isActive = true;

    // Save the currently focused element to restore focus later
    if (this.options.focusTrap) {
      this.previousActiveElement = document.activeElement;
      this.trapFocus();
    }

    this.dispatchEvent(new CustomEvent('start', { detail: { overlay: this.overlay } }));

    // Execute plugin start hooks
    this.plugins.forEach(plugin => {
      if (typeof plugin.onStart === 'function') {
        try {
          plugin.onStart(this);
        } catch (error) {
          console.error('Error in plugin onStart:', error);
        }
      }
    });

    return this;
  }

  /**
   * Stops the overlay by hiding it and returning to the inactive state.
   * Guard clause prevents stopping if already inactive.
   * @returns {OverlayManager} - The instance for chaining.
   * @throws {Error} - If the overlay is not initialized.
   */
  stop() {
    if (!this.isInitialized) {
      throw new Error('OverlayManager is not initialized. Call init() or use the create() factory method.');
    }

    if (!this.isActive) {
      console.warn('OverlayManager is already inactive.');
      return this;
    }

    // Restore body scrolling if it was prevented
    if (this.options.preventBodyScroll) {
      // Iterate over each saved style property and restore or remove accordingly
      for (const [property, value] of Object.entries(this.originalBodyStyles)) {
        if (value) {
          document.body.style[property] = value;
        } else {
          document.body.style.removeProperty(property);
        }
      }

      window.scrollTo(this.scrollPosition.left, this.scrollPosition.top);
      this.originalBodyStyles = {};
    }

    this.overlay.style.display = 'none';
    this.overlay.style.pointerEvents = 'none'; // Release event capture
    this.isActive = false;

    // Restore focus to the previously active element
    if (this.options.focusTrap && this.previousActiveElement) {
      this.previousActiveElement.focus();
      this.previousActiveElement = null;
    }

    // Remove the keydown event listener to prevent memory leaks
    if (this.options.focusTrap) {
      this.overlay.removeEventListener('keydown', this.handleKeyDown);
    }

    this.dispatchEvent(new CustomEvent('stop', { detail: { overlay: this.overlay } }));

    // Execute plugin stop hooks
    this.plugins.forEach(plugin => {
      if (typeof plugin.onStop === 'function') {
        try {
          plugin.onStop(this);
        } catch (error) {
          console.error('Error in plugin onStop:', error);
        }
      }
    });

    return this;
  }

  /**
   * Updates the overlay content.
   * @param {HTMLElement|Function|string} newContent - New content to insert into the overlay.
   * @returns {OverlayManager} - The instance for chaining.
   * @throws {Error} - If the overlay is not initialized.
   */
  #setContent(newContent) {
    this.overlay.innerHTML = '';

    if (newContent) {
      let contentElement;

      if (typeof newContent === 'function') {
        contentElement = newContent();
      } else if (typeof newContent === 'string') {
        this.overlay.innerHTML = newContent;
      } else if (newContent instanceof HTMLElement) {
        contentElement = newContent;
      } else {
        console.warn('Unsupported content type for overlay.');
      }

      if (contentElement instanceof HTMLElement) {
        this.overlay.appendChild(contentElement);
      }
    }

    this.dispatchEvent(new CustomEvent('contentChange', { detail: { newContent } }));

    // Execute plugin contentChange hooks
    this.plugins.forEach(plugin => {
      if (typeof plugin.onContentChange === 'function') {
        try {
          plugin.onContentChange(this, newContent);
        } catch (error) {
          console.error('Error in plugin onContentChange:', error);
        }
      }
    });

    // Re-trap focus if the overlay is active and focus trapping is enabled
    if (this.isActive && this.options.focusTrap) {
      this.overlay.removeEventListener('keydown', this.handleKeyDown);
      this.trapFocus();
    }

    return this;
  }


  setContent(newContent) {
    if (!this.isInitialized) {
      throw new Error('OverlayManager is not initialized. Call init() or use the create() factory method.');
    }
    return this.#setContent(newContent);
  }

  /**
   * Destroys the overlay by removing it from the DOM and cleaning up references.
   * Guard clause prevents destruction if not initialized.
   * @returns {OverlayManager} - The instance for chaining.
   * @throws {Error} - If the overlay is not initialized.
   */
  destroy() {
    if (!this.isInitialized) {
      throw new Error('OverlayManager is not initialized or already destroyed.');
    }

    // Remove the keydown event listener to ensure complete cleanup
    if (this.options.focusTrap && this.overlay) {
      this.overlay.removeEventListener('keydown', this.handleKeyDown);
    }

    if (this.overlay && this.overlay.parentElement === this.targetElement) {
      this.targetElement.removeChild(this.overlay);
      this.dispatchEvent(new CustomEvent('destroy', { detail: { overlay: this.overlay } }));
    }

    // Execute plugin destroy hooks
    this.plugins.forEach(plugin => {
      if (typeof plugin.onDestroy === 'function') {
        try {
          plugin.onDestroy(this);
        } catch (error) {
          console.error('Error in plugin onDestroy:', error);
        }
      }
    });

    // Remove from registry
    OverlayManager.registry.delete(this.targetElement);

    this.overlay = null;
    this.isActive = false;
    this.isInitialized = false;

    // Clear all plugins
    this.plugins.clear();

    return this;
  }

  /**
   * Registers a plugin to extend OverlayManager's functionality.
   * @param {Object} plugin - The plugin object.
   * @param {Function} [plugin.init] - Called during OverlayManager initialization.
   * @param {Function} [plugin.onStart] - Called when the overlay starts.
   * @param {Function} [plugin.onStop] - Called when the overlay stops.
   * @param {Function} [plugin.onDestroy] - Called when the overlay is destroyed.
   * @param {Function} [plugin.onContentChange] - Called when the overlay content changes.
   * @returns {OverlayManager} - The instance for chaining.
   */
  use(plugin) {
    if (typeof plugin !== 'object' || plugin === null) {
      throw new TypeError('Plugin must be an object');
    }

    if (this.plugins.has(plugin)) {
      console.warn('Plugin is already registered.');
      return this;
    }

    this.plugins.add(plugin);

    // If already initialized, initialize the plugin immediately
    if (this.isInitialized && typeof plugin.init === 'function') {
      try {
        plugin.init(this);
      } catch (error) {
        console.error('Error in plugin init:', error);
      }
    }

    return this;
  }

  /**
   * Removes a previously registered plugin.
   * @param {Object} plugin - The plugin object to remove.
   * @returns {OverlayManager} - The instance for chaining.
   */
  remove(plugin) {
    if (!this.plugins.has(plugin)) {
      console.warn('Plugin not found.');
      return this;
    }

    // If the plugin has a destroy method, call it
    if (typeof plugin.onDestroy === 'function') {
      try {
        plugin.onDestroy(this);
      } catch (error) {
        console.error('Error in plugin onDestroy:', error);
      }
    }

    this.plugins.delete(plugin);
    return this;
  }

  /**
   * Traps focus within the overlay when active.
   * @private
   */
  trapFocus() {
    const focusableSelectors = [
      'a[href]',
      'area[href]',
      'input:not([disabled])',
      'select:not([disabled])',
      'textarea:not([disabled])',
      'button:not([disabled])',
      'iframe',
      'object',
      'embed',
      '[contenteditable]',
      '[tabindex]:not([tabindex="-1"])',
    ];
    const focusableElements = this.overlay.querySelectorAll(focusableSelectors.join(','));
    this.cachedFocusableElements = Array.prototype.slice.call(focusableElements);

    if (this.cachedFocusableElements.length === 0) {
      console.warn('No focusable elements within the overlay. Making the overlay itself focusable.');
      this.overlay.setAttribute('tabindex', '-1');
      this.overlay.focus();
    } else {
      const firstFocusable = this.cachedFocusableElements[0];
      firstFocusable.focus();
    }

    this.overlay.addEventListener('keydown', this.handleKeyDown);
  }

  /**
   * Handles keydown events to maintain focus within the overlay and handle Escape key.
   * @param {KeyboardEvent} event - The keyboard event.
   * @private
   */
  handleKeyDown(event) {
    if (event.key === 'Tab') {
      // Handle focus trapping
      if (this.cachedFocusableElements.length === 0) {
        event.preventDefault();
        return;
      }

      const firstFocusable = this.cachedFocusableElements[0];
      const lastFocusable = this.cachedFocusableElements[this.cachedFocusableElements.length - 1];

      if (event.shiftKey) {
        // Shift + Tab
        if (document.activeElement === firstFocusable) {
          lastFocusable.focus();
          event.preventDefault();
        }
      } else {
        // Tab
        if (document.activeElement === lastFocusable) {
          firstFocusable.focus();
          event.preventDefault();
        }
      }
    } else if (event.key === 'Escape') {
      // Allow closing the overlay with Escape key
      this.stop();
    }
  }
}
"""