AIDungeon QoL Tool10

A QoL script for AID, adding customizable hotkeys, also increases performance by removing the countless span elements from last response

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         AIDungeon QoL Tool10
// @version      1.2.0
// @description  A QoL script for AID, adding customizable hotkeys, also increases performance by removing the countless span elements from last response
// @author       randyv
// @match        https://*.aidungeon.com/*
// @icon         https://play-lh.googleusercontent.com/ALmVcUVvR8X3q-hOUbcR7S__iicLgIWDwM9K_9PJy87JnK1XfHSi_tp1sUlJJBVsiSc
// @require      https://code.jquery.com/jquery-3.7.1.min.js
// @require      https://update.greasyfork.org/scripts/383527/701631/Wait_for_key_elements.js
// @require      https://update.greasyfork.org/scripts/439099/1203718/MonkeyConfig%20Modern%20Reloaded.js
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// @license      MIT
// @namespace    https://greasyfork.org/users/1302066
// ==/UserScript==

/* global jQuery, $, waitForKeyElements, MonkeyConfig */

/// require      https://cdn.jsdelivr.net/npm/tampermonkey-require-for-react
/// @downloadURL https://update.greasyfork.org/scripts/1302066/AIDungeon%20QoL%20Tool.user.js
/// @updateURL https://update.greasyfork.org/scripts/1302066/AIDungeon%20QoL%20Tool.meta.js

/// require      https://cdn.jsdelivr.net/npm/tampermonkey-require-for-react

const $ = jQuery.noConflict(true);

/********************************
* Code for handling the configuration menu and for handling shortcuts.
*/
function addEventListeners(element, events, handler) {
  events.forEach((event) => {
    if (event.startsWith('touch')) {
      element.addEventListener(event, handler, { passive: true }); // Mark touch events as passive
    } else {
      element.addEventListener(event, handler); // Other events can be added normally
    }
  });
}
if (0) {
  function disableCustomContextMenu(button) {
    console.log("called disableCustomContextMenu");
    // Remove existing listeners (optional, but good practice)
    button.removeEventListener('contextmenu', event => { });

    // Add listener using capture phase for higher priority
    button.addEventListener('contextmenu', (event) => {
      event.preventDefault();
      event.stopPropagation(); // Stop propagation to prevent AIDungeon's listener from triggering
    }, true); // true for capture phase
  }

  // Mutation observer for dynamically added buttons
  const buttonObserver = new MutationObserver((mutationsList, observer) => {
    for (const mutation of mutationsList) {
      if (mutation.type === 'childList') {
        for (const node of mutation.addedNodes) {
          if (node.nodeName === 'DIV' && node.matches('[role="button"]')) {
            disableCustomContextMenu(node);
          }
        }
      }
    }
  });

  buttonObserver.observe(document.body, { childList: true, subtree: true });

}

function waitForSubtreeElements(selector, callback, targetNode, runImmediately = false) {
  function mutationObserverCallback(mutationsList, observer) {
    const elements = targetNode.querySelectorAll(selector);
    if (elements.length > 0) {
      observer.disconnect();
      callback(elements);
    }
  }
  const observer = new MutationObserver(mutationObserverCallback);
  observer.observe(targetNode, { childList: true, subtree: true });
  if (runImmediately) {
    mutationObserverCallback([], observer);
  }

  /*
    const observer = new MutationObserver((mutationsList, observer) => {
      const elements = targetNode.querySelectorAll(selector);
      if (elements.length > 0) {
        observer.disconnect();
        callback(elements);
      }
    });
    observer.observe(targetNode, { childList: true, subtree: true });
    if (runImmediately) {
      const elements = targetNode.querySelectorAll(selector);
      if (elements.length > 0) {
        observer.disconnect();
        callback(elements);
      }
    }
  */
}

const getSetTextFunc = (value, parent) => {
  const inputElem = $(parent || value).find('input');
  if (!parent) {
    const booleans = inputElem
      .filter(':checkbox')
      .map((_, el) => el.checked)
      .get();
    if (!booleans[0]) return inputElem.val().toUpperCase();
    return booleans;
  } else {
    inputElem.each((i, el) => {
      if (el.type === 'checkbox') el.checked = value[i];
      else el.value = value.toUpperCase();
    });
  }
};

const dummy = (value, parent) => {
};

const cfg = new MonkeyConfig({
  title: 'Configure',
  menuCommand: true,
  params: {
    Modifier_Keys: {
      type: 'custom',
      html: '<input id="ALT" type="checkbox" name="ALT" /> <label for="ALT">ALT</label> <input id="CTRL" type="checkbox" name="CTRL" /> <label for="CTRL">CTRL</label> <input id="SHIFT" type="checkbox" name="SHIFT" /> <label for="SHIFT">SHIFT</label>',
      set: getSetTextFunc,
      get: getSetTextFunc,
      default: [true, true, false]
    },
    Take_Turn: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'C' },
    Continue: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'A' },
    Retry: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'S' },
    Retry_History: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'X' },
    Erase: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'D' },
    Do: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'Q' },
    Say: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'W' },
    Story: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'E' },
    See: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'R' },
    Response_Underline: { type: 'checkbox', default: true },
    Response_Bg_Color: { type: 'checkbox', default: false },

    '_label': {
      type: 'custom',
      label: '<HR>',
      set: dummy, get: dummy,
      html: '<HR>'
    },

    Toggle_Site: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'T' },
    User_Name: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'Z' },
    User_Profile: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'G' },
    Continue_Adventure: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'V' },
    Flame: { type: 'custom', html: '<input type="text" maxlength="1" />', set: getSetTextFunc, get: getSetTextFunc, default: 'F' },

    Modal_Dimensions: {
      type: 'custom',
      html: `
        <label for="Modal_Width">Width:</label>
        <input id="Modal_Width" type="number" min="100" style="width: 100px" /> px
        <label for="Modal_Height">Height:</label>
        <input id="Modal_Height" type="number" min="100" style="width: 100px" /> px`,
      set: (values, parent) => {
        const [width, height] = values.map(Number); // Convert to numbers
        parent.querySelector('#Modal_Width').value = width;
        parent.querySelector('#Modal_Height').value = height;
      },
      get: (parent) => {
        return [parent.querySelector('#Modal_Width').value, parent.querySelector('#Modal_Height').value];
      },
      default: [512, 512] // Default values for width and height
    },

    Save_Raw_Text: { type: 'checkbox', default: false },

    Default_SC_Notes: { type: 'text', default: 'Unused.' },

    'Delimiter Settings': {
      type: 'custom',
      html: 'Customize delimiters for Story Card insertion.',
      set: dummy, get: dummy,
      default: 'Customize delimiters for Story Card insertion.'
    },
    Delimiter_Start: { type: 'text', default: '{ Story Card: ' },
    Delimiter_End: { type: 'text', default: ' }' }
  }
});

const actionArray = [
  { name: 'Take_Turn', type: 'Command', 'aria-Label': 'Command: take a turn', active: ["/play"] },
  { name: 'Continue', type: 'Command', 'aria-Label': 'Command: continue', active: ["/play"] },
  { name: 'Retry', type: 'Command', 'aria-Label': 'Command: retry', active: ["/play"] },
  { name: 'Retry_History', type: 'History', 'aria-Label': 'Retry history', active: ["/play"] },
  { name: 'Erase', type: 'Command', 'aria-Label': 'Command: erase', active: ["/play"] },
  { name: 'Do', type: 'Mode', 'aria-Label': "Set to 'Do' mode", active: ["/play"] },
  { name: 'Say', type: 'Mode', 'aria-Label': "Set to 'Say' mode", active: ["/play"] },
  { name: 'Story', type: 'Mode', 'aria-Label': "Set to 'Story' mode", active: ["/play"] },
  { name: 'See', type: 'Mode', 'aria-Label': "Set to 'See' mode", active: ["/play"] },
  { name: 'Flame', type: 'Command', 'aria-Label': 'Game Menu', active: ["/play"] },
  { name: 'User_Name', type: 'User_Name', 'aria-Label': 'Game Menu', active: ["/play"] },
  { name: 'User_Profile', type: 'User_Profile', 'aria-Label': 'Game Menu', active: ["/play", "/profile/", "/scenario/", "/adventure/"] },
  { name: 'Continue_Adventure', type: 'Continue_Adventure', 'aria-Label': 'Play', active: ["/profile/", "/scenario/", "/adventure/"] },
  { name: 'Toggle_Site', type: 'Toggle_Site', 'aria-Label': 'Toggle Site', active: ["play.aidungeon.com", "beta.aidungeon.com"] }
];

const actionKeys = actionArray.map((action) => cfg.get(action.name));
// Modified handleKeyPress function

const isMac = window.navigator.userAgentData?.platform?.toLowerCase().includes('mac');

