/* === Advally Bug Report: tokens (XF style properties; the fallback chain covers styles that have variations disabled and therefore emit no --xf-* custom properties) === */ .advally-bug-ui { --advally-bug-primary: hsl(var(--xf-buttonPrimaryBg)); --advally-bug-mask: hsla(var(--xf-overlayMaskColor)); --advally-bug-outline: hsl(var(--xf-errorFeatureColor)); --advally-bug-content: hsl(var(--xf-contentBg)); --advally-bug-text: hsl(var(--xf-textColor)); --advally-bug-muted: hsl(var(--xf-textColorMuted)); --advally-bug-border: hsl(var(--xf-borderColor)); --advally-bug-highlightBg: hsl(var(--xf-contentHighlightBg)); } .advally-bug-ui [hidden] { display: none !important; } .advally-bug-mark { display: block; border-radius: 50%; object-fit: cover; } /* === Bubble (bottom-right, collapses to an edge tab) === */ #advally-bug-root { position: fixed; bottom: 80px; right: 20px; z-index: 99999; display: flex; align-items: center; gap: 4px; transition: opacity .2s, transform .2s; } #advally-bug-root.is-hidden { opacity: 0; pointer-events: none; transform: translateX(20px); visibility: hidden; transition: opacity .2s, transform .2s, visibility 0s .2s; } /* The launcher is TARS alone: 50px, arm down at rest, saluting when the pointer or keyboard focus arrives, with the words in a tooltip beside him (Doug, 2026-09-30). */ #advally-bug-bubble { width: 50px; height: 50px; box-sizing: border-box; padding: 0; border-radius: 50%; border: 0; background: transparent; box-shadow: 0 4px 12px rgba(0,0,0,.3); cursor: pointer; display: flex; align-items: center; justify-content: center; overflow: hidden; transition: transform .2s, box-shadow .2s; } #advally-bug-bubble:hover { transform: scale(1.08); box-shadow: 0 6px 16px rgba(0,0,0,.4); } #advally-bug-bubble:active { transform: scale(.95); } #advally-bug-bubble .advally-bug-tars { display: block; width: 50px; height: 50px; max-width: none; flex: none; } /* The arm is two bars on two joints. At rest the shoulder turns it straight down and the elbow straightens; hover and focus bring both back to zero, which is the salute as drawn. */ #advally-bug-root .advally-bug-tars-upper { transform: rotate(-90deg); transition: transform .42s cubic-bezier(.34,1.4,.5,1); } #advally-bug-root .advally-bug-tars-fore { transform: rotate(-147deg); transition: transform .42s cubic-bezier(.34,1.4,.5,1) .04s; } /* Only TARS himself raises the salute and the tooltip: his own hover, or keyboard focus on him. The hide arrow does not, and neither does the focus the form hands back when it closes (4.2.4 keyed both to the whole launcher, so they stuck after a mouse close). */ #advally-bug-bubble:hover .advally-bug-tars-upper, #advally-bug-bubble:hover .advally-bug-tars-fore, #advally-bug-bubble:focus-visible .advally-bug-tars-upper, #advally-bug-bubble:focus-visible .advally-bug-tars-fore { transform: rotate(0deg); } /* The tooltip sits to the left of the launcher and only exists where there is a pointer to hover with. */ /* The label is a quiet chip that hugs TARS. Its ground is the page's text colour and its ink is the page's surface, so it stands off any theme without a border, and it sets its own typeface: the 4.2.4 box borrowed the board's panel, border and font and read as a 1990s tooltip. */ #advally-bug-root .advally-bug-tip { position: absolute; right: calc(100% + 10px); top: 50%; transform: translate(4px, -50%); white-space: nowrap; padding: 7px 11px; border-radius: 999px; background: var(--advally-bug-text, CanvasText); color: var(--advally-bug-content, Canvas); box-shadow: 0 4px 14px rgba(0,0,0,.22); font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 12px; font-weight: 500; font-style: normal; letter-spacing: .01em; line-height: 1; text-transform: none; opacity: 0; pointer-events: none; transition: opacity .16s ease-out, transform .16s ease-out; } #advally-bug-bubble:hover ~ .advally-bug-tip, #advally-bug-bubble:focus-visible ~ .advally-bug-tip { opacity: 1; transform: translate(0, -50%); } @media (hover: none) { #advally-bug-root .advally-bug-tip { display: none; } } @media (prefers-reduced-motion: reduce) { #advally-bug-root .advally-bug-tars-upper, #advally-bug-root .advally-bug-tars-fore, #advally-bug-root .advally-bug-tip { transition: none; } } /* The hide control is a small badge on TARS's far shoulder, in the chip's colours: clear of the saluting arm, and nothing sits between him and his label. Its ::after widens the target to 30px. A touch screen has no hover to reveal it, so there it always shows. */ #advally-bug-collapse-btn { position: absolute; top: -7px; right: -7px; z-index: 1; width: 22px; height: 22px; box-sizing: border-box; padding: 0; border-radius: 50%; border: 0; background: var(--advally-bug-text, CanvasText); color: var(--advally-bug-content, Canvas); box-shadow: 0 1px 4px rgba(0,0,0,.3); cursor: pointer; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity .2s; font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 1; } #advally-bug-collapse-btn::after { content: ""; position: absolute; inset: -4px; border-radius: 50%; } #advally-bug-root:hover #advally-bug-collapse-btn, #advally-bug-collapse-btn:focus-visible { opacity: 1; } @media (hover: none) { #advally-bug-collapse-btn { opacity: 1; } } #advally-bug-tab { position: fixed; bottom: 100px; right: 0; z-index: 99999; width: 32px; height: 48px; box-sizing: border-box; padding: 0; border: 0; border-radius: 8px 0 0 8px; background: var(--advally-bug-primary, Highlight); box-shadow: -2px 2px 8px rgba(0,0,0,.2); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: opacity .2s, transform .2s, width .2s; } #advally-bug-tab.is-hidden { opacity: 0; pointer-events: none; transform: translateX(100%); visibility: hidden; transition: opacity .2s, transform .2s, width .2s, visibility 0s .2s; } #advally-bug-tab:hover { width: 40px; } #advally-bug-tab .advally-bug-mark { width: 26px; height: 26px; max-width: none; flex: none; } /* === Modal === */ #advally-bug-modal { position: fixed; inset: 0; z-index: 99998; display: none; align-items: center; justify-content: center; padding: 16px; box-sizing: border-box; background-color: var(--advally-bug-mask, rgba(0,0,0,.5)); background-image: linear-gradient(rgba(0,0,0,.45), rgba(0,0,0,.45)); } #advally-bug-modal.is-open { display: flex; } /* The launcher sits above the mask (z 99999) and would cover the sheet's buttons on a phone; the approved mockup shows no bubble while the card is open. */ .advally-bug-modal-open #advally-bug-root, .advally-bug-modal-open #advally-bug-tab { display: none; } /* The card paints its own surface. It borrowed .block-container's until 4.1.3, and the Newschoolers theme ships `.block-container { background: transparent }` (IT 2352): the form floated over the page with no card at all. The fallbacks are system colours, so a style that emits no --xf-* tokens (AudioKarma, 4Runner) still gets a readable card. */ #advally-bug-modal .advally-bug-card { margin: 0; width: 100%; max-width: 560px; max-height: 92vh; overflow: auto; box-sizing: border-box; background: var(--advally-bug-content, Canvas); color: var(--advally-bug-text, CanvasText); border: 1px solid var(--advally-bug-border, ButtonBorder); border-radius: 8px; box-shadow: 0 16px 48px rgba(0,0,0,.35); } /* Sent: the form folds away and only the confirmation, the ticket link and Close remain, so the state reads as finished (IT 2352). */ #advally-bug-modal .advally-bug-card.is-sent .advally-bug-label, #advally-bug-modal .advally-bug-card.is-sent textarea.input, #advally-bug-modal .advally-bug-card.is-sent .advally-bug-pick, #advally-bug-modal .advally-bug-card.is-sent .formRow-explain, #advally-bug-modal .advally-bug-card.is-sent .advally-bug-footer-note, #advally-bug-modal .advally-bug-card.is-sent .advally-bug-submit { display: none; } #advally-bug-modal .advally-bug-card.is-sent form { display: flex; flex-direction: column; } #advally-bug-modal .advally-bug-card.is-sent .advally-bug-message { order: 1; margin-top: 0; font-size: 16px; line-height: 1.5; } #advally-bug-modal .advally-bug-card.is-sent .advally-bug-actions { order: 2; margin-top: 16px; } #advally-bug-modal .block-header { display: flex; align-items: center; gap: 8px; margin: 0; } #advally-bug-modal .block-header .advally-bug-mark { width: 24px; height: 24px; flex: none; } #advally-bug-modal .advally-bug-label { display: block; font-weight: 600; margin-bottom: 6px; } #advally-bug-modal textarea.input { width: 100%; box-sizing: border-box; resize: vertical; min-height: 72px; } #advally-bug-modal .advally-bug-pick { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 12px; } #advally-bug-modal .advally-bug-pick .button svg { width: 14px; height: 14px; vertical-align: -2px; margin-right: 4px; } #advally-bug-modal .advally-bug-chip { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; padding: 3px 4px 3px 10px; border-radius: 999px; border: 1px solid var(--advally-bug-border, ButtonBorder); background: var(--advally-bug-highlightBg, Canvas); font-size: 13px; line-height: 1.4; } #advally-bug-modal .advally-bug-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px; } #advally-bug-modal .advally-bug-chip-clear { border: 0; background: transparent; color: inherit; cursor: pointer; font-size: 16px; line-height: 1; padding: 0 5px; border-radius: 50%; } #advally-bug-modal .advally-bug-chip-clear:hover { background: var(--advally-bug-border, ButtonBorder); } #advally-bug-modal .formRow-explain { margin-top: 6px; } #advally-bug-modal .advally-bug-footer-note { display: flex; align-items: flex-start; gap: 6px; margin-top: 12px; font-size: 12px; color: var(--advally-bug-muted, GrayText); } #advally-bug-modal .advally-bug-footer-note svg { width: 14px; height: 14px; flex: none; margin-top: 2px; } #advally-bug-modal .advally-bug-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; } #advally-bug-modal .advally-bug-message { margin-top: 12px; font-size: 13px; } #advally-bug-modal .advally-bug-message--error, #advally-bug-modal .advally-bug-message--empty { color: var(--advally-bug-outline, currentColor); } #advally-bug-modal .advally-bug-proof { margin-top: 4px; color: var(--advally-bug-muted, GrayText); } #advally-bug-modal .advally-bug-spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; margin-right: 6px; vertical-align: -3px; animation: advally-bug-spin .8s linear infinite; } @keyframes advally-bug-spin { to { transform: rotate(360deg); } } @media (max-width: 640px) { #advally-bug-modal { align-items: flex-end; padding: 0; } #advally-bug-modal .advally-bug-card { width: 100%; max-width: none; border-radius: 12px 12px 0 0; max-height: 92vh; overflow: auto; } #advally-bug-modal .advally-bug-chip-label { max-width: 160px; } } @media (prefers-reduced-motion: reduce) { #advally-bug-root, #advally-bug-root.is-hidden, #advally-bug-tab, #advally-bug-tab.is-hidden, #advally-bug-bubble, #advally-bug-collapse-btn { transition: none; } #advally-bug-modal .advally-bug-spinner { animation-duration: 1.6s; } } /* === Identify mode (one fixed highlight box; nothing is added to live elements) === */ .advally-bug-identify-mode, .advally-bug-identify-mode * { cursor: crosshair !important; } .advally-bug-identify-mode #advally-bug-root, .advally-bug-identify-mode #advally-bug-tab { display: none; } #advally-bug-highlight { position: fixed; display: none; pointer-events: none; z-index: 99997; box-sizing: border-box; border-radius: 2px; outline: 3px solid var(--advally-bug-outline, currentColor); outline-offset: 2px; } #advally-bug-highlight.is-active { display: block; } #advally-bug-identify-overlay { position: fixed; inset: 0; z-index: 99996; display: none; pointer-events: none; } #advally-bug-identify-overlay.is-active { display: block; } /* The mask takes the pointer while Point at it is on, so a tap never reaches an iframe under it: a video would play, and an ad would count a click (IT 2573). */ #advally-bug-identify-overlay .advally-bug-identify-mask { position: absolute; inset: 0; background: var(--advally-bug-mask, rgba(0,0,0,.5)); opacity: .5; pointer-events: auto; } /* The bar borrows block-container like the card, so it paints the card's own surface: a theme that blanks .block-container (Newschoolers) left it see-through, black text on the black header (IT 2352). */ #advally-bug-identify-overlay .advally-bug-identify-instruction { position: fixed; top: 16px; left: 50%; transform: translateX(-50%); max-width: calc(100% - 32px); box-sizing: border-box; margin: 0; pointer-events: auto; display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,.3); font-size: 14px; cursor: default; background: var(--advally-bug-content, Canvas); color: var(--advally-bug-text, CanvasText); border: 1px solid var(--advally-bug-border, ButtonBorder); }
{{ phrase('advally_feedback_widget_tooltip') }}
(function(){ function init(){ var MAX_OUTER_HTML = 1500, MAX_SELECTOR_DEPTH = 8, MAX_ANCESTOR_PATH = 5, STORAGE_KEY = 'advallyBugReporterCollapsed'; var MAX_CONSOLE = 100, MAX_NETWORK = 50, MAX_MESSAGE = 1000, MAX_JSON_BYTES = 100000; var MAX_SHOT_BYTES = 900000, SHOT_QUALITY = 0.7, SHOT_RETRY_QUALITY = 0.5, SHOT_TIMEOUT_MS = 15000, SHOT_LIB_WAIT_MS = 5000; /* How long the library may wait on ONE resource before it gives up on that resource and carries on. Its own default is 30s, which is longer than SHOT_TIMEOUT_MS above, so a single stubborn asset ran out OUR budget and the report filed with no screenshot at all. Measured on the culturecoma front page, 1280x900, 9 ad iframes (IT #2009): 33.5s at the default, 6.5s at 3s, byte-for-byte the same 66 KB JPEG. Keep it well under SHOT_TIMEOUT_MS -- the library returning a placeholder for one slow image beats us aborting the whole capture. It is NOT only a per-resource cap, which is why captureScreenshot below checks the canvas: the library spends this same value on the final SVG->canvas decode, and that wait resolves on the timer whether the image decoded or not (IT #2009). */ var SHOT_RESOURCE_TIMEOUT_MS = 3000; var SHOT_BELOW_MARGIN_PX = 200, SHOT_ABOVE_MARGIN_PX = 200; /* The longest the capture may hold the main thread before it hands it back. The capture starts as the form opens, and run in one piece it held the page for 1.3-2.6s on the culturecoma front page, so the reporter's first keystrokes queued behind it (IT 2438). */ var SHOT_SLICE_MS = 12; /* A box whose children can go while it keeps its size: the library copies every element's computed width and height onto its clone, and these are the displays that honour them. An inline box does not, so emptying one would reflow the text it sits in (IT 2352). */ var SHOT_SIZED_DISPLAYS = { 'block': 1, 'flow-root': 1, 'list-item': 1, 'flex': 1, 'grid': 1, 'inline-block': 1, 'inline-flex': 1, 'inline-grid': 1 }; var UI_CLASS = 'advally-bug-ui', TAP_SLOP = 10; var root = document.getElementById('advally-bug-root'), tab = document.getElementById('advally-bug-tab'), collapseBtn = document.getElementById('advally-bug-collapse-btn'); var bubble = document.getElementById('advally-bug-bubble'), modal = document.getElementById('advally-bug-modal'), form = document.getElementById('advally-bug-form'); if (!root || !tab || !bubble || !modal || !form) return; var card = modal.querySelector('.advally-bug-card'); var textarea = document.getElementById('advally-bug-what-wrong'), pageUrlInput = form.querySelector('[name=page_url]'); var identifyBtn = document.getElementById('advally-bug-identify-btn'), chip = document.getElementById('advally-bug-chip'), chipLabel = chip.querySelector('.advally-bug-chip-label'), chipClear = document.getElementById('advally-bug-chip-clear'); var cancelBtn = form.querySelector('.advally-bug-cancel'), cancelText = cancelBtn.querySelector('.button-text') || cancelBtn, submitBtn = form.querySelector('.advally-bug-submit'); var msgBox = form.querySelector('.advally-bug-message'), msgSending = msgBox.querySelector('.advally-bug-message--sending'), msgSuccess = msgBox.querySelector('.advally-bug-message--success'), msgError = msgBox.querySelector('.advally-bug-message--error'), msgEmpty = msgBox.querySelector('.advally-bug-message--empty'); var ticketLink = msgSuccess.querySelector('.advally-bug-ticket-link'), proofLine = msgSuccess.querySelector('.advally-bug-proof'), errorDetail = msgError.querySelector('.advally-bug-error-detail'); var overlay = document.getElementById('advally-bug-identify-overlay'), cancelIdentifyBtn = document.getElementById('advally-bug-cancel-identify'), highlight = document.getElementById('advally-bug-highlight'); var ds = modal.dataset || {}; var selected = null, isIdentifyMode = false, openedAt = null, busy = false, sent = false, lastFocus = null, openedByPointer = false; var pointerStart = null, hoverTarget = null, swallowNextClick = false, swallowTimer = null, shotLibPromise = null; /* ----- element labelling + selector path ----- */ function elLabel(el){ if (!el) return 'unknown element'; if (el.tagName === 'LABEL') return (el.textContent||'').trim().replace(/\*$/,'').trim(); if (el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.tagName === 'TEXTAREA') { if (el.id) { var lbl = document.querySelector('label[for="'+el.id+'"]'); if (lbl) return (lbl.textContent||'').trim().replace(/\*$/,'').trim() + ' field'; } return (el.placeholder || el.name || 'Input') + ' field'; } if (el.tagName === 'BUTTON' || (el.tagName === 'A' && el.classList.contains('button'))) return ((el.textContent||'').trim() || el.title || el.getAttribute('aria-label') || 'Button') + ' button'; if (el.tagName === 'TD') { var tbl = el.closest('table'); if (tbl) { var th = tbl.querySelector('thead th:nth-child('+(el.cellIndex+1)+')'); if (th) return (th.textContent||'').trim() + ' column'; } return 'Table cell'; } if (el.tagName === 'TH') return (el.textContent||'').trim() + ' column header'; if (el.tagName === 'IMG') return (el.alt || el.title || 'Image').trim().substring(0, 50) + ' image'; if (/^H[1-6]$/.test(el.tagName)) return (el.textContent||'').trim(); if (el.classList.contains('p-nav-list-item') || el.classList.contains('menu-linkRow')) return (el.textContent||'').trim() + ' menu item'; var t = (el.textContent||'').trim().replace(/\s+/g, ' ').substring(0,50); if (t && t.length > 2) return t + (t.length >= 50 ? '...' : ''); return el.tagName.toLowerCase() + ' element'; } function cssSelectorFor(el){ if (!el || el.nodeType !== 1) return null; if (el === document.body) return 'body'; var parts = [], cur = el; while (cur && cur !== document.body && parts.length < MAX_SELECTOR_DEPTH) { var p = cur.tagName.toLowerCase(); if (cur.id) { parts.unshift('#' + cur.id); break; } if (cur.className && typeof cur.className === 'string') { var classes = cur.className.trim().split(/\s+/).filter(function(c){ return c && c.indexOf('advally-bug-') !== 0; }).slice(0,3).join('.'); if (classes) p += '.' + classes; } var par = cur.parentElement; if (par) { var sib = Array.prototype.filter.call(par.children, function(c){ return c.tagName === cur.tagName; }); if (sib.length > 1) p += ':nth-of-type(' + (sib.indexOf(cur)+1) + ')'; } parts.unshift(p); cur = par; } return parts.join(' > '); } function stripTokens(root){ /* XF renders the reporter's live CSRF token into pickable markup: every XF form opens with a hidden _xfToken input, the account menu Log out row carries ?t=, some GET action links carry ?_xfToken=, and the html element carries data-csrf. None of it belongs in a ticket. ?t= is matched by the token's shape (time, comma, hash), so XF search's own ?t=post stays. The controller scrubs the same things again (scrubOuterHtml). */ var nodes = [root].concat(Array.prototype.slice.call(root.querySelectorAll('*'))); nodes.forEach(function(n){ if (n.tagName.toUpperCase() === 'INPUT' && n.getAttribute('name') === '_xfToken') { if (n === root) n.setAttribute('value', ''); else if (n.parentNode) n.parentNode.removeChild(n); return; } if (n.hasAttribute('data-csrf')) n.setAttribute('data-csrf', ''); ['href', 'action', 'formaction', 'data-href'].forEach(function(a){ var v = n.getAttribute(a); if (v === null) return; var s = v.replace(/([?&])(?:(?:_xfToken=|t=\d+(?:,|%2[Cc]))[^&#]*&?)+/g, function(m, lead){ return /&$/.test(m) ? lead : ''; }); if (s !== v) n.setAttribute(a, s); }); }); } function truncateOuterHtml(el){ try { var clone = el.cloneNode(true); (function strip(n){ if (n.nodeType !== 1) return; if (n.classList && n.classList.length) { var rm = Array.prototype.filter.call(n.classList, function(c){ return c.indexOf('advally-bug-') === 0; }); rm.forEach(function(c){ n.classList.remove(c); }); if (n.classList.length === 0 && n.hasAttribute('class')) n.removeAttribute('class'); } for (var i=0;i