Milovana: Sidebar

Milovana Sidebar

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

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.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Milovana: Sidebar
// @namespace    wompi72
// @author       wompi72
// @version      1.0.8
// @description  Milovana Sidebar
// @match        *://milovana.com/*
// @grant        none
// @license      MIT
// ==/UserScript==


// TODO add sessions history
// {orgasm type, edges, duration (auto start/or manual star)}
// TODO Add navigation for eos tease, download script and make searchable dropdown from pages keys

'use strict';
const STORAGE_PREFIX = 'mv_sidebar_';

const TEASE_TYPES = {
    none: 'none',
    text: 'Text Tease',
    eos: 'Eos Tease'
}

function getPageData() {
    const currentURL = new URL(window.location.href);
    const id = currentURL.searchParams.get('id');

    const page = currentURL.searchParams.get('p');
    const isReload = localStorage.getItem(`${STORAGE_PREFIX}_${id}_lastPage`) !== page;
    localStorage.setItem(`${STORAGE_PREFIX}_${id}_lastPage`, page);

    function getTeaseType() {
        if (!currentURL.pathname.includes('/showtease.php')) return TEASE_TYPES.none;

        return document.querySelector(".eosIframe") ? TEASE_TYPES.eos : TEASE_TYPES.text;
    }

    const type = getTeaseType();

    function getTeaseTitle(type) {
        if (type === TEASE_TYPES.eos) {
            return document.body.dataset.title;
        } else if (type === TEASE_TYPES.text) {
            const titleElement = document.querySelector('#tease_title');
            if (!titleElement) return null;

            const autorElement = titleElement.querySelector('.tease_author');
            if (autorElement) autorElement.remove();

            return titleElement.textContent.trim();
        }
    }

    const title = getTeaseTitle(type)
    return {id, page, type, title, isReload};
}

const pageData = getPageData();

console.log(pageData);

function isEmpty(value) {
    if (Array.isArray(value) && value.length === 0) return true;
    return value === null || value === undefined || value == "";
}
function pop(obj, key) {
    const value = obj[key];
    delete obj[key];
    return value;
}
function formatLocalNumericDateTime(date) {
    return date.toLocaleString(undefined, {
        year: "numeric",
        month: "2-digit",
        day: "2-digit",
        hour: "2-digit",
        minute: "2-digit"
    });
}
function teaseUrl(teaseId) {
    return `https://milovana.com/webteases/showtease.php?id=${teaseId}`;
}

const STORAGE = {
    SIDEBAR_COLLAPSED: `${STORAGE_PREFIX}_collapsed`,
    VOLUME: `${STORAGE_PREFIX}_volume`,
    NOTES: `${STORAGE_PREFIX}_${pageData.id}_notes`,
    STOPWATCH_START_ON_LOAD: `${STORAGE_PREFIX}_${pageData.id}_stopwatch_start_on_load`,
    PAGES_VISITED: `${STORAGE_PREFIX}_${pageData.id}_pages_visited`,
    RANDOM_PAGE_FROM: `${STORAGE_PREFIX}_${pageData.id}_random_page_from`,
    RANDOM_PAGE_TO: `${STORAGE_PREFIX}_${pageData.id}_random_page_to`,
    RANDOM_PAGE_RELATIVE: `${STORAGE_PREFIX}_${pageData.id}_random_page_relative`,
    RNG_FROM: `${STORAGE_PREFIX}_${pageData.id}_rng_from`,
    RNG_TO: `${STORAGE_PREFIX}_${pageData.id}_rng_to`,
    RNG_HISTORY: `${STORAGE_PREFIX}_${pageData.id}_rng_history`,
    METRONOME_BPM: `${STORAGE_PREFIX}_${pageData.id}_metronome_bpm`,
    METRONOME_TARGET_COUNT: `${STORAGE_PREFIX}_${pageData.id}_metronome_target_count`,
    METRONOME_TARGET_COUNT_ACTIVE: `${STORAGE_PREFIX}_${pageData.id}_metronome_target_count_active`,
    METRONOME_TARGET_TIME: `${STORAGE_PREFIX}_${pageData.id}_metronome_target_time`,
    METRONOME_TARGET_TIME_ACTIVE: `${STORAGE_PREFIX}_${pageData.id}_metronome_target_time_active`,
    EDGE_TOTAL: `${STORAGE_PREFIX}_${pageData.id}_edge_total`,
    EDGE_COOLDOWN_ACTIVE: `${STORAGE_PREFIX}_${pageData.id}_edge_cooldown_active`,
    EDGE_COOLDOWN_TIME: `${STORAGE_PREFIX}_${pageData.id}_edge_cooldown_time`,
    EDGE_PAUSE_METRONOME: `${STORAGE_PREFIX}_${pageData.id}_edge_pause_metronome`,
    TIMERS: `${STORAGE_PREFIX}_${pageData.id}_timers`,
    OVERLAY_SIDEBAR: `${STORAGE_PREFIX}_overlay_sidebar`,
    OVERLAY_EOS: `${STORAGE_PREFIX}_overlay_sidebar_eos`,
    CURRENT_SESSION: `${STORAGE_PREFIX}_current_session`,
    PAST_SESSIONS: `${STORAGE_PREFIX}_past_sessions`,
}

class Sidebar {
    sidebar;
    toggleBtn;
    sections = {};

    constructor() {
        this.sidebar = document.createElement('div');
        this.sidebar.id = 'mv-sidebar';
        const collapseText = "<"
        this.sidebar.innerHTML = `
        <div class="mv-sidebar-header-main flex">
            <button id="mv-collapse" class="icon-btn">${collapseText}</button>
            <span class="mv-sidebar-main-title">Milovana Sidebar</span>
        </div>
        `;
        document.body.appendChild(this.sidebar);

        this.toggleBtn = document.createElement('button');
        this.toggleBtn.id = 'mv-sidebar-toggle';
        this.toggleBtn.classList.add('icon-btn');
        this.toggleBtn.textContent = '>';
        document.body.appendChild(this.toggleBtn);


        if (localStorage.getItem(STORAGE.SIDEBAR_COLLAPSED) === 'true') {
            this.collapse();
        } else {
            this.expand();
        }

        this.sidebar.querySelector('#mv-collapse').addEventListener('click', this.collapse.bind(this));
        this.toggleBtn.addEventListener('click', this.expand.bind(this));
    }

    unfoldAll() {
        Object.keys(this.sections).forEach(key => this.unfoldSection(key));
    }

    foldAll() {
        Object.keys(this.sections).forEach(key => this.foldSection(key));
    }