const handleKeyPress = (e) => {
  if (e.repeat) return;
  const key = e.key.toUpperCase();

  //const modifiers = ['ALT', 'CTRL', 'SHIFT'].map((mod) => e[`${mod.toLowerCase()}Key`]);

  const modifiers = ['ALT', 'CTRL', 'SHIFT'].map((mod) => {
    // For Mac, use Cmd instead of Ctrl
    return (mod === 'CTRL' && isMac) ? e.metaKey : e[`${mod.toLowerCase()}Key`];
  });

  const modifsActive = modifiers.every((value, index) => value === cfg.get('Modifier_Keys')[index]);
  const index = actionKeys.indexOf(key);
  if (modifsActive && index !== -1) {
    const action = actionArray[index];
    let isPageActive = false;
    const fullURL = window.location.href;

    // Determine if the current page is active based on the action's "active" property
    if (Array.isArray(action.active)) {
      // Array of strings: check if pathname includes any of the strings
      isPageActive = action.active.some(path => fullURL.includes(path));
    } else if (action.active instanceof RegExp) {
      // Regular expression: check if pathname matches the regex
      isPageActive = action.active.test(fullURL);
    } else if (typeof action.active === 'function') {
      // Function: call the function to determine if the page is active
      isPageActive = action.active(fullURL);
    } else {
      console.warn("Invalid 'active' property type for action:", action.name);
    }
    if (isPageActive) {
      e.preventDefault();
      e.stopPropagation();
      const targetElem = `[aria-label="${action['aria-Label']}"]`;
      if ($("[aria-label='Close text input']").length) $("[aria-label='Close text input']").click();
      if (action.type === 'Command') setTimeout(() => $(targetElem).click(), 50);
      else if (action.type === 'Mode') delayedClicks([() => $('[aria-label="Command: take a turn"]').click(), () => $('[aria-label="Change input mode"]').click(), () => $(targetElem).click()]);
      else if (action.type === 'History' && $('[aria-label="Retry history"]').length) setTimeout(() => $(targetElem).click(), 50);
      else if (action.type === 'User_Profile') {
        if (window.location.pathname.includes('/play')) {
          delayedClicks([
            () => $('[role="button"][aria-label="Game Menu"]').click(),
            () => $('[role="button"][aria-label^="View"][aria-label$="profile"]').click()
          ]);
        } else {
          delayedClicks([
            () => $('[role="button"][aria-label="User Menu"]').click(),
            () => $('[role="button"][aria-label="My Stuff/Profile"]').click()
          ]);
        }
      }
      else if (action.type === 'Continue_Adventure') {
        console.log("Got continue Adventure");
        delayedClicks([
          () => $('[role="button"][aria-label="Play"]').click(),
          () => $('[role="button"][aria-label="Continue Adventure"]').click()
        ]);
      }
      else if (action.type === 'User_Name') {
        document.addEventListener('forceFocus', (event) => {
          //.log("Got custome forced event.");
          event.target.focus(); // Focus on the target of the event (the input field)
        });
        delayedClicks([
          () => $('[role="button"][aria-label="Game Menu"]').click(),
          () => $('[role="button"][aria-label="Open player menu"]').click(),
          () => $('[role="button"][aria-label="Edit Character Name"]').click(),
          /*
          , () => {
            const playersGroup = $('[role="group"][aria-label="Players"]');
            //const viewProfileButton = $('[role="group"][aria-label="Players"]');
            const inputField = playersGroup.find('button[aria-label^="View"][aria-label$="profile"]').next().find('input')[0];

            //const playersGroup = $('[role="group"][aria-label="Players"]');
            //const viewProfileButton = playersGroup.querySelector('button[aria-label^="View"][aria-label$="profile"]');
            //const inputField = viewProfileButton?.nextSibling?.firstChild; // Path to input from profile button.
            if (inputField) {
              inputField.id = "flameplayername"; // This works, can see in dev console.
              const focusElement = inputField.parentElement;
              setTimeout(
                () => {
                  //const inputField = document.querySelector('input#flameplayername');
                  focusElement.dispatchEvent(new CustomEvent('forceFocus', { bubbles: true }));
                  //const inputField = document.querySelector('input#flameplayername');
                  //focusElement.dispatchEvent(new CustomEvent('forceFocus', { bubbles: true }));

                  //focusElement.click(); // Doesn't work.
                  //focusElement.focus(); // Doesn't work.
                  //focusElement.trigger('click'); // Doesn't work.

                  //focusElement.dispatchEvent(new Event('focus', { bubbles: true }));  // Doesn't work.
                  //focusElement.dispatchEvent(new MouseEvent('click', { bubbles: true })); // Doesn't work.

                  const mouseDownEvent = new MouseEvent('mousedown', { bubbles: true }); // Doesn't work.
                  const mouseUpEvent = new MouseEvent('mouseup', { bubbles: true }); // Doesn't work.
                  focusElement.dispatchEvent(mouseDownEvent); // Doesn't work.
                  focusElement.dispatchEvent(mouseUpEvent); // Doesn't work.
                  if (0) { // This doesn't work.
                    const existingClickListener = inputField.onclick; // Get the existing click handler
                    focusElement.onclick = null; // Remove it
                    focusElement.click(); // Or use dispatchEvent as shown above
                    focusElement.onclick = existingClickListener;
                  }
                }, 2000
              );

            }
          },
        */
          () => $('input#flameplayername').click(),  // Doesn't work.
          () => $('input#flameplayername').trigger('click'), // Doesn't work.
          () => $('input#flameplayername').focus() // Doesn't work.
        ]
        );
      }
      else if (action.type === 'Toggle_Site') {
        const currentURL = window.location.href;
        console.log("Got Site Toggle: ", currentURL);
        const betaSite = 'beta.aidungeon.com';
        const playSite = 'play.aidungeon.com';
        const newURL = currentURL.includes(betaSite) ? currentURL.replace(betaSite, playSite) : currentURL.replace(playSite, betaSite);
        console.log("Got Site Toggle: ", newURL);
        window.location.href = newURL;
      } // End action.type
    } // End isPageActive

  }
  const selectKeys = ['ARROWLEFT', 'ENTER', 'ARROWRIGHT'];
  if (selectKeys.includes(key) && $('[role="dialog"]').length)
    setTimeout(() => $("[role='dialog']").find("[role='button']")[selectKeys.indexOf(key)].click(), 50);
};

const delayedClicks = (clicks, i = 0) => {
  if (i < clicks.length) {
    setTimeout(() => {
      clicks[i]();
      delayedClicks(clicks, i + 1);
    }, 50);
  }
};

class DOMObserver {
  constructor(callback, targetNode, options, startImmediately = false) {
    this.observer = new MutationObserver(callback);
    this.targetNode = targetNode;
    this.options = options;

    if (startImmediately) {
      this.observe();
    }
  }

  destroy() {
    this.disconnect();
    this.observer = null;
    this.targetNode = null;
    this.options = null;
  }

  observe(targetNode = this.targetNode, options = this.options) {
    if (this.observer && targetNode && targetNode.nodeType === Node.ELEMENT_NODE) { // Ensure targetNode is an Element
      this.observer.observe(targetNode, options);
    } else {
      console.warn("Target node is not a valid element:", targetNode); // For debugging
    }
  }
  disconnect() {
    if (this.observer !== null) {
      this.observer.disconnect();
    }
  }

  takeRecords() {
    return this.observer ? this.observer.takeRecords() : []; // Return empty array if observer is null
  }

  get isConnected() {
    return this.observer && this.observer.isConnected(); // Check if observer exists and is connected
  }
}

