Pause Point

Creates a pause screen before you enter websites with breathing exercises and alternative activities.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Pause Point
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Creates a pause screen before you enter websites with breathing exercises and alternative activities.
// @author       KHROTU
// @match        https://www.youtube.com/*
// @match        https://www.reddit.com/*
// @match        https://twitter.com/*
// @match        https://x.com/*
// @match        https://www.instagram.com/*
// @match        https://www.tiktok.com/*
// @grant        none
// @run-at       document-start
// @noframes
// @license      MIT
// ==/UserScript==

(function() {
	'use strict';
	const USER_CONFIG = {
		customizations: [
			// {
			//     url: 'https://youtube.com',
			//     matchType: 'domain',    // 'domain' | 'exact' | 'contains'
			//     altUrls: [
			//         'https://en.wikipedia.org',
			//         'https://www.google.com',
			//         'https://news.ycombinator.com'
			//     ],
			//     altActivities: [
			//         'Read 10 pages of a book',
			//         'Do 20 push-ups',
			//         'Write down 3 things you are grateful for',
			//         'Organize your desk for 5 minutes'
			//     ],
			//     nob: 3,    // number of breaths
			//     customMessage: 'Consider these though',
			//     enabled: true
			// },
			// {
			//     url: 'https://x.com',
			//     matchType: 'domain',
			//     altUrls: [
			//         'https://github.com',
			//         'https://www.duolingo.com',
			//         'https://www.coursera.org'
			//     ],
			//     altActivities: [
			//         'Work on a personal project for 15 minutes',
			//         'Meditate for 5 minutes',
			//         'Review your goals for the week'
			//     ],
			//     nob: 2,
			//     enabled: true
			// }
		],
		defaultSettings: {
			dnob: 3,
			dMessage: 'but do you really need to?'
		}
	};
	const INHALE_MS = 2000;
	const HOLD_IN_MS = 500;
	const EXHALE_MS = 2000;
	const HOLD_OUT_MS = 500;
	const BREATH_CYCLE_MS = INHALE_MS + HOLD_IN_MS + EXHALE_MS + HOLD_OUT_MS;
	const BYPASS_KEY = 'pp_bypass';
	const BYPASS_DURATION_MS = 5 * 60 * 1000;
	function getConfig() {
		const currentUrl = location.href;
		const currentHost = location.hostname.replace(/^www\./, '');
		let match = null;
		for (const c of USER_CONFIG.customizations) {
			if (!c.enabled) continue;
			const testUrl = c.url.replace(/^www\./, '');
			const testHost = new URL(testUrl).hostname.replace(/^www\./, '');
			let matched = false;
			if (c.matchType === 'exact') {
				matched = currentUrl === c.url;
			} else if (c.matchType === 'contains') {
				matched = currentUrl.includes(testUrl);
			} else {
				matched = currentHost === testHost || currentHost.endsWith('.' + testHost);
			}
			if (matched) { match = c; break; }
		}
		const defaults = USER_CONFIG.defaultSettings || {};
		return {
			nob: match?.nob ?? defaults.dnob ?? 3,
			message: match?.customMessage ?? defaults.dMessage ?? 'but do you really need to?',
			altUrls: match?.altUrls ?? [],
			altActivities: match?.altActivities ?? [
				'Take a short walk',
				'Drink a glass of water',
				'Write for five minutes',
				'Stretch your body',
				'Read a book chapter',
				'Tidy your workspace'
			]
		};
	}
	const bypass = sessionStorage.getItem(BYPASS_KEY);
	if (bypass && Date.now() - parseInt(bypass, 10) < BYPASS_DURATION_MS) return;
	const cfg = getConfig();
	const hostname = location.hostname.replace(/^www\./, '');
	const faviconUrl = 'https://www.google.com/s2/favicons?sz=64&domain=' + location.hostname;
	const totalBreathMs = cfg.nob * BREATH_CYCLE_MS;
	document.documentElement.style.setProperty('display', 'none', 'important');
	const takeover = () => {
		if (!document.body) {
			requestAnimationFrame(takeover);
			return;
		}
		window.stop();
		const head = document.head;
		const body = document.body;
		while (head.firstChild) head.removeChild(head.firstChild);
		while (body.firstChild) body.removeChild(body.firstChild);
		head.appendChild(Object.assign(document.createElement('title'), {
			textContent: 'Pause Point'
		}));
		const fontLink = document.createElement('link');
		fontLink.rel = 'stylesheet';
		fontLink.href = 'https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;1,400&family=Inter:wght@300;400;500&display=swap';
		head.appendChild(fontLink);
		const style = document.createElement('style');
		style.textContent = `
			body {
				background: #09090b;
				color: #e4e4e7;
				font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
				margin: 0;
				height: 100vh;
				overflow: hidden;
				display: flex;
				align-items: center;
				justify-content: center;
			}
			.phase {
				position: absolute;
				inset: 0;
				display: flex;
				flex-direction: column;
				align-items: center;
				justify-content: center;
				transition: opacity 0.8s ease;
			}
			.fluid-wrap {
				position: relative;
				width: 420px;
				height: 420px;
				display: flex;
				align-items: center;
				justify-content: center;
			}
			.fluid-blob {
				position: absolute;
				filter: blur(60px);
				will-change: transform, opacity;
			}
			.fluid-blob:nth-child(1) {
				width: 260px;
				height: 260px;
				background: radial-gradient(circle at 30% 30%, rgba(150, 170, 205, 0.38), rgba(120, 140, 180, 0.14));
				border-radius: 60% 40% 55% 45% / 55% 45% 60% 40%;
				animation: blobDrift1 14s ease-in-out infinite alternate;
			}
			.fluid-blob:nth-child(2) {
				width: 320px;
				height: 320px;
				background: radial-gradient(circle at 40% 60%, rgba(180, 160, 200, 0.32), rgba(150, 130, 180, 0.12));
				border-radius: 45% 55% 40% 60% / 50% 60% 45% 55%;
				animation: blobDrift2 18s ease-in-out infinite alternate;
			}
			.fluid-blob:nth-child(3) {
				width: 220px;
				height: 220px;
				background: radial-gradient(circle at 70% 30%, rgba(160, 195, 180, 0.34), rgba(130, 170, 150, 0.13));
				border-radius: 55% 45% 60% 40% / 45% 55% 40% 60%;
				animation: blobDrift3 16s ease-in-out infinite alternate;
			}
			.fluid-blob:nth-child(4) {
				width: 280px;
				height: 280px;
				background: radial-gradient(circle at 50% 50%, rgba(195, 170, 175, 0.28), rgba(170, 145, 150, 0.1));
				border-radius: 50% 50% 45% 55% / 55% 45% 50% 50%;
				animation: blobDrift4 20s ease-in-out infinite alternate;
			}
			.fluid-blob:nth-child(5) {
				width: 200px;
				height: 200px;
				background: radial-gradient(circle at 60% 40%, rgba(165, 185, 205, 0.26), rgba(140, 160, 185, 0.09));
				border-radius: 48% 52% 58% 42% / 42% 48% 52% 58%;
				animation: blobDrift5 12s ease-in-out infinite alternate;
			}
			@keyframes blobDrift1 { 0% { transform: translate(-10%, -10%) rotate(0deg); } 100% { transform: translate(10%, 10%) rotate(20deg); } }
			@keyframes blobDrift2 { 0% { transform: translate(10%, -5%) rotate(0deg); } 100% { transform: translate(-10%, 5%) rotate(-15deg); } }
			@keyframes blobDrift3 { 0% { transform: translate(-5%, 10%) rotate(0deg); } 100% { transform: translate(5%, -10%) rotate(25deg); } }
			@keyframes blobDrift4 { 0% { transform: translate(5%, 5%) rotate(0deg); } 100% { transform: translate(-5%, -5%) rotate(-20deg); } }
			@keyframes blobDrift5 { 0% { transform: translate(0%, -10%) rotate(0deg); } 100% { transform: translate(0%, 10%) rotate(30deg); } }
			.breath-ring {
				position: absolute;
				width: 180px;
				height: 180px;
				border-radius: 50%;
				border: 1.5px solid rgba(220, 225, 235, 0.1);
				animation: ringPulse var(--cycle) ease-in-out infinite;
			}
			.breath-ring:nth-of-type(2) { animation-delay: calc(var(--cycle) * 0.08); width: 220px; height: 220px; }
			.breath-ring:nth-of-type(3) { animation-delay: calc(var(--cycle) * 0.16); width: 260px; height: 260px; }
			.breath-ring:nth-of-type(4) { animation-delay: calc(var(--cycle) * 0.24); width: 300px; height: 300px; }
			@keyframes ringPulse {
				0%, 100% { transform: scale(0.72); opacity: 0.1; }
				40%, 50% { transform: scale(1.18); opacity: 0.28; }
			}
			.core-orb {
				position: absolute;
				width: 140px;
				height: 140px;
				border-radius: 50%;
				background: radial-gradient(circle at 35% 35%, rgba(210, 220, 240, 0.35), rgba(190, 200, 225, 0.12) 55%, transparent 72%);
				animation: orbBreathe var(--cycle) ease-in-out infinite;
				box-shadow: 0 0 80px rgba(180, 195, 230, 0.18), inset 0 0 50px rgba(210, 220, 240, 0.12);
			}
			@keyframes orbBreathe {
				0%, 100% { transform: scale(0.82); opacity: 0.55; }
				40% { transform: scale(1.18); opacity: 1; }
				50% { transform: scale(1.18); opacity: 1; }
				90% { transform: scale(0.82); opacity: 0.55; }
			}
			.breath-label {
				position: absolute;
				font-size: 13px;
				letter-spacing: 4px;
				text-transform: lowercase;
				color: rgba(255, 255, 255, 0.55);
				transition: opacity 0.5s ease;
				pointer-events: none;
				font-weight: 400;
			}
			.breath-counter {
				position: absolute;
				bottom: -52px;
				font-size: 11px;
				letter-spacing: 3px;
				color: rgba(255, 255, 255, 0.3);
				transition: opacity 0.4s ease;
			}
			.prompt-wrap {
				opacity: 0;
				display: none;
				flex-direction: column;
				align-items: center;
				justify-content: center;
				text-align: center;
				padding: 24px;
				max-width: 640px;
				width: 100%;
			}
			.prompt-header {
				display: flex;
				align-items: center;
				gap: 14px;
				flex-wrap: wrap;
				justify-content: center;
				margin-bottom: 8px;
			}
			.prompt-favicon {
				width: 36px;
				height: 36px;
				border-radius: 8px;
				background: rgba(255, 255, 255, 0.05);
				padding: 5px;
				box-sizing: border-box;
			}
			h1 {
				font-family: 'Playfair Display', Georgia, 'Times New Roman', serif;
				font-size: 34px;
				font-weight: 400;
				margin: 0;
				color: #f0f0f0;
				letter-spacing: -0.3px;
				line-height: 1.2;
			}
			.subtext {
				font-family: 'Playfair Display', Georgia, 'Times New Roman', serif;
				font-size: 20px;
				font-style: italic;
				color: #71717a;
				margin: 6px 0 44px;
			}
			.alts-label {
				font-size: 11px;
				text-transform: lowercase;
				letter-spacing: 2px;
				color: #3f3f46;
				margin-bottom: 16px;
			}
			.alts-grid {
				display: grid;
				grid-template-columns: 1fr;
				gap: 10px;
				width: 100%;
				max-width: 460px;
				list-style: none;
				padding: 0;
				margin: 0;
			}
			.alt-item {
				background: rgba(255, 255, 255, 0.03);
				border: 1px solid rgba(255, 255, 255, 0.05);
				border-radius: 12px;
				padding: 14px 18px;
				font-size: 14px;
				color: #a1a1aa;
				text-decoration: none;
				transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
				cursor: default;
				display: flex;
				align-items: center;
				justify-content: space-between;
			}
			.alt-item[href] {
				cursor: pointer;
			}
			.alt-item[href]:hover {
				background: rgba(255, 255, 255, 0.07);
				border-color: rgba(255, 255, 255, 0.12);
				transform: translateY(-1px);
			}
			.alt-arrow {
				opacity: 0;
				transition: opacity 0.2s ease;
				font-size: 15px;
				color: #71717a;
			}
			.alt-item[href]:hover .alt-arrow {
				opacity: 1;
			}
			.continue-btn {
				margin-top: 32px;
				font-size: 12px;
				color: #3f3f46;
				text-decoration: underline;
				text-underline-offset: 3px;
				cursor: pointer;
				background: none;
				border: none;
				padding: 8px;
				transition: color 0.2s ease;
				font-family: inherit;
			}
			.continue-btn:hover {
				color: #71717a;
			}
		`;
		head.appendChild(style);
		const breathPhase = document.createElement('div');
		breathPhase.className = 'phase';
		breathPhase.id = 'pp-breath';
		const fluidWrap = document.createElement('div');
		fluidWrap.className = 'fluid-wrap';
		fluidWrap.style.setProperty('--cycle', BREATH_CYCLE_MS + 'ms');
		for (let i = 0; i < 5; i++) {
			const blob = document.createElement('div');
			blob.className = 'fluid-blob';
			fluidWrap.appendChild(blob);
		}
		for (let i = 0; i < 4; i++) {
			const ring = document.createElement('div');
			ring.className = 'breath-ring';
			fluidWrap.appendChild(ring);
		}
		const core = document.createElement('div');
		core.className = 'core-orb';
		fluidWrap.appendChild(core);
		const breathLabel = document.createElement('div');
		breathLabel.className = 'breath-label';
		breathLabel.textContent = 'breathe in';
		fluidWrap.appendChild(breathLabel);
		const breathCounter = document.createElement('div');
		breathCounter.className = 'breath-counter';
		breathCounter.textContent = '1 / ' + cfg.nob;
		fluidWrap.appendChild(breathCounter);
		breathPhase.appendChild(fluidWrap);
		body.appendChild(breathPhase);
		const promptPhase = document.createElement('div');
		promptPhase.className = 'phase prompt-wrap';
		promptPhase.id = 'pp-prompt';
		const header = document.createElement('div');
		header.className = 'prompt-header';
		const faviconImg = document.createElement('img');
		faviconImg.className = 'prompt-favicon';
		faviconImg.src = faviconUrl;
		faviconImg.alt = '';
		header.appendChild(faviconImg);
		const h1 = document.createElement('h1');
		h1.textContent = 'You are about to visit ' + hostname;
		header.appendChild(h1);
		promptPhase.appendChild(header);
		const sub = document.createElement('p');
		sub.className = 'subtext';
		sub.textContent = cfg.message;
		promptPhase.appendChild(sub);
		if (cfg.altUrls.length > 0 || cfg.altActivities.length > 0) {
			const label = document.createElement('div');
			label.className = 'alts-label';
			label.textContent = 'instead, consider:';
			promptPhase.appendChild(label);
			const grid = document.createElement('ul');
			grid.className = 'alts-grid';
			for (const url of cfg.altUrls) {
				let name;
				try { name = new URL(url).hostname.replace(/^www\./, ''); }
				catch { name = url; }
				const li = document.createElement('li');
				const item = document.createElement('a');
				item.className = 'alt-item';
				item.href = url;
				item.target = '_blank';
				item.rel = 'noopener noreferrer';
				item.appendChild(document.createTextNode(name));
				const arrow = document.createElement('span');
				arrow.className = 'alt-arrow';
				arrow.textContent = '\u2192';
				item.appendChild(arrow);
				li.appendChild(item);
				grid.appendChild(li);
			}
			for (const act of cfg.altActivities) {
				const li = document.createElement('li');
				const item = document.createElement('div');
				item.className = 'alt-item';
				item.appendChild(document.createTextNode(act));
				li.appendChild(item);
				grid.appendChild(li);
			}
			promptPhase.appendChild(grid);
		}
		const continueBtn = document.createElement('button');
		continueBtn.className = 'continue-btn';
		continueBtn.textContent = 'Continue to ' + hostname;
		continueBtn.addEventListener('click', () => {
			sessionStorage.setItem(BYPASS_KEY, Date.now().toString());
			location.reload();
		});
		promptPhase.appendChild(continueBtn);
		body.appendChild(promptPhase);
		document.documentElement.style.display = '';
		let breathIndex = 1;
		const runBreathLabel = () => {
			if (breathIndex > cfg.nob) return;
			breathLabel.style.opacity = '0';
			setTimeout(() => {
				breathLabel.textContent = 'breathe in';
				breathLabel.style.opacity = '1';
			}, 500);
			setTimeout(() => { breathLabel.style.opacity = '0'; }, INHALE_MS);
			setTimeout(() => {
				breathLabel.textContent = 'breathe out';
				breathLabel.style.opacity = '1';
			}, INHALE_MS + HOLD_IN_MS + 400);
			setTimeout(() => {
				breathLabel.style.opacity = '0';
				breathIndex++;
				if (breathIndex <= cfg.nob) {
					breathCounter.textContent = breathIndex + ' / ' + cfg.nob;
				}
			}, INHALE_MS + HOLD_IN_MS + EXHALE_MS);
		};
		runBreathLabel();
		for (let i = 1; i < cfg.nob; i++) {
			setTimeout(runBreathLabel, i * BREATH_CYCLE_MS);
		}
		setTimeout(() => {
			const breath = document.getElementById('pp-breath');
			const prompt = document.getElementById('pp-prompt');
			if (!breath || !prompt) return;
			breath.style.opacity = '0';
			setTimeout(() => {
				breath.style.display = 'none';
				prompt.style.display = 'flex';
				requestAnimationFrame(() => {
					prompt.style.opacity = '1';
				});
			}, 800);
		}, totalBreathMs - 200);
	};
	requestAnimationFrame(takeover);
})();