// ==UserScript== // @name Tribes of Malaya — Worker Overlay // @namespace https://war.add.ph // @version 1.7.5 // @description Shows worker assignments, idle workers, and construction status as an overlay // @match https://war.add.ph/my/town/* // @match https://war2.add.ph/my/town/* // @match https://s1.tribesofmalaya.com/my/town/* // @match https://s1.tribesofmalaya.com/* // @match https://*.tribesofmalaya.com/* // @grant none // @run-at document-start // @updateURL https://raw.githubusercontent.com/margibs/tom-overlay/main/tribes-of-malaya-overlay.user.js // @downloadURL https://raw.githubusercontent.com/margibs/tom-overlay/main/tribes-of-malaya-overlay.user.js // ==/UserScript== !function(){"use strict";let t=null,e=null,n=300,o=null,a=null;const s=[],r=[],i=new Set,l=/Nag-aalok ako ng .+ sa Market!/,c=/\[item:([a-z_]+):(\d+)\]/g;function d(t){const e=t&&t.append&&t.append.global_messages;if(!Array.isArray(e)||0===e.length)return!1;let n=!1;for(const t of e){if(!t||!t.message||i.has(t.id))continue;if(!l.test(t.message))continue;const e=[];let o;for(c.lastIndex=0;null!==(o=c.exec(t.message));)e.push({slug:o[1],qty:parseInt(o[2],10)});if(e.length<2)continue;const a=t.message.match(/Nag-aalok ako ng .+? x\s*([\d,]+)\s+para sa .+? x\s*([\d,]+)/),s=a?parseInt(a[1].replace(/,/g,""),10):e[0].qty,d=a?parseInt(a[2].replace(/,/g,""),10):e[1].qty;i.add(t.id),r.unshift({id:t.id,username:t.username,timestamp:t.timestamp,emblem:t.emblem||null,offerSlug:e[0].slug,offerQty:s,takerSlug:e[1].slug,takerQty:d,raw:t.message}),n=!0}if(r.length>200){const t=r.splice(200);for(const e of t)i.delete(e.id)}return n}let m={};function p(t,e,n,o){return n&&o<=0?"#fb923c":e&&!n?"#fff":e&&n?"#fb923c":t>=.75?"#4ade80":t>=.4?"#fb923c":"#ef4444"}function u(t){const e=t.match(/(\d+)$/);return e?parseInt(e[1]):1}const f=new Set(["food","wood","mineral"]),g=[{name:"Leather",slug:"leather",yield:3,time:24,ingredients:[{slug:"food",qty:30}]},{name:"Lumber",slug:"lumber",yield:30,time:24,ingredients:[{slug:"wood",qty:3}]},{name:"Sticks",slug:"stick",yield:60,time:24,ingredients:[{slug:"wood",qty:3}]},{name:"Tent",slug:"tent",yield:2,time:32,ingredients:[{slug:"leather",qty:2}]},{name:"Iron Nugget",slug:"iron_nugget",yield:2,time:32,ingredients:[{slug:"mineral",qty:20}]},{name:"Stone Axe",slug:"stone_axe",yield:1,time:24,ingredients:[{slug:"stick",qty:2},{slug:"mineral",qty:1}]},{name:"Sword",slug:"sword",yield:2,time:32,ingredients:[{slug:"wood",qty:10},{slug:"iron_nugget",qty:10}]},{name:"Gun",slug:"gun",yield:2,time:32,ingredients:[{slug:"wood",qty:20},{slug:"iron_nugget",qty:8}]},{name:"Coconut Charcoal",slug:"coconut_charcoal",yield:5,time:40,ingredients:[{slug:"wood",qty:50}]},{name:"Steel Nugget",slug:"steel_nugget",yield:5,time:40,ingredients:[{slug:"iron_nugget",qty:5},{slug:"coconut_charcoal",qty:5}]},{name:"Steel Sword",slug:"sword2",yield:1,time:32,ingredients:[{slug:"steel_nugget",qty:5},{slug:"wood",qty:5}]},{name:"Spear",slug:"spear",yield:1,time:40,ingredients:[{slug:"wood",qty:20},{slug:"mineral",qty:20}]},{name:"Steel Spear",slug:"spear2",yield:1,time:32,ingredients:[{slug:"steel_nugget",qty:2},{slug:"wood",qty:10}]},{name:"Steel Gun",slug:"gun2",yield:1,time:32,ingredients:[{slug:"steel_nugget",qty:4},{slug:"wood",qty:10}]},{name:"Bow and Arrow",slug:"bow_and_arrow",yield:1,time:30,ingredients:[{slug:"wood",qty:8},{slug:"stick",qty:40}]},{name:"Composite Bow and Arrow",slug:"bow_and_arrow2",yield:1,time:40,ingredients:[{slug:"wood",qty:8},{slug:"stick",qty:40},{slug:"iron_nugget",qty:2}]},{name:"Gold Coin",slug:"gold_coin",yield:5,time:46,ingredients:[{slug:"gold_dust",qty:25}]},{name:"Salt",slug:"salt",yield:1,time:40,ingredients:[{slug:"food",qty:5e3}]},{name:"Tiula Itum",slug:"tiula_itum",yield:1,time:90,ingredients:[{slug:"food",qty:5e3},{slug:"salt",qty:1}]},{name:"Inasal",slug:"inasal",yield:1,time:90,ingredients:[{slug:"food",qty:5e3},{slug:"salt",qty:1}]},{name:"Adobo",slug:"adobo",yield:1,time:90,ingredients:[{slug:"food",qty:5e3},{slug:"salt",qty:1}]},{name:"Gold Dust",slug:"gold_dust",yield:3,time:50,ingredients:[{slug:"mineral",qty:60}]}],b={salt:"sugbuanon",gold_dust:"taga_ilog",coconut_charcoal:"tausug",tiula_itum:"tausug",inasal:"sugbuanon",adobo:"taga_ilog"};let y,x=null;function h(){return y===e||(y=e,x=e?new Set(Object.entries(b).filter(([,t])=>t!==e).map(([t])=>t)):new Set(Object.keys(b))),x}function v(t){return!h().has(t)}const w={};for(const t of g)w[t.slug]=t;const $=new Map;function k(t){const e=$.get(t.slug);if(e)return e;const n={};for(const e of t.ingredients){const t=w[e.slug];if(f.has(e.slug)||!t)n[e.slug]=(n[e.slug]||0)+e.qty;else{const o=e.qty/t.yield,a=k(t);for(const[t,e]of Object.entries(a))n[t]=(n[t]||0)+e*o}}return $.set(t.slug,n),n}function S(t){const e=[];for(const n of t.ingredients)if(!f.has(n.slug)&&w[n.slug]){const t=w[n.slug];if(!v(n.slug)){e.push({recipe:t,crafts:0,needed:n.qty,external:!0});continue}const o=Math.ceil(n.qty/t.yield),a=S(t);for(const t of a)e.push({...t,crafts:t.crafts*o});e.push({recipe:t,crafts:o,needed:n.qty,external:!1})}return e}function _(t,e){if(!t.ingredients.length)return 0;let n=1/0;for(const o of t.ingredients){const t=e[o.slug]||0;n=Math.min(n,Math.floor(t/o.qty))}return n===1/0?0:n}function q(t,e=1){let n=t.time*e;for(const o of t.ingredients){if(f.has(o.slug)||!v(o.slug))continue;const t=w[o.slug];if(!t)continue;n+=q(t,o.qty*e/t.yield)}return n}const C={food:.25,wood:.3125,mineral:.3125},E=[{name:"Food",slug:"food"},{name:"Wood",slug:"wood"},{name:"Mineral",slug:"mineral"},{name:"Gold Dust",slug:"gold_dust"},{name:"Gold Coin",slug:"gold_coin"},...g.map(t=>({name:t.name,slug:t.slug}))];function L(t,e){if(void 0!==C[t])return{wm:e*C[t],base:{[t]:e},craftSecs:0};const n=w[t];if(!n)return null;const o=e/n.yield,a=k(n),s={};for(const[t,e]of Object.entries(a))s[t]=e*o;const r=Object.entries(s).reduce((t,[e,n])=>t+(C[e]||0)*n,0),i=q(n)*o,l=i/60;return{wm:r+l,matWm:r,craftWm:l,craftSecs:i,base:s}}function M(t){return Object.entries(t).map(([t,e])=>`${Math.round(e).toLocaleString()} ${t.replace(/_/g," ")}`).join(" + ")||"—"}function I(e){t=e,s.forEach(t=>t(e))}const z=window.fetch;window.fetch=async function(t,e={}){const o="string"==typeof t?t:t?.url||"",s=await z.apply(this,[t,e]);return/\/my\/town\/\d+(?:\?|$)/.test(o)&&s.clone().json().then(t=>{if(t&&t.tiles&&t.populations){const e=t.last_food_production_time;if(e){const t=window._tomLastFoodTime;t&&e!==t&&(n=e-t),window._tomLastFoodTime=e}I(t)}}).catch(t=>console.warn("[TOM]",t)),/\/api\/updates(?:\?|$)/.test(o)&&s.clone().json().then(t=>{d(t)&&W()}).catch(()=>{}),/\/buildings\/\d+\/trades/.test(o)&&s.clone().json().then(t=>{t&&t.items&&t.meta&&(a=t,Z())}).catch(t=>console.warn("[TOM]",t)),s};const T=XMLHttpRequest.prototype.open,O=XMLHttpRequest.prototype.send;function B(t){const e=t.match(/^(.+?)(\d+)$/);if(e){return e[1].replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase()).trim()+" Lv"+e[2]}return t.replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase())}function A(t){const e=t.replace(/\d+$/,"");return["farmer","woodcutter","miner"].includes(e)?"resource":["barracks","training_grounds","archery_grounds"].includes(e)?"military":["crafter"].includes(e)?"crafting":"infrastructure"}function F(t){t.owner?.tribe&&!e&&(e=t.owner.tribe.toLowerCase(),$.clear(),x=null,y=void 0);const n={name:t.name,population:t.populations?.find(t=>"commoner"===t.type)?.quantity??t.population,populationCapacity:t.population_capacity,morale:t.morale,troopsCapacity:t.troops_capacity||0,troops:t.populations.filter(t=>"commoner"!==t.type).reduce((t,e)=>t+e.quantity,0)},o=t=>(t?.crafting_quantity_withheld||0)+(t?.market_quantity_withheld||0)+(t?.server_withheld||0),a={food:t.items?.food?.quantity||0,wood:t.items?.wood?.quantity||0,mineral:t.items?.mineral?.quantity||0},s={food:o(t.items?.food),wood:o(t.items?.wood),mineral:o(t.items?.mineral)},r={food:t.capacities?.food||0,wood:t.capacities?.wood||0,mineral:t.capacities?.mineral||0},i=t.production||{food:0,wood:0,mineral:0},l={};if(t.items)for(const[e,n]of Object.entries(t.items)){const t=(n.crafting_quantity_withheld||0)+(n.market_quantity_withheld||0)+(n.server_withheld||0);l[e]=Math.max(0,(n.quantity||0)-t)}const c=t.populations.map(t=>{const e={type:t.type,label:t.name||(n=t.type,n.replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase())),idle:t.idle_quantity,assigned:t.assigned_quantity,training:t.training_quantity,total:t.quantity,category:t.category,trainingCost:t.items||null,trainable:null};var n;return t.items&&Object.keys(t.items).length>0&&(e.trainable=Math.min(...Object.entries(t.items).map(([t,e])=>Math.floor((l[t]||0)/e)))),e}),d=c.filter(t=>t.idle>0),m=d.reduce((t,e)=>t+e.idle,0),p=[],u=[],f=[],g={};for(const e of t.tiles){if(!e.building)continue;const t=e.building,n={id:t.id,slug:t.slug,label:B(t.slug),category:A(t.slug),x:e.x,y:e.y,assignees:t.assignee_count,builders:t.builders_count};f.push(n),t.assignee_count>0&&p.push(n),t.builders_count>0&&u.push(n),g[t.id]={id:t.id,x:e.x,y:e.y,slug:t.slug,builders:t.builders_count}}return p.sort((t,e)=>e.assignees-t.assignees),{summary:n,resources:a,withheld:s,capacities:r,production:i,allPopulations:c,idleWorkers:d,totalIdle:m,allBuildings:f,assignedBuildings:p,underConstruction:u,buildingMap:g}}function N(t){const e=Math.floor(t/3600),n=Math.floor(t%3600/60),o=t%60;return[e&&`${e}h`,n&&`${n}m`,o&&`${o}s`].filter(Boolean).join(" ")||"0s"}function j(e){let o=document.getElementById("tom-overlay");if(!o){o=document.createElement("div"),o.id="tom-overlay",o.innerHTML='\n
\n
\n Overview\n Crafting\n Trade\n Market\n Offers 0\n
\n v1.7.5\n \n
\n
\n
\n
\n
\n
\n
\n
\n ',document.body.appendChild(o),function(t){const e=t.querySelector("#tom-overlay-header");let n=!1,o=0,a=0;e.addEventListener("mousedown",e=>{n=!0,o=e.clientX-t.getBoundingClientRect().left,a=e.clientY-t.getBoundingClientRect().top,t.style.transition="none"}),document.addEventListener("mousemove",e=>{n&&(t.style.left=e.clientX-o+"px",t.style.top=e.clientY-a+"px",t.style.right="auto",t.style.bottom="auto")}),document.addEventListener("mouseup",()=>{n=!1})}(o),document.getElementById("tom-toggle").addEventListener("click",()=>{const t=document.getElementById("tom-overlay-body"),e=document.getElementById("tom-toggle");t.classList.toggle("collapsed");const n=t.classList.contains("collapsed");e.textContent=n?"▸":"▾",n&&(o.style.height="auto")}),o.querySelectorAll(".tom-tab").forEach(t=>{t.addEventListener("click",()=>{o.querySelectorAll(".tom-tab").forEach(t=>t.classList.remove("active")),o.querySelectorAll(".tom-tab-content").forEach(t=>t.classList.remove("active")),t.classList.add("active"),document.getElementById("tom-tab-"+t.dataset.tab).classList.add("active")})});const t=E.map(t=>``).join("");document.getElementById("tom-tab-trade").innerHTML=`\n
\n
Trade Evaluator
\n
\n GET\n \n \n
\n
\n GIVE\n \n \n
\n \n
\n
\n `,document.getElementById("tom-trade-eval").addEventListener("click",()=>{const t=document.getElementById("tom-trade-get-item").value,e=document.getElementById("tom-trade-give-item").value,n=Math.max(1,parseInt(document.getElementById("tom-trade-get-qty").value)||1),o=Math.max(1,parseInt(document.getElementById("tom-trade-give-qty").value)||1),a=L(t,n),s=L(e,o),r=document.getElementById("tom-trade-result"),i=L(t,1)?.wm??0,l=L(e,1)?.wm??0;let c="";if(i>0&&l>0){let n,o,a,s;i>=l?(n=1,o=Math.round(i/l),a=Math.round(i),s=Math.round(L(e,o).wm)):(n=Math.round(l/i),o=1,a=Math.round(L(t,n).wm),s=Math.round(l));const r=t=>E.find(e=>e.slug===t)?.name??t;c=`
Fair 1:1 — ${r(t)} × ${n.toLocaleString()} (${a.toLocaleString()} wm) ↔ ${r(e)} × ${o.toLocaleString()} (${s.toLocaleString()} wm)
`}if(!a||!s)return void(r.innerHTML='
⚠ One or more items have unknown value (gold dust has no production cost).
');const d=s.wm>0?a.wm/s.wm:1/0,m=(100*(d-1)).toFixed(0);let p,u;d>=1.3?(p="✅ Great Deal",u="tom-trade-great"):d>=1.1?(p="✅ Good Deal",u="tom-trade-great"):d>=.9?(p="🟢 Fair",u="tom-trade-fair"):d>=.8?(p="🟡 Risky",u="tom-trade-risky"):(p="🔴 Bad Deal",u="tom-trade-bad");const f=d>=1?`${d.toFixed(2)}x in your favor (+${m}%)`:`${(1/d).toFixed(2)}x against you (${m}%)`;r.innerHTML=`\n
\n
\n GET\n ${n.toLocaleString()} × ${E.find(e=>e.slug===t)?.name}\n
\n
Materials: ${M(a.base)}
\n ${a.craftSecs>0?`
Craft time: ${N(a.craftSecs)} → ${a.craftWm.toFixed(1)} wm
`:""}\n
Total: ${Math.round(a.wm).toLocaleString()} wm${a.craftSecs>0?` (${Math.round(a.matWm)} mat + ${Math.round(a.craftWm)} time)`:""}
\n
\n GIVE\n ${o.toLocaleString()} × ${E.find(t=>t.slug===e)?.name}\n
\n
Materials: ${M(s.base)}
\n ${s.craftSecs>0?`
Craft time: ${N(s.craftSecs)} → ${s.craftWm.toFixed(1)} wm
`:""}\n
Total: ${Math.round(s.wm).toLocaleString()} wm${s.craftSecs>0?` (${Math.round(s.matWm)} mat + ${Math.round(s.craftWm)} time)`:""}
\n
\n ${p} — ${f}\n
\n ${c}\n
\n `})}const{summary:s,resources:r,withheld:i,capacities:l,production:c,allPopulations:d,allBuildings:m,idleWorkers:p,totalIdle:u,assignedBuildings:f,underConstruction:y}=e;let x="";const v=s.population+2*s.troops,w=c.food-v,$=w>0,C=$?"#4ade80":"#ef4444",I=$?"Growing":"Halted",z=`Prod ${c.food.toLocaleString()} vs Cons ${v.toLocaleString()} (${s.population} + ${s.troops}×2)`;x+=`
\n Pop ${s.population}/${s.populationCapacity}\n Troops ${s.troops}/${s.troopsCapacity}\n Morale ${s.morale}%\n Growth ${I}\n
`;const T=w>0?"+":"",O=w>0?"#4ade80":w<0?"#ef4444":"#fff";let B="";if(w<0){const t=r.food/Math.abs(w)*n/3600;B=t<1?`${Math.round(60*t)}m`:`${t.toFixed(1)}h`}else if(w>0&&r.food\n Cons ${v.toLocaleString()}\n Prod ${c.food.toLocaleString()}\n Net ${T}${w.toLocaleString()}/tick\n ${w<0&&B?`Depletes ${B}`:""}\n ${w>0&&r.food>=l.food?'Storage FULL':""}\n ${w>0&&r.foodFull in ${B}`:""}\n `;const A=t=>Math.round(3600*t/n),F=(t,e,n,o,a,s)=>{const r=n-o,i=o>0?` (-${o.toLocaleString()})`:"";return`
\n ${t}\n ${r.toLocaleString()}${i} +${A(s).toLocaleString()}/hr${((t,e,n)=>{const o=A(n);if(o<=0)return"";if(t>=e)return" FULL";const a=(e-t)/o;return a<1?` (full in ${Math.round(60*a)}m)`:` (full in ${a.toFixed(1)}h)`})(n,a,s)}\n
`};if(x+='
',x+='
Resources
',x+=F("Food","tom-res-food",r.food,i.food,l.food,w),x+=F("Wood","tom-res-wood",r.wood,i.wood,l.wood,c.wood),x+=F("Mineral","tom-res-mineral",r.mineral,i.mineral,l.mineral,c.mineral),x+="
",d.length>0){x+='
',x+='
Population
';for(const t of d){const e=t.idle>0,n=null!==t.trainable?`
Can train: ${t.trainable}${t.trainingCost?` (${Object.entries(t.trainingCost).map(([t,e])=>`${e} ${t}`).join(", ")})`:""}
`:"";x+=`
\n
${t.label}${t.total}
\n
\n Idle ${t.idle}\n Assigned ${t.assigned}\n Training ${t.training}\n
\n ${n}\n
`}x+="
"}const j={};for(const t of m){const e=t.slug.replace(/\d+$/,"").replace(/_$/,""),n=e.replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase());j[e]||(j[e]={label:n,category:t.category,count:0}),j[e].count++}const G={resource:"Resource",military:"Military",crafting:"Crafting",infrastructure:"Infra"},U={resource:"#22c55e",military:"#ef4444",crafting:"#a855f7",infrastructure:"#3b82f6"},H={};for(const t of Object.values(j))H[t.category]||(H[t.category]={items:[],total:0}),H[t.category].items.push(t),H[t.category].total+=t.count;for(const t of Object.values(H))t.items.sort((t,e)=>e.count-t.count);x+='
',x+=`
Buildings (${m.length})
`;for(const t of["resource","military","crafting","infrastructure"]){const e=H[t];if(!e)continue;const n=U[t]||"#888",o=G[t]||t,a=e.items.map(t=>`${t.label} ${t.count}`).join(" · ");x+=`
\n
${o} (${e.total})
\n
${a}
\n
`}if(x+="
",f.length>0){const t={};for(const e of f){const n=e.slug.replace(/\d+$/,""),o=n.replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase());t[n]||(t[n]={label:o,category:e.category,total:0,capacity:0}),t[n].total+=e.assignees}for(const e of m){const n=e.slug.replace(/\d+$/,"");if(!t[n])continue;const o=e.slug.match(/(\d+)$/);t[n].capacity+=o?parseInt(o[1]):1}const e=Object.values(t).sort((t,e)=>e.total-t.total);x+='
',x+='
Assigned Workers
';for(const t of e)x+=`
\n ${t.label}\n ${t.total}/${t.capacity}\n
`;x+="
"}if(y.length>0){x+='
',x+='
Under Construction
';for(const t of y)x+=`
\n ${t.label}(${t.x},${t.y})\n ${t.builders} builders\n
`;x+="
"}document.getElementById("tom-tab-overview").innerHTML=x;const D=t=>t.replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase()),P=t=>Object.entries(t).map(([t,e])=>{return`${e%1==0?e:e.toFixed(2)} ${D(t)}`;var n}).join(", "),R={...r};if(t?.items)for(const[e,n]of Object.entries(t.items)){const t=(n.crafting_quantity_withheld||0)+(n.market_quantity_withheld||0)+(n.server_withheld||0);R[e]=Math.max(0,(n.quantity||0)-t)}const V={};for(const t of g)for(const e of t.ingredients)V[e.slug]||(V[e.slug]=[]),V[e.slug].push({recipe:t,qty:e.qty});let J="";const X=h();for(const t of g){const e=X.has(t.slug),n=b[t.slug],o=k(t),a=S(t),s=_(t,R),r=s*t.yield,i=q(t),l=i/60;J+=`
`;const c=R[t.slug]||0;if(J+=`
\n ${t.name} (${t.yield}x)${c.toLocaleString()}\n ${null!=t.time?t.time+"s":"TBA"}\n
`,e){J+=`
Tribe Locked — ${n?n.replace(/_/g," "):"other tribe"} only
`}if(a.length>0){for(let t=0;t\n Acquire: ${e.needed} × ${e.recipe.name} (trade/buy — other tribe)\n
`;else{const n=e.recipe.ingredients.map(t=>`${t.qty*e.crafts} ${D(t.slug)}`).join(" + ");J+=`
\n Step ${t+1}: ${e.recipe.name} — ${e.crafts} craft${e.crafts>1?"s":""} (${n}) = ${e.crafts*e.recipe.yield} ${D(e.recipe.slug)}\n
`}}const e=t.ingredients.map(t=>`${t.qty} ${D(t.slug)}`).join(" + ");J+=`
\n Step ${a.length+1}: ${t.name} — ${e}\n
`}if(J+='
',J+=`
Total base cost: ${P(o)}
`,J+=`
Craft time: ${N(i)} total → ${l.toFixed(1)} wm
`,t.yield>1){const e={};for(const[n,a]of Object.entries(o))e[n]=a/t.yield;J+=`
Per unit: ${P(e)}
`}J+="
";J+=`Can craft: ${s}${r!==s?` (${r} units)`:""}`;const d=V[t.slug];if(d&&d.length>0){J+=`
Required by (${d.length})`;for(const e of d)J+=`
${e.recipe.name} — ${e.qty} ${t.name}
`;J+="
"}J+=""}const Q=document.getElementById("tom-tab-crafting"),Y=Q.querySelector("#tom-craft-search")?.value||"";Q.innerHTML=`\n
\n \n
\n
${J}
\n `;const K=document.getElementById("tom-craft-search"),Z=document.getElementById("tom-craft-list");function tt(t){const e=t.toLowerCase();Z.querySelectorAll(".tom-craft-card").forEach(t=>{const n=t.querySelector(".tom-craft-name")?.textContent?.toLowerCase()||"";t.style.display=n.includes(e)?"":"none"})}tt(Y),K.addEventListener("input",t=>tt(t.target.value)),function(){const t=document.getElementById("tom-tab-market");if(!t)return;const e=new Set;if(t.querySelectorAll('[id^="tom-market-detail-"]').forEach(t=>{"none"!==t.style.display&&e.add(t.id)}),!a)return void(t.innerHTML='
Open a market building to load listings.
');const{items:n,meta:o}=a,s=n.map(t=>{const e=L(t.offer_item,t.offer_quantity),n=L(t.taker_item,t.taker_quantity);return{trade:t,getVal:e,giveVal:n,ratio:e&&n&&n.wm>0?e.wm/n.wm:null}}),r=t=>t.replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase());let i='
';i+=`
Market Listings Page ${o.current_page}/${o.total_pages}
`;for(const{trade:t,getVal:e,giveVal:n,ratio:o}of s){let a,s,l;null===o?(a="tom-market-badge-unknown",s="?",l=""):o>=1.1?(a="tom-market-badge-good",s=o.toFixed(2)+"x",l="favor"):o>=.9?(a="tom-market-badge-good",s=o.toFixed(2)+"x",l="fair"):o>=.8?(a="tom-market-badge-fair",s=(1/o).toFixed(2)+"x",l="risky"):(a="tom-market-badge-bad",s=(1/o).toFixed(2)+"x",l="against");const c=e?Math.round(e.wm):"?",d=n?Math.round(n.wm):"?",m=`tom-market-detail-${t.id}`;if(i+=`
\n ${s}${l?`${l}`:""}\n
\n
GET ${t.offer_quantity.toLocaleString()} ${r(t.offer_item)} ${c} wm
\n
GIVE ${t.taker_quantity.toLocaleString()} ${r(t.taker_item)} ${d} wm
\n
\n
`,e&&n){const a=(100*(o-1)).toFixed(0),s=o>=1?`${o.toFixed(2)}x in your favor (+${a}%)`:`${(1/o).toFixed(2)}x against you (${a}%)`,l=o>=1.3||o>=1.1?"tom-trade-great":o>=.9?"tom-trade-fair":o>=.8?"tom-trade-risky":"tom-trade-bad",c=o>=1.3?"✅ Great Deal":o>=1.1?"✅ Good Deal":o>=.9?"🟢 Fair":o>=.8?"🟡 Risky":"🔴 Bad Deal";i+=``}}i+="
",t.innerHTML=i,e.forEach(t=>{const e=document.getElementById(t);e&&(e.style.display="block")})}(),W()}function W(){const t=document.getElementById("tom-tab-offers"),e=document.getElementById("tom-offers-count");if(e&&(e.textContent=r.length),!t)return;const n=`
\n \n Clear\n
`;if(0===r.length)return t.innerHTML=n+'
Waiting for chat market offers… (polled every ~5s)
',void G();const o=t=>t.replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase()),a=t=>{const e=new Date(1e3*t);return`${String(e.getHours()).padStart(2,"0")}:${String(e.getMinutes()).padStart(2,"0")}`};let s=n+'
';s+=`
Chat Market Offers ${r.length} captured
`;for(const t of r){const e=L(t.offerSlug,t.offerQty),n=L(t.takerSlug,t.takerQty),r=e&&n&&n.wm>0?e.wm/n.wm:null;let i,l;null===r?(i="tom-market-badge-unknown",l="?"):r>=1.1||r>=.9?(i="tom-market-badge-good",l=r.toFixed(2)+"x"):r>=.8?(i="tom-market-badge-fair",l=(1/r).toFixed(2)+"x"):(i="tom-market-badge-bad",l=(1/r).toFixed(2)+"x"),s+=`
\n ${l}\n
\n
${t.username} ${a(t.timestamp)}
\n
GET ${t.offerQty.toLocaleString()} ${o(t.offerSlug)}
\n
GIVE ${t.takerQty.toLocaleString()} ${o(t.takerSlug)}
\n
\n
`}s+="
",t.innerHTML=s,G()}function G(){const t=document.getElementById("tom-offers-hide");t&&t.addEventListener("change",t=>{return e=t.target.checked,localStorage.setItem(P,e?"1":"0"),void J();var e});const e=document.getElementById("tom-offers-clear");e&&e.addEventListener("click",()=>{r.length=0,i.clear(),W()})}XMLHttpRequest.prototype.open=function(t,e,...n){return this._tomUrl=e,this._tomMethod=t,T.call(this,t,e,...n)},XMLHttpRequest.prototype.send=function(...t){if(this._tomUrl&&/\/my\/town\/\d+(?:\?|$)/.test(this._tomUrl))this.addEventListener("load",function(){try{const t=JSON.parse(this.responseText);if(t&&t.tiles&&t.populations){const e=t.last_food_production_time;if(e){const t=window._tomLastFoodTime;t&&e!==t&&(n=e-t),window._tomLastFoodTime=e}I(t)}}catch(t){console.warn("[TOM]",t)}});else if(this._tomUrl&&/\/town-building-assignees/.test(this._tomUrl)&&"POST"===(this._tomMethod||"").toUpperCase()){let e=null;try{e=JSON.parse(t[0])}catch(t){console.warn("[TOM]",t)}e&&this.addEventListener("load",function(){try{const t=JSON.parse(this.responseText);if(t&&void 0!==t.quantity&&Q){const n=Q.allBuildings.find(t=>t.id==e.building_id);n&&(n.assignees=t.quantity);const o=Q.allPopulations.find(t=>t.type===e.population_type);o&&(o.idle=Math.max(0,o.idle-e.quantity)),K(),Z()}}catch(t){console.warn("[TOM]",t)}})}else this._tomUrl&&/\/buildings\/\d+/.test(this._tomUrl)&&"PATCH"===(this._tomMethod||"").toUpperCase()?this.addEventListener("load",function(){try{const t=JSON.parse(this.responseText);if(t&&t.building_id&&Q){const e=Q.allBuildings.find(e=>e.id==t.building_id);e&&(e.builders=1),Q.buildingMap[t.building_id]&&(Q.buildingMap[t.building_id].builders=1),K(),Z()}else if(t&&t.message){const e=t.message.match(/(\d+)\/(\d+).*building_queue/);e&&(o=parseInt(e[2]),Z())}}catch(t){console.warn("[TOM]",t)}}):this._tomUrl&&/\/api\/updates(?:\?|$)/.test(this._tomUrl)?this.addEventListener("load",function(){try{d(JSON.parse(this.responseText))&&W()}catch(t){}}):this._tomUrl&&/\/buildings\/\d+\/trades/.test(this._tomUrl)&&"GET"===(this._tomMethod||"").toUpperCase()&&this.addEventListener("load",function(){try{const t=JSON.parse(this.responseText);t&&t.items&&t.meta&&(a=t,Z())}catch(t){console.warn("[TOM]",t)}});return O.apply(this,t)};let U=0,H="";function D(t){const e=t.assignedBuildings.map(t=>`${t.id}:${t.assignees}`).join("|")+"~"+t.allPopulations.map(t=>`${t.type}:${t.idle}:${t.trainable}`).join("|");if(e===H)return;H=e,document.querySelectorAll(".tom-worker-label").forEach(t=>t.remove());const n=document.querySelector(".town-grid-content");if(!n)return void(U<20?(U++,setTimeout(()=>D(t),500)):console.log("[TOM Overlay] .town-grid-content not found after retries — badges disabled."));U=0;const o=m,a={};for(const e of t.assignedBuildings)a[`${e.x},${e.y}`]={assignees:e.assignees,category:e.category};const s=function(){try{const t=localStorage.getItem("persist:timer");if(!t)return new Set;const e=JSON.parse(t),n=JSON.parse(e.timers||"{}"),o=new Set;for(const t in n){const e=n[t];if(Array.isArray(e))for(const t of e)t?.callbackArgs?.recipeName&&t.callbackArgs.buildingId&&o.add(String(t.callbackArgs.buildingId))}return o}catch(t){return new Set}}();for(const e of t.allBuildings){const t=o[`${e.x},${e.y}`];if(!t)continue;const r=parseInt(t.left,10),i=parseInt(t.bottom,10),l=parseInt(t.top,10),c=u(e.slug),d=a[`${e.x},${e.y}`];if(!d||d.assignees<=0)continue;if(/^crafter/.test(e.slug||"")&&s.has(String(e.id)))continue;const m=document.createElement("div");m.className="tom-worker-label",m.style.position="absolute",m.style.pointerEvents="none",m.style.zIndex="2147483646",m.style.transform="translateX(-50%)",m.style.display="flex",m.style.flexDirection="column",m.style.alignItems="center";const f=d?.assignees||0,g=c>0?f/c:0,b=g>=1;let y="";f>0&&(y=b?"Full!":f+"/"+c);const x=p(g,b,!1,f),h=document.createElement("span");h.style.fontFamily="'Work Sans', system-ui, sans-serif",h.style.fontSize="7px",h.style.fontWeight="700",h.style.color=x,h.style.textShadow="0 1px 2px rgba(0,0,0,0.8), 0 0 4px rgba(0,0,0,0.5)",h.style.background="rgba(255,255,255,0.12)",h.style.borderRadius="2px",h.style.padding="1px 4px",h.textContent=y,y&&m.appendChild(h),m.style.left=r+60+"px",Number.isNaN(i)?m.style.top=l+2+"px":m.style.bottom=i+50+"px",n.appendChild(m)}}const P="tom-hide-chat-offers";function R(){return"1"===localStorage.getItem(P)}function V(t){if(!t||1!==t.nodeType)return;var e;R()&&((e=t)&&e.textContent&&l.test(e.textContent))?t.classList.add("tom-hidden-offer"):t.classList.remove("tom-hidden-offer")}function J(){document.querySelectorAll(".full-chat-message, .chat-widget-message").forEach(V)}let X=null;let Q=null,Y={};function K(){Q.assignedBuildings=Q.allBuildings.filter(t=>t.assignees>0).sort((t,e)=>e.assignees-t.assignees),Q.underConstruction=Q.allBuildings.filter(t=>t.builders>0),Q.idleWorkers=Q.allPopulations.filter(t=>t.idle>0),Q.totalIdle=Q.idleWorkers.reduce((t,e)=>t+e.idle,0)}function Z(){Q&&(!function(){const t=document.querySelectorAll(".tile-overlay");0!==t.length&&(m={},t.forEach((t,e)=>{const n=Math.floor(e/9);m[`${n},${e%9}`]={left:t.style.left,top:t.style.top,bottom:t.style.bottom}}))}(),j(Q),H="",D(Q),Y=Q.buildingMap)}var tt;tt=()=>{var e;!function(){const t=document.createElement("link");t.rel="stylesheet",t.href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@500;700&family=Newsreader:wght@700&display=swap",document.head.appendChild(t);const e=document.createElement("style");e.textContent="\n #tom-overlay {\n position: fixed;\n left: 8px;\n top: 110px;\n width: 480px;\n min-width: 200px;\n max-height: 90vh;\n background: rgba(0, 0, 0, 0.88);\n color: #e0e0e0;\n font-family: 'Segoe UI', system-ui, sans-serif;\n font-size: 13px;\n border-radius: 8px;\n z-index: 99999;\n overflow: hidden;\n box-shadow: 0 4px 20px rgba(0,0,0,0.5);\n user-select: none;\n resize: both;\n }\n #tom-overlay-header {\n background: rgba(255,255,255,0.08);\n padding: 8px 12px;\n cursor: grab;\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-weight: 600;\n font-size: 13px;\n }\n #tom-overlay-header:active { cursor: grabbing; }\n #tom-overlay-body {\n padding: 8px 12px;\n overflow-y: auto;\n max-height: calc(90vh - 40px);\n }\n #tom-overlay-body.collapsed { display: none; }\n .tom-section { margin-bottom: 10px; }\n .tom-section-title {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: #888;\n margin-bottom: 4px;\n }\n .tom-idle-alert {\n background: rgba(245, 158, 11, 0.15);\n border-left: 3px solid #f59e0b;\n padding: 6px 8px;\n border-radius: 0 4px 4px 0;\n margin-bottom: 8px;\n }\n .tom-idle-alert .tom-count {\n color: #fbbf24;\n font-weight: 700;\n }\n .tom-row {\n display: flex;\n justify-content: space-between;\n padding: 3px 0;\n border-bottom: 1px solid rgba(255,255,255,0.05);\n }\n .tom-row-label { color: #ccc; }\n .tom-row-value { color: #fff; font-weight: 600; }\n .tom-row-coord { color: #666; font-size: 12px; margin-left: 4px; }\n .tom-construction {\n background: rgba(249, 115, 22, 0.12);\n border-left: 3px solid #f97316;\n padding: 6px 8px;\n border-radius: 0 4px 4px 0;\n }\n .tom-version {\n font-size: 9px;\n opacity: 0.35;\n margin-right: 6px;\n letter-spacing: 0.03em;\n }\n .tom-toggle {\n font-size: 14px;\n cursor: pointer;\n opacity: 0.6;\n }\n .tom-toggle:hover { opacity: 1; }\n .tom-stat {\n display: inline-block;\n margin-right: 12px;\n }\n .tom-stat-value { font-weight: 700; color: #fff; }\n .tom-res-row {\n display: flex;\n justify-content: space-between;\n padding: 2px 0;\n }\n .tom-res-label { color: #999; }\n .tom-res-value { color: #fff; font-weight: 600; }\n .tom-res-rate { color: #4ade80; font-size: 12px; }\n .tom-res-food .tom-res-label { color: #fbbf24; }\n .tom-res-wood .tom-res-label { color: #a3e635; }\n .tom-res-mineral .tom-res-label { color: #60a5fa; }\n .tom-row-cat {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n margin-right: 4px;\n vertical-align: middle;\n }\n .tom-cat-resource { background: #22c55e; }\n .tom-cat-military { background: #ef4444; }\n .tom-cat-crafting { background: #a855f7; }\n .tom-cat-infrastructure { background: #3b82f6; }\n .tom-pop-card {\n background: rgba(255,255,255,0.05);\n border-radius: 4px;\n padding: 5px 8px;\n margin-bottom: 4px;\n }\n .tom-pop-name {\n color: #fbbf24;\n font-weight: 700;\n font-size: 13px;\n display: flex;\n justify-content: space-between;\n }\n .tom-pop-total { color: #fff; }\n .tom-pop-stats {\n display: flex;\n gap: 10px;\n font-size: 11px;\n color: #888;\n margin-top: 3px;\n }\n .tom-pop-val { color: #e0e0e0; font-weight: 600; }\n .tom-pop-idle-alert .tom-pop-val { color: #fbbf24; }\n .tom-tabs {\n display: flex;\n gap: 0;\n flex: 1;\n }\n .tom-tab {\n padding: 4px 10px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n border-radius: 4px 4px 0 0;\n color: #888;\n transition: color 0.15s, background 0.15s;\n }\n .tom-tab:hover { color: #ccc; }\n .tom-tab.active { color: #fff; background: rgba(255,255,255,0.1); }\n .tom-tab-content { display: none; }\n .tom-tab-content.active { display: block; }\n .tom-craft-card {\n background: rgba(255,255,255,0.05);\n border-radius: 4px;\n padding: 6px 8px;\n margin-bottom: 6px;\n border-left: 3px solid #a855f7;\n }\n .tom-craft-card.tom-craft-locked {\n opacity: 0.55;\n border-left-color: #6b7280;\n }\n .tom-tribe-lock-badge {\n font-size: 9px;\n color: #f59e0b;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 4px;\n }\n .tom-craft-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 4px;\n }\n .tom-craft-name { font-weight: 700; font-size: 12px; color: #e0e0e0; }\n .tom-craft-yield { color: #a855f7; font-size: 11px; font-weight: 600; }\n .tom-craft-time { color: #888; font-size: 11px; }\n .tom-craft-step {\n font-size: 11px;\n color: #aaa;\n padding: 2px 0 2px 8px;\n border-left: 2px solid rgba(168,85,247,0.3);\n margin: 2px 0;\n }\n .tom-craft-step-label { color: #a855f7; font-weight: 600; }\n .tom-craft-step-external { border-left-color: #fb923c; }\n .tom-craft-step-acquire { color: #fb923c; font-weight: 600; }\n .tom-search-input {\n width: 100%;\n background: rgba(255,255,255,0.08);\n border: 1px solid rgba(255,255,255,0.15);\n color: #e0e0e0;\n font-size: 12px;\n border-radius: 4px;\n padding: 5px 8px;\n box-sizing: border-box;\n outline: none;\n font-family: 'Work Sans', system-ui, sans-serif;\n }\n .tom-search-input::placeholder { color: #777; }\n .tom-search-input:focus { border-color: #fbbf24; }\n .tom-craft-search {\n padding: 4px 8px;\n }\n .tom-craft-search:focus { border-color: #a855f7; }\n .tom-craft-base {\n margin-top: 4px;\n padding-top: 4px;\n border-top: 1px solid rgba(255,255,255,0.08);\n font-size: 11px;\n }\n .tom-craft-base-label { color: #888; font-size: 10px; text-transform: uppercase; letter-spacing: 0.3px; }\n .tom-craft-mat { font-weight: 600; }\n .tom-craft-mat-food { color: #fbbf24; }\n .tom-craft-mat-wood { color: #a3e635; }\n .tom-craft-mat-mineral { color: #60a5fa; }\n .tom-craft-mat-gold_dust { color: #fbbf24; }\n .tom-craft-can {\n display: inline-block;\n font-size: 11px;\n font-weight: 700;\n padding: 1px 6px;\n border-radius: 3px;\n margin-top: 3px;\n }\n .tom-craft-can-yes { color: #4ade80; background: rgba(74,222,128,0.1); }\n .tom-craft-can-no { color: #ef4444; background: rgba(239,68,68,0.1); }\n .tom-craft-reqby { margin-top: 4px; }\n .tom-craft-reqby summary {\n font-size: 11px;\n color: #888;\n cursor: pointer;\n user-select: none;\n list-style: none;\n }\n .tom-craft-reqby summary::before { content: \"\\25B8 \"; }\n .tom-craft-reqby[open] summary::before { content: \"\\25BE \"; }\n .tom-craft-reqby-item {\n font-size: 11px;\n color: #aaa;\n padding: 1px 0 1px 12px;\n }\n .tom-trade-row {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 6px;\n }\n .tom-trade-side {\n font-size: 10px;\n font-weight: 700;\n padding: 2px 6px;\n border-radius: 3px;\n min-width: 36px;\n text-align: center;\n }\n .tom-trade-get { background: rgba(74,222,128,0.15); color: #4ade80; }\n .tom-trade-give { background: rgba(239,68,68,0.15); color: #ef4444; }\n .tom-trade-select {\n flex: 1;\n background: rgba(255,255,255,0.08);\n border: 1px solid rgba(255,255,255,0.15);\n color: #e0e0e0;\n font-size: 11px;\n border-radius: 4px;\n padding: 3px 4px;\n }\n .tom-trade-input {\n width: 64px;\n background: rgba(255,255,255,0.08);\n border: 1px solid rgba(255,255,255,0.15);\n color: #fff;\n font-size: 11px;\n border-radius: 4px;\n padding: 3px 4px;\n text-align: right;\n }\n .tom-trade-btn {\n width: 100%;\n background: rgba(168,85,247,0.2);\n border: 1px solid #a855f7;\n color: #e0d0ff;\n font-size: 12px;\n font-weight: 700;\n border-radius: 4px;\n padding: 5px;\n cursor: pointer;\n margin-bottom: 8px;\n }\n .tom-trade-btn:hover { background: rgba(168,85,247,0.35); }\n .tom-trade-result-inner {\n background: rgba(255,255,255,0.04);\n border-radius: 4px;\n padding: 8px;\n }\n .tom-trade-result-row {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: #e0e0e0;\n }\n .tom-trade-breakdown { font-size: 10px; color: #888; padding-left: 48px; margin: 1px 0; }\n .tom-trade-wm { font-size: 11px; color: #aaa; padding-left: 48px; margin-bottom: 2px; }\n .tom-trade-verdict {\n margin-top: 8px;\n padding: 6px 8px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 700;\n text-align: center;\n }\n .tom-trade-great { background: rgba(74,222,128,0.15); color: #4ade80; }\n .tom-trade-fair { background: rgba(74,222,128,0.10); color: #86efac; }\n .tom-trade-risky { background: rgba(251,191,36,0.15); color: #fbbf24; }\n .tom-trade-bad { background: rgba(239,68,68,0.15); color: #ef4444; }\n .tom-trade-unknown { color: #fb923c; font-size: 11px; padding: 6px 0; }\n .tom-trade-fair-suggest {\n margin-top: 8px;\n padding: 6px 8px;\n background: rgba(251,191,36,0.08);\n border-left: 2px solid #fbbf24;\n color: #e9c176;\n font-size: 11px;\n }\n .tom-trade-gold-row {\n display: flex;\n align-items: center;\n gap: 5px;\n margin-bottom: 3px;\n }\n .tom-trade-gold-label { font-size: 11px; color: #888; }\n .tom-trade-gold-btn {\n background: rgba(251,191,36,0.15);\n border: 1px solid #fbbf24;\n color: #fbbf24;\n font-size: 10px;\n font-weight: 700;\n border-radius: 3px;\n padding: 2px 6px;\n cursor: pointer;\n }\n .tom-trade-gold-btn:hover { background: rgba(251,191,36,0.3); }\n .tom-trade-gold-ref { font-size: 10px; color: #555; margin-bottom: 8px; }\n .tom-market-empty { color: #555; font-size: 11px; padding: 8px 0; }\n .tom-offers-count {\n display: inline-block;\n margin-left: 4px;\n background: rgba(251,191,36,0.2);\n color: #fbbf24;\n font-size: 9px;\n font-weight: 700;\n padding: 1px 5px;\n border-radius: 8px;\n min-width: 14px;\n text-align: center;\n }\n .tom-offer-meta { font-size: 10px; color: #888; margin-bottom: 2px; }\n .tom-offer-user { color: #e9c176; font-weight: 700; }\n .tom-offer-time { color: #555; margin-left: 4px; }\n .tom-hidden-offer { display: none !important; }\n .tom-offers-toolbar { display: flex; align-items: center; gap: 8px; padding: 4px 0 6px; font-size: 11px; color: #aaa; }\n .tom-offers-toggle { cursor: pointer; user-select: none; }\n .tom-offers-toggle input { vertical-align: middle; margin-right: 4px; }\n .tom-offers-clear { margin-left: auto; cursor: pointer; color: #ef4444; font-size: 10px; }\n .tom-market-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 5px 0;\n border-bottom: 1px solid rgba(255,255,255,0.05);\n }\n .tom-market-badge {\n min-width: 44px;\n text-align: center;\n font-size: 10px;\n font-weight: 700;\n padding: 2px 4px;\n border-radius: 4px;\n flex-shrink: 0;\n }\n .tom-market-badge-good { background: rgba(74,222,128,0.15); color: #4ade80; }\n .tom-market-badge-fair { background: rgba(251,191,36,0.15); color: #fbbf24; }\n .tom-market-badge-bad { background: rgba(239,68,68,0.15); color: #ef4444; }\n .tom-market-badge-unknown { background: rgba(255,255,255,0.08); color: #888; }\n .tom-market-badge-sub { display: block; font-size: 8px; font-weight: 400; opacity: 0.7; }\n .tom-market-sides { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }\n .tom-market-sides > div { display: flex; align-items: center; gap: 4px; }\n .tom-market-item { font-size: 11px; color: #e0e0e0; flex: 1; }\n .tom-market-wm { font-size: 10px; color: #666; flex-shrink: 0; }\n\n /* Building sort toolbar */\n .tom-bld-toolbar {\n display: flex; flex-direction: column; gap: 6px; padding: 4px 20px 8px;\n }\n .tom-bld-tab-row {\n display: flex; gap: 0; border-bottom: 2px solid rgba(251,191,36,0.3); margin-bottom: 2px;\n }\n .tom-bld-tab-btn {\n background: rgba(255,255,255,0.06); color: #999; border: none;\n padding: 5px 14px; font-size: 12px; font-weight: 600;\n cursor: pointer; font-family: 'Work Sans', system-ui, sans-serif;\n transition: background 0.15s, color 0.15s;\n border-radius: 4px 4px 0 0; letter-spacing: 0.3px;\n }\n .tom-bld-tab-btn:hover { background: rgba(255,255,255,0.12); color: #ddd; }\n .tom-bld-tab-btn.active { background: #fbbf24; color: #1a1a1a; }\n .tom-bld-search { /* extends .tom-search-input */ }\n .tom-bld-search:focus { border-color: #fbbf24; }\n .tom-bld-hidden { display: none !important; }\n .tom-bld-no-results {\n grid-column: 1 / -1; text-align: center; color: #888;\n padding: 20px; font-size: 13px;\n font-family: 'Work Sans', system-ui, sans-serif;\n }\n\n /* Crafter sort toolbar */\n .tom-cft-toolbar {\n display: flex; flex-direction: column; gap: 6px; padding: 6px 10px 8px;\n background: rgba(0,0,0,0.4); border-radius: 6px; margin: 4px 0 8px;\n }\n .tom-cft-search { /* extends .tom-search-input */ }\n .tom-cft-search:focus { border-color: #fbbf24; }\n .tom-cft-sort-row {\n display: flex; gap: 4px; flex-wrap: wrap;\n }\n .tom-cft-sort-btn {\n background: rgba(255,255,255,0.1); color: #ccc; border: none;\n padding: 3px 10px; border-radius: 12px; font-size: 11px;\n cursor: pointer; font-family: 'Work Sans', system-ui, sans-serif;\n transition: background 0.15s, color 0.15s;\n }\n .tom-cft-sort-btn:hover { background: rgba(255,255,255,0.18); color: #fff; }\n .tom-cft-sort-btn.active { background: #fbbf24; color: #1a1a1a; font-weight: 600; }\n .tom-cft-hidden { display: none !important; }\n .tom-cft-cat-divider {\n font-size: 11px; color: #fbbf24; opacity: 0.8;\n padding: 8px 4px 2px; font-family: 'Work Sans', system-ui, sans-serif;\n font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase;\n border-bottom: 1px solid rgba(251,191,36,0.15);\n }\n .tom-cft-no-results {\n text-align: center; color: #888;\n padding: 20px; font-size: 13px;\n font-family: 'Work Sans', system-ui, sans-serif;\n }\n\n ",document.head.appendChild(e)}(),function(){const t=new MutationObserver(t=>{for(const e of t){for(const t of e.addedNodes)1===t.nodeType&&(t.matches&&(t.matches(".full-chat-message")||t.matches(".chat-widget-message"))&&V(t),t.querySelectorAll&&t.querySelectorAll(".full-chat-message, .chat-widget-message").forEach(V));if("characterData"===e.type&&e.target.parentElement){const t=e.target.parentElement.closest(".full-chat-message, .chat-widget-message");t&&V(t)}}});t.observe(document.body,{childList:!0,subtree:!0,characterData:!0}),X=t,J()}(),ot("modal-overlay",t=>t.startsWith("Construct Building"),lt),ot("right-sidebar",t=>/^crafter/i.test(t),ht),e=t=>{Q=F(t);const e=t.tiles.find(t=>t.building&&/^command_center/.test(t.building.slug));if(e){const t=e.building.slug.match(/(\d+)$/);t&&(o=parseInt(t[1])+1)}Z()},s.push(e),t&&e(t)},"loading"!==document.readyState?tt():document.addEventListener("DOMContentLoaded",tt);const et={food:"Food",wood:"Wood",mineral:"Mineral",leather:"Leather",lumber:"Lumber",stick:"Sticks",iron_nugget:"Iron Nugget",steel_nugget:"Steel Nugget",coconut_charcoal:"Coconut Charcoal",gold_dust:"Gold Dust",gold_coin:"Gold Coin",sword:"Sword",sword2:"Steel Sword",gun:"Gun",gun2:"Steel Gun",spear:"Spear",spear2:"Steel Spear",bow_and_arrow:"Composite Bow",bow_and_arrow_2:"Composite Bow II",stone_axe:"Stone Axe",tent:"Tent",salt:"Salt",tiula_itum:"Tiula Itum",peace_amululet8_free:"Peace Amulet"},nt={};function ot(t,e,n){new MutationObserver(o=>{for(const a of o)for(const o of a.addedNodes){if(1!==o.nodeType)continue;const a=o.classList?.contains(t)?o:o.querySelector?.("."+t);if(!a)continue;const s=a.querySelector("h2");s&&e(s.textContent.trim())&&setTimeout(()=>n(a),50)}}).observe(document.body,{childList:!0,subtree:!0})}function at(t,e,n){new MutationObserver(()=>{const o=t.querySelectorAll(e);let a=!1;o.forEach((t,e)=>{t.dataset.tomOrigIdx||(t.dataset.tomOrigIdx=e,a=!0)}),a&&n()}).observe(t,{childList:!0,subtree:!0})}["food","wood","mineral"].forEach(t=>nt[t]={group:"Resources",order:0}),["leather","lumber","stick","iron_nugget","steel_nugget"].forEach(t=>nt[t]={group:"Materials",order:1}),["sword","sword2","gun","gun2","spear","spear2","bow_and_arrow","bow_and_arrow_2","stone_axe"].forEach(t=>nt[t]={group:"Weapons",order:2}),["salt","gold_dust","coconut_charcoal","tiula_itum","inasal","adobo"].forEach(t=>nt[t]={group:"Tribal Locked",order:3}),["tent","gold_coin","peace_amululet8_free"].forEach(t=>nt[t]={group:"Other",order:4});let st="infrastructure",rt="";function it(t){const e=rt.toLowerCase(),n=t.querySelector(".tom-bld-no-results");n&&n.remove();const o=[...t.querySelectorAll(".building-option")],a=[],s=[];if(o.forEach(t=>{const n=function(t){const e=t.querySelector("h3");return e?e.textContent.trim().toLowerCase():""}(t),o=function(t){const e=t.querySelector(".description");return e?e.textContent.trim().toLowerCase():""}(t),r=function(t){const e=t.toLowerCase();return["barracks","training grounds","archery grounds"].some(t=>e.includes(t))?"military":["farmer","woodcutter","miner"].some(t=>e.includes(t))?"resources":"infrastructure"}(n)===st,i=!e||n.includes(e)||o.includes(e);r&&i?a.push(t):s.push(t)}),a.sort((t,e)=>parseInt(t.dataset.tomOrigIdx,10)-parseInt(e.dataset.tomOrigIdx,10)),a.forEach(e=>{e.classList.remove("tom-bld-hidden"),t.appendChild(e)}),s.forEach(e=>{e.classList.add("tom-bld-hidden"),t.appendChild(e)}),0===a.length&&s.length>0){const e=document.createElement("div");e.className="tom-bld-no-results",e.textContent="No matching buildings",t.appendChild(e)}}function lt(t){const e=t.querySelector(".modal-body"),n=t.querySelector(".building-list");if(!e||!n)return;if(t.querySelector(".tom-bld-toolbar"))return;n.querySelectorAll(".building-option").forEach((t,e)=>{t.dataset.tomOrigIdx=e});const o=function(t){const e=document.createElement("div");e.className="tom-bld-toolbar";const n=document.createElement("div");n.className="tom-bld-tab-row",[{key:"infrastructure",label:"Infrastructure"},{key:"military",label:"Military"},{key:"resources",label:"Resources"}].forEach(e=>{const o=document.createElement("button");o.className="tom-bld-tab-btn"+(st===e.key?" active":""),o.textContent=e.label,o.addEventListener("click",()=>{n.querySelectorAll(".tom-bld-tab-btn").forEach(t=>t.classList.remove("active")),o.classList.add("active"),st=e.key,it(t)}),n.appendChild(o)}),e.appendChild(n);const o=document.createElement("input");return o.type="text",o.className="tom-search-input tom-bld-search",o.placeholder="Search buildings…",o.value=rt,o.addEventListener("input",()=>{rt=o.value,it(t)}),e.appendChild(o),e}(n);e.insertBefore(o,e.firstChild),it(n),at(n,".building-option",()=>it(n))}let ct="default",dt="";const mt=function(){const t={};for(const[e,n]of Object.entries(et))t[n.toLowerCase()]=e;return t}();function pt(t){const e=t.querySelector("h3");if(!e)return"";let n=e.textContent.trim();n=n.replace(/^Craft\s+/i,"").replace(/\s*\(\d+x\)\s*$/,"").trim();const o=mt[n.toLowerCase()];return o||n.toLowerCase().replace(/\s+/g,"_")}function ut(t){const e=t.querySelector("h3");return e?e.textContent.trim().toLowerCase():""}function ft(t){const e=t.querySelectorAll(".costs");for(const t of e){const e=t.textContent;if(/time/i.test(e)){let t=0;const n=e.match(/(\d+)m/);n&&(t+=60*parseInt(n[1],10));const o=e.match(/(\d+)s/);return o&&(t+=parseInt(o[1],10)),t}}return 0}function gt(t){const e=t.children[3];if(!e)return 0;const n=e.querySelector("span");return n&&parseInt(n.textContent.replace(/[\s,]/g,""),10)||0}const bt={get value(){return ct},set value(t){ct=t}};function yt(t){const e=document.createElement("div");e.className="tom-cft-toolbar";const n=document.createElement("input");n.type="text",n.className="tom-search-input tom-cft-search",n.placeholder="Search crafts…",n.value=dt,n.addEventListener("input",()=>{dt=n.value,xt(t)}),e.appendChild(n);const o=document.createElement("div");o.className="tom-cft-sort-row";return function(t,e,n,o,a){e.forEach(e=>{const s=document.createElement("button"),r=e.asc&&e.desc,i=r&&n.value===e.desc?"desc":r&&n.value===e.asc?"asc":null;s.className=a+(i||n.value===e.key?" active":""),s.textContent=r?e.label+" "+("asc"===i?"↑":"↓"):e.label,s.addEventListener("click",()=>{if(t.querySelectorAll("."+a).forEach(t=>t.classList.remove("active")),s.classList.add("active"),r){const t="desc"===(n.value===e.desc?"desc":n.value===e.asc?"asc":null)?"asc":"desc";n.value="desc"===t?e.desc:e.asc,s.textContent=e.label+" "+("asc"===t?"↑":"↓")}else n.value=e.key;o()}),t.appendChild(s)})}(o,[{key:"default",label:"Default"},{key:"name",label:"Name",asc:"name-asc",desc:"name-desc"},{key:"time",label:"Time",asc:"time-asc",desc:"time-desc"},{key:"cancraft",label:"Can Craft",asc:"cancraft-asc",desc:"cancraft-desc"},{key:"category",label:"Category"}],bt,()=>xt(t),"tom-cft-sort-btn"),e.appendChild(o),e}function xt(t){const e=dt.toLowerCase();t.querySelectorAll(".tom-cft-cat-divider").forEach(t=>t.remove());const n=t.querySelector(".tom-cft-no-results");n&&n.remove();const o=[...t.querySelectorAll(".building-option")],a=[],s=[];o.forEach(t=>{const n=ut(t);e&&!n.includes(e)?s.push(t):a.push(t)});const r=new Map;if(a.forEach(t=>{r.set(t,{name:ut(t),time:ft(t),qty:gt(t),slug:pt(t),idx:parseInt(t.dataset.tomOrigIdx,10)})}),a.sort((t,e)=>{const n=r.get(t),o=r.get(e);switch(ct){case"name-asc":return n.name.localeCompare(o.name);case"name-desc":return o.name.localeCompare(n.name);case"time-asc":return n.time-o.time;case"time-desc":return o.time-n.time;case"cancraft-asc":return n.qty-o.qty;case"cancraft-desc":return o.qty-n.qty;case"category":{const t=nt[n.slug]||{order:99},e=nt[o.slug]||{order:99};return t.order!==e.order?t.order-e.order:n.idx-o.idx}default:return n.idx-o.idx}}),a.forEach(t=>t.classList.remove("tom-cft-hidden")),"category"===ct?function(t,e,n,o){let a=null;e.forEach(e=>{const s=n(e);if(s.group!==a){const e=document.createElement("div");e.className=o,e.textContent=s.group,t.appendChild(e),a=s.group}t.appendChild(e)})}(t,a,t=>nt[r.get(t)?.slug||pt(t)]||{group:"Other",order:99},"tom-cft-cat-divider"):a.forEach(e=>t.appendChild(e)),s.forEach(e=>{e.classList.add("tom-cft-hidden"),t.appendChild(e)}),0===a.length&&s.length>0){const e=document.createElement("div");e.className="tom-cft-no-results",e.textContent="No matching crafts",t.appendChild(e)}}function ht(t){const e=t.querySelector(".building-list");if(!e)return;if(t.querySelector(".tom-cft-toolbar"))return;e.querySelectorAll(".building-option").forEach((t,e)=>{t.dataset.tomOrigIdx=e});const n=yt(e);e.parentElement.insertBefore(n,e),("default"!==ct||dt)&&xt(e),at(e,".building-option",()=>{("default"!==ct||dt)&&xt(e)})}}();