Kemono Comfy View

Full-res images + reader mode + smooth scroll + compact On/Off toggles (R: On/Off, F: On/Off) + keyboard navigation + persistent settings

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name         Kemono Comfy View
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Full-res images + reader mode + smooth scroll + compact On/Off toggles (R: On/Off, F: On/Off) + keyboard navigation + persistent settings
// @author       L1Z4RD
// @match        https://kemono.cr/*/user/*/post/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // Persistent settings
    let readerMode = localStorage.getItem('kc_readerMode') === 'false' ? false : true;
    let fitToScreen = localStorage.getItem('kc_fitToScreen') === 'false' ? false : true;
    let currentIndex = 0;
    let isScrolling = false;

    function getImages() {
        return Array.from(document.querySelectorAll('.post__files img'));
    }

    function upgradeGallery() {
        const links = document.querySelectorAll('a.fileThumb');
        links.forEach(link => {
            const fullUrl = link.href;
            const img = link.querySelector('img');
            if (!img || !fullUrl) return;
            if (img.dataset.fullresApplied) return;
            img.src = fullUrl;
            img.srcset = "";
            img.loading = "eager";
            img.dataset.fullresApplied = "true";
        });
        applyLayout();
    }

    function applyLayout() {
        const container = document.querySelector('.post__files') || document.body;
        const imgs = getImages();
        if (readerMode) {
            container.style.display = "flex";
            container.style.flexDirection = "column";
            container.style.alignItems = "center";
            container.style.gap = "40px";
            imgs.forEach(img => {
                img.style.display = "block";
                if (fitToScreen) {
                    img.style.maxHeight = "100vh";
                    img.style.maxWidth = "100%";
                    img.style.objectFit = "contain";
                } else {
                    img.style.maxHeight = "none";
                    img.style.maxWidth = "100%";
                }
            });
        } else {
            container.style.display = "";
            imgs.forEach(img => {
                img.style.maxHeight = "";
                img.style.maxWidth = "";
            });
        }
    }

    function scrollToImage(index) {
        const imgs = getImages();
        if (index < 0 || index >= imgs.length) return;
        currentIndex = index;
        imgs[index].scrollIntoView({ behavior: "smooth", block: "center" });
    }

    function handleWheel(e) {
        if (!readerMode) return;
        e.preventDefault();
        if (isScrolling) return;
        isScrolling = true;
        if (e.deltaY > 0) scrollToImage(currentIndex + 1);
        else scrollToImage(currentIndex - 1);
        setTimeout(() => { isScrolling = false; }, 400);
    }

    function detectCurrentImage() {
        const imgs = getImages();
        let closestIndex = 0;
        let closestDistance = Infinity;
        imgs.forEach((img, index) => {
            const rect = img.getBoundingClientRect();
            const center = Math.abs(rect.top + rect.height / 2 - window.innerHeight / 2);
            if (center < closestDistance) {
                closestDistance = center;
                closestIndex = index;
            }
        });
        currentIndex = closestIndex;
    }

    function createToggle(labelText, initialState, onChange) {
        const wrapper = document.createElement('label');
        wrapper.className = 'kc-toggle-label';

        const stateText = document.createElement('span');
        stateText.textContent = initialState ? 'On' : 'Off';
        stateText.style.fontWeight = 'bold';

        wrapper.textContent = labelText + ': ';
        wrapper.appendChild(stateText);

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.checked = initialState;
        checkbox.style.display = 'none';

        checkbox.onchange = () => {
            onChange(checkbox.checked);
            stateText.textContent = checkbox.checked ? 'On' : 'Off';
        };

        wrapper.appendChild(checkbox);

        wrapper.onclick = () => {
            checkbox.checked = !checkbox.checked;
            checkbox.onchange();
        };

        return wrapper;
    }

    function createStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .kc-toggle-label {
                cursor: pointer;
                background: rgba(255,255,255,0.15);
                border-radius: 6px;
                padding: 3px 6px;
                color: #fff;
                font-size: 12px;
                user-select: none;
                display: inline-flex;
                justify-content: space-between;
                align-items: center;
                min-width: 50px;
                transition: background 0.2s;
            }
            .kc-toggle-label:hover {
                background: rgba(255,255,255,0.25);
            }
        `;
        document.head.appendChild(style);
    }

    function createUI() {
        createStyles();
        const panel = document.createElement('div');
        panel.style.position = "fixed";
        panel.style.top = "10px";
        panel.style.right = "10px";
        panel.style.zIndex = "9999";
        panel.style.display = "flex";
        panel.style.flexDirection = "row";
        panel.style.gap = "6px";
        panel.style.background = "rgba(0,0,0,0.6)";
        panel.style.padding = "4px";
        panel.style.borderRadius = "6px";

        const readerToggle = createToggle('R', readerMode, val => {
            readerMode = val;
            localStorage.setItem('kc_readerMode', val);
            applyLayout();
        });

        const fitToggle = createToggle('F', fitToScreen, val => {
            fitToScreen = val;
            localStorage.setItem('kc_fitToScreen', val);
            applyLayout();
        });

        panel.appendChild(readerToggle);
        panel.appendChild(fitToggle);
        document.body.appendChild(panel);
    }

    function handleKeyboard(e) {
        if (!readerMode) return;
        switch (e.key) {
            case 'ArrowDown':
            case ' ':
                scrollToImage(currentIndex + 1); e.preventDefault(); break;
            case 'ArrowUp':
                scrollToImage(currentIndex - 1); e.preventDefault(); break;
        }
    }

    window.addEventListener('load', () => {
        upgradeGallery();
        createUI();
        window.addEventListener('wheel', handleWheel, { passive: false });
        window.addEventListener('scroll', detectCurrentImage);
        window.addEventListener('keydown', handleKeyboard);
    });

    const observer = new MutationObserver(() => upgradeGallery());
    observer.observe(document.body, { childList: true, subtree: true });

})();