// ==UserScript==
// @name Finviz Redirect to Robinhood
// @namespace http://tampermonkey.net/
// @version 1.5
// @description Adds buttons linking you to Robinhood from both the single stock page and the screener list.
// @author Game Abuse Studios
// @match https://elite.finviz.com/*
// @license MIT
// @grant GM_addStyle
// @grant window.open
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// --- Inject Global Styles ---
GM_addStyle(`
@keyframes scanline {
0% { transform: translateY(-100%); }
100% { transform: translateY(100%); }
}
.tech-btn {
border: 2px solid transparent;
background: linear-gradient(#1a1f2b, #1a1f2b) padding-box,
linear-gradient(to right, royalblue, blueviolet) border-box;
color: #c9d1d9;
font-family: 'monospace', 'Lucida Console', Monaco, Consolas;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0,0,0,0.4);
transition: all 0.2s ease-in-out;
cursor: pointer;
position: relative;
overflow: hidden;
touch-action: manipulation;
}
.tech-btn:hover, .tech-btn.active {
background: linear-gradient(#2a2f3b, #2a2f3b) padding-box,
linear-gradient(to right, dodgerblue, mediumorchid) border-box;
color: #ffffff;
transform: scale(1.03);
}
.tech-btn::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, transparent 0%, rgba(138, 43, 226, 0.1) 50%, transparent 100%);
opacity: 0;
transform: translateY(-100%);
transition: opacity 0.2s ease;
}
.tech-btn.scanline-active::after {
opacity: 1;
animation: scanline 0.6s linear;
}
`);
// --- Unified Activation Function ---
const handleActivation = (button, ticker) => {
if (event) {
event.preventDefault();
event.stopPropagation();
}
button.classList.add('active', 'scanline-active');
window.open(`https://robinhood.com/stocks/${ticker}`, '_blank');
setTimeout(() => {
button.classList.remove('active', 'scanline-active');
}, 600);
};
// --- Script for Single Stock Page ---
// (This part doesn't need event delegation as the button is not re-rendered)
function setupSingleStockButton() {
function waitForElement(selector, callback) {
const element = document.querySelector(selector);
if (element) {
callback(element);
} else {
const observer = new MutationObserver((mutations, obs) => {
const foundElement = document.querySelector(selector);
if (foundElement) {
obs.disconnect();
callback(foundElement);
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
}
waitForElement('.js-recent-quote-ticker', (tickerElement) => {
const ticker = tickerElement.textContent.trim().replace(/-/g, '.');
const button = document.createElement('button');
button.className = 'tech-btn';
button.textContent = `Open ${ticker} In Robinhood`;
button.title = `Go to ${ticker} on Robinhood`;
button.style.cssText = `
position: fixed;
bottom: 20px;
left: 20px;
z-index: 1000;
padding: 14px 28px;
font-size: 20px;
border-radius: 9999px;
`;
button.addEventListener('touchstart', (e) => handleActivation(button, ticker), { passive: false });
button.addEventListener('click', (e) => handleActivation(button, ticker));
document.body.appendChild(button);
});
}
// --- Script for Screener Page (Modified for Event Delegation) ---
function setupScreenerButtons() {
GM_addStyle(`
.rh-floating-btn {
height: 18px;
font-size: 12px;
border-radius: 9999px;
padding: 0 5px;
display: flex;
align-items: center;
justify-content: center;
z-index: 9998;
border-width: 1px;
width: auto;
touch-action: manipulation;
}
`);
// The logic for adding buttons remains mostly the same, but the event listener is moved.
const updateButtons = () => {
const tickerLinks = document.querySelectorAll('a.tab-link[href*="quote.ashx?t="]');
tickerLinks.forEach(link => {
const parentCell = link.closest('td');
if (!parentCell || parentCell.querySelector('.rh-floating-btn')) return;
const originalTicker = link.textContent.trim();
const robinhoodTicker = originalTicker.replace('-', '.');
const button = document.createElement('button');
button.className = 'tech-btn rh-floating-btn';
button.textContent = originalTicker;
button.dataset.ticker = robinhoodTicker; // Store ticker on the element
parentCell.insertBefore(button, link);
link.remove();
});
};
const screenerTable = document.getElementById('screener-views-table');
if (screenerTable) {
// Attach ONE event listener to the parent table
screenerTable.addEventListener('touchstart', (e) => {
const targetButton = e.target.closest('.tech-btn');
if (targetButton) {
handleActivation(targetButton, targetButton.dataset.ticker);
}
}, { passive: false });
screenerTable.addEventListener('click', (e) => {
const targetButton = e.target.closest('.tech-btn');
if (targetButton) {
handleActivation(targetButton, targetButton.dataset.ticker);
}
});
const observer = new MutationObserver(updateButtons);
observer.observe(screenerTable, { childList: true, subtree: true });
updateButtons();
}
}
// --- Main Logic ---
if (window.location.href.includes('screener.ashx')) {
setupScreenerButtons();
} else {
setupSingleStockButton();
}
})();