    foldSection(key) {
        const section = this.sections[key];
        if (!section) return;
        const storageKey = `${STORAGE_PREFIX}_${pageData.id}_section_${key}`;

        section.indicator.textContent = '▶ ';
        section.content.style.display = 'none';
        localStorage.setItem(storageKey, 'true');
    }

    unfoldSection(key) {
        const section = this.sections[key];
        if (!section) return;
        const storageKey = `${STORAGE_PREFIX}_${pageData.id}_section_${key}`;

        section.indicator.textContent = '▼ ';
        section.content.style.display = 'flex';
        localStorage.setItem(storageKey, 'false');
    }

    collapse() {
        this.sidebar.classList.add('collapsed');
        document.body.classList.remove('mv-sidebar-expanded');
        this.toggleBtn.style.display = 'block';
        localStorage.setItem(STORAGE.SIDEBAR_COLLAPSED, 'true');
    }

    expand() {
        this.sidebar.classList.remove('collapsed');
        
        const isOverlay = this.getIsOverlayed();
        if (!isOverlay) {
            document.body.classList.add('mv-sidebar-expanded');
            document.body.classList.add('mv-sidebar-dynamic-tease-size');
        } else {
            document.body.classList.remove('mv-sidebar-expanded');
        }

        this.toggleBtn.style.display = 'none';
        localStorage.setItem(STORAGE.SIDEBAR_COLLAPSED, 'false');
    }

    getIsOverlayed() {
        if (pageData.type === TEASE_TYPES.eos) {
            return localStorage.getItem(STORAGE.OVERLAY_EOS) === 'true';
        } else if (pageData.type === TEASE_TYPES.text) {
            return localStorage.getItem(STORAGE.OVERLAY_SIDEBAR) === 'true';
        } else {
            return true;
        }
    }

    addSection(key, label, classes = []) {
        const storageKey = `${STORAGE_PREFIX}_${pageData.id}_section_${key}`;

        const sectionEl = document.createElement('div');
        sectionEl.classList.add('mv-sidebar-section');

        const header = document.createElement('div');
        header.classList.add('mv-sidebar-section-header');
        header.dataset.key = key;

        const indicator = document.createElement('span');
        indicator.classList.add('mv-sidebar-section-indicator');

        const title = document.createElement('span');
        title.textContent = label;

        const content = document.createElement('div');
        content.classList.add('mv-sidebar-section-content');
        content.classList.add(...classes);

        header.appendChild(indicator);
        header.appendChild(title);
        sectionEl.appendChild(header);
        sectionEl.appendChild(content);
        this.sidebar.appendChild(sectionEl);

        this.sections[key] = {
            node: sectionEl,
            header: header,
            indicator: indicator,
            content: content
        };

        let collapsed = localStorage.getItem(storageKey) !== 'false';

        if (collapsed) {
            this.foldSection(key);
        } else {
            this.unfoldSection(key);
        }

        header.addEventListener('click', () => {
            const isCollapsed = localStorage.getItem(storageKey) === 'true';
            if (isCollapsed) {
                this.unfoldSection(key);
            } else {
                this.foldSection(key);
            }
        });
        return content;
    }

    getHeader(key) {
        return this.sections[key]?.header;
    }

    getSectionContent(key) {
        return this.sections[key]?.content;
    }

    addButton(label, callback, parent, classes = []) {
        const btn = document.createElement('button');
        btn.textContent = label;
        btn.classList.add(...classes);
        btn.addEventListener('click', callback);
        parent.appendChild(btn);
        return btn;
    }

    addCheckbox(label, callback, parent) {
        const container = document.createElement('div');
        container.classList.add('flex-row');
        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.addEventListener('change', callback);
        container.appendChild(checkbox);
        const labelEl = document.createElement('label');
        labelEl.textContent = label;
        labelEl.classList.add('auto-margin-height')
        container.appendChild(labelEl);
        parent.appendChild(container);
        return checkbox;
    }

    addDropdown(options, callback, parent, label = null, classes = []) {
        const container = document.createElement('div');
        container.classList.add('flex-row');

        if (label) {
            const labelEl = document.createElement('label');
            labelEl.textContent = label;
            labelEl.classList.add('auto-margin-height');
            container.appendChild(labelEl);
        }

        const select = document.createElement('select');
        classes.forEach(cls => select.classList.add(cls));

        options.forEach(opt => {
            const optionEl = document.createElement('option');

            if (typeof opt === 'object') {
                optionEl.value = opt.value;
                optionEl.textContent = opt.label;
            } else {
                optionEl.value = opt;
                optionEl.textContent = opt;
            }

            select.appendChild(optionEl);
        });

        select.addEventListener('change', callback);

        container.appendChild(select);
        parent.appendChild(container);

        return select;
    }

    addNumberInput(placeholder, parent, classes = [], callback = null) {
        const input = document.createElement('input');
        input.type = 'number';
        input.placeholder = placeholder;
        input.classList.add(...classes);
        if (callback) {
            input.addEventListener('input', callback);
        }
        parent.appendChild(input);
        return input;
    }

    addText(text, parent, classes = []) {
        const el = document.createElement('div');
        el.textContent = text;
        el.classList.add(...classes);
        parent.appendChild(el);
        return el;
    }

    addTextInput(placeholder, parent, classes = []) {
        const input = document.createElement('input');
        input.type = 'text';
        input.placeholder = placeholder;
        input.classList.add(...classes);
        parent.appendChild(input);
        return input;
    }

    addSlider(label, min, max, value, callback, parent, labelWidth = '100px') {
        const container = document.createElement('div');
        container.classList.add('flex-row');
        container.style.width = '100%';

        const labelEl = document.createElement('label');
        labelEl.textContent = `${label}: ${value.toFixed(2)}`;
        labelEl.style.width = "100%";
        labelEl.classList.add('auto-margin-height');

        const slider = document.createElement('input');
        slider.type = 'range';
        slider.min = min;
        slider.max = max;
        slider.step = 0.01;
        slider.value = value;

        slider.addEventListener('input', () => {
            labelEl.textContent = `${label}: ${slider.value}`;
            callback(parseFloat(slider.value));
        });

        container.appendChild(labelEl);
        container.appendChild(slider);
        parent.appendChild(container);
        return slider;
    }

}

const sidebar = new Sidebar();

class Sound {
    audioContext;
    volumeGainNode;
    content;
    constructor() {
        this.storedVolume = Math.max(0, Math.min(1, parseFloat(localStorage.getItem(STORAGE.VOLUME)) || 0.5));
    }
    
    initAudio() {
        if (this.audioContext) return;

        this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
        this.volumeGainNode = this.audioContext.createGain();
        this.volumeGainNode.connect(this.audioContext.destination);

        this.volumeGainNode.gain.setValueAtTime(this.storedVolume, this.audioContext.currentTime);
    }

    addOptions() {
        const settingsContent = sidebar.getSectionContent('settings');
        sidebar.addSlider('Volume', 0, 1, this.storedVolume, (val) => {
            this.storedVolume = val;
            if (this.audioContext) {
                this.volumeGainNode.gain.setValueAtTime(val, this.audioContext.currentTime);
            }
            localStorage.setItem(STORAGE.VOLUME, val);
        }, settingsContent);

        sidebar.addButton('Test', this.playSound.bind(this), settingsContent);
    }

    playSound(frequency = 440, duration = 0.1, waveform = 'sine', localVolume = 1) {
        // Handle cases where frequency is an Event object from a button click
        if (typeof frequency !== 'number' || !isFinite(frequency)) {
            frequency = 440;
        }

        // Ensure other parameters are finite numbers to prevent Web Audio API errors
        duration = (typeof duration === 'number' && isFinite(duration)) ? duration : 0.1;
        localVolume = (typeof localVolume === 'number' && isFinite(localVolume)) ? localVolume : 1;

        this.initAudio();
        
        if (this.audioContext.state === 'suspended') {
            this.audioContext.resume();
        }

        const oscillator = this.audioContext.createOscillator();
        const noteGain = this.audioContext.createGain();

        oscillator.type = waveform;
        oscillator.frequency.setValueAtTime(frequency, this.audioContext.currentTime);

        // Local volume relative to master
        noteGain.gain.setValueAtTime(localVolume, this.audioContext.currentTime);
        noteGain.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + duration);

        oscillator.connect(noteGain);
        noteGain.connect(this.volumeGainNode);

        oscillator.start();
        oscillator.stop(this.audioContext.currentTime + duration);
    }
}

const sound = new Sound();

class Stopwatch {
    content
    currentTime = 0;
    timer = null;
    timerDisplay;
    toggleButton;

    constructor() {
        this.content = sidebar.addSection('stopwatch', 'Stopwatch');
        this.timerDisplay = document.createElement('div');
        this.timerDisplay.classList.add('auto-margin-height');
        const startOnLoadSetting = localStorage.getItem(STORAGE.STOPWATCH_START_ON_LOAD) === 'true';
        this.content.appendChild(this.timerDisplay);
        this.toggleButton = sidebar.addButton('Start', this.toggleTimer.bind(this), this.content);
        this.updateDisplay()
        if (startOnLoadSetting){
            this.startTimer();
        }
        sidebar.addButton('Reset', this.resetTimer.bind(this), this.content);
        this.startOnLoadCheckbox = sidebar.addCheckbox('Start on page load', this.startOnLoad.bind(this), this.content);
        this.startOnLoadCheckbox.checked = startOnLoadSetting;
    }

    startOnLoad() {
        localStorage.setItem(STORAGE.STOPWATCH_START_ON_LOAD, this.startOnLoadCheckbox.checked ? 'true' : 'false');
    }

    toggleTimer() {
        if (this.timer) {
            this.stopTimer();
        } else {
            this.startTimer();
        }
    }

    startTimer() {
        this.updateDisplay();
        this.timer = setInterval(() => {
            this.currentTime += 1;
            this.updateDisplay();
        }, 1000);
        this.toggleButton.textContent = 'Stop';
    }

    stopTimer() {
        clearInterval(this.timer);
        this.timer = null;
        this.toggleButton.textContent = 'Start';
    }

    resetTimer() {
        this.stopTimer();
        this.currentTime = 0;
        this.updateDisplay();
    }


    updateDisplay() {
        this.timerDisplay.textContent = this.formatTime(this.currentTime);
    }

    formatTime(seconds) {
        const hours = Math.floor(seconds / 3600);
        const minutes = Math.floor((seconds % 3600) / 60);
        const secs = seconds % 60;

        return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
    }
}

class Notes {
    constructor() {
        this.content = sidebar.addSection('notes', "Notes")
        this.notesField = document.createElement('textarea');

        this.content.appendChild(this.notesField);
        this.notesField.addEventListener('input', () => {
            localStorage.setItem(STORAGE.NOTES, this.notesField.value);
            this.textAreaAdjust();
        });
        this.notesField.value = localStorage.getItem(STORAGE.NOTES) || '';


        this.textAreaAdjust();
    }

    textAreaAdjust() {
        this.notesField.style.height = this.notesField.scrollHeight + "px";
    }
}

class TextTeasePageNavigation {
    constructor() {
        if (pageData.type !== TEASE_TYPES.text) return;
        this.content = sidebar.addSection('navigation', 'Navigation', ['flex-column']);
        const statsRow = document.createElement('div');
        statsRow.classList.add(...['flex-row', 'text-small', 'flex-space-between', 'full-width']);
        statsRow.style.width = '100%';
        sidebar.addText(`Current Page: ${pageData.page || 1}`, statsRow);
        this.trackPage();

        const stats = JSON.parse(localStorage.getItem(STORAGE.PAGES_VISITED) || '[]');
        const uniquePages = new Set(stats).size;
        const currentRevisits = stats.filter(p => p === pageData.page).length - 1;

        statsRow.appendChild(sidebar.addText(`Pages this session: ${uniquePages}`, statsRow));
        statsRow.appendChild(sidebar.addText(`Revisited this page: ${Math.max(0, currentRevisits)}`, statsRow));
        this.content.appendChild(statsRow);

        const randomRow = document.createElement('div');
        randomRow.classList.add(...['flex-row', 'full-width']);
        this.content.appendChild(randomRow);

        sidebar.addButton('Random', this.goRandom.bind(this), randomRow);
        this.randFrom = sidebar.addNumberInput('From', randomRow, ['small-input'], () => {
            localStorage.setItem(STORAGE.RANDOM_PAGE_FROM, this.randFrom.value);
        });
        this.randTo = sidebar.addNumberInput('To', randomRow, ['small-input'], () => {
            localStorage.setItem(STORAGE.RANDOM_PAGE_TO, this.randTo.value);
        });
        this.randFrom.value = localStorage.getItem(STORAGE.RANDOM_PAGE_FROM) || 1;
        this.randTo.value = localStorage.getItem(STORAGE.RANDOM_PAGE_TO) || 6;

        this.relativeRandom = sidebar.addCheckbox('Relative to current Page', () => {
            localStorage.setItem(STORAGE.RANDOM_PAGE_RELATIVE, this.relativeRandom.checked);
        }, this.content);
        this.relativeRandom.checked = localStorage.getItem(STORAGE.RANDOM_PAGE_RELATIVE) === 'true';

        const gotoRow = document.createElement('div');
        gotoRow.classList.add('flex-row');
        this.content.appendChild(gotoRow);

        sidebar.addButton('Go To', this.goTo.bind(this), gotoRow);
        this.gotoInput = sidebar.addNumberInput('Page #', gotoRow, ['small-input']);
        this.gotoInput.value = pageData.page || 1;
        const resetBtn = sidebar.addButton('Reset Stats', () => {
            localStorage.setItem(STORAGE.PAGES_VISITED, '[]');
            if (pageData.type === TEASE_TYPES.eos) return; // should still display reset data.

            location.reload();
        }, this.content);
        resetBtn.style.width = "5rem";

        this.replacePageWithAnchors(document.querySelector("#tease_content > p.text"));
    }