GM_addStyle(`
  .css-11aywtz,._dsp_contents { 
      user-select: text !important; 
  }
`);
//textarea:not(#game-text-input, #transition-opacity, #shadow-box, #do-not-copy) {
GM_addStyle(`
  /*
  ._pt-1316335136 {
    padding-top: 6px !important;
  }
  ._pb-1316335136 {
    padding-bottom: 6px !important;
  }
  */
  /*
  ._pr-1481558400 {
    padding-right: 0px !important;
  }
  ._pl-1481558338 {
    padding-left: 8px !important;
  }
  ._pr-1481558338 {
    padding-right: 8px !important;
  }

  ._pt-1316335167 {
    padding-top: 8px !important;
  }  
  ._pb-1316335167 {
    padding-bottom: 8px !important;
  }  
  ._pl-1316335167 {
    padding-left: 8px !important;
  }  
  ._pr-1316335167 {
    padding-right: 8px !important;
  }
  ._pl-1481558307 {
    padding-left: 8px !important;
  }
  ._pr-1481558307 {
    padding-right: 8px !important;
  }
  */
  /* Target: every storyCardsTab list of button type with pad or margin left or right */
  div#modalInnerContent_storyCardsTab div[role="button"]._pl-1481558338 {
    padding-left: 0px !important;
  }
  div#modalInnerContent_storyCardsTab div[role="button"]._pr-1481558338 {
    padding-right: 0px !important;
  }
  div#modalInnerContent_storyCardsTab div[role="button"]._mr-1481558369 {
    margin: 0px !important;
  }
  div#modalInnerContent_storyCardsTab > div > div {
    padding-left: 0px !important;
    padding-right: 0px !important;
    margin-left: 0px !important;
    margin-right: 0px !important;
  }
  div#modalInnerContent_storyCardsTab > div > div > div > div:nth-child(3) {
    width: 100% !important;
  }
  div#modalInnerContent_storyCardsTab > div > div > div > div:nth-child(3) > * {
    width: 100% !important;
  }
  div#modalInnerContent_storyCardsTab > div > div > div > div:nth-child(5) {
    padding-bottom: 0px !important;
  }

  /* Make Modal bottom right border square for the resize icon. */
  div:has([aria-label="Modal" i]) {
    border-bottom-right-radius: 0px !important;
  }
  /* These classes must be overridden to get the square corner. */
  ._bbrr-1307609874 {
    border-bottom-right-radius: 0px !important;
  }
  ._bbrr-1881205710 {
    border-bottom-right-radius: 0px !important;
  }
  /* Tweak the padding for modals. */
  div[id^="modalHeader_" i] {
    padding: 8px !important;
    flex-grow: 0 !important;
    /* overflow: hidden hidden !important; */
  }
  div[id^="modalContent_" i] {
    max-height: 100% !important;
    margin: 0px !important;
    padding-bottom: 8px !important;
    padding-top: 8px !important;
    padding-left: 8px !important;
    padding-right: 8px !important;
    scrollbar-gutter: stable !important;
    min-height: 0px !important;
    overflow-y: auto !important;
    overflow-x: hidden !important;
  }
  div[id^="modalContent_" i] p[role="heading"] {
    padding-left: 0px !important;
    padding-right: 0px !important;
    margin-left: 0px !important;
    margin-right: 0px !important;
  }
  div[id^="modalInnerContent_" i] button[type="button"] {
    padding-left: 8px !important;
    padding-right: 8px !important;
    margin-left: 0px !important;
    margin-right: 0px !important;
  }
  /*
  [id^="modalInnerContent_" i] div.is_Column {
    padding-left: 0px !important;
    padding-right: 0px !important;
    margin-left: 0px !important;
    margin-right: 0px !important;
  }
  div[id^="modalContent_" i] + div:has(p[role="heading"]) {
    padding-left: 0px !important;
    padding-right: 0px !important;
    margin-left: 0px !important;
    margin-right: 0px !important;
  }
  div:has([id^="modalContent_" i] p[role="heading" i]) {
    padding-left: 0px !important;
    padding-right: 0px !important;
    margin-left: 0px !important;
    margin-right: 0px !important;
  }
  [id^="modalContent_"] > div, [id^="modalContent_"]._pb-1481558400 {
    padding: 8px !important;
    padding-bottom: 8px !important;
    padding-top: 8px !important;
    padding-left: 8px !important;
    padding-right: 8px !important;
  }
  */
    div[id^="modalContent_" i] + div.has(p[role="heading"]) {
    padding-left: 0px !important;
    padding-right: 0px !important;
    margin-left: 0px !important;
    margin-right: 0px !important;
  }
  div[id^="modalContent_" i] input {
    padding-left: 8px !important;
    padding-right: 8px !important;
    margin-left: 0px !important;
    margin-right: 0px !important;
  }
  div[id^="modalContent_" i] textarea {
    padding-left: 8px !important;
    padding-right: 8px !important;
    margin-left: 0px !important;
    margin-right: 0px !important;
  }
  div[id^="modalInnerContent_" i] {
    max-height: 100% !important;
    padding: 0px !important;
    margin: 0px !important;
    min-height: 0px !important;
    overflow-x: unset !important;
    overflow-y: unset !important;
    max-height: 100% !important;
    max-width: 100% !important;
  }  
  /*
  [id^="modalInnerContent_"] > div > div > div {
    padding-left: 0px !important;
    padding-right: 0px !important;
    margin: 0px !important;
  }
*/
  /* Target the specific modal with the selected "Story Cards" tab 
  div[aria-label="Modal"] div[id^="modalHeader_"] div[role="tablist"][aria-label="Section Tabs"] div[role="tab"][aria-label^="Selected tab story cards" i] 
  ~ div[id^="modelContent_"] [id^="modalInnerContent_"] > div > div > div {
      padding-left: 0px !important;
      padding-right: 0px !important;
      margin: 0px !important;
  }
*/
  /*
  #modalInnerContent_1722033353146 > div > div > div > div > div:nth-child(3) > div:nth-child(7) > div.css-175oi2r > div
  [id^="modalInnerContent_"] > div > div > div {
  [id^="modalHeader_"] div[role="tablist"][aria-label="Section Tabs"] div[role="tab"][aria-label^="Selected tab story cards" i] {
    [id^="modalInnerContent_"] > div > div > div {
      padding-left: 0px !important;
    padding-right: 0px !important;
    margin: 0px !important;
  }
      */

  /*
   > div > div:nth-child(1) > div > div > div > span > div
  [id^="modalInnerContent_"] > div {
    padding: 0px !important;
    padding-bottom: 0px !important;
  }
  [id^="modalInnerContent_"] > div > div > div > div > div {
    padding: 0px !important;
    margin: 0px !important;
  }
    */
  /*
  ._mah-1611765696 {
    max-height: unset !important;
  }
  ._mih-1611762875 {
    min-height: 200px !important;
  }
  input._h-606181821 {
    height: var(--size-6) !important;  
  }
  input._gap-1481558338 {
    gap: var(--space-1) !important;
  }
    */

  /* max-height: none !important;  /* auto does not work, shows error. */
  /*max-height: 1024px !important;  /* auto does not work, shows error. */
  /* min-height: auto !important;  /* Or min-height: 0; */
  /* height: unset !important;  /* This seems to set the height to a value that that's not resizable. */
  /* height: initial !important;  /* This seems to set the height to a smaller value that that's not resizable. */
  /* height: 300px !important; /* This seems to set the height to a value that that's not resizable. */
  /* height: 100% !important;  /* This seems seems to set it 100% the size of the text area, but it isn't resizable. */
  /* height: 25% !important;  /* This seems seems to set the internal textarea scrollable region to 25% of the size of the text area, but it isn't resizable. */
  /* height: '' !important;       /* Makes the text area resizable, but shows as error, and it's the full size of the text. */
  /* height: 400px !important;       /* Makes the text area resizable, but shows as error, and it's the full size of the text. */

  /* Chrome/Opera Fatten up the scroll bar a bit. This also fixes textarea resize icon. */
  ::-webkit-scrollbar {
    width: 8px !important;
  }

  /* Put vertical resizers on all textareas. */
  textarea:not([aria-label="Text input field" i], #game-text-input, #shadow-box) {
    min-height: 50px !important;  /* Or min-height: 0; */
    resize: vertical !important;
    overflow-y: auto !important;
    scrollbar-gutter: stable !important;
    /* This doesn't work the class is overriding it. */
    border-bottom-right-radius: 0px !important;
    /* None of these appear to do anything in chrome (maybe for Mozilla): */
    --scrollbar-width: 8px !important;
    color-scheme: dark !important;
    --vh: 11.76px !important;
  }
  /* Experiments with offing the dimming gradient.
  .game-text-mask {
    transition: mask-position .3s ease, -webkit-mask-position .3s ease !important; 
    -webkit-mask-image: linear-gradient(rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.5) 100%) !important; 
    mask-image: linear-gradient(rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.5) 100%) !important;
    -webkit-mask-size: 100% 100% !important;
    mask-size: 100% 100% !important;
  }
  .game-text-mask {
    transition: mask-position .3s ease, mask-size .3s ease; 
    -webkit-mask-image: linear-gradient(transparent, rgba(0, 0, 0, 0) 10%, #000 20%, #000); 
    mask-image: linear-gradient(transparent, rgba(0, 0, 0, 0) 10%, #000 20%, #000);
    -webkit-mask-size: 100% 100%; 
    mask-size: 100% 100%;
  }
  */
`);

// Clean up the the prompt area to make more efficient.
// This is the original code from QoL tool by AliH2K
function handleChanges(mutationsList, observer) {
  for (const mutation of mutationsList) {
    if (!window.location.href.includes('/play')) {
      return; // Exit early if not on a Play page
    }
    const targetNode = mutation.target; // Get the target node from the mutation
    let lastResponse = targetNode?.lastChild?.lastChild;
    if (lastResponse) {
      // Check if the last child exists, is a span, and if it has children
      if (lastResponse.lastChild && lastResponse.children.length > 0 && lastResponse.tagName === 'SPAN') {
        // Check if the last child is an HTMLElement before accessing style
        if (lastResponse.lastChild instanceof HTMLElement) {
          lastResponse.lastChild.style.pointerEvents = 'none'; // Set pointerEvents to none
        } else {
          console.warn("lastResponse.lastChild is not an HTMLElement:", lastResponse.lastChild);
        }
      } else if (lastResponse.lastChild instanceof HTMLElement && lastResponse.lastChild.style.pointerEvents === 'none') {
        lastResponse.lastChild.style.pointerEvents = ''; // Reset pointerEvents if it was set to none
      } else {
        // Handle the case where lastResponse doesn't have a lastChild yet
        const observer = new MutationObserver((mutations) => {
          for (const mutation of mutations) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
              handleChanges(mutations, observer); // Recursively call handleChanges for the new nodes
              observer.disconnect(); // Stop observing after the child is added
              break;
            }
          }
        });

        observer.observe(lastResponse, { childList: true }); // Observe for changes in childList
      }

      // Check if the first child exists, is not a text node, and the parent is a span
      if (lastResponse.firstChild && lastResponse.firstChild.nodeType !== 3 && lastResponse.tagName === 'SPAN') {
        //if (lastResponse.firstChild.nodeType !== 3 && lastResponse.tagName === 'SPAN') {

        const interval = setInterval(() => {
          const opacity = lastResponse.lastChild instanceof HTMLElement ? getComputedStyle(lastResponse.lastChild).opacity : '1';
          if (opacity === '1') {
            clearInterval(interval);
            const SPANS = Array.from(lastResponse.children);
            let joinedText = '';
            SPANS.forEach((span) => (joinedText += span.textContent));
            while (lastResponse.firstChild && lastResponse.firstChild.nodeType !== 3) lastResponse.removeChild(lastResponse.firstChild);
            if (joinedText.length > 1) lastResponse.textContent = joinedText;
          }
        }, 500);
      }
    }
  }
  // // Apply Custom CSS
  // const customCSS = cfg.get('Custom_CSS');
  // if (customCSS) {
  //   GM_addStyle(customCSS);
  // }
}

