Restyles Idle Pixel into a mobile-first UI: bottom tab navigation, skill/more drawers, glanceable chat peek strip, compact headers. Auto-activates on touch devices.
// ==UserScript==
// @name IdlePixel Mobile UI
// @namespace com.eisa.idlepixel
// @version 0.8.4
// @description Restyles Idle Pixel into a mobile-first UI: bottom tab navigation, skill/more drawers, glanceable chat peek strip, compact headers. Auto-activates on touch devices.
// @author Eisa (StoneSinew)
// @license MIT
// @match *://idle-pixel.com/login/play*
// @grant none
// @icon https://cdn.idle-pixel.com/images/skills.png
// @require https://greasyfork.org/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
// ==/UserScript==
(function () {
'use strict';
const ACTIVE_CLASS = "mobile-ui";
const NARROW_PX = 820;
const COMBAT_STYLES = { "panel-melee": 1, "panel-archery": 1, "panel-magic": 1 };
const SHEET_GROUPS = {
skills: ["mining", "crafting", "gathering", "farming", "brewing", "woodcutting", "cooking", "fishing", "breeding", "invention", "melee", "archery", "magic"],
shops: ["shop", "donor-shop", "criptoe-market"],
other: ["teams", "keyitems", "quests", "achievements", "collection-log", "history", "friends", "database"]
};
const TAB_PANEL_MAP = { "panel-main": 0, "panel-combat": 2, "panel-combat-canvas": 2, "panel-player-market": 3 };
const BREW_FAVS_KEY = "mob_brewing_favs";
const MARKET_CATEGORIES = ["gems", "ores", "bars", "bones", "seeds", "logs", "raw_fish", "cooked_fish", "brewing", "weapons", "armour", "other_equipment", "breeding", "donor", "sigils"];
const MARKET_CATEGORY_COLORS = {
gems: "#b3ffff", ores: "#dddddd", bars: "#c2c2a3", bones: "#ffffff", seeds: "#ccffcc",
logs: "#99ffcc", brewing: "#ffccff", weapons: "#ffcccc", armour: "#ffcccc", other_equipment: "#ffcccc",
breeding: "#d8b38a", raw_fish: "#b3e6ff", cooked_fish: "#ffb366", donor: "#ccccff", sigils: "#f2f2f2"
};
const COMBAT_SPELLS = [
{ slug: "heal", label: "HEAL" },
{ slug: "fire", label: "FIRE" },
{ slug: "reflect", label: "REFLECT" },
{ slug: "invisibility", label: "INVIS" }
];
const COMBAT_ACTION_AREAS = [
"combat-presets-area",
"combat-potions-area",
"combat-cannon-area",
"combat-faradox-crystals-area",
"combat-faradox-crystals-area-2"
];
const SHEET_MODAL_IDS = [
"modal-market-configure-item-to-sell",
"modal-market-select-item",
"modal-market-purchase-item",
"modal-closet",
"modal-gatherer-menu",
"modal-monster-lookup",
"modal-loot"
];
let viewportMeta = null;
let colorSchemeMeta = null;
function setMobileViewport(on) {
const root = document.head || document.documentElement;
if (on && !viewportMeta) {
viewportMeta = document.createElement("meta");
viewportMeta.name = "viewport";
viewportMeta.content = "width=device-width, initial-scale=1, viewport-fit=cover";
root.appendChild(viewportMeta);
colorSchemeMeta = document.createElement("meta");
colorSchemeMeta.name = "color-scheme";
colorSchemeMeta.content = "light";
root.appendChild(colorSchemeMeta);
} else if (!on && viewportMeta) {
viewportMeta.remove();
viewportMeta = null;
if (colorSchemeMeta) { colorSchemeMeta.remove(); colorSchemeMeta = null; }
}
}
function looksMobile() {
const coarse = window.matchMedia && window.matchMedia("(pointer: coarse)").matches;
const layoutW = document.documentElement.clientWidth || window.innerWidth;
return coarse || layoutW <= NARROW_PX;
}
const CSS = `
.mob-shell { display: none; }
html:has(body.${ACTIVE_CLASS}) {
height: 100%;
overflow: hidden;
}
body.${ACTIVE_CLASS} {
position: fixed;
inset: 0;
width: 100%;
height: 100vh;
height: 100dvh;
overflow: hidden;
}
body.${ACTIVE_CLASS} #not-credits,
body.${ACTIVE_CLASS} #content {
display: block;
height: 100%;
}
body.${ACTIVE_CLASS} .panels img {
max-width: 100%;
height: auto;
}
body.${ACTIVE_CLASS} .panel-logo-xp-area {
max-width: 100%;
box-sizing: border-box;
}
body.${ACTIVE_CLASS} .panels .progress-bar {
max-width: 100%;
}
body.${ACTIVE_CLASS} #activity-log-area {
margin: 0 !important;
height: 64px !important;
font-size: 11px;
line-height: 1.35;
padding: 5px 10px;
overflow-y: auto;
resize: none;
background: rgba(0, 52, 45, 0.05);
border-top: 1px solid #b9dcd6;
border-bottom: 1px solid #b9dcd6;
box-sizing: border-box;
}
body.${ACTIVE_CLASS} #menu-bar,
body.${ACTIVE_CLASS} .menu-bar { display: none !important; }
body.${ACTIVE_CLASS} .game-screen {
display: flex !important;
flex-direction: column;
height: 100%;
min-height: 0;
}
body.${ACTIVE_CLASS} .game-top-bar {
flex: 0 0 auto;
}
body.${ACTIVE_CLASS} .panels {
flex: 1 1 auto;
min-height: 0;
width: 100% !important;
overflow-y: auto;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
padding-bottom: calc(122px + env(safe-area-inset-bottom));
box-sizing: border-box;
}
body.${ACTIVE_CLASS} #top-menu-bar-labels {
position: sticky;
top: 0;
z-index: 5;
}
body.${ACTIVE_CLASS}.mob-peek-off .panels {
padding-bottom: calc(90px + env(safe-area-inset-bottom));
}
body.${ACTIVE_CLASS} .game-top-bar-upper {
display: flex;
align-items: center;
gap: 8px;
overflow-x: auto;
white-space: nowrap;
padding: 6px 10px;
font-size: 12px;
scrollbar-width: none;
}
body.${ACTIVE_CLASS} #top-menu-bar-labels {
align-items: center;
gap: 14px;
overflow-x: auto;
white-space: nowrap;
padding: 5px 10px;
background: #00342d;
color: silver;
border-bottom: 1px solid #00504d;
font-size: 12px;
scrollbar-width: none;
}
body.${ACTIVE_CLASS} #top-menu-bar-labels:not([style*="none"]) { display: flex; }
body.${ACTIVE_CLASS} #top-menu-bar-labels .top-bar-entry { margin: 0; padding: 0; flex: 0 0 auto; }
body.${ACTIVE_CLASS} #top-menu-bar-labels img { width: 16px; height: 16px; vertical-align: -3px; }
body.${ACTIVE_CLASS} .game-top-bar {
position: relative;
z-index: 6;
}
body.${ACTIVE_CLASS} .game-top-bar-upper:has(.dropdown-menu.show),
body.${ACTIVE_CLASS} .game-top-bar-upper:focus-within {
overflow: visible;
}
body.${ACTIVE_CLASS} .game-top-bar .dropdown-menu {
max-height: calc(100dvh - 110px);
overflow-y: auto;
}
body.${ACTIVE_CLASS} .mob-tabbar {
display: flex;
position: fixed;
left: 0; right: 0; bottom: 0;
z-index: 1040;
background: #00342d;
border-top: 2px solid #0a6e5c;
padding-bottom: env(safe-area-inset-bottom);
box-shadow: 0 -2px 10px rgba(0,0,0,0.45);
}
body.${ACTIVE_CLASS} .mob-tab {
flex: 1 1 0;
min-height: 56px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 3px;
color: #b8d8d0;
background: none;
border: none;
font-size: 10px;
letter-spacing: 0.4px;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
body.${ACTIVE_CLASS} .mob-tab img { width: 24px; height: 24px; image-rendering: pixelated; }
body.${ACTIVE_CLASS} .mob-tab .mob-glyph { font-size: 22px; line-height: 24px; }
body.${ACTIVE_CLASS} .mob-tab.active {
color: #fff;
background: #005247;
box-shadow: inset 0 3px 0 #2bd9a9;
}
body.${ACTIVE_CLASS} .mob-backdrop {
position: fixed; inset: 0;
z-index: 1042;
background: rgba(0,0,0,0.55);
display: none;
}
body.${ACTIVE_CLASS} .mob-backdrop.open { display: block; }
body.${ACTIVE_CLASS} .mob-sheet {
position: fixed;
left: 0; right: 0; bottom: 0;
z-index: 1043;
background: #00342d;
color: #e8f4f0;
border-top: 2px solid #0a6e5c;
border-radius: 14px 14px 0 0;
max-height: 72vh;
max-height: 72dvh;
display: none;
flex-direction: column;
box-shadow: 0 -4px 24px rgba(0,0,0,0.6);
}
body.${ACTIVE_CLASS} .mob-sheet.open { display: flex; }
body.${ACTIVE_CLASS} .mob-sheet-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px 8px;
font-size: 14px;
letter-spacing: 1px;
}
body.${ACTIVE_CLASS} .mob-sheet-close {
background: none;
border: 1px solid #0a6e5c;
border-radius: 8px;
color: #b8d8d0;
font-size: 14px;
padding: 4px 12px;
cursor: pointer;
}
body.${ACTIVE_CLASS} .mob-sheet-body {
overflow-y: auto;
padding: 4px 12px calc(16px + env(safe-area-inset-bottom));
}
body.${ACTIVE_CLASS} .mob-section-title {
font-size: 11px;
letter-spacing: 1.5px;
color: #6fbfae;
margin: 14px 4px 8px;
}
body.${ACTIVE_CLASS} .mob-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
}
body.${ACTIVE_CLASS} .mob-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
min-height: 72px;
background: #005247;
border: 1px solid #0a6e5c;
border-radius: 10px;
color: #e8f4f0;
font-size: 9.5px;
letter-spacing: 0.3px;
text-align: center;
cursor: pointer;
padding: 8px 2px;
-webkit-tap-highlight-color: transparent;
}
body.${ACTIVE_CLASS} .mob-item img { width: 26px; height: 26px; image-rendering: pixelated; }
body.${ACTIVE_CLASS} .mob-item .mob-glyph { font-size: 22px; line-height: 26px; }
body.${ACTIVE_CLASS} .mob-item .mob-level {
position: absolute;
top: 4px; right: 5px;
background: #022b25;
border: 1px solid #2bd9a9;
color: #2bd9a9;
border-radius: 8px;
font-size: 9px;
padding: 0 5px;
line-height: 13px;
}
body.${ACTIVE_CLASS} .mob-chatpeek {
position: fixed;
left: 0; right: 0;
bottom: calc(58px + env(safe-area-inset-bottom));
z-index: 1040;
display: flex;
align-items: center;
gap: 8px;
height: 28px;
padding: 0 10px;
background: #022b25;
border-top: 1px solid #0a6e5c;
color: #cfe9e2;
font-size: 11px;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
body.${ACTIVE_CLASS}.mob-peek-off .mob-chatpeek { display: none; }
body.${ACTIVE_CLASS} .mob-chatpeek .mob-peek-msg {
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #9fd2c6;
}
body.${ACTIVE_CLASS} .mob-chatpeek .mob-peek-chevron { flex: 0 0 auto; color: #6fbfae; font-size: 12px; }
body.${ACTIVE_CLASS}:not(.mob-chat-open) #game-chat {
position: absolute;
width: 100%;
height: 0 !important;
min-height: 0 !important;
max-height: 0 !important;
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
border: 0;
}
body.${ACTIVE_CLASS}.mob-chat-open #game-chat {
display: block !important;
position: fixed;
left: 0; right: 0;
bottom: calc(86px + env(safe-area-inset-bottom));
height: 52vh;
height: 52dvh;
z-index: 1039;
margin: 0 !important;
background: #022b25;
border-top: 2px solid #0a6e5c;
box-shadow: 0 -4px 24px rgba(0,0,0,0.5);
overflow-y: auto;
padding: 10px;
box-sizing: border-box;
}
body.${ACTIVE_CLASS} .modal-dialog {
max-width: calc(100vw - 20px);
margin: 12px auto;
display: flex;
align-items: center;
min-height: calc(100% - 24px);
}
body.${ACTIVE_CLASS} .modal-dialog .modal-content {
width: 100%;
}
body.${ACTIVE_CLASS} .modal-body {
overflow-x: auto;
max-height: calc(100dvh - 210px);
}
body.${ACTIVE_CLASS} .modal-body img {
max-width: 100%;
height: auto;
}
body.${ACTIVE_CLASS} .modal-content .btn {
min-height: 44px;
}
body.${ACTIVE_CLASS} .modal-content .btn-close {
padding: 14px;
}
body.${ACTIVE_CLASS} .modal-content input,
body.${ACTIVE_CLASS} .modal-content select,
body.${ACTIVE_CLASS} .modal-content textarea {
font-size: 16px;
min-height: 40px;
}
body.${ACTIVE_CLASS} .modal-dialog.modal-xl,
body.${ACTIVE_CLASS} .modal-dialog.modal-lg,
body.${ACTIVE_CLASS} .modal-xxl,
body.${ACTIVE_CLASS} .modal-xxxl {
min-width: 0 !important;
max-width: calc(100vw - 20px) !important;
}
body.${ACTIVE_CLASS} .modal.mob-sheet-modal .modal-dialog {
position: fixed;
left: 0; right: 0; bottom: 0;
margin: 0;
width: 100vw;
max-width: 100vw !important;
min-height: 0;
display: block;
transform: none !important;
}
body.${ACTIVE_CLASS} .modal.mob-sheet-modal .modal-content {
border-radius: 14px 14px 0 0;
border: 0;
border-top: 2px solid #0a6e5c;
max-height: 88dvh;
display: flex;
flex-direction: column;
padding-bottom: env(safe-area-inset-bottom);
}
body.${ACTIVE_CLASS} .modal.mob-sheet-modal .modal-body {
flex: 1 1 auto;
max-height: none;
overflow-y: auto;
}
body.${ACTIVE_CLASS} #crafting-table,
body.${ACTIVE_CLASS} #brewing-table,
body.${ACTIVE_CLASS} #invention-table {
display: block;
width: 100%;
border: 0 !important;
background: none !important;
box-shadow: none;
}
body.${ACTIVE_CLASS} #crafting-table tbody,
body.${ACTIVE_CLASS} #brewing-table tbody,
body.${ACTIVE_CLASS} #invention-table tbody {
display: block;
}
body.${ACTIVE_CLASS} #crafting-table tr:has(th),
body.${ACTIVE_CLASS} #brewing-table tr:has(th),
body.${ACTIVE_CLASS} #invention-table tr:has(th) {
display: none;
}
body.${ACTIVE_CLASS} #crafting-table tr,
body.${ACTIVE_CLASS} #brewing-table tr,
body.${ACTIVE_CLASS} #invention-table tr {
display: block;
position: relative;
margin: 6px 0;
border: 1px solid #9ccac2 !important;
border-radius: 10px;
background-color: #eafdfb;
padding: 6px 10px;
text-align: left !important;
}
body.${ACTIVE_CLASS} #crafting-table td,
body.${ACTIVE_CLASS} #brewing-table td,
body.${ACTIVE_CLASS} #invention-table td {
display: block;
width: auto !important;
min-width: 0 !important;
max-width: none !important;
border: 0 !important;
padding: 0;
font-size: 13px;
line-height: 1.5;
text-align: left;
}
body.${ACTIVE_CLASS} #crafting-table td br,
body.${ACTIVE_CLASS} #brewing-table td br,
body.${ACTIVE_CLASS} #invention-table td br {
display: none;
}
body.${ACTIVE_CLASS} #crafting-table td img,
body.${ACTIVE_CLASS} #brewing-table td img,
body.${ACTIVE_CLASS} #invention-table td img {
max-height: 20px;
width: auto;
vertical-align: middle;
image-rendering: pixelated;
margin-left: 10px;
margin-right: 3px;
}
body.${ACTIVE_CLASS} #crafting-table td:first-child,
body.${ACTIVE_CLASS} #brewing-table td:first-child,
body.${ACTIVE_CLASS} #invention-table td:first-child {
font-size: 15px;
font-weight: 600;
padding-bottom: 4px;
}
body.${ACTIVE_CLASS} #crafting-table td:first-child,
body.${ACTIVE_CLASS} #brewing-table td:first-child,
body.${ACTIVE_CLASS} #invention-table td:first-child {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
body.${ACTIVE_CLASS} #crafting-table td:first-child img,
body.${ACTIVE_CLASS} #brewing-table td:first-child img,
body.${ACTIVE_CLASS} #invention-table td:first-child img {
max-height: 26px;
margin-left: 0;
margin-right: 6px;
}
body.${ACTIVE_CLASS} #crafting-table td:nth-child(4),
body.${ACTIVE_CLASS} #brewing-table td:nth-child(4),
body.${ACTIVE_CLASS} #invention-table td:nth-child(5) {
color: #5a7d78;
font-size: 12px;
line-height: 1.35;
margin: 2px 0;
}
body.${ACTIVE_CLASS} #crafting-table td::before,
body.${ACTIVE_CLASS} #brewing-table td::before,
body.${ACTIVE_CLASS} #invention-table td::before {
color: #2e6e63;
font-size: 10.5px;
letter-spacing: 0.6px;
margin-right: 5px;
}
body.${ACTIVE_CLASS} #crafting-table td:nth-child(2)::before { content: "LEVEL"; }
body.${ACTIVE_CLASS} #crafting-table td:nth-child(3)::before { content: "MATERIALS"; }
body.${ACTIVE_CLASS} #crafting-table td:nth-child(6)::before { content: "USED WITH"; }
body.${ACTIVE_CLASS} #brewing-table td:nth-child(2)::before { content: "LEVEL"; }
body.${ACTIVE_CLASS} #brewing-table td:nth-child(3)::before { content: "MATERIALS"; }
body.${ACTIVE_CLASS} #brewing-table td:nth-child(5)::before { content: "DURATION"; }
body.${ACTIVE_CLASS} #brewing-table td:nth-child(7)::before { content: "BREWED"; }
body.${ACTIVE_CLASS} #brewing-table td:nth-child(8)::before { content: "USED WITH"; }
body.${ACTIVE_CLASS} #invention-table td:nth-child(2)::before { content: "LEVEL"; }
body.${ACTIVE_CLASS} #invention-table td:nth-child(3)::before { content: "TABLETTE"; }
body.${ACTIVE_CLASS} #invention-table td:nth-child(4)::before { content: "COMPONENTS"; }
body.${ACTIVE_CLASS} .gathering-box {
width: auto !important;
max-width: 100%;
box-sizing: border-box;
margin: 8px 4px;
}
body.${ACTIVE_CLASS} .panels [class*="game-shop-table-entry"] {
width: auto !important;
min-width: 0 !important;
max-width: 100% !important;
box-sizing: border-box;
}
body.${ACTIVE_CLASS} .panels [class*="game-shop-table-entry"] table {
width: 100%;
max-width: 100%;
}
body.${ACTIVE_CLASS} .panels table {
max-width: 100%;
}
body.${ACTIVE_CLASS} .panel-logo-xp-area {
display: block;
padding: 8px 12px !important;
margin-bottom: 6px;
border-radius: 8px;
}
body.${ACTIVE_CLASS} .panel-logo-xp-area br { display: none; }
body.${ACTIVE_CLASS} .panel-logo-xp-area > img,
body.${ACTIVE_CLASS} .panel-logo-xp-area > a img {
max-height: 40px;
width: auto;
display: block;
margin: 0 0 6px;
image-rendering: pixelated;
}
body.${ACTIVE_CLASS} .panel-logo-xp-area > div { margin-left: 0 !important; }
body.${ACTIVE_CLASS} .panel-logo-xp-area .font-large { font-size: 15px !important; }
body.${ACTIVE_CLASS} .panel-logo-xp-area > div > a { font-size: 12px; }
body.${ACTIVE_CLASS} .panel-logo-xp-area .progress-bar {
display: block;
height: 8px !important;
border-radius: 4px;
overflow: hidden;
margin: 4px 0 2px;
box-shadow: none !important;
}
body.${ACTIVE_CLASS} .panel-logo-xp-area .progress-bar-inner {
display: block;
height: 100% !important;
}
body.${ACTIVE_CLASS} .panel-logo-xp-area .font-small { font-size: 11px; }
body.${ACTIVE_CLASS} .panel-logo-xp-area .warning-message { display: block; }
body.${ACTIVE_CLASS} #panel-gathering .panel-logo-xp-area {
display: flex !important;
flex-wrap: wrap;
align-items: center;
column-gap: 8px;
padding: 7px 10px !important;
}
body.${ACTIVE_CLASS} #panel-gathering .panel-logo-xp-area > img,
body.${ACTIVE_CLASS} #panel-gathering .panel-logo-xp-area > a img {
max-height: 16px;
margin: 0;
}
body.${ACTIVE_CLASS} #panel-gathering .panel-logo-xp-area > img,
body.${ACTIVE_CLASS} #panel-gathering .panel-logo-xp-area > a {
flex: 0 0 auto;
order: 0;
}
body.${ACTIVE_CLASS} #panel-gathering .panel-logo-xp-area > div { display: contents; }
body.${ACTIVE_CLASS} #panel-gathering .panel-logo-xp-area .font-large {
font-size: 13px !important;
order: 1;
margin-left: auto;
white-space: nowrap;
}
body.${ACTIVE_CLASS} #panel-gathering .panel-logo-xp-area > div > a {
order: 2;
flex: 0 0 auto;
font-size: 11px;
white-space: nowrap;
}
body.${ACTIVE_CLASS} #panel-gathering .panel-logo-xp-area .progress-bar {
order: 3;
flex: 1 1 100%;
height: 6px !important;
margin: 5px 0 1px;
}
body.${ACTIVE_CLASS} #panel-gathering .panel-logo-xp-area .font-small {
order: 4;
flex: 1 1 100%;
font-size: 10px;
text-align: right;
line-height: 1.2;
}
body.${ACTIVE_CLASS} #panel-gathering .panel-logo-xp-area .warning-message {
order: 5;
flex: 1 1 100%;
}
body.${ACTIVE_CLASS}:not(.mob-on-home) #notifications-area { display: none !important; }
body.${ACTIVE_CLASS}:not(.mob-on-home) #activity-log-area { display: none !important; }
body.${ACTIVE_CLASS}:not(.mob-on-home) #top-menu-bar-optional-labels { display: none !important; }
body.${ACTIVE_CLASS}.mob-on-home #top-menu-bar-optional-labels {
display: flex;
flex-wrap: wrap;
gap: 4px 14px;
padding: 8px 10px;
background: #00342d;
color: silver;
border-bottom: 1px solid #00504d;
font-size: 12px;
}
body.${ACTIVE_CLASS}.mob-on-home #top-menu-bar-optional-labels .top-bar-entry {
margin: 0;
padding: 0;
flex: 0 0 auto;
}
body.${ACTIVE_CLASS}.mob-on-home #top-menu-bar-optional-labels img {
max-height: 16px;
width: auto;
vertical-align: -3px;
}
body.${ACTIVE_CLASS} #notifications-area {
display: flex;
flex-direction: column;
gap: 6px;
margin: 8px 2px;
}
body.${ACTIVE_CLASS} #notifications-area > * {
border-radius: 10px;
padding: 8px 10px;
font-size: 13px;
text-align: left;
min-height: 40px;
}
body.${ACTIVE_CLASS} #notifications-area img {
max-height: 22px;
width: auto;
vertical-align: middle;
image-rendering: pixelated;
}
body.${ACTIVE_CLASS} .mob-tab { position: relative; }
body.${ACTIVE_CLASS} .mob-tab-badge {
display: none;
position: absolute;
top: 5px;
right: 16%;
min-width: 16px;
height: 16px;
border-radius: 8px;
background: #f0b00a;
color: #022b25;
font-size: 10px;
line-height: 16px;
padding: 0 4px;
text-align: center;
}
body.${ACTIVE_CLASS} .mob-tab-badge.on { display: block; }
body.${ACTIVE_CLASS} #panel-gathering > br { display: none; }
body.${ACTIVE_CLASS} #panel-gathering .itembox-div.box-shortcut-potion { display: none !important; }
body.${ACTIVE_CLASS} #mob-gather-chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 2px 0 8px;
}
body.${ACTIVE_CLASS} #mob-gather-chips itembox:not([style*="display: none"]):not([style*="display:none"]) {
display: flex !important;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 3px;
width: 52px !important;
height: 52px !important;
min-width: 0 !important;
min-height: 0 !important;
margin: 0 !important;
padding: 4px !important;
border-radius: 10px;
box-sizing: border-box;
overflow: hidden;
font-size: 10px;
}
body.${ACTIVE_CLASS} #mob-gather-chips itembox div {
margin: 0 !important;
line-height: 1.1;
}
body.${ACTIVE_CLASS} #mob-gather-chips itembox img {
max-width: 26px;
max-height: 26px;
image-rendering: pixelated;
}
body.${ACTIVE_CLASS} #mob-gather-minis {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
margin: 0 0 8px;
}
body.${ACTIVE_CLASS} #mob-gather-minis .gathering-box {
margin: 0 !important;
width: auto !important;
}
body.${ACTIVE_CLASS} #mob-gather-minis .gathering-box.mob-gather-active {
grid-column: 1 / -1;
order: -1;
height: auto !important;
min-height: 0 !important;
}
body.${ACTIVE_CLASS} #mob-gather-minis .gathering-box.mob-gather-mini {
padding: 9px 6px !important;
height: auto !important;
min-height: 0 !important;
background-image: none !important;
}
body.${ACTIVE_CLASS} #mob-gather-minis .gathering-box.mob-gather-mini h2 {
font-size: 13px;
margin: 0;
}
body.${ACTIVE_CLASS} #mob-gather-minis .gathering-box.mob-gather-mini > :not(h2) {
display: none;
}
body.${ACTIVE_CLASS} #mob-gather-loot {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 6px;
margin: 4px 0 8px;
}
body.${ACTIVE_CLASS} #mob-gather-loot itembox:not([style*="display: none"]):not([style*="display:none"]) {
display: flex !important;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 3px;
width: auto !important;
height: auto !important;
min-height: 64px;
margin: 0 !important;
padding: 6px 4px !important;
border-radius: 10px;
box-sizing: border-box;
font-size: 11px;
}
body.${ACTIVE_CLASS} #mob-gather-loot itembox div { margin: 0 !important; }
body.${ACTIVE_CLASS} #mob-gather-loot itembox img {
max-width: 30px;
max-height: 30px;
image-rendering: pixelated;
}
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active {
height: auto !important;
min-height: 0 !important;
padding: 10px !important;
}
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active h2 {
font-size: 18px;
margin: 0 0 6px;
}
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active img {
display: block;
margin: 0 auto;
max-height: 140px;
width: auto;
}
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active br,
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active hr,
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active .font-tiny,
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active .gathering-items-list,
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active span.color-silver,
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active center,
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active .progress-bar,
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active span:has([id*="-x-img"]),
body.${ACTIVE_CLASS} #panel-gathering .gathering-box.mob-gather-active [id*="-x-img"] {
display: none !important;
}
body.${ACTIVE_CLASS} #panel-player-market > :not(#mob-market) { display: none !important; }
body.${ACTIVE_CLASS}.mob-mkt-loading #modal-market-select-item,
body.${ACTIVE_CLASS}.mob-mkt-loading .modal-backdrop { display: none !important; }
body.${ACTIVE_CLASS} #mob-market { display: block; }
body.${ACTIVE_CLASS} #mob-market-tabs {
display: flex;
gap: 6px;
margin: 2px 0 10px;
}
body.${ACTIVE_CLASS} .mob-mkt-tab {
flex: 1 1 0;
min-height: 40px;
background: #eafdfb;
border: 1px solid #9ccac2;
border-radius: 10px;
color: #17564c;
font-size: 12px;
letter-spacing: 0.5px;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
body.${ACTIVE_CLASS} .mob-mkt-tab.active {
background: #00584a;
border-color: #00584a;
color: #fff;
}
body.${ACTIVE_CLASS} .mob-mkt-search {
width: 100%;
box-sizing: border-box;
height: 40px;
border: 1px solid #9ccac2;
border-radius: 10px;
padding: 0 12px;
font-size: 15px;
margin-bottom: 10px;
background: #fff url('https://cdn.idle-pixel.com/images/search.png') no-repeat 10px center;
background-size: 0;
}
body.${ACTIVE_CLASS} .mob-mkt-cats {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 8px;
}
body.${ACTIVE_CLASS} .mob-mkt-cat {
min-height: 72px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 3px;
border: 1px solid rgba(0,0,0,0.15);
border-radius: 12px;
color: #1b1b1b;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.3px;
text-align: center;
padding: 6px 4px;
cursor: pointer;
text-transform: capitalize;
-webkit-tap-highlight-color: transparent;
}
body.${ACTIVE_CLASS} .mob-mkt-cat small { font-weight: 400; opacity: 0.7; font-size: 9.5px; }
body.${ACTIVE_CLASS} .mob-mkt-crumb {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
font-size: 13px;
color: #17564c;
}
body.${ACTIVE_CLASS} .mob-mkt-back {
flex: 0 0 auto;
min-width: 40px;
min-height: 34px;
border: 1px solid #9ccac2;
border-radius: 9px;
background: #eafdfb;
color: #17564c;
font-size: 16px;
cursor: pointer;
}
body.${ACTIVE_CLASS} .mob-mkt-crumb-title { font-weight: 600; text-transform: capitalize; }
body.${ACTIVE_CLASS} .mob-mkt-allbtn {
width: 100%;
min-height: 40px;
margin-bottom: 8px;
background: #005247;
border: 0;
border-radius: 10px;
color: #fff;
font-size: 12px;
letter-spacing: 0.4px;
cursor: pointer;
}
body.${ACTIVE_CLASS} .mob-mkt-row {
display: flex;
align-items: center;
gap: 10px;
background: #fff;
border: 1px solid #cfe6e1;
border-radius: 10px;
padding: 8px 10px;
margin-bottom: 6px;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
body.${ACTIVE_CLASS} .mob-mkt-row img.mob-mkt-icon {
width: 34px;
height: 34px;
flex: 0 0 auto;
image-rendering: pixelated;
object-fit: contain;
}
body.${ACTIVE_CLASS} .mob-mkt-row .mob-mkt-main { flex: 1 1 auto; min-width: 0; }
body.${ACTIVE_CLASS} .mob-mkt-row .mob-mkt-name {
font-size: 14px;
color: #1b1b1b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-transform: capitalize;
}
body.${ACTIVE_CLASS} .mob-mkt-row .mob-mkt-sub { font-size: 11px; color: #6a8781; margin-top: 1px; }
body.${ACTIVE_CLASS} .mob-mkt-row .mob-mkt-price {
flex: 0 0 auto;
text-align: right;
font-size: 13px;
color: #17564c;
font-weight: 600;
white-space: nowrap;
}
body.${ACTIVE_CLASS} .mob-mkt-row .mob-mkt-price small { display: block; font-weight: 400; font-size: 9.5px; color: #6a8781; }
body.${ACTIVE_CLASS} .mob-mkt-row.mob-mkt-cheapest { border-color: #2bd9a9; box-shadow: 0 0 0 1px #2bd9a9 inset; }
body.${ACTIVE_CLASS} .mob-mkt-row.mob-mkt-yours { background: #fffbe6; border-color: #e6d27a; }
body.${ACTIVE_CLASS} .mob-mkt-empty { text-align: center; color: #6a8781; font-size: 13px; padding: 24px 10px; }
body.${ACTIVE_CLASS} .mob-mkt-chev { flex: 0 0 auto; color: #9ccac2; font-size: 16px; }
body.${ACTIVE_CLASS} .mob-mkt-trend {
display: inline-flex;
align-items: center;
gap: 5px;
margin: 2px 0 10px;
font-size: 11px;
color: #2e6e63;
text-decoration: none;
}
body.${ACTIVE_CLASS} #modal-market-select-item-section > .select-item-tradables-catagory:first-child {
display: none !important;
}
body.${ACTIVE_CLASS} .select-item-tradables-catagory {
display: block !important;
width: 100% !important;
box-sizing: border-box;
margin: 0 0 12px !important;
padding: 0 !important;
background: none !important;
box-shadow: none !important;
text-transform: capitalize;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.4px;
color: #17564c;
text-align: left;
}
body.${ACTIVE_CLASS} .select-item-tradables-catagory hr {
margin: 4px 0 8px;
border: 0;
border-top: 1px solid #cfe6e1;
}
body.${ACTIVE_CLASS} .market-tradable-item {
display: inline-flex !important;
align-items: center;
justify-content: center;
width: 48px !important;
height: 48px !important;
margin: 3px !important;
padding: 0 !important;
border-radius: 10px;
border: 1px solid rgba(0, 0, 0, 0.12);
box-shadow: none !important;
vertical-align: middle;
}
body.${ACTIVE_CLASS} .market-tradable-item img {
width: 32px !important;
height: 32px !important;
image-rendering: pixelated;
}
body.${ACTIVE_CLASS} #mob-market-sell {
display: grid;
grid-template-columns: 1fr;
gap: 8px;
}
body.${ACTIVE_CLASS} #mob-market-sell .player-market-slot-base:not([style*="display: none"]):not([style*="display:none"]) {
display: block !important;
}
body.${ACTIVE_CLASS} #mob-market-sell .player-market-slot-base {
width: auto !important;
min-width: 0 !important;
max-width: none !important;
height: auto !important;
min-height: 0 !important;
margin: 0 !important;
box-sizing: border-box;
background: #fff !important;
border: 1px solid #cfe6e1 !important;
border-radius: 12px;
padding: 12px !important;
text-align: center;
}
body.${ACTIVE_CLASS} #mob-market-sell .player-market-slot-base br { display: none; }
body.${ACTIVE_CLASS} #mob-market-sell .player-market-slot-base hr { margin: 6px 0; opacity: 0.3; }
body.${ACTIVE_CLASS} #mob-market-sell [id^="player-market-slot-empty"] {
border-style: dashed !important;
padding: 16px 12px !important;
}
body.${ACTIVE_CLASS} #mob-market-sell [id^="player-market-slot-empty"] button {
width: 100%;
min-height: 46px;
margin: 0;
background: #005247;
color: #fff;
border: 0;
border-radius: 10px;
font-size: 14px;
letter-spacing: 0.4px;
}
body.${ACTIVE_CLASS} #mob-market-sell .player-market-slot-base h2 { font-size: 15px; margin: 0 0 6px; }
body.${ACTIVE_CLASS} #mob-market-sell .player-market-slot-base img { image-rendering: pixelated; max-height: 42px; width: auto; }
body.${ACTIVE_CLASS} #mob-market-sell [id^="player-market-slot-occupied"] { text-align: left; font-size: 13px; }
body.${ACTIVE_CLASS} #mob-market-sell [id^="player-market-slot-occupied"] h2 { text-align: center; }
body.${ACTIVE_CLASS} #mob-market-sell [id^="player-market-slot-occupied"] img { vertical-align: middle; }
body.${ACTIVE_CLASS} #mob-market-sell [id^="player-market-slot-occupied"] button {
min-height: 40px;
margin: 8px 4px 0;
padding: 0 14px;
border-radius: 9px;
}
body.${ACTIVE_CLASS} .mob-fav-star {
display: block;
position: absolute;
top: 2px;
right: 4px;
width: 36px;
height: 36px;
line-height: 36px;
padding: 0;
text-align: center;
font-size: 21px;
color: #8fb8b0;
background: none;
border: none;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
z-index: 2;
}
body.${ACTIVE_CLASS} .mob-fav-star.faved {
color: #f0b00a;
}
body.${ACTIVE_CLASS} .mob-brew-favs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
margin: 8px 0;
}
body.${ACTIVE_CLASS} .mob-brew-favs-title {
grid-column: 1 / -1;
font-size: 11px;
letter-spacing: 1.5px;
color: #2e6e63;
margin: 2px 2px 0;
}
body.${ACTIVE_CLASS} .mob-fav-card {
display: flex;
align-items: center;
gap: 6px;
min-height: 44px;
background: #eafdfb;
border: 1px solid #9ccac2;
border-radius: 10px;
padding: 4px 8px;
font-size: 12px;
color: inherit;
text-align: left;
cursor: pointer;
overflow: hidden;
-webkit-tap-highlight-color: transparent;
}
body.${ACTIVE_CLASS} .mob-fav-card img {
width: 22px;
height: 22px;
image-rendering: pixelated;
flex: 0 0 auto;
}
body.${ACTIVE_CLASS} .mob-fav-card span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
body.${ACTIVE_CLASS}.mob-combat .mob-tab { display: none; }
body.${ACTIVE_CLASS}.mob-combat .mob-chatpeek { display: none !important; }
body.${ACTIVE_CLASS} .mob-combat-bar { display: none; }
body.${ACTIVE_CLASS}.mob-combat .mob-combat-bar {
display: flex;
flex: 1 1 auto;
align-items: stretch;
gap: 5px;
padding: 5px 6px;
}
body.${ACTIVE_CLASS} .mob-combat-back {
flex: 0 0 50px;
display: flex;
align-items: center;
justify-content: center;
background: #005247;
border: 1px solid #0a6e5c;
border-radius: 10px;
color: #e8f4f0;
font-size: 22px;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
body.${ACTIVE_CLASS} .mob-spell-btn {
position: relative;
flex: 1 1 0;
min-height: 58px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
background: #0b2d54;
border: 1px solid #2b6cb0;
border-radius: 10px;
color: #dbeafe;
font-size: 9px;
letter-spacing: 0.4px;
cursor: pointer;
overflow: hidden;
-webkit-tap-highlight-color: transparent;
}
body.${ACTIVE_CLASS} .mob-spell-btn img {
width: 26px;
height: 26px;
image-rendering: pixelated;
}
body.${ACTIVE_CLASS} .mob-spell-btn.mob-cooling {
filter: grayscale(0.8) brightness(0.75);
}
body.${ACTIVE_CLASS} .mob-spell-btn .mob-spell-cd {
display: none;
position: absolute;
inset: 0;
align-items: center;
justify-content: center;
background: rgba(2, 15, 33, 0.55);
color: #fff;
font-size: 19px;
font-weight: 600;
}
body.${ACTIVE_CLASS} .mob-spell-btn.mob-cooling .mob-spell-cd { display: flex; }
body.${ACTIVE_CLASS} .mob-spell-btn.mob-unavailable { display: none; }
body.${ACTIVE_CLASS} .mob-spell-btn.mob-nomana {
border-color: #b91c1c;
filter: saturate(0.6) brightness(0.8);
}
body.${ACTIVE_CLASS} .mob-spell-btn .mob-spell-note {
display: none;
position: absolute;
left: 0;
right: 0;
bottom: 2px;
color: #fca5a5;
font-size: 8px;
text-align: center;
}
body.${ACTIVE_CLASS} .mob-spell-btn.mob-nomana .mob-spell-note { display: block; }
body.${ACTIVE_CLASS}.mob-combat [id^="fighting-spell-heal"],
body.${ACTIVE_CLASS}.mob-combat [id^="fighting-spell-fire"],
body.${ACTIVE_CLASS}.mob-combat [id^="fighting-spell-reflect"],
body.${ACTIVE_CLASS}.mob-combat [id^="fighting-spell-invisibility"] {
display: none !important;
}
body.${ACTIVE_CLASS} #combat-canvas-hero {
width: calc((100vw - 8px) * 0.375) !important;
height: auto !important;
margin: 0 !important;
}
body.${ACTIVE_CLASS} #combat-canvas-monster {
width: calc((100vw - 8px) * 0.625) !important;
height: auto !important;
margin: 0 !important;
}
body.${ACTIVE_CLASS} #panel-combat-canvas table { max-width: 100%; border-spacing: 0; }
body.${ACTIVE_CLASS} #panel-combat-canvas td {
padding: 0 !important;
border: none !important;
}
body.${ACTIVE_CLASS} #panel-combat-canvas > button { display: none; }
body.${ACTIVE_CLASS} #panel-combat-canvas { position: relative; }
body.${ACTIVE_CLASS}.mob-combat .panels,
body.${ACTIVE_CLASS}.mob-combat .panels .panel-content {
padding-left: 0 !important;
padding-right: 0 !important;
}
body.${ACTIVE_CLASS} #panel-combat-canvas center { margin: 0; }
body.${ACTIVE_CLASS} #mob-combat-hud-left,
body.${ACTIVE_CLASS} #mob-combat-hud-right {
position: absolute;
top: 4px;
width: 46%;
z-index: 5;
color: #fff;
text-shadow: 0 1px 3px #000, 0 0 2px #000;
}
body.${ACTIVE_CLASS} #mob-combat-hud-left { left: 6px; text-align: left; }
body.${ACTIVE_CLASS} #mob-combat-hud-right { right: 6px; text-align: right; }
body.${ACTIVE_CLASS} #mob-combat-hud-left .fighting-hero-stats-area,
body.${ACTIVE_CLASS} #mob-combat-hud-right .fighting-monster-stats-area {
background: none !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
margin: 0 0 2px !important;
display: block;
font-size: 13px;
font-weight: 600;
}
body.${ACTIVE_CLASS} #mob-combat-hud-left .hp-progress-bar,
body.${ACTIVE_CLASS} #mob-combat-hud-left .mana-progress-bar,
body.${ACTIVE_CLASS} #mob-combat-hud-right .hp-progress-bar {
display: block;
width: 100% !important;
min-width: 0 !important;
max-width: none !important;
height: 7px !important;
border-radius: 4px;
overflow: hidden;
margin: 2px 0 0;
box-shadow: none !important;
}
body.${ACTIVE_CLASS} #mob-combat-hud-left .hp-progress-bar-inner,
body.${ACTIVE_CLASS} #mob-combat-hud-left .mana-progress-bar-inner,
body.${ACTIVE_CLASS} #mob-combat-hud-right .hp-progress-bar-inner {
display: block;
height: 100% !important;
}
body.${ACTIVE_CLASS} #mob-combat-hud-left .progress-bar-label,
body.${ACTIVE_CLASS} #mob-combat-hud-right .progress-bar-label {
width: auto !important;
min-width: 0 !important;
font-size: 10px;
line-height: 1.3;
padding: 0;
margin: 0 0 2px;
background: none !important;
}
body.${ACTIVE_CLASS} #mob-combat-hud-left .td-combat-bottom-panel,
body.${ACTIVE_CLASS} #mob-combat-hud-right .td-combat-bottom-panel {
background: none !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
margin: 3px 0 0 !important;
min-width: 0 !important;
display: block;
}
body.${ACTIVE_CLASS} #mob-combat-hud-left .td-combat-stat-entry,
body.${ACTIVE_CLASS} #mob-combat-hud-right .td-combat-stat-entry {
display: flex;
align-items: center;
gap: 3px;
padding: 0;
font-size: 10px;
line-height: 1.45;
}
body.${ACTIVE_CLASS} #mob-combat-hud-right .td-combat-stat-entry { justify-content: flex-end; }
body.${ACTIVE_CLASS} #mob-combat-hud-left br,
body.${ACTIVE_CLASS} #mob-combat-hud-right br { display: none; }
body.${ACTIVE_CLASS} #mob-combat-actions {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 8px 6px;
align-items: flex-start;
}
body.${ACTIVE_CLASS} #mob-combat-actions .combat-presets-area {
background: rgba(2, 43, 37, 0.9) !important;
border: 1px solid #0a6e5c !important;
box-shadow: none !important;
border-radius: 10px;
padding: 5px 8px !important;
margin: 0 !important;
display: flex;
align-items: center;
gap: 5px;
font-size: 10px;
min-width: 0;
width: auto !important;
}
body.${ACTIVE_CLASS} #mob-combat-actions .combat-presets-area br { display: none; }
body.${ACTIVE_CLASS} #mob-combat-actions .combat-presets-area u {
font-size: 9px;
letter-spacing: 0.5px;
text-decoration: none;
color: #6fbfae !important;
}
body.${ACTIVE_CLASS} #mob-combat-actions .combat-presets-combat-icon {
width: 34px !important;
height: 34px !important;
border-radius: 8px;
}
body.${ACTIVE_CLASS} #mob-toasts {
display: flex;
flex-direction: column;
gap: 6px;
position: fixed;
left: 10px;
right: 10px;
top: calc(env(safe-area-inset-top) + 8px);
z-index: 1045;
pointer-events: none;
}
body.${ACTIVE_CLASS} .mob-toast {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
background: rgba(2, 43, 37, 0.94);
border: 1px solid #0a6e5c;
border-radius: 10px;
padding: 8px 12px;
font-size: 13px;
color: #e8f4f0;
text-align: center;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
transition: opacity 0.35s, transform 0.35s;
}
body.${ACTIVE_CLASS} .mob-toast img { width: 22px; height: 22px; image-rendering: pixelated; }
body.${ACTIVE_CLASS} .mob-toast-out { opacity: 0; transform: translateY(-6px); }
body.${ACTIVE_CLASS} .scroller {
font-size: 16pt;
max-width: 80vw;
}
`;
class MobileUIPlugin extends IdlePixelPlusPlugin {
constructor() {
const about = (typeof GM_info !== "undefined" && GM_info.script)
? { name: GM_info.script.name, version: GM_info.script.version, author: GM_info.script.author, description: GM_info.script.description }
: { name: "IdlePixel Mobile UI", version: "0.1.0", author: "Eisa (StoneSinew)", description: "Mobile-first UI shell" };
super("mobile-ui", {
about: about,
config: [
{ label: "Mobile UI", type: "label" },
{ id: "mode", label: "Activation (auto = touch device or narrow window)", type: "select", options: ["auto", "on", "off"], default: "auto" },
{ id: "chatPeek", label: "Chat peek strip above the tab bar", type: "boolean", default: true }
]
});
this.built = false;
this.active = false;
this.tabs = [];
this.sheets = [];
this.badgeSyncs = [];
this._origSwitchPanels = null;
this._origScrollText = null;
this._modalObserver = null;
this._brewObserver = null;
this._brewDecorateTimer = null;
this._notifObserver = null;
this._notifSyncTimer = null;
this._notifBadgeEl = null;
this._combatBuilt = false;
this._combatTicker = null;
this._spellBtns = {};
this._mkt = null;
this._mktTabBtns = {};
this._mktCatalogRequested = false;
this._mktModalHooked = false;
this._mktLoading = false;
this._mktPoll = null;
}
onLogin() {
this.injectStyles();
this.hookSwitchPanels();
this.hookToasts();
this.hookModals();
this.evaluate();
let t = null;
window.addEventListener("resize", () => {
clearTimeout(t);
t = setTimeout(() => this.evaluate(), 200);
});
window.addEventListener("scroll", () => {
if (!this.active) return;
if (document.documentElement.scrollTop) document.documentElement.scrollTop = 0;
if (document.body.scrollTop) document.body.scrollTop = 0;
}, { passive: true });
}
onConfigChange() { this.evaluate(); }
onConfigsChanged() { this.evaluate(); }
onVariableSet(key) {
if (key === "current_gathering_area" || key === "gathering_area") {
this.syncGatheringActive();
}
}
onMessageReceived(data) {
try {
if (!this._mkt) return;
const tag = String(data).split("=")[0];
if (this._mktLoading && /MARKET|TRADABLE/.test(tag)) {
console.log("[mobile-ui] market msg while loading:", tag);
}
if (this._mktLoading && this.mktCatalogReady()) this.mktCatalogArrived();
} catch (e) {}
}
cfg(id, fallback) {
try {
const v = this.getConfig(id);
return (v === undefined || v === null) ? fallback : v;
} catch (e) {
return fallback;
}
}
shouldActivate() {
const mode = String(this.cfg("mode", "auto"));
if (mode === "on") return true;
if (mode === "off") return false;
return looksMobile();
}
evaluate() {
const want = this.shouldActivate();
if (want && !this.built) this.buildShell();
document.body.classList.toggle(ACTIVE_CLASS, want);
document.body.classList.toggle("mob-peek-off", !this.cfg("chatPeek", true));
if (!want) {
document.body.classList.remove("mob-chat-open");
document.body.classList.remove("mob-combat");
this.leaveCombat();
}
this.toggleViewport(want);
this.active = want;
if (want) {
this.decorateBrewing();
this.arrangeGathering();
this.arrangeMarket();
this.syncNotifBadge();
}
}
toggleViewport(on) {
setMobileViewport(on);
}
injectStyles() {
if (document.getElementById("mob-ui-styles")) return;
const style = document.createElement("style");
style.id = "mob-ui-styles";
style.textContent = CSS;
document.head.appendChild(style);
}
parseMenu() {
const out = {};
document.querySelectorAll('[id^="left-panel-item_panel-"]').forEach((el) => {
const pid = el.id.replace("left-panel-item_", "");
if (pid.indexOf("-label") !== -1) return;
const img = el.querySelector("img");
const labelEl = document.getElementById(el.id + "-label");
out[pid.replace("panel-", "")] = {
panel: COMBAT_STYLES[pid] ? "panel-combat" : pid,
icon: img ? img.src : null,
label: labelEl ? labelEl.textContent.trim() : pid.replace("panel-", "").replace(/-/g, " ").toUpperCase(),
sourceEl: el
};
});
return out;
}
readLevel(sourceEl) {
const lvlEl = sourceEl && sourceEl.querySelector("[data-menu-bar-levels-label]");
const m = lvlEl ? lvlEl.textContent.match(/\d+/) : null;
return m ? m[0] : null;
}
buildShell() {
const menu = this.parseMenu();
const pick = (keys) => keys.map(k => menu[k]).filter(Boolean);
const skillsSheet = this.buildSheet("SKILLS", [
{ items: pick(SHEET_GROUPS.skills) }
]);
const moreSheet = this.buildSheet("MORE", [
{ title: "SHOPS", items: pick(SHEET_GROUPS.shops) },
{ title: "PLUGINS & MODS", dynamic: () => this.scanPluginMenuItems() },
{ title: "OTHER", items: pick(SHEET_GROUPS.other) }
]);
this.sheets = [skillsSheet, moreSheet];
this.buildChatPeek();
this.buildTabBar([
{ label: "HOME", icon: "https://cdn.idle-pixel.com/images/loot_bag.png", panel: "panel-main" },
{ label: "SKILLS", icon: "https://cdn.idle-pixel.com/images/skills.png", launcher: skillsSheet },
{ label: "COMBAT", icon: "https://cdn.idle-pixel.com/images/melee.png", panel: "panel-combat" },
{ label: "MARKET", icon: "https://cdn.idle-pixel.com/images/player_market.png", panel: "panel-player-market" },
{ label: "MORE", glyph: "☰", launcher: moreSheet }
]);
if (this.tabs[0]) {
const badge = document.createElement("span");
badge.className = "mob-tab-badge";
this.tabs[0].appendChild(badge);
this._notifBadgeEl = badge;
}
this.watchNotifications();
this.built = true;
const current = typeof Globals !== "undefined" && Globals.currentPanel ? Globals.currentPanel : "panel-main";
this.setActiveTab(current);
document.body.classList.toggle("mob-on-home", current === "panel-main");
}
scanPluginMenuItems() {
const items = [];
document.querySelectorAll("#menu-bar .hover-menu-bar-item").forEach((el) => {
if (el.closest('[id^="left-panel-item_"]')) return;
if (el.querySelector('[id^="left-panel-item_"]')) return;
if (el.style.display === "none") return;
const label = (el.textContent || "").replace(/\s+/g, " ").trim().slice(0, 24).toUpperCase();
if (!label || label === "BACK") return;
const img = el.querySelector("img");
items.push({
icon: img ? img.src : null,
glyph: "🔌",
label: label,
action: () => el.click()
});
});
return items;
}
goTo(panelId) {
this.closeSheets();
if (typeof window.switch_panels === "function") window.switch_panels(panelId);
window.scrollTo(0, 0);
}
closeSheets() {
this.sheets.forEach(s => s.close());
}
makeSheetItem(it) {
const b = document.createElement("button");
b.className = "mob-item";
if (it.icon) {
const im = document.createElement("img");
im.src = it.icon;
im.alt = "";
b.appendChild(im);
} else if (it.glyph) {
const g = document.createElement("span");
g.className = "mob-glyph";
g.textContent = it.glyph;
b.appendChild(g);
}
const lab = document.createElement("span");
lab.textContent = it.label;
b.appendChild(lab);
if (it.sourceEl) {
const badge = document.createElement("span");
badge.className = "mob-level";
badge.style.display = "none";
b.appendChild(badge);
this.badgeSyncs.push(() => {
const lvl = this.readLevel(it.sourceEl);
badge.style.display = lvl ? "" : "none";
badge.textContent = lvl || "";
});
}
b.addEventListener("click", () => {
if (it.action) {
this.closeSheets();
it.action();
this.setActiveTab("");
const panels = document.querySelector(".panels");
if (panels) panels.scrollTop = 0;
} else {
this.goTo(it.panel);
}
});
return b;
}
buildSheet(title, sections) {
const backdrop = document.createElement("div");
backdrop.className = "mob-backdrop mob-shell";
const sheet = document.createElement("div");
sheet.className = "mob-sheet mob-shell";
const head = document.createElement("div");
head.className = "mob-sheet-head";
const titleSpan = document.createElement("span");
titleSpan.textContent = title;
const close = document.createElement("button");
close.className = "mob-sheet-close";
close.textContent = "CLOSE";
head.appendChild(titleSpan);
head.appendChild(close);
const body = document.createElement("div");
body.className = "mob-sheet-body";
const dynamics = [];
sections.forEach((sec) => {
let titleEl = null;
if (sec.title) {
titleEl = document.createElement("div");
titleEl.className = "mob-section-title";
titleEl.textContent = sec.title;
body.appendChild(titleEl);
}
const grid = document.createElement("div");
grid.className = "mob-grid";
if (sec.dynamic) {
dynamics.push({ grid: grid, titleEl: titleEl, fn: sec.dynamic });
} else {
sec.items.forEach((it) => grid.appendChild(this.makeSheetItem(it)));
}
body.appendChild(grid);
});
sheet.appendChild(head);
sheet.appendChild(body);
document.body.appendChild(backdrop);
document.body.appendChild(sheet);
const self = this;
const api = {
open() {
self.closeSheets();
dynamics.forEach((d) => {
const items = d.fn() || [];
d.grid.innerHTML = "";
items.forEach((it) => d.grid.appendChild(self.makeSheetItem(it)));
if (d.titleEl) d.titleEl.style.display = items.length ? "" : "none";
d.grid.style.display = items.length ? "" : "none";
});
self.badgeSyncs.forEach(fn => fn());
backdrop.classList.add("open");
sheet.classList.add("open");
},
close() {
backdrop.classList.remove("open");
sheet.classList.remove("open");
},
isOpen() { return sheet.classList.contains("open"); },
toggle() { api.isOpen() ? api.close() : api.open(); }
};
close.addEventListener("click", api.close);
backdrop.addEventListener("click", api.close);
return api;
}
buildTabBar(slots) {
const bar = document.createElement("div");
bar.className = "mob-tabbar mob-shell";
this.tabs = [];
slots.forEach((slot) => {
const b = document.createElement("button");
b.className = "mob-tab";
if (slot.icon) {
const im = document.createElement("img");
im.src = slot.icon;
im.alt = "";
b.appendChild(im);
} else {
const g = document.createElement("span");
g.className = "mob-glyph";
g.textContent = slot.glyph || "?";
b.appendChild(g);
}
const lab = document.createElement("span");
lab.textContent = slot.label;
b.appendChild(lab);
b.addEventListener("click", () => {
if (slot.launcher) {
slot.launcher.toggle();
} else if (slot.panel) {
this.goTo(slot.panel);
}
});
this.tabs.push(b);
bar.appendChild(b);
});
document.body.appendChild(bar);
}
buildChatPeek() {
const peek = document.createElement("div");
peek.className = "mob-chatpeek mob-shell";
const icon = document.createElement("span");
icon.textContent = "💬";
const msg = document.createElement("span");
msg.className = "mob-peek-msg";
msg.textContent = "CHAT";
const chevron = document.createElement("span");
chevron.className = "mob-peek-chevron";
chevron.textContent = "▲";
peek.appendChild(icon);
peek.appendChild(msg);
peek.appendChild(chevron);
document.body.appendChild(peek);
peek.addEventListener("click", () => {
const open = document.body.classList.toggle("mob-chat-open");
chevron.textContent = open ? "▼" : "▲";
if (open) {
const area = document.getElementById("chat-area");
if (area) area.scrollTop = area.scrollHeight;
}
});
const area = document.getElementById("chat-area");
if (area) {
const seed = area.lastElementChild;
if (seed) msg.textContent = this.chatLineText(seed);
new MutationObserver((muts) => {
for (let i = muts.length - 1; i >= 0; i--) {
const added = muts[i].addedNodes;
if (added && added.length) {
msg.textContent = this.chatLineText(added[added.length - 1]);
return;
}
}
}).observe(area, { childList: true });
}
}
chatLineText(node) {
const text = (node.textContent || "").replace(/\s+/g, " ").trim();
return text ? text.slice(0, 140) : "CHAT";
}
hookSwitchPanels() {
if (this._origSwitchPanels || typeof window.switch_panels !== "function") return;
const orig = window.switch_panels;
this._origSwitchPanels = orig;
const self = this;
window.switch_panels = function (id) {
orig.apply(this, arguments);
if (!self.active) return;
try {
self.closeSheets();
self.setActiveTab(id);
document.body.classList.toggle("mob-on-home", id === "panel-main");
const inCombat = id === "panel-combat-canvas";
document.body.classList.toggle("mob-combat", inCombat);
if (inCombat) self.enterCombat(); else self.leaveCombat();
const panels = document.querySelector(".panels");
if (panels) panels.scrollTop = 0;
if (id === "panel-brewing") requestAnimationFrame(() => { try { self.decorateBrewing(); } catch (e) { self.reportError("brewing", e); } });
if (id === "panel-gathering") requestAnimationFrame(() => { try { self.arrangeGathering(); } catch (e) { self.reportError("gathering", e); } });
if (id === "panel-player-market") requestAnimationFrame(() => { try { self.arrangeMarket(); } catch (e) { self.reportError("market", e); } });
if (inCombat) requestAnimationFrame(() => { try { self.arrangeCombat(); } catch (e) { self.reportError("combat-arrange", e); } });
} catch (e) {
self.reportError("panel-switch", e);
}
};
}
reportError(where, e) {
try {
console.error("[mobile-ui] " + where + ":", e);
this.showToast("none", "red", "Mobile UI error (" + where + "): " + (e && e.message ? e.message : e));
} catch (e2) { }
}
buildCombatUI() {
if (this._combatBuilt) return;
const tabbar = document.querySelector(".mob-tabbar");
if (!tabbar) return;
this._combatBuilt = true;
const bar = document.createElement("div");
bar.className = "mob-combat-bar";
const back = document.createElement("button");
back.className = "mob-combat-back";
back.textContent = "←";
back.setAttribute("aria-label", "leave combat");
back.addEventListener("click", () => {
if (typeof window.switch_panels === "function") window.switch_panels("panel-combat");
});
bar.appendChild(back);
COMBAT_SPELLS.forEach((sp) => {
const b = document.createElement("button");
b.className = "mob-spell-btn";
const origIcon = document.getElementById("image-" + sp.slug + "_spell_icon");
const im = document.createElement("img");
im.src = origIcon ? origIcon.src : "https://cdn.idle-pixel.com/images/" + sp.slug + "_spell_icon.png";
im.alt = "";
b.appendChild(im);
const lab = document.createElement("span");
lab.textContent = sp.label;
b.appendChild(lab);
const cd = document.createElement("span");
cd.className = "mob-spell-cd";
b.appendChild(cd);
const note = document.createElement("span");
note.className = "mob-spell-note";
b.appendChild(note);
b.addEventListener("click", () => {
const orig = document.getElementById("fighting-spell-" + sp.slug);
if (orig) orig.click();
});
this._spellBtns[sp.slug] = { btn: b, cd: cd, note: note, img: im };
bar.appendChild(b);
});
tabbar.appendChild(bar);
}
arrangeCombat() {
if (!this.active) return;
const panel = document.getElementById("panel-combat-canvas");
if (!panel || panel.dataset.mobArranged) return;
const table = panel.querySelector("center > table");
if (!table) return;
panel.dataset.mobArranged = "1";
const hudL = document.createElement("div");
hudL.id = "mob-combat-hud-left";
const hudR = document.createElement("div");
hudR.id = "mob-combat-hud-right";
const acts = document.createElement("div");
acts.id = "mob-combat-actions";
const heroPlate = panel.querySelector(".fighting-hero-stats-area");
if (heroPlate) hudL.appendChild(heroPlate);
const heroHp = document.getElementById("hero-progress-bar-hp");
if (heroHp) {
const barsTd = heroHp.closest("td");
const center = barsTd ? barsTd.querySelector("center") : null;
if (center) hudL.appendChild(center);
}
const heroStats = panel.querySelector("td.fight-right-border .td-combat-bottom-panel");
if (heroStats) hudL.appendChild(heroStats);
const monPlate = panel.querySelector(".fighting-monster-stats-area");
if (monPlate) hudR.appendChild(monPlate);
const monHp = document.getElementById("monster-progress-bar-hp");
if (monHp) {
const td = monHp.closest("td");
const wrap = td ? (td.querySelector("center") || td.firstElementChild) : null;
if (wrap && wrap !== td) hudR.appendChild(wrap);
else if (td) { while (td.firstChild) hudR.appendChild(td.firstChild); }
}
panel.querySelectorAll("td.fight-left-border .td-combat-bottom-panel").forEach((el) => hudR.appendChild(el));
COMBAT_ACTION_AREAS.forEach((id) => {
const el = document.getElementById(id);
if (el) acts.appendChild(el);
});
panel.appendChild(hudL);
panel.appendChild(hudR);
panel.appendChild(acts);
}
syncCombatUI() {
COMBAT_SPELLS.forEach((sp) => {
const entry = this._spellBtns[sp.slug];
if (!entry) return;
const orig = document.getElementById("fighting-spell-" + sp.slug);
const available = !!orig && orig.style.display !== "none";
if (entry.btn.classList.contains("mob-unavailable") === available) {
entry.btn.classList.toggle("mob-unavailable", !available);
}
const origIcon = document.getElementById("image-" + sp.slug + "_spell_icon");
if (origIcon && entry.img.src !== origIcon.src) entry.img.src = origIcon.src;
let cdVal = 0;
try {
if (typeof Items !== "undefined" && typeof Items.getItem === "function") {
cdVal = parseInt(Items.getItem(sp.slug + "_cooldown"), 10) || 0;
}
} catch (e) { }
const label = document.getElementById("fighting-spell-label-" + sp.slug);
const labelText = label ? label.textContent.trim() : "";
if (!cdVal && /^\d+$/.test(labelText)) cdVal = parseInt(labelText, 10) || 0;
const cooling = cdVal > 0;
if (entry.btn.classList.contains("mob-cooling") !== cooling) {
entry.btn.classList.toggle("mob-cooling", cooling);
}
const text = cooling ? String(cdVal) : "";
if (entry.cd.textContent !== text) entry.cd.textContent = text;
const blocked = labelText === "NO MANA" || labelText === "MAGIC BONUS";
if (entry.btn.classList.contains("mob-nomana") !== blocked) {
entry.btn.classList.toggle("mob-nomana", blocked);
}
const noteText = blocked ? labelText : "";
if (entry.note.textContent !== noteText) entry.note.textContent = noteText;
});
}
enterCombat() {
this.buildCombatUI();
this.arrangeCombat();
this.syncCombatUI();
if (!this._combatTicker) {
this._combatTicker = setInterval(() => {
if (!this.active || !document.body.classList.contains("mob-combat")) return;
this.syncCombatUI();
}, 500);
}
}
leaveCombat() {
if (this._combatTicker) {
clearInterval(this._combatTicker);
this._combatTicker = null;
}
}
arrangeGathering() {
if (!this.active) return;
const panel = document.getElementById("panel-gathering");
if (!panel) return;
if (!panel.dataset.mobArranged) {
const header = panel.querySelector(".panel-logo-xp-area");
if (header) {
panel.dataset.mobArranged = "1";
const chips = document.createElement("div");
chips.id = "mob-gather-chips";
const minis = document.createElement("div");
minis.id = "mob-gather-minis";
const loot = document.createElement("div");
loot.id = "mob-gather-loot";
const chipEls = Array.from(panel.querySelectorAll(
':scope > itembox.box-shortcut-potion, :scope > itembox.tool-item-box-gathering, :scope > itembox[data-item="gathering_worker"]'
));
const boxEls = Array.from(panel.querySelectorAll(":scope > .gathering-box"));
const lootEls = Array.from(panel.querySelectorAll(":scope > itembox"))
.filter((el) => chipEls.indexOf(el) === -1);
chipEls.forEach((el) => chips.appendChild(el));
boxEls.forEach((el) => minis.appendChild(el));
lootEls.forEach((el) => loot.appendChild(el));
header.after(chips, minis, loot);
}
}
this.syncGatheringActive();
}
syncGatheringActive() {
const boxes = document.querySelectorAll("#mob-gather-minis .gathering-box, #panel-gathering > .gathering-box");
if (!boxes.length) return;
let area = "";
try {
if (typeof Items !== "undefined" && typeof Items.getItemString === "function") {
area = String(Items.getItemString("current_gathering_area") || "");
}
} catch (e) { }
if (area === "none" || area === "0" || area === "undefined" || area === "null") area = "";
if (!area) {
document.querySelectorAll('#panel-gathering [id^="progress-bar-gathering-"]').forEach((el) => {
if (parseFloat(el.style.width || "0") > 0) {
area = el.id.replace("progress-bar-gathering-", "");
}
});
}
boxes.forEach((b) => {
const m = (b.getAttribute("onclick") || "").match(/open_gathering_dialogue\('([^']+)'/);
const active = !!m && !!area && m[1] === area;
const mini = !!area && !active;
if (b.classList.contains("mob-gather-active") !== active) b.classList.toggle("mob-gather-active", active);
if (b.classList.contains("mob-gather-mini") !== mini) b.classList.toggle("mob-gather-mini", mini);
});
}
syncNotifBadge() {
if (!this._notifBadgeEl) return;
const area = document.getElementById("notifications-area");
let count = 0;
if (area) {
Array.from(area.children).forEach((c) => {
if (c.style.display !== "none") count++;
});
}
const text = count > 9 ? "9+" : String(count);
if (this._notifBadgeEl.textContent !== text) this._notifBadgeEl.textContent = text;
const on = count > 0;
if (this._notifBadgeEl.classList.contains("on") !== on) this._notifBadgeEl.classList.toggle("on", on);
}
watchNotifications() {
if (this._notifObserver) return;
const area = document.getElementById("notifications-area");
if (!area) return;
this._notifObserver = new MutationObserver(() => {
clearTimeout(this._notifSyncTimer);
this._notifSyncTimer = setTimeout(() => this.syncNotifBadge(), 100);
});
this._notifObserver.observe(area, { childList: true, subtree: true, attributes: true, attributeFilter: ["style"] });
}
getBrewFavs() {
try { return JSON.parse(localStorage.getItem(BREW_FAVS_KEY)) || []; } catch (e) { return []; }
}
setBrewFavs(list) {
try { localStorage.setItem(BREW_FAVS_KEY, JSON.stringify(list)); } catch (e) { }
}
toggleBrewFav(potion) {
const favs = this.getBrewFavs();
const i = favs.indexOf(potion);
if (i === -1) favs.push(potion); else favs.splice(i, 1);
this.setBrewFavs(favs);
this.syncBrewStars();
this.buildBrewFavStrip();
}
syncBrewStars() {
const favs = this.getBrewFavs();
document.querySelectorAll("#brewing-table .mob-fav-star").forEach((b) => {
const faved = favs.indexOf(b.getAttribute("data-potion")) !== -1;
const glyph = faved ? "★" : "☆";
if (b.textContent !== glyph) b.textContent = glyph;
if (b.classList.contains("faved") !== faved) b.classList.toggle("faved", faved);
});
}
buildBrewFavStrip() {
const table = document.getElementById("brewing-table");
if (!table || !table.parentElement) return;
let strip = document.getElementById("mob-brew-favs");
if (!strip) {
strip = document.createElement("div");
strip.id = "mob-brew-favs";
strip.className = "mob-brew-favs mob-shell";
table.parentElement.insertBefore(strip, table);
}
const favs = this.getBrewFavs();
strip.innerHTML = "";
if (!favs.length) {
strip.style.display = "none";
return;
}
strip.style.display = "";
const title = document.createElement("div");
title.className = "mob-brew-favs-title";
title.textContent = "FAVOURITES";
strip.appendChild(title);
favs.forEach((potion) => {
const row = table.querySelector('tr[data-brewing-item="' + potion + '"]');
const srcImg = row ? row.querySelector("td img") : null;
const name = row && row.querySelector("td")
? row.querySelector("td").textContent.replace(/\s+/g, " ").trim()
: potion.replace(/_/g, " ");
const card = document.createElement("button");
card.className = "mob-fav-card";
if (srcImg) {
const im = document.createElement("img");
im.src = srcImg.src;
im.alt = "";
card.appendChild(im);
}
const span = document.createElement("span");
span.textContent = name;
card.appendChild(span);
card.addEventListener("click", () => {
if (typeof Modals !== "undefined" && typeof Modals.open_brew_dialogue === "function") {
Modals.open_brew_dialogue(potion);
}
});
strip.appendChild(card);
});
}
decorateBrewing() {
if (!this.active) return;
const table = document.getElementById("brewing-table");
if (!table) return;
const favs = this.getBrewFavs();
table.querySelectorAll("tr[data-brewing-item]").forEach((row) => {
if (row.querySelector(".mob-fav-star")) return;
const potion = row.getAttribute("data-brewing-item");
const b = document.createElement("button");
b.className = "mob-fav-star mob-shell";
b.setAttribute("data-potion", potion);
b.setAttribute("aria-label", "favourite");
b.textContent = favs.indexOf(potion) !== -1 ? "★" : "☆";
b.classList.toggle("faved", favs.indexOf(potion) !== -1);
b.addEventListener("click", (e) => {
e.stopPropagation();
this.toggleBrewFav(potion);
});
row.appendChild(b);
});
this.syncBrewStars();
this.buildBrewFavStrip();
if (!this._brewObserver) {
this._brewObserver = new MutationObserver(() => {
clearTimeout(this._brewDecorateTimer);
this._brewDecorateTimer = setTimeout(() => this.decorateBrewing(), 150);
});
this._brewObserver.observe(table, { childList: true, subtree: true });
}
}
mktCategories() {
const map = {};
const t = (typeof Market !== "undefined" && Market.tradables) || [];
t.forEach((x) => { (map[x.category] = map[x.category] || []).push(x.item); });
return map;
}
mktPretty(item) {
try { if (typeof Items !== "undefined" && Items.get_pretty_item_name) return Items.get_pretty_item_name(item); } catch (e) {}
return String(item).replace(/_/g, " ");
}
mktFmt(n) {
try { if (typeof format_number === "function") return format_number(n); } catch (e) {}
return String(n);
}
mktPrice(n) {
try { if (typeof Market !== "undefined" && Market.get_price_after_tax) return Market.get_price_after_tax(n); } catch (e) {}
return this.mktFmt(Math.floor(n * 1.01));
}
mktColor(cat) {
try { if (typeof Market !== "undefined" && Market.get_category_background_color) { const c = Market.get_category_background_color(cat); if (c) return c; } } catch (e) {}
return MARKET_CATEGORY_COLORS[cat] || "#eafdfb";
}
mktExpire(ts) {
try { if (typeof Market !== "undefined" && Market._get_expire_time) return Market._get_expire_time(ts); } catch (e) {}
const left = 259200000 + parseInt(ts, 10) - Date.now();
if (left < 0) return "Expired";
return Math.floor(left / 3600000) + "h left";
}
hookMarketModal() {
if (this._mktModalHooked) return;
if (typeof Modals === "undefined" || typeof Modals.market_select_tradable_item !== "function") return;
this._mktModalHooked = true;
const orig = Modals.market_select_tradable_item;
const self = this;
Modals.market_select_tradable_item = function (is_sell) {
if (!is_sell && self.active) {
self.mktCatalogArrived();
return;
}
return orig.apply(this, arguments);
};
}
mktCatalogReady() {
return !!(typeof Market !== "undefined" && Market.tradables && Market.tradables.length);
}
mktCatalogArrived() {
if (!this.mktCatalogReady()) return;
if (this._mktPoll) { clearInterval(this._mktPoll); this._mktPoll = null; }
this._mktCatalogRequested = false;
document.body.classList.remove("mob-mkt-loading");
this.dismissBrowsePicker();
if (this._mktLoading) {
this._mktLoading = false;
try { this.renderMarketHome(); } catch (e) { this.reportError("market-catalog", e); }
}
}
dismissBrowsePicker() {
try {
const m = document.getElementById("modal-market-select-item");
if (m && m.classList.contains("show") && typeof jQuery !== "undefined") {
jQuery(m).modal("hide");
}
} catch (e) {}
}
loadMarketCatalog() {
if (this.mktCatalogReady()) { this.mktCatalogArrived(); return; }
this.hookMarketModal();
if (this._mktPoll) return;
document.body.classList.add("mob-mkt-loading");
const self = this;
let tries = 0;
const attempt = function () {
if (self.mktCatalogReady()) { self.mktCatalogArrived(); return; }
if (tries % 4 === 0) {
self._mktCatalogRequested = true;
try { if (typeof websocket !== "undefined" && websocket.send) websocket.send("MARKET_BROWSE_BUTTON_CLICKED"); } catch (e) {}
}
tries++;
if (tries > 24) {
clearInterval(self._mktPoll);
self._mktPoll = null;
document.body.classList.remove("mob-mkt-loading");
self.dismissBrowsePicker();
self.showMarketCatalogError();
}
};
attempt();
this._mktPoll = setInterval(attempt, 500);
}
showMarketCatalogError() {
const body = this._mktBody;
if (!body || !this._mktLoading) return;
body.innerHTML = "";
const wrap = document.createElement("div");
wrap.className = "mob-mkt-empty";
const msg = document.createElement("div");
msg.textContent = "Couldn't load the market catalog.";
wrap.appendChild(msg);
const btn = document.createElement("button");
btn.className = "mob-mkt-allbtn";
btn.style.maxWidth = "220px";
btn.style.marginTop = "10px";
btn.textContent = "RETRY";
const self = this;
btn.addEventListener("click", function () { self._mktCatalogRequested = false; self.renderMarketHome(); });
wrap.appendChild(btn);
body.appendChild(wrap);
}
mktFetch(item) {
try {
const user = (typeof Items !== "undefined" && Items.getItemString) ? Items.getItemString("username") : "";
return jQuery.get("../../market/browse/" + item + "/?username=" + user).then((d) => d || []);
} catch (e) {
return Promise.resolve([]);
}
}
arrangeMarket() {
if (!this.active) return;
const panel = document.getElementById("panel-player-market");
if (!panel || panel.dataset.mobArranged) return;
panel.dataset.mobArranged = "1";
this._mkt = { tab: "buy" };
const shell = document.createElement("div");
shell.id = "mob-market";
shell.className = "mob-shell";
const tabs = document.createElement("div");
tabs.id = "mob-market-tabs";
[["buy", "BUY"], ["sell", "SELL"], ["history", "HISTORY"]].forEach((def) => {
const b = document.createElement("button");
b.className = "mob-mkt-tab";
b.textContent = def[1];
b.addEventListener("click", () => this.marketTab(def[0]));
this._mktTabBtns[def[0]] = b;
tabs.appendChild(b);
});
shell.appendChild(tabs);
const buy = document.createElement("div");
buy.id = "mob-market-buy";
const search = document.createElement("input");
search.className = "mob-mkt-search";
search.type = "search";
search.placeholder = "Search items…";
search.addEventListener("input", () => {
const q = search.value.trim().toLowerCase();
if (!q) this.renderMarketHome(); else this.renderMarketSearch(q);
});
buy.appendChild(search);
this._mktSearchEl = search;
const buyBody = document.createElement("div");
buyBody.id = "mob-market-buy-body";
buy.appendChild(buyBody);
this._mktBody = buyBody;
shell.appendChild(buy);
const sell = document.createElement("div");
sell.id = "mob-market-sell";
for (let i = 1; i <= 3; i++) {
["empty", "occupied"].forEach((k) => {
const el = document.getElementById("player-market-slot-" + k + "-" + i);
if (el) sell.appendChild(el);
});
}
shell.appendChild(sell);
panel.insertBefore(shell, panel.firstChild);
this.hookMarketModal();
this.marketTab("buy");
this.renderMarketHome();
}
marketTab(tab) {
if (tab === "history") {
if (typeof websocket !== "undefined") websocket.send("SHOW_BUY_SELL_MARKET_HISTORY");
return;
}
this._mkt.tab = tab;
Object.keys(this._mktTabBtns).forEach((k) => this._mktTabBtns[k].classList.toggle("active", k === tab));
const buy = document.getElementById("mob-market-buy");
const sell = document.getElementById("mob-market-sell");
if (buy) buy.style.display = tab === "buy" ? "" : "none";
if (sell) sell.style.display = tab === "sell" ? "" : "none";
}
mktCrumb(title, onBack) {
const bar = document.createElement("div");
bar.className = "mob-mkt-crumb";
const back = document.createElement("button");
back.className = "mob-mkt-back";
back.textContent = "←";
back.addEventListener("click", onBack);
const t = document.createElement("span");
t.className = "mob-mkt-crumb-title";
t.textContent = title;
bar.appendChild(back);
bar.appendChild(t);
return bar;
}
mktItemRow(item, onClick) {
const row = document.createElement("div");
row.className = "mob-mkt-row";
const img = document.createElement("img");
img.className = "mob-mkt-icon";
img.src = "https://cdn.idle-pixel.com/images/" + item + ".png";
img.alt = "";
const main = document.createElement("div");
main.className = "mob-mkt-main";
const name = document.createElement("div");
name.className = "mob-mkt-name";
name.textContent = this.mktPretty(item);
main.appendChild(name);
const chev = document.createElement("span");
chev.className = "mob-mkt-chev";
chev.textContent = "›";
row.appendChild(img);
row.appendChild(main);
row.appendChild(chev);
row.addEventListener("click", onClick);
return row;
}
mktListingRow(r, cheapest, showName) {
const row = document.createElement("div");
row.className = "mob-mkt-row" + (cheapest ? " mob-mkt-cheapest" : "");
const yours = !!(typeof Items !== "undefined" && Items.getItem && String(Items.getItem("player_id")) === String(r.player_id));
if (yours) row.className += " mob-mkt-yours";
const img = document.createElement("img");
img.className = "mob-mkt-icon";
img.src = "https://cdn.idle-pixel.com/images/" + r.market_item_name + ".png";
img.alt = "";
const main = document.createElement("div");
main.className = "mob-mkt-main";
const name = document.createElement("div");
name.className = "mob-mkt-name";
const sub = document.createElement("div");
sub.className = "mob-mkt-sub";
if (showName) {
name.textContent = this.mktPretty(r.market_item_name);
sub.textContent = this.mktFmt(r.market_item_amount) + " available · " + this.mktExpire(r.market_item_post_timestamp);
} else {
name.textContent = this.mktFmt(r.market_item_amount) + " available";
sub.textContent = (yours ? "Your offer · " : "") + this.mktExpire(r.market_item_post_timestamp);
}
main.appendChild(name);
main.appendChild(sub);
const price = document.createElement("div");
price.className = "mob-mkt-price";
price.innerHTML = '<img src="https://cdn.idle-pixel.com/images/coins.png" width="12" style="vertical-align:-1px"> ' + this.mktPrice(r.market_item_price_each) + " <small>each</small>";
row.appendChild(img);
row.appendChild(main);
row.appendChild(price);
row.addEventListener("click", () => {
try {
if (typeof Modals !== "undefined" && Modals.market_purchase_item) {
Modals.market_purchase_item(r.market_id, r.market_item_name, r.market_item_amount, r.market_item_price_each);
}
} catch (e) { this.reportError("market-buy", e); }
});
return row;
}
renderMarketHome() {
const body = this._mktBody;
if (!body) return;
body.innerHTML = "";
const cats = this.mktCategories();
const grid = document.createElement("div");
grid.className = "mob-mkt-cats";
MARKET_CATEGORIES.forEach((cat) => {
const items = cats[cat];
if (!items || !items.length) return;
const tile = document.createElement("div");
tile.className = "mob-mkt-cat";
tile.style.backgroundColor = this.mktColor(cat);
const label = document.createElement("span");
label.textContent = cat.replace(/_/g, " ");
const cnt = document.createElement("small");
cnt.textContent = items.length + " items";
tile.appendChild(label);
tile.appendChild(cnt);
tile.addEventListener("click", () => this.openMarketCategory(cat));
grid.appendChild(tile);
});
if (!grid.children.length) {
const e = document.createElement("div");
e.className = "mob-mkt-empty";
e.textContent = "Loading market catalog…";
body.appendChild(e);
this._mktLoading = true;
this.loadMarketCatalog();
return;
}
body.appendChild(grid);
}
renderMarketSearch(q) {
const body = this._mktBody;
if (!body) return;
body.innerHTML = "";
const t = (typeof Market !== "undefined" && Market.tradables) || [];
const seen = {};
const matches = t.filter((x) => {
if (seen[x.item]) return false;
seen[x.item] = 1;
return x.item.toLowerCase().indexOf(q) !== -1 || this.mktPretty(x.item).toLowerCase().indexOf(q) !== -1;
}).slice(0, 40);
if (!matches.length) {
const e = document.createElement("div");
e.className = "mob-mkt-empty";
e.textContent = 'No items match "' + q + '"';
body.appendChild(e);
return;
}
matches.forEach((x) => body.appendChild(this.mktItemRow(x.item, () => this.openMarketItem(x.item, "search"))));
}
openMarketCategory(cat) {
const body = this._mktBody;
if (!body) return;
body.innerHTML = "";
body.appendChild(this.mktCrumb(cat.replace(/_/g, " "), () => this.renderMarketHome()));
const allBtn = document.createElement("button");
allBtn.className = "mob-mkt-allbtn";
allBtn.textContent = "VIEW ALL LISTINGS IN " + cat.replace(/_/g, " ").toUpperCase();
allBtn.addEventListener("click", () => this.openMarketCategoryAll(cat));
body.appendChild(allBtn);
const items = this.mktCategories()[cat] || [];
items.forEach((item) => body.appendChild(this.mktItemRow(item, () => this.openMarketItem(item, "category:" + cat))));
}
openMarketItem(item, from) {
const body = this._mktBody;
if (!body) return;
body.innerHTML = "";
const back = (from && from.indexOf("category:") === 0)
? () => this.openMarketCategory(from.slice(9))
: () => {
const q = this._mktSearchEl ? this._mktSearchEl.value.trim().toLowerCase() : "";
if (q) this.renderMarketSearch(q); else this.renderMarketHome();
};
body.appendChild(this.mktCrumb(this.mktPretty(item), back));
const trend = document.createElement("a");
trend.className = "mob-mkt-trend";
trend.target = "_blank";
trend.href = "https://data.idle-pixel.com/market/?item=" + item + "&range=7d";
trend.innerHTML = '<img src="https://cdn.idle-pixel.com/images/stocks.png" width="14"> 7-day price trend';
body.appendChild(trend);
const loading = document.createElement("div");
loading.className = "mob-mkt-empty";
loading.textContent = "Loading offers…";
body.appendChild(loading);
const self = this;
this.mktFetch(item).then((rows) => {
if (loading.parentElement) loading.remove();
rows = (rows || []).slice().sort((a, b) => a.market_item_price_each - b.market_item_price_each);
if (!rows.length) {
const e = document.createElement("div");
e.className = "mob-mkt-empty";
e.textContent = "No offers for " + self.mktPretty(item) + " right now.";
body.appendChild(e);
return;
}
rows.forEach((r, idx) => body.appendChild(self.mktListingRow(r, idx === 0, false)));
}).catch((e) => { if (loading.parentElement) loading.textContent = "Couldn't load offers."; self.reportError("market-fetch", e); });
}
openMarketCategoryAll(cat) {
const body = this._mktBody;
if (!body) return;
body.innerHTML = "";
body.appendChild(this.mktCrumb(cat.replace(/_/g, " ") + " · all listings", () => this.openMarketCategory(cat)));
const loading = document.createElement("div");
loading.className = "mob-mkt-empty";
loading.textContent = "Loading listings…";
body.appendChild(loading);
const items = this.mktCategories()[cat] || [];
const self = this;
Promise.all(items.map((it) => this.mktFetch(it).catch(() => []))).then((all) => {
if (loading.parentElement) loading.remove();
let rows = [];
all.forEach((a) => { rows = rows.concat(a || []); });
rows.sort((a, b) => a.market_item_price_each - b.market_item_price_each);
if (!rows.length) {
const e = document.createElement("div");
e.className = "mob-mkt-empty";
e.textContent = "No live listings in " + cat.replace(/_/g, " ") + ".";
body.appendChild(e);
return;
}
rows.forEach((r) => body.appendChild(self.mktListingRow(r, false, true)));
});
}
hookToasts() {
if (this._origScrollText || typeof Animations === "undefined" || typeof Animations.scrollText !== "function") return;
const orig = Animations.scrollText;
this._origScrollText = orig;
const self = this;
Animations.scrollText = function (icon, color, text) {
if (self.active) {
try { self.showToast(icon, color, text); } catch (e) { console.error("[mobile-ui] toast:", e); }
return;
}
return orig.call(this, icon, color, text);
};
}
hookModals() {
if (this._modalObserver) return;
const tagIfDeclaredBig = (m) => {
if (m.classList.contains("mob-sheet-modal")) return;
if (m.querySelector(".modal-dialog.modal-xl, .modal-dialog.modal-lg, .modal-xxl, .modal-xxxl")
|| SHEET_MODAL_IDS.indexOf(m.id) !== -1) {
m.classList.add("mob-sheet-modal");
}
};
document.querySelectorAll(".modal").forEach(tagIfDeclaredBig);
this._modalObserver = new MutationObserver((muts) => {
for (const mu of muts) {
const m = mu.target;
if (!m.classList || !m.classList.contains("modal")) continue;
if (!m.classList.contains("show")) {
m._mobSeen = false;
continue;
}
if (m._mobSeen) continue;
m._mobSeen = true;
tagIfDeclaredBig(m);
if (m.classList.contains("mob-sheet-modal")) continue;
requestAnimationFrame(() => {
if (!this.active || !m.classList.contains("show")) return;
if (m.classList.contains("mob-sheet-modal")) return;
try {
const content = m.querySelector(".modal-content");
if (content && content.offsetHeight > document.documentElement.clientHeight * 0.55) {
m.classList.add("mob-sheet-modal");
}
} catch (e) { }
});
}
});
this._modalObserver.observe(document.body, { subtree: true, attributes: true, attributeFilter: ["class"] });
}
showToast(icon, color, text) {
let host = document.getElementById("mob-toasts");
if (!host) {
host = document.createElement("div");
host.id = "mob-toasts";
host.className = "mob-shell";
document.body.appendChild(host);
}
const t = document.createElement("div");
t.className = "mob-toast";
if (icon && icon !== "none") {
const im = document.createElement("img");
im.src = "https://cdn.idle-pixel.com/" + icon;
im.alt = "";
t.appendChild(im);
}
const span = document.createElement("span");
span.textContent = String(text == null ? "" : text).replace(/<[^>]*>/g, "");
if (color && color !== "none") span.style.color = color;
t.appendChild(span);
host.appendChild(t);
while (host.children.length > 3) host.removeChild(host.firstChild);
setTimeout(function () {
t.classList.add("mob-toast-out");
setTimeout(function () { t.remove(); }, 400);
}, 3200);
}
setActiveTab(panelId) {
if (!this.tabs.length) return;
const idx = TAB_PANEL_MAP[panelId];
this.tabs.forEach((t, i) => t.classList.toggle("active", i === idx));
}
}
if (looksMobile()) setMobileViewport(true);
IdlePixelPlus.registerPlugin(new MobileUIPlugin());
})();