Skip to content

Instantly share code, notes, and snippets.

@kiranwayne
Last active January 6, 2026 23:35
Show Gist options
  • Select an option

  • Save kiranwayne/99ebb25ba98cc77ee83879c4c18a3ff7 to your computer and use it in GitHub Desktop.

Select an option

Save kiranwayne/99ebb25ba98cc77ee83879c4c18a3ff7 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Freedium Enhanced
// @namespace https://gist.github.com/kiranwayne
// @version 1.1.0
// @description Adds a custom Text width and justification toggle on freedium-mirror.cfd.
// @author kiranwayne (Adapted)
// @match https://freedium-mirror.cfd/*
// @updateURL https://gist.github.com/kiranwayne/99ebb25ba98cc77ee83879c4c18a3ff7/raw/freedium_enhanced.js
// @downloadURL https://gist.github.com/kiranwayne/99ebb25ba98cc77ee83879c4c18a3ff7/raw/freedium_enhanced.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @run-at document-end
// ==/UserScript==
(async () => {
'use strict';
// --- Configuration & Constants ---
const SCRIPT_NAME = 'Freedium Enhanced';
const SCRIPT_VERSION = '1.1.0';
const SCRIPT_AUTHOR = 'kiranwayne';
const CONFIG_PREFIX = 'freediumEnhanced_v1_';
// Settings Keys
const WIDTH_PX_KEY = CONFIG_PREFIX + 'maxWidthPx';
const USE_DEFAULT_WIDTH_KEY = CONFIG_PREFIX + 'useDefaultWidth';
const JUSTIFY_KEY = CONFIG_PREFIX + 'justifyEnabled';
const SETTINGS_UI_VISIBLE_KEY = CONFIG_PREFIX + 'settingsUiVisible';
// DOM & Styles
const STYLE_ID = 'freedium-enhanced-styles';
const SETTINGS_PANEL_ID = 'freedium-userscript-settings-panel';
// Selectors
// Note: \\ is used to escape the colon in the Tailwind class name
const CONTAINER_SELECTOR = '.container.md\\:max-w-3xl';
const TEXT_INNER_SELECTOR = '.container.md\\:max-w-3xl > div';
// Defaults
const DEFAULT_WIDTH_PX = 1000;
const MIN_WIDTH_PX = 600;
const MAX_WIDTH_PX = 2500;
const STEP_WIDTH_PX = 10;
// --- State Variables ---
let config = {};
let settingsPanel = null;
let widthSlider = null, widthLabel = null, widthInput = null, defaultWidthCheckbox = null, justifyCheckbox = null;
let menuCommandId = null;
// --- Helper Functions ---
async function loadSettings() {
config.maxWidthPx = await GM_getValue(WIDTH_PX_KEY, DEFAULT_WIDTH_PX);
config.useDefaultWidth = await GM_getValue(USE_DEFAULT_WIDTH_KEY, false);
config.justifyEnabled = await GM_getValue(JUSTIFY_KEY, false);
config.settingsUiVisible = await GM_getValue(SETTINGS_UI_VISIBLE_KEY, false);
}
async function saveSetting(key, value) {
const configKey = key.replace(CONFIG_PREFIX, '');
config[configKey] = value;
await GM_setValue(key, value);
}
// --- Style Injection ---
function applyStyles() {
let css = '';
// Width Logic
if (!config.useDefaultWidth) {
css += `
${CONTAINER_SELECTOR} {
max-width: ${config.maxWidthPx}px !important;
}
`;
}
// Justification Logic
if (config.justifyEnabled) {
css += `
${CONTAINER_SELECTOR},
${TEXT_INNER_SELECTOR} {
text-align: justify !important;
}
`;
}
let styleEl = document.getElementById(STYLE_ID);
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = STYLE_ID;
document.head.appendChild(styleEl);
}
styleEl.textContent = css;
}
// --- UI Creation/Removal ---
function removeSettingsUI() {
document.removeEventListener('click', handleClickOutside, true);
settingsPanel = document.getElementById(SETTINGS_PANEL_ID);
if (settingsPanel) {
settingsPanel.remove();
settingsPanel = null;
}
}
async function handleClickOutside(event) {
if (settingsPanel && document.body.contains(settingsPanel) && !settingsPanel.contains(event.target)) {
// Prevent closing if clicking the Tampermonkey menu
await saveSetting(SETTINGS_UI_VISIBLE_KEY, false);
removeSettingsUI();
updateTampermonkeyMenu();
}
}
function createSettingsUI() {
if (document.getElementById(SETTINGS_PANEL_ID) || !config.settingsUiVisible) return;
removeSettingsUI();
settingsPanel = document.createElement('div');
settingsPanel.id = SETTINGS_PANEL_ID;
// Moved top to 60px to prevent cutoff
Object.assign(settingsPanel.style, {
position: 'fixed', top: '60px', right: '20px', zIndex: '2147483647',
display: 'block', background: '#202021', color: '#ECECF1',
border: '1px solid #565869', borderRadius: '6px', padding: '15px',
boxShadow: '0 4px 15px rgba(0,0,0,0.6)', minWidth: '300px',
fontFamily: 'sans-serif', fontSize: '14px'
});
// --- Header Section (Title, Version, Author) ---
const headerDiv = document.createElement('div');
headerDiv.style.marginBottom = '15px';
headerDiv.style.paddingBottom = '10px';
headerDiv.style.borderBottom = '1px solid #565869';
const titleEl = document.createElement('h4');
titleEl.textContent = SCRIPT_NAME;
Object.assign(titleEl.style, { margin: '0 0 5px 0', fontSize: '1.1em', fontWeight: 'bold', color: '#FFFFFF' });
const versionEl = document.createElement('div');
versionEl.textContent = `Version: ${SCRIPT_VERSION}`;
Object.assign(versionEl.style, { fontSize: '0.85em', opacity: '0.7', marginBottom: '2px' });
const authorEl = document.createElement('div');
authorEl.textContent = `Author: ${SCRIPT_AUTHOR}`;
Object.assign(authorEl.style, { fontSize: '0.85em', opacity: '0.7' });
headerDiv.appendChild(titleEl);
headerDiv.appendChild(versionEl);
headerDiv.appendChild(authorEl);
settingsPanel.appendChild(headerDiv);
// --- Controls ---
// 1. Default Width Toggle
const defaultWidthDiv = document.createElement('div');
defaultWidthDiv.style.marginBottom = '10px';
defaultWidthCheckbox = document.createElement('input');
defaultWidthCheckbox.type = 'checkbox';
defaultWidthCheckbox.id = 'fe-default-width-toggle';
defaultWidthCheckbox.checked = config.useDefaultWidth;
const defaultWidthLabel = document.createElement('label');
defaultWidthLabel.htmlFor = 'fe-default-width-toggle';
defaultWidthLabel.textContent = ' Use Default Width';
defaultWidthLabel.style.marginLeft = '8px';
defaultWidthLabel.style.cursor = 'pointer';
defaultWidthDiv.appendChild(defaultWidthCheckbox);
defaultWidthDiv.appendChild(defaultWidthLabel);
settingsPanel.appendChild(defaultWidthDiv);
// 2. Width Slider
const widthControlsDiv = document.createElement('div');
widthControlsDiv.style.display = 'flex';
widthControlsDiv.style.alignItems = 'center';
widthControlsDiv.style.gap = '10px';
widthControlsDiv.style.marginBottom = '15px';
widthLabel = document.createElement('span');
widthLabel.style.minWidth = '50px';
widthLabel.style.fontFamily = 'monospace';
widthLabel.textContent = `${config.maxWidthPx}px`;
widthSlider = document.createElement('input');
widthSlider.type = 'range';
widthSlider.min = MIN_WIDTH_PX;
widthSlider.max = MAX_WIDTH_PX;
widthSlider.step = STEP_WIDTH_PX;
widthSlider.value = config.maxWidthPx;
widthSlider.style.flexGrow = '1';
widthSlider.style.cursor = 'pointer';
widthInput = document.createElement('input');
widthInput.type = 'number';
widthInput.min = MIN_WIDTH_PX;
widthInput.max = MAX_WIDTH_PX;
widthInput.value = config.maxWidthPx;
Object.assign(widthInput.style, { width: '70px', padding: '4px', background: '#343541', color: '#fff', border: '1px solid #565869', borderRadius: '4px' });
widthControlsDiv.appendChild(widthLabel);
widthControlsDiv.appendChild(widthSlider);
widthControlsDiv.appendChild(widthInput);
settingsPanel.appendChild(widthControlsDiv);
// 3. Justify Toggle
const justifyDiv = document.createElement('div');
justifyCheckbox = document.createElement('input');
justifyCheckbox.type = 'checkbox';
justifyCheckbox.id = 'fe-justify-toggle';
justifyCheckbox.checked = config.justifyEnabled;
const justifyLabel = document.createElement('label');
justifyLabel.htmlFor = 'fe-justify-toggle';
justifyLabel.textContent = ' Justify Text';
justifyLabel.style.marginLeft = '8px';
justifyLabel.style.cursor = 'pointer';
justifyDiv.appendChild(justifyCheckbox);
justifyDiv.appendChild(justifyLabel);
settingsPanel.appendChild(justifyDiv);
document.body.appendChild(settingsPanel);
// --- Event Listeners ---
const updateUIState = () => {
const isCustom = !defaultWidthCheckbox.checked;
widthSlider.disabled = !isCustom;
widthInput.disabled = !isCustom;
widthSlider.style.opacity = isCustom ? 1 : 0.5;
widthInput.style.opacity = isCustom ? 1 : 0.5;
widthLabel.style.opacity = isCustom ? 1 : 0.5;
};
defaultWidthCheckbox.addEventListener('change', async (e) => {
await saveSetting(USE_DEFAULT_WIDTH_KEY, e.target.checked);
applyStyles();
updateUIState();
});
// Slider input (Live update logic)
// Sliders are hard to use if they don't update immediately, so we keep this live.
widthSlider.addEventListener('input', (e) => {
const val = parseInt(e.target.value, 10);
widthInput.value = val;
widthLabel.textContent = `${val}px`;
config.maxWidthPx = val;
if (!config.useDefaultWidth) applyStyles();
});
// Slider change (Save logic)
widthSlider.addEventListener('change', async (e) => {
await saveSetting(WIDTH_PX_KEY, parseInt(e.target.value, 10));
});
// Text Input: Only update on 'change' (Enter or Blur/Tab out)
// We removed the 'input' event listener here.
widthInput.addEventListener('change', async (e) => {
let val = parseInt(e.target.value, 10);
// Clamp value
if (isNaN(val)) val = DEFAULT_WIDTH_PX;
if (val < MIN_WIDTH_PX) val = MIN_WIDTH_PX;
if (val > MAX_WIDTH_PX) val = MAX_WIDTH_PX;
// Update UI to match clamped value
e.target.value = val;
widthSlider.value = val;
widthLabel.textContent = `${val}px`;
// Apply and Save
config.maxWidthPx = val;
if (!config.useDefaultWidth) applyStyles();
await saveSetting(WIDTH_PX_KEY, val);
});
justifyCheckbox.addEventListener('change', async (e) => {
await saveSetting(JUSTIFY_KEY, e.target.checked);
applyStyles();
});
// Initialize state
updateUIState();
setTimeout(() => document.addEventListener('click', handleClickOutside, true), 100);
}
// --- Tampermonkey Menu ---
function updateTampermonkeyMenu() {
if (menuCommandId) GM_unregisterMenuCommand(menuCommandId);
const label = config.settingsUiVisible ? 'Hide Settings Panel' : 'Show Settings Panel';
menuCommandId = GM_registerMenuCommand(label, async () => {
const newState = !config.settingsUiVisible;
await saveSetting(SETTINGS_UI_VISIBLE_KEY, newState);
if (newState) createSettingsUI();
else removeSettingsUI();
updateTampermonkeyMenu();
});
}
// --- Main ---
async function main() {
await loadSettings();
applyStyles();
if (config.settingsUiVisible) createSettingsUI();
updateTampermonkeyMenu();
}
main();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment