Displays resolution and file size on Google Images & Google Lens thumbnails. Features an interactive preview lightbox with EXIF/C2PA metadata, RGB color analysis, image downloading/transcoding, online editor integration, and reverse image search.
// ==UserScript== // @name Google Images Advanced Toolbox // @name:zh-TW Google 圖片進階工具箱 // @name:ja Google 画像アドバンストツールボックス // @namespace https://greasyfork.org/en/users/1467948-stonedkhajiit // @version 0.2.3 // @author StonedKhajiit // @description Displays resolution and file size on Google Images & Google Lens thumbnails. Features an interactive preview lightbox with EXIF/C2PA metadata, RGB color analysis, image downloading/transcoding, online editor integration, and reverse image search. // @description:zh-TW 在 Google 圖片與 Google Lens 搜尋結果標示解析度與檔案容量。提供支援縮放與切換的預覽燈箱,整合 EXIF/C2PA 鑑識、色彩分析、圖片下載轉檔、線上修圖與反向搜圖功能。 // @description:ja Google 画像検索および Google レンズのサムネイルに解像度と容量を表示。プレビューライトボックス、EXIF/C2PA 解析、色彩分析、原画保存・フォーマット変換、画像編集・逆画像検索連携を搭載。 // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com // @match https://www.google.com/search* // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/exif-reader.min.js#sha256-xhYNFXSScxwbyqxhjos5OJZSS1+i9lPDv62/Ht5qExk= // @connect * // @grant GM.deleteValue // @grant GM.getValue // @grant GM.registerMenuCommand // @grant GM.setValue // @grant GM.xmlHttpRequest // @grant GM_addStyle // @grant GM_addValueChangeListener // @grant GM_deleteValue // @grant GM_download // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_removeValueChangeListener // @grant GM_setClipboard // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant unsafeWindow // ==/UserScript== /** * Google Images Advanced Toolbox (GIAT) * Copyright (C) 2026 StonedKhajiit * Licensed under the MIT License (MIT). * * --- PORTION OF THIS WORK IS DERIVED FROM THIRD-PARTY SOFTWARE --- * * 1. Image Max URL (IMU) rules: * Many of the image optimization and bypass rules utilized in this userscript * are derived from the Image Max URL (IMU) project. * Repository: https://github.com/qsniyg/maxurl * Copyright (C) 2018-2024 qsniyg * Licensed under the Apache License, Version 2.0 (the "Apache License"). * You may obtain a copy of the Apache License at: http://www.apache.org/licenses/LICENSE-2.0 * * 2. Google Image Search - Show Image Dimensions (Early Inspiration): * The early inspiration, core layout concept, and dimensions features of this * project are inspired by "Google Image Search - Show Image Dimensions". * Repository: https://github.com/tadwohlrapp/google-image-search-show-image-dimensions-userscript * Copyright (c) 2021-2024 Tad Wohlrapp * Licensed under the MIT License. * * The MIT License (MIT) * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software. */ (function(exifreader) { "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); exifreader = __toESM(exifreader); var s = new Set(); var _css = async (t) => { if (s.has(t)) return; s.add(t); ((c) => { if (typeof GM_addStyle === "function") GM_addStyle(c); else (document.head || document.documentElement).appendChild(document.createElement("style")).append(c); })(t); }; _css(":root{--giat-label-font-size:10px;--giat-label-line-height:12px;--giat-label-padding:4px;--giat-label-radius-inner:8px;--giat-label-radius-outer:12px;--giat-label-svg-size:12px;--giat-thumb-btn-size:28px;--giat-thumb-btn-svg-size:14px}body.giat-size-1{--giat-label-font-size:8px;--giat-label-line-height:10px;--giat-label-padding:2px 4px;--giat-label-radius-inner:6px;--giat-label-radius-outer:8px;--giat-label-svg-size:10px}body.giat-size-2{--giat-label-font-size:8.5px;--giat-label-line-height:10.5px;--giat-label-padding:2.3px 4.3px;--giat-label-radius-inner:6.3px;--giat-label-radius-outer:9px;--giat-label-svg-size:10.5px}body.giat-size-3{--giat-label-font-size:9px;--giat-label-line-height:11px;--giat-label-padding:2.7px 4.7px;--giat-label-radius-inner:6.7px;--giat-label-radius-outer:10px;--giat-label-svg-size:11px}body.giat-size-4{--giat-label-font-size:9.5px;--giat-label-line-height:11.5px;--giat-label-padding:3px 5px;--giat-label-radius-inner:7px;--giat-label-radius-outer:11px;--giat-label-svg-size:11.5px}body.giat-size-5{--giat-label-font-size:10px;--giat-label-line-height:12px;--giat-label-padding:4px;--giat-label-radius-inner:8px;--giat-label-radius-outer:12px;--giat-label-svg-size:12px}body.giat-size-6{--giat-label-font-size:10.5px;--giat-label-line-height:12.5px;--giat-label-padding:4.3px 5px;--giat-label-radius-inner:8.3px;--giat-label-radius-outer:13px;--giat-label-svg-size:12.5px}body.giat-size-7{--giat-label-font-size:11px;--giat-label-line-height:13px;--giat-label-padding:4.7px 6.5px;--giat-label-radius-inner:8.7px;--giat-label-radius-outer:14px;--giat-label-svg-size:13px}body.giat-size-8{--giat-label-font-size:11.5px;--giat-label-line-height:13.5px;--giat-label-padding:5px 7px;--giat-label-radius-inner:9px;--giat-label-radius-outer:15px;--giat-label-svg-size:13.5px}body.giat-size-9{--giat-label-font-size:12px;--giat-label-line-height:14px;--giat-label-padding:6px 8px;--giat-label-radius-inner:10px;--giat-label-radius-outer:16px;--giat-label-svg-size:14px}body.giat-size-10{--giat-label-font-size:12.5px;--giat-label-line-height:14.5px;--giat-label-padding:6.3px 8.5px;--giat-label-radius-inner:10.5px;--giat-label-radius-outer:16.5px;--giat-label-svg-size:14.5px}body.giat-size-11{--giat-label-font-size:13px;--giat-label-line-height:15px;--giat-label-padding:6.7px 9px;--giat-label-radius-inner:11px;--giat-label-radius-outer:17px;--giat-label-svg-size:15px}body.giat-size-12{--giat-label-font-size:14px;--giat-label-line-height:16px;--giat-label-padding:7px 10px;--giat-label-radius-inner:12px;--giat-label-radius-outer:18px;--giat-label-svg-size:16px}body.giat-thumb-btn-size-1{--giat-thumb-btn-size:20px;--giat-thumb-btn-svg-size:10px}body.giat-thumb-btn-size-2{--giat-thumb-btn-size:21.8px;--giat-thumb-btn-svg-size:10.9px}body.giat-thumb-btn-size-3{--giat-thumb-btn-size:23.6px;--giat-thumb-btn-svg-size:11.8px}body.giat-thumb-btn-size-4{--giat-thumb-btn-size:25.4px;--giat-thumb-btn-svg-size:12.7px}body.giat-thumb-btn-size-5{--giat-thumb-btn-size:27.2px;--giat-thumb-btn-svg-size:13.6px}body.giat-thumb-btn-size-6{--giat-thumb-btn-size:29px;--giat-thumb-btn-svg-size:14.5px}body.giat-thumb-btn-size-7{--giat-thumb-btn-size:30.8px;--giat-thumb-btn-svg-size:15.4px}body.giat-thumb-btn-size-8{--giat-thumb-btn-size:32.6px;--giat-thumb-btn-svg-size:16.3px}body.giat-thumb-btn-size-9{--giat-thumb-btn-size:34.4px;--giat-thumb-btn-svg-size:17.2px}body.giat-thumb-btn-size-10{--giat-thumb-btn-size:36.2px;--giat-thumb-btn-svg-size:18.1px}body.giat-thumb-btn-size-11{--giat-thumb-btn-size:38px;--giat-thumb-btn-svg-size:19px}body.giat-thumb-btn-size-12{--giat-thumb-btn-size:40px;--giat-thumb-btn-svg-size:20px}.giat-dims{background-color:var(--giat-custom-bg,#0009);opacity:var(--giat-dims-initial-opacity,1);z-index:2;white-space:nowrap;box-sizing:border-box;text-overflow:ellipsis;align-items:center;gap:3px;max-width:100%;margin:0;font-family:Roboto-Medium,Roboto,Arial,sans-serif;transition:opacity .2s ease-in-out,transform .1s,background-color .1s;display:flex;position:absolute;overflow:hidden;padding:var(--giat-label-padding)!important;color:var(--giat-custom-color,#f1f3f4)!important;font-size:var(--giat-label-font-size)!important;line-height:var(--giat-label-line-height)!important;text-decoration:none!important}.giat-dims svg{flex-shrink:0;fill:var(--giat-custom-color,#f1f3f4)!important;height:var(--giat-label-svg-size)!important;width:var(--giat-label-svg-size)!important;opacity:.4!important;pointer-events:none!important;transition:opacity .2s ease-in-out!important}.giat-dims:hover{background-color:var(--giat-custom-bg,#0009)!important;filter:saturate(1.4)contrast(1.15)brightness(1.05)!important}@supports (background-color:rgb(from white r g b)){.giat-dims:hover{background-color:rgb(from var(--giat-custom-bg,#0009) r g b / calc(alpha * 1.35))!important}}.giat-dims:hover svg,div:hover>.giat-dims,a:hover>.giat-dims,[data-giat-result]:hover .giat-dims{opacity:1!important}.giat-thumb-download-btn,.giat-thumb-copy-btn,.giat-thumb-b64-btn,.giat-thumb-lens-btn,.giat-thumb-tineye-btn,.giat-thumb-ai-btn,.giat-thumb-photopea-btn,.giat-thumb-vectorpea-btn,.giat-thumb-yandex-btn,.giat-thumb-bing-btn{width:var(--giat-thumb-btn-size);height:var(--giat-thumb-btn-size);background:var(--giat-custom-bg,#00000080);-webkit-backdrop-filter:blur(6px);cursor:pointer;box-sizing:border-box;border:none;border-radius:50%;outline:none;flex-shrink:0;justify-content:center;align-items:center;overflow:hidden;transform:translate(0,0);box-shadow:inset 0 0 0 1px #ffffff26;transition:background-color .2s,box-shadow .2s,transform .1s cubic-bezier(.175,.885,.32,1.275)!important}.giat-thumb-download-btn{display:var(--giat-thumb-download-display,flex)}.giat-thumb-copy-btn{display:var(--giat-thumb-copy-display,flex)}.giat-thumb-b64-btn{display:var(--giat-thumb-b64-display,flex)}.giat-thumb-lens-btn{display:var(--giat-thumb-lens-display,flex)}.giat-thumb-tineye-btn{display:var(--giat-thumb-tineye-display,flex)}.giat-thumb-ai-btn{display:var(--giat-thumb-ai-display,flex)}.giat-thumb-photopea-btn{display:var(--giat-thumb-photopea-display,flex)}.giat-thumb-vectorpea-btn{display:var(--giat-thumb-vectorpea-display,flex)}.giat-thumb-yandex-btn{display:var(--giat-thumb-yandex-display,flex)}.giat-thumb-bing-btn{display:var(--giat-thumb-bing-display,flex)}.giat-thumb-btn-container{z-index:3;opacity:0;pointer-events:none;align-items:center;gap:6px;transition:opacity .2s ease-in-out;display:flex;position:absolute}.giat-thumb-btn-container button{pointer-events:auto}[data-giat-result]:hover .giat-thumb-btn-container{opacity:1}body.giat-pos-br .giat-dims,body:not([class*=giat-pos-]) .giat-dims{border-radius:var(--giat-label-radius-outer) 0 0 0!important;inset:auto 0 0 auto!important}body.giat-pos-br .giat-thumb-btn-container,body:not([class*=giat-pos-]) .giat-thumb-btn-container{flex-direction:row!important;inset:6px auto auto 6px!important}body.giat-pos-bl .giat-dims{border-radius:0 var(--giat-label-radius-outer) 0 0!important;inset:auto auto 0 0!important}body.giat-pos-bl .giat-thumb-btn-container{flex-direction:row-reverse!important;inset:6px 6px auto auto!important}body.giat-pos-tr .giat-dims{border-radius:0 0 0 var(--giat-label-radius-outer)!important;inset:0 0 auto auto!important}body.giat-pos-tr .giat-thumb-btn-container{flex-direction:row!important;inset:auto auto 6px 6px!important}body.giat-pos-tl .giat-dims{border-radius:0 0 var(--giat-label-radius-outer) 0!important;inset:0 auto auto 0!important}body.giat-pos-tl .giat-thumb-btn-container{flex-direction:row-reverse!important;inset:auto 6px 6px auto!important}.giat-thumb-download-btn:hover,.giat-thumb-copy-btn:hover,.giat-thumb-b64-btn:hover,.giat-thumb-lens-btn:hover,.giat-thumb-tineye-btn:hover,.giat-thumb-ai-btn:hover,.giat-thumb-photopea-btn:hover,.giat-thumb-vectorpea-btn:hover,.giat-thumb-yandex-btn:hover,.giat-thumb-bing-btn:hover{background-color:var(--giat-custom-bg,#00000080)!important;box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--giat-custom-color,#fff) 30%, transparent), 0 4px 12px #00000026!important;filter:saturate(1.4)contrast(1.15)brightness(1.05)!important;transform:scale(1.08)translateY(-1px)!important}@supports (background-color:rgb(from white r g b)){.giat-thumb-download-btn:hover,.giat-thumb-copy-btn:hover,.giat-thumb-b64-btn:hover,.giat-thumb-lens-btn:hover,.giat-thumb-tineye-btn:hover,.giat-thumb-ai-btn:hover,.giat-thumb-photopea-btn:hover,.giat-thumb-vectorpea-btn:hover,.giat-thumb-yandex-btn:hover,.giat-thumb-bing-btn:hover{background-color:rgb(from var(--giat-custom-bg,#00000080) r g b / calc(alpha * 1.35))!important}}.giat-thumb-download-btn:active,.giat-thumb-copy-btn:active,.giat-thumb-b64-btn:active,.giat-thumb-lens-btn:active,.giat-thumb-tineye-btn:active,.giat-thumb-ai-btn:active,.giat-thumb-photopea-btn:active,.giat-thumb-vectorpea-btn:active,.giat-thumb-yandex-btn:active,.giat-thumb-bing-btn:active{background-color:var(--giat-custom-bg,#00000080)!important;box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--giat-custom-color,#fff) 40%, transparent), 0 1px 4px #0000001a!important;filter:saturate(1.5)contrast(1.2)brightness(.85)!important;transition:transform 50ms!important;transform:scale(.9)translateY(0)!important}@supports (background-color:rgb(from white r g b)){.giat-thumb-download-btn:active,.giat-thumb-copy-btn:active,.giat-thumb-b64-btn:active,.giat-thumb-lens-btn:active,.giat-thumb-tineye-btn:active,.giat-thumb-ai-btn:active,.giat-thumb-photopea-btn:active,.giat-thumb-vectorpea-btn:active,.giat-thumb-yandex-btn:active,.giat-thumb-bing-btn:active{background-color:rgb(from var(--giat-custom-bg,#00000080) r g b / calc(alpha * 1.6))!important}}.giat-dims span{pointer-events:none!important}.giat-dims:active,.giat-dims.giat-active{background-color:var(--giat-custom-bg,#0009)!important;filter:saturate(1.5)contrast(1.2)brightness(.85)!important;transition:transform 80ms cubic-bezier(.16,1,.3,1)!important;transform:scale(.94)!important}@supports (background-color:rgb(from white r g b)){.giat-dims:active,.giat-dims.giat-active{background-color:rgb(from var(--giat-custom-bg,#0009) r g b / calc(alpha * 1.6))!important}}.giat-thumb-download-btn svg,.giat-thumb-copy-btn svg,.giat-thumb-b64-btn svg,.giat-thumb-lens-btn svg,.giat-thumb-tineye-btn svg,.giat-thumb-ai-btn svg,.giat-thumb-photopea-btn svg,.giat-thumb-vectorpea-btn svg,.giat-thumb-yandex-btn svg,.giat-thumb-bing-btn svg{fill:var(--giat-custom-color,#fff)!important;width:var(--giat-thumb-btn-svg-size)!important;height:var(--giat-thumb-btn-svg-size)!important;pointer-events:none!important}body.giat-no-scroll{scrollbar-gutter:stable;overflow:hidden!important}.giat-backdrop{z-index:1010;pointer-events:none;opacity:0;background-color:#0000;justify-content:center;align-items:center;transition:opacity .2s ease-in-out,background-color .2s ease-in-out;display:flex;position:fixed;inset:0}.giat-backdrop.show{pointer-events:all;opacity:1;background-color:#0009}.giat-wrap{z-index:1011;width:min(calc(var(--img-w) * 1px), 95vw, calc(95vh * var(--img-w) / var(--img-h)));height:min(calc(var(--img-h) * 1px), 95vh, calc(95vw * var(--img-h) / var(--img-w)));transform-origin:50%;will-change:transform;opacity:0;pointer-events:none;background-color:#242424;transition:opacity .2s ease-in-out;position:relative;overflow:visible;box-shadow:0 0 100px 20px #000000a8}.giat-wrap.show{opacity:1;pointer-events:all}.giat-wrap.error{min-width:320px;min-height:220px}.giat-wrap img:not(.giat-thumb-blur),.giat-wrap video.giat-lightbox-video{z-index:1012;opacity:0;transform-origin:50%;will-change:transform, opacity;object-fit:contain;outline:none;width:100%;height:100%;transition:opacity .3s ease-in-out;display:block;position:relative}.giat-wrap.error img:not(.giat-thumb-blur),.giat-wrap.error video.giat-lightbox-video{display:none!important}.giat-thumb-blur{z-index:1011;filter:blur(8px);opacity:0;pointer-events:none;width:100%;height:100%;transition:opacity .3s ease-in-out;position:absolute;top:0;left:0}.giat-shimmer{z-index:1013;opacity:0;visibility:hidden;pointer-events:none;background:0 0;width:100%;height:100%;transition:opacity .3s ease-in-out,visibility .3s ease-in-out;position:absolute;top:0;left:0;overflow:hidden}.giat-shimmer:after{content:\"\";background:linear-gradient(90deg,#0000,#ffffff14,#0000);width:100%;height:100%;position:absolute;top:0;left:0;transform:translate(-100%)}.giat-wrap.loading .giat-shimmer{opacity:1;visibility:visible;transition:none}.giat-wrap.loading .giat-shimmer:after{animation:1.5s ease-in-out infinite giat-shimmer-sweep-transform}@keyframes giat-shimmer-sweep-transform{0%{transform:translate(-100%)}to{transform:translate(100%)}}.giat-error-box{box-sizing:border-box;z-index:1014;cursor:default;justify-content:center;align-items:center;width:100%;height:100%;padding:24px;display:none;position:absolute;top:0;left:0;background-color:#1a1a1a!important}.giat-wrap.error .giat-error-box{display:flex}.giat-error-content{text-align:center;flex-direction:column;align-items:center;max-width:400px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Arial,sans-serif;display:flex}.giat-error-content svg{margin-bottom:12px}.giat-error-title{margin-bottom:6px;font-size:16px;font-weight:600;color:#f1f3f4!important}.giat-error-desc{margin-bottom:16px;font-size:12px;line-height:1.5;color:#9aa0a6!important}.giat-error-link{border-radius:20px;align-items:center;padding:6px 16px;font-size:13px;font-weight:500;text-decoration:none;transition:background-color .2s,border-color .2s,color .2s;display:inline-flex;color:#8ab4f8!important;background-color:#0003!important;border:1px solid #3c4043!important}.giat-error-link:hover{background-color:#8ab4f814!important;border-color:#8ab4f8!important}.giat-lightbox-btn-container{z-index:1015;opacity:0;flex-direction:row-reverse;gap:8px;transition:opacity .35s cubic-bezier(.16,1,.3,1),transform .35s cubic-bezier(.16,1,.3,1);display:flex;position:absolute;bottom:24px;right:24px;transform:translateY(16px)}.giat-wrap.show:not(.loading):not(.error)~.giat-lightbox-btn-container{opacity:1;transform:translateY(0)}.giat-download-btn,.giat-copy-img-btn,.giat-copy-b64-btn,.giat-lens-btn,.giat-tineye-btn,.giat-ai-btn,.giat-photopea-btn,.giat-vectorpea-btn,.giat-yandex-btn,.giat-bing-btn{-webkit-backdrop-filter:blur(8px);opacity:.85;cursor:pointer;box-sizing:border-box;background:#00000080;border:1px solid #ffffff26;border-radius:50%;outline:none;justify-content:center;align-items:center;width:44px;height:44px;display:flex;color:#fff!important;transition:opacity .25s ease-in-out,background-color .2s,border-color .2s,transform .1s cubic-bezier(.175,.885,.32,1.275)!important}.giat-wrap.loading~.giat-lightbox-btn-container,.giat-wrap.error~.giat-lightbox-btn-container,.giat-wrap.loading~.giat-type-badge,.giat-wrap.error~.giat-type-badge{display:none!important}.giat-backdrop.giat-yt-playing .giat-lightbox-btn-container,.giat-backdrop.giat-yt-playing .giat-type-badge{opacity:0!important;visibility:hidden!important;pointer-events:none!important;display:none!important}.giat-backdrop.giat-video-mode .giat-lightbox-btn-container button:not(.giat-download-btn){display:none!important}.giat-backdrop.giat-video-mode .giat-lightbox-btn-container{opacity:1!important;visibility:visible!important;display:flex!important;transform:translateY(0)!important}.giat-backdrop.giat-video-mode .giat-type-badge{opacity:.85!important;visibility:visible!important;display:block!important;transform:translateY(0)!important}.giat-yt-play-btn{-webkit-backdrop-filter:blur(10px);cursor:pointer;opacity:1;transform-origin:50%;border-radius:20px;outline:none;align-items:center;gap:6px;padding:7px 16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Arial,sans-serif;font-size:13px;font-weight:500;display:flex;position:fixed;top:18px;right:20px;box-shadow:0 4px 16px #0006;z-index:1050!important;color:#fff!important;background:#000000b8!important;border:1px solid #ffffff38!important;transition:opacity .35s cubic-bezier(.4,0,.2,1),transform .15s,background-color .2s,border-color .2s!important}.giat-yt-play-btn:hover{transform:scale(1.05);box-shadow:0 4px 20px #ff000040;opacity:1!important;background:#000000e0!important;border-color:#ffffff73!important}.giat-yt-play-btn:active{transform:scale(.96)!important}.giat-yt-play-btn.giat-yt-dimmed{opacity:.35}.giat-lightbox-yt-iframe{z-index:1012;background-color:#000;border:none;border-radius:4px;width:100%;height:100%;position:absolute;top:0;left:0}.giat-download-btn:hover,.giat-copy-img-btn:hover,.giat-copy-b64-btn:hover,.giat-lens-btn:hover,.giat-tineye-btn:hover,.giat-ai-btn:hover,.giat-photopea-btn:hover,.giat-vectorpea-btn:hover,.giat-yandex-btn:hover,.giat-bing-btn:hover{opacity:1!important;background:#000c!important;border-color:#ffffff4d!important}.giat-download-btn svg,.giat-copy-img-btn svg,.giat-copy-b64-btn svg,.giat-lens-btn svg,.giat-tineye-btn svg,.giat-ai-btn svg,.giat-photopea-btn svg,.giat-vectorpea-btn svg,.giat-yandex-btn svg,.giat-bing-btn svg{fill:#fff!important;pointer-events:none!important;width:20px!important;height:20px!important}.giat-download-btn:active,.giat-copy-img-btn:active,.giat-copy-b64-btn:active,.giat-lens-btn:active,.giat-tineye-btn:active,.giat-ai-btn:active,.giat-photopea-btn:active,.giat-vectorpea-btn:active,.giat-yandex-btn:active,.giat-bing-btn:active{filter:brightness(.7)!important;transition:transform 50ms!important;transform:scale(.85)!important}.giat-btn-success,.giat-download-btn.giat-btn-success,.giat-copy-img-btn.giat-btn-success,.giat-copy-b64-btn.giat-btn-success,.giat-lens-btn.giat-btn-success,.giat-tineye-btn.giat-btn-success,.giat-ai-btn.giat-btn-success,.giat-photopea-btn.giat-btn-success,.giat-vectorpea-btn.giat-btn-success,.giat-yandex-btn.giat-btn-success,.giat-bing-btn.giat-btn-success,.giat-thumb-download-btn.giat-btn-success,.giat-thumb-copy-btn.giat-btn-success,.giat-thumb-b64-btn.giat-btn-success,.giat-thumb-lens-btn.giat-btn-success,.giat-thumb-tineye-btn.giat-btn-success,.giat-thumb-ai-btn.giat-btn-success,.giat-thumb-photopea-btn.giat-btn-success,.giat-thumb-vectorpea-btn.giat-btn-success,.giat-thumb-yandex-btn.giat-btn-success,.giat-thumb-bing-btn.giat-btn-success{opacity:1!important;background:#34a853!important;border-color:#34a853!important;transform:scale(1.08)!important;box-shadow:0 0 16px #34a853cc,inset 0 0 0 1px #ffffff59!important}.giat-btn-success svg,.giat-btn-success svg path{fill:#fff!important;color:#fff!important}.giat-toast{-webkit-backdrop-filter:blur(12px);z-index:99999;opacity:0;pointer-events:none;border-radius:24px;align-items:center;gap:8px;padding:10px 22px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Arial,sans-serif;font-size:13.5px;font-weight:500;transition:transform .4s cubic-bezier(.16,1,.3,1),opacity .3s;display:inline-flex;position:fixed;bottom:32px;left:50%;transform:translate(-50%,24px)}.giat-toast.giat-toast-dark{color:#fff;background:#202124e0;border:1px solid #ffffff1f;box-shadow:0 12px 36px #0006}.giat-toast.giat-toast-light{color:#202124;background:#ffffffeb;border:1px solid #0000001a;box-shadow:0 12px 36px #0000001f}.giat-toast.show{opacity:1;transform:translate(-50%)}.giat-toast .giat-toast-svg,.giat-toast-svg{vertical-align:middle!important;color:#34a853!important;fill:currentColor!important;flex-shrink:0!important;width:17px!important;height:17px!important;display:inline-block!important}.giat-toast .giat-toast-svg-alert,.giat-toast-svg-alert{color:#ea4335!important;stroke:#ea4335!important;fill:none!important}.giat-settings-overlay{z-index:2000;opacity:0;pointer-events:none;-webkit-backdrop-filter:blur(10px);justify-content:center;align-items:center;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Arial,sans-serif;transition:opacity .25s ease-in-out,background-color .25s ease-in-out;display:flex;position:fixed;inset:0}.giat-settings-overlay.show{opacity:1;pointer-events:all}.giat-settings-overlay *,.giat-settings-overlay :before,.giat-settings-overlay :after{box-sizing:border-box!important}.giat-settings-overlay.giat-theme-dark{background:#000000b8}.giat-settings-overlay.giat-theme-light{background:#0000006b}.giat-settings-panel{-webkit-backdrop-filter:blur(24px);opacity:0;border-radius:14px;width:520px;max-width:92vw;padding:22px 26px;transition:background-color .3s,border-color .3s,color .3s,box-shadow .3s,transform .28s cubic-bezier(.34,1.56,.64,1),opacity .28s ease-in-out;transform:scale(.96)translateY(12px);box-sizing:border-box!important}.giat-settings-overlay.show .giat-settings-panel{opacity:1;transform:scale(1)translateY(0)}.giat-settings-header{justify-content:space-between;align-items:center;margin-bottom:14px;padding-bottom:10px;display:flex}.giat-settings-header h3{letter-spacing:-.2px;margin:0;font-size:16.5px;font-weight:600}.giat-settings-close{cursor:pointer;background:0 0;border:none;border-radius:50%;justify-content:center;align-items:center;width:32px;height:32px;font-size:22px;line-height:1;transition:background-color .2s,color .2s;display:flex}.giat-settings-body{flex-direction:column;gap:13px;max-height:480px;padding-right:12px;display:flex;overflow:hidden auto}.giat-settings-body::-webkit-scrollbar{width:6px}.giat-settings-body::-webkit-scrollbar-track{background:0 0;border-radius:3px}.giat-settings-body::-webkit-scrollbar-thumb{border-radius:3px}.giat-settings-group-title{text-transform:uppercase;letter-spacing:.6px;margin:16px 0 6px;padding-bottom:4px;font-size:11px;font-weight:700}.giat-settings-group-title:first-of-type{margin-top:4px}.giat-settings-item{justify-content:space-between;align-items:center;padding:2px 0;display:flex}.giat-settings-item label{font-size:13px;font-weight:450;line-height:1.4}.giat-settings-item select{cursor:pointer;border-radius:6px;outline:none;padding:6px 12px;font-size:12.5px;font-weight:500;transition:background-color .2s,border-color .2s,box-shadow .2s}.giat-filename-chips{flex-wrap:wrap;gap:6px;margin-top:4px;display:flex}.giat-chip-btn{cursor:pointer;-webkit-user-select:none;user-select:none;background:#ffffff14;border:1px solid #ffffff26;border-radius:14px;padding:3px 9px;font-size:11px;font-weight:500;transition:background-color .15s,border-color .15s,transform .1s}.giat-chip-btn:hover{color:#8ab4f8;background:#8ab4f833;border-color:#8ab4f8}.giat-chip-btn:active{transform:scale(.94)}.giat-theme-light .giat-chip-btn{color:#3c4043;background:#0000000a;border:1px solid #0000001f}.giat-theme-light .giat-chip-btn:hover{color:#1a73e8;background:#1a73e81f;border-color:#1a73e8}.giat-filename-preview-wrap{flex-direction:column;gap:4px;width:100%;margin-top:4px;display:flex}.giat-filename-preview-label{text-transform:uppercase;letter-spacing:.4px;opacity:.75;font-size:11px;font-weight:600}.giat-filename-preview-text{color:#8ab4f8;word-break:break-all;background:#00000040;border:1px dashed #ffffff26;border-radius:6px;padding:6px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:11.5px}.giat-theme-light .giat-filename-preview-text{color:#1a73e8;background:#00000008;border:1px dashed #00000026}.giat-switch{flex-shrink:0;width:36px;height:20px;display:inline-block;position:relative}.giat-switch input{opacity:0;width:0;height:0}.giat-switch-slider{cursor:pointer;border-radius:20px;transition:all .3s cubic-bezier(.4,0,.2,1);position:absolute;inset:0}.giat-switch-slider:before{content:\"\";border-radius:50%;width:14px;height:14px;transition:all .3s cubic-bezier(.4,0,.2,1);position:absolute;bottom:3px;left:3px;box-shadow:0 1px 3px #0000004d}.giat-switch input:checked+.giat-switch-slider:before{transform:translate(16px)}.giat-settings-footer{justify-content:flex-end;margin-top:14px;padding-top:12px;display:flex}.giat-settings-reset,.giat-settings-sub-btn{cursor:pointer;border-radius:6px;padding:8px 18px;font-size:12.5px;font-weight:500;transition:background-color .2s,border-color .2s,transform .1s,box-shadow .2s,color .2s}.giat-settings-sub-btn{padding:5px 12px;font-size:12px}.giat-settings-reset:active,.giat-settings-sub-btn:active{transform:scale(.96)}.giat-theme-dark .giat-settings-panel{color:#f1f3f4;background:#1e1f22fa;border:1px solid #ffffff24;box-shadow:0 24px 64px #000000bf,0 0 0 1px #ffffff0f}.giat-theme-dark .giat-settings-header{border-bottom:1px solid #ffffff1a}.giat-theme-dark .giat-settings-header h3{color:#fff}.giat-theme-dark .giat-settings-close{color:#9aa0a6}.giat-theme-dark .giat-settings-close:hover{color:#fff;background-color:#ffffff14}.giat-theme-dark .giat-settings-body::-webkit-scrollbar-thumb{background:#fff3}.giat-theme-dark .giat-settings-body::-webkit-scrollbar-thumb:hover{background:#ffffff59}.giat-theme-dark .giat-settings-group-title{color:#8ab4f8;border-bottom:1px solid #ffffff14}.giat-theme-dark .giat-settings-item label{color:#e8eaed}.giat-theme-dark .giat-settings-item select{color:#fff;background:#28292c;border:1px solid #ffffff2e}.giat-theme-dark .giat-settings-item select:hover{background:#323338;border-color:#8ab4f8}.giat-theme-dark .giat-switch-slider{background-color:#ffffff38;border:1px solid #ffffff1a}.giat-theme-dark .giat-switch-slider:before{background-color:#fff}.giat-theme-dark .giat-switch input:checked+.giat-switch-slider{background-color:#8ab4f8}.giat-theme-dark .giat-switch input:checked+.giat-switch-slider:before{background-color:#202124}.giat-theme-dark .giat-settings-footer{border-top:1px solid #ffffff1a}.giat-theme-dark .giat-settings-reset,.giat-theme-dark .giat-settings-sub-btn{color:#e8eaed;background:#28292c;border:1px solid #ffffff2e}.giat-theme-dark .giat-settings-reset:hover,.giat-theme-dark .giat-settings-sub-btn:hover{color:#fff;background:#35363c;border-color:#ffffff59}.giat-theme-light .giat-settings-panel{color:#202124;background:#fffffffa;border:1px solid #0000001f;box-shadow:0 24px 64px #00000038,0 0 0 1px #0000000a}.giat-theme-light .giat-settings-header{border-bottom:1px solid #00000014}.giat-theme-light .giat-settings-header h3{color:#1a1a1c}.giat-theme-light .giat-settings-close{color:#5f6368}.giat-theme-light .giat-settings-close:hover{color:#202124;background-color:#0000000f}.giat-theme-light .giat-settings-body::-webkit-scrollbar-thumb{background:#0003}.giat-theme-light .giat-settings-body::-webkit-scrollbar-thumb:hover{background:#00000059}.giat-theme-light .giat-settings-group-title{color:#1a73e8;border-bottom:1px solid #0000000f}.giat-theme-light .giat-settings-item label{color:#202124}.giat-theme-light .giat-settings-item select{color:#202124;background:#f8f9fa;border:1px solid #0000002e}.giat-theme-light .giat-settings-item select:hover{background:#fff;border-color:#1a73e8}.giat-theme-light .giat-switch-slider{background-color:#0000002e;border:1px solid #0000000f}.giat-theme-light .giat-switch-slider:before{background-color:#fff;box-shadow:0 1px 3px #00000040}.giat-theme-light .giat-switch input:checked+.giat-switch-slider{background-color:#1a73e8}.giat-theme-light .giat-switch input:checked+.giat-switch-slider:before{background-color:#fff}.giat-theme-light .giat-settings-footer{border-top:1px solid #00000014}.giat-theme-light .giat-settings-reset,.giat-theme-light .giat-settings-sub-btn{color:#202124;background:#f1f3f4;border:1px solid #0000002e}.giat-theme-light .giat-settings-reset:hover,.giat-theme-light .giat-settings-sub-btn:hover{color:#1a73e8;background:#e8f0fe;border-color:#1a73e8}.giat-wrap.giat-bg-white img:not(.giat-thumb-blur){background-color:#fff}.giat-wrap.giat-bg-gray img:not(.giat-thumb-blur){background-color:#1a1a1a}.giat-wrap.giat-bg-black img:not(.giat-thumb-blur){background-color:#000}.giat-wrap.giat-bg-grid img:not(.giat-thumb-blur){background-color:#fff;background-image:conic-gradient(#e5e5e5 25%, transparent 0 50%, #e5e5e5 0 75%, transparent 0);background-size:16px 16px}.giat-wrap.giat-bg-dark-grid img:not(.giat-thumb-blur){background-color:#282828;background-image:conic-gradient(#1f1f1f 25%, transparent 0 50%, #1f1f1f 0 75%, transparent 0);background-size:16px 16px}.giat-download-btn svg path,.giat-copy-img-btn svg path,.giat-copy-b64-btn svg path,.giat-tineye-btn svg path,.giat-ai-btn svg path.giat-ai-star,.giat-photopea-btn svg path,.giat-vectorpea-btn svg path,.giat-yandex-btn svg path,.giat-bing-btn svg path,.giat-thumb-download-btn svg path,.giat-thumb-copy-btn svg path,.giat-thumb-b64-btn svg path,.giat-thumb-tineye-btn svg path,.giat-thumb-ai-btn svg path.giat-ai-star,.giat-thumb-photopea-btn svg path,.giat-thumb-vectorpea-btn svg path,.giat-thumb-yandex-btn svg path,.giat-thumb-bing-btn svg path{fill:inherit!important}.giat-dims svg,.giat-dims svg path{stroke:currentColor!important;fill:none!important}.giat-thumb-ai-btn svg g{stroke:var(--giat-custom-color,currentColor)!important;fill:none!important}.giat-ai-btn svg g{stroke:currentColor!important;fill:none!important}.giat-thumb-ai-btn svg g path{stroke:var(--giat-custom-color,currentColor)!important;fill:none!important}.giat-ai-btn svg g path{stroke:currentColor!important;fill:none!important}.giat-thumb-ai-btn svg line{stroke:var(--giat-custom-color,currentColor)!important}.giat-ai-btn svg line{stroke:currentColor!important}.giat-type-badge{display:var(--giat-lightbox-mime-display,block);-webkit-backdrop-filter:blur(8px);letter-spacing:.5px;z-index:1015;opacity:0;visibility:hidden;border-radius:22px;padding:8px 16px;font-family:Roboto-Medium,Roboto,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:12px;font-weight:600;line-height:12px;transition:opacity .35s cubic-bezier(.16,1,.3,1),transform .35s cubic-bezier(.16,1,.3,1),visibility .35s;position:absolute;bottom:24px;left:24px;transform:translateY(16px);color:#fff!important;pointer-events:none!important;background:#00000080!important;border:1px solid #ffffff26!important}.giat-wrap.show:not(.loading):not(.error)~.giat-type-badge{opacity:.85;transform:translateY(0);visibility:visible!important}.giat-slide-out-left{animation:.2s cubic-bezier(.4,0,1,1) forwards giat-slide-out-left-anim!important}.giat-slide-out-right{animation:.2s cubic-bezier(.4,0,1,1) forwards giat-slide-out-right-anim!important}.giat-slide-in-right{animation:.25s cubic-bezier(.16,1,.3,1) forwards giat-slide-in-right-anim!important}.giat-slide-in-left{animation:.25s cubic-bezier(.16,1,.3,1) forwards giat-slide-in-left-anim!important}@keyframes giat-slide-out-left-anim{0%{opacity:1;transform:translate(0)scale(1)}to{opacity:0;transform:translate(-35px)scale(.96)}}@keyframes giat-slide-out-right-anim{0%{opacity:1;transform:translate(0)scale(1)}to{opacity:0;transform:translate(35px)scale(.96)}}@keyframes giat-slide-in-right-anim{0%{opacity:0;transform:translate(35px)scale(.96)}to{opacity:1;transform:translate(0)scale(1)}}@keyframes giat-slide-in-left-anim{0%{opacity:0;transform:translate(-35px)scale(.96)}to{opacity:1;transform:translate(0)scale(1)}}.giat-zoomed .giat-lightbox-btn-container,.giat-zoomed .giat-type-badge,.giat-zoomed .giat-type-badge.giat-has-metadata,.giat-backdrop.giat-zoomed .giat-type-badge,.giat-backdrop.giat-zoomed .giat-type-badge.giat-has-metadata{opacity:0!important;pointer-events:none!important;visibility:hidden!important;cursor:default!important}.giat-dims-size{display:var(--giat-thumb-file-size-display,inline-block);text-overflow:ellipsis;white-space:nowrap;flex-shrink:1;min-width:0;overflow:hidden}.giat-dims-type{display:var(--giat-thumb-mime-display,none);text-overflow:ellipsis;white-space:nowrap;flex-shrink:1;min-width:0;overflow:hidden}.giat-dims-date{display:var(--giat-thumb-date-display,inline-block);text-overflow:ellipsis;white-space:nowrap;flex-shrink:1;min-width:0;overflow:hidden}.giat-native-badge-wrapper{display:var(--giat-native-date-display,flex)!important}.giat-download-bypass-banner{-webkit-backdrop-filter:blur(12px);z-index:10000;opacity:0;pointer-events:none;border-radius:12px;justify-content:space-between;align-items:center;gap:16px;width:90%;max-width:600px;padding:14px 20px;transition:transform .4s cubic-bezier(.175,.885,.32,1.275),opacity .4s;display:flex;position:fixed;top:24px;left:50%;transform:translate(-50%,-120%);box-shadow:0 8px 32px #0000004d}.giat-download-bypass-banner.show{opacity:1;pointer-events:auto;transform:translate(-50%)}.giat-banner-dark{background:#1e1e1ed9;border:1px solid #ffffff1f;color:#f1f3f4!important}.giat-banner-light{background:#ffffffd9;border:1px solid #0000001f;box-shadow:0 8px 32px #00000026;color:#202124!important}.giat-download-bypass-text{flex:1;font-size:13px;font-weight:500;line-height:1.5}.giat-download-bypass-btn{white-space:nowrap;cursor:pointer;border-radius:8px;justify-content:center;align-items:center;padding:8px 16px;font-size:13px;font-weight:600;transition:background-color .2s,transform .1s;display:inline-flex;text-decoration:none!important}.giat-banner-dark .giat-download-bypass-btn{background:#34a853;color:#fff!important}.giat-banner-dark .giat-download-bypass-btn:hover{background:#2e9649}.giat-banner-light .giat-download-bypass-btn{background:#1a73e8;color:#fff!important}.giat-banner-light .giat-download-bypass-btn:hover{background:#155cb8}.giat-download-bypass-btn:active{transform:scale(.95)}.giat-info-icon{cursor:help;color:#9aa0a6;vertical-align:middle;align-items:center;margin-left:4px;display:inline-flex}.giat-info-icon svg{fill:currentColor;transition:color .2s ease-in-out}.giat-theme-dark .giat-info-icon:hover svg{color:#8ab4f8}.giat-theme-light .giat-info-icon:hover svg{color:#1a73e8}.giat-settings-tooltip{z-index:2000;white-space:normal;word-break:break-word;opacity:0;pointer-events:none;text-transform:none;text-align:left;border-radius:6px;width:200px;padding:6px 10px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Arial,sans-serif;font-size:11px;font-weight:400;line-height:1.4;transition:opacity .15s ease-in-out,transform .15s ease-in-out;position:absolute;transform:translateY(4px)}.giat-settings-tooltip.show{opacity:1;transform:translateY(0)}.giat-theme-dark .giat-settings-tooltip{background:#202124f5;border:1px solid #ffffff1a;box-shadow:0 4px 16px #0006;color:#bdc1c6!important}.giat-theme-light .giat-settings-tooltip{background:#fffffffa;border:1px solid #00000014;box-shadow:0 4px 16px #0000001f;color:#5f6368!important}[data-giat-result]{border-radius:8px!important;transition:box-shadow 2.2s cubic-bezier(.16,1,.3,1),transform 2.2s cubic-bezier(.16,1,.3,1)!important}[data-giat-result] img{border-radius:6px!important}[data-giat-result].giat-result-active{z-index:5!important;border-radius:8px!important;transition:box-shadow .2s,transform .2s!important;position:relative!important;transform:scale(1.02)!important;box-shadow:0 0 0 3px #1a73e8,0 8px 24px #1a73e880!important}.giat-settings-subpanel{border-left:2px solid #80808033;flex-direction:column;gap:6px;margin-top:4px;margin-bottom:4px;padding-left:12px;display:flex}.giat-key-btn{cursor:pointer;color:inherit;text-align:center;background:#8080801a;border:1px solid #80808033;border-radius:4px;outline:none;min-width:85px;padding:4px 8px;font-size:11px;transition:all .2s}.giat-theme-dark .giat-key-btn:hover{background:#ffffff26;border-color:#ffffff40}.giat-theme-light .giat-key-btn:hover{background:#00000014;border-color:#00000026}.giat-key-btn.pending{animation:1.5s infinite giat-key-pending-pulse;color:#1a73e8!important;background:#1a73e81a!important;border-color:#1a73e8!important}@keyframes giat-key-pending-pulse{0%{opacity:.6}50%{opacity:1}to{opacity:.6}}.giat-thumb-lens-btn svg{width:16px!important;height:16px!important}.giat-lens-btn svg{width:24px!important;height:24px!important}.giat-settings-item-block{gap:4px;margin-top:4px;flex-direction:column!important;align-items:flex-start!important}.giat-text-input{box-sizing:border-box;border-radius:6px;outline:none;padding:6px 10px;font-size:12px;transition:background-color .2s,border-color .2s}.giat-theme-dark .giat-text-input{color:#f1f3f4!important;background:#303134cc!important;border:1px solid #5f636880!important}.giat-theme-dark .giat-text-input:focus{background:#3c4043e6!important;border-color:#8ab4f8!important}.giat-theme-light .giat-text-input{color:#3c4043!important;background:#f1f3f4e6!important;border:1px solid #dadce0!important}.giat-theme-light .giat-text-input:focus{background:#e8f0fee6!important;border-color:#1a73e8!important}.giat-settings-help-text{opacity:.8;word-break:break-word;margin-top:4px;font-size:11px;line-height:1.4}.giat-theme-dark .giat-settings-help-text{color:#9aa0a6}.giat-theme-light .giat-settings-help-text{color:#5f6368}.giat-type-badge.giat-has-metadata{cursor:help!important;border-bottom:none!important;align-items:center!important;gap:3px!important;display:inline-flex!important}.giat-wrap.show:not(.loading):not(.error)~.giat-type-badge.giat-has-metadata{pointer-events:auto!important}.giat-type-badge.giat-has-metadata:after{content:\"ⓘ\";opacity:.55;vertical-align:middle;font-size:10px;font-weight:400;line-height:1;transition:opacity .2s,transform .2s,color .2s;display:inline-block}.giat-type-badge.giat-has-metadata:hover:after{opacity:1;transform:scale(1.15);color:#8ab4f8!important}.giat-exif-copyable{border-radius:4px;margin:-1px -4px;padding:1px 4px;cursor:pointer!important;transition:background-color .15s,transform .1s!important}.giat-exif-copyable:hover{background:#8ab4f826!important}.giat-theme-light .giat-exif-copyable:hover{background:#1a73e81a!important}.giat-hud-overlay{z-index:1020;-webkit-backdrop-filter:blur(12px);opacity:0;pointer-events:none;background:#00000073;justify-content:center;align-items:center;transition:opacity .25s cubic-bezier(.16,1,.3,1);display:flex;position:fixed;inset:0}.giat-hud-overlay.show{opacity:1;pointer-events:auto}.giat-hud-card{color:#f1f3f4;background:#1e1e1ee0;border:1px solid #ffffff26;border-radius:16px;width:90%;max-width:420px;padding:22px 28px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;transition:transform .25s cubic-bezier(.16,1,.3,1);transform:scale(.92)translateY(12px);box-shadow:0 16px 48px #00000080}.giat-hud-overlay.show .giat-hud-card{transform:scale(1)translateY(0)}.giat-hud-title{color:#8ab4f8;border-bottom:1px solid #ffffff1a;justify-content:space-between;align-items:center;margin-bottom:14px;padding-bottom:8px;font-size:15px;font-weight:600;display:flex}.giat-hud-grid{flex-direction:column;gap:9px;display:flex}.giat-hud-row{justify-content:space-between;align-items:center;font-size:13px;display:flex}.giat-hud-key{color:#fff;background:#ffffff1f;border:1px solid #fff3;border-radius:6px;padding:2px 8px;font-family:monospace,sans-serif;font-size:12px;font-weight:600;box-shadow:0 2px 4px #0003}.giat-batch-trigger-btn{z-index:999;-webkit-backdrop-filter:blur(12px);cursor:pointer;will-change:width, border-radius;border-radius:50%;justify-content:center;align-items:center;width:38px;height:38px;padding:0;font-family:Roboto,-apple-system,BlinkMacSystemFont,sans-serif;font-size:13px;font-weight:500;display:inline-flex;position:fixed;bottom:24px;right:24px;overflow:hidden;transform:translateZ(0);box-shadow:0 4px 14px #0003;color:#e8eaed!important;background:#20212461!important;border:1px solid #ffffff1a!important;transition:width .28s cubic-bezier(.16,1,.3,1),border-radius .28s cubic-bezier(.16,1,.3,1),background-color .2s,border-color .2s,box-shadow .2s!important}.giat-batch-icon-svg{color:#bdc1c6;flex-shrink:0;width:18px;height:18px;transition:color .2s}.giat-batch-trigger-text{opacity:0;white-space:nowrap;max-width:0;transition:max-width .28s cubic-bezier(.16,1,.3,1),opacity .2s,margin-left .2s;overflow:hidden}.giat-batch-trigger-btn.giat-theme-light{color:#202124!important;background:#ffffffe0!important;border:1px solid #00000024!important;box-shadow:0 4px 16px #0000001f,0 2px 6px #0000000f!important}.giat-batch-trigger-btn.giat-theme-light .giat-batch-icon-svg{color:#5f6368!important}.giat-batch-trigger-btn.giat-theme-light:hover{color:#1a73e8!important;background:#fffffffa!important;border-color:#1a73e866!important;box-shadow:0 8px 24px #00000029!important}.giat-batch-trigger-btn.giat-theme-light:hover .giat-batch-icon-svg{color:#1a73e8!important}.giat-batch-trigger-btn:hover{border-radius:20px;width:auto;padding:0 16px;box-shadow:0 6px 20px #00000059;background:#202124eb!important;border-color:#ffffff40!important}.giat-batch-trigger-btn:hover .giat-batch-icon-svg{color:#8ab4f8}.giat-batch-trigger-btn:hover .giat-batch-trigger-text{opacity:1;max-width:120px;margin-left:8px}.giat-batch-active .giat-thumb-btns-wrap,.giat-batch-active .giat-thumb-download-btn,.giat-batch-active .giat-thumb-copy-btn,.giat-batch-active .giat-thumb-b64-btn,.giat-batch-active .giat-thumb-lens-btn,.giat-batch-active .giat-thumb-tineye-btn,.giat-batch-active .giat-thumb-ai-btn,.giat-batch-active .giat-thumb-photopea-btn,.giat-batch-active .giat-thumb-vectorpea-btn,.giat-batch-active .giat-thumb-yandex-btn,.giat-batch-active .giat-thumb-bing-btn{display:none!important}.giat-batch-bar{z-index:1050;-webkit-backdrop-filter:blur(16px);color:#202124;opacity:0;pointer-events:none;background:#ffffffeb;border:1px solid #0000001f;border-radius:28px;align-items:center;gap:16px;padding:10px 22px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;transition:transform .3s cubic-bezier(.16,1,.3,1),opacity .25s,background-color .3s,border-color .3s,color .3s;display:flex;position:fixed;top:20px;left:50%;transform:translate(-50%,-24px);box-shadow:0 12px 36px #00000029}.giat-batch-bar.giat-theme-light{color:#202124!important;background:#fffffff0!important;border:1px solid #0000001f!important;box-shadow:0 10px 32px #00000024!important}.giat-batch-bar.giat-theme-light .giat-batch-info{color:#1a73e8!important}.giat-batch-bar.giat-theme-light .giat-batch-btn{color:#3c4043!important;background:#0000000d!important;border-color:#0000001f!important}.giat-batch-bar.giat-theme-light .giat-batch-btn:hover{color:#202124!important;background:#00000017!important;border-color:#0003!important}.giat-batch-bar.giat-theme-light .giat-batch-btn-exit{color:#d93025!important;background:#ea433514!important;border-color:#ea433540!important}.giat-batch-bar.giat-theme-light .giat-batch-btn-exit:hover{background:#ea433526!important;border-color:#ea433566!important}.giat-batch-bar.giat-theme-dark{color:#fff!important;background:#202124f0!important;border:1px solid #ffffff2e!important;box-shadow:0 12px 36px #00000073!important}.giat-batch-bar.giat-theme-dark .giat-batch-info{color:#8ab4f8!important}.giat-batch-bar.giat-theme-dark .giat-batch-btn{color:#fff!important;background:#ffffff1a!important;border-color:#ffffff26!important}.giat-batch-bar.giat-theme-dark .giat-batch-btn:hover{color:#fff!important;background:#fff3!important;border-color:#ffffff4d!important}.giat-batch-bar.show{opacity:1;pointer-events:auto;transform:translate(-50%)}.giat-batch-info{color:#1a73e8;white-space:nowrap;font-size:13px;font-weight:600}@media (prefers-color-scheme:dark){.giat-batch-info{color:#8ab4f8}}html[dark] .giat-batch-info,body.dark .giat-batch-info{color:#8ab4f8}.giat-batch-actions{align-items:center;gap:8px;display:flex}.giat-batch-btn{cursor:pointer;color:#3c4043;background:#0000000d;border:1px solid #0000001f;border-radius:16px;outline:none;padding:6px 14px;font-size:12px;font-weight:500;transition:background-color .2s,transform .1s,border-color .2s,color .2s}.giat-batch-btn:hover{color:#202124;background:#00000017;border-color:#0003}@media (prefers-color-scheme:dark){.giat-batch-btn{color:#fff;background:#ffffff1a;border-color:#ffffff26}.giat-batch-btn:hover{color:#fff;background:#fff3;border-color:#ffffff4d}}html[dark] .giat-batch-btn,body.dark .giat-batch-btn{color:#fff;background:#ffffff1a;border-color:#ffffff26}html[dark] .giat-batch-btn:hover,body.dark .giat-batch-btn:hover{color:#fff;background:#fff3;border-color:#ffffff4d}.giat-batch-btn:active{transform:scale(.95)}.giat-batch-btn-export{font-weight:600;background:#1a73e8!important;border-color:#1a73e8!important}.giat-batch-btn-export:hover{background:#185abc!important}.giat-batch-btn-export:disabled{opacity:.4!important;cursor:not-allowed!important;background:#ffffff1a!important;border-color:#ffffff1a!important}.giat-thumb-checkbox-wrap{z-index:10;display:none;position:absolute;top:8px;left:8px}body.giat-pos-br .giat-thumb-checkbox-wrap{inset:8px auto auto 8px!important}body.giat-pos-bl .giat-thumb-checkbox-wrap{inset:8px 8px auto auto!important}body.giat-pos-tl .giat-thumb-checkbox-wrap{inset:auto 8px 8px auto!important}body.giat-pos-tr .giat-thumb-checkbox-wrap{inset:auto auto 8px 8px!important}.giat-batch-active .giat-thumb-checkbox-wrap{display:block}.giat-thumb-checkbox{cursor:pointer;accent-color:#1a73e8;width:18px;height:18px}[data-giat-result].giat-selected{z-index:5!important;border-radius:8px!important;outline:none!important;transition:box-shadow .2s,transform .2s cubic-bezier(.16,1,.3,1)!important;position:relative!important;transform:scale(1.015)!important;box-shadow:0 0 0 3px #1a73e8,0 8px 24px #1a73e880!important}[data-giat-result].giat-selected img{box-shadow:none!important;border-radius:6px!important;outline:none!important}.giat-missed-card{max-width:540px!important}.giat-missed-card.giat-theme-light{color:#202124!important;background:#fffffff5!important;border:1px solid #0000001f!important;box-shadow:0 16px 48px #0000002e,0 4px 16px #00000014!important}.giat-missed-card.giat-theme-light .giat-hud-title{color:#202124!important;border-bottom-color:#00000014!important}.giat-missed-card.giat-theme-light .giat-missed-close-btn{color:#5f6368!important}.giat-missed-card.giat-theme-light .giat-missed-close-btn:hover{color:#202124!important;background:#00000014!important}.giat-missed-card.giat-theme-light .giat-missed-row{background:#00000008!important;border:1px solid #00000014!important}.giat-missed-card.giat-theme-light .giat-missed-badge{color:#d93025!important;background:#ea43351a!important;border:1px solid #ea43354d!important}.giat-missed-card.giat-theme-light .giat-missed-url{color:#5f6368!important}.giat-missed-card.giat-theme-light .giat-missed-link-btn{color:#1a73e8!important;background:#1a73e814!important;border:1px solid #1a73e840!important}.giat-missed-card.giat-theme-light .giat-missed-link-btn:hover{background:#1a73e82e!important}.giat-missed-close-btn{color:#9aa0a6;cursor:pointer;background:0 0;border:none;border-radius:4px;padding:4px 8px;font-size:16px}.giat-missed-close-btn:hover{color:#fff;background:#ffffff1a}.giat-missed-grid{max-height:320px;padding-right:4px;overflow-y:auto}.giat-missed-row{background:#ffffff0d;border:1px solid #ffffff14;border-radius:8px;justify-content:space-between;align-items:center;gap:12px;padding:8px 12px;display:flex}.giat-missed-item-info{align-items:center;gap:10px;display:flex;overflow:hidden}.giat-missed-badge{color:#f28b82;white-space:nowrap;background:#ea433533;border:1px solid #ea433566;border-radius:4px;padding:2px 8px;font-size:11px}.giat-missed-url{color:#bdc1c6;white-space:nowrap;text-overflow:ellipsis;max-width:260px;font-size:12px;overflow:hidden}.giat-missed-link-btn{white-space:nowrap;background:#8ab4f81a;border:1px solid #8ab4f833;border-radius:12px;padding:3px 10px;font-size:12px;transition:background-color .2s;color:#8ab4f8!important;text-decoration:none!important}.giat-missed-link-btn:hover{background:#8ab4f833!important}.giat-time-history-tooltip{z-index:1050;box-sizing:border-box;opacity:0;pointer-events:auto;border-radius:8px;width:max-content;max-width:450px;padding:12px 16px;font-family:Roboto,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:12px;line-height:1.6;transition:opacity .2s cubic-bezier(.16,1,.3,1),transform .2s cubic-bezier(.16,1,.3,1);position:absolute;transform:translateY(8px);box-shadow:0 8px 32px #0000004d}.giat-time-history-tooltip.show{opacity:1;transform:translateY(0)}.giat-tooltip-dark{color:#f1f3f4!important;background:#202124f5!important;border:1px solid #ffffff14!important;box-shadow:0 8px 32px #00000080!important}.giat-tooltip-light{color:#202124!important;background:#fffffff5!important;border:1px solid #00000014!important;box-shadow:0 8px 32px #00000026!important}.giat-tooltip-header{border-bottom:1px solid #80808033;margin-bottom:8px;padding-bottom:4px;font-size:13px;font-weight:600}.giat-tooltip-row{justify-content:flex-start;gap:12px;margin:4px 0;display:flex}.giat-row-label{opacity:.8;flex-shrink:0;min-width:110px;font-weight:500;display:inline-block}.giat-row-val{text-align:left;flex-grow:1;font-weight:400}.giat-tooltip-dark .giat-row-highlight{color:#8ab4f8!important;font-weight:700!important}.giat-tooltip-light .giat-row-highlight{color:#1a73e8!important;font-weight:700!important}.giat-tooltip-section{flex-direction:column;display:flex}.giat-tooltip-divider{background:#80808033;height:1px;margin:8px 0}.giat-gps-link{cursor:pointer;font-weight:500;color:#8ab4f8!important;text-decoration:underline!important}.giat-tooltip-light .giat-gps-link{color:#1a73e8!important}.giat-gps-link:hover{opacity:.8}.giat-tooltip-row-block{margin:6px 0;display:block}.giat-row-val-prompt{white-space:pre-wrap;word-break:break-all;border-radius:4px;max-height:120px;padding:8px;font-family:monospace,Consolas,Monaco,Courier New,sans-serif;font-size:11px;line-height:1.4;overflow-y:auto}.giat-tooltip-dark .giat-row-val-prompt{color:#c9d1d9;background:#0006;border:1px solid #ffffff0d}.giat-tooltip-light .giat-row-val-prompt{color:#24292f;background:#0000000d;border:1px solid #0000000d}.p7sI2{position:relative!important}.p7sI2 .UWuvyf{display:none!important}.giat-detail-type-badge{opacity:var(--giat-dims-initial-opacity,1);transition:opacity .25s ease-in-out;border-radius:var(--giat-label-radius-outer) 0 0 0!important;cursor:pointer!important;pointer-events:auto!important;z-index:1015!important;border-bottom:none!important;position:absolute!important;inset:auto 0 0 auto!important}.p7sI2:hover .giat-detail-type-badge{opacity:.85!important}.giat-detail-btn-container{z-index:1015;opacity:0;gap:6px;transition:opacity .25s ease-in-out;display:flex!important;position:absolute!important;inset:6px auto auto 6px!important}.giat-detail-btn-container.giat-disabled-container{display:none!important}.p7sI2:hover .giat-detail-btn-container,[jsname=figiqf]:hover~.giat-detail-btn-container,.giat-detail-btn-container:hover{opacity:1!important}.giat-detail-btn-container .giat-thumb-download-btn{display:var(--giat-thumb-download-display,flex)!important}.giat-detail-btn-container .giat-thumb-copy-btn{display:var(--giat-thumb-copy-display,flex)!important}.giat-detail-btn-container .giat-thumb-b64-btn{display:var(--giat-thumb-b64-display,flex)!important}.giat-detail-btn-container .giat-thumb-lens-btn{display:var(--giat-thumb-lens-display,flex)!important}.giat-detail-btn-container .giat-thumb-tineye-btn{display:var(--giat-thumb-tineye-display,flex)!important}.giat-detail-btn-container .giat-thumb-ai-btn{display:var(--giat-thumb-ai-display,flex)!important}.giat-detail-btn-container .giat-thumb-photopea-btn{display:var(--giat-thumb-photopea-display,flex)!important}.giat-detail-btn-container .giat-thumb-vectorpea-btn{display:var(--giat-thumb-vectorpea-display,flex)!important}.giat-detail-btn-container .giat-thumb-yandex-btn{display:var(--giat-thumb-yandex-display,flex)!important}.giat-detail-btn-container .giat-thumb-bing-btn{display:var(--giat-thumb-bing-display,flex)!important}.giat-detail-btn-container button{width:calc(var(--giat-thumb-btn-size) + 4px)!important;height:calc(var(--giat-thumb-btn-size) + 4px)!important}.giat-detail-btn-container button svg{width:calc(var(--giat-thumb-btn-svg-size) + 4px)!important;height:calc(var(--giat-thumb-btn-svg-size) + 4px)!important}.giat-detail-type-badge.giat-dims{font-size:calc(var(--giat-label-font-size,10px) + 2px)!important;padding:5px 8px!important}.giat-detail-type-badge.giat-dims svg{vertical-align:middle!important;width:13px!important;height:13px!important;margin-top:-2px!important;margin-right:4px!important}.giat-c2pa-timeline{box-sizing:border-box;background:#ffffff0d;border:1px solid #ffffff14;border-radius:8px;margin-top:12px;padding:12px;font-family:inherit}.giat-theme-light .giat-c2pa-timeline{background:#00000008;border-color:#0000000f}.giat-timeline-title{color:#8ab4f8;align-items:center;gap:6px;margin-bottom:12px;font-size:13px;font-weight:600;display:flex}.giat-theme-light .giat-timeline-title{color:#1a73e8}.giat-timeline-list{margin:0 0 0 4px;padding:0;list-style:none;position:relative}.giat-timeline-list:before{content:\"\";background:#ffffff26;width:1px;position:absolute;top:6px;bottom:6px;left:5px}.giat-theme-light .giat-timeline-list:before{background:#0000001a}.giat-timeline-item{text-align:left;margin-bottom:12px;padding-left:20px;position:relative}.giat-timeline-item:last-child{margin-bottom:0}.giat-timeline-dot{background:#8ab4f8;border-radius:50%;width:7px;height:7px;position:absolute;top:5px;left:2px;box-shadow:0 0 0 2px #8ab4f833}.giat-theme-light .giat-timeline-dot{background:#1a73e8;box-shadow:0 0 0 2px #1a73e826}.giat-timeline-content{flex-direction:column;gap:2px;display:flex}.giat-timeline-action{color:#e8eaed;font-size:11px;font-weight:600}.giat-theme-light .giat-timeline-action{color:#202124}.giat-timeline-software{color:#9aa0a6;font-size:10px}.giat-theme-light .giat-timeline-software{color:#5f6368}.giat-timeline-time{color:#80868b;font-size:9px}.giat-theme-light .giat-timeline-time{color:#70757a}.giat-timeline-item.giat-action-created .giat-timeline-dot{background:#81c995!important;box-shadow:0 0 0 2px #81c99533!important}.giat-timeline-item.giat-action-edited .giat-timeline-dot{background:#8ab4f8!important;box-shadow:0 0 0 2px #8ab4f833!important}.giat-timeline-item.giat-action-other .giat-timeline-dot{background:#9aa0a6!important;box-shadow:0 0 0 2px #9aa0a633!important}.giat-theme-light .giat-timeline-item.giat-action-created .giat-timeline-dot{background:#1e8e3e!important;box-shadow:0 0 0 2px #1e8e3e26!important}.giat-theme-light .giat-timeline-item.giat-action-edited .giat-timeline-dot{background:#1a73e8!important;box-shadow:0 0 0 2px #1a73e826!important}.giat-theme-light .giat-timeline-item.giat-action-other .giat-timeline-dot{background:#5f6368!important;box-shadow:0 0 0 2px #5f636826!important}.giat-badge-pill{align-items:center;gap:4px;display:inline-flex;vertical-align:middle!important;border:1px solid #0000!important;border-radius:4px!important;margin-right:6px!important;padding:1px 6px!important;font-size:10px!important;font-weight:700!important;line-height:12px!important}.giat-pill-c2pa{color:#81c995!important;background-color:#81c99526!important;border-color:#81c99566!important}.giat-theme-light .giat-pill-c2pa,.giat-tooltip-light .giat-pill-c2pa{color:#1e8e3e!important;background-color:#1e8e3e14!important;border-color:#1e8e3e4d!important}.giat-pill-ai{color:#c58af9!important;background-color:#c58af926!important;border-color:#c58af966!important}.giat-theme-light .giat-pill-ai,.giat-tooltip-light .giat-pill-ai{color:#9333ea!important;background-color:#9333ea14!important;border-color:#9333ea4d!important}.giat-upload-overlay{z-index:99999;box-sizing:border-box;border-radius:12px;padding:16px;font-family:Google Sans,Roboto,sans-serif;font-size:14px;transition:opacity .3s,transform .3s;position:fixed;top:20px;right:20px;box-shadow:0 8px 32px #0000004d}.giat-upload-overlay.giat-theme-dark{color:#e8eaed;background:#303134;border:1px solid #ffffff14}.giat-upload-overlay.giat-theme-light{color:#202124;background:#fff;border:1px solid #00000014;box-shadow:0 8px 32px #00000026}.giat-upload-overlay .giat-progress-bg{background:#ffffff26;border-radius:2px;width:100%;height:4px;position:relative;overflow:hidden}.giat-upload-overlay.giat-theme-light .giat-progress-bg{background:#0000001a}.giat-upload-overlay.giat-status-success{color:#fff!important;background:linear-gradient(135deg,#1e8e3e,#34a853)!important;border-color:#ffffff1a!important;box-shadow:0 8px 32px #1e8e3e4d!important}.giat-upload-overlay.giat-status-error{color:#fff!important;background:linear-gradient(135deg,#d93025,#ea4335)!important;border-color:#ffffff1a!important;box-shadow:0 8px 32px #d930254d!important}.giat-lightbox-progress{-webkit-backdrop-filter:blur(12px);pointer-events:none;z-index:9999;opacity:0;visibility:hidden;box-sizing:border-box;background:#1e1e1eb3;border:1px solid #ffffff1a;border-radius:50%;flex-direction:column;justify-content:center;align-items:center;width:90px;height:90px;transition:opacity .3s,visibility .3s;display:flex;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-shadow:0 8px 32px #0000005e}.giat-lightbox-progress.show{opacity:1;visibility:visible}.giat-lightbox-progress svg{position:absolute;top:15px;left:15px;transform:rotate(-90deg)}.giat-lightbox-progress .giat-ring-bg{stroke:#ffffff14}.giat-lightbox-progress .giat-ring-fg{stroke:#8ab4f8;transition:stroke-dasharray .15s ease-out}.giat-lightbox-progress .giat-progress-text{color:#fff;margin-top:1px;font-family:Google Sans,Roboto,sans-serif;font-size:13px;font-weight:600}.giat-color-analysis-section{border-top:1px solid #ffffff14!important;margin-top:12px!important;padding-top:12px!important}.giat-tooltip-light .giat-color-analysis-section{border-top-color:#0000000f!important}.giat-color-histogram-wrap{box-sizing:border-box!important;background:#0000004d!important;border-radius:6px!important;height:75px!important;margin-bottom:12px!important;padding:6px!important;position:relative!important;overflow:hidden!important}.giat-tooltip-light .giat-color-histogram-wrap{background:#0000000d!important}.giat-color-histogram-wrap svg{width:100%!important;height:100%!important;display:block!important}.giat-color-palette-wrap{box-sizing:border-box!important;justify-content:center!important;align-items:center!important;gap:12px!important;padding:6px 0!important;display:flex!important}.giat-color-swatch{z-index:1;cursor:pointer!important;box-sizing:border-box!important;border:1px solid #0003!important;border-radius:50%!important;justify-content:center!important;align-items:center!important;width:28px!important;height:28px!important;margin:0!important;transition:transform .25s cubic-bezier(.175,.885,.32,1.275),box-shadow .25s,z-index .1s!important;display:flex!important;position:relative!important;overflow:visible!important}.giat-color-swatch:hover{z-index:3;box-shadow:0 4px 12px var(--swatch-color)!important;transform:scale(1.25)!important}.giat-color-swatch.giat-clicked{transition:transform .1s!important;transform:scale(.9)!important}.giat-color-swatch.giat-copied-flash,.giat-color-swatch.giat-copied-flash:hover{animation:.4s cubic-bezier(.1,.8,.3,1) forwards giat-flash-glow!important}@keyframes giat-flash-glow{0%{border-color:#fff!important;box-shadow:0 0 #ffffffe6!important}to{border-color:#0003!important;box-shadow:0 0 0 8px #fff0!important}}.giat-swatch-check{opacity:0;animation:.5s cubic-bezier(.175,.885,.32,1.275) forwards giat-check-fade;transform:translate(-50%,-50%)scale(.5);pointer-events:none!important;filter:drop-shadow(0 1px 1.5px #000000a6)!important;z-index:5!important;width:14px!important;height:14px!important;position:absolute!important;top:50%!important;left:50%!important}@keyframes giat-check-fade{0%{opacity:0;transform:translate(-50%,-50%)scale(.4)rotate(-20deg)}30%{opacity:1;transform:translate(-50%,-50%)scale(1.15)rotate(0)}75%{opacity:1;transform:translate(-50%,-50%)scale(1)rotate(0)}to{opacity:0;transform:translate(-50%,-50%)scale(.8)rotate(10deg)}}.giat-batch-md-menu{z-index:10000;-webkit-backdrop-filter:blur(20px);border-radius:14px;flex-direction:column;gap:3px;padding:8px;transition:background-color .2s,border-color .2s,box-shadow .2s;animation:.22s cubic-bezier(.16,1,.3,1) giatMdPopIn;display:flex;position:fixed;transform:translateZ(0)}@keyframes giatMdPopIn{0%{opacity:0;transform:translateY(8px)scale(.94)}to{opacity:1;transform:translateY(0)scale(1)}}.giat-batch-md-menu.giat-theme-light{background:#fffffff5!important;border:1px solid #0000001a!important;box-shadow:0 10px 36px #00000024,0 2px 8px #0000000d!important}.giat-batch-md-menu.giat-theme-light .giat-batch-md-item{color:#3c4043!important}.giat-batch-md-menu.giat-theme-light .giat-batch-md-item:hover{transform:translate(3px);color:#1a73e8!important;background:#1a73e814!important}.giat-batch-md-menu.giat-theme-light .giat-batch-md-divider{background:#00000012!important}.giat-batch-md-menu.giat-theme-dark{background:#202124f5!important;border:1px solid #ffffff29!important;box-shadow:0 12px 40px #00000080,0 2px 10px #0000004d!important}.giat-batch-md-menu.giat-theme-dark .giat-batch-md-item{color:#e8eaed!important}.giat-batch-md-menu.giat-theme-dark .giat-batch-md-item:hover{transform:translate(3px);color:#8ab4f8!important;background:#8ab4f81f!important}.giat-batch-md-menu.giat-theme-dark .giat-batch-md-divider{background:#ffffff1a!important}.giat-batch-md-item{cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:8px 14px;font-family:SFProText,Roboto,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:13px;font-weight:500;transition:background-color .15s,color .15s,transform .15s cubic-bezier(.16,1,.3,1);display:flex}.giat-batch-md-item .giat-btn-svg{flex-shrink:0;transition:transform .18s cubic-bezier(.16,1,.3,1);width:15px!important;height:15px!important;margin-right:2px!important}.giat-batch-md-item:hover .giat-btn-svg{transform:scale(1.15)}.giat-batch-md-divider{border:none;height:1px;margin:4px 6px}.giat-btn-svg,.giat-inline-svg,.giat-pill-svg,.giat-exif-svg,.giat-toast-svg{vertical-align:-.18em!important;color:currentColor!important;flex-shrink:0!important;width:1.1em!important;min-width:14px!important;height:1.1em!important;min-height:14px!important;margin-right:5px!important;line-height:1!important;display:inline-block!important}.giat-btn-svg path,.giat-inline-svg path,.giat-pill-svg path,.giat-exif-svg path,.giat-toast-svg path{transition:fill .15s,stroke .15s}.giat-serp-rank-badge{z-index:10;-webkit-backdrop-filter:blur(8px);pointer-events:none;opacity:0;border:1px solid #ffffff38;border-radius:4px;padding:2px 6px;font-family:SFProText,Roboto,system-ui,-apple-system,sans-serif;font-size:11px;font-weight:700;line-height:1.1;transition:transform .15s,opacity .15s,background-color .2s,color .2s;display:inline-block;position:absolute;top:6px;right:6px;box-shadow:0 2px 6px #00000059;color:var(--giat-custom-color,#ffffffeb)!important;background:var(--giat-custom-bg,#0000008c)!important}body.giat-pos-br .giat-serp-rank-badge{inset:6px 6px auto auto!important}body.giat-pos-bl .giat-serp-rank-badge{inset:6px auto auto 6px!important}body.giat-pos-tl .giat-serp-rank-badge{inset:auto auto 6px 6px!important}body.giat-pos-tr .giat-serp-rank-badge{inset:auto 6px 6px auto!important}div[data-giat-result]:hover .giat-serp-rank-badge{opacity:1;border-color:#fff6;transform:scale(1.05)}.giat-rank-badge-warning{color:#f9ab00!important;pointer-events:auto!important;cursor:help!important;background:#202124cc!important;border-color:#f9ab008c!important}div[data-giat-result]:hover .giat-rank-badge-warning{border-color:#f9ab00d9!important}.giat-rank-warning-svg{vertical-align:-.15em!important;color:#f9ab00!important;width:12px!important;height:12px!important;margin-right:3px!important;display:inline-block!important}body.giat-batch-active .giat-serp-rank-badge,body.giat-serp-rank-always .giat-serp-rank-badge{opacity:1!important}body.giat-serp-rank-never:not(.giat-batch-active) .giat-serp-rank-badge{opacity:0!important}[data-giat-result] img,[data-giat-result] h3,[data-giat-result] a:not(.giat-dims),[data-giat-result] [role=heading]{transition:opacity .22s cubic-bezier(.4,0,.2,1),filter .22s cubic-bezier(.4,0,.2,1),box-shadow .22s,color .22s!important}body.giat-visited-enabled.giat-visited-mode-dim_desaturate [data-giat-result].giat-visited img{opacity:.62!important;filter:grayscale(28%)brightness(.92)!important}body.giat-visited-enabled.giat-visited-mode-dim_desaturate [data-giat-result].giat-visited h3,body.giat-visited-enabled.giat-visited-mode-dim_desaturate [data-giat-result].giat-visited [role=heading],body.giat-visited-enabled.giat-visited-mode-dim_desaturate [data-giat-result].giat-visited a:not(.giat-dims),body.giat-visited-enabled.giat-visited-mode-dim_desaturate [data-giat-result].giat-visited span:not(.giat-dims *):not(.giat-serp-rank-badge){opacity:.62!important}body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited{position:relative;border-radius:16px!important}body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited img[data-giat-thumb-img],body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited div:first-child>a>div img,body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited img:not([width=\"16\"]):not([height=\"16\"]):not([width=\"18\"]):not([height=\"18\"]):not([width=\"20\"]):not([height=\"20\"]):not([width=\"24\"]):not([height=\"24\"]):not([src*=favicon]):not([src*=favicons]){outline-offset:-2.5px!important;border-radius:16px!important;outline:2.5px solid #c58af9!important;box-shadow:0 0 12px #c58af973!important}:is(body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited div:has(>img[data-giat-thumb-img]),body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited a:has(img[data-giat-thumb-img])){border-radius:16px!important}body.giat-visited-enabled [data-giat-result] img[src*=favicon],body.giat-visited-enabled [data-giat-result] img[src*=favicons],body.giat-visited-enabled [data-giat-result] img[width=\"16\"],body.giat-visited-enabled [data-giat-result] img[width=\"18\"],body.giat-visited-enabled [data-giat-result] img[width=\"20\"],body.giat-visited-enabled [data-giat-result] img[width=\"24\"]{box-shadow:none!important;border:none!important;border-radius:50%!important;outline:none!important}body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited h3,body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited [role=heading],body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited a:not(.giat-dims){color:#c58af9!important}body.giat-visited-enabled.giat-visited-mode-visited_badge [data-giat-result].giat-visited{position:relative}body.giat-visited-enabled.giat-visited-mode-visited_badge [data-giat-result].giat-visited:after{content:\"✓\";z-index:18;text-align:center;color:#c58af9;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);pointer-events:none;background:#202124e0;border:1px solid #ffffff38;border-radius:50%;width:19px;height:19px;font-size:11px;font-weight:700;line-height:19px;transition:opacity .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1);animation:.25s cubic-bezier(.175,.885,.32,1.275) giat-visited-pop;position:absolute;top:6px;right:6px;box-shadow:0 2px 6px #00000059}body.giat-visited-enabled.giat-visited-mode-visited_badge.giat-serp-rank-always [data-giat-result].giat-visited:after,body.giat-visited-enabled.giat-visited-mode-visited_badge.giat-batch-active [data-giat-result].giat-visited:after{display:none!important}body.giat-visited-enabled.giat-visited-mode-visited_badge.giat-serp-rank-hover [data-giat-result].giat-visited:hover:after{transform:scale(.7);opacity:0!important}body.giat-visited-enabled.giat-visited-mode-visited_badge [data-giat-result].giat-visited .giat-serp-rank-badge{color:#c58af9!important;background:#202124e6!important;border-color:#c58af9bf!important;box-shadow:0 0 8px #c58af94d!important}body.giat-visited-enabled.giat-visited-mode-visited_badge [data-giat-result].giat-visited .giat-serp-rank-badge:before{content:\"✓ \";color:#c58af9;margin-right:2px;font-weight:800}@keyframes giat-visited-pop{0%{opacity:0;transform:scale(.4)}to{opacity:1;transform:scale(1)}}body.giat-visited-enabled.giat-visited-mode-subtle_dim [data-giat-result].giat-visited img,body.giat-visited-enabled.giat-visited-mode-subtle_dim [data-giat-result].giat-visited h3,body.giat-visited-enabled.giat-visited-mode-subtle_dim [data-giat-result].giat-visited [role=heading],body.giat-visited-enabled.giat-visited-mode-subtle_dim [data-giat-result].giat-visited a:not(.giat-dims){opacity:.8!important}body.giat-visited-enabled [data-giat-result].giat-visited:hover img{opacity:1!important;filter:none!important}body.giat-visited-enabled [data-giat-result].giat-visited:hover h3,body.giat-visited-enabled [data-giat-result].giat-visited:hover [role=heading],body.giat-visited-enabled [data-giat-result].giat-visited:hover a:not(.giat-dims),body.giat-visited-enabled [data-giat-result].giat-visited:hover span:not(.giat-dims *):not(.giat-serp-rank-badge){opacity:1!important}@media (prefers-color-scheme:light){body.giat-visited-enabled.giat-visited-mode-dim_desaturate [data-giat-result].giat-visited img{opacity:.65!important;filter:grayscale(30%)contrast(.95)!important}body.giat-visited-enabled.giat-visited-mode-dim_desaturate [data-giat-result].giat-visited h3,body.giat-visited-enabled.giat-visited-mode-dim_desaturate [data-giat-result].giat-visited [role=heading],body.giat-visited-enabled.giat-visited-mode-dim_desaturate [data-giat-result].giat-visited a:not(.giat-dims){opacity:.65!important}body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited img[data-giat-thumb-img],body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited div:first-child>a>div img,body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited img:not([width=\"16\"]):not([height=\"16\"]):not([width=\"18\"]):not([height=\"18\"]):not([width=\"20\"]):not([height=\"20\"]):not([width=\"24\"]):not([height=\"24\"]):not([src*=favicon]):not([src*=favicons]){outline-color:#681da8!important;box-shadow:0 1px 6px #681da859!important}body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited h3,body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited [role=heading],body.giat-visited-enabled.giat-visited-mode-purple_border [data-giat-result].giat-visited a:not(.giat-dims){color:#681da8!important}body.giat-visited-enabled.giat-visited-mode-visited_badge [data-giat-result].giat-visited:after{color:#681da8;background:#ffffffeb;border-color:#0000001f;box-shadow:0 2px 6px #00000026}body.giat-visited-enabled.giat-visited-mode-visited_badge [data-giat-result].giat-visited .giat-serp-rank-badge{color:#681da8!important;background:#ffffffeb!important;border-color:#681da8bf!important;box-shadow:0 1px 5px #681da840!important}body.giat-visited-enabled.giat-visited-mode-visited_badge [data-giat-result].giat-visited .giat-serp-rank-badge:before{color:#681da8}}.giat-btn-success-feedback{transform:scale(.96);color:#c58af9!important;background:#c58af947!important;border-color:#c58af9!important;transition:all .2s cubic-bezier(.175,.885,.32,1.275)!important}"); var bgModes = [ { translationKey: "bgDarkCheckerboard", class: "giat-bg-dark-grid" }, { translationKey: "bgWhite", class: "giat-bg-white" }, { translationKey: "bgCheckerboard", class: "giat-bg-grid" }, { translationKey: "bgGray", class: "giat-bg-gray" }, { translationKey: "bgBlack", class: "giat-bg-black" } ]; var ConfigManager = class { enableThumbResolution; enableLightboxResolution; enableLightboxDownload; enableLightboxCopy; enableLightboxB64; enableThumbDownload; enableThumbCopy; enableThumbB64; enableHoverInfo; enableLightboxMime; enableThumbFileSize; enableLightboxFileSize; enableThumbBadges; enableLightboxDate; enableLightboxExif; enableThumbMime; enableThumbTitleTooltip; enableThumbLens; enableLightboxLens; enableBatchSelect = true; batchDownloadMode = "direct"; serpRankMode = "hover"; enableThumbTineye; enableLightboxTineye; enableThumbAi; enableLightboxAi; aiSearchPrompt; currentBgIndex; userLanguage; uiTheme; clickAction; enableWebpConversion; webpConversionFormat; webpConversionQuality; filenamePatternMode = "original"; customFilenameTemplate = "{query}_{index}"; labelPosition; labelSize; thumbBtnSize; enableLightboxKeys; lightboxPrevKey; lightboxNextKey; lightboxCloseKey; enableExperimentalAiUpload; customBgColor; customTextColor; customBgOpacity; enableLightboxForceBlob; enableUrlOptimization; enableThumbPhotopea; enableLightboxPhotopea; enableThumbVectorpea; enableLightboxVectorpea; enableThumbYandex; enableLightboxYandex; enableThumbBing; enableLightboxBing; enableLightboxColorAnalysis; enableYouTubeAutoplay; enableVisitedMark = false; visitedStyleMode = "dim_desaturate"; ctrlClickAction = "raw_image"; constructor() { this.enableThumbResolution = true; this.enableLightboxResolution = true; this.enableLightboxDownload = true; this.enableLightboxCopy = true; this.enableLightboxB64 = true; this.enableThumbDownload = true; this.enableThumbCopy = true; this.enableThumbB64 = false; this.enableHoverInfo = false; this.enableLightboxMime = true; this.enableThumbFileSize = true; this.enableLightboxFileSize = true; this.enableThumbBadges = true; this.enableLightboxDate = true; this.enableLightboxExif = true; this.enableThumbMime = false; this.enableThumbTitleTooltip = true; this.enableThumbLens = true; this.enableLightboxLens = true; this.enableBatchSelect = true; this.enableThumbTineye = false; this.enableLightboxTineye = false; this.enableThumbAi = true; this.enableLightboxAi = true; this.aiSearchPrompt = ""; this.currentBgIndex = 2; this.userLanguage = "auto"; this.uiTheme = "auto"; this.clickAction = "lightbox"; this.enableWebpConversion = false; this.webpConversionFormat = "jpeg"; this.webpConversionQuality = 95; this.labelPosition = "bottom-right"; this.labelSize = "6"; this.thumbBtnSize = "6"; this.enableLightboxKeys = true; this.lightboxPrevKey = "ArrowLeft"; this.lightboxNextKey = "ArrowRight"; this.lightboxCloseKey = "Escape"; this.enableExperimentalAiUpload = true; this.customBgColor = ""; this.customTextColor = ""; this.customBgOpacity = 60; this.enableLightboxForceBlob = true; this.enableUrlOptimization = true; this.enableThumbPhotopea = false; this.enableLightboxPhotopea = false; this.enableThumbVectorpea = false; this.enableLightboxVectorpea = false; this.enableThumbYandex = false; this.enableLightboxYandex = false; this.enableThumbBing = false; this.enableLightboxBing = false; this.enableLightboxColorAnalysis = true; this.enableYouTubeAutoplay = true; this.loadConfig(); } loadConfig() { const safeGet = (key, def) => { if (typeof GM_getValue !== "undefined") try { return GM_getValue(key, def); } catch (e) { return def; } return def; }; this.enableThumbResolution = safeGet("giat-enable-thumb-resolution", true); this.enableLightboxResolution = safeGet("giat-enable-lightbox-resolution", true); this.enableLightboxDownload = safeGet("giat-enable-lightbox-download", true); this.enableLightboxCopy = safeGet("giat-enable-lightbox-copy", true); this.enableLightboxB64 = safeGet("giat-enable-lightbox-b64", true); this.enableThumbDownload = safeGet("giat-enable-thumb-download", true); this.enableThumbCopy = safeGet("giat-enable-thumb-copy", true); this.enableThumbB64 = safeGet("giat-enable-thumb-b64", false); this.enableHoverInfo = safeGet("giat-enable-hover-info", false); this.enableLightboxMime = safeGet("giat-enable-lightbox-mime", true); this.enableThumbFileSize = safeGet("giat-enable-thumb-file-size", true); this.enableLightboxFileSize = safeGet("giat-enable-lightbox-file-size", true); this.enableThumbBadges = safeGet("giat-enable-thumb-badges", true); this.enableLightboxDate = safeGet("giat-enable-lightbox-date", true); this.enableLightboxExif = safeGet("giat-enable-lightbox-exif", true); this.enableThumbMime = safeGet("giat-enable-thumb-mime", false); this.enableThumbTitleTooltip = safeGet("giat-enable-thumb-title-tooltip", true); this.enableThumbLens = safeGet("giat-enable-thumb-lens", true); this.enableLightboxLens = safeGet("giat-enable-lightbox-lens", true); this.enableBatchSelect = safeGet("giat-enable-batch-select", true); this.batchDownloadMode = safeGet("giat-batch-download-mode", "direct"); this.enableThumbTineye = safeGet("giat-enable-thumb-tineye", false); this.enableLightboxTineye = safeGet("giat-enable-lightbox-tineye", false); this.enableThumbAi = safeGet("giat-enable-thumb-ai", true); this.enableLightboxAi = safeGet("giat-enable-lightbox-ai", true); this.aiSearchPrompt = safeGet("giat-ai-search-prompt", ""); this.currentBgIndex = safeGet("giat-bg-index", 2); this.userLanguage = safeGet("giat-user-language", "auto"); this.uiTheme = safeGet("giat-ui-theme", "auto"); this.clickAction = safeGet("giat-click-action", "lightbox"); this.enableWebpConversion = safeGet("giat-enable-webp-conversion", false); this.webpConversionFormat = safeGet("giat-webp-conversion-format", "jpeg"); this.webpConversionQuality = safeGet("giat-webp-conversion-quality", 95); this.filenamePatternMode = safeGet("giat-filename-pattern-mode", "original"); this.customFilenameTemplate = safeGet("giat-custom-filename-template", "{query}_{index}"); this.labelPosition = safeGet("giat-label-position", "bottom-right"); let storedSize = safeGet("giat-label-size", "6"); this.labelSize = storedSize === "small" ? "5" : storedSize === "medium" ? "6" : storedSize === "large" ? "7" : storedSize; this.thumbBtnSize = safeGet("giat-thumb-btn-size", "6"); this.enableLightboxKeys = safeGet("giat-enable-lightbox-keys", true); this.lightboxPrevKey = safeGet("giat-lightbox-prev-key", "ArrowLeft"); this.lightboxNextKey = safeGet("giat-lightbox-next-key", "ArrowRight"); this.lightboxCloseKey = safeGet("giat-lightbox-close-key", "Escape"); this.enableExperimentalAiUpload = safeGet("giat-enable-experimental-ai-upload", true); this.customBgColor = safeGet("giat-custom-bg-color", ""); this.customTextColor = safeGet("giat-custom-text-color", ""); this.customBgOpacity = safeGet("giat-custom-bg-opacity", 60); this.enableLightboxForceBlob = safeGet("giat-enable-lightbox-force-blob", true); this.enableUrlOptimization = safeGet("giat-enable-url-optimization", true); this.enableThumbPhotopea = safeGet("giat-enable-thumb-photopea", false); this.enableLightboxPhotopea = safeGet("giat-enable-lightbox-photopea", false); this.enableThumbVectorpea = safeGet("giat-enable-thumb-vectorpea", false); this.enableLightboxVectorpea = safeGet("giat-enable-lightbox-vectorpea", false); this.enableThumbYandex = safeGet("giat-enable-thumb-yandex", false); this.enableLightboxYandex = safeGet("giat-enable-lightbox-yandex", false); this.enableThumbBing = safeGet("giat-enable-thumb-bing", false); this.enableLightboxBing = safeGet("giat-enable-lightbox-bing", false); this.enableLightboxColorAnalysis = safeGet("giat-enable-lightbox-color-analysis", true); this.enableYouTubeAutoplay = safeGet("giat-enable-youtube-autoplay", true); this.enableVisitedMark = safeGet("giat-enable-visited-mark", false); this.visitedStyleMode = safeGet("giat-visited-style-mode", "dim_desaturate"); this.ctrlClickAction = safeGet("giat-ctrl-click-action", "raw_image"); } save() { if (typeof GM_setValue === "undefined") return; GM_setValue("giat-ctrl-click-action", this.ctrlClickAction); GM_setValue("giat-enable-visited-mark", this.enableVisitedMark); GM_setValue("giat-visited-style-mode", this.visitedStyleMode); GM_setValue("giat-enable-thumb-resolution", this.enableThumbResolution); GM_setValue("giat-enable-lightbox-resolution", this.enableLightboxResolution); GM_setValue("giat-enable-lightbox-download", this.enableLightboxDownload); GM_setValue("giat-enable-lightbox-copy", this.enableLightboxCopy); GM_setValue("giat-enable-lightbox-b64", this.enableLightboxB64); GM_setValue("giat-enable-thumb-download", this.enableThumbDownload); GM_setValue("giat-enable-thumb-copy", this.enableThumbCopy); GM_setValue("giat-enable-thumb-b64", this.enableThumbB64); GM_setValue("giat-enable-hover-info", this.enableHoverInfo); GM_setValue("giat-enable-lightbox-mime", this.enableLightboxMime); GM_setValue("giat-enable-thumb-file-size", this.enableThumbFileSize); GM_setValue("giat-enable-lightbox-file-size", this.enableLightboxFileSize); GM_setValue("giat-enable-thumb-badges", this.enableThumbBadges); GM_setValue("giat-enable-lightbox-date", this.enableLightboxDate); GM_setValue("giat-enable-lightbox-exif", this.enableLightboxExif); GM_setValue("giat-enable-thumb-mime", this.enableThumbMime); GM_setValue("giat-enable-thumb-title-tooltip", this.enableThumbTitleTooltip); GM_setValue("giat-enable-thumb-lens", this.enableThumbLens); GM_setValue("giat-enable-lightbox-lens", this.enableLightboxLens); GM_setValue("giat-enable-batch-select", this.enableBatchSelect); GM_setValue("giat-batch-download-mode", this.batchDownloadMode); GM_setValue("giat-enable-thumb-tineye", this.enableThumbTineye); GM_setValue("giat-enable-lightbox-tineye", this.enableLightboxTineye); GM_setValue("giat-enable-thumb-ai", this.enableThumbAi); GM_setValue("giat-enable-lightbox-ai", this.enableLightboxAi); GM_setValue("giat-ai-search-prompt", this.aiSearchPrompt); GM_setValue("giat-bg-index", this.currentBgIndex); GM_setValue("giat-user-language", this.userLanguage); GM_setValue("giat-ui-theme", this.uiTheme); GM_setValue("giat-click-action", this.clickAction); GM_setValue("giat-enable-webp-conversion", this.enableWebpConversion); GM_setValue("giat-webp-conversion-format", this.webpConversionFormat); GM_setValue("giat-webp-conversion-quality", this.webpConversionQuality); GM_setValue("giat-filename-pattern-mode", this.filenamePatternMode); GM_setValue("giat-custom-filename-template", this.customFilenameTemplate); GM_setValue("giat-label-position", this.labelPosition); GM_setValue("giat-label-size", this.labelSize); GM_setValue("giat-thumb-btn-size", this.thumbBtnSize); GM_setValue("giat-enable-lightbox-keys", this.enableLightboxKeys); GM_setValue("giat-lightbox-prev-key", this.lightboxPrevKey); GM_setValue("giat-lightbox-next-key", this.lightboxNextKey); GM_setValue("giat-lightbox-close-key", this.lightboxCloseKey); GM_setValue("giat-enable-experimental-ai-upload", this.enableExperimentalAiUpload); GM_setValue("giat-custom-bg-color", this.customBgColor); GM_setValue("giat-custom-text-color", this.customTextColor); GM_setValue("giat-custom-bg-opacity", this.customBgOpacity); GM_setValue("giat-enable-lightbox-force-blob", this.enableLightboxForceBlob); GM_setValue("giat-enable-url-optimization", this.enableUrlOptimization); GM_setValue("giat-enable-thumb-photopea", this.enableThumbPhotopea); GM_setValue("giat-enable-lightbox-photopea", this.enableLightboxPhotopea); GM_setValue("giat-enable-thumb-vectorpea", this.enableThumbVectorpea); GM_setValue("giat-enable-lightbox-vectorpea", this.enableLightboxVectorpea); GM_setValue("giat-enable-thumb-yandex", this.enableThumbYandex); GM_setValue("giat-enable-lightbox-yandex", this.enableLightboxYandex); GM_setValue("giat-enable-thumb-bing", this.enableThumbBing); GM_setValue("giat-enable-lightbox-bing", this.enableLightboxBing); GM_setValue("giat-enable-lightbox-color-analysis", this.enableLightboxColorAnalysis); GM_setValue("giat-enable-youtube-autoplay", this.enableYouTubeAutoplay); } reset() { this.enableThumbResolution = true; this.enableLightboxResolution = true; this.enableLightboxCopy = true; this.enableLightboxB64 = true; this.enableThumbDownload = true; this.enableThumbCopy = true; this.enableThumbB64 = false; this.enableHoverInfo = false; this.enableLightboxMime = true; this.enableThumbFileSize = true; this.enableLightboxFileSize = true; this.enableThumbBadges = true; this.enableLightboxDate = true; this.enableLightboxExif = true; this.enableThumbMime = false; this.enableThumbTitleTooltip = true; this.enableThumbLens = true; this.enableLightboxLens = true; this.enableThumbTineye = false; this.enableLightboxTineye = false; this.enableYouTubeAutoplay = true; this.enableThumbAi = true; this.enableLightboxAi = true; this.aiSearchPrompt = ""; this.currentBgIndex = 2; this.userLanguage = "auto"; this.uiTheme = "auto"; this.clickAction = "lightbox"; this.ctrlClickAction = "raw_image"; this.enableWebpConversion = false; this.webpConversionFormat = "jpeg"; this.webpConversionQuality = 95; this.filenamePatternMode = "original"; this.customFilenameTemplate = "{query}_{index}"; this.labelPosition = "bottom-right"; this.labelSize = "6"; this.thumbBtnSize = "6"; this.enableLightboxKeys = true; this.lightboxPrevKey = "ArrowLeft"; this.lightboxNextKey = "ArrowRight"; this.lightboxCloseKey = "Escape"; this.enableExperimentalAiUpload = true; this.customBgColor = ""; this.customTextColor = ""; this.customBgOpacity = 60; this.enableLightboxForceBlob = true; this.enableUrlOptimization = true; this.enableThumbPhotopea = false; this.enableLightboxPhotopea = false; this.enableThumbVectorpea = false; this.enableLightboxVectorpea = false; this.enableThumbYandex = false; this.enableLightboxYandex = false; this.enableThumbBing = false; this.enableLightboxBing = false; this.enableLightboxColorAnalysis = true; this.enableVisitedMark = false; this.visitedStyleMode = "dim_desaturate"; this.save(); } applyGlobalSettings(lightboxDownloadBtn, lightboxCopyImgBtn, lightboxCopyB64Btn, lightboxWrap, lightboxLensBtn = null, lightboxTineyeBtn = null, lightboxAiBtn = null, lightboxPhotopeaBtn = null, lightboxVectorpeaBtn = null, lightboxYandexBtn = null, lightboxBingBtn = null) { const opacityDecimal = this.customBgOpacity / 100; const bgRgba = hexToRgba(this.customBgColor.trim() || "rgba(32, 33, 36, 0.6)", opacityDecimal); document.documentElement.style.setProperty("--giat-custom-bg", bgRgba); document.documentElement.style.setProperty("--giat-custom-color", this.customTextColor.trim() || "#ffffff"); document.documentElement.style.setProperty("--giat-thumb-download-display", this.enableThumbDownload ? "flex" : "none"); document.documentElement.style.setProperty("--giat-thumb-copy-display", this.enableThumbCopy ? "flex" : "none"); document.documentElement.style.setProperty("--giat-thumb-b64-display", this.enableThumbB64 ? "flex" : "none"); document.documentElement.style.setProperty("--giat-thumb-lens-display", this.enableThumbLens ? "flex" : "none"); document.documentElement.style.setProperty("--giat-thumb-tineye-display", this.enableThumbTineye ? "flex" : "none"); document.documentElement.style.setProperty("--giat-thumb-ai-display", this.enableThumbAi ? "flex" : "none"); document.documentElement.style.setProperty("--giat-thumb-photopea-display", this.enableThumbPhotopea ? "flex" : "none"); document.documentElement.style.setProperty("--giat-thumb-vectorpea-display", this.enableThumbVectorpea ? "flex" : "none"); document.documentElement.style.setProperty("--giat-thumb-yandex-display", this.enableThumbYandex ? "flex" : "none"); document.documentElement.style.setProperty("--giat-thumb-bing-display", this.enableThumbBing ? "flex" : "none"); document.documentElement.style.setProperty("--giat-dims-initial-opacity", this.enableHoverInfo ? "0" : "1"); document.documentElement.style.setProperty("--giat-lightbox-mime-display", this.enableLightboxMime ? "block" : "none"); document.documentElement.style.setProperty("--giat-thumb-file-size-display", this.enableThumbFileSize ? "inline-block" : "none"); document.documentElement.style.setProperty("--giat-thumb-mime-display", this.enableThumbMime ? "inline-block" : "none"); document.documentElement.style.setProperty("--giat-thumb-date-display", this.enableThumbBadges ? "inline-block" : "none"); document.documentElement.style.setProperty("--giat-native-date-display", this.enableThumbBadges ? "none" : "flex"); if (typeof document !== "undefined" && document.body) { document.body.classList.remove("giat-pos-br", "giat-pos-bl", "giat-pos-tr", "giat-pos-tl"); const posClass = `giat-pos-${this.labelPosition === "bottom-right" ? "br" : this.labelPosition === "bottom-left" ? "bl" : this.labelPosition === "top-right" ? "tr" : "tl"}`; document.body.classList.add(posClass); document.body.classList.remove("giat-size-small", "giat-size-medium", "giat-size-large", "giat-size-1", "giat-size-2", "giat-size-3", "giat-size-4", "giat-size-5", "giat-size-6", "giat-size-7", "giat-size-8", "giat-size-9", "giat-size-10", "giat-size-11", "giat-size-12"); document.body.classList.add(`giat-size-${this.labelSize}`); document.body.classList.remove("giat-thumb-btn-size-1", "giat-thumb-btn-size-2", "giat-thumb-btn-size-3", "giat-thumb-btn-size-4", "giat-thumb-btn-size-5", "giat-thumb-btn-size-6", "giat-thumb-btn-size-7", "giat-thumb-btn-size-8", "giat-thumb-btn-size-9", "giat-thumb-btn-size-10", "giat-thumb-btn-size-11", "giat-thumb-btn-size-12"); document.body.classList.add(`giat-thumb-btn-size-${this.thumbBtnSize}`); document.body.classList.toggle("giat-visited-enabled", this.enableVisitedMark); document.body.classList.remove("giat-visited-mode-dim_desaturate", "giat-visited-mode-purple_border", "giat-visited-mode-visited_badge", "giat-visited-mode-subtle_dim"); document.body.classList.add(`giat-visited-mode-${this.visitedStyleMode || "dim_desaturate"}`); } if (lightboxDownloadBtn) lightboxDownloadBtn.style.setProperty("display", this.enableLightboxDownload ? "flex" : "none", this.enableLightboxDownload ? "" : "important"); if (lightboxCopyImgBtn) lightboxCopyImgBtn.style.setProperty("display", this.enableLightboxCopy ? "flex" : "none", this.enableLightboxCopy ? "" : "important"); if (lightboxCopyB64Btn) lightboxCopyB64Btn.style.setProperty("display", this.enableLightboxB64 ? "flex" : "none", this.enableLightboxB64 ? "" : "important"); if (lightboxLensBtn) lightboxLensBtn.style.setProperty("display", this.enableLightboxLens ? "flex" : "none", this.enableLightboxLens ? "" : "important"); if (lightboxTineyeBtn) lightboxTineyeBtn.style.setProperty("display", this.enableLightboxTineye ? "flex" : "none", this.enableLightboxTineye ? "" : "important"); if (lightboxAiBtn) lightboxAiBtn.style.setProperty("display", this.enableLightboxAi ? "flex" : "none", this.enableLightboxAi ? "" : "important"); if (lightboxPhotopeaBtn) lightboxPhotopeaBtn.style.setProperty("display", this.enableLightboxPhotopea ? "flex" : "none", this.enableLightboxPhotopea ? "" : "important"); if (lightboxVectorpeaBtn) lightboxVectorpeaBtn.style.setProperty("display", this.enableLightboxVectorpea ? "flex" : "none", this.enableLightboxVectorpea ? "" : "important"); if (lightboxYandexBtn) lightboxYandexBtn.style.setProperty("display", this.enableLightboxYandex ? "flex" : "none", this.enableLightboxYandex ? "" : "important"); if (lightboxBingBtn) lightboxBingBtn.style.setProperty("display", this.enableLightboxBing ? "flex" : "none", this.enableLightboxBing ? "" : "important"); if (lightboxWrap) { bgModes.forEach((m) => lightboxWrap.classList.remove(m.class)); lightboxWrap.classList.add(bgModes[this.currentBgIndex].class); } if (document.body) { document.body.classList.remove("giat-serp-rank-hover", "giat-serp-rank-always", "giat-serp-rank-never"); document.body.classList.add(`giat-serp-rank-${this.serpRankMode || "hover"}`); } } }; var config = new ConfigManager(); function isPageDark() { if (typeof document === "undefined" || !document.body) return true; const bodyBg = window.getComputedStyle(document.body).backgroundColor; if (!bodyBg || bodyBg === "rgba(0, 0, 0, 0)" || bodyBg === "transparent") return true; const match = bodyBg.match(/\d+/g); if (!match) return true; const r = parseInt(match[0], 10); const g = parseInt(match[1], 10); const b = parseInt(match[2], 10); return .2126 * r + .7152 * g + .0722 * b < 128; } function hexToRgba(hex, opacity) { hex = hex.trim(); if (hex.startsWith("rgba") || hex.startsWith("rgb") || hex === "transparent") return hex; if (/^#[0-9A-F]{6}$/i.test(hex)) return `rgba(${parseInt(hex.substring(1, 3), 16)}, ${parseInt(hex.substring(3, 5), 16)}, ${parseInt(hex.substring(5, 7), 16)}, ${opacity})`; if (/^#[0-9A-F]{3}$/i.test(hex)) return `rgba(${parseInt(hex.substring(1, 2).repeat(2), 16)}, ${parseInt(hex.substring(2, 3).repeat(2), 16)}, ${parseInt(hex.substring(3, 4).repeat(2), 16)}, ${opacity})`; return hex; } var svgSuccess = `<svg class="giat-toast-svg" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="m10.6 13.8l-2.15-2.15q-.275-.275-.7-.275t-.7.275t-.275.7t.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65q.275-.275.275-.7t-.275-.7t-.7-.275t-.7.275zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8"/></svg>`; var svgAlert = `<svg class="giat-toast-svg giat-toast-svg-alert" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v4m-1.637-9.409L2.257 17.125a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636-2.87L13.637 3.59a1.914 1.914 0 0 0-3.274 0M12 16h.01"/></svg>`; var svgShield = `<svg class="giat-pill-svg" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"><path fill="currentColor" d="M15.06 10.5a.75.75 0 0 0-1.12-1l-3.011 3.374l-.87-.974a.75.75 0 0 0-1.118 1l1.428 1.6a.75.75 0 0 0 1.119 0z"/><path fill="currentColor" fill-rule="evenodd" d="M12 1.25c-.937 0-1.833.307-3.277.801l-.727.25c-1.481.506-2.625.898-3.443 1.23c-.412.167-.767.33-1.052.495c-.275.16-.55.359-.737.626c-.185.263-.281.587-.341.9c-.063.324-.1.713-.125 1.16c-.048.886-.048 2.102-.048 3.678v1.601c0 6.101 4.608 9.026 7.348 10.224l.027.011c.34.149.66.288 1.027.382c.387.1.799.142 1.348.142c.55 0 .96-.042 1.348-.142c.367-.094.687-.233 1.026-.382l.028-.011c2.74-1.198 7.348-4.123 7.348-10.224V10.39c0-1.576 0-2.792-.048-3.679a9 9 0 0 0-.125-1.16c-.06-.312-.156-.636-.34-.9c-.188-.266-.463-.465-.738-.625a9 9 0 0 0-1.052-.495c-.818-.332-1.962-.724-3.443-1.23l-.727-.25c-1.444-.494-2.34-.801-3.277-.801M9.08 3.514c1.615-.552 2.262-.764 2.92-.764s1.305.212 2.92.764l.572.196c1.513.518 2.616.896 3.39 1.21c.387.158.667.29.864.404q.144.084.208.139c.038.03.053.048.055.05a.4.4 0 0 1 .032.074q.03.082.063.248a7 7 0 0 1 .1.958c.046.841.046 2.015.046 3.624v1.574c0 5.176-3.87 7.723-6.449 8.849c-.371.162-.586.254-.825.315c-.228.059-.506.095-.976.095s-.748-.036-.976-.095c-.24-.06-.454-.153-.825-.315c-2.58-1.126-6.449-3.674-6.449-8.849v-1.574c0-1.609 0-2.783.046-3.624a7 7 0 0 1 .1-.958q.032-.166.063-.248c.018-.05.03-.07.032-.074a.4.4 0 0 1 .055-.05q.064-.055.208-.14c.197-.114.477-.245.864-.402c.774-.315 1.877-.693 3.39-1.21z" clip-rule="evenodd"/></svg>`; var svgSparkles = `<svg class="giat-pill-svg" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"><path fill="currentColor" d="m19 1l-1.26 2.75L15 5l2.74 1.26L19 9l1.25-2.74L23 5l-2.75-1.25M9 4L6.5 9.5L1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5M19 15l-1.26 2.74L15 19l2.74 1.25L19 23l1.25-2.75L23 19l-2.75-1.26"/></svg>`; var translations = { en: { groupCoreEngine: "Core & System Controls", groupThumb: "Thumbnail Settings", groupLightbox: "Lightbox Settings", groupSystem: "AI & Experimental Settings", settings: "Settings", menuSettings: "GIAT Settings", enableUrlOptimization: "Enable Max Image URL Upgrades", settingsLang: "Language", langAuto: "Auto (Browser)", enableHoverInfo: "Hover to Reveal Labels & Buttons", enableThumbResolution: "Show Resolution on Thumbnail", enableThumbFileSize: "Show File Size on Thumbnail", enableThumbMime: "Show Image Type on Thumbnail", enableThumbBadges: "Combine Native Badges with Label", enableThumbDownload: "Thumbnail Download Button", enableThumbCopy: "Thumbnail Copy Button", enableThumbB64: "Thumbnail Base64 Button", enableThumbTitleTooltip: "Show Full Title Tooltip on Hover", enableThumbLens: "Thumbnail Google Lens Button", enableLightboxLens: "Lightbox Google Lens Button", enableThumbTineye: "Thumbnail TinEye Button", enableLightboxTineye: "Lightbox TinEye Button", enableThumbAi: "Thumbnail AI Mode Button", enableLightboxAi: "Lightbox AI Mode Button", tipLens: "Search image with Google Lens", tipTineye: "Search image with TinEye", tipAi: "Search image with Google AI", aiPromptLabel: "AI Search Custom Prompt", aiPromptPlaceholder: "e.g. explain the image {IMG} from {TITLE}", defaultAiPrompt: "Please describe the content, source, and background information of this image in detail.\n\n---\n* Source Title: {TITLE}\n* Source URL: {SRC}\n* Image Reference: {IMG}", defaultPromptPrefix: "Default: ", aiPromptVariablesHelp: "Supports placeholders: {TITLE} (Page Title), {SRC} (Page URL), {IMG} (Image URL)", aiPromptImgText: "Image: {filename}", aiPromptImgInfoText: "Image Info: {filename}", defaultTitleFallback: "source page", enableLightboxDownload: "Lightbox Download Button", enableLightboxCopy: "Lightbox Copy Button", enableLightboxB64: "Lightbox Base64 Button", enableLightboxResolution: "Show Resolution in Lightbox", enableLightboxMime: "Show Image Type in Lightbox", enableLightboxFileSize: "Show File Size in Lightbox", enableLightboxDate: "DateTime Forensics in Lightbox", enableLightboxExif: "Show Camera, AI & Copyright Info (EXIF/C2PA) in Lightbox", enableVisitedMark: "Mark Visited / Clicked Results", visitedStyleModeLabel: "Visited Mark Style Mode", visitedModeDim: "Dim & Desaturate (Default)", visitedModeBorder: "Classic Purple Glow Border", visitedModeBadge: "Checkmark Badge (Top-Right)", visitedModeSubtle: "Subtle Dim", clearVisitedBtn: "Clear Visited History", visitedStatsLabel: "Recorded / Limit", toastVisitedCleared: `${svgSuccess} Visited history footprint cleared!`, noteVisitedMark: "Visually distinguishes visited search results across sessions and tabs with customizable styles.", enableBatchSelect: "Enable Batch Selection & Image Download", batchSelectBtn: "Batch Select", batchSelectedCount: "Selected: {count}", batchSelectedCountWithSize: "Selected: {count} ({size})", batchSelectedCountWithSizeApprox: "Selected: {count} (approx. {size})", selectAll: "Select All", clearSelection: "Clear", exportZipBtn: "Export ZIP", exportDirectBtn: "Download Images", batchDownloadModeLabel: "Batch Download Mode", batchModeZipOption: "ZIP Archive (.zip)", batchModeDirectOption: "Direct Image Files", serpRankModeLabel: "SERP Rank Badge Display Mode", serpRankHover: "Hover (Default)", serpRankAlways: "Always Visible", serpRankNever: "Never (Hidden)", exitBatchMode: "Exit", viewMissedDetails: "View Details", missedTitle: "Batch Download Missed Items", reason403: "Access Forbidden (HTTP 403)", reasonCloudflare: "Cloudflare Protection (WAF)", reasonHotlink: "Hotlink Protection Blocked", reason404: "File Not Found (HTTP 404)", reasonTimeout: "Request Timeout", reasonUnsupported: "Unsupported Domain", openSourceUrl: "Open Source", toastZipping: "Zipping ({completed}/{total} items)...", toastSavingFiles: "Saving image files...", toastZipDownloaded: `${svgSuccess} ZIP Downloaded! ({count} images)`, toastFilesDownloaded: `${svgSuccess} Saved {count} original image files!`, exportDataBtn: "Export Data", copyMarkdownSuccess: `${svgSuccess} Copied Markdown list to clipboard!`, copyJsonSuccess: `${svgSuccess} Copied JSON data to clipboard!`, copyCsvSuccess: `${svgSuccess} Copied CSV table to clipboard!`, copyRawUrlsSuccess: `${svgSuccess} Copied raw Image URLs to clipboard!`, copyMarkdownMenu: "Copy Markdown List (.md)", downloadMarkdownMenu: "Download Markdown File (.md)", copyJsonMenu: "Copy JSON Data (.json)", downloadJsonMenu: "Download JSON File (.json)", copyCsvMenu: "Copy CSV Table (.csv)", downloadCsvMenu: "Download CSV File (.csv)", copyUrlsMenu: "Copy Plain Text URLs (for IDM)", enableLightboxKeys: "Lightbox Keyboard Navigation & Bindings", lightboxPrevKey: "Previous Image Key", lightboxNextKey: "Next Image Key", lightboxCloseKey: "Close Lightbox Key", keyPressToBind: "Press any key to bind...", lightboxBg: "Lightbox Background Theme", resetBtn: "Reset", labelPositionLabel: "Label Position", posBottomRight: "Bottom Right (Default)", posBottomLeft: "Bottom Left", posTopRight: "Top Right", posTopLeft: "Top Left", labelSizeLabel: "Label Size", thumbBtnSizeLabel: "Thumbnail Button Size", sizeSmall: "Small", sizeMedium: "Medium (Default)", sizeLarge: "Large", bgDarkCheckerboard: "Dark Checkerboard", bgWhite: "White", bgCheckerboard: "Checkerboard", bgGray: "Gray", bgBlack: "Black", tipDownload: "Download original image", tipCopy: "Copy image to clipboard", tipExifCopied: "Copied EXIF parameter: ", tipB64: "Copy image as Base64", btnWatchVideo: "Watch Video", btnBackToCover: "Back to Cover", hudTitle: "Lightbox Shortcut Guide", resetZoom: "Reset Zoom & Pan", hudClickZoom: "Single Click: 2.5x Zoom In / Reset", hudDragPan: "Drag: Pan Image Details", hudWheelZoom: "Wheel / Pinch: Smooth Zooming", toggleHud: "Shortcut Guide", uiThemeLabel: "Interface Theme", themeAuto: "Auto Detect", themeDark: "Dark", themeLight: "Light", clickActionLabel: "Label Click Action", actionLightbox: "Open Lightbox", actionTab: "Open in New Tab", ctrlClickActionLabel: "Thumbnail Ctrl+Click & Middle-Click", ctrlActionRawImage: "Open Original Image in New Tab", ctrlActionGoogleTab: "Native Google Preview Tab", noteCtrlClickAction: "Configure whether Ctrl+Left Click or Middle-Click on thumbnail images opens the original full-res image directly in a new tab or the native Google preview tab.", groupWebpConversion: "Download & Storage Settings", enableWebpConversion: "Convert WebP on Download", webpConversionFormat: "Conversion Format", webpConversionQuality: "JPEG Quality", groupFilenamePattern: "Download Filename Pattern", filenamePatternMode: "Filename Format", patternOriginal: "Original Filename ({original})", patternQueryIndex: "Search Query + Index ({query}_{index})", patternTitleDims: "Title + Dimensions ({title}_{dims})", patternDomainTitle: "Domain + Title ([{domain}] {title})", patternCustom: "Custom Pattern...", customFilenameTemplate: "Custom Template", previewFilenameLabel: "Live Preview", chipQuery: "Query", chipDomain: "Domain", chipTitle: "Title", chipOriginal: "Original", chipDims: "Dims", chipIndex: "Index", chipDate: "Date", chipTime: "Time", noteFilenamePattern: "Customizes downloaded image filenames. Placeholders: {query}, {domain}, {title}, {original}, {dims}, {index}, {date}, {time}.", noteUrlOptimization: "Restores cropped/compressed thumbnails to raw, high-resolution original images. Disabling falls back to Google native links.", noteHoverInfo: "Hides thumbnail badges and buttons by default, fading them in only on hover to keep the page layout clean.", noteThumbTitleTooltip: "Displays the complete, unabbreviated title in a tooltip when hovering over the thumbnail title.", noteLens: "Adds Google Lens search button to thumbnails and lightboxes to search or identify images.", noteTineye: "Adds TinEye search button to search similar images.", noteAi: "Adds an AI search button utilizing Google AI Overview. Supports placeholders: {TITLE}, {SRC}, {IMG}.", noteThumbMime: "Estimated from URL; may not be 100% accurate.", noteThumbBadges: "Hides Google native licensing/video badges and integrates them into the size label for a cleaner UI.", noteBase64: "Converts and copies the image as a text-based Data URL. Useful for developers.", noteLightboxMime: "Identified via Magic Bytes (file headers); highly accurate.", noteLightboxDate: "Cross-references EXIF timestamps, server Last-Modified headers, and webpage dates to build a complete photo timeline.", noteLightboxExif: "Parses image binary headers (EXIF, IPTC, XMP, C2PA) to extract camera parameters, shooting time, copyrights, and AI generation metadata.", noteWebpConversion: "Automatically converts WebP to highly compatible JPEG/PNG upon download for older software.", noteClickAction: "Choose whether clicking the label opens the custom lightbox or opens the link directly in a new tab.", noteLightboxKeys: "Enables keyboard navigation in lightbox, scroll-locks/tracks background image, and displays keybinding subpanel for customization.", enableExperimentalAiUpload: "Experimental: Upload Image to AI Search", noteExperimentalAiUpload: "Downloads and automatically pastes the original image along with custom prompts into the AI Search input field.", uploadingToAi: "Uploading image to AI search...", uploadSuccess: "Image uploaded successfully!", uploadFail: "Image upload failed", shoot: "Shot on", digitized: "Digitized on", modify: "Updated on", lastModified: "Server Modified on", googleBadge: "Originally Labeled as", timeHistory: "Full Time History", localCameraTime: "Local Camera Time", unknownTime: "Unknown Time", cameraSpecs: "Camera & Lens Specs", camera: "Camera Model", lensModel: "Lens Model", focalLength: "Focal Length", aperture: "Aperture", shutterSpeed: "Shutter Speed", iso: "ISO Speed", software: "Processing Software", flash: "Flash Status", flashOn: "On", flashOff: "Off", gpsLocation: "Photo Location", gpsCoords: "Coordinates", gpsLink: "Open Google Maps ↗", aiBadge: `${svgSparkles} AI Generated`, aiBadgeModified: `${svgShield} AI Modified / C2PA`, aiDetail: "AI Prompt & Specs", aiDetectionMethod: "Detection Method", aiFeatureDetails: "Feature Details", toastFetching: "Fetching image data...", toastCopied: `${svgSuccess} Image copied to clipboard!`, toastCopyFail: `${svgAlert} Failed to copy image.`, toastB64Converting: "Converting to Base64...", toastB64Copied: `${svgSuccess} Base64 copied to clipboard!`, toastB64Fail: `${svgAlert} Failed to convert Base64.`, toastDownloading: "Starting download...", toastConverting: "Converting image format...", toastDlFail: `${svgAlert} Download failed, opening in new tab...`, toastLoadingImage: "Loading high-resolution image...", toastPreparingLens: "Preparing image for Google Lens...", toastPreparingTineye: "Preparing image for TinEye...", toastPreparingAi: "Preparing image for AI Search...", toastReset: "Settings reset to default.", errFailedLoad: "Failed to Load Image", errUnexpected: "An unexpected error occurred while rendering the asset.", errHotlinkTitle: "Hotlinking Blocked", errHotlinkDesc: "The target host restricts direct external access (Anti-leech protection).", errNotFoundTitle: "Image Disappeared", errNotFoundDesc: "The requested resource was deleted from the host server or the link has expired.", errServerTitle: "Host Server Error", errServerDesc: "The remote website is currently offline, overloaded, or under maintenance.", errTimeoutTitle: "Connection Timeout", errTimeoutDesc: "The target server refused the connection, or your current network blocks this host.", errOpenNewTab: "Open in New Tab", errDlBypassGuide: "Detected [{REASON}]. Click the button to open original image in a new tab to bypass block.", errOpenInNewTabBtn: "Open Image in New Tab", errPlaceholderTitle: "1x1 Placeholder Redirected", errPlaceholderDesc: "The target server redirected the image to a blank 1x1 pixel placeholder (Anti-leech protection). Press Enter or click below to visit the source page.", serverPrefix: "[Server]", enableLightboxColorAnalysis: "Show Image Color Analysis & Histogram in Lightbox", noteLightboxColorAnalysis: "Extracts dominant colors using K-Means and renders RGB histogram.", colorAnalysisTitle: "Color Analysis & Palette", toastColorCopied: "Copied color: ", tipColorClickToCopy: "Click to copy color code", toastVideoMuted: "Video Muted", toastVideoUnmuted: "Video Unmuted", hudVideoPlay: "Space / K: Play / Pause Video", hudVideoMute: "M: Mute / Unmute Video", c2paCreated: "Created", c2paCropped: "Cropped", c2paColorAdjustments: "Color Adjustments", c2paOrientation: "Orientation", c2paResized: "Resized", c2paConverted: "Converted", c2paEdited: "Edited", c2paMetadataAdded: "Metadata Added", c2paUnknown: "Unknown Action", c2paTitle: `${svgShield} Content Credentials (C2PA)`, c2paVerified: "Verified Credentials", c2paIssuer: "Signing Issuer", c2paSoftware: "Software Used", c2paDate: "Process Date", groupCustomColors: "Custom Colors & Styles", customBgColor: "Label & Button Background Color", customTextColor: "Label & Button Text/Icon Color", customBgOpacity: "Background Opacity", noteCustomColors: "Supports all CSS formats (e.g. rgba(0,0,0,0.6), #202124, transparent). Leave empty to use defaults.", enableLightboxForceBlob: "Prioritize High-Quality Original in Lightbox (Avoid WebP/AVIF)", noteLightboxForceBlob: "Fetches high-quality originals via modified headers to block CDN automatic WebP/AVIF conversion, enabling lossless display and instant downloads.", enableYouTubeAutoplay: "Auto-play YouTube Videos in Lightbox", noteYouTubeAutoplay: "Automatically plays video when opening YouTube thumbnails. Click top-right button to view original cover artwork.", pillC2pa: `${svgShield} C2PA`, pillAi: `${svgSparkles} AI`, aiHighConf: "High Confidence", aiMedConf: "Medium Confidence", toastDownloadFail: `${svgAlert} Download failed.`, toastPreparingAiSearch: "Preparing AI search...", toastPrepareAiSearchFail: `${svgAlert} Failed to prepare AI search.`, enableThumbPhotopea: "Thumbnail Photopea Button", enableLightboxPhotopea: "Lightbox Photopea Button", enableThumbVectorpea: "Thumbnail Vectorpea Button", enableLightboxVectorpea: "Lightbox Vectorpea Button", enableThumbYandex: "Thumbnail Yandex Search Button", enableLightboxYandex: "Lightbox Yandex Search Button", enableThumbBing: "Thumbnail Bing Search Button", enableLightboxBing: "Lightbox Bing Search Button", unsupportedDomainTooltip: "SERP data export supported. Raw high-res image download unavailable for this domain.", tipPhotopea: "Open image in Photopea", tipVectorpea: "Open image in Vectorpea", tipYandex: "Search image with Yandex", tipBing: "Search image with Bing", notePhotopea: "Opens the image directly in Photopea image editor.", noteVectorpea: "Opens the image directly in Vectorpea vector editor.", noteYandex: "Reverse image search via Yandex Images.", noteBing: "Reverse image search via Bing Images." }, "zh-TW": { groupCoreEngine: "核心與系統控制", groupThumb: "縮圖顯示與操作", groupLightbox: "大圖檢視與操作", groupSystem: "實驗性與 AI 配置", settings: "設定", menuSettings: "GIAT 設定", enableUrlOptimization: "啟用原圖連結升級引擎", settingsLang: "介面語言", langAuto: "自動 (跟隨瀏覽器)", enableHoverInfo: "僅在滑鼠懸停時顯示標籤與快捷按鈕", enableThumbResolution: "在縮圖上顯示解析度資訊", enableThumbFileSize: "在縮圖上顯示檔案大小", enableThumbMime: "在縮圖上顯示圖片格式", enableThumbBadges: "整合原生徽章與日期至縮圖標籤", enableThumbDownload: "顯示縮圖下載按鈕", enableThumbCopy: "顯示縮圖複製按鈕", enableThumbB64: "顯示縮圖 Base64 按鈕", enableThumbTitleTooltip: "滑鼠懸停時以工具提示顯示完整標題", enableThumbLens: "顯示縮圖 Google Lens 按鈕", enableLightboxLens: "顯示大圖 Google Lens 按鈕", enableThumbTineye: "顯示縮圖 TinEye 按鈕", enableLightboxTineye: "顯示大圖 TinEye 按鈕", enableThumbAi: "顯示縮圖 AI 搜尋按鈕", enableLightboxAi: "顯示大圖 AI 搜尋按鈕", tipLens: "以 Google Lens 搜尋圖片", tipTineye: "以 TinEye 搜尋圖片", tipAi: "以 Google AI 搜尋圖片", aiPromptLabel: "AI 搜尋自訂提示詞", aiPromptPlaceholder: "例如:請分析這張來自 {TITLE} 的圖片 {IMG}", defaultAiPrompt: "請詳細說明這張圖片的內容、來源與背景資訊。\n\n---\n* 來源網頁標題:{TITLE}\n* 來源網頁網址:{SRC}\n* 圖片參照連結:{IMG}", defaultPromptPrefix: "預設提示詞:", aiPromptVariablesHelp: "支援佔位符:{TITLE} (來源網頁標題)、{SRC} (來源網頁網址)、{IMG} (圖片網址)", aiPromptImgText: "圖片:{filename}", aiPromptImgInfoText: "圖片資訊:{filename}", defaultTitleFallback: "來源網頁", enableLightboxDownload: "顯示大圖下載按鈕", enableLightboxCopy: "顯示大圖複製按鈕", enableLightboxB64: "顯示大圖 Base64 按鈕", enableLightboxResolution: "在大圖中顯示解析度資訊", enableLightboxMime: "在大圖中顯示圖片格式", enableLightboxFileSize: "在大圖中顯示檔案大小", enableLightboxDate: "在燈箱中顯示拍攝/修圖時間鑑識標籤", enableLightboxExif: "在燈箱中顯示相機參數、AI生成與版權資訊 (EXIF/C2PA)", enableVisitedMark: "標示已造訪/已點擊項目", visitedStyleModeLabel: "已造訪標記樣式", visitedModeDim: "半透明與灰階(預設)", visitedModeBorder: "經典紫色微光邊框", visitedModeBadge: "右上角打勾徽章", visitedModeSubtle: "極簡微幅暗化", clearVisitedBtn: "清除造訪歷史紀錄", visitedStatsLabel: "已記錄數量", toastVisitedCleared: `${svgSuccess} 已成功清除造訪歷史紀錄!`, noteVisitedMark: "在搜尋結果中為曾點擊或預覽過的圖片套用專屬視覺標記,方便辨識瀏覽足跡。", enableBatchSelect: "開啟批次選取與原圖下載功能", batchSelectBtn: "批次選取", batchSelectedCount: "已選取:{count} 張", batchSelectedCountWithSize: "已選取:{count} 張 ({size})", batchSelectedCountWithSizeApprox: "已選取:{count} 張 (約 {size})", selectAll: "全選", clearSelection: "清空", exportZipBtn: "匯出 ZIP 檔", exportDirectBtn: "儲存原圖檔案", batchDownloadModeLabel: "批次下載模式", batchModeZipOption: "ZIP 壓縮包 (.zip)", batchModeDirectOption: "直接儲存原圖檔案", serpRankModeLabel: "SERP 搜尋排名編號顯示模式", serpRankHover: "滑鼠懸停時顯示 (預設推薦)", serpRankAlways: "固定常駐顯示", serpRankNever: "完全隱藏 (僅批次模式標配顯示)", exitBatchMode: "退出批次", viewMissedDetails: "查看明細", missedTitle: "批次下載闕漏明細", reason403: "原圖存取被拒 (HTTP 403)", reasonCloudflare: "Cloudflare 阻擋 (WAF)", reasonHotlink: "防盜鏈阻擋 (Hotlink Blocked)", reason404: "原圖已下架 (HTTP 404)", reasonTimeout: "連線超時 (Timeout)", reasonUnsupported: "不支援原圖網域", openSourceUrl: "開啟連結", toastZipping: "壓縮打包中... ({completed}/{total})", toastSavingFiles: "正在儲存圖片檔案...", toastZipDownloaded: `${svgSuccess} ZIP 壓縮檔已下載!(共 {count} 張原圖)`, toastFilesDownloaded: `${svgSuccess} 已儲存 {count} 張原圖檔案!`, exportDataBtn: "導出資料", copyMarkdownSuccess: `${svgSuccess} 已複製 Markdown 清單至剪貼簿!`, copyJsonSuccess: `${svgSuccess} 已複製 JSON 數據至剪貼簿!`, copyCsvSuccess: `${svgSuccess} 已複製 CSV 資料表至剪貼簿!`, copyRawUrlsSuccess: `${svgSuccess} 已複製原圖 URL 列表至剪貼簿!`, copyMarkdownMenu: "複製 Markdown 清單 (.md)", downloadMarkdownMenu: "下載 Markdown 檔案 (.md)", copyJsonMenu: "複製 JSON 數據 (.json)", downloadJsonMenu: "下載 JSON 檔案 (.json)", copyCsvMenu: "複製 CSV 資料表 (.csv)", downloadCsvMenu: "下載 CSV 檔案 (.csv)", copyUrlsMenu: "複製純原圖 URL 列表 (供 IDM/Aria2)", enableLightboxKeys: "大圖鍵盤快捷鍵導覽與按鍵自訂", lightboxPrevKey: "上一張快捷鍵", lightboxNextKey: "下一張快捷鍵", lightboxCloseKey: "關閉燈箱快捷鍵", keyPressToBind: "按下任意鍵進行綁定...", lightboxBg: "大圖檢視器背景樣式", resetBtn: "重設", labelPositionLabel: "縮圖標籤顯示位置", posBottomRight: "右下角 (預設)", posBottomLeft: "左下角", posTopRight: "右上角", posTopLeft: "左上角", labelSizeLabel: "尺寸標籤大小", thumbBtnSizeLabel: "縮圖按鈕大小", sizeSmall: "小", sizeMedium: "中 (預設)", sizeLarge: "大", bgDarkCheckerboard: "深色棋盤格", bgWhite: "白色背景", bgCheckerboard: "棋盤格", bgGray: "灰色背景", bgBlack: "黑色背景", tipDownload: "下載原始圖片", tipCopy: "複製圖片到剪貼簿", tipExifCopied: "已複製 EXIF 參數:", tipB64: "複製圖片為 Base64 字串", hudTitle: "燈箱全域手勢與熱鍵指南", resetZoom: "重置縮放與位移", hudClickZoom: "單擊圖片:2.5x 焦距放大 / 1.0x 重置", hudDragPan: "按住拖曳:平移檢視圖片細節", hudWheelZoom: "滾輪 / 雙指 Pinch:平滑無段縮放", toggleHud: "顯示/隱藏熱鍵指南", uiThemeLabel: "介面配色主題", themeAuto: "自動偵測", themeDark: "深色", themeLight: "淺色", clickActionLabel: "點擊標籤行為", actionLightbox: "開啟大圖檢視器 (燈箱)", actionTab: "在新分頁開啟大圖", ctrlClickActionLabel: "縮圖 Ctrl+點擊與中鍵動作", ctrlActionRawImage: "在新分頁開啟原始圖片", ctrlActionGoogleTab: "Google 原生預覽分頁", noteCtrlClickAction: "設定在搜尋結果縮圖上按下 Ctrl+左鍵 或 滑鼠中鍵時,要直接開啟原始大圖或是 Google 原生預覽分頁(不影響下方標題與網頁連結)。", groupWebpConversion: "下載與儲存配置", enableWebpConversion: "下載時自動轉換 WebP 格式", webpConversionFormat: "轉檔格式", webpConversionQuality: "JPEG 品質", groupFilenamePattern: "下載檔名命名規則", filenamePatternMode: "檔名樣式", patternOriginal: "原始檔名 ({original})", patternQueryIndex: "搜尋關鍵字 + 序號 ({query}_{index})", patternTitleDims: "圖片標題 + 尺寸 ({title}_{dims})", patternDomainTitle: "來源網域 + 圖片標題 ([{domain}] {title})", patternCustom: "自訂樣式... (Custom Pattern...)", customFilenameTemplate: "自訂樣板", previewFilenameLabel: "預覽範例", chipQuery: "搜尋詞", chipDomain: "來源網域", chipTitle: "圖片標題", chipOriginal: "原始檔名", chipDims: "解析度尺寸", chipIndex: "序號", chipDate: "日期", chipTime: "時間", noteFilenamePattern: "自訂下載圖片的檔名格式。支援變數:{query}、{domain}、{title}、{original}、{dims}、{index}、{date}、{time}。", noteUrlOptimization: "自動將 Google 的縮圖與裁剪圖解析還原為最高畫質原圖。停用時將退回使用 Google 原始連結。", noteHoverInfo: "預設隱藏縮圖標籤與快捷按鈕,滑鼠懸停時才淡入顯示,以維持搜尋版面極致簡潔。", noteThumbTitleTooltip: "當滑鼠移入縮圖標題時,在瀏覽器工具提示中顯示完整、未截斷的標題文字。", noteLens: "在按鈕列中加入 Google Lens 快速圖片搜尋選項,可進行圖片搜尋與辨識。", noteTineye: "在按鈕列中加入 TinEye 快速相似圖片搜尋選項。", noteAi: "加入 Google AI 搜尋(udm=50)按鈕。支援提示詞佔位符:{TITLE}、{SRC}、{IMG}。", noteThumbMime: "由連結推斷,可能不完全精確。", noteThumbBadges: "隱藏 Google 原生的授權標記與影片徽章,並將其資訊整合至尺寸標籤中,使畫面更為乾淨。", noteBase64: "將圖片編碼為純文字的 Data URL 複製,適合網頁開發人員,或用於繞過特定剪貼簿限制。", noteLightboxMime: "採用 Magic Bytes (二進位檔案標頭) 識別,極為精確。", noteLightboxDate: "綜合比對 EXIF 拍攝時間、檔案更新、伺服器 Last-Modified 與網頁標記日期,建立照片時間履歷。", noteLightboxExif: "在大圖載入時解析圖片二進位檔頭 (EXIF, IPTC, XMP, C2PA),以提取相機參數、拍攝時間、版權憑證與 AI 算圖等資訊。", noteWebpConversion: "自動在下載時將網頁常見的 WebP 圖片轉換為通用格式(JPEG/PNG),解決舊軟體不支援的問題。", noteClickAction: "設定點選縮圖標籤時,要開啟腳本自建的精美燈箱,或是直接在新分頁打開原始圖片連結。", noteLightboxKeys: "啟用大圖檢視器中的鍵盤導覽,捲動背景並醒目提示目前圖片,並展開上一張、下一張及關閉按鈕的實體按鍵自訂面板。", enableExperimentalAiUpload: "實驗性功能:自動上傳原始圖片至 AI 搜尋", noteExperimentalAiUpload: "點選 AI 搜尋時,自動下載原圖並模擬貼上至 Google AI 搜尋輸入框,隨同自訂提示詞發送。", uploadingToAi: "正在上傳圖片至 AI 搜尋...", uploadSuccess: "圖片上傳成功!", uploadFail: "圖片上傳失敗", shoot: "拍攝於", digitized: "數位產生於", modify: "檔案更新於", lastModified: "伺服器修改於", googleBadge: "原始標示為", timeHistory: "完整時間鑑識鏈", localCameraTime: "相機本地時間", unknownTime: "未知時間", cameraSpecs: "相機與鏡頭參數", camera: "相機型號", lensModel: "鏡頭型號", focalLength: "焦距", aperture: "光圈", shutterSpeed: "快門速度", iso: "ISO 感光度", software: "影像處理軟體", flash: "閃光燈狀態", flashOn: "開啟", flashOff: "關閉", gpsLocation: "拍攝地理定位", gpsCoords: "座標位置", gpsLink: "開啟 Google 地圖 ↗", aiBadge: `${svgSparkles} AI 產生`, aiBadgeModified: `${svgShield} 包含 AI 修改/憑證`, aiDetail: "AI 算圖特徵與參數", aiDetectionMethod: "偵測方式", aiFeatureDetails: "特徵細節", toastFetching: "正在取得圖片資料...", toastCopied: `${svgSuccess} 圖片已複製到剪貼簿!`, toastCopyFail: `${svgAlert} 複製圖片失敗`, toastB64Converting: "正在轉換為 Base64...", toastB64Copied: `${svgSuccess} Base64 已複製到剪貼簿!`, toastB64Fail: `${svgAlert} 轉換 Base64 失敗`, toastDownloading: "開始下載...", toastConverting: "正在轉換圖片格式...", toastDlFail: `${svgAlert} 下載失敗,改用新分頁開啟...`, toastLoadingImage: "正在載入高清原始圖片...", btnWatchVideo: "播放影片", btnBackToCover: "返回大圖", toastPreparingLens: "正在為 Google Lens 準備圖片...", toastPreparingTineye: "正在為 TinEye 準備圖片...", toastPreparingAi: "正在為 AI 搜尋準備圖片...", toastReset: "設定已重設為預設值。", errFailedLoad: "載入圖片失敗", errUnexpected: "呈現圖片檔案時發生未預期的錯誤。", errHotlinkTitle: "阻擋外部連結 (防盜連)", errHotlinkDesc: "目標網站伺服器限制直接外部讀取(防盜連保護)。", errNotFoundTitle: "圖片不存在或已失效", errNotFoundDesc: "請求的資源已從伺服器刪除,或該連結已逾期失效。", errServerTitle: "來源伺服器錯誤", errServerDesc: "遠端網站目前可能離線、過載或正在維護中。", errTimeoutTitle: "連線逾時", errTimeoutDesc: "目標伺服器拒絕連線,或是您目前的網路封鎖了該主機。", errOpenNewTab: "在新分頁開啟 ↗", errDlBypassGuide: "已偵測到【{REASON}】。請點擊按鈕在新分頁開啟原圖以繞過阻擋限制。", errOpenInNewTabBtn: "在新分頁開啟大圖", errPlaceholderTitle: "偵測到 1x1 佔位圖片", errPlaceholderDesc: "目標伺服器已將大圖重導向為 1x1 像素的空白佔位符(防盜連保護)。您可以按下 Enter 鍵,或點選下方連結前往來源網頁查看原圖。", serverPrefix: "[伺服器]", enableLightboxColorAnalysis: "在大圖中進行色彩分析並顯示直方圖", noteLightboxColorAnalysis: "使用下採樣與 K-Means 演算法擷取 5 大主導色並繪製三通道色彩分佈圖。", colorAnalysisTitle: "色彩分析與調色盤", toastColorCopied: "已複製主導色:", tipColorClickToCopy: "點擊複製色碼", toastVideoMuted: "影片已靜音", toastVideoUnmuted: "影片聲音已開啟", hudVideoPlay: "空白鍵 / K:播放與暫停影片", hudVideoMute: "M:切換靜音狀態", c2paCreated: "建立影像", c2paCropped: "裁切與縮放", c2paColorAdjustments: "色彩調整", c2paOrientation: "方向調整/旋轉", c2paResized: "調整大小", c2paConverted: "格式轉換", c2paEdited: "影像編輯與合成", c2paMetadataAdded: "加入元數據", c2paUnknown: "未明編輯操作", c2paTitle: `${svgShield} 內容真實性憑證 (C2PA)`, c2paVerified: "數位憑證校驗合格", c2paIssuer: "憑證簽署機構", c2paSoftware: "使用處理軟體", c2paDate: "處理記錄時間", groupCustomColors: "按鈕與標籤色彩自訂", customBgColor: "標籤與按鈕背景顏色", customTextColor: "標籤與按鈕文字/圖示顏色", customBgOpacity: "縮圖標籤與按鈕背景透明度", noteCustomColors: "支援所有 CSS 格式(例如 rgba(0,0,0,0.6)、#202124 或 transparent)。留空則使用預設值。", enableLightboxForceBlob: "大圖燈箱優先載入高品質原圖 (避免 WebP/AVIF 轉檔壓縮)", noteLightboxForceBlob: "以自訂標頭阻擋內容協商,繞過 WebP/AVIF 轉碼壓縮,直接抓取高畫質原始 JPEG/PNG 進行顯示與下載。", enableYouTubeAutoplay: "以燈箱開啟 YouTube 縮圖時自動播放影片", noteYouTubeAutoplay: "開啟 YouTube 縮圖時直接在燈箱內播放影片,亦可點擊右上角隨時切回最高解析度封面原圖。", pillC2pa: `${svgShield} C2PA 憑證`, pillAi: `${svgSparkles} AI 圖片`, aiHighConf: "高度可信", aiMedConf: "中度可信", toastDownloadFail: `${svgAlert} 下載失敗。`, toastPreparingAiSearch: "正在準備 AI 搜尋...", toastPrepareAiSearchFail: `${svgAlert} 無法準備 AI 搜尋`, enableThumbPhotopea: "顯示縮圖 Photopea 按鈕", enableLightboxPhotopea: "顯示燈箱 Photopea 按鈕", enableThumbVectorpea: "顯示縮圖 Vectorpea 按鈕", enableLightboxVectorpea: "顯示燈箱 Vectorpea 按鈕", enableThumbYandex: "顯示縮圖 Yandex 搜圖按鈕", enableLightboxYandex: "顯示燈箱 Yandex 搜圖按鈕", enableThumbBing: "顯示縮圖 Bing 搜圖按鈕", enableLightboxBing: "顯示燈箱 Bing 搜圖按鈕", unsupportedDomainTooltip: "僅支援 SERP 數據導出,此網域無法直接下載高解析度原圖檔案。", tipPhotopea: "使用 Photopea 開啟圖片編輯", tipVectorpea: "使用 Vectorpea 開啟向量編輯", tipYandex: "使用 Yandex 以圖搜圖", tipBing: "使用 Bing 以圖搜圖", notePhotopea: "直接在 Photopea 線上影像編輯器中打開此圖片。", noteVectorpea: "直接在 Vectorpea 線上向量編輯器中打開此圖片。", noteYandex: "使用 Yandex Images 進行反向圖片搜尋。", noteBing: "使用 Bing Images 進行反向圖片搜尋。" }, ja: { groupCoreEngine: "コアとシステム設定", groupThumb: "サムネイルの表示と操作", groupLightbox: "ライトボックスの表示と操作", groupSystem: "AI と実験的な設定", settings: "設定", menuSettings: "GIAT 設定", enableUrlOptimization: "オリジナル画像URLへのアップグレード", settingsLang: "表示言語", langAuto: "自動 (ブラウザ設定)", enableHoverInfo: "ホバー時のみラベルと操作ボタンを表示", enableThumbResolution: "サムネイルに解像度を表示", enableThumbFileSize: "サムネイルにファイルサイズを表示", enableThumbMime: "サムネイルに画像フォーマットを表示", enableThumbBadges: "原生バッジと日付を寸法ラベルに統合", enableThumbDownload: "サムネイルダウンロードボタン", enableThumbCopy: "サムネイルコピーボタン", enableThumbB64: "サムネイル Base64 ボタン", enableThumbTitleTooltip: "ホバー時にツールチップでフルタイトルを表示", enableThumbLens: "サムネイル Google Lens ボタン", enableLightboxLens: "ライトボックス Google Lens ボタン", enableThumbTineye: "サムネイル TinEye ボタン", enableLightboxTineye: "ライトボックス TinEye ボタン", enableThumbAi: "サムネイル AI 検索ボタン", enableLightboxAi: "ライトボックス AI 検索ボタン", tipLens: "Google Lens で画像を検索", tipTineye: "TinEye で画像を検索", tipAi: "Google AI で画像を検索", aiPromptLabel: "AI 検索カスタムプロンプト", aiPromptPlaceholder: "例:タイトル {TITLE} の画像 {IMG} を説明して", defaultAiPrompt: "この画像の内容、ソース、背景情報について詳しく説明してください。\n\n---\n* ソースタイトル:{TITLE}\n* ソースURL:{SRC}\n* 画像参照リンク:{IMG}", defaultPromptPrefix: "デフォルト:", aiPromptVariablesHelp: "サポートするプレースホルダー:{TITLE} (ページタイトル)、{SRC} (ソースURL)、{IMG} (画像URL)", aiPromptImgText: "画像:{filename}", aiPromptImgInfoText: "画像情報:{filename}", defaultTitleFallback: "ソースページ", enableLightboxDownload: "ライトボックスダウンロードボタン", enableLightboxCopy: "ライトボックスコピーボタン", enableLightboxB64: "ライトボックス Base64 ボタン", enableLightboxResolution: "ライトボックスに解像度を表示", enableLightboxMime: "ライトボックスに画像フォーマットを表示", enableLightboxFileSize: "ライトボックスにファイルサイズを表示", enableLightboxDate: "時間情報の鑑識・解析 (DateTime Forensics)", enableLightboxExif: "ライトボックスにカメラ、AI、著作権情報 (EXIF/C2PA) を表示", enableVisitedMark: "閲覧/クリック済みの検索結果をマーク", visitedStyleModeLabel: "閲覧済みマークスタイル", visitedModeDim: "半透明・グレースケール(デフォルト)", visitedModeBorder: "クラシックパープル枠線", visitedModeBadge: "チェックマークバッジ", visitedModeSubtle: "控えめな暗化", clearVisitedBtn: "閲覧履歴をクリア", visitedStatsLabel: "記録済み / 上限", toastVisitedCleared: `${svgSuccess} 閲覧履歴フットプリントをクリアしました!`, noteVisitedMark: "クリックまたはプレビューした検索結果に専用のスタイルを適用し、閲覧済み画像を分かりやすくします。", enableBatchSelect: "一括選択・画像ダウンロード機能を有効化", batchSelectBtn: "一括選択", batchSelectedCount: "選択済み: {count} 枚", batchSelectedCountWithSize: "選択済み: {count} 枚 ({size})", batchSelectedCountWithSizeApprox: "選択済み: {count} 枚 (約 {size})", selectAll: "すべて選択", clearSelection: "クリア", exportZipBtn: "ZIPを出力", exportDirectBtn: "画像を保存", batchDownloadModeLabel: "一括ダウンロードモード", batchModeZipOption: "ZIPアーカイブ (.zip)", batchModeDirectOption: "直接画像ファイルを保存", serpRankModeLabel: "SERP 検索順位バッジ表示モード", serpRankHover: "ホバー時に表示 (デフォルト)", serpRankAlways: "常時表示", serpRankNever: "非表示 (一括選択時のみ表示)", exitBatchMode: "終了", viewMissedDetails: "詳細を表示", missedTitle: "一括ダウンロード欠落詳細", reason403: "アクセス拒否 (HTTP 403)", reasonCloudflare: "Cloudflare ブロック (WAF)", reasonHotlink: "直リンク制限 (Hotlink Blocked)", reason404: "画像削除済み (HTTP 404)", reasonTimeout: "タイムアウト (Timeout)", reasonUnsupported: "非対応ドメイン", openSourceUrl: "リンクを開く", toastZipping: "圧縮中... ({completed}/{total})", toastSavingFiles: "画像ファイルを保存中...", toastZipDownloaded: `${svgSuccess} ZIPを保存しました!({count} 枚)`, toastFilesDownloaded: `${svgSuccess} {count} 枚の画像を保存しました!`, exportDataBtn: "データ出力", copyMarkdownSuccess: `${svgSuccess} Markdownリストをクリップボードにコピーしました!`, copyJsonSuccess: `${svgSuccess} JSONデータをクリップボードにコピーしました!`, copyCsvSuccess: `${svgSuccess} CSVテーブルをクリップボードにコピーしました!`, copyRawUrlsSuccess: `${svgSuccess} 原寸画像URLリストをクリップボードにコピーしました!`, copyMarkdownMenu: "Markdownリストをコピー (.md)", downloadMarkdownMenu: "Markdownファイルをダウンロード (.md)", copyJsonMenu: "JSONデータをコピー (.json)", downloadJsonMenu: "JSONファイルをダウンロード (.json)", copyCsvMenu: "CSVテーブルをコピー (.csv)", downloadCsvMenu: "CSVファイルをダウンロード (.csv)", copyUrlsMenu: "URLリストをコピー (IDM用)", enableLightboxKeys: "キーボードナビゲーションとキー割り当て", lightboxPrevKey: "前の画像へのショートカット", lightboxNextKey: "次の画像へのショートカット", lightboxCloseKey: "ライトボックスを閉じる", keyPressToBind: "キーを押して割り当て...", lightboxBg: "ライトボックスの背景スタイル", resetBtn: "リセット", labelPositionLabel: "ラベルの表示位置", posBottomRight: "右下 (デフォルト)", posBottomLeft: "左下", posTopRight: "右上", posTopLeft: "左上", labelSizeLabel: "ラベルのサイズ", thumbBtnSizeLabel: "縮小版ボタンサイズ", sizeSmall: "小", sizeMedium: "中 (デフォルト)", sizeLarge: "大", bgDarkCheckerboard: "ダークチェッカーボード", bgWhite: "白背景", bgCheckerboard: "チェッカーボード", bgGray: "グレー背景", bgBlack: "黒背景", tipDownload: "オリジナル画像をダウンロード", tipCopy: "画像をクリップボードにコピー", tipExifCopied: "EXIF情報をコピーしました:", tipB64: "画像を Base64 としてコピー", hudTitle: "ライトボックスショートカット・ジェスチャーガイド", resetZoom: "ズーム・位置のリセット", hudClickZoom: "画像クリック:2.5倍ズーム / リセット", hudDragPan: "ドラッグ移動:画像の詳細をパン移動", hudWheelZoom: "ホイール / ピンチ:スムース無段階ズーム", hudVideoPlay: "Space / K:動画の再生 / 一時停止", hudVideoMute: "M:ミュートの切り替え", toggleHud: "ショートカットガイド表示/非表示", uiThemeLabel: "インターフェースのテーマ", themeAuto: "自動検出", themeDark: "ダーク", themeLight: "ライト", clickActionLabel: "ラベルのクリック動作", actionLightbox: "ライトボックスを開く", actionTab: "新しいタブで開く", ctrlClickActionLabel: "縮小版の Ctrl+クリックと中クリック動作", ctrlActionRawImage: "新しいタブで原画を直接開く", ctrlActionGoogleTab: "Google 純正プレビュータブ", noteCtrlClickAction: "サムネイル画像上で Ctrl+左クリック または 中クリックした際に、高解像度の原画を直接開くか、Google 純正のプレビュータブを開くかを設定します(下部のタイトルリンクには影響しません)。", groupWebpConversion: "ダウンロードとストレージ設定", enableWebpConversion: "ダウンロード時にWebPを自動変換", webpConversionFormat: "変換フォーマット", webpConversionQuality: "JPEG 画質", groupFilenamePattern: "ダウンロードファイル名の命名規則", filenamePatternMode: "ファイル名形式", patternOriginal: "元のファイル名 ({original})", patternQueryIndex: "検索ワード + 連番 ({query}_{index})", patternTitleDims: "タイトル + サイズ ({title}_{dims})", patternDomainTitle: "ドメイン + タイトル ([{domain}] {title})", patternCustom: "カスタム形式... (Custom Pattern...)", customFilenameTemplate: "カスタムテンプレート", previewFilenameLabel: "プレビュー例", chipQuery: "検索ワード", chipDomain: "ドメイン", chipTitle: "タイトル", chipOriginal: "元のファイル名", chipDims: "サイズ", chipIndex: "連番", chipDate: "日付", chipTime: "時刻", noteFilenamePattern: "ダウンロードする画像ファイル名をカスタマイズ。プレースホルダー:{query}、{domain}、{title}、{original}、{dims}、{index}、{date}、{time} をサポート。", noteUrlOptimization: "Googleの縮小・切り抜き画像を最高画質のオリジナル原画に自動復元します。無効時は Google の標準リンクを使用。", noteHoverInfo: "サムネイルラベルと操作ボタンをデフォルトで非表示にし、ホバー時のみフェードイン表示してノイズを削減します。", noteThumbTitleTooltip: "サムネイルのタイトルにホバーしたとき、ツールチップで省略されていないフルタイトルを表示します。", noteLens: "サムネイルとライトボックスに Google Lens 検索ボタンを追加し、画像検索と認識を直接行えます。", noteTineye: "類似の画像を検索するために TinEye 検索ボタンを追加します。", noteAi: "Google AI検索ボタンを追加します。プロンプトプレースホルダー:{TITLE}、{SRC}、{IMG} をサポート。", noteThumbMime: "URLから推測するため、100%正確とは限りません。", noteThumbBadges: "サムネイル上のライセンスや動画などの原生バッジを非表示にし、寸法ラベル内に統合して表示をスッキリさせます。", noteBase64: "画像をテキストベースのData URLとしてエンコードしてコピーします。開発者に便利です。", noteLightboxMime: "Magic Bytes (ファイルヘッダー) から識別するため、極めて正確です。", noteLightboxDate: "EXIF撮影日時、ファイル更新日、サーバー Last-Modified、元の表示日時などを統合・比較し、写真のタイムラインを生成。", noteLightboxExif: "大画像読み込み時に画像のバイナリヘッダー (EXIF, IPTC, XMP, C2PA) を解析し、カメラパラメータ、撮影時間、著作権、AI生成メタデータを取得します。", noteWebpConversion: "ダウンロード時にWebP画像を互換性の高いJPEG/PNGに自動変換し、古いソフトウェアでの開けない問題を解決します。", noteClickAction: "ラベルをクリックしたときにカスタムライトボックスを開くか、新しいタブで直接画像を開くかを選択します。", noteLightboxKeys: "有効にすると、キーボードでの画像切替(前、次、閉じる)が可能になり、キー割り当てのカスタマイズパネルを展開します。背景画像の同期スクロールやハイライトも行います。", enableExperimentalAiUpload: "実験的機能:AI 検索に画像を自動アップロード", noteExperimentalAiUpload: "AI検索の実行時、オリジナル画像を自動ダウンロードして入力エリアに貼り付け、プロンプトと同時に送信します。", uploadingToAi: "AI検索へ画像をアップロード中...", uploadSuccess: "画像のアップロードが成功しました!", uploadFail: "画像のアップロードに失敗しました", shoot: "撮影日", digitized: "デジタル化日", modify: "更新日", lastModified: "サーバー変更日", googleBadge: "元のウェブ表示", timeHistory: "時間履歴の詳細", localCameraTime: "ローカルカメラ時間", unknownTime: "不明な時間", cameraSpecs: "カメラとレンズ仕様", camera: "カメラ型番", lensModel: "レンズモデル", focalLength: "焦点距離", aperture: "絞り値 (F値)", shutterSpeed: "シャッタースピード", iso: "感光度 (ISO)", software: "編集ソフト", flash: "フラッシュ", flashOn: "オン", flashOff: "オフ", gpsLocation: "撮影位置", gpsCoords: "座標位置", gpsLink: "Google マップを開く ↗", aiBadge: `${svgSparkles} AI生成`, aiBadgeModified: `${svgShield} C2PA認定 (AI修整/補助)`, aiDetail: "AI作成プロンプト・詳細仕様", aiDetectionMethod: "検出方法", aiFeatureDetails: "特徴詳細", toastFetching: "画像データを取得中...", toastCopied: `${svgSuccess} 画像をクリップボードにコピーしました!`, toastCopyFail: `${svgAlert} コピーに失敗しました`, toastB64Converting: "Base64 に変換中...", toastB64Copied: `${svgSuccess} Base64 をクリップボードにコピーしました!`, toastB64Fail: `${svgAlert} Base64 への変換に失敗しました`, toastDownloading: "ダウンロードを開始中...", toastConverting: "画像フォーマットを変換中...", toastDlFail: `${svgAlert} ダウンロードに失敗しました。新しいタブでリンクを開きます...`, toastLoadingImage: "高解像度画像を読み込み中...", toastVideoMuted: "音声をミュートにしました", toastVideoUnmuted: "音声をミュート解除しました", btnWatchVideo: "動画を再生", btnBackToCover: "カバー画像に戻る", toastPreparingLens: "Google レンズ用の画像を準備中...", toastPreparingTineye: "TinEye 用の画像を準備中...", toastPreparingAi: "AI 検索用の画像を準備中...", toastReset: "設定をデフォルトに戻しました。", errFailedLoad: "画像の読み込みに失敗しました", errUnexpected: "アセットのレンダリング中に予期しないエラーが発生しました。", errHotlinkTitle: "直リンク禁止 (防犯対策)", errHotlinkDesc: "画像の参照元サイトによって直接アクセスがブロックされています。", errNotFoundTitle: "画像が存在しないか失効しています", errNotFoundDesc: "要求されたリソースは削除されたか、リンクの有効期限が切れています。", errServerTitle: "ホストサーバーエラー", errServerDesc: "リモートウェブサイトは現在オフラインか、過負荷、またはメンテナンス中です。", errTimeoutTitle: "接続タイムアウト", errTimeoutDesc: "対象サーバーが接続を拒否したか、現在のネットワークがホストをブロックしています。", errOpenNewTab: "新しいタブで開く ↗", errDlBypassGuide: "【{REASON}】を検出しました。ボタンをクリックして新しいタブで画像を開き、制限を回避してください。", errOpenInNewTabBtn: "新しいタブで画像を開く", errPlaceholderTitle: "1x1 プレースホルダーを検出", errPlaceholderDesc: "対象サーバーが画像を 1x1 ピクセルの空白画像にリダイレクトしました(直リンク防止対策)。Enter キーを押すか、下のリンクをクリックして参照元ページで画像を確認してください。", serverPrefix: "[サーバー]", enableLightboxColorAnalysis: "ライトボックスに色彩分析とカラーパレットを表示", noteLightboxColorAnalysis: "K-Means法で主要な5色を抽出し、RGBヒストグラムを描画します。", colorAnalysisTitle: "色彩分析とパレット", toastColorCopied: "色をコピーしました:", tipColorClickToCopy: "クリックしてカラーコードをコピー", c2paCreated: "作成", c2paCropped: "トリミング", c2paColorAdjustments: "色調整", c2paOrientation: "回転/方向調整", c2paResized: "サイズ変更", c2paConverted: "フォーマット変換", c2paEdited: "画像編集/合成", c2paMetadataAdded: "メタデータ追加", c2paUnknown: "不明な操作", c2paTitle: `${svgShield} コンテンツ資格情報 (C2PA)`, c2paVerified: "認証合格", c2paIssuer: "署名発行者", c2paSoftware: "使用ソフト", c2paDate: "処理日時", groupCustomColors: "ボタンとラベルの配色カスタマイズ", customBgColor: "ラベルとボタンの背景色", customTextColor: "ラベルとボタンの文字/アイコン色", customBgOpacity: "背景の不透明度", noteCustomColors: "すべてのCSSフォーマット(rgba(0,0,0,0.6)、#202124、transparentなど)に対応。空欄の場合はデフォルト値になります。", enableLightboxForceBlob: "ライトボックスで高品質原画を優先 (WebP/AVIF圧縮を回避)", noteLightboxForceBlob: "カスタムヘッダーでコンテンツネゴシエーションをブロックし、WebP/AVIF圧縮を回避して本来の高画質JPEG/PNGをダウンロードし表示します。", enableYouTubeAutoplay: "YouTube サムネイルを開くときに動画を自動再生", noteYouTubeAutoplay: "YouTube サムネイルを開くと自動で動画を再生します。右上のボタンで最高画質カバー画像に戻れます。", pillC2pa: `${svgShield} C2PA 証書`, pillAi: `${svgSparkles} AI 画像`, aiHighConf: "高い信頼性", aiMedConf: "中程度の信頼性", toastDownloadFail: `${svgAlert} ダウンロードに失敗しました。`, toastPreparingAiSearch: "AI 検索を準備中...", toastPrepareAiSearchFail: `${svgAlert} AI 検索の準備に失敗しました`, enableThumbPhotopea: "サムネイル Photopea ボタン", enableLightboxPhotopea: "ライトボックス Photopea ボタン", enableThumbVectorpea: "サムネイル Vectorpea ボタン", enableLightboxVectorpea: "ライトボックス Vectorpea ボタン", enableThumbYandex: "サムネイル Yandex 画像検索ボタン", enableLightboxYandex: "ライトボックス Yandex 画像検索ボタン", enableThumbBing: "サムネイル Bing 画像検索ボタン", enableLightboxBing: "ライトボックス Bing 画像検索ボタン", unsupportedDomainTooltip: "SERPデータエクスポートのみ対応。このドメインは高解像度画像ファイルの直接ダウンロードに対応していません。", tipPhotopea: "Photopea で画像を開く", tipVectorpea: "Vectorpea で画像を開く", tipYandex: "Yandex で画像検索", tipBing: "Bing で画像検索", notePhotopea: "Photopea オンライン画像エディタで直接画像を開きます。", noteVectorpea: "Vectorpea オンラインベクターエディタで直接画像を開きます。", noteYandex: "Yandex 画像検索で類似画像を検索します。", noteBing: "Bing 画像検索で類似画像を検索します。" } }; function t(key) { let lang = "en"; const userConfigLang = config?.userLanguage ?? "auto"; if (userConfigLang === "auto") { const lower = (navigator.language || navigator.userLanguage || "en").toLowerCase(); if (lower.startsWith("zh-tw") || lower.startsWith("zh-hk") || lower.startsWith("zh-hant")) lang = "zh-TW"; else if (lower.startsWith("ja")) lang = "ja"; else lang = "en"; } else lang = userConfigLang; return (translations[lang] || translations["en"])[key] || translations["en"][key] || key; } var activeToast = null; var toastTimeoutId; function showToast(message, duration = 2e3) { if (activeToast) { activeToast.innerHTML = message; if (toastTimeoutId) { clearTimeout(toastTimeoutId); toastTimeoutId = void 0; } if (duration > 0) toastTimeoutId = window.setTimeout(() => { hideToast(); }, duration); return activeToast; } const toast = document.createElement("div"); toast.classList.add("giat-toast"); const isDark = config.uiTheme === "auto" ? isPageDark() : config.uiTheme === "dark"; toast.classList.add(isDark ? "giat-toast-dark" : "giat-toast-light"); toast.innerHTML = message; document.body.appendChild(toast); activeToast = toast; setTimeout(() => toast.classList.add("show"), 10); if (duration > 0) toastTimeoutId = window.setTimeout(() => { hideToast(); }, duration); return toast; } function hideToast() { if (activeToast) { const toast = activeToast; activeToast = null; if (toastTimeoutId) { clearTimeout(toastTimeoutId); toastTimeoutId = void 0; } toast.classList.remove("show"); setTimeout(() => { toast.remove(); }, 300); } } var crcTable = new Uint32Array(256); for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1; crcTable[n] = c; } function calculateCRC32(data) { let crc = 4294967295; for (let i = 0; i < data.length; i++) crc = crc >>> 8 ^ crcTable[(crc ^ data[i]) & 255]; return (crc ^ 4294967295) >>> 0; } async function calculateBlobCRC32(blob) { const chunkSize = 262144; if (blob.size <= chunkSize) { const buffer = await blob.arrayBuffer(); return calculateCRC32(new Uint8Array(buffer)); } let crc = 4294967295; let offset = 0; while (offset < blob.size) { const buffer = await blob.slice(offset, offset + chunkSize).arrayBuffer(); const chunk = new Uint8Array(buffer); for (let i = 0; i < chunk.length; i++) crc = crc >>> 8 ^ crcTable[(crc ^ chunk[i]) & 255]; offset += chunkSize; } return (crc ^ 4294967295) >>> 0; } async function createZipPartsAsync(files) { const textEncoder = new TextEncoder(); const entries = []; const parts = []; let currentOffset = 0; for (const file of files) { const filenameBytes = textEncoder.encode(file.name); const fileSize = file.blob.size; let crc = file.crc32; if (crc === void 0) crc = await calculateBlobCRC32(file.blob); const header = new Uint8Array(30 + filenameBytes.length); const view = new DataView(header.buffer); view.setUint32(0, 67324752, true); view.setUint16(4, 10, true); view.setUint16(6, 0, true); view.setUint16(8, 0, true); view.setUint16(10, 0, true); view.setUint16(12, 0, true); view.setUint32(14, crc, true); view.setUint32(18, fileSize, true); view.setUint32(22, fileSize, true); view.setUint16(26, filenameBytes.length, true); view.setUint16(28, 0, true); header.set(filenameBytes, 30); entries.push({ filename: filenameBytes, length: fileSize, crc32: crc, offset: currentOffset }); parts.push(header); parts.push(file.blob); currentOffset += header.length + fileSize; } const centralDirectoryOffset = currentOffset; let centralDirectorySize = 0; for (const entry of entries) { const cdHeader = new Uint8Array(46 + entry.filename.length); const view = new DataView(cdHeader.buffer); view.setUint32(0, 33639248, true); view.setUint16(4, 10, true); view.setUint16(6, 10, true); view.setUint16(8, 0, true); view.setUint16(10, 0, true); view.setUint16(12, 0, true); view.setUint16(14, 0, true); view.setUint32(16, entry.crc32, true); view.setUint32(20, entry.length, true); view.setUint32(24, entry.length, true); view.setUint16(28, entry.filename.length, true); view.setUint16(30, 0, true); view.setUint16(32, 0, true); view.setUint16(34, 0, true); view.setUint16(36, 0, true); view.setUint32(38, 0, true); view.setUint32(42, entry.offset, true); cdHeader.set(entry.filename, 46); parts.push(cdHeader); centralDirectorySize += cdHeader.length; currentOffset += cdHeader.length; } const eocd = new Uint8Array(22); const eocdView = new DataView(eocd.buffer); eocdView.setUint32(0, 101010256, true); eocdView.setUint16(4, 0, true); eocdView.setUint16(6, 0, true); eocdView.setUint16(8, entries.length, true); eocdView.setUint16(10, entries.length, true); eocdView.setUint32(12, centralDirectorySize, true); eocdView.setUint32(16, centralDirectoryOffset, true); eocdView.setUint16(20, 0, true); parts.push(eocd); return parts; } var removeParams = (params) => (urlObj, opt) => { try { const local = new URL(opt); let modified = false; for (const p of params) if (local.searchParams.has(p)) { local.searchParams.delete(p); modified = true; } return modified ? local.toString() : opt; } catch (e) { return opt; } }; var replaceSuffix = (regex, replacement) => (urlObj, opt) => { return opt.replace(regex, replacement); }; var replaceAndDecode = (regex, replacement) => (urlObj, opt) => { try { return decodeURIComponent(opt.replace(regex, replacement)); } catch (e) { return opt.replace(regex, replacement); } }; var standardCdnPurge = removeParams([ "w", "width", "h", "height", "resize", "crop", "quality", "q", "size", "maxwidth", "maxheight", "fit", "scale", "format", "strip" ]); var multiReplace = (replacements) => (urlObj, opt) => { let clean = opt; for (const [regex, replacement] of replacements) clean = clean.replace(regex, replacement); return clean; }; var shopeeClean = (urlObj, opt) => opt.replace(/(\/file\/[0-9a-f]+)_tn(?:\?.*)?$/i, "$1"); var livedoorBlogimgClean = (urlObj, opt) => opt.replace(/(\/[^/.]*)-[sm](\.[^/.]*)/i, "$1$2"); var melonbooksClean = (urlObj, opt) => opt.replace(/(:\/\/[^/]*\/)(?:(?:user_data\/packages|resize_image)\/)?resize_image\.php.*?[?&]image=([^&]*).*$/i, "$1upload/save_image/$2"); var yandeFilesClean = (urlObj, opt) => opt.replace(/\/(?:sample|jpeg)\/+([0-9a-f]+\/)/i, "/image/$1"); var sankakuClean = (urlObj, opt) => { if (opt.match(/\/data\/+(?:preview|sample)\/+[0-9a-f]{2}\/+[0-9a-f]{2}\/+([0-9a-f]{10,})\./i)) return opt.replace(/\/data\/+(?:preview|sample)\/+[0-9a-f]{2}\/+[0-9a-f]{2}\/+([0-9a-f]{10,})\.[a-zA-Z0-9]+/i, "/data/$1.jpg"); return opt; }; var hmCdnOptimizer = (urlObj, opt) => { if (opt.includes("set=") || opt.includes("source=")) { const match = opt.match(/(?:source|imageURL|path)\[([^\]]+)\]/i) || opt.match(/(?:source|imageURL|path)%5B([^%]+)%5D/i); if (match) try { const path = decodeURIComponent(match[1]); if (path.startsWith("/")) return urlObj.protocol + "//" + urlObj.hostname + path; else if (path.startsWith("http")) return path; } catch (e) {} return opt.replace(/value%5B[0-9]+%5D/g, "value%5B99999%5D").replace(/value\[[0-9]+\]/g, "value[99999]"); } return standardCdnPurge(urlObj, opt); }; var polopolyFsOptimizer = (urlObj, opt) => { let clean = opt.replace(/(\/[^/.]*\.[^_/.]*)_gen\/+derivatives\/+[^/]*\/+/, "/").replace(/\/image\.[^_/]*_gen\/+derivatives\/+[^/]*\//, "/").replace(/\/image\/+[^_/]*_gen\/+derivatives\/+[^/]*\//, "/image/"); if (clean !== opt) return clean.replace(/\?.*/, ""); return opt; }; var domainOptimizers = { "imgur.com": (urlObj, opt) => { const imgurRegex = /(:\/\/(?:i\.)?imgur\.(?:com|io)\/[a-zA-Z0-9]{5,7})(?:_[a-zA-Z0-9]+)?([sbtmlh]?)(\.[a-zA-Z0-9]+)/i; if (imgurRegex.test(opt)) opt = opt.replace(imgurRegex, "$1$3"); if (opt.endsWith(".gifv")) opt = opt.substring(0, opt.length - 5) + ".mp4"; return opt; }, "imgur.io": (urlObj, opt) => domainOptimizers["imgur.com"](urlObj, opt), "stack.imgur.com": (urlObj, opt) => opt.replace(/s\.([a-zA-Z0-9]+)$/i, ".$1"), "twimg.com": (urlObj, opt) => { if (opt.includes("/profile_banners/")) return opt.replace(/\/[0-9]+x[0-9]+(?:[?#].*)?$/, ""); if (opt.includes("/profile_images/")) return opt.replace(/_(?:bigger|normal|mini|reasonably_small|[0-9]*x[0-9]+)(\.[^/_]*)$/i, "$1"); if (opt.match(/\.([a-zA-Z0-9]+):[a-z]+$/i)) return opt.replace(/\.([a-zA-Z0-9]+):[a-z]+$/i, ".$1:orig"); if (opt.includes("/media/") && urlObj.searchParams.has("name")) { const localObj = new URL(opt); localObj.searchParams.set("name", "orig"); return localObj.toString(); } return opt; }, "pinimg.com": (urlObj, opt) => { const clean = opt.replace(/[?#].*$/, ""); if (clean.includes("/media.pinterest.com/")) return clean.replace(/(:\/\/[^/]*\/media\.pinterest\.com\/)[^/]*(\/.*\/[^/]*\.[^/.]*)$/i, "$1originals$2"); return clean.replace(/(:\/\/[^/]*\/)[^/]*(\/.*\/[^/]*\.[^/.]*)$/i, "$1originals$2"); }, "upload.wikimedia.org": (urlObj, opt) => { const wikiThumbRegex = /\/wikipedia\/([^/]+)\/thumb\/+(archive\/+)?([0-9a-f])\/+([0-9a-f]{2})\/+([^/]+)\/+(?:lossless-page[0-9]+-)?[0-9]+px-.*?$/i; return wikiThumbRegex.test(opt) ? opt.replace(wikiThumbRegex, "/wikipedia/$1/$2$3/$4/$5") : opt; }, "preview.redd.it": (urlObj, opt) => { return opt.replace(/:\/\/preview\.redd\.it\/(award_images\/+t[0-9]*_[0-9a-z]+\/+)?(?:[-0-9a-z]+-)?([^/.]*\.[^/.?]*)\?.*$/i, "://i.redd.it/$1$2"); }, "i.redd.it": (urlObj, opt) => opt.replace(/\?.*$/, ""), "reddit.com": (urlObj, opt) => opt.replace(/(\/gold\/+awards\/+icon\/+[^/]+)_[1-4]?[0-9]{2}\./i, "$1_512."), "media.discordapp.net": (urlObj, opt) => { const clean = opt.replace("media.discordapp.net", "cdn.discordapp.com"); return domainOptimizers["cdn.discordapp.com"](urlObj, clean); }, "cdn.discordapp.com": (urlObj, opt) => { try { const localObj = new URL(opt); localObj.searchParams.delete("width"); localObj.searchParams.delete("height"); localObj.searchParams.delete("format"); return localObj.toString(); } catch (e) { return opt; } }, "flickr.com": (urlObj, opt) => opt.replace(/_([qtsmnzcbhk])\.([a-zA-Z0-9]+)$/i, "_o.$2"), "staticflickr.com": (urlObj, opt) => opt.replace(/_([qtsmnzcbhk])\.([a-zA-Z0-9]+)$/i, "_o.$2"), "tumblr.com": (urlObj, opt) => opt.replace(/_([0-9]+|75sq|inline_[a-zA-Z0-9]+)\.([a-zA-Z0-9]+)$/i, "_1280.$2"), "deviantart.com": (urlObj, opt) => opt.replace(/\/v1\/fill\/w_[0-9]+,h_[0-9]+[^/]+\/([^?#]+)/i, "/$1"), "artstation.com": (urlObj, opt) => { return opt.replace(/(\/assets\/+(?:images|covers|panos)\/+images\/+[0-9]{3}\/+[0-9]{3}\/+[0-9]{3}\/+)(?:[0-9]+\/+)?(?:small(?:er)?|micro|medium|large|4k)(?:_square)?\/([^/]*)$/i, "$1original/$2"); }, "behance.net": (urlObj, opt) => opt.replace(/(\/project(?:_modules|s)\/+)[^/]*\//i, "$1source/"), "gyazo.com": (urlObj, opt) => opt.replace(/\/thumb\/[0-9]+\//i, "/"), "tenor.com": (urlObj, opt) => opt.replace(/_[st]\.gif$/i, ".gif"), "giphy.com": (urlObj, opt) => opt.replace(/\/(?:[0-9]+w|giphy-downsized|giphy-downsized-small|giphy-preview)\.(gif|mp4|webp)/i, "/giphy.$1"), "weibo.com": (urlObj, opt) => opt.replace(/(\/)(?:square|thumbnail|mw690|bmiddle)(\/[0-9a-zA-Z]+\.[a-zA-Z0-9]+)$/i, "$1large$2"), "sinaimg.cn": (urlObj, opt) => opt.replace(/(\/)(?:square|thumbnail|mw690|bmiddle)(\/[0-9a-zA-Z]+\.[a-zA-Z0-9]+)$/i, "$1large$2"), "hdslb.com": (urlObj, opt) => { let clean = opt.includes("@") ? opt.split("@")[0] : opt; clean = clean.replace(/_(?:[0-9]+x[0-9]+|[0-9]+w_[0-9]+h)?\.(?:jpg|jpeg|png|gif|webp)$/i, ""); return clean.replace(/_webp$/i, ""); }, "doubanio.com": (urlObj, opt) => opt.replace(/(\/view\/photo\/)[lms](\/)/i, "$1raw$2"), "zhimg.com": (urlObj, opt) => opt.replace(/_(?:[0-9]+w|[bdefhr])(?=\.[a-zA-Z0-9]+)/i, ""), "pximg.net": (urlObj, opt) => opt.replace(/\/c\/[0-9]+x[0-9]+\/img-master\//i, "/img-original/").replace(/_master[0-9]+\./i, "."), "pixiv.net": (urlObj, opt) => opt.replace(/\/c\/[0-9]+x[0-9]+\/img-master\//i, "/img-original/").replace(/_master[0-9]+\./i, "."), "gelbooru.com": (urlObj, opt) => opt.replace(/\/thumbnails\//i, "/images/").replace(/\/sample\//i, "/images/").replace(/sample_/i, ""), "donmai.us": (urlObj, opt) => opt.replace(/\/thumbnails\//i, "/images/").replace(/\/sample\//i, "/images/").replace(/sample_/i, ""), "konachan.com": (urlObj, opt) => opt.replace(/\/post\/show\//i, "/"), "wallhaven.cc": (urlObj, opt) => opt.replace(/\/th\.wallhaven\.cc\/lg\//i, "/w.wallhaven.cc/full/").replace(/\/th\.wallhaven\.cc\/small\//i, "/w.wallhaven.cc/full/"), "sankakucomplex.com": removeParams(["width", "height"]), "500px.com": (urlObj, opt) => opt.replace(/\/[1-4]\.jpg$/i, "/2048.jpg"), "500px.org": (urlObj, opt) => opt.replace(/\/[1-4]\.jpg$/i, "/2048.jpg"), "smugmug.com": (urlObj, opt) => opt.replace(/-[ThMSL]\.(jpg|jpeg|png)/i, "-O.$1"), "dribbble.com": (urlObj, opt) => opt.replace(/_(?:teaser|1x)\.([a-zA-Z0-9]+)$/i, "_2x.$1"), "redbubble.com": replaceAndDecode(/,[0-9]+x[0-9]+,/i, ",1000x1000,"), "redbubble.net": replaceAndDecode(/,[0-9]+x[0-9]+,/i, ",1000x1000,"), "newgrounds.com": (urlObj, opt) => opt.replace(/\.adapt\.[0-9]+\.[1-9]/i, ""), "nicoseiga.jp": (urlObj, opt) => opt.replace(/\?i=[0-9]+[a-z]?/gi, ""), "lohas.nicoseiga.jp": (urlObj, opt) => opt.replace(/\?i=[0-9]+[a-z]?/gi, ""), "sndcdn.com": (urlObj, opt) => opt.replace(/-t[0-9]+x[0-9]+\.([a-zA-Z0-9]+)$/i, "-original.$1").replace(/-large\.([a-zA-Z0-9]+)$/i, "-original.$1"), "gfycat.com": (urlObj, opt) => opt.replace(/-thumb\.(mp4|gif|webp)/i, ".$1").replace(/-mobile\.(mp4|gif|webp)/i, ".$1"), "vsco.co": removeParams([ "size", "width", "height" ]), "pixieset.com": (urlObj, opt) => opt.replace(/-(?:thumb|cover|small|medium|large|xlarge)\.([a-zA-Z0-9]+)$/i, "-xxlarge.$1"), "zenfolio.com": (urlObj, opt) => opt.replace(/-[0-9]+\.([a-zA-Z0-9]+)$/i, ".$1"), "bbystatic.com": (urlObj, opt) => opt.replace(/(\/image2\/+BestBuy_[A-Z]+\/+.*)_sa(\.[^/.;?#]+)$/i, "$1_so$2").replace(/(\/image2\/+BestBuy_[A-Z]+\/+.*);.*$/i, "$1"), "walmartimages.com": (urlObj, opt) => opt.replace(/[?&]odnHeight=[0-9]+/gi, "").replace(/[?&]odnWidth=[0-9]+/gi, ""), "wfcdn.com": (urlObj, opt) => { return opt.replace(/\/im\/+[0-9]+\/+compr-r85\/+([0-9]+\/+[0-9]+\/+)/i, "/lf/unprocessed/hash/$11/").replace(/(\/lf\/+)[a-z]+(\/+hash\/)/i, "$1unprocessed$2").replace(/(\/im\/[0-9]+\/)[^/]*\//i, "$1compr-r85/"); }, "macysassets.com": (urlObj, opt) => { try { const u = new URL(opt); u.searchParams.delete("wid"); u.searchParams.delete("hei"); u.searchParams.delete("fit"); u.searchParams.set("scl", "1"); return u.toString(); } catch (e) { return opt; } }, "nordstrommedia.com": (urlObj, opt) => domainOptimizers["macysassets.com"](urlObj, opt), "costco-static.com": (urlObj, opt) => { try { const u = new URL(opt); u.searchParams.delete("recipeName"); return u.toString(); } catch (e) { return opt; } }, "costcobusinesscentre.ca": (urlObj, opt) => domainOptimizers["costco-static.com"](urlObj, opt), "sephora.com": standardCdnPurge, "hm.com": hmCdnOptimizer, "ikea.com": standardCdnPurge, "ebay.com": (urlObj, opt) => opt.replace(/\/s-l[0-9]+\.(jpg|jpeg|png|gif)/i, "/s-l1600.$1"), "ebayimg.com": (urlObj, opt) => opt.replace(/\/s-l[0-9]+\.(jpg|jpeg|png|gif)/i, "/s-l1600.$1"), "etsystatic.com": (urlObj, opt) => opt.replace(/il_(?:[0-9]+x[0-9a-zA-Z]+|[0-9]+xN)\./g, "il_fullxfull."), "ssl.cdn-redfin.com": (urlObj, opt) => opt.replace(/photo\/([0-9]+)\/mbphoto\/([0-9]+)\/genMid\.(.*)/i, "photo/$1/bigphoto/$2/$3"), "yelpcdn.com": removeParams([ "width", "height", "fit", "crop" ]), "tripadvisor.com": removeParams([ "w", "h", "fit" ]), "mzstatic.com": (urlObj, opt) => opt.replace(/\/[0-9]+x[0-9]+bb\.(?:jpg|jpeg|png|webp)$/i, "/9999x9999bb.jpg"), "bcbits.com": (urlObj, opt) => opt.replace(/_[0-9]+\.(?:jpg|jpeg|png)$/i, "_0.jpg"), "mercdn.net": (urlObj, opt) => { return standardCdnPurge(urlObj, opt.replace(/\/c!\/w=[0-9]+\//i, "/").replace(/_[a-z0-9]+\.(jpg|jpeg|png)$/i, ".$1")); }, "mercari.com": (urlObj, opt) => domainOptimizers["mercdn.net"](urlObj, opt), "cdn.myanimelist.net": (urlObj, opt) => opt.replace(/\/r\/[0-9]+x[0-9]+\//i, "/"), "myanimelist.net": (urlObj, opt) => domainOptimizers["cdn.myanimelist.net"](urlObj, opt), "obs.line-scdn.net": (urlObj, opt) => opt.replace(/\/[0-9a-zA-Z_-]+=(?:s[0-9]+|w[0-9]+|h[0-9]+|m[0-9]+).*$/i, ""), "stickershop.line-scdn.net": (urlObj, opt) => opt.replace(/\/(?:iPhone|android)\/sticker_key@2x\.png/i, "/iPhone/[email protected]").replace(/_key\.png/i, ".png"), "uploads.mangadex.org": (urlObj, opt) => opt.replace(/\.(?:512|256)\.(?:jpg|jpeg|png)$/i, ""), "mangadex.org": (urlObj, opt) => domainOptimizers["uploads.mangadex.org"](urlObj, opt), "megapx-assets.dcard.tw": (urlObj, opt) => opt.replace(/\/fit-in\/[0-9]+x[0-9]+\//i, "/"), "imgur.dcard.tw": (urlObj, opt) => opt.replace(/s\.(jpg|jpeg|png|gif)/i, ".$1"), "gamer.com.tw": (urlObj, opt) => removeParams([ "m", "w", "h", "fit" ])(urlObj, opt), "bahamut.com.tw": (urlObj, opt) => domainOptimizers["gamer.com.tw"](urlObj, opt), "bsky.app": (urlObj, opt) => domainOptimizers["cdn.bsky.app"](urlObj, opt), "substackcdn.com": (urlObj, opt) => { if (opt.includes("/image/fetch/")) { const match = opt.match(/\/image\/fetch\/.*?(https?%3A%2F%2F.*)/i); if (match) try { return decodeURIComponent(match[1]); } catch (e) {} } return opt; }, "quoracdn.net": (urlObj, opt) => opt.replace(/-c(?:\.([a-zA-Z0-9]+))$/i, ".$1").replace(/\/main-thumb-[0-9]+-[0-9]+-/i, "/main-qimg-"), "image.uniqlo.com": (urlObj, opt) => removeParams(["width", "impolicy"])(urlObj, opt), "static.zara.net": (urlObj, opt) => opt.replace(/(\/assets\/+public\/+.*?)(?:[?#].*)?$/, "$1"), "images.asos-media.com": (urlObj, opt) => { try { const u = new URL(opt); u.searchParams.delete("wid"); u.searchParams.delete("hei"); u.searchParams.set("scl", "1"); return u.toString(); } catch (e) { return opt; } }, "cdn1.epicgames.com": removeParams([ "resize", "w", "h", "quality" ]), "epicgames.com": (urlObj, opt) => domainOptimizers["cdn1.epicgames.com"](urlObj, opt), "rbxcdn.com": (urlObj, opt) => opt.replace(/\/(?:[0-9]+x[0-9]+|150\/150)\//i, "/768/768/"), "vimeocdn.com": removeParams([ "mw", "mh", "w", "q" ]), "i.vimeocdn.com": (urlObj, opt) => domainOptimizers["vimeocdn.com"](urlObj, opt), "static-cdn.jtvnw.net": (urlObj, opt) => opt.replace(/-(?:50x50|70x70|150x150|300x300)(\.[a-zA-Z0-9]+)$/i, "-600x600$1"), "upload-os-bbs.hoyolab.com": (urlObj, opt) => removeParams(["x-oss-process", "x-bce-process"])(urlObj, opt), "hoyolab.com": (urlObj, opt) => domainOptimizers["upload-os-bbs.hoyolab.com"](urlObj, opt), "miyoushe.com": (urlObj, opt) => domainOptimizers["upload-os-bbs.hoyolab.com"](urlObj, opt), "scene7.com": (urlObj, opt) => { try { const u = new URL(opt); u.searchParams.delete("wid"); u.searchParams.delete("hei"); u.searchParams.delete("fit"); u.searchParams.set("scl", "1"); return u.toString(); } catch (e) { return opt; } }, "target.scene7.com": (urlObj, opt) => domainOptimizers["scene7.com"](urlObj, opt), "thumbnail.image.rakuten.co.jp": (urlObj, opt) => opt.replace(/:\/\/[^/]*\/@[^/]*\/([^?]*).*?$/i, "https://shop.r10s.jp/$1"), "rakuten.co.jp": (urlObj, opt) => domainOptimizers["thumbnail.image.rakuten.co.jp"](urlObj, opt), "r10s.jp": (urlObj, opt) => domainOptimizers["thumbnail.image.rakuten.co.jp"](urlObj, opt), "cdn-icons-png.flaticon.com": (urlObj, opt) => opt.replace(/\/(?:32|64|128|256)\/+([0-9]+\/+[0-9]+\.)/i, "/512/$1").replace(/[?&]fd=1/gi, ""), "flaticon.com": (urlObj, opt) => domainOptimizers["cdn-icons-png.flaticon.com"](urlObj, opt), "image.ibb.co": (urlObj, opt) => opt.replace(/_[0-9]+x[0-9]+\.(jpg|jpeg|png)$/i, ".$1"), "i.ibb.co": (urlObj, opt) => opt.replace(/\.md\.([a-zA-Z0-9]+)$/i, ".$1").replace(/\.th\.([a-zA-Z0-9]+)$/i, ".$1"), "i.kfs.io": (urlObj, opt) => opt.replace(/\/(?:fit|cropresize)\/+[0-9]+x[0-9]+(\.[^/.]*)(?:[?#].*)?$/i, "/original$1"), "kfs.io": (urlObj, opt) => domainOptimizers["i.kfs.io"](urlObj, opt), "static.vecteezy.com": (urlObj, opt) => opt.replace(/\/system\/resources\/(?:thumbnails|previews)\/([0-9/]+)\/(?:small(?:_2x)?|non_2x)\//i, "/system/resources/previews/$1/original/"), "vecteezy.com": (urlObj, opt) => domainOptimizers["static.vecteezy.com"](urlObj, opt), "wallpapercave.com": (urlObj, opt) => opt.replace(/\/w\/(wp[0-9]+)/i, "/wp/$1"), "imagesvc.meredithcorp.io": (urlObj, opt) => { const match = opt.match(/[?&]url=([^&]+)/i); if (match) try { return decodeURIComponent(match[1]); } catch (e) {} return opt; }, "allrecipes.com": (urlObj, opt) => domainOptimizers["imagesvc.meredithcorp.io"](urlObj, opt), "static.wikia.nocookie.net": (urlObj, opt) => opt.replace(/\/revision\/latest\/(?:scale-to-width-down|smart)\/[0-9]+\?/i, "/").replace(/\/cb[0-9]+\//i, "/"), "cdn.akamai.steamstatic.com": removeParams(["imw", "imh"]), "ids.si.edu": (urlObj, opt) => { const match = opt.match(/\/ids\/+deliveryService\?id=([^&]+)/i); if (match) return `https://ids.si.edu/ids/iiif/${match[1]}/full/full/0/default.jpg`; return opt; }, "si.edu": (urlObj, opt) => domainOptimizers["ids.si.edu"](urlObj, opt), "espncdn.com": (urlObj, opt) => { let clean = opt.replace(/.*\/combiner\/i\?img=/i, "https://a.espncdn.com"); clean = clean.replace(/_[0-9]+x[0-9]+(?:_[0-9-]+)?\.(jpg|jpeg|png)$/i, ".$1"); return standardCdnPurge(urlObj, clean); }, "a.espncdn.com": (urlObj, opt) => domainOptimizers["espncdn.com"](urlObj, opt), "img.mlbstatic.com": (urlObj, opt) => opt.replace(/\/image\/upload\/t_[^/]+\//gi, "/image/upload/"), "mlbstatic.com": (urlObj, opt) => domainOptimizers["img.mlbstatic.com"](urlObj, opt), "hips.hearstapps.com": (urlObj, opt) => { const match = opt.match(/hips\.hearstapps\.com\/([^?#]+)/i); if (match) return `https://${match[1]}`; return removeParams(["fill", "resize"])(urlObj, opt); }, "hearstapps.com": (urlObj, opt) => domainOptimizers["hips.hearstapps.com"](urlObj, opt), "blogs.loc.gov": (urlObj, opt) => opt.replace(/-scaled\.(jpg|jpeg|png)$/i, ".$1"), "loc.gov": (urlObj, opt) => domainOptimizers["blogs.loc.gov"](urlObj, opt), "nytimes.com": (urlObj, opt) => opt.replace(/-master[0-9]+\./i, "-superJumbo.").replace(/\/master[0-9]+\./i, "/superJumbo."), "nyt.com": (urlObj, opt) => opt.replace(/-master[0-9]+\./i, "-superJumbo.").replace(/\/master[0-9]+\./i, "/superJumbo."), "gannett-cdn.com": (urlObj, opt) => opt.replace(/\/-mm-\/[^/]*\/-\//i, "/"), "usatoday.com": (urlObj, opt) => { return opt.replace(/\/-mm-\/[^/]*\/-\//i, "/").replace(/usatoday\.com\/gcdn\//i, "gannett-cdn.com/"); }, "nbcnews.com": standardCdnPurge, "foxnews.com": standardCdnPurge, "cnn.com": removeParams(["w", "width"]), "cnbc.com": standardCdnPurge, "forbes.com": standardCdnPurge, "businessinsider.com": standardCdnPurge, "insider.com": standardCdnPurge, "wired.com": standardCdnPurge, "cnet.com": standardCdnPurge, "gizmodo.com": standardCdnPurge, "ign.com": standardCdnPurge, "ichef.bbci.co.uk": (urlObj, opt) => { let clean = opt.replace(/(\.(?:jpg|png))\.webp([?#].*)?$/i, "$1$2"); clean = clean.replace(/\/[0-9]+_[0-9]+\//i, "/original/"); return clean.replace(/\/images\/ic\/[0-9]+x[0-9]+\//i, "/images/ic/raw/"); }, "bbci.co.uk": (urlObj, opt) => domainOptimizers["ichef.bbci.co.uk"](urlObj, opt), "bbc.co.uk": (urlObj, opt) => domainOptimizers["ichef.bbci.co.uk"](urlObj, opt), "dynaimage.cdn.cnn.com": (urlObj, opt) => { const match = opt.match(/\/cnn\/[^/]*\/(https?%3A%2F%2F.*|http.*)/i); if (match) try { return decodeURIComponent(match[1]); } catch (e) {} return opt; }, "i.guim.co.uk": (urlObj, opt) => { return standardCdnPurge(urlObj, opt.replace(/:\/\/[^/]*\/img\/([^/]*)\/([^?]*).*?$/i, "://$1.guim.co.uk/$2")); }, "media.guim.co.uk": (urlObj, opt) => standardCdnPurge(urlObj, opt), "guim.co.uk": (urlObj, opt) => domainOptimizers["i.guim.co.uk"](urlObj, opt), "a0.muscache.com": (urlObj, opt) => removeParams([ "aki_policy", "im_w", "im_q" ])(urlObj, opt), "muscache.com": (urlObj, opt) => domainOptimizers["a0.muscache.com"](urlObj, opt), "img.freepik.com": (urlObj, opt) => { try { const u = new URL(opt); u.searchParams.delete("size"); u.searchParams.set("w", "5000"); return u.toString(); } catch (e) { return opt; } }, "images.goodsmile.info": (urlObj, opt) => opt.replace(/\/cgm\/images\/product\/([0-9/]+)\/(?:medium|large)\//i, "/cgm/images/product/$1/original/"), "goodsmile.info": (urlObj, opt) => domainOptimizers["images.goodsmile.info"](urlObj, opt), "reuters.com": removeParams(["w", "width"]), "reutersmedia.net": removeParams(["w", "width"]), "bwbx.io": removeParams(["width", "w"]), "nationalgeographic.com": (urlObj, opt) => opt.replace(/\.adapt\.[0-9]+\.[1-9]/i, ""), "alamy.com": (urlObj, opt) => { const host = urlObj.hostname.toLowerCase(); if (/^c[0-9]*\./.test(host)) return opt.replace(/^[a-z]+:\/\/[^/]+\/+comp\/+([0-9A-Z]{5,10})\/+.*/i, "https://www.alamy.com/$1"); return opt; }, "gettyimages.com": removeParams([ "w", "h", "fit" ]), "istockphoto.com": removeParams([ "w", "h", "fit" ]), "shutterstock.com": (urlObj, opt) => opt.replace(/-[0-9]+nw\./i, "."), "dreamstime.com": standardCdnPurge, "123rf.com": standardCdnPurge, "unsplash.com": removeParams([ "w", "h", "crop", "fit" ]), "pexels.com": removeParams([ "w", "h", "fit", "crop" ]), "pixabay.com": (urlObj, opt) => opt.replace(/__[0-9]+x?[0-9]*\./i, ".").replace(/_(?:[0-9]+|640|480|340)\./i, "_1280."), "goodreads.com": (urlObj, opt) => opt.replace(/\._S[XY][0-9]+_\.(jpg|jpeg|png)/i, ".$1"), "imdb.com": (urlObj, opt) => opt.replace(/\._V1_[a-zA-Z0-9_,=+-]+\.(jpg|jpeg|png|gif)/i, ".$1"), "media-amazon.com": (urlObj, opt) => opt.replace(/\._V1_[a-zA-Z0-9_,=+-]+\.(jpg|jpeg|png|gif)/i, ".$1"), "wordpress": (urlObj, opt) => { if (opt.includes("webp-express/webp-images/uploads/")) opt = opt.replace(/(\/wp-content\/+)webp-express\/+webp-images\/(uploads\/.*?)(\.[a-z]+)\.webp(?:[?#].*)?$/i, "$1$2$3"); else if (opt.includes("smush-webp/")) opt = opt.replace(/(\/wp-content\/+)smush-webp(\/.*\.[a-z]+)\.webp(?:[?#].*)?$/i, "$1uploads$2"); return opt.replace(/-[0-9]+x[0-9]+(?:_[a-zA-Z0-9]+)?\.([a-zA-Z0-9]+)$/i, ".$1"); }, "wp.com": (urlObj, opt) => { if (/\/\/i[0-2]\.wp\.com\//.test(opt)) { const clean = opt.replace(/\/\/i[0-2]\.wp\.com\//, "//"); try { const localObj = new URL(clean); localObj.searchParams.delete("w"); localObj.searchParams.delete("h"); localObj.searchParams.delete("fit"); localObj.searchParams.delete("resize"); localObj.searchParams.delete("strip"); return localObj.toString(); } catch (e) { return clean; } } return opt; }, "googleusercontent.com": (urlObj, opt) => { opt = opt.replace(/\/s[0-9]+(-h[0-9]+)?(-[a-zA-Z0-9_-]+)?\//g, "/s0/"); opt = opt.replace(/=w[0-9]+-h[0-9]+(-[a-zA-Z0-9_-]+)?/g, "=s0"); return opt.replace(/=s[0-9]+/g, "=s0"); }, "blogspot.com": (urlObj, opt) => domainOptimizers["googleusercontent.com"](urlObj, opt), "ggpht.com": (urlObj, opt) => domainOptimizers["googleusercontent.com"](urlObj, opt), "play-lh.googleusercontent.com": (urlObj, opt) => domainOptimizers["googleusercontent.com"](urlObj, opt), "nocookie.net": (urlObj, opt) => opt.replace(/(\/revision\/latest)\/[^?#]+/i, "$1"), "wikia.nocookie.net": (urlObj, opt) => opt.replace(/\/revision\/latest\/scale-to-width-down\/[0-9]+/i, "/revision/latest"), "weebly.com": (urlObj, opt) => opt.replace(/\/uploads\/[0-9/]+\/published\//i, "/uploads/"), "editmysite.com": (urlObj, opt) => opt.replace(/\/uploads\/[0-9/]+\/published\//i, "/uploads/"), "squarespace.com": (urlObj, opt) => { try { const localObj = new URL(opt); if (localObj.searchParams.has("format")) { localObj.searchParams.set("format", "original"); return localObj.toString(); } } catch (e) {} return opt; }, "squarespace-cdn.com": (urlObj, opt) => domainOptimizers["squarespace.com"](urlObj, opt), "ghost.io": (urlObj, opt) => opt.replace(/\/content\/images\/size\/w[0-9]+\//i, "/content/images/"), "linktr.ee": (urlObj, opt) => opt.replace(/[?&]profile=[0-9]+/gi, ""), "tistory.com": (urlObj, opt) => opt.replace(/\/image\/[C_][0-9]+x[0-9]+\//gi, "/image/"), "daumcdn.net": (urlObj, opt) => { if (opt.includes("/thumb/")) try { return decodeURIComponent(opt.replace(/.*fname=([^&]*).*/, "$1")); } catch (e) {} return opt.replace(/\/image\/[C_][0-9]+x[0-9]+\//gi, "/image/"); }, "bandcamp.com": (urlObj, opt) => domainOptimizers["bcbits.com"](urlObj, opt), "ibb.co": (urlObj, opt) => opt.replace(/\.md\.([a-zA-Z0-9]+)$/i, ".$1"), "winudf.com": (urlObj, opt) => opt.replace(/\/(?:w|h)\/[0-9]+/gi, ""), "amazonaws.com": (urlObj, opt) => opt.replace(/[-_](?:[0-9]+x[0-9]+|thumb|small|scaled|preview)\.([a-zA-Z0-9]+)$/i, ".$1"), "alicdn.com": (urlObj, opt) => opt.replace(/(\.(?:jpg|jpeg|png|gif|webp))_[0-9a-zA-Z._x#]+$/i, "$1"), "steamcdn-a.akamaihd.net": (urlObj, opt) => { try { const localObj = new URL(opt); localObj.searchParams.delete("imw"); localObj.searchParams.delete("imh"); localObj.searchParams.delete("ima"); opt = localObj.toString().replace(/\/resize\/[0-9]+x\//i, "/"); } catch (e) {} if (opt.includes("/apps/") && opt.match(/\.[0-9]+x[0-9]+\.(?:jpg|jpeg|png)$/i)) opt = opt.replace(/\.[0-9]+x[0-9]+\.([a-zA-Z0-9]+)$/i, ".$1"); if (opt.includes("/apps/") && opt.includes("library_") && !opt.includes("_2x")) opt = opt.replace(/(library_[0-9]+x[0-9]+)(\.[a-zA-Z0-9]+)$/i, "$1_2x$2"); if (opt.includes("/avatars/")) { if (opt.match(/_(?:medium|reasonably_small)\.([a-zA-Z0-9]+)$/i)) opt = opt.replace(/_(?:medium|reasonably_small)\.([a-zA-Z0-9]+)$/i, "_full.$1"); else if (!opt.includes("_full") && !opt.includes("_medium")) opt = opt.replace(/\.([a-zA-Z0-9]+)$/i, "_full.$1"); } return opt; }, "steamstatic.com": (urlObj, opt) => domainOptimizers["steamcdn-a.akamaihd.net"](urlObj, opt), "patreonusercontent.com": (urlObj, opt) => opt.replace(/[?&]w=[0-9]+/gi, "").replace(/[?&]h=[0-9]+/gi, ""), "redgifs.com": (urlObj, opt) => opt.replace(/-mobile\.(mp4|gif|webp)/i, ".$1").replace(/-poster\.(jpg|jpeg)/i, ".$1"), "fanbox.cc": (urlObj, opt) => opt.replace(/_[0-9]+x[0-9]+_(?:box|rect)\.([a-zA-Z0-9]+)$/i, ".$1"), "imagedelivery.net": (urlObj, opt) => { const parts = opt.split("/"); if (parts.length > 2) { const lastSeg = parts[parts.length - 1]; if (lastSeg.includes("=") || lastSeg.includes("crop") || lastSeg.includes("fit")) { parts[parts.length - 1] = "public"; return parts.join("/"); } } return opt; }, "fastly.net": (urlObj, opt) => opt.replace(/[?&]width=[0-9]+/gi, "").replace(/[?&]height=[0-9]+/gi, ""), "yimg.com": (urlObj, opt) => opt.replace(/--\/resize=[0-9]+x[0-9]+/gi, ""), "photobucket.com": (urlObj, opt) => opt.replace(/[?&](?:width|height)=[0-9]+/gi, "").replace(/~(?:original|zps[^/]+)/i, ""), "s-microsoft.com": removeParams(["w", "h"]), "vox-cdn.com": standardCdnPurge, "theatlantic.com": standardCdnPurge, "huffpost.com": standardCdnPurge, "huffingtonpost.com": standardCdnPurge, "wsj.net": standardCdnPurge, "latimes.com": standardCdnPurge, "washingtonpost.com": (urlObj, opt) => { if (opt.includes("imrs.php")) { const src = urlObj.searchParams.get("src"); if (src) try { return decodeURIComponent(src); } catch (e) { return src; } } return standardCdnPurge(urlObj, opt); }, "nydailynews.com": standardCdnPurge, "nypost.com": standardCdnPurge, "time.com": standardCdnPurge, "timeinc.net": standardCdnPurge, "newsweek.com": standardCdnPurge, "economist.com": standardCdnPurge, "newrepublic.com": standardCdnPurge, "slate.com": standardCdnPurge, "salon.com": standardCdnPurge, "billboard.com": standardCdnPurge, "hollywoodreporter.com": standardCdnPurge, "people.com": standardCdnPurge, "usmagazine.com": standardCdnPurge, "eonline.com": standardCdnPurge, "tmz.com": standardCdnPurge, "espn.com": standardCdnPurge, "si.com": standardCdnPurge, "cbsistatic.com": standardCdnPurge, "bleacherreport.com": standardCdnPurge, "bleacherreport.net": standardCdnPurge, "nba.com": standardCdnPurge, "lifehacker.com": standardCdnPurge, "kotaku.com": standardCdnPurge, "kotaku.com.au": standardCdnPurge, "mashable.com": standardCdnPurge, "zdnet.com": standardCdnPurge, "pcmag.com": standardCdnPurge, "scientificamerican.com": standardCdnPurge, "livescience.com": standardCdnPurge, "smithsonianmag.com": standardCdnPurge, "seriouseats.com": standardCdnPurge, "marthastewart.com": standardCdnPurge, "vogue.com": standardCdnPurge, "gq.com": standardCdnPurge, "vanityfair.com": standardCdnPurge, "glamour.com": standardCdnPurge, "allure.com": standardCdnPurge, "self.com": standardCdnPurge, "architecturaldigest.com": standardCdnPurge, "cntraveler.com": standardCdnPurge, "kbb.com": standardCdnPurge, "wsj.net/public/resources/images": standardCdnPurge, "ft.com": standardCdnPurge, "reuters.tv": removeParams(["w", "width"]), "nytimes.com/images": standardCdnPurge, "inc.com": standardCdnPurge, "entrepreneur.com": standardCdnPurge, "metmuseum.org": standardCdnPurge, "getty.edu": standardCdnPurge, "artic.edu": standardCdnPurge, "sfmoma.org": standardCdnPurge, "nationalgallery.org.uk": standardCdnPurge, "vam.ac.uk": standardCdnPurge, "webmd.com": standardCdnPurge, "nih.gov": standardCdnPurge, "psychologytoday.com": standardCdnPurge, "menshealth.com": standardCdnPurge, "britannica.com": standardCdnPurge, "history.com": standardCdnPurge, "biography.com": standardCdnPurge, "pbs.org": standardCdnPurge, "npr.org": standardCdnPurge, "quizlet.com": standardCdnPurge, "weather.com": standardCdnPurge, "craigslist.org": (urlObj, opt) => opt.replace(/_[0-9]+x[0-9]+\.jpg$/i, "_1200x900.jpg"), "century21.com": standardCdnPurge, "img1-fg.wfcdn.com": standardCdnPurge, "secure.img1-fg.wfcdn.com": standardCdnPurge, "deviantart.net": (urlObj, opt) => domainOptimizers["deviantart.com"](urlObj, opt), "imageshack.com": (urlObj, opt) => domainOptimizers["imageshack.us"](urlObj, opt), "n.nordstrommedia.com": (urlObj, opt) => { const srcMatch = opt.match(/\/i\/s\/.*\?(?:.*&)?(?:\$p_)?src=(https?%3A.*?)(?:&.*)?$/i); if (srcMatch) try { return decodeURIComponent(srcMatch[1]); } catch (e) {} try { const u = new URL(opt); u.searchParams.delete("crop"); u.searchParams.delete("w"); u.searchParams.delete("h"); u.searchParams.set("scl", "1"); return u.toString(); } catch (e) { return opt; } }, "cdn.instructables.com": (urlObj, opt) => opt.replace(/\/+[^/]+\.(?:RECTANGLE1|SQUARE|RECTANGLE|MEDIUM|SMALL|THUMB)\.([a-zA-Z0-9]+)$/i, "/ORIG/.$1"), "content.instructables.com": (urlObj, opt) => domainOptimizers["cdn.instructables.com"](urlObj, opt), "shopify.com": (urlObj, opt) => opt.replace(/_(?:[0-9]+x[0-9]*|[0-9]*x[0-9]+|compact|compact_cropped|logo|small|thumb|medium|large|grande|1024x1024|2048x2048|[0-9]+x|x[0-9]+)(?:_crop_[a-z]+)?(_progressive)?\.(jpg|jpeg|png|gif|webp)/i, ".$2"), "cdn.shopify.com": (urlObj, opt) => domainOptimizers["shopify.com"](urlObj, opt), "miro.medium.com": (urlObj, opt) => opt.replace(/\/(?:fit|max)\/[0-9]+\/([a-zA-Z0-9_*.-]+@?[0-9]*x?\.(?:jpg|jpeg|png|gif|webp))/i, "/$1"), "cdn-images-1.medium.com": (urlObj, opt) => opt.replace(/\/(?:fit|max)\/[0-9]+\/([a-zA-Z0-9_*.-]+@?[0-9]*x?\.(?:jpg|jpeg|png|gif|webp))/i, "/$1"), "wixstatic.com": (urlObj, opt) => opt.replace(/\/v1\/fill\/w_[0-9]+,h_[0-9]+[^/]+\/([^?#]+)/i, "/$1"), "gravatar.com": (urlObj, opt) => { try { const u = new URL(opt); u.searchParams.set("s", "2048"); return u.toString(); } catch (e) { return opt; } }, "s.gravatar.com": (urlObj, opt) => domainOptimizers["gravatar.com"](urlObj, opt), "discourse-cdn.com": (urlObj, opt) => opt.replace(/\/_optimized\/[^?#]+/gi, ""), "static1.squarespace.com": (urlObj, opt) => domainOptimizers["squarespace.com"](urlObj, opt), "imageshack.us": (urlObj, opt) => opt.replace(/\.th\./i, "."), "tinypic.com": (urlObj, opt) => opt.replace(/_th\./i, "."), "fbcdn.net": (urlObj, opt) => opt.replace(/_[nq]\.jpg$/i, ".jpg"), "licdn.com": (urlObj, opt) => { try { const u = new URL(opt); u.searchParams.delete("shrink"); return u.toString(); } catch (e) { return opt; } }, "ftcdn.net": (urlObj, opt) => opt.replace(/_(?:500|1000)\.(jpg|jpeg|png|gif)$/i, ".$1"), "stock.adobe.com": (urlObj, opt) => domainOptimizers["ftcdn.net"](urlObj, opt), "substack.com": standardCdnPurge, "vimeo.com": (urlObj, opt) => opt.replace(/_[0-9]+x[0-9]+\.(jpg|jpeg|png|gif)$/i, ".$1"), "worldatlas.com": (urlObj, opt) => opt.replace(/(:\/\/[^/]*\/)r\/[^/]*\/(upload\/)/, "$1$2"), "thrillist.com": (urlObj, opt) => opt.replace(/(:\/\/[^/]+\/+v1\/+image\/+[0-9]+)(?:\/.*)?(?:[?#].*)?$/, "$1"), "vacationidea.com": standardCdnPurge, "weheartit.com": (urlObj, opt) => opt.replace(/\/(?:super_large|thumb)\//i, "/original/"), "zerochan.net": (urlObj, opt) => opt.replace(/\.(?:240|600)\./i, ".full."), "static.zerochan.net": (urlObj, opt) => domainOptimizers["zerochan.net"](urlObj, opt), "dpreview.com": standardCdnPurge, "e-hentai.org": (urlObj, opt) => opt.replace(/\/t\//i, "/g/"), "ehgt.org": (urlObj, opt) => domainOptimizers["e-hentai.org"](urlObj, opt), "eskipaper.com": (urlObj, opt) => opt.replace(/\/thumb\//i, "/"), "esquire.com": standardCdnPurge, "eyeem.com": (urlObj, opt) => opt.replace(/\/(?:thumb|medium)\//i, "/"), "fc2.com": (urlObj, opt) => { return opt.replace(/blog-imgs-([0-9]+)\.fc2\.com/i, "blog-imgs-$1-origin.fc2.com").replace(/([0-9a-zA-Z_-]+)s\.(jpg|jpeg|png|gif)$/i, "$1.$2"); }, "downloads.fanbox.cc": (urlObj, opt) => opt.replace(/\/images\/post\/([0-9]+)\/w\/[0-9]+\/([^/]+)/i, "/images/post/$1/$2"), "cdn-ak.f.st-hatena.com": (urlObj, opt) => opt.replace(/_(?:120|[a-z])\.(jpg|jpeg|png|gif)$/i, ".$1"), "cdn.image.st-hatena.com": replaceAndDecode(/\/image\/scale\/[^/]+\/backend=imager;enlarge=0;height=[0-9]+;version=[0-9]+;width=[0-9]+\/(.*)/i, "$1"), "stat.ameba.jp": (urlObj, opt) => { try { const u = new URL(opt); u.searchParams.delete("caw"); u.searchParams.delete("cat"); u.searchParams.delete("cp"); return u.toString(); } catch (e) { return opt; } }, "stat.profile.ameba.jp": (urlObj, opt) => domainOptimizers["stat.ameba.jp"](urlObj, opt), "stat.blogskin.ameba.jp": (urlObj, opt) => domainOptimizers["stat.ameba.jp"](urlObj, opt), "files.wordpress.com": (urlObj, opt) => opt.replace(/[?&].*$/i, ""), "findagrave.com": (urlObj, opt) => opt.replace(/_thumb/i, ""), "fineartamerica.com": standardCdnPurge, "allbirds.com": (urlObj, opt) => domainOptimizers["shopify.com"](urlObj, opt), "spigen.com": (urlObj, opt) => domainOptimizers["shopify.com"](urlObj, opt), "images-amazon.com": (urlObj, opt) => domainOptimizers["media-amazon.com"](urlObj, opt), "ssl-images-amazon.com": (urlObj, opt) => domainOptimizers["media-amazon.com"](urlObj, opt), "macrumors.com": (urlObj, opt) => domainOptimizers["wordpress"](urlObj, opt), "androidpolice.com": (urlObj, opt) => domainOptimizers["wordpress"](urlObj, opt), "windowscentral.com": (urlObj, opt) => domainOptimizers["wordpress"](urlObj, opt), "nymag.com": (urlObj, opt) => domainOptimizers["wordpress"](urlObj, opt), "media.tumblr.com": (urlObj, opt) => domainOptimizers["tumblr.com"](urlObj, opt), "wixmp.com": (urlObj, opt) => domainOptimizers["wixstatic.com"](urlObj, opt), "images.squarespace-cdn.com": (urlObj, opt) => domainOptimizers["squarespace.com"](urlObj, opt), "typepad.com": standardCdnPurge, "livejournal.com": standardCdnPurge, "pinterest.com": (urlObj, opt) => domainOptimizers["pinimg.com"](urlObj, opt), "gog.com": standardCdnPurge, "gog-statics.com": standardCdnPurge, "itch.zone": standardCdnPurge, "itch.io": standardCdnPurge, "nexusmods.com": standardCdnPurge, "moddb.com": standardCdnPurge, "githubusercontent.com": standardCdnPurge, "raw.githubusercontent.com": standardCdnPurge, "bitbucket.org": standardCdnPurge, "stackexchange.com": (urlObj, opt) => domainOptimizers["stack.imgur.com"](urlObj, opt), "stackoverflow.com": (urlObj, opt) => domainOptimizers["stack.imgur.com"](urlObj, opt), "redditmedia.com": (urlObj, opt) => domainOptimizers["i.redd.it"](urlObj, opt), "redditstatic.com": (urlObj, opt) => domainOptimizers["reddit.com"](urlObj, opt), "discord.com": (urlObj, opt) => domainOptimizers["cdn.discordapp.com"](urlObj, opt), "olympics.com": standardCdnPurge, "eurosport.com": standardCdnPurge, "skysports.com": standardCdnPurge, "motorsport.com": standardCdnPurge, "topspeed.com": standardCdnPurge, "nationalgallery.org": (urlObj, opt) => domainOptimizers["nationalgallery.org.uk"](urlObj, opt), "wikipedia.org": (urlObj, opt) => domainOptimizers["upload.wikimedia.org"](urlObj, opt), "wikimedia.org": (urlObj, opt) => domainOptimizers["upload.wikimedia.org"](urlObj, opt), "kakaocdn.net": replaceAndDecode(new RegExp(".*fname=([^&]*).*", ""), "$1"), "saostar.vn": (urlObj, opt) => { if (urlObj.hostname.match(/img[0-9]*\.saostar\.vn/)) return opt.replace(/saostar\.vn\/fb[0-9]+[^/]*(\/.*\.[^/.]*)\/[^/]*$/, "saostar.vn$1").replace(/saostar\.vn\/[a-z][0-9]+\//, "saostar.vn/").replace(/saostar\.vn\/[0-9]+x[0-9]+\//, "saostar.vn/"); return opt; }, "stephenking.com": replaceSuffix(new RegExp(".*image=([^&]*).*", ""), "$1"), "forums.audioholics.com": replaceSuffix(new RegExp(".*image=([^&]*).*", ""), "$1"), "ambercutie.com": replaceSuffix(new RegExp(".*image=([^&]*).*", ""), "$1"), "overlandbound.com": replaceSuffix(new RegExp(".*image=([^&]*).*", ""), "$1"), "lf127.net": replaceSuffix(new RegExp("\\?.*$", ""), ""), "pic-bucket.ws.126.net": replaceSuffix(new RegExp("\\?.*$", ""), ""), "board.makeshop.co.kr": replaceSuffix(new RegExp(".*src=([^&]*).*", ""), "$1"), "naver.jp": (urlObj, opt) => { if (urlObj.hostname.match(/^rr\.img[0-9]*\.naver\./)) { const match = opt.match(/^[a-z]+:\/\/[^/]*\/mig.*?[?&]src=([^&]*)/i); if (match && match[1]) try { return decodeURIComponent(match[1]); } catch (e) {} } return opt; }, "cdn.hk01.com": (urlObj, opt) => { return opt.replace(/\?.*/, "").replace(/(\/media\/+images\/+[0-9]+\/+)[^/]+\//, "$1org/"); }, "ceros.com": replaceSuffix(new RegExp("\\?.*", ""), ""), "aolcdn.com": replaceAndDecode(new RegExp(".*image_uri=([^&]*).*", ""), "$1"), "cdn-img.instyle.com": replaceSuffix(new RegExp("\\?.*", ""), ""), "etonline.com": replaceSuffix(new RegExp("\\?.*", ""), ""), "cdn.cms-twdigitalassets.com": replaceSuffix(new RegExp("(\\.[a-z]+)\\.twimg\\.[0-9]+\\.[a-z]+(?:[?#].*)?$", ""), "$1"), "cdn.carhp.in": (urlObj, opt) => opt.replace(/\?.*$/, ""), "kwcdn.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "fengimg.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "pddpic.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "sdn.cz": replaceSuffix(new RegExp("\\?.*$", ""), ""), "framerusercontent.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "ztat.net": replaceSuffix(new RegExp("\\?.*$", ""), ""), "img-static.tradesy.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "store-images.microsoft.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "store-images.s-microsoft.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "bristolworld.com": replaceSuffix(new RegExp("\\?.*", ""), "?quality=100"), "rednotecdn.com": replaceSuffix(new RegExp("^([^!?]+)(?:[!?].*)?$", ""), "$1?imageView2/2/w/format/png"), "sns-webpic-qc.xhscdn.com": replaceSuffix(new RegExp("^([^!?]+)(?:[!?].*)?$", ""), "$1?imageView2/2/w/format/png"), "dailyherald.com": replaceSuffix(new RegExp("[?&].*$", ""), ""), "dagsavisen.no": replaceSuffix(new RegExp("\\?.*", ""), ""), "opopular.com.br": polopolyFsOptimizer, "quotidiano.net": polopolyFsOptimizer, "edp24.co.uk": polopolyFsOptimizer, "www.tsn.ca": (urlObj, opt) => { return opt.replace(/(\/images\/+[0-9]{4}\/+(?:[0-9]{1,2}\/+){2}[^/]+)\/+jcr:.*/, "$1").replace(/(\/image\.[^_/]*_gen\/+derivatives\/+)[^/]*\//, "$1default/"); }, "tsn.ca": (urlObj, opt) => domainOptimizers["www.tsn.ca"](urlObj, opt), "imgix.ranker.com": replaceSuffix(new RegExp("\\?.*$", ""), "?fm=png"), "gd-hbimg.huaban.com": replaceSuffix(new RegExp("_fw[0-9]*$", ""), ""), "upaiyun.com": replaceSuffix(new RegExp("_fw[0-9]*$", ""), ""), "www.theactuary.com": replaceSuffix(new RegExp("getresource\\.axd\\?.*(AssetID=[0-9]*).*", ""), "getresource.axd?$1"), "roguewavecoffee.ca": (urlObj, opt) => { const clean = opt.replace(/(\.[a-z]+)\.webp(?:[?#].*)?$/i, "$1"); return domainOptimizers["shopify.com"](urlObj, clean); }, "medias.unifrance.org": replaceSuffix(new RegExp("\\?.*", ""), ""), "lthumb.lisimg.com": (urlObj, opt) => { return opt.replace(/\?.*/, "").replace(/^[a-z]+:\/\/[^/]+\/+[0-9]+\/+([0-9]+)\..*/i, "https://ilarge.lisimg.com/image/$1/0full.jpg"); }, "disp.cc": (urlObj, opt) => opt.replace(/^[a-z]+:\/\/[^/]+\/+imgur\/+([^/?#]+)(?:[?#].*)?$/i, "https://i.imgur.com/$1"), "i.stack.imgur.com": replaceSuffix(new RegExp("\\?.*", ""), ""), "dynamic.indigoimages.ca": replaceSuffix(new RegExp("(\\?.*)?$", ""), "?width=999999999"), "goodfon.ru": (urlObj, opt) => { let clean = opt.replace(/(:\/\/[^/]*\/)[^/]*\/[^/]*\//, "$1wallpaper/original/"); if (/\.webp(?:[?#].*)?$/i.test(opt)) clean = clean.replace(/\.webp(?:[?#].*)?$/i, ".jpg"); return clean; }, "badfon.ru": (urlObj, opt) => domainOptimizers["goodfon.ru"](urlObj, opt), "yimg.jp": (urlObj, opt) => { if (opt.includes("/im_")) return opt.replace(/(:\/\/[^/]*\/)im_[^/]*\//, "$1"); if (opt.includes("/sim?")) return opt.replace(/.*:\/\/[^/]*\/sim.*?[?&]furl=([^&]*).*/, "http://$1"); return opt; }, "imageproxy.themaven.net": replaceAndDecode(/\?.*/, ""), "pic.sucaibar.com": replaceSuffix(new RegExp("\\?down$", ""), ""), "mediaassets.wxyz.com": replaceSuffix(new RegExp("\\?down$", ""), ""), "win4000.com": replaceSuffix(new RegExp("\\?down$", ""), ""), "media.plus.rtl.de": (urlObj, opt) => { return opt.replace(/\?.*/, "").replace(/^[a-z]+:\/\/[^/]+\/+music-deezer\/+(.*\/[0-9]+x[0-9]+-[^/?#]+)(?:[?#].*)?$/i, "https://e-cdns-images.dzcdn.net/images/$1"); }, "img-mdpr.freetls.fastly.net": replaceSuffix(new RegExp("(?:\\?.*)?$", ""), "?quality=100"), "www.sonymusicshop.jp": (urlObj, opt) => { return opt.replace(/__[0-9]+_[0-9]+_[0-9]+_([a-z]+\.[a-z]*)(?:\?.*)?$/, "_$1"); }, "aidu360.com": replaceSuffix(new RegExp(".*?[?&]url=(.*)", ""), "$1"), "img.xiaohuazu.com": (urlObj, opt) => { return opt.replace(/.*?[?&]url=(.*)/, "$1").replace(/z-z/g, ".").replace(/^/, "http://"); }, "file.mk.co.kr": replaceSuffix(new RegExp("\\.thumb(?:[?#].*)?$", ""), ""), "mediapundit.net": replaceSuffix(new RegExp("\\.thumb(?:[?#].*)?$", ""), ""), "izaoxing.com": replaceSuffix(new RegExp("\\&.*", ""), ""), "lrfczp.com": replaceSuffix(new RegExp("\\&.*", ""), ""), "img-toutiao.mia.com": replaceSuffix(new RegExp("\\&.*", ""), ""), "qwant.com": replaceAndDecode(/.*[?&]u=([^&]*).*/, "$1"), "bonprix.scene7.com": replaceSuffix(new RegExp("\\$Thumbnail\\d+\\$", ""), ""), "image.panasonic.com": replaceAndDecode(/\$Thumbnail\d+\$/, ""), "pimpandhost.com": replaceSuffix(new RegExp("_thumb[0-9]+_[0-9]+(\\.[a-zA-Z]+)(?:[?#].*)?$", ""), "$1"), "image.diyidan.net": replaceSuffix(new RegExp("!.*", ""), ""), "yandex.ru": replaceSuffix(new RegExp("\\?.*", ""), ""), "steemitimages.com": multiReplace([[/^[a-z]+:\/\/[^/]*\/[0-9]+x[0-9]+\//i, ""], [/\?.*/, ""]]), "img.cache.vevo.com": replaceSuffix(new RegExp("[?#].*$", ""), ""), "scache.vevo.com": multiReplace([[/[?#].*$/, ""], [/(\/thumb\/[^/]*\/[^/]*)\/[0-9]+x[0-9]+(\.[^/.]*?)(?:[?#].*)?$/, "$1$2"]]), "resize-image.lineblog.me": replaceSuffix(new RegExp("[?].*", ""), ""), "d.line-scdn.net": replaceSuffix(new RegExp("[?].*", ""), ""), "viki.io": replaceSuffix(new RegExp("\\?.*", ""), ""), "galeria.cdn.index.hu": replaceSuffix(new RegExp("_[a-z]{1,2}(\\.[a-z]+)(?:[?#].*)?$", ""), "$1"), "www.op.gg": replaceSuffix(new RegExp("\\.[a-z]*(?:[?#].*)?$", ""), ".orig"), "ssproxy.ucloudbiz.olleh.com": replaceSuffix(new RegExp("\\.[a-z]*(?:[?#].*)?$", ""), ".orig"), "s.gae9.com": replaceSuffix(new RegExp("\\.[a-z]*(?:[?#].*)?$", ""), ".orig"), "ya.sakura.ne.jp": replaceSuffix(new RegExp("[sm](\\.jpg|\\.JPG)$", ""), "$1"), "waganeko.sakura.ne.jp": replaceSuffix(new RegExp("_(?:m|thumb)(\\?.*)?$", ""), "$1"), "storage-yahoo.jp": replaceSuffix(new RegExp("_(?:m|thumb)(\\?.*)?$", ""), "$1"), "img.jandan.net": replaceSuffix(new RegExp("!.*", ""), ""), "img.pikbest.com": replaceSuffix(new RegExp("!.*", ""), ""), "img.mgpyh.com": replaceSuffix(new RegExp("!.*", ""), ""), "community.amd.com": replaceSuffix(new RegExp(".*[?&](v=[^&]*).*", ""), "$1"), "community.spotify.com": (urlObj, opt) => { const v = opt.match(/[?&]v=([^&]*)/); const vStr = v ? "?" + v[0] : ""; return opt.replace(/(\/image-id\/+[0-9]+i[0-9A-F]+)\/+image-(?:size|dimensions|coordinates)\/.*(?:\?.*)?$/, "$1" + vStr); }, "nasz.orange.pl": (urlObj, opt) => domainOptimizers["community.spotify.com"](urlObj, opt), "www.favepeople.com": replaceSuffix(new RegExp(".*\\?url=([^&]*).*?$", ""), "$1"), "img.anews.com": replaceAndDecode(/.*\?url=([^&]*).*?$/, "$1"), "d2192bm55jmxp1.cloudfront.net": replaceSuffix(new RegExp("\\.jpg$", ""), ".jpeg"), "i.marieclaire.com.tw": (urlObj, opt) => { let clean = opt.replace(/\/[0-9]+X[0-9]+\/([0-9A-F]+\.[^/.]*)$/i, "/$1"); if (clean !== opt) return clean.replace(/\.jpg$/i, ".jpeg"); return opt; }, "images.biglots.com": hmCdnOptimizer, "d2n4wb9orp1vta.cloudfront.net": replaceSuffix(new RegExp(";.*", ""), ""), "cdn.metrotvnews.com": replaceSuffix(new RegExp("\\?.*", ""), "?w=99999999999"), "cdn.medcom.id": replaceSuffix(new RegExp("\\?.*", ""), "?w=99999999999"), "ysbnow.com": replaceSuffix(new RegExp("-[a-z]+(?:\\?.*)?$", ""), ""), "quizz.biz": replaceSuffix(new RegExp("-[a-z]+(?:\\?.*)?$", ""), ""), "flipagramcdn.com": replaceSuffix(new RegExp("-[a-z]+(?:\\?.*)?$", ""), ""), "netflixmovies.com": replaceSuffix(new RegExp("\\.tmb-img-[0-9]*\\.", ""), "."), "shakespearesglobe.com": replaceSuffix(new RegExp("\\.tmb-img-[0-9]*\\.", ""), "."), "rsc.org.uk": replaceSuffix(new RegExp("\\.tmb-img-[0-9]*\\.", ""), "."), "lazygirls.info": replaceSuffix(new RegExp("\\.thumb\\.jpg$", ""), ".sized.jpg"), "lzimages.lazygirls.info": replaceSuffix(new RegExp("\\.(?:thumb|sized)$", ""), ""), "static.congnghe.vn": (urlObj, opt) => opt.replace(new RegExp("\\\\", "g"), "/"), "congnghe.vn": (urlObj, opt) => opt.replace(new RegExp("\\\\", "g"), "/"), "pic.58pic.com": replaceSuffix(new RegExp("!.*", ""), ""), "90sjimg.com": replaceSuffix(new RegExp("!.*", ""), ""), "699pic.com": replaceSuffix(new RegExp("!.*", ""), ""), "pic.qiantucdn.com": replaceSuffix(new RegExp("!.*", ""), ""), "dxycdn.com": replaceSuffix(new RegExp("!.*", ""), ""), "images.csmonitor.com": replaceSuffix(new RegExp("(?:\\?.*)?$", ""), "?alias=original"), "modagid.ru": replaceSuffix(new RegExp("~[0-9]+x[0-9]+([?#].*)?$", ""), "~original$1"), "zdf.de": replaceSuffix(new RegExp("~[0-9]+x[0-9]+([?#].*)?$", ""), "~original$1"), "ndr.de": replaceSuffix(new RegExp("(_v-)content(?:klein|gross|xl)\\.", ""), "$1fullhd."), "avisen.dk": replaceSuffix(new RegExp("([?&]sizeid=)[0-9]+", ""), "$1255"), "dailyhunt.in": replaceSuffix(new RegExp("\\.webp(?:[?#].*)?$", ""), ".jpg"), "zmones.lt": replaceSuffix(new RegExp("(\\.[a-z]+)\\.webp(?:[?#].*)?$", ""), "$1"), "hsmedia.ru": (urlObj, opt) => { return opt.replace(/(\.[a-z]+)\.webp(?:[?#].*)?$/i, "$1").replace(/\?.*/, ""); }, "elle.ru": (urlObj, opt) => domainOptimizers["hsmedia.ru"](urlObj, opt), "glanacion.com": replaceSuffix(new RegExp("_[0-9]+x[0-9]+(\\?.*)?$", ""), "$1"), "storage.gra1.cloud.ovh.net": replaceSuffix(new RegExp("_[0-9]+x[0-9]+(\\?.*)?$", ""), "$1"), "images.ctfassets.net": replaceSuffix(new RegExp("\\?.*$", ""), ""), "ctfassets.net": replaceSuffix(new RegExp("\\?.*$", ""), ""), "imgs.ckcdn.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "i.imgscc.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "vcg.com": replaceSuffix(new RegExp("\\?x-oss-process.*", ""), ""), "cfp.cn": (urlObj, opt) => opt.replace(new RegExp("\\?x-oss-process.*", ""), ""), "athinorama.gr": replaceSuffix(new RegExp("\\.ashx\\?.*", ""), ""), "spiiky.com": replaceSuffix(new RegExp("\\.ashx\\?.*", ""), ""), "i.iplsc.com": (urlObj, opt) => { let clean = opt.replace(/(\/[0-9A-Z]+)-C[0-9]+(?:-F[0-9]+)?(\.[^/.]*)$/i, "$1-C0$2"); if (clean !== opt) return clean; return opt.replace(/\.webp(?:[?#].*)?$/i, ".jpg"); }, "bilder.bild.de": replaceSuffix(new RegExp("\\?.*", ""), ""), "images.bild.de": replaceSuffix(new RegExp("\\?.*", ""), ""), "image.kurier.at": replaceSuffix(new RegExp("\\.[0-9]+\\.(?:[0-9]+\\.)?cache(?:[?#].*)?$", ""), ""), "image.film.at": replaceSuffix(new RegExp("\\.[0-9]+\\.(?:[0-9]+\\.)?cache(?:[?#].*)?$", ""), ""), "media.game8.vn": replaceSuffix(new RegExp("\\.[0-9]+\\.(?:[0-9]+\\.)?cache(?:[?#].*)?$", ""), ""), "cdn.sex.com": replaceSuffix(new RegExp("\\?.*", ""), ""), "img.blick.ch": replaceSuffix(new RegExp("\\?.*", ""), "?ratio=FREE&x=0&y=0"), "chevrolet.com": replaceSuffix(new RegExp("\\?.*", ""), "?ratio=FREE&x=0&y=0"), "hk.louisvuitton.com": replaceSuffix(new RegExp("(?:\\?.*)?$", ""), "?ratio=FREE&x=0&y=0"), "thetimes.co.uk": replaceSuffix(new RegExp("(?:\\?.*)?$", ""), "?resize=999999999"), "thetimes.com": replaceSuffix(new RegExp("(?:\\?.*)?$", ""), "?resize=999999999"), "images.asmhentai.com": (urlObj, opt) => { let clean = opt.replace(/(\.[a-z]+)\.webp(?:[?#].*)?$/i, "$1"); let m = clean.replace(/(:\/\/[^/]+\/+(?:galleries|[0-9]+)\/+[0-9]+\/+[0-9]+)t(\.[^/.]*)(?:[?#].*)?$/i, "$1$2"); if (m !== clean) return m.replace(/:\/\/t([0-9]*)\./i, "://i$1."); return clean.replace(/(:\/\/[^/]+\/+(?:galleries|[0-9]+)\/+[0-9]+\/)thumb(\.[^/.]*)(?:[?#].*)?$/i, "$1cover$2"); }, "t.nyahentai.net": (urlObj, opt) => domainOptimizers["images.asmhentai.com"](urlObj, opt), "pics.dmm.co.jp": (urlObj, opt) => opt.replace(new RegExp("\\?.*", ""), ""), "awsimgsrc.dmm.co.jp": (urlObj, opt) => opt.replace(new RegExp("\\?.*", ""), ""), "pics.avdmm.top": (urlObj, opt) => opt.replace(new RegExp("\\?.*", ""), ""), "images.buzzerie.com": replaceSuffix(new RegExp(";,.*", ""), ""), "cocacolabrasil.com.br": replaceSuffix(new RegExp(";,.*", ""), ""), "img.uodoo.com": replaceSuffix(new RegExp(";,.*", ""), ""), "cdn.lengmenjun.com": replaceSuffix(new RegExp("!lengmenjun-[0-9]+(?:[?#].*)?$", ""), "!lengmenjun"), "tophit.ru": replaceSuffix(new RegExp("([?&]size=)[0-9]+x[0-9]+", ""), "$10x0"), "zvooq.com": replaceSuffix(new RegExp("([?&]size=)[0-9]+x[0-9]+", ""), "$10x0"), "blovcdn.com": replaceAndDecode(/([?&]format=)[^&]*/, "$1s"), "cdn-img.jamendo.com": replaceSuffix(new RegExp("(\\?(?:.*&)?width=)[0-9]+([&#].*)?$", ""), "$10$2"), "images.jamendo.com": replaceSuffix(new RegExp("(\\?(?:.*&)?width=)[0-9]+([&#].*)?$", ""), "$10$2"), "usercontent.jamendo.com": replaceSuffix(new RegExp("(\\?(?:.*&)?width=)[0-9]+([&#].*)?$", ""), "$10$2"), "cho-animedia.jp": replaceSuffix(new RegExp(".*image_id=([0-9]+)$", ""), "$1"), "pixsell.hr": (urlObj, opt) => { return opt.replace(/\/scripts\/+get_image\.php.*?[?&](image_id=[0-9]+).*?$/i, "/scripts/get_image.php?$1"); }, "allbox.tv": replaceSuffix(new RegExp("\\?.*", ""), ""), "api.superguidatv.it": replaceSuffix(new RegExp("\\?.*", ""), ""), "img.shoplineapp.com": replaceSuffix(new RegExp("\\?.*", ""), "?w=-1"), "media.blogto.com": replaceSuffix(new RegExp("\\?.*", ""), "?w=-1"), "images.madame.de": (urlObj, opt) => { if (opt.match(/^[a-z]+:\/\/[^/]*\/[^/.,]*(?:,[a-z]+=(?:[^,]+|[0-9.,]+)){1,}(?:\.[^/.]*)?(?:[?#].*)?$/i)) { const filename = opt.replace(/^[a-z]+:\/\/[^/]*\/+([^/.,]*).*?$/i, "$1"); const id = opt.replace(/.*?,(id=[0-9a-z]+).*?$/i, "$1"); const brand = opt.replace(/.*?,(b=[0-9a-z]+).*?$/i, "$1"); let ext = opt.replace(/.*?(\.[^/.]*)(?:[?#].*)?$/, "$1"); if (ext === opt) ext = ""; if (id !== opt && brand !== opt) return "https://images.madame.de/" + filename + "," + id + "," + brand + ",rm=sk" + ext; } return opt; }, "cdn.dribbble.com": (urlObj, opt) => opt.replace(new RegExp("\\?.*", ""), ""), "s.tmimgcdn.com": (urlObj, opt) => opt.replace(new RegExp("[?#].*", ""), ""), "images.themevault.net": replaceSuffix(new RegExp("-", "g"), "_"), "templatemo.com": (urlObj, opt) => opt.replace(new RegExp("-", "g"), "_"), "5true.net": replaceSuffix(new RegExp(".*[?&]u=(aHR0c[^&]+)(?:[&#].*)?$", ""), "$1"), "musicglue-images-prod.global.ssl.fastly.net": replaceSuffix(new RegExp(".*[?&]u=(aHR0c[^&]+)(?:[&#].*)?$", ""), "$1"), "choualbox.com": replaceAndDecode(/-\.-/g, "/"), "im.milliyet.com.tr": (urlObj, opt) => opt.replace(new RegExp("-\\.-", "g"), "/"), "simdunyasi.com": replaceSuffix(new RegExp("\\?(?:.*&)?(qa_blobid=[0-9]+).*?$", ""), "?qa=image&$1"), "gateoverflow.in": replaceSuffix(new RegExp("\\?(?:.*&)?(qa_blobid=[0-9]+).*?$", ""), "?qa=image&$1"), "fontid.co": replaceSuffix(new RegExp("\\?(?:.*&)?(qa_blobid=[0-9]+).*?$", ""), "?qa=image&$1"), "okkisokuho.com": replaceSuffix(new RegExp("(?:\\?.*)?$", ""), "?w=o&h=o"), "pimg.togetter.com": replaceSuffix(new RegExp("(?:\\?.*)?$", ""), "?w=o&h=o"), "abc.2008php.com": replaceSuffix(new RegExp("z-z", "g"), "."), "pro.imgcdn2.com": (urlObj, opt) => opt.replace(new RegExp("z-z", "g"), "."), "cdnx.natalie.mu": (urlObj, opt) => { let clean = opt.replace(/(?:_fixw_(?:120|234)|_fit_120x120|_fixw_(?:640|730)_hq)\./, "_fixw_750_lt."); if (clean !== opt) return clean; return opt.replace(/^[a-z]+:\/\/[^/]+\/+(media\/+(?:[^/]+\/+)?[^/]+\/+[0-9]{4}\/+[0-9]{4}\/+[^/]+)_(?:fix[a-z]|fit)_[^/.?#]+\./i, "https://ogre.natalie.mu/$1."); }, "pic-b.com": replaceSuffix(new RegExp(".*?(?:.*&)?url=(https?%[^&]*).*?$", ""), "$1"), "momogaki.com": replaceSuffix(new RegExp(".*?(?:.*&)?url=(https?%[^&]*).*?$", ""), "$1"), "images.contentexchange.me": replaceSuffix(new RegExp(".*?(?:.*&)?url=(https?%[^&]*).*?$", ""), "$1"), "photo.naiadmmm.com": replaceSuffix(new RegExp("_s[0-9]{3}x[0-9]{3}\\.", ""), "_s1200x630."), "prodimage.images-bn.com": replaceAndDecode(/_s[0-9]{3}x[0-9]{3}\./, "_s1200x630."), "is.mediadelivery.fi": (urlObj, opt) => { let clean = opt.replace(/(\/img\/+(?:square\/+)?)(?:[0-9]{3}|1[0-8][0-9]{2})\//i, "$11920/"); if (clean === opt) return opt.replace(/(\.[a-z]+)\.webp(?:[?#].*)?$/i, "$1"); return clean; }, "nrwkino.de": replaceAndDecode(/\+/g, "%20"), "biograph.de": replaceAndDecode(/\+/g, "%20"), "thumbs.externulls.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "omny.fm": replaceSuffix(new RegExp("([?&]size=)[^&]+", ""), "$1Original"), "omnycontent.com": replaceSuffix(new RegExp("([?&]size=)[^&]+", ""), "$1Original"), "assets.mubicdn.net": (urlObj, opt) => opt.replace(new RegExp("[?#].*$", ""), ""), "booksamillion.com": replaceSuffix(new RegExp("\\?.*", ""), ""), "cropper.watch.aetnd.com": (urlObj, opt) => opt.replace(new RegExp("\\?.*", ""), ""), "tagesspiegel.de": replaceSuffix(new RegExp("-format[a-zA-Z0-9]+\\.", ""), "-formatOriginal."), "handelsblatt.com": replaceSuffix(new RegExp("-format[a-zA-Z0-9]+\\.", ""), "-formatOriginal."), "cvxf2z6hud.user-space.cdn.idcfcloud.net": replaceSuffix(new RegExp("\\?.*", ""), ""), "static.picdeno.com": replaceSuffix(new RegExp(".*[?&]u=(http[^&]+)(?:[&#].*)?$", ""), "$1"), "dumpor.com": replaceAndDecode(/.*[?&]url=(http[^&]+)(?:[&#].*)?$/, "$1"), "cdn.inflact.com": replaceSuffix(new RegExp(".*[?&]u=(http[^&]+)(?:[&#].*)?$", ""), "$1"), "padlet.pics": replaceSuffix(new RegExp(".*[?&]u=(http[^&]+)(?:[&#].*)?$", ""), "$1"), "instatory.net": (urlObj, opt) => { let newsrc = opt.replace(/.*[?&]url=(http[^&]+)(?:[&#].*)?$/i, "$1"); if (newsrc !== opt) try { return decodeURIComponent(newsrc).replace(/\|\|/g, "/"); } catch (e) { return newsrc.replace(/\|\|/g, "/"); } return opt; }, "drenchme.com": replaceSuffix(new RegExp("-[0-9]+x[0-9]+-x[0-9]+y[0-9]+w[0-9]+h[0-9]+\\.", ""), "."), "images.perthnow.com.au": replaceSuffix(new RegExp("-[0-9]+x[0-9]+-x[0-9]+y[0-9]+w[0-9]+h[0-9]+\\.", ""), "."), "cdn.app.c-rayon.com": replaceSuffix(new RegExp("(\\.[a-z]+)\\.[0-9]+\\.[a-z]+([?#].*)?$", ""), "$1.nop$2"), "images.wsj.net": replaceSuffix(new RegExp("[?#].*$", ""), ""), "a4tech.ua": replaceSuffix(new RegExp("-(?:bannerdesktop|bannermobile|column|desktop|hover|imageleftorright|intro|mobile|small|zoom)(\\.)", ""), "$1"), "cdn.coolermaster.com": replaceSuffix(new RegExp("-(?:bannerdesktop|bannermobile|column|desktop|hover|imageleftorright|intro|mobile|small|zoom)(\\.)", ""), "$1"), "westerndigital.com": replaceSuffix(new RegExp("\\.wdthumb\\.\\d+\\.\\d+\\.\\w+", ""), ""), "asus.com": replaceSuffix(new RegExp("_2400(\\.[a-z]+)(?:[?#].*)?$", ""), "$1"), "intertat.tatar": replaceSuffix(new RegExp("\\?.*", ""), "?quality=100"), "img.chmedia.ch": replaceSuffix(new RegExp("\\?.*", ""), "?quality=100"), "img.luzernerzeitung.ch": replaceSuffix(new RegExp("\\?.*", ""), "?quality=100"), "static-cdn.nextapple.tw": replaceSuffix(new RegExp("_750\\.(?:jpeg|webp)(?:[?#].*)?$", ""), "_1280.webp"), "sexkontakt.net": (urlObj, opt) => opt.replace(new RegExp("(-resimage_v-[a-z0-9]+),_w-[0-9]+\\.", ""), "$1."), "kika.de": replaceSuffix(new RegExp("(-resimage_v-[a-z0-9]+)_w-[0-9]+\\.", ""), "$1."), "catalogo.biblio.unc.edu.ar": replaceSuffix(new RegExp("([?&]size=)(?:small|medium)([&#].*)?$", ""), "$1large$2"), "im.autopilot.ru": (urlObj, opt) => opt.replace(new RegExp("\\.webp(?:[?#].*),?$", ""), ".jpg"), "image.alza.cz": replaceSuffix(new RegExp("\\?.*", ""), ""), "i.crepe.land": (urlObj, opt) => opt.replace(new RegExp("\\?.*", ""), "?t=i"), "caps-a-holic.com": replaceSuffix(new RegExp("([?&]max_height=)[0-9]+", ""), "$12160"), "yougov.net": (urlObj, opt) => opt.replace(new RegExp("[?#].*$", ""), ""), "wbbasket.ru": replaceSuffix(new RegExp("([?&]img=)[^&#]+", ""), "$1full"), "pressi.universalmusic.fi": replaceSuffix(new RegExp("([?&]img=)[^&#]+", ""), "$1full"), "cartier.jp": replaceSuffix(new RegExp("\\?.*", ""), ""), "storage.onecloudpro.com": replaceSuffix(new RegExp("\\?.*", ""), ""), "img.chil-chil.net": replaceSuffix(new RegExp("(?:\\?.*)?$", ""), "?quality=100&format=png"), "images.mnstatic.com": replaceSuffix(new RegExp("(?:\\?.*)?$", ""), "?quality=100&format=png"), "girlsnews.tv": replaceSuffix(new RegExp("-100-[auto0-9]+x[auto0-9]+\\.", ""), "-100-original."), "bilder.deutschlandfunk.de": replaceSuffix(new RegExp("-100-[auto0-9]+x[auto0-9]+\\.", ""), "-100-original."), "cdn.arabsstock.com": (urlObj, opt) => opt.replace(new RegExp("-[a-z]+(\\.[a-z]+),(?:[?#].*)?$", ""), "-preview$1"), "img.fruugo.com": replaceSuffix(new RegExp("[?#].*", ""), ""), "chicos.com": (urlObj, opt) => opt.replace(new RegExp("[?#].*", ""), ""), "garden.spoonflower.com": (urlObj, opt) => opt.replace(new RegExp("([?&]size=),(?:[sm]|xs)([&#].*)?$", ""), "$1l$2"), "cdn.directvelo.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "usnews.com": (urlObj, opt) => opt.replace(new RegExp("\\?.*$", ""), ""), "twic.pics": replaceSuffix(new RegExp("-[0-9]+x[0-9]+(\\.[a-z]+)$", ""), "$1"), "renginiai.kasvyksta.lt": replaceSuffix(new RegExp("-[0-9]+x[0-9]+(\\.[a-z]+)(?:[?#].*)?$", ""), "$1"), "scrolller.com": (urlObj, opt) => opt.replace(new RegExp("-[0-9]+x[0-9]+(\\.[a-z]+),(?:[?#].*)?$", ""), "$1"), "img.fotocommunity.com": replaceSuffix(new RegExp("\\?.*", ""), "?width=1920"), "img.artlogic.net": replaceSuffix(new RegExp("^.*?,\\s*", ""), ""), "facecheck.id": replaceSuffix(new RegExp("^.*?,\\s*", ""), ""), "ss-mpvolc.meipian.me": (urlObj, opt) => opt.replace(new RegExp("~.*", ""), ""), "image.gululu.world": (urlObj, opt) => opt.replace(new RegExp("~.*", ""), ""), "awscover.netshort.com": replaceSuffix(new RegExp("(~tplv-[a-z]+)-.*", ""), "$1-noop.image"), "mattersmedia.io": (urlObj, opt) => opt.replace(new RegExp("-scaled(\\.[A-Za-z]+),(?:[?#].*)?$", ""), "$1"), "modelsocietyimagecloud.blob.core.windows.net": replaceSuffix(new RegExp("-(?:Small|Medium[0-9]*)(u[0-9]+)?(\\.[a-z]+)(?:[?#].*)?$", ""), "-FullSize$1$2"), "u-static.haozhaopian.net": replaceSuffix(new RegExp("_iconl(\\.[a-z]+)(?:[?#].*)?$", ""), "_prevstill$1"), "images.pond5.com": replaceSuffix(new RegExp("_iconl(\\.[a-z]+)(?:[?#].*)?$", ""), "_prevstill$1"), "newzealand.com": (urlObj, opt) => opt.replace(new RegExp("\\.webp$", ""), ".jpg"), "googleapis.com": replaceSuffix(new RegExp("#.*", ""), ""), "images.inkl.com": replaceSuffix(new RegExp("\\?.*$", ""), ""), "photos.peopleimages.com": replaceSuffix(new RegExp("([?&]__blob=)wide([&#].*)?$", ""), "$1poster$2"), "bsh.de": replaceSuffix(new RegExp("([?&]__blob=)wide([&#].*)?$", ""), "$1poster$2"), "bta.bg": replaceSuffix(new RegExp("\\?.*", ""), ""), "d3s3zh7icgjwgd.cloudfront.net": replaceSuffix(new RegExp("(\\.mp4)\\.f[0-9]+\\.mp4(?:[?#].*)?$", ""), "$1"), "bcebos.com": (urlObj, opt) => opt.replace(new RegExp(".*[?&]auth_key=([-0-9a-f]+),.*?$", ""), "$1"), "pic.rmb.bdstatic.com": (urlObj, opt) => opt.replace(new RegExp(".*[?&]auth_key=([-0-9a-f]+),.*?$", ""), "$1"), "assets.hcaptcha.com": replaceSuffix(/-[0-9]+x[0-9]+\.([^/.]*)$/, ".$1"), "imgs.hcaptcha.com": replaceSuffix(/-[0-9]+x[0-9]+\.([^/.]*)$/, ".$1"), "tkfile.yes24.com": replaceSuffix(/(\/upload2\/.*?)\/dims\/+.*/, "$1"), "cdn.music-flo.com": replaceSuffix(/(\/image\/.*?)(?:[?#].*)?$/, "$1"), "static.discovery-expedition.com": replaceSuffix(/(\/images\/.*)\/dims\/.*/, "$1"), "image.genie.co.kr": replaceSuffix(/(\/image\/.*)\/dims\/.*/i, "$1"), "poc-cf-image.cjenm.com": replaceSuffix(/\/resize\/+[0-9]+\/+public\/+/, "/public/"), "newstown.co.kr": replaceSuffix(/:\/\/[^/]*\/+data\/+thumb\/+[0-9]+_[0-9]+_([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])([^/]*)$/, "://photo.newsen.com/news_photo/$1/$2/$3/$1$2$3$4"), "shop.newsen.com": replaceSuffix(/:\/\/[^/]*\/+data\/+thumb\/+[0-9]+_[0-9]+_([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])([^/]*)$/, "://photo.newsen.com/news_photo/$1/$2/$3/$1$2$3$4"), "thumb.hankooki.com": replaceSuffix(/:\/\/[^/]*\/+(.*\/+)[0-9]+x[0-9]+x[^/]*@([^/]*)(?:[?#].*)?$/, "://photo.hankooki.com/$1$2"), "img.mbn.co.kr": replaceSuffix(/_s[0-9]+x[0-9]+(\.[^/]*)$/, "$1"), "imgmmw.mbn.co.kr": replaceSuffix(/(\/storage\/+news\/+[0-9]{4}\/+(?:[0-9]{2}\/+){2}[-0-9A-F]{30,})_[0-9]+(\.[^/.]*)*(?:[?#].*)?$/, "$1$2"), "image-gd.inews24.com": replaceSuffix(/:\/\/[^/]*\/image[0-9]*\.php\?u=([^&]*).*/, "://image3.inews24.com$1"), "imgcc.naver.jp": (urlObj, opt) => opt.replace(/\/[0-9]+\/[0-9]+\/*$/, ""), "klik.gr": replaceSuffix(/(\/[^/]*)_[a-z]\.([^/.]*)$/, "$1_o.$2"), "sina.com.cn": replaceSuffix(/:\/\/static([0-9]*)\.photo\.sina\.com\.cn\//, "://ss$1.sinaimg.cn/"), "sinaimg.acgsoso.com": replaceSuffix(/:\/\/[^/]+\/+/, "://wx4.sinaimg.cn/"), "thumbnail.egloos.net": replaceSuffix(/^[a-z]+:\/\/thumb(?:nail)?\.egloos\.net\/[^/]*\/*/, ""), "thumb.egloos.net": replaceSuffix(/^[a-z]+:\/\/thumb(?:nail)?\.egloos\.net\/[^/]*\/*/, ""), "k.kakaocdn.net": replaceSuffix(/\/img_[a-z]*\.([^./]*)$/, "/img.$1"), "sbs.co.kr": (urlObj, opt) => opt.replace(/(\/[^_]*),_[^/.]*(\.[^/.]*)$/, "$1_ori$2"), "image.board.sbs.co.kr": replaceSuffix(/-[0-9]+(\.[^/.]*)$/, "$1"), "photocloud.sbs.co.kr": replaceSuffix(/(:\/\/[^/]+\/+)([^/]+\/+)thumb\/+([0-9a-f]{10,})-(?:[0-9]+|c[0-9]+x[0-9]+)\./, "$1origin/edit/$2$3-p."), "jpimedia.uk": replaceSuffix(/^[a-z]+:\/\/[^/]*\/+imagefetch\/+.*?\/(https?:\/\/)/, "$1"), "fiverr-res.cloudinary.com": replaceSuffix(/(:\/\/[^/]*\/+(?:images\/+)?)[a-z]_[^/]*\//, "$1"), "assets.lybrate.com": replaceSuffix(/(:\/\/[^/]*\/+(?:images\/+)?)[a-z]_[^/]*\//, "$1"), "images.complex.com": replaceSuffix(/\/(images|image\/upload)\/[^/]*_[^/]*\//, "/$1/"), "derivates.kicker.de": replaceSuffix(/(:\/\/[^/]+\/+)q_[0-9]+\/+(images[0-9]*\/+)/, "$1$2"), "media.istra24.hr": replaceSuffix(/(:\/\/[^/]+\/+)q_[0-9]+\/+(images[0-9]*\/+)/, "$1$2"), "image.kkday.com": replaceSuffix(/\/image\/+get\/+[^/]*(?:%2C|,)[^/]*\//, "/image/get/"), "cdn.skim.gs": (urlObj, opt) => opt.replace(/(:\/\/[^/]+\/+),.*?\/+media\//, "$1media/"), "www-konga-com-res.cloudinary.com": replaceSuffix(/(:\/\/[^/]+\/+).*?\/+media\//, "$1media/"), "bridestory.com": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+images\/+.*?\/+assets\/+([^/]+)\/+[^/]+(?:[?#].*)?$/, "https://images.bridestory.com/image/upload/assets/$1.jpg"), "assets.digitalcontent.marksandspencer.app": replaceSuffix(/(\/(?:images\/+(?:image\/+private\/+)?|image\/+upload\/+|images\/+scale\/+))(?:(?:q|fl?|dpr|w|h|t|c|ar|g)_[^/]+\/+)+/, "$1c_limit/"), "images.confetticdn.com": replaceSuffix(/(\/(?:images\/+(?:image\/+private\/+)?|image\/+upload\/+|images\/+scale\/+))(?:(?:q|fl?|dpr|w|h|t|c|ar|g)_[^/]+\/+)+/, "$1c_limit/"), "img.olympics.com": replaceSuffix(/(\/(?:images\/+(?:image\/+private\/+)?|image\/+upload\/+|images\/+scale\/+))(?:(?:q|fl?|dpr|w|h|t|c|ar|g)_[^/]+\/+)+/, "$1c_limit/"), "redonline.cdnds.net": replaceSuffix(/__[a-z]+(\.[^/.]*)$/, "$1"), "img.usmagazine.com": replaceSuffix(/(.*?[^:])\/[0-9]*-[^/]*\//, "$1/"), "g.foolcdn.com": replaceAndDecode(/\/s\/[0-9]*\/[0-9]*\//, "/"), "schreyer-photo.com": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+(img-get2?\/)/, "https://ssl.c.photoshelter.com/$1"), "stellapictures.co.uk": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+(img-get2?\/)/, "https://ssl.c.photoshelter.com/$1"), "capitalpictures.com": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+(img-get2?\/)/, "https://ssl.c.photoshelter.com/$1"), "sportimage.co.uk": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+(img-get2?\/)/, "https://ssl.c.photoshelter.com/$1"), "jeffreymayerphotography.com": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+(img-get2?\/)/, "https://ssl.c.photoshelter.com/$1"), "mgpstockphotos.com": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+(img-get2?\/)/, "https://ssl.c.photoshelter.com/$1"), "otapol.jp": replaceSuffix(/^[a-z]+:\/\/[^/]*\/[a-z]+\/+(.)([a-zA-Z0-9]+(?:@+)?)(?:\.[^/]*)?\/[^/]*(\.[^/.]*)$/, "https://ia.media-imdb.com/images/$1/$1$2$3"), "movpins.com": replaceSuffix(/^[a-z]+:\/\/[^/]*\/[a-z]+\/+(.)([a-zA-Z0-9]+(?:@+)?)(?:\.[^/]*)?\/[^/]*(\.[^/.]*)$/, "https://ia.media-imdb.com/images/$1/$1$2$3"), "cdn.cafehulu.com": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+(.)([a-zA-Z0-9]{40,}(?:@+)?)(\.[^/?#]+)(?:[?#].*)?$/, "https://ia.media-imdb.com/images/$1/$1$2$3"), "media.modernluxury.com": replaceSuffix(/\/styles\/+[^/]+\/+(.*?\.[a-z]+)(?:\.webp)?(?:[?#].*)?$/, "/uploads/$1"), "gamersnexus.net": (urlObj, opt) => opt.replace(/\/styles\/+card_teaser(\/+public\/+[^/]+\/+[^/]+),(?:[?#].*)?$/, "/styles/large_responsive_no_watermark_$1"), "diveng.rosselcdn.net": replaceSuffix(/(\/sites\/+default\/+files\/).*?\/+public\//, "$1"), "cdn.okmag.de": (urlObj, opt) => opt.replace(/(:\/\/[^/]*\/),s\/[^/]*\/public\/(media\/)/, "$1$2"), "cdn.9razia.de": (urlObj, opt) => opt.replace(/(:\/\/[^/]*\/),s\/[^/]*\/public\/(media\/)/, "$1$2"), "img.elcomercio.pe": replaceSuffix(/\/files\/[^/]*\/uploads\//, "/uploads/"), "img.peru21.pe": replaceSuffix(/\/files\/[^/]*\/uploads\//, "/uploads/"), "elpais.com.co": replaceSuffix(/\/files\/[^/]*\/uploads\//, "/uploads/"), "media.voltron.voanews.com": replaceSuffix(/\/styles\/+[^/]*\/+s3\/+/, "/"), "schweizer-illustrierte.ch": replaceSuffix(/\/fp\/+(?:[0-9]+\/+){4}(sites\/+)/, "/$1"), "klassiker.nu": replaceSuffix(/\/public\/+styles\/+[^/]+\/+public\/+/, "/public/"), "trbimg.com": replaceSuffix(/(\/img-[0-9a-f]+\/+[^/]+\/+[^/]+\/+)[0-9]+(?:\/+[0-9]+x[0-9]+\/*)?(?:[?#],*)?$/, "$1"), "gimg.quizlet.com": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+((?:a-|-[-A-Za-z0-9]+)\/.*)/, "https://lh3.googleusercontent.com/$1"), "star-tool.ru": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+([^/]+\/+[^/]+\/+[^/]+\/+[^/]+\/+[swh][0-9]*(?:-[^/]*]*)?\/+)/, "https://lh3.googleusercontent.com/$1"), "thehackernews.com": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+images\/+((?:[^/]+\/+){4}[^/]+\/+[^/]+)(?:[?#].*)?$/, "https://1.bp.blogspot.com/$1"), "cdn.narcity.com": replaceSuffix(/(\/[^/.]*\.[^/._]+)_(?:facebook|[0-9]+x[0-9]+)\.[^/.]*(?:[?#].*)?$/, "$1"), "narcity.com": replaceSuffix(/(\/[^/.]*\.[^/._]+)_(?:facebook|[0-9]+x[0-9]+)\.[^/.]*(?:[?#].*)?$/, "$1"), "images.vanityfair.it": replaceSuffix(/(\/gallery\/[0-9]*\/)[^/]*\//, "$1Original/"), "images.glamour.it": replaceSuffix(/(\/gallery\/[0-9]*\/)[^/]*\//, "$1Original/"), "refinery29.com": replaceSuffix(/(\/bin\/(?:entry|public|author)\/[^/]*)\/(?:[0-9]+,[0-9]+,[0-9]+,[0-9]+\/)?[^/]*(?:,[^/]*)?\/([^,]*)$/, "$1/x,100/$2"), "nicepik.com": replaceSuffix(/-thumb(\.[^/.]*)$/, "$1"), "uihere.com": replaceSuffix(/-thumb(\.[^/.]*)$/, "$1"), "babylonbee.com": replaceSuffix(/-thumb(\.[^/.]*)$/, "$1"), "womansdiary.gr": replaceSuffix(/-thumb(\.[^/.]*)$/, "$1"), "livemint.com": (urlObj, opt) => opt.replace(/\/rf\/[^/]*\/(.*),$/, "/rw/$1"), "cdn.cliqueinc.com": (urlObj, opt) => opt.replace(/(\/[a-z]+),\.[a-z]+(?:\.[^/.]*)(\.[^/.]*)$/, "$1.original$2"), "cliqueimg.com": (urlObj, opt) => opt.replace(/(\/[a-z]+),\.[a-z]+(?:\.[^/.]*)(\.[^/.]*)$/, "$1.original$2"), "zeroinger.herokuapp.com": replaceSuffix(/.*\/vimg\.php\?(?:.*?&)?v=([^&]*).*?$/, "https://i.ytimg.com/vi/$1/mqdefault.jpg"), "you2php.me": replaceSuffix(/.*\/vimg\.php\?(?:.*?&)?v=([^&]*).*?$/, "https://i.ytimg.com/vi/$1/mqdefault.jpg"), "wapinda.in": replaceSuffix(/.*\/vimg\.php\?(?:.*?&)?v=([^&]*).*?$/, "https://i.ytimg.com/vi/$1/mqdefault.jpg"), "image.bugsm.co.kr": replaceSuffix(/\/images\/[0-9]*\//, "/images/original/"), "gala.fr": replaceSuffix(/^[a-z]+:\/\/[^/]+\/+imgre\//, "https://gal.img.pmdstatic.net/"), "cf.shopee.tw": shopeeClean, "cfshopeetw-a.akamaihd.net": shopeeClean, "cf.shopee.co.id": shopeeClean, "cf.shopee.ph": shopeeClean, "cf.shopee.com.my": shopeeClean, "cf.shopee.sg": shopeeClean, "cf.shopee.co.th": shopeeClean, "cf.shopee.vn": shopeeClean, "blogimg.jp": livedoorBlogimgClean, "image.news.livedoor.com": livedoorBlogimgClean, "cdn.livedoor.jp": (urlObj, opt) => opt.replace(/(\/[0-9a-f]{10,}\.[^/.]+)\/+r\.[0-9]+x[0-9]+(?:[?#].*)?$/i, "$1"), "sl.news.livedoor.com": (urlObj, opt) => opt.replace(/^[a-z]+:\/\/[^/]*\/[a-f0-9]+\/[^/]*\//i, ""), "images.plurk.com": (urlObj, opt) => opt.replace(/(^[a-z]+:\/\/[^/]*\/+)mx_([^/.]*\.[^/.]*)(?:[?#].*)?$/i, "$1$2"), "imgs.plurk.com": (urlObj, opt) => opt.replace(/(:\/\/[^/]*\/+[^/]{3}\/+[^/]{3}\/+[a-zA-Z0-9]{10,})_tn(\.[^/.]*)(?:[?#].*)?$/i, "$1_lg$2"), "avatars.plurk.com": (urlObj, opt) => opt.replace(/(\/[0-9]+-)(?:medium|small)([0-9]+\.[^/.]*)(?:[?#].*)?$/i, "$1big$2"), "pic.pimg.tw": (urlObj, opt) => opt.replace(/\/[a-z]+_([0-9a-f]+\.[^/.]*)(?:[?#].*)?$/i, "/$1").replace(/_w?[a-z](\.[^/.]*)$/i, "$1"), "imageproxy.pimg.tw": (urlObj, opt) => { const newsrc = opt.replace(/^[a-z]+:\/\/[^/]*\/+(?:zoomcrop|resize)\?(?:.*?&)?url=([^&]*).*$/i, "$1"); if (newsrc !== opt) try { return decodeURIComponent(newsrc); } catch (e) { return newsrc; } return opt; }, "files.yande.re": yandeFilesClean, "assets.yande.re": (urlObj, opt) => opt.replace(/:\/\/assets\.yande\.re\/data\/preview\/[0-9a-f]+\/[0-9a-f]+\//i, "://files.yande.re/image/"), "yande.re": (urlObj, opt) => { if (opt.includes("/post/show/")) return opt; return yandeFilesClean(urlObj, opt); }, "safebooru.org": (urlObj, opt) => opt.replace(/\/thumbnails\/+([0-9]+)\/+thumbnail_([0-9a-f]+\.[^/.]*)(?:[?#].*)?$/i, "/images/$1/$2"), "tbib.org": (urlObj, opt) => opt.replace(/\/(?:thumbnails|samples)\/+([0-9]+)\/+(?:thumbnail|sample)_([0-9a-f]+\.[^/.]*)(?:[?#].*)?$/i, "/images/$1/$2"), "img.xbooru.com": (urlObj, opt) => opt.replace(/\/thumbnails\/+([0-9]+)\/+thumbnail_([0-9a-f]+\.[^/.]*)(?:[?#].*)?$/i, "/images/$1/$2"), "cdn.melonbooks.co.jp": melonbooksClean, "melonbooks.co.jp": melonbooksClean, "melonbooks.akamaized.net": melonbooksClean, "gamers.co.jp": melonbooksClean, "gamers-onlineshop.jp": melonbooksClean, "jpstore.dwango.jp": melonbooksClean, "tc-animate.techorus-cdn.com": melonbooksClean, "tc-gamers.techorus-cdn.com": melonbooksClean, "livedoor.blogimg.jp": livedoorBlogimgClean, "stat.ameblo.jp": (urlObj, opt) => domainOptimizers["stat.ameba.jp"](urlObj, opt), "ecdnimg.toranoana.jp": (urlObj, opt) => opt.replace(/_thumb(\.[^/.]*)(?:[?#].*)?$/i, "$1"), "is.sankakucomplex.com": sankakuClean, "s.sankakucomplex.com": sankakuClean, "v.sankakucomplex.com": sankakuClean, "img.alice-books.com": (urlObj, opt) => opt.replace(/(\/images\/+[0-9a-f]{10,})-[sl]\./i, "$1-h."), "textures.com": (urlObj, opt) => { if (opt.match(/\/system\/+gallery\/+photos\/+[^/]*\/+(?:[^/]*\/+)?[0-9]+\/+[^/]+(?:[?#].*)?$/i)) return opt.replace(/(\/[0-9]+\/+)([^/]*_)(?:download)?[0-9]+(\.[^/.]*)(?:[?#].*)?$/i, "$1hotlink-ok/$2shared$3"); return opt; }, "carbon-media.accelerator.net": (urlObj, opt) => opt.replace(/;[0-9]+x[0-9]+(\.[a-zA-Z0-9]+)(\?.*)?$/i, ";original$1").replace(/\?auto=webp/i, ""), "carbonmade-media.accelerator.net": (urlObj, opt) => domainOptimizers["carbon-media.accelerator.net"](urlObj, opt), "cdn.bsky.app": (urlObj, opt) => { let clean = opt.replace(/(\/img\/+feed_fullsize\/.*?)@jpeg([?#].*)?$/i, "$1@png$2"); if (clean !== opt) return clean; clean = opt.replace(/(\/img\/+)feed_thumbnail\/+/i, "$1feed_fullsize/"); if (clean !== opt) return clean; const match = opt.match(/\/img\/+[^/]+\/+plain\/+(did:plc:[a-z0-9]+)\/+([0-9a-z]+)(?:@[a-z]+)?(?:[?#].*)?$/i); if (match) return `https://bsky.social/xrpc/com.atproto.sync.getBlob?did=${match[1]}&cid=${match[2]}`; return opt; }, "imgbox.com": (urlObj, opt) => opt.replace(/:\/\/(?:thumbs|images)([0-9]*)\.imgbox\.com\/(.*)_[tbns]\.([a-z0-9]+)(?:[?#].*)?$/i, "://images$1.imgbox.com/$2_o.$3"), "abload.de": (urlObj, opt) => opt.replace(/(:\/\/[^/]+\/+)thumb\/+/i, "$1img/"), "media.springernature.com": (urlObj, opt) => opt.replace(/(:\/\/[^/]+\/+)(?:l?w[0-9]+|w[0-9]+h[0-9]+)\//i, "$1full/").replace(/\?as=webp/i, ""), "media.nature.com": (urlObj, opt) => domainOptimizers["media.springernature.com"](urlObj, opt), "images.nature.com": (urlObj, opt) => domainOptimizers["media.springernature.com"](urlObj, opt), "photojournal.jpl.nasa.gov": (urlObj, opt) => opt.replace(/(:\/\/[^/]+\/+)jpegMod\/+([^/]+)_modest\./i, "$1jpeg/$2."), "nssdc.gsfc.nasa.gov": (urlObj, opt) => opt.replace(/(\/imgcat\/+)midres\/+/i, "$1hires/"), "jpl.nasa.gov": (urlObj, opt) => opt.replace(/:\/\/[^/]+\/+spaceimages\/+images\/+[^/]+\/+(PIA[0-9]+)(?:_[^/]+|-[0-9]+(?:x[0-9]+|[wh]))(\.(?:jpg|jpeg|JPG|JPEG))(?:[?#].*)?$/i, "://photojournal.jpl.nasa.gov/jpeg/$1$2"), "imagecache.jpl.nasa.gov": (urlObj, opt) => { const match = opt.match(/:\/\/[^/]+\/+images\/+[0-9]+x[0-9]+\/+(?:pia|PIA)([0-9]+)-[0-9]+-[0-9]+x[0-9]+\./i); return match ? `https://photojournal.jpl.nasa.gov/jpeg/PIA${match[1]}.jpg` : opt; }, "i.4cdn.org": (urlObj, opt) => opt.replace(/(\/[0-9]*)s(\.[^/.]*)$/i, "$1$2"), "i.4pcdn.org": (urlObj, opt) => domainOptimizers["i.4cdn.org"](urlObj, opt), "bellazon.com": (urlObj, opt) => opt.replace(/_thumb(\.[^/.]*)(?:[?#].*)?$/i, "$1"), "tapatalk.com": (urlObj, opt) => { const newsrc = opt.replace(/^[a-z]+:\/\/[^/]*\/groups\/[^/]*\/imageproxy\.php.*?[?&]url=([^&]*).*?$/i, "$1"); if (newsrc !== opt) try { return decodeURIComponent(newsrc); } catch (e) { return newsrc; } return opt; }, "rule34.xxx": (urlObj, opt) => domainOptimizers["safebooru.org"](urlObj, opt), "realbooru.com": (urlObj, opt) => domainOptimizers["safebooru.org"](urlObj, opt), "lolibooru.org": (urlObj, opt) => domainOptimizers["safebooru.org"](urlObj, opt) }; var GMAdapter = class { static async getValue(key, defaultValue) { try { if (typeof GM_getValue === "function") { const val = GM_getValue(key, defaultValue); return val !== void 0 ? val : defaultValue; } if (typeof GM !== "undefined" && typeof GM.getValue === "function") { const val = await GM.getValue(key, defaultValue); return val !== void 0 ? val : defaultValue; } } catch (e) { console.warn("[GMAdapter] getValue failed for key:", key, e); } return defaultValue; } static async setValue(key, value) { try { if (typeof GM_setValue === "function") { GM_setValue(key, value); return; } if (typeof GM !== "undefined" && typeof GM.setValue === "function") { await GM.setValue(key, value); return; } } catch (e) { console.warn("[GMAdapter] setValue failed for key:", key, e); } } static async deleteValue(key) { try { if (typeof GM_deleteValue === "function") { GM_deleteValue(key); return; } if (typeof GM !== "undefined" && typeof GM.deleteValue === "function") { await GM.deleteValue(key); return; } } catch (e) { console.warn("[GMAdapter] deleteValue failed for key:", key, e); } } static xmlHttpRequest(details) { if (typeof GM_xmlhttpRequest === "function") return GM_xmlhttpRequest(details); if (typeof GM !== "undefined" && typeof GM.xmlHttpRequest === "function") return GM.xmlHttpRequest(details); throw new Error("GM_xmlhttpRequest API is not available in current environment."); } static registerMenuCommand(caption, commandFunc, accessKey) { if (typeof GM_registerMenuCommand === "function") { GM_registerMenuCommand(caption, commandFunc, accessKey); return; } if (typeof GM !== "undefined" && typeof GM.registerMenuCommand === "function") { GM.registerMenuCommand(caption, commandFunc, accessKey); return; } } }; var booruAsyncClean = (domainKey) => async (urlObj, opt) => { const cleaned = domainOptimizers[domainKey](urlObj, opt); const ext = cleaned.split(".").pop() || ""; const base = cleaned.substring(0, cleaned.lastIndexOf(".")); const urls = [cleaned]; for (const fallback of [ "png", "jpg", "jpeg", "gif", "webm", "mp4" ]) if (fallback !== ext.toLowerCase()) urls.push(`${base}.${fallback}`); return urls; }; var ApiCache = class { cache = new Map(); ttl = 36e5; maxEntries = 200; get(key) { const item = this.cache.get(key); if (!item) return null; if (Date.now() > item.expiry) { this.cache.delete(key); return null; } return item.data; } set(key, data) { if (this.cache.size >= this.maxEntries) { const oldestKey = this.cache.keys().next().value; if (oldestKey) this.cache.delete(oldestKey); } this.cache.set(key, { data, expiry: Date.now() + this.ttl }); } }; var apiCache = new ApiCache(); async function testUrlAvailability(url, headers) { const cacheKey = `avail:${url}`; const cachedAvail = apiCache.get(cacheKey); if (cachedAvail !== null) return cachedAvail; let result = false; try { result = (await fetch(url, { method: "HEAD", headers })).ok; } catch (e) { try { const res = await fetch(url, { method: "GET", headers: { ...headers || {}, Range: "bytes=0-0" } }); result = res.ok || res.status === 206; } catch (err) { try { result = await new Promise((resolve) => { GMAdapter.xmlHttpRequest({ method: "HEAD", url, headers, timeout: 3e3, onload: (response) => resolve(response.status >= 200 && response.status < 400), onerror: () => resolve(false), ontimeout: () => resolve(false) }); }); } catch (gmErr) { result = false; } } } apiCache.set(cacheKey, result); return result; } function getFlickrFallbackList(targetRaw) { const urlsToTry = []; const flickrRegex = /\/([0-9]+)_([0-9a-f]+)(?:_[a-z0-9]*)*\.([a-zA-Z0-9]+)$/i; const match = targetRaw.match(flickrRegex); if (match) { const photoId = match[1]; const secret = match[2]; const ext = match[3]; const basePath = targetRaw.replace(flickrRegex, `/${photoId}_${secret}`); [ "o", "k", "h", "b" ].forEach((size) => { urlsToTry.push(`${basePath}_${size}.${ext}`); }); } return urlsToTry; } var asyncOptimizers = { "flickr.com": async (urlObj, opt, rawUrl) => { return getFlickrFallbackList(rawUrl || opt); }, "staticflickr.com": async (urlObj, opt, rawUrl) => { return asyncOptimizers["flickr.com"](urlObj, opt, rawUrl); }, "redgifs.com": async (urlObj, opt, rawUrl) => { const match = opt.match(/(?:watch|ifr)\/+([a-zA-Z0-9]+)/i); if (!match) return [opt]; const id = match[1]; const cacheKey = `redgifs:${id}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const authRes = await fetch("https://api.redgifs.com/v2/auth/temporary"); if (authRes.ok) { const token = (await authRes.json()).token; if (token) { const detailRes = await fetch(`https://api.redgifs.com/v2/gifs/${id}`, { headers: { "Authorization": `Bearer ${token}` } }); if (detailRes.ok) { const detailData = await detailRes.json(); const mediaUrl = detailData.gif?.urls?.hd || detailData.gif?.urls?.sd; if (mediaUrl) { const resList = [mediaUrl, opt]; apiCache.set(cacheKey, resList); return resList; } } } } } catch (e) {} return [opt]; }, "gfycat.com": async (urlObj, opt, rawUrl) => { const match = opt.match(/gfycat\.com\/(?:ifr\/)?([a-zA-Z]+)/i); if (!match) return [opt]; const id = match[1]; const cacheKey = `gfycat:${id}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(`https://api.gfycat.com/v1/gfycats/${id}`); if (res.ok) { const data = await res.json(); const media = data.gfyItem?.gifUrl || data.gfyItem?.mp4Url; if (media) { const resList = [media, opt]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [opt]; }, "fbcdn.net": async (urlObj, opt, rawUrl) => { if (await testUrlAvailability(opt)) return [opt]; const hostUrl = rawUrl || opt; const cacheKey = `fbcdn:${hostUrl}`; const cached = apiCache.get(cacheKey); if (cached) return cached; if (hostUrl && hostUrl.includes("facebook.com")) try { const res = await fetch(hostUrl); if (res.ok) { const match = (await res.text()).match(/https:\/\/scontent\.f[^"']+\.fbcdn\.net[^"']+/g); if (match && match.length > 0) { const resList = [match[0], opt]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [opt]; }, "shutterstock.com": async (urlObj, opt, rawUrl) => { return [opt.replace(/-[0-9]+w\.jpg$/i, ".jpg"), opt]; }, "vimeo.com": async (urlObj, opt, rawUrl) => { const match = opt.match(/vimeo\.com\/([0-9]+)/); if (!match) return [opt]; const id = match[1]; const cacheKey = `vimeo:${id}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(`https://vimeo.com/api/oembed.json?url=https://vimeo.com/${id}`); if (res.ok) { const imgUrl = (await res.json()).thumbnail_url; if (imgUrl) { const resList = [ imgUrl.replace(/_[0-9]+x[0-9]+\.jpg/i, ".jpg"), imgUrl, opt ]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [opt]; }, "500px.com": async (urlObj, opt, rawUrl) => { return [opt.replace(/\/[1-4]\.jpg$/i, "/2048.jpg"), opt]; }, "500px.org": async (urlObj, opt, rawUrl) => { return asyncOptimizers["500px.com"](urlObj, opt, rawUrl); }, "sndcdn.com": async (urlObj, opt, rawUrl) => { const original = opt.replace(/-t[0-9]+x[0-9]+\.([a-zA-Z0-9]+)$/i, "-original.$1").replace(/-large\.([a-zA-Z0-9]+)$/i, "-original.$1"); return await testUrlAvailability(original) ? [original, opt] : [opt]; }, "tumblr.com": async (urlObj, opt, rawUrl) => { return [opt.replace(/_[0-9]+\.(jpg|png|gif)$/i, "_1280.$1"), opt]; }, "e-hentai.org": async (urlObj, opt, rawUrl) => { if (!opt.includes("/s/")) return [opt]; const cacheKey = `ehentai:${opt}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(opt); if (res.ok) { const html = await res.text(); const fullImgMatch = html.match(/href="([^"]+fullimg\.php[^"]+)"/i); if (fullImgMatch) { const fullUrl = fullImgMatch[1].replace(/&/g, "&"); const fullRes = await fetch(fullUrl); if (fullRes.ok) { const realImg = (await fullRes.text()).match(/<img\s+id="img"\s+src="([^"]+)"/i); if (realImg) { const resList = [realImg[1], opt]; apiCache.set(cacheKey, resList); return resList; } } } const normalImg = html.match(/<img\s+id="img"\s+src="([^"]+)"/i); if (normalImg) { const resList = [normalImg[1], opt]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [opt]; }, "ehgt.org": async (urlObj, opt, rawUrl) => { return asyncOptimizers["e-hentai.org"](urlObj, opt, rawUrl); }, "alamy.com": async (urlObj, opt, rawUrl) => { const clean = opt.replace(/\/zooms\/+(?:[0-9]|10)\/+/i, "/zooms/15/"); if (!opt.includes("/photo-") && !opt.includes("/image-photo/")) return [clean, opt]; const cacheKey = `alamy:${opt}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(opt); if (res.ok) { const html = await res.text(); const zoomMatch = html.match(/data-zoom-image="([^"]+)"/i) || html.match(/zoomImageUrl\s*:\s*['"]([^'"]+)['"]/i); if (zoomMatch) { const resList = [ zoomMatch[1], clean, opt ]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [clean, opt]; }, "bandcamp.com": async (urlObj, opt, rawUrl) => { const clean = opt.replace(/_[0-9]+\.jpg$/i, "_10.jpg"); if (!opt.includes("/album/") && !opt.includes("/track/")) return [clean, opt]; const cacheKey = `bandcamp:${opt}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(opt); if (res.ok) { const html = await res.text(); const match = html.match(/https:\/\/f4\.bcbits\.com\/img\/a[0-9]+_10\.jpg/i) || html.match(/"popupImage"\s*:\s*['"]([^'"]+)['"]/i); if (match) { const resList = [ match[1] || match[0], clean, opt ]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [clean, opt]; }, "ibb.co": async (urlObj, opt, rawUrl) => { if (opt.match(/\.(?:jpg|png|gif|webp)$/i)) return [opt]; const cacheKey = `ibb:${opt}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(opt); if (res.ok) { const html = await res.text(); const imgMatch = html.match(/<img\s+class="image-viewer-image"\s+src="([^"]+)"/i) || html.match(/<link\s+rel="image_src"\s+href="([^"]+)"/i); if (imgMatch) { const resList = [imgMatch[1], opt]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [opt]; }, "tenor.com": async (urlObj, opt, rawUrl) => { if (opt.match(/\.(?:gif|mp4|webm)$/i)) return [opt]; const cacheKey = `tenor:${opt}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(opt); if (res.ok) { const html = await res.text(); const ogImg = html.match(/<meta\s+property="og:image"\s+content="([^"]+)"/i) || html.match(/<meta\s+property="twitter:image"\s+content="([^"]+)"/i); if (ogImg) { const resList = [ ogImg[1].replace(/_s\.gif/i, ".gif").replace(/\/assets\/img\//i, "/"), ogImg[1], opt ]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [opt]; }, "giphy.com": async (urlObj, opt, rawUrl) => { if (opt.match(/\.(?:gif|mp4|webp)$/i)) return [opt]; const cacheKey = `giphy:${opt}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(opt); if (res.ok) { const ogImg = (await res.text()).match(/<meta\s+property="og:image"\s+content="([^"]+)"/i); if (ogImg) { const resList = [ ogImg[1].replace(/\/giphy-downsized[^/]*\.gif/i, "/giphy.gif"), ogImg[1], opt ]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [opt]; }, "newgrounds.com": async (urlObj, opt, rawUrl) => { if (!opt.includes("/art/view/")) return [opt]; const cacheKey = `newgrounds:${opt}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(opt); if (res.ok) { const html = await res.text(); const match = html.match(/<div\s+class="pod-body">\s*<img\s+src="([^"]+)"/i) || html.match(/https:\/\/art\.ngfiles\.com\/images\/[^"']+/i); if (match) { const resList = [match[1] || match[0], opt]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [opt]; }, "sankakucomplex.com": async (urlObj, opt, rawUrl) => { if (!opt.includes("/post/show/")) return [opt]; const cacheKey = `sankaku:${opt}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(opt); if (res.ok) { const html = await res.text(); const imgMatch = html.match(/id="image"\s+src="([^"]+)"/i) || html.match(/href="([^"]+original[^"]+)"/i); if (imgMatch) { const resList = [imgMatch[1], opt]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [opt]; }, "vsco.co": async (urlObj, opt, rawUrl) => { if (!opt.includes("/media/")) return [opt]; const cacheKey = `vsco:${opt}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(opt); if (res.ok) { const jsonMatch = (await res.text()).match(/window\.__PRELOADED_STATE__\s*=\s*(.*?);<\/script>/i); if (jsonMatch) { const media = JSON.parse(jsonMatch[1]).entities?.media; if (media) { const keys = Object.keys(media); if (keys.length > 0) { const img = media[keys[0]].responsiveUrl; if (img) { const resList = [`https://${img}`, opt]; apiCache.set(cacheKey, resList); return resList; } } } } } } catch (e) {} return [opt]; }, "imdb.com": async (urlObj, opt, rawUrl) => { return [opt.replace(/_V1_.*$/, "_V1_.jpg"), opt]; }, "nypost.com": async (urlObj, opt, rawUrl) => { return [opt.replace(/\?w=[0-9]+&h=[0-9]+/i, ""), opt]; }, "wallhaven.cc": async (urlObj, opt, rawUrl) => { if (!opt.includes("/w/")) return [opt]; const cacheKey = `wallhaven:${opt}`; const cached = apiCache.get(cacheKey); if (cached) return cached; try { const res = await fetch(opt); if (res.ok) { const match = (await res.text()).match(/<img\s+id="wallpaper"\s+src="([^"]+)"/i); if (match) { const resList = [match[1], opt]; apiCache.set(cacheKey, resList); return resList; } } } catch (e) {} return [opt]; }, "dreamstime.com": async (urlObj, opt, rawUrl) => { return [opt.replace(/_thumb\./i, "_x."), opt]; }, "123rf.com": async (urlObj, opt, rawUrl) => { return [decodeURIComponent(opt.replace(/_s\.jpg$/i, "_o.jpg").replace(/_m\.jpg$/i, "_o.jpg")), opt]; }, "ftcdn.net": async (urlObj, opt, rawUrl) => { return [opt.replace(/\/(?:110|160|240|360)_F_/i, "/1000_F_"), opt]; }, "yandex.ru": async (urlObj, opt, rawUrl) => { return [opt.replace(/\/size=[0-9]+x[0-9]+/i, "/size=origin"), opt]; }, "community.amd.com": async (urlObj, opt, rawUrl) => { return [opt.replace(/\/image-size\/[^/]+/i, ""), opt]; }, "dailyhunt.in": async (urlObj, opt, rawUrl) => { return [opt.replace(/\?.*$/, ""), opt]; }, "cdn.sex.com": async (urlObj, opt, rawUrl) => { return [opt.replace(/\/mobile\//i, "/original/"), opt]; }, "omny.fm": async (urlObj, opt, rawUrl) => { return [opt.replace(/\/image\?.*$/, ""), opt]; }, "omnycontent.com": async (urlObj, opt, rawUrl) => { return asyncOptimizers["omny.fm"](urlObj, opt, rawUrl); }, "images.wsj.net": async (urlObj, opt, rawUrl) => { return [opt.replace(/_D\.(jpg|png|gif)/i, "_G.$1"), opt]; }, "image.alza.cz": async (urlObj, opt, rawUrl) => { return [opt.replace(/\/FotoFiles\/Thumb/i, "/FotoFiles/F1"), opt]; }, "images.inkl.com": async (urlObj, opt, rawUrl) => { return [opt.replace(/\/w_[0-9]+/i, "/w_2000"), opt]; }, "cdn.cafehulu.com": async (urlObj, opt, rawUrl) => { return [opt.replace(/\/thumb\//i, "/original/"), opt]; }, "lohas.nicoseiga.jp": async (urlObj, opt, rawUrl) => { return [opt.replace(/\/thumb\//i, "/priv/"), opt]; }, "wixmp.com": async (urlObj, opt, rawUrl) => { const clean = opt.replace(/\/v1\/fill\/w_[0-9]+,h_[0-9]+[^/]+\/([^?#]+)/i, "/$1"); const headers = { "Referer": "https://www.deviantart.com/" }; return await testUrlAvailability(clean, headers) ? { urls: [clean, opt], headers } : { urls: [opt], headers }; }, "deviantart.net": async (urlObj, opt, rawUrl) => { return asyncOptimizers["wixmp.com"](urlObj, opt, rawUrl); }, "deviantart.com": async (urlObj, opt, rawUrl) => { return asyncOptimizers["wixmp.com"](urlObj, opt, rawUrl); }, "pixiv.net": async (urlObj, opt, rawUrl) => { return { urls: [opt.replace(/\/c\/[0-9]+x[0-9]+\/img-master\//i, "/img-original/").replace(/_master[0-9]+\./i, "."), opt], headers: { "Referer": "https://www.pixiv.net/" } }; }, "pximg.net": async (urlObj, opt, rawUrl) => { return asyncOptimizers["pixiv.net"](urlObj, opt, rawUrl); }, "weibo.com": async (urlObj, opt, rawUrl) => { const clean = opt.replace(/(\/)(?:square|thumbnail|mw690|bmiddle)(\/[0-9a-zA-Z]+\.[a-zA-Z0-9]+)$/i, "$1large$2"); const original = opt.replace(/(\/)(?:square|thumbnail|mw690|bmiddle|large)(\/[0-9a-zA-Z]+\.[a-zA-Z0-9]+)$/i, "$1woriginal$2"); const headers = { "Referer": "https://weibo.com/" }; if (await testUrlAvailability(original, headers)) return { urls: [ original, clean, opt ], headers }; return { urls: [clean, opt], headers }; }, "sinaimg.cn": async (urlObj, opt, rawUrl) => { return asyncOptimizers["weibo.com"](urlObj, opt, rawUrl); }, "upload.wikimedia.org": async (urlObj, opt, rawUrl) => { const wikiThumbRegex = /\/wikipedia\/([^/]+)\/thumb\/+(archive\/+)?([0-9a-f])\/+([0-9a-f]{2})\/+([^/]+)\/+(?:lossless-page[0-9]+-)?[0-9]+px-.*?$/i; if (wikiThumbRegex.test(opt)) return [opt.replace(wikiThumbRegex, "/wikipedia/$1/$2$3/$4/$5"), opt]; return [opt]; }, "googleusercontent.com": async (urlObj, opt, rawUrl) => { if (opt.includes("=")) return [opt.replace(/=[ws][0-9]+.*$/, "=s0"), opt]; return [opt]; }, "googleapis.com": async (urlObj, opt, rawUrl) => { return asyncOptimizers["googleusercontent.com"](urlObj, opt, rawUrl); }, "livejournal.com": async (urlObj, opt, rawUrl) => { return await testUrlAvailability(opt) ? [opt] : [opt.replace(/_original\./i, ".")]; }, "imgur.com": async (urlObj, opt, rawUrl) => { let clean = opt; if (clean.endsWith(".gifv")) { clean = clean.substring(0, clean.length - 5) + ".mp4"; return [clean, opt]; } const imgurRegex = /(:\/\/(?:i\.)?imgur\.(?:com|io)\/[a-zA-Z0-9]{5,7})(?:_[a-zA-Z0-9]+)?([sbtmlh]?)(\.[a-zA-Z0-9]+)/i; if (imgurRegex.test(clean)) return [clean.replace(imgurRegex, "$1$3"), clean]; return [clean]; }, "imgur.io": async (urlObj, opt, rawUrl) => { return asyncOptimizers["imgur.com"](urlObj, opt, rawUrl); }, "pinimg.com": async (urlObj, opt, rawUrl) => { const clean = opt.replace(/[?#].*$/, ""); let target = clean; if (clean.includes("/media.pinterest.com/")) target = clean.replace(/(:\/\/[^/]*\/media\.pinterest\.com\/)[^/]*(\/.*\/[^/]* \.[^/.]*)$/i, "$1originals$2"); else target = clean.replace(/(:\/\/[^/]*\/)[^/]*(\/.*\/[^/]*\.[^/.]*)$/i, "$1originals$2"); if (await testUrlAvailability(target)) return [target, clean]; return [clean.replace(/(:\/\/[^/]*\/)[^/]*(\/.*)$/i, "$1736x$2"), clean]; }, "reddit.com": async (urlObj, opt, rawUrl) => { if (opt.includes("/gold/awards/icon/")) return [opt.replace(/_[1-4]?[0-9]{2}\./i, "_512.")]; return [opt]; }, "redditmedia.com": async (urlObj, opt, rawUrl) => { return [opt]; }, "preview.redd.it": async (urlObj, opt, rawUrl) => { return [opt.replace(/:\/\/preview\.redd\.it\/(award_images\/+t[0-9]*_[0-9a-z]+\/+)?(?:[-0-9a-z]+-)?([^/.]*\.[^/.?]*)\?.*$/i, "://i.redd.it/$1$2"), opt]; }, "i.redd.it": async (urlObj, opt, rawUrl) => { return [opt.replace(/\?.*$/, "")]; }, "gelbooru.com": async (urlObj, opt, rawUrl) => { return { urls: [opt.replace(/\/thumbnails\//i, "/images/").replace(/\/sample\//i, "/images/").replace(/sample_/i, ""), opt], headers: { Referer: "https://gelbooru.com/" } }; }, "konachan.com": async (urlObj, opt, rawUrl) => { return { urls: [opt.replace(/\/post\/show\//i, "/"), opt], headers: { Referer: "https://konachan.com/" } }; }, "konachan.net": async (urlObj, opt, rawUrl) => { return { urls: [opt.replace(/\/post\/show\//i, "/"), opt], headers: { Referer: "https://konachan.net/" } }; }, "rednotecdn.com": async (urlObj, opt, rawUrl) => { return { urls: [opt.replace(/^([^!?]+)(?:[!?].*)?$/, "$1?imageView2/2/w/format/png"), opt], headers: { Referer: "https://www.xiaohongshu.com/" } }; }, "sns-webpic-qc.xhscdn.com": async (urlObj, opt, rawUrl) => { return { urls: [opt.replace(/^([^!?]+)(?:[!?].*)?$/, "$1?imageView2/2/w/format/png"), opt], headers: { Referer: "https://www.xiaohongshu.com/" } }; }, "gd-hbimg.huaban.com": async (urlObj, opt, rawUrl) => { return { urls: [opt.replace(/_fw[0-9]*$/, ""), opt], headers: { Referer: "https://huaban.com/" } }; }, "upaiyun.com": async (urlObj, opt, rawUrl) => { return { urls: [opt.replace(/_fw[0-9]*$/, ""), opt], headers: { Referer: "https://huaban.com/" } }; }, "imgs.ckcdn.com": async (urlObj, opt, rawUrl) => { const clean = opt.replace(/(?:\?.*)?$/, "?quality=100"); let referer = "https://www.copymanga.org/"; if (rawUrl) try { referer = new URL(rawUrl).origin + "/"; } catch (e) {} return { urls: [clean, opt], headers: { Referer: referer } }; }, "i.imgscc.com": async (urlObj, opt, rawUrl) => { const clean = opt.replace(/(?:\?.*)?$/, "?quality=100"); let referer = "https://www.copymanga.org/"; if (rawUrl) try { referer = new URL(rawUrl).origin + "/"; } catch (e) {} return { urls: [clean, opt], headers: { Referer: referer } }; }, "ebayimg.com": async (urlObj, opt, rawUrl) => [domainOptimizers["ebayimg.com"](urlObj, opt)], "squarespace.com": async (urlObj, opt, rawUrl) => [domainOptimizers["squarespace.com"](urlObj, opt)], "n.nordstrommedia.com": async (urlObj, opt, rawUrl) => [domainOptimizers["n.nordstrommedia.com"](urlObj, opt)], "media.tumblr.com": async (urlObj, opt, rawUrl) => [domainOptimizers["media.tumblr.com"](urlObj, opt)], "images.squarespace-cdn.com": async (urlObj, opt, rawUrl) => [domainOptimizers["images.squarespace-cdn.com"](urlObj, opt)], "dynamic.indigoimages.ca": async (urlObj, opt, rawUrl) => [domainOptimizers["dynamic.indigoimages.ca"](urlObj, opt)], "image.panasonic.com": async (urlObj, opt, rawUrl) => [domainOptimizers["image.panasonic.com"](urlObj, opt)], "image.diyidan.net": async (urlObj, opt, rawUrl) => [domainOptimizers["image.diyidan.net"](urlObj, opt)], "cvxf2z6hud.user-space.cdn.idcfcloud.net": async (urlObj, opt, rawUrl) => [domainOptimizers["cvxf2z6hud.user-space.cdn.idcfcloud.net"](urlObj, opt)], "is.sankakucomplex.com": async (urlObj, opt, rawUrl) => { return { urls: [domainOptimizers["is.sankakucomplex.com"](urlObj, opt)], headers: { Referer: "https://www.sankakucomplex.com/" } }; }, "s.sankakucomplex.com": async (urlObj, opt, rawUrl) => { return { urls: [domainOptimizers["s.sankakucomplex.com"](urlObj, opt)], headers: { Referer: "https://www.sankakucomplex.com/" } }; }, "v.sankakucomplex.com": async (urlObj, opt, rawUrl) => { return { urls: [domainOptimizers["v.sankakucomplex.com"](urlObj, opt)], headers: { Referer: "https://www.sankakucomplex.com/" } }; }, "i.4cdn.org": async (urlObj, opt, rawUrl) => { const cleaned = domainOptimizers["i.4cdn.org"](urlObj, opt); const ext = cleaned.split(".").pop() || ""; const base = cleaned.substring(0, cleaned.lastIndexOf(".")); const urls = [cleaned]; for (const fallback of [ "png", "jpg", "gif", "webm", "mp4" ]) if (fallback !== ext.toLowerCase()) urls.push(`${base}.${fallback}`); return { urls, headers: { Referer: "https://boards.4chan.org/" } }; }, "i.4pcdn.org": async (urlObj, opt, rawUrl) => { const cleaned = domainOptimizers["i.4pcdn.org"](urlObj, opt); const ext = cleaned.split(".").pop() || ""; const base = cleaned.substring(0, cleaned.lastIndexOf(".")); const urls = [cleaned]; for (const fallback of [ "png", "jpg", "gif", "webm", "mp4" ]) if (fallback !== ext.toLowerCase()) urls.push(`${base}.${fallback}`); return { urls, headers: { Referer: "https://boards.4chan.org/" } }; }, "safebooru.org": booruAsyncClean("safebooru.org"), "tbib.org": booruAsyncClean("tbib.org"), "img.xbooru.com": booruAsyncClean("img.xbooru.com"), "rule34.xxx": booruAsyncClean("rule34.xxx"), "realbooru.com": booruAsyncClean("realbooru.com"), "lolibooru.org": booruAsyncClean("lolibooru.org"), "fastpic.ru": async (urlObj, opt) => { const cleaned = opt.replace(/\/thumb\//i, "/big/"); const urls = [cleaned]; if (cleaned.endsWith(".jpeg")) urls.unshift(cleaned.slice(0, -5) + ".jpg?noht=1"); else if (cleaned.endsWith(".jpg")) urls.unshift(cleaned + "?noht=1"); return { urls, headers: { "Referer": `https://${urlObj.hostname.includes("fastpic.org") ? "fastpic.org" : "fastpic.ru"}1/`, "Sec-Fetch-Dest": "image", "Accept": "image/webp,image/apng,image/*,*/*;q=0.8" } }; }, "fastpic.org": async (urlObj, opt) => { return asyncOptimizers["fastpic.ru"](urlObj, opt); }, "img.4plebs.org": async (urlObj, opt) => { const cleaned = opt.replace(/\/thumb\/+(.*?)s(\.[^/.]*)$/i, "/image/$1$2"); const urls = [cleaned]; if (cleaned !== opt) { const ext = cleaned.split(".").pop() || ""; const base = cleaned.substring(0, cleaned.lastIndexOf(".")); for (const fallback of [ "png", "jpg", "jpeg", "gif" ]) if (fallback !== ext.toLowerCase()) urls.push(`${base}.${fallback}`); } return { urls, headers: { "Referer": `https://${urlObj.hostname}/`, "Sec-Fetch-Dest": "image" } }; }, "img.fireden.net": async (urlObj, opt) => { return asyncOptimizers["img.4plebs.org"](urlObj, opt); }, "img-lb.fireden.net": async (urlObj, opt) => { return asyncOptimizers["img.4plebs.org"](urlObj, opt); }, "torako.wakarimasen.moe": async (urlObj, opt) => { return asyncOptimizers["img.4plebs.org"](urlObj, opt); } }; var BlobManager = class { activeBlobs = new Map(); maxCapacity = 30; defaultTtlMs = 6e4; cleanupIntervalTimer; constructor() { this.startAutoCleanup(); if (typeof window !== "undefined") { window.addEventListener("beforeunload", () => this.clearAll()); window.addEventListener("pagehide", () => this.clearAll()); } } createManagedUrl(blob, tag = "general", ttlMs) { const url = URL.createObjectURL(blob); const now = Date.now(); const record = { url, blob, createdAt: now, lastAccessedAt: now, ttlMs: ttlMs ?? this.defaultTtlMs, tag, isPinned: false }; if (this.activeBlobs.has(url)) this.activeBlobs.delete(url); this.activeBlobs.set(url, record); this.enforceCapacityLimit(); return url; } revokeOnDownload(url, delayMs = 1e4) { const record = this.activeBlobs.get(url); if (record) { record.isPinned = false; this.activeBlobs.delete(url); this.activeBlobs.set(url, record); } setTimeout(() => { this.revokeUrl(url); }, delayMs); } revokeUrl(url) { if (this.activeBlobs.has(url)) { try { URL.revokeObjectURL(url); } catch (e) { console.warn("Failed to revoke object URL:", url, e); } this.activeBlobs.delete(url); } } pinUrl(url) { const record = this.activeBlobs.get(url); if (record) { record.isPinned = true; record.lastAccessedAt = Date.now(); this.activeBlobs.delete(url); this.activeBlobs.set(url, record); } } unpinUrl(url) { const record = this.activeBlobs.get(url); if (record) { record.isPinned = false; record.lastAccessedAt = Date.now(); this.activeBlobs.delete(url); this.activeBlobs.set(url, record); } } enforceCapacityLimit() { if (this.activeBlobs.size <= this.maxCapacity) return; for (const [url, record] of this.activeBlobs.entries()) { if (this.activeBlobs.size <= this.maxCapacity) break; if (!record.isPinned) this.revokeUrl(url); } } purgeExpired() { const now = Date.now(); for (const [url, record] of this.activeBlobs.entries()) if (!record.isPinned && now - record.lastAccessedAt > record.ttlMs) this.revokeUrl(url); } startAutoCleanup() { if (typeof window !== "undefined") this.cleanupIntervalTimer = window.setInterval(() => { this.purgeExpired(); }, 2e4); } clearAll() { for (const url of this.activeBlobs.keys()) try { URL.revokeObjectURL(url); } catch (e) {} this.activeBlobs.clear(); } }; var blobManager = new BlobManager(); var jsContent = "(function() {\n //#region src/config.ts\n const bgModes = [\n {\n translationKey: \"bgDarkCheckerboard\",\n class: \"giat-bg-dark-grid\"\n },\n {\n translationKey: \"bgWhite\",\n class: \"giat-bg-white\"\n },\n {\n translationKey: \"bgCheckerboard\",\n class: \"giat-bg-grid\"\n },\n {\n translationKey: \"bgGray\",\n class: \"giat-bg-gray\"\n },\n {\n translationKey: \"bgBlack\",\n class: \"giat-bg-black\"\n }\n ];\n var ConfigManager = class {\n enableThumbResolution;\n enableLightboxResolution;\n enableLightboxDownload;\n enableLightboxCopy;\n enableLightboxB64;\n enableThumbDownload;\n enableThumbCopy;\n enableThumbB64;\n enableHoverInfo;\n enableLightboxMime;\n enableThumbFileSize;\n enableLightboxFileSize;\n enableThumbBadges;\n enableLightboxDate;\n enableLightboxExif;\n enableThumbMime;\n enableThumbTitleTooltip;\n enableThumbLens;\n enableLightboxLens;\n enableBatchSelect = true;\n batchDownloadMode = \"direct\";\n serpRankMode = \"hover\";\n enableThumbTineye;\n enableLightboxTineye;\n enableThumbAi;\n enableLightboxAi;\n aiSearchPrompt;\n currentBgIndex;\n userLanguage;\n uiTheme;\n clickAction;\n enableWebpConversion;\n webpConversionFormat;\n webpConversionQuality;\n filenamePatternMode = \"original\";\n customFilenameTemplate = \"{query}_{index}\";\n labelPosition;\n labelSize;\n thumbBtnSize;\n enableLightboxKeys;\n lightboxPrevKey;\n lightboxNextKey;\n lightboxCloseKey;\n enableExperimentalAiUpload;\n customBgColor;\n customTextColor;\n customBgOpacity;\n enableLightboxForceBlob;\n enableUrlOptimization;\n enableThumbPhotopea;\n enableLightboxPhotopea;\n enableThumbVectorpea;\n enableLightboxVectorpea;\n enableThumbYandex;\n enableLightboxYandex;\n enableThumbBing;\n enableLightboxBing;\n enableLightboxColorAnalysis;\n enableYouTubeAutoplay;\n enableVisitedMark = false;\n visitedStyleMode = \"dim_desaturate\";\n ctrlClickAction = \"raw_image\";\n constructor() {\n this.enableThumbResolution = true;\n this.enableLightboxResolution = true;\n this.enableLightboxDownload = true;\n this.enableLightboxCopy = true;\n this.enableLightboxB64 = true;\n this.enableThumbDownload = true;\n this.enableThumbCopy = true;\n this.enableThumbB64 = false;\n this.enableHoverInfo = false;\n this.enableLightboxMime = true;\n this.enableThumbFileSize = true;\n this.enableLightboxFileSize = true;\n this.enableThumbBadges = true;\n this.enableLightboxDate = true;\n this.enableLightboxExif = true;\n this.enableThumbMime = false;\n this.enableThumbTitleTooltip = true;\n this.enableThumbLens = true;\n this.enableLightboxLens = true;\n this.enableBatchSelect = true;\n this.enableThumbTineye = false;\n this.enableLightboxTineye = false;\n this.enableThumbAi = true;\n this.enableLightboxAi = true;\n this.aiSearchPrompt = \"\";\n this.currentBgIndex = 2;\n this.userLanguage = \"auto\";\n this.uiTheme = \"auto\";\n this.clickAction = \"lightbox\";\n this.enableWebpConversion = false;\n this.webpConversionFormat = \"jpeg\";\n this.webpConversionQuality = 95;\n this.labelPosition = \"bottom-right\";\n this.labelSize = \"6\";\n this.thumbBtnSize = \"6\";\n this.enableLightboxKeys = true;\n this.lightboxPrevKey = \"ArrowLeft\";\n this.lightboxNextKey = \"ArrowRight\";\n this.lightboxCloseKey = \"Escape\";\n this.enableExperimentalAiUpload = true;\n this.customBgColor = \"\";\n this.customTextColor = \"\";\n this.customBgOpacity = 60;\n this.enableLightboxForceBlob = true;\n this.enableUrlOptimization = true;\n this.enableThumbPhotopea = false;\n this.enableLightboxPhotopea = false;\n this.enableThumbVectorpea = false;\n this.enableLightboxVectorpea = false;\n this.enableThumbYandex = false;\n this.enableLightboxYandex = false;\n this.enableThumbBing = false;\n this.enableLightboxBing = false;\n this.enableLightboxColorAnalysis = true;\n this.enableYouTubeAutoplay = true;\n this.loadConfig();\n }\n loadConfig() {\n const safeGet = (key, def) => {\n if (typeof GM_getValue !== \"undefined\") try {\n return GM_getValue(key, def);\n } catch (e) {\n return def;\n }\n return def;\n };\n this.enableThumbResolution = safeGet(\"giat-enable-thumb-resolution\", true);\n this.enableLightboxResolution = safeGet(\"giat-enable-lightbox-resolution\", true);\n this.enableLightboxDownload = safeGet(\"giat-enable-lightbox-download\", true);\n this.enableLightboxCopy = safeGet(\"giat-enable-lightbox-copy\", true);\n this.enableLightboxB64 = safeGet(\"giat-enable-lightbox-b64\", true);\n this.enableThumbDownload = safeGet(\"giat-enable-thumb-download\", true);\n this.enableThumbCopy = safeGet(\"giat-enable-thumb-copy\", true);\n this.enableThumbB64 = safeGet(\"giat-enable-thumb-b64\", false);\n this.enableHoverInfo = safeGet(\"giat-enable-hover-info\", false);\n this.enableLightboxMime = safeGet(\"giat-enable-lightbox-mime\", true);\n this.enableThumbFileSize = safeGet(\"giat-enable-thumb-file-size\", true);\n this.enableLightboxFileSize = safeGet(\"giat-enable-lightbox-file-size\", true);\n this.enableThumbBadges = safeGet(\"giat-enable-thumb-badges\", true);\n this.enableLightboxDate = safeGet(\"giat-enable-lightbox-date\", true);\n this.enableLightboxExif = safeGet(\"giat-enable-lightbox-exif\", true);\n this.enableThumbMime = safeGet(\"giat-enable-thumb-mime\", false);\n this.enableThumbTitleTooltip = safeGet(\"giat-enable-thumb-title-tooltip\", true);\n this.enableThumbLens = safeGet(\"giat-enable-thumb-lens\", true);\n this.enableLightboxLens = safeGet(\"giat-enable-lightbox-lens\", true);\n this.enableBatchSelect = safeGet(\"giat-enable-batch-select\", true);\n this.batchDownloadMode = safeGet(\"giat-batch-download-mode\", \"direct\");\n this.enableThumbTineye = safeGet(\"giat-enable-thumb-tineye\", false);\n this.enableLightboxTineye = safeGet(\"giat-enable-lightbox-tineye\", false);\n this.enableThumbAi = safeGet(\"giat-enable-thumb-ai\", true);\n this.enableLightboxAi = safeGet(\"giat-enable-lightbox-ai\", true);\n this.aiSearchPrompt = safeGet(\"giat-ai-search-prompt\", \"\");\n this.currentBgIndex = safeGet(\"giat-bg-index\", 2);\n this.userLanguage = safeGet(\"giat-user-language\", \"auto\");\n this.uiTheme = safeGet(\"giat-ui-theme\", \"auto\");\n this.clickAction = safeGet(\"giat-click-action\", \"lightbox\");\n this.enableWebpConversion = safeGet(\"giat-enable-webp-conversion\", false);\n this.webpConversionFormat = safeGet(\"giat-webp-conversion-format\", \"jpeg\");\n this.webpConversionQuality = safeGet(\"giat-webp-conversion-quality\", 95);\n this.filenamePatternMode = safeGet(\"giat-filename-pattern-mode\", \"original\");\n this.customFilenameTemplate = safeGet(\"giat-custom-filename-template\", \"{query}_{index}\");\n this.labelPosition = safeGet(\"giat-label-position\", \"bottom-right\");\n let storedSize = safeGet(\"giat-label-size\", \"6\");\n this.labelSize = storedSize === \"small\" ? \"5\" : storedSize === \"medium\" ? \"6\" : storedSize === \"large\" ? \"7\" : storedSize;\n this.thumbBtnSize = safeGet(\"giat-thumb-btn-size\", \"6\");\n this.enableLightboxKeys = safeGet(\"giat-enable-lightbox-keys\", true);\n this.lightboxPrevKey = safeGet(\"giat-lightbox-prev-key\", \"ArrowLeft\");\n this.lightboxNextKey = safeGet(\"giat-lightbox-next-key\", \"ArrowRight\");\n this.lightboxCloseKey = safeGet(\"giat-lightbox-close-key\", \"Escape\");\n this.enableExperimentalAiUpload = safeGet(\"giat-enable-experimental-ai-upload\", true);\n this.customBgColor = safeGet(\"giat-custom-bg-color\", \"\");\n this.customTextColor = safeGet(\"giat-custom-text-color\", \"\");\n this.customBgOpacity = safeGet(\"giat-custom-bg-opacity\", 60);\n this.enableLightboxForceBlob = safeGet(\"giat-enable-lightbox-force-blob\", true);\n this.enableUrlOptimization = safeGet(\"giat-enable-url-optimization\", true);\n this.enableThumbPhotopea = safeGet(\"giat-enable-thumb-photopea\", false);\n this.enableLightboxPhotopea = safeGet(\"giat-enable-lightbox-photopea\", false);\n this.enableThumbVectorpea = safeGet(\"giat-enable-thumb-vectorpea\", false);\n this.enableLightboxVectorpea = safeGet(\"giat-enable-lightbox-vectorpea\", false);\n this.enableThumbYandex = safeGet(\"giat-enable-thumb-yandex\", false);\n this.enableLightboxYandex = safeGet(\"giat-enable-lightbox-yandex\", false);\n this.enableThumbBing = safeGet(\"giat-enable-thumb-bing\", false);\n this.enableLightboxBing = safeGet(\"giat-enable-lightbox-bing\", false);\n this.enableLightboxColorAnalysis = safeGet(\"giat-enable-lightbox-color-analysis\", true);\n this.enableYouTubeAutoplay = safeGet(\"giat-enable-youtube-autoplay\", true);\n this.enableVisitedMark = safeGet(\"giat-enable-visited-mark\", false);\n this.visitedStyleMode = safeGet(\"giat-visited-style-mode\", \"dim_desaturate\");\n this.ctrlClickAction = safeGet(\"giat-ctrl-click-action\", \"raw_image\");\n }\n save() {\n if (typeof GM_setValue === \"undefined\") return;\n GM_setValue(\"giat-ctrl-click-action\", this.ctrlClickAction);\n GM_setValue(\"giat-enable-visited-mark\", this.enableVisitedMark);\n GM_setValue(\"giat-visited-style-mode\", this.visitedStyleMode);\n GM_setValue(\"giat-enable-thumb-resolution\", this.enableThumbResolution);\n GM_setValue(\"giat-enable-lightbox-resolution\", this.enableLightboxResolution);\n GM_setValue(\"giat-enable-lightbox-download\", this.enableLightboxDownload);\n GM_setValue(\"giat-enable-lightbox-copy\", this.enableLightboxCopy);\n GM_setValue(\"giat-enable-lightbox-b64\", this.enableLightboxB64);\n GM_setValue(\"giat-enable-thumb-download\", this.enableThumbDownload);\n GM_setValue(\"giat-enable-thumb-copy\", this.enableThumbCopy);\n GM_setValue(\"giat-enable-thumb-b64\", this.enableThumbB64);\n GM_setValue(\"giat-enable-hover-info\", this.enableHoverInfo);\n GM_setValue(\"giat-enable-lightbox-mime\", this.enableLightboxMime);\n GM_setValue(\"giat-enable-thumb-file-size\", this.enableThumbFileSize);\n GM_setValue(\"giat-enable-lightbox-file-size\", this.enableLightboxFileSize);\n GM_setValue(\"giat-enable-thumb-badges\", this.enableThumbBadges);\n GM_setValue(\"giat-enable-lightbox-date\", this.enableLightboxDate);\n GM_setValue(\"giat-enable-lightbox-exif\", this.enableLightboxExif);\n GM_setValue(\"giat-enable-thumb-mime\", this.enableThumbMime);\n GM_setValue(\"giat-enable-thumb-title-tooltip\", this.enableThumbTitleTooltip);\n GM_setValue(\"giat-enable-thumb-lens\", this.enableThumbLens);\n GM_setValue(\"giat-enable-lightbox-lens\", this.enableLightboxLens);\n GM_setValue(\"giat-enable-batch-select\", this.enableBatchSelect);\n GM_setValue(\"giat-batch-download-mode\", this.batchDownloadMode);\n GM_setValue(\"giat-enable-thumb-tineye\", this.enableThumbTineye);\n GM_setValue(\"giat-enable-lightbox-tineye\", this.enableLightboxTineye);\n GM_setValue(\"giat-enable-thumb-ai\", this.enableThumbAi);\n GM_setValue(\"giat-enable-lightbox-ai\", this.enableLightboxAi);\n GM_setValue(\"giat-ai-search-prompt\", this.aiSearchPrompt);\n GM_setValue(\"giat-bg-index\", this.currentBgIndex);\n GM_setValue(\"giat-user-language\", this.userLanguage);\n GM_setValue(\"giat-ui-theme\", this.uiTheme);\n GM_setValue(\"giat-click-action\", this.clickAction);\n GM_setValue(\"giat-enable-webp-conversion\", this.enableWebpConversion);\n GM_setValue(\"giat-webp-conversion-format\", this.webpConversionFormat);\n GM_setValue(\"giat-webp-conversion-quality\", this.webpConversionQuality);\n GM_setValue(\"giat-filename-pattern-mode\", this.filenamePatternMode);\n GM_setValue(\"giat-custom-filename-template\", this.customFilenameTemplate);\n GM_setValue(\"giat-label-position\", this.labelPosition);\n GM_setValue(\"giat-label-size\", this.labelSize);\n GM_setValue(\"giat-thumb-btn-size\", this.thumbBtnSize);\n GM_setValue(\"giat-enable-lightbox-keys\", this.enableLightboxKeys);\n GM_setValue(\"giat-lightbox-prev-key\", this.lightboxPrevKey);\n GM_setValue(\"giat-lightbox-next-key\", this.lightboxNextKey);\n GM_setValue(\"giat-lightbox-close-key\", this.lightboxCloseKey);\n GM_setValue(\"giat-enable-experimental-ai-upload\", this.enableExperimentalAiUpload);\n GM_setValue(\"giat-custom-bg-color\", this.customBgColor);\n GM_setValue(\"giat-custom-text-color\", this.customTextColor);\n GM_setValue(\"giat-custom-bg-opacity\", this.customBgOpacity);\n GM_setValue(\"giat-enable-lightbox-force-blob\", this.enableLightboxForceBlob);\n GM_setValue(\"giat-enable-url-optimization\", this.enableUrlOptimization);\n GM_setValue(\"giat-enable-thumb-photopea\", this.enableThumbPhotopea);\n GM_setValue(\"giat-enable-lightbox-photopea\", this.enableLightboxPhotopea);\n GM_setValue(\"giat-enable-thumb-vectorpea\", this.enableThumbVectorpea);\n GM_setValue(\"giat-enable-lightbox-vectorpea\", this.enableLightboxVectorpea);\n GM_setValue(\"giat-enable-thumb-yandex\", this.enableThumbYandex);\n GM_setValue(\"giat-enable-lightbox-yandex\", this.enableLightboxYandex);\n GM_setValue(\"giat-enable-thumb-bing\", this.enableThumbBing);\n GM_setValue(\"giat-enable-lightbox-bing\", this.enableLightboxBing);\n GM_setValue(\"giat-enable-lightbox-color-analysis\", this.enableLightboxColorAnalysis);\n GM_setValue(\"giat-enable-youtube-autoplay\", this.enableYouTubeAutoplay);\n }\n reset() {\n this.enableThumbResolution = true;\n this.enableLightboxResolution = true;\n this.enableLightboxCopy = true;\n this.enableLightboxB64 = true;\n this.enableThumbDownload = true;\n this.enableThumbCopy = true;\n this.enableThumbB64 = false;\n this.enableHoverInfo = false;\n this.enableLightboxMime = true;\n this.enableThumbFileSize = true;\n this.enableLightboxFileSize = true;\n this.enableThumbBadges = true;\n this.enableLightboxDate = true;\n this.enableLightboxExif = true;\n this.enableThumbMime = false;\n this.enableThumbTitleTooltip = true;\n this.enableThumbLens = true;\n this.enableLightboxLens = true;\n this.enableThumbTineye = false;\n this.enableLightboxTineye = false;\n this.enableYouTubeAutoplay = true;\n this.enableThumbAi = true;\n this.enableLightboxAi = true;\n this.aiSearchPrompt = \"\";\n this.currentBgIndex = 2;\n this.userLanguage = \"auto\";\n this.uiTheme = \"auto\";\n this.clickAction = \"lightbox\";\n this.ctrlClickAction = \"raw_image\";\n this.enableWebpConversion = false;\n this.webpConversionFormat = \"jpeg\";\n this.webpConversionQuality = 95;\n this.filenamePatternMode = \"original\";\n this.customFilenameTemplate = \"{query}_{index}\";\n this.labelPosition = \"bottom-right\";\n this.labelSize = \"6\";\n this.thumbBtnSize = \"6\";\n this.enableLightboxKeys = true;\n this.lightboxPrevKey = \"ArrowLeft\";\n this.lightboxNextKey = \"ArrowRight\";\n this.lightboxCloseKey = \"Escape\";\n this.enableExperimentalAiUpload = true;\n this.customBgColor = \"\";\n this.customTextColor = \"\";\n this.customBgOpacity = 60;\n this.enableLightboxForceBlob = true;\n this.enableUrlOptimization = true;\n this.enableThumbPhotopea = false;\n this.enableLightboxPhotopea = false;\n this.enableThumbVectorpea = false;\n this.enableLightboxVectorpea = false;\n this.enableThumbYandex = false;\n this.enableLightboxYandex = false;\n this.enableThumbBing = false;\n this.enableLightboxBing = false;\n this.enableLightboxColorAnalysis = true;\n this.enableVisitedMark = false;\n this.visitedStyleMode = \"dim_desaturate\";\n this.save();\n }\n applyGlobalSettings(lightboxDownloadBtn, lightboxCopyImgBtn, lightboxCopyB64Btn, lightboxWrap, lightboxLensBtn = null, lightboxTineyeBtn = null, lightboxAiBtn = null, lightboxPhotopeaBtn = null, lightboxVectorpeaBtn = null, lightboxYandexBtn = null, lightboxBingBtn = null) {\n const opacityDecimal = this.customBgOpacity / 100;\n const bgRgba = hexToRgba(this.customBgColor.trim() || \"rgba(32, 33, 36, 0.6)\", opacityDecimal);\n document.documentElement.style.setProperty(\"--giat-custom-bg\", bgRgba);\n document.documentElement.style.setProperty(\"--giat-custom-color\", this.customTextColor.trim() || \"#ffffff\");\n document.documentElement.style.setProperty(\"--giat-thumb-download-display\", this.enableThumbDownload ? \"flex\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-copy-display\", this.enableThumbCopy ? \"flex\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-b64-display\", this.enableThumbB64 ? \"flex\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-lens-display\", this.enableThumbLens ? \"flex\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-tineye-display\", this.enableThumbTineye ? \"flex\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-ai-display\", this.enableThumbAi ? \"flex\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-photopea-display\", this.enableThumbPhotopea ? \"flex\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-vectorpea-display\", this.enableThumbVectorpea ? \"flex\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-yandex-display\", this.enableThumbYandex ? \"flex\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-bing-display\", this.enableThumbBing ? \"flex\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-dims-initial-opacity\", this.enableHoverInfo ? \"0\" : \"1\");\n document.documentElement.style.setProperty(\"--giat-lightbox-mime-display\", this.enableLightboxMime ? \"block\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-file-size-display\", this.enableThumbFileSize ? \"inline-block\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-mime-display\", this.enableThumbMime ? \"inline-block\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-thumb-date-display\", this.enableThumbBadges ? \"inline-block\" : \"none\");\n document.documentElement.style.setProperty(\"--giat-native-date-display\", this.enableThumbBadges ? \"none\" : \"flex\");\n if (typeof document !== \"undefined\" && document.body) {\n document.body.classList.remove(\"giat-pos-br\", \"giat-pos-bl\", \"giat-pos-tr\", \"giat-pos-tl\");\n const posClass = `giat-pos-${this.labelPosition === \"bottom-right\" ? \"br\" : this.labelPosition === \"bottom-left\" ? \"bl\" : this.labelPosition === \"top-right\" ? \"tr\" : \"tl\"}`;\n document.body.classList.add(posClass);\n document.body.classList.remove(\"giat-size-small\", \"giat-size-medium\", \"giat-size-large\", \"giat-size-1\", \"giat-size-2\", \"giat-size-3\", \"giat-size-4\", \"giat-size-5\", \"giat-size-6\", \"giat-size-7\", \"giat-size-8\", \"giat-size-9\", \"giat-size-10\", \"giat-size-11\", \"giat-size-12\");\n document.body.classList.add(`giat-size-${this.labelSize}`);\n document.body.classList.remove(\"giat-thumb-btn-size-1\", \"giat-thumb-btn-size-2\", \"giat-thumb-btn-size-3\", \"giat-thumb-btn-size-4\", \"giat-thumb-btn-size-5\", \"giat-thumb-btn-size-6\", \"giat-thumb-btn-size-7\", \"giat-thumb-btn-size-8\", \"giat-thumb-btn-size-9\", \"giat-thumb-btn-size-10\", \"giat-thumb-btn-size-11\", \"giat-thumb-btn-size-12\");\n document.body.classList.add(`giat-thumb-btn-size-${this.thumbBtnSize}`);\n document.body.classList.toggle(\"giat-visited-enabled\", this.enableVisitedMark);\n document.body.classList.remove(\"giat-visited-mode-dim_desaturate\", \"giat-visited-mode-purple_border\", \"giat-visited-mode-visited_badge\", \"giat-visited-mode-subtle_dim\");\n document.body.classList.add(`giat-visited-mode-${this.visitedStyleMode || \"dim_desaturate\"}`);\n }\n if (lightboxDownloadBtn) lightboxDownloadBtn.style.setProperty(\"display\", this.enableLightboxDownload ? \"flex\" : \"none\", this.enableLightboxDownload ? \"\" : \"important\");\n if (lightboxCopyImgBtn) lightboxCopyImgBtn.style.setProperty(\"display\", this.enableLightboxCopy ? \"flex\" : \"none\", this.enableLightboxCopy ? \"\" : \"important\");\n if (lightboxCopyB64Btn) lightboxCopyB64Btn.style.setProperty(\"display\", this.enableLightboxB64 ? \"flex\" : \"none\", this.enableLightboxB64 ? \"\" : \"important\");\n if (lightboxLensBtn) lightboxLensBtn.style.setProperty(\"display\", this.enableLightboxLens ? \"flex\" : \"none\", this.enableLightboxLens ? \"\" : \"important\");\n if (lightboxTineyeBtn) lightboxTineyeBtn.style.setProperty(\"display\", this.enableLightboxTineye ? \"flex\" : \"none\", this.enableLightboxTineye ? \"\" : \"important\");\n if (lightboxAiBtn) lightboxAiBtn.style.setProperty(\"display\", this.enableLightboxAi ? \"flex\" : \"none\", this.enableLightboxAi ? \"\" : \"important\");\n if (lightboxPhotopeaBtn) lightboxPhotopeaBtn.style.setProperty(\"display\", this.enableLightboxPhotopea ? \"flex\" : \"none\", this.enableLightboxPhotopea ? \"\" : \"important\");\n if (lightboxVectorpeaBtn) lightboxVectorpeaBtn.style.setProperty(\"display\", this.enableLightboxVectorpea ? \"flex\" : \"none\", this.enableLightboxVectorpea ? \"\" : \"important\");\n if (lightboxYandexBtn) lightboxYandexBtn.style.setProperty(\"display\", this.enableLightboxYandex ? \"flex\" : \"none\", this.enableLightboxYandex ? \"\" : \"important\");\n if (lightboxBingBtn) lightboxBingBtn.style.setProperty(\"display\", this.enableLightboxBing ? \"flex\" : \"none\", this.enableLightboxBing ? \"\" : \"important\");\n if (lightboxWrap) {\n bgModes.forEach((m) => lightboxWrap.classList.remove(m.class));\n lightboxWrap.classList.add(bgModes[this.currentBgIndex].class);\n }\n if (document.body) {\n document.body.classList.remove(\"giat-serp-rank-hover\", \"giat-serp-rank-always\", \"giat-serp-rank-never\");\n document.body.classList.add(`giat-serp-rank-${this.serpRankMode || \"hover\"}`);\n }\n }\n };\n new ConfigManager();\n function hexToRgba(hex, opacity) {\n hex = hex.trim();\n if (hex.startsWith(\"rgba\") || hex.startsWith(\"rgb\") || hex === \"transparent\") return hex;\n if (/^#[0-9A-F]{6}$/i.test(hex)) return `rgba(${parseInt(hex.substring(1, 3), 16)}, ${parseInt(hex.substring(3, 5), 16)}, ${parseInt(hex.substring(5, 7), 16)}, ${opacity})`;\n if (/^#[0-9A-F]{3}$/i.test(hex)) return `rgba(${parseInt(hex.substring(1, 2).repeat(2), 16)}, ${parseInt(hex.substring(2, 3).repeat(2), 16)}, ${parseInt(hex.substring(3, 4).repeat(2), 16)}, ${opacity})`;\n return hex;\n }\n //#endregion\n //#region src/i18n.ts\n const svgSuccess = `<svg class=\"giat-toast-svg\" xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"m10.6 13.8l-2.15-2.15q-.275-.275-.7-.275t-.7.275t-.275.7t.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65q.275-.275.275-.7t-.275-.7t-.7-.275t-.7.275zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8\"/></svg>`;\n const svgAlert = `<svg class=\"giat-toast-svg giat-toast-svg-alert\" xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"><path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 9v4m-1.637-9.409L2.257 17.125a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636-2.87L13.637 3.59a1.914 1.914 0 0 0-3.274 0M12 16h.01\"/></svg>`;\n const svgShield = `<svg class=\"giat-pill-svg\" xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M15.06 10.5a.75.75 0 0 0-1.12-1l-3.011 3.374l-.87-.974a.75.75 0 0 0-1.118 1l1.428 1.6a.75.75 0 0 0 1.119 0z\"/><path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"M12 1.25c-.937 0-1.833.307-3.277.801l-.727.25c-1.481.506-2.625.898-3.443 1.23c-.412.167-.767.33-1.052.495c-.275.16-.55.359-.737.626c-.185.263-.281.587-.341.9c-.063.324-.1.713-.125 1.16c-.048.886-.048 2.102-.048 3.678v1.601c0 6.101 4.608 9.026 7.348 10.224l.027.011c.34.149.66.288 1.027.382c.387.1.799.142 1.348.142c.55 0 .96-.042 1.348-.142c.367-.094.687-.233 1.026-.382l.028-.011c2.74-1.198 7.348-4.123 7.348-10.224V10.39c0-1.576 0-2.792-.048-3.679a9 9 0 0 0-.125-1.16c-.06-.312-.156-.636-.34-.9c-.188-.266-.463-.465-.738-.625a9 9 0 0 0-1.052-.495c-.818-.332-1.962-.724-3.443-1.23l-.727-.25c-1.444-.494-2.34-.801-3.277-.801M9.08 3.514c1.615-.552 2.262-.764 2.92-.764s1.305.212 2.92.764l.572.196c1.513.518 2.616.896 3.39 1.21c.387.158.667.29.864.404q.144.084.208.139c.038.03.053.048.055.05a.4.4 0 0 1 .032.074q.03.082.063.248a7 7 0 0 1 .1.958c.046.841.046 2.015.046 3.624v1.574c0 5.176-3.87 7.723-6.449 8.849c-.371.162-.586.254-.825.315c-.228.059-.506.095-.976.095s-.748-.036-.976-.095c-.24-.06-.454-.153-.825-.315c-2.58-1.126-6.449-3.674-6.449-8.849v-1.574c0-1.609 0-2.783.046-3.624a7 7 0 0 1 .1-.958q.032-.166.063-.248c.018-.05.03-.07.032-.074a.4.4 0 0 1 .055-.05q.064-.055.208-.14c.197-.114.477-.245.864-.402c.774-.315 1.877-.693 3.39-1.21z\" clip-rule=\"evenodd\"/></svg>`;\n const svgSparkles = `<svg class=\"giat-pill-svg\" xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"m19 1l-1.26 2.75L15 5l2.74 1.26L19 9l1.25-2.74L23 5l-2.75-1.25M9 4L6.5 9.5L1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5M19 15l-1.26 2.74L15 19l2.74 1.25L19 23l1.25-2.75L23 19l-2.75-1.26\"/></svg>`;\n `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSparkles}`, `${svgShield}`, `${svgSuccess}`, `${svgAlert}`, `${svgSuccess}`, `${svgAlert}`, `${svgAlert}`, `${svgShield}`, `${svgShield}`, `${svgSparkles}`, `${svgAlert}`, `${svgAlert}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSparkles}`, `${svgShield}`, `${svgSuccess}`, `${svgAlert}`, `${svgSuccess}`, `${svgAlert}`, `${svgAlert}`, `${svgShield}`, `${svgShield}`, `${svgSparkles}`, `${svgAlert}`, `${svgAlert}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSuccess}`, `${svgSparkles}`, `${svgShield}`, `${svgSuccess}`, `${svgAlert}`, `${svgSuccess}`, `${svgAlert}`, `${svgAlert}`, `${svgShield}`, `${svgShield}`, `${svgSparkles}`, `${svgAlert}`, `${svgAlert}`;\n //#endregion\n //#region src/workers/unified.worker.ts?worker&inline\n function WorkerWrapper(options) {\n return new Worker(self.location.href, { name: options?.name });\n }\n //#endregion\n //#region src/utils/workerPool/unifiedPool.ts\n var UnifiedWorkerPool = class {\n maxWorkers;\n workers = [];\n idleWorkers = [];\n pendingTasks = /* @__PURE__ */ new Map();\n taskQueue = [];\n taskIdCounter = 0;\n constructor(maxConcurrency) {\n const hardwareLimit = typeof navigator !== \"undefined\" && navigator.hardwareConcurrency ? navigator.hardwareConcurrency : 2;\n this.maxWorkers = maxConcurrency ?? Math.min(Math.max(hardwareLimit, 2), 4);\n }\n createWorker() {\n const worker = new WorkerWrapper();\n worker.onmessage = (e) => {\n const result = e.data;\n const { taskId } = result;\n const pending = this.pendingTasks.get(taskId);\n if (pending) {\n clearTimeout(pending.timeoutId);\n this.pendingTasks.delete(taskId);\n if (result.success) pending.resolve(result);\n else pending.reject(new Error(result.error || \"Worker task failed\"));\n this.releaseWorker(pending.worker);\n }\n };\n worker.onerror = (err) => {\n console.warn(\"[UnifiedWorkerPool] Worker error encountered, recycling worker instance:\", err);\n this.handleWorkerError(worker);\n };\n return worker;\n }\n handleWorkerError(worker) {\n try {\n worker.terminate();\n } catch (e) {}\n this.workers = this.workers.filter((w) => w !== worker);\n this.idleWorkers = this.idleWorkers.filter((w) => w !== worker);\n for (const [taskId, pending] of this.pendingTasks.entries()) if (pending.worker === worker) {\n clearTimeout(pending.timeoutId);\n this.pendingTasks.delete(taskId);\n pending.reject(/* @__PURE__ */ new Error(\"Worker terminated due to runtime error\"));\n }\n this.processQueue();\n }\n releaseWorker(worker) {\n if (this.taskQueue.length > 0) {\n const nextTask = this.taskQueue.shift();\n this.executeOnWorker(worker, nextTask);\n } else if (!this.idleWorkers.includes(worker)) this.idleWorkers.push(worker);\n }\n executeOnWorker(worker, queuedTask) {\n const { taskId, request, resolve, reject, timeoutMs } = queuedTask;\n const timeoutId = window.setTimeout(() => {\n this.pendingTasks.delete(taskId);\n reject(/* @__PURE__ */ new Error(`[UnifiedWorkerPool] Task ${typeTaskName(request.type)} timed out after ${timeoutMs}ms`));\n this.handleWorkerError(worker);\n }, timeoutMs);\n this.pendingTasks.set(taskId, {\n resolve,\n reject,\n timeoutId,\n worker\n });\n const message = {\n taskId,\n type: request.type,\n bitmap: request.bitmap,\n imageBlob: request.imageBlob,\n targetFormat: request.targetFormat,\n quality: request.quality\n };\n try {\n if (request.transferables && request.transferables.length > 0) worker.postMessage(message, request.transferables);\n else worker.postMessage(message);\n } catch (err) {\n clearTimeout(timeoutId);\n this.pendingTasks.delete(taskId);\n reject(err);\n this.releaseWorker(worker);\n }\n }\n processQueue() {\n while (this.taskQueue.length > 0) {\n let worker;\n if (this.idleWorkers.length > 0) worker = this.idleWorkers.pop();\n else if (this.workers.length < this.maxWorkers) {\n worker = this.createWorker();\n this.workers.push(worker);\n } else break;\n const nextTask = this.taskQueue.shift();\n this.executeOnWorker(worker, nextTask);\n }\n }\n /**\n * Dispatch a task to background worker pool\n */\n executeTask(request, timeoutMs = 5e3) {\n if (typeof Worker === \"undefined\") return Promise.reject(/* @__PURE__ */ new Error(\"Web Worker not supported in environment\"));\n const taskId = `task_${++this.taskIdCounter}_${Date.now()}`;\n return new Promise((resolve, reject) => {\n const queuedTask = {\n taskId,\n request,\n resolve,\n reject,\n timeoutMs\n };\n this.taskQueue.push(queuedTask);\n this.processQueue();\n });\n }\n /**\n * Terminate all workers and clear pending tasks\n */\n terminateAll() {\n for (const worker of this.workers) try {\n worker.terminate();\n } catch (e) {}\n this.workers = [];\n this.idleWorkers = [];\n for (const [taskId, pending] of this.pendingTasks.entries()) {\n clearTimeout(pending.timeoutId);\n pending.reject(/* @__PURE__ */ new Error(\"Worker pool terminated\"));\n }\n this.pendingTasks.clear();\n for (const queued of this.taskQueue) queued.reject(/* @__PURE__ */ new Error(\"Worker pool terminated\"));\n this.taskQueue = [];\n }\n };\n function typeTaskName(type) {\n return type;\n }\n const unifiedWorkerPool = new UnifiedWorkerPool();\n //#endregion\n //#region src/utils/workerPool.ts\n var AnalysisWorkerPoolAdapter = class {\n /**\n * Dispatches color analysis task to Dedicated Worker Pool with Adaptive Transferables.\n * Automatically falls back to Main Thread on restriction.\n */\n async analyzeColors(bitmap) {\n try {\n const res = await unifiedWorkerPool.executeTask({\n type: \"COLOR_ANALYSIS\",\n bitmap,\n transferables: [bitmap]\n });\n if (res && res.success && res.colors) return { colors: res.colors };\n return this.fallbackMainThread(bitmap);\n } catch (err) {\n console.warn(\"[AnalysisWorkerPool] Dedicated Worker Pool color analysis failed, falling back to main thread:\", err);\n return this.fallbackMainThread(bitmap);\n }\n }\n fallbackMainThread(bitmap) {\n const result = {};\n try {\n const canvas = document.createElement(\"canvas\");\n canvas.width = 100;\n canvas.height = 100;\n const ctx = canvas.getContext(\"2d\");\n if (ctx) {\n ctx.drawImage(bitmap, 0, 0, 100, 100);\n result.colors = processPixelColors(ctx.getImageData(0, 0, 100, 100).data);\n }\n } catch (e) {\n console.warn(\"[AnalysisWorkerPool] Main thread fallback color analysis failed:\", e);\n } finally {\n try {\n bitmap.close();\n } catch (e) {}\n }\n return result;\n }\n };\n new AnalysisWorkerPoolAdapter();\n //#endregion\n //#region src/ui/lightbox/colorAnalyzer.ts\n function processPixelColors(data) {\n const pixels = [];\n const rHist = new Array(256).fill(0);\n const gHist = new Array(256).fill(0);\n const bHist = new Array(256).fill(0);\n for (let i = 0; i < data.length; i += 4) {\n const r = data[i];\n const g = data[i + 1];\n const b = data[i + 2];\n if (data[i + 3] < 50) continue;\n pixels.push({\n r,\n g,\n b\n });\n rHist[r]++;\n gHist[g]++;\n bHist[b]++;\n }\n if (pixels.length === 0) return null;\n return {\n dominantColors: extractDominantColorsKMeans(pixels, 5),\n rgbHistogram: {\n r: rHist,\n g: gHist,\n b: bHist\n }\n };\n }\n function srgbToLinear(c) {\n const v = c / 255;\n return v <= .04045 ? v / 12.92 : Math.pow((v + .055) / 1.055, 2.4);\n }\n function linearToSrgb(c) {\n const v = c <= .0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - .055;\n return Math.max(0, Math.min(255, Math.round(v * 255)));\n }\n function rgbToOKLab(r, g, b) {\n const lr = srgbToLinear(r);\n const lg = srgbToLinear(g);\n const lb = srgbToLinear(b);\n const l = Math.cbrt(.4122214708 * lr + .5363325363 * lg + .0514459929 * lb);\n const m = Math.cbrt(.2119034982 * lr + .6806995451 * lg + .1073969566 * lb);\n const s = Math.cbrt(.0883024619 * lr + .2817188376 * lg + .6299787005 * lb);\n return {\n L: .2104542553 * l + .793617785 * m - .0040720404 * s,\n a: 1.9779984951 * l - 2.428592205 * m + .4505937099 * s,\n b: .0259040371 * l + .7827717662 * m - .8086757973 * s\n };\n }\n function okLabToRgb(lab) {\n const l = lab.L + .3963377774 * lab.a + .2158037573 * lab.b;\n const m = lab.L - .1055613458 * lab.a - .0638541728 * lab.b;\n const s = lab.L - .0894841775 * lab.a - 1.291485548 * lab.b;\n const l3 = l * l * l;\n const m3 = m * m * m;\n const s3 = s * s * s;\n const lr = 4.0767416621 * l3 - 3.3077115913 * m3 + .2309699292 * s3;\n const lg = -1.2684380046 * l3 + 2.6097574011 * m3 - .3413193965 * s3;\n const lb = -.0041960863 * l3 - .7034186147 * m3 + 1.707614701 * s3;\n return {\n r: linearToSrgb(lr),\n g: linearToSrgb(lg),\n b: linearToSrgb(lb)\n };\n }\n /**\n * Performs K-Means clustering in OKLab perceptually uniform color space.\n * Extracts dominant accent and background colors accurately.\n */\n function extractDominantColorsKMeans(pixels, k) {\n if (pixels.length === 0) return [];\n const labPixels = pixels.map((p) => rgbToOKLab(p.r, p.g, p.b));\n const centroids = [];\n const minDistances = new Array(labPixels.length).fill(Infinity);\n const firstIndex = Math.floor(labPixels.length / 2);\n centroids.push({ ...labPixels[firstIndex] });\n for (let c = 1; c < k; c++) {\n const lastCentroid = centroids[c - 1];\n let maxDist = -1;\n let nextCentroidIndex = 0;\n for (let i = 0; i < labPixels.length; i++) {\n const p = labPixels[i];\n const dist = (p.L - lastCentroid.L) ** 2 + (p.a - lastCentroid.a) ** 2 + (p.b - lastCentroid.b) ** 2;\n if (dist < minDistances[i]) minDistances[i] = dist;\n if (minDistances[i] > maxDist) {\n maxDist = minDistances[i];\n nextCentroidIndex = i;\n }\n }\n centroids.push({ ...labPixels[nextCentroidIndex] });\n }\n const maxIterations = 6;\n const convergenceEpsilon = 1e-5;\n let finalClusters = Array.from({ length: k }, () => []);\n for (let iter = 0; iter < maxIterations; iter++) {\n const clusters = Array.from({ length: k }, () => []);\n for (let i = 0; i < labPixels.length; i++) {\n const p = labPixels[i];\n let minDistance = Infinity;\n let closestClusterIndex = 0;\n for (let j = 0; j < k; j++) {\n const c = centroids[j];\n const d = (p.L - c.L) ** 2 + (p.a - c.a) ** 2 + (p.b - c.b) ** 2;\n if (d < minDistance) {\n minDistance = d;\n closestClusterIndex = j;\n }\n }\n clusters[closestClusterIndex].push(p);\n }\n finalClusters = clusters;\n let maxShift = 0;\n for (let j = 0; j < k; j++) {\n const cluster = clusters[j];\n if (cluster.length === 0) continue;\n let sumL = 0, sumA = 0, sumB = 0;\n for (let i = 0; i < cluster.length; i++) {\n sumL += cluster[i].L;\n sumA += cluster[i].a;\n sumB += cluster[i].b;\n }\n const newL = sumL / cluster.length;\n const newA = sumA / cluster.length;\n const newB = sumB / cluster.length;\n const shift = (newL - centroids[j].L) ** 2 + (newA - centroids[j].a) ** 2 + (newB - centroids[j].b) ** 2;\n if (shift > maxShift) maxShift = shift;\n centroids[j] = {\n L: newL,\n a: newA,\n b: newB\n };\n }\n if (maxShift < convergenceEpsilon) break;\n }\n const totalValidPixels = pixels.length;\n return centroids.map((c, j) => {\n const rgb = okLabToRgb(c);\n const hex = rgbToHex(rgb.r, rgb.g, rgb.b);\n const clusterLen = finalClusters[j] ? finalClusters[j].length : 0;\n return {\n hex,\n percent: totalValidPixels > 0 ? Math.max(1, Math.round(clusterLen / totalValidPixels * 100)) : 0\n };\n }).sort((a, b) => b.percent - a.percent);\n }\n function rgbToHex(r, g, b) {\n const componentToHex = (c) => {\n const hex = Math.max(0, Math.min(255, c)).toString(16);\n return hex.length === 1 ? \"0\" + hex : hex;\n };\n return \"#\" + componentToHex(r) + componentToHex(g) + componentToHex(b);\n }\n //#endregion\n //#region src/workers/unified.worker.ts\n self.onmessage = async (e) => {\n const { taskId, type, bitmap, imageBlob, targetFormat, quality } = e.data;\n const result = {\n taskId,\n type,\n success: false\n };\n try {\n if (type === \"COLOR_ANALYSIS\") {\n if (!bitmap) throw new Error(\"Missing ImageBitmap for COLOR_ANALYSIS\");\n try {\n const targetSize = 100;\n if (typeof OffscreenCanvas !== \"undefined\") {\n const ctx = new OffscreenCanvas(targetSize, targetSize).getContext(\"2d\");\n if (ctx) {\n ctx.drawImage(bitmap, 0, 0, targetSize, targetSize);\n result.colors = processPixelColors(ctx.getImageData(0, 0, targetSize, targetSize).data);\n result.success = true;\n } else throw new Error(\"Failed to get 2d context for OffscreenCanvas\");\n } else throw new Error(\"OffscreenCanvas unavailable in worker\");\n } finally {\n try {\n bitmap.close();\n } catch (closeErr) {}\n }\n } else if (type === \"IMAGE_TRANSCODE\") {\n if (!imageBlob) throw new Error(\"Missing imageBlob for IMAGE_TRANSCODE\");\n const imageBitmap = await createImageBitmap(imageBlob);\n try {\n if (typeof OffscreenCanvas !== \"undefined\") {\n const offscreen = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);\n const ctx = offscreen.getContext(\"2d\");\n if (!ctx) throw new Error(\"Failed to get 2D context on OffscreenCanvas\");\n ctx.drawImage(imageBitmap, 0, 0);\n const options = { type: targetFormat === \"png\" ? \"image/png\" : \"image/jpeg\" };\n if (targetFormat !== \"png\") options.quality = Math.max(.1, Math.min(1, quality ?? .95));\n result.blob = await offscreen.convertToBlob(options);\n result.success = true;\n } else throw new Error(\"OffscreenCanvas unavailable in worker\");\n } finally {\n try {\n imageBitmap.close();\n } catch (closeErr) {}\n }\n } else throw new Error(`Unknown task type: ${type}`);\n } catch (err) {\n result.success = false;\n result.error = err?.message || \"Worker task processing failed\";\n }\n self.postMessage(result);\n };\n //#endregion\n})();\n"; var blob = typeof self !== "undefined" && self.Blob && new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);", jsContent], { type: "text/javascript;charset=utf-8" }); function WorkerWrapper(options) { let objURL; try { objURL = blob && (self.URL || self.webkitURL).createObjectURL(blob); if (!objURL) throw ""; const worker = new Worker(objURL, { name: options?.name }); worker.addEventListener("error", () => { (self.URL || self.webkitURL).revokeObjectURL(objURL); }); return worker; } catch (e) { return new Worker("data:text/javascript;charset=utf-8," + encodeURIComponent(jsContent), { name: options?.name }); } } var UnifiedWorkerPool = class { maxWorkers; workers = []; idleWorkers = []; pendingTasks = new Map(); taskQueue = []; taskIdCounter = 0; constructor(maxConcurrency) { const hardwareLimit = typeof navigator !== "undefined" && navigator.hardwareConcurrency ? navigator.hardwareConcurrency : 2; this.maxWorkers = maxConcurrency ?? Math.min(Math.max(hardwareLimit, 2), 4); } createWorker() { const worker = new WorkerWrapper(); worker.onmessage = (e) => { const result = e.data; const { taskId } = result; const pending = this.pendingTasks.get(taskId); if (pending) { clearTimeout(pending.timeoutId); this.pendingTasks.delete(taskId); if (result.success) pending.resolve(result); else pending.reject(new Error(result.error || "Worker task failed")); this.releaseWorker(pending.worker); } }; worker.onerror = (err) => { console.warn("[UnifiedWorkerPool] Worker error encountered, recycling worker instance:", err); this.handleWorkerError(worker); }; return worker; } handleWorkerError(worker) { try { worker.terminate(); } catch (e) {} this.workers = this.workers.filter((w) => w !== worker); this.idleWorkers = this.idleWorkers.filter((w) => w !== worker); for (const [taskId, pending] of this.pendingTasks.entries()) if (pending.worker === worker) { clearTimeout(pending.timeoutId); this.pendingTasks.delete(taskId); pending.reject(new Error("Worker terminated due to runtime error")); } this.processQueue(); } releaseWorker(worker) { if (this.taskQueue.length > 0) { const nextTask = this.taskQueue.shift(); this.executeOnWorker(worker, nextTask); } else if (!this.idleWorkers.includes(worker)) this.idleWorkers.push(worker); } executeOnWorker(worker, queuedTask) { const { taskId, request, resolve, reject, timeoutMs } = queuedTask; const timeoutId = window.setTimeout(() => { this.pendingTasks.delete(taskId); reject(new Error(`[UnifiedWorkerPool] Task ${typeTaskName(request.type)} timed out after ${timeoutMs}ms`)); this.handleWorkerError(worker); }, timeoutMs); this.pendingTasks.set(taskId, { resolve, reject, timeoutId, worker }); const message = { taskId, type: request.type, bitmap: request.bitmap, imageBlob: request.imageBlob, targetFormat: request.targetFormat, quality: request.quality }; try { if (request.transferables && request.transferables.length > 0) worker.postMessage(message, request.transferables); else worker.postMessage(message); } catch (err) { clearTimeout(timeoutId); this.pendingTasks.delete(taskId); reject(err); this.releaseWorker(worker); } } processQueue() { while (this.taskQueue.length > 0) { let worker; if (this.idleWorkers.length > 0) worker = this.idleWorkers.pop(); else if (this.workers.length < this.maxWorkers) { worker = this.createWorker(); this.workers.push(worker); } else break; const nextTask = this.taskQueue.shift(); this.executeOnWorker(worker, nextTask); } } executeTask(request, timeoutMs = 5e3) { if (typeof Worker === "undefined") return Promise.reject(new Error("Web Worker not supported in environment")); const taskId = `task_${++this.taskIdCounter}_${Date.now()}`; return new Promise((resolve, reject) => { const queuedTask = { taskId, request, resolve, reject, timeoutMs }; this.taskQueue.push(queuedTask); this.processQueue(); }); } terminateAll() { for (const worker of this.workers) try { worker.terminate(); } catch (e) {} this.workers = []; this.idleWorkers = []; for (const [taskId, pending] of this.pendingTasks.entries()) { clearTimeout(pending.timeoutId); pending.reject(new Error("Worker pool terminated")); } this.pendingTasks.clear(); for (const queued of this.taskQueue) queued.reject(new Error("Worker pool terminated")); this.taskQueue = []; } }; function typeTaskName(type) { return type; } var unifiedWorkerPool = new UnifiedWorkerPool(); async function convertImageInWorker(imageBlob, targetFormat, quality = .95) { if (typeof OffscreenCanvas === "undefined") { console.warn("[conversion] OffscreenCanvas not supported, falling back to Main Thread conversion."); return fallbackMainThreadConvert(imageBlob, targetFormat, quality); } try { const res = await unifiedWorkerPool.executeTask({ type: "IMAGE_TRANSCODE", imageBlob, targetFormat, quality }); if (res.success && res.blob) return res.blob; else throw new Error(res.error || "Worker transcode returned empty result"); } catch (err) { console.warn("[conversion] Dedicated worker pool conversion failed, using main thread fallback:", err); return fallbackMainThreadConvert(imageBlob, targetFormat, quality); } } function fallbackMainThreadConvert(imageBlob, targetFormat, quality) { return new Promise((resolve, reject) => { const img = new Image(); const url = URL.createObjectURL(imageBlob); img.src = url; img.onload = () => { URL.revokeObjectURL(url); const canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext("2d"); if (!ctx) { reject(new Error("Canvas context generation failed")); return; } ctx.drawImage(img, 0, 0); const mimeType = targetFormat === "png" ? "image/png" : "image/jpeg"; canvas.toBlob((blob) => { if (blob) resolve(blob); else reject(new Error("Blob conversion returned null")); }, mimeType, quality); }; img.onerror = () => { URL.revokeObjectURL(url); reject(new Error("Image decoding failed on main thread")); }; }); } var TimeCache = class { cache = new Map(); maxLimit = 100; get(key) { const val = this.cache.get(key); if (val !== void 0) { this.cache.delete(key); this.cache.set(key, val); return val; } return null; } set(key, value) { if (this.cache.has(key)) this.cache.delete(key); else if (this.cache.size >= this.maxLimit) { const oldestKey = this.cache.keys().next().value; if (oldestKey !== void 0) this.cache.delete(oldestKey); } this.cache.set(key, value); } }; var timeCache = new TimeCache(); function parseExifDateTime(dateStr, offsetStr) { if (!dateStr) return null; try { const parts = dateStr.split(" "); if (parts.length !== 2) return null; const formattedDate = parts[0].replace(/:/g, "-"); const formattedTime = parts[1]; const d = formattedDate.split("-"); const t = formattedTime.split(":"); const localDate = new Date(parseInt(d[0], 10), parseInt(d[1], 10) - 1, parseInt(d[2], 10), parseInt(t[0], 10), parseInt(t[1], 10), parseInt(t[2], 10)); const hasOffset = !!(offsetStr && /^[\+\-]\d{2}:\d{2}$/.test(offsetStr)); return { date: localDate, isNaive: !hasOffset, offset: hasOffset ? offsetStr : null }; } catch (e) { return null; } } function formatExifDateStyle(date, isNaive, offset = null, isDateOnly = false) { let lang = "en"; const userConfigLang = config?.userLanguage ?? "auto"; if (userConfigLang === "auto") lang = navigator.language || navigator.userLanguage || "en"; else lang = userConfigLang; let result = new Intl.DateTimeFormat(lang, isDateOnly ? { year: "numeric", month: "long", day: "numeric" } : { year: "numeric", month: "long", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false }).format(date); if (offset) result += ` (UTC${offset})`; else if (isNaive) result += ` (${t("localCameraTime")})`; return result; } function resolveTimeForensics(imgurl, exif, serverDateStr, firstTrackDate) { let shootDate = null; let shootIsNaive = true; let shootOffset = null; let digitizedDate = null; let digitizedIsNaive = true; let digitizedOffset = null; let modifyDate = null; let modifyIsNaive = true; let modifyOffset = null; let lastModifiedDate = null; if (exif) { const shootParsed = parseExifDateTime(exif.dateTimeOriginal, exif.offsetTimeOriginal || exif.offsetTime); if (shootParsed) { shootDate = shootParsed.date; shootIsNaive = shootParsed.isNaive; shootOffset = shootParsed.offset; } const digitizedParsed = parseExifDateTime(exif.dateTimeDigitized, exif.offsetTimeDigitized || exif.offsetTime); if (digitizedParsed) { digitizedDate = digitizedParsed.date; digitizedIsNaive = digitizedParsed.isNaive; digitizedOffset = digitizedParsed.offset; } const modifyParsed = parseExifDateTime(exif.modifyDate, exif.offsetTime); if (modifyParsed) { modifyDate = modifyParsed.date; modifyIsNaive = modifyParsed.isNaive; modifyOffset = modifyParsed.offset; } } let lastModifiedIsDateOnly = false; if (serverDateStr) { let parsed = new Date(serverDateStr); if (isNaN(parsed.getTime())) { const matches = serverDateStr.match(/(\d{4})[^\d]+(\d{1,2})[^\d]+(\d{1,2})/); if (matches) { const y = parseInt(matches[1], 10); const m = parseInt(matches[2], 10) - 1; const d = parseInt(matches[3], 10); parsed = new Date(y, m, d); } } if (!isNaN(parsed.getTime())) { lastModifiedDate = parsed; lastModifiedIsDateOnly = !serverDateStr.includes(":"); } } let primarySource = "googleBadge"; let primaryText = firstTrackDate || ""; let lang = "en"; const userConfigLang = config?.userLanguage ?? "auto"; if (userConfigLang === "auto") lang = navigator.language || navigator.userLanguage || "en"; else lang = userConfigLang; const formatOptions = { year: "numeric", month: "short", day: "numeric" }; if (shootDate) { primarySource = "shoot"; let txt = shootDate.toLocaleDateString(lang, formatOptions); if (shootOffset) txt += ` (UTC${shootOffset})`; else if (shootIsNaive) txt += ` (${t("localCameraTime")})`; primaryText = txt; } else if (digitizedDate) { primarySource = "digitized"; let txt = digitizedDate.toLocaleDateString(lang, formatOptions); if (digitizedOffset) txt += ` (UTC${digitizedOffset})`; else if (digitizedIsNaive) txt += ` (${t("localCameraTime")})`; primaryText = txt; } else if (modifyDate) { primarySource = "modify"; let txt = modifyDate.toLocaleDateString(lang, formatOptions); if (modifyOffset) txt += ` (UTC${modifyOffset})`; else if (modifyIsNaive) txt += ` (${t("localCameraTime")})`; primaryText = txt; } else if (lastModifiedDate) { primarySource = "lastModified"; primaryText = lastModifiedDate.toLocaleDateString(lang, formatOptions); } else if (firstTrackDate) { primarySource = "googleBadge"; primaryText = firstTrackDate; } return { shoot: shootDate, shootIsNaive, shootOffset, digitized: digitizedDate, digitizedIsNaive, digitizedOffset, modify: modifyDate, modifyIsNaive, modifyOffset, lastModified: lastModifiedDate, lastModifiedIsDateOnly, googleBadge: firstTrackDate, primarySource, primaryText, exifRaw: exif }; } function parseXmpHistory(xmpXml) { const actions = []; try { if (typeof DOMParser !== "undefined") { const listItems = new DOMParser().parseFromString(xmpXml, "text/xml").getElementsByTagNameNS("*", "li"); for (let i = 0; i < listItems.length; i++) { const item = listItems[i]; const actionEl = item.querySelector("*|action"); const softwareEl = item.querySelector("*|softwareAgent"); const whenEl = item.querySelector("*|when"); if (actionEl) actions.push({ action: actionEl.textContent || "", software: softwareEl ? softwareEl.textContent || "" : "", date: whenEl ? whenEl.textContent || "" : "" }); } if (actions.length > 0) return actions; } } catch (e) {} try { const historyIdx = xmpXml.indexOf("History"); const targetXml = historyIdx !== -1 ? xmpXml.substring(historyIdx) : xmpXml; const liRegex = /<[^:]*:?li\b[^>]*>([\s\S]*?)<\/[^:]*:?li>/gi; let match; while ((match = liRegex.exec(targetXml)) !== null) { const content = match[1]; const actionMatch = content.match(/<[^:]*:?action\b[^>]*>([^<]+)/i); const softwareMatch = content.match(/<[^:]*:?softwareAgent\b[^>]*>([^<]+)/i); const whenMatch = content.match(/<[^:]*:?when\b[^>]*>([^<]+)/i); if (actionMatch && actionMatch[1]) actions.push({ action: actionMatch[1].trim(), software: softwareMatch ? softwareMatch[1].trim() : "", date: whenMatch ? whenMatch[1].trim() : "" }); } } catch (e) { console.warn("Failed to parse XMP History via Regex:", e); } return actions; } function extractGpsRef(tag) { if (!tag) return ""; const val = tag.value; if (Array.isArray(val) && val.length > 0) return String(val[0]).trim().toUpperCase(); if (typeof val === "string") return val.trim().toUpperCase(); const desc = tag.description; if (typeof desc === "string" && desc.length > 0) return desc.trim().charAt(0).toUpperCase(); return ""; } function parseRational(v) { if (v && typeof v === "object" && "numerator" in v && "denominator" in v) { const num = Number(v.numerator); const den = Number(v.denominator); return den !== 0 && !isNaN(den) && !isNaN(num) ? num / den : 0; } const res = Number(v); return isNaN(res) ? 0 : res; } function convertToDecimalDegrees(gpsArr, ref) { if (!Array.isArray(gpsArr) || gpsArr.length < 3) return null; const deg = typeof gpsArr[0] === "number" ? gpsArr[0] : parseFloat(gpsArr[0]); const min = typeof gpsArr[1] === "number" ? gpsArr[1] : parseFloat(gpsArr[1]); const sec = typeof gpsArr[2] === "number" ? gpsArr[2] : parseFloat(gpsArr[2]); if (isNaN(deg) || isNaN(min) || isNaN(sec)) return null; let decimal = deg + min / 60 + sec / 3600; if (ref === "S" || ref === "W") decimal = -decimal; return decimal; } function formatGPS(gpsArr, ref) { if (!Array.isArray(gpsArr) || gpsArr.length < 3) return null; const deg = typeof gpsArr[0] === "number" ? gpsArr[0] : parseFloat(gpsArr[0]); const min = typeof gpsArr[1] === "number" ? gpsArr[1] : parseFloat(gpsArr[1]); const sec = typeof gpsArr[2] === "number" ? gpsArr[2] : parseFloat(gpsArr[2]); if (isNaN(deg) || isNaN(min) || isNaN(sec)) return null; const r = ref ? ref.trim().toUpperCase() : ""; return `${deg}° ${min}' ${sec.toFixed(2)}" ${r}`; } function formatExifDate(dateStr) { try { const parts = dateStr.split(" "); if (parts.length >= 1) { const dParts = parts[0].split(":"); if (dParts.length === 3) return `${dParts[0]}/${dParts[1]}/${dParts[2]}`; } } catch (e) {} return dateStr; } function parseExif(buffer) { try { const tags = exifreader.default.load(buffer); if (!tags) return null; const data = {}; if (tags["Make"]) data.make = tags["Make"].description; if (tags["Model"]) data.model = tags["Model"].description; if (tags["ExposureTime"]) { const exp = tags["ExposureTime"].description; data.exposureTime = exp.endsWith("s") ? exp : `${exp}s`; } if (tags["FNumber"]) { const fNum = tags["FNumber"].description; data.fNumber = fNum.startsWith("f/") ? fNum : `f/${fNum}`; } if (tags["ISOSpeedRatings"]) data.iso = `ISO ${tags["ISOSpeedRatings"].description}`; if (tags["DateTimeOriginal"]) data.dateTimeOriginal = tags["DateTimeOriginal"].description; if (tags["DateTimeDigitized"]) data.dateTimeDigitized = tags["DateTimeDigitized"].description; if (tags["ModifyDate"]) data.modifyDate = tags["ModifyDate"].description; if (tags["OffsetTime"]) data.offsetTime = tags["OffsetTime"].description; if (tags["OffsetTimeOriginal"]) data.offsetTimeOriginal = tags["OffsetTimeOriginal"].description; if (tags["OffsetTimeDigitized"]) data.offsetTimeDigitized = tags["OffsetTimeDigitized"].description; if (tags["LensModel"]) data.lensModel = tags["LensModel"].description; if (tags["FocalLength"]) { const focal = tags["FocalLength"].description; data.focalLength = focal.endsWith("mm") ? focal : `${focal} mm`; } if (tags["Software"]) { const sw = tags["Software"].description.trim(); const swLower = sw.toLowerCase(); if (!swLower.includes("ver") && !swLower.includes("firmware") && !/^[0-9.]+$/.test(sw)) data.software = sw; } if (tags["Flash"] && tags["Flash"].value !== void 0) { const flashVal = Number(tags["Flash"].value); if (!isNaN(flashVal)) data.flash = (flashVal & 1) === 1 ? "on" : "off"; } if (tags["GPSLatitude"] && tags["GPSLongitude"]) { const latVal = tags["GPSLatitude"].value; const lonVal = tags["GPSLongitude"].value; if (Array.isArray(latVal) && Array.isArray(lonVal)) { data.gpsLatitude = latVal.map((v) => parseRational(v)); data.gpsLongitude = lonVal.map((v) => parseRational(v)); } } if (tags["GPSLatitudeRef"]) data.gpsLatitudeRef = extractGpsRef(tags["GPSLatitudeRef"]); if (tags["GPSLongitudeRef"]) data.gpsLongitudeRef = extractGpsRef(tags["GPSLongitudeRef"]); if (data.dateTimeOriginal) data.date = formatExifDate(data.dateTimeOriginal); else if (data.modifyDate) data.date = formatExifDate(data.modifyDate); const ai = { isAI: false, confidence: "none", method: "none" }; const hasC2pa = !!(tags["c2pa"] || tags["C2PA"] || tags["activeManifest"] || tags["c2pa:activeManifest"] || tags["xmp"] && typeof tags["xmp"].description === "string" && (tags["xmp"].description.includes("http://ns.adobe.com/c2pa/1.0/") || tags["xmp"].description.includes("c2pa:activeManifest"))); const dst = tags["DigitalSourceType"]?.description || tags["Digital Source Type"]?.description || tags["xmp"] && typeof tags["xmp"].description === "string" && tags["xmp"].description.match(/DigitalSourceType[^>]*>([^<]+)/)?.[1]?.trim(); if (hasC2pa) { ai.isAI = true; ai.confidence = "high"; ai.method = "c2pa"; ai.detail = "C2PA Cryptographic Manifest"; if (tags["activeManifest"] && tags["activeManifest"].description) data.c2paIssuer = tags["activeManifest"].description; else if (tags["c2pa"] && tags["c2pa"].description) data.c2paIssuer = tags["c2pa"].description; if (tags["xmp"] && typeof tags["xmp"].description === "string") { const historyActions = parseXmpHistory(tags["xmp"].description); if (historyActions.length > 0) data.c2paActions = historyActions; } if (!data.c2paActions) data.c2paActions = [{ action: "c2pa.created", software: data.software || data.c2paIssuer || "AI Generator", date: data.dateTimeOriginal || data.modifyDate || "" }]; } else if (dst === "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" || dst === "trainedAlgorithmicMedia") { ai.isAI = true; ai.confidence = "high"; ai.method = "iptc"; ai.detail = "IPTC trainedAlgorithmicMedia"; } else if (dst === "http://cv.iptc.org/newscodes/digitalsourcetype/compositeSynthetic" || dst === "compositeSynthetic") { ai.isAI = true; ai.confidence = "medium"; ai.method = "iptc"; ai.detail = "IPTC compositeSynthetic"; } else if (tags["parameters"] || tags["Parameters"]) { const paramVal = tags["parameters"]?.description || tags["Parameters"]?.description; if (paramVal) { ai.isAI = true; ai.confidence = "medium"; ai.method = "parameters"; ai.detail = paramVal; } } else if (data.software) { const swLower = data.software.toLowerCase(); for (const kw of [ "midjourney", "dall-e", "novelai", "stable diffusion", "firefly" ]) if (swLower.includes(kw)) { ai.isAI = true; ai.confidence = "medium"; ai.method = "software"; ai.detail = data.software; break; } } if (ai.isAI) data.ai = ai; return data; } catch (e) { console.warn("ExifReader failed to parse image metadata:", e); } return null; } function getFriendlyActionName(act) { const a = act.toLowerCase().trim(); if (a.includes("created")) return t("c2paCreated"); if (a.includes("cropped")) return t("c2paCropped"); if (a.includes("color_adjustments") || a.includes("coloradjustments")) return t("c2paColorAdjustments"); if (a.includes("orientation")) return t("c2paOrientation"); if (a.includes("resized")) return t("c2paResized"); if (a.includes("converted")) return t("c2paConverted"); if (a.includes("edited") || a.includes("manipulated")) return t("c2paEdited"); if (a.includes("metadata")) return t("c2paMetadataAdded"); return t("c2paUnknown") + ` (${act})`; } function sanitizeFilenameBase(name, maxLen = 120) { if (!name) return "image"; let clean = name.replace(/[\x00-\x1f\\/:*?"<>|]+/g, "_").replace(/\s+/g, " ").trim(); clean = clean.replace(/^[.\s]+|[.\s]+$/g, ""); if (!clean) clean = "image"; if (/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\..*)?$/i.test(clean)) clean = `_${clean}`; if (clean.length > maxLen) clean = clean.substring(0, maxLen).trim(); return clean || "image"; } function getCurrentSearchQuery() { try { const q = new URLSearchParams(window.location.search).get("q"); if (q) return q.trim(); const input = document.querySelector("input[name=\"q\"], textarea[name=\"q\"]"); if (input && input.value) return input.value.trim(); } catch (e) {} return "google_images"; } function extractDomain(urlStr) { if (!urlStr) return "unknown"; try { return new URL(urlStr).hostname.replace(/^www\./i, "").toLowerCase(); } catch (e) { return "unknown"; } } function getCurrentDateTimeTokens() { const d = new Date(); const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); const hours = String(d.getHours()).padStart(2, "0"); const mins = String(d.getMinutes()).padStart(2, "0"); const secs = String(d.getSeconds()).padStart(2, "0"); return { date: `${year}-${month}-${day}`, time: `${hours}${mins}${secs}` }; } function formatDownloadFilename(ctx) { const mode = config.filenamePatternMode || "original"; const { date, time } = getCurrentDateTimeTokens(); const original = ctx.originalName ? ctx.originalName.replace(/\.[a-zA-Z0-9]+$/, "") : "image"; const query = ctx.query || getCurrentSearchQuery(); const domain = ctx.domain || "web"; const title = ctx.title || original; let dims = ""; if (ctx.width && ctx.height && Number(ctx.width) > 0 && Number(ctx.height) > 0) dims = `${ctx.width}x${ctx.height}`; let indexStr = ""; if (ctx.index !== void 0 && ctx.index !== null && ctx.index !== "") { const idxNum = Number(ctx.index); indexStr = !isNaN(idxNum) ? String(idxNum).padStart(2, "0") : String(ctx.index); } const rankStr = ctx.rank ? String(ctx.rank).padStart(2, "0") : ""; let template = "{original}"; switch (mode) { case "query_index": template = indexStr ? "{query}_{index}" : "{query}"; break; case "title_dims": template = dims ? "{title}_{dims}" : "{title}"; break; case "domain_title": template = "[{domain}] {title}"; break; case "custom": template = config.customFilenameTemplate || (indexStr ? "{query}_{index}" : "{query}"); break; default: template = "{original}"; } const tokenMap = { original, query, domain, title, dims: dims || "", index: indexStr, rank: rankStr, date: ctx.date || date, time: ctx.time || time }; let rendered = template; Object.keys(tokenMap).forEach((key) => { const val = tokenMap[key]; const regex = new RegExp(`\\{${key}\\}`, "gi"); rendered = rendered.replace(regex, val); }); rendered = rendered.replace(/\[\s*\]/g, "").replace(/\(\s*\)/g, "").replace(/#\s*(?=[_\-\s]|$)/g, "").replace(/_{2,}/g, "_").replace(/-{2,}/g, "-").replace(/\s{2,}/g, " ").replace(/^[_\-\s]+|[_\-\s]+$/g, ""); return sanitizeFilenameBase(rendered); } function getPreviewFilename(mode, customTpl) { const sampleCtx = { originalName: "artwork_master_final", query: "cyberpunk city", domain: "artstation.com", title: "Neon Alley Concept", width: 3840, height: 2160, index: 1, rank: 1, date: "2026-08-08", time: "170800" }; const oldMode = config.filenamePatternMode; const oldTpl = config.customFilenameTemplate; try { config.filenamePatternMode = mode; if (customTpl !== void 0) config.customFilenameTemplate = customTpl; return `${formatDownloadFilename(sampleCtx)}.jpg`; } finally { config.filenamePatternMode = oldMode; config.customFilenameTemplate = oldTpl; } } var lightboxImg$2; var currentServerDate = null; var currentServerMime = null; var activeTargetUrl = null; var BoundedMap = class extends Map { maxSize; constructor(maxSize = 150) { super(); this.maxSize = maxSize; } set(key, value) { if (this.size >= this.maxSize && !this.has(key)) { const firstKey = this.keys().next().value; if (firstKey !== void 0) this.delete(firstKey); } return super.set(key, value); } }; var serverMetadataCache = new BoundedMap(150); function initNetworkState(imgEl) { lightboxImg$2 = imgEl; } function setCurrentServerDate(val) { currentServerDate = val; } function setCurrentServerMime(val) { currentServerMime = val; } function setActiveTargetUrl(val) { activeTargetUrl = val; } function flashButtonSuccess(btn) { if (!btn) return; btn.classList.add("giat-btn-success"); setTimeout(() => btn.classList.remove("giat-btn-success"), 800); } function getOriginFromUrl(url) { try { return new URL(url).origin; } catch (e) { return ""; } } function detectMimeType(buffer) { if (!buffer || buffer.byteLength < 4) return null; const arr = new Uint8Array(buffer).subarray(0, 16); const header = Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("").toUpperCase(); if (header.startsWith("FFD8FF")) return "image/jpeg"; if (header.startsWith("89504E47")) return "image/png"; if (header.startsWith("47494638")) return "image/gif"; if (header.startsWith("52494646") && header.slice(16, 24) === "57454250") return "image/webp"; if (header.startsWith("424D")) return "image/bmp"; if (header.startsWith("00000100")) return "image/x-icon"; if (header.slice(8, 16) === "66747970" && header.slice(16, 24) === "61766966") return "image/avif"; if (header.startsWith("3C737667") || header.startsWith("3C3F786D")) return "image/svg+xml"; if (header.startsWith("1A45DFA3")) return "video/webm"; if (header.slice(8, 16) === "66747970") return "video/mp4"; if (header.startsWith("4F676753")) return "video/ogg"; return null; } function formatBytes$1(bytes) { if (bytes <= 0) return ""; const k = 1024; const sizes = [ "Bytes", "KB", "MB", "GB" ]; const i = Math.floor(Math.log(bytes) / Math.log(k)); if (i === 0) return `${bytes}${sizes[i]}`; const val = bytes / Math.pow(k, i); return i === 1 ? `${Math.round(val)}KB` : `${val.toFixed(1)}${sizes[i]}`; } function estimateJpegHeaderSize(buffer) { const view = new DataView(buffer); if (view.byteLength < 4) return null; if (view.getUint16(0) !== 65496) return null; let offset = 2; const length = view.byteLength; while (offset < length - 1) { if (view.getUint16(offset) === 65498) return offset; if (offset + 3 >= length) return null; const segmentLength = view.getUint16(offset + 2); offset += 2 + segmentLength; } return null; } function appendArrayBuffer(buffer1, buffer2) { const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); tmp.set(new Uint8Array(buffer1), 0); tmp.set(new Uint8Array(buffer2), buffer1.byteLength); return tmp.buffer; } function getTbnidFromUrl() { try { return new URLSearchParams(window.location.search).get("imgrc") || ""; } catch (e) { return ""; } } function fetchServerMetadata(url) { return new Promise((resolve) => { const result = { date: null, mime: null, exif: null, fileSize: null, exifRaw: null }; if (typeof GM_xmlhttpRequest !== "undefined") { const origin = getOriginFromUrl(url); const headers = { "accept": "image/jpeg,image/png,image/gif,image/apng,image/svg+xml,image/*;q=0.8,*/*;q=0.5", "range": "bytes=0-131071" }; if (origin) headers["referer"] = url; const processBuffer = async (buffer, responseHeaders, status) => { const lmMatch = responseHeaders.match(/last-modified:\s*([^\r\n]+)/i); if (lmMatch) result.date = lmMatch[1].trim(); const ctMatch = responseHeaders.match(/content-type:\s*([^;\r\n]+)/i); if (ctMatch) result.mime = ctMatch[1].trim(); const detectedMime = detectMimeType(buffer); if (detectedMime) result.mime = detectedMime; let bytes = 0; const crMatch = responseHeaders.match(/content-range:\s*bytes\s+\d+-\d+\/(\d+)/i); if (crMatch) bytes = parseInt(crMatch[1], 10); else if (status === 200) { const clMatch = responseHeaders.match(/content-length:\s*(\d+)/i); if (clMatch) bytes = parseInt(clMatch[1], 10); } if (bytes > 0) result.fileSize = formatBytes$1(bytes); if ((result.mime === "image/jpeg" || result.mime === "image/webp" || result.mime === "image/png") && config.enableLightboxExif) try { const exif = parseExif(buffer.byteLength > 131072 ? buffer.slice(0, 131072) : buffer); if (exif) { result.exifRaw = exif; const parts = []; if (exif.make || exif.model) { const camera = exif.make && exif.model && exif.model.startsWith(exif.make) ? exif.model : [exif.make, exif.model].filter(Boolean).join(" "); parts.push(camera); } else if (exif.ai?.isAI && exif.software) parts.push(exif.software); if (parts.length > 0) result.exif = parts.join(" · "); } } catch (e) { console.warn("Failed to parse EXIF in metadata fetch:", e); } resolve(result); }; GM_xmlhttpRequest({ method: "GET", url, headers, anonymous: true, responseType: "arraybuffer", onload: (response) => { if (response.status >= 200 && response.status < 300) { const responseHeaders = response.responseHeaders || ""; const firstBuffer = response.response; if (detectMimeType(firstBuffer) === "image/jpeg") { const requiredSize = estimateJpegHeaderSize(firstBuffer); if (requiredSize !== null) { const nextHeaders = { "accept": "image/webp,image/jpeg,image/png,image/gif,image/apng,image/svg+xml,image/*;q=0.8", "range": `bytes=${firstBuffer.byteLength}-${requiredSize - 1}` }; if (origin) nextHeaders["referer"] = url; GM_xmlhttpRequest({ method: "GET", url, headers: nextHeaders, anonymous: true, responseType: "arraybuffer", onload: (nextResponse) => { let finalBuffer = firstBuffer; if (nextResponse.status >= 200 && nextResponse.status < 300) { const secondBuffer = nextResponse.response; finalBuffer = appendArrayBuffer(firstBuffer, secondBuffer); } processBuffer(finalBuffer, responseHeaders, response.status); }, onerror: () => { processBuffer(firstBuffer, responseHeaders, response.status); } }); return; } } processBuffer(firstBuffer, responseHeaders, response.status); } else resolve(result); }, onerror: () => resolve(result) }); } else resolve(result); }); } function getRefererUrl(imgUrl) { const sourceUrl = lightboxImg$2 ? lightboxImg$2.dataset.giatSourceUrl : null; if (sourceUrl) { const origin = getOriginFromUrl(sourceUrl); if (origin) return origin + "/"; } return getOriginFromUrl(imgUrl) + "/"; } function fetchImageBlob(url, onProgress, customHeaders) { const strategies = [ { name: "Same-Origin Homepage Spoofing", anonymous: true, headers: { "Referer": getRefererUrl(url), "sec-fetch-dest": "image", "sec-fetch-mode": "no-cors", "sec-fetch-site": "same-origin", "accept": "image/jpeg,image/png,image/gif,image/apng,image/svg+xml,image/*;q=0.8", ...customHeaders || {} } }, { name: "Same-Origin Image Spoofing", anonymous: true, headers: { "Referer": url, "sec-fetch-dest": "image", "sec-fetch-mode": "no-cors", "sec-fetch-site": "same-origin", "accept": "image/jpeg,image/png,image/gif,image/apng,image/svg+xml,image/*;q=0.8", ...customHeaders || {} } }, { name: "Ultimate Navigation Spoofing", anonymous: true, headers: { "Referer": "", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/jpeg,image/png,image/gif,image/apng,*/*;q=0.8", "upgrade-insecure-requests": "1", "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "none", "sec-fetch-user": "?1", "cache-control": "max-age=0", ...customHeaders || {} } }, { name: "Clean Anonymous Request", anonymous: true, headers: { "accept": "image/jpeg,image/png,image/gif,image/apng,image/svg+xml,image/*;q=0.8,*/*;q=0.5", ...customHeaders || {} } } ]; let promiseChain = Promise.reject(new Error("Initial trigger")); strategies.forEach((strat) => { promiseChain = promiseChain.catch(() => { return new Promise((resolve, reject) => { if (typeof GM_xmlhttpRequest !== "undefined") { console.log(`[GIAT] Attempting fetchImageBlob with strategy: "${strat.name}" for URL: ${url}`); GM_xmlhttpRequest({ method: "GET", url, headers: strat.headers, anonymous: strat.anonymous, responseType: "arraybuffer", onprogress: (response) => { if (response.lengthComputable && onProgress) onProgress(Math.round(response.loaded / response.total * 100)); }, onload: async (response) => { console.log(`[GIAT] Strategy "${strat.name}" response status: ${response.status} for URL: ${url}`); if (response.status >= 200 && response.status < 300) { const lastModifiedMatch = (response.responseHeaders || "").match(/last-modified:\s*([^\r\n]+)/i); currentServerDate = lastModifiedMatch ? lastModifiedMatch[1].trim() : null; const buffer = response.response; let mimeType = detectMimeType(buffer); if (!mimeType) { const typeMatch = (response.responseHeaders || "").toLowerCase().match(/content-type:\s*([^;\r\n]+)/); const contentType = typeMatch ? typeMatch[1].trim() : ""; if (contentType.startsWith("image/") || contentType.startsWith("video/")) mimeType = contentType; } if (mimeType) { if ((mimeType === "image/jpeg" || mimeType === "image/webp" || mimeType === "image/png") && config.enableLightboxExif) try { const exif = parseExif(buffer.byteLength > 131072 ? buffer.slice(0, 131072) : buffer); if (exif && lightboxImg$2) { lightboxImg$2.exifRawData = exif; const parts = []; if (exif.make || exif.model) { const camera = exif.make && exif.model && exif.model.startsWith(exif.make) ? exif.model : [exif.make, exif.model].filter(Boolean).join(" "); parts.push(camera); } else if (exif.ai?.isAI && exif.software) parts.push(exif.software); if (parts.length > 0) lightboxImg$2.dataset.giatExif = parts.join(" · "); } } catch (e) { console.warn("Failed to parse EXIF metadata:", e); } resolve(new Blob([buffer], { type: mimeType })); } else { const headers = (response.responseHeaders || "").toLowerCase(); let isCloudflare = headers.includes("server: cloudflare") || headers.includes("cf-ray") || headers.includes("cf-mitigated"); let isHotlink = false; try { const textSample = new TextDecoder("utf-8").decode(buffer.slice(0, 1024)).toLowerCase(); if (textSample.includes("hotlink")) isHotlink = true; if (textSample.includes("cloudflare") || textSample.includes("just a moment")) isCloudflare = true; } catch (e) {} if (isCloudflare) reject(new Error(`Strategy "${strat.name}" failed: CLOUDFLARE_BLOCK`)); else if (isHotlink) reject(new Error(`Strategy "${strat.name}" failed: HOTLINK_BLOCK`)); else reject(new Error(`Strategy "${strat.name}" failed: Non-image binary payload`)); } } else { const headers = (response.responseHeaders || "").toLowerCase(); if (headers.includes("server: cloudflare") || headers.includes("cf-ray") || headers.includes("cf-mitigated")) reject(new Error(`Strategy "${strat.name}" failed: CLOUDFLARE_BLOCK (HTTP ${response.status})`)); else if (response.status === 404) reject(new Error(`Strategy "${strat.name}" failed: HTTP_404`)); else reject(new Error(`Strategy "${strat.name}" failed: HTTP_${response.status}`)); } }, onerror: (err) => { console.warn(`[GIAT] Strategy "${strat.name}" network error for URL: ${url}`); reject(err); } }); } else { let responseRef; fetch(url, { referrerPolicy: "no-referrer", headers: { "accept": "image/jpeg,image/png,image/gif,image/apng,image/svg+xml,image/*;q=0.8,*/*;q=0.5" } }).then((res) => { if (res.ok) { responseRef = res; const lm = res.headers.get("last-modified"); currentServerDate = lm ? lm.trim() : null; return res.arrayBuffer(); } throw new Error("Native fetch failed"); }).then(async (buffer) => { let mimeType = detectMimeType(buffer); if (!mimeType && responseRef) { const contentType = (responseRef.headers.get("content-type") || "").toLowerCase().split(";")[0].trim(); if (contentType.startsWith("image/") || contentType.startsWith("video/")) mimeType = contentType; } if (mimeType) { if ((mimeType === "image/jpeg" || mimeType === "image/webp" || mimeType === "image/png") && config.enableLightboxExif) try { const exif = parseExif(buffer.byteLength > 131072 ? buffer.slice(0, 131072) : buffer); if (exif && lightboxImg$2) { lightboxImg$2.exifRawData = exif; const parts = []; if (exif.make || exif.model) { const camera = exif.make && exif.model && exif.model.startsWith(exif.make) ? exif.model : [exif.make, exif.model].filter(Boolean).join(" "); parts.push(camera); } else if (exif.ai?.isAI && exif.software) parts.push(exif.software); if (parts.length > 0) lightboxImg$2.dataset.giatExif = parts.join(" · "); } } catch (e) { console.warn("Failed to parse EXIF metadata:", e); } resolve(new Blob([buffer], { type: mimeType })); } else throw new Error("Unsupported binary payload from native fetch"); }).catch((err) => { reject(err); }); } }); }); }); return promiseChain; } async function fetchImageBlobWithFallback(url, rawOriginalUrl, onProgress) { if (url && (url.startsWith("blob:") || url.startsWith("data:"))) try { return { blob: await (await fetch(url)).blob(), finalUrl: url }; } catch (e) { throw new Error(`Failed reading local blob/data URL: ${url}`); } const urlsToTry = [url]; let customHeaders = void 0; if (url) try { const u = new URL(url); const hn = u.hostname; const matchedDomain = Object.keys(asyncOptimizers).find((domain) => hn.includes(domain)); if (matchedDomain) { const res = await asyncOptimizers[matchedDomain](u, url, rawOriginalUrl); if (Array.isArray(res)) res.forEach((cand) => { if (!urlsToTry.includes(cand)) urlsToTry.push(cand); }); else if (res && typeof res === "object") { if (res.headers) customHeaders = res.headers; if (Array.isArray(res.urls)) res.urls.forEach((cand) => { if (!urlsToTry.includes(cand)) urlsToTry.push(cand); }); } } if (hn.includes("pinimg.com") && u.pathname.includes("/originals/")) { const path736 = u.pathname.replace("/originals/", "/736x/"); const u736 = new URL(url); u736.pathname = path736; if (!urlsToTry.includes(u736.toString())) urlsToTry.push(u736.toString()); } if (hn.includes("artstation.com") && u.pathname.includes("/original/")) { const path8k = u.pathname.replace("/original/", "/8k/"); const u8k = new URL(url); u8k.pathname = path8k; if (!urlsToTry.includes(u8k.toString())) urlsToTry.push(u8k.toString()); const path4k = u.pathname.replace("/original/", "/4k/"); const u4k = new URL(url); u4k.pathname = path4k; if (!urlsToTry.includes(u4k.toString())) urlsToTry.push(u4k.toString()); const pathLarge = u.pathname.replace("/original/", "/large/"); const uLarge = new URL(url); uLarge.pathname = pathLarge; if (!urlsToTry.includes(uLarge.toString())) urlsToTry.push(uLarge.toString()); } if ((hn.includes("staticflickr.com") || hn.includes("flickr.com")) && !matchedDomain) { const targetRaw = rawOriginalUrl || url; const flickrRegex = /\/([0-9]+)_([0-9a-f]+)(?:_[a-z0-9]*)*\.([a-zA-Z0-9]+)$/i; const match = targetRaw.match(flickrRegex); if (match) { const photoId = match[1]; const secret = match[2]; const ext = match[3]; const basePath = targetRaw.replace(flickrRegex, `/${photoId}_${secret}`); [ "o", "k", "h", "b" ].forEach((size) => { const candidate = `${basePath}_${size}.${ext}`; if (!urlsToTry.includes(candidate)) urlsToTry.push(candidate); }); } } } catch (e) {} if (rawOriginalUrl && !urlsToTry.includes(rawOriginalUrl) && rawOriginalUrl !== url) urlsToTry.push(rawOriginalUrl); let lastError = new Error("No URLs to try"); for (const targetUrl of urlsToTry) try { console.log("[ShowDims] Fallback attempt trying URL:", targetUrl); return { blob: await fetchImageBlob(targetUrl, onProgress, customHeaders), finalUrl: targetUrl }; } catch (err) { console.warn(`[ShowDims] Failed to fetch URL: ${targetUrl}, trying next fallback. Error:`, err); lastError = err; } throw lastError; } function isSensitiveUrl$1(urlStr) { try { const hn = new URL(urlStr).hostname; return hn.includes("pinimg.com") || hn.includes("artstation.com") || hn.includes("staticflickr.com") || hn.includes("flickr.com") || hn.includes("fastpic.ru") || hn.includes("fastpic.org") || hn.includes("img.4plebs.org") || hn.includes("img.fireden.net") || hn.includes("img-lb.fireden.net") || hn.includes("torako.wakarimasen.moe"); } catch (e) { return false; } } function openUrlWithFallback$1(url, rawOriginalUrl) { if (!isSensitiveUrl$1(url)) { window.open(url, "_blank", "noopener,noreferrer"); return; } const newTab = window.open("about:blank", "_blank"); if (!newTab) { window.open(url, "_blank", "noopener,noreferrer"); return; } newTab.document.title = t("toastLoadingImage"); newTab.document.body.innerHTML = ` <div style="display:flex;flex-direction:column;justify-content:center;align-items:center;height:100vh;font-family:sans-serif;color:#888;background:#121212;"> <div style="border:3px solid #333;border-top:3px solid #888;border-radius:50%;width:30px;height:30px;animation:spin 1s linear infinite;margin-bottom:15px;"></div> <div>${t("toastLoadingImage")}</div> </div> <style> @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> `; fetchImageBlobWithFallback(url, rawOriginalUrl).then(({ finalUrl }) => { newTab.location.replace(finalUrl); }).catch(() => { newTab.location.replace(rawOriginalUrl || url); }); } function openSearchUrlWithFallback(url, rawOriginalUrl, buildSearchUrl, loadingText) { if (!isSensitiveUrl$1(url)) { window.open(buildSearchUrl(url), "_blank", "noopener,noreferrer"); return; } const newTab = window.open("about:blank", "_blank"); if (!newTab) { window.open(buildSearchUrl(url), "_blank", "noopener,noreferrer"); return; } newTab.document.title = "Searching..."; newTab.document.body.innerHTML = ` <div style="display:flex;flex-direction:column;justify-content:center;align-items:center;height:100vh;font-family:sans-serif;color:#888;background:#121212;"> <div style="border:3px solid #333;border-top:3px solid #888;border-radius:50%;width:30px;height:30px;animation:spin 1s linear infinite;margin-bottom:15px;"></div> <div>${loadingText}</div> </div> <style> @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> `; fetchImageBlobWithFallback(url, rawOriginalUrl).then(({ finalUrl }) => { newTab.location.replace(buildSearchUrl(finalUrl)); }).catch(() => { newTab.location.replace(buildSearchUrl(rawOriginalUrl || url)); }); } function convertToPngBlob(blob) { return convertImageInWorker(blob, "png"); } var bannerTimeoutId = null; function showDlBypassBanner(imgurl, reasonText) { if (bannerTimeoutId !== null) { clearTimeout(bannerTimeoutId); bannerTimeoutId = null; } const existing = document.querySelector(".giat-download-bypass-banner"); if (existing) existing.remove(); const banner = document.createElement("div"); banner.classList.add("giat-download-bypass-banner"); const isDark = config.uiTheme === "auto" ? isPageDark() : config.uiTheme === "dark"; banner.classList.add(isDark ? "giat-banner-dark" : "giat-banner-light"); const text = document.createElement("span"); text.classList.add("giat-download-bypass-text"); const reason = reasonText || t("reason403"); text.textContent = t("errDlBypassGuide").replace("{REASON}", reason); const btn = document.createElement("a"); btn.classList.add("giat-download-bypass-btn"); btn.href = imgurl; btn.target = "_blank"; btn.rel = "noopener noreferrer"; btn.textContent = t("errOpenInNewTabBtn"); banner.append(text, btn); document.body.append(banner); setTimeout(() => banner.classList.add("show"), 10); bannerTimeoutId = window.setTimeout(() => { banner.classList.remove("show"); setTimeout(() => banner.remove(), 400); bannerTimeoutId = null; }, 8e3); } function convertToBase64Async(blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => { if (typeof reader.result === "string") resolve(reader.result); else reject(new Error("Failed to convert blob to Data URL")); }; reader.onerror = () => reject(reader.error || new Error("FileReader error")); reader.readAsDataURL(blob); }); } function withVirtualProgress(promise, actionText, onCompleteText) { let progress = 0; showToast(`${actionText} (0%)`, 0); const interval = window.setInterval(() => { if (progress < 95) { progress += Math.floor(Math.random() * 3) + 1; if (progress > 95) progress = 95; showToast(`${actionText} (${progress}%)`, 0); } }, 30); return promise.then((result) => { clearInterval(interval); if (onCompleteText) showToast(onCompleteText, 1500); else showToast(`${actionText} (100%)`, 1e3); return result; }).catch((err) => { clearInterval(interval); throw err; }); } function getFailureReasonFromError(err) { const msg = err?.message || String(err); if (msg.includes("CLOUDFLARE")) return t("reasonCloudflare"); if (msg.includes("HOTLINK")) return t("reasonHotlink"); if (msg.includes("HTTP_404") || msg.includes("404")) return t("reason404"); if (msg.includes("TIMEOUT") || msg.includes("timeout")) return t("reasonTimeout"); if (msg.includes("UNSUPPORTED_DOMAIN")) return t("reasonUnsupported"); return t("reason403"); } async function copyImageToClipboard(url, btn, rawOriginalUrl) { try { showToast(`${t("toastFetching")} (0%)`, 0); const { blob } = await fetchImageBlobWithFallback(url, rawOriginalUrl || lightboxImg$2 && lightboxImg$2.dataset.giatRawOriginalUrl || void 0, (percent) => { showToast(`${t("toastFetching")} (${percent}%)`, 0); }); const pngBlob = await withVirtualProgress(convertToPngBlob(blob), t("toastConverting"), ""); try { await navigator.clipboard.write([new ClipboardItem({ [pngBlob.type]: pngBlob })]); showToast(t("toastCopied")); } catch (writeErr) { console.warn("Direct image clipboard write failed, falling back to Base64:", writeErr); const dataUrl = await convertToBase64Async(pngBlob); await navigator.clipboard.writeText(dataUrl); showToast(t("toastB64Copied") + " (Fallback)"); } if (btn) flashButtonSuccess(btn); } catch (err) { console.error("Clipboard copy failure:", err); const reason = getFailureReasonFromError(err); showToast(`${t("toastCopyFail")} · ${reason}`); showDlBypassBanner(url, reason); } } async function copyBase64ToClipboard(url, btn, rawOriginalUrl) { try { showToast(`${t("toastFetching")} (0%)`, 0); const { blob } = await fetchImageBlobWithFallback(url, rawOriginalUrl || lightboxImg$2 && lightboxImg$2.dataset.giatRawOriginalUrl || void 0, (percent) => { showToast(`${t("toastFetching")} (${percent}%)`, 0); }); const dataUrl = await withVirtualProgress(convertToBase64Async(blob), t("toastB64Converting"), t("toastB64Copied")); await navigator.clipboard.writeText(dataUrl); if (btn) flashButtonSuccess(btn); } catch (err) { console.error("Base64 copy failure:", err); const reason = getFailureReasonFromError(err); showToast(`${t("toastB64Fail")} · ${reason}`); showDlBypassBanner(url, reason); } } function triggerBlobDownload(blobUrl, filename) { const a = document.createElement("a"); a.href = blobUrl; a.download = filename; a.addEventListener("click", (e) => { e.stopPropagation(); }); document.body.appendChild(a); a.click(); a.remove(); } function getExtensionFromMimeType(mime) { const type = mime.toLowerCase().trim(); if (type === "image/jpeg") return "jpg"; if (type === "image/png") return "png"; if (type === "image/webp") return "webp"; if (type === "image/gif") return "gif"; if (type === "image/avif") return "avif"; if (type === "image/svg+xml") return "svg"; if (type === "image/bmp") return "bmp"; if (type === "image/x-icon" || type === "image/vnd.microsoft.icon") return "ico"; return null; } function convertWebpBlob(blob, format, quality) { return convertImageInWorker(blob, format.toLowerCase() === "png" ? "png" : "jpeg", quality / 100); } async function downloadImage(url, btn, rawOriginalUrl, contextOverrides) { if (btn) flashButtonSuccess(btn); let newBlobUrlCreated = false; let blobUrl = url; try { let targetUrl = url; if (url.startsWith("blob:") && lightboxImg$2) targetUrl = lightboxImg$2.dataset.giatOriginalUrl || url; const urlSegments = targetUrl.split("/"); const filename = urlSegments[urlSegments.length - 1].split("?")[0] || "downloaded-image.jpg"; let finalFilename = filename; let blob = null; if (url.startsWith("blob:")) try { const res = await fetch(url); if (res.ok) blob = await res.blob(); } catch (e) { console.warn("Failed to fetch local blob URL directly, falling back to original mime type logic:", e); } else { const rawUrl = rawOriginalUrl || lightboxImg$2 && lightboxImg$2.dataset.giatRawOriginalUrl || void 0; showToast(`${t("toastDownloading")} (0%)`, 0); const res = await fetchImageBlobWithFallback(url, rawUrl, (percent) => { showToast(`${t("toastDownloading")} (${percent}%)`, 0); }); blob = res.blob; if (res.finalUrl !== url) { const fallbackSegments = res.finalUrl.split("/"); finalFilename = fallbackSegments[fallbackSegments.length - 1].split("?")[0] || filename; } } if (blob) { let activeBlob = blob; if (config.enableWebpConversion && activeBlob.type === "image/webp") try { activeBlob = await withVirtualProgress(convertWebpBlob(activeBlob, config.webpConversionFormat, config.webpConversionQuality), t("toastConverting"), ""); } catch (convErr) { console.error(`Failed to convert WebP to ${config.webpConversionFormat.toUpperCase()}, downloading original WebP instead:`, convErr); } blobUrl = blobManager.createManagedUrl(activeBlob, "download"); newBlobUrlCreated = true; if (activeBlob.type) { const ext = getExtensionFromMimeType(activeBlob.type) || "jpg"; const safeOverrides = contextOverrides ? Object.fromEntries(Object.entries(contextOverrides).filter(([, v]) => v !== void 0)) : {}; finalFilename = `${formatDownloadFilename({ originalName: filename, query: getCurrentSearchQuery(), domain: extractDomain(targetUrl), title: lightboxImg$2 && (lightboxImg$2.dataset.giatTitle || lightboxImg$2.alt) || document.title, width: lightboxImg$2?.naturalWidth || lightboxImg$2?.dataset.giatNaturalWidth, height: lightboxImg$2?.naturalHeight || lightboxImg$2?.dataset.giatNaturalHeight, rank: lightboxImg$2 && lightboxImg$2.dataset.giatSerpRank || void 0, index: void 0, ...safeOverrides })}.${ext}`; } } triggerBlobDownload(blobUrl, finalFilename); showToast(t("toastDownloading"), 1e3); } catch (err) { console.error("Download execution failure:", err); const reason = getFailureReasonFromError(err); showToast(`${t("toastDownloadFail")} · ${reason}`); showDlBypassBanner(url, reason); } finally { if (newBlobUrlCreated) blobManager.revokeOnDownload(blobUrl, 8e3); } } var activeBlobUrls = new Map(); if (typeof window !== "undefined") window.addEventListener("beforeunload", () => { activeBlobUrls.forEach((data) => { URL.revokeObjectURL(data.url); data.channels.forEach((ch) => { try { ch.close(); } catch (e) {} }); }); activeBlobUrls.clear(); }); function generateUniqueUploadId() { if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID(); const array = new Uint32Array(4); crypto.getRandomValues(array); return array.join("-"); } function registerBlobSharing(blob) { const uploadId = generateUniqueUploadId(); const blobUrl = URL.createObjectURL(blob); activeBlobUrls.set(uploadId, { url: blobUrl, blob, channels: [] }); setTimeout(() => { const record = activeBlobUrls.get(uploadId); if (record) { URL.revokeObjectURL(record.url); record.channels.forEach((ch) => { try { ch.close(); } catch (e) {} }); activeBlobUrls.delete(uploadId); } }, 15e3); return { uploadId, blobUrl }; } if (typeof window !== "undefined") { const handleBinaryTransmissionRequest = (event) => { if (event.data === "request_fallback_buffer") { const channel = event.target; const match = channel.name.match(/^giat_channel_(.+)$/); if (match) { const uploadId = match[1]; const record = activeBlobUrls.get(uploadId); if (record) channel.postMessage({ type: "fallback_buffer", sharedBlob: record.blob }); } } }; const receiveReceiptMessage = (event) => { if (event.data && event.data.type === "upload_completed") { const channel = event.target; const match = channel.name.match(/^giat_channel_(.+)$/); if (match) { const uploadId = match[1]; const record = activeBlobUrls.get(uploadId); if (record) { URL.revokeObjectURL(record.url); record.channels.forEach((ch) => { try { ch.close(); } catch (e) {} }); activeBlobUrls.delete(uploadId); try { channel.close(); } catch (e) {} } } } }; window.addEventListener("message", (event) => { if (event.data && typeof event.data === "object" && event.data.type === "register_fallback_listener") { const uploadId = event.data.uploadId; const record = activeBlobUrls.get(uploadId); const channel = new BroadcastChannel(`giat_channel_${uploadId}`); if (record) record.channels.push(channel); channel.onmessage = (ev) => { handleBinaryTransmissionRequest(ev); receiveReceiptMessage(ev); }; } }); } async function triggerAiSearchWithUpload(imgurl, titleText, srcUrl, rawOriginalUrl) { try { showToast(t("toastPreparingAiSearch")); const { blob } = await fetchImageBlobWithFallback(imgurl, rawOriginalUrl); const { uploadId, blobUrl } = registerBlobSharing(blob); if (typeof window !== "undefined") window.postMessage({ type: "register_fallback_listener", uploadId }, "*"); const payload = { imgurl, titleText, srcUrl, blobUrl, mime: blob.type, prompt: config.aiSearchPrompt }; GM_setValue(`giat_upload_info_${uploadId}`, JSON.stringify(payload)); GM_setValue("giat_temp_upload_id", uploadId); const targetUrl = `https://www.google.com/search?q=&udm=50#giat_upload_id=${uploadId}`; window.open(targetUrl, "_blank", "noopener,noreferrer"); } catch (err) { console.error("Trigger AI search with upload failure:", err); const reason = getFailureReasonFromError(err); showToast(`${t("toastPrepareAiSearchFail")} · ${reason}`); showDlBypassBanner(imgurl, reason); } } function fetchArrayBufferViaGM(url) { return new Promise((resolve, reject) => { if (typeof GM_xmlhttpRequest === "undefined") { reject(new Error("GM_xmlhttpRequest is not available in current environment")); return; } const origin = new URL(url).origin; const headers = {}; if (origin) headers["referer"] = url; GM_xmlhttpRequest({ method: "GET", url, headers, responseType: "arraybuffer", anonymous: true, timeout: 4e3, onload: (response) => { if (response.status >= 200 && response.status < 300 && response.response) { const contentType = response.responseHeaders ? response.responseHeaders.match(/content-type:\s*([^\r\n]+)/i)?.[1] || "image/jpeg" : "image/jpeg"; resolve({ buffer: response.response, contentType }); } else reject(new Error(`GM_xmlhttpRequest failed with status: ${response.status}`)); }, onerror: (err) => reject(new Error("GM_xmlhttpRequest network error: " + String(err))), ontimeout: () => reject(new Error("GM_xmlhttpRequest request timeout (4s)")) }); }); } async function fetchCleanBlob(url) { try { const res = await fetch(url, { mode: "cors" }); if (res.ok) { const blob = await res.blob(); if (blob && blob.size > 0) return blob; } } catch (e) {} try { const { buffer, contentType } = await fetchArrayBufferViaGM(url); return new Blob([buffer], { type: contentType }); } catch (err) { throw new Error(`CORS Rescue Pipeline failed for URL [${url}]: ${err.message}`); } } async function fetchCleanImageBitmap(url) { try { const blob = await fetchCleanBlob(url); return await createImageBitmap(blob); } catch (err) { throw new Error(`Failed to generate clean ImageBitmap: ${err.message}`); } } function showMissedItemsPanel(missedItems) { if (!missedItems || missedItems.length === 0) return; const uniqueMissedMap = new Map(); missedItems.forEach((item) => { const existing = uniqueMissedMap.get(item.url); if (!existing) uniqueMissedMap.set(item.url, item); else if (existing.reason === "HTTP_403" && item.reason !== "HTTP_403") uniqueMissedMap.set(item.url, item); }); const deduplicatedItems = Array.from(uniqueMissedMap.values()); const existing = document.querySelector(".giat-missed-panel-overlay"); if (existing) existing.remove(); const isDark = config.uiTheme === "auto" ? isPageDark() : config.uiTheme === "dark"; const overlay = document.createElement("div"); overlay.classList.add("giat-hud-overlay", "giat-missed-panel-overlay"); overlay.classList.toggle("giat-theme-dark", isDark); overlay.classList.toggle("giat-theme-light", !isDark); const card = document.createElement("div"); card.classList.add("giat-hud-card", "giat-missed-card"); card.classList.toggle("giat-theme-dark", isDark); card.classList.toggle("giat-theme-light", !isDark); const svgAlert = `<svg class="giat-inline-svg" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v4m-1.637-9.409L2.257 17.125a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636-2.87L13.637 3.59a1.914 1.914 0 0 0-3.274 0M12 16h.01"/></svg>`; const svgLink = `<svg class="giat-inline-svg" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"><path fill="currentColor" d="M7 17q-2.075 0-3.537-1.463T2 12t1.463-3.537T7 7h3q.425 0 .713.288T11 8t-.288.713T10 9H7q-1.25 0-2.125.875T4 12t.875 2.125T7 15h3q.425 0 .713.288T11 16t-.288.713T10 17zm2-4q-.425 0-.712-.288T8 12t.288-.712T9 11h6q.425 0 .713.288T16 12t-.288.713T15 13zm5 4q-.425 0-.712-.288T13 16t.288-.712T14 15h3q1.25 0 2.125-.875T20 12t-.875-2.125T17 9h-3q-.425 0-.712-.288T13 8t.288-.712T14 7h3q2.075 0 3.538 1.463T22 12t-1.463 3.538T17 17z"/></svg>`; const title = document.createElement("div"); title.classList.add("giat-hud-title"); title.innerHTML = `<span>${svgAlert} ${t("missedTitle")} (${deduplicatedItems.length})</span><button class="giat-missed-close-btn">✕</button>`; const grid = document.createElement("div"); grid.classList.add("giat-hud-grid", "giat-missed-grid"); deduplicatedItems.forEach((item, index) => { const row = document.createElement("div"); row.classList.add("giat-hud-row", "giat-missed-row"); let reasonText = t("reason403"); if (item.reason === "UNSUPPORTED_DOMAIN") reasonText = t("reasonUnsupported"); else if (item.reason === "CLOUDFLARE") reasonText = t("reasonCloudflare"); else if (item.reason === "HOTLINK") reasonText = t("reasonHotlink"); else if (item.reason === "HTTP_404") reasonText = t("reason404"); else if (item.reason === "TIMEOUT") reasonText = t("reasonTimeout"); const shortUrl = item.url.length > 50 ? item.url.substring(0, 47) + "..." : item.url; row.innerHTML = ` <div class="giat-missed-item-info"> <span class="giat-missed-badge">${reasonText}</span> <span class="giat-missed-url" title="${item.url}">${shortUrl}</span> </div> <a href="${item.url}" target="_blank" rel="noopener noreferrer" class="giat-missed-link-btn">${svgLink} ${t("openSourceUrl")}</a> `; grid.appendChild(row); }); card.appendChild(title); card.appendChild(grid); overlay.appendChild(card); document.body.appendChild(overlay); const closeBtn = card.querySelector(".giat-missed-close-btn"); const closePanel = () => { overlay.classList.remove("show"); setTimeout(() => overlay.remove(), 250); }; closeBtn.onclick = closePanel; overlay.onclick = (e) => { if (e.target === overlay) closePanel(); }; requestAnimationFrame(() => overlay.classList.add("show")); } function withTimeout(promise, timeoutMs, errorMessage) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error(errorMessage)); }, timeoutMs); promise.then((res) => { clearTimeout(timer); resolve(res); }, (err) => { clearTimeout(timer); reject(err); }); }); } function parseReasonFromError(err) { if (!err) return "HTTP_403"; const msg = err?.message || String(err); if (msg.includes("CLOUDFLARE")) return "CLOUDFLARE"; if (msg.includes("HOTLINK")) return "HOTLINK"; if (msg.includes("HTTP_404") || msg.includes("404")) return "HTTP_404"; if (msg.includes("TIMEOUT") || msg.includes("timeout")) return "TIMEOUT"; if (msg.includes("UNSUPPORTED_DOMAIN")) return "UNSUPPORTED_DOMAIN"; return "HTTP_403"; } async function runConcurrentPool(concurrencyLimit, items, taskFn) { let index = 0; const workers = []; const runner = async (workerId) => { while (index < items.length) { const currentIndex = index++; const item = items[currentIndex]; try { await taskFn(item, currentIndex); } catch (e) { console.warn(`[BatchDownloader] Task ${currentIndex} failed:`, e); } } }; const activeWorkerCount = Math.min(concurrencyLimit, items.length); for (let i = 0; i < activeWorkerCount; i++) workers.push(runner(i + 1)); await Promise.all(workers); } function sanitizeFilename(name) { return name.replace(/[\\/:*?"<>|]/g, "_").trim() || "image"; } function getFilenameFromUrl(url, index, extFallback = "jpg") { try { const pathname = new URL(url).pathname; let filename = pathname.substring(pathname.lastIndexOf("/") + 1).split("?")[0]; if (!filename || filename.length < 3) filename = `image_${String(index + 1).padStart(2, "0")}.${extFallback}`; else if (!filename.includes(".")) filename = `${filename}.${extFallback}`; return sanitizeFilename(filename); } catch (e) { return `image_${String(index + 1).padStart(2, "0")}.${extFallback}`; } } async function downloadAndExportZip(items) { if (config.batchDownloadMode === "direct") await downloadDirectImages(items); else await downloadZipArchive(items); } async function downloadZipArchive(items) { if (!items || items.length === 0) return; const total = items.length; let completed = 0; let failed = 0; const missedItems = []; showToast(`${t("toastDownloading")} ZIP (0/${total})`, 0); const zipFiles = []; const filenameMap = new Map(); const processItem = async (item, index) => { let blob = null; let finalUrl = item.url; if (isUnsupportedDomain(item.url)) { failed++; completed++; missedItems.push({ url: item.url, reason: "UNSUPPORTED_DOMAIN" }); showToast(`${t("toastDownloading")} ZIP (${completed}/${total})`, 0); return; } let capturedReason = "HTTP_403"; try { const res = await withTimeout(fetchImageBlobWithFallback(item.url, item.rawUrl), 12e3, `Primary fetch timeout for ${item.url}`); blob = res.blob; if (res.finalUrl) finalUrl = res.finalUrl; } catch (err) { capturedReason = parseReasonFromError(err); try { blob = await withTimeout(fetchCleanBlob(item.url), 4e3, `CORS Rescue timeout for ${item.url}`); } catch (rescueErr) { const rescueReason = parseReasonFromError(rescueErr); if (capturedReason === "HTTP_403" && rescueReason !== "HTTP_403") capturedReason = rescueReason; console.warn(`[BatchDownloader] High-res fetch failed for item ${index}: ${item.url}`, err, rescueErr); } } if (!blob || blob.size === 0) { failed++; missedItems.push({ url: item.url, reason: capturedReason }); } if (blob && blob.size > 0) try { let ext = "jpg"; if (blob.type.includes("png")) ext = "png"; else if (blob.type.includes("webp")) ext = "webp"; else if (blob.type.includes("gif")) ext = "gif"; let rawBase = item.filename || getFilenameFromUrl(finalUrl, index, ext); let baseName = `${formatDownloadFilename({ originalName: rawBase, query: getCurrentSearchQuery(), domain: item.domain || extractDomain(finalUrl), title: item.title || rawBase, width: item.width, height: item.height, rank: item.rank || index + 1, index: index + 1 })}.${ext}`; if (filenameMap.has(baseName)) { const count = filenameMap.get(baseName) + 1; filenameMap.set(baseName, count); const lastDot = baseName.lastIndexOf("."); if (lastDot !== -1) baseName = `${baseName.substring(0, lastDot)} (${count})${baseName.substring(lastDot)}`; else baseName = `${baseName} (${count})`; } else filenameMap.set(baseName, 1); zipFiles.push({ name: baseName, blob }); completed++; } catch (bufErr) { failed++; missedItems.push({ url: item.url, reason: "HTTP_403" }); } else { failed++; missedItems.push({ url: item.url, reason: "HTTP_403" }); } const currentProcessed = completed + failed; const percent = Math.round(currentProcessed / total * 100); showToast(`${t("toastDownloading")} ZIP (${currentProcessed}/${total}) ${percent}%`, 0); }; try { const dynamicTimeoutMs = Math.max(25e3, items.length * 4e3); await Promise.race([runConcurrentPool(3, items, processItem), new Promise((r) => setTimeout(r, dynamicTimeoutMs))]); if (completed === 0) { hideToast(); showToast(t("toastDownloadFail"), 2e3); if (missedItems.length > 0) showMissedItemsPanel(missedItems); return; } showToast(t("toastZipping").replace("{completed}", String(completed)).replace("{total}", String(total)), 0); await new Promise((r) => setTimeout(r, 50)); const zipParts = await createZipPartsAsync(zipFiles); const zipBlob = new Blob(zipParts, { type: "application/zip" }); const zipFilename = `GIAT_Batch_${new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)}.zip`; const managedZipUrl = blobManager.createManagedUrl(zipBlob, "zip-export", 6e4); hideToast(); triggerBlobDownload(managedZipUrl, zipFilename); blobManager.revokeOnDownload(managedZipUrl, 1e4); if (missedItems.length > 0) { showToast(`ZIP (${completed}/${total}) [${t("viewMissedDetails")}]`, 4e3); showMissedItemsPanel(missedItems); } else showToast(t("toastZipDownloaded").replace("{count}", String(completed)), 3e3); } catch (err) { console.error("[BatchDownloader] Export Error:", err); hideToast(); showToast(t("toastDownloadFail"), 2e3); } } async function downloadDirectImages(items) { if (!items || items.length === 0) return; const total = items.length; let completed = 0; let failed = 0; const missedItems = []; const filenameMap = new Map(); showToast(`${t("toastDownloading")} (0/${total})`, 0); const dispatchQueue = []; let isDispatching = false; const processDispatchQueue = async () => { if (isDispatching) return; isDispatching = true; while (dispatchQueue.length > 0) { const task = dispatchQueue.shift(); try { if (task.blob && task.blob.size > 0) { const blobUrl = blobManager.createManagedUrl(task.blob, "direct-export", 15e3); triggerBlobDownload(blobUrl, task.filename); blobManager.revokeOnDownload(blobUrl, 3e3); } else if (task.url && typeof GM_download !== "undefined") GM_download({ url: task.url, name: task.filename, headers: { "Referer": task.rawUrl || task.url } }); } catch (err) { console.warn("[BatchDownloader] Paced dispatch error:", err); } if (dispatchQueue.length > 0) await new Promise((r) => setTimeout(r, 150)); } isDispatching = false; }; const enqueueDispatch = (task) => { dispatchQueue.push(task); processDispatchQueue(); }; const processItem = async (item, index) => { if (isUnsupportedDomain(item.url)) { failed++; completed++; missedItems.push({ url: item.url, reason: "UNSUPPORTED_DOMAIN" }); showToast(`${t("toastDownloading")} (${completed}/${total})`, 0); return; } let blob = null; let finalUrl = item.url; let capturedReason = "HTTP_403"; try { const res = await withTimeout(fetchImageBlobWithFallback(item.url, item.rawUrl), 12e3, `Primary fetch timeout for ${item.url}`); blob = res.blob; if (res.finalUrl) finalUrl = res.finalUrl; } catch (err) { capturedReason = parseReasonFromError(err); try { blob = await withTimeout(fetchCleanBlob(item.url), 4e3, `CORS Rescue timeout for ${item.url}`); } catch (rescueErr) { const rescueReason = parseReasonFromError(rescueErr); if (capturedReason === "HTTP_403" && rescueReason !== "HTTP_403") capturedReason = rescueReason; console.warn(`[BatchDownloader] High-res fetch failed for item ${index}: ${item.url}`, err, rescueErr); } } if (!blob || blob.size === 0) { failed++; missedItems.push({ url: item.url, reason: capturedReason }); } if (blob && blob.size > 0) { let ext = "jpg"; if (blob.type.includes("png")) ext = "png"; else if (blob.type.includes("webp")) ext = "webp"; else if (blob.type.includes("gif")) ext = "gif"; let rawBase = item.filename || getFilenameFromUrl(finalUrl, index, ext); let baseName = `${formatDownloadFilename({ originalName: rawBase, query: getCurrentSearchQuery(), domain: item.domain || extractDomain(finalUrl), title: item.title || rawBase, width: item.width, height: item.height, rank: item.rank || index + 1, index: index + 1 })}.${ext}`; if (filenameMap.has(baseName)) { const count = filenameMap.get(baseName) + 1; filenameMap.set(baseName, count); const lastDot = baseName.lastIndexOf("."); if (lastDot !== -1) baseName = `${baseName.substring(0, lastDot)} (${count})${baseName.substring(lastDot)}`; else baseName = `${baseName} (${count})`; } else filenameMap.set(baseName, 1); enqueueDispatch({ blob, filename: baseName, rawUrl: item.rawUrl || finalUrl }); completed++; } else { failed++; missedItems.push({ url: item.url, reason: "HTTP_403" }); } const currentProcessed = completed + failed; const percent = Math.round(currentProcessed / total * 100); showToast(`${t("toastDownloading")} (${currentProcessed}/${total}) ${percent}%`, 0); }; try { const dynamicTimeoutMs = Math.max(25e3, items.length * 4e3); await Promise.race([runConcurrentPool(3, items, processItem), new Promise((r) => setTimeout(r, dynamicTimeoutMs))]); while (dispatchQueue.length > 0 || isDispatching) await new Promise((r) => setTimeout(r, 50)); hideToast(); if (completed === 0) { showToast(t("toastDownloadFail"), 2e3); if (missedItems.length > 0) showMissedItemsPanel(missedItems); return; } if (missedItems.length > 0) { showToast(`(${completed}/${total}) [${t("viewMissedDetails")}]`, 4e3); showMissedItemsPanel(missedItems); } else showToast(t("toastFilesDownloaded").replace("{count}", String(completed)), 3e3); } catch (err) { console.error("[BatchDownloader] Direct Export Error:", err); hideToast(); showToast(t("toastDownloadFail"), 2e3); } } function extractExportableItems(selectedElements) { const items = []; selectedElements.forEach((el, i) => { const imageUrl = el.dataset.giatRawOriginalUrl || el.dataset.giatImgurl || ""; if (!imageUrl) return; const serpRankStr = el.dataset.giatSerpRank; const serpRank = serpRankStr ? parseInt(serpRankStr, 10) : i + 1; let title = el.dataset.giatTitle || ""; if (!title) { const imgEl = el.querySelector("img"); title = imgEl?.alt || imgEl?.title || ""; } if (!title) title = el.dataset.giatSourceUrl ? new URL(el.dataset.giatSourceUrl).hostname : `Image_${i + 1}`; title = title.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim(); const sourceUrl = el.dataset.giatSourceUrl || imageUrl; let domain = ""; try { if (sourceUrl && sourceUrl.startsWith("http")) domain = new URL(sourceUrl).hostname.replace(/^www\./, ""); } catch (e) { domain = ""; } const width = el.dataset.giatWidth || ""; const height = el.dataset.giatHeight || ""; const fileSize = el.dataset.giatFileSize || ""; const mimeType = el.dataset.giatMimeType || ""; let aspectRatio = ""; if (width && height) { const w = parseInt(width, 10); const h = parseInt(height, 10); if (!isNaN(w) && !isNaN(h) && h > 0) aspectRatio = `${(w / h).toFixed(2)}:1`; } items.push({ index: i + 1, serpRank, title: title.trim(), domain, imageUrl, sourceUrl, width, height, aspectRatio, fileSize, mimeType }); }); return items; } function generateMarkdownList(selectedElements) { const items = extractExportableItems(selectedElements); if (items.length === 0) return ""; const timestamp = new Date().toLocaleString(); let md = `# Google Images Batch Export\n`; md += `*Exported on: ${timestamp} | Total: ${items.length} Images*\n\n`; md += `---\n\n`; items.forEach((item) => { md += `### #${item.serpRank}. ${item.title}\n`; md += `![${item.title.replace(/[\[\]]/g, "")}](${item.imageUrl})\n`; md += `- **SERP Rank**: Position #${item.serpRank}\n`; md += `- **Original Image**: [Direct Link](${item.imageUrl})\n`; md += `- **Source Page**: [Webpage Link](${item.sourceUrl})\n`; const details = []; if (item.width && item.height) details.push(`${item.width} × ${item.height} px`); if (item.aspectRatio) details.push(`Ratio: ${item.aspectRatio}`); if (item.fileSize) details.push(item.fileSize); if (item.mimeType) details.push(item.mimeType.toUpperCase()); if (details.length > 0) md += `- **Details**: ${details.join(" | ")}\n`; md += `\n---\n\n`; }); md += `*Generated by Google Images Advanced Toolbox (GIAT)*\n`; return md; } function generateJsonData(selectedElements) { const items = extractExportableItems(selectedElements); return JSON.stringify(items, null, 2); } function escapeCsvField(field) { if (field === void 0 || field === null) return "\"\""; return `"${String(field).replace(/"/g, "\"\"")}"`; } function generateCsvData(selectedElements) { const items = extractExportableItems(selectedElements); if (items.length === 0) return ""; const headers = [ "Index", "SERP Rank", "Title", "Domain", "Image URL", "Source Page URL", "Width", "Height", "Aspect Ratio", "File Size", "Format" ]; const rows = items.map((item) => [ escapeCsvField(item.index), escapeCsvField(item.serpRank), escapeCsvField(item.title), escapeCsvField(item.domain), escapeCsvField(item.imageUrl), escapeCsvField(item.sourceUrl), escapeCsvField(item.width), escapeCsvField(item.height), escapeCsvField(item.aspectRatio), escapeCsvField(item.fileSize), escapeCsvField(item.mimeType?.toUpperCase()) ].join(",")); return "" + [headers.join(","), ...rows].join("\n"); } function generatePlainUrlList(selectedElements) { return extractExportableItems(selectedElements).map((item) => item.imageUrl).join("\n"); } var isBatchModeActive = false; var selectedElements = new Set(); var lastSelectedElement = null; var batchBarEl = null; var batchTriggerBtnEl = null; function isUnsupportedDomain(urlStr) { if (!urlStr) return false; try { const hn = new URL(urlStr).hostname.toLowerCase(); return hn.includes("lookaside") || hn.includes("tiktok.com"); } catch (e) { return false; } } var svgZip = `<svg class="giat-btn-svg" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path fill="currentColor" d="M11.288 10.288Q11 10.575 11 11v3.2l-.9-.9q-.275-.275-.7-.275t-.7.275t-.275.7t.275.7l2.6 2.6q.3.3.7.3t.7-.3l2.6-2.6q.275-.275.275-.7t-.275-.7t-.7-.275t-.7.275l-.9.9V11q0-.425-.288-.712T12 10t-.712.288M5 8v11h14V8zm0 13q-.825 0-1.412-.587T3 19V6.525q0-.35.113-.675t.337-.6L4.7 3.725q.275-.35.687-.538T6.25 3h11.5q.45 0 .863.188t.687.537l1.25 1.525q.225.275.338.6t.112.675V19q0 .825-.587 1.413T19 21zm.4-15h13.2l-.85-1H6.25zm6.6 7.5"/></svg>`; var svgDownload = `<svg class="giat-btn-svg" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path fill="currentColor" d="M11.625 15.513q-.175-.063-.325-.213l-3.6-3.6q-.3-.3-.288-.7t.288-.7q.3-.3.713-.312t.712.287L11 12.15V5q0-.425.288-.712T12 4t.713.288T13 5v7.15l1.875-1.875q.3-.3.713-.288t.712.313q.275.3.288.7t-.288.7l-3.6 3.6q-.15.15-.325.213t-.375.062t-.375-.062M6 20q-.825 0-1.412-.587T4 18v-2q0-.425.288-.712T5 15t.713.288T6 16v2h12v-2q0-.425.288-.712T19 15t.713.288T20 16v2q0 .825-.587 1.413T18 20z"/></svg>`; var svgExportData = `<svg class="giat-btn-svg" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path fill="currentColor" d="m8 19.425l-2.25 2.25q-.3.3-.7.288t-.7-.313q-.275-.3-.287-.7t.287-.7L6.6 18H5.35q-.425 0-.712-.287T4.35 17t.288-.712T5.35 16H9q.425 0 .713.288T10 17v3.65q0 .425-.288.713T9 21.65t-.712-.287T8 20.65zm-3.712-5.712Q4 13.425 4 13V4q0-.825.588-1.412T6 2h8l6 6v12q0 .825-.587 1.413T18 22h-5q-.425 0-.712-.288T12 21t.288-.712T13 20h5V9h-4q-.425 0-.712-.288T13 8V4H6v9q0 .425-.288.713T5 14t-.712-.288"/></svg>`; var svgCopy = `<svg class="giat-btn-svg" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M15 1.25h-4.056c-1.838 0-3.294 0-4.433.153c-1.172.158-2.121.49-2.87 1.238c-.748.749-1.08 1.698-1.238 2.87c-.153 1.14-.153 2.595-.153 4.433V16a3.75 3.75 0 0 0 3.166 3.705c.137.764.402 1.416.932 1.947c.602.602 1.36.86 2.26.982c.867.116 1.97.116 3.337.116h3.11c1.367 0 2.47 0 3.337-.116c.9-.122 1.658-.38 2.26-.982s.86-1.36.982-2.26c.116-.867.116-1.97.116-3.337v-5.11c0-1.367 0-2.47-.116-3.337c-.122-.9-.38-1.658-.982-2.26c-.531-.53-1.183-.795-1.947-.932A3.75 3.75 0 0 0 15 1.25m2.13 3.021A2.25 2.25 0 0 0 15 2.75h-4c-1.907 0-3.261.002-4.29.14c-1.005.135-1.585.389-2.008.812S4.025 4.705 3.89 5.71c-.138 1.029-.14 2.383-.14 4.29v6a2.25 2.25 0 0 0 1.521 2.13c-.021-.61-.021-1.3-.021-2.075v-5.11c0-1.367 0-2.47.117-3.337c.12-.9.38-1.658.981-2.26c.602-.602 1.36-.86 2.26-.981c.867-.117 1.97-.117 3.337-.117h3.11c.775 0 1.464 0 2.074.021M7.408 6.41c.277-.277.665-.457 1.4-.556c.754-.101 1.756-.103 3.191-.103h3c1.435 0 2.436.002 3.192.103c.734.099 1.122.28 1.399.556c.277.277.457.665.556 1.4c.101.754.103 1.756.103 3.191v5c0 1.435-.002 2.436-.103 3.192c-.099.734-.28 1.122-.556 1.399c-.277.277-.665.457-1.4.556c-.755.101-1.756.103-3.191.103h-3c-1.435 0-2.437-.002-3.192-.103c-.734-.099-1.122-.28-1.399-.556c-.277-.277-.457-.665-.556-1.4c-.101-.755-.103-1.756-.103-3.191v-5c0-1.435.002-2.437.103-3.192c.099-.734.28-1.122.556-1.399" clip-rule="evenodd"/></svg>`; var svgLink = `<svg class="giat-btn-svg" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"><path fill="currentColor" d="M7 17q-2.075 0-3.537-1.463T2 12t1.463-3.537T7 7h3q.425 0 .713.288T11 8t-.288.713T10 9H7q-1.25 0-2.125.875T4 12t.875 2.125T7 15h3q.425 0 .713.288T11 16t-.288.713T10 17zm2-4q-.425 0-.712-.288T8 12t.288-.712T9 11h6q.425 0 .713.288T16 12t-.288.713T15 13zm5 4q-.425 0-.712-.288T13 16t.288-.712T14 15h3q1.25 0 2.125-.875T20 12t-.875-2.125T17 9h-3q-.425 0-.712-.288T13 8t.288-.712T14 7h3q2.075 0 3.538 1.463T22 12t-1.463 3.538T17 17z"/></svg>`; function parseFileSizeToBytes(sizeStr) { if (!sizeStr) return 0; const match = sizeStr.trim().match(/^([\d.]+)\s*([KMG]?B|Bytes?)$/i); if (!match) return 0; const num = parseFloat(match[1]); const unit = match[2].toUpperCase(); if (unit.startsWith("G")) return Math.round(num * 1024 * 1024 * 1024); if (unit.startsWith("M")) return Math.round(num * 1024 * 1024); if (unit.startsWith("K")) return Math.round(num * 1024); return Math.round(num); } function formatBytes(bytes) { if (bytes === 0) return "0 B"; const k = 1024; const sizes = [ "B", "KB", "MB", "GB" ]; const i = Math.floor(Math.log(bytes) / Math.log(k)); const val = bytes / Math.pow(k, i); return `${val >= 10 || i === 0 ? val.toFixed(0) : val.toFixed(1)} ${sizes[i]}`; } function updateBatchBarUI() { if (!batchBarEl) { batchBarEl = document.createElement("div"); batchBarEl.classList.add("giat-batch-bar"); (document.body || document.documentElement).appendChild(batchBarEl); } const isDark = config.uiTheme === "auto" ? isPageDark() : config.uiTheme === "dark"; batchBarEl.classList.toggle("giat-theme-dark", isDark); batchBarEl.classList.toggle("giat-theme-light", !isDark); const count = selectedElements.size; let totalBytes = 0; let knownCount = 0; selectedElements.forEach((el) => { const sizeStr = el.dataset.giatFileSize; if (sizeStr) { const bytes = parseFileSizeToBytes(sizeStr); if (bytes > 0) { totalBytes += bytes; knownCount++; } } }); let countText = t("batchSelectedCount").replace("{count}", String(count)); if (count > 0 && totalBytes > 0) { const formattedSize = formatBytes(totalBytes); countText = t(knownCount < count ? "batchSelectedCountWithSizeApprox" : "batchSelectedCountWithSize").replace("{count}", String(count)).replace("{size}", formattedSize); } const exportIcon = config.batchDownloadMode === "direct" ? svgDownload : svgZip; const exportLabelText = config.batchDownloadMode === "direct" ? t("exportDirectBtn") : t("exportZipBtn"); batchBarEl.innerHTML = ` <div class="giat-batch-info"> <span class="giat-batch-count">${countText}</span> </div> <div class="giat-batch-actions"> <button class="giat-batch-btn giat-batch-btn-select-all">${t("selectAll")}</button> <button class="giat-batch-btn giat-batch-btn-clear">${t("clearSelection")}</button> <button class="giat-batch-btn giat-batch-btn-export" ${count === 0 ? "disabled" : ""}>${exportIcon}<span>${exportLabelText}</span></button> <button class="giat-batch-btn giat-batch-btn-data" ${count === 0 ? "disabled" : ""}>${svgExportData}<span>${t("exportDataBtn")}</span></button> <button class="giat-batch-btn giat-batch-btn-exit">✕ ${t("exitBatchMode")}</button> </div> `; const btnSelectAll = batchBarEl.querySelector(".giat-batch-btn-select-all"); const btnClear = batchBarEl.querySelector(".giat-batch-btn-clear"); const btnExport = batchBarEl.querySelector(".giat-batch-btn-export"); const btnData = batchBarEl.querySelector(".giat-batch-btn-data"); const btnExit = batchBarEl.querySelector(".giat-batch-btn-exit"); btnSelectAll.onclick = () => selectAllResults(); btnClear.onclick = () => clearAllSelection(); btnExit.onclick = () => exitBatchMode(); btnExport.onclick = () => triggerBatchZipExport(); btnData.onclick = (e) => showDataExportMenu(e, btnData); batchBarEl.offsetHeight; batchBarEl.classList.add("show"); } function toggleBatchMode() { if (!config.enableBatchSelect) return; if (isBatchModeActive) exitBatchMode(); else enterBatchMode(); } function enterBatchMode() { if (isBatchModeActive) return; isBatchModeActive = true; document.body.classList.add("giat-batch-active"); attachCheckboxesToAllResults(); updateBatchBarUI(); } function exitBatchMode() { if (!isBatchModeActive) return; isBatchModeActive = false; document.body.classList.remove("giat-batch-active"); lastSelectedElement = null; selectedElements.forEach((el) => { el.classList.remove("giat-selected"); const cb = el.querySelector(".giat-thumb-checkbox"); if (cb) cb.checked = false; }); selectedElements.clear(); if (batchBarEl) { batchBarEl.classList.remove("show"); setTimeout(() => { if (batchBarEl) { batchBarEl.remove(); batchBarEl = null; } }, 250); } } function selectAllResults() { document.querySelectorAll("div[data-giat-result]").forEach((res) => { const htmlEl = res; if (isInsideSidePanel(htmlEl)) return; selectedElements.add(htmlEl); htmlEl.classList.add("giat-selected"); const cb = htmlEl.querySelector(".giat-thumb-checkbox"); if (cb) cb.checked = true; }); updateBatchBarUI(); } function clearAllSelection() { lastSelectedElement = null; selectedElements.forEach((el) => { el.classList.remove("giat-selected"); const cb = el.querySelector(".giat-thumb-checkbox"); if (cb) cb.checked = false; }); selectedElements.clear(); updateBatchBarUI(); } function isInsideSidePanel(el) { if (el.closest("#sZmt3b, .OLKT8d, [role=\"dialog\"], [data-async-type=\"imgv\"], #islsp, [role=\"complementary\"], .TVH9nc, .Q4Lg2c, c-wiz[data-feature-id], c-wiz[data-node-index]")) return true; if (el.closest("div[aria-label*=\"相關\"], div[aria-label*=\"Related\"], div[data-ved][data-h]") && !el.hasAttribute("data-ri")) return true; if (!el.closest("#islrg, #rso, div[data-async-context]")) return true; return false; } function getThumbContainer(el) { const img = el.querySelector("img"); if (img && img.parentElement && img.parentElement !== el) { const parent = img.parentElement; if (window.getComputedStyle(parent).position === "static") parent.style.position = "relative"; return parent; } return el; } function handleSelectionClick(el, targetChecked, isShiftKey) { if (isShiftKey && lastSelectedElement && lastSelectedElement !== el && document.body.contains(lastSelectedElement)) { const mainSerpResults = Array.from(document.querySelectorAll("div[data-giat-result]")).filter((item) => !isInsideSidePanel(item)); const startIdx = mainSerpResults.indexOf(lastSelectedElement); const endIdx = mainSerpResults.indexOf(el); if (startIdx !== -1 && endIdx !== -1) { const from = Math.min(startIdx, endIdx); const to = Math.max(startIdx, endIdx); for (let i = from; i <= to; i++) { const item = mainSerpResults[i]; if (targetChecked) { selectedElements.add(item); item.classList.add("giat-selected"); } else { selectedElements.delete(item); item.classList.remove("giat-selected"); } const cb = item.querySelector(".giat-thumb-checkbox"); if (cb) cb.checked = targetChecked; } updateBatchBarUI(); lastSelectedElement = el; return; } } toggleElementSelection(el, targetChecked); lastSelectedElement = el; } function attachCheckboxesToAllResults(specificElements) { if (!config.enableBatchSelect) return; Array.from(document.querySelectorAll("div[data-giat-result]")).filter((el) => !isInsideSidePanel(el)).forEach((el, idx) => { el.dataset.giatSerpRank = String(idx + 1); }); (specificElements || Array.from(document.querySelectorAll("div[data-giat-result]"))).forEach((el) => { const isSidePanelItem = isInsideSidePanel(el); const url = el.dataset.giatImgurl || el.dataset.giatRawOriginalUrl; const thumbContainer = getThumbContainer(el); if (!isSidePanelItem && el.dataset.giatSerpRank && !el.querySelector(".giat-serp-rank-badge")) { const rankBadge = document.createElement("span"); rankBadge.classList.add("giat-serp-rank-badge"); if (isUnsupportedDomain(url)) { const svgAlertLine = `<svg class="giat-rank-warning-svg" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.8" d="M12 16h.008M12 10v3m-1.425-7.783L3.517 17a1.667 1.667 0 0 0 1.425 2.5h14.116a1.666 1.666 0 0 0 1.425-2.5L13.426 5.217a1.666 1.666 0 0 0-2.85 0"/></svg>`; rankBadge.classList.add("giat-rank-badge-warning"); rankBadge.innerHTML = `${svgAlertLine}#${el.dataset.giatSerpRank}`; rankBadge.title = t("unsupportedDomainTooltip"); rankBadge.onclick = (e) => { e.stopPropagation(); }; } else rankBadge.textContent = `#${el.dataset.giatSerpRank}`; thumbContainer.appendChild(rankBadge); } if (!isSidePanelItem && !el.querySelector(".giat-thumb-checkbox-wrap")) { const cbWrap = document.createElement("div"); cbWrap.classList.add("giat-thumb-checkbox-wrap"); const cb = document.createElement("input"); cb.type = "checkbox"; cb.classList.add("giat-thumb-checkbox"); cb.onclick = (e) => { e.stopPropagation(); handleSelectionClick(el, cb.checked, e.shiftKey); }; cbWrap.appendChild(cb); thumbContainer.appendChild(cbWrap); } if (!isSidePanelItem && !el.dataset.giatBatchListenerAttached) { el.dataset.giatBatchListenerAttached = "true"; el.addEventListener("click", (e) => { if (!isBatchModeActive) return; if (e.target.closest(".giat-thumb-checkbox-wrap")) return; e.stopPropagation(); e.preventDefault(); const cb = el.querySelector(".giat-thumb-checkbox"); const targetChecked = !selectedElements.has(el); if (cb) cb.checked = targetChecked; handleSelectionClick(el, targetChecked, e.shiftKey); }, true); } }); } function toggleElementSelection(el, select) { if (select) { selectedElements.add(el); el.classList.add("giat-selected"); } else { selectedElements.delete(el); el.classList.remove("giat-selected"); } updateBatchBarUI(); } async function triggerBatchZipExport() { if (selectedElements.size === 0) return; const items = []; const seenUrls = new Set(); selectedElements.forEach((el) => { const url = el.dataset.giatImgurl || el.dataset.giatRawOriginalUrl; const rawUrl = el.dataset.giatRawOriginalUrl; if (url && !seenUrls.has(url)) { seenUrls.add(url); const img = el.querySelector("img"); items.push({ url, rawUrl, title: el.dataset.giatTitle || img && img.alt || void 0, domain: el.dataset.giatDomain || void 0, width: el.dataset.giatNaturalWidth || img && img.naturalWidth || void 0, height: el.dataset.giatNaturalHeight || img && img.naturalHeight || void 0, rank: el.dataset.giatSerpRank || void 0 }); } }); if (items.length === 0) { showToast(t("toastDownloadFail")); return; } await downloadAndExportZip(items); } function injectBatchTriggerButton() { if (!config.enableBatchSelect || document.querySelector(".giat-batch-trigger-btn")) return; const isDark = config.uiTheme === "auto" ? isPageDark() : config.uiTheme === "dark"; batchTriggerBtnEl = document.createElement("button"); batchTriggerBtnEl.classList.add("giat-batch-trigger-btn"); batchTriggerBtnEl.classList.toggle("giat-theme-dark", isDark); batchTriggerBtnEl.classList.toggle("giat-theme-light", !isDark); batchTriggerBtnEl.title = t("enableBatchSelect"); batchTriggerBtnEl.innerHTML = ` <svg class="giat-batch-icon-svg" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"> <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"> <path d="M3 9v10.4c0 .56 0 .84.109 1.054a1 1 0 0 0 .437.437C3.76 21 4.04 21 4.598 21H15m-8-7.2V6.2c0-1.12 0-1.68.218-2.108c.192-.377.497-.682.874-.874C8.52 3 9.08 3 10.2 3h7.6c1.12 0 1.68 0 2.108.218a2 2 0 0 1 .874.874C21 4.52 21 5.08 21 6.2v7.6c0 1.12 0 1.68-.218 2.108a2 2 0 0 1-.874.874c-.428.218-.986.218-2.104.218h-7.607c-1.118 0-1.678 0-2.105-.218a2 2 0 0 1-.874-.874C7 15.48 7 14.92 7 13.8"/> <path d="m11.6 10.323l1.379 1.575a.3.3 0 0 0 .466-.022L16.245 8"/> </g> </svg> <span class="giat-batch-trigger-text">${t("batchSelectBtn")}</span> `; batchTriggerBtnEl.onclick = () => toggleBatchMode(); (document.body || document.documentElement).appendChild(batchTriggerBtnEl); } function showDataExportMenu(e, targetBtn) { e.stopPropagation(); const existingMenu = document.querySelector(".giat-batch-md-menu"); if (existingMenu) { existingMenu.remove(); return; } const selectedList = Array.from(selectedElements); if (selectedList.length === 0) return; const menu = document.createElement("div"); menu.classList.add("giat-batch-md-menu"); const isDark = config.uiTheme === "auto" ? isPageDark() : config.uiTheme === "dark"; menu.classList.toggle("giat-theme-dark", isDark); menu.classList.toggle("giat-theme-light", !isDark); const rect = targetBtn.getBoundingClientRect(); const menuHeight = 280; const menuWidth = 260; let top = rect.top - menuHeight - 8; if (top < 12) top = rect.bottom + 8; let left = rect.left; if (left + menuWidth > window.innerWidth - 16) left = window.innerWidth - menuWidth - 16; if (left < 16) left = 16; menu.style.top = `${top}px`; menu.style.left = `${left}px`; menu.innerHTML = ` <button class="giat-batch-md-item giat-md-copy">${svgCopy}<span>${t("copyMarkdownMenu")}</span></button> <button class="giat-batch-md-item giat-md-download">${svgDownload}<span>${t("downloadMarkdownMenu")}</span></button> <div class="giat-batch-md-divider"></div> <button class="giat-batch-md-item giat-json-copy">${svgCopy}<span>${t("copyJsonMenu")}</span></button> <button class="giat-batch-md-item giat-json-download">${svgDownload}<span>${t("downloadJsonMenu")}</span></button> <div class="giat-batch-md-divider"></div> <button class="giat-batch-md-item giat-csv-copy">${svgCopy}<span>${t("copyCsvMenu")}</span></button> <button class="giat-batch-md-item giat-csv-download">${svgDownload}<span>${t("downloadCsvMenu")}</span></button> <div class="giat-batch-md-divider"></div> <button class="giat-batch-md-item giat-urls-copy">${svgLink}<span>${t("copyUrlsMenu")}</span></button> `; const closeMenu = () => { menu.remove(); document.removeEventListener("click", closeMenu); }; const timestamp = () => new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19); const safeCopyText = (text, successToastMsg) => { if (typeof GM_setClipboard !== "undefined") try { GM_setClipboard(text); showToast(successToastMsg, 2500); return; } catch (e) {} if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(() => { showToast(successToastMsg, 2500); }).catch(() => { const textarea = document.createElement("textarea"); textarea.value = text; textarea.style.position = "fixed"; textarea.style.opacity = "0"; document.body.appendChild(textarea); textarea.select(); try { document.execCommand("copy"); showToast(successToastMsg, 2500); } catch (err) { showToast(t("toastCopyFail"), 2500); } finally { textarea.remove(); } }); }; menu.querySelector(".giat-md-copy")?.addEventListener("click", (evt) => { evt.stopPropagation(); const mdText = generateMarkdownList(selectedList); safeCopyText(mdText, t("copyMarkdownSuccess")); closeMenu(); }); menu.querySelector(".giat-md-download")?.addEventListener("click", (evt) => { evt.stopPropagation(); const mdText = generateMarkdownList(selectedList); const blob = new Blob([mdText], { type: "text/markdown;charset=utf-8" }); const blobUrl = URL.createObjectURL(blob); triggerBlobDownload(blobUrl, `GIAT_Export_${timestamp()}.md`); setTimeout(() => URL.revokeObjectURL(blobUrl), 1e4); closeMenu(); }); menu.querySelector(".giat-json-copy")?.addEventListener("click", (evt) => { evt.stopPropagation(); const jsonText = generateJsonData(selectedList); safeCopyText(jsonText, t("copyJsonSuccess")); closeMenu(); }); menu.querySelector(".giat-json-download")?.addEventListener("click", (evt) => { evt.stopPropagation(); const jsonText = generateJsonData(selectedList); const blob = new Blob([jsonText], { type: "application/json;charset=utf-8" }); const blobUrl = URL.createObjectURL(blob); triggerBlobDownload(blobUrl, `GIAT_Export_${timestamp()}.json`); setTimeout(() => URL.revokeObjectURL(blobUrl), 1e4); closeMenu(); }); menu.querySelector(".giat-csv-copy")?.addEventListener("click", (evt) => { evt.stopPropagation(); const csvText = generateCsvData(selectedList); safeCopyText(csvText, t("copyCsvSuccess")); closeMenu(); }); menu.querySelector(".giat-csv-download")?.addEventListener("click", (evt) => { evt.stopPropagation(); const csvText = generateCsvData(selectedList); const blob = new Blob([csvText], { type: "text/csv;charset=utf-8" }); const blobUrl = URL.createObjectURL(blob); triggerBlobDownload(blobUrl, `GIAT_Export_${timestamp()}.csv`); setTimeout(() => URL.revokeObjectURL(blobUrl), 1e4); closeMenu(); }); menu.querySelector(".giat-urls-copy")?.addEventListener("click", (evt) => { evt.stopPropagation(); const rawUrls = generatePlainUrlList(selectedList); safeCopyText(rawUrls, t("copyRawUrlsSuccess")); closeMenu(); }); document.body.appendChild(menu); setTimeout(() => { document.addEventListener("click", closeMenu); }, 50); } var GOOGLE_SELECTORS = { CENTER_COL: "center_col", RESULT_ITEM_STANDARD: "div[data-attrid=\"images universal\"][jsdata]", RESULT_ITEM_LENS: "div[data-snf][data-snm]", IMGRES_LINK: "a[href*=\"/imgres\"]", SOURCE_LINK: "a.LBcIee", BADGE_WRAPPER: ".wr8GYd", BADGE_TEXT_CONTAINER: ".GQDPdd", BADGE_EXCLUDED_SPAN_CLASS: "S2Caaf", THUMB_JSNAME: "[jsname=\"PNoEC\"]", THUMB_CLASS: ".Q6A6Dc", JS_CONTROLLER: "__jscontroller", JSDATA_SELECTOR: "[jsdata]", AI_TEXTAREA: "textarea.ITIRGe", AI_CONTROLLER_READY: "[data-sfc-inited=\"2\"]", AI_SUBMIT_CONTAINER: ".UAbVe, .AgWCw", AI_SEND_BUTTON: "[data-xid=\"input-plate-send-button\"]", AI_BUTTON_EXCLUDED: ".esoFne", SIDEBAR_CONTAINER: "[jsname=\"ujKaBc\"], [role=\"dialog\"]" }; function createSVG$2(type, attrs = {}, children = []) { const el = document.createElementNS("http://www.w3.org/2000/svg", type); for (const [key, value] of Object.entries(attrs)) el.setAttribute(key, value); if (children.length > 0) el.append(...children); return el; } function createThumbnailButtons(htmlResult, imgurl, rawOriginalUrl) { const btnContainer = document.createElement("div"); btnContainer.classList.add("giat-thumb-btn-container"); const preventMiddleScroll = (e) => { e.stopPropagation(); if (e.button === 1) e.preventDefault(); }; const thumbDownloadBtn = document.createElement("button"); thumbDownloadBtn.classList.add("giat-thumb-download-btn"); thumbDownloadBtn.title = t("tipDownload"); thumbDownloadBtn.dataset.giatUrl = imgurl; thumbDownloadBtn.dataset.giatRawUrl = rawOriginalUrl; thumbDownloadBtn.append(createSVG$2("svg", { viewBox: "0 0 24 24" }, [createSVG$2("path", { d: "M5 20h14v-2H5v2zM19 9h-4V3H9v6H5l7 7 7-7z" })])); const handleThumbDownload = () => downloadImage(imgurl, thumbDownloadBtn, rawOriginalUrl, { title: htmlResult.dataset.giatTitle || htmlResult.querySelector("img")?.alt || void 0, domain: htmlResult.dataset.giatDomain || void 0, width: htmlResult.dataset.giatWidth, height: htmlResult.dataset.giatHeight, rank: htmlResult.dataset.giatSerpRank || void 0, index: htmlResult.dataset.giatSerpRank || void 0 }); thumbDownloadBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleThumbDownload(); }); thumbDownloadBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleThumbDownload(); } }); thumbDownloadBtn.onmousedown = preventMiddleScroll; btnContainer.append(thumbDownloadBtn); const thumbCopyBtn = document.createElement("button"); thumbCopyBtn.classList.add("giat-thumb-copy-btn"); thumbCopyBtn.title = t("tipCopy"); thumbCopyBtn.dataset.giatUrl = imgurl; thumbCopyBtn.dataset.giatRawUrl = rawOriginalUrl; thumbCopyBtn.append(createSVG$2("svg", { viewBox: "0 0 24 24" }, [createSVG$2("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" })])); const handleThumbCopyImg = () => copyImageToClipboard(imgurl, thumbCopyBtn, rawOriginalUrl); thumbCopyBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleThumbCopyImg(); }); thumbCopyBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleThumbCopyImg(); } }); thumbCopyBtn.onmousedown = preventMiddleScroll; btnContainer.append(thumbCopyBtn); const thumbB64Btn = document.createElement("button"); thumbB64Btn.classList.add("giat-thumb-b64-btn"); thumbB64Btn.title = t("tipB64"); thumbB64Btn.dataset.giatUrl = imgurl; thumbB64Btn.dataset.giatRawUrl = rawOriginalUrl; thumbB64Btn.append(createSVG$2("svg", { viewBox: "0 0 24 24" }, [createSVG$2("path", { d: "M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z" })])); const handleThumbCopyB64 = () => copyBase64ToClipboard(imgurl, thumbB64Btn, rawOriginalUrl); thumbB64Btn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleThumbCopyB64(); }); thumbB64Btn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleThumbCopyB64(); } }); thumbB64Btn.onmousedown = preventMiddleScroll; btnContainer.append(thumbB64Btn); const thumbLensBtn = document.createElement("button"); thumbLensBtn.classList.add("giat-thumb-lens-btn"); thumbLensBtn.title = t("tipLens"); thumbLensBtn.append(createSVG$2("svg", { viewBox: "0 0 24 24" }, [createSVG$2("path", { d: "M0 0h24v24H0z", fill: "none" }), createSVG$2("path", { d: "M21,9v4h-2V9c0-1.1-0.9-2-2-2H7C5.9,7,5,7.9,5,9v3H3V9c0-2.21,1.79-4,4-4h2l1-2h4l1,2h2C19.21,5,21,6.79,21,9z M12,21H7 c-2.21,0-4-1.79-4-4v-2h2v2c0,1.1,0.9,2,2,2h5V21z M18,16c1.1,0,2,0.9,2,2s-0.9,2-2,2s-2-0.9-2-2S16.9,16,18,16z M12,10 c1.66,0,3,1.34,3,3s-1.34,3-3,3s-3-1.34-3-3S10.34,10,12,10z" })])); const handleThumbLens = () => { openSearchUrlWithFallback(imgurl, rawOriginalUrl, (url) => "https://lens.google.com/uploadbyurl?url=" + encodeURIComponent(url), t("toastPreparingLens")); }; thumbLensBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleThumbLens(); }); thumbLensBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleThumbLens(); } }); thumbLensBtn.onmousedown = preventMiddleScroll; btnContainer.append(thumbLensBtn); const thumbTineyeBtn = document.createElement("button"); thumbTineyeBtn.classList.add("giat-thumb-tineye-btn"); thumbTineyeBtn.title = t("tipTineye"); thumbTineyeBtn.append(createSVG$2("svg", { viewBox: "0 0 24 24" }, [createSVG$2("path", { d: "M21 10.975V8a2 2 0 0 0-2-2h-6V4.688c.305-.274.5-.668.5-1.11a1.5 1.5 0 0 0-3 0c0 .442.195.836.5 1.11V6H5a2 2 0 0 0-2 2v2.998l-.072.005A.999.999 0 0 0 2 12v2a1 1 0 0 0 1 1v5a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5a1 1 0 0 0 1-1v-1.938a1.004 1.004 0 0 0-.072-.455c-.202-.488-.635-.605-.928-.632zM7 12c0-1.104.672-2 1.5-2s1.5.896 1.5 2-.672 2-1.5 2S7 13.104 7 12zm8.998 6c-1.001-.003-7.997 0-7.998 0v-2s7.001-.002 8.002 0l-.004 2zm-.498-4c-.828 0-1.5-.896-1.5-2s.672-2 1.5-2 1.5.896 1.5 2-.672 2-1.5 2z" })])); const handleThumbTineye = () => { openSearchUrlWithFallback(imgurl, rawOriginalUrl, (url) => "https://tineye.com/search?url=" + encodeURIComponent(url), t("toastPreparingTineye")); }; thumbTineyeBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleThumbTineye(); }); thumbTineyeBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleThumbTineye(); } }); thumbTineyeBtn.onmousedown = preventMiddleScroll; btnContainer.append(thumbTineyeBtn); btnContainer.append(thumbTineyeBtn); const thumbAiBtn = document.createElement("button"); thumbAiBtn.classList.add("giat-thumb-ai-btn"); thumbAiBtn.title = t("tipAi"); thumbAiBtn.append(createSVG$2("svg", { viewBox: "0 0 100 100", width: "100%", height: "100%" }, [createSVG$2("path", { class: "giat-ai-star", fill: "currentColor", d: "M 75 18 Q 78.6 32.4 93 36 Q 78.6 39.6 75 54 Q 71.4 39.6 57 36 Q 71.4 32.4 75 18 Z" }), createSVG$2("g", { stroke: "currentColor", "stroke-width": "8", fill: "none" }, [createSVG$2("path", { d: "M 67.78 55.39 A 26 26 0 1 1 51.95 27.98", "stroke-linecap": "butt" }), createSVG$2("line", { x1: "60.38", y1: "70.38", x2: "83.01", y2: "93.01", "stroke-linecap": "square" })])])); const getAiSearchUrl = (url, title, srcUrl) => { const finalTitle = title ? title.trim() : t("defaultTitleFallback"); const finalSrc = srcUrl ? srcUrl.trim() : url; const prompt = (config.aiSearchPrompt ? config.aiSearchPrompt.trim() : "") || t("defaultAiPrompt"); let queryText = prompt; const lowerPrompt = prompt.toLowerCase(); const hasImg = lowerPrompt.includes("{img}"); const hasTitle = lowerPrompt.includes("{title}"); const hasSrc = lowerPrompt.includes("{src}"); if (hasImg || hasTitle || hasSrc) { queryText = queryText.replace(/{img}/gi, url); queryText = queryText.replace(/{title}/gi, finalTitle); queryText = queryText.replace(/{src}/gi, finalSrc); } else queryText = `${prompt} ${url}`; return `https://www.google.com/search?q=${encodeURIComponent(queryText)}&udm=50`; }; const triggerAiSearch = () => { const titleEl = htmlResult.querySelector(GOOGLE_SELECTORS.THUMB_CLASS); const titleText = titleEl ? (titleEl.textContent || "").trim() : ""; const srcUrl = htmlResult.dataset.giatSourceUrl || ""; if (config.enableExperimentalAiUpload) triggerAiSearchWithUpload(imgurl, titleText, srcUrl, rawOriginalUrl); else openSearchUrlWithFallback(imgurl, rawOriginalUrl, (url) => getAiSearchUrl(url, titleText, srcUrl), t("toastPreparingAi")); }; thumbAiBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); triggerAiSearch(); }); thumbAiBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); triggerAiSearch(); } }); thumbAiBtn.onmousedown = preventMiddleScroll; btnContainer.append(thumbAiBtn); const thumbPhotopeaBtn = document.createElement("button"); thumbPhotopeaBtn.classList.add("giat-thumb-photopea-btn"); thumbPhotopeaBtn.title = t("tipPhotopea"); thumbPhotopeaBtn.append(createSVG$2("svg", { viewBox: "0 0 400 400" }, [createSVG$2("path", { style: "fill: #18a497", d: "M64.97,0h269.47c35.91,0 64.94,29.01 64.94,64.92v269.4c0,35.91 -29.03,64.92 -64.94,64.92h-228.05l-0.76,-172.02h-0.09c0,-0.41 0,-0.8 0,-1.22c0,-65.22 51.79,-117.93 115.86,-117.93c38.44,0 69.52,31.63 69.52,70.76c0,39.13 -31.08,70.76 -69.52,70.76c-12.8,0 -23.17,-10.55 -23.17,-23.59c0,-13.03 10.37,-23.59 23.17,-23.59c12.8,0 23.17,-10.55 23.17,-23.59c0,-13.03 -10.37,-23.59 -23.17,-23.59c-38.44,0 -69.52,31.63 -69.52,70.76c0,39.13 31.08,70.76 69.52,70.76c64.07,0 115.86,-52.71 115.86,-117.93c0,-65.22 -51.79,-117.93 -115.86,-117.93c-89.7,0 -162.23,73.79 -162.23,165.1c0,0.48 0,0.94 0,1.43h-0.39l0.76,171.59c-33.38,-2.74 -59.54,-30.62 -59.54,-64.69v-269.4c0,-35.91 29.03,-64.92 64.94,-64.92z" })])); const handleThumbPhotopea = () => { openSearchUrlWithFallback(imgurl, rawOriginalUrl, (url) => "https://www.photopea.com/#" + encodeURIComponent(JSON.stringify({ files: [url] })), t("toastPreparingAiSearch")); }; thumbPhotopeaBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleThumbPhotopea(); }); thumbPhotopeaBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleThumbPhotopea(); } }); thumbPhotopeaBtn.onmousedown = preventMiddleScroll; btnContainer.append(thumbPhotopeaBtn); const thumbVectorpeaBtn = document.createElement("button"); thumbVectorpeaBtn.classList.add("giat-thumb-vectorpea-btn"); thumbVectorpeaBtn.title = t("tipVectorpea"); thumbVectorpeaBtn.append(createSVG$2("svg", { viewBox: "0 0 256 256", width: "14", height: "14" }, [createSVG$2("path", { fill: "currentColor", "fill-rule": "evenodd", d: "m0.3 41.46c0-23.11 18.7-41.66 41.66-41.66h172.38c22.96 0 41.66 18.55 41.66 41.66v172.67c0 23.12-18.7 41.66-41.66 41.66h-68.6l-0.15-38.12c42.11-8.25 73.9-45.2 73.9-89.8 0-30.03-14.42-56.67-36.8-73.31-25.32 18.4-54.61 52.85-54.61 114.23 0-61.53-29.15-95.97-54.62-114.23-22.37 16.64-36.8 43.28-36.8 73.31 0 44.31 31.36 81.11 73.16 89.65l0.15 38.27h-68.01c-22.96 0-41.66-18.54-41.66-41.66z" })])); const handleThumbVectorpea = () => { openSearchUrlWithFallback(imgurl, rawOriginalUrl, (url) => "https://www.vectorpea.com/#" + encodeURIComponent(JSON.stringify({ files: [url] })), t("toastPreparingAiSearch")); }; thumbVectorpeaBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleThumbVectorpea(); }); thumbVectorpeaBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleThumbVectorpea(); } }); thumbVectorpeaBtn.onmousedown = preventMiddleScroll; btnContainer.append(thumbVectorpeaBtn); const thumbYandexBtn = document.createElement("button"); thumbYandexBtn.classList.add("giat-thumb-yandex-btn"); thumbYandexBtn.title = t("tipYandex"); thumbYandexBtn.append(createSVG$2("svg", { viewBox: "0 0 256 512" }, [createSVG$2("path", { fill: "currentColor", d: "M200.01 319.442V512H256V0h-83.63C90.186 0 21.09 55.511 21.09 163.677c0 77.168 30.552 119 76.374 142.073L0 512h64.73l88.731-192.558zm-.175-44.918h-29.811c-48.733 0-88.746-26.684-88.746-109.62c0-85.808 43.638-116.441 88.745-116.441h29.811z" })])); const handleThumbYandex = () => { openSearchUrlWithFallback(imgurl, rawOriginalUrl, (url) => "https://yandex.ru/images/search?rpt=imageview&url=" + encodeURIComponent(url), t("toastPreparingAiSearch")); }; thumbYandexBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleThumbYandex(); }); thumbYandexBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleThumbYandex(); } }); thumbYandexBtn.onmousedown = preventMiddleScroll; btnContainer.append(thumbYandexBtn); const thumbBingBtn = document.createElement("button"); thumbBingBtn.classList.add("giat-thumb-bing-btn"); thumbBingBtn.title = t("tipBing"); thumbBingBtn.append(createSVG$2("svg", { viewBox: "0 0 16 16" }, [createSVG$2("g", { fill: "currentColor" }, [ createSVG$2("path", { d: "M8.35 5.046a.615.615 0 0 0-.54.575c-.009.13-.006.14.289.899c.67 1.727.833 2.142.86 2.2q.101.215.277.395c.089.092.148.141.247.208c.176.117.262.15.944.351c.664.197 1.026.327 1.338.482c.405.201.688.43.866.7c.128.195.242.544.291.896c.02.137.02.44 0 .564c-.041.27-.124.495-.252.684c-.067.1-.044.084.055-.039c.278-.346.562-.938.707-1.475a4.42 4.42 0 0 0-2.14-5.028a70 70 0 0 0-.888-.465l-.53-.277l-.353-.184c-.16-.082-.266-.138-.345-.18c-.368-.192-.523-.27-.568-.283a1 1 0 0 0-.194-.03z" }), createSVG$2("path", { d: "M9.152 11.493a3 3 0 0 0-.135.083a320 320 0 0 0-1.513.934l-.8.496c-.012.01-.587.367-.876.543a1.9 1.9 0 0 1-.732.257c-.12.017-.349.017-.47 0a1.9 1.9 0 0 1-.884-.358a2.5 2.5 0 0 1-.365-.364a1.9 1.9 0 0 1-.34-.76a1 1 0 0 0-.027-.121c-.005-.006.004.092.022.22c.018.132.057.324.098.489a4.1 4.1 0 0 0 2.487 2.796c.359.142.72.23 1.114.275c.147.016.566.023.72.011a4.1 4.1 0 0 0 1.956-.661l.235-.149l.394-.248l.258-.163l1.164-.736c.51-.32.663-.433.9-.665c.099-.097.248-.262.255-.283c.002-.005.028-.046.059-.091a1.64 1.64 0 0 0 .25-.682c.02-.124.02-.427 0-.565a3 3 0 0 0-.213-.758c-.15-.314-.47-.6-.928-.83a2 2 0 0 0-.273-.12c-.006 0-.433.26-.948.58l-1.113.687z" }), createSVG$2("path", { d: "m3.004 12.184l.03.129c.089.402.245.693.515.963a1.82 1.82 0 0 0 1.312.543c.361 0 .673-.09.994-.287l.472-.29l.373-.23V5.334c0-1.537-.003-2.45-.008-2.521a1.82 1.82 0 0 0-.535-1.177c-.097-.096-.18-.16-.427-.33L4.183.24c-.239-.163-.258-.175-.33-.2a.63.63 0 0 0-.842.464c-.009.042-.01.603-.01 3.646l.003 8.035Z" }) ])])); const handleThumbBing = () => { openSearchUrlWithFallback(imgurl, rawOriginalUrl, (url) => "https://www.bing.com/images/searchbyimage?cbir=sbi&imgurl=" + encodeURIComponent(url), t("toastPreparingAiSearch")); }; thumbBingBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleThumbBing(); }); thumbBingBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleThumbBing(); } }); thumbBingBtn.onmousedown = preventMiddleScroll; btnContainer.append(thumbBingBtn); return btnContainer; } var DOMScheduler = class { pendingElements = new Set(); isScheduled = false; batchSize = 20; processor = null; registerProcessor(fn) { this.processor = fn; } schedule(elements) { if (elements instanceof HTMLElement) this.pendingElements.add(elements); else if (Array.isArray(elements)) elements.forEach((el) => this.pendingElements.add(el)); else Array.from(elements).forEach((node) => { if (node instanceof HTMLElement) this.pendingElements.add(node); }); if (!this.isScheduled && this.pendingElements.size > 0) { this.isScheduled = true; this.requestIdleProcessing(); } } requestIdleProcessing() { if (typeof window !== "undefined" && "requestIdleCallback" in window) window.requestIdleCallback((deadline) => { this.processBatch(deadline); }, { timeout: 200 }); else setTimeout(() => { this.processBatch({ timeRemaining: () => 15 }); }, 16); } processBatch(deadline) { if (!this.processor) { this.pendingElements.clear(); this.isScheduled = false; return; } const iterator = this.pendingElements.values(); const batchToProcess = []; const maxBatchLimit = 80; while (batchToProcess.length < maxBatchLimit && (deadline.timeRemaining() > 1.5 || batchToProcess.length < 10) && this.pendingElements.size > 0) { const next = iterator.next(); if (next.done) break; const el = next.value; this.pendingElements.delete(el); if (el.isConnected) batchToProcess.push(el); } if (batchToProcess.length > 0) requestAnimationFrame(() => { batchToProcess.forEach((el) => { if (el.isConnected && this.processor) try { this.processor(el); } catch (e) { console.error("Error processing DOM node in scheduler:", e); } }); }); if (this.pendingElements.size > 0) this.requestIdleProcessing(); else this.isScheduled = false; } clear() { this.pendingElements.clear(); this.isScheduled = false; } }; var domScheduler = new DOMScheduler(); var STORAGE_KEY = "giat-visited-images"; var MAX_CAPACITY = 3e3; var PRUNE_BATCH = 600; var visitedMap = new Map(); var isCacheLoaded = false; var saveDebounceTimer; function canonicalizeUrl(urlStr) { if (!urlStr) return ""; try { const u = new URL(urlStr); return (u.origin + u.pathname).toLowerCase(); } catch (e) { return (urlStr || "").split("?")[0].split("#")[0].toLowerCase(); } } function loadVisitedHistory() { if (isCacheLoaded) return; isCacheLoaded = true; try { if (typeof GM_getValue !== "undefined") { const raw = GM_getValue(STORAGE_KEY, ""); if (raw) { const parsed = JSON.parse(raw); if (typeof parsed === "object" && parsed !== null) visitedMap = new Map(Object.entries(parsed)); } } } catch (err) { console.warn("[VisitedManager] Failed to load visited footprint history:", err); visitedMap = new Map(); } } function scheduleSave() { if (saveDebounceTimer !== void 0) clearTimeout(saveDebounceTimer); saveDebounceTimer = window.setTimeout(() => { saveDebounceTimer = void 0; try { if (typeof GM_setValue !== "undefined") { if (visitedMap.size > MAX_CAPACITY) { const entries = Array.from(visitedMap.entries()); entries.sort((a, b) => a[1] - b[1]); entries.slice(0, PRUNE_BATCH).forEach(([k]) => visitedMap.delete(k)); } const plainObj = Object.fromEntries(visitedMap); GM_setValue(STORAGE_KEY, JSON.stringify(plainObj)); } } catch (err) { console.warn("[VisitedManager] Failed to save visited footprint history:", err); } }, 400); } function isVisited(docId, imgurl) { if (!config.enableVisitedMark) return false; loadVisitedHistory(); if (docId && visitedMap.has(`doc:${docId}`)) return true; if (imgurl) { const canonical = canonicalizeUrl(imgurl); if (canonical && visitedMap.has(`url:${canonical}`)) return true; } return false; } function markAsVisited(docId, imgurl, targetElement) { if (!config.enableVisitedMark) return; loadVisitedHistory(); const now = Date.now(); let modified = false; if (docId) { visitedMap.set(`doc:${docId}`, now); modified = true; } if (imgurl) { const canonical = canonicalizeUrl(imgurl); if (canonical) { visitedMap.set(`url:${canonical}`, now); modified = true; } } if (modified) scheduleSave(); if (targetElement) targetElement.classList.add("giat-visited"); else if (docId) { const matchingEl = document.querySelector(`[data-giat-result][data-docid="${docId}"], [data-giat-result][data-giat-docid="${docId}"]`); if (matchingEl) matchingEl.classList.add("giat-visited"); } } function applyVisitedClass(element, docId, imgurl) { if (!config.enableVisitedMark) { element.classList.remove("giat-visited"); return; } if (isVisited(docId || element.dataset.giatDocid || element.getAttribute("data-docid") || void 0, imgurl || element.dataset.giatImgurl || void 0)) element.classList.add("giat-visited"); else element.classList.remove("giat-visited"); } function refreshAllVisitedElements() { document.querySelectorAll("[data-giat-result]").forEach((el) => { applyVisitedClass(el, el.dataset.giatDocid || el.getAttribute("data-docid") || void 0, el.dataset.giatImgurl || void 0); }); } function clearVisitedHistory() { visitedMap.clear(); try { if (typeof GM_setValue !== "undefined") GM_setValue(STORAGE_KEY, ""); } catch (err) { console.warn("[VisitedManager] Failed to clear visited storage:", err); } refreshAllVisitedElements(); } function getVisitedStats() { loadVisitedHistory(); return { count: visitedMap.size, capacity: MAX_CAPACITY }; } var isImgSearch = () => new URLSearchParams(window.location.search).get("udm") === "2"; var isLens = () => new URLSearchParams(window.location.search).get("lns_surface"); var getItemSelector = () => { return isLens() ? GOOGLE_SELECTORS.RESULT_ITEM_LENS : GOOGLE_SELECTORS.RESULT_ITEM_STANDARD; }; function extractYouTubeVideoId(url, sourceUrl) { if (!url && !sourceUrl) return null; const ytImgMatch = (url || "").match(/(?:i\.ytimg\.com|img\.youtube\.com)\/(?:vi|vi_webp)\/([a-zA-Z0-9_-]{11})/i); if (ytImgMatch && ytImgMatch[1]) return ytImgMatch[1]; const watchMatch = (sourceUrl || url || "").match(/(?:youtube\.com\/(?:watch\?.*v=|shorts\/|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/i); if (watchMatch && watchMatch[1]) return watchMatch[1]; return null; } function applyUniversalProxyUnbox(src) { const proxyParamRegex = /[?&](?:url|src|img|image|file|link|path)=((?:https?%3A%2F%2F|http%3A%2F%2F)[^&]+)/i; if (proxyParamRegex.test(src)) { const match = src.match(proxyParamRegex); if (match && match[1]) try { const decodedUrl = decodeURIComponent(match[1]); if (decodedUrl.startsWith("http://") || decodedUrl.startsWith("https://")) return decodedUrl; } catch (e) {} } return src; } function applyPlaceholderMaximization(src) { let cleanUrl = src; if (cleanUrl.indexOf("{width}") !== -1 || cleanUrl.indexOf("{w}") !== -1 || cleanUrl.indexOf("[w]") !== -1) cleanUrl = cleanUrl.replace(/{width}/gi, "3840").replace(/{w}/gi, "3840").replace(/\[w\]/gi, "3840"); if (cleanUrl.indexOf("{height}") !== -1 || cleanUrl.indexOf("{h}") !== -1 || cleanUrl.indexOf("[h]") !== -1) cleanUrl = cleanUrl.replace(/{height}/gi, "2160").replace(/{h}/gi, "2160").replace(/\[h\]/gi, "2160"); if (cleanUrl.indexOf("{size}") !== -1) cleanUrl = cleanUrl.replace(/{size}/gi, "2048"); return cleanUrl; } function applyMultiExtensionPurge(src) { return src; } function applyGenericQueryPurge(src) { const urlParts = src.split("?"); if (urlParts.length < 2) return src; const baseUrl = urlParts[0]; const params = urlParts[1].split("&"); const keepParams = []; const blacklist = /^(?:w|width|h|height|max-w|max-width|max-h|max-height|size|s|scale|resize|fit|crop|zoom|q|quality|qlt|fmt|format|auto|dpr)$/i; for (let i = 0; i < params.length; i++) { const key = params[i].split("=")[0]; if (!blacklist.test(key)) keepParams.push(params[i]); } if (keepParams.length > 0) return baseUrl + "?" + keepParams.join("&"); return baseUrl; } function applyGenericSuffixStripping(src) { const safeSemanticRegex = /[-_](?:thumb|thumbnail)(?=\.[a-zA-Z0-9]+$)/i; if (safeSemanticRegex.test(src)) return src.replace(safeSemanticRegex, ""); return src; } function applyGenericPathCorrection(src) { return src; } function optimizeImageUrl(url, depth = 0) { if (!config.enableUrlOptimization) return url; if (!url || depth > 3) return url; let optimized = url; try { optimized = applyUniversalProxyUnbox(optimized); optimized = applyPlaceholderMaximization(optimized); optimized = applyMultiExtensionPurge(optimized); const urlObj = new URL(optimized); const hasSignature = urlObj.searchParams.has("s") || urlObj.searchParams.has("sig") || urlObj.searchParams.has("hmac") || urlObj.searchParams.has("hash") || urlObj.searchParams.has("token") || urlObj.searchParams.has("sign") || urlObj.searchParams.has("auth_key") || urlObj.searchParams.has("verify") || urlObj.searchParams.has("wsSecret") || urlObj.searchParams.has("expires"); const isAxios = optimized.includes("images.axios.com"); if (optimized.includes("_next/image")) { const targetUrl = urlObj.searchParams.get("url"); if (targetUrl) { let decodedUrl = decodeURIComponent(targetUrl); if (decodedUrl.startsWith("/")) decodedUrl = window.location.origin + decodedUrl; return optimizeImageUrl(decodedUrl, depth + 1); } } if (optimized.includes("imrs.php")) { const targetUrl = urlObj.searchParams.get("src"); if (targetUrl) return optimizeImageUrl(decodeURIComponent(targetUrl), depth + 1); } if (optimized.includes("slack-imgs.com") && urlObj.searchParams.has("url")) { const targetUrl = urlObj.searchParams.get("url"); if (targetUrl) return optimizeImageUrl(decodeURIComponent(targetUrl), depth + 1); } if (hasSignature || isAxios) return optimized; const host = urlObj.hostname.toLowerCase(); let optimizer = null; if (domainOptimizers[host]) optimizer = domainOptimizers[host]; else { const parts = host.split("."); for (let i = 1; i < parts.length - 1; i++) { const parentDomain = parts.slice(i).join("."); if (domainOptimizers[parentDomain]) { optimizer = domainOptimizers[parentDomain]; break; } } } if (optimizer) optimized = optimizer(urlObj, optimized); else if (optimized.includes("/wp-content/")) { optimizer = domainOptimizers["wordpress"]; optimized = optimizer(urlObj, optimized); } if (optimized.includes("/image/upload/")) { optimized = optimized.replace(/\/image\/upload\/t_[^/]+\//g, "/image/upload/"); optimized = optimized.replace(/\/image\/upload\/c_[^/]+\//g, "/image/upload/"); optimized = optimized.replace(/\/image\/upload\/f_auto[^/]*\//g, "/image/upload/"); optimized = optimized.replace(/\/image\/upload\/q_auto[^/]*\//g, "/image/upload/"); } if (optimized.includes("/styles/")) optimized = optimized.replace(/\/styles\/(?:thumbnail|medium|large|thumbnail_square)\/public\//i, "/"); if (optimized.includes("/images/thumbnails/")) optimized = optimized.replace(/\/images\/thumbnails\/(.+?)-[0-9]+x[0-9]+\.([a-zA-Z0-9]+)$/i, "/images/$1.$2"); optimized = applyGenericQueryPurge(optimized); if (depth === 0) { optimized = applyGenericSuffixStripping(optimized); optimized = applyGenericPathCorrection(optimized); } } catch (e) { console.error("Error optimizing URL:", e); } if (optimized !== url) return optimizeImageUrl(optimized, depth + 1); return optimized; } var cachedIndexPath = null; var lazyObserver = null; function initLazyObserver() { if (lazyObserver) return; lazyObserver = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const el = entry.target; processSingleElement(el); lazyObserver?.unobserve(el); } }); }, { rootMargin: "200px" }); } function getValueByPath(obj, path) { let current = obj; for (const key of path) { if (!current || typeof current !== "object") return void 0; current = current[key]; } return current; } function findImgDataAndPath(obj, path, visited = new Set()) { if (!obj || typeof obj !== "object") return null; if (visited.has(obj)) return null; visited.add(obj); if (Array.isArray(obj) && obj.length >= 3) { const [url, h, w] = obj; if (typeof url === "string" && url.startsWith("http") && !/encrypted-tbn[0-9]*\.gstatic\.com/.test(url) && typeof h === "number" && typeof w === "number" && h > 0 && w > 0) return { result: [ url, h, w ], path }; } const keys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)]; for (const key of keys) try { const res = findImgDataAndPath(obj[key], [...path, key], visited); if (res) return res; } catch (e) {} return null; } var findImgDataSemantic = (obj, visited = new Set()) => { if (!obj || typeof obj !== "object" || visited.has(obj)) return null; visited.add(obj); if (Array.isArray(obj)) { let candidateUrl = null; const numbers = []; for (const item of obj) if (typeof item === "string" && item.startsWith("http") && !/encrypted-tbn[0-9]*\.gstatic\.com/.test(item)) candidateUrl = item; else if (typeof item === "number" && item > 10 && item < 4e4) numbers.push(item); if (candidateUrl && numbers.length >= 2) return [ candidateUrl, numbers[0], numbers[1] ]; for (const child of obj) { const res = findImgDataSemantic(child, visited); if (res) return res; } } else if (typeof obj === "object") for (const key of Object.keys(obj)) try { const res = findImgDataSemantic(obj[key], visited); if (res) return res; } catch (e) {} return null; }; var findImgData = (obj) => { if (cachedIndexPath) { const val = getValueByPath(obj, cachedIndexPath); if (Array.isArray(val) && val.length >= 3) { const [url, h, w] = val; if (typeof url === "string" && url.startsWith("http") && !/encrypted-tbn[0-9]*\.gstatic\.com/.test(url) && typeof h === "number" && typeof w === "number" && h > 0 && w > 0) return [ url, h, w ]; } } const searchResult = findImgDataAndPath(obj, []); if (searchResult) { cachedIndexPath = searchResult.path; return searchResult.result; } return findImgDataSemantic(obj); }; function createSVG$1(type, attrs = {}, children = []) { const el = document.createElementNS("http://www.w3.org/2000/svg", type); for (const [key, value] of Object.entries(attrs)) el.setAttribute(key, value); if (children.length > 0) el.append(...children); return el; } function searchHayStack(hayStack, searchValue, visited = new Set(), depth = 0, parent = null, parentKey = null) { if (depth > 4) return null; if (!hayStack || typeof hayStack === "function" || hayStack instanceof window.Node) return null; if (visited.has(hayStack)) return null; visited.add(hayStack); if (Array.isArray(hayStack)) for (const item of hayStack) { if (typeof item !== "object" || item === null) { if (item === searchValue) return { keyData: parent, parentKey }; continue; } const res = searchHayStack(item, searchValue, visited, depth + 1, parent, parentKey); if (res) return res; } else if (typeof hayStack === "object") { if (Object.keys(hayStack).includes(searchValue)) return hayStack[searchValue]; for (const [key, value] of Object.entries(hayStack)) { if (typeof value !== "object" || value === null) { if (value === searchValue) return { keyData: hayStack, parentKey: key }; continue; } const res = searchHayStack(value, searchValue, visited, depth + 1, hayStack, key); if (res) return res; } } else if (hayStack === searchValue) return { keyData: parent, parentKey }; return null; } function findUpwards(prop, el, depth = 0) { if (!el || depth > 12) return null; const rawEl = el.wrappedJSObject || el; if (rawEl[prop]) return rawEl[prop]; return findUpwards(prop, el.parentElement, depth + 1); } function scanWizPaths(obj, docId, currentPath = [], visited = new Set(), depth = 0) { if (depth > 6) return null; if (!obj || typeof obj !== "object") return null; if (visited.has(obj)) return null; visited.add(obj); let foundDocPath = null; let foundSizePath = null; const keys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)]; for (const key of keys) try { if (obj[key] === docId) { foundDocPath = [...currentPath, key]; break; } } catch (e) {} for (const key of keys) if (key === "2000") { const val = obj[key]; if (Array.isArray(val) && val[2]) { foundSizePath = [...currentPath, key]; break; } } if (foundDocPath && foundSizePath) return { docIdPath: foundDocPath, sizePath: foundSizePath }; for (const key of keys) try { const childVal = obj[key]; if (childVal && typeof childVal === "object") { const res = scanWizPaths(childVal, docId, [...currentPath, key], visited, depth + 1); if (res) { if (res.docIdPath.length > 0 && !foundDocPath) foundDocPath = res.docIdPath; if (res.sizePath.length > 0 && !foundSizePath) foundSizePath = res.sizePath; if (foundDocPath && foundSizePath) return { docIdPath: foundDocPath, sizePath: foundSizePath }; } } } catch (e) {} if (foundDocPath || foundSizePath) return { docIdPath: foundDocPath || [], sizePath: foundSizePath || [] }; return null; } var cachedWizParentPath = null; var cachedWizDocIdKey = null; var cachedWizSizeRelativePath = null; var wizCacheLoaded = false; var consecutiveFails = 0; var circuitBreakerActive = false; var circuitBreakerTimer = 0; var ERROR_LIMIT = 5; function getWizVersion() { try { const scriptEl = document.querySelector("script[src*=\"/_/k=\"]"); if (scriptEl) { const src = scriptEl.getAttribute("src") || ""; const match = src.match(/\/k=([^/]+)/); if (match) return match[1]; return src; } } catch (e) {} return "default"; } function loadWizCache() { if (wizCacheLoaded) return; wizCacheLoaded = true; try { const raw = GM_getValue("giat-wiz-path-cache", null); if (raw) { const cache = JSON.parse(raw); if (cache && cache.version === getWizVersion()) { cachedWizParentPath = cache.parentPath || null; cachedWizDocIdKey = cache.docIdKey || null; cachedWizSizeRelativePath = cache.sizeRelativePath || null; } } } catch (e) { console.warn("Failed to load Wiz path cache:", e); } } function saveWizCache() { try { const cache = { parentPath: cachedWizParentPath, docIdKey: cachedWizDocIdKey, sizeRelativePath: cachedWizSizeRelativePath, version: getWizVersion() }; GM_setValue("giat-wiz-path-cache", JSON.stringify(cache)); } catch (e) { console.warn("Failed to save Wiz path cache:", e); } } function clearWizCache() { cachedWizParentPath = null; cachedWizDocIdKey = null; cachedWizSizeRelativePath = null; try { GM_setValue("giat-wiz-path-cache", ""); } catch (e) {} } function getFileSizeFromWiz(resultBox) { const docId = resultBox.dataset.giatDocid || resultBox.dataset.docid || resultBox.getAttribute("data-docid"); if (!docId) return null; const img = resultBox.querySelector("img"); if (!img) return null; const controller = findUpwards(GOOGLE_SELECTORS.JS_CONTROLLER, img); if (!controller || !controller.pending || !controller.pending.value) return null; const data = controller.pending.value; if (circuitBreakerActive) { if (Date.now() - circuitBreakerTimer > 3e4) { circuitBreakerActive = false; consecutiveFails = 0; } } loadWizCache(); if (cachedWizParentPath && cachedWizDocIdKey && cachedWizSizeRelativePath) try { const parentObj = getValueByPath(data, cachedWizParentPath); if (parentObj && parentObj[cachedWizDocIdKey] === docId) { const sizeData = getValueByPath(parentObj, cachedWizSizeRelativePath); if (Array.isArray(sizeData) && sizeData[2]) { consecutiveFails = 0; return sizeData[2]; } } clearWizCache(); } catch (e) { clearWizCache(); } if (circuitBreakerActive) return null; const paths = scanWizPaths(data, docId); if (paths && paths.docIdPath.length > 0 && paths.sizePath.length > 0) { const p1 = paths.docIdPath; const p2 = paths.sizePath; let commonLength = 0; while (commonLength < p1.length - 1 && commonLength < p2.length - 1 && p1[commonLength] === p2[commonLength]) commonLength++; if (commonLength > 0) { cachedWizParentPath = p1.slice(0, commonLength); cachedWizDocIdKey = p1[commonLength]; cachedWizSizeRelativePath = p2.slice(commonLength); saveWizCache(); try { const parentObj = getValueByPath(data, cachedWizParentPath); if (parentObj && parentObj[cachedWizDocIdKey] === docId) { const sizeData = getValueByPath(parentObj, cachedWizSizeRelativePath); if (Array.isArray(sizeData) && sizeData[2]) { consecutiveFails = 0; return sizeData[2]; } } } catch (e) {} } } const resultData = searchHayStack(data, docId); if (resultData && resultData.keyData) { const resultInfo = searchHayStack(resultData.keyData, "2000"); if (resultInfo && Array.isArray(resultInfo) && resultInfo[2]) { consecutiveFails = 0; return resultInfo[2]; } } consecutiveFails++; if (consecutiveFails >= ERROR_LIMIT) { circuitBreakerActive = true; circuitBreakerTimer = Date.now(); console.warn(`Wiz path extraction failed ${consecutiveFails} times. Circuit breaker activated.`); } return null; } var cleanText = (txt) => { return txt.replace(/^[·•\s\u00A0\u2022]+|[·•\s\u00A0\u2022]+$/g, "").trim(); }; function filterDateOnly(dateStr) { const filtered = dateStr.split("·").map((p) => p.trim()).filter((part) => { const clean = part.toLowerCase(); if (!clean) return false; if (/^\d{1,2}:\d{2}(:\d{2})?$/.test(clean)) return false; if ([ "licens", "授權", "授权", "ライセンス", "product", "商品", "製品", "recipe", "食譜", "食谱", "レシピ", "gif", "3d", "video", "影片", "動画", "play", "播放", "再生" ].some((kw) => clean.includes(kw))) return false; return true; }); return filtered.length > 0 ? filtered.join(" · ") : null; } var inferTypeFromUrl$1 = (url) => { if (!url) return ""; try { const urlObj = new URL(url); const format = urlObj.searchParams.get("format") || urlObj.searchParams.get("fmt"); if (format) { const fmt = format.toLowerCase(); if (fmt === "jpg" || fmt === "jpeg") return "JPEG"; if (fmt === "png") return "PNG"; if (fmt === "webp") return "WEBP"; if (fmt === "gif") return "GIF"; if (fmt === "svg") return "SVG"; if (fmt === "bmp") return "BMP"; if (fmt === "ico") return "ICO"; if (fmt === "avif") return "AVIF"; } const pathname = urlObj.pathname.toLowerCase(); if (pathname.endsWith(".jpg") || pathname.endsWith(".jpeg")) return "JPEG"; if (pathname.endsWith(".png")) return "PNG"; if (pathname.endsWith(".webp")) return "WEBP"; if (pathname.endsWith(".gif")) return "GIF"; if (pathname.endsWith(".svg")) return "SVG"; if (pathname.endsWith(".bmp")) return "BMP"; if (pathname.endsWith(".ico")) return "ICO"; if (pathname.endsWith(".avif")) return "AVIF"; } catch (e) {} const lowerUrl = url.toLowerCase(); if (lowerUrl.includes(".jpg") || lowerUrl.includes(".jpeg")) return "JPEG"; if (lowerUrl.includes(".png")) return "PNG"; if (lowerUrl.includes(".webp")) return "WEBP"; if (lowerUrl.includes(".gif")) return "GIF"; if (lowerUrl.includes(".svg")) return "SVG"; if (lowerUrl.includes(".bmp")) return "BMP"; if (lowerUrl.includes(".ico")) return "ICO"; if (lowerUrl.includes(".avif")) return "AVIF"; return ""; }; var updateDimsText = (htmlResult) => { const titleEl = htmlResult.querySelector(GOOGLE_SELECTORS.THUMB_CLASS) || htmlResult.querySelector(".Yt787"); if (titleEl) { const targetEl = titleEl.closest("a") || titleEl; if (config.enableThumbTitleTooltip) { const txt = htmlResult.querySelector(".T3Fozb[aria-label]")?.getAttribute("aria-label") || htmlResult.dataset.giatTitle || titleEl.textContent || ""; if (txt) targetEl.title = txt.trim(); } else targetEl.removeAttribute("title"); } let dimsEl = htmlResult.__giatDimsEl; if (!dimsEl) { dimsEl = htmlResult.querySelector(".giat-dims"); if (dimsEl) htmlResult.__giatDimsEl = dimsEl; } if (!dimsEl) return; const w = htmlResult.dataset.giatWidth; const h = htmlResult.dataset.giatHeight; const fileSize = htmlResult.dataset.giatFileSize; const mimeType = htmlResult.dataset.giatMimeType; const badgeText = htmlResult.dataset.giatBadge; const parts = []; if (config.enableThumbResolution && w && h) parts.push(`${w} × ${h}`); if (config.enableThumbMime && mimeType) parts.push(mimeType); if (config.enableThumbFileSize && fileSize) parts.push(fileSize); if (config.enableThumbBadges && badgeText) parts.push(badgeText); const icon = dimsEl.querySelector("svg"); dimsEl.textContent = ""; if (icon) dimsEl.appendChild(icon); if (parts.length > 0) { const textNode = document.createTextNode((icon ? " " : "") + parts.join(" · ")); dimsEl.appendChild(textNode); } dimsEl.title = `${w} × ${h}` + (fileSize ? ` · ${fileSize}` : "") + (mimeType ? ` · ${mimeType}` : "") + (badgeText ? ` · ${badgeText}` : ""); }; var updateAllDims = () => { document.querySelectorAll("div[data-giat-result]").forEach((r) => { updateDimsText(r); }); }; function processSingleElement(result) { try { let imgurl = null; let height = null; let width = null; const linkEl = result.querySelector(GOOGLE_SELECTORS.IMGRES_LINK); if (linkEl) try { const urlParams = new URLSearchParams(linkEl.href.split("?")[1]); const imgurlParam = urlParams.get("imgurl"); const wParam = urlParams.get("w"); const hParam = urlParams.get("h"); if (imgurlParam && wParam && hParam) { imgurl = imgurlParam; width = parseInt(wParam, 10); height = parseInt(hParam, 10); } } catch (e) {} if (!imgurl || !width || !height) { const imgEl = result.querySelector("img"); const docId = result.getAttribute("data-docid") || ""; if (imgEl && docId) { const controller = findUpwards(GOOGLE_SELECTORS.JS_CONTROLLER, imgEl); const rawController = controller ? controller.wrappedJSObject || controller : null; const wizData = rawController && rawController.pending ? rawController.pending.value : null; if (wizData) for (const path of [ { parent: "rB", child: "gs" }, { parent: "pB", child: "Zr" }, { parent: "AB", child: "Cs" } ]) try { const parentObj = wizData[path.parent]; if (parentObj && parentObj[path.child]) { const childObj = parentObj[path.child]; if (childObj[1] === docId) { const imgData = childObj[3]; if (Array.isArray(imgData)) { const url = imgData[0]; const h = imgData[1]; const w = imgData[2]; if (typeof url === "string" && url.startsWith("http") && typeof h === "number" && typeof w === "number") { imgurl = url; height = h; width = w; break; } } } } } catch (e) {} } } if (!imgurl || !width || !height) { const W_jd = unsafeWindow.W_jd; if (W_jd) { let resultId = (result.getAttribute("jsdata") ?? result.querySelector(GOOGLE_SELECTORS.JSDATA_SELECTOR)?.getAttribute("jsdata"))?.split(";")[2]; if (resultId) { resultId = resultId.trim().split(/\s+/)[0]; let rawResultData = W_jd[resultId]; if (!rawResultData) { const docId = result.getAttribute("data-docid"); if (docId) rawResultData = W_jd[docId]; } if (rawResultData) { const imgData = findImgData(rawResultData); if (imgData) [imgurl, height, width] = imgData; } } } } if (!imgurl || !width || !height) return; const rawOriginalUrl = imgurl; imgurl = optimizeImageUrl(imgurl); const htmlResult = result; let docId = result.getAttribute("data-docid") || ""; if (!docId) docId = (result.getAttribute("jsdata") ?? result.querySelector(GOOGLE_SELECTORS.JSDATA_SELECTOR)?.getAttribute("jsdata"))?.split(";")[1] || ""; if (docId) { htmlResult.dataset.giatDocid = docId; htmlResult.setAttribute("data-docid", docId); } const fileSize = getFileSizeFromWiz(htmlResult); if (fileSize) { htmlResult.dataset.giatFileSize = fileSize; htmlResult.setAttribute("data-giat-filesize", fileSize); } const badgeWrapper = result.querySelector(GOOGLE_SELECTORS.BADGE_WRAPPER); if (badgeWrapper) { const parts = []; badgeWrapper.childNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { const el = node; if (el.classList.contains(GOOGLE_SELECTORS.BADGE_TEXT_CONTAINER.replace(".", ""))) Array.from(el.querySelectorAll(`span:not(.${GOOGLE_SELECTORS.BADGE_EXCLUDED_SPAN_CLASS})`)).forEach((s) => { const txt = s.textContent ? cleanText(s.textContent) : ""; if (txt) parts.push(txt); }); else { const txt = el.textContent ? cleanText(el.textContent) : ""; if (txt) parts.push(txt); } } else if (node.nodeType === Node.TEXT_NODE) { const txt = node.textContent ? cleanText(node.textContent) : ""; if (txt) parts.push(txt); } }); const fullBadgeText = parts.join(" · "); if (fullBadgeText) { htmlResult.dataset.giatBadge = fullBadgeText; htmlResult.setAttribute("data-giat-badge", fullBadgeText); if (!badgeWrapper.classList.contains("giat-native-badge-wrapper")) badgeWrapper.classList.add("giat-native-badge-wrapper"); } const dateText = filterDateOnly(fullBadgeText); if (dateText) { htmlResult.dataset.giatDate = dateText; htmlResult.setAttribute("data-giat-date", dateText); } } const hn = new URL(imgurl).hostname; const goodUrl = !(hn.includes("lookaside") || hn.includes("tiktok.com")); const icon = createSVG$1("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none" }, [createSVG$1("path", { stroke: "currentColor", "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M10 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4m-8-2 8-8m0 0v5m0-5h-5" })]); const dimensionsElement = document.createElement(goodUrl ? "a" : "p"); dimensionsElement.classList.add("giat-dims"); if (goodUrl) dimensionsElement.append(icon); if (goodUrl && dimensionsElement instanceof HTMLAnchorElement) dimensionsElement.href = imgurl; htmlResult.dataset.giatImgurl = imgurl; htmlResult.dataset.giatRawOriginalUrl = rawOriginalUrl; htmlResult.dataset.giatWidth = width.toString(); htmlResult.dataset.giatHeight = height.toString(); let cardTitle = ""; const lensFullEl = result.querySelector(".T3Fozb[aria-label]"); if (lensFullEl) cardTitle = (lensFullEl.getAttribute("aria-label") || "").trim(); if (!cardTitle) { const titleCandidate = result.querySelector(GOOGLE_SELECTORS.THUMB_CLASS) || result.querySelector(".iKjA8c") || result.querySelector(".Yt787") || result.querySelector("h3"); if (titleCandidate) cardTitle = (titleCandidate.getAttribute("aria-label") || titleCandidate.getAttribute("title") || titleCandidate.textContent || "").trim(); } if (!cardTitle) { const imgEl = result.querySelector("img"); if (imgEl && imgEl.alt) cardTitle = imgEl.alt.trim(); } if (cardTitle) htmlResult.dataset.giatTitle = cardTitle; let sourceUrl = result.dataset.lpage || ""; if (!sourceUrl) { const linkEl = htmlResult.querySelector(GOOGLE_SELECTORS.IMGRES_LINK); if (linkEl) try { sourceUrl = new URLSearchParams(linkEl.href.split("?")[1]).get("imgrefurl") || ""; } catch (e) {} } if (!sourceUrl) { const directLinkEl = htmlResult.querySelector(GOOGLE_SELECTORS.SOURCE_LINK) || htmlResult.querySelector("a[href^=\"http\"]:not([href*=\"google.\"]):not([href*=\"/imgres\"])"); if (directLinkEl && directLinkEl.href) sourceUrl = directLinkEl.href; } if (sourceUrl) { htmlResult.dataset.giatSourceUrl = sourceUrl; const pageDomain = extractDomain(sourceUrl); if (pageDomain && pageDomain !== "unknown") htmlResult.dataset.giatDomain = pageDomain; } if (!htmlResult.dataset.giatDomain) { const imgDomain = extractDomain(imgurl); if (imgDomain && imgDomain !== "unknown") htmlResult.dataset.giatDomain = imgDomain; } const inferredType = inferTypeFromUrl$1(imgurl); if (inferredType) htmlResult.dataset.giatMimeType = inferredType; const thumbnail = isLens() ? result.querySelector("img")?.closest(GOOGLE_SELECTORS.JSDATA_SELECTOR)?.parentElement : result.querySelector(GOOGLE_SELECTORS.THUMB_JSNAME) || result.querySelector(GOOGLE_SELECTORS.THUMB_CLASS) || result.querySelector("img")?.parentElement; if (!thumbnail) return; thumbnail.style.position = "relative"; thumbnail.append(dimensionsElement); htmlResult.__giatDimsEl = dimensionsElement; const mainImg = thumbnail.querySelector("img"); if (mainImg) mainImg.setAttribute("data-giat-thumb-img", ""); updateDimsText(htmlResult); if (!htmlResult.dataset.giatSerpRank) { if (htmlResult.closest("#islrg, #rso, div[data-async-context]") && !htmlResult.closest("#sZmt3b, .OLKT8d, [role=\"dialog\"], [data-async-type=\"imgv\"], #islsp, [role=\"complementary\"], .TVH9nc, .Q4Lg2c")) { const mainResults = document.querySelectorAll("#islrg div[data-giat-result], #rso div[data-giat-result], div[data-async-context] div[data-giat-result]"); htmlResult.dataset.giatSerpRank = String(mainResults.length + 1); } } if (goodUrl) { const btnContainer = createThumbnailButtons(htmlResult, imgurl, rawOriginalUrl); thumbnail.append(btnContainer); } htmlResult.addEventListener("mouseenter", () => { if (htmlResult.dataset.giatFileSize) return; const dynamicSize = getFileSizeFromWiz(htmlResult); if (dynamicSize) { htmlResult.dataset.giatFileSize = dynamicSize; updateDimsText(htmlResult); } }); result.setAttribute("data-giat-result", ""); attachCheckboxesToAllResults([htmlResult]); applyVisitedClass(htmlResult, docId, imgurl); } catch (error) { console.warn("Show Image Dimensions UserScript:", error); } } var showDims = (specificElements) => { initLazyObserver(); requestAnimationFrame(() => { (specificElements ? Array.from(specificElements).filter((el) => el.hasAttribute("data-giat-result") && !el.hasAttribute("data-giat-filesize")) : document.querySelectorAll("div[data-giat-result]:not([data-giat-filesize])")).forEach((result) => { const htmlResult = result; const size = getFileSizeFromWiz(htmlResult); if (size) { htmlResult.dataset.giatFileSize = size; htmlResult.setAttribute("data-giat-filesize", size); updateDimsText(htmlResult); } }); }); (specificElements ? Array.from(specificElements).filter((el) => el.hasAttribute("data-giat-result") && !el.hasAttribute("data-giat-badge")) : document.querySelectorAll("div[data-giat-result]:not([data-giat-badge])")).forEach((result) => { const htmlResult = result; const badgeWrapper = htmlResult.querySelector(GOOGLE_SELECTORS.BADGE_WRAPPER); if (badgeWrapper) { const parts = []; badgeWrapper.childNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { const el = node; if (el.classList.contains(GOOGLE_SELECTORS.BADGE_TEXT_CONTAINER.replace(".", ""))) Array.from(el.querySelectorAll(`span:not(.${GOOGLE_SELECTORS.BADGE_EXCLUDED_SPAN_CLASS})`)).forEach((s) => { const txt = s.textContent ? cleanText(s.textContent) : ""; if (txt) parts.push(txt); }); else { const txt = el.textContent ? cleanText(el.textContent) : ""; if (txt) parts.push(txt); } } else if (node.nodeType === Node.TEXT_NODE) { const txt = node.textContent ? cleanText(node.textContent) : ""; if (txt) parts.push(txt); } }); const fullBadgeText = parts.join(" · "); if (fullBadgeText) { htmlResult.dataset.giatBadge = fullBadgeText; htmlResult.setAttribute("data-giat-badge", fullBadgeText); if (!badgeWrapper.classList.contains("giat-native-badge-wrapper")) badgeWrapper.classList.add("giat-native-badge-wrapper"); } const dateText = filterDateOnly(fullBadgeText); if (dateText) { htmlResult.dataset.giatDate = dateText; htmlResult.setAttribute("data-giat-date", dateText); } updateDimsText(htmlResult); } }); if (specificElements) { specificElements.forEach((result) => { const htmlResult = result; if (!htmlResult.hasAttribute("data-giat-result") && !htmlResult.hasAttribute("data-giat-observed")) { htmlResult.setAttribute("data-giat-observed", "true"); lazyObserver?.observe(htmlResult); } }); domScheduler.schedule(specificElements); } else { const targetSelector = getItemSelector() + ":not([data-giat-result]):not([data-giat-observed])"; const results = document.querySelectorAll(targetSelector); if (!results || results.length === 0) return; results.forEach((result) => { result.setAttribute("data-giat-observed", "true"); lazyObserver?.observe(result); }); domScheduler.schedule(results); } attachCheckboxesToAllResults(); }; function getFriendlyImageInfo(url) { try { const urlObj = new URL(url); const domain = urlObj.hostname; const pathname = urlObj.pathname; let filename = pathname.substring(pathname.lastIndexOf("/") + 1); if (!filename || filename.length < 3) { const p = urlObj.searchParams.get("file") || urlObj.searchParams.get("name") || ""; if (p) filename = p; } try { filename = decodeURIComponent(filename); } catch (e) {} if (!filename || filename.length < 2) filename = "image"; return { domain, filename }; } catch (e) { return { domain: "", filename: "image" }; } } function createSVG(type, attrs = {}, children = []) { const el = document.createElementNS("http://www.w3.org/2000/svg", type); for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v); for (const child of children) el.append(child); return el; } function createLightboxDom() { const lightboxBackdrop = document.createElement("div"); lightboxBackdrop.classList.add("giat-backdrop"); const lightboxWrap = document.createElement("div"); lightboxWrap.classList.add("giat-wrap"); const lightboxShimmer = document.createElement("div"); lightboxShimmer.classList.add("giat-shimmer"); lightboxWrap.append(lightboxShimmer); const lightboxError = document.createElement("div"); lightboxError.classList.add("giat-error-box"); lightboxWrap.append(lightboxError); const lightboxDownloadBtn = document.createElement("button"); lightboxDownloadBtn.classList.add("giat-download-btn"); lightboxDownloadBtn.title = t("tipDownload"); lightboxDownloadBtn.append(createSVG("svg", { viewBox: "0 0 24 24" }, [createSVG("path", { d: "M5 20h14v-2H5v2zM19 9h-4V3H9v6H5l7 7 7-7z" })])); const lightboxCopyImgBtn = document.createElement("button"); lightboxCopyImgBtn.classList.add("giat-copy-img-btn"); lightboxCopyImgBtn.title = t("tipCopy"); lightboxCopyImgBtn.append(createSVG("svg", { viewBox: "0 0 24 24" }, [createSVG("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" })])); const lightboxCopyB64Btn = document.createElement("button"); lightboxCopyB64Btn.classList.add("giat-copy-b64-btn"); lightboxCopyB64Btn.title = t("tipB64"); lightboxCopyB64Btn.append(createSVG("svg", { viewBox: "0 0 24 24" }, [createSVG("path", { d: "M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z" })])); const lightboxLensBtn = document.createElement("button"); lightboxLensBtn.classList.add("giat-lens-btn"); lightboxLensBtn.title = t("tipLens"); lightboxLensBtn.append(createSVG("svg", { viewBox: "0 0 24 24" }, [createSVG("path", { d: "M0 0h24v24H0z", fill: "none" }), createSVG("path", { d: "M21,9v4h-2V9c0-1.1-0.9-2-2-2H7C5.9,7,5,7.9,5,9v3H3V9c0-2.21,1.79-4,4-4h2l1-2h4l1,2h2C19.21,5,21,6.79,21,9z M12,21H7 c-2.21,0-4-1.79-4-4v-2h2v2c0,1.1,0.9,2,2,2h5V21z M18,16c1.1,0,2,0.9,2,2s-0.9,2-2,2s-2-0.9-2-2S16.9,16,18,16z M12,10 c1.66,0,3,1.34,3,3s-1.34,3-3,3s-3-1.34-3-3S10.34,10,12,10z" })])); const lightboxTineyeBtn = document.createElement("button"); lightboxTineyeBtn.classList.add("giat-tineye-btn"); lightboxTineyeBtn.title = t("tipTineye"); lightboxTineyeBtn.append(createSVG("svg", { viewBox: "0 0 24 24" }, [createSVG("path", { d: "M21 10.975V8a2 2 0 0 0-2-2h-6V4.688c.305-.274.5-.668.5-1.11a1.5 1.5 0 0 0-3 0c0 .442.195.836.5 1.11V6H5a2 2 0 0 0-2 2v2.998l-.072.005A.999.999 0 0 0 2 12v2a1 1 0 0 0 1 1v5a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5a1 1 0 0 0 1-1v-1.938a1.004 1.004 0 0 0-.072-.455c-.202-.488-.635-.605-.928-.632zM7 12c0-1.104.672-2 1.5-2s1.5.896 1.5 2-.672 2-1.5 2S7 13.104 7 12zm8.998 6c-1.001-.003-7.997 0-7.998 0v-2s7.001-.002 8.002 0l-.004 2zm-.498-4c-.828 0-1.5-.896-1.5-2s.672-2 1.5-2 1.5.896 1.5 2-.672 2-1.5 2z" })])); const lightboxAiBtn = document.createElement("button"); lightboxAiBtn.classList.add("giat-ai-btn"); lightboxAiBtn.title = t("tipAi"); lightboxAiBtn.append(createSVG("svg", { viewBox: "0 0 100 100", width: "100%", height: "100%" }, [createSVG("path", { class: "giat-ai-star", fill: "currentColor", d: "M 75 18 Q 78.6 32.4 93 36 Q 78.6 39.6 75 54 Q 71.4 39.6 57 36 Q 71.4 32.4 75 18 Z" }), createSVG("g", { stroke: "currentColor", "stroke-width": "8", fill: "none" }, [createSVG("path", { d: "M 67.78 55.39 A 26 26 0 1 1 51.95 27.98", "stroke-linecap": "butt" }), createSVG("line", { x1: "60.38", y1: "70.38", x2: "83.01", y2: "93.01", "stroke-linecap": "square" })])])); const lightboxPhotopeaBtn = document.createElement("button"); lightboxPhotopeaBtn.classList.add("giat-photopea-btn"); lightboxPhotopeaBtn.title = t("tipPhotopea"); lightboxPhotopeaBtn.append(createSVG("svg", { viewBox: "0 0 400 400" }, [createSVG("path", { style: "fill: #18a497", d: "M64.97,0h269.47c35.91,0 64.94,29.01 64.94,64.92v269.4c0,35.91 -29.03,64.92 -64.94,64.92h-228.05l-0.76,-172.02h-0.09c0,-0.41 0,-0.8 0,-1.22c0,-65.22 51.79,-117.93 115.86,-117.93c38.44,0 69.52,31.63 69.52,70.76c0,39.13 -31.08,70.76 -69.52,70.76c-12.8,0 -23.17,-10.55 -23.17,-23.59c0,-13.03 10.37,-23.59 23.17,-23.59c12.8,0 23.17,-10.55 23.17,-23.59c0,-13.03 -10.37,-23.59 -23.17,-23.59c-38.44,0 -69.52,31.63 -69.52,70.76c0,39.13 31.08,70.76 69.52,70.76c64.07,0 115.86,-52.71 115.86,-117.93c0,-65.22 -51.79,-117.93 -115.86,-117.93c-89.7,0 -162.23,73.79 -162.23,165.1c0,0.48 0,0.94 0,1.43h-0.39l0.76,171.59c-33.38,-2.74 -59.54,-30.62 -59.54,-64.69v-269.4c0,-35.91 29.03,-64.92 64.94,-64.92z" })])); const lightboxVectorpeaBtn = document.createElement("button"); lightboxVectorpeaBtn.classList.add("giat-vectorpea-btn"); lightboxVectorpeaBtn.title = t("tipVectorpea"); lightboxVectorpeaBtn.append(createSVG("svg", { viewBox: "0 0 256 256", width: "20", height: "20" }, [createSVG("path", { fill: "currentColor", "fill-rule": "evenodd", d: "m0.3 41.46c0-23.11 18.7-41.66 41.66-41.66h172.38c22.96 0 41.66 18.55 41.66 41.66v172.67c0 23.12-18.7 41.66-41.66 41.66h-68.6l-0.15-38.12c42.11-8.25 73.9-45.2 73.9-89.8 0-30.03-14.42-56.67-36.8-73.31-25.32 18.4-54.61 52.85-54.61 114.23 0-61.53-29.15-95.97-54.62-114.23-22.37 16.64-36.8 43.28-36.8 73.31 0 44.31 31.36 81.11 73.16 89.65l0.15 38.27h-68.01c-22.96 0-41.66-18.54-41.66-41.66z" })])); const lightboxYandexBtn = document.createElement("button"); lightboxYandexBtn.classList.add("giat-yandex-btn"); lightboxYandexBtn.title = t("tipYandex"); lightboxYandexBtn.append(createSVG("svg", { viewBox: "0 0 256 512" }, [createSVG("path", { fill: "currentColor", d: "M200.01 319.442V512H256V0h-83.63C90.186 0 21.09 55.511 21.09 163.677c0 77.168 30.552 119 76.374 142.073L0 512h64.73l88.731-192.558zm-.175-44.918h-29.81c-48.733 0-88.746-26.684-88.746-109.62c0-85.808 43.638-116.441 88.745-116.441h29.811z" })])); const lightboxBingBtn = document.createElement("button"); lightboxBingBtn.classList.add("giat-bing-btn"); lightboxBingBtn.title = t("tipBing"); lightboxBingBtn.append(createSVG("svg", { viewBox: "0 0 16 16" }, [createSVG("g", { fill: "currentColor" }, [ createSVG("path", { d: "M8.35 5.046a.615.615 0 0 0-.54.575c-.009.13-.006.14.289.899c.67 1.727.833 2.142.86 2.2q.101.215.277.395c.089.092.148.141.247.208c.176.117.262.15.944.351c.664.197 1.026.327 1.338.482c.405.201.688.43.866.7c.128.195.242.544.291.896c.02.137.02.44 0 .564c-.041.27-.124.495-.252.684c-.067.1-.044.084.055-.039c.278-.346.562-.938.707-1.475a4.42 4.42 0 0 0-2.14-5.028a70 70 0 0 0-.888-.465l-.53-.277l-.353-.184c-.16-.082-.266-.138-.345-.18c-.368-.192-.523-.27-.568-.283a1 1 0 0 0-.194-.03z" }), createSVG("path", { d: "M9.152 11.493a3 3 0 0 0-.135.083a320 320 0 0 0-1.513.934l-.8.496c-.012.01-.587.367-.876.543a1.9 1.9 0 0 1-.732.257c-.12.017-.349.017-.47 0a1.9 1.9 0 0 1-.884-.358a2.5 2.5 0 0 1-.365-.364a1.9 1.9 0 0 1-.34-.76a1 1 0 0 0-.027-.121c-.005-.006.004.092.022.22c.018.132.057.324.098.489a4.1 4.1 0 0 0 2.487 2.796c.359.142.72.23 1.114.275c.147.016.566.023.72.011a4.1 4.1 0 0 0 1.956-.661l.235-.149l.394-.248l.258-.163l1.164-.736c.51-.32.663-.433.9-.665c.099-.097.248-.262.255-.283c.002-.005.028-.046.059-.091a1.64 1.64 0 0 0 .25-.682c.02-.124.02-.427 0-.565a3 3 0 0 0-.213-.758c-.15-.314-.47-.6-.928-.83a2 2 0 0 0-.273-.12c-.006 0-.433.26-.948.58l-1.113.687z" }), createSVG("path", { d: "m3.004 12.184l.03.129c.089.402.245.693.515.963a1.82 1.82 0 0 0 1.312.543c.361 0 .673-.09.994-.287l.472-.29l.373-.23V5.334c0-1.537-.003-2.45-.008-2.521a1.82 1.82 0 0 0-.535-1.177c-.097-.096-.18-.16-.427-.33L4.183.24c-.239-.163-.258-.175-.33-.2a.63.63 0 0 0-.842.464c-.009.042-.01.603-.01 3.646l.003 8.035Z" }) ])])); const lightboxTypeBadge = document.createElement("div"); lightboxTypeBadge.classList.add("giat-type-badge"); const lightboxBtnContainer = document.createElement("div"); lightboxBtnContainer.classList.add("giat-lightbox-btn-container"); lightboxBtnContainer.append(lightboxDownloadBtn); lightboxBtnContainer.append(lightboxCopyImgBtn); lightboxBtnContainer.append(lightboxCopyB64Btn); lightboxBtnContainer.append(lightboxLensBtn); lightboxBtnContainer.append(lightboxTineyeBtn); lightboxBtnContainer.append(lightboxAiBtn); lightboxBtnContainer.append(lightboxPhotopeaBtn); lightboxBtnContainer.append(lightboxVectorpeaBtn); lightboxBtnContainer.append(lightboxYandexBtn); lightboxBtnContainer.append(lightboxBingBtn); lightboxBackdrop.append(lightboxWrap); lightboxBackdrop.append(lightboxTypeBadge); lightboxBackdrop.append(lightboxBtnContainer); const lightboxThumbImg = document.createElement("img"); lightboxThumbImg.classList.add("giat-thumb-blur"); lightboxWrap.append(lightboxThumbImg); const lightboxImg = document.createElement("img"); lightboxImg.referrerPolicy = "no-referrer"; lightboxWrap.append(lightboxImg); const lightboxVideo = document.createElement("video"); lightboxVideo.classList.add("giat-lightbox-video"); lightboxVideo.autoplay = true; lightboxVideo.loop = true; lightboxVideo.muted = true; lightboxVideo.playsInline = true; lightboxVideo.controls = true; lightboxVideo.style.display = "none"; lightboxWrap.append(lightboxVideo); const lightboxProgress = document.createElement("div"); lightboxProgress.classList.add("giat-lightbox-progress"); lightboxProgress.innerHTML = ` <svg width="60" height="60" viewBox="0 0 36 36" style="display: block;"> <path class="giat-ring-bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" fill="none" stroke-width="3" /> <path class="giat-ring-fg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" fill="none" stroke-width="3" stroke-dasharray="0, 100" stroke-linecap="round" /> </svg> <div class="giat-progress-text">0%</div> `; lightboxWrap.append(lightboxProgress); const lightboxYtBtn = document.createElement("button"); lightboxYtBtn.classList.add("giat-yt-play-btn"); lightboxYtBtn.style.display = "none"; lightboxYtBtn.title = t("btnWatchVideo"); lightboxYtBtn.innerHTML = ` <svg viewBox="0 0 24 24" width="18" height="18" style="fill: #ff0000; flex-shrink: 0;"> <path d="M10 15l5.19-3L10 9v6m11.56-7.83c.13.47.22 1.1.28 1.9.07.8.1 1.49.1 2.09L22 12c0 2.19-.16 3.8-.44 4.83-.25.9-.83 1.48-1.73 1.73-.47.13-1.33.22-2.65.28-1.3.07-2.49.1-3.59.1L12 19c-4.19 0-6.8-.16-7.83-.44-.9-.25-1.48-.83-1.73-1.73-.13-.47-.22-1.1-.28-1.9-.07-.8-.1-1.49-.1-2.09L2 12c0-2.19.16-3.8.44-4.83.25-.9.83-1.48 1.73-1.73.47-.13 1.33-.22 2.65-.28 1.3-.07 2.49-.1 3.59-.1L12 5c4.19 0 6.8.16 7.83.44.9.25 1.48.83 1.73 1.73z"/> </svg> <span class="giat-yt-btn-text">${t("btnWatchVideo")}</span> `; lightboxBackdrop.append(lightboxYtBtn); document.body.append(lightboxBackdrop); return { lightboxBackdrop, lightboxWrap, lightboxShimmer, lightboxError, lightboxDownloadBtn, lightboxCopyImgBtn, lightboxCopyB64Btn, lightboxLensBtn, lightboxTineyeBtn, lightboxAiBtn, lightboxPhotopeaBtn, lightboxVectorpeaBtn, lightboxYandexBtn, lightboxBingBtn, lightboxTypeBadge, lightboxBtnContainer, lightboxThumbImg, lightboxImg, lightboxVideo, lightboxYtBtn, lightboxProgress }; } var AnalysisWorkerPoolAdapter = class { async analyzeColors(bitmap) { try { const res = await unifiedWorkerPool.executeTask({ type: "COLOR_ANALYSIS", bitmap, transferables: [bitmap] }); if (res && res.success && res.colors) return { colors: res.colors }; return this.fallbackMainThread(bitmap); } catch (err) { console.warn("[AnalysisWorkerPool] Dedicated Worker Pool color analysis failed, falling back to main thread:", err); return this.fallbackMainThread(bitmap); } } fallbackMainThread(bitmap) { const result = {}; try { const canvas = document.createElement("canvas"); canvas.width = 100; canvas.height = 100; const ctx = canvas.getContext("2d"); if (ctx) { ctx.drawImage(bitmap, 0, 0, 100, 100); result.colors = processPixelColors(ctx.getImageData(0, 0, 100, 100).data); } } catch (e) { console.warn("[AnalysisWorkerPool] Main thread fallback color analysis failed:", e); } finally { try { bitmap.close(); } catch (e) {} } return result; } }; var analysisWorkerPool = new AnalysisWorkerPoolAdapter(); function processPixelColors(data) { const pixels = []; const rHist = new Array(256).fill(0); const gHist = new Array(256).fill(0); const bHist = new Array(256).fill(0); for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; if (data[i + 3] < 50) continue; pixels.push({ r, g, b }); rHist[r]++; gHist[g]++; bHist[b]++; } if (pixels.length === 0) return null; return { dominantColors: extractDominantColorsKMeans(pixels, 5), rgbHistogram: { r: rHist, g: gHist, b: bHist } }; } async function analyzeImageColorsAsync(img) { try { let bitmap; if (typeof createImageBitmap === "function") try { bitmap = await createImageBitmap(img, { resizeWidth: 200, resizeHeight: 200 }); } catch (e) { if (img.src) bitmap = await fetchCleanImageBitmap(img.src); } if (bitmap) { const res = await analysisWorkerPool.analyzeColors(bitmap); if (res.colors) return res.colors; } } catch (err) { console.warn("[colorAnalyzer] Offscreen Worker analysis failed, using main thread fallback:", err); } return analyzeImageColors(img); } function analyzeImageColors(img) { try { const canvas = document.createElement("canvas"); const targetSize = 100; canvas.width = targetSize; canvas.height = targetSize; const ctx = canvas.getContext("2d", { willReadFrequently: true }); if (!ctx) return null; ctx.drawImage(img, 0, 0, targetSize, targetSize); return processPixelColors(ctx.getImageData(0, 0, targetSize, targetSize).data); } catch (err) { console.error("Error analyzing image colors:", err); return null; } } function srgbToLinear(c) { const v = c / 255; return v <= .04045 ? v / 12.92 : Math.pow((v + .055) / 1.055, 2.4); } function linearToSrgb(c) { const v = c <= .0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - .055; return Math.max(0, Math.min(255, Math.round(v * 255))); } function rgbToOKLab(r, g, b) { const lr = srgbToLinear(r); const lg = srgbToLinear(g); const lb = srgbToLinear(b); const l = Math.cbrt(.4122214708 * lr + .5363325363 * lg + .0514459929 * lb); const m = Math.cbrt(.2119034982 * lr + .6806995451 * lg + .1073969566 * lb); const s = Math.cbrt(.0883024619 * lr + .2817188376 * lg + .6299787005 * lb); return { L: .2104542553 * l + .793617785 * m - .0040720404 * s, a: 1.9779984951 * l - 2.428592205 * m + .4505937099 * s, b: .0259040371 * l + .7827717662 * m - .8086757973 * s }; } function okLabToRgb(lab) { const l = lab.L + .3963377774 * lab.a + .2158037573 * lab.b; const m = lab.L - .1055613458 * lab.a - .0638541728 * lab.b; const s = lab.L - .0894841775 * lab.a - 1.291485548 * lab.b; const l3 = l * l * l; const m3 = m * m * m; const s3 = s * s * s; const lr = 4.0767416621 * l3 - 3.3077115913 * m3 + .2309699292 * s3; const lg = -1.2684380046 * l3 + 2.6097574011 * m3 - .3413193965 * s3; const lb = -.0041960863 * l3 - .7034186147 * m3 + 1.707614701 * s3; return { r: linearToSrgb(lr), g: linearToSrgb(lg), b: linearToSrgb(lb) }; } function extractDominantColorsKMeans(pixels, k) { if (pixels.length === 0) return []; const labPixels = pixels.map((p) => rgbToOKLab(p.r, p.g, p.b)); const centroids = []; const minDistances = new Array(labPixels.length).fill(Infinity); const firstIndex = Math.floor(labPixels.length / 2); centroids.push({ ...labPixels[firstIndex] }); for (let c = 1; c < k; c++) { const lastCentroid = centroids[c - 1]; let maxDist = -1; let nextCentroidIndex = 0; for (let i = 0; i < labPixels.length; i++) { const p = labPixels[i]; const dist = (p.L - lastCentroid.L) ** 2 + (p.a - lastCentroid.a) ** 2 + (p.b - lastCentroid.b) ** 2; if (dist < minDistances[i]) minDistances[i] = dist; if (minDistances[i] > maxDist) { maxDist = minDistances[i]; nextCentroidIndex = i; } } centroids.push({ ...labPixels[nextCentroidIndex] }); } const maxIterations = 6; const convergenceEpsilon = 1e-5; let finalClusters = Array.from({ length: k }, () => []); for (let iter = 0; iter < maxIterations; iter++) { const clusters = Array.from({ length: k }, () => []); for (let i = 0; i < labPixels.length; i++) { const p = labPixels[i]; let minDistance = Infinity; let closestClusterIndex = 0; for (let j = 0; j < k; j++) { const c = centroids[j]; const d = (p.L - c.L) ** 2 + (p.a - c.a) ** 2 + (p.b - c.b) ** 2; if (d < minDistance) { minDistance = d; closestClusterIndex = j; } } clusters[closestClusterIndex].push(p); } finalClusters = clusters; let maxShift = 0; for (let j = 0; j < k; j++) { const cluster = clusters[j]; if (cluster.length === 0) continue; let sumL = 0, sumA = 0, sumB = 0; for (let i = 0; i < cluster.length; i++) { sumL += cluster[i].L; sumA += cluster[i].a; sumB += cluster[i].b; } const newL = sumL / cluster.length; const newA = sumA / cluster.length; const newB = sumB / cluster.length; const shift = (newL - centroids[j].L) ** 2 + (newA - centroids[j].a) ** 2 + (newB - centroids[j].b) ** 2; if (shift > maxShift) maxShift = shift; centroids[j] = { L: newL, a: newA, b: newB }; } if (maxShift < convergenceEpsilon) break; } const totalValidPixels = pixels.length; return centroids.map((c, j) => { const rgb = okLabToRgb(c); const hex = rgbToHex(rgb.r, rgb.g, rgb.b); const clusterLen = finalClusters[j] ? finalClusters[j].length : 0; return { hex, percent: totalValidPixels > 0 ? Math.max(1, Math.round(clusterLen / totalValidPixels * 100)) : 0 }; }).sort((a, b) => b.percent - a.percent); } function rgbToHex(r, g, b) { const componentToHex = (c) => { const hex = Math.max(0, Math.min(255, c)).toString(16); return hex.length === 1 ? "0" + hex : hex; }; return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); } function generateSvgPath(frequencies, width, height) { const maxFreq = Math.max(...frequencies) || 1; const points = []; points.push(`M 0 ${height}`); for (let i = 0; i < 256; i++) { const x = i / 255 * width; const y = height - frequencies[i] / maxFreq * height; points.push(`L ${x.toFixed(1)} ${y.toFixed(1)}`); } points.push(`L ${width} ${height} Z`); return points.join(" "); } function renderColorAnalysis(container, data) { container.innerHTML = ""; const sectionTitle = document.createElement("div"); sectionTitle.classList.add("giat-exif-header"); sectionTitle.textContent = t("colorAnalysisTitle"); container.appendChild(sectionTitle); const histogramWrap = document.createElement("div"); histogramWrap.classList.add("giat-color-histogram-wrap"); const width = 240; const height = 75; const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox", `0 0 ${width} ${height}`); svg.setAttribute("width", "100%"); svg.setAttribute("height", height.toString()); const pathR = document.createElementNS("http://www.w3.org/2000/svg", "path"); pathR.setAttribute("d", generateSvgPath(data.rgbHistogram.r, width, height)); pathR.setAttribute("fill", "rgba(234, 67, 53, 0.35)"); pathR.setAttribute("style", "mix-blend-mode: screen;"); const pathG = document.createElementNS("http://www.w3.org/2000/svg", "path"); pathG.setAttribute("d", generateSvgPath(data.rgbHistogram.g, width, height)); pathG.setAttribute("fill", "rgba(52, 168, 83, 0.35)"); pathG.setAttribute("style", "mix-blend-mode: screen;"); const pathB = document.createElementNS("http://www.w3.org/2000/svg", "path"); pathB.setAttribute("d", generateSvgPath(data.rgbHistogram.b, width, height)); pathB.setAttribute("fill", "rgba(66, 133, 244, 0.35)"); pathB.setAttribute("style", "mix-blend-mode: screen;"); svg.append(pathR, pathG, pathB); histogramWrap.appendChild(svg); container.appendChild(histogramWrap); const paletteWrap = document.createElement("div"); paletteWrap.classList.add("giat-color-palette-wrap"); data.dominantColors.forEach((colorItem) => { const swatch = document.createElement("div"); swatch.classList.add("giat-color-swatch"); swatch.style.backgroundColor = colorItem.hex; swatch.title = `${colorItem.hex} · ${colorItem.percent}% (${t("tipColorClickToCopy")})`; swatch.style.setProperty("--swatch-color", colorItem.hex); swatch.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); swatch.classList.add("giat-clicked"); setTimeout(() => swatch.classList.remove("giat-clicked"), 150); swatch.classList.add("giat-copied-flash"); setTimeout(() => swatch.classList.remove("giat-copied-flash"), 400); const checkIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); checkIcon.setAttribute("viewBox", "0 0 24 24"); checkIcon.classList.add("giat-swatch-check"); const checkPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); checkPath.setAttribute("d", "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"); checkPath.setAttribute("fill", "#ffffff"); checkIcon.appendChild(checkPath); swatch.appendChild(checkIcon); setTimeout(() => checkIcon.remove(), 500); navigator.clipboard.writeText(colorItem.hex.toUpperCase()).catch((err) => { console.warn("Failed to copy color to clipboard:", err); }); }); paletteWrap.appendChild(swatch); }); container.appendChild(paletteWrap); } var tooltipEl = null; var tooltipTimeout; var destroyTimeout; function escapeHtml(str) { return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); } function removeTooltip() { if (tooltipTimeout) { clearTimeout(tooltipTimeout); tooltipTimeout = void 0; } if (destroyTimeout) { clearTimeout(destroyTimeout); destroyTimeout = void 0; } if (tooltipEl) { tooltipEl.remove(); tooltipEl = null; } } function requestRemoveTooltip() { if (destroyTimeout) clearTimeout(destroyTimeout); destroyTimeout = window.setTimeout(() => { removeTooltip(); }, 250); } function cancelRemoveTooltip() { if (destroyTimeout) { clearTimeout(destroyTimeout); destroyTimeout = void 0; } } function bindMetadataToBadge(badge, img, parsedMeta, exif) { if (parsedMeta.shoot) badge.dataset.giatShoot = formatExifDateStyle(parsedMeta.shoot, parsedMeta.shootIsNaive, parsedMeta.shootOffset); else delete badge.dataset.giatShoot; if (parsedMeta.digitized) badge.dataset.giatDigitized = formatExifDateStyle(parsedMeta.digitized, parsedMeta.digitizedIsNaive, parsedMeta.digitizedOffset); else delete badge.dataset.giatDigitized; if (parsedMeta.modify) badge.dataset.giatModify = formatExifDateStyle(parsedMeta.modify, parsedMeta.modifyIsNaive, parsedMeta.modifyOffset); else delete badge.dataset.giatModify; if (parsedMeta.lastModified) badge.dataset.giatLastmodified = formatExifDateStyle(parsedMeta.lastModified, false, null, parsedMeta.lastModifiedIsDateOnly); else delete badge.dataset.giatLastmodified; if (parsedMeta.googleBadge) badge.dataset.giatGooglebadge = parsedMeta.googleBadge; else delete badge.dataset.giatGooglebadge; badge.dataset.giatPrimarysource = parsedMeta.primarySource; if (exif) { if (exif.make) badge.dataset.giatMake = exif.make; else delete badge.dataset.giatMake; if (exif.model) badge.dataset.giatModel = exif.model; else delete badge.dataset.giatModel; if (exif.lensModel) badge.dataset.giatLensmodel = exif.lensModel; else delete badge.dataset.giatLensmodel; if (exif.focalLength) badge.dataset.giatFocallength = exif.focalLength; else delete badge.dataset.giatFocallength; if (exif.software) badge.dataset.giatSoftware = exif.software; else delete badge.dataset.giatSoftware; if (exif.flash) badge.dataset.giatFlash = exif.flash; else delete badge.dataset.giatFlash; if (exif.fNumber) badge.dataset.giatFnumber = exif.fNumber; else delete badge.dataset.giatFnumber; if (exif.exposureTime) badge.dataset.giatExposuretime = exif.exposureTime; else delete badge.dataset.giatExposuretime; if (exif.iso) badge.dataset.giatIso = exif.iso; else delete badge.dataset.giatIso; if (exif.gpsLatitude && exif.gpsLongitude) { badge.dataset.giatGpslat = JSON.stringify(exif.gpsLatitude); badge.dataset.giatGpslon = JSON.stringify(exif.gpsLongitude); if (exif.gpsLatitudeRef) badge.dataset.giatGpslatref = exif.gpsLatitudeRef; if (exif.gpsLongitudeRef) badge.dataset.giatGpslonref = exif.gpsLongitudeRef; } else { delete badge.dataset.giatGpslat; delete badge.dataset.giatGpslon; delete badge.dataset.giatGpslatref; delete badge.dataset.giatGpslonref; } if (exif.ai?.isAI) { badge.dataset.giatAiIsai = "true"; badge.dataset.giatAiMethod = exif.ai.method; if (exif.ai.detail) badge.dataset.giatAiDetail = exif.ai.detail; else delete badge.dataset.giatAiDetail; } else { delete badge.dataset.giatAiIsai; delete badge.dataset.giatAiMethod; delete badge.dataset.giatAiDetail; } badge.dataset.giatC2paactions = exif.c2paActions ? JSON.stringify(exif.c2paActions) : ""; if (exif.c2paIssuer) badge.dataset.giatC2paissuer = exif.c2paIssuer; else delete badge.dataset.giatC2paissuer; } else { delete badge.dataset.giatMake; delete badge.dataset.giatModel; delete badge.dataset.giatLensmodel; delete badge.dataset.giatFocallength; delete badge.dataset.giatSoftware; delete badge.dataset.giatFlash; delete badge.dataset.giatFnumber; delete badge.dataset.giatExposuretime; delete badge.dataset.giatIso; delete badge.dataset.giatGpslat; delete badge.dataset.giatGpslon; delete badge.dataset.giatGpslatref; delete badge.dataset.giatGpslonref; delete badge.dataset.giatAiIsai; delete badge.dataset.giatAiMethod; delete badge.dataset.giatAiDetail; delete badge.dataset.giatC2paactions; delete badge.dataset.giatC2paissuer; } } function showMetadataTooltip(badge, e) { if (!config.enableLightboxDate && !config.enableLightboxColorAnalysis) return; if (!badge.classList.contains("giat-has-metadata")) return; const backdrop = badge.closest(".giat-backdrop"); if (backdrop && backdrop.classList.contains("giat-zoomed")) { removeTooltip(); return; } removeTooltip(); tooltipTimeout = window.setTimeout(() => { if (backdrop && backdrop.classList.contains("giat-zoomed")) { removeTooltip(); return; } const shoot = badge.dataset.giatShoot; const digitized = badge.dataset.giatDigitized; const modify = badge.dataset.giatModify; const lastModified = badge.dataset.giatLastmodified; const googleBadge = badge.dataset.giatGooglebadge; const primarySource = badge.dataset.giatPrimarysource; const make = badge.dataset.giatMake; const model = badge.dataset.giatModel; const lensModel = badge.dataset.giatLensmodel; const focalLength = badge.dataset.giatFocallength; const software = badge.dataset.giatSoftware; const flash = badge.dataset.giatFlash; const fNumber = badge.dataset.giatFnumber; const exposureTime = badge.dataset.giatExposuretime; const iso = badge.dataset.giatIso; const gpsLatStr = badge.dataset.giatGpslat; const gpsLonStr = badge.dataset.giatGpslon; const gpsLatRef = badge.dataset.giatGpslatref; const gpsLonRef = badge.dataset.giatGpslonref; const aiIsai = badge.dataset.giatAiIsai === "true"; const aiMethod = badge.dataset.giatAiMethod; const aiDetail = badge.dataset.giatAiDetail; const c2paActionsStr = badge.dataset.giatC2paactions; const c2paIssuer = badge.dataset.giatC2paissuer; tooltipEl = document.createElement("div"); tooltipEl.classList.add("giat-time-history-tooltip", "giat-tooltip-dark", "giat-theme-dark"); const sections = []; const svgCamera = `<svg class="giat-exif-svg" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24"><path fill="currentColor" d="M4 4h3l2-2h6l2 2h3a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2m8 3a5 5 0 0 0-5 5a5 5 0 0 0 5 5a5 5 0 0 0 5-5a5 5 0 0 0-5-5m0 2a3 3 0 0 1 3 3a3 3 0 0 1-3 3a3 3 0 0 1-3-3a3 3 0 0 1 3-3Z"/></svg>`; const svgCalendar = `<svg class="giat-exif-svg" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24"><path fill="currentColor" d="M12 12h5v5h-5zm7-9h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2m0 2v2H5V5zM5 19V9h14v10z"/></svg>`; const svgPencil = `<svg class="giat-exif-svg" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24"><path fill="currentColor" d="m14.06 9l.94.94L5.92 19H5v-.92zm3.6-6c-.25 0-.51.1-.7.29l-1.83 1.83l3.75 3.75l1.83-1.83c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.2-.2-.45-.29-.71-.29m-3.6 3.19L3 17.25V21h3.75L17.81 9.94z"/></svg>`; const svgGlobe = `<svg class="giat-exif-svg" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2a10 10 0 0 0-10 10a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m6.93 6h-2.95a15.65 15.65 0 0 0-1.38-3.56A8.03 8.03 0 0 1 18.93 8M12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96M4.26 14a7.82 7.82 0 0 1 0-4h3.38a16.7 16.7 0 0 0 0 4zm.81 2h2.95c.32 1.25.78 2.45 1.38 3.56A8.03 8.03 0 0 1 5.07 16m2.95-8H5.07a8.03 8.03 0 0 1 4.51-3.56A15.65 15.65 0 0 0 8.02 8M12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96m2.54-5.96h-5.08a14.7 14.7 0 0 1 0-4h5.08a14.7 14.7 0 0 1 0 4m.48 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56m1.6-5.56a16.7 16.7 0 0 0 0-4h3.38a7.82 7.82 0 0 1 0 4z"/></svg>`; const svgSearch = `<svg class="giat-exif-svg" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5A6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5S14 7.01 14 9.5S11.99 14 9.5 14"/></svg>`; const buildRow = (label, value, sourceKey) => { if (!value) return ""; return `<div class="${primarySource === sourceKey ? "giat-tooltip-row giat-exif-copyable giat-row-highlight" : "giat-tooltip-row giat-exif-copyable"}" data-copy-val="${escapeHtml(value)}"><span class="giat-row-label">${label}</span><span class="giat-row-val">${escapeHtml(value)}</span></div>`; }; let timeRowsHtml = ""; timeRowsHtml += buildRow(`${svgCamera} ${t("shoot")}`, shoot, "shoot"); timeRowsHtml += buildRow(`${svgCalendar} ${t("digitized")}`, digitized, "digitized"); timeRowsHtml += buildRow(`${svgPencil} ${t("modify")}`, modify, "modify"); timeRowsHtml += buildRow(`${svgGlobe} ${t("lastModified")}`, lastModified, "lastModified"); timeRowsHtml += buildRow(`${svgSearch} ${t("googleBadge")}`, googleBadge, "googleBadge"); if (timeRowsHtml) { let timeSectionHtml = `<div class="giat-tooltip-section">`; timeSectionHtml += `<div class="giat-tooltip-header">${t("timeHistory")}</div>`; timeSectionHtml += timeRowsHtml; timeSectionHtml += `</div>`; sections.push(timeSectionHtml); } const cameraBrand = make && model && model.startsWith(make) ? model : [make, model].filter(Boolean).join(" "); if (!!(cameraBrand || lensModel || focalLength || software || flash || fNumber || exposureTime || iso)) { let camHtml = `<div class="giat-tooltip-section">`; camHtml += `<div class="giat-tooltip-header">${t("cameraSpecs")}</div>`; if (cameraBrand) camHtml += `<div class="giat-tooltip-row giat-exif-copyable" data-copy-val="${escapeHtml(cameraBrand)}"><span class="giat-row-label">${t("camera")}</span><span class="giat-row-val">${escapeHtml(cameraBrand)}</span></div>`; if (lensModel) camHtml += `<div class="giat-tooltip-row giat-exif-copyable" data-copy-val="${escapeHtml(lensModel)}"><span class="giat-row-label">${t("lensModel")}</span><span class="giat-row-val">${escapeHtml(lensModel)}</span></div>`; if (focalLength) camHtml += `<div class="giat-tooltip-row giat-exif-copyable" data-copy-val="${escapeHtml(focalLength)}"><span class="giat-row-label">${t("focalLength")}</span><span class="giat-row-val">${escapeHtml(focalLength)}</span></div>`; if (fNumber) camHtml += `<div class="giat-tooltip-row giat-exif-copyable" data-copy-val="${escapeHtml(fNumber)}"><span class="giat-row-label">${t("aperture")}</span><span class="giat-row-val">${escapeHtml(fNumber)}</span></div>`; if (exposureTime) camHtml += `<div class="giat-tooltip-row giat-exif-copyable" data-copy-val="${escapeHtml(exposureTime)}"><span class="giat-row-label">${t("shutterSpeed")}</span><span class="giat-row-val">${escapeHtml(exposureTime)}</span></div>`; if (iso) camHtml += `<div class="giat-tooltip-row giat-exif-copyable" data-copy-val="${escapeHtml(iso)}"><span class="giat-row-label">${t("iso")}</span><span class="giat-row-val">${escapeHtml(iso)}</span></div>`; if (flash) { const flashText = flash === "on" ? t("flashOn") : t("flashOff"); camHtml += `<div class="giat-tooltip-row giat-exif-copyable" data-copy-val="${escapeHtml(flashText)}"><span class="giat-row-label">${t("flash")}</span><span class="giat-row-val">${escapeHtml(flashText)}</span></div>`; } if (software) camHtml += `<div class="giat-tooltip-row giat-exif-copyable" data-copy-val="${escapeHtml(software)}"><span class="giat-row-label">${t("software")}</span><span class="giat-row-val">${escapeHtml(software)}</span></div>`; camHtml += `</div>`; sections.push(camHtml); } if (gpsLatStr && gpsLonStr) try { const gpsLat = JSON.parse(gpsLatStr); const gpsLon = JSON.parse(gpsLonStr); const latDec = convertToDecimalDegrees(gpsLat, gpsLatRef); const lonDec = convertToDecimalDegrees(gpsLon, gpsLonRef); if (latDec !== null && lonDec !== null) { const coordsText = `${formatGPS(gpsLat, gpsLatRef)}, ${formatGPS(gpsLon, gpsLonRef)}`; const googleMapsUrl = `https://www.google.com/maps/search/?api=1&query=${latDec},${lonDec}`; let gpsHtml = `<div class="giat-tooltip-section">`; gpsHtml += `<div class="giat-tooltip-header">${t("gpsLocation")}</div>`; gpsHtml += `<div class="giat-tooltip-row"><span class="giat-row-label">${t("gpsCoords")}</span><span class="giat-row-val">${coordsText}</span></div>`; gpsHtml += `<div class="giat-tooltip-row"><span class="giat-row-label"></span><span class="giat-row-val"><a class="giat-gps-link" href="${googleMapsUrl}" target="_blank" rel="noopener noreferrer">${t("gpsLink")}</a></span></div>`; gpsHtml += `</div>`; sections.push(gpsHtml); } } catch (e) { console.error("Error parsing GPS in tooltip:", e); } if (aiIsai) { let aiHtml = `<div class="giat-tooltip-section">`; aiHtml += `<div class="giat-tooltip-header">${t("aiDetail")}</div>`; let methodText = ""; if (aiMethod === "c2pa") methodText = "C2PA Cryptographic Claims"; else if (aiMethod === "iptc") methodText = "IPTC DigitalSourceType Tag"; else if (aiMethod === "parameters") methodText = "Stable Diffusion Parameters"; else if (aiMethod === "software") methodText = "EXIF Software Tag"; else methodText = "Metadata Signature"; aiHtml += `<div class="giat-tooltip-row"><span class="giat-row-label">${t("aiDetectionMethod")}</span><span class="giat-row-val">${methodText}</span></div>`; if (aiDetail) { if (aiMethod === "parameters") aiHtml += `<div class="giat-tooltip-row-block"><div class="giat-row-val-prompt">${escapeHtml(aiDetail)}</div></div>`; else aiHtml += `<div class="giat-tooltip-row"><span class="giat-row-label">${t("aiFeatureDetails")}</span><span class="giat-row-val">${escapeHtml(aiDetail)}</span></div>`; } aiHtml += `</div>`; sections.push(aiHtml); } if (c2paActionsStr) try { const actions = JSON.parse(c2paActionsStr); if (actions.length > 0) { const svgShield = `<svg class="giat-inline-svg" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"><path fill="currentColor" d="M15.06 10.5a.75.75 0 0 0-1.12-1l-3.011 3.374l-.87-.974a.75.75 0 0 0-1.118 1l1.428 1.6a.75.75 0 0 0 1.119 0z"/><path fill="currentColor" fill-rule="evenodd" d="M12 1.25c-.937 0-1.833.307-3.277.801l-.727.25c-1.481.506-2.625.898-3.443 1.23c-.412.167-.767.33-1.052.495c-.275.16-.55.359-.737.626c-.185.263-.281.587-.341.9c-.063.324-.1.713-.125 1.16c-.048.886-.048 2.102-.048 3.678v1.601c0 6.101 4.608 9.026 7.348 10.224l.027.011c.34.149.66.288 1.027.382c.387.1.799.142 1.348.142c.55 0 .96-.042 1.348-.142c.367-.094.687-.233 1.026-.382l.028-.011c2.74-1.198 7.348-4.123 7.348-10.224V10.39c0-1.576 0-2.792-.048-3.679a9 9 0 0 0-.125-1.16c-.06-.312-.156-.636-.34-.9c-.188-.266-.463-.465-.738-.625a9 9 0 0 0-1.052-.495c-.818-.332-1.962-.724-3.443-1.23l-.727-.25c-1.444-.494-2.34-.801-3.277-.801M9.08 3.514c1.615-.552 2.262-.764 2.92-.764s1.305.212 2.92.764l.572.196c1.513.518 2.616.896 3.39 1.21c.387.158.667.29.864.404q.144.084.208.139c.038.03.053.048.055.05a.4.4 0 0 1 .032.074q.03.082.063.248a7 7 0 0 1 .1.958c.046.841.046 2.015.046 3.624v1.574c0 5.176-3.87 7.723-6.449 8.849c-.371.162-.586.254-.825.315c-.228.059-.506.095-.976.095s-.748-.036-.976-.095c-.24-.06-.454-.153-.825-.315c-2.58-1.126-6.449-3.674-6.449-8.849v-1.574c0-1.609 0-2.783.046-3.624a7 7 0 0 1 .1-.958q.032-.166.063-.248c.018-.05.03-.07.032-.074a.4.4 0 0 1 .055-.05q.064-.055.208-.14c.197-.114.477-.245.864-.402c.774-.315 1.877-.693 3.39-1.21z" clip-rule="evenodd"/></svg>`; let c2paHtml = `<div class="giat-tooltip-section giat-c2pa-timeline">`; c2paHtml += `<div class="giat-timeline-title">${svgShield} ${t("c2paTitle")}</div>`; if (c2paIssuer) c2paHtml += `<div class="giat-tooltip-row" style="margin-bottom: 8px;"><span class="giat-row-label">${t("c2paIssuer")}</span><span class="giat-row-val" style="color: #8ab4f8; font-weight: bold;">${c2paIssuer}</span></div>`; c2paHtml += `<ul class="giat-timeline-list">`; actions.forEach((act) => { const actionLabel = getFriendlyActionName(act.action); const softwareLabel = act.software ? act.software : ""; const dateLabel = act.date ? formatExifDateStyle(new Date(act.date), false) : ""; const lowerAct = act.action.toLowerCase(); const actionClass = lowerAct.includes("created") ? "giat-action-created" : lowerAct.includes("edited") || lowerAct.includes("manipulated") ? "giat-action-edited" : "giat-action-other"; c2paHtml += ` <li class="giat-timeline-item ${actionClass}"> <div class="giat-timeline-dot"></div> <div class="giat-timeline-content"> <div class="giat-timeline-action">${actionLabel}</div> ${softwareLabel ? `<div class="giat-timeline-software">${softwareLabel}</div>` : ""} ${dateLabel ? `<div class="giat-timeline-time">${dateLabel}</div>` : ""} </div> </li> `; }); c2paHtml += `</ul></div>`; sections.push(c2paHtml); } } catch (e) { console.error("Error parsing C2PA in tooltip:", e); } if (config.enableLightboxColorAnalysis) sections.push(`<div class="giat-tooltip-section giat-color-analysis-section"></div>`); tooltipEl.innerHTML = sections.join(""); const activeTooltip = tooltipEl; const repositionTooltip = () => { if (!activeTooltip || !activeTooltip.isConnected) return; const r = badge.getBoundingClientRect(); const tr = activeTooltip.getBoundingClientRect(); let left = r.left + r.width / 2 - tr.width / 2; let top = r.top - tr.height - 10; if (left < 10) left = 10; if (left + tr.width > window.innerWidth - 10) left = window.innerWidth - tr.width - 10; if (top < 10) top = r.bottom + 10; activeTooltip.style.left = `${left}px`; activeTooltip.style.top = `${top}px`; }; if (config.enableLightboxColorAnalysis) { const colorSec = tooltipEl.querySelector(".giat-color-analysis-section"); const lightboxImg = document.querySelector(".giat-wrap img:not(.giat-thumb-blur)"); if (colorSec && lightboxImg && lightboxImg.naturalWidth > 1) analyzeImageColorsAsync(lightboxImg).then((colorData) => { if (colorData) { renderColorAnalysis(colorSec, colorData); repositionTooltip(); } else { colorSec.remove(); repositionTooltip(); } }).catch(() => { colorSec.remove(); repositionTooltip(); }); else if (colorSec) colorSec.remove(); } tooltipEl.style.position = "fixed"; tooltipEl.style.zIndex = "2147483647"; tooltipEl.style.left = "0px"; tooltipEl.style.top = "0px"; tooltipEl.style.transform = "none"; tooltipEl.style.visibility = "hidden"; tooltipEl.style.display = "block"; (document.body || document.documentElement).appendChild(tooltipEl); tooltipEl.addEventListener("mouseenter", () => { cancelRemoveTooltip(); }); tooltipEl.addEventListener("mouseleave", () => { requestRemoveTooltip(); }); tooltipEl.querySelectorAll(".giat-exif-copyable").forEach((row) => { row.onclick = (ev) => { ev.stopPropagation(); const copyVal = row.dataset.copyVal; if (copyVal) { if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(copyVal).then(() => { showToast(t("tipExifCopied") + copyVal); }).catch(() => { showToast(copyVal); }); else showToast(copyVal); } }; }); tooltipEl.offsetHeight; repositionTooltip(); tooltipEl.style.transform = ""; tooltipEl.style.visibility = "visible"; tooltipEl.classList.add("show"); }, 200); } var imgScale = 1; var imgTranslateX = 0; var imgTranslateY = 0; var isDragging = false; var hasDragged = false; var startX = 0; var startY = 0; var lightboxBackdrop$1; var lightboxWrap$1; var lightboxImg$1; var lightboxVideo$1 = null; var transformTicking = false; function updateImageTransform() { if (transformTicking) return; transformTicking = true; requestAnimationFrame(() => { if (lightboxWrap$1) lightboxWrap$1.style.transform = `translate(${imgTranslateX}px, ${imgTranslateY}px) scale(${imgScale})`; if (lightboxBackdrop$1) { if (imgScale > 1) { lightboxBackdrop$1.classList.add("giat-zoomed"); removeTooltip(); } else lightboxBackdrop$1.classList.remove("giat-zoomed"); } transformTicking = false; }); } function updateCursorStyle() { const targetMedia = lightboxVideo$1 && lightboxVideo$1.style.display !== "none" ? lightboxVideo$1 : lightboxImg$1; if (targetMedia) { if (imgScale > 1) targetMedia.style.cursor = isDragging ? "grabbing" : "grab"; else targetMedia.style.cursor = "zoom-in"; } } function resetZoomPan() { imgScale = 1; imgTranslateX = 0; imgTranslateY = 0; isDragging = false; hasDragged = false; updateImageTransform(); updateCursorStyle(); } function onMouseMove(e) { if (!isDragging) return; e.preventDefault(); const newX = e.clientX - startX; const newY = e.clientY - startY; if (Math.abs(newX - imgTranslateX) > 3 || Math.abs(newY - imgTranslateY) > 3) hasDragged = true; imgTranslateX = newX; imgTranslateY = newY; updateImageTransform(); } function onMouseUp() { if (isDragging) { isDragging = false; updateCursorStyle(); window.removeEventListener("mousemove", onMouseMove); window.removeEventListener("mouseup", onMouseUp); } } var wheelDebounceTimer; function triggerBounceCheck() { if (wheelDebounceTimer) clearTimeout(wheelDebounceTimer); wheelDebounceTimer = window.setTimeout(() => { if (imgScale < 1) bounceToDefault(); }, 150); } function bounceToDefault() { if (lightboxWrap$1) { lightboxWrap$1.style.transition = "transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275)"; imgScale = 1; imgTranslateX = 0; imgTranslateY = 0; lightboxWrap$1.style.transform = `translate(0px, 0px) scale(1)`; if (lightboxBackdrop$1) lightboxBackdrop$1.classList.remove("giat-zoomed"); setTimeout(() => { if (lightboxWrap$1) lightboxWrap$1.style.transition = ""; }, 260); updateCursorStyle(); } } var initialPinchDistance = 0; var initialPinchScale = 1; function initGestureState(backdropEl, wrapEl, imgEl, videoEl) { lightboxBackdrop$1 = backdropEl; lightboxWrap$1 = wrapEl; lightboxImg$1 = imgEl; if (videoEl) lightboxVideo$1 = videoEl; window.addEventListener("blur", () => { if (isDragging) onMouseUp(); }); lightboxWrap$1.onwheel = (e) => { e.preventDefault(); const zoomFactor = .15; const oldScale = imgScale; if (e.deltaY < 0) imgScale = Math.min(imgScale + zoomFactor, 5); else imgScale = Math.max(imgScale - zoomFactor, .65); if (imgScale <= 1) { imgTranslateX = 0; imgTranslateY = 0; } else { const viewportCenterX = window.innerWidth / 2; const viewportCenterY = window.innerHeight / 2; const mouseRelativeX = e.clientX - viewportCenterX; const mouseRelativeY = e.clientY - viewportCenterY; imgTranslateX = mouseRelativeX - (mouseRelativeX - imgTranslateX) * (imgScale / oldScale); imgTranslateY = mouseRelativeY - (mouseRelativeY - imgTranslateY) * (imgScale / oldScale); } updateImageTransform(); updateCursorStyle(); triggerBounceCheck(); }; lightboxWrap$1.onmousedown = (e) => { if (e.button === 1) { e.preventDefault(); e.stopPropagation(); resetZoomPan(); } }; lightboxWrap$1.addEventListener("auxclick", (e) => { if (e.button === 1) { e.preventDefault(); e.stopPropagation(); } }); const bindDragAndZoom = (el) => { el.onmousedown = (e) => { if (e.button !== 0) return; e.preventDefault(); e.stopPropagation(); isDragging = true; hasDragged = false; startX = e.clientX - imgTranslateX; startY = e.clientY - imgTranslateY; updateCursorStyle(); window.addEventListener("mousemove", onMouseMove); window.addEventListener("mouseup", onMouseUp); }; el.onclick = (e) => { e.stopPropagation(); e.preventDefault(); if (hasDragged) { hasDragged = false; return; } if (imgScale <= 1.05) { const viewportCenterX = window.innerWidth / 2; const viewportCenterY = window.innerHeight / 2; const mouseRelativeX = e.clientX - viewportCenterX; const mouseRelativeY = e.clientY - viewportCenterY; const targetScale = 2.5; imgTranslateX = mouseRelativeX - mouseRelativeX * targetScale; imgTranslateY = mouseRelativeY - mouseRelativeY * targetScale; imgScale = targetScale; } else { imgScale = 1; imgTranslateX = 0; imgTranslateY = 0; } updateImageTransform(); updateCursorStyle(); }; el.ondblclick = (e) => { e.stopPropagation(); e.preventDefault(); resetZoomPan(); }; }; bindDragAndZoom(lightboxImg$1); if (lightboxVideo$1) bindDragAndZoom(lightboxVideo$1); lightboxImg$1.addEventListener("touchstart", (e) => { if (e.touches.length === 1) { isDragging = true; hasDragged = false; startX = e.touches[0].clientX - imgTranslateX; startY = e.touches[0].clientY - imgTranslateY; } else if (e.touches.length === 2) { isDragging = false; initialPinchDistance = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY); initialPinchScale = imgScale; } }, { passive: true }); lightboxImg$1.addEventListener("touchmove", (e) => { if (e.touches.length === 1 && isDragging && imgScale > 1) { const newX = e.touches[0].clientX - startX; const newY = e.touches[0].clientY - startY; if (Math.abs(newX - imgTranslateX) > 3 || Math.abs(newY - imgTranslateY) > 3) hasDragged = true; imgTranslateX = newX; imgTranslateY = newY; updateImageTransform(); } else if (e.touches.length === 2 && initialPinchDistance > 0) { const factor = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY) / initialPinchDistance; imgScale = Math.min(Math.max(initialPinchScale * factor, .8), 5); if (imgScale <= 1) { imgTranslateX = 0; imgTranslateY = 0; } updateImageTransform(); updateCursorStyle(); } }, { passive: true }); lightboxImg$1.addEventListener("touchend", () => { isDragging = false; initialPinchDistance = 0; triggerBounceCheck(); }); } var activeYouTubeVideoId = null; var activeYouTubeIframe = null; var ytIdleTimer; function setActiveYouTubeVideoId(id) { activeYouTubeVideoId = id; } function getActiveYouTubeIframe() { return activeYouTubeIframe; } function startYouTubePlayback$1(videoId, ctx) { if (!videoId || !ctx.lightboxWrap) return; if (ctx.lightboxImg) ctx.lightboxImg.style.display = "none"; if (ctx.lightboxVideo) { try { ctx.lightboxVideo.pause(); ctx.lightboxVideo.removeAttribute("src"); } catch (err) {} ctx.lightboxVideo.style.display = "none"; } if (activeYouTubeIframe) { try { activeYouTubeIframe.src = "about:blank"; activeYouTubeIframe.remove(); } catch (e) {} activeYouTubeIframe = null; } ctx.lightboxWrap.classList.remove("loading"); ctx.showLightboxProgress(false); if (ctx.lightboxBackdrop) ctx.lightboxBackdrop.classList.add("giat-yt-playing"); ctx.lightboxWrap.style.setProperty("--img-w", "1920"); ctx.lightboxWrap.style.setProperty("--img-h", "1080"); const iframe = document.createElement("iframe"); iframe.classList.add("giat-lightbox-yt-iframe"); iframe.allowFullscreen = true; iframe.setAttribute("allow", "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"); iframe.src = `https://www.youtube-nocookie.com/embed/${videoId}?autoplay=1&enablejsapi=1`; ctx.lightboxWrap.append(iframe); activeYouTubeIframe = iframe; if (ctx.lightboxYtBtn) { ctx.lightboxYtBtn.style.display = "flex"; const textSpan = ctx.lightboxYtBtn.querySelector(".giat-yt-btn-text"); if (textSpan) textSpan.textContent = t("btnBackToCover"); ctx.lightboxYtBtn.title = t("btnBackToCover"); ctx.lightboxYtBtn.classList.remove("giat-yt-dimmed"); } ctx.updateBadgeText(); } function destroyYouTubePlayback$1(ctx) { if (activeYouTubeIframe) { try { activeYouTubeIframe.src = "about:blank"; activeYouTubeIframe.remove(); } catch (e) {} activeYouTubeIframe = null; } if (ctx?.lightboxBackdrop) ctx.lightboxBackdrop.classList.remove("giat-yt-playing"); if (ctx?.lightboxYtBtn) { const textSpan = ctx.lightboxYtBtn.querySelector(".giat-yt-btn-text"); if (textSpan) textSpan.textContent = t("btnWatchVideo"); ctx.lightboxYtBtn.title = t("btnWatchVideo"); } } function resetYtBtnDim(lightboxYtBtn) { if (lightboxYtBtn) lightboxYtBtn.classList.remove("giat-yt-dimmed"); if (ytIdleTimer) clearTimeout(ytIdleTimer); ytIdleTimer = window.setTimeout(() => { if (lightboxYtBtn && !activeYouTubeIframe) lightboxYtBtn.classList.add("giat-yt-dimmed"); }, 1800); } function initYouTubePlayerEvents(ctx) { const resetDim = () => resetYtBtnDim(ctx.lightboxYtBtn); ctx.lightboxWrap.addEventListener("mousemove", resetDim); ctx.lightboxBackdrop.addEventListener("mousemove", resetDim); ctx.lightboxWrap.addEventListener("mouseleave", () => { if (ctx.lightboxYtBtn && !activeYouTubeIframe) ctx.lightboxYtBtn.classList.add("giat-yt-dimmed"); }); const handleYtToggle = () => { if (!activeYouTubeVideoId) return; if (activeYouTubeIframe) { destroyYouTubePlayback$1(ctx); if (ctx.lightboxImg) { ctx.lightboxImg.style.display = "block"; ctx.lightboxImg.style.opacity = "1"; const naturalW = ctx.lightboxImg.dataset.giatNaturalWidth || ctx.lightboxImg.naturalWidth; const naturalH = ctx.lightboxImg.dataset.giatNaturalHeight || ctx.lightboxImg.naturalHeight; if (naturalW && naturalH) { ctx.lightboxWrap.style.setProperty("--img-w", naturalW.toString()); ctx.lightboxWrap.style.setProperty("--img-h", naturalH.toString()); } } if (ctx.lightboxWrap) ctx.lightboxWrap.classList.remove("loading"); ctx.updateBadgeText(); resetDim(); } else startYouTubePlayback$1(activeYouTubeVideoId, ctx); }; ctx.lightboxYtBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleYtToggle(); }); ctx.lightboxYtBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleYtToggle(); } }); ctx.lightboxYtBtn.onmousedown = (e) => { e.stopPropagation(); if (e.button === 1) e.preventDefault(); }; } var hudOverlayEl = null; function initHotkeyTracking() { document.addEventListener("keydown", () => true); document.addEventListener("keyup", () => false); window.addEventListener("blur", () => false); window.addEventListener("focus", () => false); } function closeHotkeyHud() { if (hudOverlayEl && hudOverlayEl.classList.contains("show")) { hudOverlayEl.classList.remove("show"); setTimeout(() => { if (hudOverlayEl) hudOverlayEl.remove(); hudOverlayEl = null; }, 250); } } function toggleHotkeyHud() { if (hudOverlayEl && hudOverlayEl.classList.contains("show")) { hudOverlayEl.classList.remove("show"); setTimeout(() => { if (hudOverlayEl) hudOverlayEl.remove(); hudOverlayEl = null; }, 250); return; } removeTooltip(); if (hudOverlayEl) hudOverlayEl.remove(); hudOverlayEl = document.createElement("div"); hudOverlayEl.classList.add("giat-hud-overlay"); hudOverlayEl.innerHTML = ` <div class="giat-hud-card"> <div class="giat-hud-title"> <span><svg class="giat-inline-svg" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M4 19q-.825 0-1.412-.587T2 17V7q0-.825.588-1.412T4 5h16q.825 0 1.413.588T22 7v10q0 .825-.587 1.413T20 19zm0-2h16V7H4zm5-1h6q.425 0 .713-.288T16 15t-.288-.712T15 14H9q-.425 0-.712.288T8 15t.288.713T9 16m-5 1V7zm2.713-7.288Q7 9.425 7 9t-.288-.712T6 8t-.712.288T5 9t.288.713T6 10t.713-.288m3 0Q10 9.426 10 9t-.288-.712T9 8t-.712.288T8 9t.288.713T9 10t.713-.288m3 0Q13 9.426 13 9t-.288-.712T12 8t-.712.288T11 9t.288.713T12 10t.713-.288m3 0Q16 9.426 16 9t-.288-.712T15 8t-.712.288T14 9t.288.713T15 10t.713-.288m3 0Q19 9.426 19 9t-.288-.712T18 8t-.712.288T17 9t.288.713T18 10t.713-.288m-12 3Q7 12.426 7 12t-.288-.712T6 11t-.712.288T5 12t.288.713T6 13t.713-.288m3 0Q10 12.426 10 12t-.288-.712T9 11t-.712.288T8 12t.288.713T9 13t.713-.288m3 0Q13 12.426 13 12t-.288-.712T12 11t-.712.288T11 12t.288.713T12 13t.713-.288m3 0Q16 12.426 16 12t-.288-.712T15 11t-.712.288T14 12t.288.713T15 13t.713-.288m3 0Q19 12.426 19 12t-.288-.712T18 11t-.712.288T17 12t.288.713T18 13t.713-.288"/></svg> ${t("hudTitle") || "Lightbox Hotkey Guide"}</span> <span style="font-size:12px;opacity:0.6;font-weight:normal;">GIAT</span> </div> <div class="giat-hud-grid"> <div class="giat-hud-row"><span>${t("tipDownload")}</span><span class="giat-hud-key">D</span></div> <div class="giat-hud-row"><span>${t("tipCopy")}</span><span class="giat-hud-key">C</span></div> <div class="giat-hud-row"><span>${t("resetZoom") || "Reset Zoom / Pan"}</span><span class="giat-hud-key">R / Middle Click / Double Click</span></div> <div class="giat-hud-row"><span>${t("hudClickZoom") || "Click Image"}</span><span class="giat-hud-key">2.5x Zoom / Reset</span></div> <div class="giat-hud-row"><span>${t("hudWheelZoom") || "Smooth Zooming"}</span><span class="giat-hud-key">Wheel / Pinch</span></div> <div class="giat-hud-row"><span>${t("hudDragPan") || "Pan Image Details"}</span><span class="giat-hud-key">Drag</span></div> <div class="giat-hud-row"><span>${t("lightboxPrevKey")} / ${t("lightboxNextKey")}</span><span class="giat-hud-key">← / →</span></div> <div class="giat-hud-row"><span>${t("toggleHud") || "Shortcut Guide"}</span><span class="giat-hud-key">? / H</span></div> <div class="giat-hud-row"><span>${t("lightboxCloseKey")}</span><span class="giat-hud-key">Esc</span></div> </div> </div> `; hudOverlayEl.onclick = () => { toggleHotkeyHud(); }; (document.body || document.documentElement).appendChild(hudOverlayEl); hudOverlayEl.offsetHeight; hudOverlayEl.classList.add("show"); } function handleLightboxKeydown(e, ctx) { if (!config.enableLightboxKeys) return; if (e.ctrlKey || e.altKey || e.metaKey) return; const active = document.activeElement; if (active) { const tagName = active.tagName.toLowerCase(); if (tagName === "input" || tagName === "textarea") return; if (active.hasAttribute("contenteditable") && active.getAttribute("contenteditable") !== "false") return; } const keyLower = e.key.toLowerCase(); if (keyLower === "?" || keyLower === "h") { e.preventDefault(); e.stopPropagation(); toggleHotkeyHud(); return; } if (hudOverlayEl && hudOverlayEl.classList.contains("show")) { if (keyLower === "escape" || keyLower === "?" || keyLower === "h") { toggleHotkeyHud(); return; } } e.stopPropagation(); e.stopImmediatePropagation(); if ([ "enter", " ", "space", "arrowleft", "arrowright", "arrowup", "arrowdown", "escape", "tab" ].includes(keyLower)) e.preventDefault(); if (keyLower === "enter" && ctx.lightboxError && ctx.lightboxError.innerHTML !== "") { const errorLink = ctx.lightboxError.querySelector(".giat-error-link"); if (errorLink && errorLink.href) { e.preventDefault(); window.open(errorLink.href, "_blank"); } return; } if (keyLower === "escape") { ctx.hideLightbox(); return; } if (!ctx.currentResultEl) return; const mainResults = Array.from(document.querySelectorAll("div[data-giat-result]")); if (mainResults.length === 0) return; let currentIndex = mainResults.indexOf(ctx.currentResultEl); if (currentIndex === -1) { const currentTbnid = ctx.currentResultEl.dataset.giatTbnid || ""; if (currentTbnid) currentIndex = mainResults.findIndex((el) => el.dataset.giatTbnid === currentTbnid); } if (currentIndex === -1) return; let nextIndex = currentIndex; if (keyLower === "arrowleft") { nextIndex = currentIndex - 1; ctx.setCarouselDirection("prev"); } else if (keyLower === "arrowright") { nextIndex = currentIndex + 1; ctx.setCarouselDirection("next"); } if (nextIndex >= 0 && nextIndex < mainResults.length && nextIndex !== currentIndex) { const nextEl = mainResults[nextIndex]; const nextImgUrl = nextEl.dataset.giatImgurl; const nextWidth = parseInt(nextEl.dataset.giatWidth || "0", 10); const nextHeight = parseInt(nextEl.dataset.giatHeight || "0", 10); if (nextImgUrl && nextWidth && nextHeight) ctx.showLightbox(nextImgUrl, nextWidth, nextHeight, nextEl); return; } if (ctx.lightboxVideo && ctx.lightboxVideo.style.display !== "none") { if (keyLower === " " || keyLower === "space" || keyLower === "k") { e.preventDefault(); e.stopPropagation(); if (ctx.lightboxVideo.paused) ctx.lightboxVideo.play().catch(() => {}); else ctx.lightboxVideo.pause(); return; } if (keyLower === "m") { e.preventDefault(); e.stopPropagation(); ctx.lightboxVideo.muted = !ctx.lightboxVideo.muted; showToast(ctx.lightboxVideo.muted ? t("toastVideoMuted") || "Video Muted" : t("toastVideoUnmuted") || "Video Unmuted"); return; } } if (keyLower === "d" && ctx.lightboxDownloadBtn) ctx.lightboxDownloadBtn.click(); else if (keyLower === "c" && ctx.lightboxCopyImgBtn) ctx.lightboxCopyImgBtn.click(); else if (keyLower === "r") ctx.resetZoomPan(); } var getAiSearchUrl = (url, title, srcUrl) => { const finalTitle = title ? title.trim() : t("defaultTitleFallback"); const finalSrc = srcUrl ? srcUrl.trim() : url; const prompt = (config.aiSearchPrompt ? config.aiSearchPrompt.trim() : "") || t("defaultAiPrompt"); let queryText = prompt; const lowerPrompt = prompt.toLowerCase(); const hasImg = lowerPrompt.includes("{img}"); const hasTitle = lowerPrompt.includes("{title}"); const hasSrc = lowerPrompt.includes("{src}"); if (hasImg || hasTitle || hasSrc) { queryText = queryText.replace(/{img}/gi, url); queryText = queryText.replace(/{title}/gi, finalTitle); queryText = queryText.replace(/{src}/gi, finalSrc); } else queryText = `${prompt} ${url}`; return `https://www.google.com/search?q=${encodeURIComponent(queryText)}&udm=50`; }; function openInLens(targetUrl, rawUrl) { if (!targetUrl || targetUrl.startsWith("about:")) return; openSearchUrlWithFallback(targetUrl, rawUrl, (url) => "https://lens.google.com/uploadbyurl?url=" + encodeURIComponent(url), t("toastPreparingLens") || "Preparing image for Google Lens..."); } function openInTineye(targetUrl, rawUrl) { if (!targetUrl || targetUrl.startsWith("about:")) return; openSearchUrlWithFallback(targetUrl, rawUrl, (url) => "https://tineye.com/search?url=" + encodeURIComponent(url), t("toastPreparingTineye") || "Preparing image for TinEye..."); } function openInPhotopea(targetUrl, rawUrl) { if (!targetUrl || targetUrl.startsWith("about:")) return; openSearchUrlWithFallback(targetUrl, rawUrl, (url) => "https://www.photopea.com/#" + encodeURIComponent(JSON.stringify({ files: [url] })), "Preparing image for Photopea..."); } function openInVectorpea(targetUrl, rawUrl) { if (!targetUrl || targetUrl.startsWith("about:")) return; openSearchUrlWithFallback(targetUrl, rawUrl, (url) => "https://www.vectorpea.com/#" + encodeURIComponent(JSON.stringify({ files: [url] })), "Preparing image for Vectorpea..."); } function openInYandex(targetUrl, rawUrl) { if (!targetUrl || targetUrl.startsWith("about:")) return; openSearchUrlWithFallback(targetUrl, rawUrl, (url) => "https://yandex.ru/images/search?rpt=imageview&url=" + encodeURIComponent(url), "Preparing image for Yandex..."); } function openInBing(targetUrl, rawUrl) { if (!targetUrl || targetUrl.startsWith("about:")) return; openSearchUrlWithFallback(targetUrl, rawUrl, (url) => "https://www.bing.com/images/searchbyimage?cbir=sbi&imgurl=" + encodeURIComponent(url), "Preparing image for Bing..."); } function openInAiSearch(targetUrl, titleText, srcUrl, rawUrl) { if (!targetUrl || targetUrl.startsWith("about:")) return; if (config.enableExperimentalAiUpload) triggerAiSearchWithUpload(targetUrl, titleText, srcUrl, rawUrl); else openSearchUrlWithFallback(targetUrl, rawUrl, (u) => getAiSearchUrl(u, titleText, srcUrl), t("toastPreparingAi") || "Preparing image for AI Search..."); } var detailCallbacks = null; function setDetailPanelCallbacks(cb) { detailCallbacks = cb; } function preventMiddleScroll(e) { e.stopPropagation(); if (e.button === 1) e.preventDefault(); } function findOriginalUrlFromContainer(container) { const anchors = container.querySelectorAll("a"); for (let i = 0; i < anchors.length; i++) { const href = anchors[i].href || ""; if (!href) continue; if (href.includes("imgurl=")) try { const imgurl = new URLSearchParams(href.split("?")[1]).get("imgurl"); if (imgurl && imgurl.startsWith("http")) return imgurl; } catch (e) {} if (href.startsWith("http") && !/(encrypted-tbn[0-9]*\.gstatic\.com|www\.google\.)/.test(href)) return href; } return null; } function processDetailPanelImage(container, img) { const outerWrapper = container.parentElement; if (!outerWrapper) return; const imgurl = img.src || ""; if (outerWrapper.dataset.giatLastProcessedUrl === imgurl && outerWrapper.querySelector(".giat-detail-type-badge") && outerWrapper.querySelector(".giat-detail-btn-container")) return; outerWrapper.dataset.giatLastProcessedUrl = imgurl; const resolvedOriginalUrl = findOriginalUrlFromContainer(container); const resolvedImgUrl = resolvedOriginalUrl || imgurl; let tbnid = getTbnidFromUrl(); if (!tbnid) { const tbnidEl = container.querySelector("[data-tbnid]"); tbnid = tbnidEl ? tbnidEl.getAttribute("data-tbnid") || "" : ""; } let activeResultEl = null; if (tbnid) { activeResultEl = document.querySelector(`div[data-giat-result][jsdata*="${tbnid}"]`); if (!activeResultEl) activeResultEl = document.querySelector(`div[data-giat-result][data-docid="${tbnid}"]`); if (!activeResultEl) { const results = document.querySelectorAll("div[data-giat-result]"); for (let i = 0; i < results.length; i++) { const res = results[i]; const jsdata = res.getAttribute("jsdata") || ""; const docid = res.dataset.giatDocid || res.getAttribute("data-docid") || ""; if (jsdata.includes(tbnid) || docid === tbnid) { activeResultEl = res; break; } } } } if (!activeResultEl) { const cleanUrlForCompare = (urlStr) => { if (!urlStr) return ""; try { const u = new URL(urlStr); return (u.origin + u.pathname).toLowerCase(); } catch (e) { return urlStr.split("?")[0].split("#")[0].toLowerCase(); } }; const targetClean = cleanUrlForCompare(resolvedImgUrl); if (targetClean) { const results = document.querySelectorAll("div[data-giat-result]"); for (let i = 0; i < results.length; i++) { const res = results[i]; if (cleanUrlForCompare(res.dataset.giatImgurl || "") === targetClean) { activeResultEl = res; break; } } } } let width = 0; let height = 0; let realImgUrl = resolvedImgUrl; let titleText = ""; let srcUrl = ""; if (activeResultEl) { width = parseInt(activeResultEl.dataset.giatWidth || "0", 10); height = parseInt(activeResultEl.dataset.giatHeight || "0", 10); realImgUrl = activeResultEl.dataset.giatImgurl || resolvedImgUrl; const titleEl = activeResultEl.querySelector(GOOGLE_SELECTORS.THUMB_CLASS); titleText = titleEl ? (titleEl.textContent || "").trim() : ""; if (!titleText) titleText = activeResultEl.dataset.giatTitle || ""; const linkEl = activeResultEl.querySelector(GOOGLE_SELECTORS.IMGRES_LINK); if (linkEl) try { srcUrl = new URLSearchParams(linkEl.href.split("?")[1]).get("imgrefurl") || ""; } catch (e) {} } if (!titleText) { const panelH1 = container.closest("#sZmt3b, [role=\"dialog\"]")?.querySelector("h1.tE7R7"); if (panelH1) titleText = (panelH1.textContent || "").trim(); } if (width === 0 || height === 0) { width = img.naturalWidth || 0; height = img.naturalHeight || 0; if (width === 0 || height === 0) { const nativeSpan = container.querySelector(".UWuvyf"); if (nativeSpan) { const match = (nativeSpan.textContent || "").match(/([\d, \s]+)\s*[×x]\s*([\d, \s]+)/); if (match) { width = parseInt(match[1].replace(/[\s ,]/g, ""), 10); height = parseInt(match[2].replace(/[\s ,]/g, ""), 10); } } } } if (realImgUrl) realImgUrl = optimizeImageUrl(realImgUrl); const rawOriginalUrl = activeResultEl?.dataset.giatRawOriginalUrl || resolvedOriginalUrl || imgurl; let detailTypeBadge = outerWrapper.querySelector(".giat-detail-type-badge"); let isGoodUrl = false; if (realImgUrl && !realImgUrl.startsWith("data:") && !realImgUrl.startsWith("about:")) try { const hn = new URL(realImgUrl).hostname; isGoodUrl = !(hn.includes("lookaside") || hn.includes("tiktok.com")); } catch (e) { isGoodUrl = false; } if (!detailTypeBadge) { detailTypeBadge = document.createElement("a"); detailTypeBadge.classList.add("giat-detail-type-badge", "giat-dims"); outerWrapper.appendChild(detailTypeBadge); detailTypeBadge.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); const badge = e.currentTarget; const realUrl = badge.dataset.giatRealUrl; const rawUrl = badge.dataset.giatRawUrl; if (realUrl) openUrlWithFallback$1(realUrl, rawUrl); } }, true); } if (isGoodUrl) { detailTypeBadge.href = realImgUrl; detailTypeBadge.dataset.giatRealUrl = realImgUrl; detailTypeBadge.dataset.giatRawUrl = rawOriginalUrl; } else { detailTypeBadge.removeAttribute("href"); delete detailTypeBadge.dataset.giatRealUrl; delete detailTypeBadge.dataset.giatRawUrl; } let detailBtnContainer = outerWrapper.querySelector(".giat-detail-btn-container"); if (!detailBtnContainer) { detailBtnContainer = document.createElement("div"); detailBtnContainer.classList.add("giat-detail-btn-container"); outerWrapper.appendChild(detailBtnContainer); } if (detailBtnContainer.children.length === 0) createDetailPanelButtons(detailBtnContainer, img); if (isGoodUrl) detailBtnContainer.classList.remove("giat-disabled-container"); else detailBtnContainer.classList.add("giat-disabled-container"); detailBtnContainer.dataset.giatRealUrl = realImgUrl; detailBtnContainer.dataset.giatRawUrl = rawOriginalUrl; detailBtnContainer.dataset.giatTitleText = titleText; detailBtnContainer.dataset.giatSrcUrl = srcUrl; const detailDomain = activeResultEl?.dataset.giatDomain || (srcUrl ? (() => { try { return new URL(srcUrl).hostname.replace(/^www\./i, "").toLowerCase(); } catch { return ""; } })() : "") || ""; detailBtnContainer.dataset.giatDomain = detailDomain; detailBtnContainer.querySelectorAll("button").forEach((btn) => { btn.dataset.giatUrl = realImgUrl; btn.dataset.giatRawUrl = rawOriginalUrl; }); const cachedMeta = serverMetadataCache.get(realImgUrl) || (tbnid ? serverMetadataCache.get(tbnid) : null) || (resolvedOriginalUrl ? serverMetadataCache.get(resolvedOriginalUrl) : null); const w = activeResultEl?.dataset.giatWidth || (width ? width.toString() : ""); const h = activeResultEl?.dataset.giatHeight || (height ? height.toString() : ""); const fileSize = cachedMeta?.fileSize || activeResultEl?.dataset.giatFileSize || ""; const rawMime = cachedMeta?.mime || activeResultEl?.dataset.giatMimeType; const getSimpleType = detailCallbacks ? detailCallbacks.getSimpleTypeName : (m) => m; const inferType = detailCallbacks ? detailCallbacks.inferTypeFromUrl : (u) => ""; let mimeType = rawMime ? getSimpleType(rawMime) : inferType(realImgUrl); if (!mimeType) mimeType = inferType(realImgUrl); const dateText = activeResultEl?.dataset.giatDate || ""; const parts = []; if (config.enableThumbResolution && w && h && w !== "0" && h !== "0") parts.push(`${w} × ${h}`); if (config.enableThumbMime && mimeType) parts.push(mimeType); if (config.enableThumbFileSize && fileSize) parts.push(fileSize); if (config.enableThumbBadges && dateText) parts.push(dateText); const badgeStr = parts.join(" · "); detailTypeBadge.textContent = ""; if (isGoodUrl) { const icon = createSVG("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none" }, [createSVG("path", { stroke: "currentColor", "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M10 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4m-8-2 8-8m0 0v5m0-5h-5" })]); detailTypeBadge.appendChild(icon); } detailTypeBadge.appendChild(document.createTextNode(" " + badgeStr)); detailTypeBadge.title = badgeStr; if (isGoodUrl) { detailTypeBadge.style.cursor = "pointer"; detailTypeBadge.onclick = (e) => { e.stopPropagation(); e.preventDefault(); if (config.clickAction === "lightbox" && detailCallbacks) detailCallbacks.showLightbox(realImgUrl, width, height, activeResultEl || void 0); else openUrlWithFallback$1(realImgUrl, rawOriginalUrl); }; } else { detailTypeBadge.style.cursor = "default"; detailTypeBadge.onclick = (e) => { e.stopPropagation(); e.preventDefault(); }; } } function createDetailPanelButtons(btnContainer, img) { const getImgUrl = () => btnContainer.dataset.giatRealUrl || img.src || ""; const downloadBtn = document.createElement("button"); downloadBtn.classList.add("giat-thumb-download-btn"); downloadBtn.title = t("tipDownload"); downloadBtn.append(createSVG("svg", { viewBox: "0 0 24 24" }, [createSVG("path", { d: "M5 20h14v-2H5v2zM19 9h-4V3H9v6H5l7 7 7-7z" })])); const handleDetailDownload = () => { const url = getImgUrl(); const currentRes = detailCallbacks?.getCurrentResultEl(); if (url && !url.startsWith("about:")) downloadImage(url, downloadBtn, btnContainer.dataset.giatRawUrl, { title: btnContainer.dataset.giatTitleText || currentRes && currentRes.dataset.giatTitle || void 0, domain: btnContainer.dataset.giatDomain || currentRes && currentRes.dataset.giatDomain || void 0, width: currentRes && currentRes.dataset.giatWidth || void 0, height: currentRes && currentRes.dataset.giatHeight || void 0, rank: currentRes && currentRes.dataset.giatSerpRank || void 0, index: "" }); }; downloadBtn.onclick = (e) => { e.stopPropagation(); e.preventDefault(); handleDetailDownload(); }; downloadBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleDetailDownload(); } }); downloadBtn.onmousedown = preventMiddleScroll; const copyBtn = document.createElement("button"); copyBtn.classList.add("giat-thumb-copy-btn"); copyBtn.title = t("tipCopy"); copyBtn.append(createSVG("svg", { viewBox: "0 0 24 24" }, [createSVG("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" })])); const handleDetailCopyImg = () => { const url = getImgUrl(); if (url && !url.startsWith("about:")) copyImageToClipboard(url, copyBtn, btnContainer.dataset.giatRawUrl); }; copyBtn.onclick = (e) => { e.stopPropagation(); e.preventDefault(); handleDetailCopyImg(); }; copyBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleDetailCopyImg(); } }); copyBtn.onmousedown = preventMiddleScroll; const b64Btn = document.createElement("button"); b64Btn.classList.add("giat-thumb-b64-btn"); b64Btn.title = t("tipB64"); b64Btn.append(createSVG("svg", { viewBox: "0 0 24 24" }, [createSVG("path", { d: "M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z" })])); const handleDetailCopyB64 = () => { const url = getImgUrl(); if (url && !url.startsWith("about:")) copyBase64ToClipboard(url, b64Btn, btnContainer.dataset.giatRawUrl); }; b64Btn.onclick = (e) => { e.stopPropagation(); e.preventDefault(); handleDetailCopyB64(); }; b64Btn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleDetailCopyB64(); } }); b64Btn.onmousedown = preventMiddleScroll; const lensBtn = document.createElement("button"); lensBtn.classList.add("giat-thumb-lens-btn"); lensBtn.title = t("tipLens"); lensBtn.append(createSVG("svg", { viewBox: "0 0 24 24" }, [createSVG("path", { d: "M0 0h24v24H0z", fill: "none" }), createSVG("path", { d: "M21,9v4h-2V9c0-1.1-0.9-2-2-2H7C5.9,7,5,7.9,5,9v3H3V9c0-2.21,1.79-4,4-4h2l1-2h4l1,2h2C19.21,5,21,6.79,21,9z M12,21H7 c-2.21,0-4-1.79-4-4v-2h2v2c0,1.1,0.9,2,2,2h5V21z M18,16c1.1,0,2,0.9,2,2s-0.9,2-2,2s-2-0.9-2-2S16.9,16,18,16z M12,10 c1.66,0,3,1.34,3,3s-1.34,3-3,3s-3-1.34-3-3S10.34,10,12,10z" })])); const handleDetailLens = () => { const url = getImgUrl(); if (url && !url.startsWith("about:")) openInLens(url, btnContainer.dataset.giatRawUrl); }; lensBtn.onclick = (e) => { e.stopPropagation(); e.preventDefault(); handleDetailLens(); }; lensBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleDetailLens(); } }); lensBtn.onmousedown = preventMiddleScroll; const tineyeBtn = document.createElement("button"); tineyeBtn.classList.add("giat-thumb-tineye-btn"); tineyeBtn.title = t("tipTineye"); tineyeBtn.append(createSVG("svg", { viewBox: "0 0 24 24" }, [createSVG("path", { d: "M21 10.975V8a2 2 0 0 0-2-2h-6V4.688c.305-.274.5-.668.5-1.11a1.5 1.5 0 0 0-3 0c0 .442.195.836.5 1.11V6H5a2 2 0 0 0-2 2v2.998l-.072.005A.999.999 0 0 0 2 12v2a1 1 0 0 0 1 1v5a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5a1 1 0 0 0 1-1v-1.938a1.004 1.004 0 0 0-.072-.455c-.202-.488-.635-.605-.928-.632zM7 12c0-1.104.672-2 1.5-2s1.5.896 1.5 2-.672 2-1.5 2S7 13.104 7 12zm8.998 6c-1.001-.003-7.997 0-7.998 0v-2s7.001-.002 8.002 0l-.004 2zm-.498-4c-.828 0-1.5-.896-1.5-2s.672-2 1.5-2 1.5.896 1.5 2-.672 2-1.5 2z" })])); const handleDetailTineye = () => { const url = getImgUrl(); if (url && !url.startsWith("about:")) openInTineye(url, btnContainer.dataset.giatRawUrl); }; tineyeBtn.onclick = (e) => { e.stopPropagation(); e.preventDefault(); handleDetailTineye(); }; tineyeBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleDetailTineye(); } }); tineyeBtn.onmousedown = preventMiddleScroll; const aiBtn = document.createElement("button"); aiBtn.classList.add("giat-thumb-ai-btn"); aiBtn.title = t("tipAi"); aiBtn.append(createSVG("svg", { viewBox: "0 0 100 100", width: "100%", height: "100%" }, [createSVG("path", { class: "giat-ai-star", fill: "currentColor", d: "M 75 18 Q 78.6 32.4 93 36 Q 78.6 39.6 75 54 Q 71.4 39.6 57 36 Q 71.4 32.4 75 18 Z" }), createSVG("g", { stroke: "currentColor", "stroke-width": "8", fill: "none" }, [createSVG("path", { d: "M 67.78 55.39 A 26 26 0 1 1 51.95 27.98", "stroke-linecap": "butt" }), createSVG("line", { x1: "60.38", y1: "70.38", x2: "83.01", y2: "93.01", "stroke-linecap": "square" })])])); const handleAiSearch = () => { const url = getImgUrl(); if (url && !url.startsWith("about:")) { let titleText = btnContainer.dataset.giatTitleText || ""; let srcUrl = btnContainer.dataset.giatSrcUrl || ""; if (!titleText || !srcUrl) { const container = img.closest("div[jsname=\"figiqf\"]"); if (container) { if (!titleText) { const titleEl = container.nextElementSibling?.querySelector("h1") || document.querySelector("h1"); titleText = titleEl ? (titleEl.textContent || "").trim() : ""; } if (!srcUrl) { const linkEl = container.querySelector("a.YsLeY"); if (linkEl) srcUrl = linkEl.href || ""; } } } openInAiSearch(url, titleText, srcUrl, btnContainer.dataset.giatRawUrl); } }; aiBtn.onclick = (e) => { e.stopPropagation(); e.preventDefault(); handleAiSearch(); }; aiBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleAiSearch(); } }); aiBtn.onmousedown = preventMiddleScroll; const photopeaBtn = document.createElement("button"); photopeaBtn.classList.add("giat-thumb-photopea-btn"); photopeaBtn.title = t("tipPhotopea"); photopeaBtn.append(createSVG("svg", { viewBox: "0 0 400 400" }, [createSVG("path", { style: "fill: #18a497", d: "M64.97,0h269.47c35.91,0 64.94,29.01 64.94,64.92v269.4c0,35.91 -29.03,64.92 -64.94,64.92h-228.05l-0.76,-172.02h-0.09c0,-0.41 0,-0.8 0,-1.22c0,-65.22 51.79,-117.93 115.86,-117.93c38.44,0 69.52,31.63 69.52,70.76c0,39.13 -31.08,70.76 -69.52,70.76c-12.8,0 -23.17,-10.55 -23.17,-23.59c0,-13.03 10.37,-23.59 23.17,-23.59c12.8,0 23.17,-10.55 23.17,-23.59c0,-13.03 -10.37,-23.59 -23.17,-23.59c-38.44,0 -69.52,31.63 -69.52,70.76c0,39.13 31.08,70.76 69.52,70.76c64.07,0 115.86,-52.71 115.86,-117.93c0,-65.22 -51.79,-117.93 -115.86,-117.93c-89.7,0 -162.23,73.79 -162.23,165.1c0,0.48 0,0.94 0,1.43h-0.39l0.76,171.59c-33.38,-2.74 -59.54,-30.62 -59.54,-64.69v-269.4c0,-35.91 29.03,-64.92 64.94,-64.92z" })])); const handleDetailPhotopea = () => { const url = getImgUrl(); if (url && !url.startsWith("about:")) openInPhotopea(url, btnContainer.dataset.giatRawUrl); }; photopeaBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleDetailPhotopea(); }); photopeaBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleDetailPhotopea(); } }); photopeaBtn.onmousedown = preventMiddleScroll; const vectorpeaBtn = document.createElement("button"); vectorpeaBtn.classList.add("giat-thumb-vectorpea-btn"); vectorpeaBtn.title = t("tipVectorpea"); vectorpeaBtn.append(createSVG("svg", { viewBox: "0 0 256 256", width: "14", height: "14" }, [createSVG("path", { fill: "currentColor", "fill-rule": "evenodd", d: "m0.3 41.46c0-23.11 18.7-41.66 41.66-41.66h172.38c22.96 0 41.66 18.55 41.66 41.66v172.67c0 23.12-18.7 41.66-41.66 41.66h-68.6l-0.15-38.12c42.11-8.25 73.9-45.2 73.9-89.8 0-30.03-14.42-56.67-36.8-73.31-25.32 18.4-54.61 52.85-54.61 114.23 0-61.53-29.15-95.97-54.62-114.23-22.37 16.64-36.8 43.28-36.8 73.31 0 44.31 31.36 81.11 73.16 89.65l0.15 38.27h-68.01c-22.96 0-41.66-18.54-41.66-41.66z" })])); const handleDetailVectorpea = () => { const url = getImgUrl(); if (url && !url.startsWith("about:")) openInVectorpea(url, btnContainer.dataset.giatRawUrl); }; vectorpeaBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleDetailVectorpea(); }); vectorpeaBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleDetailVectorpea(); } }); vectorpeaBtn.onmousedown = preventMiddleScroll; const yandexBtn = document.createElement("button"); yandexBtn.classList.add("giat-thumb-yandex-btn"); yandexBtn.title = t("tipYandex"); yandexBtn.append(createSVG("svg", { viewBox: "0 0 256 512" }, [createSVG("path", { fill: "currentColor", d: "M200.01 319.442V512H256V0h-83.63C90.186 0 21.09 55.511 21.09 163.677c0 77.168 30.552 119 76.374 142.073L0 512h64.73l88.731-192.558zm-.175-44.918h-29.81c-48.733 0-88.746-26.684-88.746-109.62c0-85.808 43.638-116.441 88.745-116.441h29.811z" })])); const handleDetailYandex = () => { const url = getImgUrl(); if (url && !url.startsWith("about:")) openInYandex(url, btnContainer.dataset.giatRawUrl); }; yandexBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleDetailYandex(); }); yandexBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleDetailYandex(); } }); yandexBtn.onmousedown = preventMiddleScroll; const bingBtn = document.createElement("button"); bingBtn.classList.add("giat-thumb-bing-btn"); bingBtn.title = t("tipBing"); bingBtn.append(createSVG("svg", { viewBox: "0 0 16 16" }, [createSVG("g", { fill: "currentColor" }, [ createSVG("path", { d: "M8.35 5.046a.615.615 0 0 0-.54.575c-.009.13-.006.14.289.899c.67 1.727.833 2.142.86 2.2q.101.215.277.395c.089.092.148.141.247.208c.176.117.262.15.944.351c.664.197 1.026.327 1.338.482c.405.201.688.43.866.7c.128.195.242.544.291.896c.02.137.02.44 0 .564c-.041.27-.124.495-.252.684c-.067.1-.044.084.055-.039c.278-.346.562-.938.707-1.475a4.42 4.42 0 0 0-2.14-5.028a70 70 0 0 0-.888-.465l-.53-.277l-.353-.184c-.16-.082-.266-.138-.345-.18c-.368-.192-.523-.27-.568-.283a1 1 0 0 0-.194-.03z" }), createSVG("path", { d: "M9.152 11.493a3 3 0 0 0-.135.083a320 320 0 0 0-1.513.934l-.8.496c-.012.01-.587.367-.876.543a1.9 1.9 0 0 1-.732.257c-.12.017-.349.017-.47 0a1.9 1.9 0 0 1-.884-.358a2.5 2.5 0 0 1-.365-.364a1.9 1.9 0 0 1-.34-.76a1 1 0 0 0-.027-.121c-.005-.006.004.092.022.22c.018.132.057.324.098.489a4.1 4.1 0 0 0 2.487 2.796c.359.142.72.23 1.114.275c.147.016.566.023.72.011a4.1 4.1 0 0 0 1.956-.661l.235-.149l.394-.248l.258-.163l1.164-.736c.51-.32.663-.433.9-.665c.099-.097.248-.262.255-.283c.002-.005.028-.046.059-.091a1.64 1.64 0 0 0 .25-.682c.02-.124.02-.427 0-.565a3 3 0 0 0-.213-.758c-.15-.314-.47-.6-.928-.83a2 2 0 0 0-.273-.12c-.006 0-.433.26-.948.58l-1.113.687z" }), createSVG("path", { d: "m3.004 12.184l.03.129c.089.402.245.693.515.963a1.82 1.82 0 0 0 1.312.543c.361 0 .673-.09.994-.287l.472-.29l.373-.23V5.334c0-1.537-.003-2.45-.008-2.521a1.82 1.82 0 0 0-.535-1.177c-.097-.096-.18-.16-.427-.33L4.183.24c-.239-.163-.258-.175-.33-.2a.63.63 0 0 0-.842.464c-.009.042-.01.603-.01 3.646l.003 8.035Z" }) ])])); const handleDetailBing = () => { const url = getImgUrl(); if (url && !url.startsWith("about:")) openInBing(url, btnContainer.dataset.giatRawUrl); }; bingBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleDetailBing(); }); bingBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleDetailBing(); } }); bingBtn.onmousedown = preventMiddleScroll; btnContainer.append(downloadBtn); btnContainer.append(copyBtn); btnContainer.append(b64Btn); btnContainer.append(lensBtn); btnContainer.append(tineyeBtn); btnContainer.append(aiBtn); btnContainer.append(photopeaBtn); btnContainer.append(vectorpeaBtn); btnContainer.append(yandexBtn); btnContainer.append(bingBtn); } function initDetailPanelObserver() { const processMutations = () => { document.querySelectorAll("div[jsname=\"figiqf\"]").forEach((container) => { const img = container.querySelector("img"); if (img && img.src && !img.src.startsWith("data:") && img.naturalWidth > 1 && img.naturalHeight > 1) processDetailPanelImage(container, img); }); document.querySelectorAll("div[jsname=\"s1W05b\"]").forEach((container) => { const img = container.querySelector("img"); if (img && img.src && !img.src.startsWith("data:") && img.naturalWidth > 1 && img.naturalHeight > 1) processDetailPanelImage(container, img); }); }; new MutationObserver(() => { processMutations(); }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: [ "src", "class", "href" ] }); processMutations(); } var lightboxBackdrop; var lightboxWrap; var lightboxImg; var lightboxVideo; var lightboxYtBtn; var lightboxError; var lightboxTypeBadge; var lightboxDownloadBtn; var lightboxCopyImgBtn; var lightboxCopyB64Btn; var lightboxLensBtn; var lightboxTineyeBtn; var lightboxAiBtn; var lightboxPhotopeaBtn; var lightboxVectorpeaBtn; var lightboxYandexBtn; var lightboxBingBtn; var lightboxBtnContainer; var lightboxThumbImg; var lightboxProgress; var currentResultEl = null; var currentFirstTrackDate = null; var activeCarouselDirection = null; function getYouTubeContext() { return { lightboxWrap, lightboxBackdrop, lightboxImg, lightboxVideo, lightboxYtBtn, updateBadgeText, showLightboxProgress }; } function startYouTubePlayback(videoId) { startYouTubePlayback$1(videoId, getYouTubeContext()); } function destroyYouTubePlayback() { destroyYouTubePlayback$1(getYouTubeContext()); } function setCarouselDirection(dir) { activeCarouselDirection = dir; } function getSimpleTypeName(mime) { const type = mime.toLowerCase().trim(); if (type.includes("jpeg") || type.includes("jpg")) return "JPEG"; if (type.includes("png")) return "PNG"; if (type.includes("webp")) return "WEBP"; if (type.includes("gif")) return "GIF"; if (type.includes("avif")) return "AVIF"; if (type.includes("svg")) return "SVG"; if (type.includes("bmp")) return "BMP"; return ""; } function inferTypeFromUrl(url) { const lower = url.toLowerCase(); const queryIndex = lower.indexOf("?"); const cleanUrl = queryIndex !== -1 ? lower.substring(0, queryIndex) : lower; if (cleanUrl.endsWith(".jpg") || cleanUrl.endsWith(".jpeg")) return "JPEG"; if (cleanUrl.endsWith(".png")) return "PNG"; if (cleanUrl.endsWith(".webp")) return "WEBP"; if (cleanUrl.endsWith(".gif")) return "GIF"; if (cleanUrl.endsWith(".avif")) return "AVIF"; if (cleanUrl.endsWith(".svg")) return "SVG"; if (cleanUrl.endsWith(".bmp")) return "BMP"; return ""; } function getThumbnailUrl() { if (currentResultEl) { const thumbImg = currentResultEl.querySelector("img"); if (thumbImg && thumbImg.src && !thumbImg.src.startsWith("data:")) return thumbImg.src; } return null; } function isVideoUrlOrMime(url, mime) { if (mime) { const cleanMime = mime.toLowerCase().trim(); if (cleanMime.startsWith("video/")) return true; if (cleanMime.startsWith("image/")) return false; } const clean = (url || "").split("?")[0].split("#")[0].toLowerCase(); return /\.(mp4|webm|ogv|m4v|mov)$/i.test(clean); } function updateBadgeText() { const isYtMode = !!(getActiveYouTubeIframe() || lightboxBackdrop && lightboxBackdrop.classList.contains("giat-yt-playing")); const isVideoMode = !isYtMode && lightboxVideo && lightboxVideo.style.display !== "none"; let mimeTypeStr = ""; if (isYtMode) mimeTypeStr = "YouTube"; else if (isVideoMode) { const realMime = lightboxVideo.dataset.giatRealMime || ""; if (realMime.includes("webm")) mimeTypeStr = "WEBM"; else if (realMime.includes("mp4")) mimeTypeStr = "MP4"; else { const clean = (lightboxVideo.src || "").split("?")[0].split("#")[0].toLowerCase(); if (clean.endsWith(".webm")) mimeTypeStr = "WEBM"; else if (clean.endsWith(".mp4") || clean.endsWith(".m4v")) mimeTypeStr = "MP4"; else mimeTypeStr = "VIDEO"; } } else if (lightboxImg.src.startsWith("blob:")) mimeTypeStr = getSimpleTypeName(lightboxImg.dataset.giatRealMime || ""); else if (currentServerMime) mimeTypeStr = getSimpleTypeName(currentServerMime); else mimeTypeStr = inferTypeFromUrl(lightboxImg.src || ""); const activeMediaEl = isVideoMode ? lightboxVideo : lightboxImg; const imgurl = activeMediaEl.dataset.giatOriginalUrl || activeMediaEl.src || ""; const parsedMeta = resolveTimeForensics(imgurl, activeMediaEl.exifRawData || null, currentServerDate, currentFirstTrackDate); timeCache.set(imgurl, parsedMeta); if (lightboxTypeBadge) { const exif = activeMediaEl.exifRawData || null; bindMetadataToBadge(lightboxTypeBadge, activeMediaEl, parsedMeta, exif); } const parts = []; const w = isYtMode ? "1920" : isVideoMode ? lightboxVideo.videoWidth ? lightboxVideo.videoWidth.toString() : lightboxVideo.dataset.giatNaturalWidth : lightboxImg.dataset.giatNaturalWidth; const h = isYtMode ? "1080" : isVideoMode ? lightboxVideo.videoHeight ? lightboxVideo.videoHeight.toString() : lightboxVideo.dataset.giatNaturalHeight : lightboxImg.dataset.giatNaturalHeight; if (config.enableLightboxResolution && w && h) parts.push(`${w} × ${h}`); if (config.enableLightboxMime && mimeTypeStr) parts.push(mimeTypeStr); if (config.enableLightboxFileSize && activeMediaEl.dataset.giatFileSize) parts.push(activeMediaEl.dataset.giatFileSize); if (config.enableLightboxDate && parsedMeta.primaryText) parts.push(parsedMeta.primaryText); const finalBadgeStr = parts.join(" · "); let pillHtml = ""; if (lightboxImg.exifRawData?.ai?.isAI) { const aiConf = lightboxImg.exifRawData.ai.confidence; const aiConfStr = aiConf === "high" ? t("aiHighConf") : t("aiMedConf"); pillHtml = `<span style="${`background: ${aiConf === "high" ? "#ea4335" : "#fbbc05"}; color: #ffffff; padding: 2px 6px; border-radius: 4px; font-weight: bold; font-size: 11px; display: inline-block; vertical-align: middle; margin-right: 6px;`}">AI (${aiConfStr})</span>`; } let hasExtraInfo = false; if (config.enableLightboxDate) { const currentExif = parsedMeta.exifRaw; if (parsedMeta) hasExtraInfo = !!(parsedMeta.shoot || parsedMeta.digitized || parsedMeta.modify || parsedMeta.lastModified || parsedMeta.googleBadge || currentExif?.lensModel || currentExif?.focalLength || currentExif?.software || currentExif?.flash || currentExif?.fNumber || currentExif?.exposureTime || currentExif?.iso || currentExif?.gpsLatitude || currentExif?.ai?.isAI || currentExif?.c2paActions); } if (config.enableLightboxColorAnalysis) hasExtraInfo = true; if (lightboxTypeBadge) { if (hasExtraInfo) lightboxTypeBadge.classList.add("giat-has-metadata"); else lightboxTypeBadge.classList.remove("giat-has-metadata"); lightboxTypeBadge.innerHTML = (pillHtml ? pillHtml + " " : "") + finalBadgeStr; } } function showLightbox(imgurl, w, h, activeResultEl) { setActiveTargetUrl(imgurl); destroyYouTubePlayback(); const ytVideoId = extractYouTubeVideoId(imgurl, activeResultEl?.dataset.giatSourceUrl); setActiveYouTubeVideoId(ytVideoId); const isAutoPlayYt = !!(ytVideoId && config.enableYouTubeAutoplay !== false); if (lightboxYtBtn) { if (ytVideoId) { lightboxYtBtn.style.display = "flex"; lightboxYtBtn.classList.remove("giat-yt-dimmed"); } else lightboxYtBtn.style.display = "none"; } if (lightboxWrap && lightboxWrap.classList.contains("show") && lightboxImg) { const slideInClass = (activeCarouselDirection || "next") === "next" ? "giat-slide-in-right" : "giat-slide-in-left"; lightboxImg.classList.remove("giat-slide-in-right", "giat-slide-in-left", "giat-slide-out-left", "giat-slide-out-right"); lightboxImg.offsetWidth; lightboxImg.classList.add(slideInClass); setTimeout(() => { lightboxImg.classList.remove(slideInClass); }, 280); activeCarouselDirection = null; } let mainTargetEl = null; if (activeResultEl) { if (activeResultEl.closest(GOOGLE_SELECTORS.SIDEBAR_CONTAINER)) { const tbnid = activeResultEl.dataset.giatTbnid || activeResultEl.getAttribute("data-docid") || ""; if (tbnid) mainTargetEl = document.querySelector(`div[data-giat-result][jsdata*="${tbnid}"], div[data-giat-result][data-docid="${tbnid}"]`); if (!mainTargetEl) { const results = document.querySelectorAll("div[data-giat-result]"); for (let i = 0; i < results.length; i++) { const res = results[i]; if (res.closest(GOOGLE_SELECTORS.SIDEBAR_CONTAINER)) continue; if (res.dataset.giatImgurl === imgurl || res.dataset.giatRawOriginalUrl === imgurl) { mainTargetEl = res; break; } } } } else mainTargetEl = activeResultEl; } if (!mainTargetEl && imgurl) { const results = document.querySelectorAll("div[data-giat-result]"); for (let i = 0; i < results.length; i++) { const res = results[i]; if (res.closest(GOOGLE_SELECTORS.SIDEBAR_CONTAINER)) continue; if (res.dataset.giatImgurl === imgurl || res.dataset.giatRawOriginalUrl === imgurl) { mainTargetEl = res; break; } } } if (mainTargetEl) { currentResultEl = mainTargetEl; markAsVisited(mainTargetEl.dataset.giatDocid || mainTargetEl.getAttribute("data-docid") || void 0, imgurl, mainTargetEl); document.querySelectorAll("[data-giat-result].giat-result-active").forEach((el) => { el.classList.remove("giat-result-active"); }); mainTargetEl.classList.add("giat-result-active"); if (config.enableLightboxKeys) mainTargetEl.scrollIntoView({ behavior: "smooth", block: "nearest" }); } else if (imgurl) markAsVisited(void 0, imgurl, null); delete lightboxImg.dataset.giatRealMime; delete lightboxImg.dataset.giatFileSize; delete lightboxImg.dataset.giatNaturalWidth; delete lightboxImg.dataset.giatNaturalHeight; delete lightboxImg.dataset.giatExif; delete lightboxImg.dataset.giatSourceUrl; delete lightboxImg.exifRawData; delete lightboxVideo.dataset.giatRealMime; delete lightboxVideo.dataset.giatFileSize; delete lightboxVideo.dataset.giatNaturalWidth; delete lightboxVideo.dataset.giatNaturalHeight; delete lightboxVideo.exifRawData; const rawOriginalUrl = activeResultEl?.dataset.giatRawOriginalUrl || imgurl; lightboxImg.dataset.giatOriginalUrl = imgurl; lightboxImg.dataset.giatRawOriginalUrl = rawOriginalUrl; lightboxImg.dataset.giatSourceUrl = activeResultEl?.dataset.giatSourceUrl || ""; lightboxVideo.dataset.giatOriginalUrl = imgurl; lightboxVideo.dataset.giatRawOriginalUrl = rawOriginalUrl; const rawDate = activeResultEl ? activeResultEl.dataset.giatDate || null : null; currentFirstTrackDate = rawDate ? filterDateOnly(rawDate) : null; setCurrentServerDate(null); setCurrentServerMime(null); if (activeResultEl && activeResultEl.dataset.giatFileSize) { lightboxImg.dataset.giatFileSize = activeResultEl.dataset.giatFileSize; lightboxVideo.dataset.giatFileSize = activeResultEl.dataset.giatFileSize; } if (timeCache.get(imgurl)) updateBadgeText(); const thumbUrl = getThumbnailUrl(); if (thumbUrl) { lightboxThumbImg.src = thumbUrl; lightboxThumbImg.style.opacity = "1"; } else { lightboxThumbImg.src = "about:blank"; lightboxThumbImg.style.opacity = "0"; } const needServerDate = config.enableLightboxDate; const needServerMime = config.enableLightboxMime; const needServerExif = config.enableLightboxExif; if (needServerDate || needServerMime || needServerExif) fetchServerMetadata(imgurl).then((meta) => { if (meta) { serverMetadataCache.set(imgurl, meta); const currentTbnid = getTbnidFromUrl(); if (currentTbnid) serverMetadataCache.set(currentTbnid, meta); if (meta.exifRaw) lightboxImg.exifRawData = meta.exifRaw; if (needServerDate) setCurrentServerDate(meta.date); if (needServerMime) setCurrentServerMime(meta.mime); if (needServerExif && meta.exif) lightboxImg.dataset.giatExif = meta.exif; if (meta.fileSize) { lightboxImg.dataset.giatFileSize = meta.fileSize; lightboxVideo.dataset.giatFileSize = meta.fileSize; } let targetResultEl = activeResultEl; if (!targetResultEl && imgurl) { const cleanUrlForCompare = (urlStr) => { if (!urlStr) return ""; try { const u = new URL(urlStr); return (u.origin + u.pathname).toLowerCase(); } catch (e) { return urlStr.split("?")[0].split("#")[0].toLowerCase(); } }; const targetClean = cleanUrlForCompare(imgurl); if (targetClean) { const results = document.querySelectorAll("div[data-giat-result]"); for (let i = 0; i < results.length; i++) { const res = results[i]; if (cleanUrlForCompare(res.dataset.giatImgurl || "") === targetClean) { targetResultEl = res; break; } } } } if (targetResultEl) { if (meta.fileSize) { targetResultEl.dataset.giatFileSize = meta.fileSize; targetResultEl.setAttribute("data-giat-filesize", meta.fileSize); } if (meta.mime) { const friendlyMime = getSimpleTypeName(meta.mime); if (friendlyMime) targetResultEl.dataset.giatMimeType = friendlyMime; } updateDimsText(targetResultEl); } } if (lightboxImg.src === imgurl || lightboxImg.src.startsWith("blob:") || lightboxVideo.src === imgurl || lightboxVideo.src.startsWith("blob:")) updateBadgeText(); }); lightboxBackdrop.classList.add("show"); lightboxWrap.classList.add("show"); if (!isAutoPlayYt) { lightboxWrap.classList.add("loading"); if (w && h) { lightboxWrap.style.setProperty("--img-w", w.toString()); lightboxWrap.style.setProperty("--img-h", h.toString()); } } else { lightboxWrap.classList.remove("loading"); startYouTubePlayback(ytVideoId); } lightboxWrap.classList.remove("error"); lightboxError.innerHTML = ""; document.body.classList.add("giat-no-scroll"); resetZoomPan(); if (lightboxImg && lightboxImg.src && lightboxImg.src.startsWith("blob:")) URL.revokeObjectURL(lightboxImg.src); if (lightboxVideo && lightboxVideo.src && lightboxVideo.src.startsWith("blob:")) URL.revokeObjectURL(lightboxVideo.src); lightboxImg.style.opacity = "0"; lightboxVideo.style.opacity = "0"; const isVideo = isVideoUrlOrMime(imgurl, currentServerMime || activeResultEl?.dataset.giatMimeType); const renderCustomErrorUI = (title, desc) => { showLightboxProgress(false); lightboxWrap.classList.remove("loading"); lightboxWrap.classList.add("error"); lightboxError.innerHTML = ` <div class="giat-error-content"> <svg viewBox="0 0 24 24" width="40" height="40" style="fill: #ea4335;"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg> <div class="giat-error-title">${title}</div> <div class="giat-error-desc">${desc}</div> <a href="${imgurl}" target="_blank" class="giat-error-link">${t("errOpenNewTab")}</a> </div> `; }; const setupVideoPlayback = (videoSourceUrl, realMimeType) => { lightboxBackdrop.classList.add("giat-video-mode"); lightboxImg.style.display = "none"; lightboxImg.removeAttribute("src"); lightboxVideo.style.display = "block"; if (realMimeType) lightboxVideo.dataset.giatRealMime = realMimeType; lightboxVideo.onloadedmetadata = () => { showLightboxProgress(false); const realW = lightboxVideo.videoWidth; const realH = lightboxVideo.videoHeight; lightboxWrap.classList.remove("loading"); lightboxVideo.style.opacity = "1"; lightboxThumbImg.style.opacity = "0"; if (realW && realH) { lightboxWrap.style.setProperty("--img-w", realW.toString()); lightboxWrap.style.setProperty("--img-h", realH.toString()); lightboxVideo.dataset.giatNaturalWidth = realW.toString(); lightboxVideo.dataset.giatNaturalHeight = realH.toString(); } if (currentResultEl && realW && realH) { currentResultEl.dataset.giatWidth = realW.toString(); currentResultEl.dataset.giatHeight = realH.toString(); updateDimsText(currentResultEl); } updateBadgeText(); try { lightboxVideo.play().catch(() => {}); } catch (e) {} }; lightboxVideo.onerror = () => { showLightboxProgress(false); const rawOriginal = lightboxVideo.dataset.giatRawOriginalUrl; if (rawOriginal && lightboxVideo.src !== rawOriginal && !lightboxVideo.src.startsWith("blob:")) { lightboxWrap.classList.add("loading"); lightboxVideo.src = rawOriginal; return; } renderCustomErrorUI(t("errFailedLoad"), t("errFailedLoad")); }; lightboxVideo.src = videoSourceUrl; }; let isUnsupported = false; try { const hn = new URL(imgurl).hostname; isUnsupported = hn.includes("lookaside") || hn.includes("tiktok.com"); } catch (e) {} const forceBlobLoad = config.enableLightboxForceBlob && !isUnsupported; if (isVideo) { if (forceBlobLoad && typeof GM_xmlhttpRequest !== "undefined") { const currentHandlerUrl = imgurl; const rawOriginal = rawOriginalUrl; showLightboxProgress(true); fetchImageBlobWithFallback(imgurl, rawOriginal, (percent) => { updateLightboxProgress(percent); }).then(({ blob, finalUrl }) => { if (activeTargetUrl !== currentHandlerUrl) return; lightboxVideo.dataset.giatOriginalUrl = finalUrl; const blobUrl = URL.createObjectURL(blob); setupVideoPlayback(blobUrl, blob.type); }).catch(() => { if (activeTargetUrl === currentHandlerUrl) setupVideoPlayback(rawOriginal || imgurl); }); } else setupVideoPlayback(imgurl); } else { lightboxBackdrop.classList.remove("giat-video-mode"); lightboxVideo.pause(); lightboxVideo.removeAttribute("src"); lightboxVideo.style.display = "none"; lightboxImg.style.display = "block"; if (forceBlobLoad && typeof GM_xmlhttpRequest !== "undefined") { const currentHandlerUrl = imgurl; const rawOriginal = rawOriginalUrl; showLightboxProgress(true); fetchImageBlobWithFallback(imgurl, rawOriginal, (percent) => { updateLightboxProgress(percent); }).then(({ blob, finalUrl }) => { if (activeTargetUrl !== currentHandlerUrl) return; if (blob.type.startsWith("video/")) { const blobUrl = URL.createObjectURL(blob); setupVideoPlayback(blobUrl, blob.type); return; } lightboxImg.dataset.giatOriginalUrl = finalUrl; lightboxImg.dataset.giatRealMime = blob.type; const blobUrl = URL.createObjectURL(blob); if (activeTargetUrl !== currentHandlerUrl) { URL.revokeObjectURL(blobUrl); return; } lightboxWrap.classList.add("loading"); lightboxImg.src = blobUrl; }).catch((err) => { if (activeTargetUrl === currentHandlerUrl) { console.warn("[ShowDims] Fallback fetch chain also failed completely:", err); lightboxImg.src = rawOriginal || imgurl; } }); } else lightboxImg.src = imgurl; } lightboxImg.onload = () => { showLightboxProgress(false); const realW = lightboxImg.naturalWidth; const realH = lightboxImg.naturalHeight; if (realW === 1 && realH === 1) { console.warn("[ShowDims] Detected 1x1 placeholder redirect in lightbox."); const rawOriginal = lightboxImg.dataset.giatRawOriginalUrl; if (rawOriginal && lightboxImg.src !== rawOriginal && !lightboxImg.src.startsWith("blob:")) { lightboxWrap.classList.add("loading"); lightboxImg.src = rawOriginal; return; } const thumbImg = currentResultEl?.querySelector("img"); if (thumbImg && thumbImg.src && lightboxImg.src !== thumbImg.src) lightboxImg.src = thumbImg.src; renderCustomErrorUI(t("errFailedLoad"), t("errFailedLoad")); return; } if (realW && realH) { lightboxImg.dataset.giatNaturalWidth = realW.toString(); lightboxImg.dataset.giatNaturalHeight = realH.toString(); if (currentResultEl) { currentResultEl.dataset.giatWidth = realW.toString(); currentResultEl.dataset.giatHeight = realH.toString(); updateDimsText(currentResultEl); } } if (getActiveYouTubeIframe() || lightboxBackdrop && lightboxBackdrop.classList.contains("giat-yt-playing")) { lightboxWrap.classList.remove("loading"); return; } lightboxWrap.classList.remove("loading"); lightboxImg.style.opacity = "1"; lightboxThumbImg.style.opacity = "0"; if (realW && realH) { lightboxWrap.style.setProperty("--img-w", realW.toString()); lightboxWrap.style.setProperty("--img-h", realH.toString()); } if (currentResultEl && realW && realH) { currentResultEl.dataset.giatWidth = realW.toString(); currentResultEl.dataset.giatHeight = realH.toString(); updateDimsText(currentResultEl); const detailBadge = document.querySelector(".giat-detail-type-badge"); if (detailBadge) { const cachedMeta = lightboxImg.dataset.giatFileSize ? { fileSize: lightboxImg.dataset.giatFileSize, mime: lightboxImg.dataset.giatRealMime } : null; const fileSize = cachedMeta?.fileSize || currentResultEl.dataset.giatFileSize || ""; const rawMime = cachedMeta?.mime || currentResultEl.dataset.giatMimeType; const mimeType = rawMime ? getSimpleTypeName(rawMime) : inferTypeFromUrl(lightboxImg.dataset.giatOriginalUrl || lightboxImg.src || ""); const dateText = currentResultEl.dataset.giatDate || ""; const parts = []; if (config.enableThumbResolution) parts.push(`${realW} × ${realH}`); if (config.enableThumbMime && mimeType) parts.push(mimeType); if (config.enableThumbFileSize && fileSize) parts.push(fileSize); if (config.enableThumbBadges && dateText) parts.push(dateText); const badgeStr = parts.join(" · "); const icon = detailBadge.querySelector("svg"); detailBadge.textContent = ""; if (icon) detailBadge.appendChild(icon); detailBadge.appendChild(document.createTextNode((icon ? " " : "") + badgeStr)); detailBadge.title = badgeStr; } } updateBadgeText(); if (currentResultEl && !lightboxImg.dataset.giatFileSize && !currentResultEl.dataset.giatFileSize && !lightboxImg.src.startsWith("blob:")) fetchServerMetadata(imgurl).then((meta) => { if (meta && meta.fileSize) { lightboxImg.dataset.giatFileSize = meta.fileSize; if (meta.mime) lightboxImg.dataset.giatRealMime = meta.mime; updateBadgeText(); } }).catch((e) => { console.warn("[ShowDims] Failed to fetch fallback server metadata onload:", e); }); }; lightboxImg.onerror = () => { showLightboxProgress(false); const rawOriginal = lightboxImg.dataset.giatRawOriginalUrl; const currentOptUrl = lightboxImg.dataset.giatOriginalUrl || ""; if (isVideoUrlOrMime(currentOptUrl) || isVideoUrlOrMime(rawOriginal || "")) { setupVideoPlayback(currentOptUrl || rawOriginal || imgurl); return; } if (rawOriginal && lightboxImg.src !== rawOriginal && !lightboxImg.src.startsWith("blob:")) { lightboxWrap.classList.add("loading"); if (typeof GM_xmlhttpRequest !== "undefined") { const currentHandlerUrl = activeTargetUrl; showLightboxProgress(true); fetchImageBlobWithFallback(currentOptUrl, rawOriginal, (percent) => { updateLightboxProgress(percent); }).then(({ blob, finalUrl }) => { if (activeTargetUrl !== currentHandlerUrl) return; if (blob.type.startsWith("video/")) { const blobUrl = URL.createObjectURL(blob); setupVideoPlayback(blobUrl, blob.type); return; } lightboxImg.dataset.giatOriginalUrl = finalUrl; lightboxImg.dataset.giatRealMime = blob.type; const blobUrl = URL.createObjectURL(blob); if (activeTargetUrl !== currentHandlerUrl) { URL.revokeObjectURL(blobUrl); return; } lightboxWrap.classList.add("loading"); lightboxImg.src = blobUrl; }).catch(() => { if (activeTargetUrl === currentHandlerUrl) lightboxImg.src = rawOriginal; }); } else lightboxImg.src = rawOriginal; return; } if (lightboxImg.src.startsWith("blob:")) { showErrorUI(); return; } if (typeof GM_xmlhttpRequest !== "undefined") { const currentHandlerUrl = imgurl; showLightboxProgress(true); fetchImageBlob(imgurl, (percent) => { updateLightboxProgress(percent); }).then((blob) => { if (activeTargetUrl !== currentHandlerUrl) return; lightboxImg.dataset.giatRealMime = blob.type; const blobUrl = URL.createObjectURL(blob); if (activeTargetUrl !== currentHandlerUrl) { URL.revokeObjectURL(blobUrl); return; } lightboxWrap.classList.add("loading"); lightboxImg.src = blobUrl; }).catch((err) => { if (activeTargetUrl === currentHandlerUrl) { console.warn("All hotlink bypass strategies failed:", err); showErrorUI(err); } }); } else showErrorUI(); function showErrorUI(errObj) { showLightboxProgress(false); lightboxWrap.classList.remove("loading"); lightboxWrap.classList.add("error"); const targetErr = errObj; if (targetErr) { const titleText = getFailureReasonFromError(targetErr); lightboxError.innerHTML = ` <div class="giat-error-content"> <svg viewBox="0 0 24 24" width="40" height="40" style="fill: #ea4335;"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg> <div class="giat-error-title">${titleText}</div> <div class="giat-error-desc">${t("errHotlinkDesc")}</div> <a href="${imgurl}" target="_blank" class="giat-error-link">${t("errOpenNewTab")}</a> </div> `; return; } const renderErrorDetails = (status) => { let title = t("errFailedLoad"); let desc = t("errUnexpected"); if (status === 403 || status === 406) { title = t("errHotlinkTitle"); desc = t("errHotlinkDesc"); } else if (status === 404 || status === 410) { title = t("errNotFoundTitle"); desc = t("errNotFoundDesc"); } else if (status >= 500 && status <= 599) { title = t("errServerTitle"); desc = `${t("errServerDesc")} (HTTP ${status}).`; } else if (status === 0) { title = t("errTimeoutTitle"); desc = t("errTimeoutDesc"); } lightboxError.innerHTML = ` <div class="giat-error-content"> <svg viewBox="0 0 24 24" width="40" height="40" style="fill: #ea4335;"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg> <div class="giat-error-title">${title}</div> <div class="giat-error-desc">${desc}</div> <a href="${imgurl}" target="_blank" class="giat-error-link">${t("errOpenNewTab")}</a> </div> `; }; if (typeof GM_xmlhttpRequest !== "undefined") { const origin = new URL(imgurl).origin; const headers = {}; if (origin) headers["referer"] = imgurl; GM_xmlhttpRequest({ method: "GET", url: imgurl, headers, anonymous: true, onload: (response) => renderErrorDetails(response.status), onerror: () => renderErrorDetails(0) }); } else renderErrorDetails(-1); } }; document.addEventListener("keydown", onLightboxKeydown, true); } function onLightboxKeydown(e) { handleLightboxKeydown(e, { lightboxError, lightboxVideo, lightboxDownloadBtn, lightboxCopyImgBtn, currentResultEl, hideLightbox, showLightbox, resetZoomPan, setCarouselDirection }); } function hideLightbox() { removeTooltip(); destroyYouTubePlayback(); closeHotkeyHud(); if (lightboxTypeBadge) lightboxTypeBadge.classList.remove("giat-has-metadata"); lightboxBackdrop.classList.remove("show"); lightboxBackdrop.classList.remove("giat-video-mode"); lightboxWrap.classList.remove("show"); document.body.classList.remove("giat-no-scroll"); document.querySelectorAll("[data-giat-result].giat-result-active").forEach((el) => { el.classList.remove("giat-result-active"); }); setTimeout(() => { lightboxImg.removeAttribute("src"); if (lightboxVideo) { try { lightboxVideo.pause(); lightboxVideo.removeAttribute("src"); lightboxVideo.load(); } catch (e) {} lightboxVideo.style.display = "none"; } lightboxThumbImg.removeAttribute("src"); }, 200); currentResultEl = null; document.removeEventListener("keydown", onLightboxKeydown, true); } function updateLightboxProgress(percent) { if (!lightboxProgress) return; const ringFg = lightboxProgress.querySelector(".giat-ring-fg"); const textVal = lightboxProgress.querySelector(".giat-progress-text"); if (ringFg) ringFg.setAttribute("stroke-dasharray", `${percent}, 100`); if (textVal) textVal.textContent = `${percent}%`; } function showLightboxProgress(show) { if (!lightboxProgress) return; if (show) { updateLightboxProgress(0); lightboxProgress.classList.add("show"); } else lightboxProgress.classList.remove("show"); } function initLightbox() { const dom = createLightboxDom(); lightboxBackdrop = dom.lightboxBackdrop; lightboxWrap = dom.lightboxWrap; dom.lightboxShimmer; lightboxError = dom.lightboxError; lightboxDownloadBtn = dom.lightboxDownloadBtn; lightboxCopyImgBtn = dom.lightboxCopyImgBtn; lightboxCopyB64Btn = dom.lightboxCopyB64Btn; lightboxLensBtn = dom.lightboxLensBtn; lightboxTineyeBtn = dom.lightboxTineyeBtn; lightboxAiBtn = dom.lightboxAiBtn; lightboxTypeBadge = dom.lightboxTypeBadge; lightboxBtnContainer = dom.lightboxBtnContainer; lightboxThumbImg = dom.lightboxThumbImg; lightboxImg = dom.lightboxImg; lightboxVideo = dom.lightboxVideo; lightboxYtBtn = dom.lightboxYtBtn; lightboxProgress = dom.lightboxProgress; lightboxPhotopeaBtn = dom.lightboxPhotopeaBtn; lightboxVectorpeaBtn = dom.lightboxVectorpeaBtn; lightboxYandexBtn = dom.lightboxYandexBtn; lightboxBingBtn = dom.lightboxBingBtn; initNetworkState(lightboxImg); initGestureState(lightboxBackdrop, lightboxWrap, lightboxImg, lightboxVideo); initYouTubePlayerEvents(getYouTubeContext()); initHotkeyTracking(); setDetailPanelCallbacks({ showLightbox, getSimpleTypeName, inferTypeFromUrl, getCurrentResultEl: () => currentResultEl }); lightboxBackdrop.onclick = () => { hideLightbox(); }; const getActiveMediaUrl = () => { if (lightboxVideo && lightboxVideo.style.display !== "none" && lightboxVideo.src && !lightboxVideo.src.startsWith("about:")) return lightboxVideo.src; if (lightboxImg && lightboxImg.src && !lightboxImg.src.startsWith("about:")) return lightboxImg.src; return ""; }; const handleDownload = () => { const mediaUrl = getActiveMediaUrl(); if (mediaUrl) downloadImage(mediaUrl, lightboxDownloadBtn, void 0, { title: lightboxBtnContainer && lightboxBtnContainer.dataset.giatTitleText || currentResultEl && currentResultEl.dataset.giatTitle || lightboxImg && (lightboxImg.dataset.giatTitle || lightboxImg.alt) || void 0, domain: currentResultEl && currentResultEl.dataset.giatDomain || void 0, width: currentResultEl && currentResultEl.dataset.giatWidth || lightboxImg && (lightboxImg.dataset.giatNaturalWidth || lightboxImg.naturalWidth) || void 0, height: currentResultEl && currentResultEl.dataset.giatHeight || lightboxImg && (lightboxImg.dataset.giatNaturalHeight || lightboxImg.naturalHeight) || void 0, rank: currentResultEl && currentResultEl.dataset.giatSerpRank || void 0, index: currentResultEl && currentResultEl.dataset.giatSerpRank || void 0 }); }; lightboxDownloadBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleDownload(); }); lightboxDownloadBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleDownload(); } }); lightboxDownloadBtn.onmousedown = preventMiddleScroll; const handleCopyImg = () => { const mediaUrl = getActiveMediaUrl(); if (mediaUrl) copyImageToClipboard(mediaUrl, lightboxCopyImgBtn); }; lightboxCopyImgBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleCopyImg(); }); lightboxCopyImgBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleCopyImg(); } }); lightboxCopyImgBtn.onmousedown = preventMiddleScroll; const handleCopyB64 = () => { const mediaUrl = getActiveMediaUrl(); if (mediaUrl) copyBase64ToClipboard(mediaUrl, lightboxCopyB64Btn); }; lightboxCopyB64Btn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleCopyB64(); }); lightboxCopyB64Btn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleCopyB64(); } }); lightboxCopyB64Btn.onmousedown = preventMiddleScroll; const handleLensSearch = () => { if (lightboxImg && lightboxImg.src && !lightboxImg.src.startsWith("about:")) openInLens(lightboxImg.dataset.giatOriginalUrl || lightboxImg.src, lightboxImg.dataset.giatRawOriginalUrl); }; lightboxLensBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleLensSearch(); }); lightboxLensBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleLensSearch(); } }); lightboxLensBtn.onmousedown = preventMiddleScroll; const handleTineyeSearch = () => { if (lightboxImg && lightboxImg.src && !lightboxImg.src.startsWith("about:")) openInTineye(lightboxImg.dataset.giatOriginalUrl || lightboxImg.src, lightboxImg.dataset.giatRawOriginalUrl); }; lightboxTineyeBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleTineyeSearch(); }); lightboxTineyeBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleTineyeSearch(); } }); lightboxTineyeBtn.onmousedown = preventMiddleScroll; const handleAiSearchClick = () => { if (lightboxImg && lightboxImg.src && !lightboxImg.src.startsWith("about:")) { const imgUrl = lightboxImg.src || ""; openInAiSearch(imgUrl.startsWith("blob:") ? lightboxImg.dataset.giatOriginalUrl || imgUrl : imgUrl, currentResultEl ? (currentResultEl.querySelector(GOOGLE_SELECTORS.THUMB_CLASS)?.textContent || "").trim() : "", currentResultEl?.dataset.giatSourceUrl || "", lightboxImg.dataset.giatRawOriginalUrl); } }; lightboxAiBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleAiSearchClick(); }); lightboxAiBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleAiSearchClick(); } }); lightboxAiBtn.onmousedown = preventMiddleScroll; const handlePhotopeaOpen = () => { if (lightboxImg && lightboxImg.src && !lightboxImg.src.startsWith("about:")) openInPhotopea(lightboxImg.dataset.giatOriginalUrl || lightboxImg.src, lightboxImg.dataset.giatRawOriginalUrl); }; lightboxPhotopeaBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handlePhotopeaOpen(); }); lightboxPhotopeaBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handlePhotopeaOpen(); } }); lightboxPhotopeaBtn.onmousedown = preventMiddleScroll; const handleVectorpeaOpen = () => { if (lightboxImg && lightboxImg.src && !lightboxImg.src.startsWith("about:")) openInVectorpea(lightboxImg.dataset.giatOriginalUrl || lightboxImg.src, lightboxImg.dataset.giatRawOriginalUrl); }; lightboxVectorpeaBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleVectorpeaOpen(); }); lightboxVectorpeaBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleVectorpeaOpen(); } }); lightboxVectorpeaBtn.onmousedown = preventMiddleScroll; const handleYandexOpen = () => { if (lightboxImg && lightboxImg.src && !lightboxImg.src.startsWith("about:")) openInYandex(lightboxImg.dataset.giatOriginalUrl || lightboxImg.src, lightboxImg.dataset.giatRawOriginalUrl); }; lightboxYandexBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleYandexOpen(); }); lightboxYandexBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleYandexOpen(); } }); lightboxYandexBtn.onmousedown = preventMiddleScroll; const handleBingOpen = () => { if (lightboxImg && lightboxImg.src && !lightboxImg.src.startsWith("about:")) openInBing(lightboxImg.dataset.giatOriginalUrl || lightboxImg.src, lightboxImg.dataset.giatRawOriginalUrl); }; lightboxBingBtn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); handleBingOpen(); }); lightboxBingBtn.addEventListener("auxclick", (e) => { if (e.button === 1) { e.stopPropagation(); e.preventDefault(); handleBingOpen(); } }); lightboxBingBtn.onmousedown = preventMiddleScroll; lightboxTypeBadge.addEventListener("mouseenter", (e) => { cancelRemoveTooltip(); showMetadataTooltip(lightboxTypeBadge, e); }); lightboxTypeBadge.addEventListener("mouseleave", () => { requestRemoveTooltip(); }); } function openSettingsPanel(lightboxDownloadBtn, lightboxCopyImgBtn, lightboxCopyB64Btn, lightboxWrap, lightboxLensBtn = null, lightboxTineyeBtn = null, lightboxAiBtn = null) { if (document.querySelector(".giat-settings-overlay")) return; const overlay = document.createElement("div"); overlay.classList.add("giat-settings-overlay"); const isDark = config.uiTheme === "auto" ? isPageDark() : config.uiTheme === "dark"; overlay.classList.add(isDark ? "giat-theme-dark" : "giat-theme-light"); const panel = document.createElement("div"); panel.classList.add("giat-settings-panel"); panel.innerHTML = ` <div class="giat-settings-header"> <h3>${t("settings")}</h3> <button class="giat-settings-close">×</button> </div> <div class="giat-settings-body"> <!-- Group 1: Core & System Controls --> <div class="giat-settings-group-title">${t("groupCoreEngine")}</div> <div class="giat-settings-item"> <label>${t("enableUrlOptimization")} <span class="giat-info-icon" data-tooltip="${t("noteUrlOptimization")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableUrlOptimization" ${config.enableUrlOptimization ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("settingsLang")}</label> <select id="giat-opt-userLanguage"> <option value="auto" ${config.userLanguage === "auto" ? "selected" : ""}>${t("langAuto")}</option> <option value="en" ${config.userLanguage === "en" ? "selected" : ""}>English</option> <option value="zh-TW" ${config.userLanguage === "zh-TW" ? "selected" : ""}>繁體中文 (台灣)</option> <option value="ja" ${config.userLanguage === "ja" ? "selected" : ""}>日本語</option> </select> </div> <div class="giat-settings-item"> <label>${t("uiThemeLabel")}</label> <select id="giat-opt-uiTheme"> <option value="auto" ${config.uiTheme === "auto" ? "selected" : ""}>${t("themeAuto")}</option> <option value="dark" ${config.uiTheme === "dark" ? "selected" : ""}>${t("themeDark")}</option> <option value="light" ${config.uiTheme === "light" ? "selected" : ""}>${t("themeLight")}</option> </select> </div> <div class="giat-settings-item"> <label>${t("clickActionLabel")} <span class="giat-info-icon" data-tooltip="${t("noteClickAction")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <select id="giat-opt-clickAction"> <option value="lightbox" ${config.clickAction === "lightbox" ? "selected" : ""}>${t("actionLightbox")}</option> <option value="tab" ${config.clickAction === "tab" ? "selected" : ""}>${t("actionTab")}</option> </select> </div> <!-- Group 2: Thumbnail Settings --> <div class="giat-settings-group-title">${t("groupThumb")}</div> <div class="giat-settings-item"> <label>${t("ctrlClickActionLabel")} <span class="giat-info-icon" data-tooltip="${t("noteCtrlClickAction")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <select id="giat-opt-ctrlClickAction"> <option value="raw_image" ${config.ctrlClickAction === "raw_image" ? "selected" : ""}>${t("ctrlActionRawImage")}</option> <option value="google_tab" ${config.ctrlClickAction === "google_tab" ? "selected" : ""}>${t("ctrlActionGoogleTab")}</option> </select> </div> <div class="giat-settings-item"> <label>${t("enableHoverInfo")} <span class="giat-info-icon" data-tooltip="${t("noteHoverInfo")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableHoverInfo" ${config.enableHoverInfo ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbResolution")}</label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbResolution" ${config.enableThumbResolution ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbFileSize")}</label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbFileSize" ${config.enableThumbFileSize ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbMime")} <span class="giat-info-icon" data-tooltip="${t("noteThumbMime")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbMime" ${config.enableThumbMime ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbBadges")} <span class="giat-info-icon" data-tooltip="${t("noteThumbBadges")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbBadges" ${config.enableThumbBadges ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbDownload")}</label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbDownload" ${config.enableThumbDownload ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableBatchSelect")}</label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableBatchSelect" ${config.enableBatchSelect ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-subpanel" id="giat-subpanel-batch" style="display: ${config.enableBatchSelect ? "flex" : "none"}"> <div class="giat-settings-item"> <label>${t("batchDownloadModeLabel")}</label> <select id="giat-opt-batchDownloadMode" class="giat-select"> <option value="zip" ${config.batchDownloadMode === "zip" ? "selected" : ""}>${t("batchModeZipOption")}</option> <option value="direct" ${config.batchDownloadMode === "direct" ? "selected" : ""}>${t("batchModeDirectOption")}</option> </select> </div> </div> <div class="giat-settings-item"> <label>${t("enableVisitedMark")} <span class="giat-info-icon" data-tooltip="${t("noteVisitedMark")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableVisitedMark" ${config.enableVisitedMark ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-subpanel" id="giat-subpanel-visited" style="display: ${config.enableVisitedMark ? "flex" : "none"}"> <div class="giat-settings-item"> <label>${t("visitedStyleModeLabel")}</label> <select id="giat-opt-visitedStyleMode" class="giat-select"> <option value="dim_desaturate" ${config.visitedStyleMode === "dim_desaturate" ? "selected" : ""}>${t("visitedModeDim")}</option> <option value="purple_border" ${config.visitedStyleMode === "purple_border" ? "selected" : ""}>${t("visitedModeBorder")}</option> <option value="visited_badge" ${config.visitedStyleMode === "visited_badge" ? "selected" : ""}>${t("visitedModeBadge")}</option> <option value="subtle_dim" ${config.visitedStyleMode === "subtle_dim" ? "selected" : ""}>${t("visitedModeSubtle")}</option> </select> </div> <div class="giat-settings-item" style="justify-content: space-between; align-items: center; padding-top: 6px;"> <div class="giat-visited-stats" style="font-size: 12px; opacity: 0.85; display: inline-flex; align-items: center; gap: 6px;"> <span>${t("visitedStatsLabel")}:</span> <span class="giat-visited-stats-badge" id="giat-visited-stats-badge" style="font-weight: 600; font-family: monospace; font-size: 11.5px; padding: 2px 7px; border-radius: 10px; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.15);">0 / 3000</span> </div> <button id="giat-btn-clear-visited" class="giat-settings-sub-btn"> ${t("clearVisitedBtn")} </button> </div> </div> <div class="giat-settings-item"> <label>${t("enableThumbCopy")}</label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbCopy" ${config.enableThumbCopy ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbB64")} <span class="giat-info-icon" data-tooltip="${t("noteBase64")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbB64" ${config.enableThumbB64 ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbTitleTooltip")} <span class="giat-info-icon" data-tooltip="${t("noteThumbTitleTooltip")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbTitleTooltip" ${config.enableThumbTitleTooltip ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbLens")} <span class="giat-info-icon" data-tooltip="${t("noteLens")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbLens" ${config.enableThumbLens ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbTineye")} <span class="giat-info-icon" data-tooltip="${t("noteTineye")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbTineye" ${config.enableThumbTineye ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbPhotopea")} <span class="giat-info-icon" data-tooltip="${t("notePhotopea")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbPhotopea" ${config.enableThumbPhotopea ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbVectorpea")} <span class="giat-info-icon" data-tooltip="${t("noteVectorpea")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbVectorpea" ${config.enableThumbVectorpea ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbYandex")} <span class="giat-info-icon" data-tooltip="${t("noteYandex")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbYandex" ${config.enableThumbYandex ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableThumbBing")} <span class="giat-info-icon" data-tooltip="${t("noteBing")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbBing" ${config.enableThumbBing ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("serpRankModeLabel")}</label> <select id="giat-opt-serpRankMode"> <option value="hover" ${config.serpRankMode === "hover" ? "selected" : ""}>${t("serpRankHover")}</option> <option value="always" ${config.serpRankMode === "always" ? "selected" : ""}>${t("serpRankAlways")}</option> <option value="never" ${config.serpRankMode === "never" ? "selected" : ""}>${t("serpRankNever")}</option> </select> </div> <div class="giat-settings-item"> <label>${t("labelPositionLabel")}</label> <select id="giat-opt-labelPosition"> <option value="bottom-right" ${config.labelPosition === "bottom-right" ? "selected" : ""}>${t("posBottomRight")}</option> <option value="bottom-left" ${config.labelPosition === "bottom-left" ? "selected" : ""}>${t("posBottomLeft")}</option> <option value="top-right" ${config.labelPosition === "top-right" ? "selected" : ""}>${t("posTopRight")}</option> <option value="top-left" ${config.labelPosition === "top-left" ? "selected" : ""}>${t("posTopLeft")}</option> </select> </div> <div class="giat-settings-item"> <label>${t("labelSizeLabel")}</label> <div style="display: flex; align-items: center; gap: 8px; width: 100%;"> <input type="range" id="giat-opt-labelSize" min="1" max="12" step="1" value="${config.labelSize}" style="flex: 1; accent-color: #1a73e8; cursor: pointer;"> <span id="giat-labelSize-value" style="font-weight: bold; min-width: 14px; text-align: center; font-size: 13px; color: #1a73e8;">${config.labelSize}</span> </div> </div> <div class="giat-settings-item"> <label>${t("thumbBtnSizeLabel")}</label> <div style="display: flex; align-items: center; gap: 8px; width: 100%;"> <input type="range" id="giat-opt-thumbBtnSize" min="1" max="12" step="1" value="${config.thumbBtnSize}" style="flex: 1; accent-color: #1a73e8; cursor: pointer;"> <span id="giat-thumbBtnSize-value" style="font-weight: bold; min-width: 14px; text-align: center; font-size: 13px; color: #1a73e8;">${config.thumbBtnSize}</span> </div> </div> <!-- Label Styling (Moved here for architectural consistency) --> <div class="giat-settings-item giat-settings-item-block"> <label>${t("customBgColor")}</label> <div style="display: flex; gap: 8px; align-items: center; width: 100%;"> <input type="text" id="giat-opt-customBgColor" value="${config.customBgColor}" placeholder="e.g. rgba(0, 0, 0, 0.6)" class="giat-text-input" style="flex: 1; min-width: 0;"> <input type="color" id="giat-opt-customBgColorPicker" value="${config.customBgColor.startsWith("#") && config.customBgColor.length === 7 ? config.customBgColor : "#202124"}" class="giat-color-picker" style="width: 32px; height: 32px; padding: 0; border: 1px solid rgba(0,0,0,0.15); border-radius: 4px; cursor: pointer; flex-shrink: 0;"> </div> <div class="giat-settings-help-text" style="margin-bottom: 2px;">${t("noteCustomColors")}</div> </div> <div class="giat-settings-item giat-settings-item-block"> <label>${t("customTextColor")}</label> <div style="display: flex; gap: 8px; align-items: center; width: 100%;"> <input type="text" id="giat-opt-customTextColor" value="${config.customTextColor}" placeholder="e.g. #ffffff" class="giat-text-input" style="flex: 1; min-width: 0;"> <input type="color" id="giat-opt-customTextColorPicker" value="${config.customTextColor.startsWith("#") && config.customTextColor.length === 7 ? config.customTextColor : "#ffffff"}" class="giat-color-picker" style="width: 32px; height: 32px; padding: 0; border: 1px solid rgba(0,0,0,0.15); border-radius: 4px; cursor: pointer; flex-shrink: 0;"> </div> <div class="giat-settings-help-text" style="margin-bottom: 2px;">${t("noteCustomColors")}</div> </div> <div class="giat-settings-item" id="giat-item-customBgOpacity"> <label>${t("customBgOpacity")} (<span id="giat-val-customBgOpacity">${config.customBgOpacity}%</span>)</label> <input type="range" id="giat-opt-customBgOpacity" min="0" max="100" step="5" value="${config.customBgOpacity}" style="width: 120px;"> </div> <!-- Group 3: Lightbox Settings --> <div class="giat-settings-group-title">${t("groupLightbox")}</div> <div class="giat-settings-item"> <label>${t("enableYouTubeAutoplay")} <span class="giat-info-icon" data-tooltip="${t("noteYouTubeAutoplay")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableYouTubeAutoplay" ${config.enableYouTubeAutoplay ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxForceBlob")} <span class="giat-info-icon" data-tooltip="${t("noteLightboxForceBlob")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxForceBlob" ${config.enableLightboxForceBlob ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxResolution")}</label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxResolution" ${config.enableLightboxResolution ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxFileSize")}</label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxFileSize" ${config.enableLightboxFileSize ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxMime")} <span class="giat-info-icon" data-tooltip="${t("noteLightboxMime")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxMime" ${config.enableLightboxMime ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxDate")} <span class="giat-info-icon" data-tooltip="${t("noteLightboxDate")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxDate" ${config.enableLightboxDate ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxExif")} <span class="giat-info-icon" data-tooltip="${t("noteLightboxExif")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxExif" ${config.enableLightboxExif ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxColorAnalysis")} <span class="giat-info-icon" data-tooltip="${t("noteLightboxColorAnalysis")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxColorAnalysis" ${config.enableLightboxColorAnalysis ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxKeys")} <span class="giat-info-icon" data-tooltip="${t("noteLightboxKeys")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxKeys" ${config.enableLightboxKeys ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-subpanel" id="giat-subpanel-keys" style="display: ${config.enableLightboxKeys ? "flex" : "none"}"> <div class="giat-settings-item"> <label>${t("lightboxPrevKey")}</label> <button class="giat-key-btn" id="giat-btn-prevKey" data-prop="lightboxPrevKey">${config.lightboxPrevKey}</button> </div> <div class="giat-settings-item"> <label>${t("lightboxNextKey")}</label> <button class="giat-key-btn" id="giat-btn-nextKey" data-prop="lightboxNextKey">${config.lightboxNextKey}</button> </div> <div class="giat-settings-item"> <label>${t("lightboxCloseKey")}</label> <button class="giat-key-btn" id="giat-btn-closeKey" data-prop="lightboxCloseKey">${config.lightboxCloseKey}</button> </div> </div> <div class="giat-settings-item"> <label>${t("lightboxBg")}</label> <select id="giat-opt-lightboxBg"> ${bgModes.map((m, i) => `<option value="${i}" ${i === config.currentBgIndex ? "selected" : ""}>${t(m.translationKey)}</option>`).join("")} </select> </div> <div class="giat-settings-item"> <label>${t("enableLightboxDownload")}</label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxDownload" ${config.enableLightboxDownload ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxCopy")}</label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxCopy" ${config.enableLightboxCopy ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxB64")} <span class="giat-info-icon" data-tooltip="${t("noteBase64")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxB64" ${config.enableLightboxB64 ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxLens")} <span class="giat-info-icon" data-tooltip="${t("noteLens")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxLens" ${config.enableLightboxLens ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxTineye")} <span class="giat-info-icon" data-tooltip="${t("noteTineye")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxTineye" ${config.enableLightboxTineye ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxPhotopea")} <span class="giat-info-icon" data-tooltip="${t("notePhotopea")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxPhotopea" ${config.enableLightboxPhotopea ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxVectorpea")} <span class="giat-info-icon" data-tooltip="${t("noteVectorpea")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxVectorpea" ${config.enableLightboxVectorpea ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxYandex")} <span class="giat-info-icon" data-tooltip="${t("noteYandex")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxYandex" ${config.enableLightboxYandex ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxBing")} <span class="giat-info-icon" data-tooltip="${t("noteBing")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxBing" ${config.enableLightboxBing ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <!-- Group 4: Storage & Download Settings (WebP & Filename Pattern) --> <div class="giat-settings-group-title">${t("groupWebpConversion")}</div> <div class="giat-settings-item"> <label>${t("enableWebpConversion")} <span class="giat-info-icon" data-tooltip="${t("noteWebpConversion")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableWebpConversion" ${config.enableWebpConversion ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <!-- WebP Subpanel for Progressive Disclosure --> <div class="giat-settings-subpanel" id="giat-subpanel-webp" style="display: ${config.enableWebpConversion ? "flex" : "none"}; flex-direction: column; width: 100%; gap: 12px; padding: 0;"> <div class="giat-settings-item" id="giat-item-webpFormat" style="width: 100%; border-bottom: none; padding: 4px 0;"> <label>${t("webpConversionFormat")}</label> <select id="giat-opt-webpConversionFormat"> <option value="jpeg" ${config.webpConversionFormat === "jpeg" ? "selected" : ""}>JPEG</option> <option value="png" ${config.webpConversionFormat === "png" ? "selected" : ""}>PNG</option> </select> </div> <div class="giat-settings-item" id="giat-item-webpQuality" style="display: ${config.webpConversionFormat === "jpeg" ? "flex" : "none"}; width: 100%; border-bottom: none; padding: 4px 0;"> <label>${t("webpConversionQuality")} (<span id="giat-val-webpQuality">${config.webpConversionQuality}%</span>)</label> <input type="range" id="giat-opt-webpConversionQuality" min="10" max="100" step="5" value="${config.webpConversionQuality}" style="width: 120px;"> </div> </div> <!-- Filename Pattern Settings --> <div class="giat-settings-group-title">${t("groupFilenamePattern")}</div> <div class="giat-settings-item" style="width: 100%; padding: 4px 0;"> <label>${t("filenamePatternMode")} <span class="giat-info-icon" data-tooltip="${t("noteFilenamePattern")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <select id="giat-opt-filenamePatternMode" style="max-width: 260px;"> <option value="original" ${config.filenamePatternMode === "original" ? "selected" : ""}>${t("patternOriginal")}</option> <option value="query_index" ${config.filenamePatternMode === "query_index" ? "selected" : ""}>${t("patternQueryIndex")}</option> <option value="title_dims" ${config.filenamePatternMode === "title_dims" ? "selected" : ""}>${t("patternTitleDims")}</option> <option value="domain_title" ${config.filenamePatternMode === "domain_title" ? "selected" : ""}>${t("patternDomainTitle")}</option> <option value="custom" ${config.filenamePatternMode === "custom" ? "selected" : ""}>${t("patternCustom")}</option> </select> </div> <div class="giat-settings-subpanel" id="giat-subpanel-filenamePattern" style="display: ${config.filenamePatternMode === "custom" ? "flex" : "none"}; flex-direction: column; width: 100%; gap: 8px; padding: 4px 0;"> <div class="giat-settings-item" style="width: 100%; border-bottom: none; padding: 2px 0;"> <label>${t("customFilenameTemplate")}</label> <input type="text" id="giat-opt-customFilenameTemplate" value="${config.customFilenameTemplate || "{query}_{index}"}" placeholder="{query}_{index}" style="width: 220px; padding: 5px 8px; border-radius: 6px; font-size: 12px; font-family: monospace;"> </div> <div class="giat-filename-chips"> <button type="button" class="giat-chip-btn" data-token="{query}">+ {${t("chipQuery")}}</button> <button type="button" class="giat-chip-btn" data-token="{domain}">+ {${t("chipDomain")}}</button> <button type="button" class="giat-chip-btn" data-token="{title}">+ {${t("chipTitle")}}</button> <button type="button" class="giat-chip-btn" data-token="{original}">+ {${t("chipOriginal")}}</button> <button type="button" class="giat-chip-btn" data-token="{dims}">+ {${t("chipDims")}}</button> <button type="button" class="giat-chip-btn" data-token="{index}">+ {${t("chipIndex")}}</button> <button type="button" class="giat-chip-btn" data-token="{date}">+ {${t("chipDate")}}</button> <button type="button" class="giat-chip-btn" data-token="{time}">+ {${t("chipTime")}}</button> </div> </div> <div class="giat-filename-preview-wrap"> <div class="giat-filename-preview-label">${t("previewFilenameLabel")}</div> <div class="giat-filename-preview-text" id="giat-val-filenamePreview">${getPreviewFilename(config.filenamePatternMode, config.customFilenameTemplate)}</div> </div> <!-- Group 5: AI & Experimental Settings --> <div class="giat-settings-group-title">${t("groupSystem")}</div> <div class="giat-settings-item"> <label>${t("enableThumbAi")} <span class="giat-info-icon" data-tooltip="${t("noteAi")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableThumbAi" ${config.enableThumbAi ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item"> <label>${t("enableLightboxAi")} <span class="giat-info-icon" data-tooltip="${t("noteAi")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableLightboxAi" ${config.enableLightboxAi ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> <div class="giat-settings-item giat-settings-item-block" id="giat-item-aiPromptBlock" style="display: ${config.enableLightboxAi || config.enableThumbAi ? "flex" : "none"}"> <label>${t("aiPromptLabel")}</label> <input type="text" id="giat-opt-aiSearchPrompt" value="${config.aiSearchPrompt}" placeholder="${t("aiPromptPlaceholder")}" class="giat-text-input" style="width: 100%;"> <div class="giat-settings-help-text" style="margin-bottom: 2px;">${t("defaultPromptPrefix")}${t("defaultAiPrompt")}</div> <div class="giat-settings-help-text" style="font-size: 10px; opacity: 0.75;">${t("aiPromptVariablesHelp")}</div> </div> <div class="giat-settings-item"> <label>${t("enableExperimentalAiUpload")} <span class="giat-info-icon" data-tooltip="${t("noteExperimentalAiUpload")}"> <svg viewBox="0 0 24 24" width="13" height="13"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> </span> </label> <label class="giat-switch"> <input type="checkbox" id="giat-opt-enableExperimentalAiUpload" ${config.enableExperimentalAiUpload ? "checked" : ""}> <span class="giat-switch-slider"></span> </label> </div> </div> <div class="giat-settings-footer"> <button class="giat-settings-reset">${t("resetBtn")}</button> </div> `; const tooltipEl = document.createElement("div"); tooltipEl.classList.add("giat-settings-tooltip"); overlay.append(tooltipEl); panel.addEventListener("mouseover", (e) => { const icon = e.target.closest(".giat-info-icon"); if (!icon) return; const text = icon.getAttribute("data-tooltip"); if (!text) return; tooltipEl.textContent = text; tooltipEl.classList.add("show"); const iconRect = icon.getBoundingClientRect(); const overlayRect = overlay.getBoundingClientRect(); const left = iconRect.left - overlayRect.left + iconRect.width / 2 - 100; const top = iconRect.top - overlayRect.top - tooltipEl.offsetHeight - 6; tooltipEl.style.left = `${left}px`; tooltipEl.style.top = `${top}px`; }); panel.addEventListener("mouseout", (e) => { if (!e.target.closest(".giat-info-icon")) return; tooltipEl.classList.remove("show"); }); const scrollBody = panel.querySelector(".giat-settings-body"); if (scrollBody) scrollBody.addEventListener("scroll", () => { tooltipEl.classList.remove("show"); }); overlay.append(panel); document.body.append(overlay); requestAnimationFrame(() => { overlay.classList.add("show"); }); const syncSettings = () => { const lPhotopea = document.querySelector(".giat-photopea-btn"); const lVectorpea = document.querySelector(".giat-vectorpea-btn"); const lYandex = document.querySelector(".giat-yandex-btn"); const lBing = document.querySelector(".giat-bing-btn"); config.applyGlobalSettings(lightboxDownloadBtn, lightboxCopyImgBtn, lightboxCopyB64Btn, lightboxWrap, lightboxLensBtn, lightboxTineyeBtn, lightboxAiBtn, lPhotopea, lVectorpea, lYandex, lBing); updateAllDims(); }; let activeKeyCaptureRemover = null; const closeBtn = panel.querySelector(".giat-settings-close"); let isClosing = false; const cleanupAndClose = (callback) => { if (isClosing) return; isClosing = true; if (activeKeyCaptureRemover) activeKeyCaptureRemover(); window.removeEventListener("keydown", handleEscClose, true); overlay.classList.remove("show"); setTimeout(() => { overlay.remove(); if (callback) callback(); }, 250); }; const handleEscClose = (e) => { if (e.key === "Escape") { e.stopPropagation(); e.preventDefault(); if (activeKeyCaptureRemover) activeKeyCaptureRemover(); else cleanupAndClose(); } }; window.addEventListener("keydown", handleEscClose, true); closeBtn.onclick = () => cleanupAndClose(); overlay.onclick = (e) => { if (e.target === overlay) cleanupAndClose(); }; const bindCheckbox = (id, prop) => { const el = panel.querySelector(`#${id}`); if (el) el.onchange = (e) => { config[prop] = e.target.checked; config.save(); syncSettings(); if (prop === "enableThumbAi" || prop === "enableLightboxAi") { const promptBlock = panel.querySelector("#giat-item-aiPromptBlock"); if (promptBlock) promptBlock.style.display = config.enableThumbAi || config.enableLightboxAi ? "flex" : "none"; } }; }; bindCheckbox("giat-opt-enableUrlOptimization", "enableUrlOptimization"); bindCheckbox("giat-opt-enableHoverInfo", "enableHoverInfo"); bindCheckbox("giat-opt-enableThumbResolution", "enableThumbResolution"); bindCheckbox("giat-opt-enableThumbFileSize", "enableThumbFileSize"); bindCheckbox("giat-opt-enableThumbMime", "enableThumbMime"); bindCheckbox("giat-opt-enableThumbBadges", "enableThumbBadges"); bindCheckbox("giat-opt-enableThumbDownload", "enableThumbDownload"); bindCheckbox("giat-opt-enableThumbCopy", "enableThumbCopy"); bindCheckbox("giat-opt-enableThumbB64", "enableThumbB64"); bindCheckbox("giat-opt-enableThumbTitleTooltip", "enableThumbTitleTooltip"); bindCheckbox("giat-opt-enableThumbLens", "enableThumbLens"); bindCheckbox("giat-opt-enableThumbTineye", "enableThumbTineye"); bindCheckbox("giat-opt-enableThumbAi", "enableThumbAi"); bindCheckbox("giat-opt-enableThumbPhotopea", "enableThumbPhotopea"); bindCheckbox("giat-opt-enableThumbVectorpea", "enableThumbVectorpea"); bindCheckbox("giat-opt-enableThumbYandex", "enableThumbYandex"); bindCheckbox("giat-opt-enableThumbBing", "enableThumbBing"); bindCheckbox("giat-opt-enableYouTubeAutoplay", "enableYouTubeAutoplay"); bindCheckbox("giat-opt-enableLightboxForceBlob", "enableLightboxForceBlob"); bindCheckbox("giat-opt-enableBatchSelect", "enableBatchSelect"); const batchCb = panel.querySelector("#giat-opt-enableBatchSelect"); if (batchCb) batchCb.addEventListener("change", () => { const subpanel = panel.querySelector("#giat-subpanel-batch"); if (subpanel) subpanel.style.display = batchCb.checked ? "flex" : "none"; }); bindCheckbox("giat-opt-enableVisitedMark", "enableVisitedMark"); const visitedCb = panel.querySelector("#giat-opt-enableVisitedMark"); if (visitedCb) visitedCb.addEventListener("change", () => { const subpanel = panel.querySelector("#giat-subpanel-visited"); if (subpanel) subpanel.style.display = visitedCb.checked ? "flex" : "none"; refreshAllVisitedElements(); }); const optVisitedMode = panel.querySelector("#giat-opt-visitedStyleMode"); if (optVisitedMode) optVisitedMode.onchange = (e) => { config.visitedStyleMode = e.target.value; config.save(); syncSettings(); refreshAllVisitedElements(); }; const updateVisitedStatsUI = () => { const badge = panel.querySelector("#giat-visited-stats-badge"); if (badge) { const { count, capacity } = getVisitedStats(); badge.textContent = `${count} / ${capacity}`; } }; updateVisitedStatsUI(); const btnClearVisited = panel.querySelector("#giat-btn-clear-visited"); if (btnClearVisited) { const originalText = btnClearVisited.textContent || t("clearVisitedBtn"); btnClearVisited.onclick = (e) => { e.stopPropagation(); e.preventDefault(); clearVisitedHistory(); updateVisitedStatsUI(); showToast(t("toastVisitedCleared")); btnClearVisited.classList.add("giat-btn-success-feedback"); btnClearVisited.textContent = `✓ ${t("clearSelection") || "Cleared"}`; setTimeout(() => { btnClearVisited.classList.remove("giat-btn-success-feedback"); btnClearVisited.textContent = originalText; }, 1500); }; } const optBatchMode = panel.querySelector("#giat-opt-batchDownloadMode"); if (optBatchMode) optBatchMode.onchange = (e) => { config.batchDownloadMode = e.target.value; config.save(); syncSettings(); }; const optSerpRankMode = panel.querySelector("#giat-opt-serpRankMode"); if (optSerpRankMode) optSerpRankMode.onchange = (e) => { config.serpRankMode = e.target.value; config.save(); syncSettings(); }; const optPos = panel.querySelector("#giat-opt-labelPosition"); if (optPos) optPos.onchange = (e) => { config.labelPosition = e.target.value; config.save(); syncSettings(); }; const optSize = panel.querySelector("#giat-opt-labelSize"); const sizeVal = panel.querySelector("#giat-labelSize-value"); if (optSize) optSize.oninput = (e) => { const val = e.target.value; if (sizeVal) sizeVal.textContent = val; config.labelSize = val; config.save(); syncSettings(); }; const optThumbBtnSize = panel.querySelector("#giat-opt-thumbBtnSize"); const thumbBtnSizeVal = panel.querySelector("#giat-thumbBtnSize-value"); if (optThumbBtnSize) optThumbBtnSize.oninput = (e) => { const val = e.target.value; if (thumbBtnSizeVal) thumbBtnSizeVal.textContent = val; config.thumbBtnSize = val; config.save(); syncSettings(); }; bindCheckbox("giat-opt-enableLightboxResolution", "enableLightboxResolution"); bindCheckbox("giat-opt-enableLightboxFileSize", "enableLightboxFileSize"); bindCheckbox("giat-opt-enableLightboxMime", "enableLightboxMime"); bindCheckbox("giat-opt-enableLightboxDate", "enableLightboxDate"); bindCheckbox("giat-opt-enableLightboxExif", "enableLightboxExif"); bindCheckbox("giat-opt-enableLightboxColorAnalysis", "enableLightboxColorAnalysis"); bindCheckbox("giat-opt-enableLightboxDownload", "enableLightboxDownload"); bindCheckbox("giat-opt-enableLightboxCopy", "enableLightboxCopy"); bindCheckbox("giat-opt-enableLightboxB64", "enableLightboxB64"); bindCheckbox("giat-opt-enableLightboxLens", "enableLightboxLens"); bindCheckbox("giat-opt-enableLightboxTineye", "enableLightboxTineye"); bindCheckbox("giat-opt-enableLightboxAi", "enableLightboxAi"); bindCheckbox("giat-opt-enableLightboxPhotopea", "enableLightboxPhotopea"); bindCheckbox("giat-opt-enableLightboxVectorpea", "enableLightboxVectorpea"); bindCheckbox("giat-opt-enableLightboxYandex", "enableLightboxYandex"); bindCheckbox("giat-opt-enableLightboxBing", "enableLightboxBing"); bindCheckbox("giat-opt-enableExperimentalAiUpload", "enableExperimentalAiUpload"); bindCheckbox("giat-opt-enableLightboxForceBlob", "enableLightboxForceBlob"); const elPrompt = panel.querySelector("#giat-opt-aiSearchPrompt"); if (elPrompt) elPrompt.onchange = (e) => { config.aiSearchPrompt = e.target.value; config.save(); }; const elLightboxKeys = panel.querySelector("#giat-opt-enableLightboxKeys"); const subpanelKeys = panel.querySelector("#giat-subpanel-keys"); if (elLightboxKeys) elLightboxKeys.onchange = () => { config.enableLightboxKeys = elLightboxKeys.checked; config.save(); if (subpanelKeys) subpanelKeys.style.display = config.enableLightboxKeys ? "flex" : "none"; syncSettings(); }; const setupKeyButton = (btnId, prop) => { const button = panel.querySelector(`#${btnId}`); if (button) button.onclick = (e) => { e.stopPropagation(); e.preventDefault(); if (activeKeyCaptureRemover) activeKeyCaptureRemover(); button.classList.add("pending"); button.textContent = t("keyPressToBind"); const handleKeyCapture = (event) => { event.preventDefault(); event.stopPropagation(); if (event.key === "Escape") { if (activeKeyCaptureRemover) activeKeyCaptureRemover(); return; } let capturedKey = event.key; if (capturedKey === " ") capturedKey = "Space"; config[prop] = capturedKey; config.save(); button.textContent = capturedKey; button.classList.remove("pending"); activeKeyCaptureRemover = null; window.removeEventListener("keydown", handleKeyCapture, true); }; activeKeyCaptureRemover = () => { window.removeEventListener("keydown", handleKeyCapture, true); button.classList.remove("pending"); button.textContent = config[prop]; activeKeyCaptureRemover = null; }; window.addEventListener("keydown", handleKeyCapture, true); }; }; setupKeyButton("giat-btn-prevKey", "lightboxPrevKey"); setupKeyButton("giat-btn-nextKey", "lightboxNextKey"); setupKeyButton("giat-btn-closeKey", "lightboxCloseKey"); const elWebpConv = panel.querySelector("#giat-opt-enableWebpConversion"); const subpanelWebp = panel.querySelector("#giat-subpanel-webp"); const itemWebpQuality = panel.querySelector("#giat-item-webpQuality"); if (elWebpConv) elWebpConv.onchange = (e) => { config.enableWebpConversion = e.target.checked; config.save(); if (subpanelWebp) subpanelWebp.style.display = config.enableWebpConversion ? "flex" : "none"; }; const optWebpFormat = panel.querySelector("#giat-opt-webpConversionFormat"); if (optWebpFormat) optWebpFormat.onchange = (e) => { config.webpConversionFormat = e.target.value; config.save(); if (itemWebpQuality) itemWebpQuality.style.display = config.webpConversionFormat === "jpeg" ? "flex" : "none"; }; const optWebpQuality = panel.querySelector("#giat-opt-webpConversionQuality"); const valWebpQuality = panel.querySelector("#giat-val-webpQuality"); if (optWebpQuality) { optWebpQuality.oninput = (e) => { const val = e.target.value; if (valWebpQuality) valWebpQuality.textContent = `${val}%`; }; optWebpQuality.onchange = (e) => { config.webpConversionQuality = parseInt(e.target.value, 10); config.save(); }; } const optFilenameMode = panel.querySelector("#giat-opt-filenamePatternMode"); const subpanelFilename = panel.querySelector("#giat-subpanel-filenamePattern"); const inputCustomTemplate = panel.querySelector("#giat-opt-customFilenameTemplate"); const valFilenamePreview = panel.querySelector("#giat-val-filenamePreview"); const updateFilenamePreview = () => { if (valFilenamePreview) valFilenamePreview.textContent = getPreviewFilename(config.filenamePatternMode, config.customFilenameTemplate); }; if (optFilenameMode) optFilenameMode.onchange = (e) => { config.filenamePatternMode = e.target.value; config.save(); if (subpanelFilename) subpanelFilename.style.display = config.filenamePatternMode === "custom" ? "flex" : "none"; updateFilenamePreview(); }; if (inputCustomTemplate) { inputCustomTemplate.oninput = (e) => { config.customFilenameTemplate = e.target.value; updateFilenamePreview(); }; inputCustomTemplate.onchange = () => config.save(); } panel.querySelectorAll(".giat-chip-btn").forEach((chip) => { chip.onclick = (e) => { e.preventDefault(); e.stopPropagation(); let token = chip.dataset.token || ""; if (!inputCustomTemplate || !token) return; const start = inputCustomTemplate.selectionStart ?? inputCustomTemplate.value.length; const end = inputCustomTemplate.selectionEnd ?? inputCustomTemplate.value.length; const val = inputCustomTemplate.value; const prevChar = start > 0 ? val.charAt(start - 1) : ""; if (prevChar === "}" || prevChar && !/[_\s\-\[\]\(\)\/\\.,;:]/.test(prevChar)) token = "_" + token; inputCustomTemplate.value = val.substring(0, start) + token + val.substring(end); inputCustomTemplate.selectionStart = inputCustomTemplate.selectionEnd = start + token.length; inputCustomTemplate.focus(); config.customFilenameTemplate = inputCustomTemplate.value; config.save(); updateFilenamePreview(); }; }); const elCustomBg = panel.querySelector("#giat-opt-customBgColor"); const elCustomBgPicker = panel.querySelector("#giat-opt-customBgColorPicker"); if (elCustomBg && elCustomBgPicker) { elCustomBg.oninput = (e) => { const val = e.target.value; config.customBgColor = val; syncSettings(); if (/^#[0-9A-F]{6}$/i.test(val.trim())) elCustomBgPicker.value = val.trim(); }; elCustomBg.onchange = () => config.save(); elCustomBgPicker.oninput = (e) => { const val = e.target.value; elCustomBg.value = val; config.customBgColor = val; syncSettings(); }; elCustomBgPicker.onchange = () => config.save(); } const elCustomText = panel.querySelector("#giat-opt-customTextColor"); const elCustomTextPicker = panel.querySelector("#giat-opt-customTextColorPicker"); if (elCustomText && elCustomTextPicker) { elCustomText.oninput = (e) => { const val = e.target.value; config.customTextColor = val; syncSettings(); if (/^#[0-9A-F]{6}$/i.test(val.trim())) elCustomTextPicker.value = val.trim(); }; elCustomText.onchange = () => config.save(); elCustomTextPicker.oninput = (e) => { const val = e.target.value; elCustomText.value = val; config.customTextColor = val; syncSettings(); }; elCustomTextPicker.onchange = () => config.save(); } const optCustomBgOpacity = panel.querySelector("#giat-opt-customBgOpacity"); const valCustomBgOpacity = panel.querySelector("#giat-val-customBgOpacity"); if (optCustomBgOpacity) { optCustomBgOpacity.oninput = (e) => { const val = e.target.value; if (valCustomBgOpacity) valCustomBgOpacity.textContent = `${val}%`; }; optCustomBgOpacity.onchange = (e) => { config.customBgOpacity = parseInt(e.target.value, 10); config.save(); syncSettings(); }; } const optBg = panel.querySelector("#giat-opt-lightboxBg"); if (optBg) optBg.onchange = (e) => { config.currentBgIndex = parseInt(e.target.value, 10); config.save(); syncSettings(); }; const optLang = panel.querySelector("#giat-opt-userLanguage"); if (optLang) optLang.onchange = (e) => { config.userLanguage = e.target.value; config.save(); overlay.remove(); openSettingsPanel(lightboxDownloadBtn, lightboxCopyImgBtn, lightboxCopyB64Btn, lightboxWrap, lightboxLensBtn, lightboxTineyeBtn, lightboxAiBtn); }; const optTheme = panel.querySelector("#giat-opt-uiTheme"); if (optTheme) optTheme.onchange = (e) => { const val = e.target.value; config.uiTheme = val; config.save(); const isDark = val === "auto" ? isPageDark() : val === "dark"; overlay.classList.remove("giat-theme-dark", "giat-theme-light"); overlay.classList.add(isDark ? "giat-theme-dark" : "giat-theme-light"); syncSettings(); }; const optClickAction = panel.querySelector("#giat-opt-clickAction"); if (optClickAction) optClickAction.onchange = (e) => { config.clickAction = e.target.value; config.save(); syncSettings(); }; const optCtrlClickAction = panel.querySelector("#giat-opt-ctrlClickAction"); if (optCtrlClickAction) optCtrlClickAction.onchange = (e) => { config.ctrlClickAction = e.target.value; config.save(); }; const resetBtn = panel.querySelector(".giat-settings-reset"); resetBtn.onclick = () => { config.reset(); syncSettings(); overlay.remove(); openSettingsPanel(lightboxDownloadBtn, lightboxCopyImgBtn, lightboxCopyB64Btn, lightboxWrap, lightboxLensBtn, lightboxTineyeBtn, lightboxAiBtn); showToast(t("toastReset")); }; } function initSettings(lightboxDownloadBtn, lightboxCopyImgBtn, lightboxCopyB64Btn, lightboxWrap, lightboxLensBtn = null, lightboxTineyeBtn = null, lightboxAiBtn = null) { if (typeof GM_registerMenuCommand !== "undefined") GM_registerMenuCommand(t("menuSettings"), () => { openSettingsPanel(lightboxDownloadBtn, lightboxCopyImgBtn, lightboxCopyB64Btn, lightboxWrap, lightboxLensBtn, lightboxTineyeBtn, lightboxAiBtn); }); } var DisposableStore = class DisposableStore { _toDispose = new Set(); _isDisposed = false; get isDisposed() { return this._isDisposed; } add(disposable) { if (!disposable) return disposable; if (this._isDisposed) { DisposableStore.disposeResource(disposable); return disposable; } this._toDispose.add(disposable); return disposable; } addEventListener(target, type, listener, options) { target.addEventListener(type, listener, options); this.add(() => target.removeEventListener(type, listener, options)); } setTimeout(handler, timeout, ...args) { const timerId = window.setTimeout(handler, timeout, ...args); this.add(() => clearTimeout(timerId)); return timerId; } dispose() { if (this._isDisposed) return; this._isDisposed = true; const resources = Array.from(this._toDispose).reverse(); this._toDispose.clear(); for (const resource of resources) DisposableStore.disposeResource(resource); } clear() { if (this._toDispose.size === 0) return; const resources = Array.from(this._toDispose).reverse(); this._toDispose.clear(); for (const resource of resources) DisposableStore.disposeResource(resource); } static disposeResource(disposable) { if (!disposable) return; try { if (typeof disposable === "function") disposable(); else if ("dispose" in disposable && typeof disposable.dispose === "function") disposable.dispose(); else if ("disconnect" in disposable && typeof disposable.disconnect === "function") disposable.disconnect(); else if ("abort" in disposable && typeof disposable.abort === "function") disposable.abort(); else if ("destroy" in disposable && typeof disposable.destroy === "function") disposable.destroy(); } catch (err) { console.warn("[DisposableStore] Error disposing resource:", err); } } }; var HybridSentinel = class HybridSentinel { static ANIMATION_NAME = "giat-sentinel-anim"; static STYLE_ID = "giat-sentinel-styles"; store = new DisposableStore(); listeners = new Map(); processedElements = new WeakSet(); constructor() { this.injectStyles(); this.bindAnimationStart(); this.startIdleScanner(); } injectStyles() { if (document.getElementById(HybridSentinel.STYLE_ID)) return; const style = document.createElement("style"); style.id = HybridSentinel.STYLE_ID; const nonceScript = document.querySelector("script[nonce]"); if (nonceScript?.nonce) style.setAttribute("nonce", nonceScript.nonce); style.textContent = ` @keyframes ${HybridSentinel.ANIMATION_NAME} { from { outline: 1px solid transparent; } to { outline: 0px solid transparent; } } `; (document.head || document.documentElement).appendChild(style); this.store.add(() => style.remove()); } scanTimeoutMs = 2500; emptyScanStreak = 0; isSleeping = false; bindAnimationStart() { const handleAnimationStart = (e) => { if (e.animationName !== HybridSentinel.ANIMATION_NAME) return; const target = e.target; if (!target || target.nodeType !== Node.ELEMENT_NODE) return; this.processElement(target); }; this.store.addEventListener(document, "animationstart", handleAnimationStart, true); const wakeUp = () => this.wakeUpScanner(); this.store.addEventListener(window, "scroll", wakeUp, { passive: true }); this.store.addEventListener(document, "visibilitychange", wakeUp, { passive: true }); } wakeUpScanner() { if (this.isSleeping || this.emptyScanStreak > 0) { this.emptyScanStreak = 0; this.scanTimeoutMs = 2500; if (this.isSleeping) { this.isSleeping = false; this.startIdleScanner(); } } } startIdleScanner() { if (typeof requestIdleCallback !== "function" || this.isSleeping) return; let timerId = null; const scan = () => { timerId = requestIdleCallback((deadline) => { let foundNewElements = 0; if (deadline.timeRemaining() > 1) for (const selector of this.listeners.keys()) { const elements = Array.from(document.querySelectorAll(selector)); for (const el of elements) if (!this.processedElements.has(el)) { this.processElement(el); foundNewElements++; } } if (foundNewElements > 0) { this.emptyScanStreak = 0; this.scanTimeoutMs = 2500; } else { this.emptyScanStreak++; if (this.emptyScanStreak >= 2) this.scanTimeoutMs = Math.min(15e3, Math.floor(this.scanTimeoutMs * 1.8)); } if (this.emptyScanStreak >= 6) { this.isSleeping = true; return; } if (!this.store.isDisposed && !this.isSleeping) scan(); }, { timeout: this.scanTimeoutMs }); }; scan(); this.store.add(() => { if (timerId !== null && typeof cancelIdleCallback === "function") cancelIdleCallback(timerId); }); } processElement(el) { if (this.processedElements.has(el)) return; for (const [selector, callbacks] of this.listeners.entries()) if (el.matches(selector)) { this.processedElements.add(el); try { el.style.animationName = "none"; } catch (e) {} for (const callback of callbacks) try { callback(el); } catch (err) { console.error("[HybridSentinel] Callback error for selector:", selector, err); } break; } } observe(selector, callback) { let callbackSet = this.listeners.get(selector); if (!callbackSet) { callbackSet = new Set(); this.listeners.set(selector, callbackSet); this.updateRule(selector, true); } callbackSet.add(callback); const existing = document.querySelectorAll(selector); for (let i = 0; i < existing.length; i++) this.processElement(existing[i]); return { dispose: () => { const set = this.listeners.get(selector); if (set) { set.delete(callback); if (set.size === 0) { this.listeners.delete(selector); this.updateRule(selector, false); } } } }; } updateRule(selector, add) { const style = document.getElementById(HybridSentinel.STYLE_ID); if (!style || !style.sheet) return; const ruleText = `${selector} { animation-duration: 0.001s; animation-name: ${HybridSentinel.ANIMATION_NAME}; }`; if (add) try { style.sheet.insertRule(ruleText, style.sheet.cssRules.length); } catch (e) { console.warn("[HybridSentinel] Failed to insert rule:", ruleText, e); } else try { for (let i = style.sheet.cssRules.length - 1; i >= 0; i--) if (style.sheet.cssRules[i].selectorText === selector) { style.sheet.deleteRule(i); break; } } catch (e) { console.warn("[HybridSentinel] Failed to delete rule:", selector, e); } } dispose() { this.listeners.clear(); this.store.dispose(); } }; function getExtension(mime) { const match = mime.match(/\/([a-zA-Z0-9+]+)$/); if (!match) return "jpg"; let ext = match[1].toLowerCase(); if (ext === "jpeg") ext = "jpg"; if (ext === "x-icon") ext = "ico"; if (ext === "svg+xml") ext = "svg"; return ext; } function waitForInputAreaAndSubmit(file, promptText, uploadOverlay) { const maxAttempts = 100; let attempts = 0; const cleanup = () => { clearInterval(inputPoll); document.removeEventListener("visibilitychange", handleVisibilityChange); }; const handleVisibilityChange = () => { if (!document.hidden) attempts = 0; }; document.addEventListener("visibilitychange", handleVisibilityChange); const inputPoll = setInterval(() => { attempts++; const textarea = document.querySelector(GOOGLE_SELECTORS.AI_TEXTAREA); const controller = textarea?.closest(GOOGLE_SELECTORS.AI_CONTROLLER_READY); if (textarea && (controller || attempts > 30) && textarea) { cleanup(); try { textarea.focus(); textarea.value = promptText; textarea.dispatchEvent(new Event("input", { bubbles: true })); setTimeout(() => { try { textarea.focus(); const dataTransfer = new DataTransfer(); dataTransfer.items.add(file); const pasteEvent = new ClipboardEvent("paste", { bubbles: true, cancelable: true }); Object.defineProperty(pasteEvent, "clipboardData", { value: dataTransfer, writable: false, configurable: true }); textarea.dispatchEvent(pasteEvent); GM_deleteValue("giat_temp_upload_id"); GM_deleteValue("giat_temp_upload_img"); GM_deleteValue("giat_temp_upload_mime"); GM_deleteValue("giat_temp_prompt"); uploadOverlay.classList.add("giat-status-success"); uploadOverlay.innerHTML = `<span>✓ ${t("uploadSuccess")}</span>`; let submitAttempts = 0; const maxSubmitAttempts = 100; const submitPoll = setInterval(() => { submitAttempts++; try { const container = textarea.closest(GOOGLE_SELECTORS.AI_SUBMIT_CONTAINER) || document.body; let submitBtn = container.querySelector(GOOGLE_SELECTORS.AI_SEND_BUTTON); if (!submitBtn) { const validButtons = Array.from(container.querySelectorAll("button, [role=\"button\"]")).filter((el) => { if (el.closest(GOOGLE_SELECTORS.AI_BUTTON_EXCLUDED)) return false; if (!(el.querySelector("svg") !== null)) return false; if (el.offsetWidth === 0 && el.offsetHeight === 0) return false; if (el === container) return false; return true; }); if (validButtons.length > 0) submitBtn = validButtons[validButtons.length - 1]; } const isBtnDisabled = submitBtn ? submitBtn.disabled || submitBtn.getAttribute("aria-disabled") === "true" : true; if (submitBtn && !isBtnDisabled) { clearInterval(submitPoll); submitBtn.click(); setTimeout(() => { uploadOverlay.style.opacity = "0"; setTimeout(() => uploadOverlay.remove(), 300); }, 1e3); return; } } catch (submitErr) { console.error("[ShowDims] Error during submit polling:", submitErr); } if (submitAttempts >= maxSubmitAttempts) { clearInterval(submitPoll); try { let submitBtn = (textarea.closest(GOOGLE_SELECTORS.AI_SUBMIT_CONTAINER) || document.body).querySelector(GOOGLE_SELECTORS.AI_SEND_BUTTON); if (submitBtn) submitBtn.click(); else [ "keydown", "keypress", "keyup" ].forEach((type) => { const ev = new KeyboardEvent(type, { key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true, cancelable: true }); textarea.dispatchEvent(ev); }); } catch (err) { console.error("[ShowDims] Failed forced submission:", err); } setTimeout(() => { uploadOverlay.style.opacity = "0"; setTimeout(() => uploadOverlay.remove(), 300); }, 1e3); } }, 100); } catch (pasteErr) { console.error("[ShowDims] Paste simulation failed:", pasteErr); uploadOverlay.classList.add("giat-status-error"); uploadOverlay.innerHTML = `<span>❌ ${t("uploadFail")}</span>`; setTimeout(() => uploadOverlay.remove(), 3e3); } }, 50); } catch (err) { console.error("Failed to initialize input flow:", err); uploadOverlay.classList.add("giat-status-error"); uploadOverlay.innerHTML = `<span>❌ ${t("uploadFail")}</span>`; setTimeout(() => uploadOverlay.remove(), 3e3); } return; } if (attempts >= maxAttempts) { cleanup(); uploadOverlay.classList.add("giat-status-error"); uploadOverlay.innerHTML = `<span>❌ Input box not found</span>`; setTimeout(() => uploadOverlay.remove(), 3e3); } }, 100); } async function retrieveSharedBlob(uploadId, blobUrl) { try { const res = await fetch(blobUrl); if (res.ok) { const blob = await res.blob(); const channel = new BroadcastChannel(`giat_channel_${uploadId}`); try { channel.postMessage({ type: "upload_completed" }); } finally { channel.close(); } return blob; } } catch (e) { console.warn("[ShowDims] Blob URL invalid or parent tab closed, falling back to BroadcastChannel:", e); } return new Promise((resolve, reject) => { const channel = new BroadcastChannel(`giat_channel_${uploadId}`); let timeoutId = window.setTimeout(() => { timeoutId = void 0; channel.close(); reject(new Error("BroadcastChannel transmission timed out.")); }, 8e3); channel.onmessage = (event) => { if (event.data && event.data.type === "fallback_buffer") { if (timeoutId) { clearTimeout(timeoutId); timeoutId = void 0; } try { channel.postMessage({ type: "upload_completed" }); } catch (postErr) { console.warn("Failed to post upload_completed receipt:", postErr); } finally { channel.close(); } resolve(event.data.sharedBlob); } }; try { channel.postMessage("request_fallback_buffer"); } catch (err) { if (timeoutId) { clearTimeout(timeoutId); timeoutId = void 0; } channel.close(); reject(err); } }); } function startAiUploadFlow(uploadId, storedInfoStr, uploadOverlay, updateOverlayProgress) { (async () => { try { const info = JSON.parse(storedInfoStr); const imgurl = info.imgurl; const titleText = info.titleText; const srcUrl = info.srcUrl; const promptText = info.prompt; const blobUrl = info.blobUrl; const mime = info.mime || "image/jpeg"; updateOverlayProgress(30); const blob = await retrieveSharedBlob(uploadId, blobUrl); updateOverlayProgress(100); const ext = getExtension(blob.type || mime); const file = new File([blob], `upload-${Date.now()}.${ext}`, { type: blob.type || mime }); const finalTitle = titleText ? titleText.trim() : t("defaultTitleFallback"); const finalSrc = srcUrl ? srcUrl.trim() : imgurl; let finalPrompt = promptText ? promptText.trim() : t("defaultAiPrompt"); const friendlyInfo = getFriendlyImageInfo(imgurl); if (finalPrompt.toLowerCase().includes("{img}")) { const imgLabel = t("aiPromptImgText").replace("{filename}", friendlyInfo.filename); finalPrompt = finalPrompt.replace(/{img}/gi, `[${imgLabel}](${imgurl})`); } else { const imgInfoLabel = t("aiPromptImgInfoText").replace("{filename}", friendlyInfo.filename); finalPrompt = `${finalPrompt}\n[${imgInfoLabel}](${imgurl})`; } finalPrompt = finalPrompt.replace(/{title}/gi, finalTitle); finalPrompt = finalPrompt.replace(/{src}/gi, finalSrc); waitForInputAreaAndSubmit(file, finalPrompt, uploadOverlay); } catch (err) { console.error("Self-contained AI search upload failed:", err); uploadOverlay.classList.add("giat-status-error"); uploadOverlay.innerHTML = `<span>❌ ${t("uploadFail")}</span>`; setTimeout(() => uploadOverlay.remove(), 3e3); } })(); } function handleExperimentalAiUpload() { const hash = window.location.hash || ""; if (!hash.includes("giat_upload_id=")) return; const match = hash.match(/giat_upload_id=([^&]+)/); if (!match) return; const uploadId = match[1]; try { const newHash = window.location.hash.replace(new RegExp(`[#&]giat_upload_id=${uploadId}`), ""); history.replaceState(null, "", window.location.pathname + window.location.search + newHash); } catch (e) {} const isDark = config.uiTheme === "auto" ? isPageDark() : config.uiTheme === "dark"; const uploadOverlay = document.createElement("div"); uploadOverlay.classList.add("giat-upload-overlay"); uploadOverlay.classList.add(isDark ? "giat-theme-dark" : "giat-theme-light"); uploadOverlay.innerHTML = ` <div style="display: flex; flex-direction: column; gap: 10px; width: max-content; min-width: 220px; max-width: 360px;"> <div style="display: flex; align-items: center; gap: 10px;"> ${` <svg width="18" height="18" viewBox="0 0 24 24" style="animation: giat-spin 1s linear infinite; fill: none; stroke: ${isDark ? "#8ab4f8" : "#1a73e8"}; stroke-width: 3; stroke-linecap: round; flex-shrink: 0;"> <circle cx="12" cy="12" r="10" stroke="${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.06)"}"></circle> <path d="M12 2a10 10 0 0 1 10 10"></path> </svg> <style> @keyframes giat-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> `} <span class="giat-progress-text" style="font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${t("uploadingToAi")} (0%)</span> </div> <div class="giat-progress-bg"> <div class="giat-progress-bar" style="width: 0%; height: 100%; background: linear-gradient(90deg, #8ab4f8, #c58af9); transition: width 0.2s ease; border-radius: 2px;"></div> </div> </div> `; document.body.appendChild(uploadOverlay); const updateOverlayProgress = (percent) => { const textEl = uploadOverlay.querySelector(".giat-progress-text"); const barEl = uploadOverlay.querySelector(".giat-progress-bar"); if (textEl) textEl.textContent = `${t("uploadingToAi")} (${percent}%)`; if (barEl) barEl.style.width = `${percent}%`; }; const infoKey = `giat_upload_info_${uploadId}`; const storedInfoStr = GM_getValue(infoKey); if (storedInfoStr) { try { GM_deleteValue(infoKey); } catch (e) {} startAiUploadFlow(uploadId, storedInfoStr, uploadOverlay, updateOverlayProgress); return; } let isHandled = false; let listenerId = null; let timeoutId = null; let metadataPoll = null; const cleanup = () => { isHandled = true; if (timeoutId !== null) clearTimeout(timeoutId); if (metadataPoll !== null) clearInterval(metadataPoll); if (listenerId !== null && typeof window.GM_removeValueChangeListener === "function") try { window.GM_removeValueChangeListener(listenerId); } catch (e) {} }; const processInfoStr = (dataStr) => { if (isHandled) return; cleanup(); try { GM_deleteValue(infoKey); } catch (e) {} startAiUploadFlow(uploadId, dataStr, uploadOverlay, updateOverlayProgress); }; if (typeof window.GM_addValueChangeListener === "function") try { listenerId = window.GM_addValueChangeListener(infoKey, (_name, _oldVal, newVal, remote) => { if (newVal && remote) processInfoStr(newVal); }); } catch (e) {} const startTime = Date.now(); const maxWaitMs = 3e4; metadataPoll = setInterval(() => { if (isHandled) return; const infoStr = GM_getValue(infoKey); if (infoStr) processInfoStr(infoStr); else if (Date.now() - startTime >= maxWaitMs) { cleanup(); console.warn("[ShowDims] Timeout waiting for sharing metadata info payload."); uploadOverlay.classList.add("giat-status-error"); uploadOverlay.innerHTML = `<span>❌ ${t("uploadFail")} (Metadata Timeout)</span>`; setTimeout(() => uploadOverlay.remove(), 3e3); } }, 200); timeoutId = setTimeout(() => { if (!isHandled) { cleanup(); console.warn("[ShowDims] Timeout waiting for sharing metadata info payload."); uploadOverlay.classList.add("giat-status-error"); uploadOverlay.innerHTML = `<span>❌ ${t("uploadFail")} (Metadata Timeout)</span>`; setTimeout(() => uploadOverlay.remove(), 3e3); } }, maxWaitMs); } var handleUrlChange = () => { if (!isImgSearch() && !isLens()) return; showDims(); }; function isThumbnailImageTarget(target) { if (!!target.closest(".giat-dims, .giat-thumb-download-btn, .giat-thumb-copy-btn, .giat-thumb-b64-btn, .giat-thumb-lens-btn, .giat-thumb-tineye-btn, .giat-thumb-ai-btn, .giat-thumb-photopea-btn, .giat-thumb-vectorpea-btn, .giat-thumb-yandex-btn, .giat-thumb-bing-btn, .giat-thumb-checkbox")) return false; if (!!target.closest("a.LBcIee, .Q6A6Dc, .VYhLad, [data-snf=\"ub58cd\"], .K8ZDdf")) return false; return !!target.closest(".ImUqSb, [jsname=\"PNoEC\"], a[href*=\"/imgres\"], [data-img-wrapper], .DeNS1c, .RmwKgd, .bFtXbb"); } function initGlobalDelegation() { const targetContainer = document.body; targetContainer.addEventListener("click", (e) => { const target = e.target; if (e.ctrlKey || e.metaKey) { if (isThumbnailImageTarget(target)) { const resultItem = target.closest("[data-giat-result]"); if (resultItem) { const imgurl = resultItem.dataset.giatImgurl; const rawUrl = resultItem.dataset.giatRawOriginalUrl; const docId = resultItem.dataset.giatDocid || resultItem.getAttribute("data-docid") || void 0; if (imgurl) { markAsVisited(docId, imgurl, resultItem); if (config.ctrlClickAction === "raw_image") { e.stopPropagation(); e.preventDefault(); openUrlWithFallback(imgurl, rawUrl); return; } } } } } const clickedResult = target.closest("[data-giat-result]"); if (clickedResult) markAsVisited(clickedResult.dataset.giatDocid || clickedResult.getAttribute("data-docid") || void 0, clickedResult.dataset.giatImgurl || void 0, clickedResult); const dimsEl = target.closest(".giat-dims"); if (dimsEl) { if (dimsEl.classList.contains("giat-detail-type-badge")) { if (e.ctrlKey || e.metaKey) { e.stopPropagation(); e.preventDefault(); const realUrl = dimsEl.getAttribute("data-giat-real-url") || dimsEl.href; const rawUrl = dimsEl.getAttribute("data-giat-raw-url"); if (realUrl) openUrlWithFallback(realUrl, rawUrl || void 0); } return; } e.stopPropagation(); e.preventDefault(); const resultItem = dimsEl.closest("[data-giat-result]"); if (resultItem && dimsEl.tagName === "A") { const imgurl = resultItem.dataset.giatImgurl; const rawUrl = resultItem.dataset.giatRawOriginalUrl; const docId = resultItem.dataset.giatDocid || resultItem.getAttribute("data-docid") || void 0; const w = parseInt(resultItem.dataset.giatWidth || "0", 10); const h = parseInt(resultItem.dataset.giatHeight || "0", 10); if (imgurl) { markAsVisited(docId, imgurl, resultItem); if (e.ctrlKey || e.metaKey) openUrlWithFallback(imgurl, rawUrl); else if (config.clickAction === "lightbox") showLightbox(imgurl, w, h, resultItem); else openUrlWithFallback(imgurl, rawUrl); } } return; } }, true); targetContainer.addEventListener("mousedown", (e) => { if (e.target.closest(".giat-dims, .giat-thumb-download-btn, .giat-thumb-copy-btn, .giat-thumb-b64-btn, .giat-thumb-lens-btn, .giat-thumb-tineye-btn, .giat-thumb-ai-btn, .giat-thumb-photopea-btn, .giat-thumb-vectorpea-btn, .giat-thumb-yandex-btn, .giat-thumb-bing-btn")) { e.stopPropagation(); if (e.button === 1) e.preventDefault(); } }, true); targetContainer.addEventListener("auxclick", (e) => { if (e.button !== 1) return; const target = e.target; const clickedResult = target.closest("[data-giat-result]"); if (clickedResult) markAsVisited(clickedResult.dataset.giatDocid || clickedResult.getAttribute("data-docid") || void 0, clickedResult.dataset.giatImgurl || void 0, clickedResult); const dimsEl = target.closest(".giat-dims"); if (dimsEl) { if (dimsEl.classList.contains("giat-detail-type-badge")) return; e.stopPropagation(); e.preventDefault(); const resultItem = dimsEl.closest("[data-giat-result]"); if (resultItem && dimsEl.tagName === "A") { const imgurl = resultItem.dataset.giatImgurl; const rawUrl = resultItem.dataset.giatRawOriginalUrl; const docId = resultItem.dataset.giatDocid || resultItem.getAttribute("data-docid") || void 0; if (imgurl) { markAsVisited(docId, imgurl, resultItem); openUrlWithFallback(imgurl, rawUrl); } } return; } const customBtn = target.closest(".giat-thumb-download-btn, .giat-thumb-copy-btn, .giat-thumb-b64-btn, .giat-thumb-lens-btn, .giat-thumb-tineye-btn, .giat-thumb-ai-btn, .giat-thumb-photopea-btn, .giat-thumb-vectorpea-btn, .giat-thumb-yandex-btn, .giat-thumb-bing-btn"); if (customBtn) { const resultItem = customBtn.closest("[data-giat-result]"); if (resultItem) markAsVisited(resultItem.dataset.giatDocid || resultItem.getAttribute("data-docid") || void 0, resultItem.dataset.giatImgurl || void 0, resultItem); e.stopPropagation(); e.preventDefault(); customBtn.click(); return; } if (isThumbnailImageTarget(target)) { const resultItem = target.closest("[data-giat-result]"); if (resultItem) { const imgurl = resultItem.dataset.giatImgurl; const rawUrl = resultItem.dataset.giatRawOriginalUrl; const docId = resultItem.dataset.giatDocid || resultItem.getAttribute("data-docid") || void 0; if (imgurl) { markAsVisited(docId, imgurl, resultItem); if (config.ctrlClickAction === "raw_image") { e.stopPropagation(); e.preventDefault(); openUrlWithFallback(imgurl, rawUrl); return; } } } } }, true); } function initFirstScreenSizePoller() { let attempts = 0; const maxAttempts = 25; const interval = window.setInterval(() => { attempts++; const processed = document.querySelectorAll("div[data-giat-result]"); const pending = document.querySelectorAll("div[data-giat-result]:not([data-giat-filesize])"); if (processed.length > 0 && pending.length === 0 || attempts >= maxAttempts) { clearInterval(interval); return; } if (pending.length > 0) showDims(); }, 200); } function init() { const isImg = isImgSearch(); const isLns = isLens(); const isAiSearch = new URLSearchParams(window.location.search).get("udm") === "50"; const hasUploadId = window.location.hash.includes("giat_upload_id="); if (isImg || isLns) initLightbox(); initSettings(isImg || isLns ? lightboxDownloadBtn : null, isImg || isLns ? lightboxCopyImgBtn : null, isImg || isLns ? lightboxCopyB64Btn : null, isImg || isLns ? lightboxWrap : null, isImg || isLns ? lightboxLensBtn : null, isImg || isLns ? lightboxTineyeBtn : null, isImg || isLns ? lightboxAiBtn : null); config.applyGlobalSettings(isImg || isLns ? lightboxDownloadBtn : null, isImg || isLns ? lightboxCopyImgBtn : null, isImg || isLns ? lightboxCopyB64Btn : null, isImg || isLns ? lightboxWrap : null, isImg || isLns ? lightboxLensBtn : null, isImg || isLns ? lightboxTineyeBtn : null, isImg || isLns ? lightboxAiBtn : null, isImg || isLns ? lightboxPhotopeaBtn : null, isImg || isLns ? lightboxVectorpeaBtn : null, isImg || isLns ? lightboxYandexBtn : null, isImg || isLns ? lightboxBingBtn : null); if (isAiSearch && hasUploadId && config.enableExperimentalAiUpload) { handleExperimentalAiUpload(); return; } if (isImg || isLns) { initGlobalDelegation(); initDetailPanelObserver(); handleUrlChange(); initFirstScreenSizePoller(); injectBatchTriggerButton(); const sentinel = new HybridSentinel(); const globalStore = new DisposableStore(); globalStore.add(sentinel); const targetSel = getItemSelector(); sentinel.observe(targetSel, (element) => { showDims([element]); }); const titleEl = document.querySelector("title"); if (titleEl) { const titleObserver = new MutationObserver(() => { handleUrlChange(); }); titleObserver.observe(titleEl, { subtree: true, characterData: true, childList: true }); globalStore.add(titleObserver); } } } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init); else init(); function isSensitiveUrl(urlStr) { try { const hn = new URL(urlStr).hostname; return hn.includes("pinimg.com") || hn.includes("artstation.com") || hn.includes("staticflickr.com") || hn.includes("flickr.com") || hn.includes("fastpic.ru") || hn.includes("fastpic.org") || hn.includes("img.4plebs.org") || hn.includes("img.fireden.net") || hn.includes("img-lb.fireden.net") || hn.includes("torako.wakarimasen.moe"); } catch (e) { return false; } } function openUrlWithFallback(url, rawOriginalUrl) { if (!isSensitiveUrl(url)) { window.open(url, "_blank", "noopener,noreferrer"); return; } const newTab = window.open("about:blank", "_blank"); if (!newTab) { window.open(url, "_blank", "noopener,noreferrer"); return; } newTab.document.title = "Loading Image..."; newTab.document.body.innerHTML = ` <div style="display:flex;flex-direction:column;justify-content:center;align-items:center;height:100vh;font-family:sans-serif;color:#888;background:#121212;"> <div style="border:3px solid #333;border-top:3px solid #888;border-radius:50%;width:30px;height:30px;animation:spin 1s linear infinite;margin-bottom:15px;"></div> <div>Loading high-resolution image...</div> </div> <style> @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> `; fetchImageBlobWithFallback(url, rawOriginalUrl).then(({ finalUrl }) => { newTab.location.replace(finalUrl); }).catch(() => { newTab.location.replace(rawOriginalUrl || url); }); } })(ExifReader);