//setNativeValue(input, 'foo');
//input.dispatchEvent(new Event('input', { bubbles: true }));

function setNativeValue(element, value) {
  const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
  const prototype = Object.getPrototypeOf(element);
  const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;

  if (valueSetter && valueSetter !== prototypeValueSetter) {
    prototypeValueSetter.call(element, value);
  } else {
    valueSetter.call(element, value);
  }
}


const ActionToggleMsgOn = "A+";
const ActionToggleMsgOff = "A-";

let actionsExpanded = null;
let toggleButtonText = null;

GM_addStyle(`
    [aria-label="Story"] .is_Row {
        visibility: visible;
    }
    .actions-hidden .is_Row > * {
        visibility: hidden;
        display: none !important;
    }
    .actions-hidden .is_Row {
        visibility: hidden;
        margin-top: 2.5em !important;
        margin-right: 0 !important;
        margin-bottom: 0 !important;
        margin-left: 0 !important;
        padding: 0 !important;
    }
`);
/*
*/
function setActionVisibility(visible) {
  const container = $("[aria-label='Story']");
  if (visible) {
    container.removeClass("actions-hidden");
  } else {
    container.addClass("actions-hidden");
  }
  actionsExpanded = visible;
  sessionStorage.setItem("actionsExpanded", actionsExpanded); // Save state
}

function toggleOnClick(buttonTextElement) {
  actionsExpanded = !actionsExpanded;
  setActionVisibility(actionsExpanded); // Use the set function
  buttonTextElement.innerText = actionsExpanded ? ActionToggleMsgOn : ActionToggleMsgOff;
}

function removeHeightClasses(element) {
  const classList = element.classList; // Get the element's classList

  for (const className of classList) {
    if (className.startsWith('_mih-') || className.startsWith('_mah-')) {
      classList.remove(className);
    }
  }
}

function buttonClone(cloneReference, label, action) {
  if (!cloneReference) {
    console.warn("Null cloneReference in buttonClone!");
    return;
  }
  //console.log("cloneReference: ", cloneReference);

  const clonedElement = cloneReference.cloneNode(true); // Clone the entire reference

  // Determine if the cloned element is a span or a div (button)
  const isSpan = clonedElement.tagName === 'SPAN';

  // The play page wraps the button DIVs in spans, while the read page does not.
  const button = isSpan ? clonedElement.querySelector('[role=button]') : clonedElement;

  // Generate a unique ID
  const uniqueId = `custom-button-${label.replace(/\s+/g, '-').toLowerCase()}`;
  button.id = uniqueId;

  // Remove anything that might disable it.
  button.classList.remove('disabled');
  button.removeAttribute('aria-disabled');

  button.setAttribute('style', 'pointer-events: all !important; z-index:100000; font-weight: bold;');

  // Explicitly add the 'enabled' attribute to the cloned button
  button.setAttribute('aria-enabled', 'true');

  button.setAttribute('aria-label', `${label} button`);

  // Add a unique class to the cloned button for easier targeting
  const uniqueClass = `custom-button-${uniqueId}`;
  button.classList.add(uniqueClass);

  //Use GM_addStyle with a more specific selector and !important
  GM_addStyle(`
    #${uniqueId}.${uniqueClass} {
      background-color: black !important;
      color: white !important;
      opacity: 1 !important;
      font-weight: bold !important;
    }
`);

  // Cache the <p> element for later access
  const buttonTextElement = button.querySelector('p');
  //console.log("button: ", button);
  buttonTextElement.innerText = label;
  // Remove the dimming opacity class.
  buttonTextElement.classList.remove('_o-0d0t546');
  button.onclick = (e) => {
    e.preventDefault();
    e.bubbles = false;
    action(buttonTextElement); // Pass the <p> element to the action function
  };
  return clonedElement;
}

function headerInject(container, cloneReference, label, action) {
  const clonedElement = buttonClone(cloneReference, label, action); // Clone the entire reference
  container.prepend(clonedElement);
}

/**********************************
** Code for Read Pages.
*/

function handleReadPage(targetNode) {

  // Use the second button if available, otherwise use the first

  // Find all buttons with innerText 'Aa'
  const aaButtons = [...$('[role=button]')].filter((e) => e.innerText === 'Aa');
  const aaButton = aaButtons.length >= 2 ? aaButtons[1] : aaButtons[0];

  const buttonContainer = aaButton.parentElement;

  function onSave(type) {
    const story = $('[aria-label="Story"]')[0];
    const title = $('[role=heading]')[0]?.innerText;

    const saveRaw = cfg.get('Save_Raw_Text');

    if (!story || !title) return alert('Wait for content to load first!');

    let text = story.innerText.replaceAll(/w_\w+\n+\s+/g, type === 'text' ? '' : '> ');
    if (type === 'md') text = '## ' + title + '\n\n' + text;

    text = text.replaceAll(/\n+/g, '\n\n');
    const blob = URL.createObjectURL(new Blob([text], { type: type === 'text' ? 'text/plain' : 'text/x-markdown' }));
    const a = document.createElement('a');
    a.download = title + (type === 'text' ? '.txt' : '.md');
    a.href = blob;
    a.click();
    URL.revokeObjectURL(blob);
  }

  headerInject(buttonContainer, aaButton, '.txt', () => onSave('text'));
  headerInject(buttonContainer, aaButton, '.md', () => onSave('md'));
  headerInject(buttonContainer, aaButton, toggleButtonText, toggleOnClick);
}

/**********************************
** Code for Modals.
*/



