diff --git a/torn-attribute-tracker.user.js b/torn-attribute-tracker.user.js index 19780bf..697b019 100644 --- a/torn-attribute-tracker.user.js +++ b/torn-attribute-tracker.user.js @@ -276,6 +276,7 @@ // ===== ui.js (embedded) ===== const TAT_STYLE = ` .tat-root { position: fixed; z-index: 99999; min-width: 320px; max-width: 420px; background: #2b2b2b; color: #ddd; border: 1px solid #444; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.4); font: 13px/1.4 Tahoma, Verdana, sans-serif; padding: 12px 14px; } + .tat-root.tat-anchored { position: static; margin: 0 auto 12px; max-width: 720px; box-shadow: none; } .tat-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 8px; margin-bottom: 10px; border-bottom: 1px solid #444; cursor: move; user-select: none; } .tat-header strong { color: #fff; } .tat-close { cursor: pointer; opacity: 0.6; padding: 0 4px; } @@ -350,10 +351,36 @@ setMode(mode, anchorInfo) { this.mode = mode; if (!this.root) return; + // Clear all position styles this.root.style.transform = ''; this.root.style.top = ''; this.root.style.bottom = ''; this.root.style.left = ''; this.root.style.right = ''; - if (mode === 'free') { this.root.style.bottom = '20px'; this.root.style.left = '20px'; } - else if (anchorInfo && anchorInfo.canAnchor) { this._positionAnchored(anchorInfo.rect); } - else { this.root.style.top = '20px'; this.root.style.left = '50%'; this.root.style.transform = 'translateX(-50%)'; } + this.root.classList.remove('tat-anchored'); + if (mode === 'free') { + // Floating mode: ensure dialog is in body and position at bottom-left. + if (this.root.parentNode !== document.body) { + document.body.appendChild(this.root); + } + this.root.style.bottom = '20px'; + this.root.style.left = '20px'; + } else if (anchorInfo && anchorInfo.canAnchor) { + if (anchorInfo.insertBefore) { + // Docked mode: insert the dialog into the page flow before the + // given element, and add the tat-anchored class to switch to + // static positioning. + anchorInfo.insertBefore.parentNode.insertBefore(this.root, anchorInfo.insertBefore); + this.root.classList.add('tat-anchored'); + } else if (anchorInfo.rect) { + // Fallback: position fixed above the rect (old behavior, used when + // no insertion point is available but a rect was given). + this._positionAnchored(anchorInfo.rect); + } + // If neither insertBefore nor rect, leave the dialog where it is + // (the caller will show an anchorError note). + } else { + // Top-center fallback (used when mode is anchored but no anchor info). + this.root.style.top = '20px'; + this.root.style.left = '50%'; + this.root.style.transform = 'translateX(-50%)'; + } } _positionAnchored(rect) { if (!rect) return; // defensive: setMode may be called without a rect @@ -439,20 +466,20 @@ // ===== main.js (embedded) ===== function findAnchorElement() { - // Try several selectors in priority order. Torn's gym page renders the - // training UI as