// ==UserScript==
// @name Perplexity Helper — True Turquoise
// @namespace https://greasyfork.org/users/1513610
// @version 1.0.0
// @description Auto-enable Incognito mode, enable Social sources, and open the Model switcher popup on perplexity.ai with a full-screen True Turquoise animated loader
// @description:en Auto-enable Incognito mode, enable Social sources, and open the Model switcher popup on perplexity.ai with a full-screen True Turquoise animated loader
// @license MIT
// @match https://www.perplexity.ai/*
// @run-at document-idle
// @grant none
// ==/UserScript==
(() => {
'use strict';
// ────── guard: avoid double-run ──────────────────────────────────────────
if (window.__ppxHelperActive) return;
window.__ppxHelperActive = true;
// ────── configuration ────────────────────────────────────────────────────
const CFG = {
debug: false,
initialDelayMs: 1000,
waitTimeoutMs: 10_000,
waitIntervalMs: 100,
smallPauseMs: 300,
menuPauseMs: 400,
loader: { enable: true },
};
// ────── logging ─────────────────────────────────────────────────────────
const log = (...args) => {
if (!CFG.debug) return;
console.log('%c[Perplexity-Helper]', 'color:#1ABC9C;font-weight:bold;', ...args);
};
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
function withTimeout(promise, ms, label = 'timeout') {
let t;
const timeout = new Promise((_, rej) => (t = setTimeout(() => rej(new Error(label)), ms)));
return Promise.race([promise.finally(() => clearTimeout(t)), timeout]);
}
function waitFor(predicate, { timeout = CFG.waitTimeoutMs, interval = CFG.waitIntervalMs } = {}) {
return withTimeout(
new Promise((resolve, reject) => {
(function tick() {
try {
const val = predicate();
if (val) return resolve(val);
setTimeout(tick, interval);
} catch (e) {
reject(e);
}
})();
}),
timeout,
'waitFor: timed-out'
);
}
function waitForEl(selector, { timeout = CFG.waitTimeoutMs, root = document } = {}) {
const now = root.querySelector(selector);
if (now) return Promise.resolve(now);
if (typeof MutationObserver !== 'function') {
return waitFor(() => root.querySelector(selector), { timeout, interval: CFG.waitIntervalMs });
}
return withTimeout(
new Promise((resolve, reject) => {
let done = false;
const finish = (val, err) => {
if (done) return;
done = true;
try { observer.disconnect(); } catch {}
clearTimeout(timer);
if (err) reject(err); else resolve(val);
};
const observer = new MutationObserver(() => {
try {
const el = root.querySelector(selector);
if (el) finish(el);
} catch (e) {
finish(null, e);
}
});
try {
observer.observe(root === document ? document.documentElement : root, {
childList: true,
subtree: true,
});
} catch (e) {
// Fallback polling if observe fails (rare)
waitFor(() => root.querySelector(selector), { timeout, interval: CFG.waitIntervalMs })
.then(resolve, reject);
return;
}
queueMicrotask(() => {
const el = root.querySelector(selector);
if (el) finish(el);
});
const timer = setTimeout(() => {
finish(null, new Error(`waitForEl: timed-out for "${selector}"`));
}, timeout);
}),
timeout + 50,
'waitForEl: timed-out'
);
}
const normalize = (s) => s?.replace(/\s+/g, ' ').trim() ?? '';
async function findByText(tag, text, { exact = false, timeout = 3_000 } = {}) {
const needle = normalize(text);
return waitFor(() => {
const nodes = Array.from(document.getElementsByTagName(tag));
return nodes.find((n) => {
const t = normalize(n.textContent || '');
return exact ? t === needle : t.toLowerCase().includes(needle.toLowerCase());
});
}, { timeout, interval: CFG.waitIntervalMs });
}
async function safeClick(el) {
if (!el) return;
try {
el.scrollIntoView?.({ block: 'center', inline: 'center' });
await sleep(36);
el.dispatchEvent?.(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
el.click?.();
} catch {}
}
// ────── animated loader (True Turquoise) ─────────────────────────────────
function showLoader() {
if (!CFG.loader.enable) return;
try {
document.getElementById('ppx-loader-backdrop')?.remove();
document.getElementById('ppx-loader-style')?.remove();
const style = document.createElement('style');
style.id = 'ppx-loader-style';
style.textContent = `
:root {
--ppx-bg: rgba(6, 18, 18, 0.75);
--ppx-card: rgba(255, 255, 255, 0.06);
--ppx-stroke: rgba(255, 255, 255, 0.16);
--ppx-accent: #40E0D0;
--ppx-accent-2: #1ABC9C;
--ppx-text: #eafffb;
--ppx-subtext: #bff3ea;
}
#ppx-loader-backdrop {
position: fixed;
inset: 0;
z-index: 2147483647;
display: grid;
place-items: center;
background:
radial-gradient(1200px 800px at 10% -10%, rgba(64,224,208,0.14), transparent 60%),
radial-gradient(1000px 700px at 110% 110%, rgba(26,188,156,0.12), transparent 60%),
var(--ppx-bg);
backdrop-filter: saturate(120%) blur(5px);
-webkit-backdrop-filter: saturate(120%) blur(5px);
animation: ppx-fade-in 220ms ease-out both;
pointer-events: auto;
}
#ppx-loader-card {
display: grid;
grid-auto-rows: min-content;
gap: 14px;
padding: 24px 28px;
border-radius: 16px;
background: var(--ppx-card);
box-shadow:
0 10px 30px rgba(0,0,0,0.35),
inset 0 0 0 1px var(--ppx-stroke);
transform: translateZ(0);
animation: ppx-pop 220ms cubic-bezier(.2,.9,.2,1) both;
}
#ppx-spinner-wrap { position: relative; width: 64px; height: 64px; margin-inline: auto; }
#ppx-spinner {
box-sizing: border-box; width: 64px; height: 64px; border-radius: 50%;
border: 6px solid rgba(255,255,255,0.18);
border-top-color: var(--ppx-accent);
border-right-color: var(--ppx-accent-2);
animation: ppx-spin 1000ms linear infinite;
}
#ppx-spinner-ring {
position: absolute; inset: -8px; border-radius: 50%;
border: 2px dotted rgba(64,224,208,0.28);
animation: ppx-spin 6s linear infinite reverse;
}
#ppx-title {
color: var(--ppx-text);
font: 700 15px/1.35 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial;
text-align: center; letter-spacing: .2px;
}
#ppx-sub {
color: var(--ppx-subtext);
font: 600 12.5px/1.4 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial;
text-align: center;
}
#ppx-dots { display: inline-grid; grid-auto-flow: column; gap: 4px; margin-left: 4px; vertical-align: baseline; }
.ppx-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--ppx-accent); opacity: .45; animation: ppx-bounce 900ms ease-in-out infinite; }
.ppx-dot:nth-child(2) { animation-delay: 120ms; }
.ppx-dot:nth-child(3) { animation-delay: 240ms; }
@keyframes ppx-spin { to { transform: rotate(360deg); } }
@keyframes ppx-bounce { 0%,100% { transform: translateY(0); opacity: .5; } 50% { transform: translateY(-3px); opacity: 1; } }
@keyframes ppx-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ppx-pop { from { transform: translateY(4px) scale(.98); opacity: 0; } to { transform: translateY(0) scale(1); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { #ppx-spinner, #ppx-spinner-ring, .ppx-dot { animation-duration: 2.5s; } }
`;
const overlay = document.createElement('div');
overlay.id = 'ppx-loader-backdrop';
overlay.setAttribute('aria-hidden', 'true');
const card = document.createElement('div');
card.id = 'ppx-loader-card';
card.setAttribute('role', 'status');
card.setAttribute('aria-live', 'polite');
const spinnerWrap = document.createElement('div');
spinnerWrap.id = 'ppx-spinner-wrap';
const spinner = document.createElement('div');
spinner.id = 'ppx-spinner';
const ring = document.createElement('div');
ring.id = 'ppx-spinner-ring';
const title = document.createElement('div');
title.id = 'ppx-title';
title.textContent = 'Preparing workspace';
const sub = document.createElement('div');
sub.id = 'ppx-sub';
sub.innerHTML = 'Please wait<span id="ppx-dots"><i class="ppx-dot"></i><i class="ppx-dot"></i><i class="ppx-dot"></i></span>';
spinnerWrap.append(spinner, ring);
card.append(spinnerWrap, title, sub);
overlay.append(card);
const prevOverflow = {
html: document.documentElement.style.overflow,
body: document.body.style.overflow,
};
document.documentElement.style.overflow = 'hidden';
document.body.style.overflow = 'hidden';
const prevBusyHtml = document.documentElement.getAttribute('aria-busy');
document.documentElement.setAttribute('aria-busy', 'true');
document.head.appendChild(style);
document.body.appendChild(overlay);
window.__ppxAnimLock = { style, overlay, prevOverflow, prevBusyHtml };
} catch (err) {
log('showLoader error:', err?.message || err);
}
}
function hideLoader() {
if (!CFG.loader.enable) return;
try {
const lock = window.__ppxAnimLock;
if (!lock) return;
const { overlay, style, prevOverflow, prevBusyHtml } = lock;
overlay.style.animation = 'ppx-fade-in 180ms ease-in reverse both';
setTimeout(() => {
try {
overlay?.parentNode?.removeChild(overlay);
style?.parentNode?.removeChild(style);
} catch {}
document.documentElement.style.overflow = prevOverflow?.html ?? '';
document.body.style.overflow = prevOverflow?.body ?? '';
if (prevBusyHtml == null) {
document.documentElement.removeAttribute('aria-busy');
} else {
document.documentElement.setAttribute('aria-busy', prevBusyHtml);
}
delete window.__ppxAnimLock;
}, 180);
} catch (err) {
log('hideLoader error:', err?.message || err);
}
}
// ────── tasks ────────────────────────────────────────────────────────────
async function toggleIncognitoIfNeeded() {
try {
const avatarBtn = await waitForEl('[data-testid="sidebar-popover-trigger-signed-in"], [data-testid="sidebar-popover-trigger"]', { timeout: 5_000 });
// Heuristic: if avatar has an <img> it likely indicates signed-in mode; otherwise Incognito is already active.
if (!avatarBtn.querySelector('img')) {
log('Incognito appears already enabled');
return;
}
await safeClick(avatarBtn);
log('Avatar clicked. Waiting for menu…');
await sleep(CFG.menuPauseMs);
// Find the "Incognito" control by common patterns: explicit text or a role=switch near "Incognito".
let incognitoBtn = null;
try {
incognitoBtn = await findByText('button', 'Incognito', { exact: false, timeout: 3_000 });
} catch {}
if (!incognitoBtn) {
// Fallback: any switch adjacent to a label containing "Incognito"
const buttons = Array.from(document.querySelectorAll('button[role="switch"], [role="menuitemcheckbox"], button'));
const guess = buttons.find((b) => /incognito/i.test(b.textContent || '') || /incognito/i.test(b.getAttribute('aria-label') || ''));
if (guess) incognitoBtn = guess;
}
await safeClick(incognitoBtn);
log('Incognito enabled');
await sleep(CFG.smallPauseMs);
} catch (err) {
log('toggleIncognitoIfNeeded error:', err?.message || err);
}
}
async function enableSocialSources() {
try {
const sourcesBtn = await waitForEl('[data-testid="sources-switcher-button"]', { timeout: 6_000 });
await safeClick(sourcesBtn);
log('Sources button clicked. Waiting for popup…');
await sleep(CFG.menuPauseMs);
const socialToggleContainer = await waitForEl('[data-testid="source-toggle-social"]', { timeout: 6_000 });
const socialSwitch = socialToggleContainer.querySelector('button[role="switch"], [role="switch"]');
const isEnabled =
socialSwitch?.getAttribute('aria-checked') === 'true' ||
socialSwitch?.getAttribute('data-state') === 'checked';
if (!isEnabled) {
await safeClick(socialSwitch);
log('Social sources enabled');
await sleep(CFG.smallPauseMs);
} else {
log('Social sources already enabled');
}
} catch (err) {
log('enableSocialSources error:', err?.message || err);
}
}
async function openModelSwitcher() {
try {
let modelBtn =
document.querySelector('[data-testid="model-switcher-button"]') ||
document.querySelector('button[aria-haspopup="menu"][aria-expanded], .max-w-24');
modelBtn = modelBtn || (await waitForEl('[data-testid="model-switcher-button"]', { timeout: 5_000 }));
await safeClick(modelBtn);
log('Model switcher opened (no selection performed)');
await sleep(CFG.smallPauseMs);
} catch (err) {
log(`openModelSwitcher error: ${err?.message || err}`);
}
}
// ────── bootstrap ────────────────────────────────────────────────────────
(async () => {
try {
await sleep(CFG.initialDelayMs);
showLoader();
await toggleIncognitoIfNeeded();
await enableSocialSources();
await openModelSwitcher();
log('Helper script completed');
} catch (err) {
log('bootstrap error:', err?.message || err);
} finally {
hideLoader();
delete window.__ppxHelperActive;
}
})();
})();