    trackPage() {
        if (!pageData.page) return;
        const stats = JSON.parse(localStorage.getItem(STORAGE.PAGES_VISITED) || '[]');
        // Only track if it's a new "hit" (reload or navigation)
        stats.push(pageData.page);
        localStorage.setItem(STORAGE.PAGES_VISITED, JSON.stringify(stats));
    }

    goRandom() {
        const from = parseInt(this.randFrom.value);
        const to = parseInt(this.randTo.value);
        let roll = Math.floor(Math.random() * (to - from + 1)) + from;

        if (this.relativeRandom.checked) {
            roll += parseInt(pageData.page || 0);
        }
        this.navigateTo(roll);
    }

    goTo() {
        const target = parseInt(this.gotoInput.value);
        if (!isNaN(target)) this.navigateTo(target);
    }

    navigateTo(pageNum) {
        const url = new URL(window.location.href);
        url.searchParams.set('p', pageNum);
        window.location.href = url.toString();
    }

    replacePageWithAnchors(node) {
        if (!node || !(node instanceof Node)) return;
        const url = new URL(window.location.href);
        node.innerHTML = node.innerHTML.replace(/page (\d+)/gi, (match, pageNumber) => {
            url.searchParams.set('p', pageNumber);
            return `<a href="${url}">${match}</a>`;
        });
    }
}

class EdgeCounter {
    constructor() {
        this.content = sidebar.addSection('edge-counter', 'Edges', ['flex-column']);
        this.cooldownTimer = null;
        this.pageEdges = 0;
        this._restartMetronome = false;

        const statsRow = document.createElement('div');
        statsRow.classList.add('flex-row', 'flex-space-between', 'full-width');
        this.totalDisplay = sidebar.addText('Total: 0', statsRow);
        this.pageDisplay = sidebar.addText('This page: 0', statsRow);
        this.content.appendChild(statsRow);

        const actionRow = document.createElement('div');
        actionRow.classList.add('flex-row');
        sidebar.addButton('Edge!', this.addEdge.bind(this), actionRow);
        sidebar.addButton('Reset Total', this.resetTotal.bind(this), actionRow);
        this.content.appendChild(actionRow);

        const cooldownRow = document.createElement('div');
        cooldownRow.classList.add('flex-row');
        this.cooldownActive = sidebar.addCheckbox('Cooldown (s):', (e) => {
            localStorage.setItem(STORAGE.EDGE_COOLDOWN_ACTIVE, e.target.checked);
        }, cooldownRow);
        this.cooldownInput = sidebar.addNumberInput('Secs', cooldownRow, ['small-input'], () => {
            localStorage.setItem(STORAGE.EDGE_COOLDOWN_TIME, this.cooldownInput.value);
        });
        this.cooldownDisplay = sidebar.addText('', cooldownRow, ['auto-margin-height']);
        this.content.appendChild(cooldownRow);

        this.pauseMetronomeCheckbox = sidebar.addCheckbox('Pause Metronome on Edge', (e) => {
            localStorage.setItem(STORAGE.EDGE_PAUSE_METRONOME, e.target.checked);
        }, this.content);

        this.totalCount = parseInt(localStorage.getItem(STORAGE.EDGE_TOTAL)) || 0;
        this.cooldownInput.value = localStorage.getItem(STORAGE.EDGE_COOLDOWN_TIME) || 30;
        this.cooldownActive.checked = localStorage.getItem(STORAGE.EDGE_COOLDOWN_ACTIVE) !== 'false';
        this.pauseMetronomeCheckbox.checked = localStorage.getItem(STORAGE.EDGE_PAUSE_METRONOME) !== 'false';

        this.updateDisplay();
    }

    addEdge() {
        this.totalCount++;
        this.pageEdges++;
        localStorage.setItem(STORAGE.EDGE_TOTAL, this.totalCount);
        this.updateDisplay();

        try {
            session.count("edge")
        } catch {}

        if (this.cooldownActive.checked) {
            if (this.pauseMetronomeCheckbox.checked && metronome.isRunning) {
                this._restartMetronome = true;
                metronome.stop();
            }
            this.startCooldown();
        }
    }

    startCooldown() {
        let remaining = parseInt(this.cooldownInput.value) || 0;

        this.cooldownDisplay.textContent = ` (${remaining}s)`;

        clearInterval(this.cooldownTimer);
        this.cooldownTimer = setInterval(() => {
            remaining--;
            if (remaining <= 0) {
                this.stopCooldown();
            } else {
                this.cooldownDisplay.textContent = ` (${remaining}s)`;
            }
        }, 1000);
    }

    stopCooldown() {
        clearInterval(this.cooldownTimer);
        this.cooldownTimer = null;
        this.cooldownDisplay.textContent = '';
        sound.playSound(880, 0.5, 'triangle', 1);
        if (this.pauseMetronomeCheckbox.checked && this._restartMetronome) {
            metronome.start();
            this._restartMetronome = false;
        }
    }

    resetTotal() {
        if (confirm("Reset total edge count?")) {
            this.totalCount = 0;
            localStorage.setItem(STORAGE.EDGE_TOTAL, 0);
            this.updateDisplay();
        }
    }

    updateDisplay() {
        this.totalDisplay.textContent = `Total: ${this.totalCount}`;
        this.pageDisplay.textContent = `This page: ${this.pageEdges}`;
    }
}

class Metronome {
    content;
    isRunning = false;
    beatCount = 0;
    totalSeconds = 0;
    
    bpmInterval = null;
    timerInterval = null;
    bpmUpdatePending = false;