function insertAfter(referenceNode, newNode) {
  referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

function cloneAndModifyModalButton(modalNode, originalButtonSelector, newButtonText, newButtonIcon) {
  const originalButton = modalNode.querySelector(originalButtonSelector);
  if (!originalButton) {
    console.warn("Original button not found:", originalButtonSelector);
    return null;
  }

  const clonedButton = originalButton.cloneNode(true);

  // Modify the cloned button's attributes and content
  clonedButton.setAttribute('aria-label', newButtonText);
  const buttonTextElement = clonedButton.querySelector('.is_ButtonText');
  if (buttonTextElement) {
    buttonTextElement.textContent = newButtonText;
  }

  const iconElement = clonedButton.querySelector('.font_icons');
  if (iconElement) {
    iconElement.textContent = newButtonIcon;
  }

  // Remove the original button's click event handler
  clonedButton.onclick = null;
  clonedButton.removeEventListener('click', originalButton.onclick);

  return clonedButton;
}


// Function to create and insert a button and text fields into the Story Card Modal
// For adding { Story Card: } into the entry field.
function modifyStoryCardEditor(modalNode) {
  const mkycfg_startDelim = cfg.get('Delimiter_Start');
  const mkycfg_endDelim = cfg.get('Delimiter_End');

  const entryField = modalNode.querySelector("textarea[aria-labelledby='scEntryLabel']");
  const entryLabel = modalNode.querySelector("p#scEntryLabel");
  const entrySection = entryLabel.parentNode;

  const delimEntryButton = cloneAndModifyModalButton(modalNode, "div[role='button'][aria-label='Close modal']", "Insert", "w_add");
  if (!delimEntryButton) return; // Handle the case where the button wasn't found
  const buttonText = delimEntryButton.querySelector('.is_ButtonText');
  delimEntryButton.id = "scDelimInsertButton";
  //delimEntryButton.classList.add('is_Button', 'is_ButtonText', 'insert-button', 'css-11aywtz', 'r-6taxm2'); // Add classes for styling

  // Clone the Triggers section (get parentNode of p with id 'scTriggersLabel')
  const triggersSection = modalNode.querySelector("p#scTriggersLabel").parentNode;
  const delimEntrySection = triggersSection.cloneNode(true);

  // Modify label, placeholder, and aria-label
  const delimEntryLabel = delimEntrySection.querySelector("p");
  delimEntryLabel.setAttribute('aria-label', "Delimiter Entry Label");
  delimEntryLabel.id = "scDelimEntryLabel";
  delimEntryLabel.textContent = "DELIM ENTRY";

  const delimStartInput = delimEntrySection.querySelector("input");
  delimStartInput.placeholder = mkycfg_startDelim;
  delimStartInput.value = mkycfg_startDelim;
  delimStartInput.setAttribute('aria-label', "Delimiter Start Input");
  delimStartInput.setAttribute('aria-labelledby', "scDelimEntryLabel");
  delimStartInput.id = "scDelimStartInput";

  // Get the span that contains the input field
  const delimInputSpan = delimEntrySection.querySelector('span._dsp_contents');
  delimInputSpan.setAttribute('aria-label', "Delimiter Input Span");
  delimInputSpan.id = "scDelimInputSpan";
  delimInputSpan.setAttribute('aria-labelledby', "scDelimEntryLabel");

  const delimEndInput = delimStartInput.cloneNode(true);
  delimEndInput.placeholder = mkycfg_endDelim; // Update the placeholder text
  delimEndInput.value = mkycfg_endDelim; // Update the value text
  delimEndInput.setAttribute('aria-label', "Delimiter End Input");
  delimEndInput.setAttribute('aria-labelledby', "scDelimEntryLabel");
  delimEndInput.id = "scDelimEndInput";

  delimInputSpan.appendChild(delimEndInput);
  delimInputSpan.appendChild(delimEntryButton);

  // Insert the cloned section above the original Triggers section
  //triggersSection.parentNode.insertBefore(delimEntrySection, triggersSection);
  insertAfter(entrySection, delimEntrySection);

  delimEntryButton.onclick = () => {
    const startDelim = delimStartInput.value || delimStartInput.placeholder;
    const endDelim = delimEndInput.value || delimEndInput.placeholder;

    const selectionStart = entryField.selectionStart;
    const selectionEnd = entryField.selectionEnd;
    const currentValue = entryField.value;

    // Check if text is selected
    if (selectionStart !== selectionEnd) {
      // Bracket the selected text
      const newValue =
        currentValue.slice(0, selectionStart) +
        startDelim +
        currentValue.slice(selectionStart, selectionEnd) +
        endDelim +
        currentValue.slice(selectionEnd);

      entryField.focus();
      textFieldInsert(entryField, newValue);
      entryField.focus();
      entryField.setSelectionRange(selectionStart + startDelim.length, selectionEnd + startDelim.length);

    } else {
      // If no text is selected, insert delimiters at cursor position
      const newValue =
        currentValue.slice(0, selectionStart) +
        startDelim + endDelim +
        currentValue.slice(selectionStart);

      entryField.focus();
      textFieldInsert(entryField, newValue);
      entryField.focus();
      entryField.setSelectionRange(selectionStart + startDelim.length, selectionStart + startDelim.length);
    }
  };

  triggersSection.parentNode.insertBefore(delimEntrySection, triggersSection);

  // Find the notes textarea element and set it to a default from monkey config.
  const notesTextArea = modalNode.querySelector("textarea[aria-label='Notes']");
  if (notesTextArea) {
    const defaultSCNotes = cfg.get('Default_SC_Notes');
    if (defaultSCNotes !== "") {
      if (notesTextArea && notesTextArea.value.trim() === "") {
        textFieldInsert(notesTextArea, defaultSCNotes);
      }
    }
  }

  GM_addStyle(`

  #scDelimInputSpan { /* Use the ID of the span */
    display: flex !important;  /* Use !important to override existing styles */
    justify-content: space-between !important;
    align-items: center !important;  /* Add this to vertically center items */
    gap: 5px !important;
  }
  #scDelimInputSpan > #scDelimStartInput {
    width: 65% !important;  /* Adjust as needed to control the width of the inputs */
  }
  #scDelimInputSpan > #scDelimEndInput { /* Target the end delimiter specifically */
    width: 25% !important;  /* Set a narrower width for the end delimiter */
  }
  #scDelimInputSpan > button {
    width: auto !important; /* Make the button expand to fit its content */
    white-space: nowrap !important;
  }
`);
}

function textFieldInsert(field, text) {
  setNativeValue(field, text); // Update the value using setNativeValue

  // Trigger an input event to notify React
  const inputEvent = new InputEvent('input', { bubbles: true });
  field.dispatchEvent(inputEvent);
}

function unsetOverflowRecursively(node) {
  // Unset all overflow properties on the current node
  node.style.overflow = '';
  node.style.overflowX = '';
  node.style.overflowY = '';

  // Recursively process child nodes
  for (const child of node.children) {
    unsetOverflowRecursively(child);
  }
}


const classListRemove = [
  '_h-512px',
  '_mih-0px', '_miw-0px', '_fs-0',
  /* Padding we want removed */
  '_pt-1481558400', '_pr-1481558400', '_pb-1481558400', '_pl-1481558400',
  'r-150rngu', // -webkit-overflow-scrolling: touch; // (has error.) 
  'r-1rnoaur', // overflow-y: auto; // (we don't want auto scrolling on nested divs. have unset)
  'r-11yh6sk', // overflow-x: auto; // (we don't want auto scrolling on nested divs. have unset)
  'r-eqz5dr', // flex-direction: column;
  'r-16y2uox', // flex-grow: 1;
  'r-1wbh5a2', // flex-shrink: 1;
  'r-agouwx' // transform: translateZ(0);
];
const classListRemove2 = [
  '_mih-0px', '_miw-0px', '_fs-0',
  /* Padding we want removed */
  '_pt-1481558400', '_pr-1481558400', '_pb-1481558400', '_pl-1481558400',
  'r-150rngu', // -webkit-overflow-scrolling: touch; // (has error.) 
  'r-1rnoaur', // overflow-y: auto; // (we don't want auto scrolling on nested divs. have unset)
  'r-11yh6sk', // overflow-x: auto; // (we don't want auto scrolling on nested divs. have unset)
  'r-eqz5dr', // flex-direction: column;
  'r-16y2uox', // flex-grow: 1;
  'r-1wbh5a2', // flex-shrink: 1;
  'r-agouwx' // transform: translateZ(0);
];

function classListRemoveRecursively(node, classList) {
  // Unset all overflow properties on the current node
  //classList.forEach(className => node.classList.remove(className));

  // Recursively process child nodes
  for (const child of node.children) {
    classListRemoveRecursively(child);
  }
}

function fixStyles(modalNode) {
  const modalContent = modalNode.children[1];
  classListRemove.forEach(className => modalContent.classList.remove(className));
}

function makeModalDraggableAndResizable(timestamp, modalNodeTree, modalNode) {
  console.log("makeModalDraggableAndResizable");
  const modalDimensions = cfg.get('Modal_Dimensions');
  const [modalWidth, modalHeight] = modalDimensions;

  // Setup the initial modal dimensions from configure monkey.
  modalNode.style.width = `${modalWidth}px`;
  modalNode.style.maxWidth = `100%`;
  modalNode.style.height = `${modalHeight}px`;
  modalNode.style.maxHeight = `100%`;
  modalNode.style.minHeight = '0px';

  modalNode.style.resize = 'both';
  modalNode.style.overflowY = 'hidden';
  modalNode.style.overflowX = 'hidden';

  // Get some references to important things and assign id's
  const modalHeader = modalNode?.children[0];
  const modalHeaderId = "modalHeader_" + timestamp;
  modalHeader.id = modalHeaderId;

  const modalContent = modalNode?.children[1];
  modalContent.id = "modalContent_" + timestamp;

  let modalInnerContent = modalContent?.children[0];
  let modalInnerContentId = null;

  modalInnerContentId = "modalInnerContent_" + timestamp;

  // Called by both fixModalContent, and by a mutation observer that watches the modal content
  // incase of changes.
  function fixModalInnerContent(timestamp, modalNode, modalInnerContent) {
    if (!modalInnerContent) return;
    console.log("fixModalInnerContent");
    classListRemove.forEach(className => modalInnerContent.classList.remove(className));
    const classListRemovez = [
      '_mih-0px', '_miw-0px', '_fs-0',
      '_pt-1481558400', '_pr-1481558400', '_pb-1481558400', '_pl-1481558400',
      'r-150rngu', 'r-1rnoaur', 'r-11yh6sk',
      '_mr-1481558369',
      'r-agouwx'
    ];
    //'_h-512px',
    // '_mih-0px', '_miw-0px', '_fs-0',
    // // Padding we want removed
    // '_pt-1481558400', '_pr-1481558400', '_pb-1481558400', '_pl-1481558400',
    // 'r-150rngu', // -webkit-overflow-scrolling: touch; // (has error.) 
    // 'r-1rnoaur', // overflow-y: auto; // (we don't want auto scrolling on nested divs. have unset)
    // 'r-11yh6sk', // overflow-x: auto; // (we don't want auto scrolling on nested divs. have unset)
    // 'r-eqz5dr', // flex-direction: column;
    // 'r-16y2uox', // flex-grow: 1;
    // 'r-1wbh5a2', // flex-shrink: 1;
    // 'r-agouwx' // transform: translateZ(0);

    classListRemoveRecursively(modalInnerContent.firstChild, classListRemovez);

    modalInnerContent.id = modalInnerContentId;

    //modalInnerContent.id = "modalInnerContentId_" + timestamp;
    unsetOverflowRecursively(modalInnerContent);
    modalInnerContent.style.padding = '0px';
    modalInnerContent.style.overflowX = 'unset';
    modalInnerContent.style.overflowY = 'unset';
    modalInnerContent.style.maxHeight = '100%';
    modalInnerContent.style.maxWidth = '100%';

    // When different tabs are selected, assign id's for CSS.
    if (modalHeader.querySelectorAll(
      'div[role="tablist"][aria-label="Section Tabs"] div[role="tab"][aria-label^="Selected tab story cards" i]'
    ).length >= 1) {
      modalInnerContent.firstChild.id = 'modalInnerContent_storyCardsTab';
      modalInnerContent.firstChild.firstChild.classList.remove('r-150rngu', 'r-1rnoaur', 'r-11yh6sk');
    }
    else if (modalHeader.querySelectorAll(
      'div[role="tablist"][aria-label="Section Tabs"] div[role="tab"][aria-label^="Selected tab plot" i]'
    ).length >= 1) {
      modalInnerContent.firstChild.id = 'modalInnerContent_plotTab';
    }
    else if (modalHeader.querySelectorAll(
      'div[role="tablist"][aria-label="Section Tabs"] div[role="tab"][aria-label^="Selected tab details" i]'
    ).length >= 1) {
      modalInnerContent.firstChild.id = 'modalInnerContent_detailsTab';
    }

  }


  if (modalInnerContent) {
    modalInnerContent.id = modalInnerContentId;
    modalInnerContent.style.overflowX = 'unset';
    modalInnerContent.style.overflowY = 'unset';
    modalInnerContent.style.maxHeight = '100%';
    modalInnerContent.style.maxWidth = '100%';
  } else {
    console.log("modalInnerContent Failed.");
  }

  setTimeout(() => {
    fixStyles(modalNode);
    fixModalInnerContent(timestamp, modalNode, modalInnerContent);

    // Center the modal initially (after a slight delay for rendering)
    // To allow dragging and resizing the modal, we have to disable centering.
    // But we don't want the modal to jump to the top left of the viewport.
    // So we center it manually.
    if (modalNodeTree) {
      const modalRect = modalNode.getBoundingClientRect();

      const viewportWidth = window.innerWidth;
      const viewportHeight = window.innerHeight;

      const left = (viewportWidth - modalRect.width) / 2.0;
      const top = (viewportHeight - modalRect.height) / 2.0;

      // The path to the div that is centering the modal preventing it from being moved.
      const centeringParent = modalNodeTree.querySelector('div > span > span > div');

      // Remove potentially conflicting classes before centering
      centeringParent.classList.remove('_ai-center', '_jc-center', '_pos-fixed');

      // Apply initial left and top positions
      modalNode.style.left = `${Math.max(0, left)}px`; // Ensure left is not negative
      modalNode.style.top = `${Math.max(0, top)}px`;   // Ensure top is not negative

      // Override centering styles on the parent (AFTER centering the modal)
      centeringParent.style.justifyContent = 'unset';
      centeringParent.style.alignItems = 'unset';
    }


    if (modalInnerContent) {
      modalInnerContentId = "modalInnerContent_" + timestamp;
      modalInnerContent.id = modalInnerContentId;

      if (1) { // Turned off to experiment with using the original scroller.
        const originalScroller = modalNode.closest('[data-remove-scroll-container="true"]');
        if (originalScroller) {
          originalScroller.style.overflowY = 'hidden'; // Disable scrolling on the original element
          originalScroller.removeAttribute('data-remove-scroll-container'); // Remove the attribute
        } else {
          console.warn("Original scrolling element not found in modal.");
        }
      }

    } else {
      console.warn("Content div not found in modal after delay.");
    }

  }, 100);

  // Use a MutationObserver to monitor changes in the modal's content
  const observer = new MutationObserver((mutationsList, observer) => {
    for (let mutation of mutationsList) {
      if (mutation.type === 'childList') {
        //let modalNode.offsetWidth; // Trigger a reflow
        let newModalInnerContent = modalNode?.children[1]?.children[0]; // Look for updated content div
        if (newModalInnerContent) {
          modalInnerContent = newModalInnerContent;
          fixModalInnerContent(timestamp, modalNode, newModalInnerContent); // Apply styles to the new inner content element
        }
      }
    }
  });
  observer.observe(modalNode, { childList: true, subtree: true }); // Observe all child nodes

  let startX = null;
  let startY = null;

  // New event listeners for touch events (passive) for dragging
  modalHeader.addEventListener('mousedown', handleDragStart);
  modalHeader.addEventListener('touchstart', handleDragStart, { passive: true });
  // Prevent default behavior for touch events to avoid scrolling and interfering with dragging
  modalNode.addEventListener('touchmove', (e) => e.preventDefault(), { passive: false });

  function handleDragStart(e) {
    isDragging = true;
    startX = e.clientX - modalNode.offsetLeft;
    startY = e.clientY - modalNode.offsetTop;
    document.addEventListener('mousemove', handleDragMove);
    document.addEventListener('mouseup', handleDragEnd);
    console.log("handleDragStart");

    // Prevent default behavior for touch events to avoid scrolling
    if (e.type === 'touchstart') {
      e.preventDefault();
    }

    e.stopPropagation(); // Stop event propagation to prevent conflicts
  }

  function handleDragMove(e) {
    if (!isDragging) return;
    const x = e.clientX || e.touches[0].clientX; // Get x position for mouse or touch
    const y = e.clientY || e.touches[0].clientY; // Get y position for mouse or touch
    modalNode.style.left = `${x - startX}px`;
    modalNode.style.top = `${y - startY}px`;
  }

  function handleDragEnd(e) {
    isDragging = false;
    document.removeEventListener('mousemove', handleDragMove);
    document.removeEventListener('mouseup', handleDragEnd);
    console.log("handleDragEnd");
  }
  /*
    // This is an alternate approach that handles touch events. 
    // But it is a bit jumpy and needs work.
  
    modalHeader.addEventListener('mousedown', handleDragStart);
    modalHeader.addEventListener('touchstart', handleDragStart);  // Remove passive here
  
    let activeTouches = 0; // Counter for active touch points
  
    function handleDragStart(e) {
      activeTouches++;
      if (activeTouches > 1) return; // Allow multi-touch for zoom/pinch gestures
  
      if (e.type === 'touchstart') {
        startX = e.touches[0].clientX - modalHeader.getBoundingClientRect().left;
        startY = e.touches[0].clientY - modalHeader.getBoundingClientRect().top;
      } else {
        startX = e.offsetX;
        startY = e.offsetY;
      }
  
      // Add touchmove and touchend listeners ONLY when touchstart occurs
      if (e.type === 'touchstart') {
        document.addEventListener('touchmove', handleDragMove);
        document.addEventListener('touchend', handleDragEnd);
      } else { // For mousedown
        document.addEventListener('mousemove', handleDragMove);
        document.addEventListener('mouseup', handleDragEnd);
      }
  
      console.log("handleDragStart e.type: ", e.type);
    }
  
    function handleDragMove(e) {
      if (activeTouches > 1) return; // Allow multi-touch for zoom/pinch gestures
  
      const x = e.clientX || e.touches[0].clientX; // Get x position for mouse or touch
      const y = e.clientY || e.touches[0].clientY; // Get y position for mouse or touch
      modalNode.style.left = `${x - startX}px`;
      modalNode.style.top = `${y - startY}px`;
  
      e.stopPropagation(); // Stop event propagation ONLY during dragging
    }
  
    function handleDragEnd(e) {
      activeTouches--;
      if (activeTouches > 0) return; // Wait for all touch points to be released
  
      // Remove touchmove and touchend listeners when touch ends
      if (e.type === 'touchend') {
        document.removeEventListener('touchmove', handleDragMove);
        document.removeEventListener('touchend', handleDragEnd);
      } else { // For mouseup
        document.removeEventListener('mousemove', handleDragMove);
        document.removeEventListener('mouseup', handleDragEnd);
      }
      console.log("handleDragEnd e.type: ", e.type);
      }
  */
}

/*
// Global variables
let isGearMenuResizing = false;
let gearMenuStartX, gearMenuStartWidth;

// Get reference to app-root div
const appRootDiv = document.querySelector('.app-root');
const gearMenuSelector = 'body > div.app-root > div#__next > div > span > div:nth-child(1) > div:nth-child(1)';

// MutationObserver for detecting the gear menu within the app-root div
const appRootObserver = new MutationObserver((mutationsList, observer) => {
  console.log("appRootObserver Got Here:");
  for (let mutation of mutationsList) {
    if (mutation.type === 'childList') {
      console.log("appRootObserver Got Here:2");
      const gearMenu = document.querySelector(gearMenuSelector); // Target the gear menu
      if (gearMenu) {
        console.log("appRootObserver Got Here:3");
        //makeGearMenuDraggableAndResizable(gearMenu);
        observer.disconnect(); // Stop observing after the gear menu is found
      }
    }
  }
});

// MutationObserver for detecting changes in the Adventure tabs
const adventureTabObserver = new MutationObserver((mutationsList, observer) => {
  console.log("adventureTabObserver Got Here:");
  for (let mutation of mutationsList) {
    if (mutation.type === 'childList') {
      const plotTab = document.querySelector('div[role="tablist"][aria-label="Section Tabs" i] [role="tab"][aria-label*="Plot" i]');
      const detailsTab = document.querySelector('div[role="tablist"][aria-label="Section Tabs" i] [role="tab"][aria-label*="Details" i]');
      if (plotTab && plotTab.getAttribute('aria-selected') === 'true') {
        console.log("adventureTabObserver Got Here: 1");
        //resizeAllTextareasInNode(plotTab);
      } else if (detailsTab && detailsTab.getAttribute('aria-selected') === 'true') {
        console.log("adventureTabObserver Got Here: 2");
        //resizeAllTextareasInNode(detailsTab);
      }
    }
  }
});

function makeGearMenuDraggableAndResizable(gearMenu) {
  const resizeHandle = document.createElement('div');
  resizeHandle.classList.add('resize-handle');
  resizeHandle.style.left = 0; // Position on the left edge
  gearMenu.appendChild(resizeHandle);

  function toggleFullScreen(buttonTextElement) {
    const modalRect = modalNode.getBoundingClientRect();
    if (modalNode.requestFullscreen) {
      if (document.fullscreenElement) {
        document.exitFullscreen();
        buttonTextElement.innerText = "[ ]";

        // Restore previous size and position (ensure consistent pixel values)
        modalNode.style.width = modalNode.dataset.originalWidth + 'px';
        modalNode.style.height = modalNode.dataset.originalHeight + 'px';
        modalNode.style.left = modalNode.dataset.originalLeft + 'px';
        modalNode.style.top = modalNode.dataset.originalTop + 'px';

        // Force reflow to ensure styles are applied correctly
        void modalNode.offsetWidth; // Trigger a reflow
      } else {
        // Store current size and position before going fullscreen (ensure pixel values)
        modalNode.dataset.originalWidth = modalRect.width;
        modalNode.dataset.originalHeight = modalRect.height;
        modalNode.dataset.originalLeft = modalRect.left;
        modalNode.dataset.originalTop = modalRect.top;

        modalNode.requestFullscreen();
        buttonTextElement.innerText = "[X]";
      }
    }
  }

  function createFullScreenButton(cloneRef, container) {
    if (!cloneRef) return;
    const fullScreenButton = buttonClone(
      cloneRef,
      "[ ]", // Button label (you can customize this)
      toggleFullScreen  // Function to handle the toggle
    );
    container.insertBefore(fullScreenButton, container.firstChild);
    container.style.display = 'flex';
    container.style.flexDirection = 'row';
    container.style.alignItems = 'center';
  }

  // Add fullscreen button (reusing existing createFullScreenButton function)
  const header = gearMenu.querySelector('[role="tablist"]'); // Find the header within the gear menu
  //const header = gearMenu.querySelector('[role="button"][aria-label="Close settings" i]'); // Find the header within the gear menu
  const closeButton = gearMenu.querySelector('[role="button"][aria-label="Close settings" i]').parentElement; // Find the header within the gear menu
  if (header) {
    const fullScreenButton = createFullScreenButton(closeButton, closeButton.parentElement.ParentElement);
    header.insertBefore(fullScreenButton, header.firstChild);
  }

  resizeHandle.addEventListener('mousedown', handleResizeStart);
  resizeHandle.addEventListener('touchstart', handleResizeStart, { passive: true });

  function handleResizeStart(e) {
    isGearMenuResizing = true;
    gearMenuStartX = e.clientX;
    gearMenuStartWidth = parseInt(document.defaultView.getComputedStyle(gearMenu).width, 10);
    document.addEventListener('mousemove', handleResizeMove);
    document.addEventListener('mouseup', handleResizeEnd);

    // Prevent default behavior for touch events to avoid scrolling
    if (e.type === 'touchstart') {
      e.preventDefault();
    }
  }

  // Modified resize logic (horizontal resizing only)
  function handleResizeMove(e) {
    if (!isGearMenuResizing) return;
    const x = e.clientX || e.touches[0].clientX;
    const newWidth = gearMenuStartWidth + (x - gearMenuStartX);
    gearMenu.style.width = `${Math.max(200, newWidth)}px`; // Minimum width of 200px
  }


  function handleResizeEnd(e) {
    isGearMenuResizing = false;
    document.removeEventListener('mousemove', handleResizeMove);
    document.removeEventListener('mouseup', handleResizeEnd);
  }
}
*/
/*
// Start observing the app-root div
if (appRootDiv) {
  appRootObserver.observe(appRootDiv, { childList: true, subtree: true });
} else {
  console.warn("app-root div not found.");
}
*/

// ... (rest of your existing code) ...

function modalAddFullScreenButton(cloneRef, container, eventHandler) {
  if (!cloneRef) {
    console.warn("Null cloneRef in modalAddFullScreenButton!");
    return;
  }
  const fullScreenButton = buttonClone(
    cloneRef,
    "[ ]", // Button label (you can customize this)
    eventHandler  // Function to handle the toggle
  );
  container.insertBefore(fullScreenButton, container.lastChild);

  container.style.display = 'flex';
  container.style.flexDirection = 'row';
  container.style.alignItems = 'center';
  container.style.justifyContent = 'unset'; // Remove default justification

  //fullScreenButton.style.marginRight = '8px';
  fullScreenButton.style.minWidth = '30px';
  fullScreenButton.style.whiteSpace = 'nowrap';

  container.style.display = 'flex';
  container.style.alignItems = 'right';
  container.style.flexGrow = '1';
  container.style.justifyContent = 'flex-end';
}

function toggleFullScreen(buttonTextElement) {
  const modalNode = buttonTextElement.closest("div[aria-label='Modal' i]");
  if (!modalNode) {
    console.error("Error: Modal node not found. Fullscreen toggle failed.");
    return;
  }
  const modalRect = modalNode.getBoundingClientRect();
  if (modalNode.requestFullscreen) {
    if (document.fullscreenElement) {
      document.exitFullscreen();
      buttonTextElement.innerText = "[ ]";

      // Restore previous size and position (ensure consistent pixel values)
      modalNode.style.width = modalNode.dataset.originalWidth + 'px';
      modalNode.style.height = modalNode.dataset.originalHeight + 'px';
      modalNode.style.left = modalNode.dataset.originalLeft + 'px';
      modalNode.style.top = modalNode.dataset.originalTop + 'px';

      // Force reflow to ensure styles are applied correctly
      void modalNode.offsetWidth; // Trigger a reflow
    } else {
      // Store current size and position before going fullscreen (ensure pixel values)
      modalNode.dataset.originalWidth = modalRect.width;
      modalNode.dataset.originalHeight = modalRect.height;
      modalNode.dataset.originalLeft = modalRect.left;
      modalNode.dataset.originalTop = modalRect.top;

      modalNode.requestFullscreen();
      buttonTextElement.innerText = "[X]";
    }
  }
}

// Function to handle new modals
//
function handleNewModal(modalNodeTree) {

  const timestamp = Date.now();

  // Wait for the specific modal structure
  waitForSubtreeElements(
    "div[aria-label='Modal' i]",
    (modalNodes) => {
      if (modalNodes.length !== 1) {
        console.warn("Modal nodes, there can be only 1. Found: ", modalNodes.length);
        return;
      }

      const modalNode = modalNodes[0];
      if (!modalNode) {
        console.warn("Null Modal node found in handleNewModle.");
        return;
      }

      modalNodeTree.id = "modalNodeTree";
      modalNode.style.padding = 0;
      modalNode.style.margin = 0;
      modalNode.style.borderBottomRightRadius = 0;

      waitForSubtreeElements(
        "div[aria-label='Modal' i] > div:nth-child(2) button", // The selector for the element you want to wait for within the modal
        //"div[aria-label='Modal' i] > div > div", // The selector for the element you want to wait for within the modal
        //"div[aria-label='Modal' i]:has(> div:nth-child(2))", // Wait for the 2nd child to appear.
        (modalSubNodes) => {
          setTimeout(() => { // Need to wait some time for react to render the contents and post it.
            const modalHeader = modalNode?.children[0];

            /* Assign IDs for CSS. */
            const modalContent = modalNode?.children[1];
            modalContent.id = "modalContent_" + timestamp;

            const modalHeaderTitleContainer = modalHeader?.firstChild;
            modalHeaderTitleContainer.id = "modalHeaderTitleContainer_" + timestamp;

            const modalInnerContent = modalContent?.children[0];
            modalInnerContent.id = "modalInnerContent" + timestamp;

            // Add resizing and dragging for edit Adventure and Scenario modals.
            const tablistSelector =
              'div[role="tablist"][aria-label="Section Tabs"] [role="tab"][aria-label*="plot" i], ' +
              'div[role="tablist"][aria-label="Section Tabs"] [role="tab"][aria-label*="Story Cards" i],' +
              'div[role="tablist"][aria-label="Section Tabs"] [role="tab"][aria-label*="details" i],' +
              'div[role="button"][aria-label="Close modal" i] > div > p';

            if (modalNode.querySelectorAll(tablistSelector).length >= 4) {

              //centeringParent.classList.remove('_ai-center', '_jc-center', '_pos-fixed');

              if (modalHeaderTitleContainer) {
                //modalHeaderTitleContainer.style.justifyContent = 'space-between';

              }
              waitForSubtreeElements(
                tablistSelector,
                (matchingElements) => {
                  if (matchingElements.length >= 4) { // Check if all 3 tabs are found
                    modalNodeTree.id += ".ScenarioAdventureEditor";

                    makeModalDraggableAndResizable(timestamp, modalNodeTree, modalNode);

                    // Find the nested button
                    setTimeout(() => {
                      let closeButton = null;
                      let container = null;
                      //modalInnerContent.style.justifyContent = 'space-between';
                      //modalHeaderTitleContainer.style.justifyContent = 'space-between';

                      // Check for double button first.
                      let closeButtons = modalNode.querySelectorAll(
                        "button[role='button'][type='button' i] div[role='button'][aria-label='Close modal' i]");
                      if (closeButtons.length > 0) {
                        closeButton = closeButtons[0]; // Keep the pointer to the inner div button for cloning.
                        // Find the enclosing button element.
                        container = closeButton.closest("button[role='button'][type='button' i]")?.parentNode;
                      }
                      // It's not a double button. 
                      else {
                        closeButtons = modalNode.querySelectorAll("div[role='button'][aria-label='Close modal' i]");
                        if (closeButtons.length > 0) {
                          closeButton = closeButtons[0];
                          container = closeButton.parentNode;
                        }
                      }

                      if (!closeButton || !container) {
                        console.error("Error: Close button not found in modal. Fullscreen button not added.");
                        console.log(closeButton);
                        console.log(container);
                      } else {
                        modalAddFullScreenButton(closeButton, container, toggleFullScreen);
                        container.style.justifyContent = 'space-between';
                        modalInnerContent.style.minHeight = '0px';
                      }
                    }, 100); // adjust as needed
                  }
                },
                modalNode,
                true
              );
            }
            // Story card updates.
            else if (modalNode.querySelectorAll("textarea[aria-labelledby='scEntryLabel']").length >= 1) {
              //console.log("Found story card modal");
              //console.log("scEntryLable", modalNode);

              modalNodeTree.id += ".StoryCardEditor";
              modalHeader.padding = '8px';
              setTimeout(() => {
                modalContent.style.maxHeight = 'calc(100% - ' + modalHeader.offsetHeight + 'px)';
                makeModalDraggableAndResizable(timestamp, modalNodeTree, modalNode);
                modifyStoryCardEditor(modalNode);
                const closeButton = modalNode.querySelector("div[role='button'][aria-label='Close modal' i]");
                modalAddFullScreenButton(closeButton, modalHeader, toggleFullScreen);
              }, 100); // Adjust delay as needed
              // Add additional story card updates here...
              //
            }

            // #content-\:r6ji\: > div > div._dsp-flex._fb-auto._bxs-border-box._pos-relative._mih-0px._miw-0px._fs-0._pr-1481558369._pl-1481558307._pt-1481558338._pb-1481558338._gap-1481558338._w-10037._fd-row._ai-center._jc-441309761._bbw-0px._btc-43811612._brc-43811612._bbc-43811612._blc-43811612._maw-480px._btw-0px._brw-0px._blw-0px._bbs-solid._bts-solid._bls-solid._brs-solid > div > div.is_Row._dsp-flex._fd-row._fb-auto._bxs-border-box._pos-relative._mih-0px._miw-0px._fs-0._ai-center._jc-441309761 > div > h1

            else if ($(modalHeader).find("h1:contains('Adventure')").length > 0) {
              setTimeout(() => {
                modalNodeTree.id += ".ContentView.Adventure";
                makeModalDraggableAndResizable(timestamp, modalNodeTree, modalNode);
              }, 100); // adjust as needed
            }
            // Fix the irritating small window size for the script editor.
            //
            else if ($(modalNode).find("p:contains('Shared Library')").length > 0) {
              setTimeout(() => {
                waitForKeyElements(".monaco-editor .view-lines", (editorElements) => {
                  modalNodeTree.id += ".ScriptEditor";
                  const editorContainer = modalNode.children[1];
                  if (editorContainer) {
                    editorContainer.style.height = "90%";
                  } else {
                    console.warn("Editor container div not found in script editor modal");
                  }
                  //modalNode.dataset.hasEditorMods = "true";
                }, true);
              }, 100); // adjust as needed
            } else {
              //console.log("Found other modal", modalNode);
              // ... you can add handlers for other types of modals here ...
            }
          }, 500); // adjust as needed
          //}, 1); // adjust as needed

        },
        modalNode, // Use the modal node as the targetNode
        true  // Run immediately.
      );
    },
    modalNodeTree, // Use the modal node as the targetNode
    true  // Run immediately.
  );
}

// Mutation observer to detect new div elements in document.body
const bodyObserver = new MutationObserver((mutationsList, observer) => {
  for (const mutation of mutationsList) {
    if (mutation.type === 'childList') {
      for (const node of mutation.addedNodes) {
        if (node.nodeName === 'DIV') {
          // Check for modals and new buttons
          if (node.querySelector("div[aria-label='Modal' i]")) {
            console.log("New modal detected in document.body");
            handleNewModal(node);
          }

        }
      }
    }
  }
});

// Start observing the document body for new children added.
bodyObserver.observe(document.body, { childList: true });


/**********************************
** Code for Play Pages.
*/
//let modalMutationObserver = new DOMObserver(modalMutationObserverCB, document.body, { childList: true, subtree: true });
//modalMutationObserver.observe();

// Fist the state.message display locking out the Navigation bar.
//
function fixNavigationBar() {
  const navBar = document.querySelector('div[aria-label="Navigation bar"]');
  const dialog = document.querySelector('.css-175oi2r[style*="z-index: 3"]');

  //const circleInfoDiv = dialog?.querySelector('p.font_icons[aria-hidden="true"]:has(+ p:contains("w_circle_info"))')?.closest('div[role="button"]'); // Find the closest parent div with role="button"

  if (navBar && dialog) {
    navBar.style.zIndex = 100011; // Higher than the overlay pane
    dialog.style.zIndex = 2; // Lower z-index for the dialog
    dialog.id = 'TheDialog';

    const alertContainer = navBar.querySelectorAll('div[role="alert"]');

    const nestedOverlay = dialog.querySelector('div[style*="z-index: 100000"]'); // Select nested overlay
    if (nestedOverlay) {
      nestedOverlay.style.zIndex = 1; // Set a lower z-index for the nested overlay
      //nestedOverlay.id = 'TheNestedOverlay'
    }

    const navBarButtons = navBar.querySelectorAll('div[id="game-blur-button"]');
    navBarButtons.forEach(button => {
      button.style.zIndex = 100012; // Even higher for the buttons

      const buttonZi1Elements = button.querySelectorAll('._zi-1');
      buttonZi1Elements.forEach(element => {
        element.style.zIndex = 100013; // Highest z-index for elements inside buttons
      });
    });

    const navBarZi1Elements = navBar.querySelectorAll('._zi-1');
    navBarZi1Elements.forEach(element => {
      element.style.zIndex = 100011; // Match the navBar's z-index
    });

    // Find the 'alert' div.
    const alertDialog = dialog.querySelector("div[role='alert']");

    // Add click listener to dismiss the dialog when the circle info div is clicked
    const $circleInfoDiv = $(dialog).find(
      "div[role='alert'] > div > div > div:first-child > div:first-child"
    );

    if ($circleInfoDiv.length > 0) {
      $circleInfoDiv.on("click", () => {
        dialog.remove();
      });
    }
    // Add click listener to dismiss the dialog when the circle info div is clicked
    const circleInfoDiv = dialog.querySelector("div[role='alert'] > div > div:first-child");
    if (circleInfoDiv) {
      console.log("found circleInfoDiv:", circleInfoDiv);
      circleInfoDiv.addEventListener("click", () => {
        dialog.remove();
      });
    }
  }
}

let fixNavigationBarObserver = new DOMObserver(
  fixNavigationBar, document.body,
  { childList: true, subtree: true }
);

function handlePlayPage(targetNode) {
  // handleChanges();
  handleChangesObserver = new DOMObserver(handleChanges, targetNode, { childList: true, subtree: true });
  handleChangesObserver.observe();

  const CSS = `
  div>span:last-child>#transition-opacity:last-child, #game-backdrop-saturate {
    border-bottom-color: ${cfg.get('Response_Underline') ? 'var(--color-61)' : 'unset'};
    border-bottom-width: ${cfg.get('Response_Underline') ? '2px' : 'unset'};
    border-bottom-style: ${cfg.get('Response_Underline') ? 'solid' : 'unset'};
    background-color: ${cfg.get('Response_Bg_Color') ? 'var(--color-60)' : 'unset'};
    backdrop-filter: unset;
  }
`;
  GM_addStyle(CSS);


  const referenceSpan = [...$('[role=button]')].find((e) => e.innerText === 'w_undo').parentElement; // Select the span
  const container = referenceSpan.parentElement;
  headerInject(container, referenceSpan, toggleButtonText, toggleOnClick);

  fixNavigationBarObserver.observe();
}

document.addEventListener('keydown', handleKeyPress);


waitForKeyElements("[role='article']", (targetNodes) => {

  const targetNode = targetNodes[0];

  const isReadPage = window.location.href.includes('/read');
  const isPlayPage = window.location.href.includes('/play');

  if (isReadPage || isPlayPage) {
    // This is setup code for both read and play pages.
    //targetNode = $("[role='article']")[0];

    // Load toggle state from sessionStorage (default to ON if not found)
    actionsExpanded = sessionStorage.getItem("actionsExpanded") === "true";
    toggleButtonText = actionsExpanded ? ActionToggleMsgOn : ActionToggleMsgOff;

    setActionVisibility(actionsExpanded); // Set the visibility from the session saved state.

    if (isReadPage) {
      handleReadPage(targetNode);
    } else if (isPlayPage) {
      handlePlayPage(targetNode);
    }
  }

});