Horizontal floating search window with paste & search functionality
// ==UserScript==
// @name Quick search - Ctrl+Shift+F - 2.3
// @namespace http://tampermonkey.net/
// @version 2.3
// @description Horizontal floating search window with paste & search functionality
// @author </j0tsarup>
// @match *://*/*
// @grant GM_addStyle
// @grant GM_addElement
// @run-at document-start
// ==/UserScript==
/*
*
* • Keyboard shortcuts: Ctrl+Shift+F (open), ESC (close)
* FEATURES:
* • Floating search window with drag-to-move functionality
* • Paste & search directly from clipboard
* • Multiple search engines: Google, Bing, DuckDuckGo, Yahoo
* • Optional location-based search
* • Toggle bubble for quick access
* • Clean, modern UI with smooth animations
*
* CHANGELOG v2.3:
* • Added version number to script name
* • Added formatted header with feature list
* • Updated author information
* • Improved code documentation
*
* ═══════════════════════════════════════════════════════════════════════════
*/
(function() {
'use strict';
// Wait for document to be ready
function init() {
// Add CSS styles with higher priority
GM_addStyle(`
#floatingSearchWindow {
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
width: 900px !important;
max-width: 90vw !important;
background: #ffffff !important;
border-radius: 12px !important;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15) !important;
z-index: 2147483647 !important;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif !important;
display: none !important;
padding: 20px !important;
}
#floatingSearchWindow.active {
display: block !important;
}
.search-header {
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
margin-bottom: 16px !important;
padding-bottom: 12px !important;
border-bottom: 1px solid #e5e5e5 !important;
}
.search-title {
font-size: 14px !important;
font-weight: 500 !important;
color: #000000 !important;
margin: 0 !important;
}
.header-controls {
display: flex !important;
gap: 8px !important;
align-items: center !important;
}
.bubble-toggle {
font-size: 11px !important;
padding: 6px 12px !important;
background: #f5f5f5 !important;
border: 1px solid #ddd !important;
border-radius: 6px !important;
cursor: pointer !important;
color: #000000 !important;
transition: all 0.2s !important;
}
.bubble-toggle:hover {
background: #e8e8e8 !important;
}
.bubble-toggle.enabled {
background: #000000 !important;
color: #ffffff !important;
border-color: #000000 !important;
}
.close-btn {
background: none !important;
border: none !important;
font-size: 20px !important;
color: #666 !important;
cursor: pointer !important;
padding: 0 !important;
width: 24px !important;
height: 24px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
border-radius: 4px !important;
transition: all 0.2s !important;
}
.close-btn:hover {
background: #f0f0f0 !important;
color: #000 !important;
}
.search-main {
display: flex !important;
gap: 12px !important;
align-items: stretch !important;
margin-bottom: 16px !important;
}
.search-input-wrapper {
flex: 1 !important;
position: relative !important;
}
.search-input {
width: 100% !important;
padding: 14px 16px !important;
border: 2px solid #e5e5e5 !important;
border-radius: 8px !important;
font-size: 14px !important;
transition: all 0.2s !important;
box-sizing: border-box !important;
font-family: inherit !important;
color: #000000 !important;
}
.search-input::placeholder {
color: #999 !important;
}
.search-input:focus {
outline: none !important;
border-color: #000000 !important;
}
.search-btn {
padding: 14px 28px !important;
background: #000000 !important;
color: #ffffff !important;
border: none !important;
border-radius: 8px !important;
font-size: 14px !important;
font-weight: 500 !important;
cursor: pointer !important;
transition: all 0.2s !important;
white-space: nowrap !important;
}
.search-btn:hover {
background: #333333 !important;
}
.paste-search-btn {
padding: 14px 24px !important;
background: #f5f5f5 !important;
color: #000000 !important;
border: 2px solid #e5e5e5 !important;
border-radius: 8px !important;
font-size: 14px !important;
font-weight: 500 !important;
cursor: pointer !important;
transition: all 0.2s !important;
white-space: nowrap !important;
}
.paste-search-btn:hover {
background: #e8e8e8 !important;
border-color: #000000 !important;
}
.search-options {
display: flex !important;
gap: 8px !important;
flex-wrap: wrap !important;
}
.option-group {
display: flex !important;
gap: 6px !important;
}
.option-btn {
padding: 8px 16px !important;
background: #f5f5f5 !important;
color: #000000 !important;
border: 2px solid #e5e5e5 !important;
border-radius: 6px !important;
font-size: 13px !important;
cursor: pointer !important;
transition: all 0.2s !important;
font-weight: 500 !important;
}
.option-btn:hover {
background: #e8e8e8 !important;
}
.option-btn.active {
background: #000000 !important;
color: #ffffff !important;
border-color: #000000 !important;
}
.location-input {
padding: 8px 14px !important;
border: 2px solid #e5e5e5 !important;
border-radius: 6px !important;
font-size: 13px !important;
font-family: inherit !important;
color: #000000 !important;
min-width: 150px !important;
}
.location-input::placeholder {
color: #999 !important;
}
.location-input:focus {
outline: none !important;
border-color: #000000 !important;
}
.toggle-bubble {
position: fixed !important;
bottom: 30px !important;
right: 30px !important;
width: 56px !important;
height: 56px !important;
background: #000000 !important;
border: none !important;
border-radius: 50% !important;
color: white !important;
font-size: 20px !important;
cursor: pointer !important;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2) !important;
z-index: 2147483646 !important;
transition: all 0.3s !important;
display: none !important;
}
.toggle-bubble.visible {
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
.toggle-bubble:hover {
transform: scale(1.1) !important;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3) !important;
}
.draggable-handle {
position: absolute !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
height: 12px !important;
cursor: move !important;
}
`);
// Create the floating window HTML
const searchWindow = document.createElement('div');
searchWindow.id = 'floatingSearchWindow';
searchWindow.innerHTML = `
<div class="draggable-handle"></div>
<div class="search-header">
<h2 class="search-title">rumpsy's quick search</h2>
<div class="header-controls">
<button class="bubble-toggle" id="bubbleToggle">bubble: off</button>
<button class="close-btn" id="closeSearch">×</button>
</div>
</div>
<div class="search-main">
<div class="search-input-wrapper">
<input type="text" class="search-input" id="searchQuery" placeholder="search query">
</div>
<button class="search-btn" id="executeSearch">search</button>
<button class="paste-search-btn" id="pasteSearch">paste & search</button>
</div>
<div class="search-options">
<div class="option-group">
<button class="option-btn active" data-engine="google">google</button>
<button class="option-btn" data-engine="bing">bing</button>
<button class="option-btn" data-engine="duckduckgo">duckduckgo</button>
<button class="option-btn" data-engine="yahoo">yahoo</button>
</div>
<input type="text" class="location-input" id="searchLocation" placeholder="location (optional)">
</div>
`;
// Create toggle bubble
const toggleBubble = document.createElement('button');
toggleBubble.className = 'toggle-bubble';
toggleBubble.innerHTML = '🔍';
toggleBubble.title = 'Open Search (Ctrl+Shift+F)';
// Append to body
if (document.body) {
document.body.appendChild(searchWindow);
document.body.appendChild(toggleBubble);
} else {
// Wait for body to be available
const observer = new MutationObserver(() => {
if (document.body) {
document.body.appendChild(searchWindow);
document.body.appendChild(toggleBubble);
observer.disconnect();
}
});
observer.observe(document.documentElement, { childList: true });
}
// State
let selectedEngine = 'google';
let bubbleEnabled = false;
// Toggle window visibility
function toggleWindow() {
searchWindow.classList.toggle('active');
if (searchWindow.classList.contains('active')) {
document.getElementById('searchQuery').focus();
}
}
toggleBubble.addEventListener('click', toggleWindow);
// Close button
document.getElementById('closeSearch').addEventListener('click', () => {
searchWindow.classList.remove('active');
});
// Bubble toggle
document.getElementById('bubbleToggle').addEventListener('click', () => {
bubbleEnabled = !bubbleEnabled;
const bubbleBtn = document.getElementById('bubbleToggle');
if (bubbleEnabled) {
toggleBubble.classList.add('visible');
bubbleBtn.classList.add('enabled');
bubbleBtn.textContent = 'bubble: on';
} else {
toggleBubble.classList.remove('visible');
bubbleBtn.classList.remove('enabled');
bubbleBtn.textContent = 'bubble: off';
}
});
// Engine selection
document.querySelectorAll('.option-btn[data-engine]').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.option-btn[data-engine]').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
selectedEngine = btn.dataset.engine;
});
});
// Make window draggable
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
const dragHandle = searchWindow.querySelector('.draggable-handle');
dragHandle.addEventListener('mousedown', (e) => {
isDragging = true;
initialX = e.clientX - searchWindow.offsetLeft;
initialY = e.clientY - searchWindow.offsetTop;
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
searchWindow.style.transform = 'none';
searchWindow.style.left = currentX + 'px';
searchWindow.style.top = currentY + 'px';
}
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
// Search functionality
function performSearch(query = null) {
const searchQuery = query || document.getElementById('searchQuery').value.trim();
const location = document.getElementById('searchLocation').value.trim();
if (!searchQuery) {
alert('Please enter a search query');
return;
}
let fullQuery = searchQuery;
if (location) {
fullQuery += ' ' + location;
}
const encodedQuery = encodeURIComponent(fullQuery);
let searchURL = '';
switch(selectedEngine) {
case 'google':
searchURL = `https://www.google.com/search?q=${encodedQuery}`;
break;
case 'bing':
searchURL = `https://www.bing.com/search?q=${encodedQuery}`;
break;
case 'duckduckgo':
searchURL = `https://duckduckgo.com/?q=${encodedQuery}`;
break;
case 'yahoo':
searchURL = `https://search.yahoo.com/search?p=${encodedQuery}`;
break;
}
window.open(searchURL, '_blank');
}
document.getElementById('executeSearch').addEventListener('click', () => performSearch());
document.getElementById('searchQuery').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
performSearch();
}
});
// Paste and search functionality
document.getElementById('pasteSearch').addEventListener('click', async () => {
try {
const text = await navigator.clipboard.readText();
if (text) {
performSearch(text);
} else {
alert('Clipboard is empty');
}
} catch (err) {
alert('Unable to read clipboard. Please paste manually.');
}
});
// Keyboard shortcut: Ctrl+Shift+F to toggle
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'F') {
e.preventDefault();
toggleWindow();
}
// ESC to close
if (e.key === 'Escape' && searchWindow.classList.contains('active')) {
searchWindow.classList.remove('active');
}
});
// Close window when clicking outside
searchWindow.addEventListener('click', (e) => {
if (e.target === searchWindow) {
searchWindow.classList.remove('active');
}
});
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();