Universal Manual Blur

Manual blur anywhere. Client-side.

// ==UserScript==
// @name         Universal Manual Blur
// @namespace    https://greasyfork.org/en/users/1451802
// @version      1.0
// @icon         https://www.svgrepo.com/show/416283/blur-drop-water.svg
// @author       NormalRandomPeople (https://github.com/NormalRandomPeople)
// @description  Manual blur anywhere. Client-side.
// @match        *://*/*
// @grant        GM_addStyle
// @run-at       document-idle
// @compatible   chrome
// @compatible   firefox
// @compatible   opera
// @compatible   edge
// @compatible   brave
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  const DEFAULT_SLIDER_VALUE = 40;
  const REGION_BG_MIN_ALPHA = 0.02;
  const UI_ZINDEX = 2147483647;
  const HANDLE_SIZE = 12;

  const css = `
  .umb-toolbar {
    position: fixed;
    right: 12px;
    bottom: 12px;
    z-index: ${UI_ZINDEX};
    background: rgba(16,16,16,0.88);
    color: #fff;
    border-radius: 10px;
    padding: 8px;
    font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
    display:flex;
    gap:8px;
    align-items:center;
    box-shadow: 0 8px 24px rgba(0,0,0,0.5);
    backdrop-filter: blur(6px);
    transition: box-shadow .18s ease, transform .12s linear, background .18s ease;
  }

  .umb-toolbar .umb-toggle-edit {
    transition: box-shadow .18s ease, transform .12s linear;
  }
  .umb-toolbar.umb-editing .umb-toggle-edit {
    box-shadow: 0 4px 18px rgba(255,60,60,0.28), inset 0 0 6px rgba(255,60,60,0.06);
    transform: translateY(-1px);
    border-radius: 6px;
  }

  .umb-toolbar button, .umb-toolbar select {
    background:#222; color:#fff; border: none; padding:6px 8px; border-radius:6px; cursor:pointer;
  }
  .umb-toolbar input[type=range] { width:140px; }
  .umb-help { font-size:12px; color:#ddd; margin-left:8px; }

  .umb-toolbar.umb-collapsed {
    width:44px;
    height:44px;
    padding:6px;
    gap:0;
    justify-content:center;
  }
  .umb-toolbar.umb-collapsed > *:not(.umb-toggle-collapse) { display:none; }

  .umb-toggle-collapse { font-weight:700; padding:6px 8px; }

  .umb-overlay {
    position: absolute;
    top: 0; left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: ${UI_ZINDEX - 1};
  }

  .umb-region {
    position: absolute;
    box-sizing: border-box;
    background: rgba(255,255,255,${REGION_BG_MIN_ALPHA});
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    border: 2px dashed rgba(255,255,255,0.6);
    pointer-events: none;
    overflow: visible;
  }

  .umb-region.ellipse { border-radius: 50%; }

  .umb-region.editing { border-style: solid; box-shadow: 0 6px 20px rgba(0,0,0,0.6); }

  .umb-handle {
    position: absolute;
    width: ${HANDLE_SIZE}px;
    height: ${HANDLE_SIZE}px;
    background: #fff;
    border-radius: 3px;
    border: 1px solid #333;
    box-shadow: 0 2px 6px rgba(0,0,0,0.4);
    transform: translate(-50%,-50%);
    cursor: nwse-resize;
    z-index: ${UI_ZINDEX + 1};
  }
  .umb-handle.hidden { display: none; }

  .umb-bubble {
    position: fixed;
    z-index: ${UI_ZINDEX + 2};
    background: rgba(0,0,0,0.88);
    color: #fff;
    padding:8px 10px;
    border-radius:8px;
    font-size:13px;
    pointer-events: auto;
    max-width: 320px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.6);
  }

  .umb-bubble small { display:block; color:#bbb; margin-top:6px; font-size:12px; }
  `;

  try { GM_addStyle(css); } catch (e) {
    const s = document.createElement('style'); s.textContent = css; document.head.appendChild(s);
  }

  function mapSliderToBlurAndAlpha(sliderValue) {
    const v = Math.max(0, Math.min(100, Number(sliderValue) || 0));
    const blurPx = Math.round(Math.pow(v / 100, 1.1) * 90);
    const alpha = Math.min(0.65, Math.max(REGION_BG_MIN_ALPHA, 0.02 + blurPx / 180));
    return { blurPx, alpha };
  }

  const toolbar = document.createElement('div');
  toolbar.className = 'umb-toolbar';
  toolbar.innerHTML = `
    <button class="umb-toggle-collapse" id="umb-collapse-btn">⏵</button>
    <button class="umb-toggle-edit" id="umb-toggle-edit">Enter edit</button>
    <button id="umb-new-rect">New rectangle</button>
    <button id="umb-new-ellipse">New ellipse</button>
    <label class="umb-tag">Blur: <input id="umb-global-blur" type="range" min="0" max="100" value="${DEFAULT_SLIDER_VALUE}" /></label>
    <button id="umb-clear-all">Clear all</button>
    <button id="umb-help-btn">Help</button>
  `;
  document.body.appendChild(toolbar);

  const btnEdit = toolbar.querySelector('#umb-toggle-edit');
  const btnNewRect = toolbar.querySelector('#umb-new-rect');
  const btnNewEllipse = toolbar.querySelector('#umb-new-ellipse');
  const inputGlobalBlur = toolbar.querySelector('#umb-global-blur');
  const btnClear = toolbar.querySelector('#umb-clear-all');
  const btnHelp = toolbar.querySelector('#umb-help-btn');
  const btnCollapse = toolbar.querySelector('#umb-collapse-btn');

  const overlay = document.createElement('div');
  overlay.className = 'umb-overlay';
  overlay.style.top = '0px';
  overlay.style.left = '0px';
  document.body.appendChild(overlay);
  function updateOverlaySize() {
    overlay.style.width = Math.max(document.documentElement.scrollWidth, window.innerWidth) + 'px';
    overlay.style.height = Math.max(document.documentElement.scrollHeight, window.innerHeight) + 'px';
  }
  window.addEventListener('resize', updateOverlaySize);
  window.addEventListener('scroll', updateOverlaySize);
  const mo = new MutationObserver(updateOverlaySize);
  mo.observe(document.documentElement, { childList: true, subtree: true, attributes: true });
  updateOverlaySize();

  let regions = [];
  let editing = false;
  let selectedRegion = null;
  let isPointerDown = false;
  let drawMode = null;
  let drawStart = null;
  let perRegionBubble = null;
  let helpBubble = null;

  function createRegion({ left = 100, top = 100, width = 240, height = 120, sliderValue = DEFAULT_SLIDER_VALUE, shape = 'rect' } = {}) {
    const el = document.createElement('div');
    el.className = 'umb-region' + (shape === 'ellipse' ? ' ellipse' : '');
    el.style.left = left + 'px';
    el.style.top = top + 'px';
    el.style.width = width + 'px';
    el.style.height = height + 'px';
    el.style.pointerEvents = editing ? 'auto' : 'none';
    el.style.zIndex = UI_ZINDEX - 0;
    overlay.appendChild(el);

    const region = { el, left, top, width, height, sliderValue, shape, handles: null };
    applyMappedStyleToRegion(region);

    el.addEventListener('pointerdown', regionPointerDown);
    el.addEventListener('dblclick', () => {
      region.shape = region.shape === 'rect' ? 'ellipse' : 'rect';
      el.classList.toggle('ellipse', region.shape === 'ellipse');
      applyMappedStyleToRegion(region);
    });

    regions.push(region);
    return region;
  }

  function applyMappedStyleToRegion(region) {
    const { blurPx, alpha } = mapSliderToBlurAndAlpha(region.sliderValue);
    region.el.style.left = region.left + 'px';
    region.el.style.top = region.top + 'px';
    region.el.style.width = Math.max(2, region.width) + 'px';
    region.el.style.height = Math.max(2, region.height) + 'px';
    region.el.style.background = `rgba(255,255,255,${alpha})`;
    if (blurPx === 0) {
      region.el.style.backdropFilter = 'none';
      region.el.style.webkitBackdropFilter = 'none';
    } else {
      region.el.style.backdropFilter = `blur(${blurPx}px)`;
      region.el.style.webkitBackdropFilter = `blur(${blurPx}px)`;
    }
    region.el.classList.toggle('ellipse', region.shape === 'ellipse');
  }

  function setGlobalBlurToRegions(sliderValue) {
    regions.forEach(r => { r.sliderValue = sliderValue; applyMappedStyleToRegion(r); });
  }

  function selectRegion(region) {
    if (selectedRegion && selectedRegion !== region) deselectRegion(selectedRegion);
    selectedRegion = region;
    if (!region) return;
    region.el.classList.add('editing');
    addHandles(region);
    showPerRegionBubble(region);
  }

  function deselectRegion(region) {
    if (!region) return;
    region.el.classList.remove('editing');
    removeHandles(region);
    hidePerRegionBubble();
    if (selectedRegion === region) selectedRegion = null;
  }

  function addHandles(region) {
    removeHandles(region);
    const pos = ['nw','n','ne','e','se','s','sw','w'];
    region.handles = {};
    pos.forEach(p => {
      const h = document.createElement('div');
      h.className = 'umb-handle';
      h.dataset.pos = p;
      h.style.pointerEvents = editing ? 'auto' : 'none';
      overlay.appendChild(h);
      region.handles[p] = h;
      h.addEventListener('pointerdown', handlePointerDown);
    });
    positionHandles(region);
  }

  function removeHandles(region) {
    if (!region || !region.handles) return;
    for (const k in region.handles) {
      const h = region.handles[k];
      if (h && h.parentNode) h.parentNode.removeChild(h);
    }
    region.handles = null;
  }

  function positionHandles(region) {
    if (!region.handles) return;
    const x = region.left, y = region.top, w = region.width, h = region.height;
    const centers = { nw: [x, y], n: [x + w/2, y], ne: [x + w, y], e: [x + w, y + h/2], se: [x + w, y + h], s: [x + w/2, y + h], sw: [x, y + h], w: [x, y + h/2] };
    for (const p in centers) {
      const [cx, cy] = centers[p];
      const el = region.handles[p];
      el.style.left = (cx - window.scrollX) + 'px';
      el.style.top = (cy - window.scrollY) + 'px';
      el.style.display = editing ? '' : 'none';
      el.style.pointerEvents = editing ? 'auto' : 'none';
      el.style.position = 'fixed';
    }
  }

  let activeAction = null;

  overlay.addEventListener('pointerdown', (ev) => {
    if (!editing) return;
    if (ev.target !== overlay) return;
    if (!drawMode) return;
    isPointerDown = true;
    const docX = ev.pageX, docY = ev.pageY;
    drawStart = { x: docX, y: docY };
    const region = createRegion({ left: docX, top: docY, width: 2, height: 2, sliderValue: Number(inputGlobalBlur.value), shape: drawMode === 'ellipse' ? 'ellipse' : 'rect' });
    activeAction = { type: 'draw', region, startX: docX, startY: docY };
    selectRegion(region);
    ev.preventDefault();
  });

  function regionPointerDown(ev) {
    if (!editing) return;
    ev.stopPropagation();
    isPointerDown = true;
    const el = ev.currentTarget;
    const region = regions.find(r => r.el === el);
    if (!region) return;
    selectRegion(region);
    const docX = ev.pageX, docY = ev.pageY;
    activeAction = { type: 'move', region, startX: docX, startY: docY, origLeft: region.left, origTop: region.top };
    el.setPointerCapture(ev.pointerId);
    ev.preventDefault();
  }

  function handlePointerDown(ev) {
    if (!editing) return;
    ev.stopPropagation();
    isPointerDown = true;
    const handle = ev.currentTarget;
    const region = regions.find(r => r.handles && Object.values(r.handles).includes(handle));
    if (!region) return;
    selectRegion(region);
    activeAction = { type: 'resize', region, handlePos: handle.dataset.pos, startX: ev.pageX, startY: ev.pageY, orig: { left: region.left, top: region.top, width: region.width, height: region.height } };
    handle.setPointerCapture(ev.pointerId);
    ev.preventDefault();
  }

  window.addEventListener('pointermove', (ev) => {
    if (!editing || !isPointerDown || !activeAction) return;
    const a = activeAction;
    const dx = ev.pageX - a.startX, dy = ev.pageY - a.startY;

    if (a.type === 'draw') {
      const left = Math.min(a.startX, ev.pageX);
      const top = Math.min(a.startY, ev.pageY);
      const w = Math.max(2, Math.abs(ev.pageX - a.startX));
      const h = Math.max(2, Math.abs(ev.pageY - a.startY));
      a.region.left = left; a.region.top = top; a.region.width = w; a.region.height = h;
      applyMappedStyleToRegion(a.region);
      positionHandles(a.region);
    } else if (a.type === 'move') {
      a.region.left = a.origLeft + dx; a.region.top = a.origTop + dy;
      applyMappedStyleToRegion(a.region);
      positionHandles(a.region);
    } else if (a.type === 'resize') {
      const orig = a.orig;
      let { left, top, width, height } = orig;
      const minSize = 6; const hp = a.handlePos;
      if (hp.includes('n')) { const newTop = top + dy; const newHeight = height - dy; if (newHeight > minSize) { top = newTop; height = newHeight; } }
      if (hp.includes('s')) { const newHeight = height + dy; if (newHeight > minSize) { height = newHeight; } }
      if (hp.includes('w')) { const newLeft = left + dx; const newWidth = width - dx; if (newWidth > minSize) { left = newLeft; width = newWidth; } }
      if (hp.includes('e')) { const newWidth = width + dx; if (newWidth > minSize) { width = newWidth; } }
      a.region.left = left; a.region.top = top; a.region.width = width; a.region.height = height;
      applyMappedStyleToRegion(a.region);
      positionHandles(a.region);
    }
    ev.preventDefault();
  });

  window.addEventListener('pointerup', (ev) => {
    if (!editing) return;
    if (!isPointerDown) return;
    isPointerDown = false;
    if (activeAction && activeAction.type === 'draw') {
      const r = activeAction.region;
      if (r.width < 6 || r.height < 6) removeRegion(r);
      else { applyMappedStyleToRegion(r); positionHandles(r); positionPerRegionBubble(r); }
    } else if (activeAction && activeAction.type === 'move') {
      if (activeAction.region) positionPerRegionBubble(activeAction.region);
    } else if (activeAction && activeAction.type === 'resize') {
      if (activeAction.region) positionPerRegionBubble(activeAction.region);
    }
    if (activeAction && activeAction.region && activeAction.region.el) {
      try { activeAction.region.el.releasePointerCapture(ev.pointerId); } catch (e) {}
    }
    activeAction = null;
  });

  function removeRegion(region) {
    if (!region) return;
    if (region.el && region.el.parentNode) region.el.parentNode.removeChild(region.el);
    removeHandles(region);
    if (selectedRegion === region) selectedRegion = null;
    regions = regions.filter(r => r !== region);
    hidePerRegionBubble();
  }

  window.addEventListener('keydown', (ev) => {
    if (ev.key === 'Escape') {
      if (editing) toggleEdit(false);
    }
    if ((ev.key === 'Delete' || ev.key === 'Backspace') && selectedRegion) {
      removeRegion(selectedRegion);
    }
    if ((ev.key === '+' || ev.key === '=') && selectedRegion) {
      selectedRegion.sliderValue = Math.min(100, (selectedRegion.sliderValue || DEFAULT_SLIDER_VALUE) + 5);
      applyMappedStyleToRegion(selectedRegion);
      updatePerRegionBubbleSlider(selectedRegion);
    }
    if ((ev.key === '-' || ev.key === '_') && selectedRegion) {
      selectedRegion.sliderValue = Math.max(0, (selectedRegion.sliderValue || DEFAULT_SLIDER_VALUE) - 5);
      applyMappedStyleToRegion(selectedRegion);
      updatePerRegionBubbleSlider(selectedRegion);
    }
  });

  function toggleEdit(state = !editing) {
    editing = state;
    overlay.style.pointerEvents = editing ? 'auto' : 'none';
    regions.forEach(r => {
      r.el.style.pointerEvents = editing ? 'auto' : 'none';
      if (r.handles) {
        for (const k in r.handles) {
          r.handles[k].style.display = editing ? '' : 'none';
          r.handles[k].style.pointerEvents = editing ? 'auto' : 'none';
        }
      }
    });
    btnEdit.textContent = editing ? 'Exit edit' : 'Enter edit';

    if (editing) {
      toolbar.classList.add('umb-editing');
      btnCollapse.title = 'Collapse toolbar (you are in edit mode)';
    } else {
      toolbar.classList.remove('umb-editing');
      btnCollapse.title = 'Collapse toolbar';
    }

    if (!editing && selectedRegion) deselectRegion(selectedRegion);
    if (helpBubble) positionHelpBubble();
  }

  btnEdit.addEventListener('click', () => toggleEdit(!editing));
  btnNewRect.addEventListener('click', () => {
    const left = window.scrollX + (window.innerWidth - 320) / 2;
    const top = window.scrollY + (window.innerHeight - 160) / 2;
    const r = createRegion({ left, top, width: 320, height: 160, sliderValue: Number(inputGlobalBlur.value), shape: 'rect' });
    applyMappedStyleToRegion(r); positionHandles(r); selectRegion(r);
    if (!editing) toggleEdit(true);
  });
  btnNewEllipse.addEventListener('click', () => {
    const left = window.scrollX + (window.innerWidth - 240) / 2;
    const top = window.scrollY + (window.innerHeight - 240) / 2;
    const r = createRegion({ left, top, width: 240, height: 240, sliderValue: Number(inputGlobalBlur.value), shape: 'ellipse' });
    applyMappedStyleToRegion(r); positionHandles(r); selectRegion(r);
    if (!editing) toggleEdit(true);
  });

  inputGlobalBlur.addEventListener('input', () => {
    const v = Number(inputGlobalBlur.value);
    setGlobalBlurToRegions(v);
  });

  btnClear.addEventListener('click', () => {
    if (!confirm('Clear all blur regions?')) return;
    for (const r of [...regions]) removeRegion(r);
  });

  function showPerRegionBubble(region) {
    hidePerRegionBubble();
    perRegionBubble = document.createElement('div');
    perRegionBubble.className = 'umb-bubble';
    perRegionBubble.innerHTML = `
      <div><strong>Region</strong></div>
      <div style="margin-top:8px;">Blur: <input id="umb-region-blur" type="range" min="0" max="100" value="${region.sliderValue}" /></div>
      <small>Double-click shape to toggle rect/ellipse • Drag to move • Handles to resize</small>
    `;
    document.body.appendChild(perRegionBubble);

    const slider = perRegionBubble.querySelector('#umb-region-blur');
    slider.addEventListener('input', () => {
      region.sliderValue = Number(slider.value);
      applyMappedStyleToRegion(region);
    });

    function positionBubble() {
      if (!perRegionBubble || !region) return;
      const vx = region.left - window.scrollX;
      const vy = region.top - window.scrollY;
      const bubbleW = perRegionBubble.offsetWidth || 220;
      const bubbleH = perRegionBubble.offsetHeight || 80;
      let left = vx + region.width + 12;
      let top = vy;
      if (left + bubbleW > window.innerWidth - 12) { left = vx - bubbleW - 12; }
      if (left < 12) left = 12;
      if (top + bubbleH > window.innerHeight - 12) top = Math.max(12, window.innerHeight - bubbleH - 12);
      perRegionBubble.style.left = left + 'px';
      perRegionBubble.style.top = top + 'px';
    }
    positionBubble();
    window.addEventListener('scroll', positionBubble);
    window.addEventListener('resize', positionBubble);
    perRegionBubble._positionHandler = positionBubble;
  }

  function updatePerRegionBubbleSlider(region) {
    if (!perRegionBubble) return;
    const slider = perRegionBubble.querySelector('#umb-region-blur');
    if (slider) slider.value = region.sliderValue;
  }

  function hidePerRegionBubble() {
    if (!perRegionBubble) return;
    window.removeEventListener('scroll', perRegionBubble._positionHandler);
    window.removeEventListener('resize', perRegionBubble._positionHandler);
    perRegionBubble.remove();
    perRegionBubble = null;
  }

  function positionPerRegionBubble(region) {
    if (!perRegionBubble || !region) return;
    const vx = region.left - window.scrollX;
    const vy = region.top - window.scrollY;
    const bubbleW = perRegionBubble.offsetWidth || 220;
    const bubbleH = perRegionBubble.offsetHeight || 80;
    let left = vx + region.width + 12;
    let top = vy;
    if (left + bubbleW > window.innerWidth - 12) { left = vx - bubbleW - 12; }
    if (left < 12) left = 12;
    if (top + bubbleH > window.innerHeight - 12) top = Math.max(12, window.innerHeight - bubbleH - 12);
    perRegionBubble.style.left = left + 'px';
    perRegionBubble.style.top = top + 'px';
  }

  btnCollapse.addEventListener('click', () => {
    const collapsed = toolbar.classList.toggle('umb-collapsed');
    btnCollapse.textContent = collapsed ? '⏴' : '⏵';
    if (collapsed && helpBubble) hideHelpBubble();
    if (!collapsed && helpBubble) positionHelpBubble();
  });

  btnHelp.addEventListener('click', () => {
    if (helpBubble) { hideHelpBubble(); return; }
    helpBubble = document.createElement('div');
    helpBubble.className = 'umb-bubble';
    helpBubble.innerHTML = `
      <strong>Universal Manual Blur - Help</strong>
      <div style="margin-top:6px; color:#ddd; font-size:13px;">
        • Enter edit to create and edit blur regions (New rectangle / New ellipse).<br/>
        • Drag on empty page (in edit + after New) to draw a region.<br/>
        • Drag region to move; use handles to resize; double-click to toggle rect/ellipse.<br/>
        • Use the global slider or per-region slider (visible when a region is selected).<br/>
        • Exit edit to interact with the page; regions remain but are non-interactive.<br/>
        • Right-click the toolbar to copy a JSON snapshot of regions.<br/>
        • Ctrl+Shift+e to paste/import a snapshot<br/>
      </div>
    `;
    document.body.appendChild(helpBubble);
    positionHelpBubble();
    window.addEventListener('scroll', positionHelpBubble);
    window.addEventListener('resize', positionHelpBubble);
  });

  function positionHelpBubble() {
    if (!helpBubble) return;
    const rect = toolbar.getBoundingClientRect();
    const bw = helpBubble.offsetWidth || 320;
    let left = Math.max(8, rect.left + rect.width/2 - bw/2);
    let top = rect.top - helpBubble.offsetHeight - 10;
    if (toolbar.classList.contains('umb-collapsed') || top < 8) {
      top = rect.bottom + 10;
    }
    helpBubble.style.left = left + 'px';
    helpBubble.style.top = top + 'px';
  }

  function hideHelpBubble() {
    if (!helpBubble) return;
    window.removeEventListener('scroll', positionHelpBubble);
    window.removeEventListener('resize', positionHelpBubble);
    helpBubble.remove();
    helpBubble = null;
  }

  function updateAllHandles() {
    regions.forEach(r => positionHandles(r));
    if (perRegionBubble && selectedRegion) perRegionBubble._positionHandler && perRegionBubble._positionHandler();
  }
  window.addEventListener('scroll', updateAllHandles);
  window.addEventListener('resize', updateAllHandles);

  toolbar.addEventListener('contextmenu', (ev) => {
    ev.preventDefault();
    const exportData = regions.map(r => ({ left: r.left, top: r.top, width: r.width, height: r.height, sliderValue: r.sliderValue, shape: r.shape }));
    prompt('Copy JSON snapshot (paste with Ctrl+Shift+e):', JSON.stringify(exportData));
  });
  window.addEventListener('keydown', (ev) => {
    if (ev.ctrlKey && ev.shiftKey && ev.key.toLowerCase() === 'e') {
      const raw = prompt('Paste JSON snapshot to import blur regions:');
      if (!raw) return;
      try {
        const arr = JSON.parse(raw);
        if (!Array.isArray(arr)) throw new Error('Not an array');
        for (const r of [...regions]) removeRegion(r);
        for (const it of arr) {
          const rr = createRegion({ left: it.left, top: it.top, width: it.width, height: it.height, sliderValue: it.sliderValue || DEFAULT_SLIDER_VALUE, shape: it.shape || 'rect' });
          applyMappedStyleToRegion(rr); positionHandles(rr);
        }
        alert('Imported ' + arr.length + ' regions.');
      } catch (e) {
        alert('Import failed: ' + e.message);
      }
    }
  });

  function supportsBackdropFilter() {
    const test = document.createElement('div');
    test.style.webkitBackdropFilter = 'blur(2px)';
    test.style.backdropFilter = 'blur(2px)';
    return (test.style.webkitBackdropFilter.length > 0) || (test.style.backdropFilter.length > 0);
  }
  if (!supportsBackdropFilter()) {
    const warn = document.createElement('div');
    warn.style.position = 'fixed';
    warn.style.left = '12px';
    warn.style.bottom = '12px';
    warn.style.zIndex = UI_ZINDEX;
    warn.style.background = 'rgba(200,60,60,0.95)';
    warn.style.color = '#fff';
    warn.style.padding = '8px 10px';
    warn.style.borderRadius = '8px';
    warn.style.fontFamily = 'system-ui, Arial';
    warn.style.fontSize = '13px';
    warn.textContent = 'Notice: your browser may not support backdrop-filter; blur may not appear. Use Chromium, Edge or Firefox for best results.';
    document.body.appendChild(warn);
    setTimeout(() => { try { warn.remove(); } catch (e) {} }, 12000);
  }

  window.addEventListener('beforeunload', () => {
    try { overlay.remove(); toolbar.remove(); } catch (e) {}
  });
})();