// ==UserScript==
// @name PixAI Utilities Mod
// @namespace Violentmonkey Scripts
// @match https://pixai.art/*
// @version 1.1.0
// @author brunon
// @description Preloads images; download prompt filename; auto open slideshow; negative prompt persist option; keeps selection highlighted.
// @grant GM_addStyle
// @grant GM_download
// @grant GM_info
// @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1
// ==/UserScript==
(async () => {
if (!VM.shortcut) {
console.error('VM.shortcut is not available!');
return;
}
const shortcuts = new VM.shortcut.KeyboardService();
shortcuts.enable();
shortcuts.setContext('slideshowOpen', false);
shortcuts.register(
'd',
() => {
let downloadBtn = document.querySelector('#custom-download');
if (!!downloadBtn) downloadBtn.click();
},
{
condition: 'slideshowOpen',
}
);
let imgPreviewSelector = 'main img[src^="https://images-ng.pixai.art/images/thumb/"]';
let imgOriginalSelector = 'main img[src^="https://images-ng.pixai.art/images/orig/"]';
let imgThumbsSelector = '[data-test-id="virtuoso-item-list"] .contents>button';
let promptTextareaSelector = 'textarea.w-full';
let scrollListSelector = '[data-test-id="virtuoso-scroller"]';
let thumbIcons = [];
let openedPreviewCache = new Map();
let lastCacheUpdate = 0;
let slideshowPresent = false;
let dbNameAntiban = `${GM_info.script.name}-${GM_info.uuid}`.replace(/\s+/g, '-');
let generateButtonListener;
let pauseThumbListener = false;
let latestClickedElement;
let currentImgSrcObserver;
let latestEventListener;
let shouldEnforceNegative = !!localStorage.getItem('enforceNegativeNegativePrompt');
let previewListCheckListener;
let latestClickedID;
await waitForElement(imgThumbsSelector);
console.log("Running")
window.print = function () { };
function preloadImages(imageUrls) {
imageUrls.forEach(url => {
const img = new Image();
img.src = url;
});
}
async function waitForElements(selector) {
const startTime = Date.now();
const waitTime = 10000;
return new Promise(resolve => {
const checkInterval = setInterval(() => {
const elements = document.querySelectorAll(selector);
if (elements.length >= 4 || Date.now() - startTime > waitTime) {
clearInterval(checkInterval);
resolve(elements);
}
}, 150);
});
}
async function updateThumbs(refresh = false) {
let scroller = document.querySelector(scrollListSelector);
let loader = document.createElement('progress');
loader.style.width = "100%";
// scroller.prepend(loader);
if (refresh) {
thumbIcons = [];
await updatePreviewCache();
}
const newThumbs = await waitForElements(imgThumbsSelector);
newThumbs.forEach(newThumb => {
let id = extractPreviewId(newThumb);
displayOpenedState(id, newThumb);
if (!thumbIcons.includes(newThumb)) thumbIcons.push(newThumb);
});
updateListeners();
loader.remove();
}
async function getImagePreviews() {
return await waitForElements(imgPreviewSelector);
}
async function preloadFullImages() {
const imagePreviews = await getImagePreviews();
preloadImages(Array.from(imagePreviews).map(img => {
return img.src.replace("thumb", "orig");
}));
}
async function waitForElement(selector) {
return new Promise((resolve) => {
const observer = new MutationObserver(() => {
const element = document.querySelector(selector);
if (element) {
observer.disconnect();
resolve(element);
}
});
observer.observe(document.body, { childList: true, subtree: true });
});
}
async function waitForClass(element, className) {
if (!element) {
return Promise.reject('Element is null');
}
return new Promise(resolve => {
const checkInterval = setInterval(() => {
if (element.classList.contains(className)) {
clearInterval(checkInterval);
resolve();
}
}, 100); // Check every 100 milliseconds
});
}
async function highlightSelected(target) {
await waitForClass(target, 'ring-offset-background-light');
thumbIcons.forEach(icon => {
icon.classList.remove('selected-thumb');
});
target.classList.add('selected-thumb');
}
function eventToElement(event) {
return event.currentTarget;
}
async function updatePreviewCache() {
if (Date.now() - lastCacheUpdate < 100) return Promise.resolve();
lastCacheUpdate = Date.now();
try {
const request = openDatabase(); // Get the database request
const db = await new Promise((resolve, reject) => {
request.onsuccess = ({ target }) => resolve(target.result); // Resolve with the database object
request.onerror = () => reject('IndexedDB error');
});
const store = db.transaction('previews', 'readonly').objectStore('previews');
const allValues = [];
return new Promise((resolve, reject) => {
const cursorRequest = store.openCursor();
cursorRequest.onsuccess = ({ target }) => {
const cursor = target.result;
if (!cursor) {
allValues.forEach(item => openedPreviewCache.set(item.id, item));
resolve();
} else {
allValues.push(cursor.value);
cursor.continue();
}
};
cursorRequest.onerror = () => reject('Error retrieving cache values');
});
} catch (error) {
console.error("Error accessing the database:", error);
throw new Error('IndexedDB error');
}
}
async function measureUpdatePreviewCacheTime() {
const startTime = performance.now(); // Start timing
await updatePreviewCache();
const endTime = performance.now(); // End timing
console.log(`updatePreviewCache execution time: ${endTime - startTime} ms`);
}
measureUpdatePreviewCacheTime();
function extractSrcId(src) {
try {
return src.split('/').pop();
} catch (error) {
console.error('Error occurred with src:', src);
}
}
function extractPreviewId(element) {
const img = element.querySelector('div img');
const src = img?.getAttribute('src');
if (!src) return null;
return (extractSrcId(src))
}
function openDatabase() {
return indexedDB.open(dbNameAntiban, 1);
}
function performDatabaseOperation(id, storeName, operation) {
const request = openDatabase();
request.onupgradeneeded = function (event) {
const db = event.target.result;
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName, { keyPath: 'id' });
}
};
request.onsuccess = function (event) {
const db = event.target.result;
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
operation(store, id); // Perform the operation
transaction.oncomplete = () => null;
transaction.onerror = () => console.error(`Transaction ${id} error: ${event.target.error}`);
};
request.onerror = function (event) {
console.error('IndexedDB error:', event.target.error);
};
}
function upsertDatabase(id) {
if (!id) {
console.error('Invalid ID provided for upsert operation');
return;
}
let infoID = { id, timestamp: new Date().toISOString() };
performDatabaseOperation(id, 'previews', (store, id) => {
store.put(infoID);
});
openedPreviewCache.set(id, infoID);
}
async function isIdPresentInDatabase(id, storeName) {
return new Promise((resolve) => {
performDatabaseOperation(id, storeName, (store, id) => {
const request = store.get(id);
request.onsuccess = () => resolve(request.result !== undefined);
request.onerror = () => resolve(false);
});
});
}
async function getValueById(id, storeName) {
const request = openDatabase();
return new Promise((resolve, reject) => {
request.onsuccess = ({ target }) => {
const store = target.result.transaction(storeName, 'readonly').objectStore(storeName);
store.get(id).onsuccess = e => resolve(e.target.result || null);
store.get(id).onerror = () => reject('Error retrieving value');
};
request.onerror = () => reject('IndexedDB error');
});
}
async function wasAlreadyOpenedCheck(id, forceRefresh = false) {
if (forceRefresh) {
let storedValue = await getValueById(id, 'previews');
if (storedValue) openedPreviewCache.set(id, storedValue);
return !!storedValue;
}
return openedPreviewCache.has(id);
}
function displayOpenedState(id, previewButton, forceRecheckDB = false) {
let existingSpan = previewButton.querySelector('span[data-label="check"]');
wasAlreadyOpenedCheck(id, forceRecheckDB).then(isPresent => {
if (!isPresent && !!existingSpan) {
existingSpan.remove();
return;
}
if (isPresent && !existingSpan) {
let span = document.createElement('span');
span.setAttribute('data-label', 'check');
span.textContent = '✔️';
span.style.opacity = '0';
previewButton.appendChild(span);
setTimeout(() => span.style.opacity = '1', 5)
}
});
}
function selectThumbFromId(id) {
let img = document.querySelector(`[data-test-id="virtuoso-item-list"] .contents>button img[src$="${id}"]`);
if (!img) return;
thumbIcons.forEach(icon => {
icon.classList.remove('selected-thumb');
});
let elementToSelect = img.parentElement.parentElement;
elementToSelect.classList.add('selected-thumb');
// console.log("Selecting",elementToSelect,'because of ID',id);
}
function latestClickSrc() {
if (!latestClickedElement) return {
src: null,
img: null
};
let latestClickImg = latestClickedElement.querySelector("img");
if (!latestClickImg) return;
return {
src: latestClickImg.src,
img: latestClickImg
};
}
async function updateListenersOnNewGeneration() {
let srcRestore = latestClickSrc();
if (!srcRestore) return;
if (currentImgSrcObserver) currentImgSrcObserver.disconnect();
currentImgSrcObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'attributes' && mutation.attributeName === 'src') {
selectThumbFromId(extractSrcId(srcRestore.src));
updateThumbs(true);
currentImgSrcObserver.disconnect();
}
});
});
currentImgSrcObserver.observe(srcRestore.img, { attributes: true, attributeFilter: ['src'] });
}
async function addGenerateButtonListener() {
const button = await waitForElement('[data-tutorial-target="generate-button"]');
if (!button) return;
if (generateButtonListener) button.removeEventListener('click', generateButtonListener);
generateButtonListener = () => {
updateListenersOnNewGeneration();
}
button.addEventListener('click', generateButtonListener);
}
async function openFirstImage() {
let observer = new MutationObserver(() => {
if (!document.body.innerText.match(/completed/i)) {
let firstPreview = document.querySelector(imgOriginalSelector);
if (firstPreview) {
firstPreview.click();
observer.disconnect();
} else {
console.error("Couldn't locate", firstPreview, "with", imgPreviewSelector)
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
async function thumbListener(event) {
let clickedElementTarget = eventToElement(event);
let latestSrc = latestClickSrc();
if (!latestSrc) {
console.error("Returning because there is no latestSrc");
return;
}
if (!!latestSrc && latestSrc.src && extractSrcId(clickedElementTarget.src) === extractSrcId(latestSrc.src)) return;
console.log("Clicked:", clickedElementTarget, event)
let id = extractPreviewId(clickedElementTarget);
latestClickedElement = clickedElementTarget;
let alreadyInsideDB = await isIdPresentInDatabase(id, 'previews');
// console.log(alreadyInsideDB ? "was already inside" : "wasn't already inside");
await highlightSelected(clickedElementTarget);
// console.log("Was highlight selected!");
// upsertDatabase(id);
latestClickedID = id;
preloadFullImages();
updateThumbs();
displayOpenedState(id, clickedElementTarget, true);
addGenerateButtonListener();
createEnforceNegativeCheckbox();
if (!alreadyInsideDB) openFirstImage()
}
function manageEnforceNegative(isChecked) {
const textarea = document.querySelector('textarea[placeholder="Enter negative prompt here"]');
let storedValue = localStorage.getItem('enforceNegativeNegativePrompt');
if (!isChecked && !storedValue) {
localStorage.removeItem('enforceNegativeNegativePrompt');
shouldEnforceNegative = false;
return;
}
localStorage.setItem('enforceNegativeNegativePrompt', textarea.value);
shouldEnforceNegative = true;
}
let toggleCheckbox = (selector, isChecked) => {
let checkboxLabel = document.querySelector(selector);
if (!checkboxLabel) return;
let checkedPath = checkboxLabel.querySelector('.checked-path');
checkedPath.style.display = !isChecked ? 'none' : 'block';
let checkbox = checkboxLabel.querySelector('input[type="checkbox"]');
checkbox.checked = isChecked;
// console.log("setting",checkbox, "to", isChecked)
};
function syncNegativePrompt() {
toggleCheckbox('#enforce-negative', shouldEnforceNegative);
if (!shouldEnforceNegative) {
// console.log("if (!shouldEnforceNegative)")
return;
}
let textarea = document.querySelector('textarea[placeholder="Enter negative prompt here"]');
if (document.activeElement === textarea) {
localStorage.removeItem('enforceNegativeNegativePrompt');
shouldEnforceNegative = false;
// console.log("Active element, skipping")
return;
}
let storedValue = localStorage.getItem('enforceNegativeNegativePrompt');
if (!storedValue) {
// console.log("f (!storedValue) {")
return;
}
if (textarea.value.trim() === storedValue.trim()) {
// console.log("alrready changed");
return;
}
textarea.value = ''; // Clear the textarea
textarea.value = storedValue; // Set the new value
textarea.dispatchEvent(new Event('input', { bubbles: true })); // Trigger input event
console.log("set to", storedValue);
awakeTextarea(textarea);
}
setInterval(syncNegativePrompt, 1 * 1000);
async function slideShowDowloadButtonManager() {
const observer = new MutationObserver(() => {
const nextButton = document.querySelector('.pswp__button--arrow--next');
if (!nextButton || !!document.querySelector('#custom-download')) return;
const button = document.createElement('button');
button.id = 'custom-download';
button.title = 'Download with prompt as file name';
button.innerHTML = `<svg aria-hidden="true" viewBox="0 0 32 32" width="32" height="32"><use class="pswp__icn-shadow" xlink:href="#pswp__icn-download"></use><path d="M20.5 14.3 17.1 18V10h-2.2v7.9l-3.4-3.6L10 16l6 6.1 6-6.1ZM23 23H9v2h14Z" id="pswp__icn-download"></path></svg>`;
button.onclick = async () => {
nextButton.style.cursor = 'pointer';
await saveImage();
};
nextButton.insertAdjacentElement('beforebegin', button);
setTimeout(() => button.classList.add('show'), 10);
addHideSlideshowListeners(button);
});
observer.observe(document.body, { childList: true, subtree: true });
window.addEventListener('beforeunload', () => {
observer.disconnect();
});
}
function sanitizeFilename(filename) {
const maxLength = 125;
const dotIndex = filename.lastIndexOf('.');
const extension = dotIndex !== -1 ? filename.substring(dotIndex) : ''; // Get the file extension
const baseFilename = dotIndex !== -1 ? filename.substring(0, dotIndex) : filename; // Get the base filename
const sanitizedBase = baseFilename
.replace(/[^a-zA-Z0-9-_\. ]/g, '_') // Replace invalid characters with underscores
.replace(/\s+/g, '_') // Replace spaces with underscores
.replace(/_+/g, '_') // Remove duplicate underscores
.substring(0, maxLength - extension.length); // Truncate to max length minus extension
return sanitizedBase + extension; // Combine sanitized base with extension
}
async function saveImage() {
const textarea = document.querySelector(promptTextareaSelector);
const imgSrc = document.querySelector('#pswp__items .pswp__item[aria-hidden="false"] img.pswp__img')?.src;
if (!textarea || !imgSrc) return;
let filename = sanitizeFilename(`${textarea.value.trim()}.png`);
await GM_download({
url: imgSrc,
name: filename,
saveAs: false
});
}
function highlightOpenThumbnail() {
let firstPreviewSrc = document.querySelector(imgPreviewSelector);
if (!firstPreviewSrc) return console.warn(`Element not found for selector: ${imgPreviewSelector}`);
let currentId = extractSrcId(firstPreviewSrc.src);
if (!currentId) return console.warn('Current ID could not be extracted from the image source.');
console.log("highlightOpenThumbnail(): Selection id", currentId, "from", firstPreviewSrc.src, 'of', firstPreviewSrc)
selectThumbFromId(currentId);
}
function createCheckbox(id, labelText, onChangeFunction) {
const newLabel = document.createElement('label');
newLabel.style.userSelect = "none";
newLabel.id = id;
newLabel.innerHTML = `
${labelText}
<input type="checkbox" style="display:none">
<svg class="sc-eDvSVe cSfylm MuiSvgIcon-root MuiSvgIcon-fontSizeMedium" focusable="false" aria-hidden="true" viewBox="0 0 24 24">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path>
<path class="checked-path" d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path>
</svg>`;
const checkbox = newLabel.querySelector('input[type="checkbox"]');
const checkedPath = newLabel.querySelector('.checked-path');
checkbox.checked = shouldEnforceNegative;
checkbox.addEventListener('change', function () {
checkedPath.style.display = !this.checked ? 'none' : 'block';
if (typeof onChangeFunction === 'function') onChangeFunction(this.checked);
});
return newLabel;
}
function createEnforceNegativeCheckbox() {
const negativeLabel = Array.from(document.querySelectorAll('label')).find(label => label.textContent === 'Negative');
if (!negativeLabel || document.getElementById('enforce-negative')) return;
negativeLabel.parentElement.insertBefore(createCheckbox('enforce-negative', 'Enforce negative for every task', manageEnforceNegative), negativeLabel);
}
async function slideShowLifetimeMonitor() {
while (true) {
while (!document.querySelector('#pswp__items')) await new Promise(resolve => setTimeout(resolve, 100));
slideshowPresent = true;
shortcuts.setContext('slideshowOpen', true);
if (latestClickedID) {
console.log("Adding", latestClickedID, "to DB");
upsertDatabase(latestClickedID);
}
while (!!document.querySelector('#pswp__items')) await new Promise(resolve => setTimeout(resolve, 100));
slideshowPresent = false;
shortcuts.setContext('slideshowOpen', false);
await new Promise(resolve => setTimeout(resolve, 100));
highlightOpenThumbnail();
}
}
function checkOpenedImageOpacity() {
let openedImage = document.querySelector('div.pswp__item[aria-hidden="false"] > div.pswp__zoom-wrap > img');
// console.log("opened",openedImage,"opacity:",parseFloat(openedImage.style.opacity));
return !!openedImage && (openedImage.complete && openedImage.naturalWidth !== 0) && (!openedImage.style.opacity || parseFloat(openedImage.style.opacity) >= 1);
}
async function addHideSlideshowListeners(customDownload) {
if (!customDownload) return;
let firstImageShowed = false;
const elements = document.querySelectorAll('.pswp__scroll-wrap, .pswp__button--close');
const bg = document.querySelector('.pswp__bg');
bg.classList.add("black-bg");
if (!firstImageShowed) {
firstImageShowed = checkOpenedImageOpacity();
}
const hideDownload = async (e) => {
const initialOpacity = parseFloat(bg.style.opacity) || 1;
let opacityChanged = false;
if (!firstImageShowed) {
firstImageShowed = checkOpenedImageOpacity();
}
const observer = new MutationObserver(() => {
if (firstImageShowed && !bg.classList.contains("black-bg") && !opacityChanged) bg.classList.add("black-bg");
if (parseFloat(bg.style.opacity) !== initialOpacity) {
customDownload.classList.add('hide');
opacityChanged = true;
bg.classList.remove("black-bg");
observer.disconnect();
}
});
observer.observe(bg, { attributes: true });
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible' && !opacityChanged) observer.disconnect();
});
bg.classList.remove("black-bg");
await new Promise(resolve => setTimeout(resolve, 2000));
if (opacityChanged) {
customDownload.classList.add('hide');
}
observer.disconnect();
};
elements.forEach(el => el.addEventListener('click', hideDownload));
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hideDownload(e); });
}
function updateListeners() {
thumbIcons.forEach(icon => {
if (!icon.thumbClickListenerAdded) {
icon.addEventListener('click', thumbListener);
icon.thumbClickListenerAdded = true;
}
});
}
async function detectScroll() {
let scroller = await waitForElements(scrollListSelector);
// if (!scroller.length) return;
scroller[0].addEventListener('scroll', () => {
// console.log("scrolling")
requestAnimationFrame(() => updateThumbs(true));
});
}
const scale = (x) => {
if (x >= 2) return 1.5;
if (x < 1) return x;
return 0.5 * (x - 1) + 1;
};
function awakeTextarea(textarea, input = null) {
textarea.focus();
if (!input) {
textarea.value += ' ';
} else {
textarea.value = input;
}
textarea.dispatchEvent(new InputEvent('input', { bubbles: true }));
setTimeout(() => {
textarea.value = textarea.value.slice(0, -1);
textarea.dispatchEvent(new InputEvent('input', { bubbles: true }));
textarea.dispatchEvent(new Event('change', { bubbles: true }));
textarea.blur();
}, 500);
};
const textareaPasteFix = async (promptTextareaSelector) => {
const textarea = await waitForElement(promptTextareaSelector);
textarea.addEventListener('paste', (event) => {
const clipboardData = event.clipboardData.getData('text/plain');
const modifiedText = clipboardData
.replace(/(\d+) year old/g, '$1yo')
.replace(/(\d+) years old/g, '$1yo')
.replace(/(\d+) years-old/g, '$1yo')
.replace(/thx/g, 'thanks')
.replace(/\(\(/g, '(')
.replace(/\)\)/g, ')')
.replace(/:(\d+(\.\d+)?)/g, (match, num) => {
const scaledNum = scale(parseFloat(num));
return `:${Math.round(scaledNum * 10) / 10}`;
});
if (clipboardData !== modifiedText) {
event.preventDefault();
const { selectionStart: start, selectionEnd: end } = textarea;
const textBefore = textarea.value.slice(0, start);
const textAfter = textarea.value.slice(end);
textarea.value = textBefore + modifiedText + textAfter;
textarea.selectionStart = textarea.selectionEnd = start + modifiedText.length;
awakeTextarea(textarea);
}
});
};
window.addEventListener('blur', () => previewListCheckListener.disconnect());
window.addEventListener('focus', startScrollListListener);
function startScrollListListener() {
const targetNode = document.querySelector(scrollListSelector);
if (!targetNode) return;
previewListCheckListener = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType !== 1 || !node.matches('[data-label="check"]')) return;
const parent = node.parentElement;
const id = extractPreviewId(parent);
if (!openedPreviewCache.get(id)) {
node.remove();
console.log("Removed", node, "because", id, "not present")
}
});
});
});
previewListCheckListener.observe(targetNode, { childList: true, subtree: true });
}
startScrollListListener();
textareaPasteFix(promptTextareaSelector)
upsertDatabase(1)
detectScroll();
slideShowLifetimeMonitor();
slideShowDowloadButtonManager();
createEnforceNegativeCheckbox();
updateThumbs(true);
window.addEventListener('focus', () => {
updateThumbs(true);
highlightOpenThumbnail();
});
let scrollList = document.querySelector(scrollListSelector);
scrollList.addEventListener('mouseenter', () => updateThumbs(true));
scrollList.addEventListener('mouseleave', () => updateThumbs(true));
scrollList.addEventListener('mousemove', () => updateThumbs());
GM_addStyle(`
.black-bg{
opacity: 1 !important;
}
.pswp__bg{
transition: opacity .5s cubic-bezier(0.25,0.1,0.25,1);
}
.selected-thumb{
outline: 2px solid hsla(0, 12%, 85.3%, 0.77);
transition: outline 100ms;
}
#app .ring-2{
box-shadow: none;
}
[data-test-id="virtuoso-item-list"] .contents>button img{
cursor:pointer;
transition: filter .1s ease;
}
[data-test-id="virtuoso-item-list"] .contents>button img:hover{
filter: brightness(1.05);
}
[data-label="check"] {
position: absolute;
left: 0;
bottom: 0;
background: #ffffff2b;
border-top-right-radius: 5px;
backdrop-filter: blur(10px);
filter: brightness(1.3);
transition: opacity .5s ease;
opacity: 0;
}
#custom-download{
width: 75px;
height: 100px;
margin-top: -50px;
position: absolute;
top: 50%;
right: calc(75px + .5rem);
display: flex; justify-content: center; align-items: center;
opacity:0;
will-change: opacity;
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
}
#custom-download.show{
opacity:1;
}
#custom-download.hide{
opacity:0;
}
#custom-download > svg{
fill: var(--pswp-icon-color);
/* color: var(--pswp-icon-color-secondary); */
width: 60px;
height: 60px;
}
#custom-download > svg > .pswp__icn-shadow {
stroke-width: 1px;
}
button[aria-label="Download"][type="button"]{
display:none
}
`);
})();