    constructor() {
        this.content = sidebar.addSection('metronome', 'Metronome', ['flex-column']);
        
        // Stats Row (Strokes and Duration)
        const statsRow = document.createElement('div');
        statsRow.classList.add('flex-row', 'flex-space-between', 'full-width');
        this.strokeDisplay = sidebar.addText('Strokes: 0', statsRow);
        this.timeDisplay = sidebar.addText('Time: 00:00', statsRow);
        this.content.appendChild(statsRow);

        // Controls Row (Start/Stop/Reset)
        const ctrlRow = document.createElement('div');
        ctrlRow.classList.add('flex-row');
        sidebar.addText('BPM:', ctrlRow, ["auto-margin-height"]);
        this.bpmInput = sidebar.addNumberInput('BPM', ctrlRow, ['small-input']);
        this.bpmInput.value = localStorage.getItem(STORAGE.METRONOME_BPM) || 120;
        this.bpmInput.addEventListener('change', () => {
            this.saveToStorage();
        });
        this.bpmInput.addEventListener('input', () => {
            this.saveToStorage();
        });

        this.toggleBtn = sidebar.addButton('Start', this.toggle.bind(this), ctrlRow);
        sidebar.addButton('Reset', this.reset.bind(this), ctrlRow);
        this.content.appendChild(ctrlRow);

        // BPM Row
        const bpmRow = document.createElement('div');
        bpmRow.classList.add('flex-row');


        sidebar.addButton('-10', () => this.adjustBpm(-10), bpmRow, ["icon-btn"]);
        sidebar.addButton('+10', () => this.adjustBpm(10), bpmRow, ["icon-btn"]);
        sidebar.addButton('1ps', () => this.setBpm(60), bpmRow, ["icon-btn"]);
        sidebar.addButton('2ps', () => this.setBpm(120), bpmRow, ["icon-btn"]);
        sidebar.addButton('3ps', () => this.setBpm(180), bpmRow, ["icon-btn"]);
        sidebar.addButton('4ps', () => this.setBpm(240), bpmRow, ["icon-btn"]);
        this.content.appendChild(bpmRow);

        // Targets Section
        const targetCountRow = document.createElement('div');
        targetCountRow.classList.add('flex-row');
        const targetCountActiveValue = localStorage.getItem(STORAGE.METRONOME_TARGET_COUNT_ACTIVE) === 'true';
        this.targetCountActive = sidebar.addCheckbox('Target Count:', () => {
            localStorage.setItem(STORAGE.METRONOME_TARGET_COUNT_ACTIVE, this.targetCountActive.checked ? 'true' : 'false');
        }, targetCountRow);
        this.targetCountActive.checked = targetCountActiveValue;
        this.targetCountInput = sidebar.addNumberInput('Count', targetCountRow, ['small-input'], () => {
            localStorage.setItem(STORAGE.METRONOME_TARGET_COUNT, this.targetCountInput.value);
        });
        this.targetCountInput.value = localStorage.getItem(STORAGE.METRONOME_TARGET_COUNT) || 100;
        this.content.appendChild(targetCountRow);

        const targetTimeRow = document.createElement('div');
        targetTimeRow.classList.add('flex-row');
        const targetTimeActiveValue = localStorage.getItem(STORAGE.METRONOME_TARGET_TIME_ACTIVE) === 'true';
        this.targetTimeActive = sidebar.addCheckbox('Target Time (s):', () => {
            localStorage.setItem(STORAGE.METRONOME_TARGET_TIME_ACTIVE, this.targetTimeActive.checked ? 'true' : 'false');
        }, targetTimeRow);
        this.targetTimeActive.checked = targetTimeActiveValue;
        this.targetTimeInput = sidebar.addNumberInput('Secs', targetTimeRow, ['small-input'], () => {
            localStorage.setItem(STORAGE.METRONOME_TARGET_TIME, this.targetTimeInput.value);
        });
        this.targetTimeInput.value = localStorage.getItem(STORAGE.METRONOME_TARGET_TIME) || 60;
        this.content.appendChild(targetTimeRow);
    }

    saveToStorage() {
        const val = parseInt(this.bpmInput.value);
        if (val > 0) {
            localStorage.setItem(STORAGE.METRONOME_BPM, val);
            if (this.isRunning) this.bpmUpdatePending = true;
        }
    }

    toggle() {
        if (this.isRunning) {
            this.stop();
        } else {
            this.start();
        }
    }

    start() {
        this.isRunning = true;
        this.bpmUpdatePending = false;
        this.toggleBtn.textContent = 'Stop';
        this.startBeat();

        if (this.timerInterval) clearInterval(this.timerInterval);
        this.timerInterval = setInterval(() => {
            this.totalSeconds++;
            this.updateDisplay();
            this.checkTargets();
        }, 1000);
    }

    stop() {
        this.isRunning = false;
        this.bpmUpdatePending = false;
        this.toggleBtn.textContent = 'Start';
        clearInterval(this.bpmInterval);
        this.bpmInterval = null;
        clearInterval(this.timerInterval);
        this.bpmInterval = null;
    }

    reset() {
        this.stop();
        this.bpmUpdatePending = false;
        this.beatCount = 0;
        this.totalSeconds = 0;
        this.updateDisplay();
    }

    startBeat() {
        if (this.bpmInterval) clearInterval(this.bpmInterval);
        const bpm = parseInt(this.bpmInput.value) || 120;
        const ms = (60 / bpm) * 1000;

        this.bpmInterval = setInterval(() => {
            this.beatCount++;

            try {
                session.count("strokes")
            } catch {}
            this.updateDisplay();
            sound.playSound(440, 0.05, 'sine', 0.5);
            this.checkTargets();

            if (this.bpmUpdatePending) {
                this.bpmUpdatePending = false;
                this.startBeat();
            }
        }, ms);
    }

    adjustBpm(delta) {
        let bpm = parseInt(this.bpmInput.value) || 120;
        bpm = Math.max(1, bpm + delta);
        this.bpmInput.value = bpm;
        this.saveToStorage();
        if (this.isRunning) this.bpmUpdatePending = true;
    }

    setBpm(bpm) {
        this.bpmInput.value = bpm;
        this.saveToStorage();
        if (this.isRunning) this.bpmUpdatePending = true;
    }

    checkTargets() {
        if (this.targetCountActive.checked && this.beatCount >= parseInt(this.targetCountInput.value)) {
            this.onTargetReached("Stroke target reached!");
        }
        if (this.targetTimeActive.checked && this.totalSeconds >= parseInt(this.targetTimeInput.value)) {
            this.onTargetReached("Time target reached!");
        }
    }

