nahruck / Compact Location Redesign

// ==UserScript==
// @name         Compact Location Redesign
// @namespace    https://tampermonkey.net/
// @version      2.0
// @description  Three fixed sectors (info+actions / field / chat) filling the viewport with no page scroll, icon-free vertical action list, smaller cat models
// @match        *https://wcwh.ru/game/lock*
// @grant        none
// @run-at       document-idle
// @license     MIT
// ==/UserScript==

/*
  HOW TO SET THIS UP
  -------------------
  Replace YOUR-GAME-DOMAIN in the @match line above with the game's real
  domain (keep /lock* if location page URLs start with "lock").

  WHAT THIS DOES
  --------------
  1. Actions cleanup (same as before): pulls self-actions, actions with
     other cats, and the two movement blocks out of their icon grid,
     strips the icons, and stacks them as a vertical list under the
     "Параметры" block.
  2. Layout: rebuilds the page as three fixed sectors that fill the
     browser viewport with no page-level scrolling —
       left   = menu links + location info + parameters + the action list
       center = the field/stage (background, cats present, catch/herb/etc)
                — the largest sector
       right  = the "say something" bar + chat log
     If content inside a sector is taller than the viewport, that sector
     scrolls internally; the page itself never does.

  This is written against the page structure as given. If the game
  changes its markup this may stop finding elements — check the browser
  console for "[compact-redesign]" warnings.
*/

