Greasy Fork is available in English.
Find out exact colour for perfect score.
// ==UserScript==
// @name HSB Value Helper
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Find out exact colour for perfect score.
// @author You
// @match https://dialed.gg/*
// @match https://www.dialed.gg/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const fontLink = document.createElement('link');
fontLink.rel = 'stylesheet';
fontLink.href = 'https://fonts.googleapis.com/css2?family=DM+Sans:opsz,[email protected],400;9..40,600;9..40,800&display=swap';
document.head.appendChild(fontLink);
const ICON_CAMERA = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/>
<circle cx="12" cy="13" r="3"/>
</svg>`;
const ICON_CHECK = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
const ICON_CROSS = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
const style = document.createElement('style');
style.textContent = `
@keyframes hsb-in {
from { opacity: 0; transform: translateY(-6px) scale(0.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes dot-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(1.6); }
}
@keyframes btn-pop {
0% { transform: scale(1); }
40% { transform: scale(0.88); }
100%{ transform: scale(1); }
}
#hsb-toggle {
position: fixed; top: 190px; right: 12px; z-index: 9999999;
display: flex; align-items: center; gap: 5px;
padding: 5px 11px 5px 8px; border-radius: 20px;
font-family: 'DM Sans', sans-serif; font-size: 9px;
font-weight: 800; letter-spacing: 2px; text-transform: uppercase;
cursor: pointer; user-select: none; color: rgba(255,255,255,0.85);
background: rgba(0,0,0,0.45); backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255,255,255,0.15);
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
transition: background 0.2s;
}
#hsb-toggle:hover { background: rgba(0,0,0,0.6); }
#hsb-toggle .dot {
width: 5px; height: 5px; border-radius: 50%;
background: #f9c74f; box-shadow: 0 0 5px #f9c74f;
animation: dot-pulse 2s ease infinite; flex-shrink: 0;
}
#hsb-panel {
position: fixed; top: 228px; right: 12px; z-index: 999999;
width: 168px; font-family: 'DM Sans', sans-serif;
background: rgba(0,0,0,0.55);
backdrop-filter: blur(24px) saturate(180%);
-webkit-backdrop-filter: blur(24px) saturate(180%);
border: 1px solid rgba(255,255,255,0.14); border-radius: 18px;
box-shadow: 0 8px 32px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.08);
overflow: hidden; animation: hsb-in 0.3s ease forwards;
}
#hsb-panel::before {
content: ''; display: block; height: 1px;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
}
#hsb-panel-inner { padding: 11px 13px 13px; }
#hsb-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
#hsb-head-label { font-size: 8px; font-weight: 800; letter-spacing: 2.5px; text-transform: uppercase; color: rgba(255,255,255,0.25); }
#hsb-dot { width: 5px; height: 5px; border-radius: 50%; background: rgba(255,255,255,0.12); transition: background 0.3s, box-shadow 0.3s; }
#hsb-dot.on { background: #00ff88; box-shadow: 0 0 7px #00ff88; }
#hsb-body { display: flex; gap: 11px; align-items: stretch; margin-bottom: 12px; }
#hsb-swatch {
width: 13px; min-height: 72px; border-radius: 7px;
background: rgba(255,255,255,0.07); border: 1px solid rgba(255,255,255,0.1);
flex-shrink: 0; transition: background 0.4s, box-shadow 0.4s;
}
#hsb-rows { flex: 1; display: flex; flex-direction: column; justify-content: space-between; }
.hsb-row {
display: flex; align-items: center; justify-content: space-between;
padding: 3px 0; border-bottom: 1px solid rgba(255,255,255,0.05);
}
.hsb-row:last-child { border-bottom: none; }
.hsb-lbl { font-size: 9px; font-weight: 600; color: rgba(255,255,255,0.25); width: 10px; letter-spacing: 0.5px; }
.hsb-val, .hsb-live {
font-size: 13px; font-weight: 700; letter-spacing: -0.3px;
min-width: 38px; text-align: right;
}
.hsb-live { transition: color 0.1s; }
#hsb-btn-row { display: flex; justify-content: center; margin-top: 2px; }
#hsb-btn {
width: 40px; height: 40px; border-radius: 50%;
border: 1.5px solid rgba(255,255,255,0.15);
background: rgba(255,255,255,0.07); color: rgba(255,255,255,0.75);
cursor: pointer; display: flex; align-items: center; justify-content: center;
transition: background 0.18s, border-color 0.18s, color 0.18s;
box-shadow: 0 2px 8px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.08);
}
#hsb-btn:hover { background: rgba(255,255,255,0.14); border-color: rgba(255,255,255,0.3); color: #fff; }
#hsb-btn:active { animation: btn-pop 0.2s ease; }
#hsb-btn.locked { border-color: rgba(144,190,109,0.55); color: #90be6d; background: rgba(144,190,109,0.12); }
#hsb-btn.error { border-color: rgba(230,57,70,0.55); color: #e63946; background: rgba(230,57,70,0.1); }
`;
document.head.appendChild(style);
// ── Toggle ────────────────────────────────────────────────────────────────
const toggle = document.createElement('button');
toggle.id = 'hsb-toggle';
toggle.innerHTML = `<span class="dot"></span>HSB`;
document.body.appendChild(toggle);
// ── Panel ─────────────────────────────────────────────────────────────────
const panel = document.createElement('div');
panel.id = 'hsb-panel';
panel.innerHTML = `
<div id="hsb-panel-inner">
<div id="hsb-head">
<span id="hsb-head-label">Hint</span>
<span id="hsb-dot"></span>
</div>
<div id="hsb-body">
<div id="hsb-swatch"></div>
<div id="hsb-rows">
<div class="hsb-row">
<span class="hsb-lbl">H</span>
<span id="v-h" class="hsb-val" style="color:#f9c74f">—</span>
<span id="l-h" class="hsb-live" style="color:rgba(255,255,255,0.15)">—</span>
</div>
<div class="hsb-row">
<span class="hsb-lbl">S</span>
<span id="v-s" class="hsb-val" style="color:#90be6d">—</span>
<span id="l-s" class="hsb-live" style="color:rgba(255,255,255,0.15)">—</span>
</div>
<div class="hsb-row">
<span class="hsb-lbl">B</span>
<span id="v-b" class="hsb-val" style="color:#4cc9f0">—</span>
<span id="l-b" class="hsb-live" style="color:rgba(255,255,255,0.15)">—</span>
</div>
</div>
</div>
<div id="hsb-btn-row">
<button id="hsb-btn">${ICON_CAMERA}</button>
</div>
</div>
`;
document.body.appendChild(panel);
// ── Toggle ────────────────────────────────────────────────────────────────
let visible = true;
toggle.addEventListener('click', () => {
visible = !visible;
panel.style.display = visible ? 'block' : 'none';
const dot = toggle.querySelector('.dot');
dot.style.background = visible ? '#f9c74f' : 'rgba(255,255,255,0.2)';
dot.style.boxShadow = visible ? '0 0 5px #f9c74f' : 'none';
dot.style.animation = visible ? '' : 'none';
});
// ── Helpers ───────────────────────────────────────────────────────────────
function hsbToCSS(h, s, b) {
s /= 100; b /= 100;
const k = n => (n + h / 60) % 6;
const f = n => b * (1 - s * Math.max(0, Math.min(k(n), 4 - k(n), 1)));
return `rgb(${Math.round(f(5)*255)},${Math.round(f(3)*255)},${Math.round(f(1)*255)})`;
}
function rgbToHSB(r, g, b) {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r,g,b), min = Math.min(r,g,b), d = max-min;
let h = 0, s = 0, bv = Math.round(max*100);
if (d) {
s = Math.round((d/max)*100);
if (max===r) h = ((g-b)/d)%6;
else if (max===g) h = (b-r)/d+2;
else h = (r-g)/d+4;
h = Math.round(h*60); if (h<0) h+=360;
}
return {h, s, b:bv};
}
function getBgColor() {
const cx = window.innerWidth/2, cy = window.innerHeight/2;
for (const el of document.elementsFromPoint(cx, cy)) {
if (panel.contains(el) || el === toggle) continue;
const bg = window.getComputedStyle(el).backgroundColor;
const m = bg.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (!m) continue;
const r=+m[1], g=+m[2], b=+m[3];
if (r===0 && g===0 && b===0) continue;
return {r, g, b};
}
return null;
}
function getLiveHSB() {
for (const el of document.body.querySelectorAll('*')) {
if (panel.contains(el) || el === toggle) continue;
for (const n of el.childNodes) {
if (n.nodeType !== Node.TEXT_NODE) continue;
const m = n.textContent.match(/H\s*(\d+)\s+S\s*(\d+)\s+B\s*(\d+)/i);
if (m) return {h:+m[1], s:+m[2], b:+m[3]};
}
}
return null;
}
function hueDiff(a, b) { const d = Math.abs(a-b)%360; return d>180?360-d:d; }
// ── Dynamic live colors ───────────────────────────────────────────────────
// Each live value is colored to match what it actually represents:
// H live → pure hue at full S+B (vivid hue color)
// S live → that hue at current saturation, full B (washed vs vivid)
// B live → that hue+sat at current brightness (dark vs bright)
function liveColors(h, s, b) {
const hueColor = hsbToCSS(h, 100, 90); // pure hue — always vivid
const satColor = hsbToCSS(h, s, 90); // same hue, current sat, full bright
const briColor = hsbToCSS(h, Math.max(s, 30), b); // hue+sat, current brightness
return { hueColor, satColor, briColor };
}
// ── Live loop ─────────────────────────────────────────────────────────────
let target = null;
setInterval(() => {
if (!target) return;
const live = getLiveHSB();
if (!live) return;
const { hueColor, satColor, briColor } = liveColors(live.h, live.s, live.b);
const lh = document.getElementById('l-h');
const ls = document.getElementById('l-s');
const lb = document.getElementById('l-b');
lh.textContent = `${live.h}°`; lh.style.color = hueColor;
ls.textContent = `${live.s}%`; ls.style.color = satColor;
lb.textContent = `${live.b}%`; lb.style.color = briColor;
// Status dot — still useful to know when all three are close
const dh = hueDiff(live.h, target.h);
const ds = Math.abs(live.s - target.s);
const db = Math.abs(live.b - target.b);
document.getElementById('hsb-dot').classList.toggle('on', dh/180<0.08 && ds/100<0.08 && db/100<0.08);
}, 150);
// ── Capture ───────────────────────────────────────────────────────────────
document.getElementById('hsb-btn').addEventListener('click', () => {
const btn = document.getElementById('hsb-btn');
const color = getBgColor();
if (!color) {
btn.classList.add('error'); btn.innerHTML = ICON_CROSS;
setTimeout(() => { btn.classList.remove('error'); btn.innerHTML = ICON_CAMERA; }, 1200);
return;
}
const {h, s, b} = rgbToHSB(color.r, color.g, color.b);
target = {h, s, b};
document.getElementById('v-h').textContent = `${h}°`;
document.getElementById('v-s').textContent = `${s}%`;
document.getElementById('v-b').textContent = `${b}%`;
const css = hsbToCSS(h, s, b);
const sw = document.getElementById('hsb-swatch');
sw.style.background = css;
sw.style.boxShadow = `0 0 10px ${css}`;
btn.classList.add('locked'); btn.innerHTML = ICON_CHECK;
setTimeout(() => { btn.classList.remove('locked'); btn.innerHTML = ICON_CAMERA; }, 1500);
});
})();