    onTargetReached(msg) {
        this.stop();
        sound.playSound(880, 0.5, 'triangle', 1);
    }

    updateDisplay() {
        this.strokeDisplay.textContent = `Strokes: ${this.beatCount}`;
        const mins = Math.floor(this.totalSeconds / 60);
        const secs = this.totalSeconds % 60;
        this.timeDisplay.textContent = `Time: ${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
    }

}

class RNG {
    constructor() {
        this.content = sidebar.addSection('rng', 'RNG');
        this.rngHistory = JSON.parse(localStorage.getItem(STORAGE.RNG_HISTORY) || '[]');
        this.thisPageHisory = []

        const randomRow = document.createElement('div');
        randomRow.classList.add(...['flex-row', 'full-width']);
        this.content.appendChild(randomRow);
        this.randFrom = sidebar.addNumberInput('From', randomRow, ['small-input'], () => {
            localStorage.setItem(STORAGE.RNG_FROM, this.randFrom.value);
        });
        this.randTo = sidebar.addNumberInput('To', randomRow, ['small-input'], () => {
            localStorage.setItem(STORAGE.RNG_TO, this.randTo.value);
        });
        this.randFrom.value = localStorage.getItem(STORAGE.RNG_FROM) || 1;
        this.randTo.value = localStorage.getItem(STORAGE.RNG_TO) || 6;
        sidebar.addButton('Generate', this.generateNumber.bind(this), randomRow);
        this.displayGenerated = sidebar.addText("...", randomRow, ["rng-result"])
        this.hitoryEl = sidebar.addText(`History: ${this.rngHistory.join(',')}`, this.content, ["text-small"]);
    }

    async generateNumber() {
        this.displayGenerated.textContent = "...";
        const max = parseInt(this.randTo.value);
        const min = parseInt(this.randFrom.value);
        const generated = Math.floor(Math.random() * (max - min + 1)) + min;

        this.thisPageHisory.unshift(generated);
        localStorage.setItem(STORAGE.RNG_HISTORY, JSON.stringify([...this.thisPageHisory,...this.rngHistory].slice(0,15)));
        let numberLog = `History: ${this.thisPageHisory.join(',')}`;
        if (this.rngHistory.length > 0) {
            numberLog += ` | ${this.rngHistory.join(',')}`;
        }
        await new Promise(r => setTimeout(r, 200));
        this.hitoryEl.textContent = numberLog;
        this.displayGenerated.textContent = generated;
        return generated;
    }
}

class Settings {
    constructor() {
        this.overlaySidebarStored = localStorage.getItem(STORAGE.OVERLAY_SIDEBAR) === 'true';
        this.overlayEosStored = localStorage.getItem(STORAGE.OVERLAY_EOS) === 'true';
    }
    addSection() {
        this.content = sidebar.addSection('settings', 'Settings');
    }
    addSectionContent() {
        sidebar.addButton('Reset Everything', this.resetEverything.bind(this), this.content);
        sidebar.addButton('Unfold All', this.unfoldAll.bind(this), this.content);
        sidebar.addButton('Fold All', this.foldAll.bind(this), this.content);
        this.overlaySidebar = sidebar.addCheckbox("Overlay Sidebar", () => {
            localStorage.setItem(STORAGE.OVERLAY_SIDEBAR, this.overlaySidebar.checked);
        }, this.content)
        this.overlaySidebar.checked = this.overlaySidebarStored;

        this.overlayEos = sidebar.addCheckbox("Overlay Sidebar EOS", () => {
            localStorage.setItem(STORAGE.OVERLAY_EOS, this.overlayEos.checked);
        }, this.content)
        this.overlayEos.checked = this.overlayEosStored;
    }

    resetEverything() {
        const keysToRemove = Object.keys(localStorage).filter(key => key.startsWith(`${STORAGE_PREFIX}_${pageData.id}`));
        keysToRemove.forEach(key => localStorage.removeItem(key));
        if (pageData.type === TEASE_TYPES.eos) return; // should still display reset data.

        location.reload();
    }
    unfoldAll() {
        sidebar.unfoldAll();
    }
    foldAll() {
        sidebar.foldAll();
        sidebar.unfoldSection('settings');
    }
}

class Timers {
    content;
    timers = [];
    listContainer;
    refreshInterval = null;

    constructor() {
        this.content = sidebar.addSection('timers', 'Timers', ['flex-column']);
        this.header = sidebar.getHeader('timers');

        const inputRow = document.createElement('div');
        inputRow.classList.add('flex-column', 'full-width');
        inputRow.style.gap = '2px';
        inputRow.style.marginBottom = '5px';

        this.labelInput = sidebar.addTextInput('Timer Label', inputRow);

        const timeRow = document.createElement('div');
        timeRow.classList.add('flex-row', 'flex-space-between');
        this.daysInput = sidebar.addNumberInput('Days', timeRow, ['small-input']);
        this.daysInput.placeholder = 'D';
        this.hoursInput = sidebar.addNumberInput('Hours', timeRow, ['small-input']);
        this.hoursInput.placeholder = 'H';
        this.minsInput = sidebar.addNumberInput('Mins', timeRow, ['small-input']);
        this.minsInput.placeholder = 'M';
        inputRow.appendChild(timeRow);

        sidebar.addButton('Add Timer', this.addTimer.bind(this), inputRow);
        this.content.appendChild(inputRow);

        this.listContainer = document.createElement('div');
        this.listContainer.classList.add('flex-column', 'full-width');
        this.content.appendChild(this.listContainer);

        this.loadTimers();
        this.startRefresh();
    }

    addTimer() {
        const label = this.labelInput.value || 'Timer';
        const days = parseInt(this.daysInput.value) || 0;
        const hours = parseInt(this.hoursInput.value) || 0;
        const mins = parseInt(this.minsInput.value) || 0;

        const totalMs = ((days * 24 * 60 * 60) + (hours * 60 * 60) + (mins * 60)) * 1000;
        if (totalMs <= 0) return;

        const endTime = Date.now() + totalMs;
        this.timers.push({ label, endTime, id: Date.now() });
        this.saveTimers();
        this.renderTimers();

        this.labelInput.value = '';
        this.daysInput.value = '';
        this.hoursInput.value = '';
        this.minsInput.value = '';
    }

    loadTimers() {
        this.timers = JSON.parse(localStorage.getItem(STORAGE.TIMERS) || '[]');
        this.renderTimers();
    }

    saveTimers() {
        localStorage.setItem(STORAGE.TIMERS, JSON.stringify(this.timers));
    }

    removeTimer(id) {
        this.timers = this.timers.filter(t => t.id !== id);
        this.saveTimers();
        this.renderTimers();
    }

    renderTimers() {
        this.listContainer.innerHTML = '';
        this.timers.forEach(timer => {
            const row = document.createElement('div');
            row.classList.add('flex-row', 'flex-space-between', 'full-width');
            row.style.borderBottom = '1px solid #eee';
            row.style.padding = '2px 0';

            const info = document.createElement('div');
            info.classList.add('flex-column');

            const label = document.createElement('strong');
            label.textContent = timer.label;
            info.appendChild(label);

            const countdown = document.createElement('span');
            countdown.className = 'timer-display';
            countdown.dataset.endTime = timer.endTime;
            countdown.title = `Ends at: ${new Date(timer.endTime).toLocaleString()}`;
            info.appendChild(countdown);

            row.appendChild(info);

            const delBtn = sidebar.addButton('X', () => this.removeTimer(timer.id), row, ['icon-btn']);
            delBtn.style.minWidth = '1.5rem';
            delBtn.style.height = '1.5rem';

            this.listContainer.appendChild(row);
        });
        this.updateDisplays();
    }

    startRefresh() {
        this.refreshInterval = setInterval(() => this.updateDisplays(), 1000);
    }

    updateDisplays() {
        const now = Date.now();
        let anyExpired = false;
        this.listContainer.querySelectorAll('.timer-display').forEach(el => {
            const endTime = parseInt(el.dataset.endTime);
            const diff = endTime - now;

            if (diff <= 0) {
                el.textContent = 'EXPIRED';
                el.style.color = 'red';
                anyExpired = true;
            } else {
                const d = Math.floor(diff / (1000 * 60 * 60 * 24));
                const h = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
                const m = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
                const s = Math.floor((diff % (1000 * 60)) / 1000);

                let timeStr = '';
                if (d > 0) timeStr += `${d}d `;
                timeStr += `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
                el.textContent = timeStr;
            }
        });
        if (anyExpired) {
            this.header.classList.add('highlight');
        } else {
            this.header.classList.remove('highlight');
        }
    }
}