(function () {
  'use strict';

  const LOG_PREFIX = '[compact-redesign]';

  function injectStyles() {
    if (document.getElementById('compact-redesign-style')) return;

    const css = `
            /* ---- no page scroll, full viewport ---- */
            html, body {
                background: #000 !important;
            }
            #compact-app {
                background: #000 !important;
            }
            
                        /* ---- kill inherited beige backgrounds inside the side panels ---- */
            #compact-left, #compact-right,
            #compact-left *, #compact-right * {
                background-color: transparent !important;
                background-image: none !important;
            }
            #compact-left {
                background-color: #1e1e1e !important;
            }
            #compact-right {
                background-color: #1e1e1e !important;
            }
            #compact-left select, #compact-left input, #compact-left button,
            #compact-right select, #compact-right input, #compact-right button {
                background-color: #2b2b2b !important;
                color: #EDEDED !important;
                border: 1px solid #555 !important;
            }
            .compact-list-button {
                background-color: #2b2b2b !important;
            }
            .compact-list-button:hover {
                background-color: #3a3a3a !important;
            }
            .compact-list-button:active {
                background-color: #4a4a4a !important;
            }
            
            #compact-left, #compact-right {
                background: #1e1e1e !important;
                color: #EDEDED !important;
            }
            #compact-left *, #compact-right * {
                color: #EDEDED !important;
            }
            #compact-left a, #compact-right a {
                color: #8fc4ff !important;
            }
            #compact-left a:visited, #compact-right a:visited {
                color: #b79fe0 !important;
            }
            #compact-left select, #compact-left input,
            #compact-right select, #compact-right input {
                background: #2b2b2b !important;
                color: #EDEDED !important;
                border: 1px solid #555 !important;
            }
            #compact-left hr, #compact-right hr {
                border-color: #444 !important;
            }
            #compact-actions-list .compact-action-section {
                border-bottom-color: #444 !important;
            }
            #compact-actions-list,
            #compact-actions-list .compact-action-heading {
                border-color: #444 !important;
            }
            .compact-list-button {
                background: #2b2b2b !important;
                color: #f0f0f0 !important;
                border-color: #555 !important;
            }
            .compact-list-button:hover {
                background: #3a3a3a !important;
            }
            .compact-list-button:active {
                background: #4a4a4a !important;
            }
            
            html, body {
                height: 100% !important;
                margin: 0 !important;
                overflow: hidden !important;
            }

            /* ---- general compactness ---- */
            #compact-app, #compact-app table, #compact-app td {
                font-size: 13px !important;
            }
            #compact-app table[border] {
                border-spacing: 2px !important;
            }
            #compact-app h4 {
                font-size: 13px !important;
                margin: 4px 0 !important;
            }
            #compact-app hr {
                margin: 4px 0 !important;
            }

            /* ---- three-sector grid ---- */
            #compact-app {
                display: grid;
                grid-template-columns: 260px 1fr 300px;
                grid-template-rows: 100vh;
                width: 100vw;
                height: 100vh;
                box-sizing: border-box;
                overflow: hidden;
            }
            #compact-left, #compact-center, #compact-right {
                display: block !important; /* these are repurposed <td>s, drop table-cell behavior */
                box-sizing: border-box;
                height: 100%;
                min-width: 0;
                min-height: 0;
            }
            #compact-left {
                overflow-y: auto;
                padding: 3px;
                border-right: 1px solid #999;
            }
            #compact-right {
                display: flex !important;
                flex-direction: column;
                padding: 3px;
                border-left: 1px solid #999;
            }
            #compact-center {
                display: flex !important;
                flex-direction: column;
                align-items: top;
                justify-content: space-evenly;
                overflow: auto;
                padding: 4px;
            }

            /* ---- vertical action list (left sector) ---- */
            #compact-actions-list {
                display: flex;
                flex-direction: column;
                margin-top: 3px;
                padding-top: 3px;
                border-top: 1px solid #999;
            }
            #compact-actions-list .compact-action-heading {
                font-weight: bold;
                font-size: 11px;
                letter-spacing: 0.02em;
                text-transform: uppercase;
                margin-bottom: 2px;
            }
            #compact-actions-list .compact-action-section {
                padding-bottom: 2px;
                border-bottom: 1px dashed #ccc;
            }
            #compact-actions-list .compact-action-section:last-child {
                border-bottom: none;
            }
            #compact-actions-list img[src*="acticon"] {
                display: none !important;
            }
            #compact-actions-list select,
            #compact-actions-list input {
                font-size: 11px !important;
                max-width: 100%;
            }

/* ---- dropdown -> button lists ---- */
            .compact-button-list {
                display: flex;
                flex-direction: column;
                gap: 3px;
                margin: 4px 0;
            }
            .compact-list-button {
                font-size: 11px;
                padding: 3px 6px;
                text-align: left;
                background: #f2f2f2;
                border: 1px solid #999;
                border-radius: 3px;
                cursor: pointer;
            }
            .compact-list-button:hover {
                background: #e2e2e2;
            }
            .compact-list-button:active {
                background: #cfcfcf;
            }
            
            /* ---- field sector fills its space ---- */
            #compact-center table#game_field {
                width: 100% !important;
                height: 100% !important;
                max-width: none !important;
                margin: 0 !important;
            }
            #cats-field {
                display: flex !important;
                justify-content: space-evenly !important;
                align-items: flex-end !important;
                flex-wrap: wrap !important;
                gap: 4px !important;
            }
            #cats-field .cat {
                height: 70px !important;
            }
            #cats-field .cat img.cat__element,
            #cats-field .cat img.cat__base {
                max-height: 70px !important;
                width: auto !important;
            }
            #cats-field .tabcats.flex {
                max-width: 140px !important;
            }

            /* ---- chat sector: log scrolls, input row stays put ---- */
            #compact-right #cats {
                flex: 1 1 auto !important;
                height: auto !important;
                overflow-y: auto !important;
            }
        `;
    const style = document.createElement('style');
    style.id = 'compact-redesign-style';
    style.textContent = css;
    document.head.appendChild(style);
  }

  function buildActionsList(actionsTable) {
    const rows = actionsTable.querySelectorAll(':scope > tbody > tr');
    if (rows.length < 4) {
      console.warn(LOG_PREFIX, 'Unexpected actions table shape, skipping move.');
      return null;
    }

    const selfTd = rows[1].children[0];
    const othersTd = rows[1].children[1];
    const campTd = rows[3].children[0];
    const offcampTd = rows[3].children[1];

    const list = document.createElement('div');
    list.id = 'compact-actions-list';

    function addSection(title, node) {
      if (!node) return;
      const section = document.createElement('div');
      section.className = 'compact-action-section';

      const heading = document.createElement('div');
      heading.className = 'compact-action-heading';
      heading.textContent = title;

      section.appendChild(heading);
      section.appendChild(node); // moves the live node, ids/handlers stay intact
      list.appendChild(section);
    }

    addSection('С собой', selfTd);
    addSection('С другими котами', othersTd);
    addSection('Лагерная территория', campTd);
    addSection('Внелагерная территория', offcampTd);

    list.querySelectorAll('img[src*="acticon"]').forEach((img) => img.remove());

    return list;
  }

  function mergeActionsIntoParams() {
    if (document.getElementById('compact-actions-list')) return true; // already done

    const paramsTd = document.getElementById('prmtr_happy')?.closest('td.col1');
    const actionsTable = document.querySelector('table.tabact');

    if (!paramsTd || !actionsTable) {
      console.warn(LOG_PREFIX, 'Could not find params/actions elements.');
      return false;
    }

    const list = buildActionsList(actionsTable);
    if (!list) return false;

    const paramsTable = paramsTd.querySelector('table.tabprmtr');
    if (paramsTable) {
      const anchor = paramsTable.closest('center') || paramsTable;
      anchor.insertAdjacentElement('afterend', list);
    }
    else {
      paramsTd.appendChild(list);
    }

    const actionsColumnTd = actionsTable.closest('td.col1');
    if (actionsColumnTd) {
      actionsColumnTd.style.display = 'none';
    }

    return true;
  }

  function buildLayout() {
    if (document.getElementById('compact-app')) return true; // already done

    const menuTd = document.getElementById('prmtr_happy')?.closest('td.col1');
    const chatTd = document.getElementById('outdata')?.closest('td.col1');
    const fieldTd = document.getElementById('game_field')?.closest('td.col1');

    if (!menuTd || !chatTd || !fieldTd) {
      console.warn(LOG_PREFIX, 'Could not find one of the three main sectors.');
      return false;
    }

    const outerTable = menuTd.closest('table');

    const app = document.createElement('div');
    app.id = 'compact-app';

    const left = document.createElement('div');
    left.id = 'compact-left';
    left.appendChild(menuTd);

    const center = document.createElement('div');
    center.id = 'compact-center';
    center.appendChild(fieldTd);

    const right = document.createElement('div');
    right.id = 'compact-right';
    right.appendChild(chatTd);

    app.appendChild(left);
    app.appendChild(center);
    app.appendChild(right);

    if (outerTable) {
      outerTable.insertAdjacentElement('beforebegin', app);
      outerTable.style.display = 'none';
    }
    else {
      document.body.appendChild(app);
    }

    return true;
  }

  function convertSayingSelect() {
    const selectEl = document.querySelector('select[name="saying"]');
    if (!selectEl || selectEl.dataset.compactConverted === 'true') return;

    const triggerButton = document.querySelector('input[name="activ"]');
    if (!triggerButton) {
      console.warn(LOG_PREFIX, 'Could not find the "Ок" button for the saying dropdown.');
      return;
    }

    const wrap = document.createElement('div');
    wrap.className = 'compact-saying-groups';

    Array.from(selectEl.children).forEach((child) => {
      if (child.tagName !== 'OPTGROUP') return; // skip the placeholder "Выбрать действие" option

      const groupHeading = document.createElement('div');
      groupHeading.className = 'compact-action-heading';
      groupHeading.textContent = child.label;
      wrap.appendChild(groupHeading);

      const grid = document.createElement('div');
      grid.className = 'compact-button-grid';
      Array.from(child.children).forEach((opt) => {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'compact-list-button';
        btn.textContent = opt.textContent;
        btn.addEventListener('click', () => {
          selectEl.value = opt.value;
          selectEl.dispatchEvent(new Event('change', {
            bubbles: true
          }));
          triggerButton.click();
        });
        grid.appendChild(btn);
      });
      wrap.appendChild(grid);
    });

    selectEl.style.display = 'none';
    selectEl.insertAdjacentElement('afterend', wrap);
    selectEl.dataset.compactConverted = 'true';
  }

  function convertDropdownsToButtons() {
    convertSelectToButtons(document.getElementById('self-actions'));
    convertSelectToButtons(document.querySelector('select[name="going"]'));
    convertSelectToButtons(document.querySelector('select[name="going2"]'));
    convertSayingSelect();
  }

  function applyRedesign() {
    const actionsOk = mergeActionsIntoParams();
    if (!actionsOk) return;
    buildLayout();
  }

  injectStyles();

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', applyRedesign);
  }
  else {
    applyRedesign();
  }

  // The page can re-render parts of itself (e.g. after moving locations
  // via AJAX), which would undo the restructuring. Keep checking and
  // re-apply if that happens.
  setInterval(applyRedesign, 1000);
})();