NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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);
})();