class Sessions {

    constructor() {
        this.currentSession = JSON.parse(localStorage.getItem(STORAGE.CURRENT_SESSION) || '{"active": false}');
        this.pastSessions = JSON.parse(localStorage.getItem(STORAGE.PAST_SESSIONS) || '[]');
    }

    addSection() {
        this.content = sidebar.addSection("sessions", "Sessions", ["flex-column", "full-width"]);

        this.includeNotes = sidebar.addCheckbox("Include Notes", () => {}, this.content)

        const controlRow = document.createElement('div');
        controlRow.classList.add(...['flex-row']);
        this.content.appendChild(controlRow);

        this.startButton = sidebar.addButton("Start", () => {
            this.currentSession = {active: true, startTime: Date.now(), tease: pageData.title, teaseId: pageData.id};
            this.startButton.innerText = 'ReStart';
            this.updateCurrentSession();
        }, controlRow);

        if (this.isActive()) {
            this.startButton.innerText = 'ReStart';
        }

        this.orgasmType = sidebar.addDropdown(  [
            { value: 'Orgasm', label: 'Orgasm' },
            { value: 'Ruined', label: 'Ruined' },
            { value: 'Denied', label: 'Denied' }
        ], () => {}, controlRow);
        sidebar.addButton("End", () => {
            delete this.currentSession["active"];

            this.currentSession.endTime = Date.now();
            this.currentSession.type = this.orgasmType.value;
            if (this.includeNotes.checked) {
                this.currentSession.notes = localStorage.getItem(STORAGE.NOTES) || ''
            }
            if (this.currentSession.teaseId === undefined) {
                this.currentSession.teaseId = pageData.id;
                this.currentSession.tease = pageData.title;
            }

            this.pastSessions.unshift(this.currentSession);
            localStorage.setItem(STORAGE.PAST_SESSIONS, JSON.stringify(this.pastSessions));
            localStorage.removeItem(STORAGE.CURRENT_SESSION);
            this.currentSession = {active: false};
            this.startButton.innerText = 'Start';
            this.displaySessionData();
        }, controlRow);

        this.display = sidebar.addText("", this.content, ["sessions-display"])
        this.displaySessionData();
    }

    displaySessionData() {
        let displayString = '';
        const now = Date.now();
        const sessionsCopy = JSON.parse(JSON.stringify(this.pastSessions));
        for (const session of sessionsCopy) {
            delete session["active"];
            const endTime = new Date(pop(session, "endTime"));
            const endType = pop(session, "type");
            displayString += `<b>${endType} (${formatLocalNumericDateTime(endTime)})</b></br>`;
            displayString += `${this.formatDuration(endTime, now)} ago</br>`;


            const startTime = pop(session, "startTime");
            if (startTime !== undefined) {
                displayString += `Duration ${this.formatDuration(new Date(startTime), endTime)}</br>`;
            }

            const notes = pop(session, "notes");
            for (const [key, value] of Object.entries(session)) {
                if (key === "teaseId" && !isEmpty(value)) {
                    displayString += `${key}: <a href="${teaseUrl(value)}">${value}</a></br>`;
                } else {
                    displayString += `${key}: ${value}</br>`;
                }
            }

            if (!isEmpty(notes)) {
                displayString += `
                    <details>
                      <summary>Notes</summary>
                      ${notes.replace("\n", "</br>")}
                    </details></br>`;
            }


            displayString += `</br>`;
        }

        this.display.innerHTML = displayString;
    }

    updateCurrentSession() {
        localStorage.setItem(STORAGE.CURRENT_SESSION, JSON.stringify(this.currentSession));
    }

    count(key, value=1) {
        if (!this.currentSession.active) return;

        this.currentSession[key] = (this.currentSession[key] || 0) + value;
        this.updateCurrentSession();
    }

    isActive() {
        return this.currentSession.active;
    }

    formatDuration(start, end) {
        let diffMs = Math.abs(end - start);
        let diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));

        const months = Math.floor(diffDays / 30);
        diffDays %= 30;

        const weeks = Math.floor(diffDays / 7);
        const days = diffDays % 7;

        if (months > 0) {
            return buildResult([
                [months, "month"],
                [diffDays, "day"]
            ]);
        }

        if (weeks > 0) {
            return buildResult([
                [weeks, "week"],
                [days, "day"]
            ]);
        }
        const hours = Math.floor(diffMs / (1000 * 60 * 60));
        const minutes = Math.floor(diffMs / (1000 * 60));
        const seconds = Math.floor(diffMs / 1000);


        function buildResult(units) {
            return units
                .filter(([value]) => value > 0)
                .slice(0, 2)
                .map(([value, label]) =>
                    `${value} ${label}${value !== 1 ? "s" : ""}`
                )
                .join(", ");
        }

        return buildResult([
            [diffDays, "day"],
            [hours % 24, "hour"],
            [minutes % 60, "minute"],
            [seconds % 60, "second"]
        ]);
    }
}

