// ==UserScript==
// @name Floating Link Menu
// @namespace http://tampermonkey.net/
// @version 2.2 universal
// @description Customizable link menu.
// @author echoZ
// @license MIT
// @match *://*/*
// @exclude *://*routerlogin.net/*
// @exclude *://*192.168.1.1/*
// @exclude *://*192.168.0.1/*
// @exclude *://*my.bankofamerica.com/*
// @exclude *://*wellsfargo.com/*
// @exclude *://*chase.com/*
// @exclude *://*citibank.com/*
// @exclude *://*online.citi.com/*
// @exclude *://*capitalone.com/*
// @exclude *://*usbank.com/*
// @exclude *://*paypal.com/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// --- SCRIPT EXCLUSION LOGIC ---
const excludedDomainsStorageKey = 'excludedUniversalDomains';
const isBubbleHiddenStorageKey = 'isBubbleHidden';
function getExcludedDomains() {
const storedDomains = localStorage.getItem(excludedDomainsStorageKey);
return storedDomains ? JSON.parse(storedDomains) : [];
}
const excludedDomains = getExcludedDomains();
const currentUrl = window.location.href;
const isExcluded = excludedDomains.some(domain => currentUrl.includes(domain));
if (isExcluded) {
return;
}
// --- END EXCLUSION LOGIC ---
// --- UNIFIED LINKS & STATE ---
const storageKey = 'universalLinkManagerLinks';
let isDeleteMode = false;
let isExcludeDeleteMode = false;
let isExportMode = false;
let isImportMode = false;
let clickCount = 0;
let clickTimer = null;
function getBubbleHiddenState() {
return localStorage.getItem(isBubbleHiddenStorageKey) === 'true';
}
function saveBubbleHiddenState(isHidden) {
localStorage.setItem(isBubbleHiddenStorageKey, isHidden);
}
function saveExcludedDomains() {
localStorage.setItem(excludedDomainsStorageKey, JSON.stringify(excludedDomains));
}
function getLinks() {
const storedLinks = localStorage.getItem(storageKey);
if (storedLinks) {
return JSON.parse(storedLinks);
}
return [
{ label: 'Google', url: 'https://www.google.com/' },
{ label: 'Gemini AI', url: 'https://gemini.google.com/' },
{ label: 'OpenAI', url: 'https://www.openai.com/' }
];
}
let userLinks = getLinks();
function saveLinks() {
localStorage.setItem(storageKey, JSON.stringify(userLinks));
}
function populateLinkList(linkListElement) {
linkListElement.innerHTML = '';
userLinks.forEach((linkData, index) => {
const linkWrapper = document.createElement('div');
linkWrapper.className = 'link-wrapper';
const link = document.createElement('a');
link.href = linkData.url;
link.textContent = linkData.label;
link.target = '_blank';
linkWrapper.appendChild(link);
if (isDeleteMode) {
const deleteButton = document.createElement('button');
deleteButton.className = 'delete-link-button';
deleteButton.textContent = 'x';
deleteButton.addEventListener('click', (event) => {
event.preventDefault();
userLinks.splice(index, 1);
saveLinks();
populateLinkList(linkListElement);
});
linkWrapper.appendChild(deleteButton);
}
linkListElement.appendChild(linkWrapper);
});
}
function populateExcludeList(excludeListElement) {
excludeListElement.innerHTML = '';
excludedDomains.forEach((domain, index) => {
const domainWrapper = document.createElement('div');
domainWrapper.className = 'exclude-wrapper';
const domainLabel = document.createElement('span');
domainLabel.textContent = domain;
domainWrapper.appendChild(domainLabel);
if (isExcludeDeleteMode) {
const deleteButton = document.createElement('button');
deleteButton.className = 'delete-exclude-button';
deleteButton.textContent = 'x';
deleteButton.addEventListener('click', (event) => {
event.preventDefault();
excludedDomains.splice(index, 1);
saveExcludedDomains();
populateExcludeList(excludeListElement);
});
domainWrapper.appendChild(deleteButton);
}
excludeListElement.appendChild(domainWrapper);
});
}
function initializeScript() {
if (document.getElementById('customFloatingBubble')) {
return;
}
const bubble = document.createElement('div');
bubble.id = 'customFloatingBubble';
bubble.textContent = 'λ';
const menu = document.createElement('div');
menu.id = 'floatingMenu';
const linkList = document.createElement('div');
linkList.id = 'linkList';
const linkForm = document.createElement('div');
linkForm.id = 'linkForm';
linkForm.innerHTML = `
<h3>Add New Link</h3>
<input type="text" id="linkLabel" placeholder="Label (e.g. My Site)">
<input type="text" id="linkUrl" placeholder="URL (e.g. https://example.com)">
<button id="saveLinkButton">Save</button>
`;
const saveLinkButton = linkForm.querySelector('#saveLinkButton');
const linkLabelInput = linkForm.querySelector('#linkLabel');
const linkUrlInput = linkForm.querySelector('#linkUrl');
const excludeSection = document.createElement('div');
excludeSection.id = 'excludeSection';
excludeSection.innerHTML = `
<h3>Excluded Websites</h3>
<div id="excludeList"></div>
<input type="text" id="excludeUrl" placeholder="Domain (e.g. example.com)">
<button id="saveExcludeButton">Add Exclude</button>
<button id="deleteExcludeButton">Delete Excludes</button>
`;
const excludeListElement = excludeSection.querySelector('#excludeList');
const deleteExcludeButton = excludeSection.querySelector('#deleteExcludeButton');
const saveExcludeButton = excludeSection.querySelector('#saveExcludeButton');
const excludeUrlInput = excludeSection.querySelector('#excludeUrl');
const backupSection = document.createElement('div');
backupSection.id = 'backupSection';
backupSection.innerHTML = `
<h3>Backup & Restore</h3>
<div id="exportWrapper">
<button id="exportButton">Export</button>
</div>
<div id="importWrapper">
<button id="importButton">Import</button>
</div>
`;
const exportWrapper = backupSection.querySelector('#exportWrapper');
const importWrapper = backupSection.querySelector('#importWrapper');
const controls = document.createElement('div');
controls.id = 'menuControls';
controls.innerHTML = `
<button id="deleteLinksButton">Delete Links</button>
<button id="hideButton">Hide Button</button>
<button id="closeMenuButton">Close Menu</button>
`;
const deleteLinksButton = controls.querySelector('#deleteLinksButton');
const hideButton = controls.querySelector('#hideButton');
const closeMenuButton = controls.querySelector('#closeMenuButton');
// Triple-click functionality
const showBubbleButton = document.createElement('div');
showBubbleButton.id = 'showBubbleButton';
const style = document.createElement('style');
style.innerHTML = `
#customFloatingBubble {
position: fixed;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
background-color: #0ff;
border-radius: 50%;
box-shadow: 0 0 15px 3px #0ff, 0 0 30px 10px #0ff;
cursor: pointer;
z-index: 9999999;
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
font-weight: 900;
color: #001f3f;
user-select: none;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
#customFloatingBubble:hover {
transform: scale(1.15);
box-shadow: 0 0 20px 5px #0ff, 0 0 40px 15px #0ff;
}
#floatingMenu {
position: fixed;
bottom: 100px;
right: 30px;
width: 300px;
background-color: #222;
border: 2px solid #0ff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,255,255,0.7);
padding: 10px;
z-index: 9999998;
display: none;
flex-direction: column;
gap: 10px;
max-height: 80vh;
overflow-y: auto;
}
#linkList, #excludeList {
display: flex;
flex-direction: column;
gap: 5px;
}
.link-wrapper, .exclude-wrapper {
display: flex;
align-items: center;
gap: 5px;
}
#linkList a, .exclude-wrapper span {
flex-grow: 1;
padding: 8px;
color: #fff;
background-color: #333;
border: 1px solid #0ff;
text-align: center;
text-decoration: none;
border-radius: 5px;
transition: background-color 0.2s ease, color 0.2s ease;
}
#linkList a:hover {
background-color: #0ff;
color: #000;
}
.delete-link-button, .delete-exclude-button {
width: 30px;
height: 30px;
background-color: #a00;
color: #fff;
border: 1px solid #f00;
border-radius: 50%;
cursor: pointer;
font-weight: bold;
transition: background-color 0.2s ease;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
}
.delete-link-button:hover, .delete-exclude-button:hover {
background-color: #f00;
}
#menuControls {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 5px;
}
#menuControls button {
padding: 8px 12px;
background-color: #444;
color: #0ff;
border: 1px solid #0ff;
border-radius: 5px;
cursor: pointer;
flex: 1 1 45%;
font-size: 12px;
text-align: center;
}
#menuControls button:hover {
background-color: #0ff;
color: #000;
}
#linkForm, #excludeSection, #backupSection {
display: flex;
flex-direction: column;
gap: 5px;
padding-top: 10px;
border-top: 1px solid #444;
}
#linkForm h3, #excludeSection h3, #backupSection h3 {
color: #fff;
margin: 0;
text-align: center;
}
#linkForm input, #excludeSection input, #backupSection textarea {
padding: 8px;
border: 1px solid #0ff;
background-color: #333;
color: #fff;
border-radius: 5px;
}
#backupSection button {
padding: 8px 12px;
background-color: #444;
color: #0ff;
border: 1px solid #0ff;
border-radius: 5px;
cursor: pointer;
flex: 1;
font-size: 14px;
}
#backupSection button:hover {
background-color: #0ff;
color: #000;
}
#showBubbleButton {
position: fixed;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
cursor: pointer;
z-index: 9999997;
}
`;
document.head.appendChild(style);
document.body.appendChild(bubble);
document.body.appendChild(menu);
menu.appendChild(linkList);
menu.appendChild(linkForm);
menu.appendChild(excludeSection);
menu.appendChild(backupSection);
menu.appendChild(controls);
populateLinkList(linkList);
populateExcludeList(excludeListElement);
const isHidden = getBubbleHiddenState();
bubble.style.display = isHidden ? 'none' : 'flex';
if (isHidden) {
document.body.appendChild(showBubbleButton);
}
// --- Event Listeners for the Triple-Click functionality ---
function handleBubbleClick(e) {
clickCount++;
if (clickCount === 1) {
clickTimer = setTimeout(() => {
if (clickCount === 1) {
const isMenuVisible = menu.style.display === 'flex';
menu.style.display = isMenuVisible ? 'none' : 'flex';
}
clickCount = 0;
}, 300); // 300ms window for triple-click
} else if (clickCount === 3) {
clearTimeout(clickTimer);
bubble.style.display = 'none';
menu.style.display = 'none';
saveBubbleHiddenState(true);
document.body.appendChild(showBubbleButton);
clickCount = 0;
}
}
function handleShowBubbleClick(e) {
clickCount++;
if (clickCount === 3) {
bubble.style.display = 'flex';
saveBubbleHiddenState(false);
menu.style.display = 'flex';
showBubbleButton.remove();
clickCount = 0;
}
}
bubble.addEventListener('click', handleBubbleClick);
showBubbleButton.addEventListener('click', handleShowBubbleClick);
// --- UI BUTTON LISTENERS ---
const exportButton = backupSection.querySelector('#exportButton');
const importButton = backupSection.querySelector('#importWrapper #importButton');
hideButton.addEventListener('click', () => {
bubble.style.display = 'none';
menu.style.display = 'none';
saveBubbleHiddenState(true);
document.body.appendChild(showBubbleButton);
});
closeMenuButton.addEventListener('click', () => {
menu.style.display = 'none';
});
saveLinkButton.addEventListener('click', () => {
const label = linkLabelInput.value;
const url = linkUrlInput.value;
if (label && url) {
userLinks.push({ label, url });
saveLinks();
populateLinkList(linkList);
linkLabelInput.value = '';
linkUrlInput.value = '';
} else {
alert('Please enter both a label and a URL.');
}
});
deleteLinksButton.addEventListener('click', () => {
isDeleteMode = !isDeleteMode;
deleteLinksButton.textContent = isDeleteMode ? 'Exit Delete' : 'Delete Links';
populateLinkList(linkList);
});
saveExcludeButton.addEventListener('click', () => {
const domain = excludeUrlInput.value;
if (domain && !excludedDomains.includes(domain)) {
excludedDomains.push(domain);
saveExcludedDomains();
populateExcludeList(excludeListElement);
excludeUrlInput.value = '';
} else if (excludedDomains.includes(domain)) {
alert('This domain is already on the exclusion list.');
} else {
alert('Please enter a domain to exclude.');
}
});
deleteExcludeButton.addEventListener('click', () => {
isExcludeDeleteMode = !isExcludeDeleteMode;
deleteExcludeButton.textContent = isExcludeDeleteMode ? 'Exit Exclude Delete' : 'Delete Excludes';
populateExcludeList(excludeListElement);
});
// --- BACKUP & RESTORE EVENT LISTENERS ---
exportButton.addEventListener('click', () => {
isExportMode = !isExportMode;
if (isExportMode) {
exportWrapper.innerHTML = `
<textarea readonly>${JSON.stringify(userLinks)}</textarea>
<button id="exportButton">Close</button>
`;
exportWrapper.querySelector('#exportButton').addEventListener('click', () => {
isExportMode = false;
exportWrapper.innerHTML = `<button id="exportButton">Export</button>`;
exportWrapper.querySelector('#exportButton').addEventListener('click', exportButton.click);
});
} else {
exportWrapper.innerHTML = `<button id="exportButton">Export</button>`;
}
});
importButton.addEventListener('click', () => {
isImportMode = !isImportMode;
if (isImportMode) {
importWrapper.innerHTML = `
<textarea placeholder="Paste your link data here..."></textarea>
<button id="loadButton">Load</button>
<button id="importButton">Close</button>
`;
importWrapper.querySelector('#loadButton').addEventListener('click', () => {
const data = importWrapper.querySelector('textarea').value;
try {
const importedLinks = JSON.parse(data);
if (Array.isArray(importedLinks)) {
userLinks = importedLinks;
saveLinks();
populateLinkList(linkList);
alert('Links imported successfully!');
isImportMode = false;
importWrapper.innerHTML = `<button id="importButton">Import</button>`;
importWrapper.querySelector('#importButton').addEventListener('click', importButton.click);
} else {
alert('Invalid data format. Please paste the correct JSON data.');
}
} catch (e) {
alert('Invalid data format. Please paste the correct JSON data.');
}
});
importWrapper.querySelector('#importButton').addEventListener('click', () => {
isImportMode = false;
importWrapper.innerHTML = `<button id="importButton">Import</button>`;
importWrapper.querySelector('#importButton').addEventListener('click', importButton.click);
});
} else {
importWrapper.innerHTML = `<button id="importButton">Import</button>`;
}
});
}
function waitForBody() {
if (document.body) {
initializeScript();
} else {
setTimeout(waitForBody, 100);
}
}
waitForBody();
})();