diff --git a/wg-greyknights/module.json b/wg-greyknights/module.json
index 0d2a015..3169b5f 100644
--- a/wg-greyknights/module.json
+++ b/wg-greyknights/module.json
@@ -1,7 +1,7 @@
{
"id": "gk-greyknight",
"title": "Grey Knights — Compendium (Wrath & Glory)",
- "version": "0.6.4.6",
+ "version": "0.6.5.2",
"compatibility": {
"minimum": "11",
"verified": "13"
@@ -26,7 +26,8 @@
],
"esmodules": [
"scripts/auto-loadout.js",
- "scripts/helpers.js"
+ "scripts/helpers.js",
+ "scripts/ui-gkpanel.js"
],
"readme": "README.txt"
}
\ No newline at end of file
diff --git a/wg-greyknights/packs/gk-items.db b/wg-greyknights/packs/gk-items.db
index 1ee04d5..75e5667 100644
--- a/wg-greyknights/packs/gk-items.db
+++ b/wg-greyknights/packs/gk-items.db
@@ -10,8 +10,8 @@
{"name":"The Shrouding (Sanctic)","type":"psychicPower","img":"modules/wng-core/assets/icons/powers/power.webp","effects":[],"flags":{"core":{}},"system":{"dn":"7","cost":10,"activation":"action","duration":"Sustained","range":"Self or Ally within 15 m","multiTarget":false,"keywords":"PSYCHIC, SANCTIC","prerequisites":"PSYKER Keyword","description":"
Warp-wrought mist blurs the target. Attacks against the target suffer +1 DN (ranged) while Sustained; moving more than normal speed ends the effect at end of Turn. Potency: also impose +1 DN on melee attacks.
"},"_id":"a0b1c2d3e4f50619"}
{"name":"Might of Titan (Sanctic)","type":"psychicPower","img":"modules/wng-core/assets/icons/powers/power.webp","effects":[],"flags":{"core":{}},"system":{"dn":"7","cost":15,"activation":"action","duration":"Sustained","range":"15 m","multiTarget":false,"keywords":"PSYCHIC, SANCTIC","prerequisites":"PSYKER Keyword","description":"Infuse an ally with the adamant strength of Titan. The target gains +1 to Strength-based Tests and +1ED on melee attacks until the start of your next Turn; while Sustained, the bonuses persist. Potency: also +1 to Toughness Tests.
"},"_id":"a0b1c2d3e4f5061a"}
{"name":"Brotherhood of Psykers","type":"ability","img":"modules/wng-core/assets/icons/abilities/adeptus-astartes.webp","effects":[],"flags":{"core":{}},"system":{"description":"Brotherhood of Psykers. The Grey Knights fight as one mind, their souls warded by hexagrammic rites and the tutelage of Titan. This Ability is the Grey Knights counterpart to Chapter Cult.
- Discipline: When you gain this Ability, choose Librarius or Sanctic. You know one power from the chosen Discipline and may learn further powers from it as normal.
- Brotherhood Focus (house rule, narrative): While two or more allied characters with this Ability are present in the same scene, each such character gains +1 bonus die to Psychic Mastery tests per other allied character with this Ability (cumulative). Each subsequent power cast in the same round using this shared focus adds one additional Wrath die to the test (GM adjudication). Only one character linked by this Ability may use a psychic power as their Action in a given round.
","cost":0,"requirements":"GREY KNIGHTS"},"_id":"b1a2c3d4e5f60718"}
+{"name":"PsyboltAmmunition","type":"ammo","img":"modules/wng-core/assets/icons/ammo/ammo.webp","system":{"quantity":1,"rarity":"unique","description":"UpgradeforBOLT/StormBolterweapons.Grants+1ED.AgainstDAEMON/PSYKER,attacksgainRending(1).Applymanuallytoattackresolvingrules;conditionaleffectsarenotautomated.","value":5,"keywords":"BOLT,SANCTIFIED","effect":"","traits":{"list":[{"name":"force","type":"add"},{"name":"blessed","type":"add","rating":"2"}]}},"effects":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1756061909914,"modifiedTime":1756061909914,"lastModifiedBy":"ETeghv7x8fjtIEKK","exportSource":{"worldId":"gilead2","uuid":"Item.iMlwOxHqV9PWIEz5","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":null,"flags":{},"_id":"iMlwOxHqV9PWIEz5"}
{"name":"Psycannon","type":"weapon","img":"modules/wng-core/assets/icons/weapons/autocannon.webp","system":{"quantity":1,"rarity":"unique","description":"Anti-daemonicboltweapon.VsDAEMON/PSYKER:+1ED.OnCritical:gainsRending(1)fortheresult.","value":9,"keywords":"BOLT,IMPERIUM,ADEPTUSASTARTES,GREYKNIGHTS,SANCTIFIED","equipped":false,"attack":{"base":0,"bonus":0,"rank":"none"},"damage":{"base":14,"bonus":0,"rank":0,"ed":{"base":3,"bonus":0,"rank":0,"dice":""},"ap":{"base":-2,"bonus":0,"rank":0,"dice":""},"enabled":false,"otherDamage":{"mortal":"0","wounds":"0","shock":"0"},"dice":""},"ed":{"base":0,"bonus":0,"rank":0},"ap":{"base":0,"bonus":0,"rank":0},"category":"ranged","range":{"short":20,"medium":40,"long":60,"melee":1,"thrown":null},"ammo":{"id":""},"salvo":2,"traits":{"list":[{"name":"heavy","rating":"7"},{"name":"rapidFire","rating":"2"}]},"upgrades":[],"otherDamage":{},"combi":{},"twinned":false},"effects":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1755858910417,"modifiedTime":1756051080757,"lastModifiedBy":"ETeghv7x8fjtIEKK","exportSource":{"worldId":"gilead2","uuid":"Item.luQa0ffP6nroOeN8","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":null,"flags":{},"_id":"luQa0ffP6nroOeN8"}
{"name":"ORDO MALLEUS","type":"keyword","img":"modules/wng-core/assets/factions/the-inquisition.svg","effects":[{"_id":"xs6DzM62O4RjebzK","changes":[{"key":"system.combat.conviction.bonus","mode":2,"value":"1","priority":null},{"key":"system.corruption.current","mode":2,"value":"3","priority":null}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"transfer":true,"flags":{"wrath-and-glory":{"description":"","migrated":true,"manualEffectKeys":true}},"tint":"#ffffff","origin":null,"name":"ORDO MALLEUS (Choice 1)","description":"","statuses":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","lastModifiedBy":null},"img":"modules/wng-core/assets/factions/the-inquisition.svg","type":"base","system":{"transferData":{"type":"other","originalType":"document","documentType":"Actor","avoidTest":{"value":"none","opposed":false,"prevention":true,"reversed":false},"testIndependent":false,"equipTransfer":false,"selfOnly":false,"prompt":false,"area":{"templateData":{"borderColor":null,"fillColor":null,"texture":null},"keep":false,"aura":{"transferred":false,"render":false},"duration":"sustained"},"zone":{"type":"zone","transferred":false,"traits":{},"skipImmediateOnPlacement":false,"keep":false}},"itemTargetData":{"ids":[],"allItems":false},"scriptData":[],"zone":{"type":"zone","traits":{},"skipImmediateOnPlacement":false},"sourceData":{"test":{}}},"sort":0},{"_id":"Q0aNWykjDCqeTtQL","changes":[],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"transfer":true,"flags":{"wrath-and-glory":{"description":"Scholar/Investigation related to Daemons/The Warp","changeCondition":{"0":{"description":"Scholar/Investigation related to Daemons/The Warp","script":""}},"manualEffectKeys":true}},"tint":"#ffffff","origin":null,"name":"ORDO MALLEUS (Choice 2)","description":"","statuses":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","lastModifiedBy":null},"img":"modules/wng-core/assets/factions/the-inquisition.svg","type":"base","system":{"scriptData":[{"script":"return args.fields.pool += 2;","label":"Bonus dice on any Scholar (Int) and Investigation (Int) Tests related to daemons and the Warp.","trigger":"dialog","options":{"targeter":false,"hideScript":"return ![\"scholar\", \"investigation\"].includes(args.skill);","activateScript":"","submissionScript":"","deleteEffect":false,"defending":false},"async":false}],"transferData":{"type":"other","originalType":"document","documentType":"Actor","avoidTest":{"value":"none","opposed":false,"prevention":true,"reversed":false},"testIndependent":false,"equipTransfer":false,"selfOnly":false,"prompt":false,"area":{"templateData":{"borderColor":null,"fillColor":null,"texture":null},"keep":false,"aura":{"transferred":false,"render":false},"duration":"sustained"},"zone":{"type":"zone","transferred":false,"traits":{},"skipImmediateOnPlacement":false,"keep":false}},"itemTargetData":{"ids":[],"allItems":false},"zone":{"type":"zone","traits":{},"skipImmediateOnPlacement":false},"sourceData":{"test":{}}},"sort":0},{"name":"Choice","img":"modules/wng-core/assets/factions/the-inquisition.svg","_id":"tk4CpA9MHTJ47VAe","type":"base","system":{"transferData":{"type":"document","originalType":"document","documentType":"Actor","avoidTest":{"value":"none","opposed":false,"prevention":true,"reversed":false},"testIndependent":false,"equipTransfer":false,"selfOnly":false,"prompt":false,"area":{"templateData":{"borderColor":null,"fillColor":null,"texture":null},"keep":false,"aura":{"transferred":false,"render":false},"duration":"sustained"},"zone":{"type":"zone","transferred":false,"traits":{},"skipImmediateOnPlacement":false,"keep":false}},"itemTargetData":{"ids":[],"allItems":false},"scriptData":[{"script":"let effects = this.item.effects.contents.filter(i => i.id != this.effect.id);\n\nlet choice = await ItemDialog.create(effects, 1, {title : this.item.name, text: this.effect.name});\nchoice[0].updateSource({name : choice[0].baseName, \"system.transferData.type\" : \"document\"})","label":"Choice","trigger":"immediate","options":{"targeter":false,"deleteEffect":true,"defending":false},"async":false}],"zone":{"type":"zone","traits":{},"skipImmediateOnPlacement":false},"sourceData":{"test":{}}},"changes":[],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"description":"","origin":null,"tint":"#ffffff","transfer":true,"statuses":[],"sort":0,"flags":{"wrath-and-glory":{"manualEffectKeys":false}},"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","lastModifiedBy":null}}],"flags":{"wng-core":{"initialization-folder":"Ordos","sort":0},"core":{}},"system":{"description":"The Daemon Hunters
Smallest amongst the Ordos, duty bound to battle daemons and the corrupting influence of Chaos. Every encounter with their foe, every scrap of arcane lore learnt, degrades body and soul. The Chamber Militant of the Ordo Malleus are the Grey Knights, making rare and terrible crusades together.
- You gain either: +3 Corruption and +1 Conviction. Or: +2 bonus dice on any Scholar (Int) and Investigation (Int) Tests related to daemons and the Warp.
"},"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1755816946335,"modifiedTime":1755816946335,"lastModifiedBy":"ETeghv7x8fjtIEKK","exportSource":{"worldId":"gilead2","uuid":"Item.mpcvHYlffwcBy9gP","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":"SXjZUgQ1pROPVE9j","_id":"mpcvHYlffwcBy9gP"}
-{"name":"PsyboltAmmunition","type":"ammo","img":"modules/wng-core/assets/icons/ammo/ammo.webp","system":{"quantity":1,"rarity":"unique","description":"UpgradeforBOLT/StormBolterweapons.Grants+1ED.AgainstDAEMON/PSYKER,attacksgainRending(1).Applymanuallytoattackresolvingrules;conditionaleffectsarenotautomated.","value":5,"keywords":"BOLT,SANCTIFIED","effect":"","traits":{"list":[{"name":"force","type":"add"},{"name":"blessed","type":"add","rating":"2"}]}},"effects":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1756061909914,"modifiedTime":1756061909914,"lastModifiedBy":"ETeghv7x8fjtIEKK","exportSource":{"worldId":"gilead2","uuid":"Item.iMlwOxHqV9PWIEz5","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":null,"flags":{},"_id":"iMlwOxHqV9PWIEz5"}
{"name":"Grey Knight (Archetype)","type":"archetype","img":"modules/wng-core/assets/icons/abilities/the-inquisition.webp","effects":[{"_id":"uMkkBfS2rRXluXA6","name":"Aegis of Titan — Innate Purity","icon":"icons/svg/aura.svg","origin":"","disabled":false,"duration":{"startTime":null},"changes":[{"key":"system.combat.resolve.bonus","mode":2,"value":1,"priority":20}],"transfer":true,"flags":{}}],"flags":{"wng-forsaken":{"initialization-folder":"Archetypes","sort":0},"core":{}},"system":{"tier":4,"journal":"DMmW708Q5AQvQfuy","species":{"name":"Adeptus Astartes","id":"KpwHo0A92bR7iVMB"},"faction":{"name":"Adeptus Astartes","id":"CN5E9Z5GqfiR1MDn"},"influence":2,"cost":359,"keywords":["IMPERIUM","ADEPTUS ASTARTES","GREY KNIGHTS","INQUISITION","DAEMONHUNTERS","ORDO MALLEUS"],"attributes":{"strength":5,"toughness":5,"agility":5,"initiative":5,"willpower":6,"intellect":3,"fellowship":null},"skills":{"athletics":3,"awareness":3,"ballisticSkill":5,"cunning":null,"deception":null,"insight":null,"intimidation":null,"investigation":null,"leadership":null,"medicae":null,"persuasion":null,"pilot":null,"psychicMastery":4,"scholar":2,"stealth":3,"survival":1,"tech":null,"weaponSkill":5},"ability":{"id":"DaEMUDPe8wmrWz70","name":"Aegis of Titan","description":"Innate Purity: +1 vs Corruption. Aegis Synergy: +1 vs Corruption while equipped with Aegis armour. Re-roll 1 die on Deny the Witch each round."},"wargear":{"freeItems":[]},"suggested":{"attributes":{"strength":null,"toughness":null,"agility":null,"initiative":null,"willpower":null,"intellect":null,"fellowship":null},"skills":{"athletics":null,"awareness":null,"ballisticSkill":null,"cunning":null,"deception":null,"insight":null,"intimidation":null,"investigation":null,"leadership":null,"medicae":null,"persuasion":null,"pilot":null,"psychicMastery":null,"scholar":null,"stealth":null,"survival":null,"tech":null,"weaponSkill":null},"talents":{"list":[]}}},"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1755771384639,"modifiedTime":1755771384639,"lastModifiedBy":"8fThFXcxV83JCCcX","exportSource":{"worldId":"gilead-wandg","uuid":"Item.TCmE7YmUDUVQ3xoz","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":"DEb55m6iVZ0NUOxA","_id":"qSv5JzFRjTYdw1zI"}
{"name":"Psilencer","type":"weapon","img":"modules/wng-core/assets/icons/weapons/assault-cannon.webp","system":{"quantity":1,"rarity":"unique","description":"High-ratepsykicdisruptor.VsPSYKER:+1ED;hitPSYKERcannotAiminthesameround.","value":9,"keywords":"IMPERIUM,ADEPTUSASTARTES,GREYKNIGHTS,SANCTIFIED","equipped":false,"attack":{"base":0,"bonus":0,"rank":"none"},"damage":{"base":11,"bonus":0,"rank":0,"ed":{"base":2,"bonus":0,"rank":0,"dice":""},"ap":{"base":0,"bonus":0,"rank":0,"dice":""},"enabled":false,"otherDamage":{"mortal":"0","wounds":"0","shock":"0"},"dice":""},"ed":{"base":0,"bonus":0,"rank":0},"ap":{"base":0,"bonus":0,"rank":0},"category":"ranged","range":{"short":12,"medium":24,"long":36,"melee":1,"thrown":null},"ammo":{"id":""},"salvo":3,"traits":{"list":[{"name":"rapidFire","rating":"3"},{"name":"reliable"}]},"upgrades":[],"otherDamage":{},"combi":{},"twinned":false},"effects":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1755859392521,"modifiedTime":1756051080757,"lastModifiedBy":"ETeghv7x8fjtIEKK","exportSource":{"worldId":"gilead2","uuid":"Item.qvk9TxIezbO2C5OK","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":null,"flags":{},"_id":"qvk9TxIezbO2C5OK"}
diff --git a/wg-greyknights/scripts/ui-gkpanel.js b/wg-greyknights/scripts/ui-gkpanel.js
new file mode 100644
index 0000000..0328758
--- /dev/null
+++ b/wg-greyknights/scripts/ui-gkpanel.js
@@ -0,0 +1,201 @@
+// gk-greyknight — ui-gkpanel.js (0.6.5.2)
+
+const GK_NS = "gk-greyknight";
+const GK_DEBUG = false;
+
+/* ---------------- helpers ---------------- */
+function gkActorHasBrotherhood(actor) {
+ return !!actor?.items?.some(i => i.type === "ability" && i.name === "Brotherhood of Psykers");
+}
+function gkAlliesAndBonus(actor) {
+ const tokens = canvas?.tokens?.placeables || [];
+ const ids = new Set();
+ for (const t of tokens) {
+ const a = t.actor; if (!a) continue;
+ if (gkActorHasBrotherhood(a)) ids.add(a.id);
+ }
+ const total = ids.size;
+ const bonus = ids.has(actor?.id) ? Math.max(0, total - 1) : total;
+ return { total, bonus };
+}
+
+/* ---------------- settings (per-user) ---------------- */
+Hooks.once("init", () => {
+ game.settings.register(GK_NS, "panelVisible", {
+ name: "Brotherhood panel visible",
+ hint: "Show the Brotherhood Focus panel by default.",
+ scope: "client", config: true, type: Boolean, default: true,
+ onChange: (v) => { v ? GKFocusPanel.show(true) : GKFocusPanel.hide(); }
+ });
+ game.settings.register(GK_NS, "panelAutoOpen", {
+ name: "Auto-open when eligible",
+ hint: "Auto-open the panel when your selected actor (or controlled token) has Brotherhood of Psykers.",
+ scope: "client", config: true, type: Boolean, default: true,
+ onChange: () => GKFocusPanel.render()
+ });
+});
+
+// v13: dodatkowy hook na zmiany ustawień po stronie klienta
+Hooks.on?.("clientSettingChanged", (key, value) => {
+ if (key === `${GK_NS}.panelVisible`) return value ? GKFocusPanel.show(true) : GKFocusPanel.hide();
+ if (key === `${GK_NS}.panelAutoOpen`) return GKFocusPanel.render();
+});
+
+/* ---------------- panel ---------------- */
+class GKFocusPanel {
+ static async getState() {
+ return (await game.user.getFlag(GK_NS, "focusPanel")) || {};
+ }
+ static async saveState(patch) {
+ const cur = await this.getState();
+ return game.user.setFlag(GK_NS, "focusPanel", { ...cur, ...patch });
+ }
+ static async isVisible() {
+ const v = game.settings.get(GK_NS, "panelVisible");
+ const st = await this.getState();
+ return v && !st.closed;
+ }
+ static async hide() {
+ await this.saveState({ closed: true, forced: false });
+ $("#gk-focus-panel").remove();
+ }
+ static async show(force = false) {
+ await this.saveState({ closed: false, forced: !!force });
+ this.render();
+ }
+ static async toggle() {
+ const vis = await this.isVisible();
+ if (vis) return this.hide();
+ return this.show(true); // force-open nawet bez aktora GK
+ }
+
+ static currentActor() {
+ return game.user?.character || canvas.tokens?.controlled?.[0]?.actor || null;
+ }
+
+ static async render() {
+ const st = await this.getState();
+ if (st.closed || !game.settings.get(GK_NS, "panelVisible")) { $("#gk-focus-panel").remove(); return; }
+
+ const actor = this.currentActor();
+ if (!st.forced) {
+ if (!actor) { $("#gk-focus-panel").remove(); return; }
+ if (!gkActorHasBrotherhood(actor) && !game.settings.get(GK_NS, "panelAutoOpen")) {
+ $("#gk-focus-panel").remove(); return;
+ }
+ }
+ return this._renderInternal(actor);
+ }
+
+ static async _renderInternal(actor) {
+ const st = await this.getState();
+
+ const { total, bonus } = gkAlliesAndBonus(actor);
+ const x = Number.isFinite(st.x) ? st.x : (window.innerWidth - 260);
+ const y = Number.isFinite(st.y) ? st.y : (window.innerHeight - 120);
+ const w = Number.isFinite(st.w) ? st.w : 260;
+ const h = Number.isFinite(st.h) ? st.h : 110;
+
+ const root = $("#gk-focus-panel");
+ const html = $(`
+
+
+
+ Allies: ${total} · Bonus: +${bonus}
+ Only one linked GK should use a psychic power as their Action per round. Subsequent powers add +1 Wrath die.
+
+ `);
+
+ if (root.length) root.replaceWith(html); else $("body").append(html);
+
+ html.find(".gk-close").off("click").on("click", async () => {
+ const left = parseInt(html.css("left")) || x;
+ const top = parseInt(html.css("top")) || y;
+ const ww = parseInt(html.outerWidth()) || w;
+ const hh = parseInt(html.outerHeight()) || h;
+ await this.saveState({ closed: true, forced: false, x:left, y:top, w:ww, h:hh });
+ html.remove();
+ });
+
+ const handle = html.find(".window-header");
+ let dragging = false, ox = 0, oy = 0;
+ function onMove(ev) {
+ if (!dragging) return;
+ const nx = Math.max(0, Math.min(window.innerWidth - html.outerWidth(), ev.clientX - ox));
+ const ny = Math.max(0, Math.min(window.innerHeight - html.outerHeight(), ev.clientY - oy));
+ html.css({ left: nx, top: ny });
+ }
+ async function onUp() {
+ if (!dragging) return;
+ dragging = false;
+ $(window).off("pointermove.gkfp").off("pointerup.gkfp");
+ const left = parseInt(html.css("left")) || 0;
+ const top = parseInt(html.css("top")) || 0;
+ const ww = parseInt(html.outerWidth()) || w;
+ const hh = parseInt(html.outerHeight()) || h;
+ await GKFocusPanel.saveState({ x:left, y:top, w:ww, h:hh });
+ }
+ handle.off("pointerdown.gkfp").on("pointerdown.gkfp", (ev) => {
+ dragging = true;
+ const r = html[0].getBoundingClientRect();
+ ox = ev.clientX - r.left; oy = ev.clientY - r.top;
+ $(window).on("pointermove.gkfp", onMove).on("pointerup.gkfp", onUp);
+ });
+
+ $(window).off("pointerup.gkfp-size").on("pointerup.gkfp-size", async () => {
+ if (!document.body.contains(html[0])) return;
+ const ww = parseInt(html.outerWidth()) || w;
+ const hh = parseInt(html.outerHeight()) || h;
+ await GKFocusPanel.saveState({ w: ww, h: hh });
+ });
+ }
+}
+
+/* ---------------- left toolbar button (v12 & v13) ---------------- */
+Hooks.on("getSceneControlButtons", (controls) => {
+ // v12: controls = SceneControl[]
+ if (Array.isArray(controls)) {
+ const token = controls.find(c => c?.name === "token");
+ if (!token) return;
+ if (token.tools?.some?.(t => t?.name === "gk-toggle-focus")) return;
+ token.tools.push({
+ name: "gk-toggle-focus",
+ title: "Toggle Brotherhood Focus Panel",
+ icon: "fas fa-bolt",
+ button: true,
+ visible: true,
+ order: 999,
+ onClick: () => GKFocusPanel.toggle()
+ });
+ return;
+ }
+ // v13: controls = Record; tools = Record
+ const token = controls?.token;
+ if (!token) return;
+ token.tools ??= {};
+ if (!token.tools["gk-toggle-focus"]) {
+ token.tools["gk-toggle-focus"] = {
+ name: "gk-toggle-focus",
+ title: "Toggle Brotherhood Focus Panel",
+ icon: "fas fa-bolt",
+ button: true,
+ visible: true,
+ order: 999,
+ onClick: () => GKFocusPanel.toggle()
+ };
+ }
+});
+
+/* ---------------- reactive hooks ---------------- */
+Hooks.on("canvasReady", () => GKFocusPanel.render());
+Hooks.on("controlToken", () => GKFocusPanel.render());
+Hooks.on("updateCombat", () => GKFocusPanel.render());
+Hooks.on("createToken", () => GKFocusPanel.render());
+Hooks.on("deleteToken", () => GKFocusPanel.render());