Kindroid Character Counter

A character counter for message length when messaging Kins via browser

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Kindroid Character Counter
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  A character counter for message length when messaging Kins via browser
// @match        https://kindroid.ai/chat*
// @grant        none
// @author       Valco (Discord @alphawuff)
// @license      GNU GPLv3
// ==/UserScript==

(function () {
    'use strict';

    const MAX_CHARS = 4000;
    const ACCENT = "#8271f2";

    function findElements() {
        const input = document.querySelector("textarea");
        const button = document.querySelector(".chakra-button.css-s31pb8");
        return { input, button };
    }

    function createCounter(input, button) {
        if (document.querySelector(".kindroid-counter")) return;

        const counterWrapper = document.createElement('div');
        counterWrapper.className = "kindroid-counter";
        counterWrapper.style.position = 'fixed';
        counterWrapper.style.zIndex = '9999';
        counterWrapper.style.pointerEvents = 'none';

        const size = 42;
        const strokeWidth = 4;
        const radius = (size / 2) - strokeWidth - 1;
        const circumference = 2 * Math.PI * radius;

        const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        svg.setAttribute("width", size);
        svg.setAttribute("height", size);

        const bgCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
        bgCircle.setAttribute("cx", size / 2);
        bgCircle.setAttribute("cy", size / 2);
        bgCircle.setAttribute("r", radius);
        bgCircle.setAttribute("stroke", "rgba(255,255,255,0.15)");
        bgCircle.setAttribute("stroke-width", strokeWidth);
        bgCircle.setAttribute("fill", "none");

        const progressCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
        progressCircle.setAttribute("cx", size / 2);
        progressCircle.setAttribute("cy", size / 2);
        progressCircle.setAttribute("r", radius);
        progressCircle.setAttribute("stroke", ACCENT);
        progressCircle.setAttribute("stroke-width", strokeWidth);
        progressCircle.setAttribute("fill", "none");
        progressCircle.setAttribute("stroke-dasharray", circumference);
        progressCircle.setAttribute("stroke-dashoffset", circumference);
        progressCircle.style.transition = "all 0.2s ease";
        progressCircle.style.filter = `drop-shadow(0 0 4px ${ACCENT})`;
        progressCircle.setAttribute("transform", `rotate(-90 ${size / 2} ${size / 2})`);

        const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
        text.setAttribute("x", "50%");
        text.setAttribute("y", "54%");
        text.setAttribute("dominant-baseline", "middle");
        text.setAttribute("text-anchor", "middle");
        text.setAttribute("fill", "white");

        text.style.fontFamily = "system-ui, sans-serif";
        text.style.fontWeight = "700";
        text.style.fontSize = "14px";
        text.style.paintOrder = "stroke";


        text.style.textShadow = "0 0 4px rgba(0,0,0,0.8), 0 0 2px rgba(0,0,0,0.6)";

        text.textContent = "0";

        svg.appendChild(bgCircle);
        svg.appendChild(progressCircle);
        svg.appendChild(text);
        counterWrapper.appendChild(svg);
        document.body.appendChild(counterWrapper);

        function positionCounter() {
            if (!button) return;

            const rect = button.getBoundingClientRect();

            counterWrapper.style.left = `${rect.right + 10}px`;
            counterWrapper.style.top = `${rect.top + rect.height / 2 - size / 2}px`;
        }

        function update() {
            const length = input.value.length;
            const progress = Math.min(length / MAX_CHARS, 1);

            progressCircle.setAttribute(
                "stroke-dashoffset",
                circumference * (1 - progress)
            );

            text.textContent = length;

            const digits = length.toString().length;

            let fontSize = 14;

            if (digits >= 4) fontSize = 10;
            else if (digits === 3) fontSize = 12;

            text.style.fontSize = fontSize + "px";

            if (length >= MAX_CHARS) {
                progressCircle.setAttribute("stroke", "#ff4d4f");
            } else if (length > 3000) {
                progressCircle.setAttribute("stroke", "#facc15");
            } else {
                progressCircle.setAttribute("stroke", ACCENT);
            }
        }

        input.addEventListener("input", update);

        window.addEventListener("resize", positionCounter);
        window.addEventListener("scroll", positionCounter);

        const observer = new MutationObserver(() => {
            const { button: newButton } = findElements();
            if (newButton) button = newButton;
            positionCounter();
        });

        observer.observe(document.body, { childList: true, subtree: true });

        positionCounter();
        update();
    }

    function init() {
        const observer = new MutationObserver(() => {
            const { input, button } = findElements();

            if (input && button) {
                createCounter(input, button);
            }
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    init();
})();