Загрузка данных
// ==UserScript==
// @name Pocket Option — симуляция для видео
// @namespace local.pocket-demo-video
// @version 1.0.0
// @description Отдельные постановочные сделки с таймером и явной маркировкой симуляции.
// @match https://pocketoption.com/*/cabinet/demo-quick-high-low/*
// @match https://pocketoption.com/*/cabinet/demo-quick-high-low/
// @grant none
// @noframes
// @run-at document-idle
// ==/UserScript==
(() => {
'use strict';
const HOST_ID = 'po-video-simulation';
const allowed = () => location.hostname === 'pocketoption.com' &&
/^\/[a-z]{2}(?:-[a-z]{2})?\/cabinet\/demo-quick-high-low\/?$/i.test(location.pathname);
if (!allowed() || document.getElementById(HOST_ID)) return;
const host = document.createElement('div');
host.id = HOST_ID;
host.style.cssText = 'all:initial!important;position:fixed!important;left:16px!important;bottom:16px!important;z-index:2147483647!important;display:block!important;';
const root = host.attachShadow({ mode: 'open' });
root.innerHTML = `
<style>
:host { color-scheme:dark; }
* { box-sizing:border-box; }
.panel { width:358px; max-width:calc(100vw - 32px); background:#151d2b;
color:#edf3ff; border:1px solid #3e5068; border-radius:16px;
box-shadow:0 12px 44px #0008; font:13px/1.45 system-ui,sans-serif; overflow:hidden; }
header { padding:15px 16px 12px; background:#1e2b40; }
.top { display:flex; align-items:center; justify-content:space-between; gap:8px; }
strong { font-size:13px; letter-spacing:.05em; }
.badge { color:#ffdc91; font-size:11px; font-weight:800; }
.note { margin-top:5px; color:#c2cee1; font-size:11px; }
.body { padding:12px 16px 16px; }
.fields { display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px; }
label { display:block; font-size:11px; color:#c2cee1; }
input { width:100%; margin-top:4px; padding:8px; background:#0d1522;
color:#fff; border:1px solid #485971; border-radius:7px; font:inherit; }
button { cursor:pointer; font:600 12px system-ui,sans-serif; border:1px solid #485971;
border-radius:8px; background:#26354b; color:#edf3ff; padding:9px 10px; }
button:hover { filter:brightness(1.15); }
button:focus-visible, input:focus-visible, summary:focus-visible { outline:2px solid #96d4ff; outline-offset:2px; }
button:disabled { opacity:.5; cursor:default; }
#toggle { padding:3px 8px; font-size:16px; }
.actions { display:flex; gap:8px; margin-top:10px; }
.actions button { flex:1; }
#up { background:#087144; border-color:#179b65; }
#down { background:#304d7d; border-color:#537fb2; }
#status { min-height:30px; margin-top:10px; color:#c2cee1; font-size:11px; }
#trades { display:grid; gap:8px; max-height:280px; overflow:auto; margin:8px 0; }
.trade { border:1px solid #19784d; background:#102c25; border-radius:10px; padding:10px; }
.trade-top { display:flex; justify-content:space-between; gap:8px; }
.value { color:#5aefa1; font-size:21px; font-weight:750; font-variant-numeric:tabular-nums; }
.meta { color:#bad2c8; font-size:11px; }
.tag { color:#ffd991; font-size:10px; font-weight:800; letter-spacing:.04em; }
progress { width:100%; height:5px; accent-color:#3fe28c; display:block; margin-top:9px; }
details { margin-top:10px; border-top:1px solid #34445c; padding-top:10px; }
summary { cursor:pointer; color:#c2cee1; font-size:12px; }
p { margin:8px 0; font-size:11px; color:#c2cee1; }
.small { font-size:11px; padding:7px; }
#clear { width:100%; margin-top:6px; }
[hidden] { display:none!important; }
.empty { color:#a9b9ce; padding:12px 0; text-align:center; }
@media (prefers-reduced-motion:no-preference) { .trade { animation:appear .18s ease-out; } }
@keyframes appear { from { opacity:0; transform:translateY(5px); } }
</style>
<section class="panel" aria-label="Симуляция сделок для видео">
<header>
<div class="top"><strong>ДЕМО / СИМУЛЯЦИЯ</strong><button id="toggle" aria-expanded="true" aria-label="Свернуть панель">−</button></div>
<div class="note">Постановочные результаты для видео · не данные счёта</div>
</header>
<div class="body" id="body">
<div class="fields">
<label>Сумма, $<input id="amount" type="number" value="100" min="1" max="100000" step="0.01"></label>
<label>Прибыль, %<input id="payout" type="number" value="92" min="1" max="100" step="0.01"></label>
<label>Время, сек.<input id="seconds" type="number" value="10" min="1" max="3600" step="1"></label>
</div>
<div class="actions"><button id="up">↑ Сцена «Выше»</button><button id="down">↓ Сцена «Ниже»</button></div>
<div id="status" role="status" aria-live="polite">Создай сцену или привяжи кнопки сайта ниже.</div>
<div id="trades" aria-label="Постановочные сделки"></div>
<button id="clear" class="small">Очистить сцены</button>
<details>
<summary>Привязать к нажатию кнопок сайта</summary>
<p>Выбери направление, затем нажми соответствующую кнопку сайта. Клик выбора будет перехвачен. Следующие клики выполняют обычное действие сайта и добавляют сцену здесь.</p>
<div class="actions"><button id="bind-up" class="small">Выбрать «Выше»</button><button id="bind-down" class="small">Выбрать «Ниже»</button></div>
<div class="actions"><button id="unbind" class="small">Убрать привязки</button><button id="cancel" class="small">Отмена выбора</button></div>
<p>Сумма, процент и время берутся из этой панели. Привязки действуют до перезагрузки; при замене кнопки сайтом выбери её заново. Esc отменяет выбор.</p>
</details>
</div>
</section>`;
document.body.append(host);
const $ = id => root.getElementById(id);
const money = value => '$' + value.toFixed(2);
const directions = { up: '↑ Выше', down: '↓ Ниже' };
const bindings = new Map();
let selection = null;
let counter = 0;
let trades = [];
const status = message => { $('status').textContent = message; };
function settings() {
const amount = Number($('amount').value);
const payout = Number($('payout').value);
const seconds = Number($('seconds').value);
if (!Number.isFinite(amount) || amount < 1 || amount > 100000 ||
!Number.isFinite(payout) || payout < 1 || payout > 100 ||
!Number.isInteger(seconds) || seconds < 1 || seconds > 3600) {
status('Проверь поля: сумма 1–100 000 $, прибыль 1–100 %, время 1–3600 целых секунд.');
return null;
}
return { amount: Math.round(amount * 100) / 100, payout, seconds };
}
function renderEmpty() {
if (!trades.length) {
const empty = document.createElement('div');
empty.className = 'empty';
empty.textContent = 'Сцен пока нет';
$('trades').replaceChildren(empty);
}
}
function updateTrade(trade, now) {
const remaining = Math.max(0, trade.end - now);
const fraction = Math.min(1, Math.max(0, 1 - remaining / trade.duration));
// This is a scripted positive animation, not a quote or a platform P/L.
const cents = remaining === 0 ? trade.profitCents :
Math.max(1, Math.min(trade.profitCents, Math.round(trade.profitCents * (0.05 + 0.95 * fraction))));
trade.value.textContent = '+' + money(cents / 100);
trade.state.textContent = remaining === 0 ? 'Сцена закрыта в плюс' : `Симуляция · ${Math.ceil(remaining / 1000)} сек.`;
trade.progress.value = fraction;
}
function addTrade(direction) {
if (!allowed()) return;
const config = settings();
if (!config) return;
if (trades.filter(t => t.end > Date.now()).length >= 20) {
status('Уже запущено 20 сцен. Дождись закрытия или очисти список.');
return;
}
if (!trades.length) $('trades').replaceChildren();
const card = document.createElement('article');
card.className = 'trade';
card.innerHTML = '<div class="tag">ДЕМО / СИМУЛЯЦИЯ</div><div class="trade-top"><div><b class="name"></b><div class="meta settings"></div></div><div class="value"></div></div><div class="meta state"></div><progress max="1" aria-label="Время сцены"></progress>';
card.querySelector('.name').textContent = `#${++counter} · ${directions[direction]}`;
card.querySelector('.settings').textContent = `${money(config.amount)} · ${config.payout}%`;
const trade = {
card, end: Date.now() + config.seconds * 1000, duration: config.seconds * 1000,
profitCents: Math.round(config.amount * config.payout),
value: card.querySelector('.value'), state: card.querySelector('.state'),
progress: card.querySelector('progress')
};
trades.unshift(trade);
// Retain every running scene; discard the oldest completed scenes first.
while (trades.length > 20) {
const old = trades.findLastIndex(t => t.end <= Date.now());
if (old < 0) break;
trades.splice(old, 1)[0].card.remove();
}
$('trades').prepend(card);
updateTrade(trade, Date.now());
status(`Сцена #${counter} запущена. Результат задан сценарием.`);
}
function choose(direction) {
selection = direction;
status(`Нажми кнопку «${direction === 'up' ? 'Выше' : 'Ниже'}» на сайте. Esc — отмена.`);
}
function intercept(event) {
if (!allowed() || event.composedPath().includes(host)) return;
if (selection) {
// Block the selection gesture before the site receives it.
event.preventDefault();
event.stopImmediatePropagation();
if (event.type !== 'click') return;
const target = event.target instanceof Element ? event.target : null;
if (!target) return;
let control = target.closest('button, a, [role="button"], input[type="button"], input[type="submit"]');
if (!control) {
control = target;
while (control.parentElement && control.parentElement !== document.body &&
getComputedStyle(control.parentElement).cursor === 'pointer') control = control.parentElement;
}
if (control === document.body || control === document.documentElement || control.contains(host)) {
status('Выбери саму кнопку сайта. Esc — отмена.');
return;
}
for (const [key, value] of bindings) if (value === control) bindings.delete(key);
bindings.set(selection, control);
status(`«${selection === 'up' ? 'Выше' : 'Ниже'}» привязана. Следующие нажатия добавят сцену.`);
selection = null;
return;
}
if (event.type !== 'click') return;
for (const [direction, control] of bindings) {
if (control.isConnected && event.composedPath().includes(control)) {
if (!control.matches(':disabled, [aria-disabled="true"]')) addTrade(direction);
break;
}
}
}
const eventTypes = ['pointerdown', 'pointerup', 'mousedown', 'mouseup', 'touchstart', 'touchend', 'click'];
const eventOptions = { capture: true, passive: false };
for (const type of eventTypes) window.addEventListener(type, intercept, eventOptions);
const keydown = event => {
if (event.key === 'Escape' && selection) { selection = null; status('Выбор отменён.'); }
};
window.addEventListener('keydown', keydown, true);
$('up').onclick = () => addTrade('up');
$('down').onclick = () => addTrade('down');
$('bind-up').onclick = () => choose('up');
$('bind-down').onclick = () => choose('down');
$('cancel').onclick = () => { selection = null; status('Выбор отменён.'); };
$('unbind').onclick = () => { bindings.clear(); selection = null; status('Привязки удалены.'); };
$('clear').onclick = () => { trades = []; $('trades').replaceChildren(); renderEmpty(); status('Сцены очищены.'); };
$('toggle').onclick = () => {
$('body').hidden = !$('body').hidden;
if ($('body').hidden) selection = null;
$('toggle').textContent = $('body').hidden ? '+' : '−';
$('toggle').setAttribute('aria-expanded', String(!$('body').hidden));
$('toggle').setAttribute('aria-label', $('body').hidden ? 'Развернуть панель' : 'Свернуть панель');
};
renderEmpty();
const timer = window.setInterval(() => {
if (!allowed() || !host.isConnected) {
window.clearInterval(timer);
for (const type of eventTypes) window.removeEventListener(type, intercept, eventOptions);
window.removeEventListener('keydown', keydown, true);
host.remove();
return;
}
for (const [direction, control] of bindings) {
if (!control.isConnected) {
bindings.delete(direction);
status('Сайт заменил привязанную кнопку. Выбери её заново.');
}
}
const now = Date.now();
for (const trade of trades) updateTrade(trade, now);
}, 100);
})();