const settings = new Settings();
const session = new Sessions();

new TextTeasePageNavigation();
const metronome = new Metronome();
new EdgeCounter();
new RNG();
new Stopwatch()
const notes = new Notes();
new Timers();
session.addSection();
settings.addSection();
sound.addOptions();
settings.addSectionContent();


window.pageData = pageData;
window.TEASE_TYPES = TEASE_TYPES;
window.sidebar = sidebar;
window.sessions = session;

if (pageData.type !== TEASE_TYPES.none) {

    function disableRedirectOnSpacebar() {
        function isEditable(el) {
            return el && (
                el.tagName === 'INPUT' ||
                el.tagName === 'TEXTAREA' ||
                el.isContentEditable
            );
        }
        const handleKey = function(e) {
            if ((e.code === 'Space' || e.key === ' ' || e.keyCode === 32)) {
                const target = e.target;

                if (isEditable(target)) {
                    // Allow spacebar behavior inside inputs by manually dispatching
                    e.stopImmediatePropagation();
                    e.preventDefault();

                    // Create and dispatch a new event to simulate a space input
                    const evt = new InputEvent("input", {
                        bubbles: true,
                        cancelable: true,
                        inputType: "insertText",
                        data: " ",
                        dataTransfer: null
                    });

                    if (target.setRangeText) {
                        target.setRangeText(" ", target.selectionStart, target.selectionEnd, "end");
                        target.dispatchEvent(evt);
                    } else {
                        // Fallback for contenteditable
                        document.execCommand("insertText", false, " ");
                    }

                } else {
                    e.stopImmediatePropagation();
                    e.preventDefault();
                }
            }
        };

        window.addEventListener('keydown', handleKey, true);
        window.addEventListener('keypress', handleKey, true);
    }
    disableRedirectOnSpacebar();
}

function addCSS() {
    const style = document.createElement('style');
    style.textContent =  `
:root {
    --mv-primary: #00779b;
    --mv-bg: #f2cfcf;
    --mv-section-bg: #eebfb8;
    --mv-header-bg: #6671a3;
    --mv-text: #333333;
    --mv-text-muted: #666666;
    --mv-hover: #f5f5f5;
    --mv-shadow: 0 4px 12px rgba(0,0,0,0.1);
    --mv-btn-bg: #a36666;
    --mv-btn-text: #ffffff;
    --mv-input-bg: #f2cfcf;
    --mv-border: #cccccc;
}    
    
#mv-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    width: 240px;
    height: 100vh;
    background: var(--mv-bg);
    border-right: 1px solid #ccc;
    transform: translateX(0);
    transition: transform 0.2s linear;
    z-index: 9999;
    overflow-y: auto;
    padding: .3rem;
    font-size: 13px;
}
.mv-sidebar-main-title {
    font-size: 1.3em;
    color: #6f1313;
    border-radius: 4px;
    padding: .1rem .5rem;
    font-weight: bold;
}
#mv-sidebar.collapsed {
    transform: translateX(-100%);
}

body.mv-sidebar-expanded {
    margin-left: 240px;
    width: calc(100% - 240px);
}
body.mv-sidebar-dynamic-tease-size #csl {
    width: calc(100% - 25px);
}

#mv-sidebar-toggle {
    position: fixed;
    left: .3rem;
    top: 25px;
    transform: translateY(-50%);
    z-index: 9999;
    display: none;
}

#mv-sidebar button,
#mv-sidebar select,
.icon-btn  {
    min-width: 4rem;
    background-color: var(--mv-btn-bg);
    color: var(--mv-btn-text);
    border: none;
    border-radius: 4px;
    padding: 4px 8px;
    cursor: pointer;
    font-size: 12px;
    transition: opacity 0.2s;
    margin: 2px;
}
#mv-sidebar .icon-btn,
.icon-btn {
    min-width: unset;
    padding: 2px 6px;
}
#mv-sidebar button:hover {
    opacity: 0.9;
}
#mv-sidebar button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
#mv-sidebar button:active {
    transform: translateY(1px);
}
#mv-sidebar input[type="text"],
#mv-sidebar input[type="number"],
#mv-sidebar textarea {
    background-color: var(--mv-input-bg);
    border: 1px solid var(--mv-btn-bg);
    border-radius: 4px;
    color: var(--mv-text);
    margin: 2px;
    accent-color: var(--mv-btn-bg);
}

#mv-sidebar input[type="checkbox"] {
    accent-color: var(--mv-btn-bg);
    cursor: pointer;
    width: 14px;
    height: 14px;
    vertical-align: middle;
    margin: 4px;
}
#mv-sidebar input[type="range"] {
    accent-color: var(--mv-btn-bg);
    cursor: pointer;
}
#mv-sidebar textarea {
    width: 100%;
    min-height: 3rem;
    box-sizing: border-box;
}
.mv-sidebar-section-header {
    background: var(--mv-section-bg);
    padding: .3rem;
    border-radius: 4px;
}
.mv-sidebar-section-header:hover {
    background: var(--mv-bg);;
}
.mv-sidebar-section-content {
    display: flex;
    flex-wrap: wrap;
    padding: .5rem 0;
}
.flex-column {
    display: flex;
    flex-direction: column;
}
.flex-row {
    display: flex;
}
.full-width {
    width: 100%;
}
.flex-space-between {
    justify-content: space-between;
}
.text-small {
    font-size: 0.5rem;
}
.small-input {
    width: 3rem;
}
.auto-margin-height {
    margin: auto 0;
}
.mv-sidebar-section-header.highlight {
    background: #ffcccc;
    animation: pulse-red 2s infinite;
}
@keyframes pulse-red {
    0% { background-color: #ffcccc; }
    50% { background-color: #ff8888; }
    100% { background-color: #ffcccc; }
}
.rng-result {
    margin: auto;
    font-size: 1.2rem;
}
.width-100 {
    width: 100%;
}
#mv-sidebar .sessions-display {
  border: 1px solid var(--mv-btn-bg);
  border-radius: 4px;

  max-height: 150px;
  overflow-y: scroll;
  padding: .1rem .5rem;
}

`;
    document.head.appendChild(style);
}
addCSS();