Greasy Fork

来自缓存

Greasy Fork is available in English.

UTags 快速导航

悬浮快速导航,支持按站点分组、图标与可编辑导航项。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name                 UTags Quick Nav
// @name:zh-CN           UTags 快速导航
// @namespace            https://github.com/utags
// @homepageURL          https://github.com/utags/userscripts#readme
// @supportURL           https://github.com/utags/userscripts/issues
// @version              0.1.0
// @description          Floating quick navigation with per-site groups, icons, and editable items.
// @description:zh-CN    悬浮快速导航,支持按站点分组、图标与可编辑导航项。
// @icon                 data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%2064%2064%22%20fill%3D%22none%22%3E%3Crect%20x%3D%228%22%20y%3D%228%22%20width%3D%2248%22%20height%3D%2248%22%20rx%3D%2212%22%20stroke%3D%22%231f2937%22%20stroke-width%3D%224%22/%3E%3Cpath%20d%3D%22M22%2032h20M22%2042h16M22%2022h12%22%20stroke%3D%22%231f2937%22%20stroke-width%3D%226%22%20stroke-linecap%3D%22round%22/%3E%3C/svg%3E
// @author               Pipecraft
// @license              MIT
// @match                *://*/*
// @connect              cdn.jsdelivr.net
// @connect              fastly.jsdelivr.net
// @connect              unpkg.com
// @connect              wsrv.nl
// @noframes
// @run-at               document-body
// @grant                GM_xmlhttpRequest
// @grant                GM.getValue
// @grant                GM.setValue
// @grant                GM_registerMenuCommand
// @grant                GM_unregisterMenuCommand
// @grant                GM_addValueChangeListener
// ==/UserScript==
//
;(() => {
  'use strict'
  var __defProp = Object.defineProperty
  var __defProps = Object.defineProperties
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors
  var __getOwnPropSymbols = Object.getOwnPropertySymbols
  var __hasOwnProp = Object.prototype.hasOwnProperty
  var __propIsEnum = Object.prototype.propertyIsEnumerable
  var __defNormalProp = (obj, key, value) =>
    key in obj
      ? __defProp(obj, key, {
          enumerable: true,
          configurable: true,
          writable: true,
          value,
        })
      : (obj[key] = value)
  var __spreadValues = (a, b) => {
    for (var prop in b || (b = {}))
      if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop])
    if (__getOwnPropSymbols)
      for (var prop of __getOwnPropSymbols(b)) {
        if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop])
      }
    return a
  }
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b))
  function getFaviconUrl(href, size = 64) {
    try {
      const domain = new URL(href, location.origin).origin
      const url =
        'https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url='
          .concat(domain, '&size=')
          .concat(size)
      const wrapUrl = 'https://wsrv.nl/?w='
        .concat(size, '&h=')
        .concat(size, '&url=')
        .concat(encodeURIComponent(url), '&default=')
        .concat(defaultFavicons[size])
      return wrapUrl
    } catch (error) {
      console.error('Error generating favicon URL:', error)
      return decodeURIComponent(defaultFavicons[size])
    }
  }
  function getWrappedIconUrl(href, size = 64) {
    try {
      const url = new URL(href, location.origin).toString()
      if (url.startsWith('https://wsrv.nl/')) {
        return url
      }
      const wrapUrl = 'https://wsrv.nl/?w='
        .concat(size, '&h=')
        .concat(size, '&url=')
        .concat(encodeURIComponent(url), '&default=')
        .concat(defaultFavicons[size])
      return wrapUrl
    } catch (error) {
      console.error('Error generating favicon URL:', error)
      return decodeURIComponent(defaultFavicons[size])
    }
  }
  var defaultFavicon16 = encodeURIComponent(
    'https://wsrv.nl/?w=16&h=16&url=th.bing.com/th?id=ODLS.A2450BEC-5595-40BA-9F13-D9EC6AB74B9F'
  )
  var defaultFavicon32 = encodeURIComponent(
    'https://wsrv.nl/?w=32&h=32&url=th.bing.com/th?id=ODLS.A2450BEC-5595-40BA-9F13-D9EC6AB74B9F'
  )
  var defaultFavicon64 = encodeURIComponent(
    'https://wsrv.nl/?w=64&h=64&url=th.bing.com/th?id=ODLS.A2450BEC-5595-40BA-9F13-D9EC6AB74B9F'
  )
  var defaultFavicons = {
    16: defaultFavicon16,
    32: defaultFavicon32,
    64: defaultFavicon64,
  }
  function clearChildren(el) {
    try {
      el.textContent = ''
    } catch (e) {
      try {
        while (el.firstChild) el.firstChild.remove()
      } catch (e2) {}
    }
  }
  function querySelectorAllDeep(root, selector) {
    const result = []
    const visited = /* @__PURE__ */ new Set()
    const visit = (node) => {
      if (!node || visited.has(node)) return
      visited.add(node)
      const anyNode = node
      try {
        if (typeof anyNode.querySelectorAll === 'function') {
          const found = Array.from(anyNode.querySelectorAll(selector))
          for (const el of found) if (el instanceof Element) result.push(el)
        }
      } catch (e) {}
      try {
        const children = Array.from(anyNode.childNodes || [])
        for (const child of children) visit(child)
      } catch (e) {}
      try {
        const shadow = anyNode.shadowRoot
        if (shadow) visit(shadow)
      } catch (e) {}
    }
    visit(root)
    return Array.from(new Set(result))
  }
  var iconCache = /* @__PURE__ */ new Map()
  function renderIcon(s) {
    const span = document.createElement('span')
    span.className = 'icon'
    let t = String(s || '').trim()
    if (!t) t = 'lucide:link'
    if (t.startsWith('lucide:')) {
      const k = t.split(':')[1]
      injectLucideIcon(span, k)
      return span
    }
    if (t.startsWith('url:')) {
      const url = t.slice(4)
      injectImageAsData(span, getWrappedIconUrl(url))
      return span
    }
    if (t.startsWith('svg:')) {
      try {
        const svg = t.slice(4)
        const url =
          'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg)
        const img = document.createElement('img')
        img.width = 16
        img.height = 16
        img.style.objectFit = 'contain'
        img.src = url
        clearChildren(span)
        span.append(img)
      } catch (e) {}
      return span
    }
    span.textContent = t
    return span
  }
  function setIcon(el, icon, title) {
    try {
      clearChildren(el)
      el.append(renderIcon(icon))
      if (title !== void 0) el.title = title
    } catch (e) {}
  }
  var lastSuccessfulCdnIndex = 0
  var cdnBases = [
    'https://cdn.jsdelivr.net/npm',
    'https://fastly.jsdelivr.net/npm',
    'https://unpkg.com',
  ]
  function injectLucideIcon(container, name) {
    try {
      const cached = iconCache.get(name)
      if (cached) {
        const img = document.createElement('img')
        img.width = 16
        img.height = 16
        img.style.objectFit = 'contain'
        img.className = 'lucide-icon'
        img.src = cached
        clearChildren(container)
        container.append(img)
        return
      }
    } catch (e) {}
    const orderedCdnIndices = [
      lastSuccessfulCdnIndex,
      ...[0, 1, 2].filter((i) => i !== lastSuccessfulCdnIndex),
    ]
    const tryFetch = (attempt) => {
      if (attempt >= orderedCdnIndices.length) {
        return
      }
      const cdnIndex = orderedCdnIndices[attempt]
      const cdnBase = cdnBases[cdnIndex]
      const url = ''
        .concat(cdnBase, '/lucide-static@latest/icons/')
        .concat(name, '.svg')
      try {
        GM_xmlhttpRequest({
          method: 'GET',
          url,
          onload(res) {
            try {
              const svg = String(res.responseText || '')
              if (res.status >= 200 && res.status < 300 && svg) {
                const dataUrl =
                  'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg)
                iconCache.set(name, dataUrl)
                const img = document.createElement('img')
                img.width = 16
                img.height = 16
                img.style.objectFit = 'contain'
                img.className = 'lucide-icon'
                img.src = dataUrl
                clearChildren(container)
                container.append(img)
                lastSuccessfulCdnIndex = cdnIndex
              } else {
                tryFetch(attempt + 1)
              }
            } catch (e) {
              tryFetch(attempt + 1)
            }
          },
          onerror() {
            tryFetch(attempt + 1)
          },
        })
      } catch (e) {
        tryFetch(attempt + 1)
      }
    }
    tryFetch(0)
  }
  function injectImageAsData(container, url) {
    try {
      GM_xmlhttpRequest({
        method: 'GET',
        url,
        responseType: 'blob',
        onload(res) {
          try {
            const blob = res.response
            if (!blob) return
            const reader = new FileReader()
            reader.addEventListener('load', () => {
              const img = document.createElement('img')
              img.width = 16
              img.height = 16
              img.style.objectFit = 'contain'
              img.src = String(reader.result || '')
              clearChildren(container)
              container.append(img)
            })
            reader.readAsDataURL(blob)
          } catch (e) {}
        },
      })
    } catch (e) {}
  }
  function uid() {
    return Math.random().toString(36).slice(2, 10)
  }
  function resolveUrlTemplate(s) {
    const re = /{([^}]+)}/g
    return String(s || '').replaceAll(re, (_, body) => {
      var _a
      const parts = String(body || '')
        .split('||')
        .map((x) => x.trim())
        .filter(Boolean)
      const resolvers = {
        hostname() {
          var _a2
          return (
            ((_a2 = globalThis.location) == null ? void 0 : _a2.hostname) || ''
          )
        },
        hostname_without_www() {
          var _a2
          const h =
            ((_a2 = globalThis.location) == null ? void 0 : _a2.hostname) || ''
          return h.startsWith('www.') ? h.slice(4) : h
        },
        query() {
          var _a2
          try {
            const href =
              ((_a2 = globalThis.location) == null ? void 0 : _a2.href) || ''
            const u = new URL(href)
            return (
              u.searchParams.get('query') ||
              u.searchParams.get('q') ||
              u.searchParams.get('kw') ||
              u.searchParams.get('wd') ||
              u.searchParams.get('keyword') ||
              u.searchParams.get('p') ||
              u.searchParams.get('s') ||
              u.searchParams.get('term') ||
              ''
            )
          } catch (e) {}
          return ''
        },
        kw() {
          var _a2
          try {
            const href =
              ((_a2 = globalThis.location) == null ? void 0 : _a2.href) || ''
            const u = new URL(href)
            return u.searchParams.get('kw') || ''
          } catch (e) {}
          return ''
        },
        wd() {
          var _a2
          try {
            const href =
              ((_a2 = globalThis.location) == null ? void 0 : _a2.href) || ''
            const u = new URL(href)
            return u.searchParams.get('wd') || ''
          } catch (e) {}
          return ''
        },
        keyword() {
          var _a2
          try {
            const href =
              ((_a2 = globalThis.location) == null ? void 0 : _a2.href) || ''
            const u = new URL(href)
            return u.searchParams.get('keyword') || ''
          } catch (e) {}
          return ''
        },
        p() {
          var _a2
          try {
            const href =
              ((_a2 = globalThis.location) == null ? void 0 : _a2.href) || ''
            const u = new URL(href)
            return u.searchParams.get('p') || ''
          } catch (e) {}
          return ''
        },
        s() {
          var _a2
          try {
            const href =
              ((_a2 = globalThis.location) == null ? void 0 : _a2.href) || ''
            const u = new URL(href)
            return u.searchParams.get('s') || ''
          } catch (e) {}
          return ''
        },
        term() {
          var _a2
          try {
            const href =
              ((_a2 = globalThis.location) == null ? void 0 : _a2.href) || ''
            const u = new URL(href)
            return u.searchParams.get('term') || ''
          } catch (e) {}
          return ''
        },
        selected() {
          var _a2, _b
          try {
            return (
              ((_b =
                ((_a2 = globalThis.getSelection) == null
                  ? void 0
                  : _a2.call(globalThis)) || void 0) == null
                ? void 0
                : _b.toString()) || ''
            )
          } catch (e) {}
          return ''
        },
      }
      for (const p of parts) {
        const v = String(
          ((_a = resolvers[p]) == null ? void 0 : _a.call(resolvers)) || ''
        ).trim()
        if (v) return v
      }
      return ''
    })
  }
  var style_default =
    '/*! tailwindcss v4.1.17 | MIT License | https://tailwindcss.com */@layer properties;@layer theme, base, components, utilities;@layer theme{:host,:root{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-blue-400:oklch(70.7% 0.165 254.624);--color-blue-500:oklch(62.3% 0.214 259.815);--color-blue-600:oklch(54.6% 0.245 262.881);--color-blue-700:oklch(48.8% 0.243 264.376);--color-gray-50:oklch(98.5% 0.002 247.839);--color-gray-100:oklch(96.7% 0.003 264.542);--color-gray-200:oklch(92.8% 0.006 264.531);--color-gray-300:oklch(87.2% 0.01 258.338);--color-gray-400:oklch(70.7% 0.022 261.325);--color-gray-500:oklch(55.1% 0.027 264.364);--color-gray-600:oklch(44.6% 0.03 256.802);--color-gray-700:oklch(37.3% 0.034 259.733);--color-gray-800:oklch(27.8% 0.033 256.848);--color-gray-900:oklch(21% 0.034 264.665);--color-black:#000;--color-white:#fff;--spacing:0.25rem;--text-xs:0.75rem;--text-xs--line-height:1.33333;--text-sm:0.875rem;--text-sm--line-height:1.42857;--font-weight-medium:500;--font-weight-semibold:600;--tracking-wider:0.05em;--leading-snug:1.375;--radius-md:0.375rem;--radius-lg:0.5rem;--radius-xl:0.75rem;--radius-2xl:1rem;--ease-in:cubic-bezier(0.4,0,1,1);--blur-sm:8px;--default-transition-duration:150ms;--default-transition-timing-function:cubic-bezier(0.4,0,0.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,::backdrop,::file-selector-button,:after,:before{border:0 solid;box-sizing:border-box;margin:0;padding:0}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-size:1em;font-variation-settings:var(--default-mono-font-variation-settings,normal)}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}menu,ol,ul{list-style:none}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}::file-selector-button,button,input,optgroup,select,textarea{background-color:transparent;border-radius:0;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;opacity:1}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentcolor;@supports (color:color-mix(in lab,red,red)){color:color-mix(in oklab,currentcolor 50%,transparent)}}::placeholder{color:currentcolor;@supports (color:color-mix(in lab,red,red)){color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}::file-selector-button,button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer utilities{.collapse{visibility:collapse}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.container{width:100%;@media (width >= 40rem){max-width:40rem}@media (width >= 48rem){max-width:48rem}@media (width >= 64rem){max-width:64rem}@media (width >= 80rem){max-width:80rem}@media (width >= 96rem){max-width:96rem}}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.flex-shrink{flex-shrink:1}.flex-grow{flex-grow:1}.border-collapse{border-collapse:collapse}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.resize{resize:both}.flex-wrap{flex-wrap:wrap}.border{border-style:var(--tw-border-style);border-width:1px}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,rgba(0,0,0,.1)),0 1px 2px -1px var(--tw-shadow-color,rgba(0,0,0,.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-filter{backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}}:host{all:initial}div{line-height:normal}.utqn{color:var(--color-gray-900);font-family:var(--font-sans);font-size:13px;position:fixed;z-index:21474836}.utqn.dark{color:var(--color-gray-100)}.panel{background-color:var(--color-white);border-color:var(--color-gray-200);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;display:flex;flex-direction:column;gap:calc(var(--spacing)*3);max-height:100vh;max-width:360px;overflow-y:auto;padding:calc(var(--spacing)*3);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,rgba(0,0,0,.1)),0 8px 10px -6px var(--tw-shadow-color,rgba(0,0,0,.1))}.panel,.utqn.dark .panel{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.utqn.dark .panel{background-color:var(--color-gray-900);border-color:var(--color-gray-700);--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,rgba(0,0,0,.25))}@keyframes utqn-slide-in-left{0%{opacity:0;transform:translateX(-12px)}to{opacity:1;transform:translateX(0)}}@keyframes utqn-slide-in-right{0%{opacity:0;transform:translateX(12px)}to{opacity:1;transform:translateX(0)}}@keyframes utqn-slide-in-top{0%{opacity:0;transform:translateY(0)}to{opacity:1;transform:translateY(0)}}@keyframes utqn-slide-in-bottom{0%{opacity:0;transform:translateY(0)}to{opacity:1;transform:translateY(0)}}@keyframes utqn-slide-out-left{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(-12px)}}@keyframes utqn-slide-out-right{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(12px)}}@keyframes utqn-slide-out-top{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(0)}}@keyframes utqn-slide-out-bottom{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(0)}}.anim-in-left{animation:utqn-slide-in-left .2s ease-out}.anim-in-right{animation:utqn-slide-in-right .2s ease-out}.anim-in-top{animation:utqn-slide-in-top .2s ease-out}.anim-in-bottom{animation:utqn-slide-in-bottom .2s ease-out}.anim-out-left{animation:utqn-slide-out-left .18s ease-in forwards}.anim-out-right{animation:utqn-slide-out-right .18s ease-in forwards}.anim-out-top{animation:utqn-slide-out-top .18s ease-in forwards}.anim-out-bottom{animation:utqn-slide-out-bottom .18s ease-in forwards}.header{gap:calc(var(--spacing)*2);justify-content:space-between}.header,.header-actions{align-items:center;display:flex}.header-actions{gap:calc(var(--spacing)*1.5)}.header-actions .icon-btn{opacity:0;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:150ms;transition-duration:.15s}.header-actions .icon-btn.toggle,.section .header:hover .header-actions .icon-btn:not(.toggle){opacity:100%}.section .header{margin-bottom:calc(var(--spacing)*0)}.icon-btn{align-items:center;border-radius:var(--radius-md);color:var(--color-gray-600);display:flex;height:calc(var(--spacing)*6);justify-content:center;padding:calc(var(--spacing)*0);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:calc(var(--spacing)*6);--tw-duration:150ms;transition-duration:.15s;&:hover{@media (hover:hover){background-color:var(--color-gray-100)}}&:hover{@media (hover:hover){color:var(--color-gray-900)}}}.utqn.dark .icon-btn{color:var(--color-gray-300);&:hover{@media (hover:hover){background-color:var(--color-gray-800)}}&:hover{@media (hover:hover){color:var(--color-white)}}}.utqn.dark .icon img.lucide-icon{filter:invert(1) brightness(1.15) saturate(1.1)}.icon-btn.active{background-color:var(--color-gray-200);color:var(--color-gray-900);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);--tw-ring-color:var(--color-gray-300)}.icon-btn.active,.utqn.dark .icon-btn.active{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.utqn.dark .icon-btn.active{background-color:var(--color-gray-700);color:var(--color-white);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);--tw-ring-color:var(--color-gray-600)}.title{align-items:center;display:flex;gap:calc(var(--spacing)*1.5);--tw-font-weight:var(--font-weight-semibold);color:var(--color-gray-800);font-weight:var(--font-weight-semibold)}.utqn.dark .title{color:var(--color-gray-100)}.btn{align-items:center;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--color-white);border-color:var(--color-gray-300);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;cursor:pointer;display:inline-flex;gap:calc(var(--spacing)*1.5);justify-content:center;padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*2.5);--tw-font-weight:var(--font-weight-medium);color:var(--color-gray-800);font-weight:var(--font-weight-medium);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,rgba(0,0,0,.1)),0 1px 2px -1px var(--tw-shadow-color,rgba(0,0,0,.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:150ms;transition-duration:.15s;&:hover{@media (hover:hover){background-color:var(--color-gray-100)}}&:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-gray-300);--tw-outline-style:none;outline-style:none}&:active{scale:.99}}.utqn.dark .btn{background-color:var(--color-gray-800);border-color:var(--color-gray-700);color:var(--color-gray-200);&:hover{@media (hover:hover){background-color:var(--color-gray-700)}}&:focus{--tw-ring-color:var(--color-gray-700)}}.btn-primary{background-color:var(--color-blue-600);border-color:var(--color-blue-600);color:var(--color-white);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,rgba(0,0,0,.1)),0 2px 4px -2px var(--tw-shadow-color,rgba(0,0,0,.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);&:hover{@media (hover:hover){border-color:var(--color-blue-700)}}&:hover{@media (hover:hover){background-color:var(--color-blue-700)}}}.modal.dark .btn-primary,.utqn.dark .btn-primary{background-color:var(--color-blue-500);border-color:var(--color-blue-500);color:var(--color-white);&:hover{@media (hover:hover){border-color:var(--color-blue-600)}}&:hover{@media (hover:hover){background-color:var(--color-blue-600)}}}.btn-secondary{background-color:var(--color-gray-100);border-color:var(--color-gray-300);color:var(--color-gray-800);&:hover{@media (hover:hover){background-color:var(--color-gray-200)}}}.modal.dark .btn-secondary,.utqn.dark .btn-secondary{background-color:var(--color-gray-800);border-color:var(--color-gray-700);color:var(--color-gray-200);&:hover{@media (hover:hover){background-color:var(--color-gray-700)}}}.items{display:grid;gap:calc(var(--spacing)*1);grid-template-columns:repeat(var(--cols,1),minmax(0,1fr))}.items input[type=checkbox]{flex:none;height:14px;width:14px}.item{align-items:center;border-radius:var(--radius-md);color:var(--color-gray-900);display:inline-flex;gap:calc(var(--spacing)*1.5);min-width:calc(var(--spacing)*0);overflow:hidden;padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*2);text-decoration-line:none;text-overflow:ellipsis;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));white-space:nowrap;--tw-duration:150ms;transition-duration:.15s;width:100%}.item:hover{background-color:var(--color-gray-100)}.utqn.dark .item:hover{background-color:var(--color-gray-800)}.utqn.dark .item{background-color:var(--color-gray-800);border-color:var(--color-gray-700);color:var(--color-gray-100);&:hover{@media (hover:hover){background-color:var(--color-gray-700)}}}.icon{align-items:center;display:inline-flex;flex:none;height:calc(var(--spacing)*4);justify-content:center;overflow:hidden;width:calc(var(--spacing)*4);--tw-leading:1;line-height:1;white-space:nowrap}.collapsed-tab{background-color:var(--color-gray-700);border-radius:0;height:60px;opacity:40%;position:fixed;width:3px;z-index:21474836}.utqn.dark .collapsed-tab{background-color:var(--color-gray-400);opacity:40%}.collapsed-tab:hover{opacity:80%}.modal-mask{align-items:center;background-color:color-mix(in srgb,#000 40%,transparent);display:flex;inset:calc(var(--spacing)*0);justify-content:center;position:fixed;z-index:2147483647;@supports (color:color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-black) 40%,transparent)}}.modal{color:var(--color-gray-900);font-family:var(--font-sans);font-size:13px}.modal h2:not(.section-title){font-size:16px;margin:calc(var(--spacing)*0);margin-bottom:calc(var(--spacing)*2.5)}.row{display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*2);margin-block:calc(var(--spacing)*1.5)}.modal .row{align-items:center}.modal .actions{justify-content:flex-end}.modal .check{align-items:center;display:inline-flex;gap:calc(var(--spacing)*2);height:32px;width:unset}.modal .check input[type=checkbox]{height:14px;width:14px}.segmented{align-items:center;background-color:var(--color-gray-100);border-color:var(--color-gray-200);border-radius:calc(infinity*1px);border-style:var(--tw-border-style);border-width:1px;display:inline-flex;gap:calc(var(--spacing)*1);padding-block:calc(var(--spacing)*.5);padding-inline:calc(var(--spacing)*1);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,rgba(0,0,0,.1)),0 1px 2px -1px var(--tw-shadow-color,rgba(0,0,0,.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.modal .segmented{margin-bottom:calc(var(--spacing)*3)}.utqn.dark .segmented{background-color:var(--color-gray-800);border-color:var(--color-gray-700)}.seg-item{align-items:center;border-radius:calc(infinity*1px);cursor:pointer;display:inline-flex;-webkit-user-select:none;-moz-user-select:none;user-select:none}.seg-radio{border-width:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.seg-text{border-radius:calc(infinity*1px);color:var(--color-gray-700);padding-block:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*2);text-align:center;width:100%}.utqn.dark .seg-text{color:var(--color-gray-300)}.seg-item .seg-radio:checked+.seg-text{background-color:var(--color-white);color:var(--color-gray-900);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);--tw-ring-color:var(--color-gray-300)}.seg-item .seg-radio:checked+.seg-text,.utqn.dark .seg-item .seg-radio:checked+.seg-text{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.utqn.dark .seg-item .seg-radio:checked+.seg-text{background-color:var(--color-gray-700);color:var(--color-gray-100);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);--tw-ring-color:var(--color-gray-600)}.seg-item .seg-radio:focus+.seg-text{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500)}.field-help{background-color:var(--color-gray-100);border-radius:var(--radius-md);display:block;flex-basis:100%;font-size:12px;margin-left:130px;padding-block:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*2);width:100%;--tw-leading:var(--leading-snug);color:var(--color-gray-700);line-height:var(--leading-snug)}.modal.dark .field-help,.utqn.dark .field-help{background-color:var(--color-gray-800);color:var(--color-gray-300)}.field-help-title{align-items:center;display:flex;gap:calc(var(--spacing)*1);margin-bottom:calc(var(--spacing)*1);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.field-help a{color:var(--color-blue-600);text-decoration-line:underline}.modal.dark .field-help a,.utqn.dark .field-help a{color:var(--color-blue-400);text-decoration-line:underline}input,select,textarea{border-color:var(--color-gray-300);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;flex:1;font-size:13px;padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*2)}textarea{min-height:80px}.grid{display:grid;gap:calc(var(--spacing)*2);grid-template-columns:repeat(2,minmax(0,1fr))}.group-list{display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*1.5);margin-top:calc(var(--spacing)*1.5)}.group-pill{border-color:var(--color-gray-200);border-radius:calc(infinity*1px);border-style:var(--tw-border-style);border-width:1px;cursor:pointer;padding-block:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*2);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:150ms;transition-duration:.15s;&:hover{@media (hover:hover){background-color:var(--color-gray-100)}}}.group-pill.active{background-color:var(--color-gray-900);border-color:var(--color-gray-900);color:var(--color-white)}.utqn.dark .group-pill{border-color:var(--color-gray-700);color:var(--color-gray-200);&:hover{@media (hover:hover){background-color:var(--color-gray-700)}}}.utqn.dark .group-pill.active{background-color:var(--color-gray-100);border-color:var(--color-gray-100);color:var(--color-gray-900)}.mini{border-radius:var(--radius-md);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));padding-block:calc(var(--spacing)*.5);padding-inline:calc(var(--spacing)*1.5)}.btn:disabled{cursor:not-allowed;opacity:50%}.divider{background-color:var(--color-gray-200);height:1px}.utqn.dark .divider{background-color:var(--color-gray-700)}.section-title{background-color:var(--color-gray-100);border-radius:var(--radius-md);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));margin-bottom:calc(var(--spacing)*1);margin-top:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*2);--tw-tracking:var(--tracking-wider);color:var(--color-gray-600);letter-spacing:var(--tracking-wider);text-transform:uppercase}.utqn.dark .section-title{background-color:var(--color-gray-800);color:var(--color-gray-300)}.row label.mini{align-items:center;display:inline-flex;gap:calc(var(--spacing)*2)}.modal{background-color:var(--color-white);border-radius:var(--radius-2xl);max-width:92vw;padding:calc(var(--spacing)*3);width:720px;--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,rgba(0,0,0,.25));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.modal.dark,.utqn.dark .modal{background-color:var(--color-gray-900);color:var(--color-gray-100)}.modal.dark input,.modal.dark select,.modal.dark textarea,.utqn.dark .modal input,.utqn.dark .modal select,.utqn.dark .modal textarea{background-color:var(--color-gray-800);border-color:var(--color-gray-700);color:var(--color-gray-100)}.utqn.dark .modal input::-moz-placeholder,.utqn.dark .modal textarea::-moz-placeholder{color:#9ca3af}.utqn.dark .modal input::placeholder,.utqn.dark .modal textarea::placeholder{color:#9ca3af}.modal.dark input::-moz-placeholder,.modal.dark textarea::-moz-placeholder{color:#9ca3af}.modal.dark input::placeholder,.modal.dark textarea::placeholder{color:#9ca3af}.modal.dark .row label{color:var(--color-gray-400)}.modal.dark .segmented{background-color:var(--color-gray-800);border-color:var(--color-gray-700)}.modal.dark .seg-item .seg-radio:checked+.seg-text{background-color:var(--color-gray-700);color:var(--color-gray-100);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-gray-600)}.modal.dark .seg-text{color:var(--color-gray-300)}.editor{border-radius:var(--radius-2xl);max-height:72vh;overflow-y:auto;padding:calc(var(--spacing)*4)}.editor .grid,.editor .row{gap:calc(var(--spacing)*2)}.editor .row{align-items:center}.editor .row label{color:var(--color-gray-500);width:120px}.utqn.dark .editor .row label{color:var(--color-gray-400)}.editor input,.editor select,.editor textarea{background-color:var(--color-white);border-color:var(--color-gray-300);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*2);&:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-gray-300);--tw-outline-style:none;outline-style:none}}.utqn.dark .editor input,.utqn.dark .editor select,.utqn.dark .editor textarea{background-color:var(--color-gray-800);border-color:var(--color-gray-700);&:focus{--tw-ring-color:var(--color-gray-700)}}input:disabled,select:disabled,textarea:disabled{background-color:var(--color-gray-100);cursor:not-allowed;opacity:60%}.dark input:disabled,.dark select:disabled,.dark textarea:disabled{background-color:var(--color-gray-700);cursor:not-allowed;opacity:60%}.editor .item-row{align-items:center;background-color:var(--color-gray-50);border-radius:var(--radius-md);display:grid;gap:8px;grid-template-columns:1.2fr 1.1fr .9fr 2fr 1fr .9fr 1.3fr auto auto;padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*2)}.editor .item-row:hover{background-color:var(--color-gray-100)}.utqn.dark .editor .item-row{background-color:var(--color-gray-800)}.utqn.dark .editor .item-row:hover{background-color:var(--color-gray-700)}.editor .btn{border-radius:var(--radius-md);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));padding-block:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*2)}.row label{color:var(--color-gray-500);width:120px}.utqn.dark .row label{color:var(--color-gray-400)}.panel-actions,.panel-actions-left{align-items:center;display:flex;gap:calc(var(--spacing)*1.5)}.theme-switch{align-items:center;background-color:var(--color-gray-100);border-color:var(--color-gray-200);border-radius:calc(infinity*1px);border-style:var(--tw-border-style);border-width:1px;display:inline-flex;gap:calc(var(--spacing)*1);padding-block:2px;padding-inline:calc(var(--spacing)*1);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,rgba(0,0,0,.1)),0 1px 2px -1px var(--tw-shadow-color,rgba(0,0,0,.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.utqn.dark .theme-switch{background-color:var(--color-gray-800)}.theme-btn{align-items:center;border-radius:calc(infinity*1px);color:var(--color-gray-600);display:flex;height:calc(var(--spacing)*6);justify-content:center;padding:calc(var(--spacing)*0);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:calc(var(--spacing)*6);--tw-duration:150ms;transition-duration:.15s;&:hover{@media (hover:hover){background-color:var(--color-gray-200)}}&:hover{@media (hover:hover){color:var(--color-gray-900)}}}.utqn.dark .theme-btn{color:var(--color-gray-300);&:hover{@media (hover:hover){background-color:var(--color-gray-700)}}&:hover{@media (hover:hover){color:var(--color-white)}}}.theme-btn.active{background-color:var(--color-white);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);--tw-ring-color:var(--color-gray-300)}.theme-btn.active,.utqn.dark .theme-btn.active{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.utqn.dark .theme-btn.active{background-color:var(--color-gray-700);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);--tw-ring-color:var(--color-gray-600)}.collapse-btn{align-items:center;border-radius:var(--radius-md);color:var(--color-gray-600);display:flex;height:calc(var(--spacing)*6);justify-content:center;padding:calc(var(--spacing)*0);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:calc(var(--spacing)*6);--tw-duration:150ms;transition-duration:.15s;&:hover{@media (hover:hover){background-color:var(--color-gray-200)}}&:hover{@media (hover:hover){color:var(--color-gray-900)}}}.utqn.dark .collapse-btn{color:var(--color-gray-300);&:hover{@media (hover:hover){background-color:var(--color-gray-700)}}&:hover{@media (hover:hover){color:var(--color-white)}}}.item+.icon-btn{justify-self:flex-end}.items{align-items:center;margin-top:calc(var(--spacing)*1.5)}.item-wrap{align-items:center;display:flex;gap:8px;justify-content:space-between}.item-wrap .item{flex:1}.item-wrap .icon-btn{opacity:0;transition:opacity .15s ease-in-out}.item-wrap:hover .icon-btn{opacity:1}.item-wrap:focus-within .icon-btn{opacity:1}.quick-add-menu{background-color:var(--color-white);border-color:var(--color-gray-200);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;font-family:var(--font-sans);font-size:13px;min-width:160px;padding:calc(var(--spacing)*1.5);position:fixed;z-index:2147483647;--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,rgba(0,0,0,.1)),0 8px 10px -6px var(--tw-shadow-color,rgba(0,0,0,.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.utqn.dark .quick-add-menu,.utqn.dark~.quick-add-menu{background-color:var(--color-gray-900);border-color:var(--color-gray-700);color:var(--color-gray-100);--tw-shadow-color:color-mix(in srgb,#000 40%,transparent);@supports (color:color-mix(in lab,red,red)){--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-black) 40%,transparent) var(--tw-shadow-alpha),transparent)}}.quick-add-item{align-items:center;border-radius:var(--radius-md);color:var(--color-gray-900);display:flex;gap:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*2);text-align:left;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:100%;--tw-duration:150ms;transition-duration:.15s;&:hover{@media (hover:hover){background-color:var(--color-gray-100)}}}.utqn.dark .quick-add-menu .quick-add-item,.utqn.dark~.quick-add-menu .quick-add-item{color:var(--color-gray-100);&:hover{@media (hover:hover){background-color:var(--color-gray-800)}}}.utqn.dark .quick-add-menu .icon img.lucide-icon,.utqn.dark~.quick-add-menu .icon img.lucide-icon{filter:invert(1) brightness(1.15) saturate(1.1)}.picker-highlight{cursor:pointer!important;outline:2px dashed #ef4444!important;outline-offset:2px!important}.picker-tip{background:#fff;border:1px solid #e5e7eb;border-radius:8px;box-shadow:0 10px 20px rgba(0,0,0,.1);color:#111827;font:13px/1.4 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,Apple Color Emoji,Segoe UI Emoji;padding:6px 10px;position:fixed;right:12px;top:12px;z-index:2147483647}.utqn.dark .picker-tip,.utqn.dark~.picker-tip{background:#111827;border-color:#374151;color:#f9fafb}.panel.all-mode{height:100vh;max-width:100vw;overflow:hidden;width:100vw}.panel-scroll{height:calc(100% - 36px);overflow-x:auto;width:100%}.panel.all-mode .header{background-color:#fff;position:sticky;top:0;z-index:21474836}.utqn.dark .panel.all-mode .header{background-color:#111827}.panel-columns{-moz-column-gap:12px;column-gap:12px;-moz-column-width:360px;column-width:360px;height:100%}.divider,.section{-moz-column-break-inside:avoid;break-inside:avoid}.check{align-items:center;display:inline-flex;gap:calc(var(--spacing)*2);height:32px}.check input[type=checkbox]{height:14px;width:14px}.item-wrap,.section{transition:opacity .15s ease}@keyframes utqn-fade-in{0%{opacity:.01}to{opacity:1}}.item-wrap.fade-in,.section.fade-in{animation:utqn-fade-in .15s ease both}.section.is-hidden .header{opacity:60%}.section.is-hidden{background-color:var(--color-gray-50);border-radius:var(--radius-lg);outline-color:var(--color-gray-300);outline-style:var(--tw-outline-style);outline-width:1px;--tw-outline-style:dashed;outline-style:dashed}.utqn.dark .section.is-hidden{background-color:var(--color-gray-800);outline-color:var(--color-gray-600)}.item-wrap.is-hidden .item{opacity:60%}.item-wrap.is-hidden{border-radius:var(--radius-md);outline-color:var(--color-gray-300);outline-style:var(--tw-outline-style);outline-width:1px;--tw-outline-style:dashed;outline-style:dashed}.utqn.dark .item-wrap.is-hidden{outline-color:var(--color-gray-600)}.empty-msg{color:var(--color-gray-500);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));padding-block:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*2)}.utqn.dark .empty-msg{color:var(--color-gray-400)}.segmented label.seg-item{min-width:50px;width:unset}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-leading{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@layer properties{*,::backdrop,:after,:before{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-ease:initial;--tw-duration:initial;--tw-font-weight:initial;--tw-leading:initial;--tw-tracking:initial}}'
  function ensurePickerStylesIn(r) {
    var _a
    const has =
      (_a = r.querySelector) == null
        ? void 0
        : _a.call(r, '#utqn-picker-styles')
    if (has) return
    const st = document.createElement('style')
    st.id = 'utqn-picker-styles'
    st.textContent =
      '.utqn-picker-highlight{outline:2px dashed #ef4444!important;outline-offset:0!important;box-shadow:0 0 0 2px rgba(239,68,68,.35) inset!important;cursor:pointer!important;}.utqn-picker-tip{position:fixed;top:12px;right:12px;z-index:2147483647;background:#fff;color:#111827;border:1px solid #e5e7eb;border-radius:8px;padding:6px 10px;box-shadow:0 10px 20px rgba(0,0,0,0.1);font:13px/1.4 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,"Apple Color Emoji","Segoe UI Emoji";}'
    if (r instanceof Document) {
      r.head.append(st)
    } else {
      r.append(st)
    }
  }
  function addCurrentPageLinkToGroup(root, cfg, helpers, groupId, openMode) {
    const grp = (cfg.groups || []).find((g) => g.id === groupId)
    if (!grp) return
    let nm = '\u5F53\u524D\u7F51\u9875'
    let href = location.href
    try {
      nm = document.title || nm
    } catch (e) {}
    try {
      href = location.href
    } catch (e) {}
    if (hasDuplicateInGroup(grp, 'url', String(href || '/'))) {
      const ok = globalThis.confirm(
        '\u8BE5\u5206\u7EC4\u5185\u5DF2\u5B58\u5728\u76F8\u540C\u7684 URL\uFF0C\u662F\u5426\u7EE7\u7EED\u6DFB\u52A0\uFF1F'
      )
      if (!ok) return
    }
    const it = {
      id: uid(),
      name: String(nm || href),
      icon: void 0,
      type: 'url',
      data: String(href || '/'),
      openIn: openMode,
    }
    grp.items.push(it)
    try {
      helpers.saveConfig(cfg)
    } catch (e) {}
    try {
      helpers.rerender(root, cfg)
    } catch (e) {}
  }
  function pickLinkFromPageAndAdd(root, cfg, helpers, groupId, openMode, opts) {
    const grp = (cfg.groups || []).find((g) => g.id === groupId)
    if (!grp) return
    pickLinkFromPage(root, {
      beforeStart: opts == null ? void 0 : opts.beforeStart,
      afterFinish: opts == null ? void 0 : opts.afterFinish,
      onPicked(nm, href) {
        if (hasDuplicateInGroup(grp, 'url', String(href || '/'))) {
          const ok = globalThis.confirm(
            '\u8BE5\u5206\u7EC4\u5185\u5DF2\u5B58\u5728\u76F8\u540C\u7684 URL\uFF0C\u662F\u5426\u7EE7\u7EED\u6DFB\u52A0\uFF1F'
          )
          if (!ok) return
        }
        const it = {
          id: uid(),
          name: nm,
          icon: void 0,
          type: 'url',
          data: href,
          openIn: openMode,
        }
        grp.items.push(it)
        try {
          helpers.saveConfig(cfg)
        } catch (e) {}
        try {
          helpers.rerender(root, cfg)
        } catch (e) {}
      },
    })
  }
  function pickLinkFromPage(root, opts) {
    ensurePickerStylesIn(document)
    if (opts.beforeStart) {
      try {
        opts.beforeStart()
      } catch (e) {}
    }
    const tip = document.createElement('div')
    tip.className = 'utqn-picker-tip'
    tip.textContent =
      '\u70B9\u51FB\u7EA2\u6846\u94FE\u63A5\u6DFB\u52A0\uFF0CESC \u53D6\u6D88'
    document.body.append(tip)
    const anchors = querySelectorAllDeep(document, 'a[href]').filter((el) => {
      const href = (el.getAttribute('href') || '').trim()
      if (!href || href === '#') return false
      let u
      try {
        u = new URL(href, location.href)
      } catch (e) {
        return false
      }
      return u.protocol === 'http:' || u.protocol === 'https:'
    })
    const panelEl = root.querySelector('.utqn')
    const prevPanelDisplay =
      panelEl instanceof HTMLElement ? panelEl.style.display || '' : ''
    if (panelEl instanceof HTMLElement) panelEl.style.display = 'none'
    const cleanup = () => {
      for (const a of anchors) a.classList.remove('utqn-picker-highlight')
      try {
        tip.remove()
      } catch (e) {}
      if (panelEl instanceof HTMLElement)
        panelEl.style.display = prevPanelDisplay
      try {
        const ov = document.querySelector('#utqn-picker-overlay')
        ov == null ? void 0 : ov.remove()
      } catch (e) {}
      if (opts.afterFinish) {
        try {
          opts.afterFinish()
        } catch (e) {}
      }
    }
    const onEsc = (ev) => {
      if (ev.key === 'Escape') {
        document.removeEventListener('keydown', onEsc, true)
        cleanup()
      }
    }
    document.addEventListener('keydown', onEsc, true)
    for (const a of anchors) {
      const rn = a.getRootNode()
      if (rn instanceof Document || rn instanceof ShadowRoot)
        ensurePickerStylesIn(rn)
      a.classList.add('utqn-picker-highlight')
    }
    const overlay = document.createElement('div')
    overlay.id = 'utqn-picker-overlay'
    overlay.style.position = 'fixed'
    overlay.style.inset = '0'
    overlay.style.zIndex = '2147483647'
    overlay.style.background = 'transparent'
    overlay.style.cursor = 'crosshair'
    const onOverlayClick = (ev) => {
      var _a
      ev.preventDefault()
      ev.stopPropagation()
      ;(_a = ev.stopImmediatePropagation) == null ? void 0 : _a.call(ev)
      let picked
      try {
        const x = ev.clientX
        const y = ev.clientY
        const seen = /* @__PURE__ */ new Set()
        const search = (r) => {
          var _a2
          const arr = r.elementsFromPoint(x, y)
          for (const el of arr) {
            if (el === overlay) continue
            if (seen.has(el)) continue
            seen.add(el)
            const a =
              (_a2 = el.closest) == null ? void 0 : _a2.call(el, 'a[href]')
            if (a instanceof HTMLAnchorElement) return a
            const sr = el.shadowRoot
            if (sr) {
              const inner = search(sr)
              if (inner) return inner
            }
          }
          return void 0
        }
        picked = search(document)
        if (picked) {
          const href = picked.href
          const text = (picked.textContent || '').trim() || href
          try {
            opts.onPicked(text, href)
          } catch (e) {}
        }
      } catch (e) {}
      if (picked) {
        document.removeEventListener('keydown', onEsc, true)
        cleanup()
      }
    }
    overlay.addEventListener('click', onOverlayClick, true)
    document.body.append(overlay)
  }
  function hasDuplicateInGroup(grp, type, data, excludeId) {
    const d = String(data || '').trim()
    return (grp.items || []).some((x) => {
      if (!x || x.type !== type) return false
      const xd = String(x.data || '').trim()
      if (excludeId && x.id === excludeId) return false
      return xd === d
    })
  }
  function createSegmentedRadios(initial, values, onChange, opts) {
    var _a, _b
    const wrap = document.createElement('div')
    wrap.className = 'segmented'
    const name =
      ((opts == null ? void 0 : opts.namePrefix) || 'utqn-seg-') + uid()
    const labels = (_a = opts == null ? void 0 : opts.labels) != null ? _a : {}
    for (const m of values) {
      const label = document.createElement('label')
      label.className = 'seg-item'
      const input = document.createElement('input')
      input.type = 'radio'
      input.name = name
      input.value = m
      input.className = 'seg-radio'
      input.checked = initial === m
      input.addEventListener('change', () => {
        if (input.checked) onChange(m)
      })
      const text = document.createElement('span')
      text.className = 'seg-text'
      text.textContent = (_b = labels[m]) != null ? _b : String(m)
      label.append(input)
      label.append(text)
      wrap.append(label)
    }
    return wrap
  }
  function createOpenModeRadios(initial, onChange, opts) {
    var _a
    const labels =
      (_a = opts == null ? void 0 : opts.labels) != null
        ? _a
        : {
            'same-tab': '\u5F53\u524D\u9875',
            'new-tab': '\u65B0\u6807\u7B7E\u9875',
          }
    return createSegmentedRadios(initial, ['same-tab', 'new-tab'], onChange, {
      labels,
      namePrefix: 'utqn-open-',
    })
  }
  function debounce(fn, delay) {
    let timer
    return function (...args) {
      clearTimeout(timer)
      timer = setTimeout(() => {
        fn.apply(this, args)
      }, delay)
    }
  }
  function detectIconKind(v, kinds) {
    const s = String(v || '').trim()
    if (kinds.includes('favicon') && s.startsWith('favicon')) return 'favicon'
    if (s.startsWith('url:')) return 'url'
    if (s.includes(':')) return 'icon'
    if (s) return 'emoji'
    return 'icon'
  }
  function createIconInput(initialValue, kinds, opts) {
    var _a
    const wrap = document.createElement('div')
    wrap.style.flex = '1'
    const inputContainer = document.createElement('div')
    inputContainer.style.display = 'flex'
    inputContainer.style.alignItems = 'center'
    inputContainer.style.gap = '0.5em'
    const preview = document.createElement('span')
    preview.style.display = 'inline-flex'
    preview.style.alignItems = 'center'
    preview.style.justifyContent = 'center'
    preview.style.width = '1.5em'
    preview.style.height = '1.em'
    const input = document.createElement('input')
    try {
      input.style.width = '100%'
    } catch (e) {}
    inputContainer.append(preview)
    inputContainer.append(input)
    const help = document.createElement('div')
    help.className = 'field-help'
    try {
      help.style.marginLeft = '0'
      help.style.marginTop = '0.8em'
    } catch (e) {}
    let kind = detectIconKind(initialValue, kinds)
    const radios = createSegmentedRadios(
      kind,
      kinds,
      (v) => {
        kind = v
        syncPlaceholder()
        input.value = ''
        if (typeof (opts == null ? void 0 : opts.onKindChange) === 'function')
          opts.onKindChange(kind)
        updatePreview()
        syncHelp()
      },
      {
        labels: (_a = opts == null ? void 0 : opts.labels) != null ? _a : {},
        namePrefix: opts == null ? void 0 : opts.namePrefix,
      }
    )
    function syncPlaceholder() {
      var _a2, _b, _c, _d, _e
      const p =
        (_a2 = opts == null ? void 0 : opts.placeholders) != null ? _a2 : {}
      input.placeholder =
        kind === 'icon'
          ? (_b = p.icon) != null
            ? _b
            : 'home | search | folder | file | ...'
          : kind === 'favicon'
            ? (_c = p.favicon) != null
              ? _c
              : '16 | 32 | 64'
            : kind === 'url'
              ? (_d = p.url) != null
                ? _d
                : 'https://...'
              : (_e = p.emoji) != null
                ? _e
                : '\u{1F525} | \u{1F353} | \u{1F3BE} | ...'
    }
    {
      const raw = String(initialValue || '')
      let shown = raw
      switch (kind) {
        case 'icon': {
          shown = raw.includes(':') ? raw.split(':').pop() || '' : raw
          break
        }
        case 'favicon': {
          if (raw.startsWith('favicon')) {
            const param = raw.split(':')[1]
            shown = param || ''
          }
          break
        }
        case 'url': {
          shown = raw.startsWith('url:') ? raw.slice(4) : raw
          break
        }
        case 'emoji': {
          shown = raw
          break
        }
      }
      input.value = shown
    }
    const debouncedUpdatePreview = debounce(updatePreview, 500)
    input.addEventListener('change', () => {
      debouncedUpdatePreview()
      if (typeof (opts == null ? void 0 : opts.onValueChange) === 'function') {
        opts.onValueChange(input.value)
      }
    })
    input.addEventListener('input', () => {
      debouncedUpdatePreview()
    })
    syncPlaceholder()
    updatePreview()
    syncHelp()
    const br = document.createElement('div')
    br.style.flexBasis = '100%'
    wrap.append(radios)
    wrap.append(br)
    wrap.append(inputContainer)
    wrap.append(help)
    function updatePreview() {
      const finalValue = getFinalValue()
      clearChildren(preview)
      if (finalValue && !finalValue.startsWith('favicon')) {
        setIcon(preview, finalValue)
      }
    }
    function getFinalValue() {
      const raw = input.value.trim()
      if (!raw && kind !== 'favicon') return void 0
      switch (kind) {
        case 'icon': {
          return raw.includes(':') ? raw : 'lucide:' + raw
        }
        case 'favicon': {
          const sizeNum = Number.parseInt(raw, 10)
          const s =
            sizeNum === 16 ? 16 : sizeNum === 32 ? 32 : sizeNum === 64 ? 64 : 64
          return 'favicon' + (raw ? ':' + String(s) : '')
        }
        case 'url': {
          return raw.startsWith('url:') ? raw : 'url:' + raw
        }
        case 'emoji': {
          return raw
        }
      }
    }
    function syncHelp() {
      clearChildren(help)
      switch (kind) {
        case 'icon': {
          const line = document.createElement('div')
          line.append('\u67E5\u627E\u56FE\u6807\uFF1A ')
          const a = document.createElement('a')
          a.href = 'https://lucide.dev/icons/'
          a.target = '_blank'
          a.rel = 'noopener noreferrer'
          a.textContent = 'https://lucide.dev/icons/'
          line.append(a)
          help.append(line)
          break
        }
        case 'favicon': {
          const line = document.createElement('div')
          line.textContent = '\u65E0\u9884\u89C8\u6548\u679C'
          help.append(line)
          break
        }
        case 'url': {
          const line = document.createElement('div')
          line.textContent = '\u8BF7\u8F93\u5165\u56FE\u7247 URL'
          help.append(line)
          break
        }
        case 'emoji': {
          const line = document.createElement('div')
          line.textContent = '\u8BF7\u8F93\u5165\u4E00\u4E2A emoji'
          help.append(line)
          break
        }
      }
    }
    return {
      el: wrap,
      input,
      radios,
      getKind: () => kind,
      setKind(k) {
        kind = k
        syncPlaceholder()
      },
      getRaw: () => input.value,
      getFinal: getFinalValue,
    }
  }
  function openAddLinkModal(root, cfg, helpers) {
    var _a, _b, _c
    for (const n of Array.from(root.querySelectorAll('.modal-mask'))) n.remove()
    const mask = document.createElement('div')
    mask.className = 'modal-mask'
    try {
      mask.style.zIndex = '2147483649'
    } catch (e) {}
    const modal = document.createElement('div')
    modal.className = 'modal'
    try {
      const panel = root.querySelector('.utqn')
      const isDarkPanel =
        panel == null ? void 0 : panel.classList.contains('dark')
      if (isDarkPanel) modal.classList.add('dark')
    } catch (e) {}
    const h2 = document.createElement('h2')
    h2.textContent = helpers.existingItem
      ? '\u7F16\u8F91\u94FE\u63A5'
      : '\u6DFB\u52A0\u94FE\u63A5'
    const grid = document.createElement('div')
    grid.className = 'grid'
    try {
      grid.style.gridTemplateColumns = '1fr'
    } catch (e) {}
    const grpRow = document.createElement('div')
    grpRow.className = 'row'
    const grpLabel = document.createElement('label')
    grpLabel.textContent = '\u5206\u7EC4'
    const grpSel = document.createElement('select')
    const firstGroup = (cfg.groups && cfg.groups[0]) || void 0
    const defaultGroup =
      helpers.defaultGroupId || (firstGroup && firstGroup.id) || ''
    for (const g of cfg.groups || []) {
      const o = document.createElement('option')
      o.value = g.id
      o.textContent = g.name
      if (g.id === defaultGroup) o.selected = true
      grpSel.append(o)
    }
    if (helpers.existingItem) {
      try {
        const gid = helpers.defaultGroupId || defaultGroup
        grpSel.value = gid
        grpSel.disabled = true
      } catch (e) {}
    }
    grpRow.append(grpLabel)
    grpRow.append(grpSel)
    const nameRow = document.createElement('div')
    nameRow.className = 'row'
    const nameLabel = document.createElement('label')
    nameLabel.textContent = '\u540D\u79F0'
    const nameInput = document.createElement('input')
    nameInput.value = helpers.existingItem
      ? String(helpers.existingItem.name || '\u65B0\u9879')
      : '\u65B0\u9879'
    nameRow.append(nameLabel)
    nameRow.append(nameInput)
    const iconRow = document.createElement('div')
    iconRow.className = 'row'
    const iconLabel = document.createElement('label')
    iconLabel.textContent = '\u56FE\u6807'
    const existingIcon = helpers.existingItem
      ? String(helpers.existingItem.icon || '')
      : ''
    const iconComp = createIconInput(
      existingIcon,
      ['icon', 'favicon', 'url', 'emoji'],
      {
        labels: {
          icon: '\u56FE\u6807',
          favicon: 'Favicon',
          url: 'URL',
          emoji: 'Emoji',
        },
        namePrefix: 'utqn-item-icon-kind-',
      }
    )
    iconRow.append(iconLabel)
    iconRow.append(iconComp.el)
    const urlRow = document.createElement('div')
    urlRow.className = 'row'
    const urlLabel = document.createElement('label')
    urlLabel.textContent = 'URL'
    const urlInput = document.createElement('input')
    urlInput.placeholder = 'https://...'
    urlInput.value = helpers.existingItem
      ? String(helpers.existingItem.data || '/')
      : '/'
    urlRow.append(urlLabel)
    urlRow.append(urlInput)
    const urlHelpRow = document.createElement('div')
    urlHelpRow.className = 'row'
    const urlHelp = document.createElement('div')
    urlHelp.className = 'field-help'
    const uTitle = document.createElement('div')
    uTitle.className = 'field-help-title'
    uTitle.textContent = '\u{1F517} URL \u53D8\u91CF\u4E0E\u793A\u4F8B'
    const uLine1 = document.createElement('div')
    uLine1.textContent =
      '\u53D8\u91CF\uFF1A{hostname}\u3001{hostname_without_www}\u3001{query}\u3001{selected}'
    const uLine2 = document.createElement('div')
    uLine2.textContent =
      '\u793A\u4F8B\uFF1Ahttp://example.com/search?query={selected||query}'
    const uLine3 = document.createElement('div')
    const uLink = document.createElement('a')
    uLink.href = 'https://github.com/utags/userscripts'
    uLink.target = '_blank'
    uLink.rel = 'noopener noreferrer'
    uLink.textContent = 'https://github.com/utags/userscripts'
    uLine3.append('\u66F4\u591A\u4F7F\u7528\u8BF4\u660E\u53C2\u8003 ')
    uLine3.append(uLink)
    urlHelp.append(uTitle)
    urlHelp.append(uLine1)
    urlHelp.append(uLine2)
    urlHelp.append(uLine3)
    urlHelpRow.append(urlHelp)
    const jsRow = document.createElement('div')
    jsRow.className = 'row'
    const jsLabel = document.createElement('label')
    jsLabel.textContent = 'JS'
    const jsInput = document.createElement('textarea')
    jsInput.placeholder =
      'console.log("hello")\n// \u6216\u8005\u7C98\u8D34\u811A\u672C\u5185\u5BB9'
    jsInput.value =
      helpers.existingItem && helpers.existingItem.type === 'js'
        ? String(helpers.existingItem.data || '')
        : ''
    jsRow.append(jsLabel)
    jsRow.append(jsInput)
    const jsHelpRow = document.createElement('div')
    jsHelpRow.className = 'row'
    const jsHelp = document.createElement('div')
    jsHelp.className = 'field-help'
    const jTitle = document.createElement('div')
    jTitle.className = 'field-help-title'
    jTitle.textContent = '\u{1F9E9} JS \u8FD4\u56DE\u4E0E\u793A\u4F8B'
    const jLine1 = document.createElement('div')
    jLine1.textContent =
      'JS\uFF1A\u8FD4\u56DE\u5B57\u7B26\u4E32\u6216 {url, mode} \u5BFC\u822A'
    const jLine2 = document.createElement('div')
    jLine2.textContent =
      '\u793A\u4F8B\uFF1Areturn "http://example.com/search?query={selected||query}"'
    const jLine3 = document.createElement('div')
    jLine3.textContent =
      '\u793A\u4F8B\uFF1Areturn { url: "http://example.com/?q={query}", mode: "new-tab" }'
    const jLine4 = document.createElement('div')
    const jLink = document.createElement('a')
    jLink.href = 'https://github.com/utags/userscripts'
    jLink.target = '_blank'
    jLink.rel = 'noopener noreferrer'
    jLink.textContent = 'https://github.com/utags/userscripts'
    jLine4.append('\u66F4\u591A\u4F7F\u7528\u8BF4\u660E\u53C2\u8003 ')
    jLine4.append(jLink)
    jsHelp.append(jTitle)
    jsHelp.append(jLine1)
    jsHelp.append(jLine2)
    jsHelp.append(jLine3)
    jsHelp.append(jLine4)
    jsHelpRow.append(jsHelp)
    const typeRow = document.createElement('div')
    typeRow.className = 'row'
    const typeLabel = document.createElement('label')
    typeLabel.textContent = '\u7C7B\u578B'
    let typeValue =
      ((_a = helpers.existingItem) == null ? void 0 : _a.type) || 'url'
    const quickRef = { el: void 0 }
    const typeRadios = createSegmentedRadios(
      typeValue,
      ['url', 'js'],
      (v) => {
        typeValue = v
        syncTypeUi()
      },
      { labels: { url: 'URL', js: 'JS' }, namePrefix: 'utqn-item-type-' }
    )
    const syncTypeUi = () => {
      if (typeValue === 'url') {
        urlRow.style.display = ''
        jsRow.style.display = 'none'
        if (quickRef.el) quickRef.el.style.display = ''
        urlHelpRow.style.display = ''
        jsHelpRow.style.display = 'none'
      } else {
        urlRow.style.display = 'none'
        jsRow.style.display = ''
        if (quickRef.el) quickRef.el.style.display = 'none'
        urlHelpRow.style.display = 'none'
        jsHelpRow.style.display = ''
      }
    }
    typeRow.append(typeLabel)
    typeRow.append(typeRadios)
    const openRow = document.createElement('div')
    openRow.className = 'row'
    const openLabel = document.createElement('label')
    openLabel.textContent = '\u6253\u5F00\u65B9\u5F0F'
    let openValue =
      ((_b = helpers.existingItem) == null ? void 0 : _b.openIn) ||
      helpers.defaultOpen ||
      'same-tab'
    const openRadios = createOpenModeRadios(openValue, (m) => {
      openValue = m
    })
    openRow.append(openLabel)
    openRow.append(openRadios)
    const visibleRow = document.createElement('div')
    visibleRow.className = 'row'
    const visibleLabel = document.createElement('label')
    visibleLabel.textContent = '\u663E\u793A\u72B6\u6001'
    let itemState = ((_c = helpers.existingItem) == null ? void 0 : _c.hidden)
      ? 'hidden'
      : 'visible'
    const stateRadios = createSegmentedRadios(
      itemState,
      ['visible', 'hidden'],
      (v) => {
        itemState = v
      },
      {
        labels: { visible: '\u663E\u793A', hidden: '\u9690\u85CF' },
        namePrefix: 'utqn-item-state-',
      }
    )
    visibleRow.append(visibleLabel)
    visibleRow.append(stateRadios)
    const quickRow = document.createElement('div')
    quickRef.el = quickRow
    quickRow.className = 'row'
    const addCurrentBtn = document.createElement('button')
    addCurrentBtn.className = 'btn btn-secondary'
    addCurrentBtn.textContent = '\u6DFB\u52A0\u5F53\u524D\u7F51\u9875'
    const pickLinksBtn = document.createElement('button')
    pickLinksBtn.className = 'btn btn-secondary'
    pickLinksBtn.textContent =
      '\u4ECE\u5F53\u524D\u7F51\u9875\u91C7\u96C6\u94FE\u63A5'
    quickRow.append(addCurrentBtn)
    quickRow.append(pickLinksBtn)
    syncTypeUi()
    addCurrentBtn.addEventListener('click', () => {
      try {
        nameInput.value = document.title || '\u5F53\u524D\u7F51\u9875'
        urlInput.value = location.href
      } catch (e) {}
    })
    pickLinksBtn.addEventListener('click', () => {
      try {
        pickLinkFromPage(root, {
          beforeStart() {
            modal.style.display = 'none'
            mask.remove()
          },
          afterFinish() {
            modal.style.display = ''
            root.append(mask)
          },
          onPicked(nm, href) {
            nameInput.value = nm
            urlInput.value = href
          },
        })
      } catch (e) {}
    })
    const actions = document.createElement('div')
    actions.className = 'row actions'
    const saveBtn = document.createElement('button')
    saveBtn.className = 'btn btn-primary'
    saveBtn.textContent = helpers.existingItem ? '\u786E\u8BA4' : '\u6DFB\u52A0'
    const cancelBtn = document.createElement('button')
    cancelBtn.className = 'btn btn-secondary'
    cancelBtn.textContent = '\u53D6\u6D88'
    const deleteBtn = document.createElement('button')
    deleteBtn.className = 'btn btn-secondary'
    deleteBtn.textContent = '\u5220\u9664'
    const isEditableTarget2 = (t) => {
      const el = t
      if (!el) return false
      const tag = el.tagName ? el.tagName.toLowerCase() : ''
      if (tag === 'input' || tag === 'textarea' || tag === 'select') return true
      const ce = el.isContentEditable
      return Boolean(ce)
    }
    const close = () => {
      try {
        mask.remove()
      } catch (e) {}
      try {
        document.removeEventListener('keydown', onKey, true)
      } catch (e) {}
    }
    const onKey = (e) => {
      const visible = root.contains(mask) && modal.style.display !== 'none'
      if (!visible) return
      if (e.key === 'Escape') {
        e.preventDefault()
        close()
        return
      }
      if (e.key === 'Enter') {
        const ae = root.activeElement
        const inModal = ae ? Boolean(modal.contains(ae)) : false
        if (!inModal) return
        const tag = (ae == null ? void 0 : ae.tagName)
          ? ae.tagName.toLowerCase()
          : ''
        if (tag === 'textarea' || tag === 'button') return
        e.preventDefault()
        saveBtn.click()
      }
    }
    document.addEventListener('keydown', onKey, true)
    saveBtn.addEventListener('click', () => {
      var _a2
      const gid = grpSel.value
      const grp = (cfg.groups || []).find((g) => g.id === gid)
      if (!grp) return
      const finalIcon = iconComp.getFinal()
      const hiddenVal = itemState === 'hidden'
      const proposedData =
        typeValue === 'url' ? urlInput.value.trim() || '/' : jsInput.value
      const hasDup = hasDuplicateInGroup(
        grp,
        typeValue,
        proposedData,
        (_a2 = helpers.existingItem) == null ? void 0 : _a2.id
      )
      if (hasDup) {
        const msg =
          typeValue === 'url'
            ? helpers.existingItem
              ? '\u8BE5\u5206\u7EC4\u5185\u5DF2\u5B58\u5728\u76F8\u540C\u7684 URL\uFF0C\u662F\u5426\u7EE7\u7EED\u4FDD\u5B58\uFF1F'
              : '\u8BE5\u5206\u7EC4\u5185\u5DF2\u5B58\u5728\u76F8\u540C\u7684 URL\uFF0C\u662F\u5426\u7EE7\u7EED\u6DFB\u52A0\uFF1F'
            : helpers.existingItem
              ? '\u8BE5\u5206\u7EC4\u5185\u5DF2\u5B58\u5728\u76F8\u540C\u7684 JS\uFF0C\u662F\u5426\u7EE7\u7EED\u4FDD\u5B58\uFF1F'
              : '\u8BE5\u5206\u7EC4\u5185\u5DF2\u5B58\u5728\u76F8\u540C\u7684 JS\uFF0C\u662F\u5426\u7EE7\u7EED\u6DFB\u52A0\uFF1F'
        const ok = globalThis.confirm(msg)
        if (!ok) return
      }
      if (helpers.existingItem) {
        const it = helpers.existingItem
        it.name = nameInput.value.trim() || '\u65B0\u9879'
        it.icon = finalIcon
        it.type = typeValue
        it.data = proposedData
        it.openIn = openValue
        it.hidden = hiddenVal
      } else {
        const it = {
          id: uid(),
          name: nameInput.value.trim() || '\u65B0\u9879',
          icon: finalIcon,
          type: typeValue,
          data: proposedData,
          openIn: openValue,
          hidden: hiddenVal ? true : void 0,
        }
        grp.items.push(it)
      }
      try {
        helpers.saveConfig(cfg)
      } catch (e) {}
      try {
        helpers.rerender(root, cfg)
      } catch (e) {}
      close()
    })
    deleteBtn.addEventListener('click', () => {
      if (!helpers.existingItem) return
      const ok = globalThis.confirm(
        '\u662F\u5426\u5220\u9664\u6B64\u94FE\u63A5\uFF1F'
      )
      if (!ok) return
      const gid = grpSel.value
      const grp = (cfg.groups || []).find((g) => g.id === gid)
      if (!grp) return
      const idx = grp.items.findIndex(
        (x) => x && x.id === helpers.existingItem.id
      )
      if (idx !== -1) {
        try {
          grp.items.splice(idx, 1)
        } catch (e) {}
        try {
          helpers.saveConfig(cfg)
        } catch (e) {}
        try {
          helpers.rerender(root, cfg)
        } catch (e) {}
        close()
      }
    })
    cancelBtn.addEventListener('click', () => {
      close()
    })
    actions.append(saveBtn)
    actions.append(cancelBtn)
    grid.append(grpRow)
    grid.append(nameRow)
    grid.append(iconRow)
    grid.append(typeRow)
    grid.append(urlRow)
    grid.append(urlHelpRow)
    grid.append(jsRow)
    grid.append(jsHelpRow)
    grid.append(openRow)
    grid.append(visibleRow)
    grid.append(quickRow)
    modal.append(h2)
    modal.append(grid)
    modal.append(actions)
    mask.append(modal)
    root.append(mask)
    if (helpers.existingItem) {
      actions.append(deleteBtn)
    }
    syncTypeUi()
  }
  function openAddGroupModal(root, cfg, helpers) {
    var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m
    for (const n of Array.from(root.querySelectorAll('.modal-mask'))) n.remove()
    const mask = document.createElement('div')
    mask.className = 'modal-mask'
    try {
      mask.style.zIndex = '2147483649'
    } catch (e) {}
    const modal = document.createElement('div')
    modal.className = 'modal'
    try {
      const panel = root.querySelector('.utqn')
      const isDarkPanel =
        panel == null ? void 0 : panel.classList.contains('dark')
      const prefersDark = (() => {
        var _a2, _b2
        try {
          return (_b2 =
            (_a2 = globalThis.matchMedia) == null
              ? void 0
              : _a2.call(globalThis, '(prefers-color-scheme: dark)')) == null
            ? void 0
            : _b2.matches
        } catch (e) {
          return false
        }
      })()
      if (isDarkPanel || prefersDark) modal.classList.add('dark')
    } catch (e) {}
    const h2 = document.createElement('h2')
    h2.textContent = helpers.existingGroup
      ? '\u7F16\u8F91\u5206\u7EC4'
      : '\u6DFB\u52A0\u5206\u7EC4'
    const grid = document.createElement('div')
    grid.className = 'grid'
    try {
      grid.style.gridTemplateColumns = '1fr'
    } catch (e) {}
    const nameRow = document.createElement('div')
    nameRow.className = 'row'
    const nameLabel = document.createElement('label')
    nameLabel.textContent = '\u7EC4\u540D'
    const nameInput = document.createElement('input')
    nameInput.value =
      (_b = (_a = helpers.existingGroup) == null ? void 0 : _a.name) != null
        ? _b
        : '\u65B0\u5206\u7EC4'
    nameRow.append(nameLabel)
    nameRow.append(nameInput)
    const displayRow = document.createElement('div')
    displayRow.className = 'row'
    const displayLabel = document.createElement('label')
    displayLabel.textContent = '\u663E\u793A\u7EC4\u540D'
    const displayInput = document.createElement('input')
    const displayCtrl = document.createElement('label')
    displayCtrl.className = 'check'
    const displayToggle = document.createElement('input')
    displayToggle.type = 'checkbox'
    const displayText = document.createElement('span')
    displayText.textContent = '\u81EA\u5B9A\u4E49'
    displayCtrl.append(displayToggle)
    displayCtrl.append(displayText)
    const hasCustomDisplay =
      typeof ((_c = helpers.existingGroup) == null
        ? void 0
        : _c.displayName) === 'string' &&
      helpers.existingGroup.displayName !== helpers.existingGroup.name
    displayToggle.checked = Boolean(hasCustomDisplay)
    displayInput.value = hasCustomDisplay
      ? ((_d = helpers.existingGroup) == null ? void 0 : _d.displayName) || ''
      : ((_e = helpers.existingGroup) == null ? void 0 : _e.name) ||
        nameInput.value
    displayInput.disabled = !displayToggle.checked
    nameInput.addEventListener('input', () => {
      if (!displayToggle.checked) displayInput.value = nameInput.value
    })
    displayToggle.addEventListener('change', () => {
      displayInput.disabled = !displayToggle.checked
      if (!displayToggle.checked) displayInput.value = nameInput.value
    })
    displayRow.append(displayLabel)
    displayRow.append(displayInput)
    displayRow.append(displayCtrl)
    const iconRow = document.createElement('div')
    iconRow.className = 'row'
    const iconLabel = document.createElement('label')
    iconLabel.textContent = '\u56FE\u6807'
    const iconComp = createIconInput(
      (_g = (_f = helpers.existingGroup) == null ? void 0 : _f.icon) != null
        ? _g
        : 'lucide:folder',
      ['icon', 'url', 'emoji'],
      {
        labels: { icon: '\u56FE\u6807', url: 'URL', emoji: 'Emoji' },
        namePrefix: 'utqn-group-icon-kind-',
      }
    )
    iconRow.append(iconLabel)
    iconRow.append(iconComp.el)
    const ruleRow = document.createElement('div')
    ruleRow.className = 'row'
    const ruleLabel = document.createElement('label')
    ruleLabel.textContent = 'URL \u89C4\u5219'
    const ta = document.createElement('textarea')
    const host = location.hostname || ''
    ta.value = (
      (_i = (_h = helpers.existingGroup) == null ? void 0 : _h.match) != null
        ? _i
        : helpers.defaultMatch && helpers.defaultMatch.length > 0
          ? helpers.defaultMatch
          : ['*://' + host + '/*']
    ).join('\n')
    ruleRow.append(ruleLabel)
    ruleRow.append(ta)
    function escRe(s) {
      let out = ''
      const specials = '\\^$.*+?()[]{}|'
      for (const ch of s) out += specials.includes(ch) ? '\\' + ch : ch
      return out
    }
    function regexHostAll(h) {
      const hh = escRe(h)
      return '/.+://'.concat(hh, '/.*$/')
    }
    function regexHostDir(h, d) {
      const hh = escRe(h)
      const dd = escRe(d)
      return '/.+://'.concat(hh).concat(dd, '.*$/')
    }
    function regexHostPath(h, p) {
      const hh = escRe(h)
      const pp = escRe(p)
      return '/.+://'.concat(hh).concat(pp, '$/')
    }
    const tplRow = document.createElement('div')
    tplRow.className = 'row'
    const tplLabel = document.createElement('label')
    tplLabel.textContent = '\u89C4\u5219\u6A21\u677F'
    const tplSel = document.createElement('select')
    const pathname = location.pathname || '/'
    const dir = pathname.endsWith('/')
      ? pathname
      : pathname.replace(/[^/]+$/, '')
    const opts = [
      {
        v: '*://'.concat(host, '/*'),
        t: '\u5F53\u524D\u57DF\u540D\u6240\u6709\u9875\u9762',
      },
      {
        v: '*://'.concat(host).concat(dir, '*'),
        t: '\u5F53\u524D\u8DEF\u5F84\u524D\u7F00',
      },
      {
        v: '*://'.concat(host).concat(pathname),
        t: '\u5F53\u524D\u5B8C\u6574\u8DEF\u5F84',
      },
      { v: '*', t: '\u4EFB\u610F\u57DF\u540D\u6240\u6709\u9875\u9762' },
      {
        v: regexHostAll(host),
        t: '\u6B63\u5219\uFF1A\u5F53\u524D\u57DF\u540D\u6240\u6709\u9875\u9762',
      },
      {
        v: regexHostDir(host, dir),
        t: '\u6B63\u5219\uFF1A\u5F53\u524D\u8DEF\u5F84\u524D\u7F00',
      },
      {
        v: regexHostPath(host, pathname),
        t: '\u6B63\u5219\uFF1A\u5F53\u524D\u5B8C\u6574\u8DEF\u5F84',
      },
    ]
    for (const it of opts) {
      const o = document.createElement('option')
      o.value = it.v
      o.textContent = it.t
      tplSel.append(o)
    }
    tplSel.addEventListener('change', () => {
      ta.value = tplSel.value
    })
    tplRow.append(tplLabel)
    tplRow.append(tplSel)
    const openRow = document.createElement('div')
    openRow.className = 'row'
    const openLabel = document.createElement('label')
    openLabel.textContent = '\u9ED8\u8BA4\u6253\u5F00\u65B9\u5F0F'
    let openValue =
      ((_j = helpers.existingGroup) == null ? void 0 : _j.defaultOpen) ||
      helpers.defaultOpen ||
      'same-tab'
    const openRadios = createOpenModeRadios(openValue, (m) => {
      openValue = m
    })
    openRow.append(openLabel)
    openRow.append(openRadios)
    const colsRow = document.createElement('div')
    colsRow.className = 'row'
    const colsLabel = document.createElement('label')
    colsLabel.textContent = '\u6BCF\u884C\u663E\u793A\u4E2A\u6570'
    let colVal = String(
      (_l = (_k = helpers.existingGroup) == null ? void 0 : _k.itemsPerRow) !=
        null
        ? _l
        : 1
    )
    const colsRadios = createSegmentedRadios(
      colVal,
      ['1', '2', '3', '4', '5', '6'],
      (v) => {
        colVal = v
      },
      { namePrefix: 'utqn-cols-' }
    )
    colsRow.append(colsLabel)
    colsRow.append(colsRadios)
    const stateRow = document.createElement('div')
    stateRow.className = 'row'
    const stateLabel = document.createElement('label')
    stateLabel.textContent = '\u5206\u7EC4\u663E\u793A\u72B6\u6001'
    let groupState = ((_m = helpers.existingGroup) == null ? void 0 : _m.hidden)
      ? 'hidden'
      : 'visible'
    const stateRadios = createSegmentedRadios(
      groupState,
      ['visible', 'hidden'],
      (v) => {
        groupState = v
      },
      {
        labels: { visible: '\u663E\u793A', hidden: '\u9690\u85CF' },
        namePrefix: 'utqn-state-',
      }
    )
    stateRow.append(stateLabel)
    stateRow.append(stateRadios)
    const actions = document.createElement('div')
    actions.className = 'row actions'
    const saveBtn = document.createElement('button')
    saveBtn.className = 'btn btn-primary'
    saveBtn.textContent = helpers.existingGroup
      ? '\u786E\u8BA4'
      : '\u6DFB\u52A0'
    const cancelBtn = document.createElement('button')
    cancelBtn.className = 'btn btn-secondary'
    cancelBtn.textContent = '\u53D6\u6D88'
    const isEditableTarget2 = (t) => {
      const el = t
      if (!el) return false
      const tag = el.tagName ? el.tagName.toLowerCase() : ''
      if (tag === 'input' || tag === 'textarea' || tag === 'select') return true
      const ce = el.isContentEditable
      return Boolean(ce)
    }
    const close = () => {
      try {
        mask.remove()
      } catch (e) {}
      try {
        document.removeEventListener('keydown', onKey, true)
      } catch (e) {}
    }
    const onKey = (e) => {
      const visible = root.contains(mask) && modal.style.display !== 'none'
      if (!visible) return
      if (e.key === 'Escape') {
        e.preventDefault()
        close()
        return
      }
      if (e.key === 'Enter') {
        const ae = root.activeElement
        const inModal = ae ? Boolean(modal.contains(ae)) : false
        if (!inModal) return
        const tag = (ae == null ? void 0 : ae.tagName)
          ? ae.tagName.toLowerCase()
          : ''
        if (tag === 'textarea' || tag === 'button') return
        e.preventDefault()
        saveBtn.click()
      }
    }
    document.addEventListener('keydown', onKey, true)
    saveBtn.addEventListener('click', () => {
      const nm = nameInput.value.trim()
      if (!nm) {
        try {
          nameInput.focus()
        } catch (e) {}
        return
      }
      const toMatch = ta.value
        .split(/\n+/)
        .map((v) => v.trim())
        .filter(Boolean)
      const toCols = Math.max(1, Math.min(6, Number.parseInt(colVal, 10)))
      const toHidden = helpers.existingGroup ? groupState === 'hidden' : false
      if (helpers.existingGroup) {
        const g = helpers.existingGroup
        g.name = nm
        g.icon = iconComp.getFinal() || g.icon || 'lucide:folder'
        g.match = toMatch
        g.defaultOpen = openValue
        g.itemsPerRow = toCols
        g.hidden = Boolean(toHidden)
        if (displayToggle.checked) {
          g.displayName = displayInput.value
        } else {
          try {
            delete g.displayName
          } catch (e) {}
        }
      } else {
        const g = {
          id: uid(),
          name: nm,
          icon: iconComp.getFinal() || 'lucide:folder',
          match: toMatch,
          items: [],
          defaultOpen: openValue,
          itemsPerRow: toCols,
          hidden: Boolean(toHidden),
        }
        if (displayToggle.checked) {
          g.displayName = displayInput.value
        }
        cfg.groups.push(g)
      }
      try {
        helpers.saveConfig(cfg)
      } catch (e) {}
      try {
        helpers.rerender(root, cfg)
      } catch (e) {}
      close()
    })
    cancelBtn.addEventListener('click', () => {
      close()
    })
    actions.append(saveBtn)
    actions.append(cancelBtn)
    grid.append(nameRow)
    grid.append(displayRow)
    grid.append(iconRow)
    grid.append(tplRow)
    grid.append(ruleRow)
    grid.append(openRow)
    grid.append(colsRow)
    if (helpers.existingGroup) grid.append(stateRow)
    modal.append(h2)
    modal.append(grid)
    modal.append(actions)
    mask.append(modal)
    root.append(mask)
  }
  function showDropdownMenu(root, anchor, items, rightSide) {
    for (const n of Array.from(root.querySelectorAll('.quick-add-menu')))
      n.remove()
    const menu = document.createElement('div')
    menu.className = 'quick-add-menu'
    menu.setAttribute('role', 'menu')
    for (const it of items) {
      const btn = document.createElement('button')
      btn.className = 'quick-add-item'
      btn.setAttribute('role', 'menuitem')
      btn.setAttribute('tabindex', '0')
      btn.dataset.icon = it.icon
      btn.textContent = it.label
      btn.addEventListener('click', (e) => {
        e.stopPropagation()
        try {
          it.onClick(e)
        } finally {
          for (const n of Array.from(root.querySelectorAll('.quick-add-menu')))
            n.remove()
        }
      })
      menu.append(btn)
    }
    const r = anchor.getBoundingClientRect()
    menu.style.position = 'fixed'
    const top = Math.round(r.bottom + 6)
    if (rightSide) {
      const right = Math.round(window.innerWidth - r.right)
      menu.style.top = ''.concat(top, 'px')
      menu.style.right = ''.concat(right, 'px')
    } else {
      const left = Math.round(r.left)
      menu.style.top = ''.concat(top, 'px')
      menu.style.left = ''.concat(left, 'px')
    }
    root.append(menu)
    setTimeout(() => {
      const onOutside = () => {
        for (const n of Array.from(root.querySelectorAll('.quick-add-menu')))
          n.remove()
      }
      root.addEventListener('click', onOutside, { once: true })
      document.addEventListener('click', onOutside, { once: true })
      document.addEventListener(
        'keydown',
        (ev) => {
          if (ev.key === 'Escape') onOutside()
        },
        { once: true }
      )
    }, 0)
  }
  function createSettingsPanel(root, cfg, helpers) {
    var _a, _b, _c
    const wrap = document.createElement('div')
    const globalHeader = document.createElement('h2')
    globalHeader.className = 'section-title'
    globalHeader.textContent = '\u5168\u5C40\u8BBE\u7F6E'
    const globalGrid = document.createElement('div')
    globalGrid.className = 'grid'
    try {
      globalGrid.style.gridTemplateColumns = '1fr'
    } catch (e) {}
    const siteHeader = document.createElement('h2')
    siteHeader.className = 'section-title'
    siteHeader.textContent = '\u7AD9\u70B9\u8BBE\u7F6E'
    const siteGrid = document.createElement('div')
    siteGrid.className = 'grid'
    try {
      siteGrid.style.gridTemplateColumns = '1fr'
    } catch (e) {}
    const posRow = document.createElement('div')
    posRow.className = 'row'
    const posLabel = document.createElement('label')
    posLabel.textContent = '\u4F4D\u7F6E'
    const posSel = document.createElement('select')
    for (const p of [
      'right-top',
      'right-center',
      'right-bottom',
      'left-top',
      'left-center',
      'left-bottom',
      'top-left',
      'top-center',
      'top-right',
      'bottom-left',
      'bottom-center',
      'bottom-right',
    ]) {
      const o = document.createElement('option')
      o.value = p
      o.textContent = p
      if (helpers.sitePref.position === p) o.selected = true
      posSel.append(o)
    }
    posSel.addEventListener('change', () => {
      helpers.sitePref.position = posSel.value
      helpers.saveConfig(cfg)
      helpers.rerender(root, cfg)
    })
    posRow.append(posLabel)
    posRow.append(posSel)
    const openRow = document.createElement('div')
    openRow.className = 'row'
    const openLabel = document.createElement('label')
    openLabel.textContent = '\u9ED8\u8BA4\u6253\u5F00\u65B9\u5F0F'
    let siteOpen = helpers.sitePref.defaultOpen
    const openRadios1 = createOpenModeRadios(siteOpen, (m) => {
      siteOpen = m
      helpers.sitePref.defaultOpen = m
      helpers.saveConfig(cfg)
      helpers.rerender(root, cfg)
    })
    openRow.append(openLabel)
    openRow.append(openRadios1)
    const themeRow = document.createElement('div')
    themeRow.className = 'row'
    const themeLabel = document.createElement('label')
    themeLabel.textContent = '\u4E3B\u9898'
    const themeRadios = createSegmentedRadios(
      helpers.sitePref.theme || 'system',
      ['system', 'light', 'dark'],
      (val) => {
        helpers.sitePref.theme = val
        helpers.saveConfig(cfg)
        helpers.updateThemeUI(root, cfg)
      },
      {
        labels: {
          system: '\u7CFB\u7EDF',
          light: '\u6D45\u8272',
          dark: '\u6DF1\u8272',
        },
        namePrefix: 'utqn-theme-',
      }
    )
    themeRow.append(themeLabel)
    themeRow.append(themeRadios)
    const hotkeyRow = document.createElement('div')
    hotkeyRow.className = 'row'
    const hotkeyLabel = document.createElement('label')
    hotkeyLabel.textContent = '\u5FEB\u6377\u952E'
    const hotkeyInput = document.createElement('input')
    hotkeyInput.placeholder = 'Alt+Shift+K'
    hotkeyInput.value = String(cfg.global.hotkey || 'Alt+Shift+K')
    hotkeyInput.addEventListener('change', () => {
      const v = hotkeyInput.value.trim()
      cfg.global.hotkey = v
      helpers.saveConfig(cfg)
    })
    hotkeyRow.append(hotkeyLabel)
    hotkeyRow.append(hotkeyInput)
    const syncRow = document.createElement('div')
    syncRow.className = 'row'
    const syncLabel = document.createElement('label')
    syncLabel.textContent = '\u540C\u6B65 URL'
    const syncInput = document.createElement('input')
    syncInput.value = cfg.global.syncUrl || ''
    const syncBtn = document.createElement('button')
    syncBtn.className = 'btn'
    syncBtn.textContent = '\u4ECE\u8FDC\u7A0B\u62C9\u53D6'
    syncBtn.addEventListener('click', async () => {
      const u = syncInput.value.trim()
      if (!u) return
      try {
        const res = await fetch(u, { credentials: 'omit' })
        const text = await res.text()
        const obj = JSON.parse(text)
        if (obj && obj.global && obj.groups) {
          cfg.global = obj.global
          cfg.groups = obj.groups
          helpers.saveConfig(cfg)
          helpers.rerender(root, cfg)
        }
      } catch (e) {}
    })
    syncInput.addEventListener('change', () => {
      cfg.global.syncUrl = syncInput.value.trim() || void 0
      helpers.saveConfig(cfg)
    })
    syncRow.append(syncLabel)
    syncRow.append(syncInput)
    syncRow.append(syncBtn)
    const widthRow = document.createElement('div')
    widthRow.className = 'row'
    const widthLabel = document.createElement('label')
    widthLabel.textContent = '\u7AD6\u7EBF\u5BBD\u5EA6'
    const widthInput = document.createElement('input')
    widthInput.type = 'number'
    widthInput.min = '1'
    widthInput.max = '24'
    widthInput.value = String(
      (_a = helpers.sitePref.edgeWidth) != null
        ? _a
        : helpers.edgeDefaults.width
    )
    const widthHelp = document.createElement('div')
    widthHelp.className = 'field-help'
    widthHelp.textContent = '\u5355\u4F4D\uFF1Apx\uFF0C\u8303\u56F4 1\u201324'
    widthRow.append(widthLabel)
    widthRow.append(widthInput)
    widthRow.append(widthHelp)
    const heightRow = document.createElement('div')
    heightRow.className = 'row'
    const heightLabel = document.createElement('label')
    heightLabel.textContent = '\u7AD6\u7EBF\u9AD8\u5EA6'
    const heightInput = document.createElement('input')
    heightInput.type = 'number'
    heightInput.min = '24'
    heightInput.max = '320'
    heightInput.value = String(
      (_b = helpers.sitePref.edgeHeight) != null
        ? _b
        : helpers.edgeDefaults.height
    )
    const heightHelp = document.createElement('div')
    heightHelp.className = 'field-help'
    heightHelp.textContent =
      '\u5355\u4F4D\uFF1Apx\uFF0C\u8303\u56F4 24\u2013320'
    heightRow.append(heightLabel)
    heightRow.append(heightInput)
    heightRow.append(heightHelp)
    const opacityRow = document.createElement('div')
    opacityRow.className = 'row'
    const opacityLabel = document.createElement('label')
    opacityLabel.textContent = '\u4E0D\u900F\u660E\u5EA6'
    const opacityInput = document.createElement('input')
    opacityInput.type = 'number'
    opacityInput.min = '0'
    opacityInput.max = '1'
    opacityInput.step = '0.05'
    opacityInput.value = String(
      (_c = helpers.sitePref.edgeOpacity) != null
        ? _c
        : helpers.edgeDefaults.opacity
    )
    const opacityHelp = document.createElement('div')
    opacityHelp.className = 'field-help'
    opacityHelp.textContent = '\u8303\u56F4 0\u20131\uFF0C\u6B65\u957F 0.05'
    opacityRow.append(opacityLabel)
    opacityRow.append(opacityInput)
    opacityRow.append(opacityHelp)
    const lightColorRow = document.createElement('div')
    lightColorRow.className = 'row'
    const lightColorLabel = document.createElement('label')
    lightColorLabel.textContent = '\u6D45\u8272\u4E3B\u9898\u989C\u8272'
    const lightColorInput = document.createElement('input')
    lightColorInput.type = 'color'
    lightColorInput.value = String(
      helpers.sitePref.edgeColorLight || helpers.edgeDefaults.colorLight
    )
    const lightColorHelp = document.createElement('div')
    lightColorHelp.className = 'field-help'
    lightColorHelp.textContent = '\u7528\u4E8E\u6D45\u8272\u4E3B\u9898'
    lightColorRow.append(lightColorLabel)
    lightColorRow.append(lightColorInput)
    lightColorRow.append(lightColorHelp)
    const darkColorRow = document.createElement('div')
    darkColorRow.className = 'row'
    const darkColorLabel = document.createElement('label')
    darkColorLabel.textContent = '\u6DF1\u8272\u4E3B\u9898\u989C\u8272'
    const darkColorInput = document.createElement('input')
    darkColorInput.type = 'color'
    darkColorInput.value = String(
      helpers.sitePref.edgeColorDark || helpers.edgeDefaults.colorDark
    )
    const darkColorHelp = document.createElement('div')
    darkColorHelp.className = 'field-help'
    darkColorHelp.textContent = '\u7528\u4E8E\u6DF1\u8272\u4E3B\u9898'
    darkColorRow.append(darkColorLabel)
    darkColorRow.append(darkColorInput)
    darkColorRow.append(darkColorHelp)
    widthInput.addEventListener('change', () => {
      const v = Math.max(1, Math.min(24, Number.parseInt(widthInput.value, 10)))
      helpers.sitePref.edgeWidth = v
      helpers.saveConfig(cfg)
      if (!helpers.sitePref.pinned && !helpers.tempOpenGetter())
        helpers.rerender(root, cfg)
    })
    heightInput.addEventListener('change', () => {
      const v = Math.max(
        24,
        Math.min(320, Number.parseInt(heightInput.value, 10))
      )
      helpers.sitePref.edgeHeight = v
      helpers.saveConfig(cfg)
      if (!helpers.sitePref.pinned && !helpers.tempOpenGetter())
        helpers.rerender(root, cfg)
    })
    opacityInput.addEventListener('change', () => {
      const v = Math.max(0, Math.min(1, Number.parseFloat(opacityInput.value)))
      helpers.sitePref.edgeOpacity = v
      helpers.saveConfig(cfg)
      if (!helpers.sitePref.pinned && !helpers.tempOpenGetter())
        helpers.rerender(root, cfg)
    })
    lightColorInput.addEventListener('change', () => {
      helpers.sitePref.edgeColorLight = lightColorInput.value
      helpers.saveConfig(cfg)
      if (!helpers.sitePref.pinned && !helpers.tempOpenGetter())
        helpers.rerender(root, cfg)
    })
    darkColorInput.addEventListener('change', () => {
      helpers.sitePref.edgeColorDark = darkColorInput.value
      helpers.saveConfig(cfg)
      if (!helpers.sitePref.pinned && !helpers.tempOpenGetter())
        helpers.rerender(root, cfg)
    })
    const resetRow = document.createElement('div')
    resetRow.className = 'row'
    const resetLabel = document.createElement('label')
    resetLabel.textContent = '\u7AD6\u7EBF\u5916\u89C2'
    const edgeReset = document.createElement('button')
    edgeReset.className = 'btn mini'
    edgeReset.textContent = '\u91CD\u7F6E\u9ED8\u8BA4'
    edgeReset.addEventListener('click', () => {
      helpers.sitePref.edgeWidth = helpers.edgeDefaults.width
      helpers.sitePref.edgeHeight = helpers.edgeDefaults.height
      helpers.sitePref.edgeOpacity = helpers.edgeDefaults.opacity
      helpers.sitePref.edgeColorLight = helpers.edgeDefaults.colorLight
      helpers.sitePref.edgeColorDark = helpers.edgeDefaults.colorDark
      widthInput.value = String(helpers.edgeDefaults.width)
      heightInput.value = String(helpers.edgeDefaults.height)
      opacityInput.value = String(helpers.edgeDefaults.opacity)
      lightColorInput.value = helpers.edgeDefaults.colorLight
      darkColorInput.value = helpers.edgeDefaults.colorDark
      helpers.saveConfig(cfg)
      if (!helpers.sitePref.pinned && !helpers.tempOpenGetter())
        helpers.rerender(root, cfg)
    })
    resetRow.append(resetLabel)
    resetRow.append(edgeReset)
    const panelCtrlRow = document.createElement('div')
    panelCtrlRow.className = 'row'
    const panelCtrlLabel = document.createElement('label')
    panelCtrlLabel.textContent = '\u9762\u677F\u63A7\u5236'
    const pinnedRadios = createSegmentedRadios(
      helpers.sitePref.pinned ? 'true' : 'false',
      ['true', 'false'],
      (val) => {
        helpers.sitePref.pinned = val === 'true'
        helpers.saveConfig(cfg)
        helpers.rerender(root, cfg)
      },
      {
        labels: { true: '\u56FA\u5B9A', false: '\u53D6\u6D88\u56FA\u5B9A' },
        namePrefix: 'utqn-pinned-',
      }
    )
    const hideEdgeWrap = document.createElement('label')
    hideEdgeWrap.className = 'mini'
    const hideEdgeChk = document.createElement('input')
    hideEdgeChk.type = 'checkbox'
    hideEdgeChk.checked = Boolean(helpers.sitePref.edgeHidden)
    const hideEdgeText = document.createElement('span')
    hideEdgeText.textContent = '\u9690\u85CF\u7AD6\u7EBF'
    hideEdgeWrap.append(hideEdgeChk)
    hideEdgeWrap.append(hideEdgeText)
    hideEdgeChk.addEventListener('change', () => {
      helpers.sitePref.edgeHidden = hideEdgeChk.checked
      helpers.saveConfig(cfg)
      if (!helpers.sitePref.pinned && !helpers.tempOpenGetter())
        helpers.rerender(root, cfg)
    })
    panelCtrlRow.append(panelCtrlLabel)
    panelCtrlRow.append(pinnedRadios)
    panelCtrlRow.append(hideEdgeWrap)
    const exportBtn = document.createElement('button')
    exportBtn.className = 'btn btn-secondary'
    exportBtn.textContent = '\u5BFC\u51FA\u914D\u7F6E'
    exportBtn.addEventListener('click', async () => {
      try {
        await navigator.clipboard.writeText(JSON.stringify(cfg, null, 2))
      } catch (e) {}
    })
    globalGrid.append(hotkeyRow)
    globalGrid.append(syncRow)
    const ioRow = document.createElement('div')
    ioRow.className = 'row'
    const ioLabel = document.createElement('label')
    ioLabel.textContent = '\u6570\u636E\u5BFC\u5165\u4E0E\u5BFC\u51FA'
    const exportJsonBtn = document.createElement('button')
    exportJsonBtn.className = 'btn btn-secondary'
    exportJsonBtn.textContent = '\u5BFC\u51FA JSON \u6587\u4EF6'
    exportJsonBtn.addEventListener('click', () => {
      const blob = new Blob([JSON.stringify(cfg, null, 2)], {
        type: 'application/json',
      })
      const url = URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = url
      a.download = 'utags-quick-nav-config.json'
      a.click()
      setTimeout(() => {
        URL.revokeObjectURL(url)
      }, 1e3)
    })
    const importJsonBtn = document.createElement('button')
    importJsonBtn.className = 'btn btn-secondary'
    importJsonBtn.textContent = '\u4ECE JSON \u6587\u4EF6\u5BFC\u5165'
    const fileInput = document.createElement('input')
    fileInput.type = 'file'
    fileInput.accept = 'application/json'
    fileInput.style.display = 'none'
    importJsonBtn.addEventListener('click', () => {
      fileInput.click()
    })
    fileInput.addEventListener('change', async () => {
      const f = fileInput.files && fileInput.files[0]
      if (!f) return
      try {
        const text = await f.text()
        const obj = JSON.parse(text)
        if (obj && obj.global && obj.groups) {
          cfg.global = obj.global
          cfg.groups = obj.groups
          helpers.saveConfig(cfg)
          helpers.rerender(root, cfg)
        }
      } catch (e) {}
    })
    ioRow.append(ioLabel)
    ioRow.append(exportJsonBtn)
    ioRow.append(importJsonBtn)
    ioRow.append(fileInput)
    const ioHelp = document.createElement('div')
    ioHelp.className = 'field-help'
    ioHelp.textContent =
      '\u5305\u542B\u5168\u5C40\u8BBE\u7F6E\u3001\u5206\u7EC4\u914D\u7F6E\u4E0E\u5BFC\u822A\u9879\u6570\u636E'
    ioRow.append(ioHelp)
    globalGrid.append(ioRow)
    siteGrid.append(posRow)
    siteGrid.append(openRow)
    siteGrid.append(themeRow)
    siteGrid.append(widthRow)
    siteGrid.append(heightRow)
    siteGrid.append(opacityRow)
    siteGrid.append(lightColorRow)
    siteGrid.append(darkColorRow)
    siteGrid.append(resetRow)
    siteGrid.append(panelCtrlRow)
    wrap.append(globalHeader)
    wrap.append(globalGrid)
    wrap.append(siteHeader)
    wrap.append(siteGrid)
    return wrap
  }
  function createGroupManagerPanel(root, cfg, helpers) {
    const wrap = document.createElement('div')
    const grpHeader = document.createElement('h2')
    grpHeader.className = 'section-title'
    grpHeader.textContent = '\u5206\u7EC4'
    const grpList = document.createElement('div')
    grpList.className = 'group-list'
    let active = (cfg.groups || [])[0]
    function rebuildGroupPills() {
      clearChildren(grpList)
      for (const g of cfg.groups || []) {
        const pill = document.createElement('button')
        pill.className = 'group-pill' + (g.id === active.id ? ' active' : '')
        pill.textContent = g.displayName || g.name
        pill.dataset.gid = g.id
        grpList.append(pill)
      }
    }
    grpList.addEventListener('click', (ev) => {
      var _a
      const target = ev.target
      const btn = target.closest('.group-pill')
      if (!btn) return
      const pill = btn
      const gid = ((_a = pill.dataset) == null ? void 0 : _a.gid) || ''
      const next = (cfg.groups || []).find((gg) => gg.id === gid)
      if (!next) return
      active = next
      rebuildGroupPills()
      rebuildGroupEditor()
    })
    const groupEditor = document.createElement('div')
    function rebuildGroupEditor() {
      clearChildren(groupEditor)
      const row1 = document.createElement('div')
      row1.className = 'row'
      const l1 = document.createElement('label')
      l1.textContent = '\u7EC4\u540D'
      const nameInput = document.createElement('input')
      nameInput.value = active.name
      nameInput.addEventListener('change', () => {
        active.name = nameInput.value
        rebuildGroupPills()
        helpers.saveConfig(cfg)
        helpers.rerender(root, cfg)
      })
      row1.append(l1)
      row1.append(nameInput)
      const row2 = document.createElement('div')
      row2.className = 'row'
      const l2 = document.createElement('label')
      l2.textContent = '\u56FE\u6807'
      const iconComp = createIconInput(
        active.icon || '',
        ['icon', 'url', 'emoji'],
        {
          labels: { icon: '\u56FE\u6807', url: 'URL', emoji: 'Emoji' },
          namePrefix: 'utqn-group-icon-kind-',
          onValueChange() {
            const v = iconComp.getFinal()
            active.icon = v
            helpers.saveConfig(cfg)
            helpers.rerender(root, cfg)
          },
          onKindChange() {
            const v = iconComp.getFinal()
            active.icon = v
            helpers.saveConfig(cfg)
            helpers.rerender(root, cfg)
          },
        }
      )
      row2.append(l2)
      row2.append(iconComp.el)
      const row3 = document.createElement('div')
      row3.className = 'row'
      const l3 = document.createElement('label')
      l3.textContent = 'URL \u89C4\u5219'
      const ta = document.createElement('textarea')
      ta.value = (active.match || []).join('\n')
      ta.addEventListener('change', () => {
        const grp = (cfg.groups || []).find((g) => g.id === active.id)
        if (!grp) return
        grp.match = ta.value
          .split(/\n+/)
          .map((v) => v.trim())
          .filter(Boolean)
        helpers.saveConfig(cfg)
        helpers.rerender(root, cfg)
      })
      row3.append(l3)
      row3.append(ta)
      const row4 = document.createElement('div')
      row4.className = 'row'
      const l4 = document.createElement('label')
      l4.textContent = '\u7EC4\u9ED8\u8BA4\u6253\u5F00\u65B9\u5F0F'
      let grpOpen = active.defaultOpen || helpers.sitePref.defaultOpen
      const openRadios2 = createOpenModeRadios(grpOpen, (m) => {
        grpOpen = m
        active.defaultOpen = m
        helpers.saveConfig(cfg)
      })
      row4.append(l4)
      row4.append(openRadios2)
      const row5 = document.createElement('div')
      row5.className = 'row'
      const l5 = document.createElement('label')
      l5.textContent = '\u6BCF\u884C\u4E2A\u6570'
      const colsSel = document.createElement('select')
      for (const c of [1, 2, 3, 4, 5, 6]) {
        const o = document.createElement('option')
        o.value = String(c)
        o.textContent = String(c)
        if ((active.itemsPerRow || 1) === c) o.selected = true
        colsSel.append(o)
      }
      colsSel.addEventListener('change', () => {
        const v = Number.parseInt(colsSel.value, 10)
        active.itemsPerRow = Number.isNaN(v) ? 1 : Math.max(1, Math.min(6, v))
        helpers.saveConfig(cfg)
        helpers.rerender(root, cfg)
      })
      row5.append(l5)
      row5.append(colsSel)
      const row6 = document.createElement('div')
      row6.className = 'row'
      const l6 = document.createElement('label')
      l6.textContent = '\u5206\u7EC4\u663E\u793A\u72B6\u6001'
      const visSel = document.createElement('select')
      for (const st of ['\u663E\u793A', '\u9690\u85CF']) {
        const o = document.createElement('option')
        o.value = st
        o.textContent = st
        if ((active.hidden ? '\u9690\u85CF' : '\u663E\u793A') === st)
          o.selected = true
        visSel.append(o)
      }
      visSel.addEventListener('change', () => {
        active.hidden = visSel.value === '\u9690\u85CF'
        helpers.saveConfig(cfg)
        helpers.rerender(root, cfg)
      })
      row6.append(l6)
      row6.append(visSel)
      const itemsHeader = document.createElement('h2')
      itemsHeader.className = 'section-title'
      itemsHeader.textContent = '\u5BFC\u822A\u9879'
      const itemsList = document.createElement('div')
      function rebuildItems() {
        clearChildren(itemsList)
        const groupId = active.id
        for (const it of active.items || []) {
          const row = document.createElement('div')
          row.className = 'row item-row'
          const n = document.createElement('input')
          n.value = it.name
          n.addEventListener('change', () => {
            const grp = (cfg.groups || []).find((g) => g.id === groupId)
            if (!grp) return
            const item = (grp.items || []).find((x) => x.id === it.id)
            if (!item) return
            item.name = n.value
            helpers.saveConfig(cfg)
            helpers.rerender(root, cfg)
          })
          const iconComp2 = createIconInput(
            it.icon || '',
            ['icon', 'url', 'emoji'],
            {
              labels: { icon: '\u56FE\u6807', url: 'URL', emoji: 'Emoji' },
              namePrefix: 'utqn-item-icon-kind-',
              placeholders: {
                icon: 'home',
                url: 'https://...',
                emoji: 'emoji',
              },
              onValueChange() {
                const grp = (cfg.groups || []).find((g) => g.id === groupId)
                if (!grp) return
                const item = (grp.items || []).find((x) => x.id === it.id)
                if (!item) return
                item.icon = iconComp2.getFinal()
                helpers.saveConfig(cfg)
                helpers.rerender(root, cfg)
              },
              onKindChange() {
                const grp = (cfg.groups || []).find((g) => g.id === groupId)
                if (!grp) return
                const item = (grp.items || []).find((x) => x.id === it.id)
                if (!item) return
                item.icon = iconComp2.getFinal()
                helpers.saveConfig(cfg)
                helpers.rerender(root, cfg)
              },
            }
          )
          const t = document.createElement('select')
          for (const tp of ['url', 'js']) {
            const o = document.createElement('option')
            o.value = tp
            o.textContent = tp
            if (it.type === tp) o.selected = true
            t.append(o)
          }
          t.addEventListener('change', () => {
            const grp = (cfg.groups || []).find((g) => g.id === groupId)
            if (!grp) return
            const item = (grp.items || []).find((x) => x.id === it.id)
            if (!item) return
            item.type = t.value
            helpers.saveConfig(cfg)
          })
          const d = document.createElement('input')
          d.value = it.data
          d.addEventListener('change', () => {
            const grp = (cfg.groups || []).find((g) => g.id === groupId)
            if (!grp) return
            const item = (grp.items || []).find((x) => x.id === it.id)
            if (!item) return
            item.data = d.value
            helpers.saveConfig(cfg)
            helpers.rerender(root, cfg)
          })
          const m = document.createElement('select')
          for (const mm of ['same-tab', 'new-tab']) {
            const o = document.createElement('option')
            o.value = mm
            o.textContent = mm
            if (
              (it.openIn ||
                active.defaultOpen ||
                helpers.sitePref.defaultOpen) === mm
            )
              o.selected = true
            m.append(o)
          }
          m.addEventListener('change', () => {
            const grp = (cfg.groups || []).find((g) => g.id === groupId)
            if (!grp) return
            const item = (grp.items || []).find((x) => x.id === it.id)
            if (!item) return
            item.openIn = m.value
            helpers.saveConfig(cfg)
          })
          const visibleSel = document.createElement('select')
          for (const st of ['\u663E\u793A', '\u9690\u85CF']) {
            const o = document.createElement('option')
            o.value = st
            o.textContent = st
            if ((it.hidden ? '\u9690\u85CF' : '\u663E\u793A') === st)
              o.selected = true
            visibleSel.append(o)
          }
          visibleSel.addEventListener('change', () => {
            const grp = (cfg.groups || []).find((g) => g.id === groupId)
            if (!grp) return
            const item = (grp.items || []).find((x) => x.id === it.id)
            if (!item) return
            item.hidden = visibleSel.value === '\u9690\u85CF'
            helpers.saveConfig(cfg)
            helpers.rerender(root, cfg)
          })
          const del = document.createElement('button')
          del.className = 'btn'
          del.textContent = '\u5220\u9664'
          del.addEventListener('click', () => {
            const grp = (cfg.groups || []).find((g) => g.id === groupId)
            if (!grp) return
            grp.items = (grp.items || []).filter((x) => x.id !== it.id)
            helpers.saveConfig(cfg)
            rebuildItems()
            helpers.rerender(root, cfg)
          })
          const moveToSel = document.createElement('select')
          for (const g of cfg.groups || []) {
            if (g.id === groupId) continue
            const o = document.createElement('option')
            o.value = g.id
            o.textContent = '\u590D\u5236\u5230 ' + String(g.name)
            moveToSel.append(o)
          }
          const moveBtn = document.createElement('button')
          moveBtn.className = 'btn mini'
          moveBtn.textContent = '\u590D\u5236\u5230\u5206\u7EC4'
          moveBtn.addEventListener('click', () => {
            const toId = moveToSel.value
            if (!toId) return
            copyItemToGroup(cfg, groupId, it.id, toId)
            helpers.saveConfig(cfg)
            rebuildItems()
            helpers.rerender(root, cfg)
          })
          row.append(n)
          row.append(iconComp2.el)
          row.append(t)
          row.append(d)
          row.append(m)
          row.append(visibleSel)
          row.append(moveToSel)
          row.append(moveBtn)
          row.append(del)
          itemsList.append(row)
        }
      }
      const addRow = document.createElement('div')
      addRow.className = 'row'
      const addBtn = document.createElement('button')
      addBtn.className = 'btn btn-secondary'
      addBtn.textContent = '\u6DFB\u52A0\u5BFC\u822A\u9879'
      addBtn.addEventListener('click', () => {
        var _a
        openAddLinkModal(root, cfg, {
          saveConfig(c) {
            helpers.saveConfig(c)
          },
          rerender(r, c) {
            helpers.rerender(r, c)
          },
          defaultOpen:
            (_a = active.defaultOpen) != null
              ? _a
              : helpers.sitePref.defaultOpen,
          defaultGroupId: active.id,
        })
      })
      addRow.append(addBtn)
      const grpActions = document.createElement('div')
      grpActions.className = 'row'
      const addGroup = document.createElement('button')
      addGroup.className = 'btn btn-secondary'
      addGroup.textContent = '\u6DFB\u52A0\u5206\u7EC4'
      addGroup.addEventListener('click', () => {
        const ng = {
          id: uid(),
          name: '\u65B0\u5206\u7EC4',
          icon: 'lucide:folder',
          match: ['*://' + (location.hostname || '') + '/*'],
          items: [],
          defaultOpen: helpers.sitePref.defaultOpen,
        }
        cfg.groups.push(ng)
        active = ng
        helpers.saveConfig(cfg)
        rebuildGroupPills()
        rebuildGroupEditor()
        helpers.rerender(root, cfg)
      })
      const delGroup = document.createElement('button')
      delGroup.className = 'btn btn-secondary'
      delGroup.textContent = '\u5220\u9664\u5206\u7EC4'
      delGroup.addEventListener('click', () => {
        if ((cfg.groups || []).length <= 1) {
          return
        }
        cfg.groups = (cfg.groups || []).filter((g) => g.id !== active.id)
        active = cfg.groups[0]
        helpers.saveConfig(cfg)
        rebuildGroupPills()
        rebuildGroupEditor()
        helpers.rerender(root, cfg)
      })
      const delEmptyGroups = document.createElement('button')
      delEmptyGroups.className = 'btn btn-secondary'
      delEmptyGroups.textContent =
        '\u5220\u9664\u6240\u6709\u7A7A\u7684\u5206\u7EC4'
      delEmptyGroups.addEventListener('click', () => {
        const empties = (cfg.groups || []).filter(
          (g) => (g.items || []).length === 0
        )
        const n = empties.length
        if (n === 0) return
        const ok = globalThis.confirm(
          '\u786E\u8BA4\u5220\u9664 ' + String(n) + ' \u4E2A\u5206\u7EC4\uFF1F'
        )
        if (!ok) return
        const kept = (cfg.groups || []).filter(
          (g) => (g.items || []).length > 0
        )
        if (kept.length === 0) {
          const ng = {
            id: uid(),
            name: '\u65B0\u5206\u7EC4',
            icon: 'lucide:folder',
            match: ['*://' + (location.hostname || '') + '/*'],
            items: [],
            defaultOpen: helpers.sitePref.defaultOpen,
          }
          kept.push(ng)
        }
        cfg.groups = kept
        active = cfg.groups[0]
        helpers.saveConfig(cfg)
        rebuildGroupPills()
        rebuildGroupEditor()
        helpers.rerender(root, cfg)
      })
      grpActions.append(addGroup)
      grpActions.append(delGroup)
      grpActions.append(delEmptyGroups)
      groupEditor.append(row1)
      groupEditor.append(row2)
      groupEditor.append(row3)
      groupEditor.append(row4)
      groupEditor.append(row5)
      groupEditor.append(row6)
      groupEditor.append(itemsHeader)
      groupEditor.append(itemsList)
      groupEditor.append(addRow)
      groupEditor.append(grpActions)
      rebuildItems()
    }
    rebuildGroupPills()
    rebuildGroupEditor()
    wrap.append(grpHeader)
    wrap.append(grpList)
    wrap.append(groupEditor)
    return wrap
  }
  function copyItemToGroup(cfg, fromGroupId, itemId, toGroupId) {
    const from = (cfg.groups || []).find((g) => g.id === fromGroupId)
    const to = (cfg.groups || []).find((g) => g.id === toGroupId)
    if (!from || !to) return
    const it = (from.items || []).find((x) => x.id === itemId)
    if (!it) return
    const dup = __spreadProps(__spreadValues({}, it), { id: uid() })
    to.items.push(dup)
  }
  function openEditorModal(root, cfg, helpers) {
    for (const n of Array.from(root.querySelectorAll('.modal-mask'))) n.remove()
    const mask = document.createElement('div')
    mask.className = 'modal-mask'
    try {
      mask.style.zIndex = '2147483648'
    } catch (e) {}
    const modal = document.createElement('div')
    modal.className = 'modal editor'
    const h2 = document.createElement('h2')
    h2.textContent = '\u5FEB\u901F\u5BFC\u822A\u8BBE\u7F6E'
    let tab = 'settings'
    const tabs = createSegmentedRadios(
      tab,
      ['settings', 'groups'],
      (v) => {
        tab = v
        syncUi()
      },
      {
        labels: { settings: '\u8BBE\u7F6E', groups: '\u5206\u7EC4' },
        namePrefix: 'utqn-editor-tabs-',
      }
    )
    const settingsWrap = document.createElement('div')
    const groupsWrap = document.createElement('div')
    const settingsPanel = createSettingsPanel(root, cfg, helpers)
    const groupsPanel = createGroupManagerPanel(root, cfg, {
      saveConfig: helpers.saveConfig,
      rerender: helpers.rerender,
      sitePref: helpers.sitePref,
    })
    settingsWrap.append(settingsPanel)
    groupsWrap.append(groupsPanel)
    const actions = document.createElement('div')
    actions.className = 'row'
    const closeBtn = document.createElement('button')
    closeBtn.className = 'btn btn-secondary'
    closeBtn.textContent = '\u5173\u95ED'
    closeBtn.addEventListener('click', () => {
      mask.remove()
    })
    actions.append(closeBtn)
    const syncUi = () => {
      settingsWrap.style.display = tab === 'settings' ? '' : 'none'
      groupsWrap.style.display = tab === 'groups' ? '' : 'none'
    }
    syncUi()
    modal.append(h2)
    modal.append(tabs)
    modal.append(settingsWrap)
    modal.append(groupsWrap)
    modal.append(actions)
    mask.append(modal)
    root.append(mask)
  }
  var KEY = 'utqn_config'
  var SITE_KEY = location.hostname || ''
  var sitePref
  var lastSaved = ''
  var EDGE_DEFAULT_WIDTH = 3
  var EDGE_DEFAULT_HEIGHT = 60
  var EDGE_DEFAULT_OPACITY = 0.6
  var EDGE_DEFAULT_COLOR_LIGHT = '#1A73E8'
  var EDGE_DEFAULT_COLOR_DARK = '#8AB4F8'
  var EDGE_DEFAULT_HIDDEN = false
  var POSITION_DEFAULT = 'right-top'
  var OPEN_DEFAULT = 'same-tab'
  var THEME_DEFAULT = 'system'
  var PINNED_DEFAULT = false
  var ENABLED_DEFAULT = true
  var HOTKEY_DEFAULT = 'Alt+Shift+K'
  var tempOpen = false
  var tempClosed = false
  var menuIds = []
  var showAllGroups = false
  var showHiddenGroups = false
  var showHiddenItems = false
  var editingGroups = /* @__PURE__ */ new Set()
  var selectedItemsByGroup = /* @__PURE__ */ new Map()
  function matchPattern(url, pattern) {
    try {
      const t = String(pattern || '')
      if (t.startsWith('/') && t.lastIndexOf('/') > 0) {
        const last = t.lastIndexOf('/')
        const body = t.slice(1, last)
        const flags = t.slice(last + 1)
        const re2 = new RegExp(body, flags)
        return re2.test(url)
      }
      const esc = t
        .replaceAll(/[.+^${}()|[\]\\]/g, '\\$&')
        .replaceAll('*', '.*')
      const re = new RegExp('^' + esc + '$')
      return re.test(url)
    } catch (e) {
      return false
    }
  }
  function initSitePref(cfg) {
    var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k
    if (!cfg.sitePrefs) cfg.sitePrefs = {}
    const stored = cfg.sitePrefs[SITE_KEY] || {}
    const cur = {
      position: (_a = stored.position) != null ? _a : POSITION_DEFAULT,
      defaultOpen: (_b = stored.defaultOpen) != null ? _b : OPEN_DEFAULT,
      theme: (_c = stored.theme) != null ? _c : THEME_DEFAULT,
      pinned: (_d = stored.pinned) != null ? _d : PINNED_DEFAULT,
      enabled: (_e = stored.enabled) != null ? _e : ENABLED_DEFAULT,
      edgeWidth: (_f = stored.edgeWidth) != null ? _f : EDGE_DEFAULT_WIDTH,
      edgeHeight: (_g = stored.edgeHeight) != null ? _g : EDGE_DEFAULT_HEIGHT,
      edgeOpacity:
        (_h = stored.edgeOpacity) != null ? _h : EDGE_DEFAULT_OPACITY,
      edgeColorLight:
        (_i = stored.edgeColorLight) != null ? _i : EDGE_DEFAULT_COLOR_LIGHT,
      edgeColorDark:
        (_j = stored.edgeColorDark) != null ? _j : EDGE_DEFAULT_COLOR_DARK,
      edgeHidden: (_k = stored.edgeHidden) != null ? _k : EDGE_DEFAULT_HIDDEN,
    }
    sitePref = cur
    cfg.sitePrefs[SITE_KEY] = cur
  }
  function openItem(it, group, cfg, opts) {
    const mode = it.openIn || group.defaultOpen || sitePref.defaultOpen
    if (it.type === 'url') {
      const url = new URL(
        resolveUrlTemplate(String(it.data || '/')),
        location.href
      ).href
      const finalMode = (opts == null ? void 0 : opts.forceNewTab)
        ? 'new-tab'
        : mode
      if (finalMode === 'new-tab') {
        window.open(url, '_blank', 'noopener')
      } else {
        location.assign(url)
      }
      return
    }
    try {
      const onMsg = (ev) => {
        const d = (ev && ev.data) || null
        if (d && typeof d.__utqn_err__ === 'string' && d.__utqn_err__) {
          try {
            if (typeof globalThis.alert === 'function') {
              globalThis.alert(
                '\u811A\u672C\u6267\u884C\u51FA\u9519\uFF1A' +
                  String(d.__utqn_err__)
              )
            } else {
              console.error(
                '\u811A\u672C\u6267\u884C\u51FA\u9519\uFF1A' +
                  String(d.__utqn_err__)
              )
            }
          } catch (e) {}
          return
        }
        const raw =
          d && typeof d.__utqn_url__ === 'string' ? d.__utqn_url__ : ''
        if (!raw) return
        try {
          const url = new URL(
            resolveUrlTemplate(String(raw).trim()),
            location.href
          ).href
          const overrideMode =
            d && typeof d.__utqn_mode__ === 'string' ? d.__utqn_mode__ : void 0
          const finalMode = (opts == null ? void 0 : opts.forceNewTab)
            ? 'new-tab'
            : overrideMode || mode
          if (finalMode === 'new-tab') window.open(url, '_blank', 'noopener')
          else location.assign(url)
        } catch (e) {}
      }
      window.addEventListener('message', onMsg, { once: true })
      const s = document.createElement('script')
      const codeSrc = JSON.stringify(String(it.data || ''))
      s.textContent = '(async function(){try{var __code='.concat(
        codeSrc,
        ";var __fn=new Function(__code);var __ret=__fn();if(__ret&&typeof __ret.then==='function'){__ret=await __ret;}var __url='';var __mode='';if(typeof __ret==='string'&&__ret.trim()){__url=__ret.trim();}else if(__ret&&typeof __ret==='object'){try{if(typeof __ret.error==='string'&&__ret.error){window.postMessage({__utqn_err__:__ret.error},'*');return;}var __x=__ret.url||(__ret.href?String(__ret):'');if(typeof __x==='string'&&__x.trim()){__url=__x.trim();}var __m=__ret.mode; if(__m==='same-tab'||__m==='new-tab'){__mode=__m;} }catch{}}if(__url){window.postMessage({__utqn_url__:__url,__utqn_mode__:__mode},'*');}}catch(e){try{window.postMessage({__utqn_err__:String(e&&(e.message||e))},'*');}catch{}}})()"
      )
      ;(document.documentElement || document.body).append(s)
      s.remove()
    } catch (e) {}
  }
  async function loadConfig() {
    try {
      const v = await GM.getValue(KEY, '')
      if (v) {
        const raw = JSON.parse(String(v) || '{}')
        const host2 = location.hostname || ''
        const ensureGroup = (gg) => ({
          id: String((gg == null ? void 0 : gg.id) || uid()),
          name: String((gg == null ? void 0 : gg.name) || '\u9ED8\u8BA4\u7EC4'),
          icon: String((gg == null ? void 0 : gg.icon) || 'lucide:folder'),
          match: Array.isArray(gg == null ? void 0 : gg.match)
            ? gg.match
            : ['*'],
          defaultOpen:
            (gg == null ? void 0 : gg.defaultOpen) === 'new-tab'
              ? 'new-tab'
              : 'same-tab',
          items: Array.isArray(gg == null ? void 0 : gg.items) ? gg.items : [],
          collapsed: Boolean(gg == null ? void 0 : gg.collapsed),
          itemsPerRow: Number.isFinite(gg == null ? void 0 : gg.itemsPerRow)
            ? gg.itemsPerRow
            : 1,
          hidden: Boolean(gg == null ? void 0 : gg.hidden),
        })
        const groupsArr = Array.isArray(raw == null ? void 0 : raw.groups)
          ? raw.groups.map((x) => ensureGroup(x))
          : []
        if (groupsArr.length === 0) {
          const g2 = ensureGroup({})
          g2.items = [
            {
              id: uid(),
              name: '\u9996\u9875',
              icon: 'lucide:home',
              type: 'url',
              data: '/',
              openIn: 'same-tab',
              hidden: false,
            },
          ]
          groupsArr.push(g2)
        }
        const cfg = {
          global: (raw == null ? void 0 : raw.global) || {},
          sitePrefs: (raw == null ? void 0 : raw.sitePrefs) || {},
          groups: groupsArr,
        }
        return cfg
      }
    } catch (e) {}
    const host = location.hostname || ''
    const g = {
      id: uid(),
      name: '\u9ED8\u8BA4\u7EC4',
      icon: 'lucide:folder',
      match: ['*'],
      defaultOpen: 'same-tab',
      items: [
        {
          id: uid(),
          name: '\u9996\u9875',
          icon: 'lucide:home',
          type: 'url',
          data: '/',
          openIn: 'same-tab',
          hidden: false,
        },
        {
          id: uid(),
          name: '\u7AD9\u5185\u641C\u7D22',
          icon: 'favicon',
          type: 'url',
          data: 'https://www.google.com/search?q=site:{hostname}%20{selected||query}',
          openIn: 'same-tab',
          hidden: false,
        },
      ],
      collapsed: false,
      itemsPerRow: 1,
      hidden: false,
    }
    return {
      global: {},
      groups: [g],
    }
  }
  async function saveConfig(cfg) {
    var _a, _b, _c, _d, _e, _f
    try {
      const sp = {}
      if (sitePref.position !== POSITION_DEFAULT)
        sp.position = sitePref.position
      if (sitePref.defaultOpen !== OPEN_DEFAULT)
        sp.defaultOpen = sitePref.defaultOpen
      if ((sitePref.theme || THEME_DEFAULT) !== THEME_DEFAULT)
        sp.theme = sitePref.theme
      if (sitePref.pinned !== PINNED_DEFAULT) sp.pinned = sitePref.pinned
      if (sitePref.enabled !== ENABLED_DEFAULT) sp.enabled = sitePref.enabled
      if (
        ((_a = sitePref.edgeWidth) != null ? _a : EDGE_DEFAULT_WIDTH) !==
        EDGE_DEFAULT_WIDTH
      )
        sp.edgeWidth = sitePref.edgeWidth
      if (
        ((_b = sitePref.edgeHeight) != null ? _b : EDGE_DEFAULT_HEIGHT) !==
        EDGE_DEFAULT_HEIGHT
      )
        sp.edgeHeight = sitePref.edgeHeight
      if (
        ((_c = sitePref.edgeOpacity) != null ? _c : EDGE_DEFAULT_OPACITY) !==
        EDGE_DEFAULT_OPACITY
      )
        sp.edgeOpacity = sitePref.edgeOpacity
      if (
        ((_d = sitePref.edgeColorLight) != null
          ? _d
          : EDGE_DEFAULT_COLOR_LIGHT) !== EDGE_DEFAULT_COLOR_LIGHT
      )
        sp.edgeColorLight = sitePref.edgeColorLight
      if (
        ((_e = sitePref.edgeColorDark) != null
          ? _e
          : EDGE_DEFAULT_COLOR_DARK) !== EDGE_DEFAULT_COLOR_DARK
      )
        sp.edgeColorDark = sitePref.edgeColorDark
      if (
        ((_f = sitePref.edgeHidden) != null ? _f : EDGE_DEFAULT_HIDDEN) !==
        EDGE_DEFAULT_HIDDEN
      )
        sp.edgeHidden = sitePref.edgeHidden
      const nextSitePrefs = __spreadValues({}, cfg.sitePrefs)
      if (Object.keys(sp).length > 0) nextSitePrefs[SITE_KEY] = sp
      else delete nextSitePrefs[SITE_KEY]
      const next = __spreadProps(__spreadValues({}, cfg), {
        sitePrefs: nextSitePrefs,
      })
      const s = JSON.stringify(next)
      if (s === lastSaved) return
      lastSaved = s
      await GM.setValue(KEY, s)
    } catch (e) {}
  }
  function createRoot() {
    const existing = document.querySelector(
      '[data-utqn-host="utags-quick-nav"]'
    )
    if (existing instanceof HTMLElement) {
      const root2 = existing.shadowRoot
      return { host: existing, root: root2 }
    }
    const host = document.createElement('div')
    host.dataset.utqnHost = 'utags-quick-nav'
    const root = host.attachShadow({ mode: 'open' })
    const style = document.createElement('style')
    style.textContent = style_default
    root.append(style)
    document.documentElement.append(host)
    return { host, root }
  }
  function place(el, cfg) {
    const p = sitePref.position
    el.style.position = 'fixed'
    el.style.inset = 'auto'
    switch (p) {
      case 'right-top': {
        el.style.top = '0'
        el.style.right = '0'
        break
      }
      case 'left-top': {
        el.style.top = '0'
        el.style.left = '0'
        break
      }
      case 'left-bottom': {
        el.style.bottom = '0'
        el.style.left = '0'
        break
      }
      case 'right-bottom': {
        el.style.bottom = '0'
        el.style.right = '0'
        break
      }
      case 'left-center': {
        el.style.top = '50%'
        el.style.left = '0'
        el.style.transform = 'translateY(-50%)'
        break
      }
      case 'right-center': {
        el.style.top = '50%'
        el.style.right = '0'
        el.style.transform = 'translateY(-50%)'
        break
      }
      case 'top-left': {
        el.style.top = '0'
        el.style.left = '0'
        break
      }
      case 'top-center': {
        el.style.top = '0'
        el.style.left = '50%'
        el.style.transform = 'translateX(-50%)'
        break
      }
      case 'top-right': {
        el.style.top = '0'
        el.style.right = '0'
        break
      }
      case 'bottom-left': {
        el.style.bottom = '0'
        el.style.left = '0'
        break
      }
      case 'bottom-center': {
        el.style.bottom = '0'
        el.style.left = '50%'
        el.style.transform = 'translateX(-50%)'
        break
      }
      case 'bottom-right': {
        el.style.bottom = '0'
        el.style.right = '0'
        break
      }
    }
  }
  function isHorizontalPos(pos) {
    return pos.startsWith('top-') || pos.startsWith('bottom-')
  }
  function isRightSide(pos) {
    return pos.startsWith('right-')
  }
  function isTopSide(pos) {
    return pos.startsWith('top-')
  }
  function scorePattern(url, pattern) {
    const neg = pattern.startsWith('!')
    const pat = neg ? pattern.slice(1) : pattern
    if (!matchPattern(url, pat)) return -1
    if (pat.startsWith('/') && pat.lastIndexOf('/') > 0) {
      const last = pat.lastIndexOf('/')
      return pat.slice(1, last).length
    }
    return pat.replaceAll('*', '').length
  }
  function groupScore(url, g) {
    let max = -1
    for (const p of g.match) {
      const neg = p.startsWith('!')
      const pat = neg ? p.slice(1) : p
      if (neg) {
        if (matchPattern(url, pat)) return -1
        continue
      }
      const s = scorePattern(url, p)
      if (s > max) max = s
    }
    return max
  }
  function currentGroups(cfg) {
    if (showAllGroups) {
      return cfg.groups.filter((g) => showHiddenGroups || !g.hidden)
    }
    const url = location.href
    return cfg.groups
      .map((g) => ({ g, s: groupScore(url, g) }))
      .filter((x) => x.s >= 0 && !x.g.hidden)
      .sort((a, b) => b.s - a.s)
      .map((x) => x.g)
  }
  function preserveScroll(panel, cb) {
    const scroller = panel.querySelector('.panel-scroll') || panel
    const sx = scroller.scrollLeft
    const sy = scroller.scrollTop
    cb()
    const apply = () => {
      try {
        scroller.scrollLeft = sx
        scroller.scrollTop = sy
      } catch (e) {}
    }
    apply()
    try {
      requestAnimationFrame(apply)
    } catch (e) {}
  }
  function isDarkTheme(cfg) {
    const t = sitePref.theme || THEME_DEFAULT
    if (t === 'dark') return true
    if (t === 'light') return false
    try {
      return (
        globalThis.window !== void 0 &&
        Boolean(globalThis.matchMedia) &&
        globalThis.matchMedia('(prefers-color-scheme: dark)').matches
      )
    } catch (e) {
      return false
    }
  }
  function parseHotkeySpec(spec) {
    const s = String(spec || '').trim()
    if (!s) return null
    const parts = s.split('+').map((x) => x.trim().toLowerCase())
    let key = ''
    const need = { ctrl: false, meta: false, alt: false, shift: false }
    for (const p of parts) {
      switch (p) {
        case 'ctrl':
        case 'control': {
          need.ctrl = true
          break
        }
        case 'meta':
        case 'cmd':
        case 'command': {
          need.meta = true
          break
        }
        case 'alt':
        case 'option': {
          need.alt = true
          break
        }
        case 'shift': {
          need.shift = true
          break
        }
        default: {
          key = p
          break
        }
      }
    }
    if (!key) return null
    let code = ''
    if (key.length === 1) code = 'Key' + key.toUpperCase()
    else if (key === 'space') code = 'Space'
    else code = key
    return {
      ctrl: need.ctrl,
      meta: need.meta,
      alt: need.alt,
      shift: need.shift,
      code,
    }
  }
  function isEditableTarget(t) {
    const el = t
    if (!el) return false
    const tag = el.tagName ? el.tagName.toLowerCase() : ''
    if (tag === 'input' || tag === 'textarea' || tag === 'select') return true
    const ce = el.isContentEditable
    return Boolean(ce)
  }
  function registerHotkeys(root, cfg) {
    document.addEventListener('keydown', (e) => {
      if (e.defaultPrevented) return
      if (isEditableTarget(e.target || void 0)) return
      const spec = cfg.global.hotkey || HOTKEY_DEFAULT
      const p = parseHotkeySpec(spec)
      if (!p) return
      if (!(p.ctrl || p.meta || p.alt)) return
      const hasCtrl = Boolean(e.ctrlKey)
      const hasMeta = Boolean(e.metaKey)
      const hasAlt = Boolean(e.altKey)
      const hasShift = Boolean(e.shiftKey)
      if (p.ctrl !== hasCtrl) return
      if (p.meta !== hasMeta) return
      if (p.alt !== hasAlt) return
      if (p.shift !== hasShift) return
      if (e.code !== p.code) return
      e.preventDefault()
      const visible = Boolean(root.querySelector('.utqn .panel'))
      if (visible) {
        collapseWithAnim(root, cfg)
      } else {
        tempOpen = true
        rerender(root, cfg)
      }
    })
  }
  function renderNavItem(
    root,
    cfg,
    g,
    it,
    section,
    isEditing,
    siteDefaultOpenConst,
    defOpen
  ) {
    var _a
    const wrap = document.createElement('div')
    wrap.className = 'item-wrap'
    wrap.dataset.itemId = it.id
    wrap.classList.add('fade-in')
    if (it.hidden) wrap.classList.add('is-hidden')
    const a = document.createElement('a')
    a.className = 'item'
    if (isEditing) {
      a.href = '#'
      a.addEventListener('click', (e) => {
        e.preventDefault()
        e.stopImmediatePropagation()
      })
    } else if (it.type === 'url') {
      const url = new URL(
        resolveUrlTemplate(String(it.data || '/')),
        location.href
      ).href
      a.href = url
      a.addEventListener('click', (e) => {
        e.preventDefault()
        const forceNew = Boolean(e.ctrlKey || e.metaKey)
        openItem(it, g, cfg, { forceNewTab: forceNew })
      })
      a.addEventListener('auxclick', (e) => {
        if (e.button === 1) {
          e.preventDefault()
          openItem(it, g, cfg, { forceNewTab: true })
        }
      })
    } else {
      a.href = '#'
      a.addEventListener('click', (e) => {
        e.preventDefault()
        const forceNew = Boolean(e.ctrlKey || e.metaKey)
        openItem(it, g, cfg, { forceNewTab: forceNew })
      })
      a.addEventListener('auxclick', (e) => {
        if (e.button === 1) {
          e.preventDefault()
          openItem(it, g, cfg, { forceNewTab: true })
        }
      })
    }
    {
      const rawIcon = String(it.icon || '')
      let iconStr = rawIcon
      if (rawIcon.startsWith('favicon')) {
        const param = rawIcon.split(':')[1]
        const sizeNum = param ? Number.parseInt(param, 10) : 64
        const size = sizeNum === 32 ? 32 : sizeNum === 64 ? 64 : 64
        const targetUrl =
          it.type === 'url'
            ? new URL(resolveUrlTemplate(String(it.data || '/')), location.href)
                .href
            : location.href
        try {
          iconStr = 'url:' + getFaviconUrl(targetUrl, size)
        } catch (e) {}
      }
      setIcon(a, iconStr)
    }
    const t = document.createElement('span')
    t.textContent = it.name
    a.append(t)
    if (isEditing) {
      const set = selectedItemsByGroup.get(g.id) || /* @__PURE__ */ new Set()
      selectedItemsByGroup.set(g.id, set)
      const sel = document.createElement('input')
      sel.type = 'checkbox'
      sel.checked = set.has(it.id)
      const updateDeleteBtnState = () => {
        var _a2
        const btn = section.querySelector(
          '.header-actions .btn.mini:last-child'
        )
        if (btn instanceof HTMLButtonElement) {
          const count =
            ((_a2 = selectedItemsByGroup.get(g.id)) == null
              ? void 0
              : _a2.size) || 0
          btn.disabled = !(count > 0)
        }
      }
      sel.addEventListener('change', () => {
        if (sel.checked) set.add(it.id)
        else set.delete(it.id)
        updateDeleteBtnState()
      })
      wrap.append(sel)
    }
    wrap.append(a)
    if (isEditing) {
      const editItemBtn = document.createElement('button')
      editItemBtn.className = 'icon-btn'
      setIcon(editItemBtn, 'lucide:edit-3', '\u7F16\u8F91\u8BE5\u5BFC\u822A')
      const defaultOpenForItems =
        (_a = g.defaultOpen) != null ? _a : siteDefaultOpenConst
      editItemBtn.addEventListener('click', (e) => {
        e.stopPropagation()
        openAddLinkModal(root, cfg, {
          saveConfig(c) {
            void saveConfig(c)
          },
          rerender(r, c) {
            rerender(r, c)
          },
          defaultOpen: defaultOpenForItems,
          defaultGroupId: g.id,
          existingItem: it,
        })
      })
      const hideBtn = document.createElement('button')
      hideBtn.className = 'icon-btn'
      if (it.hidden) {
        setIcon(hideBtn, 'lucide:eye', '\u663E\u793A\u8BE5\u5BFC\u822A')
      } else {
        setIcon(hideBtn, 'lucide:eye-off', '\u9690\u85CF\u8BE5\u5BFC\u822A')
      }
      hideBtn.addEventListener('click', (e) => {
        e.stopPropagation()
        it.hidden = !it.hidden
        void saveConfig(cfg)
        rerender(root, cfg)
      })
      wrap.append(editItemBtn)
      wrap.append(hideBtn)
    }
    return wrap
  }
  function renderGroupSection(root, cfg, g, body) {
    var _a
    const isEditing = editingGroups.has(g.id)
    const div = document.createElement('div')
    div.className = 'divider'
    body.append(div)
    const section = document.createElement('div')
    section.className = 'section'
    section.dataset.gid = g.id
    if (g.hidden) section.classList.add('is-hidden')
    const header = document.createElement('div')
    header.className = 'header'
    const title = document.createElement('div')
    title.className = 'title'
    setIcon(title, g.icon || 'lucide:folder')
    const nameSpan = document.createElement('span')
    nameSpan.textContent = g.displayName || g.name
    title.append(nameSpan)
    header.append(title)
    title.addEventListener('click', () => {
      g.collapsed = !g.collapsed
      void saveConfig(cfg)
      const itemsDiv = section.querySelector('.items')
      if (itemsDiv) itemsDiv.style.display = g.collapsed ? 'none' : ''
      const btn = section.querySelector('.header .icon-btn.toggle')
      if (btn instanceof HTMLElement)
        setIcon(
          btn,
          g.collapsed ? 'lucide:chevron-right' : 'lucide:chevron-down',
          g.collapsed ? '\u5C55\u5F00' : '\u6298\u53E0'
        )
    })
    const actions = document.createElement('div')
    actions.className = 'header-actions'
    const siteDefaultOpenConst = sitePref.defaultOpen
    const editMenuRightSide = isRightSide(sitePref.position)
    const groupMenuRightSide = editMenuRightSide
    if (isEditing) {
      const exitBtn = document.createElement('button')
      exitBtn.className = 'btn mini'
      exitBtn.textContent = '\u9000\u51FA\u7F16\u8F91'
      exitBtn.addEventListener('click', () => {
        editingGroups.delete(g.id)
        selectedItemsByGroup.delete(g.id)
        rerender(root, cfg)
      })
      const delBtn = document.createElement('button')
      delBtn.className = 'btn mini'
      delBtn.textContent = '\u5220\u9664'
      {
        const count =
          ((_a = selectedItemsByGroup.get(g.id)) == null ? void 0 : _a.size) ||
          0
        delBtn.disabled = !(count > 0)
      }
      delBtn.addEventListener('click', () => {
        const set = selectedItemsByGroup.get(g.id)
        if (!set || set.size === 0) return
        const ok = globalThis.confirm(
          '\u662F\u5426\u5220\u9664\u6240\u9009\u5BFC\u822A\u9879\uFF1F'
        )
        if (!ok) return
        const ids = new Set(Array.from(set))
        g.items = g.items.filter((x) => !ids.has(x.id))
        selectedItemsByGroup.delete(g.id)
        void saveConfig(cfg)
        rerender(root, cfg)
      })
      actions.append(exitBtn)
      actions.append(delBtn)
    } else {
      const addLinkBtn = document.createElement('button')
      addLinkBtn.className = 'icon-btn'
      setIcon(
        addLinkBtn,
        'lucide:plus',
        '\u6DFB\u52A0\u94FE\u63A5\u5230\u6B64\u5206\u7EC4'
      )
      addLinkBtn.addEventListener('click', (e) => {
        e.stopPropagation()
        showDropdownMenu(
          root,
          addLinkBtn,
          [
            {
              icon: 'lucide:keyboard',
              label: '\u624B\u52A8\u8F93\u5165',
              onClick() {
                var _a2
                openAddLinkModal(root, cfg, {
                  saveConfig(c) {
                    void saveConfig(c)
                  },
                  rerender(r, c) {
                    rerender(r, c)
                  },
                  defaultOpen:
                    (_a2 = g.defaultOpen) != null
                      ? _a2
                      : sitePref.defaultOpen || 'same-tab',
                  defaultGroupId: g.id,
                })
              },
            },
            {
              icon: 'lucide:globe',
              label: '\u6DFB\u52A0\u5F53\u524D\u7F51\u9875',
              onClick() {
                var _a2
                addCurrentPageLinkToGroup(
                  root,
                  cfg,
                  {
                    saveConfig(c) {
                      void saveConfig(c)
                    },
                    rerender(r, c) {
                      rerender(r, c)
                    },
                  },
                  g.id,
                  (_a2 = g.defaultOpen) != null
                    ? _a2
                    : sitePref.defaultOpen || 'same-tab'
                )
              },
            },
            {
              icon: 'lucide:link',
              label: '\u4ECE\u5F53\u524D\u7F51\u9875\u91C7\u96C6\u94FE\u63A5',
              onClick() {
                var _a2
                pickLinkFromPageAndAdd(
                  root,
                  cfg,
                  {
                    saveConfig(c) {
                      void saveConfig(c)
                    },
                    rerender(r, c) {
                      rerender(r, c)
                    },
                  },
                  g.id,
                  (_a2 = g.defaultOpen) != null
                    ? _a2
                    : sitePref.defaultOpen || 'same-tab'
                )
              },
            },
          ],
          groupMenuRightSide
        )
      })
      const hideGroupBtn = document.createElement('button')
      hideGroupBtn.className = 'icon-btn'
      setIcon(
        hideGroupBtn,
        g.hidden ? 'lucide:eye' : 'lucide:eye-off',
        g.hidden ? '\u663E\u793A\u5206\u7EC4' : '\u9690\u85CF\u5206\u7EC4'
      )
      hideGroupBtn.addEventListener('click', () => {
        g.hidden = !g.hidden
        void saveConfig(cfg)
        rerender(root, cfg)
      })
      const editBtn = document.createElement('button')
      editBtn.className = 'icon-btn'
      setIcon(editBtn, 'lucide:edit-3', '\u7F16\u8F91')
      editBtn.addEventListener('click', (ev) => {
        ev.stopPropagation()
        showDropdownMenu(
          root,
          editBtn,
          [
            {
              icon: 'lucide:edit-3',
              label: '\u7F16\u8F91\u5206\u7EC4',
              onClick() {
                openAddGroupModal(root, cfg, {
                  saveConfig(c) {
                    void saveConfig(c)
                  },
                  rerender(r, c) {
                    rerender(r, c)
                  },
                  defaultOpen: g.defaultOpen || siteDefaultOpenConst,
                  defaultMatch: g.match,
                  existingGroup: g,
                })
              },
            },
            {
              icon: 'lucide:list',
              label: '\u7F16\u8F91\u5BFC\u822A\u9879',
              onClick() {
                if (editingGroups.has(g.id)) editingGroups.delete(g.id)
                else editingGroups.add(g.id)
                rerender(root, cfg)
              },
            },
          ],
          editMenuRightSide
        )
      })
      const toggleBtn = document.createElement('button')
      toggleBtn.className = 'icon-btn toggle'
      setIcon(
        toggleBtn,
        g.collapsed ? 'lucide:chevron-right' : 'lucide:chevron-down',
        g.collapsed ? '\u5C55\u5F00' : '\u6298\u53E0'
      )
      toggleBtn.addEventListener('click', () => {
        g.collapsed = !g.collapsed
        void saveConfig(cfg)
        const itemsDiv = section.querySelector('.items')
        if (itemsDiv) itemsDiv.style.display = g.collapsed ? 'none' : ''
        setIcon(
          toggleBtn,
          g.collapsed ? 'lucide:chevron-right' : 'lucide:chevron-down',
          g.collapsed ? '\u5C55\u5F00' : '\u6298\u53E0'
        )
      })
      actions.append(addLinkBtn)
      actions.append(editBtn)
      actions.append(hideGroupBtn)
      actions.append(toggleBtn)
    }
    header.append(actions)
    section.append(header)
    const items = document.createElement('div')
    items.className = 'items'
    items.style.setProperty(
      '--cols',
      String(isEditing ? 1 : g.itemsPerRow || 1)
    )
    items.style.display = g.collapsed ? 'none' : ''
    let visibleCount = 0
    const defOpen = sitePref.defaultOpen || 'same-tab'
    for (const it of g.items) {
      if (it.hidden && !showHiddenItems && !isEditing) continue
      visibleCount++
      const wrap = renderNavItem(
        root,
        cfg,
        g,
        it,
        section,
        isEditing,
        siteDefaultOpenConst,
        defOpen
      )
      items.append(wrap)
    }
    items.style.setProperty(
      '--cols',
      String(
        isEditing
          ? 1
          : Math.max(1, Math.min(g.itemsPerRow || 1, visibleCount || 1))
      )
    )
    if (visibleCount === 0) {
      const msg = document.createElement('div')
      msg.className = 'empty-msg'
      msg.textContent =
        g.items.length === 0
          ? '\u65E0\u9879\u76EE'
          : '\u9879\u76EE\u5DF2\u88AB\u9690\u85CF'
      items.append(msg)
    }
    section.append(items)
    section.classList.add('fade-in')
    body.append(section)
  }
  function renderPanelHeader(root, cfg, panel) {
    const collapseRow = document.createElement('div')
    collapseRow.className = 'header'
    const leftActions = document.createElement('div')
    leftActions.className = 'panel-actions-left'
    const rightActions = document.createElement('div')
    rightActions.className = 'panel-actions'
    const closeBtn = document.createElement('button')
    closeBtn.className = 'collapse-btn'
    setIcon(closeBtn, 'lucide:x', '\u5173\u95ED')
    closeBtn.addEventListener('click', () => {
      collapseWithAnim(root, cfg)
    })
    const plusBtn = document.createElement('button')
    plusBtn.className = 'icon-btn'
    setIcon(plusBtn, 'lucide:plus', '\u6DFB\u52A0')
    plusBtn.addEventListener('click', (ev) => {
      ev.stopPropagation()
      openQuickAddMenu(root, cfg, plusBtn)
    })
    const showAllBtn = document.createElement('button')
    showAllBtn.className = 'icon-btn'
    setIcon(showAllBtn, 'lucide:layout-dashboard', '\u663E\u793A\u5168\u90E8')
    showAllBtn.classList.toggle('active', Boolean(showAllGroups))
    showAllBtn.addEventListener('click', () => {
      showAllGroups = !showAllGroups
      showAllBtn.classList.toggle('active', Boolean(showAllGroups))
      rerender(root, cfg)
    })
    const settingsBtn = document.createElement('button')
    settingsBtn.className = 'icon-btn'
    setIcon(settingsBtn, 'lucide:settings', '\u8BBE\u7F6E')
    settingsBtn.addEventListener('click', () => {
      openEditor(root, cfg)
    })
    const pinBtn = document.createElement('button')
    pinBtn.className = 'icon-btn'
    setIcon(
      pinBtn,
      sitePref.pinned ? 'lucide:pin' : 'lucide:pin-off',
      sitePref.pinned ? '\u53D6\u6D88\u56FA\u5B9A' : '\u56FA\u5B9A\u663E\u793A'
    )
    pinBtn.classList.toggle('active', Boolean(sitePref.pinned))
    pinBtn.addEventListener('click', () => {
      sitePref.pinned = !sitePref.pinned
      void saveConfig(cfg)
      pinBtn.classList.toggle('active', Boolean(sitePref.pinned))
      setIcon(
        pinBtn,
        sitePref.pinned ? 'lucide:pin' : 'lucide:pin-off',
        sitePref.pinned
          ? '\u53D6\u6D88\u56FA\u5B9A'
          : '\u56FA\u5B9A\u663E\u793A'
      )
    })
    rightActions.append(plusBtn)
    rightActions.append(showAllBtn)
    if (showAllGroups) {
      const showHiddenGroupsLabel = document.createElement('label')
      showHiddenGroupsLabel.className = 'check'
      const showHiddenGroupsCb = document.createElement('input')
      showHiddenGroupsCb.type = 'checkbox'
      showHiddenGroupsCb.checked = Boolean(showHiddenGroups)
      const showHiddenGroupsSpan = document.createElement('span')
      showHiddenGroupsSpan.textContent =
        '\u663E\u793A\u9690\u85CF\u7684\u5206\u7EC4'
      showHiddenGroupsLabel.append(showHiddenGroupsCb)
      showHiddenGroupsLabel.append(showHiddenGroupsSpan)
      showHiddenGroupsCb.addEventListener('change', () => {
        showHiddenGroups = Boolean(showHiddenGroupsCb.checked)
        rerender(root, cfg)
      })
      const showHiddenItemsLabel = document.createElement('label')
      showHiddenItemsLabel.className = 'check'
      const showHiddenItemsCb = document.createElement('input')
      showHiddenItemsCb.type = 'checkbox'
      showHiddenItemsCb.checked = Boolean(showHiddenItems)
      const showHiddenItemsSpan = document.createElement('span')
      showHiddenItemsSpan.textContent =
        '\u663E\u793A\u9690\u85CF\u7684\u5BFC\u822A'
      showHiddenItemsLabel.append(showHiddenItemsCb)
      showHiddenItemsLabel.append(showHiddenItemsSpan)
      showHiddenItemsCb.addEventListener('change', () => {
        showHiddenItems = Boolean(showHiddenItemsCb.checked)
        rerender(root, cfg)
      })
      const expandAllBtn = document.createElement('button')
      expandAllBtn.className = 'btn mini'
      expandAllBtn.textContent = '\u5C55\u5F00\u6240\u6709\u5206\u7EC4'
      expandAllBtn.addEventListener('click', () => {
        preserveScroll(panel, () => {
          for (const g of cfg.groups) g.collapsed = false
          void saveConfig(cfg)
          for (const sec of Array.from(panel.querySelectorAll('.section'))) {
            const itemsDiv = sec.querySelector('.items')
            if (itemsDiv) itemsDiv.style.display = ''
            const gid = sec.dataset.gid
            const grp = cfg.groups.find((x) => x.id === gid)
            const btn = sec.querySelector('.header .icon-btn:nth-last-child(1)')
            if (grp && btn) setIcon(btn, 'lucide:chevron-down', '\u6298\u53E0')
          }
        })
      })
      const collapseAllBtn = document.createElement('button')
      collapseAllBtn.className = 'btn mini'
      collapseAllBtn.textContent = '\u6298\u53E0\u6240\u6709\u5206\u7EC4'
      collapseAllBtn.addEventListener('click', () => {
        preserveScroll(panel, () => {
          for (const g of cfg.groups) g.collapsed = true
          void saveConfig(cfg)
          for (const sec of Array.from(panel.querySelectorAll('.section'))) {
            const itemsDiv = sec.querySelector('.items')
            if (itemsDiv) itemsDiv.style.display = 'none'
            const gid = sec.dataset.gid
            const grp = cfg.groups.find((x) => x.id === gid)
            const btn = sec.querySelector('.header .icon-btn:nth-last-child(1)')
            if (grp && btn) setIcon(btn, 'lucide:chevron-right', '\u5C55\u5F00')
          }
        })
      })
      rightActions.append(showHiddenGroupsLabel)
      rightActions.append(showHiddenItemsLabel)
      rightActions.append(expandAllBtn)
      rightActions.append(collapseAllBtn)
    }
    rightActions.append(settingsBtn)
    rightActions.append(pinBtn)
    rightActions.append(closeBtn)
    collapseRow.append(leftActions)
    collapseRow.append(rightActions)
    panel.append(collapseRow)
    let body = panel
    if (showAllGroups) {
      panel.classList.add('all-mode')
      const scroller = document.createElement('div')
      scroller.className = 'panel-scroll'
      const columns = document.createElement('div')
      columns.className = 'panel-columns'
      scroller.append(columns)
      panel.append(scroller)
      body = columns
    } else {
      panel.classList.remove('all-mode')
    }
    return body
  }
  function renderPanel(root, cfg, animIn) {
    const wrapper = document.createElement('div')
    wrapper.className = 'utqn' + (isDarkTheme(cfg) ? ' dark' : '')
    const panel = document.createElement('div')
    panel.className = 'panel'
    const pos = sitePref.position
    const isRight = isRightSide(pos)
    const isHoriz = isHorizontalPos(pos)
    const isTop = isTopSide(pos)
    if (animIn)
      panel.classList.add(
        isHoriz
          ? isTop
            ? 'anim-in-top'
            : 'anim-in-bottom'
          : isRight
            ? 'anim-in-right'
            : 'anim-in-left'
      )
    const body = renderPanelHeader(root, cfg, panel)
    const groupsToShow = currentGroups(cfg)
    for (const g of groupsToShow) renderGroupSection(root, cfg, g, body)
    wrapper.append(panel)
    wrapper.addEventListener('mouseenter', () => {
      try {
        if (collapseTimer) clearTimeout(collapseTimer)
      } catch (e) {}
    })
    wrapper.addEventListener('mouseleave', () => {
      if (!sitePref.pinned && !suppressCollapse) scheduleAutoCollapse(root, cfg)
    })
    place(wrapper, cfg)
    root.append(wrapper)
  }
  function openEditor(root, cfg) {
    openEditorModal(root, cfg, {
      saveConfig(c) {
        void saveConfig(c)
      },
      rerender(r, c) {
        rerender(r, c)
      },
      sitePref,
      updateThemeUI,
      edgeDefaults: {
        width: EDGE_DEFAULT_WIDTH,
        height: EDGE_DEFAULT_HEIGHT,
        opacity: EDGE_DEFAULT_OPACITY,
        colorLight: EDGE_DEFAULT_COLOR_LIGHT,
        colorDark: EDGE_DEFAULT_COLOR_DARK,
      },
      tempOpenGetter: () => tempOpen,
    })
  }
  function openQuickAddMenu(root, cfg, anchor) {
    suppressCollapse = true
    tempOpen = true
    const rightSide = isRightSide(sitePref.position)
    showDropdownMenu(
      root,
      anchor,
      [
        {
          icon: 'lucide:folder',
          label: '\u6DFB\u52A0\u5206\u7EC4',
          onClick() {
            suppressCollapse = false
            openAddGroupModal(root, cfg, {
              saveConfig(c) {
                void saveConfig(c)
              },
              rerender(r, c) {
                rerender(r, c)
              },
              defaultOpen: sitePref.defaultOpen,
              defaultMatch: ['*://' + (location.hostname || '') + '/*'],
            })
          },
        },
        {
          icon: 'lucide:link',
          label: '\u6DFB\u52A0\u94FE\u63A5',
          onClick() {
            var _a
            suppressCollapse = false
            const matched = currentGroups(cfg)
            openAddLinkModal(root, cfg, {
              saveConfig(c) {
                void saveConfig(c)
              },
              rerender(r, c) {
                rerender(r, c)
              },
              defaultOpen: sitePref.defaultOpen || 'same-tab',
              defaultGroupId:
                (_a = matched[0] || cfg.groups[0]) == null ? void 0 : _a.id,
            })
          },
        },
      ],
      rightSide
    )
  }
  var lastCollapsed = true
  var suppressCollapse = false
  function rerender(root, cfg) {
    var _a, _b, _c
    suppressCollapse = true
    let sx = 0
    let sy = 0
    try {
      const cur =
        root.querySelector('.utqn .panel-scroll') ||
        root.querySelector('.utqn .panel')
      if (cur) {
        sx = cur.scrollLeft
        sy = cur.scrollTop
      }
    } catch (e) {}
    for (const n of Array.from(
      root.querySelectorAll('.utqn,.collapsed-tab,.quick-add-menu')
    ))
      n.remove()
    if (sitePref.enabled === false) {
      lastCollapsed = true
      suppressCollapse = false
      return
    }
    const isCollapsed = !tempOpen && (tempClosed || !sitePref.pinned)
    if (isCollapsed) {
      if (!sitePref.edgeHidden) {
        const tab = document.createElement('div')
        tab.className = 'collapsed-tab'
        place(tab, cfg)
        try {
          const gw = (_a = sitePref.edgeWidth) != null ? _a : EDGE_DEFAULT_WIDTH
          const gh =
            (_b = sitePref.edgeHeight) != null ? _b : EDGE_DEFAULT_HEIGHT
          const go =
            (_c = sitePref.edgeOpacity) != null ? _c : EDGE_DEFAULT_OPACITY
          const horiz = isHorizontalPos(sitePref.position)
          const thickness = Math.max(1, Math.min(24, gw))
          const length = Math.max(24, Math.min(320, gh))
          tab.style.width = horiz
            ? ''.concat(length, 'px')
            : ''.concat(thickness, 'px')
          tab.style.height = horiz
            ? ''.concat(thickness, 'px')
            : ''.concat(length, 'px')
          tab.style.opacity = String(Math.max(0, Math.min(1, go)))
          tab.style.backgroundColor = isDarkTheme(cfg)
            ? String(sitePref.edgeColorDark || EDGE_DEFAULT_COLOR_DARK)
            : String(sitePref.edgeColorLight || EDGE_DEFAULT_COLOR_LIGHT)
        } catch (e) {}
        tab.addEventListener('mouseenter', () => {
          tempOpen = true
          rerender(root, cfg)
        })
        tab.addEventListener('mouseleave', () => {
          if (!sitePref.pinned && !suppressCollapse)
            scheduleAutoCollapse(root, cfg)
        })
        root.append(tab)
      }
      lastCollapsed = true
      suppressCollapse = false
      return
    }
    renderPanel(root, cfg, lastCollapsed)
    try {
      const cur =
        root.querySelector('.utqn .panel-scroll') ||
        root.querySelector('.utqn .panel')
      if (cur) {
        cur.scrollLeft = sx
        cur.scrollTop = sy
        try {
          requestAnimationFrame(() => {
            cur.scrollLeft = sx
            cur.scrollTop = sy
          })
        } catch (e) {}
      }
    } catch (e) {}
    lastCollapsed = false
    suppressCollapse = false
  }
  function registerMenu(root, cfg) {
    if (typeof GM_registerMenuCommand !== 'function') {
      return
    }
    if (
      typeof GM_unregisterMenuCommand === 'function' &&
      Array.isArray(menuIds)
    ) {
      for (const id of menuIds) {
        try {
          GM_unregisterMenuCommand(id)
        } catch (e) {}
      }
      menuIds = []
    }
    try {
      const text = sitePref.enabled
        ? '\u{1F6AB} \u7981\u7528\u5F53\u524D\u7F51\u7AD9\u5FEB\u901F\u5BFC\u822A'
        : '\u2705 \u542F\u7528\u5F53\u524D\u7F51\u7AD9\u5FEB\u901F\u5BFC\u822A'
      menuIds.push(
        GM_registerMenuCommand(
          '\u{1F9ED} \u6253\u5F00\u5FEB\u901F\u5BFC\u822A\u9762\u677F',
          () => {
            if (sitePref.enabled === false) {
              const ok = globalThis.confirm(
                '\u5F53\u524D\u7F51\u7AD9\u5DF2\u7981\u7528\uFF0C\u662F\u5426\u542F\u7528\u5E76\u6253\u5F00\u9762\u677F\uFF1F'
              )
              if (ok) {
                sitePref.enabled = true
                void saveConfig(cfg)
                tempOpen = true
                rerender(root, cfg)
                registerMenu(root, cfg)
              }
              return
            }
            tempOpen = true
            rerender(root, cfg)
          }
        ),
        GM_registerMenuCommand(
          '\u2699\uFE0F \u8BBE\u7F6E\u5FEB\u901F\u5BFC\u822A',
          () => {
            openEditor(root, cfg)
          }
        ),
        GM_registerMenuCommand(text, () => {
          sitePref.enabled = !sitePref.enabled
          void saveConfig(cfg)
          rerender(root, cfg)
          registerMenu(root, cfg)
        })
      )
    } catch (e) {}
  }
  function registerStorageListener(root, cfg) {
    try {
      if (typeof GM_addValueChangeListener === 'function')
        GM_addValueChangeListener(KEY, (_name, _old, nv, remote) => {
          if (!remote) return
          try {
            const obj = JSON.parse(nv)
            if (obj && obj.global && obj.groups) {
              cfg.global = obj.global
              cfg.groups = obj.groups
              if (obj.sitePrefs) cfg.sitePrefs = obj.sitePrefs
              initSitePref(cfg)
              rerender(root, cfg)
            }
          } catch (e) {}
        })
    } catch (e) {}
  }
  var collapseTimer
  function scheduleAutoCollapse(root, cfg) {
    if (collapseTimer) clearTimeout(collapseTimer)
    collapseTimer = setTimeout(() => {
      collapseWithAnim(root, cfg)
    }, 500)
  }
  function collapseWithAnim(root, cfg) {
    try {
      const p = sitePref.position
      const sel = root.querySelector('.utqn .panel')
      if (sel) {
        if (isHorizontalPos(p)) {
          const isTop = isTopSide(p)
          sel.classList.add(isTop ? 'anim-out-top' : 'anim-out-bottom')
        } else {
          const right = isRightSide(p)
          sel.classList.add(right ? 'anim-out-right' : 'anim-out-left')
        }
        sel.addEventListener(
          'animationend',
          () => {
            tempClosed = true
            tempOpen = false
            rerender(root, cfg)
          },
          { once: true }
        )
        return
      }
    } catch (e) {}
    tempOpen = false
    rerender(root, cfg)
  }
  function updateThemeUI(root, cfg) {
    const wrapper = root.querySelector('.utqn')
    if (!wrapper) return
    wrapper.classList.toggle('dark', isDarkTheme(cfg))
    const curTheme = sitePref.theme || THEME_DEFAULT
    const map = {
      系统: 'system',
      浅色: 'light',
      深色: 'dark',
    }
    const btns = wrapper.querySelectorAll('.theme-btn')
    for (const b of Array.from(btns)) {
      const key = b.title
      const val = map[key] || ''
      b.classList.toggle('active', val === curTheme)
    }
  }
  function registerUrlChangeListener(root, cfg) {
    let last = location.href
    function onChange() {
      const now = location.href
      if (now === last) return
      last = now
      rerender(root, cfg)
    }
    try {
      const origPush = history.pushState.bind(history)
      history.pushState = function (...args) {
        const r = origPush(...args)
        try {
          onChange()
        } catch (e) {}
        return r
      }
    } catch (e) {}
    try {
      const origReplace = history.replaceState.bind(history)
      history.replaceState = function (...args) {
        const r = origReplace(...args)
        try {
          onChange()
        } catch (e) {}
        return r
      }
    } catch (e) {}
    globalThis.addEventListener('popstate', () => {
      onChange()
    })
    globalThis.addEventListener('hashchange', () => {
      onChange()
    })
  }
  function main() {
    try {
      const de = document.documentElement
      if (de && de.dataset && de.dataset.utqn === '1') return
      if (de && de.dataset) de.dataset.utqn = '1'
    } catch (e) {}
    const { root } = createRoot()
    void (async () => {
      const cfg = await loadConfig()
      initSitePref(cfg)
      if (sitePref.enabled === false) {
        registerMenu(root, cfg)
        registerStorageListener(root, cfg)
        registerUrlChangeListener(root, cfg)
        return
      }
      rerender(root, cfg)
      registerHotkeys(root, cfg)
      registerMenu(root, cfg)
      registerStorageListener(root, cfg)
      registerUrlChangeListener(root, cfg)
      try {
        const mq = globalThis.matchMedia('(prefers-color-scheme: dark)')
        mq.addEventListener('change', () => {
          if ((sitePref.theme || 'system') === 'system') rerender(root, cfg)
        })
      } catch (e) {}
      try {
        document.addEventListener('visibilitychange', () => {
          if (document.visibilityState === 'visible') rerender(root, cfg)
        })
      } catch (e) {}
    })()
  }
  main()
})()