HSB Value Helper

Find out exact colour for perfect score.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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);
  });

})();